From 10b53b525d4e3cf894c751e269d3bc0643bf0038 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Thu, 29 Jan 2026 08:52:18 -0500 Subject: [PATCH] refactor: event manager (#25565) --- web/src/lib/actions/zoom-image.ts | 2 +- .../lib/components/AssetViewerEvents.svelte | 23 +++++-------- web/src/lib/components/OnEvents.svelte | 23 ++++--------- .../lib/managers/AssetCacheManager.svelte.ts | 8 +++-- web/src/lib/managers/cast-manager.svelte.ts | 4 ++- .../managers/feature-flags-manager.svelte.ts | 4 ++- .../lib/managers/language-manager.svelte.ts | 4 ++- web/src/lib/managers/queue-manager.svelte.ts | 4 ++- .../lib/managers/release-manager.svelte.ts | 4 ++- .../managers/server-config-manager.svelte.ts | 4 ++- .../managers/system-config-manager.svelte.ts | 4 ++- web/src/lib/managers/theme-manager.svelte.ts | 4 ++- .../timeline-manager.svelte.ts | 8 +++-- web/src/lib/managers/upload-manager.svelte.ts | 2 +- web/src/lib/stores/folders.svelte.ts | 4 ++- web/src/lib/stores/memory.store.svelte.ts | 2 +- .../lib/stores/notification-manager.svelte.ts | 2 +- web/src/lib/stores/search.svelte.ts | 4 ++- web/src/lib/stores/user.store.ts | 4 ++- web/src/lib/stores/user.svelte.ts | 4 ++- .../lib/utils/base-event-manager.svelte.ts | 34 ++++++++++--------- 21 files changed, 84 insertions(+), 68 deletions(-) diff --git a/web/src/lib/actions/zoom-image.ts b/web/src/lib/actions/zoom-image.ts index 20eabd892b..6288daa380 100644 --- a/web/src/lib/actions/zoom-image.ts +++ b/web/src/lib/actions/zoom-image.ts @@ -5,7 +5,7 @@ export const zoomImageAction = (node: HTMLElement, options?: { disabled?: boolea const zoomInstance = createZoomImageWheel(node, { maxZoom: 10, initialState: assetViewerManager.zoomState }); const unsubscribes = [ - assetViewerManager.on('ZoomChange', (state) => zoomInstance.setState(state)), + assetViewerManager.on({ ZoomChange: (state) => zoomInstance.setState(state) }), zoomInstance.subscribe(({ state }) => assetViewerManager.onZoomChange(state)), ]; diff --git a/web/src/lib/components/AssetViewerEvents.svelte b/web/src/lib/components/AssetViewerEvents.svelte index bf915ae217..148da0e258 100644 --- a/web/src/lib/components/AssetViewerEvents.svelte +++ b/web/src/lib/components/AssetViewerEvents.svelte @@ -1,6 +1,6 @@ + +const event = name.slice(2) as keyof Events; diff --git a/web/src/lib/components/OnEvents.svelte b/web/src/lib/components/OnEvents.svelte index aa137ccba1..fe8039cf38 100644 --- a/web/src/lib/components/OnEvents.svelte +++ b/web/src/lib/components/OnEvents.svelte @@ -1,5 +1,6 @@ diff --git a/web/src/lib/managers/AssetCacheManager.svelte.ts b/web/src/lib/managers/AssetCacheManager.svelte.ts index f36f6a9a8a..f3c85acfa5 100644 --- a/web/src/lib/managers/AssetCacheManager.svelte.ts +++ b/web/src/lib/managers/AssetCacheManager.svelte.ts @@ -37,9 +37,11 @@ class AssetCacheManager { #ocrCache = new AsyncCache(); constructor() { - eventManager.on('AssetEditsApplied', () => { - this.#assetCache.clear(); - this.#ocrCache.clear(); + eventManager.on({ + AssetEditsApplied: () => { + this.#assetCache.clear(); + this.#ocrCache.clear(); + }, }); } diff --git a/web/src/lib/managers/cast-manager.svelte.ts b/web/src/lib/managers/cast-manager.svelte.ts index 81aedd9d85..19e44b17f5 100644 --- a/web/src/lib/managers/cast-manager.svelte.ts +++ b/web/src/lib/managers/cast-manager.svelte.ts @@ -59,7 +59,9 @@ class CastManager { // Add other cast destinations here (ie FCast) ]; - eventManager.on('AppInit', () => void this.initialize()); + eventManager.on({ + AppInit: () => void this.initialize(), + }); } private async initialize() { diff --git a/web/src/lib/managers/feature-flags-manager.svelte.ts b/web/src/lib/managers/feature-flags-manager.svelte.ts index e27f3ff471..3e7d2e145e 100644 --- a/web/src/lib/managers/feature-flags-manager.svelte.ts +++ b/web/src/lib/managers/feature-flags-manager.svelte.ts @@ -5,7 +5,9 @@ class FeatureFlagsManager { #value?: ServerFeaturesDto = $state(); constructor() { - eventManager.on('SystemConfigUpdate', () => void this.#loadFeatureFlags()); + eventManager.on({ + SystemConfigUpdate: () => void this.#loadFeatureFlags(), + }); } async init() { diff --git a/web/src/lib/managers/language-manager.svelte.ts b/web/src/lib/managers/language-manager.svelte.ts index 91d621f679..e3069e4504 100644 --- a/web/src/lib/managers/language-manager.svelte.ts +++ b/web/src/lib/managers/language-manager.svelte.ts @@ -4,7 +4,9 @@ import { lang } from '$lib/stores/preferences.store'; class LanguageManager { constructor() { - eventManager.on('AppInit', () => lang.subscribe((lang) => this.setLanguage(lang))); + eventManager.on({ + AppInit: () => lang.subscribe((lang) => this.setLanguage(lang)), + }); } rtl = $state(false); diff --git a/web/src/lib/managers/queue-manager.svelte.ts b/web/src/lib/managers/queue-manager.svelte.ts index f6bd1ad052..36e1fa905e 100644 --- a/web/src/lib/managers/queue-manager.svelte.ts +++ b/web/src/lib/managers/queue-manager.svelte.ts @@ -19,7 +19,9 @@ export class QueueManager { } constructor() { - eventManager.on('QueueUpdate', () => this.refresh()); + eventManager.on({ + QueueUpdate: () => this.refresh(), + }); } listen() { diff --git a/web/src/lib/managers/release-manager.svelte.ts b/web/src/lib/managers/release-manager.svelte.ts index 1a3108a227..15baa6de8f 100644 --- a/web/src/lib/managers/release-manager.svelte.ts +++ b/web/src/lib/managers/release-manager.svelte.ts @@ -5,7 +5,9 @@ class ReleaseManager { value = $state(); constructor() { - eventManager.on('ReleaseEvent', (event) => (this.value = event)); + eventManager.on({ + ReleaseEvent: (event) => (this.value = event), + }); } } diff --git a/web/src/lib/managers/server-config-manager.svelte.ts b/web/src/lib/managers/server-config-manager.svelte.ts index e315fba818..b0e42aff3c 100644 --- a/web/src/lib/managers/server-config-manager.svelte.ts +++ b/web/src/lib/managers/server-config-manager.svelte.ts @@ -5,7 +5,9 @@ class ServerConfigManager { #value?: ServerConfigDto = $state(); constructor() { - eventManager.on('SystemConfigUpdate', () => this.loadServerConfig()); + eventManager.on({ + SystemConfigUpdate: () => this.loadServerConfig(), + }); } async init() { diff --git a/web/src/lib/managers/system-config-manager.svelte.ts b/web/src/lib/managers/system-config-manager.svelte.ts index 7e80a44a0c..116137c59c 100644 --- a/web/src/lib/managers/system-config-manager.svelte.ts +++ b/web/src/lib/managers/system-config-manager.svelte.ts @@ -7,7 +7,9 @@ class SystemConfigManager { #defaultValue?: SystemConfigDto = $state(); constructor() { - eventManager.on('SystemConfigUpdate', (config) => (this.#value = config)); + eventManager.on({ + SystemConfigUpdate: (config) => (this.#value = config), + }); } async init() { diff --git a/web/src/lib/managers/theme-manager.svelte.ts b/web/src/lib/managers/theme-manager.svelte.ts index 98b38b6afb..26c3fe31d5 100644 --- a/web/src/lib/managers/theme-manager.svelte.ts +++ b/web/src/lib/managers/theme-manager.svelte.ts @@ -37,7 +37,9 @@ class ThemeManager { isDark = $derived(this.value === Theme.DARK); constructor() { - eventManager.on('AppInit', () => this.#onAppInit()); + eventManager.on({ + AppInit: () => this.#onAppInit(), + }); } setSystem(system: boolean) { diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts index 06601caff6..24365f41e5 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -111,9 +111,11 @@ export class TimelineManager extends VirtualScrollManager { constructor() { super(); - const onAssetUpdate = (asset: AssetResponseDto) => this.upsertAssets([toTimelineAsset(asset)]); - - this.#unsubscribes.push(eventManager.on('AssetUpdate', onAssetUpdate)); + this.#unsubscribes.push( + eventManager.on({ + AssetUpdate: (asset: AssetResponseDto) => this.upsertAssets([toTimelineAsset(asset)]), + }), + ); } override get scrollTop(): number { diff --git a/web/src/lib/managers/upload-manager.svelte.ts b/web/src/lib/managers/upload-manager.svelte.ts index 031c43b8c4..18fab1158b 100644 --- a/web/src/lib/managers/upload-manager.svelte.ts +++ b/web/src/lib/managers/upload-manager.svelte.ts @@ -6,7 +6,7 @@ class UploadManager { mediaTypes = $state({ image: [], sidecar: [], video: [] }); constructor() { - eventManager.onMany({ + eventManager.on({ AppInit: () => this.#loadExtensions(), AuthLogout: () => this.reset(), }); diff --git a/web/src/lib/stores/folders.svelte.ts b/web/src/lib/stores/folders.svelte.ts index 3480e95f28..3adebf51f9 100644 --- a/web/src/lib/stores/folders.svelte.ts +++ b/web/src/lib/stores/folders.svelte.ts @@ -19,7 +19,9 @@ class FoldersStore { private assets = $state({}); constructor() { - eventManager.on('AuthLogout', () => this.clearCache()); + eventManager.on({ + AuthLogout: () => this.clearCache(), + }); } async fetchTree(): Promise { diff --git a/web/src/lib/stores/memory.store.svelte.ts b/web/src/lib/stores/memory.store.svelte.ts index 877e91a856..cdd6af9606 100644 --- a/web/src/lib/stores/memory.store.svelte.ts +++ b/web/src/lib/stores/memory.store.svelte.ts @@ -25,7 +25,7 @@ class MemoryStoreSvelte { #loading: Promise | undefined; constructor() { - eventManager.onMany({ + eventManager.on({ AuthLogout: () => this.clearCache(), AuthUserLoaded: () => this.initialize(), }); diff --git a/web/src/lib/stores/notification-manager.svelte.ts b/web/src/lib/stores/notification-manager.svelte.ts index 9dc35a4ea6..1d1dda3d06 100644 --- a/web/src/lib/stores/notification-manager.svelte.ts +++ b/web/src/lib/stores/notification-manager.svelte.ts @@ -8,7 +8,7 @@ class NotificationStore { notifications = $state([]); constructor() { - eventManager.onMany({ + eventManager.on({ AuthLogin: () => this.refresh(), AuthLogout: () => this.clear(), }); diff --git a/web/src/lib/stores/search.svelte.ts b/web/src/lib/stores/search.svelte.ts index 007c764fcf..453a8ce6c6 100644 --- a/web/src/lib/stores/search.svelte.ts +++ b/web/src/lib/stores/search.svelte.ts @@ -5,7 +5,9 @@ class SearchStore { isSearchEnabled = $state(false); constructor() { - eventManager.on('AuthLogout', () => this.clearCache()); + eventManager.on({ + AuthLogout: () => this.clearCache(), + }); } clearCache() { diff --git a/web/src/lib/stores/user.store.ts b/web/src/lib/stores/user.store.ts index bc23917d22..331df7ad5f 100644 --- a/web/src/lib/stores/user.store.ts +++ b/web/src/lib/stores/user.store.ts @@ -16,4 +16,6 @@ export const resetSavedUser = () => { purchaseStore.setPurchaseStatus(false); }; -eventManager.on('AuthLogout', () => resetSavedUser()); +eventManager.on({ + AuthLogout: () => resetSavedUser(), +}); diff --git a/web/src/lib/stores/user.svelte.ts b/web/src/lib/stores/user.svelte.ts index f9319fcfa1..508fd156b8 100644 --- a/web/src/lib/stores/user.svelte.ts +++ b/web/src/lib/stores/user.svelte.ts @@ -26,4 +26,6 @@ const reset = () => { Object.assign(userInteraction, defaultUserInteraction); }; -eventManager.on('AuthLogout', () => reset()); +eventManager.on({ + AuthLogout: () => reset(), +}); diff --git a/web/src/lib/utils/base-event-manager.svelte.ts b/web/src/lib/utils/base-event-manager.svelte.ts index 89a5c7390e..1b5135dfd9 100644 --- a/web/src/lib/utils/base-event-manager.svelte.ts +++ b/web/src/lib/utils/base-event-manager.svelte.ts @@ -1,8 +1,9 @@ -type EventMap = Record; +type EventsBase = Record; type PromiseLike = Promise | T; -export type EventCallback = (...args: E[T]) => PromiseLike; -export type EventItem = { +export type EventMap = { [K in keyof E]?: EventCallback }; +export type EventCallback = (...args: E[T]) => PromiseLike; +export type EventItem = { id: number; event: T; callback: EventCallback; @@ -13,10 +14,22 @@ const nextId = () => count++; const noop = () => {}; -export class BaseEventManager { +export class BaseEventManager { #callbacks: EventItem[] = $state([]); - on(event: T, callback?: EventCallback) { + on(subscriptions: EventMap): () => void { + const cleanups = Object.entries(subscriptions).map(([event, callback]) => + this.#onEvent(event as keyof Events, callback as EventCallback), + ); + + return () => { + for (const cleanup of cleanups) { + cleanup(); + } + }; + } + + #onEvent(event: T, callback?: EventCallback) { if (!callback) { return noop; } @@ -30,17 +43,6 @@ export class BaseEventManager { }; } - onMany(subscriptions: { [T in keyof Events]?: EventCallback }) { - const cleanups = Object.entries(subscriptions).map(([event, callback]) => - this.on(event as keyof Events, callback as EventCallback), - ); - return () => { - for (const cleanup of cleanups) { - cleanup(); - } - }; - } - emit(event: T, ...params: Events[T]) { const listeners = this.getListeners(event); for (const listener of listeners) {