@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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const appConfig = {
|
|
2
|
+
app: {
|
|
3
|
+
name: "Electron App",
|
|
4
|
+
logo: "./logo.png",
|
|
5
|
+
shortName: "App",
|
|
6
|
+
description: "An Electron application built with @imksh/create",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
author: "imksh",
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
api: {
|
|
12
|
+
baseURL: import.meta.env.VITE_API_URL,
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
routes: {
|
|
17
|
+
home: "/",
|
|
18
|
+
login: "/login",
|
|
19
|
+
profile: "/profile",
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
pagination: {
|
|
23
|
+
pageSize: 10,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
github: {
|
|
27
|
+
repository: "",
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
social: {
|
|
31
|
+
website: "",
|
|
32
|
+
linkedin: "",
|
|
33
|
+
github: "",
|
|
34
|
+
twitter: "",
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
features: {
|
|
38
|
+
darkMode: true,
|
|
39
|
+
notifications: true,
|
|
40
|
+
sidebar: true,
|
|
41
|
+
electron: true,
|
|
42
|
+
customTitleBar: true,
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
electron: {
|
|
46
|
+
tray: true,
|
|
47
|
+
autoUpdater: false, // Set to true and configure publish in electron-builder.json5 to enable
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default appConfig;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useElectron — safely access window.electronAPI in the renderer.
|
|
5
|
+
*
|
|
6
|
+
* Returns typed wrappers for all IPC calls. Every method is a no-op
|
|
7
|
+
* (or returns a resolved promise) when running outside of Electron
|
|
8
|
+
* (e.g. during browser-based development or testing).
|
|
9
|
+
*
|
|
10
|
+
* @returns {{ isElectron: boolean } & ElectronAPI}
|
|
11
|
+
*/
|
|
12
|
+
export function useElectron() {
|
|
13
|
+
const isElectron = typeof window !== "undefined" && Boolean(window.electronAPI);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
isElectron,
|
|
17
|
+
|
|
18
|
+
// Window controls
|
|
19
|
+
minimize: () => window.electronAPI?.minimize(),
|
|
20
|
+
maximize: () => window.electronAPI?.maximize(),
|
|
21
|
+
close: () => window.electronAPI?.close(),
|
|
22
|
+
|
|
23
|
+
// Window state
|
|
24
|
+
isMaximized: () => window.electronAPI?.isMaximized() ?? Promise.resolve(false),
|
|
25
|
+
|
|
26
|
+
// App info
|
|
27
|
+
getVersion: () => window.electronAPI?.getVersion() ?? Promise.resolve("web"),
|
|
28
|
+
getPlatform: () => window.electronAPI?.getPlatform() ?? Promise.resolve("web"),
|
|
29
|
+
|
|
30
|
+
// Shell
|
|
31
|
+
openExternal: (url) =>
|
|
32
|
+
window.electronAPI?.openExternal(url) ?? Promise.resolve(),
|
|
33
|
+
|
|
34
|
+
// Event subscription helpers
|
|
35
|
+
onMaximizeChange: (cb) => window.electronAPI?.onMaximizeChange(cb),
|
|
36
|
+
onMenuAction: (cb) => window.electronAPI?.onMenuAction(cb),
|
|
37
|
+
|
|
38
|
+
// Cleanup
|
|
39
|
+
removeAllListeners: (channel) =>
|
|
40
|
+
window.electronAPI?.removeAllListeners(channel),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* useElectronEvent — subscribe to an Electron IPC event in a React component.
|
|
46
|
+
*
|
|
47
|
+
* Automatically cleans up the listener on unmount.
|
|
48
|
+
*
|
|
49
|
+
* @param {"maximize" | "menu"} event
|
|
50
|
+
* @param {Function} callback
|
|
51
|
+
*/
|
|
52
|
+
export function useElectronEvent(event, callback) {
|
|
53
|
+
const electron = useElectron();
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!electron.isElectron) return;
|
|
57
|
+
|
|
58
|
+
if (event === "maximize") {
|
|
59
|
+
electron.onMaximizeChange(callback);
|
|
60
|
+
return () => electron.removeAllListeners("window:maximized-change");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (event === "menu") {
|
|
64
|
+
electron.onMenuAction(callback);
|
|
65
|
+
return () => electron.removeAllListeners("menu:preferences");
|
|
66
|
+
}
|
|
67
|
+
}, [event, callback, electron.isElectron]);
|
|
68
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import { HashRouter } from "react-router-dom";
|
|
4
|
+
import App from "./App.jsx";
|
|
5
|
+
import "./index.css";
|
|
6
|
+
|
|
7
|
+
// HashRouter is required for Electron because the renderer is served via
|
|
8
|
+
// the file:// protocol in production, which breaks BrowserRouter's history API.
|
|
9
|
+
// Hash-based URLs (#/path) work correctly in both dev and packaged builds.
|
|
10
|
+
|
|
11
|
+
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
12
|
+
<React.StrictMode>
|
|
13
|
+
<HashRouter>
|
|
14
|
+
<App />
|
|
15
|
+
</HashRouter>
|
|
16
|
+
</React.StrictMode>,
|
|
17
|
+
);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
const useUiStore = create((set, get) => ({
|
|
4
|
+
open: false,
|
|
5
|
+
isModalOpen: false,
|
|
6
|
+
collapsed: false,
|
|
7
|
+
isMobileMenuOpened: false,
|
|
8
|
+
headerTitle: "Dashboard",
|
|
9
|
+
headerActions: null,
|
|
10
|
+
breadcrumbs: null,
|
|
11
|
+
layoutClass: "",
|
|
12
|
+
|
|
13
|
+
// ── Layout helpers ──────────────────────────────────────────────────────────
|
|
14
|
+
setLayoutClass: (layoutClass) => set({ layoutClass }),
|
|
15
|
+
setHeaderTitle: (title) => set({ headerTitle: title }),
|
|
16
|
+
setHeaderActions: (actions) => set({ headerActions: actions }),
|
|
17
|
+
setBreadcrumbs: (breadcrumbs) => set({ breadcrumbs }),
|
|
18
|
+
|
|
19
|
+
// ── Sidebar ─────────────────────────────────────────────────────────────────
|
|
20
|
+
toggleCollapsed: () => set((state) => ({ collapsed: !state.collapsed })),
|
|
21
|
+
setMobileMenuOpened: (open) => set({ isMobileMenuOpened: open }),
|
|
22
|
+
toggleMobileMenuOpened: () =>
|
|
23
|
+
set((state) => ({ isMobileMenuOpened: !state.isMobileMenuOpened })),
|
|
24
|
+
toggleSidebar: () => set((state) => ({ open: !state.open })),
|
|
25
|
+
openSidebar: () => set({ open: true }),
|
|
26
|
+
close: () => set({ open: false }),
|
|
27
|
+
|
|
28
|
+
// ── Modal ───────────────────────────────────────────────────────────────────
|
|
29
|
+
setModalOpen: (open) => set({ isModalOpen: open }),
|
|
30
|
+
|
|
31
|
+
// ── Theme ───────────────────────────────────────────────────────────────────
|
|
32
|
+
theme: localStorage.getItem("theme") || "light",
|
|
33
|
+
setTheme: (theme) => {
|
|
34
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
35
|
+
localStorage.setItem("theme", theme);
|
|
36
|
+
set({ theme });
|
|
37
|
+
},
|
|
38
|
+
toggleTheme: () => {
|
|
39
|
+
const nextTheme = get().theme === "light" ? "dark" : "light";
|
|
40
|
+
document.documentElement.setAttribute("data-theme", nextTheme);
|
|
41
|
+
localStorage.setItem("theme", nextTheme);
|
|
42
|
+
set({ theme: nextTheme });
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// ── Electron window state ───────────────────────────────────────────────────
|
|
46
|
+
windowMaximized: false,
|
|
47
|
+
setWindowMaximized: (value) => set({ windowMaximized: value }),
|
|
48
|
+
|
|
49
|
+
/** Minimize the Electron window (no-op in browser) */
|
|
50
|
+
minimizeWindow: () => window.electronAPI?.minimize(),
|
|
51
|
+
|
|
52
|
+
/** Toggle maximize / restore the Electron window (no-op in browser) */
|
|
53
|
+
maximizeWindow: () => window.electronAPI?.maximize(),
|
|
54
|
+
|
|
55
|
+
/** Close the Electron window (no-op in browser) */
|
|
56
|
+
closeWindow: () => window.electronAPI?.close(),
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
export { useUiStore };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
4
|
+
import electron from 'vite-plugin-electron';
|
|
5
|
+
import renderer from 'vite-plugin-electron-renderer';
|
|
6
|
+
|
|
7
|
+
// https://vitejs.dev/config/
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
plugins: [
|
|
10
|
+
react(),
|
|
11
|
+
tailwindcss(),
|
|
12
|
+
electron([
|
|
13
|
+
{
|
|
14
|
+
// Main process entry
|
|
15
|
+
entry: 'electron/main.js',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
// Preload script — reload renderer on change
|
|
19
|
+
entry: 'electron/preload.js',
|
|
20
|
+
onstart(options) {
|
|
21
|
+
options.reload();
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
]),
|
|
25
|
+
renderer(),
|
|
26
|
+
],
|
|
27
|
+
// Critical: ensures built assets resolve correctly via file:// in production
|
|
28
|
+
base: './',
|
|
29
|
+
});
|
|
@@ -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,213 @@
|
|
|
1
|
+
import {
|
|
2
|
+
app,
|
|
3
|
+
BrowserWindow,
|
|
4
|
+
Menu,
|
|
5
|
+
Tray,
|
|
6
|
+
shell,
|
|
7
|
+
ipcMain,
|
|
8
|
+
nativeImage,
|
|
9
|
+
MenuItemConstructorOptions,
|
|
10
|
+
} from 'electron';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
17
|
+
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
|
|
18
|
+
const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'];
|
|
19
|
+
const preloadPath = path.join(__dirname, 'preload.js');
|
|
20
|
+
|
|
21
|
+
let mainWindow: BrowserWindow | null = null;
|
|
22
|
+
let tray: Tray | null = null;
|
|
23
|
+
|
|
24
|
+
// ─── Window ───────────────────────────────────────────────────────────────────
|
|
25
|
+
function createWindow(): void {
|
|
26
|
+
mainWindow = new BrowserWindow({
|
|
27
|
+
width: 1280,
|
|
28
|
+
height: 800,
|
|
29
|
+
minWidth: 800,
|
|
30
|
+
minHeight: 600,
|
|
31
|
+
frame: false,
|
|
32
|
+
titleBarStyle: 'hidden',
|
|
33
|
+
trafficLightPosition: { x: 16, y: 16 },
|
|
34
|
+
backgroundColor: '#0f172a',
|
|
35
|
+
show: false,
|
|
36
|
+
icon: path.join(__dirname, '../public/icon.png'),
|
|
37
|
+
webPreferences: {
|
|
38
|
+
preload: preloadPath,
|
|
39
|
+
contextIsolation: true,
|
|
40
|
+
nodeIntegration: false,
|
|
41
|
+
sandbox: false,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
mainWindow.once('ready-to-show', () => {
|
|
46
|
+
mainWindow!.show();
|
|
47
|
+
if (isDev) mainWindow!.webContents.openDevTools({ mode: 'detach' });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
mainWindow.on('maximize', () => mainWindow?.webContents.send('window:maximized-change', true));
|
|
51
|
+
mainWindow.on('unmaximize', () => mainWindow?.webContents.send('window:maximized-change', false));
|
|
52
|
+
|
|
53
|
+
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
54
|
+
shell.openExternal(url);
|
|
55
|
+
return { action: 'deny' };
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (isDev && VITE_DEV_SERVER_URL) {
|
|
59
|
+
mainWindow.loadURL(VITE_DEV_SERVER_URL);
|
|
60
|
+
} else {
|
|
61
|
+
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Native Application Menu ──────────────────────────────────────────────────
|
|
66
|
+
function buildMenu(): void {
|
|
67
|
+
const isMac = process.platform === 'darwin';
|
|
68
|
+
|
|
69
|
+
const template: MenuItemConstructorOptions[] = [
|
|
70
|
+
...(isMac
|
|
71
|
+
? [{
|
|
72
|
+
label: app.name,
|
|
73
|
+
submenu: [
|
|
74
|
+
{ role: 'about' as const },
|
|
75
|
+
{ type: 'separator' as const },
|
|
76
|
+
{ label: 'Preferences…', accelerator: 'CmdOrCtrl+,', click: () => mainWindow?.webContents.send('menu:preferences') },
|
|
77
|
+
{ type: 'separator' as const },
|
|
78
|
+
{ role: 'services' as const },
|
|
79
|
+
{ type: 'separator' as const },
|
|
80
|
+
{ role: 'hide' as const },
|
|
81
|
+
{ role: 'hideOthers' as const },
|
|
82
|
+
{ role: 'unhide' as const },
|
|
83
|
+
{ type: 'separator' as const },
|
|
84
|
+
{ role: 'quit' as const },
|
|
85
|
+
],
|
|
86
|
+
}]
|
|
87
|
+
: []),
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
label: 'File',
|
|
91
|
+
submenu: [
|
|
92
|
+
{ label: 'New Window', accelerator: 'CmdOrCtrl+Shift+N', click: () => createWindow() },
|
|
93
|
+
{ type: 'separator' },
|
|
94
|
+
isMac ? { role: 'close' as const } : { role: 'quit' as const },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
{
|
|
99
|
+
label: 'Edit',
|
|
100
|
+
submenu: [
|
|
101
|
+
{ role: 'undo' as const },
|
|
102
|
+
{ role: 'redo' as const },
|
|
103
|
+
{ type: 'separator' as const },
|
|
104
|
+
{ role: 'cut' as const },
|
|
105
|
+
{ role: 'copy' as const },
|
|
106
|
+
{ role: 'paste' as const },
|
|
107
|
+
...(isMac
|
|
108
|
+
? [{ role: 'pasteAndMatchStyle' as const }, { role: 'delete' as const }, { role: 'selectAll' as const }]
|
|
109
|
+
: [{ role: 'delete' as const }, { type: 'separator' as const }, { role: 'selectAll' as const }]),
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
{
|
|
114
|
+
label: 'View',
|
|
115
|
+
submenu: [
|
|
116
|
+
{ role: 'reload' as const },
|
|
117
|
+
{ role: 'forceReload' as const },
|
|
118
|
+
{ role: 'toggleDevTools' as const },
|
|
119
|
+
{ type: 'separator' as const },
|
|
120
|
+
{ role: 'resetZoom' as const },
|
|
121
|
+
{ role: 'zoomIn' as const },
|
|
122
|
+
{ role: 'zoomOut' as const },
|
|
123
|
+
{ type: 'separator' as const },
|
|
124
|
+
{ role: 'togglefullscreen' as const },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
label: 'Window',
|
|
130
|
+
submenu: [
|
|
131
|
+
{ role: 'minimize' as const },
|
|
132
|
+
{ role: 'zoom' as const },
|
|
133
|
+
...(isMac
|
|
134
|
+
? [{ type: 'separator' as const }, { role: 'front' as const }, { type: 'separator' as const }, { role: 'window' as const }]
|
|
135
|
+
: [{ role: 'close' as const }]),
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
role: 'help' as const,
|
|
141
|
+
submenu: [
|
|
142
|
+
{ label: 'Learn More', click: () => shell.openExternal('https://github.com/imksh/create') },
|
|
143
|
+
{
|
|
144
|
+
label: 'Toggle Developer Tools',
|
|
145
|
+
accelerator: isMac ? 'Alt+Cmd+I' : 'Alt+Shift+I',
|
|
146
|
+
click: () => mainWindow?.webContents.toggleDevTools(),
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ─── System Tray ──────────────────────────────────────────────────────────────
|
|
156
|
+
function createTray(): void {
|
|
157
|
+
const iconPath = path.join(__dirname, '../public/icon.png');
|
|
158
|
+
const trayIcon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 });
|
|
159
|
+
tray = new Tray(trayIcon);
|
|
160
|
+
|
|
161
|
+
const contextMenu = Menu.buildFromTemplate([
|
|
162
|
+
{ label: 'Show App', click: () => { mainWindow?.show(); mainWindow?.focus(); } },
|
|
163
|
+
{ type: 'separator' },
|
|
164
|
+
{ label: 'Quit', click: () => { (app as any).isQuiting = true; app.quit(); } },
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
tray.setToolTip(app.name);
|
|
168
|
+
tray.setContextMenu(contextMenu);
|
|
169
|
+
tray.on('double-click', () => { mainWindow?.show(); mainWindow?.focus(); });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ─── IPC Handlers ─────────────────────────────────────────────────────────────
|
|
173
|
+
function registerIpcHandlers(): void {
|
|
174
|
+
ipcMain.on('window:minimize', () => mainWindow?.minimize());
|
|
175
|
+
ipcMain.on('window:maximize', () => {
|
|
176
|
+
mainWindow?.isMaximized() ? mainWindow.unmaximize() : mainWindow?.maximize();
|
|
177
|
+
});
|
|
178
|
+
ipcMain.on('window:close', () => mainWindow?.close());
|
|
179
|
+
|
|
180
|
+
ipcMain.handle('window:isMaximized', () => mainWindow?.isMaximized() ?? false);
|
|
181
|
+
ipcMain.handle('app:version', () => app.getVersion());
|
|
182
|
+
ipcMain.handle('app:platform', () => process.platform);
|
|
183
|
+
|
|
184
|
+
ipcMain.handle('shell:openExternal', (_event, url: string) => {
|
|
185
|
+
if (typeof url === 'string' && url.startsWith('http')) {
|
|
186
|
+
return shell.openExternal(url);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── App Lifecycle ────────────────────────────────────────────────────────────
|
|
192
|
+
app.whenReady().then(() => {
|
|
193
|
+
if (process.platform === 'darwin') {
|
|
194
|
+
app.setAboutPanelOptions({
|
|
195
|
+
applicationName: app.name,
|
|
196
|
+
applicationVersion: app.getVersion(),
|
|
197
|
+
copyright: `© ${new Date().getFullYear()} imksh`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
registerIpcHandlers();
|
|
202
|
+
buildMenu();
|
|
203
|
+
createWindow();
|
|
204
|
+
createTray();
|
|
205
|
+
|
|
206
|
+
app.on('activate', () => {
|
|
207
|
+
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
app.on('window-all-closed', () => {
|
|
212
|
+
if (process.platform !== 'darwin') app.quit();
|
|
213
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ElectronAPI — typed interface exposed to the renderer via window.electronAPI.
|
|
5
|
+
* Declare this in vite-env.d.ts to get full type safety in the renderer.
|
|
6
|
+
*/
|
|
7
|
+
export interface ElectronAPI {
|
|
8
|
+
// Window controls
|
|
9
|
+
minimize: () => void;
|
|
10
|
+
maximize: () => void;
|
|
11
|
+
close: () => void;
|
|
12
|
+
|
|
13
|
+
// Window state
|
|
14
|
+
isMaximized: () => Promise<boolean>;
|
|
15
|
+
|
|
16
|
+
// App info
|
|
17
|
+
getVersion: () => Promise<string>;
|
|
18
|
+
getPlatform: () => Promise<NodeJS.Platform>;
|
|
19
|
+
|
|
20
|
+
// Shell
|
|
21
|
+
openExternal: (url: string) => Promise<void>;
|
|
22
|
+
|
|
23
|
+
// Event subscriptions
|
|
24
|
+
onMaximizeChange: (callback: (isMaximized: boolean) => void) => void;
|
|
25
|
+
onMenuAction: (callback: (data: { label: string }) => void) => void;
|
|
26
|
+
|
|
27
|
+
// Cleanup
|
|
28
|
+
removeAllListeners: (channel: string) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
32
|
+
minimize: () => ipcRenderer.send('window:minimize'),
|
|
33
|
+
maximize: () => ipcRenderer.send('window:maximize'),
|
|
34
|
+
close: () => ipcRenderer.send('window:close'),
|
|
35
|
+
|
|
36
|
+
isMaximized: () => ipcRenderer.invoke('window:isMaximized'),
|
|
37
|
+
|
|
38
|
+
getVersion: () => ipcRenderer.invoke('app:version'),
|
|
39
|
+
getPlatform: () => ipcRenderer.invoke('app:platform'),
|
|
40
|
+
|
|
41
|
+
openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url),
|
|
42
|
+
|
|
43
|
+
onMaximizeChange: (callback: (isMaximized: boolean) => void) => {
|
|
44
|
+
ipcRenderer.on('window:maximized-change', (_event, value: boolean) => callback(value));
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
onMenuAction: (callback: (data: { label: string }) => void) => {
|
|
48
|
+
ipcRenderer.on('menu:preferences', (_event, data) => callback({ label: 'preferences', ...data }));
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
removeAllListeners: (channel: string) => ipcRenderer.removeAllListeners(channel),
|
|
52
|
+
} satisfies ElectronAPI);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "electron-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"description": "An Electron + Vite + React (TypeScript) application built with @imksh/create",
|
|
6
|
+
"author": "imksh",
|
|
7
|
+
"main": "dist-electron/main.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "vite",
|
|
10
|
+
"build": "tsc && vite build && electron-builder",
|
|
11
|
+
"preview": "electron .",
|
|
12
|
+
"pack": "tsc && vite build && electron-builder --dir",
|
|
13
|
+
"dist": "tsc && vite build && electron-builder",
|
|
14
|
+
"dist:mac": "tsc && vite build && electron-builder --mac",
|
|
15
|
+
"dist:win": "tsc && vite build && electron-builder --win",
|
|
16
|
+
"dist:linux": "tsc && vite build && electron-builder --linux",
|
|
17
|
+
"lint": "eslint . --ext ts,tsx --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
|
+
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
|
39
|
+
"@typescript-eslint/parser": "^6.10.0",
|
|
40
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
41
|
+
"electron": "^31.0.0",
|
|
42
|
+
"electron-builder": "^24.13.0",
|
|
43
|
+
"eslint": "^8.53.0",
|
|
44
|
+
"eslint-plugin-react": "^7.33.2",
|
|
45
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
46
|
+
"eslint-plugin-react-refresh": "^0.4.4",
|
|
47
|
+
"typescript": "^5.2.2",
|
|
48
|
+
"vite": "^5.0.0",
|
|
49
|
+
"vite-plugin-electron": "^0.28.0",
|
|
50
|
+
"vite-plugin-electron-renderer": "^0.14.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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: React.FC = () => {
|
|
10
|
+
const { headerTitle, headerActions, setHeaderTitle, setHeaderActions } =
|
|
11
|
+
useUiStore();
|
|
12
|
+
const { pathname } = useLocation();
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setHeaderTitle(appConfig.app.name);
|
|
16
|
+
setHeaderActions(null);
|
|
17
|
+
}, [pathname, setHeaderTitle, setHeaderActions]);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex flex-col h-screen overflow-hidden">
|
|
21
|
+
{/* Electron custom title bar — only renders inside Electron */}
|
|
22
|
+
<TitleBar />
|
|
23
|
+
|
|
24
|
+
<div className="flex flex-1 overflow-hidden">
|
|
25
|
+
<Sidebar />
|
|
26
|
+
|
|
27
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
28
|
+
<SidebarHeader title={headerTitle} actions={headerActions} />
|
|
29
|
+
|
|
30
|
+
<main
|
|
31
|
+
className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
|
|
32
|
+
data-lenis-prevent
|
|
33
|
+
>
|
|
34
|
+
<Outlet />
|
|
35
|
+
</main>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default SidebarLayout;
|