Feature: Added lazy loading of settings dialog (#983)

* Feat: Added configurable lazy loading for the settings dialog.

* Allow lazyloading to be configurable
This commit is contained in:
Jas Singh
2025-06-02 02:30:10 -07:00
committed by GitHub
parent 3616e5806b
commit 8be178f5a9
6 changed files with 125 additions and 5 deletions

View File

@@ -1,4 +1,5 @@
import { App, Gdk } from 'astal/gtk3';
import { SettingsDialogLoader } from 'src/components/settings/lazyLoader';
export const SettingsButton = (): JSX.Element => {
return (
@@ -6,7 +7,7 @@ export const SettingsButton = (): JSX.Element => {
className={'dashboard-button'}
tooltipText={'HyprPanel Configuration'}
vexpand
onButtonPressEvent={(_, event) => {
onButtonPressEvent={async (_, event) => {
const buttonClicked = event.get_button()[1];
if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
@@ -14,7 +15,8 @@ export const SettingsButton = (): JSX.Element => {
}
App.get_window('dashboardmenu')?.set_visible(false);
App.toggle_window('settings-dialog');
const loader = SettingsDialogLoader.getInstance();
await loader.toggle();
}}
>
<label className={'button-label txt-icon'} label={'󰒓'} />

View File

@@ -0,0 +1,91 @@
import { App } from 'astal/gtk3';
import { Timer } from 'src/lib/performance/timer';
/**
* Manages lazy loading of the settings dialog to improve startup performance.
* The dialog is only created when first accessed, not during app initialization.
*/
export class SettingsDialogLoader {
private static _instance: SettingsDialogLoader | null = null;
private _settingsDialog: JSX.Element | null = null;
private _loadPromise: Promise<JSX.Element> | null = null;
private constructor() {}
/**
* Gets the singleton instance of the settings dialog loader
*/
public static getInstance(): SettingsDialogLoader {
if (!SettingsDialogLoader._instance) {
SettingsDialogLoader._instance = new SettingsDialogLoader();
}
return SettingsDialogLoader._instance;
}
/**
* Preloads the settings dialog
*/
public static async preload(): Promise<void> {
const instance = SettingsDialogLoader.getInstance();
await instance._getDialog();
}
/**
* Loads and returns the settings dialog, creating it if necessary.
* Multiple concurrent calls will share the same loading promise.
*/
private async _getDialog(): Promise<JSX.Element> {
if (this._settingsDialog) {
return this._settingsDialog;
}
if (this._loadPromise) {
return this._loadPromise;
}
this._loadPromise = this._loadSettingsDialog();
try {
this._settingsDialog = await this._loadPromise;
return this._settingsDialog;
} finally {
this._loadPromise = null;
}
}
/**
* Performs the actual loading of the settings dialog module
*/
private async _loadSettingsDialog(): Promise<JSX.Element> {
const { default: options } = await import('src/configuration');
const isLazyLoading = options.hyprpanel.useLazyLoading.get();
const timerLabel = isLazyLoading ? 'Lazy loading settings dialog' : 'Preloading settings dialog';
const timer = new Timer(timerLabel);
try {
const { default: SettingsDialog } = await import('./index');
const dialog = SettingsDialog();
timer.end();
return dialog;
} catch (error) {
timer.end();
throw new Error(`Failed to load settings dialog: ${error}`);
}
}
/**
* Toggles the settings dialog visibility, loading it if necessary
*/
public async toggle(): Promise<void> {
await this._getDialog();
App.toggle_window('settings-dialog');
}
}
/**
* Convenience function to toggle the settings dialog
*/
export async function toggleSettingsDialog(): Promise<void> {
const loader = SettingsDialogLoader.getInstance();
await loader.toggle();
}

View File

@@ -48,6 +48,15 @@ export const BarGeneral = (): JSX.Element => {
subtitle="Command executed when restarting. Use '-b busName' flag if needed."
type="string"
/>
<Option
opt={options.hyprpanel.useLazyLoading}
title="Lazy Load Settings Dialog"
subtitle={
'Only loads the settings dialog when first opened, rather than at startup. (Requires restart)\n' +
'Improves launch speed and reduces memory usage until the dialog is accessed.'
}
type="boolean"
/>
<Option
opt={options.terminal}
title="Terminal"

View File

@@ -16,6 +16,7 @@ export default {
hyprpanel: {
restartAgs: opt(true),
restartCommand: opt('hyprpanel -q; hyprpanel'),
useLazyLoading: opt(true),
},
dummy: opt(true),
bar,

View File

@@ -1,7 +1,6 @@
import { execAsync } from 'astal';
import { Bar } from 'src/components/bar';
import Notifications from 'src/components/notifications';
import SettingsDialog from 'src/components/settings/index';
import OSD from 'src/components/osd/index';
import { handleRealization } from 'src/components/menus/shared/dropdown/helpers/helpers';
import { isDropdownMenu } from 'src/components/settings/constants.js';
@@ -12,6 +11,8 @@ import { BarRefreshManager } from 'src/services/display/bar/refreshManager';
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import { Timer } from 'src/lib/performance/timer';
import { JSXElement } from 'src/core/types';
import { SettingsDialogLoader } from 'src/components/settings/lazyLoader';
import options from 'src/configuration';
/**
* Manages the complete initialization sequence for HyprPanel.
@@ -36,11 +37,14 @@ export class InitializationService {
return bars;
});
Timer.measureSync('Settings dialog', () => SettingsDialog());
Timer.measureSync('Menus', () => this._initializeMenus());
Timer.measureSync('System behaviors', () => initializeSystemBehaviors());
Timer.measureSync('Monitor handlers', () => this._setupMonitorHandlers());
if (!options.hyprpanel.useLazyLoading.get()) {
await Timer.measureAsync('Settings dialog preload', () => SettingsDialogLoader.preload());
}
overallTimer.end();
} catch (error) {
console.error('Error during application initialization:', error);

View File

@@ -3,6 +3,7 @@ import { App } from 'astal/gtk3';
import { isWindowVisible } from 'src/lib/window/visibility';
import { BarVisibility } from 'src/services/display/bar';
import { errorHandler } from 'src/core/errors/handler';
import { SettingsDialogLoader } from 'src/components/settings/lazyLoader';
export const windowManagementCommands: Command[] = [
{
@@ -35,9 +36,21 @@ export const windowManagementCommands: Command[] = [
required: true,
},
],
handler: (args: Record<string, unknown>): string => {
handler: async (args: Record<string, unknown>): Promise<string> => {
try {
const windowName = args['window'] as string;
if (windowName === 'settings-dialog') {
const loader = SettingsDialogLoader.getInstance();
await loader.toggle();
const foundWindow = App.get_window(windowName);
const windowStatus = foundWindow?.visible ? 'visible' : 'hidden';
BarVisibility.set(windowName, windowStatus === 'visible');
return windowStatus;
}
const foundWindow = App.get_window(windowName);
if (!foundWindow) {