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 { uiSubjects } from './ui';
|
||||||
import {
|
import {
|
||||||
addTabWithUrl,
|
addTabWithUrl,
|
||||||
getMainWindow,
|
|
||||||
loadUrlInActiveTab,
|
loadUrlInActiveTab,
|
||||||
openUrlInHiddenWindow,
|
openUrlInHiddenWindow,
|
||||||
showMainWindow,
|
showMainWindow,
|
||||||
@@ -74,13 +73,8 @@ export function setupDeepLink(app: App) {
|
|||||||
|
|
||||||
// on windows & linux, we need to listen for the second-instance event
|
// on windows & linux, we need to listen for the second-instance event
|
||||||
app.on('second-instance', (event, commandLine) => {
|
app.on('second-instance', (event, commandLine) => {
|
||||||
getMainWindow()
|
showMainWindow()
|
||||||
.then(window => {
|
.then(() => {
|
||||||
if (!window) {
|
|
||||||
logger.error('main window is not ready');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.show();
|
|
||||||
const url = commandLine.pop();
|
const url = commandLine.pop();
|
||||||
if (url?.startsWith(`${protocol}://`)) {
|
if (url?.startsWith(`${protocol}://`)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -149,11 +143,12 @@ async function handleAffineUrl(url: string) {
|
|||||||
? await openUrlInHiddenWindow(urlObj)
|
? await openUrlInHiddenWindow(urlObj)
|
||||||
: await loadUrlInActiveTab(url);
|
: await loadUrlInActiveTab(url);
|
||||||
|
|
||||||
const main = await getMainWindow();
|
if (hiddenWindow) {
|
||||||
if (main && hiddenWindow) {
|
|
||||||
// when hidden window closed, the main window will be hidden somehow
|
// when hidden window closed, the main window will be hidden somehow
|
||||||
hiddenWindow.on('close', () => {
|
hiddenWindow.on('close', () => {
|
||||||
main.show();
|
void showMainWindow().catch(e => {
|
||||||
|
logger.error('Failed to restore main window:', e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import {
|
|||||||
MeetingSettingsSchema,
|
MeetingSettingsSchema,
|
||||||
} from '../shared-state-schema';
|
} from '../shared-state-schema';
|
||||||
import { globalStateStorage } from '../shared-storage/storage';
|
import { globalStateStorage } from '../shared-storage/storage';
|
||||||
import { getMainWindow } from '../windows-manager';
|
import { showMainWindow } from '../windows-manager';
|
||||||
import { popupManager } from '../windows-manager/popup';
|
import { popupManager } from '../windows-manager/popup';
|
||||||
import { isAppNameAllowed } from './allow-list';
|
import { isAppNameAllowed } from './allow-list';
|
||||||
import { RecordingCoordinator } from './coordinator';
|
import { RecordingCoordinator } from './coordinator';
|
||||||
@@ -512,13 +512,7 @@ export async function startRecording(
|
|||||||
export async function stopRecording(id: number) {
|
export async function stopRecording(id: number) {
|
||||||
const job = await recordingCoordinator.stop(id);
|
const job = await recordingCoordinator.stop(id);
|
||||||
if (job?.phase === 'recorded') {
|
if (job?.phase === 'recorded') {
|
||||||
void getMainWindow()
|
void showMainWindow().catch(err => {
|
||||||
.then(mainWindow => {
|
|
||||||
if (mainWindow) {
|
|
||||||
mainWindow.show();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('failed to bring up the window', err);
|
logger.error('failed to bring up the window', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
} from '../recording/feature';
|
} from '../recording/feature';
|
||||||
import { MenubarStateKey, MenubarStateSchema } from '../shared-state-schema';
|
import { MenubarStateKey, MenubarStateSchema } from '../shared-state-schema';
|
||||||
import { globalStateStorage } from '../shared-storage/storage';
|
import { globalStateStorage } from '../shared-storage/storage';
|
||||||
import { getMainWindow } from '../windows-manager';
|
import { showMainWindow } from '../windows-manager';
|
||||||
import { icons } from './icons';
|
import { icons } from './icons';
|
||||||
export interface TrayMenuConfigItem {
|
export interface TrayMenuConfigItem {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -43,12 +43,10 @@ interface TrayMenuProvider {
|
|||||||
getConfig(): TrayMenuConfig;
|
getConfig(): TrayMenuConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMainWindow() {
|
function activateMainWindow() {
|
||||||
getMainWindow()
|
void showMainWindow().catch(err => {
|
||||||
.then(w => {
|
logger.error('Failed to show main window:', err);
|
||||||
w.show();
|
});
|
||||||
})
|
|
||||||
.catch(err => logger.error('Failed to show main window:', err));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMenuConfig(config: TrayMenuConfig): MenuItemConstructorOptions[] {
|
function buildMenuConfig(config: TrayMenuConfig): MenuItemConstructorOptions[] {
|
||||||
@@ -109,7 +107,7 @@ class TrayState implements Disposable {
|
|||||||
icon: icons.journal,
|
icon: icons.journal,
|
||||||
click: () => {
|
click: () => {
|
||||||
logger.info('User action: Open Journal');
|
logger.info('User action: Open Journal');
|
||||||
showMainWindow();
|
activateMainWindow();
|
||||||
applicationMenuSubjects.openJournal$.next();
|
applicationMenuSubjects.openJournal$.next();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -118,7 +116,7 @@ class TrayState implements Disposable {
|
|||||||
icon: icons.page,
|
icon: icons.page,
|
||||||
click: () => {
|
click: () => {
|
||||||
logger.info('User action: New Page');
|
logger.info('User action: New Page');
|
||||||
showMainWindow();
|
activateMainWindow();
|
||||||
applicationMenuSubjects.newPageAction$.next('page');
|
applicationMenuSubjects.newPageAction$.next('page');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -127,7 +125,7 @@ class TrayState implements Disposable {
|
|||||||
icon: icons.edgeless,
|
icon: icons.edgeless,
|
||||||
click: () => {
|
click: () => {
|
||||||
logger.info('User action: New Edgeless');
|
logger.info('User action: New Edgeless');
|
||||||
showMainWindow();
|
activateMainWindow();
|
||||||
applicationMenuSubjects.newPageAction$.next('edgeless');
|
applicationMenuSubjects.newPageAction$.next('edgeless');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -232,7 +230,7 @@ class TrayState implements Disposable {
|
|||||||
items.push({
|
items.push({
|
||||||
label: `Meetings Settings...`,
|
label: `Meetings Settings...`,
|
||||||
click: () => {
|
click: () => {
|
||||||
showMainWindow();
|
activateMainWindow();
|
||||||
applicationMenuSubjects.openInSettingModal$.next({
|
applicationMenuSubjects.openInSettingModal$.next({
|
||||||
activeTab: 'meetings',
|
activeTab: 'meetings',
|
||||||
});
|
});
|
||||||
@@ -257,19 +255,13 @@ class TrayState implements Disposable {
|
|||||||
label: 'Open AFFiNE',
|
label: 'Open AFFiNE',
|
||||||
click: () => {
|
click: () => {
|
||||||
logger.info('User action: Open AFFiNE');
|
logger.info('User action: Open AFFiNE');
|
||||||
getMainWindow()
|
activateMainWindow();
|
||||||
.then(w => {
|
|
||||||
w.show();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Failed to open AFFiNE:', err);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Menubar settings...',
|
label: 'Menubar settings...',
|
||||||
click: () => {
|
click: () => {
|
||||||
showMainWindow();
|
activateMainWindow();
|
||||||
applicationMenuSubjects.openInSettingModal$.next({
|
applicationMenuSubjects.openInSettingModal$.next({
|
||||||
activeTab: 'appearance',
|
activeTab: 'appearance',
|
||||||
scrollAnchor: 'menubar',
|
scrollAnchor: 'menubar',
|
||||||
@@ -279,7 +271,7 @@ class TrayState implements Disposable {
|
|||||||
{
|
{
|
||||||
label: `About ${app.getName()}`,
|
label: `About ${app.getName()}`,
|
||||||
click: () => {
|
click: () => {
|
||||||
showMainWindow();
|
activateMainWindow();
|
||||||
applicationMenuSubjects.openInSettingModal$.next({
|
applicationMenuSubjects.openInSettingModal$.next({
|
||||||
activeTab: 'about',
|
activeTab: 'about',
|
||||||
});
|
});
|
||||||
@@ -329,7 +321,7 @@ class TrayState implements Disposable {
|
|||||||
TraySettingsState.value.enabled &&
|
TraySettingsState.value.enabled &&
|
||||||
TraySettingsState.value.openOnLeftClick
|
TraySettingsState.value.openOnLeftClick
|
||||||
) {
|
) {
|
||||||
showMainWindow();
|
activateMainWindow();
|
||||||
} else {
|
} else {
|
||||||
this.tray?.popUpContextMenu();
|
this.tray?.popUpContextMenu();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type { NamespaceHandlers } from '../type';
|
|||||||
import {
|
import {
|
||||||
activateView,
|
activateView,
|
||||||
addTab,
|
addTab,
|
||||||
|
closeMainWindowToBackground,
|
||||||
closeTab,
|
closeTab,
|
||||||
ensureTabLoaded,
|
ensureTabLoaded,
|
||||||
getMainWindow,
|
getMainWindow,
|
||||||
@@ -104,8 +105,7 @@ export const uiHandlers = {
|
|||||||
TraySettingsState.value.enabled &&
|
TraySettingsState.value.enabled &&
|
||||||
TraySettingsState.value.closeToTray
|
TraySettingsState.value.closeToTray
|
||||||
) {
|
) {
|
||||||
const window = await getMainWindow();
|
await closeMainWindowToBackground();
|
||||||
window?.hide();
|
|
||||||
} else {
|
} else {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
||||||
import { BrowserWindow, nativeTheme } from 'electron';
|
import { app, BrowserWindow, nativeTheme } from 'electron';
|
||||||
import electronWindowState from 'electron-window-state';
|
import electronWindowState from 'electron-window-state';
|
||||||
import { BehaviorSubject, map, shareReplay } from 'rxjs';
|
import { BehaviorSubject, map, shareReplay } from 'rxjs';
|
||||||
|
|
||||||
@@ -17,6 +17,8 @@ import { buildWebPreferences } from '../web-preferences';
|
|||||||
|
|
||||||
const IS_DEV: boolean =
|
const IS_DEV: boolean =
|
||||||
process.env.NODE_ENV === 'development' && !process.env.CI;
|
process.env.NODE_ENV === 'development' && !process.env.CI;
|
||||||
|
const DOCK_VISIBILITY_THROTTLE_MS = 1100;
|
||||||
|
const FULL_SCREEN_EXIT_TIMEOUT_MS = 2000;
|
||||||
|
|
||||||
const TraySettingsState = {
|
const TraySettingsState = {
|
||||||
$: globalStateStorage.watch<MenubarStateSchema>(MenubarStateKey).pipe(
|
$: globalStateStorage.watch<MenubarStateSchema>(MenubarStateKey).pipe(
|
||||||
@@ -44,7 +46,26 @@ export class MainWindowManager {
|
|||||||
mainWindowReady: Promise<BrowserWindow> | undefined;
|
mainWindowReady: Promise<BrowserWindow> | undefined;
|
||||||
mainWindow$ = new BehaviorSubject<BrowserWindow | undefined>(undefined);
|
mainWindow$ = new BehaviorSubject<BrowserWindow | undefined>(undefined);
|
||||||
|
|
||||||
|
private backgroundRequested = false;
|
||||||
private hiddenMacWindow: BrowserWindow | undefined;
|
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() {
|
get mainWindow() {
|
||||||
return this.mainWindow$.value;
|
return this.mainWindow$.value;
|
||||||
@@ -66,6 +87,7 @@ export class MainWindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private cleanupWindows() {
|
private cleanupWindows() {
|
||||||
|
this.cancelPendingDockHide();
|
||||||
closeAllWindows();
|
closeAllWindows();
|
||||||
this.mainWindowReady = undefined;
|
this.mainWindowReady = undefined;
|
||||||
this.mainWindow$.next(undefined);
|
this.mainWindow$.next(undefined);
|
||||||
@@ -73,6 +95,81 @@ export class MainWindowManager {
|
|||||||
this.hiddenMacWindow = undefined;
|
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() {
|
private async createMainWindow() {
|
||||||
logger.info('create window');
|
logger.info('create window');
|
||||||
const mainWindowState = electronWindowState({
|
const mainWindowState = electronWindowState({
|
||||||
@@ -132,10 +229,6 @@ export class MainWindowManager {
|
|||||||
uiSubjects.onFullScreen$.next(mainWindow.isFullScreen());
|
uiSubjects.onFullScreen$.next(mainWindow.isFullScreen());
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAppQuit(() => {
|
|
||||||
this.cleanupWindows();
|
|
||||||
});
|
|
||||||
|
|
||||||
mainWindow.on('close', e => {
|
mainWindow.on('close', e => {
|
||||||
// TODO(@pengx17): gracefully close the app, for example, ask user to save unsaved changes
|
// TODO(@pengx17): gracefully close the app, for example, ask user to save unsaved changes
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -151,25 +244,9 @@ export class MainWindowManager {
|
|||||||
this.mainWindow$.next(undefined);
|
this.mainWindow$.next(undefined);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// hide window on macOS
|
void this.hideMainWindow(mainWindow).catch(err => {
|
||||||
// application quit will be handled by closing the hidden window
|
logger.error('Failed to hide main window:', err);
|
||||||
//
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -230,18 +307,42 @@ export class MainWindowManager {
|
|||||||
|
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
// do not gain focus in dev mode
|
// do not gain focus in dev mode
|
||||||
|
this.backgroundRequested = false;
|
||||||
|
await this.showDock();
|
||||||
mainWindow.showInactive();
|
mainWindow.showInactive();
|
||||||
} else if (
|
} else if (
|
||||||
!TraySettingsState.value.enabled ||
|
!TraySettingsState.value.enabled ||
|
||||||
!TraySettingsState.value.startMinimized
|
!TraySettingsState.value.startMinimized
|
||||||
) {
|
) {
|
||||||
mainWindow.show();
|
await this.showMainWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.preventMacAppQuit();
|
this.preventMacAppQuit();
|
||||||
|
|
||||||
return mainWindow;
|
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() {
|
export async function initAndShowMainWindow() {
|
||||||
@@ -253,12 +354,15 @@ export async function getMainWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function showMainWindow() {
|
export async function showMainWindow() {
|
||||||
const window = await getMainWindow();
|
return MainWindowManager.instance.showMainWindow();
|
||||||
if (!window) return;
|
}
|
||||||
if (window.isMinimized()) {
|
|
||||||
window.restore();
|
export async function closeMainWindowToBackground() {
|
||||||
}
|
return MainWindowManager.instance.closeMainWindowToBackground();
|
||||||
window.focus();
|
}
|
||||||
|
|
||||||
|
export async function ensureDockVisible() {
|
||||||
|
return MainWindowManager.instance.ensureDockVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
const getWindowAdditionalArguments = async () => {
|
const getWindowAdditionalArguments = async () => {
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
|
|
||||||
import {
|
import { BrowserWindow, type BrowserWindowConstructorOptions } from 'electron';
|
||||||
app,
|
|
||||||
BrowserWindow,
|
|
||||||
type BrowserWindowConstructorOptions,
|
|
||||||
} from 'electron';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { popupViewUrl } from '../../shared/internal-origin';
|
import { popupViewUrl } from '../../shared/internal-origin';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import type { MainEventRegister, NamespaceHandlers } from '../type';
|
import type { MainEventRegister, NamespaceHandlers } from '../type';
|
||||||
import { buildWebPreferences } from '../web-preferences';
|
import { buildWebPreferences } from '../web-preferences';
|
||||||
|
import { ensureDockVisible } from './main-window';
|
||||||
import { getCurrentDisplay } from './utils';
|
import { getCurrentDisplay } from './utils';
|
||||||
|
|
||||||
type PopupWindowType = 'notification' | 'recording';
|
type PopupWindowType = 'notification' | 'recording';
|
||||||
@@ -97,8 +94,7 @@ abstract class PopupWindow {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// it seems that the dock will disappear when popup windows are shown
|
await ensureDockVisible();
|
||||||
await app.dock?.show();
|
|
||||||
|
|
||||||
// required to make the window transparent
|
// required to make the window transparent
|
||||||
browserWindow.setBackgroundColor('#00000000');
|
browserWindow.setBackgroundColor('#00000000');
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ const ensureDirSync = vi.fn();
|
|||||||
const resolveExistingPathInBase = vi.fn(
|
const resolveExistingPathInBase = vi.fn(
|
||||||
async (_base: string, filepath: string) => filepath
|
async (_base: string, filepath: string) => filepath
|
||||||
);
|
);
|
||||||
const getMainWindow = vi.fn(async () => ({
|
const showMainWindow = vi.fn(async () => undefined);
|
||||||
show: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const storageState = new Map<string, unknown>();
|
const storageState = new Map<string, unknown>();
|
||||||
const watchSubjects = new Map<string, BehaviorSubject<unknown>>();
|
const watchSubjects = new Map<string, BehaviorSubject<unknown>>();
|
||||||
@@ -103,7 +101,7 @@ beforeEach(() => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.doMock('../../src/main/windows-manager', () => ({
|
vi.doMock('../../src/main/windows-manager', () => ({
|
||||||
getMainWindow,
|
showMainWindow,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.doMock('../../src/main/windows-manager/popup', () => ({
|
vi.doMock('../../src/main/windows-manager/popup', () => ({
|
||||||
@@ -245,6 +243,7 @@ describe('recording feature', () => {
|
|||||||
await stopPromise;
|
await stopPromise;
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
|
|
||||||
|
expect(showMainWindow).toHaveBeenCalledOnce();
|
||||||
expect(getCurrentRecordingStatus()).toMatchObject({
|
expect(getCurrentRecordingStatus()).toMatchObject({
|
||||||
id: started!.id,
|
id: started!.id,
|
||||||
status: 'pending_import',
|
status: 'pending_import',
|
||||||
|
|||||||
@@ -80,13 +80,15 @@ const MenubarSetting = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingWrapper>
|
</SettingWrapper>
|
||||||
{traySetting.enabled && !environment.isMacOs ? (
|
{traySetting.enabled ? (
|
||||||
<SettingWrapper
|
<SettingWrapper
|
||||||
id="windowBehavior"
|
id="windowBehavior"
|
||||||
title={t[
|
title={t[
|
||||||
'com.affine.appearanceSettings.menubar.windowBehavior.title'
|
'com.affine.appearanceSettings.menubar.windowBehavior.title'
|
||||||
]()}
|
]()}
|
||||||
>
|
>
|
||||||
|
{!environment.isMacOs ? (
|
||||||
|
<>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t[
|
name={t[
|
||||||
'com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.toggle'
|
'com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.toggle'
|
||||||
@@ -117,6 +119,8 @@ const MenubarSetting = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t[
|
name={t[
|
||||||
'com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.toggle'
|
'com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.toggle'
|
||||||
@@ -130,6 +134,7 @@ const MenubarSetting = () => {
|
|||||||
onChange={checked => traySettingService.setCloseToTray(checked)}
|
onChange={checked => traySettingService.setCloseToTray(checked)}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
{!environment.isMacOs ? (
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={t[
|
name={t[
|
||||||
'com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.toggle'
|
'com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.toggle'
|
||||||
@@ -145,6 +150,7 @@ const MenubarSetting = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
) : null}
|
||||||
</SettingWrapper>
|
</SettingWrapper>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -246,7 +246,7 @@
|
|||||||
"com.affine.appearanceSettings.menubar.title": "菜单栏",
|
"com.affine.appearanceSettings.menubar.title": "菜单栏",
|
||||||
"com.affine.appearanceSettings.menubar.toggle": "启用菜单栏应用",
|
"com.affine.appearanceSettings.menubar.toggle": "启用菜单栏应用",
|
||||||
"com.affine.appearanceSettings.menubar.description": "在托盘中显示菜单栏应用程序,以便快速访问 AFFiNE 或会议记录。",
|
"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.toggle": "点击托盘图标时启动",
|
||||||
"com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.description": "点击AFFiNE的系统托盘图标时启动AFFiNE。",
|
"com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.description": "点击AFFiNE的系统托盘图标时启动AFFiNE。",
|
||||||
"com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.toggle": "最小化到托盘",
|
"com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.toggle": "最小化到托盘",
|
||||||
|
|||||||
Reference in New Issue
Block a user