Skip to content

Commit

Permalink
init: electron app with typescript+reactjs
Browse files Browse the repository at this point in the history
  • Loading branch information
hlolli committed Oct 25, 2023
0 parents commit 98accc1
Show file tree
Hide file tree
Showing 31 changed files with 9,473 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
*.log
.next
app
dist
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"trailingComma": "all",
"singleQuote": false,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"printWidth": 100
}
40 changes: 40 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Nextron: Main",
"type": "node",
"request": "attach",
"protocol": "inspector",
"port": 9292,
"skipFiles": ["<node_internals>/**"],
"sourceMapPathOverrides": {
"webpack:///./~/*": "${workspaceFolder}/node_modules/*",
"webpack:///./*": "${workspaceFolder}/*",
"webpack:///*": "*"
}
},
{
"name": "Nextron: Renderer",
"type": "chrome",
"request": "attach",
"port": 5858,
"timeout": 10000,
"urlFilter": "http://localhost:*",
"webRoot": "${workspaceFolder}/app",
"sourceMapPathOverrides": {
"webpack:///./src/*": "${webRoot}/*"
}
}
],
"compounds": [
{
"name": "Nextron: All",
"preLaunchTask": "dev",
"configurations": ["Nextron: Main", "Nextron: Renderer"]
}
]
}
21 changes: 21 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "dev",
"isBackground": true,
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": ""
},
"background": {
"beginsPattern": "started server",
"endsPattern": "Debugger listening on"
}
},
"label": "dev"
}
]
}
38 changes: 38 additions & 0 deletions 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/NZfsD1p.png"></p>

## Usage

### Create an App

```
# with npx
$ npx create-nextron-app my-app --example basic-lang-typescript
# with yarn
$ yarn create nextron-app my-app --example basic-lang-typescript
# with pnpm
$ pnpm dlx create-nextron-app my-app --example basic-lang-typescript
```

### 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 electron-builder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
appId: org.arweave.miner-ui
productName: Arweave Miner UI
copyright: Copyright © 2023 DigitalHistoryAssociation
directories:
output: dist
buildResources: resources
files:
- from: .
filter:
- package.json
- app
publish: null
40 changes: 40 additions & 0 deletions main/background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import path from "path";
import { app, ipcMain } from "electron";
import serve from "electron-serve";
import { createWindow } from "./helpers";

const isProd = process.env.NODE_ENV === "production";

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!`);
});
81 changes: 81 additions & 0 deletions main/helpers/create-window.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { screen, BrowserWindow, BrowserWindowConstructorOptions, Rectangle } from "electron";
import Store from "electron-store";

export const createWindow = (
windowName: string,
options: BrowserWindowConstructorOptions,
): BrowserWindow => {
const key = "window-state";
const name = `window-state-${windowName}`;
const store = new Store<Rectangle>({ 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 main/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./create-window";
19 changes: 19 additions & 0 deletions main/preload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { contextBridge, ipcRenderer, IpcRendererEvent } from "electron";

const handler = {
send(channel: string, value: unknown) {
ipcRenderer.send(channel, value);
},
on(channel: string, callback: (...args: unknown[]) => void) {
const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => callback(...args);
ipcRenderer.on(channel, subscription);

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

contextBridge.exposeInMainWorld("ipc", handler);

export type IpcHandler = typeof handler;
Loading

0 comments on commit 98accc1

Please sign in to comment.