aboutsummaryrefslogtreecommitdiffstats
path: root/src
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
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')
-rw-r--r--src/actions/lib/actions.ts28
-rw-r--r--src/components/ui/input/index.tsx5
-rw-r--r--src/electron/ipc-api/appIndicator.ts8
-rw-r--r--src/electron/ipc-api/index.ts5
-rw-r--r--src/features/workspaces/actions.ts9
-rw-r--r--src/index.ts4
-rw-r--r--src/lib/DBus.ts13
-rw-r--r--src/lib/Menu.ts (renamed from src/lib/Menu.js)633
-rw-r--r--src/lib/Tray.ts (renamed from src/lib/Tray.js)130
9 files changed, 441 insertions, 394 deletions
diff --git a/src/actions/lib/actions.ts b/src/actions/lib/actions.ts
index da27c2e2b..faf576fd8 100644
--- a/src/actions/lib/actions.ts
+++ b/src/actions/lib/actions.ts
@@ -1,8 +1,6 @@
1import PropTypes from 'prop-types';
2
3export interface ActionDefinitions { 1export interface ActionDefinitions {
4 [key: string]: { 2 [key: string]: {
5 [key: string]: PropTypes.InferType<any>; 3 [key: string]: any;
6 }; 4 };
7} 5}
8 6
@@ -18,18 +16,19 @@ export interface Actions {
18 }; 16 };
19} 17}
20 18
21export const createActionsFromDefinitions = <T extends {}>( 19export const createActionsFromDefinitions = <T>(
22 actionDefinitions: ActionDefinitions, 20 actionDefinitions: ActionDefinitions,
23 validate: any, 21 validate: any,
24): T => { 22): T => {
25 const actions = {}; 23 const actions = {};
26 // eslint-disable-next-line unicorn/no-array-for-each 24
27 Object.keys(actionDefinitions).forEach(actionName => { 25 for (const actionName of Object.keys(actionDefinitions)) {
28 const action = (params = {}) => { 26 const action = (params = {}) => {
29 const schema = actionDefinitions[actionName]; 27 const schema = actionDefinitions[actionName];
30 validate(schema, params, actionName); 28 validate(schema, params, actionName);
31 action.notify(params); 29 action.notify(params);
32 }; 30 };
31
33 actions[actionName] = action; 32 actions[actionName] = action;
34 action.listeners = []; 33 action.listeners = [];
35 action.listen = listener => action.listeners.push(listener); 34 action.listen = listener => action.listeners.push(listener);
@@ -37,21 +36,24 @@ export const createActionsFromDefinitions = <T extends {}>(
37 const { listeners } = action; 36 const { listeners } = action;
38 listeners.splice(listeners.indexOf(listener), 1); 37 listeners.splice(listeners.indexOf(listener), 1);
39 }; 38 };
40 action.notify = params => 39 action.notify = params => {
41 // eslint-disable-next-line unicorn/no-array-for-each 40 for (const listener of action.listeners) {
42 action.listeners.forEach(listener => listener(params)); 41 listener(params);
43 }); 42 }
43 };
44 }
45
44 return actions as T; 46 return actions as T;
45}; 47};
46 48
47export default (definitions, validate) => { 49export default (definitions, validate) => {
48 const newActions = {}; 50 const newActions = {};
49 // eslint-disable-next-line unicorn/no-array-for-each 51 for (const scopeName of Object.keys(definitions)) {
50 Object.keys(definitions).forEach(scopeName => {
51 newActions[scopeName] = createActionsFromDefinitions( 52 newActions[scopeName] = createActionsFromDefinitions(
52 definitions[scopeName], 53 definitions[scopeName],
53 validate, 54 validate,
54 ); 55 );
55 }); 56 }
57
56 return newActions; 58 return newActions;
57}; 59};
diff --git a/src/components/ui/input/index.tsx b/src/components/ui/input/index.tsx
index cb26c0ea4..a6d401195 100644
--- a/src/components/ui/input/index.tsx
+++ b/src/components/ui/input/index.tsx
@@ -84,11 +84,6 @@ class Input extends Component<IProps, IState> {
84 onChange(e); 84 onChange(e);
85 85
86 if (scorePassword) { 86 if (scorePassword) {
87 console.log(
88 '--->',
89 e.target.value,
90 scorePasswordFunc(e.target.value as string),
91 );
92 this.setState({ 87 this.setState({
93 passwordScore: scorePasswordFunc(e.target.value), 88 passwordScore: scorePasswordFunc(e.target.value),
94 }); 89 });
diff --git a/src/electron/ipc-api/appIndicator.ts b/src/electron/ipc-api/appIndicator.ts
index bd5f6a68f..766e6937a 100644
--- a/src/electron/ipc-api/appIndicator.ts
+++ b/src/electron/ipc-api/appIndicator.ts
@@ -1,7 +1,8 @@
1import { app, ipcMain, BrowserWindow, Tray } from 'electron'; 1import { app, ipcMain, BrowserWindow } from 'electron';
2import { join } from 'path'; 2import { join } from 'path';
3import { autorun } from 'mobx'; 3import { autorun } from 'mobx';
4import { isMac, isWindows, isLinux } from '../../environment'; 4import { isMac, isWindows, isLinux } from '../../environment';
5import TrayIcon from '../../lib/Tray';
5 6
6const INDICATOR_TASKBAR = 'taskbar'; 7const INDICATOR_TASKBAR = 'taskbar';
7const FILE_EXTENSION = isWindows ? 'ico' : 'png'; 8const FILE_EXTENSION = isWindows ? 'ico' : 'png';
@@ -24,16 +25,14 @@ function getAsset(type: 'tray' | 'taskbar', asset: string) {
24export default (params: { 25export default (params: {
25 mainWindow: BrowserWindow; 26 mainWindow: BrowserWindow;
26 settings: any; 27 settings: any;
27 trayIcon: Tray; 28 trayIcon: TrayIcon;
28}) => { 29}) => {
29 autorun(() => { 30 autorun(() => {
30 isTrayIconEnabled = params.settings.app.get('enableSystemTray'); 31 isTrayIconEnabled = params.settings.app.get('enableSystemTray');
31 32
32 if (!isTrayIconEnabled) { 33 if (!isTrayIconEnabled) {
33 // @ts-expect-error Property 'hide' does not exist on type 'Tray'.
34 params.trayIcon.hide(); 34 params.trayIcon.hide();
35 } else if (isTrayIconEnabled) { 35 } else if (isTrayIconEnabled) {
36 // @ts-expect-error Property 'show' does not exist on type 'Tray'.
37 params.trayIcon.show(); 36 params.trayIcon.show();
38 } 37 }
39 }); 38 });
@@ -87,7 +86,6 @@ export default (params: {
87 } 86 }
88 87
89 // Update Tray 88 // Update Tray
90 // @ts-expect-error Property 'setIndicator' does not exist on type 'Tray'.
91 params.trayIcon.setIndicator(args.indicator); 89 params.trayIcon.setIndicator(args.indicator);
92 }); 90 });
93}; 91};
diff --git a/src/electron/ipc-api/index.ts b/src/electron/ipc-api/index.ts
index 1a8838c4e..b57a9d881 100644
--- a/src/electron/ipc-api/index.ts
+++ b/src/electron/ipc-api/index.ts
@@ -1,4 +1,4 @@
1import { BrowserWindow, Tray } from 'electron'; 1import { BrowserWindow } from 'electron';
2import autoUpdate from './autoUpdate'; 2import autoUpdate from './autoUpdate';
3import settings from './settings'; 3import settings from './settings';
4import sessionStorage from './sessionStorage'; 4import sessionStorage from './sessionStorage';
@@ -9,11 +9,12 @@ import localServer from './localServer';
9import languageDetect from './languageDetect'; 9import languageDetect from './languageDetect';
10import dnd from './dnd'; 10import dnd from './dnd';
11import focusState from './focusState'; 11import focusState from './focusState';
12import TrayIcon from '../../lib/Tray';
12 13
13export default (params: { 14export default (params: {
14 mainWindow: BrowserWindow; 15 mainWindow: BrowserWindow;
15 settings: any; 16 settings: any;
16 trayIcon: Tray; 17 trayIcon: TrayIcon;
17}) => { 18}) => {
18 settings(params); 19 settings(params);
19 sessionStorage(); 20 sessionStorage();
diff --git a/src/features/workspaces/actions.ts b/src/features/workspaces/actions.ts
index b4e1d0758..4c8b74450 100644
--- a/src/features/workspaces/actions.ts
+++ b/src/features/workspaces/actions.ts
@@ -2,7 +2,14 @@ import PropTypes from 'prop-types';
2import Workspace from './models/Workspace'; 2import Workspace from './models/Workspace';
3import { createActionsFromDefinitions } from '../../actions/lib/actions'; 3import { createActionsFromDefinitions } from '../../actions/lib/actions';
4 4
5export default createActionsFromDefinitions( 5export interface WorkspaceActions {
6 openWorkspaceSettings: () => void;
7 toggleWorkspaceDrawer: () => void;
8 deactivate: () => void;
9 activate: (options: any) => void;
10}
11
12export default createActionsFromDefinitions<WorkspaceActions>(
6 { 13 {
7 edit: { 14 edit: {
8 workspace: PropTypes.instanceOf(Workspace).isRequired, 15 workspace: PropTypes.instanceOf(Workspace).isRequired,
diff --git a/src/index.ts b/src/index.ts
index 3233714e0..0efd0437d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -33,7 +33,7 @@ import { ifUndefined } from './jsUtils';
33 33
34import { mainIpcHandler as basicAuthHandler } from './features/basicAuth'; 34import { mainIpcHandler as basicAuthHandler } from './features/basicAuth';
35import ipcApi from './electron/ipc-api'; 35import ipcApi from './electron/ipc-api';
36import Tray from './lib/Tray'; 36import TrayIcon from './lib/Tray';
37import DBus from './lib/DBus'; 37import DBus from './lib/DBus';
38import Settings from './electron/Settings'; 38import Settings from './electron/Settings';
39import handleDeepLink from './electron/deepLinking'; 39import handleDeepLink from './electron/deepLinking';
@@ -251,7 +251,7 @@ const createWindow = () => {
251 }); 251 });
252 252
253 // Initialize System Tray 253 // Initialize System Tray
254 const trayIcon: Tray = new Tray(); 254 const trayIcon: TrayIcon = new TrayIcon();
255 255
256 // Initialize DBus interface 256 // Initialize DBus interface
257 const dbus = new DBus(trayIcon); 257 const dbus = new DBus(trayIcon);
diff --git a/src/lib/DBus.ts b/src/lib/DBus.ts
index b1febc2d1..bbff405c4 100644
--- a/src/lib/DBus.ts
+++ b/src/lib/DBus.ts
@@ -1,17 +1,20 @@
1import { MessageBus, sessionBus } from 'dbus-next'; 1import { MessageBus, sessionBus } from 'dbus-next';
2import { isLinux } from '../environment'; 2import { isLinux } from '../environment';
3import TrayIcon from './Tray';
3 4
4export default class DBus { 5export default class DBus {
5 bus: MessageBus | null = null; 6 bus: MessageBus | null = null;
6 7
7 trayIcon: any; 8 trayIcon: TrayIcon;
8 9
9 constructor(trayIcon: any) { 10 constructor(trayIcon: TrayIcon) {
10 this.trayIcon = trayIcon; 11 this.trayIcon = trayIcon;
11 } 12 }
12 13
13 start() { 14 start() {
14 if (!isLinux || this.bus) return; 15 if (!isLinux || this.bus) {
16 return;
17 }
15 18
16 try { 19 try {
17 this.bus = sessionBus(); 20 this.bus = sessionBus();
@@ -47,7 +50,9 @@ export default class DBus {
47 } 50 }
48 51
49 stop() { 52 stop() {
50 if (!this.bus) return; 53 if (!this.bus) {
54 return;
55 }
51 56
52 this.bus.disconnect(); 57 this.bus.disconnect();
53 this.bus = null; 58 this.bus = null;
diff --git a/src/lib/Menu.js b/src/lib/Menu.ts
index 52b6be18a..c206ea55d 100644
--- a/src/lib/Menu.js
+++ b/src/lib/Menu.ts
@@ -1,4 +1,4 @@
1import { clipboard } from 'electron'; 1import { clipboard, MenuItemConstructorOptions } from 'electron';
2import { 2import {
3 app, 3 app,
4 Menu, 4 Menu,
@@ -8,7 +8,7 @@ import {
8 getCurrentWindow, 8 getCurrentWindow,
9} from '@electron/remote'; 9} from '@electron/remote';
10import { autorun, action, makeObservable, observable } from 'mobx'; 10import { autorun, action, makeObservable, observable } from 'mobx';
11import { defineMessages } from 'react-intl'; 11import { defineMessages, IntlShape } from 'react-intl';
12import osName from 'os-name'; 12import osName from 'os-name';
13import { fromJS } from 'immutable'; 13import { fromJS } from 'immutable';
14import semver from 'semver'; 14import semver from 'semver';
@@ -42,9 +42,11 @@ import { importExportURL, serverBase, serverName } from '../api/apiBase';
42import { openExternalUrl } from '../helpers/url-helpers'; 42import { openExternalUrl } from '../helpers/url-helpers';
43import globalMessages from '../i18n/globalMessages'; 43import globalMessages from '../i18n/globalMessages';
44import { onAuthGoToReleaseNotes } from '../helpers/update-helpers'; 44import { onAuthGoToReleaseNotes } from '../helpers/update-helpers';
45
46// @ts-expect-error Cannot find module '../buildInfo.json' or its corresponding type declarations. 45// @ts-expect-error Cannot find module '../buildInfo.json' or its corresponding type declarations.
47import * as buildInfo from '../buildInfo.json'; 46import { timestamp, gitHashShort, gitBranch } from '../buildInfo.json';
47import Service from '../models/Service';
48import { StoresProps } from '../@types/ferdium-components.types';
49import { RealStores } from '../stores';
48 50
49const menuItems = defineMessages({ 51const menuItems = defineMessages({
50 edit: { 52 edit: {
@@ -349,11 +351,11 @@ const menuItems = defineMessages({
349 }, 351 },
350}); 352});
351 353
352function getActiveService() { 354function getActiveService(): Service | undefined {
353 return window['ferdium'].stores.services.active; 355 return window['ferdium'].stores.services.active;
354} 356}
355 357
356function _toggleFullScreen() { 358function toggleFullScreen(): void {
357 const mainWindow = getCurrentWindow(); 359 const mainWindow = getCurrentWindow();
358 360
359 if (!mainWindow) return; 361 if (!mainWindow) return;
@@ -365,314 +367,337 @@ function _toggleFullScreen() {
365 } 367 }
366} 368}
367 369
368const _titleBarTemplateFactory = (intl, locked) => [ 370function titleBarTemplateFactory(
369 { 371 intl: IntlShape,
370 label: intl.formatMessage(menuItems.edit), 372 locked: boolean,
371 accelerator: `${altKey()}+E`, 373): MenuItemConstructorOptions[] {
372 submenu: [ 374 return [
373 { 375 {
374 label: intl.formatMessage(menuItems.undo), 376 label: intl.formatMessage(menuItems.edit),
375 role: 'undo', 377 accelerator: `${altKey()}+E`,
376 }, 378 submenu: [
377 { 379 {
378 label: intl.formatMessage(menuItems.redo), 380 label: intl.formatMessage(menuItems.undo),
379 role: 'redo', 381 role: 'undo',
380 },
381 {
382 type: 'separator',
383 },
384 {
385 label: intl.formatMessage(menuItems.cut),
386 accelerator: `${cmdOrCtrlShortcutKey()}+X`,
387 role: 'cut',
388 },
389 {
390 label: intl.formatMessage(menuItems.copy),
391 accelerator: `${cmdOrCtrlShortcutKey()}+C`,
392 role: 'copy',
393 },
394 {
395 label: intl.formatMessage(menuItems.paste),
396 accelerator: `${cmdOrCtrlShortcutKey()}+V`,
397 role: 'paste',
398 },
399 {
400 label: intl.formatMessage(menuItems.pasteAndMatchStyle),
401 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+V`, // Override the accelerator since this adds new key combo in macos
402 role: 'pasteAndMatchStyle',
403 },
404 {
405 label: intl.formatMessage(menuItems.delete),
406 role: 'delete',
407 },
408 {
409 label: intl.formatMessage(menuItems.selectAll),
410 accelerator: `${cmdOrCtrlShortcutKey()}+A`,
411 role: 'selectall',
412 },
413 ],
414 },
415 {
416 label: intl.formatMessage(menuItems.view),
417 accelerator: `${altKey()}+V`,
418 visible: !locked,
419 submenu: [
420 {
421 type: 'separator',
422 },
423 {
424 label: intl.formatMessage(menuItems.openQuickSwitch),
425 accelerator: `${cmdOrCtrlShortcutKey()}+S`,
426 click() {
427 window['ferdium'].features.quickSwitch.state.isModalVisible = true;
428 }, 382 },
429 }, 383 {
430 { 384 label: intl.formatMessage(menuItems.redo),
431 type: 'separator', 385 role: 'redo',
432 }, 386 },
433 { 387 {
434 label: intl.formatMessage(menuItems.findInPage), 388 type: 'separator',
435 accelerator: `${cmdOrCtrlShortcutKey()}+F`, 389 },
436 click() { 390 {
437 const service = getActiveService(); 391 label: intl.formatMessage(menuItems.cut),
438 // Check if there is a service active 392 accelerator: `${cmdOrCtrlShortcutKey()}+X`,
439 if (service) { 393 role: 'cut',
440 // Focus webview so find in page popup gets focused 394 },
441 service.webview.focus(); 395 {
442 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();
443 window['ferdium'].actions.service.sendIPCMessage({ 448 window['ferdium'].actions.service.sendIPCMessage({
444 serviceId: service.id, 449 serviceId: activeService.id,
445 channel: 'find-in-page', 450 channel: 'find-in-page',
446 args: {}, 451 args: {},
447 }); 452 });
448 } 453 },
449 }, 454 },
450 }, 455 {
451 { 456 type: 'separator',
452 type: 'separator',
453 },
454 {
455 label: intl.formatMessage(menuItems.back),
456 accelerator: `${!isMac ? altKey() : cmdOrCtrlShortcutKey()}+Left`,
457 click() {
458 getActiveService().webview.goBack();
459 }, 457 },
460 }, 458 {
461 { 459 label: intl.formatMessage(menuItems.back),
462 label: intl.formatMessage(menuItems.forward), 460 accelerator: `${!isMac ? altKey() : cmdOrCtrlShortcutKey()}+Left`,
463 accelerator: `${!isMac ? altKey() : cmdOrCtrlShortcutKey()}+Right`, 461 click() {
464 click() { 462 const activeService = getActiveService();
465 getActiveService().webview.goForward(); 463 if (!activeService) {
464 return;
465 }
466 activeService.webview.goBack();
467 },
466 }, 468 },
467 }, 469 {
468 { 470 label: intl.formatMessage(menuItems.forward),
469 type: 'separator', 471 accelerator: `${!isMac ? altKey() : cmdOrCtrlShortcutKey()}+Right`,
470 }, 472 click() {
471 { 473 const activeService = getActiveService();
472 label: intl.formatMessage(menuItems.resetZoom), 474 if (!activeService) {
473 accelerator: `${cmdOrCtrlShortcutKey()}+0`, 475 return;
474 click() { 476 }
475 getActiveService().webview.setZoomLevel(0); 477 activeService.webview.goForward();
478 },
476 }, 479 },
477 }, 480 {
478 { 481 type: 'separator',
479 label: intl.formatMessage(menuItems.zoomIn),
480 accelerator: `${cmdOrCtrlShortcutKey()}+plus`,
481 click() {
482 const activeService = getActiveService().webview;
483 const level = activeService.getZoomLevel();
484
485 activeService.setZoomLevel(level + 0.5);
486 }, 482 },
487 }, 483 {
488 { 484 label: intl.formatMessage(menuItems.resetZoom),
489 label: intl.formatMessage(menuItems.zoomOut), 485 accelerator: `${cmdOrCtrlShortcutKey()}+0`,
490 accelerator: `${cmdOrCtrlShortcutKey()}+-`, 486 click() {
491 click() { 487 const activeService = getActiveService();
492 const activeService = getActiveService().webview; 488 if (!activeService) {
493 const level = activeService.getZoomLevel(); 489 return;
494 490 }
495 activeService.setZoomLevel(level - 0.5); 491 activeService.webview.setZoomLevel(0);
492 },
496 }, 493 },
497 }, 494 {
498 { 495 label: intl.formatMessage(menuItems.zoomIn),
499 type: 'separator', 496 accelerator: `${cmdOrCtrlShortcutKey()}+plus`,
500 }, 497 click() {
501 { 498 const activeService = getActiveService();
502 label: intl.formatMessage(menuItems.toggleFullScreen), 499 if (!activeService) {
503 click: () => { 500 return;
504 _toggleFullScreen(); 501 }
502 const { webview } = activeService;
503 const level = webview.getZoomLevel();
504 webview.setZoomLevel(level + 0.5);
505 },
505 }, 506 },
506 accelerator: toggleFullScreenKey(), 507 {
507 }, 508 label: intl.formatMessage(menuItems.zoomOut),
508 { 509 accelerator: `${cmdOrCtrlShortcutKey()}+-`,
509 label: intl.formatMessage(menuItems.toggleNavigationBar), 510 click() {
510 accelerator: `${cmdOrCtrlShortcutKey()}+B`, 511 const activeService = getActiveService();
511 role: 'toggleNavigationBar', 512 if (!activeService) {
512 type: 'checkbox', 513 return;
513 checked: 514 }
514 window['ferdium'].stores.settings.app.navigationBarManualActive, 515 const { webview } = activeService;
515 click: () => { 516 const level = webview.getZoomLevel();
516 window['ferdium'].actions.settings.update({ 517 webview.setZoomLevel(level - 0.5);
517 type: 'app', 518 },
518 data: {
519 navigationBarManualActive:
520 !window['ferdium'].stores.settings.app
521 .navigationBarManualActive,
522 },
523 });
524 }, 519 },
525 }, 520 {
526 { 521 type: 'separator',
527 label: intl.formatMessage(menuItems.splitModeToggle),
528 accelerator: `${splitModeToggleShortcutKey()}`,
529 role: 'splitModeToggle',
530 type: 'checkbox',
531 checked: window['ferdium'].stores.settings.app.splitMode,
532 click: () => {
533 window['ferdium'].actions.settings.update({
534 type: 'app',
535 data: {
536 splitMode: !window['ferdium'].stores.settings.app.splitMode,
537 },
538 });
539 }, 522 },
540 }, 523 {
541 { 524 label: intl.formatMessage(menuItems.toggleFullScreen),
542 label: intl.formatMessage(menuItems.toggleDarkMode), 525 click: () => {
543 type: 'checkbox', 526 toggleFullScreen();
544 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+D`, 527 },
545 checked: window['ferdium'].stores.settings.app.darkMode, 528 accelerator: toggleFullScreenKey(),
546 click: () => {
547 window['ferdium'].actions.settings.update({
548 type: 'app',
549 data: {
550 darkMode: !window['ferdium'].stores.settings.app.darkMode,
551 },
552 });
553 }, 529 },
554 }, 530 {
555 ], 531 label: intl.formatMessage(menuItems.toggleNavigationBar),
556 }, 532 accelerator: `${cmdOrCtrlShortcutKey()}+B`,
557 { 533 // role: 'toggleNavigationBar',
558 label: intl.formatMessage(menuItems.services), 534 type: 'checkbox',
559 accelerator: `${altKey()}+S`, 535 checked:
560 visible: !locked, 536 window['ferdium'].stores.settings.app.navigationBarManualActive,
561 submenu: [], 537 click: () => {
562 }, 538 window['ferdium'].actions.settings.update({
563 { 539 type: 'app',
564 label: intl.formatMessage(menuItems.workspaces), 540 data: {
565 accelerator: `${altKey()}+W`, 541 navigationBarManualActive:
566 submenu: [], 542 !window['ferdium'].stores.settings.app
567 visible: !locked, 543 .navigationBarManualActive,
568 }, 544 },
569 { 545 });
570 label: intl.formatMessage(menuItems.todos), 546 },
571 submenu: [],
572 visible: !locked,
573 },
574 {
575 label: intl.formatMessage(menuItems.window),
576 role: 'window',
577 submenu: [
578 {
579 label: intl.formatMessage(menuItems.minimize),
580 role: 'minimize',
581 },
582 {
583 label: intl.formatMessage(menuItems.close),
584 role: 'close',
585 },
586 ],
587 },
588 {
589 label: intl.formatMessage(menuItems.help),
590 accelerator: `${altKey()}+H`,
591 role: 'help',
592 submenu: [
593 {
594 label: intl.formatMessage(menuItems.learnMore),
595 click() {
596 openExternalUrl(LIVE_API_FERDIUM_WEBSITE, true);
597 }, 547 },
598 }, 548 {
599 { 549 label: intl.formatMessage(menuItems.splitModeToggle),
600 label: intl.formatMessage(menuItems.changelog), 550 accelerator: `${splitModeToggleShortcutKey()}`,
601 click() { 551 // role: 'splitModeToggle',
602 window.location.href = onAuthGoToReleaseNotes(window.location.href); 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 },
603 }, 562 },
604 }, 563 {
605 { 564 label: intl.formatMessage(menuItems.toggleDarkMode),
606 label: intl.formatMessage(menuItems.importExportData), 565 type: 'checkbox',
607 click() { 566 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+D`,
608 openExternalUrl(importExportURL(), true); 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 },
609 }, 576 },
610 enabled: !locked, 577 ],
611 }, 578 },
612 { 579 {
613 type: 'separator', 580 label: intl.formatMessage(menuItems.services),
614 }, 581 accelerator: `${altKey()}+S`,
615 { 582 visible: !locked,
616 label: intl.formatMessage(menuItems.support), 583 submenu: [],
617 click() { 584 },
618 openExternalUrl(`${LIVE_API_FERDIUM_WEBSITE}/contact`, true); 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',
619 }, 603 },
620 }, 604 {
621 { 605 label: intl.formatMessage(menuItems.close),
622 type: 'separator', 606 role: 'close',
623 },
624 {
625 label: intl.formatMessage(menuItems.tos),
626 click() {
627 openExternalUrl(`${serverBase()}/terms`, true);
628 }, 607 },
629 }, 608 ],
630 { 609 },
631 label: intl.formatMessage(menuItems.privacy), 610 {
632 click() { 611 label: intl.formatMessage(menuItems.help),
633 openExternalUrl(`${serverBase()}/privacy`, true); 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 },
634 }, 620 },
635 }, 621 {
636 ], 622 label: intl.formatMessage(menuItems.changelog),
637 }, 623 click() {
638]; 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}
639 662
640class FranzMenu { 663class FranzMenu implements StoresProps {
641 @observable currentTemplate = []; 664 @observable currentTemplate: MenuItemConstructorOptions[];
642 665
643 constructor(stores, actions) { 666 actions: any;
667
668 stores: RealStores;
669
670 constructor(stores: RealStores, actions: any) {
644 this.stores = stores; 671 this.stores = stores;
645 this.actions = actions; 672 this.actions = actions;
673 this.currentTemplate = [];
646 674
647 makeObservable(this); 675 makeObservable(this);
648 676
649 setTimeout(() => { 677 setTimeout(() => autorun(this._build.bind(this)), 10);
650 autorun(this._build.bind(this));
651 }, 10);
652 } 678 }
653 679
654 @action _setCurrentTemplate(tpl) { 680 @action _setCurrentTemplate(tpl: MenuItemConstructorOptions[]): void {
655 this.currentTemplate = tpl; 681 this.currentTemplate = tpl;
656 } 682 }
657 683
658 rebuild() { 684 rebuild(): void {
659 this._build(); 685 this._build();
660 } 686 }
661 687
662 get template() { 688 get template(): any {
663 return fromJS(this.currentTemplate).toJS(); 689 return fromJS(this.currentTemplate).toJS();
664 } 690 }
665 691
666 getOsName() { 692 getOsName(): string {
667 let osNameParse = osName(); 693 let osNameParse = osName();
668 const isWin11 = semver.satisfies(os.release(), '>=10.0.22000'); 694 const isWin11 = semver.satisfies(os.release(), '>=10.0.22000');
669
670 osNameParse = isWindows && isWin11 ? 'Windows 11' : osNameParse; 695 osNameParse = isWindows && isWin11 ? 'Windows 11' : osNameParse;
671 696
672 return osNameParse; 697 return osNameParse;
673 } 698 }
674 699
675 _build() { 700 _build(): void {
676 // need to clone object so we don't modify computed (cached) object 701 // need to clone object so we don't modify computed (cached) object
677 const serviceTpl = Object.assign([], this.serviceTpl()); 702 const serviceTpl = Object.assign([], this.serviceTpl());
678 703
@@ -687,11 +712,11 @@ class FranzMenu {
687 this.stores.settings.app.locked && 712 this.stores.settings.app.locked &&
688 this.stores.settings.app.lockingFeatureEnabled && 713 this.stores.settings.app.lockingFeatureEnabled &&
689 this.stores.user.isLoggedIn; 714 this.stores.user.isLoggedIn;
690 const tpl = _titleBarTemplateFactory(intl, locked);
691 const { actions } = this; 715 const { actions } = this;
716 const tpl = titleBarTemplateFactory(intl, locked);
692 717
693 if (!isMac) { 718 if (!isMac) {
694 tpl[1].submenu.push({ 719 (tpl[1].submenu as MenuItemConstructorOptions[]).push({
695 label: intl.formatMessage(menuItems.autohideMenuBar), 720 label: intl.formatMessage(menuItems.autohideMenuBar),
696 type: 'checkbox', 721 type: 'checkbox',
697 checked: window['ferdium'].stores.settings.app.autohideMenuBar, 722 checked: window['ferdium'].stores.settings.app.autohideMenuBar,
@@ -708,7 +733,7 @@ class FranzMenu {
708 } 733 }
709 734
710 if (!locked) { 735 if (!locked) {
711 tpl[1].submenu.push( 736 (tpl[1].submenu as MenuItemConstructorOptions[]).push(
712 { 737 {
713 type: 'separator', 738 type: 'separator',
714 }, 739 },
@@ -740,7 +765,7 @@ class FranzMenu {
740 ); 765 );
741 766
742 if (this.stores.todos.isFeatureEnabledByUser) { 767 if (this.stores.todos.isFeatureEnabledByUser) {
743 tpl[1].submenu.push({ 768 (tpl[1].submenu as MenuItemConstructorOptions[]).push({
744 label: intl.formatMessage(menuItems.toggleTodosDevTools), 769 label: intl.formatMessage(menuItems.toggleTodosDevTools),
745 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+${altKey()}+O`, 770 accelerator: `${cmdOrCtrlShortcutKey()}+${shiftKey()}+${altKey()}+O`,
746 click: () => { 771 click: () => {
@@ -750,7 +775,7 @@ class FranzMenu {
750 }); 775 });
751 } 776 }
752 777
753 tpl[1].submenu.unshift( 778 (tpl[1].submenu as MenuItemConstructorOptions[]).unshift(
754 { 779 {
755 label: intl.formatMessage(menuItems.reloadService), 780 label: intl.formatMessage(menuItems.reloadService),
756 accelerator: `${cmdOrCtrlShortcutKey()}+R`, 781 accelerator: `${cmdOrCtrlShortcutKey()}+R`,
@@ -759,8 +784,8 @@ class FranzMenu {
759 this.stores.user.isLoggedIn && 784 this.stores.user.isLoggedIn &&
760 this.stores.services.enabled.length > 0 785 this.stores.services.enabled.length > 0
761 ) { 786 ) {
762 if (getActiveService().recipe.id === CUSTOM_WEBSITE_RECIPE_ID) { 787 if (getActiveService()?.recipe.id === CUSTOM_WEBSITE_RECIPE_ID) {
763 getActiveService().webview.reload(); 788 getActiveService()?.webview.reload();
764 } else { 789 } else {
765 this.actions.service.reloadActive(); 790 this.actions.service.reloadActive();
766 } 791 }
@@ -816,7 +841,7 @@ class FranzMenu {
816 systemPreferences.canPromptTouchID() 841 systemPreferences.canPromptTouchID()
817 : false; 842 : false;
818 843
819 tpl[0].submenu.unshift( 844 (tpl[0].submenu as MenuItemConstructorOptions[]).unshift(
820 { 845 {
821 label: intl.formatMessage(menuItems.touchId), 846 label: intl.formatMessage(menuItems.touchId),
822 accelerator: `${lockFerdiumShortcutKey()}`, 847 accelerator: `${lockFerdiumShortcutKey()}`,
@@ -909,9 +934,9 @@ class FranzMenu {
909 `Node.js: ${nodeVersion}`, 934 `Node.js: ${nodeVersion}`,
910 `Platform: ${this.getOsName()}`, 935 `Platform: ${this.getOsName()}`,
911 `Arch: ${osArch}`, 936 `Arch: ${osArch}`,
912 `Build date: ${new Date(Number(buildInfo.timestamp))}`, 937 `Build date: ${new Date(Number(timestamp))}`,
913 `Git SHA: ${buildInfo.gitHashShort}`, 938 `Git SHA: ${gitHashShort}`,
914 `Git branch: ${buildInfo.gitBranch}`, 939 `Git branch: ${gitBranch}`,
915 ].join('\n'); 940 ].join('\n');
916 941
917 const about = { 942 const about = {
@@ -940,7 +965,7 @@ class FranzMenu {
940 965
941 if (isMac) { 966 if (isMac) {
942 // Edit menu. 967 // Edit menu.
943 tpl[1].submenu.push( 968 (tpl[1].submenu as MenuItemConstructorOptions[]).push(
944 { 969 {
945 type: 'separator', 970 type: 'separator',
946 }, 971 },
@@ -949,17 +974,17 @@ class FranzMenu {
949 submenu: [ 974 submenu: [
950 { 975 {
951 label: intl.formatMessage(menuItems.startSpeaking), 976 label: intl.formatMessage(menuItems.startSpeaking),
952 role: 'startspeaking', 977 role: 'startSpeaking',
953 }, 978 },
954 { 979 {
955 label: intl.formatMessage(menuItems.stopSpeaking), 980 label: intl.formatMessage(menuItems.stopSpeaking),
956 role: 'stopspeaking', 981 role: 'stopSpeaking',
957 }, 982 },
958 ], 983 ],
959 }, 984 },
960 ); 985 );
961 986
962 tpl[0].submenu.unshift(about, { 987 (tpl[0].submenu as MenuItemConstructorOptions[]).unshift(about, {
963 type: 'separator', 988 type: 'separator',
964 }); 989 });
965 } else { 990 } else {
@@ -985,7 +1010,7 @@ class FranzMenu {
985 }, 1010 },
986 ]; 1011 ];
987 1012
988 tpl[tpl.length - 1].submenu.push( 1013 (tpl[tpl.length - 1].submenu as MenuItemConstructorOptions[]).push(
989 { 1014 {
990 type: 'separator', 1015 type: 'separator',
991 }, 1016 },
@@ -1002,7 +1027,7 @@ class FranzMenu {
1002 1027
1003 tpl[5].submenu = this.todosMenu(); 1028 tpl[5].submenu = this.todosMenu();
1004 1029
1005 tpl[tpl.length - 1].submenu.push( 1030 (tpl[tpl.length - 1].submenu as MenuItemConstructorOptions[]).push(
1006 { 1031 {
1007 type: 'separator', 1032 type: 'separator',
1008 }, 1033 },
@@ -1014,13 +1039,15 @@ class FranzMenu {
1014 Menu.setApplicationMenu(menu); 1039 Menu.setApplicationMenu(menu);
1015 } 1040 }
1016 1041
1017 serviceTpl() { 1042 serviceTpl(): MenuItemConstructorOptions[] {
1018 const { intl } = window['ferdium']; 1043 const { intl } = window['ferdium'];
1019 const { user, services, settings } = this.stores; 1044 const { user, services, settings } = this.stores;
1020 if (!user.isLoggedIn) return []; 1045 if (!user.isLoggedIn) {
1021 const menu = []; 1046 return [];
1022 const cmdAltShortcutsVisibile = !isLinux; 1047 }
1023 1048
1049 const cmdAltShortcutsVisibile = !isLinux;
1050 const menu: MenuItemConstructorOptions[] = [];
1024 menu.push( 1051 menu.push(
1025 { 1052 {
1026 label: intl.formatMessage(menuItems.addNewService), 1053 label: intl.formatMessage(menuItems.addNewService),
@@ -1075,7 +1102,7 @@ class FranzMenu {
1075 for (const [i, service] of services.allDisplayed.entries()) { 1102 for (const [i, service] of services.allDisplayed.entries()) {
1076 menu.push({ 1103 menu.push({
1077 label: this._getServiceName(service), 1104 label: this._getServiceName(service),
1078 accelerator: i < 9 ? `${cmdOrCtrlShortcutKey()}+${i + 1}` : null, 1105 accelerator: i < 9 ? `${cmdOrCtrlShortcutKey()}+${i + 1}` : undefined,
1079 type: 'radio', 1106 type: 'radio',
1080 checked: service.isActive, 1107 checked: service.isActive,
1081 click: () => { 1108 click: () => {
@@ -1108,12 +1135,12 @@ class FranzMenu {
1108 return menu; 1135 return menu;
1109 } 1136 }
1110 1137
1111 workspacesMenu() { 1138 workspacesMenu(): MenuItemConstructorOptions[] {
1112 const { workspaces, activeWorkspace, isWorkspaceDrawerOpen } = 1139 const { workspaces, activeWorkspace, isWorkspaceDrawerOpen } =
1113 workspaceStore; 1140 workspaceStore;
1114 const { intl } = window['ferdium']; 1141 const { intl } = window['ferdium'];
1115 const menu = [];
1116 1142
1143 const menu: MenuItemConstructorOptions[] = [];
1117 // Add new workspace item: 1144 // Add new workspace item:
1118 menu.push({ 1145 menu.push({
1119 label: intl.formatMessage(menuItems.addNewWorkspace), 1146 label: intl.formatMessage(menuItems.addNewWorkspace),
@@ -1159,7 +1186,7 @@ class FranzMenu {
1159 menu.push({ 1186 menu.push({
1160 label: workspace.name, 1187 label: workspace.name,
1161 accelerator: 1188 accelerator:
1162 i < 9 ? `${cmdOrCtrlShortcutKey()}+${altKey()}+${i + 1}` : null, 1189 i < 9 ? `${cmdOrCtrlShortcutKey()}+${altKey()}+${i + 1}` : undefined,
1163 type: 'radio', 1190 type: 'radio',
1164 checked: activeWorkspace ? workspace.id === activeWorkspace.id : false, 1191 checked: activeWorkspace ? workspace.id === activeWorkspace.id : false,
1165 click: () => { 1192 click: () => {
@@ -1171,11 +1198,11 @@ class FranzMenu {
1171 return menu; 1198 return menu;
1172 } 1199 }
1173 1200
1174 todosMenu() { 1201 todosMenu(): MenuItemConstructorOptions[] {
1175 const { isTodosPanelVisible, isFeatureEnabledByUser } = this.stores.todos; 1202 const { isTodosPanelVisible, isFeatureEnabledByUser } = this.stores.todos;
1176 const { intl } = window['ferdium']; 1203 const { intl } = window['ferdium'];
1177 const menu = [];
1178 1204
1205 const menu: MenuItemConstructorOptions[] = [];
1179 menu.push({ 1206 menu.push({
1180 label: intl.formatMessage( 1207 label: intl.formatMessage(
1181 isFeatureEnabledByUser ? menuItems.disableTodos : menuItems.enableTodos, 1208 isFeatureEnabledByUser ? menuItems.disableTodos : menuItems.enableTodos,
@@ -1209,7 +1236,7 @@ class FranzMenu {
1209 return menu; 1236 return menu;
1210 } 1237 }
1211 1238
1212 debugMenu() { 1239 debugMenu(): MenuItemConstructorOptions[] {
1213 const { intl } = window['ferdium']; 1240 const { intl } = window['ferdium'];
1214 1241
1215 return [ 1242 return [
@@ -1233,9 +1260,8 @@ class FranzMenu {
1233 { 1260 {
1234 label: intl.formatMessage(menuItems.publishDebugInfo), 1261 label: intl.formatMessage(menuItems.publishDebugInfo),
1235 click: () => { 1262 click: () => {
1236 window[ 1263 window['ferdium'].features.publishDebugInfo.state.isModalVisible =
1237 'ferdium' 1264 true;
1238 ].features.publishDebugInfo.state.isModalVisible = true;
1239 }, 1265 },
1240 }, 1266 },
1241 ]; 1267 ];
@@ -1246,15 +1272,14 @@ class FranzMenu {
1246 return service.name; 1272 return service.name;
1247 } 1273 }
1248 1274
1249 let { name } = service.recipe; 1275 let { name: serviceName } = service.recipe;
1250
1251 if (service.team) { 1276 if (service.team) {
1252 name = `${name} (${service.team})`; 1277 serviceName = `${serviceName} (${service.team})`;
1253 } else if (service.customUrl) { 1278 } else if (service.customUrl) {
1254 name = `${name} (${service.customUrl})`; 1279 serviceName = `${serviceName} (${service.customUrl})`;
1255 } 1280 }
1256 1281
1257 return name; 1282 return serviceName;
1258 } 1283 }
1259} 1284}
1260 1285
diff --git a/src/lib/Tray.js b/src/lib/Tray.ts
index fffdec64d..8e489edde 100644
--- a/src/lib/Tray.js
+++ b/src/lib/Tray.ts
@@ -7,6 +7,7 @@ import {
7 Tray, 7 Tray,
8 ipcMain, 8 ipcMain,
9 BrowserWindow, 9 BrowserWindow,
10 NativeImage,
10} from 'electron'; 11} from 'electron';
11import { join } from 'path'; 12import { join } from 'path';
12import macosVersion from 'macos-version'; 13import macosVersion from 'macos-version';
@@ -19,56 +20,30 @@ const INDICATOR_TRAY_INDIRECT = 'tray-indirect';
19 20
20// TODO: Need to support i18n for a lot of the hard-coded strings in this file 21// TODO: Need to support i18n for a lot of the hard-coded strings in this file
21export default class TrayIcon { 22export default class TrayIcon {
22 trayIcon = null; 23 trayIcon: Tray | null = null;
23 24
24 indicator = 0; 25 indicator: string | number = 0;
25 26
26 themeChangeSubscriberId = null; 27 themeChangeSubscriberId: number | null = null;
27 28
28 trayMenu = null; 29 trayMenu: Menu | null = null;
29 30
30 visible = false; 31 visible = false;
31 32
32 isAppMuted = false; 33 isAppMuted = false;
33 34
34 mainWindow = null; 35 mainWindow: BrowserWindow | null = null;
35
36 trayMenuTemplate = tray => [
37 {
38 label:
39 tray.mainWindow.isVisible() && tray.mainWindow.isFocused()
40 ? 'Hide Ferdium'
41 : 'Show Ferdium',
42 click() {
43 tray._toggleWindow();
44 },
45 },
46 {
47 label: tray.isAppMuted
48 ? 'Enable Notifications && Audio'
49 : 'Disable Notifications && Audio',
50 click() {
51 if (!tray.mainWindow) return;
52 tray.mainWindow.webContents.send('muteApp');
53 },
54 },
55 {
56 label: 'Quit Ferdium',
57 click() {
58 app.quit();
59 },
60 },
61 ];
62 36
63 constructor() { 37 constructor() {
64 ipcMain.on('initialAppSettings', (event, appSettings) => { 38 ipcMain.on('initialAppSettings', (_, appSettings) => {
65 this._updateTrayMenu(appSettings); 39 this._updateTrayMenu(appSettings);
66 }); 40 });
67 ipcMain.on('updateAppSettings', (event, appSettings) => { 41 ipcMain.on('updateAppSettings', (_, appSettings) => {
68 this._updateTrayMenu(appSettings); 42 this._updateTrayMenu(appSettings);
69 }); 43 });
70 44
71 this.mainWindow = BrowserWindow.getAllWindows()[0]; 45 const [firstWindow] = BrowserWindow.getAllWindows();
46 this.mainWindow = firstWindow;
72 47
73 // listen to window events to be able to set correct string 48 // listen to window events to be able to set correct string
74 // to tray menu ('Hide Ferdium' / 'Show Ferdium') 49 // to tray menu ('Hide Ferdium' / 'Show Ferdium')
@@ -92,7 +67,36 @@ export default class TrayIcon {
92 }); 67 });
93 } 68 }
94 69
95 _updateTrayMenu(appSettings) { 70 trayMenuTemplate(tray) {
71 return [
72 {
73 label:
74 tray.mainWindow.isVisible() && tray.mainWindow.isFocused()
75 ? 'Hide Ferdium'
76 : 'Show Ferdium',
77 click() {
78 tray._toggleWindow();
79 },
80 },
81 {
82 label: tray.isAppMuted
83 ? 'Enable Notifications && Audio'
84 : 'Disable Notifications && Audio',
85 click() {
86 if (!tray.mainWindow) return;
87 tray.mainWindow.webContents.send('muteApp');
88 },
89 },
90 {
91 label: 'Quit Ferdium',
92 click() {
93 app.quit();
94 },
95 },
96 ];
97 }
98
99 _updateTrayMenu(appSettings): void {
96 if (!this.trayIcon) return; 100 if (!this.trayIcon) return;
97 101
98 if (appSettings && appSettings.type === 'app') { 102 if (appSettings && appSettings.type === 'app') {
@@ -105,13 +109,15 @@ export default class TrayIcon {
105 } 109 }
106 } 110 }
107 111
108 show() { 112 show(): void {
109 this.visible = true; 113 this.visible = true;
110 this._show(); 114 this._show();
111 } 115 }
112 116
113 _show() { 117 _show(): void {
114 if (this.trayIcon) return; 118 if (this.trayIcon) {
119 return;
120 }
115 121
116 this.trayIcon = new Tray(this._getAsset('tray', INDICATOR_TRAY_PLAIN)); 122 this.trayIcon = new Tray(this._getAsset('tray', INDICATOR_TRAY_PLAIN));
117 this.trayIcon.setToolTip('Ferdium'); 123 this.trayIcon.setToolTip('Ferdium');
@@ -127,7 +133,9 @@ export default class TrayIcon {
127 133
128 if (isMac || isWindows) { 134 if (isMac || isWindows) {
129 this.trayIcon.on('right-click', () => { 135 this.trayIcon.on('right-click', () => {
130 this.trayIcon.popUpContextMenu(this.trayMenu); 136 if (this.trayIcon && this.trayMenu) {
137 this.trayIcon.popUpContextMenu(this.trayMenu);
138 }
131 }); 139 });
132 } 140 }
133 141
@@ -141,9 +149,11 @@ export default class TrayIcon {
141 } 149 }
142 } 150 }
143 151
144 _toggleWindow() { 152 _toggleWindow(): void {
145 const mainWindow = BrowserWindow.getAllWindows()[0]; 153 const [mainWindow] = BrowserWindow.getAllWindows();
146 if (!mainWindow) return; 154 if (!mainWindow) {
155 return;
156 }
147 157
148 if (mainWindow.isMinimized()) { 158 if (mainWindow.isMinimized()) {
149 mainWindow.restore(); 159 mainWindow.restore();
@@ -161,12 +171,12 @@ export default class TrayIcon {
161 } 171 }
162 } 172 }
163 173
164 hide() { 174 hide(): void {
165 this.visible = false; 175 this.visible = false;
166 this._hide(); 176 this._hide();
167 } 177 }
168 178
169 _hide() { 179 _hide(): void {
170 if (!this.trayIcon) return; 180 if (!this.trayIcon) return;
171 181
172 this.trayIcon.destroy(); 182 this.trayIcon.destroy();
@@ -178,7 +188,7 @@ export default class TrayIcon {
178 } 188 }
179 } 189 }
180 190
181 recreateIfVisible() { 191 recreateIfVisible(): void {
182 if (this.visible) { 192 if (this.visible) {
183 this._hide(); 193 this._hide();
184 setTimeout(() => { 194 setTimeout(() => {
@@ -189,23 +199,26 @@ export default class TrayIcon {
189 } 199 }
190 } 200 }
191 201
192 setIndicator(indicator) { 202 setIndicator(indicator: string | number): void {
193 this.indicator = indicator; 203 this.indicator = indicator;
194 this._refreshIcon(); 204 this._refreshIcon();
195 } 205 }
196 206
197 _getAssetFromIndicator(indicator) { 207 _getAssetFromIndicator(indicator: string | number): string {
208 let assetFromIndicator = INDICATOR_TRAY_PLAIN;
198 if (indicator === '•') { 209 if (indicator === '•') {
199 return INDICATOR_TRAY_INDIRECT; 210 assetFromIndicator = INDICATOR_TRAY_INDIRECT;
200 } 211 }
201 if (indicator !== 0) { 212 if (indicator !== 0) {
202 return INDICATOR_TRAY_UNREAD; 213 assetFromIndicator = INDICATOR_TRAY_UNREAD;
203 } 214 }
204 return INDICATOR_TRAY_PLAIN; 215 return assetFromIndicator;
205 } 216 }
206 217
207 _refreshIcon() { 218 _refreshIcon(): void {
208 if (!this.trayIcon) return; 219 if (!this.trayIcon) {
220 return;
221 }
209 222
210 this.trayIcon.setImage( 223 this.trayIcon.setImage(
211 this._getAsset('tray', this._getAssetFromIndicator(this.indicator)), 224 this._getAsset('tray', this._getAssetFromIndicator(this.indicator)),
@@ -221,13 +234,14 @@ export default class TrayIcon {
221 } 234 }
222 } 235 }
223 236
224 _getAsset(type, asset) { 237 _getAsset(type, asset): NativeImage {
225 let { platform } = process; 238 const { platform } = process;
239 let platformPath: string = platform;
226 240
227 if (isMac && macosVersion.isGreaterThanOrEqualTo('11')) { 241 if (isMac && macosVersion.isGreaterThanOrEqualTo('11')) {
228 platform = `${platform}-20`; 242 platformPath = `${platform}-20`;
229 } else if (isMac && nativeTheme.shouldUseDarkColors) { 243 } else if (isMac && nativeTheme.shouldUseDarkColors) {
230 platform = `${platform}-dark`; 244 platformPath = `${platform}-dark`;
231 } 245 }
232 246
233 const trayImg = nativeImage.createFromPath( 247 const trayImg = nativeImage.createFromPath(
@@ -237,7 +251,7 @@ export default class TrayIcon {
237 'assets', 251 'assets',
238 'images', 252 'images',
239 type, 253 type,
240 platform, 254 platformPath,
241 `${asset}.${FILE_EXTENSION}`, 255 `${asset}.${FILE_EXTENSION}`,
242 ), 256 ),
243 ); 257 );