index.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. /*
  2. LiveMe Pro Tools
  3. */
  4. const appName = 'LiveMe Pro Tools';
  5. const { app, BrowserWindow, ipcMain, Menu, shell, dialog, autoUpdater } = require('electron'),
  6. { exec, execFile } = require('child_process');
  7. os = require('os'),
  8. fs = require('fs'),
  9. path = require('path'),
  10. request = require('request'),
  11. tarfs = require('tar-fs'),
  12. DataManager = new(require('./datamanager').DataManager)(),
  13. LiveMe = require('liveme-api'),
  14. isDev = require('electron-is-dev'),
  15. formatDuration = require('format-duration'),
  16. ffmpeg = require('fluent-ffmpeg');
  17. // m3u8stream = require('./modules/m3u8stream'); DISCONTINUED USE ON 04/09/2018 BY THECODER75
  18. var mainWindow = null,
  19. playerWindow = null,
  20. bookmarksWindow = null,
  21. chatWindow = null,
  22. wizardWindow = null,
  23. menu = null,
  24. appSettings = require('electron-settings'),
  25. download_list = [],
  26. errored_list = [],
  27. download_active = false;
  28. function createWindow() {
  29. var isFreshInstall = appSettings.get('general.fresh_install') == null;
  30. if (isFreshInstall == true) {
  31. appSettings.set('general', {
  32. fresh_install: true,
  33. playerpath: '',
  34. hide_zeroreplay_fans: false,
  35. hide_zeroreplay_followings: true
  36. });
  37. appSettings.set('position', {
  38. mainWindow: [ -1, -1],
  39. playerWindow: [ -1, -1],
  40. bookmarksWindow: [ -1, -1],
  41. fansWindow: [-1, -1],
  42. followingsWindow: [-1, -1],
  43. });
  44. appSettings.set('size', {
  45. mainWindow: [ 1024, 600],
  46. playerWindow: [ 370, 680 ],
  47. bookmarksWindow: [ 400, 720 ]
  48. });
  49. appSettings.set('downloads', {
  50. path: path.join(app.getPath('home'), 'Downloads'),
  51. template: '%%replayid%%'
  52. });
  53. appSettings.set('lamd', {
  54. enabled: false,
  55. url: 'http://localhost:8280',
  56. handle_downloads: false
  57. });
  58. }
  59. if (!appSettings.get('downloads.path')) {
  60. appSettings.set('downloads', {
  61. path: path.join(app.getPath('home'), 'Downloads'),
  62. template: '%%replayid%%'
  63. });
  64. }
  65. if (!appSettings.get('downloads.chunks')) { appSettings.set('downloads.chunks', 1); }
  66. if (!appSettings.get('lamd.enabled')) {
  67. appSettings.set('lamd', {
  68. enabled: false,
  69. url: 'http://localhost:8280',
  70. handle_downloads : false
  71. });
  72. }
  73. if (!appSettings.get('history.viewed_maxage')) {
  74. appSettings.set('history', {
  75. viewed_maxage: 1
  76. });
  77. }
  78. var test = appSettings.get('position');
  79. if (test.mainWindow[1] == undefined) {
  80. appSettings.set('position', {
  81. mainWindow: [ -1, -1],
  82. playerWindow: [ -1, -1],
  83. bookmarksWindow: [ -1, -1]
  84. });
  85. }
  86. /*
  87. Create our window definitions
  88. */
  89. var winposition = appSettings.get('position.mainWindow'), winsize = appSettings.get('size.mainWindow');
  90. mainWindow = new BrowserWindow({
  91. icon: __dirname + '/appicon.ico',
  92. width: winsize[0],
  93. height: winsize[1],
  94. minWidth: 1024,
  95. maxWidth: 1024,
  96. minHeight: 480,
  97. maxHeight: 1200,
  98. autoHideMenuBar: true,
  99. disableAutoHideCursor: true,
  100. titleBarStyle: 'default',
  101. fullscreen: false,
  102. maximizable: false,
  103. frame: false,
  104. show: false,
  105. backgroundColor: '#000000',
  106. webPreferences: {
  107. webSecurity: false,
  108. textAreasAreResizable: false,
  109. plugins: true
  110. }
  111. });
  112. wizardWindow = new BrowserWindow({
  113. icon: __dirname + '/appicon.ico',
  114. width: 520,
  115. height: 300,
  116. darkTheme: true,
  117. autoHideMenuBar: false,
  118. disableAutoHideCursor: true,
  119. titleBarStyle: 'default',
  120. resizable: false,
  121. fullscreen: false,
  122. maximizable: false,
  123. show: false,
  124. frame: false,
  125. backgroundColor: 'transparent',
  126. webPreferences: {
  127. webSecurity: false,
  128. textAreasAreResizable: false,
  129. plugins: true
  130. }
  131. });
  132. /*
  133. Configure our window contents and callbacks
  134. */
  135. mainWindow.loadURL(`file://${__dirname}/app/index.html`);
  136. mainWindow
  137. .on('open', () => {
  138. })
  139. .on('close', () => {
  140. appSettings.set('position.mainWindow', mainWindow.getPosition() );
  141. appSettings.set('size.mainWindow', mainWindow.getSize() );
  142. DataManager.saveToDisk();
  143. if (playerWindow != null) { playerWindow.close(); }
  144. if (bookmarksWindow != null) { bookmarksWindow.close(); }
  145. if (chatWindow != null) { chatWindow.close(); }
  146. mainWindow.webContents.session.clearCache(() => {
  147. // Purge the cache to help avoid eating up space on the drive
  148. });
  149. mainWindow = null;
  150. setTimeout(function(){
  151. app.quit();
  152. }, 500);
  153. });
  154. wizardWindow.on('close', () => {
  155. wizardWindow.webContents.session.clearCache(() => {
  156. // Purge the cache to help avoid eating up space on the drive
  157. });
  158. if (mainWindow != null) {
  159. var pos = appSettings.get('position.mainWindow');
  160. mainWindow.setPosition(pos[0], pos[1], false).show();
  161. }
  162. wizardWindow = null;
  163. });
  164. /*
  165. Build our application menus using the templates provided
  166. further down.
  167. */
  168. menu = Menu.buildFromTemplate(getMenuTemplate());
  169. Menu.setApplicationMenu(menu);
  170. global.isDev = isDev;
  171. global.LiveMe = LiveMe;
  172. global.DataManager = DataManager;
  173. DataManager.loadFromDisk();
  174. setTimeout(() => {
  175. var dt = new Date(), ma = appSettings.get('history.viewed_maxage'), od = Math.floor((dt.getTime() - (ma * 86400000)) / 1000);
  176. DataManager.unviewProfiles(od, false);
  177. }, 250);
  178. if (isFreshInstall) {
  179. DataManager.disableWrites();
  180. wizardWindow.loadURL(`file://${__dirname}/app/wizard.html`);
  181. wizardWindow.show();
  182. } else {
  183. mainWindow.show();
  184. var pos = appSettings.get('position.mainWindow').length > 1 ? appSettings.get('position.mainWindow') : [null, null];
  185. if (pos[0] != null) mainWindow.setPosition(pos[0], pos[1], false);
  186. }
  187. /*
  188. 20180409 - Removed by TheCoder
  189. setInterval(() => {
  190. downloadFile();
  191. }, 1000);
  192. */
  193. }
  194. var shouldQuit = app.makeSingleInstance( function(commandLine,workingDirectory) {
  195. if (mainWindow) {
  196. mainWindow.focus();
  197. }
  198. });
  199. if (shouldQuit) {
  200. app.quit();
  201. return;
  202. }
  203. app.on('ready', () => {
  204. createWindow();
  205. });
  206. app.on('window-all-closed', () => {
  207. app.quit();
  208. });
  209. app.on('activate', () => {
  210. if (mainWindow === null) {
  211. createWindow();
  212. }
  213. });
  214. /*
  215. IPC Event Handlers
  216. */
  217. ipcMain.on('import-queue', (event, arg) => {
  218. });
  219. ipcMain.on('import-users', (event, arg) => {
  220. });
  221. ipcMain.on('export-users', (event, arg) => {
  222. });
  223. ipcMain.on('download-replay', (event, arg) => {
  224. download_list.push(arg.videoid);
  225. DataManager.addToQueueList(arg.videoid);
  226. if (download_active == false) {
  227. downloadFile();
  228. }
  229. });
  230. /*
  231. * Cannot cancel active download, only remove queued entries.
  232. */
  233. ipcMain.on('download-cancel', (event, arg) => {
  234. for (var i = 0; i < download_list.length; i++) {
  235. if (download_list[i] == arg.videois) {
  236. download_list.splice(i, 1);
  237. DataManager.removeFromQueueList(arg.videoid);
  238. }
  239. }
  240. });
  241. /*
  242. It is done this way in case the API call to jDownloader returns an error or doesn't connect.
  243. */
  244. function downloadFile() {
  245. if (download_list.length == 0) return;
  246. download_active = true;
  247. LiveMe.getVideoInfo(download_list[0]).then(video => {
  248. console.log(JSON.stringify(video, null, 2));
  249. return;
  250. var path = appSettings.get('downloads.path'),
  251. dt = new Date(video.vtime * 1000), mm = dt.getMonth() + 1, dd = dt.getDate(), filename = '';
  252. filename = appSettings.get('downloads.template')
  253. .replace(/%%broadcaster%%/g, video.uname)
  254. .replace(/%%longid%%/g, video.userid)
  255. .replace(/%%replayid%%/g, video.vid)
  256. .replace(/%%replayviews%%/g, video.playnumber)
  257. .replace(/%%replaylikes%%/g, video.likenum)
  258. .replace(/%%replayshares%%/g, video.sharenum)
  259. .replace(/%%replaytitle%%/g, video.title ? video.title : 'untitled')
  260. .replace(/%%replayduration%%/g, video.videolength)
  261. .replace(/%%replaydatepacked%%/g, (dt.getFullYear() + (mm < 10 ? '0' : '') + mm + (dd < 10 ? '0' : '') + dd))
  262. .replace(/%%replaydateus%%/g, ((mm < 10 ? '0' : '') + mm + '-' + (dd < 10 ? '0' : '') + dd + '-' + dt.getFullYear()))
  263. .replace(/%%replaydateeu%%/g, ((dd < 10 ? '0' : '') + dd + '-' + (mm < 10 ? '0' : '') + mm + '-' + dt.getFullYear()));
  264. filename = filename.replace(/[/\\?%*:|"<>]/g, '-');
  265. filename = filename.replace(/([^a-z0-9\s]+)/gi, '-');
  266. filename = filename.replace(/[\u{0080}-\u{FFFF}]/gu, '');
  267. filename += '.mp4';
  268. video._filename = filename;
  269. DataManager.addDownloaded(video.vid);
  270. /*
  271. 20180409 - Added FFMPEG downloader by TheCoder
  272. */
  273. ffmpeg(video.hlsvideosource)
  274. .outputOptions([
  275. '-c copy',
  276. '-bsf:a aac_adtstoasc',
  277. '-vsync 2',
  278. '-movflags faststart'
  279. ])
  280. .output(path + '/' + filename)
  281. .on('end', function(stdout, stderr) {
  282. mainWindow.webContents.send('download-complete', { videoid: download_list[0] });
  283. download_list.shift();
  284. download_active = false;
  285. setTimeout(() => {
  286. downloadFile();
  287. }, 100);
  288. })
  289. .on('progress', function(progress) {
  290. mainWindow.webContents.send('download-progress', {
  291. videoid: download_list[0],
  292. current: progress.percent,
  293. total: 100
  294. });
  295. })
  296. .on('start', function(c) {
  297. mainWindow.webContents.send('download-start', {
  298. videoid: download_list[0],
  299. filename: filename
  300. });
  301. })
  302. .on('error', function(err, stdout, etderr) {
  303. mainWindow.webContents.send('download-error', { videoid: download_list[0], error: err });
  304. download_list.shift();
  305. download_active = false;
  306. setTimeout(() => {
  307. downloadFile();
  308. }, 100);
  309. })
  310. .run();
  311. /*
  312. 20180409 - Removed by TheCoder
  313. mainWindow.webContents.send('download-start', {
  314. videoid: download_list[0],
  315. filename: filename
  316. });
  317. m3u8stream(video, {
  318. chunkReadahead: appSettings.get('downloads.chunks'),
  319. on_progress: (e) => {
  320. if (mainWindow != null) {
  321. mainWindow.webContents.send('download-progress', {
  322. videoid: e.videoid,
  323. current: e.index,
  324. total: e.total
  325. });
  326. }
  327. },
  328. on_complete: (e) => {
  329. if (mainWindow != null) { mainWindow.webContents.send('download-complete', { videoid: e.videoid }); }
  330. DataManager.addDownloaded(e.videoid);
  331. DataManager.removeFromQueueList(e.videoid);
  332. download_active = false;
  333. download_list.shift();
  334. },
  335. on_error: (e) => {
  336. if (mainWindow != null) { mainWindow.webContents.send('download-error', { videoid: e.videoid, error: e.error }); }
  337. DataManager.addToErroredList(e.videoid);
  338. download_active = false;
  339. download_list.shift();
  340. }
  341. }).pipe(fs.createWriteStream(path + '/' + filename));
  342. */
  343. });
  344. }
  345. /*
  346. Watch a Replay - Use either internal player or external depending on settings
  347. */
  348. ipcMain.on('watch-replay', (event, arg) => {
  349. DataManager.addWatched(arg.videoid);
  350. LiveMe.getVideoInfo(arg.videoid)
  351. .then(video => {
  352. var internalplayer = playerpath = appSettings.get('general.playerpath');
  353. if (playerpath.length > 5) {
  354. exec(playerpath.replace('%url%', video.hlsvideosource));
  355. } else {
  356. // Open internal player
  357. if (playerWindow == null) {
  358. var winposition = appSettings.get('position.playerWindow'), winsize = appSettings.get('size.playerWindow');
  359. playerWindow = new BrowserWindow({
  360. icon: __dirname + '/appicon.ico',
  361. width: winsize[0],
  362. height: winsize[1],
  363. x: winposition[0] != -1 ? winposition[0] : null,
  364. y: winposition[1] != -1 ? winposition[1] : null,
  365. minWidth: 380,
  366. minHeight: 708,
  367. darkTheme: true,
  368. autoHideMenuBar: false,
  369. disableAutoHideCursor: true,
  370. titleBarStyle: 'default',
  371. fullscreen: false,
  372. maximizable: false,
  373. frame: false,
  374. backgroundColor: '#000000',
  375. webPreferences: {
  376. webSecurity: false,
  377. textAreasAreResizable: false,
  378. plugins: true
  379. }
  380. });
  381. playerWindow.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()));
  382. playerWindow.on('close', () => {
  383. appSettings.set('position.playerWindow', playerWindow.getPosition());
  384. appSettings.set('size.playerWindow', playerWindow.getSize());
  385. playerWindow.webContents.session.clearCache(() => {
  386. // Purge the cache to help avoid eating up space on the drive
  387. });
  388. playerWindow = null;
  389. });
  390. }
  391. playerWindow.loadURL(`file://${__dirname}/app/player.html?${video.vid}`);
  392. }
  393. });
  394. });
  395. ipcMain.on('open-bookmarks', (event, arg) => {
  396. });
  397. ipcMain.on('show-user', (event, arg) => {
  398. mainWindow.webContents.send('show-user', { userid: arg.userid });
  399. });
  400. ipcMain.on('open-followings-window', (event, arg) => {
  401. var winposition = appSettings.get('position.followingsWindow') ? appSettings.get('position.followingsWindow') : [-1, -1];
  402. var win = new BrowserWindow({
  403. x: winposition[0] != -1 ? winposition[0] : null,
  404. y: winposition[1] != -1 ? winposition[1] : null,
  405. width: 420,
  406. height: 720,
  407. resizable: false,
  408. darkTheme: false,
  409. autoHideMenuBar: true,
  410. skipTaskbar: false,
  411. backgroundColor: '#000000',
  412. disableAutoHideCursor: true,
  413. titleBarStyle: 'default',
  414. fullscreen: false,
  415. maximizable: false,
  416. closable: true,
  417. frame: false,
  418. show: false
  419. });
  420. win.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()));
  421. win.on('ready-to-show', () => {
  422. win.show();
  423. }).on('close', () => {
  424. appSettings.set('position.followingsWindow', win.getPosition());
  425. }).loadURL(`file://${__dirname}/app/listwindow.html?1&` + arg.userid);
  426. });
  427. ipcMain.on('open-followers-window', (event, arg) => {
  428. var winposition = appSettings.get('position.fansWindow') ? appSettings.get('position.fansWindow') : [-1, -1];
  429. var win = new BrowserWindow({
  430. x: winposition[0] != -1 ? winposition[0] : null,
  431. y: winposition[1] != -1 ? winposition[1] : null,
  432. width: 420,
  433. height: 720,
  434. resizable: false,
  435. darkTheme: false,
  436. autoHideMenuBar: true,
  437. skipTaskbar: false,
  438. backgroundColor: '#000000',
  439. disableAutoHideCursor: true,
  440. titleBarStyle: 'default',
  441. fullscreen: false,
  442. maximizable: false,
  443. closable: true,
  444. frame: false,
  445. show: false
  446. });
  447. win.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()));
  448. win.on('ready-to-show', () => {
  449. win.show();
  450. }).on('close', () => {
  451. appSettings.set('position.fansWindow', win.getPosition());
  452. }).loadURL(`file://${__dirname}/app/listwindow.html?0&` + arg.userid);
  453. });
  454. ipcMain.on('read-comments', (event, arg) => {
  455. var win = new BrowserWindow({
  456. width: 400,
  457. height: 660,
  458. resizable: false,
  459. darkTheme: false,
  460. autoHideMenuBar: true,
  461. skipTaskbar: false,
  462. backgroundColor: '#000000',
  463. disableAutoHideCursor: true,
  464. titleBarStyle: 'default',
  465. fullscreen: false,
  466. maximizable: false,
  467. closable: true,
  468. frame: false,
  469. show: false
  470. });
  471. win.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()));
  472. win.on('ready-to-show', () => {
  473. win.showInactive();
  474. }).loadURL(`file://${__dirname}/app/comments.html?` + arg.userid);
  475. });
  476. ipcMain.on('open-bookmarks', (event, arg) => {
  477. if (bookmarksWindow == null) {
  478. var winposition = appSettings.get('position.bookmarksWindow'), winsize = appSettings.get('size.bookmarksWindow');
  479. bookmarksWindow = new BrowserWindow({
  480. x: winposition[0] > -1 ? winposition[0] : null,
  481. y: winposition[1] > -1 ? winposition[1] : null,
  482. width: 400,
  483. height: winsize[1],
  484. minWidth: 400,
  485. maxWidth: 400,
  486. minHeight: 480,
  487. maxHeight: 1200,
  488. darkTheme: true,
  489. autoHideMenuBar: false,
  490. disableAutoHideCursor: true,
  491. titleBarStyle: 'default',
  492. fullscreen: false,
  493. maximizable: false,
  494. frame: false,
  495. show: false,
  496. backgroundColor: '#000000'
  497. });
  498. bookmarksWindow.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()));
  499. bookmarksWindow.on('close', () => {
  500. appSettings.set('position.bookmarksWindow', bookmarksWindow.getPosition());
  501. appSettings.set('size.bookmarksWindow', bookmarksWindow.getSize());
  502. bookmarksWindow.webContents.session.clearCache(() => {
  503. // Purge the cache to help avoid eating up space on the drive
  504. });
  505. bookmarksWindow = null;
  506. });
  507. }
  508. bookmarksWindow.on('ready-to-show', () => {
  509. bookmarksWindow.show();
  510. }).loadURL(`file://${__dirname}/app/bookmarks.html`);
  511. });
  512. ipcMain.on('restore-backup', (event, arg) => {
  513. let d = dialog.showOpenDialog(
  514. {
  515. properties: [
  516. 'openFile',
  517. ],
  518. buttonLabel : 'Restore',
  519. filters : [
  520. { name : 'TAR files', extensions: [ 'tar' ]}
  521. ]
  522. },
  523. (filePath) => {
  524. if (filePath != null) {
  525. mainWindow.webContents.send('shutdown');
  526. DataManager.disableWrites();
  527. var config_path = path.join(app.getPath('appData'), app.getName(), '/');
  528. fs.createReadStream(filePath[0]).pipe(tarfs.extract(config_path));
  529. setTimeout(function(){
  530. app.relaunch();
  531. app.quit();
  532. }, 1000);
  533. }
  534. }
  535. );
  536. });
  537. ipcMain.on('create-backup', (event, arg) => {
  538. var config_path = path.join(app.getPath('appData'), app.getName()), backup_file = path.join(app.getPath('home'), 'Downloads', 'liveme-pro-tools-backup.tar');
  539. tarfs.pack(
  540. config_path,
  541. {
  542. entries: [ 'bookmarks.json', 'downloaded.json', 'profiles.json', 'watched.json' ]
  543. }
  544. ).pipe(fs.createWriteStream(backup_file));
  545. });
  546. function getMenuTemplate() {
  547. var template = [
  548. {
  549. label: 'Edit',
  550. submenu: [
  551. { role: 'undo' },
  552. { role: 'redo' },
  553. { type: 'separator' },
  554. { role: 'cut' },
  555. { role: 'copy' },
  556. { role: 'paste' },
  557. { role: 'delete' },
  558. { role: 'selectall' }
  559. ]
  560. },
  561. {
  562. role: 'window',
  563. submenu: [
  564. { role: 'minimize' },
  565. { role: 'close' },
  566. { type: 'separator' },
  567. {
  568. label: 'Developer Tools',
  569. submenu: [
  570. { role: 'reload' },
  571. { role: 'forcereload' },
  572. { role: 'toggledevtools' }
  573. ]
  574. }
  575. ]
  576. },
  577. {
  578. role: 'help',
  579. submenu: [
  580. {
  581. label: 'LiveMe Pro Tools Page',
  582. click: () => shell.openExternal('https://thecoder75.github.io/liveme-pro-tools/')
  583. }
  584. ]
  585. }
  586. ];
  587. /*
  588. This is here in case macOS version gets added back end after all the bugs/issues are figured out.
  589. Requires a contributor running macOS now.
  590. */
  591. if (process.platform === 'darwin') {
  592. template.unshift({
  593. label: appName,
  594. submenu: [
  595. {
  596. label: 'About ' + appName,
  597. click: () => {}
  598. },
  599. { type: 'separator' },
  600. { role: 'services', submenu: [] },
  601. { type: 'separator' },
  602. { role: 'hide' },
  603. { role: 'hideothers' },
  604. { role: 'unhide' },
  605. { type: 'separator' },
  606. {
  607. label: 'Quit ' + appName,
  608. accelerator: 'CommandOrControl+Q',
  609. click: () => { mainWindow.close(); }
  610. }
  611. ]
  612. });
  613. }
  614. return template;
  615. }
  616. function getMiniMenuTemplate() {
  617. var template = [
  618. {
  619. label: 'Edit',
  620. submenu: [
  621. { role: 'undo' },
  622. { role: 'redo' },
  623. { type: 'separator' },
  624. { role: 'cut' },
  625. { role: 'copy' },
  626. { role: 'paste' },
  627. { role: 'delete' },
  628. { role: 'selectall' }
  629. ]
  630. },
  631. {
  632. role: 'window',
  633. submenu: [
  634. { role: 'minimize' },
  635. { role: 'close' },
  636. { type: 'separator' },
  637. {
  638. label: 'Developer Tools',
  639. submenu: [
  640. { role: 'reload' },
  641. { role: 'forcereload' },
  642. { role: 'toggledevtools' }
  643. ]
  644. }
  645. ]
  646. }
  647. ];
  648. return template;
  649. }