From a580b87e1c1ce8ce264c51542f55d3622baba6e0 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Fri, 19 Sep 2025 22:10:31 +0300 Subject: [PATCH] Improve Electron experience and build system --- .github/workflows/build.yml | 153 ++++++++++++------ .github/workflows/deploy.yml | 1 - .vscode/settings.json | 5 +- deployment/frontend/Dockerfile | 2 +- frontend/electron/forge/package.json | 33 ---- frontend/electron/main.ts | 8 +- frontend/{electron/forge => }/forge.config.ts | 19 +-- frontend/src/electron/electron.ts | 6 +- frontend/src/main.tsx | 1 + frontend/src/resources/css/_electron.scss | 18 +-- frontend/src/resources/css/style.scss | 15 +- frontend/src/ui/App.tsx | 3 +- frontend/src/ui/components/Electron.tsx | 21 +-- .../components/chat/MessagePanelRenderer.tsx | 12 +- frontend/src/ui/state.ts | 3 +- frontend/vite.config.ts | 15 +- package.json | 28 +++- 17 files changed, 197 insertions(+), 146 deletions(-) delete mode 100644 frontend/electron/forge/package.json rename frontend/{electron/forge => }/forge.config.ts (80%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 167ca31..92af7ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,72 +17,39 @@ permissions: contents: read jobs: - build: - name: Build (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + build-linux: + name: Build Linux + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Force npm to use Bash - if: runner.os == 'Windows' - run: npm config set script-shell "C:\Program Files\Git\bin\bash.exe" - - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '24' - - name: Cache npm (Linux/macOS) - if: runner.os != 'Windows' + - name: Cache dependencies uses: actions/cache@v4 with: - path: ~/.npm - key: npm-${{ runner.os }}-node-24-${{ hashFiles('package.json') }} + path: node_modules + key: npm-ubuntu-latest-node-24-${{ hashFiles('package.json') }} restore-keys: | - npm-${{ runner.os }}-node-24- + npm-ubuntu-latest-node-24- - - name: Cache npm (Windows) - if: runner.os == 'Windows' - uses: actions/cache@v4 - with: - path: ~\AppData\Local\npm-cache - key: npm-${{ runner.os }}-node-24-${{ hashFiles('package.json') }} - restore-keys: | - npm-${{ runner.os }}-node-24- - - - name: Install root deps (no scripts) + - name: Install dependencies run: npm install --ignore-scripts --no-audit --no-fund - - name: Install Electron Forge deps - run: npm run frontend:electron:dependencies - - - name: Cache Electron downloads (Linux/macOS) - if: runner.os != 'Windows' + - name: Cache Electron downloads uses: actions/cache@v4 with: path: | ~/.cache/electron ~/.cache/electron-builder - key: electron-${{ runner.os }}-${{ hashFiles('package.json', 'frontend/electron/forge/package.json') }} + key: electron-ubuntu-latest-${{ hashFiles('package.json', 'package.json') }} restore-keys: | - electron-${{ runner.os }}- - - - name: Cache Electron downloads (Windows) - if: runner.os == 'Windows' - uses: actions/cache@v4 - with: - path: | - ~\AppData\Local\electron\Cache - ~\AppData\Local\electron-builder\Cache - key: electron-${{ runner.os }}-${{ hashFiles('package.json', 'frontend/electron/forge/package.json') }} - restore-keys: | - electron-${{ runner.os }}- + electron-ubuntu-latest- - name: Build Electron app shell: bash @@ -91,8 +58,102 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: fromchat-${{ runner.os }} + name: FromChat-linux path: | - frontend/electron/forge/out/** + frontend/build/electron/forge/make/*/** if-no-files-found: error + build-macos: + name: Build macOS + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: node_modules + key: npm-macos-latest-node-24-${{ hashFiles('package.json') }} + restore-keys: | + npm-macos-latest-node-24- + + - name: Install dependencies + run: npm install --ignore-scripts --no-audit --no-fund + + # - name: Cache Electron downloads + # uses: actions/cache@v4 + # with: + # path: | + # ~/.cache/electron + # ~/.cache/electron-builder + # key: electron-macos-latest-${{ hashFiles('package.json') }} + # restore-keys: | + # electron-macos-latest- + + - name: Build Electron app + shell: bash + run: npm run build:electron + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: FromChat-macOS + path: | + frontend/build/electron/forge/make/zip/darwin/*/**.zip + if-no-files-found: error + + build-windows: + name: Build Windows + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Force npm to use Bash + run: npm config set script-shell "C:\Program Files\Git\bin\bash.exe" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: node_modules + key: npm-windows-latest-node-24-${{ hashFiles('package.json') }} + restore-keys: | + npm-windows-latest-node-24- + + - name: Install dependencies + run: npm install --ignore-scripts --no-audit --no-fund + + - name: Cache Electron downloads + uses: actions/cache@v4 + with: + path: | + ~\AppData\Local\electron\Cache + ~\AppData\Local\electron-builder\Cache + key: electron-windows-latest-${{ hashFiles('package.json') }} + restore-keys: | + electron-windows-latest- + + - name: Build Electron app + shell: bash + run: npm run build:electron + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: FromChat-windows + path: | + frontend/build/electron/forge/make/zip/win32/x64 + if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6277bd0..d183da5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,7 +13,6 @@ on: - ".nvmrc" - ".github/workflows/deploy.yml" - "!frontend/electron/**" - - "!frontend/src/electron/**" - "!**.d.ts" workflow_dispatch: diff --git a/.vscode/settings.json b/.vscode/settings.json index fa1af56..16545c5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "files.exclude": { "**/__pycache__": true, "**/package-lock.json": true - } + }, + "github-actions.workflows.pinned.workflows": [], + "github-actions.workflows.pinned.workflows.ignore": true, + "github-actions.workflows.pinned.workflows.ignoreContextAccess": true } \ No newline at end of file diff --git a/deployment/frontend/Dockerfile b/deployment/frontend/Dockerfile index 8f00220..6f37ca0 100644 --- a/deployment/frontend/Dockerfile +++ b/deployment/frontend/Dockerfile @@ -36,7 +36,7 @@ USER app # 3.1. Frontend static files WORKDIR /app -COPY --from=frontend --chown=app /app/frontend/dist . +COPY --from=frontend --chown=app /app/frontend/build/normal/dist . # 3.2. Static file server WORKDIR /server diff --git a/frontend/electron/forge/package.json b/frontend/electron/forge/package.json deleted file mode 100644 index 9ca842d..0000000 --- a/frontend/electron/forge/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "FromChat", - "productName": "FromChat", - "version": "1.0.0", - "description": "A 100% Open Source Messenger", - "main": "dist-electron/main.js", - "scripts": { - "package": "electron-forge package", - "make": "electron-forge make", - "publish": "electron-forge publish" - }, - "keywords": [], - "author": { - "name": "denis0001-dev", - "email": "denis0001.dev@ya.ru" - }, - "license": "GPL-2.0", - "devDependencies": { - "@electron-forge/cli": "^7.8.3", - "@electron-forge/maker-deb": "^7.8.3", - "@electron-forge/maker-rpm": "^7.8.3", - "@electron-forge/maker-squirrel": "^7.8.3", - "@electron-forge/maker-zip": "^7.8.3", - "@electron-forge/plugin-auto-unpack-natives": "^7.8.3", - "@electron-forge/plugin-fuses": "^7.8.3", - "@electron/fuses": "^1.8.0", - "electron": "37.3.1", - "vite": "^5.4.19" - }, - "dependencies": { - "electron-squirrel-startup": "^1.0.1" - } -} diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index cb6e23a..46c4322 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -4,7 +4,7 @@ import path from "node:path"; app.whenReady().then(() => { const win = new BrowserWindow({ title: 'Main window', - minWidth: 650, + minWidth: 800, minHeight: 420, webPreferences: { preload: path.join(import.meta.dirname, "preload.mjs") @@ -16,11 +16,11 @@ app.whenReady().then(() => { y: 16 - 4 }, titleBarOverlay: process.platform !== "darwin" - }) + }); if (process.env.VITE_DEV_SERVER_URL) { - win.loadURL(process.env.VITE_DEV_SERVER_URL) + win.loadURL(process.env.VITE_DEV_SERVER_URL); } else { - win.loadFile('dist/index.html'); + win.loadFile('frontend/build/electron/dist/index.html'); } }); \ No newline at end of file diff --git a/frontend/electron/forge/forge.config.ts b/frontend/forge.config.ts similarity index 80% rename from frontend/electron/forge/forge.config.ts rename to frontend/forge.config.ts index ab5fefd..b96f865 100644 --- a/frontend/electron/forge/forge.config.ts +++ b/frontend/forge.config.ts @@ -1,29 +1,28 @@ import { FusesPlugin } from '@electron-forge/plugin-fuses'; import { FuseV1Options, FuseVersion } from '@electron/fuses'; -import type { ForgeConfig } from "@electron-forge/shared-types"; +import type { ForgeConfig } from '@electron-forge/shared-types'; -const config: ForgeConfig = { +export default { packagerConfig: { - asar: true + asar: true, }, + outDir: "frontend/build/electron/forge", rebuildConfig: {}, makers: [ - { - name: '@electron-forge/maker-squirrel', - config: {}, - }, { name: '@electron-forge/maker-zip', config: {}, - platforms: ['darwin'], + platforms: ['win32', 'darwin'], }, { name: '@electron-forge/maker-deb', config: {}, + platforms: ['linux'], }, { name: '@electron-forge/maker-rpm', config: {}, + platforms: ['linux'], }, ], plugins: [ @@ -43,6 +42,4 @@ const config: ForgeConfig = { [FuseV1Options.OnlyLoadAppFromAsar]: true, }), ], -}; - -export default config; \ No newline at end of file +} satisfies ForgeConfig; \ No newline at end of file diff --git a/frontend/src/electron/electron.ts b/frontend/src/electron/electron.ts index 4810cc0..0eb9ab1 100644 --- a/frontend/src/electron/electron.ts +++ b/frontend/src/electron/electron.ts @@ -5,13 +5,11 @@ * @version 1.0.0 */ -import { PRODUCT_NAME } from "../core/config.ts"; -import { id } from "../utils/utils.ts"; +export const isElectron = import.meta.env.VITE_ELECTRON && window.electronInterface != undefined; -if (window.electronInterface !== undefined) { +if (isElectron) { console.log("Running in Electron"); document.documentElement.classList.add("electron", `platform-${window.electronInterface.platform}`); - id("window-title").textContent = PRODUCT_NAME; } else { console.log("Running in normal browser"); } \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 5dd0530..b923780 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -10,6 +10,7 @@ import "mdui/mdui.css"; import "./utils/material"; import "./core/init"; +import "./electron/electron"; import { createRoot } from 'react-dom/client'; import App from './ui/App'; import { StrictMode } from 'react'; diff --git a/frontend/src/resources/css/_electron.scss b/frontend/src/resources/css/_electron.scss index a5db111..69aa4ed 100644 --- a/frontend/src/resources/css/_electron.scss +++ b/frontend/src/resources/css/_electron.scss @@ -9,13 +9,14 @@ html.electron { display: flex; flex-direction: row; gap: 8px; - height: 40px; + min-height: 40px; background-color: $color-dark-surface-container; width: 100%; -webkit-app-region: drag; user-select: none; z-index: 10; transition: background-color 0.5s ease; + flex-shrink: 0; &.color-surface { background-color: $color-dark-surface; @@ -29,15 +30,12 @@ html.electron { font-weight: 500; } - &.platform-darwin .macos-padding { - width: 70px; + #main-wrapper { + flex: 1; + min-height: 0; } - // .window-controls { - // -webkit-app-region: no-drag; - - // .hidden { - // display: none; - // } - // } + &.platform-darwin .macos-padding { + width: 80px; + } } \ No newline at end of file diff --git a/frontend/src/resources/css/style.scss b/frontend/src/resources/css/style.scss index a63eebf..4cfe673 100644 --- a/frontend/src/resources/css/style.scss +++ b/frontend/src/resources/css/style.scss @@ -24,17 +24,20 @@ body { background-color: $color-dark-surface; color: $color-dark-on-surface; line-height: 1.6; + + #main-wrapper { + flex: 1; + position: relative; + min-height: 0; + } +} + +body, #root { height: 100vh; position: relative; margin: 0; display: flex; flex-direction: column; - - #main-wrapper { - flex: 1; - position: relative; - height: 100vh; - } } mdui-dialog { diff --git a/frontend/src/ui/App.tsx b/frontend/src/ui/App.tsx index 9ece2e2..2712a2e 100644 --- a/frontend/src/ui/App.tsx +++ b/frontend/src/ui/App.tsx @@ -1,4 +1,5 @@ import { MINIMUM_WIDTH } from "../core/config"; +import { isElectron } from "../electron/electron"; import { ElectronTitleBar } from "./components/Electron"; import useWindowSize from "./hooks/useWindowSize"; import ChatScreen from "./screen/ChatScreen"; @@ -17,7 +18,7 @@ export default function App() { restoreUserFromStorage(); }, [restoreUserFromStorage]); - if (width < MINIMUM_WIDTH) { + if (!isElectron && width < MINIMUM_WIDTH) { return } diff --git a/frontend/src/ui/components/Electron.tsx b/frontend/src/ui/components/Electron.tsx index d637ffb..1052454 100644 --- a/frontend/src/ui/components/Electron.tsx +++ b/frontend/src/ui/components/Electron.tsx @@ -1,18 +1,11 @@ import { PRODUCT_NAME } from "../../core/config"; +import { isElectron } from "../../electron/electron"; export function ElectronTitleBar() { - if (window.electronInterface !== undefined) { - return ( -
- {window.electronInterface.platform == "darwin" ?
: undefined} -
{PRODUCT_NAME}
- {/*
- - - - -
*/} -
- ) - } + return isElectron && ( +
+ {window.electronInterface.platform == "darwin" &&
} +
{PRODUCT_NAME}
+
+ ) } \ No newline at end of file diff --git a/frontend/src/ui/components/chat/MessagePanelRenderer.tsx b/frontend/src/ui/components/chat/MessagePanelRenderer.tsx index 062511c..40feee4 100644 --- a/frontend/src/ui/components/chat/MessagePanelRenderer.tsx +++ b/frontend/src/ui/components/chat/MessagePanelRenderer.tsx @@ -153,20 +153,20 @@ export function MessagePanelRenderer({ panel, isChatSwitching }: MessagePanelRen { + onReplySelect={(message) => { if (editMessage || editVisible) { - setPendingAction({ type: "reply", message: m }); + setPendingAction({ type: "reply", message: message }); setEditVisible(false); // onCloseEdit will apply pending } else { - setReplyTo(m); + setReplyTo(message); } }} - onEditSelect={(m) => { + onEditSelect={(message) => { if (replyTo || replyToVisible) { - setPendingAction({ type: "edit", message: m }); + setPendingAction({ type: "edit", message: message }); setReplyToVisible(false); // onCloseReply will apply pending } else { - setEditMessage(m); + setEditMessage(message); } }} > diff --git a/frontend/src/ui/state.ts b/frontend/src/ui/state.ts index cbf1879..70166e2 100644 --- a/frontend/src/ui/state.ts +++ b/frontend/src/ui/state.ts @@ -6,6 +6,7 @@ import { PublicChatPanel } from "./panels/PublicChatPanel"; import { DMPanel, type DMPanelData } from "./panels/DMPanel"; import { getAuthHeaders } from "../auth/api"; import { restoreKeys } from "../auth/crypto"; +import { API_BASE_URL } from "../core/config"; type Page = "login" | "register" | "chat" export type ChatTabs = "chats" | "channels" | "contacts" | "dms" @@ -199,7 +200,7 @@ export const useAppState = create((set, get) => ({ const token = localStorage.getItem('authToken'); if (token) { - const response = await fetch("/api/user/profile", { + const response = await fetch(`${API_BASE_URL}/user/profile`, { headers: getAuthHeaders(token) }); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 930ffb3..cb18e68 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -25,9 +25,19 @@ if (process.env.VITE_ELECTRON) { electron({ main: { entry: "electron/main.ts", + vite: { + build: { + outDir: "build/electron/core" + } + } }, preload: { - input: "frontend/electron/preload.ts" + input: "frontend/electron/preload.ts", + vite: { + build: { + outDir: "build/electron/core" + } + } }, renderer: {}, }) @@ -68,6 +78,7 @@ export default defineConfig({ } }, cssMinify: true, - assetsInlineLimit: 0 + assetsInlineLimit: 0, + outDir: process.env.VITE_ELECTRON ? "build/electron/dist" : "build/normal/dist" } }); \ No newline at end of file diff --git a/package.json b/package.json index 73373c5..13a3c9b 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,12 @@ { - "name": "pixelchat", + "name": "FromChat", "private": true, "version": "0.0.0", "type": "module", - "main": "frontend/dist-electron/main.js", + "main": "frontend/build/electron/core/main.js", + "description": "A 100% Open Source Messenger", + "license": "GPL-3.0", + "authors": "denis0001-dev", "scripts": { "backend:run": "cd backend && dotenv -e ../deployment/.env -- ../.venv/bin/fastapi dev --port 8300 main.py", "backend:dependencies": "python3 -m venv .venv && ./.venv/bin/pip3 install -r backend/requirements.txt", @@ -13,8 +16,7 @@ "frontend:typecheck": "tsc --project frontend", "frontend:build": "npm run frontend:typecheck && vite build frontend", "frontend:electron:dev": "VITE_ELECTRON=true npm run frontend:dev", - "frontend:electron:dependencies": "cd frontend/electron/forge && npm install", - "frontend:electron:build": "VITE_ELECTRON=true npm run frontend:build && cp -r frontend/dist frontend/dist-electron frontend/electron/forge && cd frontend/electron/forge && npm run make", + "frontend:electron:build": "VITE_ELECTRON=true npm run frontend:build && rm -rf out && electron-forge make --force", "frontend:preview": "vite preview frontend", "frontend:dependencies": "npm install --ignore-scripts", "frontend:clean": "rm -rf frontend/dist", @@ -25,10 +27,22 @@ "preview": "cd deployment && docker compose up --build --watch", "preview:clean": "cd deployment && docker compose down -v", "clean": "npm run backend:clean && npm run frontend:clean && npm run preview:clean", - "install": "npm run backend:dependencies && npm run frontend:electron:dependencies && cp deployment/.env.example deployment/.env", + "install": "npm run backend:dependencies && cp deployment/.env.example deployment/.env", "prepare": "husky" }, + "files": [ + "frontend/build/electron" + ], "devDependencies": { + "@electron-forge/cli": "^7.9.0", + "@electron-forge/maker-deb": "^7.9.0", + "@electron-forge/maker-dmg": "^7.9.0", + "@electron-forge/maker-rpm": "^7.9.0", + "@electron-forge/maker-squirrel": "^7.9.0", + "@electron-forge/maker-zip": "^7.9.0", + "@electron-forge/plugin-auto-unpack-natives": "^7.9.0", + "@electron-forge/plugin-fuses": "^7.9.0", + "@electron/fuses": "^1.8.0", "@types/react": "^19.1.12", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.2", @@ -47,11 +61,15 @@ "vite-plugin-html": "^3.2.2" }, "dependencies": { + "electron-squirrel-startup": "^1.0.1", "mdui": "^2.1.4", "react": "^19.1.1", "react-dom": "^19.1.1", "tweetnacl": "^1.0.3", "use-immer": "^0.11.0", "zustand": "^5.0.8" + }, + "config": { + "forge": "frontend/forge.config.ts" } }