mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 13:58:50 +08:00
feat: improve mac dock behavior (#15334)
#### PR Dependency Tree * **PR #15334** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Enhancements** * Improved main-window restoration for deep links, second-instance launches, tray/menu actions, and when recordings finish. * Refined macOS Dock show/hide behavior with throttling for smoother window visibility. * Updated close-to-tray/close-to-background handling to better manage the app’s window lifecycle. * Ensured popup/dock visibility is consistent when opening new windows. * Updated window behavior settings display so tray-related options render correctly across platforms. * **Localization** * Updated Simplified Chinese wording for menubar window behavior title. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -7,7 +7,6 @@ import { logger } from './logger';
|
||||
import { uiSubjects } from './ui';
|
||||
import {
|
||||
addTabWithUrl,
|
||||
getMainWindow,
|
||||
loadUrlInActiveTab,
|
||||
openUrlInHiddenWindow,
|
||||
showMainWindow,
|
||||
@@ -74,13 +73,8 @@ export function setupDeepLink(app: App) {
|
||||
|
||||
// on windows & linux, we need to listen for the second-instance event
|
||||
app.on('second-instance', (event, commandLine) => {
|
||||
getMainWindow()
|
||||
.then(window => {
|
||||
if (!window) {
|
||||
logger.error('main window is not ready');
|
||||
return;
|
||||
}
|
||||
window.show();
|
||||
showMainWindow()
|
||||
.then(() => {
|
||||
const url = commandLine.pop();
|
||||
if (url?.startsWith(`${protocol}://`)) {
|
||||
event.preventDefault();
|
||||
@@ -149,11 +143,12 @@ async function handleAffineUrl(url: string) {
|
||||
? await openUrlInHiddenWindow(urlObj)
|
||||
: await loadUrlInActiveTab(url);
|
||||
|
||||
const main = await getMainWindow();
|
||||
if (main && hiddenWindow) {
|
||||
if (hiddenWindow) {
|
||||
// when hidden window closed, the main window will be hidden somehow
|
||||
hiddenWindow.on('close', () => {
|
||||
main.show();
|
||||
void showMainWindow().catch(e => {
|
||||
logger.error('Failed to restore main window:', e);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
MeetingSettingsSchema,
|
||||
} from '../shared-state-schema';
|
||||
import { globalStateStorage } from '../shared-storage/storage';
|
||||
import { getMainWindow } from '../windows-manager';
|
||||
import { showMainWindow } from '../windows-manager';
|
||||
import { popupManager } from '../windows-manager/popup';
|
||||
import { isAppNameAllowed } from './allow-list';
|
||||
import { RecordingCoordinator } from './coordinator';
|
||||
@@ -512,15 +512,9 @@ export async function startRecording(
|
||||
export async function stopRecording(id: number) {
|
||||
const job = await recordingCoordinator.stop(id);
|
||||
if (job?.phase === 'recorded') {
|
||||
void getMainWindow()
|
||||
.then(mainWindow => {
|
||||
if (mainWindow) {
|
||||
mainWindow.show();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('failed to bring up the window', err);
|
||||
});
|
||||
void showMainWindow().catch(err => {
|
||||
logger.error('failed to bring up the window', err);
|
||||
});
|
||||
}
|
||||
return serializeRecordingStatus(getCurrentRecordingStatus());
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from '../recording/feature';
|
||||
import { MenubarStateKey, MenubarStateSchema } from '../shared-state-schema';
|
||||
import { globalStateStorage } from '../shared-storage/storage';
|
||||
import { getMainWindow } from '../windows-manager';
|
||||
import { showMainWindow } from '../windows-manager';
|
||||
import { icons } from './icons';
|
||||
export interface TrayMenuConfigItem {
|
||||
label: string;
|
||||
@@ -43,12 +43,10 @@ interface TrayMenuProvider {
|
||||
getConfig(): TrayMenuConfig;
|
||||
}
|
||||
|
||||
function showMainWindow() {
|
||||
getMainWindow()
|
||||
.then(w => {
|
||||
w.show();
|
||||
})
|
||||
.catch(err => logger.error('Failed to show main window:', err));
|
||||
function activateMainWindow() {
|
||||
void showMainWindow().catch(err => {
|
||||
logger.error('Failed to show main window:', err);
|
||||
});
|
||||
}
|
||||
|
||||
function buildMenuConfig(config: TrayMenuConfig): MenuItemConstructorOptions[] {
|
||||
@@ -109,7 +107,7 @@ class TrayState implements Disposable {
|
||||
icon: icons.journal,
|
||||
click: () => {
|
||||
logger.info('User action: Open Journal');
|
||||
showMainWindow();
|
||||
activateMainWindow();
|
||||
applicationMenuSubjects.openJournal$.next();
|
||||
},
|
||||
},
|
||||
@@ -118,7 +116,7 @@ class TrayState implements Disposable {
|
||||
icon: icons.page,
|
||||
click: () => {
|
||||
logger.info('User action: New Page');
|
||||
showMainWindow();
|
||||
activateMainWindow();
|
||||
applicationMenuSubjects.newPageAction$.next('page');
|
||||
},
|
||||
},
|
||||
@@ -127,7 +125,7 @@ class TrayState implements Disposable {
|
||||
icon: icons.edgeless,
|
||||
click: () => {
|
||||
logger.info('User action: New Edgeless');
|
||||
showMainWindow();
|
||||
activateMainWindow();
|
||||
applicationMenuSubjects.newPageAction$.next('edgeless');
|
||||
},
|
||||
},
|
||||
@@ -232,7 +230,7 @@ class TrayState implements Disposable {
|
||||
items.push({
|
||||
label: `Meetings Settings...`,
|
||||
click: () => {
|
||||
showMainWindow();
|
||||
activateMainWindow();
|
||||
applicationMenuSubjects.openInSettingModal$.next({
|
||||
activeTab: 'meetings',
|
||||
});
|
||||
@@ -257,19 +255,13 @@ class TrayState implements Disposable {
|
||||
label: 'Open AFFiNE',
|
||||
click: () => {
|
||||
logger.info('User action: Open AFFiNE');
|
||||
getMainWindow()
|
||||
.then(w => {
|
||||
w.show();
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Failed to open AFFiNE:', err);
|
||||
});
|
||||
activateMainWindow();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Menubar settings...',
|
||||
click: () => {
|
||||
showMainWindow();
|
||||
activateMainWindow();
|
||||
applicationMenuSubjects.openInSettingModal$.next({
|
||||
activeTab: 'appearance',
|
||||
scrollAnchor: 'menubar',
|
||||
@@ -279,7 +271,7 @@ class TrayState implements Disposable {
|
||||
{
|
||||
label: `About ${app.getName()}`,
|
||||
click: () => {
|
||||
showMainWindow();
|
||||
activateMainWindow();
|
||||
applicationMenuSubjects.openInSettingModal$.next({
|
||||
activeTab: 'about',
|
||||
});
|
||||
@@ -329,7 +321,7 @@ class TrayState implements Disposable {
|
||||
TraySettingsState.value.enabled &&
|
||||
TraySettingsState.value.openOnLeftClick
|
||||
) {
|
||||
showMainWindow();
|
||||
activateMainWindow();
|
||||
} else {
|
||||
this.tray?.popUpContextMenu();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import type { NamespaceHandlers } from '../type';
|
||||
import {
|
||||
activateView,
|
||||
addTab,
|
||||
closeMainWindowToBackground,
|
||||
closeTab,
|
||||
ensureTabLoaded,
|
||||
getMainWindow,
|
||||
@@ -104,8 +105,7 @@ export const uiHandlers = {
|
||||
TraySettingsState.value.enabled &&
|
||||
TraySettingsState.value.closeToTray
|
||||
) {
|
||||
const window = await getMainWindow();
|
||||
window?.hide();
|
||||
await closeMainWindowToBackground();
|
||||
} else {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { BrowserWindow, nativeTheme } from 'electron';
|
||||
import { app, BrowserWindow, nativeTheme } from 'electron';
|
||||
import electronWindowState from 'electron-window-state';
|
||||
import { BehaviorSubject, map, shareReplay } from 'rxjs';
|
||||
|
||||
@@ -17,6 +17,8 @@ import { buildWebPreferences } from '../web-preferences';
|
||||
|
||||
const IS_DEV: boolean =
|
||||
process.env.NODE_ENV === 'development' && !process.env.CI;
|
||||
const DOCK_VISIBILITY_THROTTLE_MS = 1100;
|
||||
const FULL_SCREEN_EXIT_TIMEOUT_MS = 2000;
|
||||
|
||||
const TraySettingsState = {
|
||||
$: globalStateStorage.watch<MenubarStateSchema>(MenubarStateKey).pipe(
|
||||
@@ -44,7 +46,26 @@ export class MainWindowManager {
|
||||
mainWindowReady: Promise<BrowserWindow> | undefined;
|
||||
mainWindow$ = new BehaviorSubject<BrowserWindow | undefined>(undefined);
|
||||
|
||||
private backgroundRequested = false;
|
||||
private hiddenMacWindow: BrowserWindow | undefined;
|
||||
private lastDockShowAt = 0;
|
||||
private pendingDockHide: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
private constructor() {
|
||||
const traySettingsSubscription = TraySettingsState.$.subscribe(state => {
|
||||
if (!state.enabled || !state.closeToTray) {
|
||||
void this.ensureDockVisible().catch(err => {
|
||||
logger.error('Failed to restore Dock visibility:', err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
beforeAppQuit(() => {
|
||||
traySettingsSubscription.unsubscribe();
|
||||
this.cancelPendingDockHide();
|
||||
this.cleanupWindows();
|
||||
});
|
||||
}
|
||||
|
||||
get mainWindow() {
|
||||
return this.mainWindow$.value;
|
||||
@@ -66,6 +87,7 @@ export class MainWindowManager {
|
||||
}
|
||||
|
||||
private cleanupWindows() {
|
||||
this.cancelPendingDockHide();
|
||||
closeAllWindows();
|
||||
this.mainWindowReady = undefined;
|
||||
this.mainWindow$.next(undefined);
|
||||
@@ -73,6 +95,81 @@ export class MainWindowManager {
|
||||
this.hiddenMacWindow = undefined;
|
||||
}
|
||||
|
||||
private cancelPendingDockHide() {
|
||||
if (this.pendingDockHide) {
|
||||
clearTimeout(this.pendingDockHide);
|
||||
this.pendingDockHide = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private shouldHideDock() {
|
||||
const settings = TraySettingsState.value;
|
||||
return (
|
||||
isMacOS() &&
|
||||
this.backgroundRequested &&
|
||||
settings.enabled &&
|
||||
settings.closeToTray
|
||||
);
|
||||
}
|
||||
|
||||
private async showDock() {
|
||||
this.cancelPendingDockHide();
|
||||
if (app.isReady() && app.dock && !app.dock.isVisible()) {
|
||||
await app.dock.show();
|
||||
this.lastDockShowAt = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleDockHide(mainWindow: BrowserWindow) {
|
||||
this.cancelPendingDockHide();
|
||||
if (!app.dock || !this.shouldHideDock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hideDock = () => {
|
||||
this.pendingDockHide = undefined;
|
||||
if (
|
||||
this.shouldHideDock() &&
|
||||
!mainWindow.isDestroyed() &&
|
||||
!mainWindow.isVisible()
|
||||
) {
|
||||
app.dock?.hide();
|
||||
}
|
||||
};
|
||||
const delay =
|
||||
DOCK_VISIBILITY_THROTTLE_MS - (Date.now() - this.lastDockShowAt);
|
||||
if (delay > 0) {
|
||||
this.pendingDockHide = setTimeout(hideDock, delay);
|
||||
} else {
|
||||
hideDock();
|
||||
}
|
||||
}
|
||||
|
||||
private async hideMainWindow(mainWindow: BrowserWindow) {
|
||||
this.backgroundRequested = true;
|
||||
this.cancelPendingDockHide();
|
||||
|
||||
if (mainWindow.isFullScreen()) {
|
||||
await new Promise<void>(resolve => {
|
||||
const done = () => {
|
||||
clearTimeout(timeout);
|
||||
mainWindow.removeListener('leave-full-screen', done);
|
||||
mainWindow.removeListener('closed', done);
|
||||
resolve();
|
||||
};
|
||||
const timeout = setTimeout(done, FULL_SCREEN_EXIT_TIMEOUT_MS);
|
||||
mainWindow.once('leave-full-screen', done);
|
||||
mainWindow.once('closed', done);
|
||||
mainWindow.setFullScreen(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.backgroundRequested && !mainWindow.isDestroyed()) {
|
||||
mainWindow.hide();
|
||||
this.scheduleDockHide(mainWindow);
|
||||
}
|
||||
}
|
||||
|
||||
private async createMainWindow() {
|
||||
logger.info('create window');
|
||||
const mainWindowState = electronWindowState({
|
||||
@@ -132,10 +229,6 @@ export class MainWindowManager {
|
||||
uiSubjects.onFullScreen$.next(mainWindow.isFullScreen());
|
||||
});
|
||||
|
||||
beforeAppQuit(() => {
|
||||
this.cleanupWindows();
|
||||
});
|
||||
|
||||
mainWindow.on('close', e => {
|
||||
// TODO(@pengx17): gracefully close the app, for example, ask user to save unsaved changes
|
||||
e.preventDefault();
|
||||
@@ -151,25 +244,9 @@ export class MainWindowManager {
|
||||
this.mainWindow$.next(undefined);
|
||||
}
|
||||
} else {
|
||||
// hide window on macOS
|
||||
// application quit will be handled by closing the hidden window
|
||||
//
|
||||
// explanation:
|
||||
// - closing the top window (by clicking close button or CMD-w)
|
||||
// - will be captured in "close" event here
|
||||
// - hiding the app to make the app open faster when user click the app icon
|
||||
// - quit the app by "cmd+q" or right click on the dock icon and select "quit"
|
||||
// - all browser windows will capture the "close" event
|
||||
// - the hidden window will close all windows
|
||||
// - "window-all-closed" event will be emitted and eventually quit the app
|
||||
if (mainWindow.isFullScreen()) {
|
||||
mainWindow.once('leave-full-screen', () => {
|
||||
mainWindow.hide();
|
||||
});
|
||||
mainWindow.setFullScreen(false);
|
||||
} else {
|
||||
mainWindow.hide();
|
||||
}
|
||||
void this.hideMainWindow(mainWindow).catch(err => {
|
||||
logger.error('Failed to hide main window:', err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -230,18 +307,42 @@ export class MainWindowManager {
|
||||
|
||||
if (IS_DEV) {
|
||||
// do not gain focus in dev mode
|
||||
this.backgroundRequested = false;
|
||||
await this.showDock();
|
||||
mainWindow.showInactive();
|
||||
} else if (
|
||||
!TraySettingsState.value.enabled ||
|
||||
!TraySettingsState.value.startMinimized
|
||||
) {
|
||||
mainWindow.show();
|
||||
await this.showMainWindow();
|
||||
}
|
||||
|
||||
this.preventMacAppQuit();
|
||||
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
async showMainWindow() {
|
||||
this.backgroundRequested = false;
|
||||
const mainWindow = await this.ensureMainWindow();
|
||||
await this.showDock();
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
}
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
async closeMainWindowToBackground() {
|
||||
await this.hideMainWindow(await this.ensureMainWindow());
|
||||
}
|
||||
|
||||
async ensureDockVisible() {
|
||||
if (!this.shouldHideDock()) {
|
||||
await this.showDock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function initAndShowMainWindow() {
|
||||
@@ -253,12 +354,15 @@ export async function getMainWindow() {
|
||||
}
|
||||
|
||||
export async function showMainWindow() {
|
||||
const window = await getMainWindow();
|
||||
if (!window) return;
|
||||
if (window.isMinimized()) {
|
||||
window.restore();
|
||||
}
|
||||
window.focus();
|
||||
return MainWindowManager.instance.showMainWindow();
|
||||
}
|
||||
|
||||
export async function closeMainWindowToBackground() {
|
||||
return MainWindowManager.instance.closeMainWindowToBackground();
|
||||
}
|
||||
|
||||
export async function ensureDockVisible() {
|
||||
return MainWindowManager.instance.ensureDockVisible();
|
||||
}
|
||||
|
||||
const getWindowAdditionalArguments = async () => {
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { join } from 'node:path';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
type BrowserWindowConstructorOptions,
|
||||
} from 'electron';
|
||||
import { BrowserWindow, type BrowserWindowConstructorOptions } from 'electron';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { popupViewUrl } from '../../shared/internal-origin';
|
||||
import { logger } from '../logger';
|
||||
import type { MainEventRegister, NamespaceHandlers } from '../type';
|
||||
import { buildWebPreferences } from '../web-preferences';
|
||||
import { ensureDockVisible } from './main-window';
|
||||
import { getCurrentDisplay } from './utils';
|
||||
|
||||
type PopupWindowType = 'notification' | 'recording';
|
||||
@@ -97,8 +94,7 @@ abstract class PopupWindow {
|
||||
}),
|
||||
});
|
||||
|
||||
// it seems that the dock will disappear when popup windows are shown
|
||||
await app.dock?.show();
|
||||
await ensureDockVisible();
|
||||
|
||||
// required to make the window transparent
|
||||
browserWindow.setBackgroundColor('#00000000');
|
||||
|
||||
@@ -8,9 +8,7 @@ const ensureDirSync = vi.fn();
|
||||
const resolveExistingPathInBase = vi.fn(
|
||||
async (_base: string, filepath: string) => filepath
|
||||
);
|
||||
const getMainWindow = vi.fn(async () => ({
|
||||
show: vi.fn(),
|
||||
}));
|
||||
const showMainWindow = vi.fn(async () => undefined);
|
||||
|
||||
const storageState = new Map<string, unknown>();
|
||||
const watchSubjects = new Map<string, BehaviorSubject<unknown>>();
|
||||
@@ -103,7 +101,7 @@ beforeEach(() => {
|
||||
}));
|
||||
|
||||
vi.doMock('../../src/main/windows-manager', () => ({
|
||||
getMainWindow,
|
||||
showMainWindow,
|
||||
}));
|
||||
|
||||
vi.doMock('../../src/main/windows-manager/popup', () => ({
|
||||
@@ -245,6 +243,7 @@ describe('recording feature', () => {
|
||||
await stopPromise;
|
||||
subscription.unsubscribe();
|
||||
|
||||
expect(showMainWindow).toHaveBeenCalledOnce();
|
||||
expect(getCurrentRecordingStatus()).toMatchObject({
|
||||
id: started!.id,
|
||||
status: 'pending_import',
|
||||
|
||||
+52
-46
@@ -80,43 +80,47 @@ const MenubarSetting = () => {
|
||||
/>
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
{traySetting.enabled && !environment.isMacOs ? (
|
||||
{traySetting.enabled ? (
|
||||
<SettingWrapper
|
||||
id="windowBehavior"
|
||||
title={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.title'
|
||||
]()}
|
||||
>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.toggle'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.description'
|
||||
]()}
|
||||
>
|
||||
<Switch
|
||||
checked={traySetting.openOnLeftClick}
|
||||
onChange={checked =>
|
||||
traySettingService.setOpenOnLeftClick(checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.toggle'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.description'
|
||||
]()}
|
||||
>
|
||||
<Switch
|
||||
checked={traySetting.minimizeToTray}
|
||||
onChange={checked =>
|
||||
traySettingService.setMinimizeToTray(checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
{!environment.isMacOs ? (
|
||||
<>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.toggle'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.description'
|
||||
]()}
|
||||
>
|
||||
<Switch
|
||||
checked={traySetting.openOnLeftClick}
|
||||
onChange={checked =>
|
||||
traySettingService.setOpenOnLeftClick(checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.toggle'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.description'
|
||||
]()}
|
||||
>
|
||||
<Switch
|
||||
checked={traySetting.minimizeToTray}
|
||||
onChange={checked =>
|
||||
traySettingService.setMinimizeToTray(checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
) : null}
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.toggle'
|
||||
@@ -130,21 +134,23 @@ const MenubarSetting = () => {
|
||||
onChange={checked => traySettingService.setCloseToTray(checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.toggle'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.description'
|
||||
]()}
|
||||
>
|
||||
<Switch
|
||||
checked={traySetting.startMinimized}
|
||||
onChange={checked =>
|
||||
traySettingService.setStartMinimized(checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
{!environment.isMacOs ? (
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.toggle'
|
||||
]()}
|
||||
desc={t[
|
||||
'com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.description'
|
||||
]()}
|
||||
>
|
||||
<Switch
|
||||
checked={traySetting.startMinimized}
|
||||
onChange={checked =>
|
||||
traySettingService.setStartMinimized(checked)
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
) : null}
|
||||
</SettingWrapper>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
"com.affine.appearanceSettings.menubar.title": "菜单栏",
|
||||
"com.affine.appearanceSettings.menubar.toggle": "启用菜单栏应用",
|
||||
"com.affine.appearanceSettings.menubar.description": "在托盘中显示菜单栏应用程序,以便快速访问 AFFiNE 或会议记录。",
|
||||
"com.affine.appearanceSettings.menubar.windowBehavior.title": "Windows行为",
|
||||
"com.affine.appearanceSettings.menubar.windowBehavior.title": "窗口行为",
|
||||
"com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.toggle": "点击托盘图标时启动",
|
||||
"com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.description": "点击AFFiNE的系统托盘图标时启动AFFiNE。",
|
||||
"com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.toggle": "最小化到托盘",
|
||||
|
||||
Reference in New Issue
Block a user