github.com/easysoft/zendata@v0.0.0-20240513203326-705bd5a7fd67/client/src/app/app.js (about) 1 import {app, BrowserWindow, ipcMain, Menu, shell, dialog, globalShortcut} from 'electron'; 2 import path from "path"; 3 import { 4 DEBUG, 5 electronMsg, electronMsgReboot, 6 electronMsgReplay, 7 electronMsgUpdate, 8 minimumSizeHeight, 9 minimumSizeWidth 10 } from './utils/consts'; 11 import {IS_MAC_OSX, IS_LINUX, IS_WINDOWS_OS} from './utils/env'; 12 import {logInfo, logErr} from './utils/log'; 13 import Config from './utils/config'; 14 import Lang, {initLang} from './core/lang'; 15 import {startUIService} from "./core/ui"; 16 import {startZdServer, killZdServer} from "./core/zd"; 17 import {getCurrVersion} from "./utils/comm"; 18 import {checkUpdate, downLoadAndUpdateApp, reboot} from "./utils/hot-update"; 19 20 const cp = require('child_process'); 21 const fs = require('fs'); 22 23 export class ZdApp { 24 constructor() { 25 app.name = Lang.string('app.title', Config.pkg.displayName); 26 27 this._windows = new Map(); 28 29 startZdServer().then((zdServerUrl)=> { 30 if (zdServerUrl) logInfo(`>> zd server started successfully on : ${zdServerUrl}`); 31 this.bindElectronEvents(); 32 }).catch((err) => { 33 logErr('>> zd server started failed, err: ' + err); 34 process.exit(1); 35 }) 36 } 37 38 showAndFocus() { 39 logInfo(`>> zd app: AppWindow[${this.name}]: show and focus`); 40 41 const {browserWindow} = this; 42 if (browserWindow.isMinimized()) { 43 browserWindow.restore(); 44 } else { 45 browserWindow.setOpacity(1); 46 browserWindow.show(); 47 } 48 browserWindow.focus(); 49 } 50 51 async createWindow() { 52 process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; 53 54 const frame = IS_MAC_OSX ? true: false; 55 const mainWin = new BrowserWindow({ 56 show: false, 57 frame: frame, 58 webPreferences: { 59 nodeIntegration: true, 60 contextIsolation: false, 61 }, 62 }) 63 if (IS_LINUX && !DEBUG) { 64 const pth = path.join(__dirname, 'icon', 'favicon.png') 65 mainWin.setIcon(pth); 66 } 67 68 require('@electron/remote/main').initialize() 69 require('@electron/remote/main').enable(mainWin.webContents) 70 const {currVersionStr} = getCurrVersion() 71 global.sharedObj = { version : currVersionStr}; 72 73 mainWin.setSize(minimumSizeWidth, minimumSizeHeight) 74 mainWin.setMovable(true) 75 mainWin.maximize() 76 mainWin.show() 77 78 this._windows.set('main', mainWin); 79 80 const url = await startUIService() 81 await mainWin.loadURL(url); 82 83 ipcMain.on(electronMsg, (event, arg) => { 84 logInfo('msg from renderer: ' + arg) 85 86 switch (arg) { 87 case 'selectDir': 88 this.showFolderSelection(event) 89 break; 90 case 'selectFile': 91 this.showFileSelection(event) 92 break; 93 94 case 'fullScreen': 95 mainWin.setFullScreen(!mainWin.isFullScreen()); 96 break; 97 case 'minimize': 98 mainWin.minimize(); 99 break; 100 case 'maximize': 101 mainWin.maximize(); 102 break; 103 case 'unmaximize': 104 mainWin.unmaximize(); 105 break; 106 107 case 'help': 108 shell.openExternal('https://zd.im'); 109 break; 110 case 'exit': 111 app.quit() 112 break; 113 default: 114 logInfo('--', arg.action, arg.path) 115 if (arg.action == 'openInExplore') 116 this.openInExplore(arg.path) 117 else if (arg.action == 'openInTerminal') 118 this.openInTerminal(arg.path) 119 } 120 }) 121 } 122 123 async openOrCreateWindow() { 124 const mainWin = this._windows.get('main'); 125 if (mainWin) { 126 this.showAndFocus(mainWin) 127 } else { 128 await this.createWindow(); 129 } 130 } 131 132 showAndFocus(mainWin) { 133 if (mainWin.isMinimized()) { 134 mainWin.restore(); 135 } else { 136 mainWin.setOpacity(1); 137 mainWin.show(); 138 } 139 mainWin.focus(); 140 } 141 142 ready() { 143 logInfo('>> zd app ready.'); 144 145 initLang() 146 this.buildAppMenu(); 147 this.openOrCreateWindow() 148 this.setAboutPanel(); 149 150 globalShortcut.register('CommandOrControl+D', () => { 151 const mainWin = this._windows.get('main'); 152 mainWin.toggleDevTools() 153 }) 154 155 ipcMain.on(electronMsgUpdate, (event, arg) => { 156 logInfo('update confirm from renderer', arg) 157 158 const mainWin = this._windows.get('main'); 159 downLoadAndUpdateApp(arg.newVersion, mainWin) 160 }); 161 162 ipcMain.on(electronMsgReboot, (event, arg) => { 163 logInfo('reboot from renderer', arg) 164 reboot() 165 }); 166 167 setInterval(async () => { 168 await checkUpdate(this._windows.get('main')) 169 }, 6000); 170 } 171 172 quit() { 173 killZdServer(); 174 } 175 176 bindElectronEvents() { 177 app.on('window-all-closed', () => { 178 logInfo(`>> event: window-all-closed`) 179 app.quit(); 180 }); 181 182 app.on('quit', () => { 183 logInfo(`>> event: quit`) 184 this.quit(); 185 }); 186 187 app.on('activate', () => { 188 logInfo('>> event: activate'); 189 190 this.buildAppMenu(); 191 192 // 在 OS X 系统上,可能存在所有应用窗口关闭了,但是程序还没关闭,此时如果收到激活应用请求,需要重新打开应用窗口并创建应用菜单。 193 this.openOrCreateWindow() 194 }); 195 } 196 197 showFileSelection(event) { 198 dialog.showOpenDialog({ 199 properties: ['openFile'] 200 }).then(result => { 201 this.reply(event, result) 202 }).catch(err => { 203 logErr(err) 204 }) 205 } 206 207 showFolderSelection(event) { 208 dialog.showOpenDialog({ 209 properties: ['openDirectory'] 210 }).then(result => { 211 this.reply(event, result) 212 }).catch(err => { 213 logErr(err) 214 }) 215 } 216 217 reply(event, result) { 218 if (result.filePaths && result.filePaths.length > 0) { 219 event.reply(electronMsgReplay, result.filePaths[0]); 220 } 221 } 222 223 openInExplore(pth) { 224 shell.showItemInFolder(pth); 225 } 226 openInTerminal(pth) { 227 logInfo('openInTerminal') 228 229 const stats = fs.statSync(pth); 230 if (stats.isFile()) { 231 pth = path.resolve(pth, '..') 232 } 233 234 if (IS_WINDOWS_OS) { 235 cp.exec('start cmd.exe /K cd /D ' + pth); 236 } else if (IS_LINUX) { 237 // support other terminal types 238 cp.spawn ('gnome-terminal', [], { cwd: pth }); 239 } else if (IS_MAC_OSX) { 240 cp.exec('open -a Terminal ' + pth); 241 } 242 } 243 244 get windows() { 245 return this._windows; 246 } 247 248 setAboutPanel() { 249 if (!app.setAboutPanelOptions) { 250 return; 251 } 252 253 let version = Config.pkg.buildTime ? 'build at ' + new Date(Config.pkg.buildTime).toLocaleString() : '' 254 version += DEBUG ? '[debug]' : '' 255 app.setAboutPanelOptions({ 256 applicationName: Lang.string(Config.pkg.name) || Config.pkg.displayName, 257 applicationVersion: Config.pkg.version, 258 copyright: `${Config.pkg.copyright} ${Config.pkg.company}`, 259 credits: `Licence: ${Config.pkg.license}`, 260 version: version 261 }); 262 } 263 264 buildAppMenu() { 265 logInfo('>> zd app: build application menu.'); 266 267 if (IS_MAC_OSX) { 268 const template = [ 269 { 270 label: Lang.string('app.title', Config.pkg.displayName), 271 submenu: [ 272 { 273 label: Lang.string('app.about'), 274 selector: 'orderFrontStandardAboutPanel:' 275 }, { 276 label: Lang.string('app.exit'), 277 accelerator: 'Command+Q', 278 click: () => { 279 app.quit(); 280 } 281 } 282 ] 283 }, 284 { 285 label: Lang.string('app.edit'), 286 submenu: [{ 287 label: Lang.string('app.undo'), 288 accelerator: 'Command+Z', 289 selector: 'undo:' 290 }, { 291 label: Lang.string('app.redo'), 292 accelerator: 'Shift+Command+Z', 293 selector: 'redo:' 294 }, { 295 type: 'separator' 296 }, { 297 label: Lang.string('app.cut'), 298 accelerator: 'Command+X', 299 selector: 'cut:' 300 }, { 301 label: Lang.string('app.copy'), 302 accelerator: 'Command+C', 303 selector: 'copy:' 304 }, { 305 label: Lang.string('app.paste'), 306 accelerator: 'Command+V', 307 selector: 'paste:' 308 }, { 309 label: Lang.string('app.select_all'), 310 accelerator: 'Command+A', 311 selector: 'selectAll:' 312 }] 313 }, 314 { 315 label: Lang.string('app.view'), 316 submenu: [ 317 { 318 label: Lang.string('app.switch_to_full_screen'), 319 accelerator: 'Ctrl+Command+F', 320 click: () => { 321 const mainWin = this._windows.get('main'); 322 mainWin.setFullScreen(!mainWin.isFullScreen()); 323 } 324 } 325 ] 326 }, 327 { 328 label: Lang.string('app.window'), 329 submenu: [ 330 { 331 label: Lang.string('app.minimize'), 332 accelerator: 'Command+M', 333 selector: 'performMiniaturize:' 334 }, 335 { 336 label: Lang.string('app.close'), 337 accelerator: 'Command+W', 338 selector: 'performClose:' 339 }, 340 { 341 label: 'Reload', 342 accelerator: 'Command+R', 343 click: () => { 344 this._windows.get('main').webContents.reload(); 345 } 346 }, 347 { 348 type: 'separator' 349 }, 350 { 351 label: Lang.string('app.bring_all_to_front'), 352 selector: 'arrangeInFront:' 353 } 354 ] 355 }, 356 { 357 label: Lang.string('app.help'), 358 submenu: [{ 359 label: Lang.string('app.website'), 360 click: () => { 361 shell.openExternal('https://zd.im'); 362 } 363 }] 364 } 365 ]; 366 367 const menu = Menu.buildFromTemplate(template); 368 Menu.setApplicationMenu(menu); 369 } else { 370 Menu.setApplicationMenu(null); 371 } 372 } 373 }