From 375725c7d7c5ffe4b28d77002156ed0edf901b9f Mon Sep 17 00:00:00 2001 From: nkhangg Date: Tue, 6 May 2025 18:50:07 +0700 Subject: [PATCH] fix build env --- .env | 2 +- .env.example | 1 + .gitignore | 1 + electron-builder.json5 | 71 ++++---- electron/main.ts | 232 +++++++++++------------- electron/windows/mails.window.ts | 25 +++ package-lock.json | 35 ---- package.json | 2 - src/apis/index.ts | 80 ++++----- src/components/confirm-modal.tsx | 50 +++--- src/components/message.tsx | 94 +++++----- src/pages/mails.tsx | 292 +++++++++++++++---------------- src/pages/main.tsx | 171 +++++++++--------- src/vite-env.d.ts | 25 ++- tailwind.config.js | 28 +++ 15 files changed, 536 insertions(+), 573 deletions(-) create mode 100644 .env.example create mode 100644 electron/windows/mails.window.ts create mode 100644 tailwind.config.js diff --git a/.env b/.env index 781d4de..5e7091c 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -API_KEY = '@yQveRWre@b(!_9HmL' \ No newline at end of file +VITE_API_KEY = '@yQveRWre@b(!_9HmL' \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..194a2de --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +VITE_API_KEY = '' \ No newline at end of file diff --git a/.gitignore b/.gitignore index f4bb2e4..7d8596e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ dist-electron release dist-ssr *.local +.env # Editor directories and files .vscode/* diff --git a/electron-builder.json5 b/electron-builder.json5 index cd633dc..4ebb93b 100644 --- a/electron-builder.json5 +++ b/electron-builder.json5 @@ -1,43 +1,34 @@ // @see - https://www.electron.build/configuration/configuration { - "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", - "appId": "YourAppID", - "asar": true, - "productName": "YourAppName", - "directories": { - "output": "release/${version}" - }, - "files": [ - "dist", - "dist-electron" - ], - "mac": { - "target": [ - "dmg" - ], - "artifactName": "${productName}-Mac-${version}-Installer.${ext}" - }, - "win": { - "target": [ - { - "target": "nsis", - "arch": [ - "x64" - ] - } - ], - "artifactName": "${productName}-Windows-${version}-Setup.${ext}" - }, - "nsis": { - "oneClick": false, - "perMachine": false, - "allowToChangeInstallationDirectory": true, - "deleteAppDataOnUninstall": false - }, - "linux": { - "target": [ - "AppImage" - ], - "artifactName": "${productName}-Linux-${version}.${ext}" - } + $schema: 'https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json', + appId: 'b1730d2e-6f3c-4f92-9f4a-9f8e918b40d2', + asar: true, + productName: 'Zulip messages', + directories: { + output: 'release/${version}', + }, + files: ['dist', 'dist-electron'], + mac: { + target: ['dmg'], + artifactName: '${productName}-Mac-${version}-Installer.${ext}', + }, + win: { + target: [ + { + target: 'nsis', + arch: ['x64'], + }, + ], + artifactName: '${productName}-Windows-${version}-Setup.${ext}', + }, + nsis: { + oneClick: false, + perMachine: false, + allowToChangeInstallationDirectory: true, + deleteAppDataOnUninstall: false, + }, + linux: { + target: ['AppImage'], + artifactName: '${productName}-Linux-${version}.${ext}', + }, } diff --git a/electron/main.ts b/electron/main.ts index 4256207..5ec1404 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,17 +1,13 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { config } from "dotenv"; -import { app, BrowserWindow, ipcMain, Notification, screen } from "electron"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { io } from "socket.io-client"; -import { WebSocket } from "ws"; -import { addEmail, deleteEmail, fetchEmails, fetchMessages } from "../src/apis"; +import { app, BrowserWindow, ipcMain, Menu, Notification, screen } from 'electron'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { io } from 'socket.io-client'; +import { addEmail, deleteEmail, fetchEmails, fetchMessages } from '../src/apis'; +import { createMailWindow } from './windows/mails.window'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -// dot env -config(); - // The built directory structure // // ├─┬─┬ dist @@ -21,174 +17,142 @@ config(); // │ │ ├── main.js // │ │ └── preload.mjs // │ -process.env.APP_ROOT = path.join(__dirname, ".."); - -let newWin: BrowserWindow | null = null; +process.env.APP_ROOT = path.join(__dirname, '..'); // 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x -export const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; -export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron"); -export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist"); +export const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']; +export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron'); +export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist'); -process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL - ? path.join(process.env.APP_ROOT, "public") - : RENDERER_DIST; +process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST; let win: BrowserWindow | null; +Menu.setApplicationMenu(null); + function createWindow() { - // Lấy thông tin tất cả các màn hình - const displays = screen.getAllDisplays(); + // Lấy thông tin tất cả các màn hình + const displays = screen.getAllDisplays(); - // Lấy vị trí con trỏ chuột - const cursorPoint = screen.getCursorScreenPoint(); + // Lấy vị trí con trỏ chuột + const cursorPoint = screen.getCursorScreenPoint(); - // Tìm màn hình có chứa con trỏ chuột - let display = displays.find((display) => { - const { x, y, width, height } = display.bounds; - return ( - cursorPoint.x >= x && - cursorPoint.x <= x + width && - cursorPoint.y >= y && - cursorPoint.y <= y + height - ); - }); + // Tìm màn hình có chứa con trỏ chuột + let display = displays.find((display) => { + const { x, y, width, height } = display.bounds; + return cursorPoint.x >= x && cursorPoint.x <= x + width && cursorPoint.y >= y && cursorPoint.y <= y + height; + }); - // Nếu không tìm thấy màn hình chứa con trỏ, sử dụng màn hình chính - if (!display) { - display = screen.getPrimaryDisplay(); - } + // Nếu không tìm thấy màn hình chứa con trỏ, sử dụng màn hình chính + if (!display) { + display = screen.getPrimaryDisplay(); + } - const { width, height } = display.workAreaSize; + const { width, height } = display.workAreaSize; - // Vị trí cửa sổ ở góc phải dưới của màn hình đã chọn + // Vị trí cửa sổ ở góc phải dưới của màn hình đã chọn - win = new BrowserWindow({ - width: 600, - height: 200, - x: 0, // Đặt cửa sổ ở góc phải - y: 0, // Đặt cửa sổ ở góc dưới - alwaysOnTop: true, // Cửa sổ luôn nằm trên các cửa sổ khác - resizable: true, // Không cho phép thay đổi kích thước - icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"), - webPreferences: { - preload: path.join(__dirname, "preload.mjs"), - }, - }); + win = new BrowserWindow({ + width: 600, + height: 200, + x: 0, // Đặt cửa sổ ở góc phải + y: 0, // Đặt cửa sổ ở góc dưới + alwaysOnTop: true, // Cửa sổ luôn nằm trên các cửa sổ khác + resizable: true, // Không cho phép thay đổi kích thước + icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'), + webPreferences: { + preload: path.join(__dirname, 'preload.mjs'), + }, + }); - // Test active push message to Renderer-process. - win.webContents.on("did-finish-load", () => { - win?.webContents.send("main-process-message", new Date().toLocaleString()); - }); + // Test active push message to Renderer-process. + win.webContents.on('did-finish-load', () => { + win?.webContents.send('main-process-message', new Date().toLocaleString()); + }); - win.setPosition(width - 600, height - 200); + win.setPosition(width - 600, height - 200); - if (VITE_DEV_SERVER_URL) { - win.loadURL(VITE_DEV_SERVER_URL); - } else { - // win.loadFile('dist/index.html') - win.loadFile(path.join(RENDERER_DIST, "index.html")); - } + if (VITE_DEV_SERVER_URL) { + win.loadURL(VITE_DEV_SERVER_URL); + } else { + // win.loadFile('dist/index.html') + win.loadFile(path.join(RENDERER_DIST, 'index.html')); + } } // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. -app.on("window-all-closed", () => { - if (process.platform !== "darwin") { - app.quit(); - win = null; - } +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + win = null; + } }); -app.on("activate", () => { - // On OS X it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } +app.on('activate', () => { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } }); app.whenReady().then(createWindow); // IPC Main Events -ipcMain.on("open-devtools", (event) => { - const webContents = event.sender; - webContents.openDevTools({ mode: "detach" }); +ipcMain.on('open-devtools', (event) => { + const webContents = event.sender; + webContents.openDevTools({ mode: 'detach' }); }); // Xử lý connect socket -ipcMain.handle("connect-socket", async (_) => { - const socket = io("https://zulip.ipsupply.com.au", { - path: "/apac-custom/socket.io", - secure: true, - query: { token: process.env.API_KEY }, - rejectUnauthorized: false, - }); +ipcMain.handle('connect-socket', async (_) => { + const socket = io('https://zulip.ipsupply.com.au', { + path: '/apac-custom/socket.io', + secure: true, + query: { token: import.meta.env.VITE_API_KEY }, + rejectUnauthorized: false, + }); - if (!socket.connected) { - socket.connect(); - } + if (!socket.connected) { + socket.connect(); + } - socket.on("connect", () => { - console.log(socket.connected); // true - }); + socket.on('connect', () => { + console.log(socket.connected); // true + }); - socket.on("newNote", (data) => { - win?.webContents.send("newNote", data); - }); + socket.on('newNote', (data) => { + win?.webContents.send('newNote', data); + }); }); -ipcMain.handle("fetchMessages", async () => { - return await fetchMessages(); +ipcMain.handle('fetchMessages', async () => { + return await fetchMessages(); }); -ipcMain.handle("fetchEmails", async () => { - return await fetchEmails(); +ipcMain.handle('fetchEmails', async () => { + return await fetchEmails(); }); -ipcMain.handle("open-new-window", async () => { - createMailWindow(); +ipcMain.handle('open-new-window', async () => { + createMailWindow({ RENDERER_DIST }); }); -ipcMain.handle("add-email", async (_, email: string) => { - return addEmail(email); +ipcMain.handle('add-email', async (_, email: string) => { + return addEmail(email); }); -ipcMain.handle("del-email", async (_, id: number) => { - return deleteEmail(id); +ipcMain.handle('del-email', async (_, id: number) => { + return deleteEmail(id); }); -ipcMain.handle("show-notification", async (_, { title, body }) => { - const notification = new Notification({ - title, - body, - silent: false, - }); - notification.show(); +ipcMain.handle('show-notification', async (_, { title, body }) => { + const notification = new Notification({ + title, + body, + silent: false, + }); + notification.show(); }); - -// Funtions -export function createMailWindow() { - newWin = new BrowserWindow({ - width: 600, - height: 400, - webPreferences: { - preload: path.join(__dirname, "preload.mjs"), - }, - }); - - // Nếu đang phát triển, sử dụng HashRouter trong URL - const url = process.env.VITE_DEV_SERVER_URL - ? `${process.env.VITE_DEV_SERVER_URL}/#/mails` // URL với HashRouter - : path.join(RENDERER_DIST, "index.html"); // Nếu build, dùng đúng path - - newWin.loadURL(url); - - // 👉 Mở DevTools sau khi load - newWin.webContents.openDevTools(); - - newWin.on("closed", () => { - newWin = null; - }); -} diff --git a/electron/windows/mails.window.ts b/electron/windows/mails.window.ts new file mode 100644 index 0000000..ffa70e8 --- /dev/null +++ b/electron/windows/mails.window.ts @@ -0,0 +1,25 @@ +import { BrowserWindow } from 'electron'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +process.env.APP_ROOT = path.join(__dirname, '..'); + +export function createMailWindow({ RENDERER_DIST }: { RENDERER_DIST: string }) { + let newWin: BrowserWindow | null = new BrowserWindow({ + width: 600, + height: 400, + webPreferences: { + preload: path.join(__dirname, 'preload.mjs'), + }, + }); + + const url = process.env.VITE_DEV_SERVER_URL ? `${process.env.VITE_DEV_SERVER_URL}/#/mails` : `file://${path.join(RENDERER_DIST, 'index.html')}#/mails`; + + newWin.loadURL(url); + + newWin.on('closed', () => { + newWin = null; + }); + + return newWin; +} diff --git a/package-lock.json b/package-lock.json index 3f909f2..2f73f97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,14 +16,12 @@ "@types/ws": "^8.18.1", "axios": "^1.9.0", "bufferutil": "^4.0.9", - "dotenv": "^16.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^7.5.3", "socket.io-client": "^4.8.1", "tailwindcss": "^4.1.4", "utf-8-validate": "^6.0.5", - "ws": "^8.18.2", "zod": "^3.24.4" }, "devDependencies": { @@ -3916,18 +3914,6 @@ "node": ">=6.0.0" } }, - "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dotenv-expand": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", @@ -8228,27 +8214,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", diff --git a/package.json b/package.json index 73bb37c..02cdd57 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,12 @@ "@types/ws": "^8.18.1", "axios": "^1.9.0", "bufferutil": "^4.0.9", - "dotenv": "^16.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^7.5.3", "socket.io-client": "^4.8.1", "tailwindcss": "^4.1.4", "utf-8-validate": "^6.0.5", - "ws": "^8.18.2", "zod": "^3.24.4" }, "devDependencies": { diff --git a/src/apis/index.ts b/src/apis/index.ts index 91446af..e33c169 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -1,61 +1,55 @@ -import { config } from "dotenv"; -import axios from "../instants/axios"; +import axios from '../instants/axios'; -config(); - -const API_KEY = process.env.API_KEY; +const API_KEY = import.meta.env.VITE_API_KEY; export const getAllNote = async () => { - try { - const response = await axios({ - method: "GET", - url: "getAllNotes", - }); + try { + const response = await axios({ + method: 'GET', + url: 'getAllNotes', + }); - return response.data; - } catch (error) { - return []; - } + return response.data; + } catch (error) { + return []; + } }; export async function fetchMessages(onError?: (error: unknown) => void) { - try { - const response = await axios.get(`getAllNotes?key=${API_KEY}`); + try { + const response = await axios.get(`getAllNotes?key=${API_KEY}`); - return response.data; - } catch (error) { - console.error("Error fetching notes:", error); - onError?.(error); - } + return response.data; + } catch (error) { + console.error('Error fetching notes:', error); + onError?.(error); + } } export async function fetchEmails(onError?: (error: unknown) => void) { - try { - const response = await axios.get(`emails?key=${API_KEY}`); - return response.data; - } catch (error) { - console.error("Error fetching emails:", error); - onError?.(error); - } + try { + const response = await axios.get(`emails?key=${API_KEY}`); + return response.data; + } catch (error) { + console.error('Error fetching emails:', error); + onError?.(error); + } } -export async function addEmail( - email: string, - onError?: (error: unknown) => void -) { - try { - const response = await axios.post(`add-email`, { - email: email, - key: API_KEY, - }); +export async function addEmail(email: string, onError?: (error: unknown) => void) { + try { + const response = await axios.post(`add-email`, { + email: email, + key: API_KEY, + }); - return response.data; - } catch (error) { - console.error("Error add email:", error); - onError?.(error); - } + return response.data; + } catch (error) { + console.error('Error add email:', error); + onError?.(error); + } } export async function deleteEmail(id: number) { - await axios.delete(`delete-email/${id}?key=${API_KEY}`); + await axios.delete(`delete-email/${id}?key=${API_KEY}`); } diff --git a/src/components/confirm-modal.tsx b/src/components/confirm-modal.tsx index 2ba949c..7b703a7 100644 --- a/src/components/confirm-modal.tsx +++ b/src/components/confirm-modal.tsx @@ -1,34 +1,28 @@ -import { Button, Modal, Text } from "@mantine/core"; +import { Button, Modal, Text } from '@mantine/core'; interface ConfirmModalProps { - opened: boolean; - title?: string; - message: string; - onConfirm: () => void; - onCancel: () => void; + opened: boolean; + title?: string; + message: string; + onConfirm: () => void; + onCancel: () => void; } -export default function ConfirmModal({ - opened, - title = "Xác nhận", - message, - onConfirm, - onCancel, -}: ConfirmModalProps) { - return ( - - - {message} - +export default function ConfirmModal({ opened, title = 'Xác nhận', message, onConfirm, onCancel }: ConfirmModalProps) { + return ( + + + {message} + -
- - -
-
- ); +
+ + +
+
+ ); } diff --git a/src/components/message.tsx b/src/components/message.tsx index 1ce8af7..d2c5771 100644 --- a/src/components/message.tsx +++ b/src/components/message.tsx @@ -1,48 +1,62 @@ -import { ActionIcon, Box, Paper, Textarea, Tooltip } from "@mantine/core"; -import { useDisclosure } from "@mantine/hooks"; -import { IconEye, IconEyeOff, IconTrash } from "@tabler/icons-react"; -import { useMemo } from "react"; +import { ActionIcon, Box, Paper, Textarea, Tooltip } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { IconEye, IconEyeOff, IconTrash } from '@tabler/icons-react'; +import { useEffect, useMemo, useState } from 'react'; export interface IMessageProps { - data: IMessage; + data: IMessage; } export default function Message({ data }: IMessageProps) { - const [show, { toggle }] = useDisclosure(false); + const [show, { toggle }] = useDisclosure(false); - const messageContent = useMemo(() => { - return show ? data.message : data.message.replace(/./g, "•"); - }, [data.message, show]); + const [animation, setAnimation] = useState(false); - return ( - -
-