Compare commits

...

1 Commits

Author SHA1 Message Date
Jason Rasmussen
8388f578f7 refactor: event manager 2026-01-27 13:54:54 -05:00
21 changed files with 84 additions and 68 deletions

View File

@@ -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)),
];

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { assetViewerManager, type Events } from '$lib/managers/asset-viewer-manager.svelte';
import type { EventCallback } from '$lib/utils/base-event-manager.svelte';
import type { EventCallback, EventMap } from '$lib/utils/base-event-manager.svelte';
import { onMount } from 'svelte';
type Props = {
@@ -10,22 +10,17 @@
const props: Props = $props();
onMount(() => {
const unsubscribes: Array<() => void> = [];
const events: EventMap<Events> = {};
for (const name of Object.keys(props)) {
const event = name.slice(2) as keyof Events;
const listener = props[name as keyof Props] as EventCallback<Events, typeof event> | undefined;
if (!listener) {
continue;
for (const [name, listener] of Object.entries(props)) {
if (listener) {
const event = name.slice(2) as keyof Events;
events[event] = listener as EventCallback<Events, typeof event>;
}
unsubscribes.push(assetViewerManager.on(event, listener));
}
return () => {
for (const unsubscribe of unsubscribes) {
unsubscribe();
}
};
return assetViewerManager.on(events);
});
</script>
const event = name.slice(2) as keyof Events;

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { eventManager, type Events } from '$lib/managers/event-manager.svelte';
import type { EventCallback, EventMap } from '$lib/utils/base-event-manager.svelte';
import { onMount } from 'svelte';
type Props = {
@@ -9,25 +10,15 @@
const props: Props = $props();
onMount(() => {
const unsubscribes: Array<() => void> = [];
const events: EventMap<Events> = {};
for (const name of Object.keys(props)) {
const event = name.slice(2) as keyof Events;
const listener = props[name as keyof Props];
if (!listener) {
continue;
for (const [name, listener] of Object.entries(props)) {
if (listener) {
const event = name.slice(2) as keyof Events;
events[event] = listener as EventCallback<Events, typeof event>;
}
const args = [event, listener as (...args: Events[typeof event]) => void] as const;
unsubscribes.push(eventManager.on(...args));
}
return () => {
for (const unsubscribe of unsubscribes) {
unsubscribe();
}
};
return eventManager.on(events);
});
</script>

View File

@@ -37,9 +37,11 @@ class AssetCacheManager {
#ocrCache = new AsyncCache<AssetOcrResponseDto[]>();
constructor() {
eventManager.on('AssetEditsApplied', () => {
this.#assetCache.clear();
this.#ocrCache.clear();
eventManager.on({
AssetEditsApplied: () => {
this.#assetCache.clear();
this.#ocrCache.clear();
},
});
}

View File

@@ -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() {

View File

@@ -5,7 +5,9 @@ class FeatureFlagsManager {
#value?: ServerFeaturesDto = $state();
constructor() {
eventManager.on('SystemConfigUpdate', () => void this.#loadFeatureFlags());
eventManager.on({
SystemConfigUpdate: () => void this.#loadFeatureFlags(),
});
}
async init() {

View File

@@ -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);

View File

@@ -19,7 +19,9 @@ export class QueueManager {
}
constructor() {
eventManager.on('QueueUpdate', () => this.refresh());
eventManager.on({
QueueUpdate: () => this.refresh(),
});
}
listen() {

View File

@@ -5,7 +5,9 @@ class ReleaseManager {
value = $state<ReleaseEvent | undefined>();
constructor() {
eventManager.on('ReleaseEvent', (event) => (this.value = event));
eventManager.on({
ReleaseEvent: (event) => (this.value = event),
});
}
}

View File

@@ -5,7 +5,9 @@ class ServerConfigManager {
#value?: ServerConfigDto = $state();
constructor() {
eventManager.on('SystemConfigUpdate', () => this.loadServerConfig());
eventManager.on({
SystemConfigUpdate: () => this.loadServerConfig(),
});
}
async init() {

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -6,7 +6,7 @@ class UploadManager {
mediaTypes = $state<ServerMediaTypesResponseDto>({ image: [], sidecar: [], video: [] });
constructor() {
eventManager.onMany({
eventManager.on({
AppInit: () => this.#loadExtensions(),
AuthLogout: () => this.reset(),
});

View File

@@ -19,7 +19,9 @@ class FoldersStore {
private assets = $state<AssetCache>({});
constructor() {
eventManager.on('AuthLogout', () => this.clearCache());
eventManager.on({
AuthLogout: () => this.clearCache(),
});
}
async fetchTree(): Promise<TreeNode> {

View File

@@ -23,7 +23,7 @@ class MemoryStoreSvelte {
#loading: Promise<void> | undefined;
constructor() {
eventManager.onMany({
eventManager.on({
AuthLogout: () => this.clearCache(),
AuthUserLoaded: () => this.initialize(),
});

View File

@@ -8,7 +8,7 @@ class NotificationStore {
notifications = $state<NotificationDto[]>([]);
constructor() {
eventManager.onMany({
eventManager.on({
AuthLogin: () => this.refresh(),
AuthLogout: () => this.clear(),
});

View File

@@ -5,7 +5,9 @@ class SearchStore {
isSearchEnabled = $state(false);
constructor() {
eventManager.on('AuthLogout', () => this.clearCache());
eventManager.on({
AuthLogout: () => this.clearCache(),
});
}
clearCache() {

View File

@@ -16,4 +16,6 @@ export const resetSavedUser = () => {
purchaseStore.setPurchaseStatus(false);
};
eventManager.on('AuthLogout', () => resetSavedUser());
eventManager.on({
AuthLogout: () => resetSavedUser(),
});

View File

@@ -26,4 +26,6 @@ const reset = () => {
Object.assign(userInteraction, defaultUserInteraction);
};
eventManager.on('AuthLogout', () => reset());
eventManager.on({
AuthLogout: () => reset(),
});

View File

@@ -1,8 +1,9 @@
type EventMap = Record<string, unknown[]>;
type EventsBase = Record<string, unknown[]>;
type PromiseLike<T> = Promise<T> | T;
export type EventCallback<E extends EventMap, T extends keyof E> = (...args: E[T]) => PromiseLike<unknown>;
export type EventItem<E extends EventMap, T extends keyof E = keyof E> = {
export type EventMap<E extends EventsBase> = { [K in keyof E]?: EventCallback<E, K> };
export type EventCallback<E extends EventsBase, T extends keyof E> = (...args: E[T]) => PromiseLike<unknown>;
export type EventItem<E extends EventsBase, T extends keyof E = keyof E> = {
id: number;
event: T;
callback: EventCallback<E, T>;
@@ -13,10 +14,22 @@ const nextId = () => count++;
const noop = () => {};
export class BaseEventManager<Events extends EventMap> {
export class BaseEventManager<Events extends EventsBase> {
#callbacks: EventItem<Events>[] = $state([]);
on<T extends keyof Events>(event: T, callback?: EventCallback<Events, T>) {
on(subscriptions: EventMap<Events>): () => void {
const cleanups = Object.entries(subscriptions).map(([event, callback]) =>
this.#onEvent(event as keyof Events, callback as EventCallback<Events, keyof Events>),
);
return () => {
for (const cleanup of cleanups) {
cleanup();
}
};
}
#onEvent<T extends keyof Events>(event: T, callback?: EventCallback<Events, T>) {
if (!callback) {
return noop;
}
@@ -30,17 +43,6 @@ export class BaseEventManager<Events extends EventMap> {
};
}
onMany(subscriptions: { [T in keyof Events]?: EventCallback<Events, T> }) {
const cleanups = Object.entries(subscriptions).map(([event, callback]) =>
this.on(event as keyof Events, callback as EventCallback<Events, keyof Events>),
);
return () => {
for (const cleanup of cleanups) {
cleanup();
}
};
}
emit<T extends keyof Events>(event: T, ...params: Events[T]) {
const listeners = this.getListeners(event);
for (const listener of listeners) {