Compare commits
9 Commits
54f4b46755
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cb92835d2f | |||
| 97c4e7ad96 | |||
| f641d42a8b | |||
| 77b6caab7b | |||
| 4473aa1b8b | |||
| 5c4770f051 | |||
| 8952b9d3ed | |||
| ca51ba7063 | |||
| 1476ff7bb4 |
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
renderer/
|
||||||
|
|||||||
84
README.md
@@ -1,64 +1,52 @@
|
|||||||
# Fabric.Desktop
|
# Fabric.Desktop
|
||||||
|
|
||||||
Electron desktop shell for Fabric.Frontend.
|
A **self-contained** Electron desktop app for Fabric. It bundles the
|
||||||
|
`Fabric.Frontend` SPA inside the package and loads it locally over
|
||||||
|
`file://` — no separate frontend web server is needed. It still talks to the
|
||||||
|
Center/Guild **backends** over HTTP (the Center base is set on the login
|
||||||
|
screen, so the desktop app points at whatever host runs the backends).
|
||||||
|
|
||||||
## 功能(当前)
|
## How it works
|
||||||
- BrowserWindow 基础配置(尺寸/最小尺寸/标题)
|
|
||||||
- Dev/Prod 加载策略(dev server / 本地 offline.html)
|
|
||||||
- 基础菜单与快捷键(刷新、开发者工具、退出)
|
|
||||||
- 安全基线:`contextIsolation` + `sandbox` + 禁止任意新窗口/导航
|
|
||||||
- `preload + IPC` 白名单:
|
|
||||||
- `fabric:config:get`
|
|
||||||
- `fabric:config:set`
|
|
||||||
- `fabric:notify`
|
|
||||||
|
|
||||||
## Dev
|
- `npm run build:renderer` builds `Fabric.Frontend` with a relative asset
|
||||||
|
base (`build:desktop`) and copies the bundle into `renderer/`.
|
||||||
|
- `main.js` loads `renderer/index.html` in production (falls back to
|
||||||
|
`offline.html` if the bundle is missing); dev mode loads
|
||||||
|
`FABRIC_DESKTOP_URL` (default `http://localhost:5173`).
|
||||||
|
- Security baseline: `contextIsolation` + `sandbox`, no arbitrary new
|
||||||
|
windows/navigation; `preload` exposes a small IPC allowlist
|
||||||
|
(`fabric:config:get|set`, `fabric:notify`).
|
||||||
|
- System tray (show/hide window, quit) with the Fabric tray icon; app/window
|
||||||
|
icons use the Fabric mark.
|
||||||
|
|
||||||
|
## Develop
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm run start:dev
|
npm run start:dev # expects Fabric.Frontend dev server at :5173
|
||||||
|
# or, against the bundled renderer:
|
||||||
|
npm run build:renderer && npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
`start:dev` expects Frontend dev server at `http://localhost:5173`.
|
## Build / package
|
||||||
|
|
||||||
## Standalone
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build / Release
|
|
||||||
|
|
||||||
先安装依赖(包含 `electron-builder`):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
|
npm run pack # unpacked dir (no installer)
|
||||||
|
npm run dist:linux # AppImage, deb, tar.gz -> dist/
|
||||||
|
npm run dist:mac # dmg, zip
|
||||||
|
npm run dist:win # nsis, zip
|
||||||
```
|
```
|
||||||
|
|
||||||
仅打包目录(不生成安装包):
|
Each `dist:*`/`pack` runs `build:renderer` first (so `Fabric.Frontend` must
|
||||||
|
be present beside this submodule, which it is in the `Fabric` checkout).
|
||||||
|
Output goes to `dist/` (git-ignored). `productName` is **Fabric** so the app
|
||||||
|
installs to `/opt/Fabric/` (a space there breaks Electron's sandbox); the
|
||||||
|
launcher is still labelled "Fabric Desktop". Linux icons are generated from
|
||||||
|
`build/icons/`.
|
||||||
|
|
||||||
```bash
|
## Notes
|
||||||
npm run pack
|
|
||||||
```
|
|
||||||
|
|
||||||
跨平台构建入口:
|
- The bundled SPA uses `HashRouter` under `file://` (no server for the
|
||||||
|
History API). The Center/Guild backends are HTTP and CORS-allow the
|
||||||
```bash
|
Electron `file://` origin (`null`).
|
||||||
npm run dist
|
|
||||||
```
|
|
||||||
|
|
||||||
按平台构建:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dist:linux
|
|
||||||
npm run dist:mac
|
|
||||||
npm run dist:win
|
|
||||||
```
|
|
||||||
|
|
||||||
构建产物输出到:
|
|
||||||
|
|
||||||
- `dist/`
|
|
||||||
|
|
||||||
产物命名规范:
|
|
||||||
|
|
||||||
- `Fabric-Desktop-${version}-${os}-${arch}.${ext}`
|
|
||||||
|
|||||||
BIN
assets/icon.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
assets/tray.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/tray@2x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
build/icon.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
build/icons/1024x1024.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
build/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
build/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 683 B |
BIN
build/icons/24x24.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
build/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
build/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
build/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
build/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
build/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
11
main.js
@@ -4,7 +4,11 @@ const path = require('path')
|
|||||||
|
|
||||||
const isDev = !!process.env.FABRIC_DESKTOP_URL
|
const isDev = !!process.env.FABRIC_DESKTOP_URL
|
||||||
const DEFAULT_DEV_URL = 'http://localhost:5173'
|
const DEFAULT_DEV_URL = 'http://localhost:5173'
|
||||||
const DEFAULT_PROD_ENTRY = path.join(__dirname, 'offline.html')
|
// Self-contained: load the bundled frontend if present, else the placeholder.
|
||||||
|
const BUNDLED_ENTRY = path.join(__dirname, 'renderer', 'index.html')
|
||||||
|
const DEFAULT_PROD_ENTRY = fs.existsSync(BUNDLED_ENTRY)
|
||||||
|
? BUNDLED_ENTRY
|
||||||
|
: path.join(__dirname, 'offline.html')
|
||||||
|
|
||||||
let mainWindow = null
|
let mainWindow = null
|
||||||
let tray = null
|
let tray = null
|
||||||
@@ -60,6 +64,7 @@ function createWindow() {
|
|||||||
minWidth: 1024,
|
minWidth: 1024,
|
||||||
minHeight: 700,
|
minHeight: 700,
|
||||||
title: 'Fabric Desktop',
|
title: 'Fabric Desktop',
|
||||||
|
icon: path.join(__dirname, 'assets/icon.png'),
|
||||||
autoHideMenuBar: false,
|
autoHideMenuBar: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@@ -99,9 +104,7 @@ function createWindow() {
|
|||||||
function createTrayIcon() {
|
function createTrayIcon() {
|
||||||
if (tray) return tray
|
if (tray) return tray
|
||||||
|
|
||||||
const iconDataUrl =
|
const icon = nativeImage.createFromPath(path.join(__dirname, 'assets/tray.png'))
|
||||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAfElEQVR4AWNw4PjPwMDA8J+BgYHhP4MDA8N/BgYGhv8MDAwM/2dgYGB4R2JgYGB4w8DAwPCfgYGB4R8DAwPDf4aGhob/BgwMDAz/Z2BgYHhHYmBgYHjDwMDA8J+BgYHhHwMDA8N/hoYGhv8GDAwMDP9nYGBgeEdiYGAAAB7RImfVq6X8AAAAAElFTkSuQmCC'
|
|
||||||
const icon = nativeImage.createFromDataURL(iconDataUrl)
|
|
||||||
|
|
||||||
tray = new Tray(icon)
|
tray = new Tray(icon)
|
||||||
tray.setToolTip('Fabric Desktop')
|
tray.setToolTip('Fabric Desktop')
|
||||||
|
|||||||
28
package.json
@@ -6,17 +6,18 @@
|
|||||||
"homepage": "https://github.com/hangman0414/Fabric",
|
"homepage": "https://github.com/hangman0414/Fabric",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Hangman",
|
"name": "Hangman",
|
||||||
"email": "hangman@example.com"
|
"email": "noreply@hangman-lab.top"
|
||||||
},
|
},
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"start:dev": "FABRIC_DESKTOP_URL=http://localhost:5173 electron .",
|
"start:dev": "FABRIC_DESKTOP_URL=http://localhost:5173 electron .",
|
||||||
"pack": "electron-builder --dir",
|
"build:renderer": "node scripts/build-renderer.mjs",
|
||||||
"dist": "electron-builder",
|
"pack": "npm run build:renderer && electron-builder --dir",
|
||||||
"dist:linux": "electron-builder --linux AppImage deb tar.gz",
|
"dist": "npm run build:renderer && electron-builder",
|
||||||
"dist:mac": "electron-builder --mac dmg zip",
|
"dist:linux": "npm run build:renderer && electron-builder --linux AppImage deb tar.gz",
|
||||||
"dist:win": "electron-builder --win nsis zip"
|
"dist:mac": "npm run build:renderer && electron-builder --mac dmg zip",
|
||||||
|
"dist:win": "npm run build:renderer && electron-builder --win nsis zip"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^37.2.1",
|
"electron": "^37.2.1",
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "ai.hangman.fabric.desktop",
|
"appId": "ai.hangman.fabric.desktop",
|
||||||
"productName": "Fabric Desktop",
|
"productName": "Fabric",
|
||||||
"artifactName": "Fabric-Desktop-${version}-${os}-${arch}.${ext}",
|
"artifactName": "Fabric-Desktop-${version}-${os}-${arch}.${ext}",
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist"
|
"output": "dist"
|
||||||
@@ -33,9 +34,12 @@
|
|||||||
"main.js",
|
"main.js",
|
||||||
"preload.js",
|
"preload.js",
|
||||||
"offline.html",
|
"offline.html",
|
||||||
"package.json"
|
"package.json",
|
||||||
|
"assets/**",
|
||||||
|
"renderer/**"
|
||||||
],
|
],
|
||||||
"asar": true,
|
"asar": true,
|
||||||
|
"icon": "build/icon.png",
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": [
|
"target": [
|
||||||
"AppImage",
|
"AppImage",
|
||||||
@@ -43,7 +47,13 @@
|
|||||||
"tar.gz"
|
"tar.gz"
|
||||||
],
|
],
|
||||||
"category": "Utility",
|
"category": "Utility",
|
||||||
"maintainer": "Hangman <hangman@example.com>"
|
"maintainer": "Hangman <noreply@hangman-lab.top>",
|
||||||
|
"executableName": "fabric-desktop",
|
||||||
|
"icon": "build/icons",
|
||||||
|
"desktop": {
|
||||||
|
"Name": "Fabric Desktop",
|
||||||
|
"StartupWMClass": "Fabric Desktop"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"mac": {
|
"mac": {
|
||||||
"target": [
|
"target": [
|
||||||
|
|||||||
36
scripts/build-renderer.mjs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Build the Fabric web frontend with a relative asset base and copy the
|
||||||
|
* static bundle into Fabric.Desktop/renderer/ so the packaged Electron app
|
||||||
|
* is self-contained (loaded via file://, no separate web server needed).
|
||||||
|
*
|
||||||
|
* Fabric.Frontend is the sibling submodule under the parent Fabric repo.
|
||||||
|
*/
|
||||||
|
import { execSync } from 'node:child_process'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
const DESKTOP = path.resolve(__dirname, '..')
|
||||||
|
const FRONTEND = path.resolve(DESKTOP, '..', 'Fabric.Frontend')
|
||||||
|
const SRC = path.join(FRONTEND, 'dist-desktop')
|
||||||
|
const DEST = path.join(DESKTOP, 'renderer')
|
||||||
|
|
||||||
|
if (!fs.existsSync(FRONTEND)) {
|
||||||
|
console.error(`[build-renderer] Fabric.Frontend not found at ${FRONTEND}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[build-renderer] building frontend (relative base)…')
|
||||||
|
execSync('npm install --no-audit --no-fund', { cwd: FRONTEND, stdio: 'inherit' })
|
||||||
|
execSync('npm run build:desktop', { cwd: FRONTEND, stdio: 'inherit' })
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(SRC, 'index.html'))) {
|
||||||
|
console.error(`[build-renderer] expected ${SRC}/index.html after build`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.rmSync(DEST, { recursive: true, force: true })
|
||||||
|
fs.cpSync(SRC, DEST, { recursive: true })
|
||||||
|
console.log(`[build-renderer] copied ${SRC} -> ${DEST}`)
|
||||||