@imksh/create 1.0.28 → 1.0.31
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/package.json +1 -1
- package/src/utils/askTemplate.js +2 -1
- package/templates/layers/electron/base/electron/main.js +262 -0
- package/templates/layers/electron/base/electron/preload.js +37 -0
- package/templates/layers/electron/base/electron-builder.json5 +68 -0
- package/templates/layers/electron/javascript/_gitignore +38 -0
- package/templates/layers/electron/javascript/package.json +49 -0
- package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
- package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
- package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
- package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
- package/templates/layers/electron/javascript/src/main.jsx +17 -0
- package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
- package/templates/layers/electron/javascript/vite.config.js +29 -0
- package/templates/layers/electron/typescript/_gitignore +38 -0
- package/templates/layers/electron/typescript/electron/main.ts +213 -0
- package/templates/layers/electron/typescript/electron/preload.ts +52 -0
- package/templates/layers/electron/typescript/package.json +52 -0
- package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
- package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
- package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
- package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
- package/templates/layers/electron/typescript/src/main.tsx +17 -0
- package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
- package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
- package/templates/layers/electron/typescript/tsconfig.json +25 -0
- package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
- package/templates/layers/electron/typescript/vite.config.ts +29 -0
- package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
- package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
- package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
- package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
- package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
- package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
- package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
- package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
- package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
- package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
- package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
- package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
- package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
- package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
- package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
- package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
- package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
- package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
- package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -0
- package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
- package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
- package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
- package/templates/layers/next/typescript/src/components/common/Table.tsx +201 -35
- package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
- package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
- package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
- package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
- package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +1 -0
- package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
- package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
- package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
- package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
- package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
- package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
- package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
- package/templates/layers/node/javascript/package.json +2 -1
- package/templates/layers/node/javascript/src/controllers/push.controller.js +35 -14
- package/templates/layers/node/javascript/src/models/pushSubscription.model.js +5 -0
- package/templates/layers/node/javascript/src/utils/pushHelper.js +59 -12
- package/templates/layers/node/typescript/package-lock.json +3795 -0
- package/templates/layers/node/typescript/package.json +3 -2
- package/templates/layers/node/typescript/src/controllers/push.controller.ts +35 -14
- package/templates/layers/node/typescript/src/models/pushSubscription.model.ts +6 -0
- package/templates/layers/node/typescript/src/services/auth.service.ts +3 -3
- package/templates/layers/node/typescript/src/utils/pushHelper.ts +66 -12
- package/templates/layers/node-prisma/javascript/package.json +6 -3
- package/templates/layers/node-prisma/javascript/prisma/schema.prisma +20 -9
- package/templates/layers/node-prisma/javascript/prisma.config.js +12 -0
- package/templates/layers/node-prisma/javascript/src/config/{db.js → prisma.js} +9 -1
- package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +58 -19
- package/templates/layers/node-prisma/javascript/src/utils/pushHelper.js +62 -14
- package/templates/layers/node-prisma/typescript/package-lock.json +4808 -0
- package/templates/layers/node-prisma/typescript/package.json +8 -4
- package/templates/layers/node-prisma/typescript/prisma/schema.prisma +20 -9
- package/templates/layers/node-prisma/typescript/prisma.config.ts +12 -0
- package/templates/layers/node-prisma/typescript/src/config/{db.ts → prisma.ts} +9 -1
- package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -22
- package/templates/layers/node-prisma/typescript/src/index.ts +1 -1
- package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +15 -5
- package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +134 -128
- package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +69 -14
- package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
- package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
- package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
- package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
- package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
- package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
- package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
- package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
- package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
- package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
- package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
- package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
- package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
- package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
- package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
- package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
- package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
- package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
- package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
- package/templates/layers/react/typescript/src/components/common/Table.tsx +197 -34
- package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
- package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
- package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
- package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
- package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
- package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
- package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
- package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
- package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/react-native/base/src/components/ui/BiometricLock.tsx +2 -1
- package/templates/layers/react-native/base/src/config/appConfig.ts +44 -0
- package/templates/presets/electron/preset.json +9 -0
- package/templates/presets/electron-ts/preset.json +9 -0
- package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
package/package.json
CHANGED
package/src/utils/askTemplate.js
CHANGED
|
@@ -8,6 +8,7 @@ export async function askTemplate() {
|
|
|
8
8
|
message: 'Which framework would you like to use?',
|
|
9
9
|
choices: [
|
|
10
10
|
{ name: 'React', value: 'react' },
|
|
11
|
+
{ name: 'Electron', value: 'electron' },
|
|
11
12
|
{ name: 'Node.js', value: 'node' },
|
|
12
13
|
{ name: 'MERN Stack', value: 'mern' },
|
|
13
14
|
{ name: 'React Native', value: 'react-native' },
|
|
@@ -19,7 +20,7 @@ export async function askTemplate() {
|
|
|
19
20
|
},
|
|
20
21
|
]);
|
|
21
22
|
|
|
22
|
-
if (framework === 'react' || framework === 'next' || framework === 'mern') {
|
|
23
|
+
if (framework === 'react' || framework === 'next' || framework === 'mern' || framework === 'electron') {
|
|
23
24
|
const { language } = await inquirer.prompt([
|
|
24
25
|
{
|
|
25
26
|
type: 'list',
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { app, BrowserWindow, Menu, Tray, shell, ipcMain, nativeImage } from 'electron';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
10
|
+
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
|
|
11
|
+
const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'];
|
|
12
|
+
const preloadPath = path.join(__dirname, 'preload.js');
|
|
13
|
+
|
|
14
|
+
/** @type {BrowserWindow | null} */
|
|
15
|
+
let mainWindow = null;
|
|
16
|
+
/** @type {Tray | null} */
|
|
17
|
+
let tray = null;
|
|
18
|
+
|
|
19
|
+
// ─── Window ───────────────────────────────────────────────────────────────────
|
|
20
|
+
function createWindow() {
|
|
21
|
+
mainWindow = new BrowserWindow({
|
|
22
|
+
width: 1280,
|
|
23
|
+
height: 800,
|
|
24
|
+
minWidth: 800,
|
|
25
|
+
minHeight: 600,
|
|
26
|
+
frame: false, // Custom title bar in renderer
|
|
27
|
+
titleBarStyle: 'hidden',
|
|
28
|
+
trafficLightPosition: { x: 16, y: 16 }, // macOS traffic lights position
|
|
29
|
+
backgroundColor: '#0f172a',
|
|
30
|
+
show: false, // Don't show until ready-to-show
|
|
31
|
+
icon: path.join(__dirname, '../public/icon.png'),
|
|
32
|
+
webPreferences: {
|
|
33
|
+
preload: preloadPath,
|
|
34
|
+
contextIsolation: true,
|
|
35
|
+
nodeIntegration: false,
|
|
36
|
+
sandbox: false,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Show window once ready to avoid white flash
|
|
41
|
+
mainWindow.once('ready-to-show', () => {
|
|
42
|
+
mainWindow.show();
|
|
43
|
+
if (isDev) mainWindow.webContents.openDevTools({ mode: 'detach' });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Notify renderer when window maximize state changes
|
|
47
|
+
mainWindow.on('maximize', () => mainWindow?.webContents.send('window:maximized-change', true));
|
|
48
|
+
mainWindow.on('unmaximize', () => mainWindow?.webContents.send('window:maximized-change', false));
|
|
49
|
+
|
|
50
|
+
// Open external links in the default browser instead of Electron
|
|
51
|
+
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
52
|
+
shell.openExternal(url);
|
|
53
|
+
return { action: 'deny' };
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Load app
|
|
57
|
+
if (isDev && VITE_DEV_SERVER_URL) {
|
|
58
|
+
mainWindow.loadURL(VITE_DEV_SERVER_URL);
|
|
59
|
+
} else {
|
|
60
|
+
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Native Application Menu ──────────────────────────────────────────────────
|
|
65
|
+
function buildMenu() {
|
|
66
|
+
const isMac = process.platform === 'darwin';
|
|
67
|
+
|
|
68
|
+
const template = [
|
|
69
|
+
// App menu (macOS only)
|
|
70
|
+
...(isMac
|
|
71
|
+
? [{
|
|
72
|
+
label: app.name,
|
|
73
|
+
submenu: [
|
|
74
|
+
{ role: 'about' },
|
|
75
|
+
{ type: 'separator' },
|
|
76
|
+
{ label: 'Preferences…', accelerator: 'CmdOrCtrl+,', click: () => mainWindow?.webContents.send('menu:preferences') },
|
|
77
|
+
{ type: 'separator' },
|
|
78
|
+
{ role: 'services' },
|
|
79
|
+
{ type: 'separator' },
|
|
80
|
+
{ role: 'hide' },
|
|
81
|
+
{ role: 'hideOthers' },
|
|
82
|
+
{ role: 'unhide' },
|
|
83
|
+
{ type: 'separator' },
|
|
84
|
+
{ role: 'quit' },
|
|
85
|
+
],
|
|
86
|
+
}]
|
|
87
|
+
: []),
|
|
88
|
+
|
|
89
|
+
// File
|
|
90
|
+
{
|
|
91
|
+
label: 'File',
|
|
92
|
+
submenu: [
|
|
93
|
+
{
|
|
94
|
+
label: 'New Window',
|
|
95
|
+
accelerator: 'CmdOrCtrl+Shift+N',
|
|
96
|
+
click: () => createWindow(),
|
|
97
|
+
},
|
|
98
|
+
{ type: 'separator' },
|
|
99
|
+
isMac ? { role: 'close' } : { role: 'quit' },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Edit
|
|
104
|
+
{
|
|
105
|
+
label: 'Edit',
|
|
106
|
+
submenu: [
|
|
107
|
+
{ role: 'undo' },
|
|
108
|
+
{ role: 'redo' },
|
|
109
|
+
{ type: 'separator' },
|
|
110
|
+
{ role: 'cut' },
|
|
111
|
+
{ role: 'copy' },
|
|
112
|
+
{ role: 'paste' },
|
|
113
|
+
...(isMac
|
|
114
|
+
? [{ role: 'pasteAndMatchStyle' }, { role: 'delete' }, { role: 'selectAll' }]
|
|
115
|
+
: [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }]),
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// View
|
|
120
|
+
{
|
|
121
|
+
label: 'View',
|
|
122
|
+
submenu: [
|
|
123
|
+
{ role: 'reload' },
|
|
124
|
+
{ role: 'forceReload' },
|
|
125
|
+
{ role: 'toggleDevTools' },
|
|
126
|
+
{ type: 'separator' },
|
|
127
|
+
{ role: 'resetZoom' },
|
|
128
|
+
{ role: 'zoomIn' },
|
|
129
|
+
{ role: 'zoomOut' },
|
|
130
|
+
{ type: 'separator' },
|
|
131
|
+
{ role: 'togglefullscreen' },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Window
|
|
136
|
+
{
|
|
137
|
+
label: 'Window',
|
|
138
|
+
submenu: [
|
|
139
|
+
{ role: 'minimize' },
|
|
140
|
+
{ role: 'zoom' },
|
|
141
|
+
...(isMac
|
|
142
|
+
? [{ type: 'separator' }, { role: 'front' }, { type: 'separator' }, { role: 'window' }]
|
|
143
|
+
: [{ role: 'close' }]),
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Help
|
|
148
|
+
{
|
|
149
|
+
role: 'help',
|
|
150
|
+
submenu: [
|
|
151
|
+
{
|
|
152
|
+
label: 'Learn More',
|
|
153
|
+
click: () => shell.openExternal('https://github.com/imksh/create'),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: 'Toggle Developer Tools',
|
|
157
|
+
accelerator: isMac ? 'Alt+Cmd+I' : 'Alt+Shift+I',
|
|
158
|
+
click: () => mainWindow?.webContents.toggleDevTools(),
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const menu = Menu.buildFromTemplate(template);
|
|
165
|
+
Menu.setApplicationMenu(menu);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── System Tray ──────────────────────────────────────────────────────────────
|
|
169
|
+
function createTray() {
|
|
170
|
+
// Use a simple nativeImage as placeholder — replace public/tray-icon.png with your 16x16 icon
|
|
171
|
+
const iconPath = path.join(__dirname, '../public/icon.png');
|
|
172
|
+
const trayIcon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 });
|
|
173
|
+
tray = new Tray(trayIcon);
|
|
174
|
+
|
|
175
|
+
const contextMenu = Menu.buildFromTemplate([
|
|
176
|
+
{
|
|
177
|
+
label: 'Show App',
|
|
178
|
+
click: () => {
|
|
179
|
+
mainWindow?.show();
|
|
180
|
+
mainWindow?.focus();
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{ type: 'separator' },
|
|
184
|
+
{
|
|
185
|
+
label: 'Quit',
|
|
186
|
+
click: () => {
|
|
187
|
+
app.isQuiting = true;
|
|
188
|
+
app.quit();
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
tray.setToolTip(app.name);
|
|
194
|
+
tray.setContextMenu(contextMenu);
|
|
195
|
+
|
|
196
|
+
// Double-click tray icon to show window
|
|
197
|
+
tray.on('double-click', () => {
|
|
198
|
+
mainWindow?.show();
|
|
199
|
+
mainWindow?.focus();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── IPC Handlers ─────────────────────────────────────────────────────────────
|
|
204
|
+
function registerIpcHandlers() {
|
|
205
|
+
// Window controls
|
|
206
|
+
ipcMain.on('window:minimize', () => mainWindow?.minimize());
|
|
207
|
+
ipcMain.on('window:maximize', () => {
|
|
208
|
+
if (mainWindow?.isMaximized()) {
|
|
209
|
+
mainWindow.unmaximize();
|
|
210
|
+
} else {
|
|
211
|
+
mainWindow?.maximize();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
ipcMain.on('window:close', () => mainWindow?.close());
|
|
215
|
+
|
|
216
|
+
// Window state queries
|
|
217
|
+
ipcMain.handle('window:isMaximized', () => mainWindow?.isMaximized() ?? false);
|
|
218
|
+
|
|
219
|
+
// App info
|
|
220
|
+
ipcMain.handle('app:version', () => app.getVersion());
|
|
221
|
+
ipcMain.handle('app:platform', () => process.platform);
|
|
222
|
+
|
|
223
|
+
// Shell
|
|
224
|
+
ipcMain.handle('shell:openExternal', (_event, url) => {
|
|
225
|
+
if (typeof url === 'string' && url.startsWith('http')) {
|
|
226
|
+
return shell.openExternal(url);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─── App Lifecycle ────────────────────────────────────────────────────────────
|
|
232
|
+
app.whenReady().then(() => {
|
|
233
|
+
// macOS About panel
|
|
234
|
+
if (process.platform === 'darwin') {
|
|
235
|
+
app.setAboutPanelOptions({
|
|
236
|
+
applicationName: app.name,
|
|
237
|
+
applicationVersion: app.getVersion(),
|
|
238
|
+
copyright: `© ${new Date().getFullYear()} imksh`,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
registerIpcHandlers();
|
|
243
|
+
buildMenu();
|
|
244
|
+
createWindow();
|
|
245
|
+
createTray();
|
|
246
|
+
|
|
247
|
+
app.on('activate', () => {
|
|
248
|
+
// On macOS re-create window when dock icon is clicked and no windows are open
|
|
249
|
+
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
app.on('window-all-closed', () => {
|
|
254
|
+
if (process.platform !== 'darwin') {
|
|
255
|
+
app.quit();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Minimize to tray on close (Windows/Linux)
|
|
260
|
+
app.on('before-quit', () => {
|
|
261
|
+
app.isQuiting = true;
|
|
262
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Expose a safe, typed API to the renderer via window.electronAPI.
|
|
5
|
+
* Never expose ipcRenderer directly — that would allow any renderer code
|
|
6
|
+
* to send arbitrary IPC messages.
|
|
7
|
+
*/
|
|
8
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
9
|
+
// ── Window controls ────────────────────────────────────────────────────────
|
|
10
|
+
minimize: () => ipcRenderer.send('window:minimize'),
|
|
11
|
+
maximize: () => ipcRenderer.send('window:maximize'),
|
|
12
|
+
close: () => ipcRenderer.send('window:close'),
|
|
13
|
+
|
|
14
|
+
// ── Window state ───────────────────────────────────────────────────────────
|
|
15
|
+
isMaximized: () => ipcRenderer.invoke('window:isMaximized'),
|
|
16
|
+
|
|
17
|
+
// ── App info ───────────────────────────────────────────────────────────────
|
|
18
|
+
getVersion: () => ipcRenderer.invoke('app:version'),
|
|
19
|
+
getPlatform: () => ipcRenderer.invoke('app:platform'),
|
|
20
|
+
|
|
21
|
+
// ── Shell ──────────────────────────────────────────────────────────────────
|
|
22
|
+
openExternal: (url) => ipcRenderer.invoke('shell:openExternal', url),
|
|
23
|
+
|
|
24
|
+
// ── Event subscriptions ────────────────────────────────────────────────────
|
|
25
|
+
/** @param {(isMaximized: boolean) => void} callback */
|
|
26
|
+
onMaximizeChange: (callback) => {
|
|
27
|
+
ipcRenderer.on('window:maximized-change', (_event, value) => callback(value));
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/** @param {(data: { label: string }) => void} callback */
|
|
31
|
+
onMenuAction: (callback) => {
|
|
32
|
+
ipcRenderer.on('menu:preferences', (_event, data) => callback({ label: 'preferences', ...data }));
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// ── Cleanup ────────────────────────────────────────────────────────────────
|
|
36
|
+
removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel),
|
|
37
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Application ID — change this to your own reverse-domain identifier
|
|
3
|
+
appId: "com.imksh.electron-app",
|
|
4
|
+
productName: "Electron App",
|
|
5
|
+
|
|
6
|
+
// Directories
|
|
7
|
+
directories: {
|
|
8
|
+
output: "release",
|
|
9
|
+
buildResources: "public"
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
// Files to include in the app bundle
|
|
13
|
+
files: [
|
|
14
|
+
"dist/**/*",
|
|
15
|
+
"dist-electron/**/*",
|
|
16
|
+
"public/**/*"
|
|
17
|
+
],
|
|
18
|
+
|
|
19
|
+
// Pack into an asar archive for faster startup
|
|
20
|
+
asar: true,
|
|
21
|
+
|
|
22
|
+
// macOS
|
|
23
|
+
mac: {
|
|
24
|
+
target: [{ target: "dmg", arch: ["x64", "arm64"] }],
|
|
25
|
+
icon: "public/icon.icns",
|
|
26
|
+
category: "public.app-category.productivity",
|
|
27
|
+
hardenedRuntime: true,
|
|
28
|
+
gatekeeperAssess: false,
|
|
29
|
+
entitlements: "build/entitlements.mac.plist",
|
|
30
|
+
entitlementsInherit: "build/entitlements.mac.plist"
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
dmg: {
|
|
34
|
+
contents: [
|
|
35
|
+
{ x: 130, y: 220 },
|
|
36
|
+
{ x: 410, y: 220, type: "link", path: "/Applications" }
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// Windows
|
|
41
|
+
win: {
|
|
42
|
+
target: [{ target: "nsis", arch: ["x64"] }],
|
|
43
|
+
icon: "public/icon.ico"
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
nsis: {
|
|
47
|
+
oneClick: false,
|
|
48
|
+
perMachine: false,
|
|
49
|
+
allowToChangeInstallationDirectory: true,
|
|
50
|
+
deleteAppDataOnUninstall: false,
|
|
51
|
+
createDesktopShortcut: true,
|
|
52
|
+
createStartMenuShortcut: true
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Linux
|
|
56
|
+
linux: {
|
|
57
|
+
target: ["AppImage", "deb", "snap"],
|
|
58
|
+
icon: "public/icon.png",
|
|
59
|
+
category: "Utility"
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Publish config (for auto-updates — fill in your GitHub repo)
|
|
63
|
+
// publish: {
|
|
64
|
+
// provider: "github",
|
|
65
|
+
// owner: "your-github-username",
|
|
66
|
+
// repo: "your-repo-name"
|
|
67
|
+
// }
|
|
68
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
dist-electron
|
|
14
|
+
*.local
|
|
15
|
+
|
|
16
|
+
# Electron build output
|
|
17
|
+
release/
|
|
18
|
+
out/
|
|
19
|
+
|
|
20
|
+
# Electron platform artifacts
|
|
21
|
+
*.dmg
|
|
22
|
+
*.exe
|
|
23
|
+
*.msi
|
|
24
|
+
*.AppImage
|
|
25
|
+
*.deb
|
|
26
|
+
*.snap
|
|
27
|
+
*.rpm
|
|
28
|
+
|
|
29
|
+
# Editor directories and files
|
|
30
|
+
.vscode/*
|
|
31
|
+
!.vscode/extensions.json
|
|
32
|
+
.idea
|
|
33
|
+
.DS_Store
|
|
34
|
+
*.suo
|
|
35
|
+
*.ntvs*
|
|
36
|
+
*.njsproj
|
|
37
|
+
*.sln
|
|
38
|
+
*.sw?
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "electron-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"description": "An Electron + Vite + React application built with @imksh/create",
|
|
6
|
+
"author": "imksh",
|
|
7
|
+
"main": "dist-electron/main.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "vite",
|
|
10
|
+
"build": "vite build && electron-builder",
|
|
11
|
+
"preview": "electron .",
|
|
12
|
+
"pack": "vite build && electron-builder --dir",
|
|
13
|
+
"dist": "vite build && electron-builder",
|
|
14
|
+
"dist:mac": "vite build && electron-builder --mac",
|
|
15
|
+
"dist:win": "vite build && electron-builder --win",
|
|
16
|
+
"dist:linux": "vite build && electron-builder --linux",
|
|
17
|
+
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@tailwindcss/vite": "^4.3.2",
|
|
21
|
+
"axios": "^1.6.0",
|
|
22
|
+
"flyonui": "^2.4.1",
|
|
23
|
+
"framer-motion": "^11.0.0",
|
|
24
|
+
"lenis": "^1.1.3",
|
|
25
|
+
"lottie-react": "^2.4.0",
|
|
26
|
+
"lucide-react": "^0.292.0",
|
|
27
|
+
"react": "^18.2.0",
|
|
28
|
+
"react-dom": "^18.2.0",
|
|
29
|
+
"react-hot-toast": "^2.4.1",
|
|
30
|
+
"react-icons": "^4.11.0",
|
|
31
|
+
"react-router-dom": "^6.18.0",
|
|
32
|
+
"tailwindcss": "^4.3.2",
|
|
33
|
+
"zustand": "^4.5.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "^18.2.37",
|
|
37
|
+
"@types/react-dom": "^18.2.15",
|
|
38
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
39
|
+
"electron": "^31.0.0",
|
|
40
|
+
"electron-builder": "^24.13.0",
|
|
41
|
+
"eslint": "^8.53.0",
|
|
42
|
+
"eslint-plugin-react": "^7.33.2",
|
|
43
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
44
|
+
"eslint-plugin-react-refresh": "^0.4.4",
|
|
45
|
+
"vite": "^5.0.0",
|
|
46
|
+
"vite-plugin-electron": "^0.28.0",
|
|
47
|
+
"vite-plugin-electron-renderer": "^0.14.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { Outlet, useLocation } from "react-router-dom";
|
|
3
|
+
import SidebarHeader from "../ui/SidebarHeader";
|
|
4
|
+
import Sidebar from "../ui/sidebar";
|
|
5
|
+
import TitleBar from "../ui/TitleBar";
|
|
6
|
+
import { useUiStore } from "../../store/useUiStore";
|
|
7
|
+
import appConfig from "../../config/appConfig";
|
|
8
|
+
|
|
9
|
+
const SidebarLayout = () => {
|
|
10
|
+
const { headerTitle, headerActions, setHeaderTitle, setHeaderActions } =
|
|
11
|
+
useUiStore();
|
|
12
|
+
const { pathname } = useLocation();
|
|
13
|
+
|
|
14
|
+
// Reset header title and actions whenever the route shifts
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setHeaderTitle(appConfig.app.name);
|
|
17
|
+
setHeaderActions(null);
|
|
18
|
+
}, [pathname, setHeaderTitle, setHeaderActions]);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex flex-col h-screen overflow-hidden">
|
|
22
|
+
{/* Electron custom title bar — only renders inside Electron */}
|
|
23
|
+
<TitleBar />
|
|
24
|
+
|
|
25
|
+
<div className="flex flex-1 overflow-hidden">
|
|
26
|
+
<Sidebar />
|
|
27
|
+
|
|
28
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
29
|
+
<SidebarHeader title={headerTitle} actions={headerActions} />
|
|
30
|
+
|
|
31
|
+
<main
|
|
32
|
+
className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
|
|
33
|
+
data-lenis-prevent
|
|
34
|
+
>
|
|
35
|
+
<Outlet />
|
|
36
|
+
</main>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default SidebarLayout;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { Minus, Square, X, Maximize2, Minimize2 } from "lucide-react";
|
|
3
|
+
import { useUiStore } from "../../store/useUiStore";
|
|
4
|
+
import appConfig from "../../config/appConfig";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TitleBar — custom frameless window title bar for Electron.
|
|
8
|
+
*
|
|
9
|
+
* Renders a draggable title bar with:
|
|
10
|
+
* - App icon + name on the left
|
|
11
|
+
* - Window controls (minimize / maximize / close) positioned per platform:
|
|
12
|
+
* · macOS: right side (native traffic lights are hidden via frame: false)
|
|
13
|
+
* · Windows/Linux: right side (Windows style)
|
|
14
|
+
*
|
|
15
|
+
* Only renders when running inside Electron (window.electronAPI is present).
|
|
16
|
+
*/
|
|
17
|
+
const TitleBar = () => {
|
|
18
|
+
const { windowMaximized, setWindowMaximized, minimizeWindow, maximizeWindow, closeWindow } =
|
|
19
|
+
useUiStore();
|
|
20
|
+
const [platform, setPlatform] = useState("win32");
|
|
21
|
+
|
|
22
|
+
const isElectron = Boolean(window.electronAPI);
|
|
23
|
+
|
|
24
|
+
// Don't render in browser
|
|
25
|
+
if (!isElectron) return null;
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
// Get platform for conditional layout
|
|
29
|
+
window.electronAPI.getPlatform().then(setPlatform);
|
|
30
|
+
|
|
31
|
+
// Sync initial maximize state
|
|
32
|
+
window.electronAPI.isMaximized().then(setWindowMaximized);
|
|
33
|
+
|
|
34
|
+
// Listen for window state changes from main process
|
|
35
|
+
window.electronAPI.onMaximizeChange((isMax) => {
|
|
36
|
+
setWindowMaximized(isMax);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
window.electronAPI.removeAllListeners("window:maximized-change");
|
|
41
|
+
};
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const isMac = platform === "darwin";
|
|
45
|
+
|
|
46
|
+
const windowControls = (
|
|
47
|
+
<div className={`flex items-center gap-1.5 ${isMac ? "mr-3" : "ml-auto"}`}>
|
|
48
|
+
{/* Minimize */}
|
|
49
|
+
<button
|
|
50
|
+
id="titlebar-minimize"
|
|
51
|
+
onClick={minimizeWindow}
|
|
52
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
53
|
+
text-base-content/50 hover:bg-warning/20 hover:text-warning
|
|
54
|
+
transition-all duration-150 group"
|
|
55
|
+
title="Minimize"
|
|
56
|
+
>
|
|
57
|
+
<Minus size={12} className="group-hover:scale-110 transition-transform" />
|
|
58
|
+
</button>
|
|
59
|
+
|
|
60
|
+
{/* Maximize / Restore */}
|
|
61
|
+
<button
|
|
62
|
+
id="titlebar-maximize"
|
|
63
|
+
onClick={maximizeWindow}
|
|
64
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
65
|
+
text-base-content/50 hover:bg-success/20 hover:text-success
|
|
66
|
+
transition-all duration-150 group"
|
|
67
|
+
title={windowMaximized ? "Restore" : "Maximize"}
|
|
68
|
+
>
|
|
69
|
+
{windowMaximized ? (
|
|
70
|
+
<Minimize2 size={11} className="group-hover:scale-110 transition-transform" />
|
|
71
|
+
) : (
|
|
72
|
+
<Maximize2 size={11} className="group-hover:scale-110 transition-transform" />
|
|
73
|
+
)}
|
|
74
|
+
</button>
|
|
75
|
+
|
|
76
|
+
{/* Close */}
|
|
77
|
+
<button
|
|
78
|
+
id="titlebar-close"
|
|
79
|
+
onClick={closeWindow}
|
|
80
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
81
|
+
text-base-content/50 hover:bg-error/20 hover:text-error
|
|
82
|
+
transition-all duration-150 group"
|
|
83
|
+
title="Close"
|
|
84
|
+
>
|
|
85
|
+
<X size={12} className="group-hover:scale-110 transition-transform" />
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
id="electron-titlebar"
|
|
93
|
+
className="h-10 shrink-0 flex items-center px-3
|
|
94
|
+
bg-base-200 border-b border-base-300
|
|
95
|
+
select-none"
|
|
96
|
+
// The entire bar is draggable except the buttons (buttons set no-drag via CSS)
|
|
97
|
+
style={{ WebkitAppRegion: "drag" }}
|
|
98
|
+
>
|
|
99
|
+
{/* macOS: controls on left */}
|
|
100
|
+
{isMac && (
|
|
101
|
+
<div style={{ WebkitAppRegion: "no-drag" }}>
|
|
102
|
+
{windowControls}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{/* App name — centered */}
|
|
107
|
+
<div className="flex items-center gap-2 mx-auto">
|
|
108
|
+
{appConfig.app.logo ? (
|
|
109
|
+
<img src={appConfig.app.logo} alt="" className="w-4 h-4 rounded" />
|
|
110
|
+
) : (
|
|
111
|
+
<div className="w-4 h-4 rounded bg-primary flex items-center justify-center text-primary-content text-[8px] font-bold">
|
|
112
|
+
{appConfig.app.name.charAt(0)}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
<span className="text-xs font-semibold text-base-content/70">
|
|
116
|
+
{appConfig.app.name}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* Windows/Linux: controls on right */}
|
|
121
|
+
{!isMac && (
|
|
122
|
+
<div style={{ WebkitAppRegion: "no-drag" }}>
|
|
123
|
+
{windowControls}
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default TitleBar;
|