mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
[Fix] Electron plugin window management (#9571)
* feat(desktop-window): improve plugin marketplace window lifecycle management - Format constructor parameters across multiple lines for better readability - Add close event listener to properly destroy window on close - Implement show() override to restore minimized windows and focus on display - Implement close() override with destruction state checks - Add isDestroyed() helper method to safely check window destruction state - Prevent operations on destroyed windows to avoid runtime errors * feat(desktop-window): enhance splash screen lifecycle management - Add close event handler to properly cleanup splash screen on window close - Add isDestroyed() method to safely check window destruction state - Unregister splash screen from WindowManager when window closes - Sort import statements alphabetically for consistency - Reformat code indentation from spaces to tabs for consistency with codebase - Improve JSDoc comments for better documentation clarity - Ensure proper resource cleanup and prevent memory leaks during splash screen lifecycle * feat(desktop-window): enhance always-on window lifecycle and formatting - Reorganize imports alphabetically for better maintainability - Add readonly modifier to manager property for immutability - Improve ternary operator formatting for better readability - Add close event listener to properly destroy window on close - Add isDestroyed() helper method to check window destruction state - Add override keyword to show() and hide() methods for clarity - Add new close() method override with destruction checks - Add destruction checks in show() method to prevent operations on destroyed windows - Improve code consistency and prevent potential memory leaks from unclosed windows * feat(desktop-lib): enhance window lifecycle management and code organization - Remove empty constructor from AppWindowManager class - Add null checks in pluginsWindow and alwaysOnWindow getters to handle destroyed windows - Update window initialization methods to check if window exists and is not destroyed before reusing - Simplify window close event handlers by using 'closed' event instead of 'close' with manual cleanup - Improve initSettingWindow formatting for better readability - Refactor initPluginsWindow to consolidate window creation and event handling logic - Refactor initAlwaysOnWindow to improve code clarity and reduce redundant checks - Update return type annotations for pluginsWindow and alwaysOnWindow getters to include null - Consolidate settingShow method signature formatting * feat(desktop-lib): enhance menu visibility and tray icon initialization - Add plugin menu visibility control based on authentication state in desktop-menu - Import AppWindowManager at top of desktop-menu for consistent ordering - Make TrayIcon class properties readonly for immutability - Add PluginEventManager initialization in TrayIcon constructor - Notify plugin event manager on authentication state changes - Use consistent property references (this.config, this.dependencies) throughout TrayIcon - Improve code organization and dependency injection patterns * feat(desktop-lib): enhance settings command window lifecycle and type safety - Add IPathWindow interface import for improved type safety - Replace any type with readonly IPathWindow in constructor parameter - Extract timeTrackerUi from windowPath for cleaner variable access - Refactor window initialization logic to check if window exists and is not destroyed - Consolidate window show operation to execute after initialization or reuse - Improve error handling by using consistent error variable naming - Simplify loadSetting call by using extracted timeTrackerUi variable - Enhance window lifecycle management to prevent duplicate initialization attempts * feat(desktop-lib): refine unauthenticated menu strategy and remove plugins - Remove OpenPluginsCommand import and plugins menu item from unauthenticated menu - Update timer menu item label from 'TIMER_TRACKER.MENU.OPEN_TIMER' to 'LOGIN_PAGE.TITLE' - Change timer menu accelerator from 'CmdOrCtrl+O' to 'CmdOrCtrl+L' - Simplify unauthenticated user menu by removing plugin access option * feat(desktop-timer): refactor window lifecycle - Simplify function parameter type definitions by collapsing multi-line signatures - Refactor always-on window initialization to use direct variable assignment instead of property access - Improve splash screen lifecycle management with closed event handler - Refactor language change callback to use arrow function syntax - Simplify plugins window closing logic with null check instead of optional chaining * feat(desktop-window): improve isDestroyed() null safety checks - Refactor AlwaysOn.isDestroyed() to explicitly check for null browserWindow - Refactor PluginMarketplaceWindow.isDestroyed() to explicitly check for null browserWindow - Replace ternary operator with early return pattern for improved readability - Ensure consistent null handling across window lifecycle implementations * feat(desktop-menu): improve null safety for auth store retrieval - Add null coalescing operator to handle undefined LocalStore.getStore() response - Prevent potential runtime errors when auth store is not initialized - Ensure pluginMenu visibility logic operates on valid object reference * feat(desktop-splash-screen): improve isDestroyed() null safety checks - Change isDestroyed() method from private to public for external access - Add explicit null check for browserWindow before calling isDestroyed() - Return true immediately if browserWindow is not initialized - Improve type safety and prevent potential null reference errors * feat(desktop-lib): improve settings window initialization order - Move initSettingWindow call after onReady listener registration - Ensure IPC listener is attached before window initialization begins - Improve reliability of settings window ready event handling - Maintain proper error handling with listener cleanup on failure * fix: plashScreen correctly calls this.manager.unregister(RegisteredWindow.SPLASH) inside its close event handler to remove the stale reference from the WindowManager. Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: calls unregister(RegisteredWindow.PLUGINS) on close Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: unregister window references on close for AlwaysOn, SplashScreen, and PluginMarketplaceWindow --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
greptile-apps[bot]
parent
5fed02c657
commit
c87354be22
@@ -27,6 +27,7 @@ initSentry();
|
||||
|
||||
remoteMain.initialize();
|
||||
|
||||
import { DesktopSetupConfig } from '@gauzy/contracts';
|
||||
import {
|
||||
AppError,
|
||||
AppMenu,
|
||||
@@ -59,7 +60,6 @@ import {
|
||||
setLaunchPathAndLoad,
|
||||
SplashScreen
|
||||
} from '@gauzy/desktop-window';
|
||||
import { DesktopSetupConfig } from '@gauzy/contracts';
|
||||
import { fork } from 'child_process';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
|
||||
@@ -229,11 +229,7 @@ function initializeAppManager() {
|
||||
appWindowManager.preloadPath = pathWindow.preloadPath;
|
||||
}
|
||||
|
||||
function setGlobalVariable(configs: {
|
||||
isLocalServer?: boolean;
|
||||
serverUrl?: string;
|
||||
port?: string;
|
||||
}) {
|
||||
function setGlobalVariable(configs: { isLocalServer?: boolean; serverUrl?: string; port?: string }) {
|
||||
global.variableGlobal = {
|
||||
API_BASE_URL: getApiBaseUrl(configs || {}),
|
||||
IS_INTEGRATED_DESKTOP: configs?.isLocalServer
|
||||
@@ -314,17 +310,14 @@ async function startServer(setupConfig: DesktopSetupConfig, restart = false) {
|
||||
// Create the tray icon and menu
|
||||
tray = TrayIconFactory.create(environment, pathWindow, path.join(__dirname, 'assets', 'icons', 'tray', 'icon.png'));
|
||||
// Language change
|
||||
TranslateService.onLanguageChange(() => {
|
||||
new AppMenu(timeTrackerWindow, settingsWindow, updaterWindow, knex, pathWindow, null, false);
|
||||
});
|
||||
TranslateService.onLanguageChange(() =>
|
||||
new AppMenu(timeTrackerWindow, settingsWindow, updaterWindow, knex, pathWindow, null, false)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const getApiBaseUrl = (configs: {
|
||||
serverUrl?: string;
|
||||
port?: string;
|
||||
}) => {
|
||||
const getApiBaseUrl = (configs: { serverUrl?: string; port?: string }) => {
|
||||
if (configs.serverUrl) return configs.serverUrl;
|
||||
else {
|
||||
return configs.port ? `http://localhost:${configs.port}` : `http://localhost:${environment.API_DEFAULT_PORT}`;
|
||||
@@ -334,8 +327,10 @@ const getApiBaseUrl = (configs: {
|
||||
async function launchSplashScreen() {
|
||||
try {
|
||||
splashScreen = new SplashScreen(pathWindow.timeTrackerUi);
|
||||
splashScreen.browserWindow.on('closed', () => (splashScreen = null));
|
||||
await splashScreen.loadURL();
|
||||
splashScreen.show();
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new AppError('MAINLOADSPLASH', error);
|
||||
@@ -384,8 +379,8 @@ async function launchWidget(settings: any) {
|
||||
const auth = store.get('auth');
|
||||
|
||||
if (settings?.alwaysOn && auth?.token && !auth?.isLogout) {
|
||||
await appWindowManager.initAlwaysOnWindow(pathWindow.timeTrackerUi);
|
||||
appWindowManager.alwaysOnWindow.show();
|
||||
const alwaysOnWindow = await appWindowManager.initAlwaysOnWindow(pathWindow.timeTrackerUi);
|
||||
alwaysOnWindow.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { LocalStore } from './desktop-store';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import {
|
||||
AlwaysOn,
|
||||
createAboutWindow,
|
||||
createSetupWindow,
|
||||
createImageViewerWindow,
|
||||
createSettingsWindow,
|
||||
createSetupWindow,
|
||||
PluginMarketplaceWindow,
|
||||
AlwaysOn,
|
||||
setLaunchPathAndLoad
|
||||
} from '@gauzy/desktop-window';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { LocalStore } from './desktop-store';
|
||||
import { DesktopUpdater } from './desktop-updater';
|
||||
|
||||
enum WindowName {
|
||||
@@ -34,7 +34,6 @@ export class AppWindowManager {
|
||||
private _preloadPath: string = null;
|
||||
|
||||
private static instance: AppWindowManager;
|
||||
constructor() { };
|
||||
|
||||
get settingWindow(): BrowserWindow {
|
||||
return this._settingWindow;
|
||||
@@ -52,11 +51,19 @@ export class AppWindowManager {
|
||||
return this._imageView;
|
||||
}
|
||||
|
||||
get pluginsWindow(): PluginMarketplaceWindow {
|
||||
get pluginsWindow(): PluginMarketplaceWindow | null {
|
||||
// Return null if window is destroyed
|
||||
if (this._pluginsWindow?.isDestroyed()) {
|
||||
this._pluginsWindow = null;
|
||||
}
|
||||
return this._pluginsWindow;
|
||||
}
|
||||
|
||||
get alwaysOnWindow(): AlwaysOn {
|
||||
get alwaysOnWindow(): AlwaysOn | null {
|
||||
// Return null if window is destroyed
|
||||
if (this._alwaysOnWindow?.isDestroyed()) {
|
||||
this._alwaysOnWindow = null;
|
||||
}
|
||||
return this._alwaysOnWindow;
|
||||
}
|
||||
|
||||
@@ -102,43 +109,48 @@ export class AppWindowManager {
|
||||
}
|
||||
|
||||
async initSettingWindow(filePath: string, preloadPath?: string, launch = false): Promise<BrowserWindow> {
|
||||
if (this._settingWindow) {
|
||||
if (this._settingWindow && !this._settingWindow.isDestroyed()) {
|
||||
return this._settingWindow;
|
||||
}
|
||||
this._settingWindow = await createSettingsWindow(this._settingWindow, filePath, this._preloadPath || preloadPath, false, launch);
|
||||
|
||||
this._settingWindow = await createSettingsWindow(
|
||||
this._settingWindow,
|
||||
filePath,
|
||||
this._preloadPath || preloadPath,
|
||||
false,
|
||||
launch
|
||||
);
|
||||
this.eventCloseWindow(this._settingWindow, WindowName.SETTING);
|
||||
return this._settingWindow;
|
||||
}
|
||||
|
||||
async initPluginsWindow(filePath: string, preloadPath?: string): Promise<PluginMarketplaceWindow> {
|
||||
if (!this._pluginsWindow) {
|
||||
this._pluginsWindow = new PluginMarketplaceWindow(filePath, this._preloadPath || preloadPath);
|
||||
await this._pluginsWindow.loadURL();
|
||||
// If window exists and is not destroyed, return it
|
||||
if (this._pluginsWindow && !this._pluginsWindow.isDestroyed()) {
|
||||
return this._pluginsWindow;
|
||||
}
|
||||
this._pluginsWindow.browserWindow.removeAllListeners('close');
|
||||
this._pluginsWindow.browserWindow.on('close', () => {
|
||||
if (!this._pluginsWindow?.browserWindow?.isDestroyed()) {
|
||||
this._pluginsWindow?.browserWindow?.destroy();
|
||||
}
|
||||
|
||||
this._pluginsWindow = null;
|
||||
});
|
||||
// Create new window instance
|
||||
this._pluginsWindow = new PluginMarketplaceWindow(filePath, this._preloadPath || preloadPath);
|
||||
// Handle window close event to clean up reference
|
||||
this._pluginsWindow.browserWindow.on('closed', () => (this._pluginsWindow = null));
|
||||
// Load the window URL
|
||||
await this._pluginsWindow.loadURL();
|
||||
// Return the new window instance
|
||||
return this._pluginsWindow;
|
||||
}
|
||||
|
||||
async initAlwaysOnWindow(filePath: string): Promise<AlwaysOn> {
|
||||
try {
|
||||
if (!this._alwaysOnWindow || this._alwaysOnWindow?.browserWindow?.isDestroyed()) {
|
||||
this._alwaysOnWindow = new AlwaysOn(filePath);
|
||||
await this._alwaysOnWindow.loadURL();
|
||||
this._alwaysOnWindow.browserWindow.removeAllListeners('close');
|
||||
this._alwaysOnWindow.browserWindow.on('close', () => {
|
||||
if (!this._alwaysOnWindow?.browserWindow?.isDestroyed()) {
|
||||
this._alwaysOnWindow?.browserWindow?.destroy();
|
||||
}
|
||||
this._alwaysOnWindow = null;
|
||||
});
|
||||
if (this._alwaysOnWindow && !this._alwaysOnWindow.isDestroyed()) {
|
||||
return this._alwaysOnWindow;
|
||||
}
|
||||
// Create new AlwaysOn window instance
|
||||
this._alwaysOnWindow = new AlwaysOn(filePath);
|
||||
// Handle window close event to clean up reference
|
||||
this._alwaysOnWindow.browserWindow.on('closed', () => (this._alwaysOnWindow = null));
|
||||
// Load the window URL
|
||||
await this._alwaysOnWindow.loadURL();
|
||||
// Return the new window instance
|
||||
return this._alwaysOnWindow;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize always-on window', error);
|
||||
@@ -175,9 +187,7 @@ export class AppWindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
settingShow(
|
||||
nav: 'goto_top_menu' | 'goto_update'
|
||||
) {
|
||||
settingShow(nav: 'goto_top_menu' | 'goto_update') {
|
||||
const appSetting = LocalStore.getStore('appSetting');
|
||||
const config = LocalStore.getStore('configs');
|
||||
const auth = LocalStore.getStore('auth');
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { DesktopSetupConfig, IActivityWatchEventResult, TimerActionTypeEnum, TimerSyncStateEnum } from '@gauzy/contracts';
|
||||
import {
|
||||
DesktopSetupConfig,
|
||||
IActivityWatchEventResult,
|
||||
TimerActionTypeEnum,
|
||||
TimerSyncStateEnum
|
||||
} from '@gauzy/contracts';
|
||||
import { AkitaStorageEngine, WindowManager, logger as log } from '@gauzy/desktop-core';
|
||||
import { ScreenCaptureNotification } from '@gauzy/desktop-window';
|
||||
import { BrowserWindow, app, desktopCapturer, ipcMain, screen, systemPreferences } from 'electron';
|
||||
@@ -911,9 +916,7 @@ export function ipcTimer(
|
||||
ipcMain.once('setting_window_ready', () => {
|
||||
appWindowManager.settingShow('goto_top_menu');
|
||||
});
|
||||
await appWindowManager.loadSetting(
|
||||
windowPath.timeTrackerUi
|
||||
);
|
||||
await appWindowManager.loadSetting(windowPath.timeTrackerUi);
|
||||
} else {
|
||||
appWindowManager.settingShow('goto_top_menu');
|
||||
}
|
||||
@@ -1151,14 +1154,14 @@ export function ipcTimer(
|
||||
const setting = LocalStore.getStore('appSetting');
|
||||
const auth = LocalStore.getStore('auth');
|
||||
if (setting?.alwaysOn && auth?.employeeId) {
|
||||
await appWindowManager.initAlwaysOnWindow(windowPath.timeTrackerUi);
|
||||
appWindowManager.alwaysOnWindow.show?.();
|
||||
const alwaysOnWindow = await appWindowManager.initAlwaysOnWindow(windowPath.timeTrackerUi);
|
||||
alwaysOnWindow?.show();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('hide_ao', (event, arg) => {
|
||||
if (appWindowManager.alwaysOnWindow) {
|
||||
appWindowManager.alwaysOnWindow.browserWindow?.close?.();
|
||||
appWindowManager.alwaysOnWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { logger } from '@gauzy/desktop-core';
|
||||
import { createAboutWindow } from '@gauzy/desktop-window';
|
||||
import { BrowserWindow, ipcMain, Menu, MenuItemConstructorOptions, shell } from 'electron';
|
||||
import { AppWindowManager } from './app-window-manager';
|
||||
import { LocalStore } from './desktop-store';
|
||||
import { TimerService } from './offline';
|
||||
import { PluginManager } from './plugin-system/data-access/plugin-manager';
|
||||
import { PluginEventManager } from './plugin-system/events/plugin-event.manager';
|
||||
import { TranslateService } from './translation';
|
||||
import { AppWindowManager } from './app-window-manager';
|
||||
|
||||
export class AppMenu {
|
||||
public menu: MenuItemConstructorOptions[] = [];
|
||||
@@ -267,11 +267,14 @@ export class AppMenu {
|
||||
public get pluginMenu(): MenuItemConstructorOptions {
|
||||
// Retrieve submenu items from the plugin manager
|
||||
const pluginSubmenu = this.pluginManager.getMenuPlugins();
|
||||
const { employeeId = null, isLogout = false } = LocalStore.getStore('auth') || {};
|
||||
const visible = !!employeeId && !isLogout;
|
||||
|
||||
// Return the plugin menu structure
|
||||
return {
|
||||
id: 'plugin-menu',
|
||||
label: TranslateService.instant('TIMER_TRACKER.SETTINGS.PLUGINS'),
|
||||
visible,
|
||||
submenu: [
|
||||
{
|
||||
label: TranslateService.instant('TIMER_TRACKER.MENU.INSTALL_PLUGIN'),
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { AppWindowManager } from '../../app-window-manager';
|
||||
import { IPathWindow } from '../../interfaces/i-path-window';
|
||||
import { MenuCommand } from './menu-command';
|
||||
|
||||
export class OpenSettingsCommand extends MenuCommand {
|
||||
constructor(private windowPath: any) {
|
||||
constructor(private readonly windowPath: IPathWindow) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async execute(): Promise<void> {
|
||||
const appWindowManager = AppWindowManager.getInstance();
|
||||
if (!appWindowManager.settingWindow) {
|
||||
const { timeTrackerUi } = this.windowPath;
|
||||
|
||||
if (appWindowManager.settingWindow && !appWindowManager.settingWindow.isDestroyed()) {
|
||||
appWindowManager.settingShow('goto_top_menu');
|
||||
} else {
|
||||
const onReady = () => appWindowManager.settingShow('goto_top_menu');
|
||||
ipcMain.once('setting_window_ready', onReady);
|
||||
try {
|
||||
await appWindowManager.initSettingWindow(this.windowPath.timeTrackerUi);
|
||||
await appWindowManager.loadSetting(
|
||||
this.windowPath.timeTrackerUi,
|
||||
);
|
||||
} catch (err) {
|
||||
await appWindowManager.initSettingWindow(timeTrackerUi);
|
||||
await appWindowManager.loadSetting(timeTrackerUi);
|
||||
} catch (error) {
|
||||
ipcMain.removeListener('setting_window_ready', onReady);
|
||||
throw err;
|
||||
throw error;
|
||||
}
|
||||
appWindowManager.settingWindow?.show?.();
|
||||
} else {
|
||||
appWindowManager.settingShow('goto_top_menu');
|
||||
}
|
||||
appWindowManager.settingWindow?.show?.();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,8 @@ export class AuthIPCHandler {
|
||||
private closePluginsWindow(): void {
|
||||
try {
|
||||
const appWindowManager = AppWindowManager.getInstance();
|
||||
appWindowManager.pluginsWindow?.close?.();
|
||||
const pluginsWindow = appWindowManager.pluginsWindow;
|
||||
if (pluginsWindow) pluginsWindow.close();
|
||||
} catch (error) {
|
||||
console.error('An error occurred while closing plugin window', error);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
CheckUpdateCommand,
|
||||
ExitAppCommand,
|
||||
OpenMainWindowCommand,
|
||||
OpenPluginsCommand,
|
||||
OpenSettingsCommand,
|
||||
OpenTimerCommand
|
||||
} from '../commands';
|
||||
@@ -41,8 +40,8 @@ export class UnauthenticatedMenuStrategy implements IMenuStrategy {
|
||||
menu.push(
|
||||
new MenuItemBuilder()
|
||||
.withId('3')
|
||||
.withLabel(this.translationService.instant('TIMER_TRACKER.MENU.OPEN_TIMER'))
|
||||
.withAccelerator('CmdOrCtrl+O')
|
||||
.withLabel(this.translationService.instant('LOGIN_PAGE.TITLE'))
|
||||
.withAccelerator('CmdOrCtrl+L')
|
||||
.withCommand(new OpenTimerCommand(this.windowService))
|
||||
.build()
|
||||
);
|
||||
@@ -58,16 +57,6 @@ export class UnauthenticatedMenuStrategy implements IMenuStrategy {
|
||||
.build()
|
||||
);
|
||||
|
||||
// Plugins
|
||||
menu.push(
|
||||
new MenuItemBuilder()
|
||||
.withId('6-0')
|
||||
.withLabel(this.translationService.instant('TIMER_TRACKER.SETTINGS.PLUGINS'))
|
||||
.withAccelerator('CmdOrCtrl+P')
|
||||
.withCommand(new OpenPluginsCommand(this.windowPath))
|
||||
.build()
|
||||
);
|
||||
|
||||
// Check update
|
||||
menu.push(
|
||||
new MenuItemBuilder()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { RegisteredWindow } from '@gauzy/desktop-core';
|
||||
import { ipcMain, nativeImage, Tray } from 'electron';
|
||||
import * as path from 'node:path';
|
||||
import { PluginEventManager } from '../plugin-system/events/plugin-event.manager';
|
||||
import { TrayIPCHandler } from './handlers/tray-ipc-handler';
|
||||
import { IConfigStore, ITranslationService, ITrayIconConfig, IWindowService } from './interfaces';
|
||||
import { TrayMenuManager } from './managers/tray-menu-manager';
|
||||
@@ -11,29 +12,33 @@ import { ILanguageObserver, LanguageChangeSubject } from './observer/language-su
|
||||
* DOES NOT recreate on language change - only rebuilds menu
|
||||
*/
|
||||
export class TrayIcon implements ILanguageObserver {
|
||||
private tray: Tray;
|
||||
private menuManager: TrayMenuManager;
|
||||
private trayIPCHandler: TrayIPCHandler;
|
||||
private languageSubject: LanguageChangeSubject;
|
||||
private readonly tray: Tray;
|
||||
private readonly menuManager: TrayMenuManager;
|
||||
private readonly trayIPCHandler: TrayIPCHandler;
|
||||
private readonly languageSubject: LanguageChangeSubject;
|
||||
private readonly pluginEventManager: PluginEventManager;
|
||||
|
||||
constructor(
|
||||
config: ITrayIconConfig,
|
||||
private dependencies: {
|
||||
private readonly config: ITrayIconConfig,
|
||||
private readonly dependencies: {
|
||||
windowService: IWindowService;
|
||||
configStore: IConfigStore;
|
||||
translationService: ITranslationService;
|
||||
}
|
||||
) {
|
||||
// Initialize plugin event manager
|
||||
this.pluginEventManager = PluginEventManager.getInstance();
|
||||
|
||||
// Create tray icon
|
||||
this.tray = this.createTray(config.iconPath);
|
||||
this.tray = this.createTray(this.config.iconPath);
|
||||
|
||||
// Create menu manager with Strategy pattern
|
||||
this.menuManager = new TrayMenuManager(
|
||||
this.tray,
|
||||
dependencies.translationService,
|
||||
dependencies.windowService,
|
||||
dependencies.configStore,
|
||||
config.windowPath
|
||||
this.dependencies.translationService,
|
||||
this.dependencies.windowService,
|
||||
this.dependencies.configStore,
|
||||
this.config.windowPath
|
||||
);
|
||||
|
||||
// Build initial menu
|
||||
@@ -42,8 +47,8 @@ export class TrayIcon implements ILanguageObserver {
|
||||
// Setup IPC handlers
|
||||
this.trayIPCHandler = new TrayIPCHandler(
|
||||
this.menuManager,
|
||||
dependencies.windowService,
|
||||
dependencies.configStore
|
||||
this.dependencies.windowService,
|
||||
this.dependencies.configStore
|
||||
);
|
||||
this.trayIPCHandler.setupHandlers();
|
||||
|
||||
@@ -73,6 +78,7 @@ export class TrayIcon implements ILanguageObserver {
|
||||
*/
|
||||
onAuthStateChanged(isAuthenticated: boolean, authData?: any): void {
|
||||
this.menuManager.updateAuthState(isAuthenticated, authData);
|
||||
this.pluginEventManager.notify();
|
||||
}
|
||||
|
||||
private createTray(iconPath: string): Tray {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { screen } from 'electron';
|
||||
import {
|
||||
IBaseWindow,
|
||||
BaseWindow,
|
||||
WindowManager,
|
||||
DefaultWindow,
|
||||
IBaseWindow,
|
||||
RegisteredWindow,
|
||||
WindowConfig,
|
||||
RegisteredWindow
|
||||
WindowManager
|
||||
} from '@gauzy/desktop-core';
|
||||
import { screen } from 'electron';
|
||||
|
||||
export class AlwaysOn extends BaseWindow implements IBaseWindow {
|
||||
private static WIDTH: number = 60;
|
||||
private static HEIGHT: number = 110;
|
||||
private manager = WindowManager.getInstance();
|
||||
private readonly manager = WindowManager.getInstance();
|
||||
|
||||
constructor(
|
||||
private readonly path?: string,
|
||||
@@ -32,42 +32,74 @@ export class AlwaysOn extends BaseWindow implements IBaseWindow {
|
||||
roundedCorners: true,
|
||||
width: AlwaysOn.WIDTH,
|
||||
height: AlwaysOn.HEIGHT,
|
||||
...(isExpandMode ? {
|
||||
transparent: true
|
||||
} : {
|
||||
opacity: 0.8
|
||||
}),
|
||||
...(isExpandMode
|
||||
? {
|
||||
transparent: true
|
||||
}
|
||||
: {
|
||||
opacity: 0.8
|
||||
}),
|
||||
alwaysOnTop: true,
|
||||
center: false,
|
||||
x: 16,
|
||||
y: Math.floor((screen.getPrimaryDisplay().workAreaSize.height - AlwaysOn.HEIGHT) / 2),
|
||||
...(contextIsolation && preloadPath ? {
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: false,
|
||||
webSecurity: false,
|
||||
preload: preloadPath
|
||||
},
|
||||
} : {})
|
||||
...(contextIsolation && preloadPath
|
||||
? {
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: false,
|
||||
webSecurity: false,
|
||||
preload: preloadPath
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
)
|
||||
);
|
||||
this.browserWindow.setMenuBarVisibility(false);
|
||||
this.manager.register(RegisteredWindow.WIDGET, this);
|
||||
this.manager.overrideSystemContextMenu(this.browserWindow);
|
||||
this.browserWindow.on('close', () => {
|
||||
if (this.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
this.browserWindow.destroy();
|
||||
});
|
||||
|
||||
this.browserWindow.on('closed', () => {
|
||||
this.manager.unregister(RegisteredWindow.WIDGET);
|
||||
});
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
public override show(): void {
|
||||
if (this.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
this.onTop();
|
||||
super.show();
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
public override hide(): void {
|
||||
this.undoOnTop();
|
||||
super.hide();
|
||||
}
|
||||
|
||||
public override close(): void {
|
||||
if (this.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
super.close();
|
||||
}
|
||||
|
||||
public isDestroyed(): boolean {
|
||||
if(!this.browserWindow) {
|
||||
return true;
|
||||
}
|
||||
return this.browserWindow.isDestroyed();
|
||||
}
|
||||
|
||||
private onTop(): void {
|
||||
this.browserWindow.setSkipTaskbar(true);
|
||||
this.browserWindow.setVisibleOnAllWorkspaces(true, {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
IBaseWindow,
|
||||
BaseWindow,
|
||||
WindowManager,
|
||||
DefaultWindow,
|
||||
IBaseWindow,
|
||||
RegisteredWindow,
|
||||
WindowConfig,
|
||||
RegisteredWindow
|
||||
WindowManager
|
||||
} from '@gauzy/desktop-core';
|
||||
|
||||
/**
|
||||
@@ -12,51 +12,75 @@ import {
|
||||
* This class is responsible for creating and managing the splash screen.
|
||||
*/
|
||||
export class SplashScreen extends BaseWindow implements IBaseWindow {
|
||||
private readonly manager = WindowManager.getInstance();
|
||||
private readonly manager = WindowManager.getInstance();
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the SplashScreen class.
|
||||
*
|
||||
* @param {string} path - The file path to load in the splash screen.
|
||||
*/
|
||||
constructor(public path: string, public preloadPath?: string, public contextIsolation?: boolean) {
|
||||
// Configure the splash screen with default properties
|
||||
super(
|
||||
new DefaultWindow(
|
||||
new WindowConfig('/splash-screen', path, {
|
||||
frame: false,
|
||||
resizable: false,
|
||||
width: 300,
|
||||
height: 240
|
||||
})
|
||||
)
|
||||
);
|
||||
/**
|
||||
* Initializes a new instance of the SplashScreen class.
|
||||
*
|
||||
* @param {string} path - The file path to load in the splash screen.
|
||||
*/
|
||||
constructor(
|
||||
public path: string,
|
||||
public preloadPath?: string,
|
||||
public contextIsolation?: boolean
|
||||
) {
|
||||
// Configure the splash screen with default properties
|
||||
super(
|
||||
new DefaultWindow(
|
||||
new WindowConfig('/splash-screen', path, {
|
||||
frame: false,
|
||||
resizable: false,
|
||||
width: 300,
|
||||
height: 240
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Disable the menu bar for the splash screen
|
||||
this.initializeWindowSettings();
|
||||
// Disable the menu bar for the splash screen
|
||||
this.initializeWindowSettings();
|
||||
if (contextIsolation) {
|
||||
this.config.options.webPreferences.contextIsolation = true;
|
||||
this.config.options.webPreferences.preload = preloadPath;
|
||||
this.config.options.webPreferences.nodeIntegration = false;
|
||||
}
|
||||
|
||||
// Register the splash screen with the WindowManager
|
||||
this.registerWindow();
|
||||
// Register the splash screen with the WindowManager
|
||||
this.registerWindow();
|
||||
this.manager.overrideSystemContextMenu(this.browserWindow);
|
||||
}
|
||||
// Handle the close event to ensure proper cleanup
|
||||
this.browserWindow.on('close', () => {
|
||||
if (this.isDestroyed()) return;
|
||||
this.browserWindow.destroy();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the settings for the splash screen window.
|
||||
* Disables the menu bar and applies additional configurations.
|
||||
*/
|
||||
private initializeWindowSettings(): void {
|
||||
this.browserWindow.setMenuBarVisibility(false);
|
||||
}
|
||||
this.browserWindow.on('closed', () => {
|
||||
this.manager.unregister(RegisteredWindow.SPLASH);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the splash screen with the WindowManager.
|
||||
*/
|
||||
private registerWindow(): void {
|
||||
this.manager.register(RegisteredWindow.SPLASH, this);
|
||||
}
|
||||
/**
|
||||
* Initializes the settings for the splash screen window.
|
||||
* Disables the menu bar and applies additional configurations.
|
||||
*/
|
||||
private initializeWindowSettings(): void {
|
||||
this.browserWindow.setMenuBarVisibility(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the splash screen with the WindowManager.
|
||||
*/
|
||||
private registerWindow(): void {
|
||||
this.manager.register(RegisteredWindow.SPLASH, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the splash screen window has been destroyed.
|
||||
* @return {boolean} `true` if the window is destroyed; otherwise, `false`.
|
||||
*/
|
||||
public isDestroyed(): boolean {
|
||||
if (!this.browserWindow) {
|
||||
return true;
|
||||
}
|
||||
return this.browserWindow.isDestroyed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ export class PluginMarketplaceWindow extends BaseWindow implements IBaseWindow {
|
||||
*
|
||||
* @param {string} path - The file path to load in the plugin marketplace window.
|
||||
*/
|
||||
constructor(public path: string, public preloadPath?: string, public contextIsolation?: boolean) {
|
||||
constructor(
|
||||
public path: string,
|
||||
public preloadPath?: string,
|
||||
public contextIsolation?: boolean
|
||||
) {
|
||||
// Configure the plugin marketplace window with default properties
|
||||
super(
|
||||
new DefaultWindow(
|
||||
@@ -37,6 +41,47 @@ export class PluginMarketplaceWindow extends BaseWindow implements IBaseWindow {
|
||||
// Register the plugin marketplace window with the WindowManager
|
||||
this.registerWindow();
|
||||
this.manager.overrideSystemContextMenu(this.browserWindow);
|
||||
|
||||
this.browserWindow.on('close', () => {
|
||||
if (this.isDestroyed()) return;
|
||||
this.browserWindow.destroy();
|
||||
});
|
||||
|
||||
this.browserWindow.on('closed', () => {
|
||||
this.manager.unregister(RegisteredWindow.PLUGINS);
|
||||
});
|
||||
}
|
||||
|
||||
public override show(): void {
|
||||
if (this.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.browserWindow.isMinimized()) {
|
||||
this.browserWindow.restore();
|
||||
}
|
||||
|
||||
super.show();
|
||||
this.browserWindow.focus();
|
||||
}
|
||||
|
||||
public override close(): void {
|
||||
if (this.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the plugin marketplace window has been destroyed.
|
||||
* @return {boolean} `true` if the window is destroyed; otherwise, `false`.
|
||||
*/
|
||||
public isDestroyed(): boolean {
|
||||
if (!this.browserWindow) {
|
||||
return true;
|
||||
}
|
||||
return this.browserWindow.isDestroyed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user