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:
@@ -1,4 +1,5 @@
|
|||||||
import { App, Gdk } from 'astal/gtk3';
|
import { App, Gdk } from 'astal/gtk3';
|
||||||
|
import { SettingsDialogLoader } from 'src/components/settings/lazyLoader';
|
||||||
|
|
||||||
export const SettingsButton = (): JSX.Element => {
|
export const SettingsButton = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
@@ -6,7 +7,7 @@ export const SettingsButton = (): JSX.Element => {
|
|||||||
className={'dashboard-button'}
|
className={'dashboard-button'}
|
||||||
tooltipText={'HyprPanel Configuration'}
|
tooltipText={'HyprPanel Configuration'}
|
||||||
vexpand
|
vexpand
|
||||||
onButtonPressEvent={(_, event) => {
|
onButtonPressEvent={async (_, event) => {
|
||||||
const buttonClicked = event.get_button()[1];
|
const buttonClicked = event.get_button()[1];
|
||||||
|
|
||||||
if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
|
if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
|
||||||
@@ -14,7 +15,8 @@ export const SettingsButton = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
App.get_window('dashboardmenu')?.set_visible(false);
|
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={''} />
|
<label className={'button-label txt-icon'} label={''} />
|
||||||
|
|||||||
91
src/components/settings/lazyLoader.ts
Normal file
91
src/components/settings/lazyLoader.ts
Normal 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();
|
||||||
|
}
|
||||||
@@ -48,6 +48,15 @@ export const BarGeneral = (): JSX.Element => {
|
|||||||
subtitle="Command executed when restarting. Use '-b busName' flag if needed."
|
subtitle="Command executed when restarting. Use '-b busName' flag if needed."
|
||||||
type="string"
|
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
|
<Option
|
||||||
opt={options.terminal}
|
opt={options.terminal}
|
||||||
title="Terminal"
|
title="Terminal"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default {
|
|||||||
hyprpanel: {
|
hyprpanel: {
|
||||||
restartAgs: opt(true),
|
restartAgs: opt(true),
|
||||||
restartCommand: opt('hyprpanel -q; hyprpanel'),
|
restartCommand: opt('hyprpanel -q; hyprpanel'),
|
||||||
|
useLazyLoading: opt(true),
|
||||||
},
|
},
|
||||||
dummy: opt(true),
|
dummy: opt(true),
|
||||||
bar,
|
bar,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { execAsync } from 'astal';
|
import { execAsync } from 'astal';
|
||||||
import { Bar } from 'src/components/bar';
|
import { Bar } from 'src/components/bar';
|
||||||
import Notifications from 'src/components/notifications';
|
import Notifications from 'src/components/notifications';
|
||||||
import SettingsDialog from 'src/components/settings/index';
|
|
||||||
import OSD from 'src/components/osd/index';
|
import OSD from 'src/components/osd/index';
|
||||||
import { handleRealization } from 'src/components/menus/shared/dropdown/helpers/helpers';
|
import { handleRealization } from 'src/components/menus/shared/dropdown/helpers/helpers';
|
||||||
import { isDropdownMenu } from 'src/components/settings/constants.js';
|
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 AstalHyprland from 'gi://AstalHyprland?version=0.1';
|
||||||
import { Timer } from 'src/lib/performance/timer';
|
import { Timer } from 'src/lib/performance/timer';
|
||||||
import { JSXElement } from 'src/core/types';
|
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.
|
* Manages the complete initialization sequence for HyprPanel.
|
||||||
@@ -36,11 +37,14 @@ export class InitializationService {
|
|||||||
return bars;
|
return bars;
|
||||||
});
|
});
|
||||||
|
|
||||||
Timer.measureSync('Settings dialog', () => SettingsDialog());
|
|
||||||
Timer.measureSync('Menus', () => this._initializeMenus());
|
Timer.measureSync('Menus', () => this._initializeMenus());
|
||||||
Timer.measureSync('System behaviors', () => initializeSystemBehaviors());
|
Timer.measureSync('System behaviors', () => initializeSystemBehaviors());
|
||||||
Timer.measureSync('Monitor handlers', () => this._setupMonitorHandlers());
|
Timer.measureSync('Monitor handlers', () => this._setupMonitorHandlers());
|
||||||
|
|
||||||
|
if (!options.hyprpanel.useLazyLoading.get()) {
|
||||||
|
await Timer.measureAsync('Settings dialog preload', () => SettingsDialogLoader.preload());
|
||||||
|
}
|
||||||
|
|
||||||
overallTimer.end();
|
overallTimer.end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during application initialization:', error);
|
console.error('Error during application initialization:', error);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { App } from 'astal/gtk3';
|
|||||||
import { isWindowVisible } from 'src/lib/window/visibility';
|
import { isWindowVisible } from 'src/lib/window/visibility';
|
||||||
import { BarVisibility } from 'src/services/display/bar';
|
import { BarVisibility } from 'src/services/display/bar';
|
||||||
import { errorHandler } from 'src/core/errors/handler';
|
import { errorHandler } from 'src/core/errors/handler';
|
||||||
|
import { SettingsDialogLoader } from 'src/components/settings/lazyLoader';
|
||||||
|
|
||||||
export const windowManagementCommands: Command[] = [
|
export const windowManagementCommands: Command[] = [
|
||||||
{
|
{
|
||||||
@@ -35,9 +36,21 @@ export const windowManagementCommands: Command[] = [
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
handler: (args: Record<string, unknown>): string => {
|
handler: async (args: Record<string, unknown>): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
const windowName = args['window'] as string;
|
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);
|
const foundWindow = App.get_window(windowName);
|
||||||
|
|
||||||
if (!foundWindow) {
|
if (!foundWindow) {
|
||||||
|
|||||||
Reference in New Issue
Block a user