1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-01 08:31:59 +00:00

refactor(web): ConfirmDialog and dialogController (#9716)

* wrapper

* no more callback

* refactor: wip

* refactor: wip

* refactor: wip

* pr feedback

* fix

* pr feedback
This commit is contained in:
Alex 2024-05-28 09:10:43 +07:00 committed by GitHub
parent f020d29ab6
commit bce916e4c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 281 additions and 317 deletions

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { deleteUserAdmin, type UserResponseDto } from '@immich/sdk'; import { deleteUserAdmin, type UserResponseDto } from '@immich/sdk';
import { serverConfig } from '$lib/stores/server-config.store'; import { serverConfig } from '$lib/stores/server-config.store';
@ -42,12 +42,12 @@
}; };
</script> </script>
<ConfirmDialogue <ConfirmDialog
id="delete-user-confirmation-modal" id="delete-user-confirmation-modal"
title="Delete user" title="Delete user"
confirmText={forceDelete ? 'Permanently Delete' : 'Delete'} confirmText={forceDelete ? 'Permanently Delete' : 'Delete'}
onConfirm={handleDeleteUser} onConfirm={handleDeleteUser}
onClose={() => dispatch('cancel')} onCancel={() => dispatch('cancel')}
disabled={deleteButtonDisabled} disabled={deleteButtonDisabled}
> >
<svelte:fragment slot="prompt"> <svelte:fragment slot="prompt">
@ -96,4 +96,4 @@
{/if} {/if}
</div> </div>
</svelte:fragment> </svelte:fragment>
</ConfirmDialogue> </ConfirmDialog>

View file

@ -20,9 +20,9 @@
mdiVideo, mdiVideo,
} from '@mdi/js'; } from '@mdi/js';
import type { ComponentType } from 'svelte'; import type { ComponentType } from 'svelte';
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
import JobTile from './job-tile.svelte'; import JobTile from './job-tile.svelte';
import StorageMigrationDescription from './storage-migration-description.svelte'; import StorageMigrationDescription from './storage-migration-description.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let jobs: AllJobStatusResponseDto; export let jobs: AllJobStatusResponseDto;
@ -38,25 +38,24 @@
handleCommand?: (jobId: JobName, jobCommand: JobCommandDto) => Promise<void>; handleCommand?: (jobId: JobName, jobCommand: JobCommandDto) => Promise<void>;
} }
let confirmJob: JobName | null = null;
const handleConfirmCommand = async (jobId: JobName, dto: JobCommandDto) => { const handleConfirmCommand = async (jobId: JobName, dto: JobCommandDto) => {
if (dto.force) { if (dto.force) {
confirmJob = jobId; const isConfirmed = await dialogController.show({
id: 'confirm-reprocess-all-faces',
prompt: 'Are you sure you want to reprocess all faces? This will also clear named people.',
});
if (isConfirmed) {
await handleCommand(jobId, { command: JobCommand.Start, force: true });
return;
}
return; return;
} }
await handleCommand(jobId, dto); await handleCommand(jobId, dto);
}; };
const onConfirm = async () => {
if (!confirmJob) {
return;
}
await handleCommand(confirmJob, { command: JobCommand.Start, force: true });
confirmJob = null;
};
$: jobDetails = <Partial<Record<JobName, JobDetails>>>{ $: jobDetails = <Partial<Record<JobName, JobDetails>>>{
[JobName.ThumbnailGeneration]: { [JobName.ThumbnailGeneration]: {
icon: mdiFileJpgBox, icon: mdiFileJpgBox,
@ -152,15 +151,6 @@
} }
</script> </script>
{#if confirmJob}
<ConfirmDialogue
id="reprocess-faces-modal"
prompt="Are you sure you want to reprocess all faces? This will also clear named people."
{onConfirm}
onClose={() => (confirmJob = null)}
/>
{/if}
<div class="flex flex-col gap-7"> <div class="flex flex-col gap-7">
{#each jobList as [jobName, { title, subtitle, disabled, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]} {#each jobList as [jobName, { title, subtitle, disabled, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]}
{@const { jobCounts, queueStatus } = jobs[jobName]} {@const { jobCounts, queueStatus } = jobs[jobName]}

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { restoreUserAdmin, type UserResponseDto } from '@immich/sdk'; import { restoreUserAdmin, type UserResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
@ -27,15 +27,15 @@
}; };
</script> </script>
<ConfirmDialogue <ConfirmDialog
id="restore-user-modal" id="restore-user-modal"
title="Restore user" title="Restore user"
confirmText="Continue" confirmText="Continue"
confirmColor="green" confirmColor="green"
onConfirm={handleRestoreUser} onConfirm={handleRestoreUser}
onClose={() => dispatch('cancel')} onCancel={() => dispatch('cancel')}
> >
<svelte:fragment slot="prompt"> <svelte:fragment slot="prompt">
<p><b>{user.name}</b>'s account will be restored.</p> <p><b>{user.name}</b>'s account will be restored.</p>
</svelte:fragment> </svelte:fragment>
</ConfirmDialogue> </ConfirmDialog>

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte'; import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte'; import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
import SettingInputField, { import SettingInputField, {
@ -42,10 +42,10 @@
</script> </script>
{#if isConfirmOpen} {#if isConfirmOpen}
<ConfirmDialogue <ConfirmDialog
id="disable-login-modal" id="disable-login-modal"
title="Disable login" title="Disable login"
onClose={() => (isConfirmOpen = false)} onCancel={() => (isConfirmOpen = false)}
onConfirm={() => handleSave(true)} onConfirm={() => handleSave(true)}
> >
<svelte:fragment slot="prompt"> <svelte:fragment slot="prompt">
@ -64,7 +64,7 @@
</p> </p>
</div> </div>
</svelte:fragment> </svelte:fragment>
</ConfirmDialogue> </ConfirmDialog>
{/if} {/if}
<div> <div>

View file

@ -5,7 +5,6 @@
import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js'; import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte'; import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte'; import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
import { import {
NotificationType, NotificationType,
@ -33,6 +32,7 @@
} from '$lib/stores/preferences.store'; } from '$lib/stores/preferences.store';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let ownedAlbums: AlbumResponseDto[] = []; export let ownedAlbums: AlbumResponseDto[] = [];
export let sharedAlbums: AlbumResponseDto[] = []; export let sharedAlbums: AlbumResponseDto[] = [];
@ -275,9 +275,10 @@
sharedAlbums = sharedAlbums.filter(({ id }) => id !== albumToDelete.id); sharedAlbums = sharedAlbums.filter(({ id }) => id !== albumToDelete.id);
}; };
const setAlbumToDelete = () => { const setAlbumToDelete = async () => {
albumToDelete = contextMenuTargetAlbum ?? null; albumToDelete = contextMenuTargetAlbum ?? null;
closeAlbumContextMenu(); closeAlbumContextMenu();
await deleteSelectedAlbum();
}; };
const handleEdit = (album: AlbumResponseDto) => { const handleEdit = (album: AlbumResponseDto) => {
@ -289,6 +290,16 @@
if (!albumToDelete) { if (!albumToDelete) {
return; return;
} }
const isConfirmed = await dialogController.show({
id: 'delete-album',
prompt: `Are you sure you want to delete the album ${albumToDelete.albumName}?\nIf this album is shared, other users will not be able to access it anymore.`,
});
if (!isConfirmed) {
return;
}
try { try {
await handleDeleteAlbum(albumToDelete); await handleDeleteAlbum(albumToDelete);
} catch { } catch {
@ -456,20 +467,4 @@
/> />
{/if} {/if}
{/if} {/if}
<!-- Delete Modal -->
{#if albumToDelete}
<ConfirmDialogue
id="delete-album-dialogue-modal"
title="Delete album"
confirmText="Delete"
onConfirm={deleteSelectedAlbum}
onClose={() => (albumToDelete = null)}
>
<svelte:fragment slot="prompt">
<p>Are you sure you want to delete the album <b>{albumToDelete.albumName}</b>?</p>
<p>If this album is shared, other users will not be able to access it anymore.</p>
</svelte:fragment>
</ConfirmDialogue>
{/if}
{/if} {/if}

View file

@ -12,7 +12,7 @@
import { getContextMenuPosition } from '../../utils/context-menu'; import { getContextMenuPosition } from '../../utils/context-menu';
import { handleError } from '../../utils/handle-error'; import { handleError } from '../../utils/handle-error';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte'; import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
import ContextMenu from '../shared-components/context-menu/context-menu.svelte'; import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
import MenuOption from '../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../shared-components/context-menu/menu-option.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { NotificationType, notificationController } from '../shared-components/notification/notification';
@ -155,23 +155,23 @@
{/if} {/if}
{#if selectedRemoveUser && selectedRemoveUser?.id === currentUser?.id} {#if selectedRemoveUser && selectedRemoveUser?.id === currentUser?.id}
<ConfirmDialogue <ConfirmDialog
id="leave-album-modal" id="leave-album-modal"
title="Leave album?" title="Leave album?"
prompt="Are you sure you want to leave {album.albumName}?" prompt="Are you sure you want to leave {album.albumName}?"
confirmText="Leave" confirmText="Leave"
onConfirm={handleRemoveUser} onConfirm={handleRemoveUser}
onClose={() => (selectedRemoveUser = null)} onCancel={() => (selectedRemoveUser = null)}
/> />
{/if} {/if}
{#if selectedRemoveUser && selectedRemoveUser?.id !== currentUser?.id} {#if selectedRemoveUser && selectedRemoveUser?.id !== currentUser?.id}
<ConfirmDialogue <ConfirmDialog
id="remove-user-modal" id="remove-user-modal"
title="Remove user?" title="Remove user?"
prompt="Are you sure you want to remove {selectedRemoveUser.name}?" prompt="Are you sure you want to remove {selectedRemoveUser.name}?"
confirmText="Remove" confirmText="Remove"
onConfirm={handleRemoveUser} onConfirm={handleRemoveUser}
onClose={() => (selectedRemoveUser = null)} onCancel={() => (selectedRemoveUser = null)}
/> />
{/if} {/if}

View file

@ -12,17 +12,16 @@
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import ControlAppBar from '../shared-components/control-app-bar.svelte'; import ControlAppBar from '../shared-components/control-app-bar.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { NotificationType, notificationController } from '../shared-components/notification/notification';
import FaceThumbnail from './face-thumbnail.svelte'; import FaceThumbnail from './face-thumbnail.svelte';
import PeopleList from './people-list.svelte'; import PeopleList from './people-list.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let person: PersonResponseDto; export let person: PersonResponseDto;
let people: PersonResponseDto[] = []; let people: PersonResponseDto[] = [];
let selectedPeople: PersonResponseDto[] = []; let selectedPeople: PersonResponseDto[] = [];
let screenHeight: number; let screenHeight: number;
let isShowConfirmation = false;
let dispatch = createEventDispatcher<{ let dispatch = createEventDispatcher<{
back: void; back: void;
@ -65,6 +64,15 @@
}; };
const handleMerge = async () => { const handleMerge = async () => {
const isConfirm = await dialogController.show({
id: 'merge-people',
prompt: 'Do you want to merge these people?',
});
if (!isConfirm) {
return;
}
try { try {
let results = await mergePerson({ let results = await mergePerson({
id: person.id, id: person.id,
@ -79,8 +87,6 @@
dispatch('merge', mergedPerson); dispatch('merge', mergedPerson);
} catch (error) { } catch (error) {
handleError(error, 'Cannot merge people'); handleError(error, 'Cannot merge people');
} finally {
isShowConfirmation = false;
} }
}; };
</script> </script>
@ -101,13 +107,7 @@
<div /> <div />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="trailing"> <svelte:fragment slot="trailing">
<Button <Button size={'sm'} disabled={!hasSelection} on:click={handleMerge}>
size={'sm'}
disabled={!hasSelection}
on:click={() => {
isShowConfirmation = true;
}}
>
<Icon path={mdiMerge} size={18} /> <Icon path={mdiMerge} size={18} />
<span class="ml-2"> Merge</span></Button <span class="ml-2"> Merge</span></Button
> >
@ -150,19 +150,5 @@
<PeopleList {people} {peopleToNotShow} {screenHeight} on:select={({ detail }) => onSelect(detail)} /> <PeopleList {people} {peopleToNotShow} {screenHeight} on:select={({ detail }) => onSelect(detail)} />
</section> </section>
{#if isShowConfirmation}
<ConfirmDialogue
id="merge-people-modal"
title="Merge people"
confirmText="Merge"
onConfirm={handleMerge}
onClose={() => (isShowConfirmation = false)}
>
<svelte:fragment slot="prompt">
<p>Are you sure you want merge these people ?</p></svelte:fragment
>
</ConfirmDialogue>
{/if}
</section> </section>
</section> </section>

View file

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { serverInfo } from '$lib/stores/server-info.store'; import { serverInfo } from '$lib/stores/server-info.store';
@ -9,6 +8,7 @@
import { mdiAccountEditOutline } from '@mdi/js'; import { mdiAccountEditOutline } from '@mdi/js';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let user: UserAdminResponseDto; export let user: UserAdminResponseDto;
export let canResetPassword = true; export let canResetPassword = true;
@ -17,7 +17,6 @@
let error: string; let error: string;
let success: string; let success: string;
let isShowResetPasswordConfirmation = false;
let quotaSize = user.quotaSizeInBytes ? convertFromBytes(user.quotaSizeInBytes, 'GiB') : null; let quotaSize = user.quotaSizeInBytes ? convertFromBytes(user.quotaSizeInBytes, 'GiB') : null;
const previousQutoa = user.quotaSizeInBytes; const previousQutoa = user.quotaSizeInBytes;
@ -53,6 +52,15 @@
}; };
const resetPassword = async () => { const resetPassword = async () => {
const isConfirmed = await dialogController.show({
id: 'confirm-reset-password',
prompt: `Are you sure you want to reset ${user.name}'s password?`,
});
if (!isConfirmed) {
return;
}
try { try {
newPassword = generatePassword(); newPassword = generatePassword();
@ -67,8 +75,6 @@
dispatch('resetPasswordSuccess'); dispatch('resetPasswordSuccess');
} catch (error) { } catch (error) {
handleError(error, 'Unable to reset password'); handleError(error, 'Unable to reset password');
} finally {
isShowResetPasswordConfirmation = false;
} }
}; };
@ -140,26 +146,8 @@
</form> </form>
<svelte:fragment slot="sticky-bottom"> <svelte:fragment slot="sticky-bottom">
{#if canResetPassword} {#if canResetPassword}
<Button color="light-red" fullwidth on:click={() => (isShowResetPasswordConfirmation = true)} <Button color="light-red" fullwidth on:click={resetPassword}>Reset password</Button>
>Reset password</Button
>
{/if} {/if}
<Button type="submit" fullwidth form="edit-user-form">Confirm</Button> <Button type="submit" fullwidth form="edit-user-form">Confirm</Button>
</svelte:fragment> </svelte:fragment>
</FullScreenModal> </FullScreenModal>
{#if isShowResetPasswordConfirmation}
<ConfirmDialogue
id="reset-password-modal"
title="Reset password"
confirmText="Reset"
onConfirm={resetPassword}
onClose={() => (isShowResetPasswordConfirmation = false)}
>
<svelte:fragment slot="prompt">
<p>
Are you sure you want to reset <b>{user.name}</b>'s password?
</p>
</svelte:fragment>
</ConfirmDialogue>
{/if}

View file

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import { import {
NotificationType, NotificationType,
notificationController, notificationController,
@ -10,6 +9,7 @@
import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { s } from '$lib/utils'; import { s } from '$lib/utils';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
export let onRemove: ((assetIds: string[]) => void) | undefined; export let onRemove: ((assetIds: string[]) => void) | undefined;
@ -17,9 +17,16 @@
const { getAssets, clearSelect } = getAssetControlContext(); const { getAssets, clearSelect } = getAssetControlContext();
let isShowConfirmation = false;
const removeFromAlbum = async () => { const removeFromAlbum = async () => {
const isConfirmed = await dialogController.show({
id: 'remove-from-album',
prompt: `Are you sure you want to remove ${getAssets().size} asset${s(getAssets().size)} from the album?`,
});
if (!isConfirmed) {
return;
}
try { try {
const ids = [...getAssets()].map((a) => a.id); const ids = [...getAssets()].map((a) => a.id);
const results = await removeAssetFromAlbum({ const results = await removeAssetFromAlbum({
@ -44,36 +51,12 @@
type: NotificationType.Error, type: NotificationType.Error,
message: 'Error removing assets from album, check console for more details', message: 'Error removing assets from album, check console for more details',
}); });
} finally {
isShowConfirmation = false;
} }
}; };
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption text="Remove from album" icon={mdiImageRemoveOutline} on:click={() => (isShowConfirmation = true)} /> <MenuOption text="Remove from album" icon={mdiImageRemoveOutline} on:click={removeFromAlbum} />
{:else} {:else}
<CircleIconButton title="Remove from album" icon={mdiDeleteOutline} on:click={() => (isShowConfirmation = true)} /> <CircleIconButton title="Remove from album" icon={mdiDeleteOutline} on:click={removeFromAlbum} />
{/if}
{#if isShowConfirmation}
<ConfirmDialogue
id="remove-from-album-modal"
title="Remove from {album.albumName}"
confirmText="Remove"
onConfirm={removeFromAlbum}
onClose={() => (isShowConfirmation = false)}
>
<svelte:fragment slot="prompt">
<p>
Are you sure you want to remove
{#if getAssets().size > 1}
these <b>{getAssets().size}</b> assets
{:else}
this asset
{/if}
from the album?
</p>
</svelte:fragment>
</ConfirmDialogue>
{/if} {/if}

View file

@ -1,20 +1,29 @@
<script lang="ts"> <script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { getKey } from '$lib/utils'; import { getKey, s } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { removeSharedLinkAssets, type SharedLinkResponseDto } from '@immich/sdk'; import { removeSharedLinkAssets, type SharedLinkResponseDto } from '@immich/sdk';
import { mdiDeleteOutline } from '@mdi/js'; import { mdiDeleteOutline } from '@mdi/js';
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
import { NotificationType, notificationController } from '../../shared-components/notification/notification'; import { NotificationType, notificationController } from '../../shared-components/notification/notification';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let sharedLink: SharedLinkResponseDto; export let sharedLink: SharedLinkResponseDto;
let removing = false;
const { getAssets, clearSelect } = getAssetControlContext(); const { getAssets, clearSelect } = getAssetControlContext();
const handleRemove = async () => { const handleRemove = async () => {
const isConfirmed = await dialogController.show({
id: 'remove-from-shared-link',
title: 'Remove assets?',
prompt: `Are you sure you want to remove ${getAssets().size} asset${s(getAssets().size)} from this shared link?`,
confirmText: 'Remove',
});
if (!isConfirmed) {
return;
}
try { try {
const results = await removeSharedLinkAssets({ const results = await removeSharedLinkAssets({
id: sharedLink.id, id: sharedLink.id,
@ -46,15 +55,4 @@
}; };
</script> </script>
<CircleIconButton title="Remove from shared link" on:click={() => (removing = true)} icon={mdiDeleteOutline} /> <CircleIconButton title="Remove from shared link" on:click={handleRemove} icon={mdiDeleteOutline} />
{#if removing}
<ConfirmDialogue
id="remove-assets-modal"
title="Remove assets?"
prompt="Are you sure you want to remove {getAssets().size} asset(s) from this shared link?"
confirmText="Remove"
onConfirm={() => handleRemove()}
onClose={() => (removing = false)}
/>
{/if}

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte'; import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
import { showDeleteModal } from '$lib/stores/preferences.store'; import { showDeleteModal } from '$lib/stores/preferences.store';
import Checkbox from '$lib/components/elements/checkbox.svelte'; import Checkbox from '$lib/components/elements/checkbox.svelte';
import { s } from '$lib/utils'; import { s } from '$lib/utils';
@ -22,12 +22,12 @@
}; };
</script> </script>
<ConfirmDialogue <ConfirmDialog
id="permanently-delete-asset-modal" id="permanently-delete-asset-modal"
title="Permanently delete asset{s(size)}" title="Permanently delete asset{s(size)}"
confirmText="Delete" confirmText="Delete"
onConfirm={handleConfirm} onConfirm={handleConfirm}
onClose={() => dispatch('cancel')} onCancel={() => dispatch('cancel')}
> >
<svelte:fragment slot="prompt"> <svelte:fragment slot="prompt">
<p> <p>
@ -44,4 +44,4 @@
<Checkbox id="confirm-deletion-input" label="Do not show this message again" bind:checked /> <Checkbox id="confirm-deletion-input" label="Do not show this message again" bind:checked />
</div> </div>
</svelte:fragment> </svelte:fragment>
</ConfirmDialogue> </ConfirmDialog>

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import ConfirmDialogue from './confirm-dialogue.svelte'; import ConfirmDialog from './dialog/confirm-dialog.svelte';
import Combobox from './combobox.svelte'; import Combobox from './combobox.svelte';
import DateInput from '../elements/date-input.svelte'; import DateInput from '../elements/date-input.svelte';
@ -55,14 +55,14 @@
}; };
</script> </script>
<ConfirmDialogue <ConfirmDialog
id="edit-date-time-modal" id="edit-date-time-modal"
confirmColor="primary" confirmColor="primary"
title="Edit date and time" title="Edit date and time"
prompt="Please select a new date:" prompt="Please select a new date:"
disabled={!date.isValid} disabled={!date.isValid}
onConfirm={handleConfirm} onConfirm={handleConfirm}
onClose={handleCancel} onCancel={handleCancel}
> >
<div class="flex flex-col text-md px-4 text-center gap-2" slot="prompt"> <div class="flex flex-col text-md px-4 text-center gap-2" slot="prompt">
<div class="flex flex-col"> <div class="flex flex-col">
@ -84,4 +84,4 @@
/> />
</div> </div>
</div> </div>
</ConfirmDialogue> </ConfirmDialog>

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import ConfirmDialogue from './confirm-dialogue.svelte'; import ConfirmDialog from './dialog/confirm-dialog.svelte';
import { timeDebounceOnSearch } from '$lib/constants'; import { timeDebounceOnSearch } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
@ -103,13 +103,13 @@
}; };
</script> </script>
<ConfirmDialogue <ConfirmDialog
id="change-location-modal" id="change-location-modal"
confirmColor="primary" confirmColor="primary"
title="Change location" title="Change location"
width="wide" width="wide"
onConfirm={handleConfirm} onConfirm={handleConfirm}
onClose={handleCancel} onCancel={handleCancel}
> >
<div slot="prompt" class="flex flex-col w-full h-full gap-2"> <div slot="prompt" class="flex flex-col w-full h-full gap-2">
<div <div
@ -182,4 +182,4 @@
{/await} {/await}
</div> </div>
</div> </div>
</ConfirmDialogue> </ConfirmDialog>

View file

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import FullScreenModal from './full-screen-modal.svelte'; import FullScreenModal from '../full-screen-modal.svelte';
import Button from '../elements/buttons/button.svelte'; import Button from '../../elements/buttons/button.svelte';
import type { Color } from '$lib/components/elements/buttons/button.svelte'; import type { Color } from '$lib/components/elements/buttons/button.svelte';
export let id: string; export let id: string = 'confirm-dialog';
export let title = 'Confirm'; export let title = 'Confirm';
export let prompt = 'Are you sure you want to do this?'; export let prompt = 'Are you sure you want to do this?';
export let confirmText = 'Confirm'; export let confirmText = 'Confirm';
@ -13,7 +13,7 @@
export let hideCancelButton = false; export let hideCancelButton = false;
export let disabled = false; export let disabled = false;
export let width: 'wide' | 'narrow' = 'narrow'; export let width: 'wide' | 'narrow' = 'narrow';
export let onClose: () => void; export let onCancel: () => void;
export let onConfirm: () => void; export let onConfirm: () => void;
let isConfirmButtonDisabled = false; let isConfirmButtonDisabled = false;
@ -24,7 +24,7 @@
}; };
</script> </script>
<FullScreenModal {title} {id} {onClose} {width}> <FullScreenModal {title} {id} onClose={onCancel} {width}>
<div class="text-md py-5 text-center"> <div class="text-md py-5 text-center">
<slot name="prompt"> <slot name="prompt">
<p>{prompt}</p> <p>{prompt}</p>
@ -33,7 +33,7 @@
<svelte:fragment slot="sticky-bottom"> <svelte:fragment slot="sticky-bottom">
{#if !hideCancelButton} {#if !hideCancelButton}
<Button color={cancelColor} fullwidth on:click={onClose}> <Button color={cancelColor} fullwidth on:click={onCancel}>
{cancelText} {cancelText}
</Button> </Button>
{/if} {/if}

View file

@ -0,0 +1,9 @@
<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}

View file

@ -0,0 +1,48 @@
import { writable } from 'svelte/store';
type DialogActions = {
onConfirm: () => void;
onCancel: () => void;
};
type DialogOptions = {
id?: string;
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((resolve) => {
const newDialog: Dialog = {
...options,
onConfirm: () => {
dialog.set(undefined);
resolve(true);
},
onCancel: () => {
dialog.set(undefined);
resolve(false);
},
};
dialog.set(newDialog);
});
}
return {
dialog,
show,
};
}
export const dialogController = createDialogWrapper();

View file

@ -47,7 +47,7 @@
role="presentation" role="presentation"
in:fade={{ duration: 100 }} in:fade={{ duration: 100 }}
out:fade={{ duration: 100 }} out:fade={{ duration: 100 }}
class="fixed left-0 top-0 z-[9990] flex h-screen w-screen place-content-center place-items-center bg-black/40" class="fixed left-0 top-0 z-[9999] flex h-screen w-screen place-content-center place-items-center bg-black/40"
on:keydown={(event) => { on:keydown={(event) => {
event.stopPropagation(); event.stopPropagation();
}} }}

View file

@ -2,36 +2,47 @@
import { deleteAllSessions, deleteSession, getSessions, type SessionResponseDto } from '@immich/sdk'; import { deleteAllSessions, deleteSession, getSessions, type SessionResponseDto } from '@immich/sdk';
import { handleError } from '../../utils/handle-error'; import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import { notificationController, NotificationType } from '../shared-components/notification/notification'; import { notificationController, NotificationType } from '../shared-components/notification/notification';
import DeviceCard from './device-card.svelte'; import DeviceCard from './device-card.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let devices: SessionResponseDto[]; export let devices: SessionResponseDto[];
let deleteDevice: SessionResponseDto | null = null;
let deleteAll = false;
const refresh = () => getSessions().then((_devices) => (devices = _devices)); const refresh = () => getSessions().then((_devices) => (devices = _devices));
$: currentDevice = devices.find((device) => device.current); $: currentDevice = devices.find((device) => device.current);
$: otherDevices = devices.filter((device) => !device.current); $: otherDevices = devices.filter((device) => !device.current);
const handleDelete = async () => { const handleDelete = async (device: SessionResponseDto) => {
if (!deleteDevice) { const isConfirmed = await dialogController.show({
id: 'log-out-device',
prompt: 'Are you sure you want to log out this device?',
});
if (!isConfirmed) {
return; return;
} }
try { try {
await deleteSession({ id: deleteDevice.id }); await deleteSession({ id: device.id });
notificationController.show({ message: `Logged out device`, type: NotificationType.Info }); notificationController.show({ message: `Logged out device`, type: NotificationType.Info });
} catch (error) { } catch (error) {
handleError(error, 'Unable to log out device'); handleError(error, 'Unable to log out device');
} finally { } finally {
await refresh(); await refresh();
deleteDevice = null;
} }
}; };
const handleDeleteAll = async () => { const handleDeleteAll = async () => {
const isConfirmed = await dialogController.show({
id: 'log-out-all-devices',
prompt: 'Are you sure you want to log out all devices?',
});
if (!isConfirmed) {
return;
}
try { try {
await deleteAllSessions(); await deleteAllSessions();
notificationController.show({ notificationController.show({
@ -42,29 +53,10 @@
handleError(error, 'Unable to log out all devices'); handleError(error, 'Unable to log out all devices');
} finally { } finally {
await refresh(); await refresh();
deleteAll = false;
} }
}; };
</script> </script>
{#if deleteDevice}
<ConfirmDialogue
id="log-out-device-modal"
prompt="Are you sure you want to log out this device?"
onConfirm={() => handleDelete()}
onClose={() => (deleteDevice = null)}
/>
{/if}
{#if deleteAll}
<ConfirmDialogue
id="log-out-all-modal"
prompt="Are you sure you want to log out all devices?"
onConfirm={() => handleDeleteAll()}
onClose={() => (deleteAll = false)}
/>
{/if}
<section class="my-4"> <section class="my-4">
{#if currentDevice} {#if currentDevice}
<div class="mb-6"> <div class="mb-6">
@ -76,7 +68,7 @@
<div class="mb-6"> <div class="mb-6">
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">OTHER DEVICES</h3> <h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">OTHER DEVICES</h3>
{#each otherDevices as device, index} {#each otherDevices as device, index}
<DeviceCard {device} on:delete={() => (deleteDevice = device)} /> <DeviceCard {device} on:delete={() => handleDelete(device)} />
{#if index !== otherDevices.length - 1} {#if index !== otherDevices.length - 1}
<hr class="my-3" /> <hr class="my-3" />
{/if} {/if}
@ -84,7 +76,7 @@
</div> </div>
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">LOG OUT ALL DEVICES</h3> <h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">LOG OUT ALL DEVICES</h3>
<div class="flex justify-end"> <div class="flex justify-end">
<Button color="red" size="sm" on:click={() => (deleteAll = true)}>Log Out All Devices</Button> <Button color="red" size="sm" on:click={handleDeleteAll}>Log Out All Devices</Button>
</div> </div>
{/if} {/if}
</section> </section>

View file

@ -13,10 +13,10 @@
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import Icon from '../elements/icon.svelte'; import Icon from '../elements/icon.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte'; import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import PartnerSelectionModal from './partner-selection-modal.svelte'; import PartnerSelectionModal from './partner-selection-modal.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
interface PartnerSharing { interface PartnerSharing {
user: UserResponseDto; user: UserResponseDto;
@ -28,7 +28,7 @@
export let user: UserResponseDto; export let user: UserResponseDto;
let createPartnerFlag = false; let createPartnerFlag = false;
let removePartnerDto: PartnerResponseDto | null = null; // let removePartnerDto: PartnerResponseDto | null = null;
let partners: Array<PartnerSharing> = []; let partners: Array<PartnerSharing> = [];
onMount(async () => { onMount(async () => {
@ -75,14 +75,19 @@
} }
}; };
const handleRemovePartner = async () => { const handleRemovePartner = async (partner: PartnerResponseDto) => {
if (!removePartnerDto) { const isConfirmed = await dialogController.show({
id: 'remove-partner',
title: 'Stop sharing your photos?',
prompt: `${partner.name} will no longer be able to access your photos.`,
});
if (!isConfirmed) {
return; return;
} }
try { try {
await removePartner({ id: removePartnerDto.id }); await removePartner({ id: partner.id });
removePartnerDto = null;
await refreshPartners(); await refreshPartners();
} catch (error) { } catch (error) {
handleError(error, 'Unable to remove partner'); handleError(error, 'Unable to remove partner');
@ -133,7 +138,7 @@
{#if partner.sharedByMe} {#if partner.sharedByMe}
<CircleIconButton <CircleIconButton
on:click={() => (removePartnerDto = partner.user)} on:click={() => handleRemovePartner(partner.user)}
icon={mdiClose} icon={mdiClose}
size={'16'} size={'16'}
title="Stop sharing your photos with this user" title="Stop sharing your photos with this user"
@ -186,13 +191,3 @@
on:add-users={(event) => handleCreatePartners(event.detail)} on:add-users={(event) => handleCreatePartners(event.detail)}
/> />
{/if} {/if}
{#if removePartnerDto}
<ConfirmDialogue
id="stop-sharing-photos-modal"
title="Stop sharing your photos?"
prompt="{removePartnerDto.name} will no longer be able to access your photos."
onClose={() => (removePartnerDto = null)}
onConfirm={() => handleRemovePartner()}
/>
{/if}

View file

@ -7,15 +7,14 @@
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import APIKeyForm from '../forms/api-key-form.svelte'; import APIKeyForm from '../forms/api-key-form.svelte';
import APIKeySecret from '../forms/api-key-secret.svelte'; import APIKeySecret from '../forms/api-key-secret.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { NotificationType, notificationController } from '../shared-components/notification/notification';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let keys: ApiKeyResponseDto[]; export let keys: ApiKeyResponseDto[];
let newKey: Partial<ApiKeyResponseDto> | null = null; let newKey: Partial<ApiKeyResponseDto> | null = null;
let editKey: ApiKeyResponseDto | null = null; let editKey: ApiKeyResponseDto | null = null;
let deleteKey: ApiKeyResponseDto | null = null;
let secret = ''; let secret = '';
const format: Intl.DateTimeFormatOptions = { const format: Intl.DateTimeFormatOptions = {
@ -59,22 +58,26 @@
} }
}; };
const handleDelete = async () => { const handleDelete = async (key: ApiKeyResponseDto) => {
if (!deleteKey) { const isConfirmed = await dialogController.show({
id: 'delete-api-key',
prompt: 'Are you sure you want to delete this API key?',
});
if (!isConfirmed) {
return; return;
} }
try { try {
await deleteApiKey({ id: deleteKey.id }); await deleteApiKey({ id: key.id });
notificationController.show({ notificationController.show({
message: `Removed API Key: ${deleteKey.name}`, message: `Removed API Key: ${key.name}`,
type: NotificationType.Info, type: NotificationType.Info,
}); });
} catch (error) { } catch (error) {
handleError(error, 'Unable to remove API Key'); handleError(error, 'Unable to remove API Key');
} finally { } finally {
await refreshKeys(); await refreshKeys();
deleteKey = null;
} }
}; };
</script> </script>
@ -103,15 +106,6 @@
/> />
{/if} {/if}
{#if deleteKey}
<ConfirmDialogue
id="confirm-api-key-delete-modal"
prompt="Are you sure you want to delete this API key?"
onConfirm={() => handleDelete()}
onClose={() => (deleteKey = null)}
/>
{/if}
<section class="my-4"> <section class="my-4">
<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}> <div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
<div class="mb-2 flex justify-end"> <div class="mb-2 flex justify-end">
@ -156,7 +150,7 @@
icon={mdiTrashCanOutline} icon={mdiTrashCanOutline}
title="Delete key" title="Delete key"
size="16" size="16"
on:click={() => (deleteKey = key)} on:click={() => handleDelete(key)}
/> />
</td> </td>
</tr> </tr>

View file

@ -24,7 +24,6 @@
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'; import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte'; import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte'; import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
@ -81,6 +80,7 @@
} from '@mdi/js'; } from '@mdi/js';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let data: PageData; export let data: PageData;
@ -98,7 +98,6 @@
} }
enum ViewMode { enum ViewMode {
CONFIRM_DELETE = 'confirm-delete',
LINK_SHARING = 'link-sharing', LINK_SHARING = 'link-sharing',
SELECT_USERS = 'select-users', SELECT_USERS = 'select-users',
SELECT_THUMBNAIL = 'select-thumbnail', SELECT_THUMBNAIL = 'select-thumbnail',
@ -248,10 +247,7 @@
viewMode = ViewMode.VIEW; viewMode = ViewMode.VIEW;
return; return;
} }
if (viewMode === ViewMode.CONFIRM_DELETE) {
viewMode = ViewMode.VIEW;
return;
}
if (viewMode === ViewMode.SELECT_ASSETS) { if (viewMode === ViewMode.SELECT_ASSETS) {
handleCloseSelectAssets(); handleCloseSelectAssets();
return; return;
@ -353,6 +349,16 @@
}; };
const handleRemoveAlbum = async () => { const handleRemoveAlbum = async () => {
const isConfirmed = await dialogController.show({
id: 'remove-album',
prompt: `Are you sure you want to delete the album ${album.albumName}?\nIf this album is shared, other users will not be able to access it anymore.`,
});
if (!isConfirmed) {
viewMode = ViewMode.VIEW;
return;
}
try { try {
await deleteAlbum({ id: album.id }); await deleteAlbum({ id: album.id });
await goto(backUrl); await goto(backUrl);
@ -471,11 +477,7 @@
on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)} on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)}
/> />
<MenuOption icon={mdiCogOutline} text="Options" on:click={() => (viewMode = ViewMode.OPTIONS)} /> <MenuOption icon={mdiCogOutline} text="Options" on:click={() => (viewMode = ViewMode.OPTIONS)} />
<MenuOption <MenuOption icon={mdiDeleteOutline} text="Delete album" on:click={() => handleRemoveAlbum()} />
icon={mdiDeleteOutline}
text="Delete album"
on:click={() => (viewMode = ViewMode.CONFIRM_DELETE)}
/>
</ContextMenu> </ContextMenu>
{/if} {/if}
</div> </div>
@ -697,21 +699,6 @@
/> />
{/if} {/if}
{#if viewMode === ViewMode.CONFIRM_DELETE}
<ConfirmDialogue
id="delete-album-modal"
title="Delete album"
confirmText="Delete"
onConfirm={handleRemoveAlbum}
onClose={() => (viewMode = ViewMode.VIEW)}
>
<svelte:fragment slot="prompt">
<p>Are you sure you want to delete the album <b>{album.albumName}</b>?</p>
<p>If this album is shared, other users will not be able to access it anymore.</p>
</svelte:fragment>
</ConfirmDialogue>
{/if}
{#if viewMode === ViewMode.OPTIONS && $user} {#if viewMode === ViewMode.OPTIONS && $user}
<AlbumOptions <AlbumOptions
{album} {album}

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte'; import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
import { import {
@ -15,12 +15,11 @@
import { getAllSharedLinks, removeSharedLink, type SharedLinkResponseDto } from '@immich/sdk'; import { getAllSharedLinks, removeSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
import { mdiArrowLeft } from '@mdi/js'; import { mdiArrowLeft } from '@mdi/js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
let sharedLinks: SharedLinkResponseDto[] = []; let sharedLinks: SharedLinkResponseDto[] = [];
let editSharedLink: SharedLinkResponseDto | null = null; let editSharedLink: SharedLinkResponseDto | null = null;
let deleteLinkId: string | null = null;
const refresh = async () => { const refresh = async () => {
sharedLinks = await getAllSharedLinks(); sharedLinks = await getAllSharedLinks();
}; };
@ -29,15 +28,21 @@
await refresh(); await refresh();
}); });
const handleDeleteLink = async () => { const handleDeleteLink = async (id: string) => {
if (!deleteLinkId) { const isConfirmed = await dialogController.show({
id: 'delete-shared-link',
title: 'Delete shared link',
prompt: 'Are you sure you want to delete this shared link?',
confirmText: 'Delete',
});
if (!isConfirmed) {
return; return;
} }
try { try {
await removeSharedLink({ id: deleteLinkId }); await removeSharedLink({ id });
notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info }); notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info });
deleteLinkId = null;
await refresh(); await refresh();
} catch (error) { } catch (error) {
handleError(error, 'Unable to delete shared link'); handleError(error, 'Unable to delete shared link');
@ -73,7 +78,7 @@
{#each sharedLinks as link (link.id)} {#each sharedLinks as link (link.id)}
<SharedLinkCard <SharedLinkCard
{link} {link}
on:delete={() => (deleteLinkId = link.id)} on:delete={() => handleDeleteLink(link.id)}
on:edit={() => (editSharedLink = link)} on:edit={() => (editSharedLink = link)}
on:copy={() => handleCopyLink(link.key)} on:copy={() => handleCopyLink(link.key)}
/> />
@ -85,14 +90,3 @@
{#if editSharedLink} {#if editSharedLink}
<CreateSharedLinkModal editingLink={editSharedLink} onClose={handleEditDone} /> <CreateSharedLinkModal editingLink={editSharedLink} onClose={handleEditDone} />
{/if} {/if}
{#if deleteLinkId}
<ConfirmDialogue
id="delete-shared-link-modal"
title="Delete shared link"
prompt="Are you sure you want to delete this shared link?"
confirmText="Delete"
onConfirm={() => handleDeleteLink()}
onClose={() => (deleteLinkId = null)}
/>
{/if}

View file

@ -9,7 +9,6 @@
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte'; import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'; import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { import {
NotificationType, NotificationType,
@ -24,6 +23,7 @@
import { mdiDeleteOutline, mdiHistory } from '@mdi/js'; import { mdiDeleteOutline, mdiHistory } from '@mdi/js';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { handlePromiseError } from '$lib/utils'; import { handlePromiseError } from '$lib/utils';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let data: PageData; export let data: PageData;
@ -32,10 +32,18 @@
const assetStore = new AssetStore({ isTrashed: true }); const assetStore = new AssetStore({ isTrashed: true });
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore; const { isMultiSelectState, selectedAssets } = assetInteractionStore;
let isShowEmptyConfirmation = false;
const handleEmptyTrash = async () => { const handleEmptyTrash = async () => {
isShowEmptyConfirmation = false; const isConfirmed = await dialogController.show({
id: 'empty-trash',
prompt:
'Are you sure you want to empty the trash? This will remove all the assets in trash permanently from Immich.\nYou cannot undo this action!',
});
if (!isConfirmed) {
return;
}
try { try {
await emptyTrash(); await emptyTrash();
@ -87,7 +95,7 @@
Restore all Restore all
</div> </div>
</LinkButton> </LinkButton>
<LinkButton on:click={() => (isShowEmptyConfirmation = true)}> <LinkButton on:click={() => handleEmptyTrash()}>
<div class="flex place-items-center gap-2 text-sm"> <div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDeleteOutline} size="18" /> <Icon path={mdiDeleteOutline} size="18" />
Empty trash Empty trash
@ -103,18 +111,3 @@
</AssetGrid> </AssetGrid>
</UserPageLayout> </UserPageLayout>
{/if} {/if}
{#if isShowEmptyConfirmation}
<ConfirmDialogue
id="empty-trash-modal"
title="Empty trash"
confirmText="Empty"
onConfirm={handleEmptyTrash}
onClose={() => (isShowEmptyConfirmation = false)}
>
<svelte:fragment slot="prompt">
<p>Are you sure you want to empty the trash? This will remove all the assets in trash permanently from Immich.</p>
<p><b>You cannot undo this action!</b></p>
</svelte:fragment>
</ConfirmDialogue>
{/if}

View file

@ -18,6 +18,7 @@
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import '../app.css'; import '../app.css';
import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation'; import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation';
import DialogWrapper from '$lib/components/shared-components/dialog/dialog-wrapper.svelte';
let showNavigationLoadingBar = false; let showNavigationLoadingBar = false;
@ -121,6 +122,7 @@
<DownloadPanel /> <DownloadPanel />
<UploadPanel /> <UploadPanel />
<NotificationList /> <NotificationList />
<DialogWrapper />
{#if $user?.isAdmin} {#if $user?.isAdmin}
<VersionAnnouncementBox /> <VersionAnnouncementBox />

View file

@ -6,7 +6,6 @@
import LibraryScanSettingsForm from '$lib/components/forms/library-scan-settings-form.svelte'; import LibraryScanSettingsForm from '$lib/components/forms/library-scan-settings-form.svelte';
import LibraryUserPickerForm from '$lib/components/forms/library-user-picker-form.svelte'; import LibraryUserPickerForm from '$lib/components/forms/library-user-picker-form.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte'; import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
@ -37,6 +36,7 @@
import LinkButton from '../../../lib/components/elements/buttons/link-button.svelte'; import LinkButton from '../../../lib/components/elements/buttons/link-button.svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let data: PageData; export let data: PageData;
@ -282,29 +282,39 @@
const onDeleteLibraryClicked = async () => { const onDeleteLibraryClicked = async () => {
closeAll(); closeAll();
if (selectedLibrary && confirm(`Are you sure you want to delete ${selectedLibrary.name} library?`) == true) { if (!selectedLibrary) {
await refreshStats(selectedLibraryIndex); return;
if (totalCount[selectedLibraryIndex] > 0) { }
deleteAssetCount = totalCount[selectedLibraryIndex];
confirmDeleteLibrary = selectedLibrary; const isConfirmedLibrary = await dialogController.show({
} else { id: 'delete-library',
deletedLibrary = selectedLibrary; prompt: `Are you sure you want to delete ${selectedLibrary.name} library?`,
await handleDelete(); });
if (!isConfirmedLibrary) {
return;
}
await refreshStats(selectedLibraryIndex);
if (totalCount[selectedLibraryIndex] > 0) {
deleteAssetCount = totalCount[selectedLibraryIndex];
const isConfirmedLibraryAssetCount = await dialogController.show({
id: 'delete-library-assets',
prompt: `Are you sure you want to delete this library? This will delete all ${deleteAssetCount} contained assets from Immich and cannot be undone. Files will remain on disk.`,
});
if (!isConfirmedLibraryAssetCount) {
return;
} }
await handleDelete();
} else {
deletedLibrary = selectedLibrary;
await handleDelete();
} }
}; };
</script> </script>
{#if confirmDeleteLibrary}
<ConfirmDialogue
id="warning-modal"
title="Warning!"
prompt="Are you sure you want to delete this library? This will delete all {deleteAssetCount} contained assets from Immich and cannot be undone. Files will remain on disk."
onConfirm={handleDelete}
onClose={() => (confirmDeleteLibrary = null)}
/>
{/if}
{#if toCreateLibrary} {#if toCreateLibrary}
<LibraryUserPickerForm <LibraryUserPickerForm
on:submit={({ detail }) => handleCreate(detail.ownerId)} on:submit={({ detail }) => handleCreate(detail.ownerId)}

View file

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialogue.svelte'; import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialogue.svelte';
import RestoreDialogue from '$lib/components/admin-page/restore-dialogue.svelte'; import RestoreDialogue from '$lib/components/admin-page/restore-dialogue.svelte';
import Button from '$lib/components/elements/buttons/button.svelte'; import Button from '$lib/components/elements/buttons/button.svelte';
@ -9,7 +10,6 @@
import CreateUserForm from '$lib/components/forms/create-user-form.svelte'; import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
import EditUserForm from '$lib/components/forms/edit-user-form.svelte'; import EditUserForm from '$lib/components/forms/edit-user-form.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import { import {
NotificationType, NotificationType,
notificationController, notificationController,
@ -153,12 +153,12 @@
{/if} {/if}
{#if shouldShowPasswordResetSuccess} {#if shouldShowPasswordResetSuccess}
<ConfirmDialogue <ConfirmDialog
id="password-reset-success-modal" id="password-reset-success-modal"
title="Password reset success" title="Password reset success"
confirmText="Done" confirmText="Done"
onConfirm={() => (shouldShowPasswordResetSuccess = false)} onConfirm={() => (shouldShowPasswordResetSuccess = false)}
onClose={() => (shouldShowPasswordResetSuccess = false)} onCancel={() => (shouldShowPasswordResetSuccess = false)}
hideCancelButton={true} hideCancelButton={true}
confirmColor="green" confirmColor="green"
> >
@ -185,7 +185,7 @@
</p> </p>
</div> </div>
</svelte:fragment> </svelte:fragment>
</ConfirmDialogue> </ConfirmDialog>
{/if} {/if}
<table class="my-5 w-full text-left"> <table class="my-5 w-full text-left">