create-athenea-app 0.1.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -5
- package/bin/index.js +30 -23
- package/package.json +5 -3
- package/template/README.md +48 -10
- package/template/electron.vite.config.js +43 -0
- package/template/package.json +29 -78
- package/template/resources/.gitkeep +2 -0
- package/template/src/main/index.js +152 -0
- package/template/src/preload/index.js +11 -0
- package/template/{index.html → src/renderer/index.html} +2 -2
- package/template/src/renderer/public/logo.svg +10 -0
- package/template/src/renderer/src/app.css +22 -0
- package/template/src/{main.jsx → renderer/src/app.jsx} +2 -5
- package/template/src/renderer/src/components/Counter/Counter.jsx +14 -0
- package/template/src/renderer/src/components/Counter/style.css +46 -0
- package/template/src/renderer/src/index.css +23 -0
- package/template/src/renderer/src/main.jsx +5 -0
- package/template/src/{routes → renderer/src/routes}/Home/Home.jsx +3 -2
- package/template/src/renderer/src/routes/Home/style.css +48 -0
- package/template/electron.js +0 -230
- package/template/eslint.config.js +0 -3
- package/template/preload.cjs +0 -9
- package/template/preload.js +0 -1
- package/template/public/logo.svg +0 -4
- package/template/scripts/dev-electron.mjs +0 -71
- package/template/src/components/Counter/Counter.jsx +0 -13
- package/template/src/components/Counter/style.css +0 -9
- package/template/src/index.css +0 -7
- package/template/src/routes/Home/style.css +0 -13
- package/template/tsconfig.json +0 -18
- package/template/vite.config.js +0 -25
package/README.md
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
# create-athenea-app
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI para generar una app de escritorio lista para laburar con **Electron + Preact + Vite**.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Usa [electron-vite](https://electron-vite.org/) para un flujo de desarrollo integrado:
|
|
6
|
+
|
|
7
|
+
- **HMR real** para el renderer (Preact)
|
|
8
|
+
- **Hot reload** para main y preload
|
|
9
|
+
- **Build unificado** de main, preload y renderer
|
|
10
|
+
- **electron-builder** listo para empaquetar
|
|
11
|
+
|
|
12
|
+
## Uso
|
|
6
13
|
|
|
7
14
|
```bash
|
|
8
15
|
npm create athenea-app@latest
|
|
9
16
|
```
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
O con nombre directo:
|
|
12
19
|
|
|
13
20
|
```bash
|
|
14
21
|
npm create athenea-app@latest mi-proyecto
|
|
15
22
|
```
|
|
16
23
|
|
|
17
|
-
Opciones
|
|
24
|
+
## Opciones
|
|
25
|
+
|
|
26
|
+
- `--no-install`: no ejecuta `npm install` automaticamente
|
|
27
|
+
- `--help`: muestra la ayuda
|
|
28
|
+
|
|
29
|
+
## Stack incluido
|
|
18
30
|
|
|
19
|
-
-
|
|
31
|
+
- **Electron** - App de escritorio multiplataforma
|
|
32
|
+
- **Preact** - UI rapida y liviana (3kB)
|
|
33
|
+
- **preact-iso** - Routing simple
|
|
34
|
+
- **Vite** - Dev server ultra rapido + build optimizado
|
|
35
|
+
- **electron-vite** - Integra Vite con Electron (main + preload + renderer)
|
|
36
|
+
- **electron-builder** - Empaquetado para Windows, macOS, Linux
|
package/bin/index.js
CHANGED
|
@@ -189,34 +189,26 @@ function bmpSolid24(width, height, r, g, b) {
|
|
|
189
189
|
return Buffer.concat([fileHeader, dib, pixels]);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
async function
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
await fsp.mkdir(
|
|
196
|
-
await fsp.mkdir(publicDir, { recursive: true });
|
|
192
|
+
async function ensureBuildAssets(projectDir) {
|
|
193
|
+
// electron-vite usa "resources" como directorio de assets para main/preload
|
|
194
|
+
const resourcesDir = path.join(projectDir, "resources");
|
|
195
|
+
await fsp.mkdir(resourcesDir, { recursive: true });
|
|
197
196
|
|
|
198
197
|
const png256 = pngSolidRGBA(256, 256, 255, 255, 255, 255);
|
|
199
198
|
const ico = icoFromPng(png256, 256);
|
|
200
|
-
|
|
199
|
+
|
|
200
|
+
// PNG para Linux, ICO para Windows
|
|
201
|
+
await fsp.writeFile(path.join(resourcesDir, "icon.png"), png256);
|
|
202
|
+
await fsp.writeFile(path.join(resourcesDir, "icon.ico"), ico);
|
|
201
203
|
|
|
202
204
|
await fsp.writeFile(
|
|
203
|
-
path.join(
|
|
205
|
+
path.join(resourcesDir, "installer-sidebar.bmp"),
|
|
204
206
|
bmpSolid24(164, 314, 255, 255, 255),
|
|
205
207
|
);
|
|
206
208
|
await fsp.writeFile(
|
|
207
|
-
path.join(
|
|
209
|
+
path.join(resourcesDir, "installer-header.bmp"),
|
|
208
210
|
bmpSolid24(150, 57, 255, 255, 255),
|
|
209
211
|
);
|
|
210
|
-
|
|
211
|
-
const viteSvgPath = path.join(publicDir, "vite.svg");
|
|
212
|
-
if (!fs.existsSync(viteSvgPath)) {
|
|
213
|
-
const svg =
|
|
214
|
-
'<?xml version="1.0" encoding="UTF-8"?>\n' +
|
|
215
|
-
'<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">\n' +
|
|
216
|
-
' <rect width="64" height="64" fill="#ffffff"/>\n' +
|
|
217
|
-
"</svg>\n";
|
|
218
|
-
await fsp.writeFile(viteSvgPath, svg, "utf8");
|
|
219
|
-
}
|
|
220
212
|
}
|
|
221
213
|
|
|
222
214
|
async function npmInstall(projectDir) {
|
|
@@ -282,24 +274,39 @@ async function main() {
|
|
|
282
274
|
__APP_APP_ID__: appId,
|
|
283
275
|
};
|
|
284
276
|
|
|
277
|
+
// Reemplazar tokens en los archivos correspondientes (nueva estructura electron-vite)
|
|
285
278
|
await Promise.all([
|
|
286
|
-
replaceTokensInFile(
|
|
287
|
-
|
|
279
|
+
replaceTokensInFile(
|
|
280
|
+
path.join(targetDir, "src", "main", "index.js"),
|
|
281
|
+
tokenMap,
|
|
282
|
+
),
|
|
283
|
+
replaceTokensInFile(
|
|
284
|
+
path.join(targetDir, "src", "renderer", "index.html"),
|
|
285
|
+
tokenMap,
|
|
286
|
+
),
|
|
288
287
|
replaceTokensInFile(path.join(targetDir, "README.md"), tokenMap),
|
|
289
288
|
replaceTokensInFile(
|
|
290
|
-
path.join(
|
|
289
|
+
path.join(
|
|
290
|
+
targetDir,
|
|
291
|
+
"src",
|
|
292
|
+
"renderer",
|
|
293
|
+
"src",
|
|
294
|
+
"routes",
|
|
295
|
+
"Home",
|
|
296
|
+
"Home.jsx",
|
|
297
|
+
),
|
|
291
298
|
tokenMap,
|
|
292
299
|
),
|
|
293
300
|
]);
|
|
294
301
|
|
|
295
|
-
await
|
|
302
|
+
await ensureBuildAssets(targetDir);
|
|
296
303
|
|
|
297
304
|
console.log("");
|
|
298
305
|
console.log(`Proyecto creado en: ${targetDir}`);
|
|
299
306
|
console.log("");
|
|
300
307
|
console.log("Siguientes pasos:");
|
|
301
308
|
console.log(` cd ${npmName}`);
|
|
302
|
-
console.log(" npm run dev
|
|
309
|
+
console.log(" npm run dev");
|
|
303
310
|
|
|
304
311
|
if (!noInstall) {
|
|
305
312
|
console.log("");
|
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-athenea-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "CLI para scaffoldear apps de escritorio con Electron + Preact + Vite (DX rapida: dev server, hot reload, build y empaquetado)",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"create",
|
|
8
8
|
"starter",
|
|
9
9
|
"template",
|
|
10
10
|
"electron",
|
|
11
|
+
"electron-vite",
|
|
11
12
|
"preact",
|
|
12
|
-
"vite"
|
|
13
|
+
"vite",
|
|
14
|
+
"desktop"
|
|
13
15
|
],
|
|
14
16
|
"bin": {
|
|
15
17
|
"create-athenea-app": "bin/index.js"
|
package/template/README.md
CHANGED
|
@@ -1,19 +1,57 @@
|
|
|
1
1
|
# **APP_TITLE**
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Aplicación de escritorio creada con [Athenea](https://github.com/tu-usuario/athenea-desktop).
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- **Electron** - Framework para apps de escritorio
|
|
8
|
+
- **electron-vite** - Build tool optimizado para Electron
|
|
9
|
+
- **Preact** - UI library liviana (3KB)
|
|
10
|
+
- **preact-iso** - Router minimalista
|
|
11
|
+
|
|
12
|
+
## Estructura
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
src/
|
|
16
|
+
├── main/ # Proceso principal de Electron
|
|
17
|
+
│ └── index.js
|
|
18
|
+
├── preload/ # Scripts de preload (bridge seguro)
|
|
19
|
+
│ └── index.js
|
|
20
|
+
└── renderer/ # Aplicación frontend
|
|
21
|
+
├── index.html
|
|
22
|
+
├── public/ # Assets estáticos
|
|
23
|
+
└── src/ # Código fuente React/Preact
|
|
24
|
+
├── main.jsx
|
|
25
|
+
├── app.jsx
|
|
26
|
+
├── components/
|
|
27
|
+
└── routes/
|
|
28
|
+
```
|
|
4
29
|
|
|
5
30
|
## Scripts
|
|
6
31
|
|
|
7
32
|
```bash
|
|
8
|
-
|
|
9
|
-
npm run
|
|
10
|
-
npm run
|
|
11
|
-
|
|
33
|
+
# Desarrollo
|
|
34
|
+
npm run dev # Inicia en modo desarrollo (Windows)
|
|
35
|
+
npm run dev:linux # Inicia en modo desarrollo (Linux)
|
|
36
|
+
|
|
37
|
+
# Build
|
|
38
|
+
npm run build # Compila la aplicación
|
|
39
|
+
npm run pack # Empaqueta sin crear instalador
|
|
40
|
+
npm run dist # Crea instaladores para la plataforma actual
|
|
41
|
+
npm run dist:win # Crea instalador para Windows
|
|
42
|
+
npm run dist:linux # Crea instalador para Linux
|
|
12
43
|
```
|
|
13
44
|
|
|
14
|
-
##
|
|
45
|
+
## IPC disponible
|
|
46
|
+
|
|
47
|
+
El preload expone `window.electronAPI` con:
|
|
15
48
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
49
|
+
```javascript
|
|
50
|
+
// Guardar/leer configuración persistente
|
|
51
|
+
await window.electronAPI.settings.get();
|
|
52
|
+
await window.electronAPI.settings.set({ key: "value" });
|
|
53
|
+
|
|
54
|
+
// Abrir ventana secundaria con una ruta específica
|
|
55
|
+
window.electronAPI.window.openRoute("/mi-ruta");
|
|
56
|
+
window.electronAPI.window.openRoute({ route: "/mi-ruta", title: "Mi Ventana" });
|
|
57
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
|
|
2
|
+
import preact from "@preact/preset-vite";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
main: {
|
|
7
|
+
plugins: [externalizeDepsPlugin()],
|
|
8
|
+
build: {
|
|
9
|
+
rollupOptions: {
|
|
10
|
+
input: {
|
|
11
|
+
index: resolve(__dirname, "src/main/index.js"),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
preload: {
|
|
17
|
+
plugins: [externalizeDepsPlugin()],
|
|
18
|
+
build: {
|
|
19
|
+
rollupOptions: {
|
|
20
|
+
input: {
|
|
21
|
+
index: resolve(__dirname, "src/preload/index.js"),
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
renderer: {
|
|
27
|
+
root: "src/renderer",
|
|
28
|
+
publicDir: "public",
|
|
29
|
+
build: {
|
|
30
|
+
rollupOptions: {
|
|
31
|
+
input: {
|
|
32
|
+
index: resolve(__dirname, "src/renderer/index.html"),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
plugins: [preact()],
|
|
37
|
+
resolve: {
|
|
38
|
+
alias: {
|
|
39
|
+
"@": resolve(__dirname, "src/renderer/src"),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
package/template/package.json
CHANGED
|
@@ -1,103 +1,55 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "my-athenea-app",
|
|
3
3
|
"private": true,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Aplicación de escritorio creada con Athenea",
|
|
6
|
+
"author": "Developer",
|
|
5
7
|
"type": "module",
|
|
6
|
-
"main": "
|
|
8
|
+
"main": "./out/main/index.js",
|
|
7
9
|
"engines": {
|
|
8
10
|
"node": ">=22.0.0"
|
|
9
11
|
},
|
|
10
12
|
"scripts": {
|
|
11
|
-
"dev": "vite
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"build": "vite build && npm run copy-preload",
|
|
16
|
-
"preview": "vite preview",
|
|
13
|
+
"dev": "electron-vite dev",
|
|
14
|
+
"dev:linux": "electron-vite dev -- --no-sandbox",
|
|
15
|
+
"build": "electron-vite build",
|
|
16
|
+
"preview": "electron-vite preview",
|
|
17
17
|
"pack": "npm run build && electron-builder --dir",
|
|
18
18
|
"dist": "npm run build && electron-builder",
|
|
19
19
|
"dist:linux": "npm run build && electron-builder --linux",
|
|
20
|
-
"dist:win": "npm run build && electron-builder --win"
|
|
21
|
-
"lint": "eslint .",
|
|
22
|
-
"format": "prettier --write .",
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
24
|
-
"test": "vitest",
|
|
25
|
-
"postinstall": "electron-builder install-app-deps"
|
|
20
|
+
"dist:win": "npm run build && electron-builder --win"
|
|
26
21
|
},
|
|
27
22
|
"dependencies": {
|
|
28
|
-
"@preact/signals": "^2.5.1",
|
|
29
|
-
"@tanstack/react-table": "^8.21.3",
|
|
30
|
-
"dayjs": "^1.11.13",
|
|
31
|
-
"electron-log": "^5.4.3",
|
|
32
|
-
"electron-updater": "^6.6.2",
|
|
33
|
-
"jwt-decode": "^4.0.0",
|
|
34
|
-
"keytar": "7.9.0",
|
|
35
|
-
"ky": "^1.2.4",
|
|
36
|
-
"pdf-to-printer": "5.6.1",
|
|
37
23
|
"preact": "^10.26.2",
|
|
38
|
-
"preact-iso": "^2.9.1"
|
|
39
|
-
"sweetalert2": "^11.26.10",
|
|
40
|
-
"zod": "^3.23.8",
|
|
41
|
-
"zustand": "^5.0.3"
|
|
24
|
+
"preact-iso": "^2.9.1"
|
|
42
25
|
},
|
|
43
26
|
"devDependencies": {
|
|
44
|
-
"@eslint/eslintrc": "^3.3.1",
|
|
45
|
-
"@eslint/js": "^9.39.1",
|
|
46
27
|
"@preact/preset-vite": "^2.10.1",
|
|
47
|
-
"@testing-library/preact": "3.2.3",
|
|
48
|
-
"@types/node": "^22.7.8",
|
|
49
|
-
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
|
50
|
-
"@typescript-eslint/parser": "^8.46.4",
|
|
51
|
-
"autoprefixer": "^10.4.20",
|
|
52
|
-
"concurrently": "^8.2.0",
|
|
53
|
-
"cross-env": "^7.0.3",
|
|
54
|
-
"dotenv": "^16.4.5",
|
|
55
28
|
"electron": "^35.1.4",
|
|
56
29
|
"electron-builder": "^24.13.1",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"eslint-plugin-prettier": "^5.5.4",
|
|
60
|
-
"eslint-plugin-react-hooks": "^7.0.1",
|
|
61
|
-
"postcss": "^8.4.47",
|
|
62
|
-
"prettier": "^3.6.2",
|
|
63
|
-
"tailwindcss": "^3.4.14",
|
|
64
|
-
"typescript": "^5.9.3",
|
|
65
|
-
"vite": "^7.2.2",
|
|
66
|
-
"vite-tsconfig-paths": "^5.0.0",
|
|
67
|
-
"vitest": "^4.0.8",
|
|
68
|
-
"wait-on": "^7.0.1"
|
|
69
|
-
},
|
|
70
|
-
"overrides": {
|
|
71
|
-
"tar": "7.5.7"
|
|
30
|
+
"electron-vite": "^5.0.0",
|
|
31
|
+
"vite": "^7.2.2"
|
|
72
32
|
},
|
|
73
33
|
"build": {
|
|
74
|
-
"appId": "
|
|
75
|
-
"productName": "
|
|
34
|
+
"appId": "com.example.app",
|
|
35
|
+
"productName": "App",
|
|
76
36
|
"asar": true,
|
|
77
|
-
"asarUnpack": [
|
|
78
|
-
"**/*.node",
|
|
79
|
-
"preload.cjs",
|
|
80
|
-
"node_modules/pdf-to-printer/**/*"
|
|
81
|
-
],
|
|
82
37
|
"directories": {
|
|
83
|
-
"buildResources": "
|
|
84
|
-
"output": "
|
|
38
|
+
"buildResources": "resources",
|
|
39
|
+
"output": "release"
|
|
85
40
|
},
|
|
86
|
-
"extraResources": [],
|
|
87
41
|
"files": [
|
|
88
|
-
"
|
|
89
|
-
"electron.js",
|
|
90
|
-
"preload.cjs",
|
|
42
|
+
"out/**/*",
|
|
91
43
|
"!**/*.map",
|
|
92
|
-
"
|
|
93
|
-
"
|
|
44
|
+
"!src/**/*",
|
|
45
|
+
"!electron.vite.config.*"
|
|
94
46
|
],
|
|
95
47
|
"win": {
|
|
96
48
|
"target": [
|
|
97
49
|
"nsis"
|
|
98
50
|
],
|
|
99
51
|
"signAndEditExecutable": false,
|
|
100
|
-
"icon": "
|
|
52
|
+
"icon": "resources/icon.ico"
|
|
101
53
|
},
|
|
102
54
|
"nsis": {
|
|
103
55
|
"oneClick": false,
|
|
@@ -105,19 +57,18 @@
|
|
|
105
57
|
"allowToChangeInstallationDirectory": true,
|
|
106
58
|
"createDesktopShortcut": true,
|
|
107
59
|
"createStartMenuShortcut": true,
|
|
108
|
-
"installerIcon": "
|
|
109
|
-
"uninstallerIcon": "
|
|
110
|
-
"installerSidebar": "
|
|
111
|
-
"uninstallerSidebar": "
|
|
112
|
-
"installerHeader": "
|
|
60
|
+
"installerIcon": "resources/icon.ico",
|
|
61
|
+
"uninstallerIcon": "resources/icon.ico",
|
|
62
|
+
"installerSidebar": "resources/installer-sidebar.bmp",
|
|
63
|
+
"uninstallerSidebar": "resources/installer-sidebar.bmp",
|
|
64
|
+
"installerHeader": "resources/installer-header.bmp"
|
|
113
65
|
},
|
|
114
66
|
"linux": {
|
|
115
67
|
"target": [
|
|
116
|
-
"AppImage"
|
|
117
|
-
"deb"
|
|
68
|
+
"AppImage"
|
|
118
69
|
],
|
|
119
|
-
"
|
|
120
|
-
"
|
|
70
|
+
"icon": "resources/icon.png",
|
|
71
|
+
"category": "Office"
|
|
121
72
|
}
|
|
122
73
|
}
|
|
123
74
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { app, BrowserWindow, ipcMain, screen } from "electron";
|
|
2
|
+
import path, { join } from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
// ------------------- VENTANA PRINCIPAL -------------------
|
|
6
|
+
|
|
7
|
+
/** @type {BrowserWindow | null} */
|
|
8
|
+
let mainWindow = null;
|
|
9
|
+
|
|
10
|
+
/** @type {Map<string, BrowserWindow>} */
|
|
11
|
+
const childWindowsByRoute = new Map();
|
|
12
|
+
|
|
13
|
+
const settingsPath = path.join(app.getPath("userData"), "settings.json");
|
|
14
|
+
|
|
15
|
+
function createWindow() {
|
|
16
|
+
const { width: screenWidth, height: screenHeight } =
|
|
17
|
+
screen.getPrimaryDisplay().workAreaSize;
|
|
18
|
+
|
|
19
|
+
mainWindow = new BrowserWindow({
|
|
20
|
+
width: screenWidth,
|
|
21
|
+
height: screenHeight,
|
|
22
|
+
title: __APP_TITLE_JSON__,
|
|
23
|
+
webPreferences: {
|
|
24
|
+
preload: join(__dirname, "../preload/index.mjs"),
|
|
25
|
+
sandbox: false,
|
|
26
|
+
contextIsolation: true,
|
|
27
|
+
nodeIntegration: false,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
mainWindow.setMenuBarVisibility(false);
|
|
32
|
+
mainWindow.setAutoHideMenuBar(true);
|
|
33
|
+
mainWindow.maximize();
|
|
34
|
+
|
|
35
|
+
// electron-vite: usa ELECTRON_RENDERER_URL en dev
|
|
36
|
+
if (process.env.ELECTRON_RENDERER_URL) {
|
|
37
|
+
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL);
|
|
38
|
+
mainWindow.webContents.openDevTools();
|
|
39
|
+
} else {
|
|
40
|
+
mainWindow.loadFile(join(__dirname, "../renderer/index.html"));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
mainWindow.on("minimize", () => {
|
|
44
|
+
for (const [, child] of childWindowsByRoute) {
|
|
45
|
+
if (!child.isDestroyed() && !child.isMinimized()) {
|
|
46
|
+
child.minimize();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
mainWindow.on("restore", () => {
|
|
52
|
+
for (const [, child] of childWindowsByRoute) {
|
|
53
|
+
if (!child.isDestroyed()) {
|
|
54
|
+
child.restore();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
app.whenReady().then(() => {
|
|
61
|
+
createWindow();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
app.on("window-all-closed", () => {
|
|
65
|
+
if (process.platform !== "darwin") app.quit();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
app.on("activate", () => {
|
|
69
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
70
|
+
createWindow();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ------------------- HELPERS SETTINGS -------------------
|
|
75
|
+
|
|
76
|
+
function readSettings() {
|
|
77
|
+
try {
|
|
78
|
+
if (!fs.existsSync(settingsPath)) return {};
|
|
79
|
+
return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
80
|
+
} catch {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function writeSettings(data) {
|
|
86
|
+
try {
|
|
87
|
+
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ------------------- IPC -------------------
|
|
95
|
+
|
|
96
|
+
ipcMain.handle("settings:get", () => readSettings());
|
|
97
|
+
|
|
98
|
+
ipcMain.handle("settings:set", (_event, data) => {
|
|
99
|
+
const current = readSettings();
|
|
100
|
+
const merged = { ...current, ...data };
|
|
101
|
+
writeSettings(merged);
|
|
102
|
+
return merged;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
ipcMain.on("window:openRoute", (_event, options) => {
|
|
106
|
+
const route = typeof options === "string" ? options : options?.route;
|
|
107
|
+
const title = typeof options === "object" ? options.title : null;
|
|
108
|
+
if (!route) return;
|
|
109
|
+
|
|
110
|
+
const routeKey = String(route);
|
|
111
|
+
const existing = childWindowsByRoute.get(routeKey);
|
|
112
|
+
if (existing && !existing.isDestroyed()) {
|
|
113
|
+
if (existing.isMinimized()) existing.restore();
|
|
114
|
+
existing.focus();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const child = new BrowserWindow({
|
|
119
|
+
width: 1200,
|
|
120
|
+
height: 700,
|
|
121
|
+
title: title || __APP_TITLE_JSON__,
|
|
122
|
+
webPreferences: {
|
|
123
|
+
preload: join(__dirname, "../preload/index.mjs"),
|
|
124
|
+
sandbox: false,
|
|
125
|
+
contextIsolation: true,
|
|
126
|
+
nodeIntegration: false,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
child.setMenuBarVisibility(false);
|
|
131
|
+
child.setAutoHideMenuBar(true);
|
|
132
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
133
|
+
child.setParentWindow(mainWindow);
|
|
134
|
+
}
|
|
135
|
+
childWindowsByRoute.set(routeKey, child);
|
|
136
|
+
|
|
137
|
+
if (process.env.ELECTRON_RENDERER_URL) {
|
|
138
|
+
child.loadURL(
|
|
139
|
+
`${process.env.ELECTRON_RENDERER_URL}?route=${encodeURIComponent(route)}&child=1`,
|
|
140
|
+
);
|
|
141
|
+
child.webContents.openDevTools();
|
|
142
|
+
} else {
|
|
143
|
+
child.loadFile(join(__dirname, "../renderer/index.html"), {
|
|
144
|
+
query: { route, child: "1" },
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
child.on("closed", () => {
|
|
149
|
+
const current = childWindowsByRoute.get(routeKey);
|
|
150
|
+
if (current === child) childWindowsByRoute.delete(routeKey);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer } from "electron";
|
|
2
|
+
|
|
3
|
+
contextBridge.exposeInMainWorld("electronAPI", {
|
|
4
|
+
settings: {
|
|
5
|
+
get: () => ipcRenderer.invoke("settings:get"),
|
|
6
|
+
set: (data) => ipcRenderer.invoke("settings:set", data),
|
|
7
|
+
},
|
|
8
|
+
window: {
|
|
9
|
+
openRoute: (options) => ipcRenderer.send("window:openRoute", options),
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="./public/logo.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>__APP_TITLE__</title>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="app"></div>
|
|
11
|
-
<script type="module" src="src/main.jsx"></script>
|
|
11
|
+
<script type="module" src="./src/main.jsx"></script>
|
|
12
12
|
</body>
|
|
13
13
|
</html>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
|
|
5
|
+
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<circle cx="50" cy="50" r="45" fill="url(#grad)"/>
|
|
9
|
+
<text x="50" y="62" font-family="Arial, sans-serif" font-size="36" font-weight="bold" fill="white" text-anchor="middle">A</text>
|
|
10
|
+
</svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#app {
|
|
2
|
+
max-width: 1280px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 2rem;
|
|
5
|
+
text-align: center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.logo {
|
|
9
|
+
height: 6em;
|
|
10
|
+
padding: 1.5em;
|
|
11
|
+
}
|
|
12
|
+
.logo:hover {
|
|
13
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.card {
|
|
17
|
+
padding: 2em;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.read-the-docs {
|
|
21
|
+
color: #888;
|
|
22
|
+
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { render } from "preact";
|
|
2
1
|
import { LocationProvider, Router, Route } from "preact-iso";
|
|
3
2
|
import Home from "./routes/Home/Home";
|
|
4
|
-
import "./index.css";
|
|
5
3
|
|
|
6
|
-
function App() {
|
|
4
|
+
export default function App() {
|
|
7
5
|
return (
|
|
8
6
|
<LocationProvider>
|
|
9
7
|
<Router>
|
|
10
8
|
<Route path="/" component={Home} />
|
|
9
|
+
<Route default component={Home} />
|
|
11
10
|
</Router>
|
|
12
11
|
</LocationProvider>
|
|
13
12
|
);
|
|
14
13
|
}
|
|
15
|
-
|
|
16
|
-
render(<App />, document.getElementById("app"));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useState } from "preact/hooks";
|
|
2
|
+
import "./style.css";
|
|
3
|
+
|
|
4
|
+
export function Counter() {
|
|
5
|
+
const [count, setCount] = useState(0);
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div class="counter">
|
|
9
|
+
<button onClick={() => setCount(count - 1)}>-</button>
|
|
10
|
+
<p>{count}</p>
|
|
11
|
+
<button onClick={() => setCount(count + 1)}>+</button>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* counter.css */
|
|
2
|
+
.counter {
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
gap: 1rem;
|
|
7
|
+
padding: 1.5rem;
|
|
8
|
+
background-color: #f8f9fa;
|
|
9
|
+
border: 1px solid #e0e0e0;
|
|
10
|
+
border-radius: 12px;
|
|
11
|
+
width: fit-content;
|
|
12
|
+
margin: 2rem auto;
|
|
13
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.counter p {
|
|
17
|
+
margin: 0;
|
|
18
|
+
font-size: 1.5rem;
|
|
19
|
+
font-weight: 500;
|
|
20
|
+
color: #333;
|
|
21
|
+
min-width: 2rem;
|
|
22
|
+
text-align: center;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.counter button {
|
|
26
|
+
background-color: #ffffff;
|
|
27
|
+
border: 1px solid #ccc;
|
|
28
|
+
border-radius: 8px;
|
|
29
|
+
width: 2.5rem;
|
|
30
|
+
height: 2.5rem;
|
|
31
|
+
font-size: 1.2rem;
|
|
32
|
+
font-weight: 600;
|
|
33
|
+
color: #333;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition: all 0.2s ease-in-out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.counter button:hover {
|
|
39
|
+
background-color: #f0f0f0;
|
|
40
|
+
border-color: #999;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.counter button:active {
|
|
44
|
+
background-color: #e0e0e0;
|
|
45
|
+
transform: scale(0.96);
|
|
46
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
html,
|
|
8
|
+
body {
|
|
9
|
+
height: 100%;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
font-family:
|
|
15
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
|
|
16
|
+
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
|
17
|
+
-webkit-font-smoothing: antialiased;
|
|
18
|
+
-moz-osx-font-smoothing: grayscale;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#app {
|
|
22
|
+
height: 100%;
|
|
23
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Counter } from "../../components/Counter/Counter";
|
|
2
|
+
|
|
2
3
|
import "./style.css";
|
|
3
4
|
|
|
4
5
|
export default function Home() {
|
|
5
6
|
return (
|
|
6
7
|
<div className="home">
|
|
7
|
-
<h1 className="home-title"
|
|
8
|
-
<img className="home-logo" src="/logo.svg" alt="" />
|
|
8
|
+
<h1 className="home-title">¡Bienvenido a __APP_TITLE__!</h1>
|
|
9
|
+
<img className="home-logo" src="../../public/logo.svg" alt="" />
|
|
9
10
|
<Counter />
|
|
10
11
|
</div>
|
|
11
12
|
);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.home {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
padding: 2rem;
|
|
7
|
+
background-color: #f8f9fa;
|
|
8
|
+
height: 100vh;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
font-family: "Inter", system-ui, sans-serif;
|
|
11
|
+
color: #333;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.home-title {
|
|
15
|
+
font-size: 2.4rem;
|
|
16
|
+
font-weight: 600;
|
|
17
|
+
margin-bottom: 1.5rem;
|
|
18
|
+
color: #2c3e50;
|
|
19
|
+
letter-spacing: 0.5px;
|
|
20
|
+
text-align: center;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.home-logo {
|
|
24
|
+
width: 180px;
|
|
25
|
+
height: auto;
|
|
26
|
+
margin-bottom: 1.5rem;
|
|
27
|
+
animation: pulse-logo 8s ease-in-out infinite;
|
|
28
|
+
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.1));
|
|
29
|
+
transition: transform 0.3s ease;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@keyframes pulse-logo {
|
|
33
|
+
0% {
|
|
34
|
+
transform: scale(1);
|
|
35
|
+
}
|
|
36
|
+
25% {
|
|
37
|
+
transform: scale(1.1);
|
|
38
|
+
}
|
|
39
|
+
50% {
|
|
40
|
+
transform: scale(1.25);
|
|
41
|
+
}
|
|
42
|
+
75% {
|
|
43
|
+
transform: scale(1.1);
|
|
44
|
+
}
|
|
45
|
+
100% {
|
|
46
|
+
transform: scale(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/template/electron.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { app, BrowserWindow, ipcMain, dialog, screen } from "electron";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
import fs from "fs";
|
|
5
|
-
import os from "os";
|
|
6
|
-
import keytar from "keytar";
|
|
7
|
-
import pdfPrinter from "pdf-to-printer";
|
|
8
|
-
import { spawn } from "child_process";
|
|
9
|
-
import log from "electron-log";
|
|
10
|
-
|
|
11
|
-
// Inicializar logger
|
|
12
|
-
log.initialize();
|
|
13
|
-
log.transports.file.level = "info";
|
|
14
|
-
log.info("Iniciando aplicacion...");
|
|
15
|
-
|
|
16
|
-
const { print: printPDF, getPrinters } = pdfPrinter;
|
|
17
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
|
|
19
|
-
app.commandLine.appendSwitch("disable-features", "Autofill");
|
|
20
|
-
if (!app.isPackaged || process.env.ELECTRON_DISABLE_SANDBOX === "1") {
|
|
21
|
-
app.commandLine.appendSwitch("no-sandbox");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let backendProcess = null;
|
|
25
|
-
|
|
26
|
-
function getBackendPath() {
|
|
27
|
-
const isWin = process.platform === "win32";
|
|
28
|
-
const executableName = isWin
|
|
29
|
-
? "api_billing_software.exe"
|
|
30
|
-
: "api_billing_software";
|
|
31
|
-
|
|
32
|
-
if (app.isPackaged) {
|
|
33
|
-
return path.join(
|
|
34
|
-
process.resourcesPath,
|
|
35
|
-
"api_billing_software",
|
|
36
|
-
executableName,
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return path.join(
|
|
41
|
-
__dirname,
|
|
42
|
-
"..",
|
|
43
|
-
"backend",
|
|
44
|
-
"dist",
|
|
45
|
-
"api_billing_software",
|
|
46
|
-
executableName,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function startBackend() {
|
|
51
|
-
const backendPath = getBackendPath();
|
|
52
|
-
log.info("Intentando iniciar backend desde:", backendPath);
|
|
53
|
-
|
|
54
|
-
if (!fs.existsSync(backendPath)) {
|
|
55
|
-
log.error("No se encontro el ejecutable del backend en:", backendPath);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
backendProcess = spawn(backendPath, [], {
|
|
60
|
-
cwd: path.dirname(backendPath),
|
|
61
|
-
stdio: "pipe",
|
|
62
|
-
windowsHide: true,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
backendProcess.on("close", () => {
|
|
66
|
-
backendProcess = null;
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function stopBackend() {
|
|
71
|
-
if (backendProcess) {
|
|
72
|
-
backendProcess.kill();
|
|
73
|
-
backendProcess = null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** @type {BrowserWindow | null} */
|
|
78
|
-
let mainWindow = null;
|
|
79
|
-
|
|
80
|
-
/** @type {Map<string, BrowserWindow>} */
|
|
81
|
-
const childWindowsByRoute = new Map();
|
|
82
|
-
|
|
83
|
-
const settingsPath = path.join(app.getPath("userData"), "settings.json");
|
|
84
|
-
|
|
85
|
-
function createWindow() {
|
|
86
|
-
const isDev = !app.isPackaged;
|
|
87
|
-
const devServerUrl = process.env.DEV_SERVER_URL || "http://localhost:5171";
|
|
88
|
-
const { width: screenWidth, height: screenHeight } =
|
|
89
|
-
screen.getPrimaryDisplay().workAreaSize;
|
|
90
|
-
|
|
91
|
-
mainWindow = new BrowserWindow({
|
|
92
|
-
width: screenWidth,
|
|
93
|
-
height: screenHeight,
|
|
94
|
-
title: __APP_TITLE_JSON__,
|
|
95
|
-
webPreferences: {
|
|
96
|
-
preload: path.join(__dirname, "preload.cjs"),
|
|
97
|
-
devTools: isDev,
|
|
98
|
-
contextIsolation: true,
|
|
99
|
-
nodeIntegration: false,
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
mainWindow.setMenuBarVisibility(false);
|
|
104
|
-
mainWindow.setAutoHideMenuBar(true);
|
|
105
|
-
mainWindow.maximize();
|
|
106
|
-
|
|
107
|
-
if (isDev) {
|
|
108
|
-
mainWindow.loadURL(devServerUrl);
|
|
109
|
-
mainWindow.webContents.openDevTools();
|
|
110
|
-
} else {
|
|
111
|
-
const indexPath = path.join(__dirname, "dist", "index.html");
|
|
112
|
-
mainWindow.loadFile(indexPath, { query: { route: "/" } });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
mainWindow.on("minimize", () => {
|
|
116
|
-
for (const [, child] of childWindowsByRoute) {
|
|
117
|
-
if (!child.isDestroyed() && !child.isMinimized()) child.minimize();
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
mainWindow.on("restore", () => {
|
|
122
|
-
for (const [, child] of childWindowsByRoute) {
|
|
123
|
-
if (!child.isDestroyed()) child.restore();
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
app.whenReady().then(() => {
|
|
129
|
-
startBackend();
|
|
130
|
-
createWindow();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
app.on("will-quit", () => {
|
|
134
|
-
stopBackend();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
app.on("window-all-closed", () => {
|
|
138
|
-
if (process.platform !== "darwin") app.quit();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
app.on("activate", () => {
|
|
142
|
-
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
function readSettings() {
|
|
146
|
-
try {
|
|
147
|
-
if (!fs.existsSync(settingsPath)) return {};
|
|
148
|
-
return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
149
|
-
} catch {
|
|
150
|
-
return {};
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function writeSettings(data) {
|
|
155
|
-
try {
|
|
156
|
-
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
|
|
157
|
-
return true;
|
|
158
|
-
} catch {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
ipcMain.handle("settings:get", () => readSettings());
|
|
164
|
-
ipcMain.handle("settings:set", (_event, data) => {
|
|
165
|
-
const current = readSettings();
|
|
166
|
-
const merged = { ...current, ...data };
|
|
167
|
-
writeSettings(merged);
|
|
168
|
-
return merged;
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
ipcMain.handle("printer:getPrinters", async () => {
|
|
172
|
-
return await getPrinters();
|
|
173
|
-
});
|
|
174
|
-
ipcMain.handle("printer:printPDF", async (_event, { filePath, options }) => {
|
|
175
|
-
try {
|
|
176
|
-
await printPDF(filePath, options || {});
|
|
177
|
-
return { ok: true };
|
|
178
|
-
} catch (err) {
|
|
179
|
-
return { ok: false, error: err?.message || String(err) };
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
ipcMain.on("window:openRoute", (_event, options) => {
|
|
184
|
-
const route = typeof options === "string" ? options : options?.route;
|
|
185
|
-
const title = typeof options === "object" ? options.title : null;
|
|
186
|
-
if (!route) return;
|
|
187
|
-
|
|
188
|
-
const routeKey = String(route);
|
|
189
|
-
const existing = childWindowsByRoute.get(routeKey);
|
|
190
|
-
if (existing && !existing.isDestroyed()) {
|
|
191
|
-
if (existing.isMinimized()) existing.restore();
|
|
192
|
-
existing.focus();
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const isDev = !app.isPackaged;
|
|
197
|
-
const devServerUrl = process.env.DEV_SERVER_URL || "http://localhost:5171";
|
|
198
|
-
const preloadPath = path.join(__dirname, "preload.cjs");
|
|
199
|
-
|
|
200
|
-
const child = new BrowserWindow({
|
|
201
|
-
width: 1200,
|
|
202
|
-
height: 700,
|
|
203
|
-
title: title || __APP_TITLE_JSON__,
|
|
204
|
-
webPreferences: {
|
|
205
|
-
preload: preloadPath,
|
|
206
|
-
devTools: isDev,
|
|
207
|
-
contextIsolation: true,
|
|
208
|
-
nodeIntegration: false,
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
child.setMenuBarVisibility(false);
|
|
213
|
-
child.setAutoHideMenuBar(true);
|
|
214
|
-
if (mainWindow && !mainWindow.isDestroyed())
|
|
215
|
-
child.setParentWindow(mainWindow);
|
|
216
|
-
childWindowsByRoute.set(routeKey, child);
|
|
217
|
-
|
|
218
|
-
if (isDev) {
|
|
219
|
-
child.loadURL(`${devServerUrl}?route=${encodeURIComponent(route)}&child=1`);
|
|
220
|
-
child.webContents.openDevTools();
|
|
221
|
-
} else {
|
|
222
|
-
const indexPath = path.join(__dirname, "dist", "index.html");
|
|
223
|
-
child.loadFile(indexPath, { query: { route, child: "1" } });
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
child.on("closed", () => {
|
|
227
|
-
const current = childWindowsByRoute.get(routeKey);
|
|
228
|
-
if (current === child) childWindowsByRoute.delete(routeKey);
|
|
229
|
-
});
|
|
230
|
-
});
|
package/template/preload.cjs
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
const { contextBridge, ipcRenderer } = require("electron");
|
|
2
|
-
|
|
3
|
-
contextBridge.exposeInMainWorld("electronAPI", {
|
|
4
|
-
settingsGet: () => ipcRenderer.invoke("settings:get"),
|
|
5
|
-
settingsSet: (data) => ipcRenderer.invoke("settings:set", data),
|
|
6
|
-
openRoute: (options) => ipcRenderer.send("window:openRoute", options),
|
|
7
|
-
getPrinters: () => ipcRenderer.invoke("printer:getPrinters"),
|
|
8
|
-
printPDF: (payload) => ipcRenderer.invoke("printer:printPDF", payload),
|
|
9
|
-
});
|
package/template/preload.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// Compat (si alguien lo usa desde ESM). El preload real es preload.cjs
|
package/template/public/logo.svg
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { createServer } from "vite";
|
|
2
|
-
import { spawn } from "child_process";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
|
|
9
|
-
async function start() {
|
|
10
|
-
const server = await createServer({
|
|
11
|
-
server: {
|
|
12
|
-
host: "127.0.0.1",
|
|
13
|
-
port: 0,
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
await server.listen();
|
|
18
|
-
|
|
19
|
-
const url =
|
|
20
|
-
server.resolvedUrls?.local?.[0] ||
|
|
21
|
-
`http://localhost:${server.config.server.port || 5173}`;
|
|
22
|
-
|
|
23
|
-
console.log(`[dev-electron] Vite dev server en ${url}`);
|
|
24
|
-
|
|
25
|
-
const isWin = process.platform === "win32";
|
|
26
|
-
const localElectronBin = path.resolve(
|
|
27
|
-
__dirname,
|
|
28
|
-
"..",
|
|
29
|
-
"node_modules",
|
|
30
|
-
".bin",
|
|
31
|
-
isWin ? "electron.cmd" : "electron",
|
|
32
|
-
);
|
|
33
|
-
const electronBin = fs.existsSync(localElectronBin)
|
|
34
|
-
? localElectronBin
|
|
35
|
-
: "electron";
|
|
36
|
-
|
|
37
|
-
const electronProcess = spawn(electronBin, [".", "--no-sandbox"], {
|
|
38
|
-
stdio: "inherit",
|
|
39
|
-
shell: isWin,
|
|
40
|
-
env: {
|
|
41
|
-
...process.env,
|
|
42
|
-
DEV_SERVER_URL: url,
|
|
43
|
-
NODE_OPTIONS: "--experimental-require-module --no-warnings",
|
|
44
|
-
ELECTRON_DISABLE_SANDBOX: "1",
|
|
45
|
-
},
|
|
46
|
-
cwd: path.resolve(__dirname, ".."),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const clean = () => {
|
|
50
|
-
electronProcess.kill();
|
|
51
|
-
server.close();
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
electronProcess.on("close", (code) => {
|
|
55
|
-
clean();
|
|
56
|
-
process.exit(code ?? 0);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const handleSignal = () => {
|
|
60
|
-
clean();
|
|
61
|
-
process.exit(0);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
process.on("SIGINT", handleSignal);
|
|
65
|
-
process.on("SIGTERM", handleSignal);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
start().catch((err) => {
|
|
69
|
-
console.error("[dev-electron] Error:", err);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { useState } from "preact/hooks";
|
|
2
|
-
import "./style.css";
|
|
3
|
-
|
|
4
|
-
export function Counter() {
|
|
5
|
-
const [count, setCount] = useState(0);
|
|
6
|
-
return (
|
|
7
|
-
<div className="counter">
|
|
8
|
-
<button onClick={() => setCount((c) => c - 1)}>-</button>
|
|
9
|
-
<span>{count}</span>
|
|
10
|
-
<button onClick={() => setCount((c) => c + 1)}>+</button>
|
|
11
|
-
</div>
|
|
12
|
-
);
|
|
13
|
-
}
|
package/template/src/index.css
DELETED
package/template/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"useDefineForClassFields": true,
|
|
5
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
"moduleResolution": "Bundler",
|
|
9
|
-
"resolveJsonModule": true,
|
|
10
|
-
"isolatedModules": true,
|
|
11
|
-
"noEmit": true,
|
|
12
|
-
"jsx": "react-jsx",
|
|
13
|
-
"jsxImportSource": "preact",
|
|
14
|
-
"strict": true,
|
|
15
|
-
"types": ["node"]
|
|
16
|
-
},
|
|
17
|
-
"include": ["src"]
|
|
18
|
-
}
|
package/template/vite.config.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from "vite";
|
|
2
|
-
import preact from "@preact/preset-vite";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
plugins: [preact()],
|
|
7
|
-
base: "./",
|
|
8
|
-
publicDir: "public",
|
|
9
|
-
build: {
|
|
10
|
-
outDir: "dist",
|
|
11
|
-
assetsDir: "assets",
|
|
12
|
-
rollupOptions: {
|
|
13
|
-
output: {
|
|
14
|
-
assetFileNames: "assets/[name].[hash][extname]",
|
|
15
|
-
chunkFileNames: "assets/[name].[hash].js",
|
|
16
|
-
entryFileNames: "assets/[name].[hash].js",
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
resolve: {
|
|
21
|
-
alias: {
|
|
22
|
-
"@": path.resolve(__dirname, "./src"),
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
});
|