From 389356149aeed73c70702b7aaf8c04e6b5cef598 Mon Sep 17 00:00:00 2001
From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Date: Thu, 26 Mar 2026 18:18:06 +0100
Subject: [PATCH] refactor: actionable toasts (#27203)
---
pnpm-lock.yaml | 10 +++----
web/package.json | 2 +-
web/src/lib/components/ToastAction.svelte | 33 -----------------------
web/src/lib/services/album.service.ts | 28 ++++---------------
web/src/lib/utils/actions.ts | 26 ++++++------------
web/src/lib/utils/asset-utils.ts | 16 ++++-------
6 files changed, 24 insertions(+), 91 deletions(-)
delete mode 100644 web/src/lib/components/ToastAction.svelte
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 320815b631..0ffd7e02c4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -744,8 +744,8 @@ importers:
specifier: workspace:*
version: link:../open-api/typescript-sdk
'@immich/ui':
- specifier: ^0.65.3
- version: 0.65.3(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.13)(vite@8.0.0(@types/node@25.4.0)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.13)(typescript@5.9.3)(vite@8.0.0(@types/node@25.4.0)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.13)
+ specifier: ^0.67.2
+ version: 0.67.2(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.13)(vite@8.0.0(@types/node@25.4.0)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.13)(typescript@5.9.3)(vite@8.0.0(@types/node@25.4.0)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.13)
'@mapbox/mapbox-gl-rtl-text':
specifier: 0.3.0
version: 0.3.0
@@ -3038,8 +3038,8 @@ packages:
peerDependencies:
svelte: ^5.0.0
- '@immich/ui@0.65.3':
- resolution: {integrity: sha512-jMXzCzMNTcCdWXt9IUP7GkALE5oEvPQk/jCOuI2bfxsxCZFzMkUfUS+AV83Vg1vQ6l+g39PbKSPKBEzv125ATQ==}
+ '@immich/ui@0.67.2':
+ resolution: {integrity: sha512-GsaoJRiRORJ34CT+W3pAOdhbLr61nNlgFaOzDcnVnSWFonu7+HR3CXdCxbSdyU4+r3xEmcawwo6rMuLgRplHfw==}
peerDependencies:
svelte: ^5.0.0
@@ -15123,7 +15123,7 @@ snapshots:
node-emoji: 2.2.0
svelte: 5.53.13
- '@immich/ui@0.65.3(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.13)(vite@8.0.0(@types/node@25.4.0)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.13)(typescript@5.9.3)(vite@8.0.0(@types/node@25.4.0)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.13)':
+ '@immich/ui@0.67.2(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.13)(vite@8.0.0(@types/node@25.4.0)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.13)(typescript@5.9.3)(vite@8.0.0(@types/node@25.4.0)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.13)':
dependencies:
'@immich/svelte-markdown-preprocess': 0.2.1(svelte@5.53.13)
'@internationalized/date': 3.10.0
diff --git a/web/package.json b/web/package.json
index 53d95906cb..b6f4ca6c4e 100644
--- a/web/package.json
+++ b/web/package.json
@@ -27,7 +27,7 @@
"@formatjs/icu-messageformat-parser": "^3.0.0",
"@immich/justified-layout-wasm": "^0.4.3",
"@immich/sdk": "workspace:*",
- "@immich/ui": "^0.65.3",
+ "@immich/ui": "^0.67.2",
"@mapbox/mapbox-gl-rtl-text": "0.3.0",
"@mdi/js": "^7.4.47",
"@photo-sphere-viewer/core": "^5.14.0",
diff --git a/web/src/lib/components/ToastAction.svelte b/web/src/lib/components/ToastAction.svelte
deleted file mode 100644
index 5dc430f323..0000000000
--- a/web/src/lib/components/ToastAction.svelte
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
- {#if button}
-
-
-
- {/if}
-
-
diff --git a/web/src/lib/services/album.service.ts b/web/src/lib/services/album.service.ts
index 6ccd67584e..3a70d72478 100644
--- a/web/src/lib/services/album.service.ts
+++ b/web/src/lib/services/album.service.ts
@@ -1,5 +1,4 @@
import { goto } from '$app/navigation';
-import ToastAction from '$lib/components/ToastAction.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
@@ -138,16 +137,8 @@ const notifyAddToAlbum = ($t: MessageFormatter, albumId: string, assetIds: strin
description = $t('assets_were_part_of_album_count', { values: { count: duplicateCount } });
}
- toastManager.custom(
- {
- component: ToastAction,
- props: {
- title: $t('info'),
- color: 'primary',
- description,
- button: { text: $t('view_album'), color: 'primary', onClick: () => goto(Route.viewAlbum({ id: albumId })) },
- },
- },
+ toastManager.primary(
+ { description, button: { label: $t('view_album'), onclick: () => goto(Route.viewAlbum({ id: albumId })) } },
{ timeout: 5000 },
);
};
@@ -229,18 +220,9 @@ export const handleUpdateAlbum = async ({ id }: { id: string }, dto: UpdateAlbum
try {
const response = await updateAlbumInfo({ id, updateAlbumDto: dto });
eventManager.emit('AlbumUpdate', response);
- toastManager.custom({
- component: ToastAction,
- props: {
- color: 'primary',
- title: $t('success'),
- description: $t('album_info_updated'),
- button: {
- text: $t('view_album'),
- color: 'primary',
- onClick: () => goto(Route.viewAlbum({ id })),
- },
- },
+ toastManager.primary({
+ description: $t('album_info_updated'),
+ button: { label: $t('view_album'), onclick: () => goto(Route.viewAlbum({ id })) },
});
return true;
diff --git a/web/src/lib/utils/actions.ts b/web/src/lib/utils/actions.ts
index 05de75d3bc..46265a78bb 100644
--- a/web/src/lib/utils/actions.ts
+++ b/web/src/lib/utils/actions.ts
@@ -1,4 +1,3 @@
-import ToastAction from '$lib/components/ToastAction.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import type { StackResponse } from '$lib/utils/asset-utils';
@@ -32,24 +31,15 @@ export const deleteAssets = async (
await deleteBulk({ assetBulkDeleteDto: { ids, force } });
onAssetDelete(ids);
- toastManager.custom(
+ toastManager.primary(
{
- component: ToastAction,
- props: {
- title: $t('success'),
- description: force
- ? $t('assets_permanently_deleted_count', { values: { count: ids.length } })
- : $t('assets_trashed_count', { values: { count: ids.length } }),
- color: 'success',
- button:
- onUndoDelete && !force
- ? {
- color: 'secondary',
- text: $t('undo'),
- onClick: () => undoDeleteAssets(onUndoDelete, assets),
- }
- : undefined,
- },
+ description: force
+ ? $t('assets_permanently_deleted_count', { values: { count: ids.length } })
+ : $t('assets_trashed_count', { values: { count: ids.length } }),
+ button:
+ onUndoDelete && !force
+ ? { label: $t('undo'), color: 'secondary', onclick: () => undoDeleteAssets(onUndoDelete, assets) }
+ : undefined,
},
{ timeout: 5000 },
);
diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts
index f82377b63f..284f540d1a 100644
--- a/web/src/lib/utils/asset-utils.ts
+++ b/web/src/lib/utils/asset-utils.ts
@@ -1,4 +1,3 @@
-import ToastAction from '$lib/components/ToastAction.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { downloadManager } from '$lib/managers/download-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
@@ -326,16 +325,11 @@ export const stackAssets = async (assets: { id: string }[], showNotification = t
try {
const stack = await createStack({ stackCreateDto: { assetIds: assets.map(({ id }) => id) } });
if (showNotification) {
- toastManager.custom({
- component: ToastAction,
- props: {
- title: $t('success'),
- description: $t('stacked_assets_count', { values: { count: stack.assets.length } }),
- color: 'success',
- button: {
- text: $t('view_stack'),
- onClick: () => navigate({ targetRoute: 'current', assetId: stack.primaryAssetId }),
- },
+ toastManager.primary({
+ description: $t('stacked_assets_count', { values: { count: stack.assets.length } }),
+ button: {
+ label: $t('view_stack'),
+ onclick: () => navigate({ targetRoute: 'current', assetId: stack.primaryAssetId }),
},
});
}