@imksh/create 1.0.30 → 1.0.32
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 +2 -0
- package/package.json +1 -1
- package/src/commands/create.js +2 -0
- package/src/index.js +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/package.json +8 -2
- 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/.eslintrc.json +12 -0
- package/templates/layers/next/typescript/package.json +14 -3
- package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +2 -1
- package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
- 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 +205 -39
- 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 +3 -1
- 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/Header.tsx +2 -2
- 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/usePwaInstall.ts +10 -4
- package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/next/typescript/src/services/authService.ts +1 -1
- package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
- package/templates/layers/node/javascript/.env.example +3 -1
- package/templates/layers/node/javascript/package.json +12 -3
- package/templates/layers/node/javascript/src/app.js +2 -1
- package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
- package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
- package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
- package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
- package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node/typescript/.env.example +3 -1
- package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node/typescript/package-lock.json +2 -2
- package/templates/layers/node/typescript/package.json +17 -3
- package/templates/layers/node/typescript/src/app.ts +3 -2
- package/templates/layers/node/typescript/src/config/db.ts +3 -2
- package/templates/layers/node/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
- package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
- package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
- package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
- package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
- package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
- package/templates/layers/node-prisma/javascript/.env.example +3 -1
- package/templates/layers/node-prisma/javascript/package.json +11 -2
- package/templates/layers/node-prisma/javascript/src/app.js +2 -1
- package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
- package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
- package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
- package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
- package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node-prisma/typescript/.env.example +3 -1
- package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node-prisma/typescript/package.json +19 -5
- package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
- package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
- package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
- package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
- package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
- package/templates/layers/prisma/javascript/package.json +8 -0
- package/templates/layers/prisma/typescript/package.json +14 -1
- package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
- package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
- package/templates/layers/react/javascript/package.json +6 -0
- 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/.eslintrc.cjs +20 -0
- package/templates/layers/react/typescript/package.json +11 -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 +201 -38
- 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/typescript/src/main.tsx +11 -11
- package/templates/layers/react/typescript/src/services/authService.ts +1 -1
- package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/react-native/base/app.json +9 -22
- package/templates/layers/react-native/base/eslint.config.js +5 -1
- package/templates/layers/react-native/base/package.json +11 -1
- package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
- package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
- package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
- package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
- package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
- package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
- package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
- package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
- package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
- package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
- package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
- package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
- package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
- package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
- package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
- package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
- package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
- package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
- package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
- package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
- package/templates/layers/react-native/base/src/config/api.ts +5 -5
- package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
- package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
- package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
- package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
- package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
- package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
- package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
- package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
- package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
- package/templates/presets/electron/preset.json +9 -0
- package/templates/presets/electron-ts/preset.json +9 -0
- package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
- package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
- package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
- package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
- package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
- package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
- /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
package/README.md
CHANGED
|
@@ -58,6 +58,8 @@ imksh create my-app --template next --skip-install
|
|
|
58
58
|
- `next-ts`: Next.js App Router boilerplate with TypeScript.
|
|
59
59
|
- `react-native`: Expo React Native template with NativeWind, Zustand, and React Navigation.
|
|
60
60
|
- `mern-react-native`: Full-stack Expo React Native client + Express/Node server.
|
|
61
|
+
- `electron`: Electron + React boilerplate.
|
|
62
|
+
- `electron-ts`: Electron + React boilerplate with TypeScript.
|
|
61
63
|
|
|
62
64
|
## Adding Templates
|
|
63
65
|
|
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
package/src/index.js
CHANGED
|
@@ -24,7 +24,7 @@ program
|
|
|
24
24
|
.argument("<project-name>", "Name of the project")
|
|
25
25
|
.option(
|
|
26
26
|
"-t, --template <template>",
|
|
27
|
-
"Template to use (react, react-ts, node, node-ts, node-prisma, node-prisma-ts, mern, mern-ts, next, next-ts, react-native, mern-react-native, production-fullstack, fullstack-prisma)",
|
|
27
|
+
"Template to use (react, react-ts, node, node-ts, node-prisma, node-prisma-ts, mern, mern-ts, next, next-ts, react-native, mern-react-native, electron, electron-ts, production-fullstack, fullstack-prisma)",
|
|
28
28
|
)
|
|
29
29
|
.option("--skip-install", "Skip installing dependencies")
|
|
30
30
|
.option("--no-git", "Skip initializing a git repository")
|
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;
|