Files
Fabric.Desktop/main.js
hzhang ca51ba7063 feat(brand): apply Fabric app + tray icons
- assets/icon.png (512) -> BrowserWindow icon; build/icon.png (1024)
  for electron-builder (linux uses it directly, mac/win generated from
  it); package.json build.files += assets/**, build.icon set.
- Tray: replace the placeholder base64 data-URL with the designed
  no-text tray icon (assets/tray.png 22 + tray@2x.png 44) via
  nativeImage.createFromPath.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:04:55 +01:00

179 lines
4.4 KiB
JavaScript

const { app, BrowserWindow, Menu, Tray, ipcMain, Notification, nativeImage, shell } = require('electron')
const fs = require('fs')
const path = require('path')
const isDev = !!process.env.FABRIC_DESKTOP_URL
const DEFAULT_DEV_URL = 'http://localhost:5173'
const DEFAULT_PROD_ENTRY = path.join(__dirname, 'offline.html')
let mainWindow = null
let tray = null
let isQuitting = false
function configPath() {
return path.join(app.getPath('userData'), 'fabric-desktop.config.json')
}
function readConfig() {
try {
const raw = fs.readFileSync(configPath(), 'utf8')
return JSON.parse(raw)
} catch {
return {
centerApiBase: 'http://localhost:7001/api',
guildApiBase: 'http://localhost:7002/api',
guildSocketBase: 'http://localhost:7002/realtime',
apiKey: '',
}
}
}
function writeConfig(next) {
fs.mkdirSync(path.dirname(configPath()), { recursive: true })
fs.writeFileSync(configPath(), JSON.stringify(next, null, 2), 'utf8')
return next
}
function createMenu() {
const template = [
{
label: 'Fabric',
submenu: [
{ role: 'reload', accelerator: 'CmdOrCtrl+R' },
{ role: 'toggleDevTools', accelerator: 'CmdOrCtrl+Shift+I' },
{ type: 'separator' },
{ role: 'quit', accelerator: 'CmdOrCtrl+Q' },
],
},
{
label: 'Edit',
submenu: [{ role: 'undo' }, { role: 'redo' }, { type: 'separator' }, { role: 'copy' }, { role: 'paste' }],
},
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
}
function createWindow() {
const win = new BrowserWindow({
width: 1280,
height: 840,
minWidth: 1024,
minHeight: 700,
title: 'Fabric Desktop',
icon: path.join(__dirname, 'assets/icon.png'),
autoHideMenuBar: false,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: path.join(__dirname, 'preload.js'),
},
})
win.webContents.setWindowOpenHandler(() => ({ action: 'deny' }))
win.webContents.on('will-navigate', (event, url) => {
const allowedDev = isDev && url.startsWith((process.env.FABRIC_DESKTOP_URL || DEFAULT_DEV_URL))
const allowedFile = url.startsWith('file://')
if (!allowedDev && !allowedFile) {
event.preventDefault()
shell.openExternal(url)
}
})
const devUrl = process.env.FABRIC_DESKTOP_URL || DEFAULT_DEV_URL
if (isDev) {
win.loadURL(devUrl)
} else {
win.loadFile(DEFAULT_PROD_ENTRY)
}
win.on('close', (event) => {
if (!isQuitting) {
event.preventDefault()
win.hide()
}
})
return win
}
function createTrayIcon() {
if (tray) return tray
const icon = nativeImage.createFromPath(path.join(__dirname, 'assets/tray.png'))
tray = new Tray(icon)
tray.setToolTip('Fabric Desktop')
const buildTrayMenu = () =>
Menu.buildFromTemplate([
{
label: mainWindow?.isVisible() ? '隐藏窗口' : '显示窗口',
click: () => {
if (!mainWindow) {
mainWindow = createWindow()
return
}
if (mainWindow.isVisible()) mainWindow.hide()
else {
mainWindow.show()
mainWindow.focus()
}
tray.setContextMenu(buildTrayMenu())
},
},
{
label: '退出',
click: () => {
isQuitting = true
app.quit()
},
},
])
tray.setContextMenu(buildTrayMenu())
tray.on('double-click', () => {
if (!mainWindow) {
mainWindow = createWindow()
return
}
mainWindow.show()
mainWindow.focus()
tray.setContextMenu(buildTrayMenu())
})
return tray
}
app.whenReady().then(() => {
createMenu()
mainWindow = createWindow()
createTrayIcon()
ipcMain.handle('fabric:config:get', () => readConfig())
ipcMain.handle('fabric:config:set', (_evt, next) => writeConfig(next || {}))
ipcMain.handle('fabric:notify', (_evt, payload) => {
const title = payload?.title || 'Fabric'
const body = payload?.body || ''
if (Notification.isSupported()) {
new Notification({ title, body }).show()
return { ok: true }
}
return { ok: false }
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) mainWindow = createWindow()
else if (mainWindow && !mainWindow.isVisible()) mainWindow.show()
})
app.on('before-quit', () => {
isQuitting = true
})
})
app.on('window-all-closed', () => {
// 保持后台驻留,由托盘控制退出
})