diff --git a/web/src/lib/components/asset-viewer/face-editor/FaceEditor.svelte b/web/src/lib/components/asset-viewer/face-editor/FaceEditor.svelte index 5ad016ae7..915fec539 100644 --- a/web/src/lib/components/asset-viewer/face-editor/FaceEditor.svelte +++ b/web/src/lib/components/asset-viewer/face-editor/FaceEditor.svelte @@ -312,20 +312,17 @@ return; } - const isConfirmed = await modalManager.showDialog({ - prompt: person.name ? $t('confirm_tag_face', { values: { name: person.name } }) : $t('confirm_tag_face_unnamed'), - }); - - if (!isConfirmed) { - return; - } - const onSuccess = async () => { await assetViewerManager.setAssetId(assetId); faceManager.clear(); }; await executeMutation(createFaceMutation, [{ assetFaceCreateDto: { assetId, personId: person.id, ...data } }], { + confirm: { + prompt: person.name + ? $t('confirm_tag_face', { values: { name: person.name } }) + : $t('confirm_tag_face_unnamed'), + }, onSuccess: () => void onSuccess(), onSettled: onClose, }); diff --git a/web/src/lib/components/faces-page/PersonSidePanel.svelte b/web/src/lib/components/faces-page/PersonSidePanel.svelte index 65b21e9e5..e202b7085 100644 --- a/web/src/lib/components/faces-page/PersonSidePanel.svelte +++ b/web/src/lib/components/faces-page/PersonSidePanel.svelte @@ -12,7 +12,7 @@ import { zoomImageToBase64 } from '#lib/utils/people-utils.ts'; import { getPersonNameWithHiddenValue } from '#lib/utils/person.ts'; import { AssetTypeEnum, createPerson, type AssetFaceResponseDto, type PersonResponseDto } from '@immich/sdk'; - import { Icon, IconButton, LoadingSpinner, modalManager, toastManager } from '@immich/ui'; + import { Icon, IconButton, LoadingSpinner, toastManager } from '@immich/ui'; import { mdiAccountOff, mdiArrowLeftThin, mdiArrowRightThin, mdiPencil, mdiRestart, mdiTrashCan } from '@mdi/js'; import { createQuery } from '@tanstack/svelte-query'; import { t } from 'svelte-i18n'; @@ -151,14 +151,8 @@ return; } - const isConfirmed = await modalManager.showDialog({ - prompt: $t('confirm_delete_face', { values: { name: person.name } }), - }); - if (!isConfirmed) { - return; - } - await executeMutation(deleteFaceMutation, [{ id: face.id, assetFaceDeleteDto: { force: false } }], { + confirm: { prompt: $t('confirm_delete_face', { values: { name: person.name } }) }, onSuccess: () => { eventManager.emit('PersonAssetDelete', { id: person.id, assetId }); void assetViewerManager.setAssetId(assetId); diff --git a/web/src/lib/components/timeline/actions/RemoveFromSharedLinkAction.svelte b/web/src/lib/components/timeline/actions/RemoveFromSharedLinkAction.svelte index ddbda3252..68a2d5902 100644 --- a/web/src/lib/components/timeline/actions/RemoveFromSharedLinkAction.svelte +++ b/web/src/lib/components/timeline/actions/RemoveFromSharedLinkAction.svelte @@ -4,7 +4,7 @@ import { executeMutation } from '#lib/queries/client.ts'; import { removeSharedLinkAssetsMutation } from '#lib/queries/shared-link.query.ts'; import { type SharedLinkResponseDto } from '@immich/sdk'; - import { IconButton, modalManager, toastManager } from '@immich/ui'; + import { IconButton, toastManager } from '@immich/ui'; import { mdiDeleteOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -16,16 +16,12 @@ const onAction = async () => { const assetIds = assetMultiSelectManager.assets.map(({ id }) => id); - const success = await modalManager.showDialog({ - title: $t('remove_assets_title'), - prompt: $t('remove_assets_shared_link_confirmation', { values: { count: assetIds.length } }), - confirmText: $t('remove'), - }); - if (!success) { - return; - } - await executeMutation(removeSharedLinkAssetsMutation, [{ id: sharedLink.id, assetIdsDto: { assetIds } }], { + confirm: { + title: $t('remove_assets_title'), + prompt: $t('remove_assets_shared_link_confirmation', { values: { count: assetIds.length } }), + confirmText: $t('remove'), + }, onSuccess: (results) => { const removed = new Set(results.filter(({ success }) => success).map(({ assetId }) => assetId)); sharedLink.assets = sharedLink.assets.filter(({ id }) => !removed.has(id)); diff --git a/web/src/lib/queries/client.spec.ts b/web/src/lib/queries/client.spec.ts index c631fd801..34d6eb1e7 100644 --- a/web/src/lib/queries/client.spec.ts +++ b/web/src/lib/queries/client.spec.ts @@ -1,8 +1,10 @@ -import { queryClient } from '#lib/queries/client.ts'; +import { executeMutation, queryClient } from '#lib/queries/client.ts'; import { getAdminConfigQuery } from '#lib/queries/config-admin.query.ts'; import { getPublicConfigQuery } from '#lib/queries/config-public.query.ts'; import { getUserConfigQuery } from '#lib/queries/config-user.query.ts'; import { getServerConfigQuery } from '#lib/queries/server-info.query.ts'; +import { deleteTagMutation } from '#lib/queries/tag.query.ts'; +import { factory } from '#test/unit/factories.ts'; import { test } from '#test/unit/fixtures.ts'; import { QueryObserver, type QueryKey, type QueryObserverOptions } from '@tanstack/svelte-query'; import { noop } from 'lodash-es'; @@ -50,3 +52,34 @@ describe('queryClient', () => { }); }); }); + +describe('executeMutation', () => { + const confirm = { prompt: 'delete_tag_confirmation_prompt' }; + + test('runs the mutation once the confirm is accepted', async ({ data, api, app, settle }) => { + const tag = factory.tag(); + data.addTags(tag); + const onSuccess = vi.fn<() => void>(); + + const result = executeMutation(deleteTagMutation, [{ id: tag.id }], { confirm, onSuccess }); + await app.confirmButton.click(); + await result; + await settle(); + + expect(api.deleteTag).toHaveBeenCalledWith({ params: { id: tag.id } }); + expect(onSuccess).toHaveBeenCalled(); + }); + + test('skips the mutation when the confirm is declined', async ({ data, api, app }) => { + const tag = factory.tag(); + data.addTags(tag); + const onSuccess = vi.fn<() => void>(); + + const result = executeMutation(deleteTagMutation, [{ id: tag.id }], { confirm, onSuccess }); + await app.cancelButton.click(); + await result; + + expect(api.deleteTag).not.toHaveBeenCalled(); + expect(onSuccess).not.toHaveBeenCalled(); + }); +}); diff --git a/web/src/lib/queries/client.ts b/web/src/lib/queries/client.ts index 7c051b7ce..fb449e835 100644 --- a/web/src/lib/queries/client.ts +++ b/web/src/lib/queries/client.ts @@ -3,7 +3,7 @@ import { getPublicConfigQuery } from '#lib/queries/config-public.query.ts'; import { getServerConfigQuery } from '#lib/queries/server-info.query.ts'; import { handleError } from '#lib/utils/handle-error.ts'; import { getFormatter } from '#lib/utils/i18n.ts'; -import { toastManager } from '@immich/ui'; +import { modalManager, toastManager } from '@immich/ui'; import { MutationCache, MutationObserver, @@ -130,14 +130,27 @@ export const createPreloadedQuery = queryClient, ); -type MutationArgs = TVariables extends void - ? [variables?: TVariables, mutateOptions?: MutateOptions] - : [variables: TVariables, mutateOptions?: MutateOptions]; +export type ConfirmOptions = Parameters[0]; -export const executeMutation = ( +type ExecuteMutationOptions = MutateOptions< + TData, + TError, + TVariables, + TOnMutateResult +> & { confirm?: ConfirmOptions | false }; + +type MutationArgs = TVariables extends void + ? [variables?: TVariables, mutateOptions?: ExecuteMutationOptions] + : [variables: TVariables, mutateOptions?: ExecuteMutationOptions]; + +export const executeMutation = async ( options: () => CreateMutationOptions, - ...[variables, mutateOptions]: MutationArgs + ...[variables, { confirm, ...mutateOptions } = {}]: MutationArgs ) => { + if (confirm && !(await modalManager.showDialog(confirm))) { + return; + } + const observer = new MutationObserver(queryClient, options()); const unsubscribe = observer.subscribe(noop); return observer diff --git a/web/src/lib/services/album.service.ts b/web/src/lib/services/album.service.ts index 372d2c9de..b22113a9d 100644 --- a/web/src/lib/services/album.service.ts +++ b/web/src/lib/services/album.service.ts @@ -176,33 +176,20 @@ export const handleAddUsersToAlbum = (album: AlbumResponseDto, users: UserRespon export const handleRemoveUserFromAlbum = async (album: AlbumResponseDto, albumUser: UserResponseDto) => { const $t = await getFormatter(); - const confirmed = await modalManager.showDialog({ - title: $t('album_remove_user'), - prompt: $t('album_remove_user_confirmation', { values: { user: albumUser.name } }), - confirmText: $t('remove_user'), + await executeMutation(removeUserFromAlbumMutation, [{ id: album.id, userId: albumUser.id }], { + confirm: { + title: $t('album_remove_user'), + prompt: $t('album_remove_user_confirmation', { values: { user: albumUser.name } }), + confirmText: $t('remove_user'), + }, }); - - if (!confirmed) { - return; - } - - await executeMutation(removeUserFromAlbumMutation, [{ id: album.id, userId: albumUser.id }]); }; export const handleLeaveAlbum = async (album: AlbumResponseDto) => { const $t = await getFormatter(); - const confirmed = await modalManager.showDialog({ - title: $t('leave_album'), - prompt: $t('are_you_sure_to_do_this'), - confirmText: $t('leave'), - }); - - if (!confirmed) { - return; - } - await executeMutation(removeUserFromAlbumMutation, [{ id: album.id, userId: 'me' }], { + confirm: { title: $t('leave_album'), prompt: $t('are_you_sure_to_do_this'), confirmText: $t('leave') }, onSuccess: () => leaveAlbumPage(album), }); }; @@ -221,17 +208,11 @@ export const handleDeleteAlbum = async (album: AlbumResponseDto, options?: { pro const $t = await getFormatter(); const { prompt = true, notify = true } = options ?? {}; - if (prompt) { - const confirmation = - album.albumName.length > 0 - ? $t('album_delete_confirmation', { values: { album: album.albumName } }) - : $t('unnamed_album_delete_confirmation'); - const description = $t('album_delete_confirmation_description'); - const success = await modalManager.showDialog({ prompt: `${confirmation} ${description}` }); - if (!success) { - return; - } - } + const confirmation = + album.albumName.length > 0 + ? $t('album_delete_confirmation', { values: { album: album.albumName } }) + : $t('unnamed_album_delete_confirmation'); + const description = $t('album_delete_confirmation_description'); await executeMutation( () => @@ -239,7 +220,10 @@ export const handleDeleteAlbum = async (album: AlbumResponseDto, options?: { pro notify ? { success: { toast: true }, error: { toast: 'errors.unable_to_delete_album' } } : {}, ), [{ id: album.id }], - { onSuccess: () => leaveAlbumPage(album) }, + { + confirm: prompt && { prompt: `${confirmation} ${description}` }, + onSuccess: () => leaveAlbumPage(album), + }, ); }; diff --git a/web/src/lib/services/api-key.service.ts b/web/src/lib/services/api-key.service.ts index 306c65970..769068aae 100644 --- a/web/src/lib/services/api-key.service.ts +++ b/web/src/lib/services/api-key.service.ts @@ -44,12 +44,8 @@ export const getApiKeyActions = ($t: MessageFormatter, apiKey: ApiKeyResponseDto const handleRotateApiKey = async (apiKey: ApiKeyResponseDto) => { const $t = await getFormatter(); - const confirmed = await modalManager.showDialog({ prompt: $t('rotate_api_key_prompt') }); - if (!confirmed) { - return; - } - await executeMutation(rotateApiKeyMutation, [{ id: apiKey.id }], { + confirm: { prompt: $t('rotate_api_key_prompt') }, onSuccess: ({ secret }) => void modalManager.show(ApiKeySecretModal, { secret }), }); }; @@ -57,12 +53,8 @@ const handleRotateApiKey = async (apiKey: ApiKeyResponseDto) => { const handleDeleteApiKey = async (apiKey: ApiKeyResponseDto) => { const $t = await getFormatter(); - const confirmed = await modalManager.showDialog({ prompt: $t('delete_api_key_prompt') }); - if (!confirmed) { - return; - } - await executeMutation(deleteApiKeyMutation, [{ id: apiKey.id }], { + confirm: { prompt: $t('delete_api_key_prompt') }, onSuccess: () => toastManager.primary($t('removed_api_key', { values: { name: apiKey.name } })), }); }; diff --git a/web/src/lib/services/asset.service.ts b/web/src/lib/services/asset.service.ts index 51096ce22..14bcbbcd6 100644 --- a/web/src/lib/services/asset.service.ts +++ b/web/src/lib/services/asset.service.ts @@ -9,7 +9,7 @@ import AssetTagModal from '#lib/modals/AssetTagModal.svelte'; import ProfileImageCropperModal from '#lib/modals/ProfileImageCropperModal.svelte'; import SharedLinkCreateModal from '#lib/modals/SharedLinkCreateModal.svelte'; import { removeAssetFromAlbumMutation } from '#lib/queries/album.query.ts'; -import { executeMutation, readPreloadedQuery } from '#lib/queries/client.ts'; +import { executeMutation, readPreloadedQuery, type ConfirmOptions } from '#lib/queries/client.ts'; import { getUserConfigQuery } from '#lib/queries/config-user.query.ts'; import { Route } from '#lib/route.ts'; import { SlideshowState, slideshowStore } from '#lib/stores/slideshow.store.ts'; @@ -603,22 +603,21 @@ const undoArchiveAssets = async (assets: TimelineAsset[]) => { const handleBulkRemoveAssetsFromAlbum = async (assetIds: string[], album: AlbumResponseDto) => { const $t = await getFormatter(); - const isConfirmed = await modalManager.showDialog({ - prompt: $t('remove_assets_album_confirmation', { values: { count: assetIds.length } }), + await handleRemoveAssetsFromAlbum(assetIds, album, { + confirm: { prompt: $t('remove_assets_album_confirmation', { values: { count: assetIds.length } }) }, + onSettled: () => assetMultiSelectManager.clear(), }); - - if (!isConfirmed) { - return; - } - - await handleRemoveAssetsFromAlbum(assetIds, album); - assetMultiSelectManager.clear(); }; -const handleRemoveAssetsFromAlbum = async (assetIds: string[], album: AlbumResponseDto) => { +const handleRemoveAssetsFromAlbum = async ( + assetIds: string[], + album: AlbumResponseDto, + options: { confirm?: ConfirmOptions | false; onSettled?: () => void } = {}, +) => { const $t = await getFormatter(); await executeMutation(removeAssetFromAlbumMutation, [{ id: album.id, bulkIdsDto: { ids: assetIds } }], { + ...options, onSuccess: (results) => { const count = results.filter(({ success }) => success).length; toastManager.primary($t('assets_removed_count', { values: { count } })); diff --git a/web/src/lib/services/library.service.ts b/web/src/lib/services/library.service.ts index 2619779f0..caa5fdc89 100644 --- a/web/src/lib/services/library.service.ts +++ b/web/src/lib/services/library.service.ts @@ -123,16 +123,10 @@ const handleDeleteLibrary = async (library: LibraryResponseDto) => { return; } - if (library.assetCount > 0) { - const isConfirmed = await modalManager.showDialog({ - prompt: $t('admin.confirm_delete_library_assets', { values: { count: library.assetCount } }), - }); - if (!isConfirmed) { - return; - } - } - await executeMutation(deleteLibraryMutation, [{ id: library.id }], { + confirm: library.assetCount > 0 && { + prompt: $t('admin.confirm_delete_library_assets', { values: { count: library.assetCount } }), + }, onSuccess: () => void goto(Route.libraries()), }); }; @@ -140,27 +134,17 @@ const handleDeleteLibrary = async (library: LibraryResponseDto) => { const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: string) => { const $t = await getFormatter(); - const confirmed = await modalManager.showDialog({ - prompt: $t('admin.library_remove_folder_prompt'), - confirmText: $t('remove'), - }); - - if (!confirmed) { - return; - } - const importPaths = library.importPaths.filter((path) => path !== folder); - await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { importPaths } }]); + await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { importPaths } }], { + confirm: { prompt: $t('admin.library_remove_folder_prompt'), confirmText: $t('remove') }, + }); }; const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusionPattern: string) => { const $t = await getFormatter(); - const confirmed = await modalManager.showDialog({ prompt: $t('admin.library_remove_exclusion_pattern_prompt') }); - if (!confirmed) { - return; - } - const exclusionPatterns = library.exclusionPatterns.filter((pattern) => pattern !== exclusionPattern); - await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { exclusionPatterns } }]); + await executeMutation(updateLibraryMutation, [{ id: library.id, updateLibraryDto: { exclusionPatterns } }], { + confirm: { prompt: $t('admin.library_remove_exclusion_pattern_prompt') }, + }); }; diff --git a/web/src/lib/services/passkey.service.ts b/web/src/lib/services/passkey.service.ts index 57d9871c7..560dd411a 100644 --- a/web/src/lib/services/passkey.service.ts +++ b/web/src/lib/services/passkey.service.ts @@ -41,12 +41,8 @@ export const getPasskeyActions = ($t: MessageFormatter, passkey: PasskeyResponse const handleDeletePasskey = async (passkey: PasskeyResponseDto) => { const $t = await getFormatter(); - const confirmed = await modalManager.showDialog({ prompt: $t('delete_passkey_prompt') }); - if (!confirmed) { - return; - } - await executeMutation(deletePasskeyMutation, [{ id: passkey.id }], { + confirm: { prompt: $t('delete_passkey_prompt') }, onSuccess: () => toastManager.primary( $t('removed_passkey', { values: { name: passkey.name ?? $t('passkeys', { values: { count: 1 } }) } }), diff --git a/web/src/lib/services/shared-link.service.ts b/web/src/lib/services/shared-link.service.ts index 26939905d..45bd702c2 100644 --- a/web/src/lib/services/shared-link.service.ts +++ b/web/src/lib/services/shared-link.service.ts @@ -62,14 +62,7 @@ export const showSharedLinkQrCode = async (sharedLink: SharedLinkResponseDto) => const handleDeleteSharedLink = async (sharedLink: SharedLinkResponseDto) => { const $t = await getFormatter(); - const success = await modalManager.showDialog({ - title: $t('delete_shared_link'), - prompt: $t('confirm_delete_shared_link'), - confirmText: $t('delete'), + await executeMutation(removeSharedLinkMutation, [{ id: sharedLink.id }], { + confirm: { title: $t('delete_shared_link'), prompt: $t('confirm_delete_shared_link'), confirmText: $t('delete') }, }); - if (!success) { - return; - } - - await executeMutation(removeSharedLinkMutation, [{ id: sharedLink.id }]); }; diff --git a/web/src/lib/services/tag.service.ts b/web/src/lib/services/tag.service.ts index 78c42fcc7..c0c2f64f5 100644 --- a/web/src/lib/services/tag.service.ts +++ b/web/src/lib/services/tag.service.ts @@ -33,17 +33,12 @@ export const getTagActions = ($t: MessageFormatter, tag: TreeNode) => { return; } - const confirmed = await modalManager.showDialog({ - title: $t('delete_tag'), - prompt: $t('delete_tag_confirmation_prompt', { values: { tagName: tag.value } }), - confirmText: $t('delete'), - }); - - if (!confirmed) { - return; - } - await executeMutation(deleteTagMutation, [{ id: tagId }], { + confirm: { + title: $t('delete_tag'), + prompt: $t('delete_tag_confirmation_prompt', { values: { tagName: tag.value } }), + confirmText: $t('delete'), + }, onSuccess: () => void goto(Route.tags({ path: tag.parent?.path ?? '' })), }); }, diff --git a/web/src/lib/services/user-admin.service.ts b/web/src/lib/services/user-admin.service.ts index 459df9355..a6077128e 100644 --- a/web/src/lib/services/user-admin.service.ts +++ b/web/src/lib/services/user-admin.service.ts @@ -112,27 +112,20 @@ const generatePassword = (length: number = 16) => { const handleResetPasswordUserAdmin = async (user: UserAdminResponseDto) => { const $t = await getFormatter(); - const prompt = $t('admin.confirm_user_password_reset', { values: { user: user.name } }); - const success = await modalManager.showDialog({ prompt }); - if (!success) { - return; - } - const password = generatePassword(); await executeMutation( updateUserAdminMutation, [{ id: user.id, userAdminUpdateDto: { password, shouldChangePassword: true } }], - { onSuccess: () => void modalManager.show(PasswordResetSuccessModal, { newPassword: password }) }, + { + confirm: { prompt: $t('admin.confirm_user_password_reset', { values: { user: user.name } }) }, + onSuccess: () => void modalManager.show(PasswordResetSuccessModal, { newPassword: password }), + }, ); }; const handleResetPinCodeUserAdmin = async (user: UserAdminResponseDto) => { const $t = await getFormatter(); - const prompt = $t('admin.confirm_user_pin_code_reset', { values: { user: user.name } }); - const success = await modalManager.showDialog({ prompt }); - if (!success) { - return; - } - - await executeMutation(updateUserAdminMutation, [{ id: user.id, userAdminUpdateDto: { pinCode: null } }]); + await executeMutation(updateUserAdminMutation, [{ id: user.id, userAdminUpdateDto: { pinCode: null } }], { + confirm: { prompt: $t('admin.confirm_user_pin_code_reset', { values: { user: user.name } }) }, + }); }; diff --git a/web/src/lib/services/workflow.service.ts b/web/src/lib/services/workflow.service.ts index 45d95c265..5cca67ec2 100644 --- a/web/src/lib/services/workflow.service.ts +++ b/web/src/lib/services/workflow.service.ts @@ -130,16 +130,8 @@ export const getWorkflowShowSchemaAction = ( const handleDeleteWorkflow = async (workflow: WorkflowResponseDto) => { const $t = await getFormatter(); - const confirmed = await modalManager.showDialog({ - prompt: $t('workflow_delete_prompt'), - confirmColor: 'danger', - }); - - if (!confirmed) { - return; - } - await executeMutation(deleteWorkflowMutation, [{ id: workflow.id }], { + confirm: { prompt: $t('workflow_delete_prompt'), confirmColor: 'danger' }, onSuccess: () => { if (page.params.workflowId === workflow.id) { void goto(Route.workflows()); diff --git a/web/src/routes/(user)/user-settings/DeviceList.svelte b/web/src/routes/(user)/user-settings/DeviceList.svelte index 544182063..ff0d12e18 100644 --- a/web/src/routes/(user)/user-settings/DeviceList.svelte +++ b/web/src/routes/(user)/user-settings/DeviceList.svelte @@ -3,7 +3,7 @@ import { createPreloadedQuery, executeMutation } from '#lib/queries/client.ts'; import { deleteAllSessionsMutation, deleteSessionMutation, getSessionsQuery } from '#lib/queries/session.query.ts'; import { type SessionResponseDto } from '@immich/sdk'; - import { Button, Text, modalManager } from '@immich/ui'; + import { Button, Text } from '@immich/ui'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; @@ -13,23 +13,15 @@ let currentSession = $derived(devices.find((device) => device.current)); let otherSessions = $derived(devices.filter((device) => !device.current)); - const handleDelete = async (device: SessionResponseDto) => { - const isConfirmed = await modalManager.showDialog({ prompt: $t('logout_this_device_confirmation') }); - if (!isConfirmed) { - return; - } + const handleDelete = (device: SessionResponseDto) => + executeMutation(deleteSessionMutation, [{ id: device.id }], { + confirm: { prompt: $t('logout_this_device_confirmation') }, + }); - await executeMutation(deleteSessionMutation, [{ id: device.id }]); - }; - - const handleDeleteAll = async () => { - const isConfirmed = await modalManager.showDialog({ prompt: $t('logout_all_device_confirmation') }); - if (!isConfirmed) { - return; - } - - await executeMutation(deleteAllSessionsMutation); - }; + const handleDeleteAll = () => + executeMutation(deleteAllSessionsMutation, undefined, { + confirm: { prompt: $t('logout_all_device_confirmation') }, + });
diff --git a/web/src/routes/admin/queues/QueuePanel.svelte b/web/src/routes/admin/queues/QueuePanel.svelte index 507c1ac03..2ead7f798 100644 --- a/web/src/routes/admin/queues/QueuePanel.svelte +++ b/web/src/routes/admin/queues/QueuePanel.svelte @@ -4,7 +4,7 @@ import { runQueueCommandLegacyMutation } from '#lib/queries/queue.query.ts'; import { asQueueItem } from '#lib/services/queue.service.ts'; import { QueueCommand, QueueName, type QueueCommandDto, type QueueResponseDto } from '@immich/sdk'; - import { modalManager, toastManager } from '@immich/ui'; + import { toastManager } from '@immich/ui'; import type { Component } from 'svelte'; import { t } from 'svelte-i18n'; import QueueCard from './QueueCard.svelte'; @@ -87,21 +87,10 @@ const handleCommand = async (name: QueueName, dto: QueueCommandDto) => { const item = asQueueItem($t, { name }); - switch (name) { - case QueueName.FaceDetection: - case QueueName.FacialRecognition: { - if (dto.force) { - const confirmed = await modalManager.showDialog({ prompt: $t('admin.confirm_reprocess_all_faces') }); - if (!confirmed) { - return; - } - break; - } - } - // no default - } + const reprocessesAllFaces = dto.force && (name === QueueName.FaceDetection || name === QueueName.FacialRecognition); await executeMutation(runQueueCommandLegacyMutation, [{ name, queueCommandDto: dto }], { + confirm: reprocessesAllFaces && { prompt: $t('admin.confirm_reprocess_all_faces') }, onSuccess: () => { if (dto.command === QueueCommand.Empty) { toastManager.primary($t('admin.cleared_jobs', { values: { job: item.title } }));