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  }