1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +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">
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 { deleteUserAdmin, type UserResponseDto } from '@immich/sdk';
import { serverConfig } from '$lib/stores/server-config.store';
@ -42,12 +42,12 @@
};
</script>
<ConfirmDialogue
<ConfirmDialog
id="delete-user-confirmation-modal"
title="Delete user"
confirmText={forceDelete ? 'Permanently Delete' : 'Delete'}
onConfirm={handleDeleteUser}
onClose={() => dispatch('cancel')}
onCancel={() => dispatch('cancel')}
disabled={deleteButtonDisabled}
>
<svelte:fragment slot="prompt">
@ -96,4 +96,4 @@
{/if}
</div>
</svelte:fragment>
</ConfirmDialogue>
</ConfirmDialog>

View file

@ -20,9 +20,9 @@
mdiVideo,
} from '@mdi/js';
import type { ComponentType } from 'svelte';
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
import JobTile from './job-tile.svelte';
import StorageMigrationDescription from './storage-migration-description.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let jobs: AllJobStatusResponseDto;
@ -38,25 +38,24 @@
handleCommand?: (jobId: JobName, jobCommand: JobCommandDto) => Promise<void>;
}
let confirmJob: JobName | null = null;
const handleConfirmCommand = async (jobId: JobName, dto: JobCommandDto) => {
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;
}
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>>>{
[JobName.ThumbnailGeneration]: {
icon: mdiFileJpgBox,
@ -152,15 +151,6 @@
}
</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">
{#each jobList as [jobName, { title, subtitle, disabled, allText, missingText, allowForceCommand, icon, component, handleCommand: handleCommandOverride }]}
{@const { jobCounts, queueStatus } = jobs[jobName]}

View file

@ -1,5 +1,5 @@
<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 { restoreUserAdmin, type UserResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
@ -27,15 +27,15 @@
};
</script>
<ConfirmDialogue
<ConfirmDialog
id="restore-user-modal"
title="Restore user"
confirmText="Continue"
confirmColor="green"
onConfirm={handleRestoreUser}
onClose={() => dispatch('cancel')}
onCancel={() => dispatch('cancel')}
>
<svelte:fragment slot="prompt">
<p><b>{user.name}</b>'s account will be restored.</p>
</svelte:fragment>
</ConfirmDialogue>
</ConfirmDialog>

View file

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

View file

@ -5,7 +5,6 @@
import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js';
import Icon from '$lib/components/elements/icon.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 {
NotificationType,
@ -33,6 +32,7 @@
} from '$lib/stores/preferences.store';
import { goto } from '$app/navigation';
import { AppRoute } from '$lib/constants';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let ownedAlbums: AlbumResponseDto[] = [];
export let sharedAlbums: AlbumResponseDto[] = [];
@ -275,9 +275,10 @@
sharedAlbums = sharedAlbums.filter(({ id }) => id !== albumToDelete.id);
};
const setAlbumToDelete = () => {
const setAlbumToDelete = async () => {
albumToDelete = contextMenuTargetAlbum ?? null;
closeAlbumContextMenu();
await deleteSelectedAlbum();
};
const handleEdit = (album: AlbumResponseDto) => {
@ -289,6 +290,16 @@
if (!albumToDelete) {
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 {
await handleDeleteAlbum(albumToDelete);
} catch {
@ -456,20 +467,4 @@
/>
{/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}

View file

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

View file

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

View file

@ -1,5 +1,4 @@
<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 { AppRoute } from '$lib/constants';
import { serverInfo } from '$lib/stores/server-info.store';
@ -9,6 +8,7 @@
import { mdiAccountEditOutline } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let user: UserAdminResponseDto;
export let canResetPassword = true;
@ -17,7 +17,6 @@
let error: string;
let success: string;
let isShowResetPasswordConfirmation = false;
let quotaSize = user.quotaSizeInBytes ? convertFromBytes(user.quotaSizeInBytes, 'GiB') : null;
const previousQutoa = user.quotaSizeInBytes;
@ -53,6 +52,15 @@
};
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 {
newPassword = generatePassword();
@ -67,8 +75,6 @@
dispatch('resetPasswordSuccess');
} catch (error) {
handleError(error, 'Unable to reset password');
} finally {
isShowResetPasswordConfirmation = false;
}
};
@ -140,26 +146,8 @@
</form>
<svelte:fragment slot="sticky-bottom">
{#if canResetPassword}
<Button color="light-red" fullwidth on:click={() => (isShowResetPasswordConfirmation = true)}
>Reset password</Button
>
<Button color="light-red" fullwidth on:click={resetPassword}>Reset password</Button>
{/if}
<Button type="submit" fullwidth form="edit-user-form">Confirm</Button>
</svelte:fragment>
</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">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import {
NotificationType,
notificationController,
@ -10,6 +9,7 @@
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { s } from '$lib/utils';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let album: AlbumResponseDto;
export let onRemove: ((assetIds: string[]) => void) | undefined;
@ -17,9 +17,16 @@
const { getAssets, clearSelect } = getAssetControlContext();
let isShowConfirmation = false;
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 {
const ids = [...getAssets()].map((a) => a.id);
const results = await removeAssetFromAlbum({
@ -44,36 +51,12 @@
type: NotificationType.Error,
message: 'Error removing assets from album, check console for more details',
});
} finally {
isShowConfirmation = false;
}
};
</script>
{#if menuItem}
<MenuOption text="Remove from album" icon={mdiImageRemoveOutline} on:click={() => (isShowConfirmation = true)} />
<MenuOption text="Remove from album" icon={mdiImageRemoveOutline} on:click={removeFromAlbum} />
{:else}
<CircleIconButton title="Remove from album" icon={mdiDeleteOutline} on:click={() => (isShowConfirmation = true)} />
{/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>
<CircleIconButton title="Remove from album" icon={mdiDeleteOutline} on:click={removeFromAlbum} />
{/if}

View file

@ -1,20 +1,29 @@
<script lang="ts">
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 { removeSharedLinkAssets, type SharedLinkResponseDto } from '@immich/sdk';
import { mdiDeleteOutline } from '@mdi/js';
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
import { NotificationType, notificationController } from '../../shared-components/notification/notification';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let sharedLink: SharedLinkResponseDto;
let removing = false;
const { getAssets, clearSelect } = getAssetControlContext();
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 {
const results = await removeSharedLinkAssets({
id: sharedLink.id,
@ -46,15 +55,4 @@
};
</script>
<CircleIconButton title="Remove from shared link" on:click={() => (removing = true)} 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}
<CircleIconButton title="Remove from shared link" on:click={handleRemove} icon={mdiDeleteOutline} />

View file

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

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { DateTime } from 'luxon';
import ConfirmDialogue from './confirm-dialogue.svelte';
import ConfirmDialog from './dialog/confirm-dialog.svelte';
import Combobox from './combobox.svelte';
import DateInput from '../elements/date-input.svelte';
@ -55,14 +55,14 @@
};
</script>
<ConfirmDialogue
<ConfirmDialog
id="edit-date-time-modal"
confirmColor="primary"
title="Edit date and time"
prompt="Please select a new date:"
disabled={!date.isValid}
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">
@ -84,4 +84,4 @@
/>
</div>
</div>
</ConfirmDialogue>
</ConfirmDialog>

View file

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

View file

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

View file

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

View file

@ -13,10 +13,10 @@
import Button from '../elements/buttons/button.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.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 PartnerSelectionModal from './partner-selection-modal.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
interface PartnerSharing {
user: UserResponseDto;
@ -28,7 +28,7 @@
export let user: UserResponseDto;
let createPartnerFlag = false;
let removePartnerDto: PartnerResponseDto | null = null;
// let removePartnerDto: PartnerResponseDto | null = null;
let partners: Array<PartnerSharing> = [];
onMount(async () => {
@ -75,14 +75,19 @@
}
};
const handleRemovePartner = async () => {
if (!removePartnerDto) {
const handleRemovePartner = async (partner: PartnerResponseDto) => {
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;
}
try {
await removePartner({ id: removePartnerDto.id });
removePartnerDto = null;
await removePartner({ id: partner.id });
await refreshPartners();
} catch (error) {
handleError(error, 'Unable to remove partner');
@ -133,7 +138,7 @@
{#if partner.sharedByMe}
<CircleIconButton
on:click={() => (removePartnerDto = partner.user)}
on:click={() => handleRemovePartner(partner.user)}
icon={mdiClose}
size={'16'}
title="Stop sharing your photos with this user"
@ -186,13 +191,3 @@
on:add-users={(event) => handleCreatePartners(event.detail)}
/>
{/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 APIKeyForm from '../forms/api-key-form.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 CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let keys: ApiKeyResponseDto[];
let newKey: Partial<ApiKeyResponseDto> | null = null;
let editKey: ApiKeyResponseDto | null = null;
let deleteKey: ApiKeyResponseDto | null = null;
let secret = '';
const format: Intl.DateTimeFormatOptions = {
@ -59,22 +58,26 @@
}
};
const handleDelete = async () => {
if (!deleteKey) {
const handleDelete = async (key: ApiKeyResponseDto) => {
const isConfirmed = await dialogController.show({
id: 'delete-api-key',
prompt: 'Are you sure you want to delete this API key?',
});
if (!isConfirmed) {
return;
}
try {
await deleteApiKey({ id: deleteKey.id });
await deleteApiKey({ id: key.id });
notificationController.show({
message: `Removed API Key: ${deleteKey.name}`,
message: `Removed API Key: ${key.name}`,
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to remove API Key');
} finally {
await refreshKeys();
deleteKey = null;
}
};
</script>
@ -103,15 +106,6 @@
/>
{/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">
<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
<div class="mb-2 flex justify-end">
@ -156,7 +150,7 @@
icon={mdiTrashCanOutline}
title="Delete key"
size="16"
on:click={() => (deleteKey = key)}
on:click={() => handleDelete(key)}
/>
</td>
</tr>

View file

@ -24,7 +24,6 @@
import AssetGrid from '$lib/components/photos-page/asset-grid.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 ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.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 ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
@ -81,6 +80,7 @@
} from '@mdi/js';
import { fly } from 'svelte/transition';
import type { PageData } from './$types';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let data: PageData;
@ -98,7 +98,6 @@
}
enum ViewMode {
CONFIRM_DELETE = 'confirm-delete',
LINK_SHARING = 'link-sharing',
SELECT_USERS = 'select-users',
SELECT_THUMBNAIL = 'select-thumbnail',
@ -248,10 +247,7 @@
viewMode = ViewMode.VIEW;
return;
}
if (viewMode === ViewMode.CONFIRM_DELETE) {
viewMode = ViewMode.VIEW;
return;
}
if (viewMode === ViewMode.SELECT_ASSETS) {
handleCloseSelectAssets();
return;
@ -353,6 +349,16 @@
};
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 {
await deleteAlbum({ id: album.id });
await goto(backUrl);
@ -471,11 +477,7 @@
on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)}
/>
<MenuOption icon={mdiCogOutline} text="Options" on:click={() => (viewMode = ViewMode.OPTIONS)} />
<MenuOption
icon={mdiDeleteOutline}
text="Delete album"
on:click={() => (viewMode = ViewMode.CONFIRM_DELETE)}
/>
<MenuOption icon={mdiDeleteOutline} text="Delete album" on:click={() => handleRemoveAlbum()} />
</ContextMenu>
{/if}
</div>
@ -697,21 +699,6 @@
/>
{/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}
<AlbumOptions
{album}

View file

@ -1,6 +1,6 @@
<script lang="ts">
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 CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
import {
@ -15,12 +15,11 @@
import { getAllSharedLinks, removeSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
import { mdiArrowLeft } from '@mdi/js';
import { onMount } from 'svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
let sharedLinks: SharedLinkResponseDto[] = [];
let editSharedLink: SharedLinkResponseDto | null = null;
let deleteLinkId: string | null = null;
const refresh = async () => {
sharedLinks = await getAllSharedLinks();
};
@ -29,15 +28,21 @@
await refresh();
});
const handleDeleteLink = async () => {
if (!deleteLinkId) {
const handleDeleteLink = async (id: string) => {
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;
}
try {
await removeSharedLink({ id: deleteLinkId });
await removeSharedLink({ id });
notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info });
deleteLinkId = null;
await refresh();
} catch (error) {
handleError(error, 'Unable to delete shared link');
@ -73,7 +78,7 @@
{#each sharedLinks as link (link.id)}
<SharedLinkCard
{link}
on:delete={() => (deleteLinkId = link.id)}
on:delete={() => handleDeleteLink(link.id)}
on:edit={() => (editSharedLink = link)}
on:copy={() => handleCopyLink(link.key)}
/>
@ -85,14 +90,3 @@
{#if editSharedLink}
<CreateSharedLinkModal editingLink={editSharedLink} onClose={handleEditDone} />
{/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 AssetGrid from '$lib/components/photos-page/asset-grid.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 {
NotificationType,
@ -24,6 +23,7 @@
import { mdiDeleteOutline, mdiHistory } from '@mdi/js';
import type { PageData } from './$types';
import { handlePromiseError } from '$lib/utils';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let data: PageData;
@ -32,10 +32,18 @@
const assetStore = new AssetStore({ isTrashed: true });
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
let isShowEmptyConfirmation = false;
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 {
await emptyTrash();
@ -87,7 +95,7 @@
Restore all
</div>
</LinkButton>
<LinkButton on:click={() => (isShowEmptyConfirmation = true)}>
<LinkButton on:click={() => handleEmptyTrash()}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDeleteOutline} size="18" />
Empty trash
@ -103,18 +111,3 @@
</AssetGrid>
</UserPageLayout>
{/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 '../app.css';
import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation';
import DialogWrapper from '$lib/components/shared-components/dialog/dialog-wrapper.svelte';
let showNavigationLoadingBar = false;
@ -121,6 +122,7 @@
<DownloadPanel />
<UploadPanel />
<NotificationList />
<DialogWrapper />
{#if $user?.isAdmin}
<VersionAnnouncementBox />

View file

@ -6,7 +6,6 @@
import LibraryScanSettingsForm from '$lib/components/forms/library-scan-settings-form.svelte';
import LibraryUserPickerForm from '$lib/components/forms/library-user-picker-form.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 MenuOption from '$lib/components/shared-components/context-menu/menu-option.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 type { PageData } from './$types';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
export let data: PageData;
@ -282,29 +282,39 @@
const onDeleteLibraryClicked = async () => {
closeAll();
if (selectedLibrary && confirm(`Are you sure you want to delete ${selectedLibrary.name} library?`) == true) {
await refreshStats(selectedLibraryIndex);
if (totalCount[selectedLibraryIndex] > 0) {
deleteAssetCount = totalCount[selectedLibraryIndex];
confirmDeleteLibrary = selectedLibrary;
} else {
deletedLibrary = selectedLibrary;
await handleDelete();
if (!selectedLibrary) {
return;
}
const isConfirmedLibrary = await dialogController.show({
id: 'delete-library',
prompt: `Are you sure you want to delete ${selectedLibrary.name} library?`,
});
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>
{#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}
<LibraryUserPickerForm
on:submit={({ detail }) => handleCreate(detail.ownerId)}

View file

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