chore: scaffold Electron + Vite + React + TypeScript project
Set up electron-vite build (main/preload/renderer split), TypeScript project references, Vitest config, and a secure preload bridge exposing save/open diagram IPC. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
dcfc0094e4
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules/
|
||||||
|
out/
|
||||||
|
dist/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.vite/
|
||||||
|
coverage/
|
||||||
35
electron.vite.config.ts
Normal file
35
electron.vite.config.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { resolve } from 'path'
|
||||||
|
import { defineConfig } from 'electron-vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
main: {
|
||||||
|
build: {
|
||||||
|
outDir: 'out/main',
|
||||||
|
lib: { entry: resolve(__dirname, 'src/main/index.ts') }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
build: {
|
||||||
|
outDir: 'out/preload',
|
||||||
|
lib: { entry: resolve(__dirname, 'src/preload/index.ts') }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
root: 'src/renderer',
|
||||||
|
build: {
|
||||||
|
outDir: 'out/renderer',
|
||||||
|
rollupOptions: {
|
||||||
|
input: resolve(__dirname, 'src/renderer/index.html')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@renderer': resolve(__dirname, 'src/renderer'),
|
||||||
|
'@diagram': resolve(__dirname, 'src/renderer/diagram'),
|
||||||
|
'@joint/core': resolve(__dirname, 'node_modules/@joint/core/joint.mjs')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [react()]
|
||||||
|
}
|
||||||
|
})
|
||||||
3945
package-lock.json
generated
Normal file
3945
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
package.json
Normal file
36
package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "pipeline-diagram-editor",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Electron diagram editor for pipeline / network diagrams with SVG symbol nodes, typed ports, orthogonal routing and animated mock flow.",
|
||||||
|
"main": "out/main/index.js",
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "electron-vite dev",
|
||||||
|
"build": "electron-vite build",
|
||||||
|
"preview": "electron-vite preview",
|
||||||
|
"start": "electron-vite preview",
|
||||||
|
"typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@joint/core": "^4.0.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@testing-library/dom": "^10.4.0",
|
||||||
|
"@testing-library/react": "^16.1.0",
|
||||||
|
"@types/react": "^18.3.12",
|
||||||
|
"@types/react-dom": "^18.3.1",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"electron": "^33.2.1",
|
||||||
|
"electron-vite": "^2.3.0",
|
||||||
|
"jsdom": "^25.0.1",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^5.4.11",
|
||||||
|
"vitest": "^2.1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/main/index.ts
Normal file
63
src/main/index.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { app, BrowserWindow, ipcMain, dialog } from 'electron'
|
||||||
|
import { readFile, writeFile } from 'node:fs/promises'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||||
|
|
||||||
|
function createWindow(): void {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 1440,
|
||||||
|
height: 900,
|
||||||
|
minWidth: 900,
|
||||||
|
minHeight: 600,
|
||||||
|
title: 'Pipeline Diagram Editor',
|
||||||
|
backgroundColor: '#1e1f26',
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, '../preload/index.js'),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
sandbox: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (process.env['ELECTRON_RENDERER_URL']) {
|
||||||
|
win.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||||
|
} else {
|
||||||
|
win.loadFile(join(__dirname, '../renderer/index.html'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- File dialog IPC (save / load diagram JSON) --------------------------
|
||||||
|
ipcMain.handle('dialog:saveDiagram', async (_e, json: string) => {
|
||||||
|
const { canceled, filePath } = await dialog.showSaveDialog({
|
||||||
|
title: 'Save diagram',
|
||||||
|
defaultPath: 'diagram.pnd.json',
|
||||||
|
filters: [{ name: 'Pipeline Network Diagram', extensions: ['json'] }]
|
||||||
|
})
|
||||||
|
if (canceled || !filePath) return { ok: false as const }
|
||||||
|
await writeFile(filePath, json, 'utf-8')
|
||||||
|
return { ok: true as const, filePath }
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('dialog:openDiagram', async () => {
|
||||||
|
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||||
|
title: 'Open diagram',
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [{ name: 'Pipeline Network Diagram', extensions: ['json'] }]
|
||||||
|
})
|
||||||
|
if (canceled || filePaths.length === 0) return { ok: false as const }
|
||||||
|
const json = await readFile(filePaths[0], 'utf-8')
|
||||||
|
return { ok: true as const, filePath: filePaths[0], json }
|
||||||
|
})
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
createWindow()
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') app.quit()
|
||||||
|
})
|
||||||
13
src/preload/index.ts
Normal file
13
src/preload/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { contextBridge, ipcRenderer } from 'electron'
|
||||||
|
|
||||||
|
export interface DiagramFileApi {
|
||||||
|
save(json: string): Promise<{ ok: boolean; filePath?: string }>
|
||||||
|
open(): Promise<{ ok: boolean; filePath?: string; json?: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const api: DiagramFileApi = {
|
||||||
|
save: (json) => ipcRenderer.invoke('dialog:saveDiagram', json),
|
||||||
|
open: () => ipcRenderer.invoke('dialog:openDiagram')
|
||||||
|
}
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('diagramFile', api)
|
||||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.node.json" },
|
||||||
|
{ "path": "./tsconfig.web.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
13
tsconfig.node.json
Normal file
13
tsconfig.node.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/main/**/*", "src/preload/**/*", "electron.vite.config.ts"]
|
||||||
|
}
|
||||||
24
tsconfig.web.json
Normal file
24
tsconfig.web.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"types": ["vite/client", "node"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@renderer/*": ["src/renderer/*"],
|
||||||
|
"@diagram/*": ["src/renderer/diagram/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/renderer/**/*", "tests/**/*", "vitest.config.ts"]
|
||||||
|
}
|
||||||
20
vitest.config.ts
Normal file
20
vitest.config.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { resolve } from 'path'
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@renderer': resolve(__dirname, 'src/renderer'),
|
||||||
|
'@diagram': resolve(__dirname, 'src/renderer/diagram'),
|
||||||
|
'@joint/core': resolve(__dirname, 'node_modules/@joint/core/joint.mjs')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'],
|
||||||
|
setupFiles: ['tests/setup.ts']
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user