feat(electron): more desktop app related shortcuts (#9724)

fix AF-2126, AF-2124

- Add CMD+M for minimize the app.
- Enhance how CMD+W works. Close the following in order, stop if any one is closed:
  - peek view
  - split view
  - tab
  - otherwise, hide the app
This commit is contained in:
pengx17
2025-01-16 06:50:08 +00:00
parent 676f855564
commit 9f3a304885
6 changed files with 56 additions and 8 deletions
@@ -2,10 +2,10 @@ import { app, Menu } from 'electron';
import { isMacOS } from '../../shared/utils';
import { logger, revealLogFile } from '../logger';
import { uiSubjects } from '../ui/subject';
import { checkForUpdates } from '../updater';
import {
addTab,
closeTab,
initAndShowMainWindow,
reloadView,
showDevTools,
@@ -103,6 +103,9 @@ export function createApplicationMenu() {
reloadView().catch(console.error);
},
},
{
role: 'windowMenu',
},
{
label: 'Open devtools',
accelerator: isMac ? 'Cmd+Option+I' : 'Ctrl+Shift+I',
@@ -129,11 +132,12 @@ export function createApplicationMenu() {
},
},
{
label: 'Close tab',
label: 'Close view',
accelerator: 'CommandOrControl+W',
click() {
logger.info('Close tab with shortcut');
closeTab().catch(console.error);
logger.info('Close view with shortcut');
// tell the active workbench to close the current view
uiSubjects.onCloseView$.next();
},
},
{
@@ -195,7 +199,7 @@ export function createApplicationMenu() {
{
label: 'Learn More',
click: async () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
// oxlint-disable-next-line
const { shell } = require('electron');
await shell.openExternal('https://affine.pro/');
},
@@ -216,7 +220,7 @@ export function createApplicationMenu() {
{
label: 'Documentation',
click: async () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
// oxlint-disable-next-line
const { shell } = require('electron');
await shell.openExternal(
'https://docs.affine.pro/docs/hello-bonjour-aloha-你好'
@@ -42,4 +42,10 @@ export const uiEvents = {
sub.unsubscribe();
};
},
onCloseView: (fn: () => void) => {
const sub = uiSubjects.onCloseView$.subscribe(fn);
return () => {
sub.unsubscribe();
};
},
} satisfies Record<string, MainEventRegister>;
@@ -71,6 +71,10 @@ export const uiHandlers = {
handleCloseApp: async () => {
app.quit();
},
handleHideApp: async () => {
const window = await getMainWindow();
window?.hide();
},
handleNetworkChange: async (_, _isOnline: boolean) => {
isOnline = _isOnline;
},
@@ -191,6 +195,7 @@ export const uiHandlers = {
closeTab: async (_, ...args: Parameters<typeof closeTab>) => {
await closeTab(...args);
},
activateView: async (_, ...args: Parameters<typeof activateView>) => {
await activateView(...args);
},
@@ -7,4 +7,6 @@ export const uiSubjects = {
onFullScreen$: new Subject<boolean>(),
onToggleRightSidebar$: new Subject<string>(),
authenticationRequest$: new Subject<AuthenticationRequest>(),
// via menu -> close view (CMD+W)
onCloseView$: new Subject<void>(),
};
@@ -14,6 +14,7 @@ export { WorkbenchRoot } from './view/workbench-root';
import { type Framework } from '@toeverything/infra';
import { DesktopApiService } from '../desktop-api';
import { PeekViewService } from '../peek-view';
import { GlobalStateService } from '../storage';
import { WorkspaceScope } from '../workspace';
import { SidebarTab } from './entities/sidebar-tab';
@@ -64,5 +65,9 @@ export function configureDesktopWorkbenchModule(services: Framework) {
.impl(WorkbenchNewTabHandler, DesktopWorkbenchNewTabHandler, [
DesktopApiService,
])
.service(DesktopStateSynchronizer, [WorkbenchService, DesktopApiService]);
.service(DesktopStateSynchronizer, [
WorkbenchService,
DesktopApiService,
PeekViewService,
]);
}
@@ -1,6 +1,7 @@
import { LiveData, Service } from '@toeverything/infra';
import type { DesktopApiService } from '../../desktop-api';
import type { PeekViewService } from '../../peek-view';
import type { WorkbenchService } from '../../workbench';
/**
@@ -9,7 +10,8 @@ import type { WorkbenchService } from '../../workbench';
export class DesktopStateSynchronizer extends Service {
constructor(
private readonly workbenchService: WorkbenchService,
private readonly electronApi: DesktopApiService
private readonly electronApi: DesktopApiService,
private readonly peekViewService: PeekViewService
) {
super();
this.startSync();
@@ -52,6 +54,30 @@ export class DesktopStateSynchronizer extends Service {
}
});
this.electronApi.events.ui.onCloseView(() => {
(async () => {
if (await this.electronApi.handler.ui.isActiveTab()) {
// close current view. stop if any one is successful
// 1. peek view
// 2. split view
// 3. tab
// 4. otherwise, hide the window
if (this.peekViewService.peekView.show$.value?.value) {
this.peekViewService.peekView.close();
} else if (workbench.views$.value.length > 1) {
workbench.close(workbench.activeView$.value);
} else {
const tabs = await this.electronApi.handler.ui.getTabsStatus();
if (tabs.length > 1) {
await this.electronApi.handler.ui.closeTab();
} else {
await this.electronApi.handler.ui.handleHideApp();
}
}
}
})().catch(console.error);
});
this.electronApi.events.ui.onToggleRightSidebar(tabId => {
if (tabId === appInfo?.viewId) {
workbench.sidebarOpen$.next(!workbench.sidebarOpen$.value);