aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/Menu.ts
diff options
context:
space:
mode:
authorLibravatar muhamedsalih-tw <104364298+muhamedsalih-tw@users.noreply.github.com>2022-11-06 10:36:51 +0530
committerLibravatar GitHub <noreply@github.com>2022-11-06 05:06:51 +0000
commite7dbea5bc6d7e6b121dbc94b21b759a29f16e0c0 (patch)
tree958d6dad84687352af27c19c50bdcac345bc3a79 /src/lib/Menu.ts
parent6.2.1-nightly.39 [skip ci] (diff)
downloadferdium-app-e7dbea5bc6d7e6b121dbc94b21b759a29f16e0c0.tar.gz
ferdium-app-e7dbea5bc6d7e6b121dbc94b21b759a29f16e0c0.tar.zst
ferdium-app-e7dbea5bc6d7e6b121dbc94b21b759a29f16e0c0.zip
Transform tray & menu files to typescript (#740)
Diffstat (limited to 'src/lib/Menu.ts')
-rw-r--r--src/lib/Menu.ts1286
1 files changed, 1286 insertions, 0 deletions
diff --git a/src/lib/Menu.ts b/src/lib/Menu.ts
new file mode 100644
index 000000000..c206ea55d
--- /dev/null
+++ b/src/lib/Menu.ts
@@ -0,0 +1,1286 @@
1import { clipboard, MenuItemConstructorOptions } from 'electron';
2import {
3 app,
4 Menu,
5 dialog,
6 webContents,
7 systemPreferences,
8 getCurrentWindow,
9} from '@electron/remote';
10import { autorun, action, makeObservable, observable } from 'mobx';
11import { defineMessages, IntlShape } from 'react-intl';
12import osName from 'os-name';
13import { fromJS } from 'immutable';
14import semver from 'semver';
15import os from 'os';
16import {
17 isWindows,
18 cmdOrCtrlShortcutKey,
19 altKey,
20 shiftKey,
21 settingsShortcutKey,
22 isLinux,
23 isMac,
24 lockFerdiumShortcutKey,
25 todosToggleShortcutKey,
26 workspaceToggleShortcutKey,
27 addNewServiceShortcutKey,
28 splitModeToggleShortcutKey,
29 muteFerdiumShortcutKey,
30 electronVersion,
31 chromeVersion,
32 nodeVersion,
33 osArch,
34 toggleFullScreenKey,
35} from '../environment';
36import { CUSTOM_WEBSITE_RECIPE_ID, LIVE_API_FERDIUM_WEBSITE } from '../config';
37import { ferdiumVersion } from '../environment-remote';
38import { todoActions } from '../features/todos/actions';
39import workspaceActions from '../features/workspaces/actions';
40import { workspaceStore } from '../features/workspaces/index';
41import { importExportURL, serverBase, serverName } from '../api/apiBase';
42import { openExternalUrl } from '../helpers/url-helpers';
43import globalMessages from '../i18n/globalMessages';
44import { onAuthGoToReleaseNotes } from '../helpers/update-helpers';
45// @ts-expect-error Cannot find module '../buildInfo.json' or its corresponding type declarations.
46import { timestamp, gitHashShort, gitBranch } from '../buildInfo.json';
47import Service from '../models/Service';
48import { StoresProps } from '../@types/ferdium-components.types';
49import { RealStores } from '../stores';
50
51const menuItems = defineMessages({
52 edit: {
53 id: 'menu.edit',
54 defaultMessage: 'Edit',
55 },
56 undo: {
57 id: 'menu.edit.undo',
58 defaultMessage: 'Undo',
59 },
60 redo: {
61 id: 'menu.edit.redo',
62 defaultMessage: 'Redo',
63 },
64 cut: {
65 id: 'menu.edit.cut',
66 defaultMessage: 'Cut',
67 },
68 copy: {
69 id: 'menu.edit.copy',
70 defaultMessage: 'Copy',
71 },
72 paste: {
73 id: 'menu.edit.paste',
74 defaultMessage: 'Paste',
75 },
76 pasteAndMatchStyle: {
77 id: 'menu.edit.pasteAndMatchStyle',
78 defaultMessage: 'Paste And Match Style',
79 },
80 delete: {
81 id: 'menu.edit.delete',
82 defaultMessage: 'Delete',
83 },
84 selectAll: {
85 id: 'menu.edit.selectAll',
86 defaultMessage: 'Select All',
87 },
88 findInPage: {
89 id: 'menu.edit.findInPage',
90 defaultMessage: 'Find in Page',
91 },
92 speech: {
93 id: 'menu.edit.speech',
94 defaultMessage: 'Speech',
95 },
96 startSpeaking: {
97 id: 'menu.edit.startSpeaking',
98 defaultMessage: 'Start Speaking',
99 },
100 stopSpeaking: {
101 id: 'menu.edit.stopSpeaking',
102 defaultMessage: 'Stop Speaking',
103 },
104 startDictation: {
105 id: 'menu.edit.startDictation',
106 defaultMessage: 'Start Dictation',
107 },
108 emojiSymbols: {
109 id: 'menu.edit.emojiSymbols',
110 defaultMessage: 'Emoji & Symbols',
111 },
112 openQuickSwitch: {
113 id: 'menu.view.openQuickSwitch',
114 defaultMessage: 'Open Quick Switch',
115 },
116 back: {
117 id: 'menu.view.back',
118 defaultMessage: 'Back',
119 },
120 forward: {
121 id: 'menu.view.forward',
122 defaultMessage: 'Forward',
123 },
124 resetZoom: {
125 id: 'menu.view.resetZoom',
126 defaultMessage: 'Actual Size',
127 },
128 zoomIn: {
129 id: 'menu.view.zoomIn',
130 defaultMessage: 'Zoom In',
131 },
132 zoomOut: {
133 id: 'menu.view.zoomOut',
134 defaultMessage: 'Zoom Out',
135 },
136 toggleFullScreen: {
137 id: 'menu.view.toggleFullScreen',
138 defaultMessage: 'Toggle Full Screen',
139 },
140 toggleNavigationBar: {
141 id: 'menu.view.toggleNavigationBar',
142 defaultMessage: 'Toggle Navigation Bar',
143 },
144 splitModeToggle: {
145 id: 'menu.view.splitModeToggle',
146 defaultMessage: 'Toggle Split Mode',
147 },
148 toggleDarkMode: {
149 id: 'menu.view.toggleDarkMode',
150 defaultMessage: 'Toggle Dark Mode',
151 },
152 toggleDevTools: {
153 id: 'menu.view.toggleDevTools',
154 defaultMessage: 'Toggle Developer Tools',
155 },
156 toggleTodosDevTools: {
157 id: 'menu.view.toggleTodosDevTools',
158 defaultMessage: 'Toggle Todos Developer Tools',
159 },
160 toggleServiceDevTools: {
161 id: 'menu.view.toggleServiceDevTools',
162 defaultMessage: 'Toggle Service Developer Tools',
163 },
164 reloadService: {
165 id: 'menu.view.reloadService',
166 defaultMessage: 'Reload Service',
167 },
168 reloadFerdium: {
169 id: 'menu.view.reloadFerdium',
170 defaultMessage: 'Reload Ferdium',
171 },
172 lockFerdium: {
173 id: 'menu.view.lockFerdium',
174 defaultMessage: 'Lock Ferdium',
175 },
176 reloadTodos: {
177 id: 'menu.view.reloadTodos',
178 defaultMessage: 'Reload ToDos',
179 },
180 minimize: {
181 id: 'menu.window.minimize',
182 defaultMessage: 'Minimize',
183 },
184 close: {
185 id: 'menu.window.close',
186 defaultMessage: 'Close',
187 },
188 learnMore: {
189 id: 'menu.help.learnMore',
190 defaultMessage: 'Learn More',
191 },
192 changelog: {
193 id: 'menu.help.changelog',
194 defaultMessage: 'Changelog',
195 },
196 importExportData: {
197 id: 'menu.help.importExportData',
198 defaultMessage: 'Import/Export Configuration Data',
199 },
200 support: {
201 id: 'menu.help.support',
202 defaultMessage: 'Support',
203 },
204 debugInfo: {
205 id: 'menu.help.debugInfo',
206 defaultMessage: 'Copy Debug Information',
207 },
208 publishDebugInfo: {
209 id: 'menu.help.publishDebugInfo',
210 defaultMessage: 'Publish Debug Information',
211 },
212 debugInfoCopiedHeadline: {
213 id: 'menu.help.debugInfoCopiedHeadline',
214 defaultMessage: 'Ferdium Debug Information',
215 },
216 debugInfoCopiedBody: {
217 id: 'menu.help.debugInfoCopiedBody',
218 defaultMessage: 'Your Debug Information has been copied to your clipboard.',
219 },
220 touchId: {
221 id: 'locked.touchId',
222 defaultMessage: 'Unlock with Touch ID',
223 },
224 touchIdPrompt: {
225 id: 'locked.touchIdPrompt',
226 defaultMessage: 'unlock via Touch ID',
227 },
228 tos: {
229 id: 'menu.help.tos',
230 defaultMessage: 'Terms of Service',
231 },
232 privacy: {
233 id: 'menu.help.privacy',
234 defaultMessage: 'Privacy Statement',
235 },
236 file: {
237 id: 'menu.file',
238 defaultMessage: 'File',
239 },
240 view: {
241 id: 'menu.view',
242 defaultMessage: 'View',
243 },
244 services: {
245 id: 'menu.services',
246 defaultMessage: 'Services',
247 },
248 window: {
249 id: 'menu.window',
250 defaultMessage: 'Window',
251 },
252 help: {
253 id: 'menu.help',
254 defaultMessage: 'Help',
255 },
256 about: {
257 id: 'menu.app.about',
258 defaultMessage: 'About Ferdium',
259 },
260 checkForUpdates: {
261 id: 'menu.app.checkForUpdates',
262 defaultMessage: 'Check for updates',
263 },
264 hide: {
265 id: 'menu.app.hide',
266 defaultMessage: 'Hide',
267 },
268 hideOthers: {
269 id: 'menu.app.hideOthers',
270 defaultMessage: 'Hide Others',
271 },
272 unhide: {
273 id: 'menu.app.unhide',
274 defaultMessage: 'Unhide',
275 },
276 autohideMenuBar: {
277 id: 'menu.app.autohideMenuBar',
278 defaultMessage: 'Auto-hide menu bar',
279 },
280 addNewService: {
281 id: 'menu.services.addNewService',
282 defaultMessage: 'Add New Service...',
283 },
284 addNewWorkspace: {
285 id: 'menu.workspaces.addNewWorkspace',
286 defaultMessage: 'Add New Workspace...',
287 },
288 openWorkspaceDrawer: {
289 id: 'menu.workspaces.openWorkspaceDrawer',
290 defaultMessage: 'Open workspace drawer',
291 },
292 closeWorkspaceDrawer: {
293 id: 'menu.workspaces.closeWorkspaceDrawer',
294 defaultMessage: 'Close workspace drawer',
295 },
296 activateNextService: {
297 id: 'menu.services.setNextServiceActive',
298 defaultMessage: 'Activate next service',
299 },
300 activatePreviousService: {
301 id: 'menu.services.activatePreviousService',
302 defaultMessage: 'Activate previous service',
303 },
304 muteApp: {
305 id: 'sidebar.muteApp',
306 defaultMessage: 'Disable notifications & audio',
307 },
308 unmuteApp: {
309 id: 'sidebar.unmuteApp',
310 defaultMessage: 'Enable notifications & audio',
311 },
312 workspaces: {
313 id: 'menu.workspaces',
314 defaultMessage: 'Workspaces',
315 },
316 defaultWorkspace: {
317 id: 'menu.workspaces.defaultWorkspace',
318 defaultMessage: 'All services',
319 },
320 todos: {
321 id: 'menu.todos',
322 defaultMessage: 'Todos',
323 },
324 openTodosDrawer: {
325 id: 'menu.Todoss.openTodosDrawer',
326 defaultMessage: 'Open Todos drawer',
327 },
328 closeTodosDrawer: {
329 id: 'menu.Todoss.closeTodosDrawer',
330 defaultMessage: 'Close Todos drawer',
331 },
332 enableTodos: {
333 id: 'menu.todos.enableTodos',
334 defaultMessage: 'Enable Todos',
335 },
336 disableTodos: {
337 id: 'menu.todos.disableTodos',
338 defaultMessage: 'Disable Todos',
339 },
340 serviceGoHome: {
341 id: 'menu.services.goHome',
342 defaultMessage: 'Home',
343 },
344 ok: {
345 id: 'global.ok',
346 defaultMessage: 'Ok',
347 },
348 copyToClipboard: {
349 id: 'menu.services.copyToClipboard',
350 defaultMessage: 'Copy to clipboard',
351 },
352});
353
354function getActiveService(): Service | undefined {
355 return window['ferdium'].stores.services.active;
356}
357
358function toggleFullScreen(): void {
359 const mainWindow = getCurrentWindow();
360
361 if (!mainWindow) return;
362
363 if (mainWindow.isFullScreen()) {
364 mainWindow.setFullScreen(false);
365 } else {
366 mainWindow.setFullScreen(true);
367 }
368}
369
370function titleBarTemplateFactory(
371 intl: IntlShape,
372 locked: boolean,
373): MenuItemConstructorOptions[] {
374 return [
375 {
376 label: intl.formatMessage(menuItems.edit),
377 accelerator: `${altKey()}+E`,
378 submenu: [
379 {
380 label: intl.formatMessage(menuItems.undo),
381 role: 'undo',
382 },
383 {
384 label: intl.formatMessage(menuItems.redo),
385 role: 'redo',
386 },
387 {
388 type: 'separator',
389 },
390 {
391 label: intl.formatMessage(menuItems.cut),
392 accelerator: `${cmdOrCtrlShortcutKey()}+X`,
393 role: 'cut',
394 },
395 {
396 label: intl.formatMessage(menuItems.copy),
397 accelerator: `${cmdOrCtrlShortcutKey()}+C`,
398 role: 'copy',
399 },
400 {
401 label: intl.formatMessage(menuItems.paste),
402 accelerator: `${cmdOrCtrlShortcutKey()}+V`,
403 role: 'paste',
404 },
405 {
406 label: intl.formatMessage(menuItems.pasteAndMatchStyle),
407 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+V`, // Override the accelerator since this adds new key combo in macos
408 role: 'pasteAndMatchStyle',
409 },
410 {
411 label: intl.formatMessage(menuItems.delete),
412 role: 'delete',
413 },
414 {
415 label: intl.formatMessage(menuItems.selectAll),
416 accelerator: `${cmdOrCtrlShortcutKey()}+A`,
417 role: 'selectAll',
418 },
419 ],
420 },
421 {
422 label: intl.formatMessage(menuItems.view),
423 accelerator: `${altKey()}+V`,
424 visible: !locked,
425 submenu: [
426 {
427 type: 'separator',
428 },
429 {
430 label: intl.formatMessage(menuItems.openQuickSwitch),
431 accelerator: `${cmdOrCtrlShortcutKey()}+S`,
432 click() {
433 window['ferdium'].features.quickSwitch.state.isModalVisible = true;
434 },
435 },
436 {
437 type: 'separator',
438 },
439 {
440 label: intl.formatMessage(menuItems.findInPage),
441 accelerator: `${cmdOrCtrlShortcutKey()}+F`,
442 click() {
443 const activeService = getActiveService();
444 if (!activeService) {
445 return;
446 }
447 activeService.webview.focus();
448 window['ferdium'].actions.service.sendIPCMessage({
449 serviceId: activeService.id,
450 channel: 'find-in-page',
451 args: {},
452 });
453 },
454 },
455 {
456 type: 'separator',
457 },
458 {
459 label: intl.formatMessage(menuItems.back),
460 accelerator: `${!isMac ? altKey() : cmdOrCtrlShortcutKey()}+Left`,
461 click() {
462 const activeService = getActiveService();
463 if (!activeService) {
464 return;
465 }
466 activeService.webview.goBack();
467 },
468 },
469 {
470 label: intl.formatMessage(menuItems.forward),
471 accelerator: `${!isMac ? altKey() : cmdOrCtrlShortcutKey()}+Right`,
472 click() {
473 const activeService = getActiveService();
474 if (!activeService) {
475 return;
476 }
477 activeService.webview.goForward();
478 },
479 },
480 {
481 type: 'separator',
482 },
483 {
484 label: intl.formatMessage(menuItems.resetZoom),
485 accelerator: `${cmdOrCtrlShortcutKey()}+0`,
486 click() {
487 const activeService = getActiveService();
488 if (!activeService) {
489 return;
490 }
491 activeService.webview.setZoomLevel(0);
492 },
493 },
494 {
495 label: intl.formatMessage(menuItems.zoomIn),
496 accelerator: `${cmdOrCtrlShortcutKey()}+plus`,
497 click() {
498 const activeService = getActiveService();
499 if (!activeService) {
500 return;
501 }
502 const { webview } = activeService;
503 const level = webview.getZoomLevel();
504 webview.setZoomLevel(level + 0.5);
505 },
506 },
507 {
508 label: intl.formatMessage(menuItems.zoomOut),
509 accelerator: `${cmdOrCtrlShortcutKey()}+-`,
510 click() {
511 const activeService = getActiveService();
512 if (!activeService) {
513 return;
514 }
515 const { webview } = activeService;
516 const level = webview.getZoomLevel();
517 webview.setZoomLevel(level - 0.5);
518 },
519 },
520 {
521 type: 'separator',
522 },
523 {
524 label: intl.formatMessage(menuItems.toggleFullScreen),
525 click: () => {
526 toggleFullScreen();
527 },
528 accelerator: toggleFullScreenKey(),
529 },
530 {
531 label: intl.formatMessage(menuItems.toggleNavigationBar),
532 accelerator: `${cmdOrCtrlShortcutKey()}+B`,
533 // role: 'toggleNavigationBar',
534 type: 'checkbox',
535 checked:
536 window['ferdium'].stores.settings.app.navigationBarManualActive,
537 click: () => {
538 window['ferdium'].actions.settings.update({
539 type: 'app',
540 data: {
541 navigationBarManualActive:
542 !window['ferdium'].stores.settings.app
543 .navigationBarManualActive,
544 },
545 });
546 },
547 },
548 {
549 label: intl.formatMessage(menuItems.splitModeToggle),
550 accelerator: `${splitModeToggleShortcutKey()}`,
551 // role: 'splitModeToggle',
552 type: 'checkbox',
553 checked: window['ferdium'].stores.settings.app.splitMode,
554 click: () => {
555 window['ferdium'].actions.settings.update({
556 type: 'app',
557 data: {
558 splitMode: !window['ferdium'].stores.settings.app.splitMode,
559 },
560 });
561 },
562 },
563 {
564 label: intl.formatMessage(menuItems.toggleDarkMode),
565 type: 'checkbox',
566 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+D`,
567 checked: window['ferdium'].stores.settings.app.darkMode,
568 click: () => {
569 window['ferdium'].actions.settings.update({
570 type: 'app',
571 data: {
572 darkMode: !window['ferdium'].stores.settings.app.darkMode,
573 },
574 });
575 },
576 },
577 ],
578 },
579 {
580 label: intl.formatMessage(menuItems.services),
581 accelerator: `${altKey()}+S`,
582 visible: !locked,
583 submenu: [],
584 },
585 {
586 label: intl.formatMessage(menuItems.workspaces),
587 accelerator: `${altKey()}+W`,
588 submenu: [],
589 visible: !locked,
590 },
591 {
592 label: intl.formatMessage(menuItems.todos),
593 submenu: [],
594 visible: !locked,
595 },
596 {
597 label: intl.formatMessage(menuItems.window),
598 role: 'window',
599 submenu: [
600 {
601 label: intl.formatMessage(menuItems.minimize),
602 role: 'minimize',
603 },
604 {
605 label: intl.formatMessage(menuItems.close),
606 role: 'close',
607 },
608 ],
609 },
610 {
611 label: intl.formatMessage(menuItems.help),
612 accelerator: `${altKey()}+H`,
613 role: 'help',
614 submenu: [
615 {
616 label: intl.formatMessage(menuItems.learnMore),
617 click() {
618 openExternalUrl(LIVE_API_FERDIUM_WEBSITE, true);
619 },
620 },
621 {
622 label: intl.formatMessage(menuItems.changelog),
623 click() {
624 window.location.href = onAuthGoToReleaseNotes(window.location.href);
625 },
626 },
627 {
628 label: intl.formatMessage(menuItems.importExportData),
629 click() {
630 openExternalUrl(importExportURL(), true);
631 },
632 enabled: !locked,
633 },
634 {
635 type: 'separator',
636 },
637 {
638 label: intl.formatMessage(menuItems.support),
639 click() {
640 openExternalUrl(`${LIVE_API_FERDIUM_WEBSITE}/contact`, true);
641 },
642 },
643 {
644 type: 'separator',
645 },
646 {
647 label: intl.formatMessage(menuItems.tos),
648 click() {
649 openExternalUrl(`${serverBase()}/terms`, true);
650 },
651 },
652 {
653 label: intl.formatMessage(menuItems.privacy),
654 click() {
655 openExternalUrl(`${serverBase()}/privacy`, true);
656 },
657 },
658 ],
659 },
660 ];
661}
662
663class FranzMenu implements StoresProps {
664 @observable currentTemplate: MenuItemConstructorOptions[];
665
666 actions: any;
667
668 stores: RealStores;
669
670 constructor(stores: RealStores, actions: any) {
671 this.stores = stores;
672 this.actions = actions;
673 this.currentTemplate = [];
674
675 makeObservable(this);
676
677 setTimeout(() => autorun(this._build.bind(this)), 10);
678 }
679
680 @action _setCurrentTemplate(tpl: MenuItemConstructorOptions[]): void {
681 this.currentTemplate = tpl;
682 }
683
684 rebuild(): void {
685 this._build();
686 }
687
688 get template(): any {
689 return fromJS(this.currentTemplate).toJS();
690 }
691
692 getOsName(): string {
693 let osNameParse = osName();
694 const isWin11 = semver.satisfies(os.release(), '>=10.0.22000');
695 osNameParse = isWindows && isWin11 ? 'Windows 11' : osNameParse;
696
697 return osNameParse;
698 }
699
700 _build(): void {
701 // need to clone object so we don't modify computed (cached) object
702 const serviceTpl = Object.assign([], this.serviceTpl());
703
704 // Don't initialize when window['ferdium'] is undefined
705 if (window['ferdium'] === undefined) {
706 console.log('skipping menu init');
707 return;
708 }
709
710 const { intl } = window['ferdium'];
711 const locked =
712 this.stores.settings.app.locked &&
713 this.stores.settings.app.lockingFeatureEnabled &&
714 this.stores.user.isLoggedIn;
715 const { actions } = this;
716 const tpl = titleBarTemplateFactory(intl, locked);
717
718 if (!isMac) {
719 (tpl[1].submenu as MenuItemConstructorOptions[]).push({
720 label: intl.formatMessage(menuItems.autohideMenuBar),
721 type: 'checkbox',
722 checked: window['ferdium'].stores.settings.app.autohideMenuBar,
723 click: () => {
724 window['ferdium'].actions.settings.update({
725 type: 'app',
726 data: {
727 autohideMenuBar:
728 !window['ferdium'].stores.settings.app.autohideMenuBar,
729 },
730 });
731 },
732 });
733 }
734
735 if (!locked) {
736 (tpl[1].submenu as MenuItemConstructorOptions[]).push(
737 {
738 type: 'separator',
739 },
740 {
741 label: intl.formatMessage(menuItems.toggleDevTools),
742 accelerator: `${cmdOrCtrlShortcutKey()}+${altKey()}+I`,
743 click: () => {
744 const windowWebContents = webContents.fromId(1);
745 const { isDevToolsOpened, openDevTools, closeDevTools } =
746 windowWebContents;
747
748 if (isDevToolsOpened()) {
749 closeDevTools();
750 } else {
751 openDevTools({ mode: 'right' });
752 }
753 },
754 },
755 {
756 label: intl.formatMessage(menuItems.toggleServiceDevTools),
757 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+${altKey()}+I`,
758 click: () => {
759 this.actions.service.openDevToolsForActiveService();
760 },
761 enabled:
762 this.stores.user.isLoggedIn &&
763 this.stores.services.enabled.length > 0,
764 },
765 );
766
767 if (this.stores.todos.isFeatureEnabledByUser) {
768 (tpl[1].submenu as MenuItemConstructorOptions[]).push({
769 label: intl.formatMessage(menuItems.toggleTodosDevTools),
770 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+${altKey()}+O`,
771 click: () => {
772 const webview = document.querySelector('#todos-panel webview');
773 if (webview) this.actions.todos.openDevTools();
774 },
775 });
776 }
777
778 (tpl[1].submenu as MenuItemConstructorOptions[]).unshift(
779 {
780 label: intl.formatMessage(menuItems.reloadService),
781 accelerator: `${cmdOrCtrlShortcutKey()}+R`,
782 click: () => {
783 if (
784 this.stores.user.isLoggedIn &&
785 this.stores.services.enabled.length > 0
786 ) {
787 if (getActiveService()?.recipe.id === CUSTOM_WEBSITE_RECIPE_ID) {
788 getActiveService()?.webview.reload();
789 } else {
790 this.actions.service.reloadActive();
791 }
792 } else {
793 window.location.reload();
794 }
795 },
796 },
797 {
798 label: intl.formatMessage(menuItems.reloadFerdium),
799 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+R`,
800 click: () => {
801 window.location.reload();
802 },
803 },
804 {
805 label: intl.formatMessage(menuItems.reloadTodos),
806 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+${altKey()}+R`,
807 click: () => {
808 this.actions.todos.reload();
809 },
810 },
811 {
812 type: 'separator',
813 },
814 {
815 label: intl.formatMessage(menuItems.lockFerdium),
816 accelerator: `${lockFerdiumShortcutKey()}`,
817 enabled:
818 this.stores.user.isLoggedIn &&
819 this.stores.settings.app.lockingFeatureEnabled,
820 click() {
821 actions.settings.update({
822 type: 'app',
823 data: {
824 locked: true,
825 },
826 });
827 },
828 },
829 );
830
831 if (serviceTpl.length > 0) {
832 tpl[2].submenu = serviceTpl;
833 }
834
835 tpl[3].submenu = this.workspacesMenu();
836
837 tpl[4].submenu = this.todosMenu();
838 } else {
839 const touchIdEnabled = isMac
840 ? this.stores.settings.app.useTouchIdToUnlock &&
841 systemPreferences.canPromptTouchID()
842 : false;
843
844 (tpl[0].submenu as MenuItemConstructorOptions[]).unshift(
845 {
846 label: intl.formatMessage(menuItems.touchId),
847 accelerator: `${lockFerdiumShortcutKey()}`,
848 visible: touchIdEnabled,
849 click() {
850 systemPreferences
851 .promptTouchID(intl.formatMessage(menuItems.touchIdPrompt))
852 .then(() => {
853 actions.settings.update({
854 type: 'app',
855 data: {
856 locked: false,
857 },
858 });
859 });
860 },
861 },
862 {
863 type: 'separator',
864 visible: touchIdEnabled,
865 },
866 );
867 }
868
869 tpl.unshift({
870 label: isMac ? app.name : intl.formatMessage(menuItems.file),
871 accelerator: `${altKey()}+F`,
872 submenu: [
873 {
874 type: 'separator',
875 },
876 {
877 label: intl.formatMessage(globalMessages.settings),
878 accelerator: `${settingsShortcutKey()}`,
879 click: () => {
880 this.actions.ui.openSettings({ path: 'app' });
881 },
882 enabled: this.stores.user.isLoggedIn,
883 visible: !locked,
884 },
885 {
886 label: intl.formatMessage(menuItems.checkForUpdates),
887 visible: !locked,
888 click: () => {
889 this.actions.app.checkForUpdates();
890 },
891 },
892 {
893 type: 'separator',
894 visible: !locked,
895 },
896 {
897 label: intl.formatMessage(menuItems.services),
898 role: 'services',
899 submenu: [],
900 },
901 {
902 type: 'separator',
903 },
904 {
905 label: intl.formatMessage(menuItems.hide),
906 role: 'hide',
907 },
908 {
909 label: intl.formatMessage(menuItems.hideOthers),
910 role: 'hideOthers',
911 },
912 {
913 label: intl.formatMessage(menuItems.unhide),
914 role: 'unhide',
915 },
916 {
917 type: 'separator',
918 },
919 {
920 label: intl.formatMessage(globalMessages.quit),
921 accelerator: `${cmdOrCtrlShortcutKey()}+Q`,
922 click() {
923 app.quit();
924 },
925 },
926 ],
927 });
928
929 const aboutAppDetails = [
930 `Version: ${ferdiumVersion}`,
931 `Server: ${serverName()} Server`,
932 `Electron: ${electronVersion}`,
933 `Chrome: ${chromeVersion}`,
934 `Node.js: ${nodeVersion}`,
935 `Platform: ${this.getOsName()}`,
936 `Arch: ${osArch}`,
937 `Build date: ${new Date(Number(timestamp))}`,
938 `Git SHA: ${gitHashShort}`,
939 `Git branch: ${gitBranch}`,
940 ].join('\n');
941
942 const about = {
943 label: intl.formatMessage(menuItems.about),
944 click: () => {
945 dialog
946 .showMessageBox({
947 type: 'info',
948 title: 'Ferdium',
949 message: 'Ferdium',
950 detail: aboutAppDetails,
951 buttons: [
952 intl.formatMessage(menuItems.ok),
953 intl.formatMessage(menuItems.copyToClipboard),
954 ],
955 })
956 .then(result => {
957 if (result.response === 1) {
958 clipboard.write({
959 text: aboutAppDetails,
960 });
961 }
962 });
963 },
964 };
965
966 if (isMac) {
967 // Edit menu.
968 (tpl[1].submenu as MenuItemConstructorOptions[]).push(
969 {
970 type: 'separator',
971 },
972 {
973 label: intl.formatMessage(menuItems.speech),
974 submenu: [
975 {
976 label: intl.formatMessage(menuItems.startSpeaking),
977 role: 'startSpeaking',
978 },
979 {
980 label: intl.formatMessage(menuItems.stopSpeaking),
981 role: 'stopSpeaking',
982 },
983 ],
984 },
985 );
986
987 (tpl[0].submenu as MenuItemConstructorOptions[]).unshift(about, {
988 type: 'separator',
989 });
990 } else {
991 tpl[0].submenu = [
992 {
993 label: intl.formatMessage(globalMessages.settings),
994 accelerator: `${settingsShortcutKey()}`,
995 click: () => {
996 this.actions.ui.openSettings({ path: 'app' });
997 },
998 enabled: this.stores.user.isLoggedIn,
999 visible: !locked,
1000 },
1001 {
1002 type: 'separator',
1003 },
1004 {
1005 label: intl.formatMessage(globalMessages.quit),
1006 accelerator: `${cmdOrCtrlShortcutKey()}+Q`,
1007 click() {
1008 app.quit();
1009 },
1010 },
1011 ];
1012
1013 (tpl[tpl.length - 1].submenu as MenuItemConstructorOptions[]).push(
1014 {
1015 type: 'separator',
1016 },
1017 about,
1018 );
1019 }
1020
1021 if (!locked) {
1022 if (serviceTpl.length > 0) {
1023 tpl[3].submenu = serviceTpl;
1024 }
1025
1026 tpl[4].submenu = this.workspacesMenu();
1027
1028 tpl[5].submenu = this.todosMenu();
1029
1030 (tpl[tpl.length - 1].submenu as MenuItemConstructorOptions[]).push(
1031 {
1032 type: 'separator',
1033 },
1034 ...this.debugMenu(),
1035 );
1036 }
1037 this._setCurrentTemplate(tpl);
1038 const menu = Menu.buildFromTemplate(tpl);
1039 Menu.setApplicationMenu(menu);
1040 }
1041
1042 serviceTpl(): MenuItemConstructorOptions[] {
1043 const { intl } = window['ferdium'];
1044 const { user, services, settings } = this.stores;
1045 if (!user.isLoggedIn) {
1046 return [];
1047 }
1048
1049 const cmdAltShortcutsVisibile = !isLinux;
1050 const menu: MenuItemConstructorOptions[] = [];
1051 menu.push(
1052 {
1053 label: intl.formatMessage(menuItems.addNewService),
1054 accelerator: `${addNewServiceShortcutKey()}`,
1055 click: () => {
1056 this.actions.ui.openSettings({ path: 'recipes' });
1057 },
1058 },
1059 {
1060 type: 'separator',
1061 },
1062 {
1063 label: intl.formatMessage(menuItems.activateNextService),
1064 accelerator: `${cmdOrCtrlShortcutKey()}+tab`,
1065 click: () => this.actions.service.setActiveNext(),
1066 visible: !cmdAltShortcutsVisibile,
1067 },
1068 {
1069 label: intl.formatMessage(menuItems.activateNextService),
1070 accelerator: `${cmdOrCtrlShortcutKey()}+${altKey()}+right`,
1071 click: () => this.actions.service.setActiveNext(),
1072 visible: cmdAltShortcutsVisibile,
1073 },
1074 {
1075 label: intl.formatMessage(menuItems.activatePreviousService),
1076 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+tab`,
1077 click: () => this.actions.service.setActivePrev(),
1078 visible: !cmdAltShortcutsVisibile,
1079 },
1080 {
1081 label: intl.formatMessage(menuItems.activatePreviousService),
1082 accelerator: `${cmdOrCtrlShortcutKey()}+${altKey()}+left`,
1083 click: () => this.actions.service.setActivePrev(),
1084 visible: cmdAltShortcutsVisibile,
1085 },
1086 {
1087 label: intl
1088 .formatMessage(
1089 settings.all.app.isAppMuted
1090 ? menuItems.unmuteApp
1091 : menuItems.muteApp,
1092 )
1093 .replace('&', '&&'),
1094 accelerator: `${muteFerdiumShortcutKey()}`,
1095 click: () => this.actions.app.toggleMuteApp(),
1096 },
1097 {
1098 type: 'separator',
1099 },
1100 );
1101
1102 for (const [i, service] of services.allDisplayed.entries()) {
1103 menu.push({
1104 label: this._getServiceName(service),
1105 accelerator: i < 9 ? `${cmdOrCtrlShortcutKey()}+${i + 1}` : undefined,
1106 type: 'radio',
1107 checked: service.isActive,
1108 click: () => {
1109 this.actions.service.setActive({ serviceId: service.id });
1110
1111 if (isMac && i === 0) {
1112 // feat(Mac): Open Window with Cmd+1
1113 getCurrentWindow().restore();
1114 }
1115 },
1116 });
1117 }
1118
1119 if (
1120 services.active &&
1121 services.active.recipe.id === CUSTOM_WEBSITE_RECIPE_ID
1122 ) {
1123 menu.push(
1124 {
1125 type: 'separator',
1126 },
1127 {
1128 label: intl.formatMessage(menuItems.serviceGoHome),
1129 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+H`,
1130 click: () => this.actions.service.reloadActive(),
1131 },
1132 );
1133 }
1134
1135 return menu;
1136 }
1137
1138 workspacesMenu(): MenuItemConstructorOptions[] {
1139 const { workspaces, activeWorkspace, isWorkspaceDrawerOpen } =
1140 workspaceStore;
1141 const { intl } = window['ferdium'];
1142
1143 const menu: MenuItemConstructorOptions[] = [];
1144 // Add new workspace item:
1145 menu.push({
1146 label: intl.formatMessage(menuItems.addNewWorkspace),
1147 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+N`,
1148 click: () => {
1149 workspaceActions.openWorkspaceSettings();
1150 },
1151 enabled: this.stores.user.isLoggedIn,
1152 });
1153
1154 // Open workspace drawer:
1155 if (!this.stores.settings.app.alwaysShowWorkspaces) {
1156 const drawerLabel = isWorkspaceDrawerOpen
1157 ? menuItems.closeWorkspaceDrawer
1158 : menuItems.openWorkspaceDrawer;
1159 menu.push({
1160 label: intl.formatMessage(drawerLabel),
1161 accelerator: `${workspaceToggleShortcutKey()}`,
1162 click: () => {
1163 workspaceActions.toggleWorkspaceDrawer();
1164 },
1165 enabled: this.stores.user.isLoggedIn,
1166 });
1167 }
1168
1169 menu.push(
1170 {
1171 type: 'separator',
1172 },
1173 {
1174 label: intl.formatMessage(menuItems.defaultWorkspace),
1175 accelerator: `${cmdOrCtrlShortcutKey()}+${altKey()}+0`,
1176 type: 'radio',
1177 checked: !activeWorkspace,
1178 click: () => {
1179 workspaceActions.deactivate();
1180 },
1181 },
1182 );
1183
1184 // Workspace items
1185 for (const [i, workspace] of workspaces.entries()) {
1186 menu.push({
1187 label: workspace.name,
1188 accelerator:
1189 i < 9 ? `${cmdOrCtrlShortcutKey()}+${altKey()}+${i + 1}` : undefined,
1190 type: 'radio',
1191 checked: activeWorkspace ? workspace.id === activeWorkspace.id : false,
1192 click: () => {
1193 workspaceActions.activate({ workspace });
1194 },
1195 });
1196 }
1197
1198 return menu;
1199 }
1200
1201 todosMenu(): MenuItemConstructorOptions[] {
1202 const { isTodosPanelVisible, isFeatureEnabledByUser } = this.stores.todos;
1203 const { intl } = window['ferdium'];
1204
1205 const menu: MenuItemConstructorOptions[] = [];
1206 menu.push({
1207 label: intl.formatMessage(
1208 isFeatureEnabledByUser ? menuItems.disableTodos : menuItems.enableTodos,
1209 ),
1210 click: () => {
1211 todoActions.toggleTodosFeatureVisibility();
1212 },
1213 enabled: this.stores.user.isLoggedIn,
1214 });
1215
1216 if (isFeatureEnabledByUser) {
1217 menu.push(
1218 {
1219 type: 'separator',
1220 },
1221 {
1222 label: intl.formatMessage(
1223 isTodosPanelVisible
1224 ? menuItems.closeTodosDrawer
1225 : menuItems.openTodosDrawer,
1226 ),
1227 accelerator: `${todosToggleShortcutKey()}`,
1228 click: () => {
1229 todoActions.toggleTodosPanel();
1230 },
1231 enabled: this.stores.user.isLoggedIn,
1232 },
1233 );
1234 }
1235
1236 return menu;
1237 }
1238
1239 debugMenu(): MenuItemConstructorOptions[] {
1240 const { intl } = window['ferdium'];
1241
1242 return [
1243 {
1244 label: intl.formatMessage(menuItems.debugInfo),
1245 click: () => {
1246 const { debugInfo } = this.stores.app;
1247
1248 clipboard.write({
1249 text: JSON.stringify(debugInfo),
1250 });
1251
1252 this.actions.app.notify({
1253 title: intl.formatMessage(menuItems.debugInfoCopiedHeadline),
1254 options: {
1255 body: intl.formatMessage(menuItems.debugInfoCopiedBody),
1256 },
1257 });
1258 },
1259 },
1260 {
1261 label: intl.formatMessage(menuItems.publishDebugInfo),
1262 click: () => {
1263 window['ferdium'].features.publishDebugInfo.state.isModalVisible =
1264 true;
1265 },
1266 },
1267 ];
1268 }
1269
1270 _getServiceName(service) {
1271 if (service.name) {
1272 return service.name;
1273 }
1274
1275 let { name: serviceName } = service.recipe;
1276 if (service.team) {
1277 serviceName = `${serviceName} (${service.team})`;
1278 } else if (service.customUrl) {
1279 serviceName = `${serviceName} (${service.customUrl})`;
1280 }
1281
1282 return serviceName;
1283 }
1284}
1285
1286export default FranzMenu;