app.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. /* jshint esversion: 6 */
  2. const electron = require('electron');
  3. const updater = require('electron-updater');
  4. const fs = require('fs');
  5. const os = require('os');
  6. const path = require('path');
  7. const process = require('process');
  8. const url = require('url');
  9. class Application {
  10. constructor() {
  11. this._views = new ViewCollection();
  12. this._configuration = new ConfigurationService();
  13. this._menu = new MenuService();
  14. this._openQueue = [];
  15. electron.app.setAppUserModelId('com.lutzroeder.netron');
  16. electron.app.allowRendererProcessReuse = true;
  17. if (!electron.app.requestSingleInstanceLock()) {
  18. electron.app.quit();
  19. return;
  20. }
  21. electron.app.on('second-instance', (event, commandLine, workingDirectory) => {
  22. const currentDirectory = process.cwd();
  23. process.chdir(workingDirectory);
  24. const open = this._parseCommandLine(commandLine);
  25. process.chdir(currentDirectory);
  26. if (!open) {
  27. if (this._views.count > 0) {
  28. const view = this._views.item(0);
  29. if (view) {
  30. view.restore();
  31. }
  32. }
  33. }
  34. });
  35. electron.ipcMain.on('open-file-dialog', () => {
  36. this._openFileDialog();
  37. });
  38. electron.ipcMain.on('get-environment', (event) => {
  39. event.returnValue = {
  40. version: electron.app.getVersion(),
  41. package: electron.app.isPackaged,
  42. zoom: 'scroll'
  43. // zoom: 'drag'
  44. };
  45. });
  46. electron.ipcMain.on('get-configuration', (event, obj) => {
  47. event.returnValue = this._configuration.has(obj.name) ? this._configuration.get(obj.name) : undefined;
  48. });
  49. electron.ipcMain.on('set-configuration', (event, obj) => {
  50. this._configuration.set(obj.name, obj.value);
  51. });
  52. electron.ipcMain.on('drop-paths', (event, data) => {
  53. const paths = data.paths.filter((path) => {
  54. if (fs.existsSync(path)) {
  55. const stat = fs.statSync(path);
  56. return stat.isFile() || stat.isDirectory();
  57. }
  58. return false;
  59. });
  60. this._dropPaths(event.sender, paths);
  61. });
  62. electron.ipcMain.on('show-message-box', (event, options) => {
  63. const owner = event.sender.getOwnerBrowserWindow();
  64. event.returnValue = electron.dialog.showMessageBoxSync(owner, options);
  65. });
  66. electron.ipcMain.on('show-save-dialog', (event, options) => {
  67. const owner = event.sender.getOwnerBrowserWindow();
  68. event.returnValue = electron.dialog.showSaveDialogSync(owner, options);
  69. });
  70. electron.app.on('will-finish-launching', () => {
  71. electron.app.on('open-file', (event, path) => {
  72. this._openPath(path);
  73. });
  74. });
  75. electron.app.on('ready', () => {
  76. this._ready();
  77. });
  78. electron.app.on('window-all-closed', () => {
  79. if (process.platform !== 'darwin') {
  80. electron.app.quit();
  81. }
  82. });
  83. electron.app.on('will-quit', () => {
  84. this._configuration.save();
  85. });
  86. this._parseCommandLine(process.argv);
  87. this._checkForUpdates();
  88. }
  89. _parseCommandLine(argv) {
  90. let open = false;
  91. if (argv.length > 1) {
  92. for (const arg of argv.slice(1)) {
  93. if (!arg.startsWith('-') && arg !== path.dirname(__dirname)) {
  94. const extension = path.extname(arg).toLowerCase();
  95. if (extension != '' && extension != 'js' && fs.existsSync(arg)) {
  96. const stat = fs.statSync(arg);
  97. if (stat.isFile() || stat.isDirectory()) {
  98. this._openPath(arg);
  99. open = true;
  100. }
  101. }
  102. }
  103. }
  104. }
  105. return open;
  106. }
  107. _ready() {
  108. this._configuration.load();
  109. if (!this._configuration.has('userId')) {
  110. this._configuration.set('userId', this._uuid());
  111. }
  112. if (this._openQueue) {
  113. const queue = this._openQueue;
  114. this._openQueue = null;
  115. while (queue.length > 0) {
  116. const file = queue.shift();
  117. this._openPath(file);
  118. }
  119. }
  120. if (this._views.count == 0) {
  121. this._views.openView();
  122. }
  123. this._resetMenu();
  124. this._views.on('active-view-changed', () => {
  125. this._updateMenu();
  126. });
  127. this._views.on('active-view-updated', () => {
  128. this._updateMenu();
  129. });
  130. }
  131. _uuid() {
  132. const buffer = new Uint8Array(16);
  133. require("crypto").randomFillSync(buffer);
  134. buffer[6] = buffer[6] & 0x0f | 0x40;
  135. buffer[8] = buffer[8] & 0x3f | 0x80;
  136. const text = Array.from(buffer).map((value) => value < 0x10 ? '0' + value.toString(16) : value.toString(16)).join('');
  137. return text.slice(0, 8) + '-' + text.slice(8, 12) + '-' + text.slice(12, 16) + '-' + text.slice(16, 20) + '-' + text.slice(20, 32);
  138. }
  139. _openFileDialog() {
  140. const showOpenDialogOptions = {
  141. properties: [ 'openFile' ],
  142. filters: [
  143. { name: 'All Model Files', extensions: [
  144. 'onnx', 'pb',
  145. 'h5', 'hd5', 'hdf5', 'json', 'keras',
  146. 'mlmodel', 'mlpackage',
  147. 'caffemodel',
  148. 'model', 'dnn', 'cmf', 'mar', 'params',
  149. 'pdmodel', 'pdparams',
  150. 'meta',
  151. 'tflite', 'lite', 'tfl',
  152. 'armnn', 'mnn', 'nn', 'uff', 'uff.txt', 'rknn', 'xmodel',
  153. 'ncnn', 'param', 'tnnproto', 'tmfile', 'ms', 'om',
  154. 'pt', 'pth', 't7',
  155. 'pkl', 'joblib',
  156. 'pbtxt', 'prototxt',
  157. 'cfg', 'xml',
  158. 'zip', 'tar' ] }
  159. ]
  160. };
  161. const selectedFiles = electron.dialog.showOpenDialogSync(showOpenDialogOptions);
  162. if (selectedFiles) {
  163. for (const file of selectedFiles) {
  164. this._openPath(file);
  165. }
  166. }
  167. }
  168. _openPath(path) {
  169. if (this._openQueue) {
  170. this._openQueue.push(path);
  171. return;
  172. }
  173. if (path && path.length > 0 && fs.existsSync(path)) {
  174. const stat = fs.statSync(path);
  175. if (stat.isFile() || stat.isDirectory()) {
  176. // find existing view for this file
  177. let view = this._views.find(path);
  178. // find empty welcome window
  179. if (view == null) {
  180. view = this._views.find(null);
  181. }
  182. // create new window
  183. if (view == null) {
  184. view = this._views.openView();
  185. }
  186. this._loadPath(path, view);
  187. }
  188. }
  189. }
  190. _loadPath(path, view) {
  191. const recents = this._configuration.get('recents').filter((recent) => path != recent.path);
  192. view.open(path);
  193. recents.unshift({ path: path });
  194. if (recents.length > 9) {
  195. recents.splice(9);
  196. }
  197. this._configuration.set('recents', recents);
  198. this._resetMenu();
  199. }
  200. _dropPaths(sender, paths) {
  201. let view = this._views.from(sender);
  202. for (const path of paths) {
  203. if (view) {
  204. this._loadPath(path, view);
  205. view = null;
  206. }
  207. else {
  208. this._openPath(path);
  209. }
  210. }
  211. }
  212. _export() {
  213. const view = this._views.activeView;
  214. if (view && view.path) {
  215. let defaultPath = 'Untitled';
  216. const file = view.path;
  217. const lastIndex = file.lastIndexOf('.');
  218. if (lastIndex != -1) {
  219. defaultPath = file.substring(0, lastIndex);
  220. }
  221. const owner = electron.BrowserWindow.getFocusedWindow();
  222. const showSaveDialogOptions = {
  223. title: 'Export',
  224. defaultPath: defaultPath,
  225. buttonLabel: 'Export',
  226. filters: [
  227. { name: 'PNG', extensions: [ 'png' ] },
  228. { name: 'SVG', extensions: [ 'svg' ] }
  229. ]
  230. };
  231. const selectedFile = electron.dialog.showSaveDialogSync(owner, showSaveDialogOptions);
  232. if (selectedFile) {
  233. view.execute('export', { 'file': selectedFile });
  234. }
  235. }
  236. }
  237. service(name) {
  238. if (name == 'configuration') {
  239. return this._configuration;
  240. }
  241. return undefined;
  242. }
  243. execute(command, data) {
  244. const view = this._views.activeView;
  245. if (view) {
  246. view.execute(command, data || {});
  247. }
  248. this._updateMenu();
  249. }
  250. _reload() {
  251. const view = this._views.activeView;
  252. if (view && view.path) {
  253. this._loadPath(view.path, view);
  254. }
  255. }
  256. _checkForUpdates() {
  257. if (!electron.app.isPackaged) {
  258. return;
  259. }
  260. const autoUpdater = updater.autoUpdater;
  261. if (autoUpdater.app && autoUpdater.app.appUpdateConfigPath && !fs.existsSync(autoUpdater.app.appUpdateConfigPath)) {
  262. return;
  263. }
  264. const promise = autoUpdater.checkForUpdates();
  265. if (promise) {
  266. promise.catch((error) => {
  267. console.log(error.message);
  268. });
  269. }
  270. }
  271. get package() {
  272. if (!this._package) {
  273. const file = path.join(path.dirname(__dirname), 'package.json');
  274. const data = fs.readFileSync(file);
  275. this._package = JSON.parse(data);
  276. this._package.date = new Date(fs.statSync(file).mtime);
  277. }
  278. return this._package;
  279. }
  280. _about() {
  281. let dialog = null;
  282. const options = {
  283. show: false,
  284. backgroundColor: electron.nativeTheme.shouldUseDarkColors ? '#2d2d2d' : '#e6e6e6',
  285. width: 400,
  286. height: 250,
  287. center: true,
  288. minimizable: false,
  289. maximizable: false,
  290. useContentSize: true,
  291. resizable: true,
  292. fullscreenable: false,
  293. webPreferences: {
  294. nodeIntegration: true,
  295. }
  296. };
  297. if (process.platform === 'darwin') {
  298. options.title = '';
  299. dialog = Application._aboutDialog;
  300. }
  301. else {
  302. options.title = 'About ' + electron.app.name;
  303. options.parent = electron.BrowserWindow.getFocusedWindow();
  304. options.modal = true;
  305. options.showInTaskbar = false;
  306. }
  307. if (process.platform === 'win32') {
  308. options.type = 'toolbar';
  309. }
  310. if (!dialog) {
  311. dialog = new electron.BrowserWindow(options);
  312. if (process.platform === 'darwin') {
  313. Application._aboutDialog = dialog;
  314. }
  315. dialog.removeMenu();
  316. dialog.excludedFromShownWindowsMenu = true;
  317. dialog.webContents.on('new-window', (event, url) => {
  318. if (url.startsWith('http://') || url.startsWith('https://')) {
  319. event.preventDefault();
  320. electron.shell.openExternal(url);
  321. }
  322. });
  323. let content = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8');
  324. content = content.replace('{version}', this.package.version);
  325. content = content.replace('<title>Netron</title>', '');
  326. content = content.replace('<body class="welcome spinner">', '<body class="about desktop">');
  327. content = content.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
  328. content = content.replace(/<link.*>/gi, '');
  329. dialog.once('ready-to-show', () => {
  330. dialog.resizable = false;
  331. dialog.show();
  332. });
  333. dialog.on('close', function() {
  334. electron.globalShortcut.unregister('Escape');
  335. Application._aboutDialog = null;
  336. });
  337. dialog.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(content));
  338. electron.globalShortcut.register('Escape', function() {
  339. dialog.close();
  340. });
  341. }
  342. else {
  343. dialog.show();
  344. }
  345. }
  346. _updateMenu() {
  347. const window = electron.BrowserWindow.getFocusedWindow();
  348. this._menu.update({
  349. window: window,
  350. webContents: window ? window.webContents : null,
  351. view: this._views.activeView
  352. }, this._views.views.map((view) => view.window));
  353. }
  354. _resetMenu() {
  355. const menuRecentsTemplate = [];
  356. if (this._configuration.has('recents')) {
  357. let recents = this._configuration.get('recents');
  358. recents = recents.filter((recent) => {
  359. const path = recent.path;
  360. if (fs.existsSync(path)) {
  361. const stat = fs.statSync(path);
  362. if (stat.isFile() || stat.isDirectory()) {
  363. return true;
  364. }
  365. }
  366. return false;
  367. });
  368. if (recents.length > 9) {
  369. recents.splice(9);
  370. }
  371. this._configuration.set('recents', recents);
  372. for (let i = 0; i < recents.length; i++) {
  373. const recent = recents[i];
  374. menuRecentsTemplate.push({
  375. path: recent.path,
  376. label: Application.minimizePath(recent.path),
  377. accelerator: ((process.platform === 'darwin') ? 'Cmd+' : 'Ctrl+') + (i + 1).toString(),
  378. click: (item) => { this._openPath(item.path); }
  379. });
  380. }
  381. }
  382. const menuTemplate = [];
  383. if (process.platform === 'darwin') {
  384. menuTemplate.unshift({
  385. label: electron.app.name,
  386. submenu: [
  387. {
  388. label: 'About ' + electron.app.name,
  389. click: () => this._about()
  390. },
  391. { type: 'separator' },
  392. { role: 'hide' },
  393. { role: 'hideothers' },
  394. { role: 'unhide' },
  395. { type: 'separator' },
  396. { role: 'quit' }
  397. ]
  398. });
  399. }
  400. menuTemplate.push({
  401. label: '&File',
  402. submenu: [
  403. {
  404. label: '&Open...',
  405. accelerator: 'CmdOrCtrl+O',
  406. click: () => { this._openFileDialog(); }
  407. },
  408. {
  409. label: 'Open &Recent',
  410. submenu: menuRecentsTemplate
  411. },
  412. { type: 'separator' },
  413. {
  414. id: 'file.export',
  415. label: '&Export...',
  416. accelerator: 'CmdOrCtrl+Shift+E',
  417. click: () => this._export(),
  418. },
  419. { type: 'separator' },
  420. { role: 'close' },
  421. ]
  422. });
  423. if (process.platform !== 'darwin') {
  424. menuTemplate.slice(-1)[0].submenu.push(
  425. { type: 'separator' },
  426. { role: 'quit' }
  427. );
  428. }
  429. if (process.platform == 'darwin') {
  430. electron.systemPreferences.setUserDefault('NSDisabledDictationMenuItem', 'boolean', true);
  431. electron.systemPreferences.setUserDefault('NSDisabledCharacterPaletteMenuItem', 'boolean', true);
  432. }
  433. menuTemplate.push({
  434. label: '&Edit',
  435. submenu: [
  436. {
  437. id: 'edit.cut',
  438. label: 'Cu&t',
  439. accelerator: 'CmdOrCtrl+X',
  440. click: () => this.execute('cut', null),
  441. },
  442. {
  443. id: 'edit.copy',
  444. label: '&Copy',
  445. accelerator: 'CmdOrCtrl+C',
  446. click: () => this.execute('copy', null),
  447. },
  448. {
  449. id: 'edit.paste',
  450. label: '&Paste',
  451. accelerator: 'CmdOrCtrl+V',
  452. click: () => this.execute('paste', null),
  453. },
  454. {
  455. id: 'edit.select-all',
  456. label: 'Select &All',
  457. accelerator: 'CmdOrCtrl+A',
  458. click: () => this.execute('selectall', null),
  459. },
  460. { type: 'separator' },
  461. {
  462. id: 'edit.find',
  463. label: '&Find...',
  464. accelerator: 'CmdOrCtrl+F',
  465. click: () => this.execute('find', null),
  466. }
  467. ]
  468. });
  469. const viewTemplate = {
  470. label: '&View',
  471. submenu: [
  472. {
  473. id: 'view.show-attributes',
  474. accelerator: 'CmdOrCtrl+D',
  475. click: () => this.execute('toggle-attributes', null),
  476. },
  477. {
  478. id: 'view.show-initializers',
  479. accelerator: 'CmdOrCtrl+I',
  480. click: () => this.execute('toggle-initializers', null),
  481. },
  482. {
  483. id: 'view.show-names',
  484. accelerator: 'CmdOrCtrl+U',
  485. click: () => this.execute('toggle-names', null),
  486. },
  487. {
  488. id: 'view.show-horizontal',
  489. accelerator: 'CmdOrCtrl+K',
  490. click: () => this.execute('toggle-direction', null),
  491. },
  492. { type: 'separator' },
  493. {
  494. id: 'view.reload',
  495. label: '&Reload',
  496. accelerator: (process.platform === 'darwin') ? 'Cmd+R' : 'F5',
  497. click: () => this._reload(),
  498. },
  499. { type: 'separator' },
  500. {
  501. id: 'view.reset-zoom',
  502. label: 'Actual &Size',
  503. accelerator: 'Shift+Backspace',
  504. click: () => this.execute('reset-zoom', null),
  505. },
  506. {
  507. id: 'view.zoom-in',
  508. label: 'Zoom &In',
  509. accelerator: 'Shift+Up',
  510. click: () => this.execute('zoom-in', null),
  511. },
  512. {
  513. id: 'view.zoom-out',
  514. label: 'Zoom &Out',
  515. accelerator: 'Shift+Down',
  516. click: () => this.execute('zoom-out', null),
  517. },
  518. { type: 'separator' },
  519. {
  520. id: 'view.show-properties',
  521. label: '&Properties...',
  522. accelerator: 'CmdOrCtrl+Enter',
  523. click: () => this.execute('show-properties', null),
  524. }
  525. ]
  526. };
  527. if (!electron.app.isPackaged) {
  528. viewTemplate.submenu.push({ type: 'separator' });
  529. viewTemplate.submenu.push({ role: 'toggledevtools' });
  530. }
  531. menuTemplate.push(viewTemplate);
  532. if (process.platform === 'darwin') {
  533. menuTemplate.push({
  534. role: 'window',
  535. submenu: [
  536. { role: 'minimize' },
  537. { role: 'zoom' },
  538. { type: 'separator' },
  539. { role: 'front'}
  540. ]
  541. });
  542. }
  543. const helpSubmenu = [
  544. {
  545. label: '&Search Feature Requests',
  546. click: () => { electron.shell.openExternal('https://www.github.com/' + this.package.repository + '/issues'); }
  547. },
  548. {
  549. label: 'Report &Issues',
  550. click: () => { electron.shell.openExternal('https://www.github.com/' + this.package.repository + '/issues/new'); }
  551. }
  552. ];
  553. if (process.platform != 'darwin') {
  554. helpSubmenu.push({ type: 'separator' });
  555. helpSubmenu.push({
  556. label: 'About ' + electron.app.name,
  557. click: () => this._about()
  558. });
  559. }
  560. menuTemplate.push({
  561. role: 'help',
  562. submenu: helpSubmenu
  563. });
  564. const commandTable = new Map();
  565. commandTable.set('file.export', {
  566. enabled: (context) => { return context.view && context.view.path ? true : false; }
  567. });
  568. commandTable.set('edit.cut', {
  569. enabled: (context) => { return context.view && context.view.path ? true : false; }
  570. });
  571. commandTable.set('edit.copy', {
  572. enabled: (context) => { return context.view && context.view.path ? true : false; }
  573. });
  574. commandTable.set('edit.paste', {
  575. enabled: (context) => { return context.view && context.view.path ? true : false; }
  576. });
  577. commandTable.set('edit.select-all', {
  578. enabled: (context) => { return context.view && context.view.path ? true : false; }
  579. });
  580. commandTable.set('edit.find', {
  581. enabled: (context) => { return context.view && context.view.path ? true : false; }
  582. });
  583. commandTable.set('view.show-attributes', {
  584. enabled: (context) => { return context.view && context.view.path ? true : false; },
  585. label: (context) => { return !context.view || !context.view.get('show-attributes') ? 'Show &Attributes' : 'Hide &Attributes'; }
  586. });
  587. commandTable.set('view.show-initializers', {
  588. enabled: (context) => { return context.view && context.view.path ? true : false; },
  589. label: (context) => { return !context.view || !context.view.get('show-initializers') ? 'Show &Initializers' : 'Hide &Initializers'; }
  590. });
  591. commandTable.set('view.show-names', {
  592. enabled: (context) => { return context.view && context.view.path ? true : false; },
  593. label: (context) => { return !context.view || !context.view.get('show-names') ? 'Show &Names' : 'Hide &Names'; }
  594. });
  595. commandTable.set('view.show-horizontal', {
  596. enabled: (context) => { return context.view && context.view.path ? true : false; },
  597. label: (context) => { return !context.view || !context.view.get('show-horizontal') ? 'Show &Horizontal' : 'Show &Vertical'; }
  598. });
  599. commandTable.set('view.reload', {
  600. enabled: (context) => { return context.view && context.view.path ? true : false; }
  601. });
  602. commandTable.set('view.reset-zoom', {
  603. enabled: (context) => { return context.view && context.view.path ? true : false; }
  604. });
  605. commandTable.set('view.zoom-in', {
  606. enabled: (context) => { return context.view && context.view.path ? true : false; }
  607. });
  608. commandTable.set('view.zoom-out', {
  609. enabled: (context) => { return context.view && context.view.path ? true : false; }
  610. });
  611. commandTable.set('view.show-properties', {
  612. enabled: (context) => { return context.view && context.view.path ? true : false; }
  613. });
  614. this._menu.build(menuTemplate, commandTable, this._views.views.map((view) => view.window));
  615. this._updateMenu();
  616. }
  617. static minimizePath(file) {
  618. if (process.platform != 'win32') {
  619. const homeDir = os.homedir();
  620. if (file.startsWith(homeDir)) {
  621. return '~' + file.substring(homeDir.length);
  622. }
  623. }
  624. return file;
  625. }
  626. }
  627. class View {
  628. constructor(owner) {
  629. this._owner = owner;
  630. this._ready = false;
  631. this._path = null;
  632. this._properties = new Map();
  633. this._location = url.format({ protocol: 'file:', slashes: true, pathname: path.join(__dirname, 'electron.html') });
  634. const size = electron.screen.getPrimaryDisplay().workAreaSize;
  635. const options = {
  636. show: false,
  637. title: electron.app.name,
  638. backgroundColor: electron.nativeTheme.shouldUseDarkColors ? '#1d1d1d' : '#e6e6e6',
  639. icon: electron.nativeImage.createFromPath(path.join(__dirname, 'icon.png')),
  640. minWidth: 600,
  641. minHeight: 400,
  642. width: size.width > 1024 ? 1024 : size.width,
  643. height: size.height > 768 ? 768 : size.height,
  644. webPreferences: {
  645. preload: path.join(__dirname, 'electron.js'),
  646. nodeIntegration: true
  647. }
  648. };
  649. if (this._owner.count > 0 && View._position && View._position.length == 2) {
  650. options.x = View._position[0] + 30;
  651. options.y = View._position[1] + 30;
  652. if (options.x + options.width > size.width) {
  653. options.x = 0;
  654. }
  655. if (options.y + options.height > size.height) {
  656. options.y = 0;
  657. }
  658. }
  659. this._window = new electron.BrowserWindow(options);
  660. View._position = this._window.getPosition();
  661. this._updateCallback = (event, data) => {
  662. if (event.sender == this._window.webContents) {
  663. this.update(data.name, data.value);
  664. this._raise('updated');
  665. }
  666. };
  667. electron.ipcMain.on('update', this._updateCallback);
  668. this._window.on('closed', () => {
  669. electron.ipcMain.removeListener('update', this._updateCallback);
  670. this._owner.closeView(this);
  671. });
  672. this._window.on('focus', () => {
  673. this._raise('activated');
  674. });
  675. this._window.on('blur', () => {
  676. this._raise('deactivated');
  677. });
  678. this._window.webContents.on('did-finish-load', () => {
  679. this._didFinishLoad = true;
  680. });
  681. this._window.webContents.on('new-window', (event, url) => {
  682. if (url.startsWith('http://') || url.startsWith('https://')) {
  683. event.preventDefault();
  684. electron.shell.openExternal(url);
  685. }
  686. });
  687. this._window.once('ready-to-show', () => {
  688. this._window.show();
  689. });
  690. this._window.loadURL(this._location);
  691. }
  692. get window() {
  693. return this._window;
  694. }
  695. get path() {
  696. return this._path;
  697. }
  698. open(path) {
  699. this._openPath = path;
  700. if (this._didFinishLoad) {
  701. this._window.webContents.send('open', { path: path });
  702. }
  703. else {
  704. this._window.webContents.on('did-finish-load', () => {
  705. this._window.webContents.send('open', { path: path });
  706. });
  707. this._window.loadURL(this._location);
  708. }
  709. }
  710. restore() {
  711. if (this._window) {
  712. if (this._window.isMinimized()) {
  713. this._window.restore();
  714. }
  715. this._window.show();
  716. }
  717. }
  718. match(path) {
  719. if (this._openPath) {
  720. if (path === null) {
  721. return false;
  722. }
  723. if (path === this._openPath) {
  724. return true;
  725. }
  726. }
  727. return this._path == path;
  728. }
  729. execute(command, data) {
  730. if (this._window && this._window.webContents) {
  731. this._window.webContents.send(command, data);
  732. }
  733. }
  734. update(name, value) {
  735. if (name === 'path') {
  736. if (value) {
  737. this._path = value;
  738. const title = Application.minimizePath(this._path);
  739. this._window.setTitle(process.platform !== 'darwin' ? title + ' - ' + electron.app.name : title);
  740. this._window.focus();
  741. }
  742. this._openPath = null;
  743. return;
  744. }
  745. this._properties.set(name, value);
  746. }
  747. get(name) {
  748. return this._properties.get(name);
  749. }
  750. on(event, callback) {
  751. this._events = this._events || {};
  752. this._events[event] = this._events[event] || [];
  753. this._events[event].push(callback);
  754. }
  755. _raise(event, data) {
  756. if (this._events && this._events[event]) {
  757. for (const callback of this._events[event]) {
  758. callback(this, data);
  759. }
  760. }
  761. }
  762. }
  763. class ViewCollection {
  764. constructor() {
  765. this._views = [];
  766. }
  767. get views() {
  768. return this._views;
  769. }
  770. get count() {
  771. return this._views.length;
  772. }
  773. item(index) {
  774. return this._views[index];
  775. }
  776. openView() {
  777. const view = new View(this);
  778. view.on('activated', (sender) => {
  779. this._activeView = sender;
  780. this._raise('active-view-changed', { activeView: this._activeView });
  781. });
  782. view.on('updated', () => {
  783. this._raise('active-view-updated', { activeView: this._activeView });
  784. });
  785. view.on('deactivated', () => {
  786. this._activeView = null;
  787. this._raise('active-view-changed', { activeView: this._activeView });
  788. });
  789. this._views.push(view);
  790. this._updateActiveView();
  791. return view;
  792. }
  793. closeView(view) {
  794. for (let i = this._views.length - 1; i >= 0; i--) {
  795. if (this._views[i] == view) {
  796. this._views.splice(i, 1);
  797. }
  798. }
  799. this._updateActiveView();
  800. }
  801. find(path) {
  802. return this._views.find(view => view.match(path));
  803. }
  804. from(contents) {
  805. return this._views.find(view => view && view.window && view.window.webContents && view.window.webContents == contents);
  806. }
  807. get activeView() {
  808. return this._activeView;
  809. }
  810. on(event, callback) {
  811. this._events = this._events || {};
  812. this._events[event] = this._events[event] || [];
  813. this._events[event].push(callback);
  814. }
  815. _raise(event, data) {
  816. if (this._events && this._events[event]) {
  817. for (const callback of this._events[event]) {
  818. callback(this, data);
  819. }
  820. }
  821. }
  822. _updateActiveView() {
  823. const window = electron.BrowserWindow.getFocusedWindow();
  824. const view = this._views.find(view => view.window == window) || null;
  825. if (view != this._activeView) {
  826. this._activeView = view;
  827. this._raise('active-view-changed', { activeView: this._activeView });
  828. }
  829. }
  830. }
  831. class ConfigurationService {
  832. load() {
  833. this._data = { 'recents': [] };
  834. const dir = electron.app.getPath('userData');
  835. if (dir && dir.length > 0) {
  836. const file = path.join(dir, 'configuration.json');
  837. if (fs.existsSync(file)) {
  838. const data = fs.readFileSync(file);
  839. if (data) {
  840. try {
  841. this._data = JSON.parse(data);
  842. }
  843. catch (error) {
  844. // continue regardless of error
  845. }
  846. }
  847. }
  848. }
  849. }
  850. save() {
  851. if (this._data) {
  852. const data = JSON.stringify(this._data, null, 2);
  853. if (data) {
  854. const dir = electron.app.getPath('userData');
  855. if (dir && dir.length > 0) {
  856. const file = path.join(dir, 'configuration.json');
  857. fs.writeFileSync(file, data);
  858. }
  859. }
  860. }
  861. }
  862. has(name) {
  863. return this._data && Object.prototype.hasOwnProperty.call(this._data, name);
  864. }
  865. set(name, value) {
  866. this._data[name] = value;
  867. }
  868. get(name) {
  869. return this._data[name];
  870. }
  871. }
  872. class MenuService {
  873. build(menuTemplate, commandTable, windows) {
  874. this._menuTemplate = menuTemplate;
  875. this._commandTable = commandTable;
  876. this._itemTable = new Map();
  877. for (const menu of menuTemplate) {
  878. for (const item of menu.submenu) {
  879. if (item.id) {
  880. if (!item.label) {
  881. item.label = '';
  882. }
  883. this._itemTable.set(item.id, item);
  884. }
  885. }
  886. }
  887. this._rebuild(windows);
  888. }
  889. update(context, windows) {
  890. if (!this._menu && !this._commandTable) {
  891. return;
  892. }
  893. if (this._updateLabel(context)) {
  894. this._rebuild(windows);
  895. }
  896. this._updateEnabled(context);
  897. }
  898. _rebuild(windows) {
  899. this._menu = electron.Menu.buildFromTemplate(this._menuTemplate);
  900. if (process.platform === 'darwin') {
  901. electron.Menu.setApplicationMenu(this._menu);
  902. }
  903. else {
  904. for (const window of windows) {
  905. window.setMenu(this._menu);
  906. }
  907. }
  908. }
  909. _updateLabel(context) {
  910. let rebuild = false;
  911. for (const entry of this._commandTable.entries()) {
  912. const menuItem = this._menu.getMenuItemById(entry[0]);
  913. const command = entry[1];
  914. if (command && command.label) {
  915. const label = command.label(context);
  916. if (label != menuItem.label) {
  917. if (this._itemTable.has(entry[0])) {
  918. this._itemTable.get(entry[0]).label = label;
  919. rebuild = true;
  920. }
  921. }
  922. }
  923. }
  924. return rebuild;
  925. }
  926. _updateEnabled(context) {
  927. for (const entry of this._commandTable.entries()) {
  928. const menuItem = this._menu.getMenuItemById(entry[0]);
  929. if (menuItem) {
  930. const command = entry[1];
  931. if (command.enabled) {
  932. menuItem.enabled = command.enabled(context);
  933. }
  934. }
  935. }
  936. }
  937. }
  938. global.application = new Application();