From dd393c83461a2d396bc2b9759fb3210059a76735 Mon Sep 17 00:00:00 2001
From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Date: Mon, 10 Nov 2025 17:49:46 +0100
Subject: [PATCH] feat(web): event handler component (#23763)
---
web/src/lib/components/OnEvents.svelte | 33 +++++++++++++++++++
web/src/lib/managers/auth-manager.svelte.ts | 2 +-
web/src/lib/managers/event-manager.svelte.ts | 18 +++++-----
.../lib/managers/language-manager.svelte.ts | 4 +--
web/src/lib/managers/theme-manager.svelte.ts | 4 +--
web/src/lib/managers/upload-manager.svelte.ts | 2 +-
web/src/lib/stores/folders.svelte.ts | 2 +-
web/src/lib/stores/memory.store.svelte.ts | 2 +-
.../lib/stores/notification-manager.svelte.ts | 4 +--
web/src/lib/stores/search.svelte.ts | 2 +-
web/src/lib/stores/user.store.ts | 2 +-
web/src/lib/stores/user.svelte.ts | 2 +-
web/src/routes/+layout.svelte | 2 +-
web/src/routes/auth/login/+page.svelte | 2 +-
14 files changed, 58 insertions(+), 23 deletions(-)
create mode 100644 web/src/lib/components/OnEvents.svelte
diff --git a/web/src/lib/components/OnEvents.svelte b/web/src/lib/components/OnEvents.svelte
new file mode 100644
index 0000000000..3933f4df7b
--- /dev/null
+++ b/web/src/lib/components/OnEvents.svelte
@@ -0,0 +1,33 @@
+
diff --git a/web/src/lib/managers/auth-manager.svelte.ts b/web/src/lib/managers/auth-manager.svelte.ts
index 0128892c3f..515fde0996 100644
--- a/web/src/lib/managers/auth-manager.svelte.ts
+++ b/web/src/lib/managers/auth-manager.svelte.ts
@@ -30,7 +30,7 @@ class AuthManager {
globalThis.location.href = redirectUri;
}
} finally {
- eventManager.emit('auth.logout');
+ eventManager.emit('AuthLogout');
}
}
}
diff --git a/web/src/lib/managers/event-manager.svelte.ts b/web/src/lib/managers/event-manager.svelte.ts
index f8e39411cf..7c7717a4d4 100644
--- a/web/src/lib/managers/event-manager.svelte.ts
+++ b/web/src/lib/managers/event-manager.svelte.ts
@@ -1,6 +1,15 @@
import type { ThemeSetting } from '$lib/managers/theme-manager.svelte';
import type { LoginResponseDto } from '@immich/sdk';
+export type Events = {
+ AppInit: [];
+ UserLogin: [];
+ AuthLogin: [LoginResponseDto];
+ AuthLogout: [];
+ LanguageChange: [{ name: string; code: string; rtl?: boolean }];
+ ThemeChange: [ThemeSetting];
+};
+
type Listener, K extends keyof EventMap> = (...params: EventMap[K]) => void;
class EventManager> {
@@ -51,11 +60,4 @@ class EventManager> {
}
}
-export const eventManager = new EventManager<{
- 'app.init': [];
- 'user.login': [];
- 'auth.login': [LoginResponseDto];
- 'auth.logout': [];
- 'language.change': [{ name: string; code: string; rtl?: boolean }];
- 'theme.change': [ThemeSetting];
-}>();
+export const eventManager = new EventManager();
diff --git a/web/src/lib/managers/language-manager.svelte.ts b/web/src/lib/managers/language-manager.svelte.ts
index 5acae27aa3..91d621f679 100644
--- a/web/src/lib/managers/language-manager.svelte.ts
+++ b/web/src/lib/managers/language-manager.svelte.ts
@@ -4,7 +4,7 @@ import { lang } from '$lib/stores/preferences.store';
class LanguageManager {
constructor() {
- eventManager.on('app.init', () => lang.subscribe((lang) => this.setLanguage(lang)));
+ eventManager.on('AppInit', () => lang.subscribe((lang) => this.setLanguage(lang)));
}
rtl = $state(false);
@@ -19,7 +19,7 @@ class LanguageManager {
document.body.setAttribute('dir', item.rtl ? 'rtl' : 'ltr');
- eventManager.emit('language.change', item);
+ eventManager.emit('LanguageChange', item);
}
}
diff --git a/web/src/lib/managers/theme-manager.svelte.ts b/web/src/lib/managers/theme-manager.svelte.ts
index 394c9850de..5095b5739e 100644
--- a/web/src/lib/managers/theme-manager.svelte.ts
+++ b/web/src/lib/managers/theme-manager.svelte.ts
@@ -36,7 +36,7 @@ class ThemeManager {
isDark = $derived(this.value === Theme.DARK);
constructor() {
- eventManager.on('app.init', () => this.#onAppInit());
+ eventManager.on('AppInit', () => this.#onAppInit());
}
setSystem(system: boolean) {
@@ -71,7 +71,7 @@ class ThemeManager {
this.#theme.current = theme;
- eventManager.emit('theme.change', theme);
+ eventManager.emit('ThemeChange', theme);
}
}
diff --git a/web/src/lib/managers/upload-manager.svelte.ts b/web/src/lib/managers/upload-manager.svelte.ts
index 61c6d73b53..b51756678b 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.on('app.init', () => void this.#loadExtensions()).on('auth.logout', () => void this.reset());
+ eventManager.on('AppInit', () => void this.#loadExtensions()).on('AuthLogout', () => void this.reset());
}
reset() {
diff --git a/web/src/lib/stores/folders.svelte.ts b/web/src/lib/stores/folders.svelte.ts
index f77b67bb7c..3480e95f28 100644
--- a/web/src/lib/stores/folders.svelte.ts
+++ b/web/src/lib/stores/folders.svelte.ts
@@ -19,7 +19,7 @@ class FoldersStore {
private assets = $state({});
constructor() {
- eventManager.on('auth.logout', () => 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 bc42053a21..05f45b3d7d 100644
--- a/web/src/lib/stores/memory.store.svelte.ts
+++ b/web/src/lib/stores/memory.store.svelte.ts
@@ -21,7 +21,7 @@ export type MemoryAsset = MemoryIndex & {
class MemoryStoreSvelte {
constructor() {
- eventManager.on('auth.logout', () => this.clearCache());
+ eventManager.on('AuthLogout', () => this.clearCache());
}
memories = $state([]);
diff --git a/web/src/lib/stores/notification-manager.svelte.ts b/web/src/lib/stores/notification-manager.svelte.ts
index 3eba15deed..a0f0f6bb93 100644
--- a/web/src/lib/stores/notification-manager.svelte.ts
+++ b/web/src/lib/stores/notification-manager.svelte.ts
@@ -9,8 +9,8 @@ class NotificationStore {
notifications = $state([]);
constructor() {
- eventManager.on('auth.login', () => handlePromiseError(this.refresh()));
- eventManager.on('auth.logout', () => this.clear());
+ eventManager.on('AuthLogin', () => handlePromiseError(this.refresh()));
+ eventManager.on('AuthLogout', () => this.clear());
}
async refresh() {
diff --git a/web/src/lib/stores/search.svelte.ts b/web/src/lib/stores/search.svelte.ts
index 32f2741955..007c764fcf 100644
--- a/web/src/lib/stores/search.svelte.ts
+++ b/web/src/lib/stores/search.svelte.ts
@@ -5,7 +5,7 @@ class SearchStore {
isSearchEnabled = $state(false);
constructor() {
- eventManager.on('auth.logout', () => 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 0790788278..bc23917d22 100644
--- a/web/src/lib/stores/user.store.ts
+++ b/web/src/lib/stores/user.store.ts
@@ -16,4 +16,4 @@ export const resetSavedUser = () => {
purchaseStore.setPurchaseStatus(false);
};
-eventManager.on('auth.logout', () => resetSavedUser());
+eventManager.on('AuthLogout', () => resetSavedUser());
diff --git a/web/src/lib/stores/user.svelte.ts b/web/src/lib/stores/user.svelte.ts
index 94d73efb9c..f9319fcfa1 100644
--- a/web/src/lib/stores/user.svelte.ts
+++ b/web/src/lib/stores/user.svelte.ts
@@ -26,4 +26,4 @@ const reset = () => {
Object.assign(userInteraction, defaultUserInteraction);
};
-eventManager.on('auth.logout', () => reset());
+eventManager.on('AuthLogout', () => reset());
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index 979c7b5c42..f5d4619943 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -58,7 +58,7 @@
// if the browser theme changes, changes the Immich theme too
});
- eventManager.emit('app.init');
+ eventManager.emit('AppInit');
beforeNavigate(({ from, to }) => {
if (isAssetViewerRoute(from) && isAssetViewerRoute(to)) {
diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte
index 9ed89a0c63..352eaed408 100644
--- a/web/src/routes/auth/login/+page.svelte
+++ b/web/src/routes/auth/login/+page.svelte
@@ -27,7 +27,7 @@
const onSuccess = async (user: LoginResponseDto) => {
await goto(data.continueUrl, { invalidateAll: true });
- eventManager.emit('auth.login', user);
+ eventManager.emit('AuthLogin', user);
};
const onFirstLogin = () => goto(AppRoute.AUTH_CHANGE_PASSWORD);