Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Basic js python example #428

Merged
merged 12 commits into from
Feb 3, 2024
38 changes: 38 additions & 0 deletions examples/basic-js-python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<p align="center"><img src="https://i.imgur.com/X7dSE68.png"></p>

## Usage

### Create an App

```
# with npx
$ npx create-nextron-app my-app --example basic-lang-javascript

# with yarn
$ yarn create nextron-app my-app --example basic-lang-javascript

# with pnpm
$ pnpm dlx create-nextron-app my-app --example basic-lang-javascript
```

### Install Dependencies

```
$ cd my-app

# using yarn or npm
$ yarn (or `npm install`)

# using pnpm
$ pnpm install --shamefully-hoist
```

### Use it

```
# development mode
$ yarn dev (or `npm run dev` or `pnpm run dev`)

# production build
$ yarn build (or `npm run build` or `pnpm run build`)
```
12 changes: 12 additions & 0 deletions examples/basic-js-python/electron-builder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
appId: com.example.nextron
productName: My Nextron App
copyright: Copyright © 2018 Yoshihide Shiono
directories:
output: dist
buildResources: resources
files:
- from: .
filter:
- package.json
- app
publish: null
91 changes: 91 additions & 0 deletions examples/basic-js-python/main/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { app, ipcMain } from 'electron';
import serve from 'electron-serve'
import { createWindow } from './helpers'
const { exec } = require('child_process');
const os = require('os');
const path = require('path');

const isProd = process.env.NODE_ENV === 'production'

function resolveHome(filepath) {
if (filepath.startsWith('~')) {
return path.join(os.homedir(), filepath.slice(1));
}
return filepath;
}

async function startProcess(event, value) {
if(event) {
/*
'parentDir' is used to get this folder -> /Applications/<youApp>.app/Contents/
so that we can run our .sh file which will also launch the Python or Rust script.
So the script folder will be moved into parentDir/ in prod mode.
Note: this will only work if the target mahcine have Python or Rust installed.
*/
let scriptPath
if (isProd) {
const parentDir = path.dirname(path.dirname(path.dirname(__dirname)))
scriptPath = path.join(parentDir, 'scripts/runner.sh');
} else {
scriptPath = path.join(__dirname, '../scripts/runner.sh');
}
// console.log(`DEBUG: scriptPath: ${scriptPath}`)
const cmd = `sh ${scriptPath} ${value}`

exec(cmd, (error, stdout, stderr) => {
Fixed Show fixed Hide fixed
if (error) {
console.error(`ERROR: Error executing post-install script: ${error}`); // will be seen only dev mode, not in prod mode
event.sender.send("log", error.message); // will be seen in both dev and prod mode (in the frontend)
return;
}
event.sender.send("log", "Python script executed successfully");
event.sender.send("message", stdout);
});

// ~/.yourApp.log will be helpfull to log process in production mode
}
}

ipcMain.on('run-sh', async (event, value) => {
console.log("DEBUG: starting process") // for dev mode
event.sender.send("log", "Running...") // for prod mode
await startProcess(event, value);
}
);

if (isProd) {
serve({ directory: 'app' })
} else {
app.setPath('userData', `${app.getPath('userData')} (development)`)
}


;(async () => {
await app.whenReady()

const mainWindow = createWindow('main', {
width: 1000,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})

if (isProd) {
await mainWindow.loadURL('app://./home')
} else {
const port = process.argv[2]
await mainWindow.loadURL(`http://localhost:${port}/home`)
mainWindow.webContents.openDevTools()
}
})()

app.on('window-all-closed', () => {
app.quit()
})

// ipcMain.on('message', async (event, arg) => {
// event.reply('message', `${arg} World!`)
// })


78 changes: 78 additions & 0 deletions examples/basic-js-python/main/helpers/create-window.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { screen, BrowserWindow } from 'electron'
import Store from 'electron-store'

export const createWindow = (windowName, options) => {
const key = 'window-state'
const name = `window-state-${windowName}`
const store = new Store({ name })
const defaultSize = {
width: options.width,
height: options.height,
}
let state = {}

const restore = () => store.get(key, defaultSize)

const getCurrentPosition = () => {
const position = win.getPosition()
const size = win.getSize()
return {
x: position[0],
y: position[1],
width: size[0],
height: size[1],
}
}

const windowWithinBounds = (windowState, bounds) => {
return (
windowState.x >= bounds.x &&
windowState.y >= bounds.y &&
windowState.x + windowState.width <= bounds.x + bounds.width &&
windowState.y + windowState.height <= bounds.y + bounds.height
)
}

const resetToDefaults = () => {
const bounds = screen.getPrimaryDisplay().bounds
return Object.assign({}, defaultSize, {
x: (bounds.width - defaultSize.width) / 2,
y: (bounds.height - defaultSize.height) / 2,
})
}

const ensureVisibleOnSomeDisplay = (windowState) => {
const visible = screen.getAllDisplays().some((display) => {
return windowWithinBounds(windowState, display.bounds)
})
if (!visible) {
// Window is partially or fully not visible now.
// Reset it to safe defaults.
return resetToDefaults()
}
return windowState
}

const saveState = () => {
if (!win.isMinimized() && !win.isMaximized()) {
Object.assign(state, getCurrentPosition())
}
store.set(key, state)
}

state = ensureVisibleOnSomeDisplay(restore())

const win = new BrowserWindow({
...state,
...options,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
...options.webPreferences,
},
})

win.on('close', saveState)

return win
}
1 change: 1 addition & 0 deletions examples/basic-js-python/main/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './create-window'
17 changes: 17 additions & 0 deletions examples/basic-js-python/main/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { contextBridge, ipcRenderer } from 'electron'

const handler = {
send(channel, value) {
ipcRenderer.send(channel, value)
},
on(channel, callback) {
const subscription = (_event, ...args) => callback(...args)
ipcRenderer.on(channel, subscription)

return () => {
ipcRenderer.removeListener(channel, subscription)
}
},
}

contextBridge.exposeInMainWorld('ipc', handler)
34 changes: 34 additions & 0 deletions examples/basic-js-python/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"private": true,
"name": "my-nextron-app",
"description": "My application description",
"version": "1.0.0",
"author": "Yoshihide Shiono <[email protected]>",
"main": "app/background.js",
"scripts": {
"dev": "nextron",
"build": "nextron build",
"postinstall": "electron-builder install-app-deps"
},
"build": {
"extraFiles": [
{
"from": "scripts/",
"to": "scripts/",
"filter": "**/*"
}
]
},
"dependencies": {
"electron-serve": "^1.1.0",
"electron-store": "^8.1.0"
},
"devDependencies": {
"electron": "^26.2.2",
"electron-builder": "^24.6.4",
"next": "^12.3.4",
"nextron": "^8.12.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
10 changes: 10 additions & 0 deletions examples/basic-js-python/renderer/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import('next').NextConfig} */
module.exports = {
trailingSlash: true,
images: {
unoptimized: true,
},
webpack: (config) => {
return config
},
}
63 changes: 63 additions & 0 deletions examples/basic-js-python/renderer/pages/home.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react'
import Head from 'next/head'
import Link from 'next/link'
import Image from 'next/image'

export default function HomePage() {
const [log, setLog] = React.useState('')
const [value, setValue] = React.useState('5')
const [message, setMessage] = React.useState("")

const handleChange = (event) => {
setValue(event.target.value)
setMessage("")
}

React.useEffect(() => {
window.ipc.on('log', (log) => {
setLog(log)
})
window.ipc.on('message', (msg) => {
setMessage(msg)
})
}, [])

return (
<React.Fragment>
<Head>
<title>Home - Nextron (basic-js-python)</title>
</Head>
<div>
<p>
⚡ Electron + Next.js ⚡ -
<Link href="/next">
<a>Go to next page</a>
</Link>
</p>
<Image
src="/images/logo.png"
alt="Logo image"
width="256px"
height="256px"
/>
</div>
<div>

<p>
Calculate the sqaure of your number:
<input type='number' value={value} onChange={handleChange} />
</p>
<button
onClick={() => {
window.ipc.send('run-sh', value)
}}
>
Test running the Python script via IPC
</button>
<p>{log}</p>
<p>the square of {value} is {"-> "} {message}</p>

</div>
</React.Fragment>
)
}
21 changes: 21 additions & 0 deletions examples/basic-js-python/renderer/pages/next.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import Head from 'next/head'
import Link from 'next/link'

export default function NextPage() {
return (
<React.Fragment>
<Head>
<title>Next - Nextron (basic-js-python)</title>
</Head>
<div>
<p>
⚡ Electron + Next.js ⚡ -
<Link href="/home">
<a>Go to home page</a>
</Link>
</p>
</div>
</React.Fragment>
)
}
9 changes: 9 additions & 0 deletions examples/basic-js-python/scripts/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import sys, time

def square_of_number():
# time.sleep(3) # Simulate a long running process :), you can comment this line to have RT
print( int(sys.argv[1]) ** 2 )

if __name__ == "__main__":
square_of_number()

8 changes: 8 additions & 0 deletions examples/basic-js-python/scripts/runner.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

# get the directory of the script
script_dir="$(dirname "$0")"
cd $script_dir || exit 1 # cd to the script directory or exit if failed

# run the app and pass the second argument to the app
echo $(python main.py $1)
Loading