From 9f3a304885dc30a1f70f0e106dfcff99cac883fd Mon Sep 17 00:00:00 2001 From: pengx17 Date: Thu, 16 Jan 2025 06:50:08 +0000 Subject: [PATCH] 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 --- .../src/main/application-menu/create.ts | 16 +++++++---- .../apps/electron/src/main/ui/events.ts | 6 ++++ .../apps/electron/src/main/ui/handlers.ts | 5 ++++ .../apps/electron/src/main/ui/subject.ts | 2 ++ .../core/src/modules/workbench/index.ts | 7 ++++- .../services/desktop-state-synchronizer.ts | 28 ++++++++++++++++++- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/packages/frontend/apps/electron/src/main/application-menu/create.ts b/packages/frontend/apps/electron/src/main/application-menu/create.ts index f428b0b707..eb3211f453 100644 --- a/packages/frontend/apps/electron/src/main/application-menu/create.ts +++ b/packages/frontend/apps/electron/src/main/application-menu/create.ts @@ -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-你好' diff --git a/packages/frontend/apps/electron/src/main/ui/events.ts b/packages/frontend/apps/electron/src/main/ui/events.ts index 01461f1166..3c4dd8b51a 100644 --- a/packages/frontend/apps/electron/src/main/ui/events.ts +++ b/packages/frontend/apps/electron/src/main/ui/events.ts @@ -42,4 +42,10 @@ export const uiEvents = { sub.unsubscribe(); }; }, + onCloseView: (fn: () => void) => { + const sub = uiSubjects.onCloseView$.subscribe(fn); + return () => { + sub.unsubscribe(); + }; + }, } satisfies Record; diff --git a/packages/frontend/apps/electron/src/main/ui/handlers.ts b/packages/frontend/apps/electron/src/main/ui/handlers.ts index 9211f8d36a..8ef369f814 100644 --- a/packages/frontend/apps/electron/src/main/ui/handlers.ts +++ b/packages/frontend/apps/electron/src/main/ui/handlers.ts @@ -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) => { await closeTab(...args); }, + activateView: async (_, ...args: Parameters) => { await activateView(...args); }, diff --git a/packages/frontend/apps/electron/src/main/ui/subject.ts b/packages/frontend/apps/electron/src/main/ui/subject.ts index d2832c462e..ac28173a4f 100644 --- a/packages/frontend/apps/electron/src/main/ui/subject.ts +++ b/packages/frontend/apps/electron/src/main/ui/subject.ts @@ -7,4 +7,6 @@ export const uiSubjects = { onFullScreen$: new Subject(), onToggleRightSidebar$: new Subject(), authenticationRequest$: new Subject(), + // via menu -> close view (CMD+W) + onCloseView$: new Subject(), }; diff --git a/packages/frontend/core/src/modules/workbench/index.ts b/packages/frontend/core/src/modules/workbench/index.ts index c2349d5fea..9415274fb8 100644 --- a/packages/frontend/core/src/modules/workbench/index.ts +++ b/packages/frontend/core/src/modules/workbench/index.ts @@ -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, + ]); } diff --git a/packages/frontend/core/src/modules/workbench/services/desktop-state-synchronizer.ts b/packages/frontend/core/src/modules/workbench/services/desktop-state-synchronizer.ts index b71f341734..35751ddc74 100644 --- a/packages/frontend/core/src/modules/workbench/services/desktop-state-synchronizer.ts +++ b/packages/frontend/core/src/modules/workbench/services/desktop-state-synchronizer.ts @@ -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);