From 93ee6ee0a55a000ca17e277b6e081dc75f9396df Mon Sep 17 00:00:00 2001
From: Jason Rasmussen <jason@rasm.me>
Date: Mon, 12 May 2025 17:48:05 -0400
Subject: [PATCH] refactor: dialog controller (#18235)

---
 .../admin-page/jobs/jobs-panel.svelte         |  6 +--
 .../actions/keep-this-delete-others.svelte    |  6 +--
 .../face-editor/face-editor.svelte            |  7 +---
 .../faces-page/merge-face-selector.svelte     |  7 +---
 .../faces-page/person-side-panel.svelte       |  5 +--
 .../actions/remove-from-album.svelte          |  6 +--
 .../actions/remove-from-shared-link.svelte    |  4 +-
 .../dialog/dialog-wrapper.svelte              |  9 ----
 .../shared-components/dialog/dialog.ts        | 42 -------------------
 .../user-settings-page/device-list.svelte     | 13 +++---
 .../partner-settings.svelte                   |  3 +-
 .../user-api-key-list.svelte                  |  3 +-
 .../user-purchase-settings.svelte             | 24 +++++------
 web/src/lib/managers/modal-manager.svelte.ts  |  2 +-
 web/src/lib/utils/album-utils.ts              |  4 +-
 .../shared-links/[[id=id]]/+page.svelte       |  4 +-
 .../[[assetId=id]]/+page.svelte               |  6 +--
 .../[[assetId=id]]/+page.svelte               | 12 ++----
 .../[[assetId=id]]/+page.svelte               |  3 +-
 web/src/routes/+layout.svelte                 |  2 -
 .../admin/library-management/+page.svelte     |  7 ++--
 web/src/routes/admin/users/[id]/+page.svelte  |  4 +-
 22 files changed, 53 insertions(+), 126 deletions(-)
 delete mode 100644 web/src/lib/components/shared-components/dialog/dialog-wrapper.svelte
 delete mode 100644 web/src/lib/components/shared-components/dialog/dialog.ts

diff --git a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
index 2c59f59416..73dfb30908 100644
--- a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
+++ b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
@@ -3,6 +3,7 @@
     notificationController,
     NotificationType,
   } from '$lib/components/shared-components/notification/notification';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { featureFlags } from '$lib/stores/server-config.store';
   import { getJobName } from '$lib/utils';
   import { handleError } from '$lib/utils/handle-error';
@@ -20,10 +21,9 @@
     mdiVideo,
   } from '@mdi/js';
   import type { Component } from 'svelte';
+  import { t } from 'svelte-i18n';
   import JobTile from './job-tile.svelte';
   import StorageMigrationDescription from './storage-migration-description.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
-  import { t } from 'svelte-i18n';
 
   interface Props {
     jobs: AllJobStatusResponseDto;
@@ -45,7 +45,7 @@
 
   const handleConfirmCommand = async (jobId: JobName, dto: JobCommandDto) => {
     if (dto.force) {
-      const isConfirmed = await dialogController.show({
+      const isConfirmed = await modalManager.showDialog({
         prompt: $t('admin.confirm_reprocess_all_faces'),
       });
 
diff --git a/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte b/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte
index 8705476d8d..090e87f4a9 100644
--- a/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte
+++ b/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte
@@ -1,12 +1,12 @@
 <script lang="ts">
   import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
   import { AssetAction } from '$lib/constants';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { keepThisDeleteOthers } from '$lib/utils/asset-utils';
   import type { AssetResponseDto, StackResponseDto } from '@immich/sdk';
   import { mdiPinOutline } from '@mdi/js';
-  import type { OnAction } from './action';
   import { t } from 'svelte-i18n';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
+  import type { OnAction } from './action';
 
   interface Props {
     stack: StackResponseDto;
@@ -17,7 +17,7 @@
   let { stack, asset, onAction }: Props = $props();
 
   const handleKeepThisDeleteOthers = async () => {
-    const isConfirmed = await dialogController.show({
+    const isConfirmed = await modalManager.showDialog({
       title: $t('keep_this_delete_others'),
       prompt: $t('confirm_keep_this_delete_others'),
       confirmText: $t('delete_others'),
diff --git a/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte b/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte
index f25276aca9..00e00b9b92 100644
--- a/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte
+++ b/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte
@@ -1,7 +1,7 @@
 <script lang="ts">
   import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import { notificationController } from '$lib/components/shared-components/notification/notification';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { assetViewingStore } from '$lib/stores/asset-viewing.store';
   import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
   import { getPeopleThumbnailUrl } from '$lib/utils';
@@ -284,10 +284,7 @@
         return;
       }
 
-      const isConfirmed = await dialogController.show({
-        prompt: `Do you want to tag this face as ${person.name}?`,
-      });
-
+      const isConfirmed = await modalManager.showDialog({ prompt: `Do you want to tag this face as ${person.name}?` });
       if (!isConfirmed) {
         return;
       }
diff --git a/web/src/lib/components/faces-page/merge-face-selector.svelte b/web/src/lib/components/faces-page/merge-face-selector.svelte
index 2c57fcd120..d3ecd5a6da 100644
--- a/web/src/lib/components/faces-page/merge-face-selector.svelte
+++ b/web/src/lib/components/faces-page/merge-face-selector.svelte
@@ -2,8 +2,8 @@
   import { goto } from '$app/navigation';
   import { page } from '$app/state';
   import Icon from '$lib/components/elements/icon.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import { ActionQueryParameterValue, AppRoute, QueryParameter } from '$lib/constants';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { handleError } from '$lib/utils/handle-error';
   import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
   import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
@@ -69,10 +69,7 @@
   };
 
   const handleMerge = async () => {
-    const isConfirm = await dialogController.show({
-      prompt: $t('merge_people_prompt'),
-    });
-
+    const isConfirm = await modalManager.showDialog({ prompt: $t('merge_people_prompt') });
     if (!isConfirm) {
       return;
     }
diff --git a/web/src/lib/components/faces-page/person-side-panel.svelte b/web/src/lib/components/faces-page/person-side-panel.svelte
index 89b078ce2d..fbfa0d5eaa 100644
--- a/web/src/lib/components/faces-page/person-side-panel.svelte
+++ b/web/src/lib/components/faces-page/person-side-panel.svelte
@@ -1,9 +1,9 @@
 <script lang="ts">
   import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
   import Icon from '$lib/components/elements/icon.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
   import { timeBeforeShowLoadingSpinner } from '$lib/constants';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { assetViewingStore } from '$lib/stores/asset-viewing.store';
   import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
   import { boundingBoxesArray } from '$lib/stores/people.store';
@@ -173,10 +173,9 @@
         return;
       }
 
-      const isConfirmed = await dialogController.show({
+      const isConfirmed = await modalManager.showDialog({
         prompt: $t('confirm_delete_face', { values: { name: face.person.name } }),
       });
-
       if (!isConfirmed) {
         return;
       }
diff --git a/web/src/lib/components/photos-page/actions/remove-from-album.svelte b/web/src/lib/components/photos-page/actions/remove-from-album.svelte
index e1f7e33baa..91db8d0432 100644
--- a/web/src/lib/components/photos-page/actions/remove-from-album.svelte
+++ b/web/src/lib/components/photos-page/actions/remove-from-album.svelte
@@ -4,12 +4,12 @@
     NotificationType,
     notificationController,
   } from '$lib/components/shared-components/notification/notification';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { getAlbumInfo, removeAssetFromAlbum, type AlbumResponseDto } from '@immich/sdk';
   import { mdiDeleteOutline, mdiImageRemoveOutline } from '@mdi/js';
+  import { t } from 'svelte-i18n';
   import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
   import { getAssetControlContext } from '../asset-select-control-bar.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
-  import { t } from 'svelte-i18n';
 
   interface Props {
     album: AlbumResponseDto;
@@ -22,7 +22,7 @@
   const { getAssets, clearSelect } = getAssetControlContext();
 
   const removeFromAlbum = async () => {
-    const isConfirmed = await dialogController.show({
+    const isConfirmed = await modalManager.showDialog({
       prompt: $t('remove_assets_album_confirmation', { values: { count: getAssets().length } }),
     });
 
diff --git a/web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte b/web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte
index e1e803ad50..2c41f77cf9 100644
--- a/web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte
+++ b/web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte
@@ -1,7 +1,7 @@
 <script lang="ts">
   import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import { authManager } from '$lib/managers/auth-manager.svelte';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { handleError } from '$lib/utils/handle-error';
   import { removeSharedLinkAssets, type SharedLinkResponseDto } from '@immich/sdk';
   import { mdiDeleteOutline } from '@mdi/js';
@@ -18,7 +18,7 @@
   const { getAssets, clearSelect } = getAssetControlContext();
 
   const handleRemove = async () => {
-    const isConfirmed = await dialogController.show({
+    const isConfirmed = await modalManager.showDialog({
       title: $t('remove_assets_title'),
       prompt: $t('remove_assets_shared_link_confirmation', { values: { count: getAssets().length } }),
       confirmText: $t('remove'),
diff --git a/web/src/lib/components/shared-components/dialog/dialog-wrapper.svelte b/web/src/lib/components/shared-components/dialog/dialog-wrapper.svelte
deleted file mode 100644
index 65f194291b..0000000000
--- a/web/src/lib/components/shared-components/dialog/dialog-wrapper.svelte
+++ /dev/null
@@ -1,9 +0,0 @@
-<script lang="ts">
-  import { dialogController } from './dialog';
-  import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
-  const { dialog } = dialogController;
-</script>
-
-{#if $dialog}
-  <ConfirmDialog {...$dialog} />
-{/if}
diff --git a/web/src/lib/components/shared-components/dialog/dialog.ts b/web/src/lib/components/shared-components/dialog/dialog.ts
deleted file mode 100644
index 69a64aad21..0000000000
--- a/web/src/lib/components/shared-components/dialog/dialog.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { writable } from 'svelte/store';
-
-type DialogActions = {
-  onClose: (confirmed: boolean) => void;
-};
-
-type DialogOptions = {
-  title?: string;
-  prompt?: string;
-  confirmText?: string;
-  cancelText?: string;
-  hideCancelButton?: boolean;
-  disable?: boolean;
-  width?: 'wide' | 'narrow' | undefined;
-};
-
-export type Dialog = DialogOptions & DialogActions;
-
-function createDialogWrapper() {
-  const dialog = writable<Dialog | undefined>();
-
-  async function show(options: DialogOptions) {
-    return new Promise<boolean>((resolve) => {
-      const newDialog: Dialog = {
-        ...options,
-        onClose: (confirmed) => {
-          dialog.set(undefined);
-          resolve(confirmed);
-        },
-      };
-
-      dialog.set(newDialog);
-    });
-  }
-
-  return {
-    dialog,
-    show,
-  };
-}
-
-export const dialogController = createDialogWrapper();
diff --git a/web/src/lib/components/user-settings-page/device-list.svelte b/web/src/lib/components/user-settings-page/device-list.svelte
index 96870046df..a56f4cc316 100644
--- a/web/src/lib/components/user-settings-page/device-list.svelte
+++ b/web/src/lib/components/user-settings-page/device-list.svelte
@@ -1,11 +1,11 @@
 <script lang="ts">
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { deleteAllSessions, deleteSession, getSessions, type SessionResponseDto } from '@immich/sdk';
+  import { Button } from '@immich/ui';
+  import { t } from 'svelte-i18n';
   import { handleError } from '../../utils/handle-error';
   import { notificationController, NotificationType } from '../shared-components/notification/notification';
   import DeviceCard from './device-card.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
-  import { t } from 'svelte-i18n';
-  import { Button } from '@immich/ui';
 
   interface Props {
     devices: SessionResponseDto[];
@@ -19,10 +19,7 @@
   let otherDevices = $derived(devices.filter((device) => !device.current));
 
   const handleDelete = async (device: SessionResponseDto) => {
-    const isConfirmed = await dialogController.show({
-      prompt: $t('logout_this_device_confirmation'),
-    });
-
+    const isConfirmed = await modalManager.showDialog({ prompt: $t('logout_this_device_confirmation') });
     if (!isConfirmed) {
       return;
     }
@@ -38,7 +35,7 @@
   };
 
   const handleDeleteAll = async () => {
-    const isConfirmed = await dialogController.show({ prompt: $t('logout_all_device_confirmation') });
+    const isConfirmed = await modalManager.showDialog({ prompt: $t('logout_all_device_confirmation') });
     if (!isConfirmed) {
       return;
     }
diff --git a/web/src/lib/components/user-settings-page/partner-settings.svelte b/web/src/lib/components/user-settings-page/partner-settings.svelte
index b18390e28f..008d6476c7 100644
--- a/web/src/lib/components/user-settings-page/partner-settings.svelte
+++ b/web/src/lib/components/user-settings-page/partner-settings.svelte
@@ -1,5 +1,4 @@
 <script lang="ts">
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
   import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
   import { modalManager } from '$lib/managers/modal-manager.svelte';
@@ -81,7 +80,7 @@
   };
 
   const handleRemovePartner = async (partner: PartnerResponseDto) => {
-    const isConfirmed = await dialogController.show({
+    const isConfirmed = await modalManager.showDialog({
       title: $t('stop_photo_sharing'),
       prompt: $t('stop_photo_sharing_description', { values: { partner: partner.name } }),
     });
diff --git a/web/src/lib/components/user-settings-page/user-api-key-list.svelte b/web/src/lib/components/user-settings-page/user-api-key-list.svelte
index c1b39ff9a8..6aebab282c 100644
--- a/web/src/lib/components/user-settings-page/user-api-key-list.svelte
+++ b/web/src/lib/components/user-settings-page/user-api-key-list.svelte
@@ -1,6 +1,5 @@
 <script lang="ts">
   import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import { modalManager } from '$lib/managers/modal-manager.svelte';
   import ApiKeyModal from '$lib/modals/ApiKeyModal.svelte';
   import ApiKeySecretModal from '$lib/modals/ApiKeySecretModal.svelte';
@@ -88,7 +87,7 @@
   };
 
   const handleDelete = async (key: ApiKeyResponseDto) => {
-    const isConfirmed = await dialogController.show({ prompt: $t('delete_api_key_prompt') });
+    const isConfirmed = await modalManager.showDialog({ prompt: $t('delete_api_key_prompt') });
     if (!isConfirmed) {
       return;
     }
diff --git a/web/src/lib/components/user-settings-page/user-purchase-settings.svelte b/web/src/lib/components/user-settings-page/user-purchase-settings.svelte
index ba8aadd73f..5a3852cc8d 100644
--- a/web/src/lib/components/user-settings-page/user-purchase-settings.svelte
+++ b/web/src/lib/components/user-settings-page/user-purchase-settings.svelte
@@ -1,27 +1,27 @@
 <script lang="ts">
   import { fade } from 'svelte/transition';
 
-  import { onMount } from 'svelte';
+  import Icon from '$lib/components/elements/icon.svelte';
+  import PurchaseContent from '$lib/components/shared-components/purchasing/purchase-content.svelte';
+  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { purchaseStore } from '$lib/stores/purchase.store';
   import { preferences, user } from '$lib/stores/user.store';
+  import { handleError } from '$lib/utils/handle-error';
+  import { setSupportBadgeVisibility } from '$lib/utils/purchase-utils';
   import {
-    deleteServerLicense as deleteServerProductKey,
     deleteUserLicense as deleteIndividualProductKey,
+    deleteServerLicense as deleteServerProductKey,
     getAboutInfo,
     getMyUser,
     getServerLicense,
     isHttpError,
     type LicenseResponseDto,
   } from '@immich/sdk';
-  import Icon from '$lib/components/elements/icon.svelte';
-  import { mdiKey } from '@mdi/js';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
-  import { handleError } from '$lib/utils/handle-error';
-  import PurchaseContent from '$lib/components/shared-components/purchasing/purchase-content.svelte';
-  import { t } from 'svelte-i18n';
-  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
-  import { setSupportBadgeVisibility } from '$lib/utils/purchase-utils';
   import { Button } from '@immich/ui';
+  import { mdiKey } from '@mdi/js';
+  import { onMount } from 'svelte';
+  import { t } from 'svelte-i18n';
   const { isPurchased } = purchaseStore;
 
   let isServerProduct = $state(false);
@@ -62,7 +62,7 @@
 
   const removeIndividualProductKey = async () => {
     try {
-      const isConfirmed = await dialogController.show({
+      const isConfirmed = await modalManager.showDialog({
         title: $t('purchase_remove_product_key'),
         prompt: $t('purchase_remove_product_key_prompt'),
         confirmText: $t('remove'),
@@ -82,7 +82,7 @@
 
   const removeServerProductKey = async () => {
     try {
-      const isConfirmed = await dialogController.show({
+      const isConfirmed = await modalManager.showDialog({
         title: $t('purchase_remove_server_product_key'),
         prompt: $t('purchase_remove_server_product_key_prompt'),
         confirmText: $t('remove'),
diff --git a/web/src/lib/managers/modal-manager.svelte.ts b/web/src/lib/managers/modal-manager.svelte.ts
index 12f9224018..2c86f47787 100644
--- a/web/src/lib/managers/modal-manager.svelte.ts
+++ b/web/src/lib/managers/modal-manager.svelte.ts
@@ -34,7 +34,7 @@ class ModalManager {
     };
   }
 
-  openDialog(options: Omit<ComponentProps<typeof ConfirmDialog>, 'onClose'>) {
+  showDialog(options: Omit<ComponentProps<typeof ConfirmDialog>, 'onClose'>) {
     return this.show(ConfirmDialog, options);
   }
 }
diff --git a/web/src/lib/utils/album-utils.ts b/web/src/lib/utils/album-utils.ts
index a5b830774c..1a4097b78d 100644
--- a/web/src/lib/utils/album-utils.ts
+++ b/web/src/lib/utils/album-utils.ts
@@ -1,6 +1,6 @@
 import { goto } from '$app/navigation';
-import { dialogController } from '$lib/components/shared-components/dialog/dialog';
 import { AppRoute } from '$lib/constants';
+import { modalManager } from '$lib/managers/modal-manager.svelte';
 import {
   AlbumFilter,
   AlbumGroupBy,
@@ -213,7 +213,7 @@ export const confirmAlbumDelete = async (album: AlbumResponseDto) => {
   const description = $t('album_delete_confirmation_description');
   const prompt = `${confirmation} ${description}`;
 
-  return dialogController.show({ prompt });
+  return modalManager.showDialog({ prompt });
 };
 
 interface AlbumSortOption {
diff --git a/web/src/routes/(user)/shared-links/[[id=id]]/+page.svelte b/web/src/routes/(user)/shared-links/[[id=id]]/+page.svelte
index 1c1e1cfbd4..7062966b71 100644
--- a/web/src/routes/(user)/shared-links/[[id=id]]/+page.svelte
+++ b/web/src/routes/(user)/shared-links/[[id=id]]/+page.svelte
@@ -3,13 +3,13 @@
   import { page } from '$app/state';
   import GroupTab from '$lib/components/elements/group-tab.svelte';
   import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import {
     notificationController,
     NotificationType,
   } from '$lib/components/shared-components/notification/notification';
   import SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
   import { AppRoute } from '$lib/constants';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
   import { handleError } from '$lib/utils/handle-error';
   import { getAllSharedLinks, removeSharedLink, SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
@@ -35,7 +35,7 @@
   });
 
   const handleDeleteLink = async (id: string) => {
-    const isConfirmed = await dialogController.show({
+    const isConfirmed = await modalManager.showDialog({
       title: $t('delete_shared_link'),
       prompt: $t('confirm_delete_shared_link'),
       confirmText: $t('delete'),
diff --git a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 8d33a2eb6e..a12555c708 100644
--- a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -4,7 +4,6 @@
   import SkipLink from '$lib/components/elements/buttons/skip-link.svelte';
   import UserPageLayout, { headerId } from '$lib/components/layouts/user-page-layout.svelte';
   import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import {
     notificationController,
@@ -16,15 +15,16 @@
   import TreeItemThumbnails from '$lib/components/shared-components/tree/tree-item-thumbnails.svelte';
   import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte';
   import { AppRoute, AssetAction, QueryParameter, SettingInputFieldType } from '$lib/constants';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
   import { AssetStore } from '$lib/stores/assets-store.svelte';
   import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
   import { deleteTag, getAllTags, updateTag, upsertTags, type TagResponseDto } from '@immich/sdk';
   import { Button, HStack, Text } from '@immich/ui';
   import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js';
+  import { onDestroy } from 'svelte';
   import { t } from 'svelte-i18n';
   import type { PageData } from './$types';
-  import { onDestroy } from 'svelte';
 
   interface Props {
     data: PageData;
@@ -116,7 +116,7 @@
       return;
     }
 
-    const isConfirm = await dialogController.show({
+    const isConfirm = await modalManager.showDialog({
       title: $t('delete_tag'),
       prompt: $t('delete_tag_confirmation_prompt', { values: { tagName: tag.value } }),
       confirmText: $t('delete'),
diff --git a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 209f75a302..3750b239d5 100644
--- a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -7,13 +7,13 @@
   import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
   import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
   import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
   import {
     NotificationType,
     notificationController,
   } from '$lib/components/shared-components/notification/notification';
   import { AppRoute } from '$lib/constants';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
   import { AssetStore } from '$lib/stores/assets-store.svelte';
   import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
@@ -43,10 +43,7 @@
   const assetInteraction = new AssetInteraction();
 
   const handleEmptyTrash = async () => {
-    const isConfirmed = await dialogController.show({
-      prompt: $t('empty_trash_confirmation'),
-    });
-
+    const isConfirmed = await modalManager.showDialog({ prompt: $t('empty_trash_confirmation') });
     if (!isConfirmed) {
       return;
     }
@@ -64,10 +61,7 @@
   };
 
   const handleRestoreTrash = async () => {
-    const isConfirmed = await dialogController.show({
-      prompt: $t('assets_restore_confirmation'),
-    });
-
+    const isConfirmed = await modalManager.showDialog({ prompt: $t('assets_restore_confirmation') });
     if (!isConfirmed) {
       return;
     }
diff --git a/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 14b1420110..0bb581b885 100644
--- a/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -1,7 +1,6 @@
 <script lang="ts">
   import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
   import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import DuplicatesModal from '$lib/components/shared-components/duplicates-modal.svelte';
   import {
     NotificationType,
@@ -55,7 +54,7 @@
   let hasDuplicates = $derived(duplicates.length > 0);
   const withConfirmation = async (callback: () => Promise<void>, prompt?: string, confirmText?: string) => {
     if (prompt && confirmText) {
-      const isConfirmed = await dialogController.show({ prompt, confirmText });
+      const isConfirmed = await modalManager.showDialog({ prompt, confirmText });
       if (!isConfirmed) {
         return;
       }
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index 844037a13f..3a6320a265 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -5,7 +5,6 @@
   import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
   import ErrorLayout from '$lib/components/layouts/ErrorLayout.svelte';
   import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
-  import DialogWrapper from '$lib/components/shared-components/dialog/dialog-wrapper.svelte';
   import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
   import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
   import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
@@ -122,7 +121,6 @@
 <DownloadPanel />
 <UploadPanel />
 <NotificationList />
-<DialogWrapper />
 
 {#if $user?.isAdmin}
   <VersionAnnouncementBox />
diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/+page.svelte
index 10865628a8..e5d635bf18 100644
--- a/web/src/routes/admin/library-management/+page.svelte
+++ b/web/src/routes/admin/library-management/+page.svelte
@@ -7,13 +7,13 @@
   import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
   import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
   import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
-  import { dialogController } from '$lib/components/shared-components/dialog/dialog';
   import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
   import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
   import {
     notificationController,
     NotificationType,
   } from '$lib/components/shared-components/notification/notification';
+  import { modalManager } from '$lib/managers/modal-manager.svelte';
   import { locale } from '$lib/stores/preferences.store';
   import { ByteUnit, getBytesWithUnit } from '$lib/utils/byte-units';
   import { handleError } from '$lib/utils/handle-error';
@@ -210,7 +210,7 @@
       return;
     }
 
-    const isConfirmed = await dialogController.show({
+    const isConfirmed = await modalManager.showDialog({
       prompt: $t('admin.confirm_delete_library', { values: { library: library.name } }),
     });
 
@@ -221,10 +221,9 @@
     await refreshStats(index);
     const assetCount = totalCount[index];
     if (assetCount > 0) {
-      const isConfirmed = await dialogController.show({
+      const isConfirmed = await modalManager.showDialog({
         prompt: $t('admin.confirm_delete_library_assets', { values: { count: assetCount } }),
       });
-
       if (!isConfirmed) {
         return;
       }
diff --git a/web/src/routes/admin/users/[id]/+page.svelte b/web/src/routes/admin/users/[id]/+page.svelte
index b0a9327fcc..d5dfda92e2 100644
--- a/web/src/routes/admin/users/[id]/+page.svelte
+++ b/web/src/routes/admin/users/[id]/+page.svelte
@@ -97,7 +97,7 @@
   };
 
   const handleResetPassword = async () => {
-    const isConfirmed = await modalManager.openDialog({
+    const isConfirmed = await modalManager.showDialog({
       prompt: $t('admin.confirm_user_password_reset', { values: { user: user.name } }),
     });
 
@@ -123,7 +123,7 @@
   };
 
   const handleResetUserPinCode = async () => {
-    const isConfirmed = await modalManager.openDialog({
+    const isConfirmed = await modalManager.showDialog({
       prompt: $t('admin.confirm_user_pin_code_reset', { values: { user: user.name } }),
     });