[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:
Kifungo A
2026-03-05 18:21:38 +01:00
committed by GitHub
co-authored by greptile-apps[bot]
parent 5fed02c657
commit c87354be22
11 changed files with 264 additions and 155 deletions
+10 -15
View File
@@ -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');
+10 -7
View File
@@ -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();
}
});
+4 -1
View File
@@ -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()
+19 -13
View File
@@ -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 {
+53 -21
View File
@@ -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();
}
/**