mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	chore(web): replace window.confirm by ConfirmDialogues and cleanup existing ones (#3039)
* chore(web): replace window.confirm by ConfirmDialogues and cleanup existing ones * fix(web): linter and svelte-check issues * fix(web): rephrase some confirm dialogs * fix(web): run prettier * fix(web): merge with last version and run prettier again * fix(web): run prettier
This commit is contained in:
		
							parent
							
								
									734f8e02b5
								
							
						
					
					
						commit
						5869648f19
					
				@ -1,7 +1,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
	import { api, UserResponseDto } from '@api';
 | 
						import { api, UserResponseDto } from '@api';
 | 
				
			||||||
	import { createEventDispatcher } from 'svelte';
 | 
						import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
	import Button from '../elements/buttons/button.svelte';
 | 
						import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
				
			||||||
	import { handleError } from '../../utils/handle-error';
 | 
						import { handleError } from '../../utils/handle-error';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let user: UserResponseDto;
 | 
						export let user: UserResponseDto;
 | 
				
			||||||
@ -23,25 +23,14 @@
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div
 | 
					<ConfirmDialogue title="Delete User" confirmText="Delete" on:confirm={deleteUser} on:cancel>
 | 
				
			||||||
	class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
 | 
						<svelte:fragment slot="prompt">
 | 
				
			||||||
>
 | 
							<div class="flex flex-col gap-4">
 | 
				
			||||||
	<div
 | 
								<p>
 | 
				
			||||||
		class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
 | 
									<b>{user.firstName} {user.lastName}</b>'s account and assets will be permanently deleted
 | 
				
			||||||
	>
 | 
									after 7 days.
 | 
				
			||||||
		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
 | 
								</p>
 | 
				
			||||||
			Confirm User Deletion
 | 
								<p>Are you sure you want to continue?</p>
 | 
				
			||||||
		</h1>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		<p class="ml-4 text-md py-5 text-center">
 | 
					 | 
				
			||||||
			{user.firstName}
 | 
					 | 
				
			||||||
			{user.lastName} account and assets along will be marked to delete completely after 7 days. are
 | 
					 | 
				
			||||||
			you sure you want to proceed ?
 | 
					 | 
				
			||||||
		</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		<div class="flex w-full px-4 gap-4 mt-8">
 | 
					 | 
				
			||||||
			<Button fullwidth color="red" on:click={deleteUser}>Confirm</Button>
 | 
					 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</svelte:fragment>
 | 
				
			||||||
</div>
 | 
					</ConfirmDialogue>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
	import { api, UserResponseDto } from '@api';
 | 
						import { api, UserResponseDto } from '@api';
 | 
				
			||||||
	import { createEventDispatcher } from 'svelte';
 | 
						import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
	import Button from '../elements/buttons/button.svelte';
 | 
						import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let user: UserResponseDto;
 | 
						export let user: UserResponseDto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,24 +14,14 @@
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div
 | 
					<ConfirmDialogue
 | 
				
			||||||
	class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
 | 
						title="Restore User"
 | 
				
			||||||
 | 
						confirmText="Continue"
 | 
				
			||||||
 | 
						confirmColor="green"
 | 
				
			||||||
 | 
						on:confirm={restoreUser}
 | 
				
			||||||
 | 
						on:cancel
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
	<div
 | 
						<svelte:fragment slot="prompt">
 | 
				
			||||||
		class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
 | 
							<p><b>{user.firstName} {user.lastName}</b>'s account will be restored.</p>
 | 
				
			||||||
	>
 | 
						</svelte:fragment>
 | 
				
			||||||
		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
 | 
					</ConfirmDialogue>
 | 
				
			||||||
			Restore User
 | 
					 | 
				
			||||||
		</h1>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		<p class="ml-4 text-md py-5 text-center">
 | 
					 | 
				
			||||||
			{user.firstName}
 | 
					 | 
				
			||||||
			{user.lastName} account will restored
 | 
					 | 
				
			||||||
		</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		<div class="flex w-full px-4 gap-4 mt-8">
 | 
					 | 
				
			||||||
			<Button color="green" fullwidth on:click={restoreUser}>Confirm</Button>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -4,12 +4,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<ConfirmDialogue title="Disable Login" on:cancel on:confirm>
 | 
					<ConfirmDialogue title="Disable Login" on:cancel on:confirm>
 | 
				
			||||||
	<svelte:fragment slot="prompt">
 | 
						<svelte:fragment slot="prompt">
 | 
				
			||||||
		<div class="flex flex-col gap-4 p-3">
 | 
							<div class="flex flex-col gap-4">
 | 
				
			||||||
			<p class="text-md text-center">
 | 
								<p>Are you sure you want to disable all login methods? Login will be completely disabled.</p>
 | 
				
			||||||
				Are you sure you want to disable all login methods? Login will be completely disabled.
 | 
								<p>
 | 
				
			||||||
			</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<p class="text-md text-center">
 | 
					 | 
				
			||||||
				To re-enable, use a
 | 
									To re-enable, use a
 | 
				
			||||||
				<a
 | 
									<a
 | 
				
			||||||
					href="https://immich.app/docs/administration/server-commands"
 | 
										href="https://immich.app/docs/administration/server-commands"
 | 
				
			||||||
 | 
				
			|||||||
@ -43,6 +43,7 @@
 | 
				
			|||||||
	import ShareInfoModal from './share-info-modal.svelte';
 | 
						import ShareInfoModal from './share-info-modal.svelte';
 | 
				
			||||||
	import ThumbnailSelection from './thumbnail-selection.svelte';
 | 
						import ThumbnailSelection from './thumbnail-selection.svelte';
 | 
				
			||||||
	import UserSelectionModal from './user-selection-modal.svelte';
 | 
						import UserSelectionModal from './user-selection-modal.svelte';
 | 
				
			||||||
 | 
						import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
				
			||||||
	import { handleError } from '../../utils/handle-error';
 | 
						import { handleError } from '../../utils/handle-error';
 | 
				
			||||||
	import { downloadArchive } from '../../utils/asset-utils';
 | 
						import { downloadArchive } from '../../utils/asset-utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -71,6 +72,7 @@
 | 
				
			|||||||
	let isShowShareInfoModal = false;
 | 
						let isShowShareInfoModal = false;
 | 
				
			||||||
	let isShowAlbumOptions = false;
 | 
						let isShowAlbumOptions = false;
 | 
				
			||||||
	let isShowThumbnailSelection = false;
 | 
						let isShowThumbnailSelection = false;
 | 
				
			||||||
 | 
						let isShowDeleteConfirmation = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let backUrl = '/albums';
 | 
						let backUrl = '/albums';
 | 
				
			||||||
	let currentAlbumName = '';
 | 
						let currentAlbumName = '';
 | 
				
			||||||
@ -223,21 +225,17 @@
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const removeAlbum = async () => {
 | 
						const removeAlbum = async () => {
 | 
				
			||||||
		if (
 | 
							try {
 | 
				
			||||||
			window.confirm(
 | 
								await api.albumApi.deleteAlbum({ id: album.id });
 | 
				
			||||||
				`Are you sure you want to delete album ${album.albumName}? If the album is shared, other users will not be able to access it.`
 | 
								goto(backUrl);
 | 
				
			||||||
			)
 | 
							} catch (e) {
 | 
				
			||||||
		) {
 | 
								console.error('Error [userDeleteMenu] ', e);
 | 
				
			||||||
			try {
 | 
								notificationController.show({
 | 
				
			||||||
				await api.albumApi.deleteAlbum({ id: album.id });
 | 
									type: NotificationType.Error,
 | 
				
			||||||
				goto(backUrl);
 | 
									message: 'Error deleting album, check console for more details'
 | 
				
			||||||
			} catch (e) {
 | 
								});
 | 
				
			||||||
				console.error('Error [userDeleteMenu] ', e);
 | 
							} finally {
 | 
				
			||||||
				notificationController.show({
 | 
								isShowDeleteConfirmation = false;
 | 
				
			||||||
					type: NotificationType.Error,
 | 
					 | 
				
			||||||
					message: 'Error deleting album, check console for more details'
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -348,7 +346,11 @@
 | 
				
			|||||||
							on:click={() => (isShowShareUserSelection = true)}
 | 
												on:click={() => (isShowShareUserSelection = true)}
 | 
				
			||||||
							logo={ShareVariantOutline}
 | 
												logo={ShareVariantOutline}
 | 
				
			||||||
						/>
 | 
											/>
 | 
				
			||||||
						<CircleIconButton title="Remove album" on:click={removeAlbum} logo={DeleteOutline} />
 | 
											<CircleIconButton
 | 
				
			||||||
 | 
												title="Remove album"
 | 
				
			||||||
 | 
												on:click={() => (isShowDeleteConfirmation = true)}
 | 
				
			||||||
 | 
												logo={DeleteOutline}
 | 
				
			||||||
 | 
											/>
 | 
				
			||||||
					{/if}
 | 
										{/if}
 | 
				
			||||||
				{/if}
 | 
									{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -515,3 +517,17 @@
 | 
				
			|||||||
		on:thumbnail-selected={setAlbumThumbnailHandler}
 | 
							on:thumbnail-selected={setAlbumThumbnailHandler}
 | 
				
			||||||
	/>
 | 
						/>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if isShowDeleteConfirmation}
 | 
				
			||||||
 | 
						<ConfirmDialogue
 | 
				
			||||||
 | 
							title="Delete Album"
 | 
				
			||||||
 | 
							confirmText="Delete"
 | 
				
			||||||
 | 
							on:confirm={removeAlbum}
 | 
				
			||||||
 | 
							on:cancel={() => (isShowDeleteConfirmation = false)}
 | 
				
			||||||
 | 
						>
 | 
				
			||||||
 | 
							<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}
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@
 | 
				
			|||||||
	import DetailPanel from './detail-panel.svelte';
 | 
						import DetailPanel from './detail-panel.svelte';
 | 
				
			||||||
	import PhotoViewer from './photo-viewer.svelte';
 | 
						import PhotoViewer from './photo-viewer.svelte';
 | 
				
			||||||
	import VideoViewer from './video-viewer.svelte';
 | 
						import VideoViewer from './video-viewer.svelte';
 | 
				
			||||||
 | 
						import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	import { assetStore } from '$lib/stores/assets.store';
 | 
						import { assetStore } from '$lib/stores/assets.store';
 | 
				
			||||||
	import { isShowDetail } from '$lib/stores/preferences.store';
 | 
						import { isShowDetail } from '$lib/stores/preferences.store';
 | 
				
			||||||
@ -37,6 +38,7 @@
 | 
				
			|||||||
	let halfRightHover = false;
 | 
						let halfRightHover = false;
 | 
				
			||||||
	let appearsInAlbums: AlbumResponseDto[] = [];
 | 
						let appearsInAlbums: AlbumResponseDto[] = [];
 | 
				
			||||||
	let isShowAlbumPicker = false;
 | 
						let isShowAlbumPicker = false;
 | 
				
			||||||
 | 
						let isShowDeleteConfirmation = false;
 | 
				
			||||||
	let addToSharedAlbum = true;
 | 
						let addToSharedAlbum = true;
 | 
				
			||||||
	let shouldPlayMotionPhoto = false;
 | 
						let shouldPlayMotionPhoto = false;
 | 
				
			||||||
	let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true;
 | 
						let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true;
 | 
				
			||||||
@ -77,7 +79,7 @@
 | 
				
			|||||||
				closeViewer();
 | 
									closeViewer();
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			case 'Delete':
 | 
								case 'Delete':
 | 
				
			||||||
				deleteAsset();
 | 
									isShowDeleteConfirmation = true;
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			case 'i':
 | 
								case 'i':
 | 
				
			||||||
				$isShowDetail = !$isShowDetail;
 | 
									$isShowDetail = !$isShowDetail;
 | 
				
			||||||
@ -116,23 +118,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	const deleteAsset = async () => {
 | 
						const deleteAsset = async () => {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			if (
 | 
								const { data: deletedAssets } = await api.assetApi.deleteAsset({
 | 
				
			||||||
				window.confirm(
 | 
									deleteAssetDto: {
 | 
				
			||||||
					`Caution! Are you sure you want to delete this asset? This step also deletes this asset in the album(s) to which it belongs. You can not undo this action!`
 | 
										ids: [asset.id]
 | 
				
			||||||
				)
 | 
									}
 | 
				
			||||||
			) {
 | 
								});
 | 
				
			||||||
				const { data: deletedAssets } = await api.assetApi.deleteAsset({
 | 
					 | 
				
			||||||
					deleteAssetDto: {
 | 
					 | 
				
			||||||
						ids: [asset.id]
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				navigateAssetForward();
 | 
								navigateAssetForward();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				for (const asset of deletedAssets) {
 | 
								for (const asset of deletedAssets) {
 | 
				
			||||||
					if (asset.status == 'SUCCESS') {
 | 
									if (asset.status == 'SUCCESS') {
 | 
				
			||||||
						assetStore.removeAsset(asset.id);
 | 
										assetStore.removeAsset(asset.id);
 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} catch (e) {
 | 
							} catch (e) {
 | 
				
			||||||
@ -140,7 +136,9 @@
 | 
				
			|||||||
				type: NotificationType.Error,
 | 
									type: NotificationType.Error,
 | 
				
			||||||
				message: 'Error deleting this asset, check console for more details'
 | 
									message: 'Error deleting this asset, check console for more details'
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			console.error('Error deleteSelectedAssetHandler', e);
 | 
								console.error('Error deleteAsset', e);
 | 
				
			||||||
 | 
							} finally {
 | 
				
			||||||
 | 
								isShowDeleteConfirmation = false;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -227,6 +225,17 @@
 | 
				
			|||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const getAssetType = () => {
 | 
				
			||||||
 | 
							switch (asset.type) {
 | 
				
			||||||
 | 
								case 'IMAGE':
 | 
				
			||||||
 | 
									return 'Photo';
 | 
				
			||||||
 | 
								case 'VIDEO':
 | 
				
			||||||
 | 
									return 'Video';
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									return 'Asset';
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<section
 | 
					<section
 | 
				
			||||||
@ -244,7 +253,7 @@
 | 
				
			|||||||
			on:goBack={closeViewer}
 | 
								on:goBack={closeViewer}
 | 
				
			||||||
			on:showDetail={showDetailInfoHandler}
 | 
								on:showDetail={showDetailInfoHandler}
 | 
				
			||||||
			on:download={() => downloadFile(asset, publicSharedKey)}
 | 
								on:download={() => downloadFile(asset, publicSharedKey)}
 | 
				
			||||||
			on:delete={deleteAsset}
 | 
								on:delete={() => (isShowDeleteConfirmation = true)}
 | 
				
			||||||
			on:favorite={toggleFavorite}
 | 
								on:favorite={toggleFavorite}
 | 
				
			||||||
			on:addToAlbum={() => openAlbumPicker(false)}
 | 
								on:addToAlbum={() => openAlbumPicker(false)}
 | 
				
			||||||
			on:addToSharedAlbum={() => openAlbumPicker(true)}
 | 
								on:addToSharedAlbum={() => openAlbumPicker(true)}
 | 
				
			||||||
@ -358,6 +367,23 @@
 | 
				
			|||||||
			on:close={() => (isShowAlbumPicker = false)}
 | 
								on:close={() => (isShowAlbumPicker = false)}
 | 
				
			||||||
		/>
 | 
							/>
 | 
				
			||||||
	{/if}
 | 
						{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{#if isShowDeleteConfirmation}
 | 
				
			||||||
 | 
							<ConfirmDialogue
 | 
				
			||||||
 | 
								title="Delete {getAssetType()}"
 | 
				
			||||||
 | 
								confirmText="Delete"
 | 
				
			||||||
 | 
								on:confirm={deleteAsset}
 | 
				
			||||||
 | 
								on:cancel={() => (isShowDeleteConfirmation = false)}
 | 
				
			||||||
 | 
							>
 | 
				
			||||||
 | 
								<svelte:fragment slot="prompt">
 | 
				
			||||||
 | 
									<p>
 | 
				
			||||||
 | 
										Are you sure you want to delete this {getAssetType().toLowerCase()}? This will also remove
 | 
				
			||||||
 | 
										it from its album(s).
 | 
				
			||||||
 | 
									</p>
 | 
				
			||||||
 | 
									<p><b>You cannot undo this action!</b></p>
 | 
				
			||||||
 | 
								</svelte:fragment>
 | 
				
			||||||
 | 
							</ConfirmDialogue>
 | 
				
			||||||
 | 
						{/if}
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@
 | 
				
			|||||||
		NotificationType
 | 
							NotificationType
 | 
				
			||||||
	} from '../shared-components/notification/notification';
 | 
						} from '../shared-components/notification/notification';
 | 
				
			||||||
	import Button from '../elements/buttons/button.svelte';
 | 
						import Button from '../elements/buttons/button.svelte';
 | 
				
			||||||
 | 
						import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
				
			||||||
	import { handleError } from '../../utils/handle-error';
 | 
						import { handleError } from '../../utils/handle-error';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let user: UserResponseDto;
 | 
						export let user: UserResponseDto;
 | 
				
			||||||
@ -15,6 +16,8 @@
 | 
				
			|||||||
	let error: string;
 | 
						let error: string;
 | 
				
			||||||
	let success: string;
 | 
						let success: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let isShowResetPasswordConfirmation = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const dispatch = createEventDispatcher();
 | 
						const dispatch = createEventDispatcher();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const editUser = async () => {
 | 
						const editUser = async () => {
 | 
				
			||||||
@ -41,20 +44,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	const resetPassword = async () => {
 | 
						const resetPassword = async () => {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			if (window.confirm('Do you want to reset the user password?')) {
 | 
								const defaultPassword = 'password';
 | 
				
			||||||
				const defaultPassword = 'password';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				const { status } = await api.userApi.updateUser({
 | 
								const { status } = await api.userApi.updateUser({
 | 
				
			||||||
					updateUserDto: {
 | 
									updateUserDto: {
 | 
				
			||||||
						id: user.id,
 | 
										id: user.id,
 | 
				
			||||||
						password: defaultPassword,
 | 
										password: defaultPassword,
 | 
				
			||||||
						shouldChangePassword: true
 | 
										shouldChangePassword: true
 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (status == 200) {
 | 
					 | 
				
			||||||
					dispatch('reset-password-success');
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (status == 200) {
 | 
				
			||||||
 | 
									dispatch('reset-password-success');
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} catch (e) {
 | 
							} catch (e) {
 | 
				
			||||||
			console.error('Error reseting user password', e);
 | 
								console.error('Error reseting user password', e);
 | 
				
			||||||
@ -62,6 +63,8 @@
 | 
				
			|||||||
				message: 'Error reseting user password, check console for more details',
 | 
									message: 'Error reseting user password, check console for more details',
 | 
				
			||||||
				type: NotificationType.Error
 | 
									type: NotificationType.Error
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
							} finally {
 | 
				
			||||||
 | 
								isShowResetPasswordConfirmation = false;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@ -125,9 +128,9 @@
 | 
				
			|||||||
			/>
 | 
								/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<p>
 | 
								<p>
 | 
				
			||||||
				Note: To apply the Storage Label to previously uploaded assets, run the <a
 | 
									Note: To apply the Storage Label to previously uploaded assets, run the
 | 
				
			||||||
					href="/admin/jobs-status"
 | 
									<a href="/admin/jobs-status" class="text-immich-primary dark:text-immich-dark-primary">
 | 
				
			||||||
					class="text-immich-primary dark:text-immich-dark-primary">Storage Migration Job</a
 | 
										Storage Migration Job</a
 | 
				
			||||||
				>
 | 
									>
 | 
				
			||||||
			</p>
 | 
								</p>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
@ -157,9 +160,28 @@
 | 
				
			|||||||
		{/if}
 | 
							{/if}
 | 
				
			||||||
		<div class="flex w-full px-4 gap-4 mt-8">
 | 
							<div class="flex w-full px-4 gap-4 mt-8">
 | 
				
			||||||
			{#if canResetPassword}
 | 
								{#if canResetPassword}
 | 
				
			||||||
				<Button color="light-red" fullwidth on:click={resetPassword}>Reset password</Button>
 | 
									<Button
 | 
				
			||||||
 | 
										color="light-red"
 | 
				
			||||||
 | 
										fullwidth
 | 
				
			||||||
 | 
										on:click={() => (isShowResetPasswordConfirmation = true)}>Reset password</Button
 | 
				
			||||||
 | 
									>
 | 
				
			||||||
			{/if}
 | 
								{/if}
 | 
				
			||||||
			<Button type="submit" fullwidth>Confirm</Button>
 | 
								<Button type="submit" fullwidth>Confirm</Button>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</form>
 | 
						</form>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if isShowResetPasswordConfirmation}
 | 
				
			||||||
 | 
						<ConfirmDialogue
 | 
				
			||||||
 | 
							title="Reset Password"
 | 
				
			||||||
 | 
							confirmText="Reset"
 | 
				
			||||||
 | 
							on:confirm={resetPassword}
 | 
				
			||||||
 | 
							on:cancel={() => (isShowResetPasswordConfirmation = false)}
 | 
				
			||||||
 | 
						>
 | 
				
			||||||
 | 
							<svelte:fragment slot="prompt">
 | 
				
			||||||
 | 
								<p>
 | 
				
			||||||
 | 
									Are you sure you want to reset <b>{user.firstName} {user.lastName}</b>'s password?
 | 
				
			||||||
 | 
								</p>
 | 
				
			||||||
 | 
							</svelte:fragment>
 | 
				
			||||||
 | 
						</ConfirmDialogue>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
	export let onAssetDelete: OnAssetDelete;
 | 
						export let onAssetDelete: OnAssetDelete;
 | 
				
			||||||
	const { getAssets, clearSelect } = getAssetControlContext();
 | 
						const { getAssets, clearSelect } = getAssetControlContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let confirm = false;
 | 
						let isShowConfirmation = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const handleDelete = async () => {
 | 
						const handleDelete = async () => {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
@ -32,24 +32,43 @@
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			notificationController.show({ message: `Deleted ${count}`, type: NotificationType.Info });
 | 
								notificationController.show({
 | 
				
			||||||
 | 
									message: `Deleted ${count}`,
 | 
				
			||||||
 | 
									type: NotificationType.Info
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			clearSelect();
 | 
								clearSelect();
 | 
				
			||||||
		} catch (e) {
 | 
							} catch (e) {
 | 
				
			||||||
			handleError(e, 'Error deleting assets');
 | 
								handleError(e, 'Error deleting assets');
 | 
				
			||||||
 | 
							} finally {
 | 
				
			||||||
 | 
								isShowConfirmation = false;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<CircleIconButton title="Delete" logo={DeleteOutline} on:click={() => (confirm = true)} />
 | 
					<CircleIconButton
 | 
				
			||||||
 | 
						title="Delete"
 | 
				
			||||||
 | 
						logo={DeleteOutline}
 | 
				
			||||||
 | 
						on:click={() => (isShowConfirmation = true)}
 | 
				
			||||||
 | 
					/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if confirm}
 | 
					{#if isShowConfirmation}
 | 
				
			||||||
	<ConfirmDialogue
 | 
						<ConfirmDialogue
 | 
				
			||||||
		prompt="Are you sure you want to delete {getAssets()
 | 
							title="Delete Asset{getAssets().size > 1 ? 's' : ''}"
 | 
				
			||||||
			.size} assets? This step also deletes assets in the album(s) to which they belong. You can not undo this action!"
 | 
					 | 
				
			||||||
		title="Delete assets?"
 | 
					 | 
				
			||||||
		confirmText="Delete"
 | 
							confirmText="Delete"
 | 
				
			||||||
		on:confirm={handleDelete}
 | 
							on:confirm={handleDelete}
 | 
				
			||||||
		on:cancel={() => (confirm = false)}
 | 
							on:cancel={() => (isShowConfirmation = false)}
 | 
				
			||||||
	/>
 | 
						>
 | 
				
			||||||
 | 
							<svelte:fragment slot="prompt">
 | 
				
			||||||
 | 
								<p>
 | 
				
			||||||
 | 
									Are you sure you want to delete
 | 
				
			||||||
 | 
									{#if getAssets().size > 1}
 | 
				
			||||||
 | 
										these <b>{getAssets().size}</b> assets? This will also remove them from their album(s).
 | 
				
			||||||
 | 
									{:else}
 | 
				
			||||||
 | 
										this asset? This will also remove it from its album(s).
 | 
				
			||||||
 | 
									{/if}
 | 
				
			||||||
 | 
								</p>
 | 
				
			||||||
 | 
								<p><b>You cannot undo this action!</b></p>
 | 
				
			||||||
 | 
							</svelte:fragment>
 | 
				
			||||||
 | 
						</ConfirmDialogue>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,32 +7,60 @@
 | 
				
			|||||||
	import { AlbumResponseDto, api } from '@api';
 | 
						import { AlbumResponseDto, api } from '@api';
 | 
				
			||||||
	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
 | 
						import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
 | 
				
			||||||
	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
						import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
				
			||||||
 | 
						import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let album: AlbumResponseDto;
 | 
						export let album: AlbumResponseDto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const { getAssets, clearSelect } = getAssetControlContext();
 | 
						const { getAssets, clearSelect } = getAssetControlContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const handleRemoveFromAlbum = async () => {
 | 
						let isShowConfirmation = false;
 | 
				
			||||||
		if (window.confirm('Do you want to remove selected assets from the album?')) {
 | 
					 | 
				
			||||||
			try {
 | 
					 | 
				
			||||||
				const { data } = await api.albumApi.removeAssetFromAlbum({
 | 
					 | 
				
			||||||
					id: album.id,
 | 
					 | 
				
			||||||
					removeAssetsDto: {
 | 
					 | 
				
			||||||
						assetIds: Array.from(getAssets()).map((a) => a.id)
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				album = data;
 | 
						const removeFromAlbum = async () => {
 | 
				
			||||||
				clearSelect();
 | 
							try {
 | 
				
			||||||
			} catch (e) {
 | 
								const { data } = await api.albumApi.removeAssetFromAlbum({
 | 
				
			||||||
				console.error('Error [album-viewer] [removeAssetFromAlbum]', e);
 | 
									id: album.id,
 | 
				
			||||||
				notificationController.show({
 | 
									removeAssetsDto: {
 | 
				
			||||||
					type: NotificationType.Error,
 | 
										assetIds: Array.from(getAssets()).map((a) => a.id)
 | 
				
			||||||
					message: 'Error removing assets from album, check console for more details'
 | 
									}
 | 
				
			||||||
				});
 | 
								});
 | 
				
			||||||
			}
 | 
					
 | 
				
			||||||
 | 
								album = data;
 | 
				
			||||||
 | 
								clearSelect();
 | 
				
			||||||
 | 
							} catch (e) {
 | 
				
			||||||
 | 
								console.error('Error [album-viewer] [removeAssetFromAlbum]', e);
 | 
				
			||||||
 | 
								notificationController.show({
 | 
				
			||||||
 | 
									type: NotificationType.Error,
 | 
				
			||||||
 | 
									message: 'Error removing assets from album, check console for more details'
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} finally {
 | 
				
			||||||
 | 
								isShowConfirmation = false;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<CircleIconButton title="Remove from album" on:click={handleRemoveFromAlbum} logo={DeleteOutline} />
 | 
					<CircleIconButton
 | 
				
			||||||
 | 
						title="Remove from album"
 | 
				
			||||||
 | 
						on:click={() => (isShowConfirmation = true)}
 | 
				
			||||||
 | 
						logo={DeleteOutline}
 | 
				
			||||||
 | 
					/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if isShowConfirmation}
 | 
				
			||||||
 | 
						<ConfirmDialogue
 | 
				
			||||||
 | 
							title="Remove Asset{getAssets().size > 1 ? 's' : ''}"
 | 
				
			||||||
 | 
							confirmText="Remove"
 | 
				
			||||||
 | 
							on:confirm={removeFromAlbum}
 | 
				
			||||||
 | 
							on:cancel={() => (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}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,18 +2,29 @@
 | 
				
			|||||||
	import { createEventDispatcher } from 'svelte';
 | 
						import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
	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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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';
 | 
				
			||||||
 | 
						export let confirmColor: Color = 'red';
 | 
				
			||||||
	export let cancelText = 'Cancel';
 | 
						export let cancelText = 'Cancel';
 | 
				
			||||||
 | 
						export let cancelColor: Color = 'primary';
 | 
				
			||||||
 | 
						export let hideCancelButton = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const dispatch = createEventDispatcher();
 | 
						const dispatch = createEventDispatcher();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let isConfirmButtonDisabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const handleCancel = () => dispatch('cancel');
 | 
						const handleCancel = () => dispatch('cancel');
 | 
				
			||||||
	const handleConfirm = () => dispatch('confirm');
 | 
					
 | 
				
			||||||
 | 
						const handleConfirm = () => {
 | 
				
			||||||
 | 
							isConfirmButtonDisabled = true;
 | 
				
			||||||
 | 
							dispatch('confirm');
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<FullScreenModal on:clickOutside={() => handleCancel()}>
 | 
					<FullScreenModal on:clickOutside={handleCancel}>
 | 
				
			||||||
	<div
 | 
						<div
 | 
				
			||||||
		class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
 | 
							class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
 | 
				
			||||||
	>
 | 
						>
 | 
				
			||||||
@ -25,13 +36,26 @@
 | 
				
			|||||||
			</h1>
 | 
								</h1>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div>
 | 
							<div>
 | 
				
			||||||
			<slot name="prompt">
 | 
								<div class="px-4 py-5 text-md text-center">
 | 
				
			||||||
				<p class="ml-4 text-md py-5 text-center">{prompt}</p>
 | 
									<slot name="prompt">
 | 
				
			||||||
			</slot>
 | 
										<p>{prompt}</p>
 | 
				
			||||||
 | 
									</slot>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<div class="flex w-full px-4 gap-4 mt-4">
 | 
								<div class="flex w-full px-4 gap-4 mt-4">
 | 
				
			||||||
				<Button fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
 | 
									{#if !hideCancelButton}
 | 
				
			||||||
				<Button color="red" fullwidth on:click={() => handleConfirm()}>{confirmText}</Button>
 | 
										<Button color={cancelColor} fullwidth on:click={handleCancel}>
 | 
				
			||||||
 | 
											{cancelText}
 | 
				
			||||||
 | 
										</Button>
 | 
				
			||||||
 | 
									{/if}
 | 
				
			||||||
 | 
									<Button
 | 
				
			||||||
 | 
										color={confirmColor}
 | 
				
			||||||
 | 
										fullwidth
 | 
				
			||||||
 | 
										on:click={handleConfirm}
 | 
				
			||||||
 | 
										disabled={isConfirmButtonDisabled}
 | 
				
			||||||
 | 
									>
 | 
				
			||||||
 | 
										{confirmText}
 | 
				
			||||||
 | 
									</Button>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,12 @@
 | 
				
			|||||||
	import { onMount } from 'svelte';
 | 
						import { onMount } from 'svelte';
 | 
				
			||||||
	import { flip } from 'svelte/animate';
 | 
						import { flip } from 'svelte/animate';
 | 
				
			||||||
	import Dropdown from '$lib/components/elements/dropdown.svelte';
 | 
						import Dropdown from '$lib/components/elements/dropdown.svelte';
 | 
				
			||||||
 | 
						import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
				
			||||||
 | 
						import {
 | 
				
			||||||
 | 
							notificationController,
 | 
				
			||||||
 | 
							NotificationType
 | 
				
			||||||
 | 
						} from '$lib/components/shared-components/notification/notification';
 | 
				
			||||||
 | 
						import type { AlbumResponseDto } from '@api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let data: PageData;
 | 
						export let data: PageData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -23,14 +29,36 @@
 | 
				
			|||||||
		albums: unsortedAlbums,
 | 
							albums: unsortedAlbums,
 | 
				
			||||||
		isShowContextMenu,
 | 
							isShowContextMenu,
 | 
				
			||||||
		contextMenuPosition,
 | 
							contextMenuPosition,
 | 
				
			||||||
 | 
							contextMenuTargetAlbum,
 | 
				
			||||||
		createAlbum,
 | 
							createAlbum,
 | 
				
			||||||
		deleteAlbum,
 | 
							deleteAlbum,
 | 
				
			||||||
		deleteSelectedContextAlbum,
 | 
					 | 
				
			||||||
		showAlbumContextMenu,
 | 
							showAlbumContextMenu,
 | 
				
			||||||
		closeAlbumContextMenu
 | 
							closeAlbumContextMenu
 | 
				
			||||||
	} = useAlbums({ albums: data.albums });
 | 
						} = useAlbums({ albums: data.albums });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let albums = unsortedAlbums;
 | 
						let albums = unsortedAlbums;
 | 
				
			||||||
 | 
						let albumToDelete: AlbumResponseDto | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const setAlbumToDelete = () => {
 | 
				
			||||||
 | 
							albumToDelete = $contextMenuTargetAlbum ?? null;
 | 
				
			||||||
 | 
							closeAlbumContextMenu();
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const deleteSelectedAlbum = async () => {
 | 
				
			||||||
 | 
							if (!albumToDelete) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								await deleteAlbum(albumToDelete);
 | 
				
			||||||
 | 
							} catch {
 | 
				
			||||||
 | 
								notificationController.show({
 | 
				
			||||||
 | 
									message: 'Error deleting album',
 | 
				
			||||||
 | 
									type: NotificationType.Error
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} finally {
 | 
				
			||||||
 | 
								albumToDelete = null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const sortByDate = (a: string, b: string) => {
 | 
						const sortByDate = (a: string, b: string) => {
 | 
				
			||||||
		const aDate = new Date(a);
 | 
							const aDate = new Date(a);
 | 
				
			||||||
@ -69,7 +97,6 @@
 | 
				
			|||||||
			for (const album of $albums) {
 | 
								for (const album of $albums) {
 | 
				
			||||||
				if (album.assetCount == 0 && album.albumName == 'Untitled') {
 | 
									if (album.assetCount == 0 && album.albumName == 'Untitled') {
 | 
				
			||||||
					await deleteAlbum(album);
 | 
										await deleteAlbum(album);
 | 
				
			||||||
					$albums = $albums.filter((a) => a.id !== album.id);
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} catch (error) {
 | 
							} catch (error) {
 | 
				
			||||||
@ -120,7 +147,7 @@
 | 
				
			|||||||
<!-- Context Menu -->
 | 
					<!-- Context Menu -->
 | 
				
			||||||
{#if $isShowContextMenu}
 | 
					{#if $isShowContextMenu}
 | 
				
			||||||
	<ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu}>
 | 
						<ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu}>
 | 
				
			||||||
		<MenuOption on:click={deleteSelectedContextAlbum}>
 | 
							<MenuOption on:click={() => setAlbumToDelete()}>
 | 
				
			||||||
			<span class="flex place-items-center place-content-center gap-2">
 | 
								<span class="flex place-items-center place-content-center gap-2">
 | 
				
			||||||
				<DeleteOutline size="18" />
 | 
									<DeleteOutline size="18" />
 | 
				
			||||||
				<p>Delete album</p>
 | 
									<p>Delete album</p>
 | 
				
			||||||
@ -128,3 +155,17 @@
 | 
				
			|||||||
		</MenuOption>
 | 
							</MenuOption>
 | 
				
			||||||
	</ContextMenu>
 | 
						</ContextMenu>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if albumToDelete}
 | 
				
			||||||
 | 
						<ConfirmDialogue
 | 
				
			||||||
 | 
							title="Delete Album"
 | 
				
			||||||
 | 
							confirmText="Delete"
 | 
				
			||||||
 | 
							on:confirm={deleteSelectedAlbum}
 | 
				
			||||||
 | 
							on:cancel={() => (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}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,10 +12,6 @@ jest.mock('@api');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const apiMock: jest.MockedObject<typeof api> = api as jest.MockedObject<typeof api>;
 | 
					const apiMock: jest.MockedObject<typeof api> = api as jest.MockedObject<typeof api>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function mockWindowConfirm(result: boolean) {
 | 
					 | 
				
			||||||
	jest.spyOn(global, 'confirm').mockReturnValueOnce(result);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Albums BLoC', () => {
 | 
					describe('Albums BLoC', () => {
 | 
				
			||||||
	let sut: ReturnType<typeof useAlbums>;
 | 
						let sut: ReturnType<typeof useAlbums>;
 | 
				
			||||||
	const _albums = albumFactory.buildList(5);
 | 
						const _albums = albumFactory.buildList(5);
 | 
				
			||||||
@ -115,8 +111,6 @@ describe('Albums BLoC', () => {
 | 
				
			|||||||
			statusText: ''
 | 
								statusText: ''
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		mockWindowConfirm(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const albumToDelete = get(sut.albums)[2]; // delete third album
 | 
							const albumToDelete = get(sut.albums)[2]; // delete third album
 | 
				
			||||||
		const albumToDeleteId = albumToDelete.id;
 | 
							const albumToDeleteId = albumToDelete.id;
 | 
				
			||||||
		const contextMenuCoords = { x: 100, y: 150 };
 | 
							const contextMenuCoords = { x: 100, y: 150 };
 | 
				
			||||||
@ -125,52 +119,15 @@ describe('Albums BLoC', () => {
 | 
				
			|||||||
		sut.showAlbumContextMenu(contextMenuCoords, albumToDelete);
 | 
							sut.showAlbumContextMenu(contextMenuCoords, albumToDelete);
 | 
				
			||||||
		expect(get(sut.contextMenuPosition)).toEqual(contextMenuCoords);
 | 
							expect(get(sut.contextMenuPosition)).toEqual(contextMenuCoords);
 | 
				
			||||||
		expect(get(sut.isShowContextMenu)).toBe(true);
 | 
							expect(get(sut.isShowContextMenu)).toBe(true);
 | 
				
			||||||
 | 
							expect(get(sut.contextMenuTargetAlbum)).toEqual(albumToDelete);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		await sut.deleteSelectedContextAlbum();
 | 
							await sut.deleteAlbum(albumToDelete);
 | 
				
			||||||
		const updatedAlbums = get(sut.albums);
 | 
							const updatedAlbums = get(sut.albums);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledTimes(1);
 | 
							expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledTimes(1);
 | 
				
			||||||
		expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledWith({ id: albumToDeleteId });
 | 
							expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledWith({ id: albumToDeleteId });
 | 
				
			||||||
		expect(updatedAlbums).toHaveLength(4);
 | 
							expect(updatedAlbums).toHaveLength(4);
 | 
				
			||||||
		expect(updatedAlbums).not.toContain(albumToDelete);
 | 
							expect(updatedAlbums).not.toContain(albumToDelete);
 | 
				
			||||||
		expect(get(sut.isShowContextMenu)).toBe(false);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	it('shows error message when it fails deleting an album', async () => {
 | 
					 | 
				
			||||||
		mockWindowConfirm(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const albumToDelete = get(sut.albums)[2]; // delete third album
 | 
					 | 
				
			||||||
		const contextMenuCoords = { x: 100, y: 150 };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		apiMock.albumApi.deleteAlbum.mockRejectedValueOnce({});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		sut.showAlbumContextMenu(contextMenuCoords, albumToDelete);
 | 
					 | 
				
			||||||
		const newAlbum = await sut.deleteSelectedContextAlbum();
 | 
					 | 
				
			||||||
		const notifications = get(notificationController.notificationList);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
		expect(newAlbum).not.toBeDefined();
 | 
					 | 
				
			||||||
		expect(notifications).toHaveLength(1);
 | 
					 | 
				
			||||||
		expect(notifications[0].type).toEqual(NotificationType.Error);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	it('prevents deleting an album when rejecting confirm dialog', async () => {
 | 
					 | 
				
			||||||
		const albumToDelete = get(sut.albums)[2]; // delete third album
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		mockWindowConfirm(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		sut.showAlbumContextMenu({ x: 100, y: 150 }, albumToDelete);
 | 
					 | 
				
			||||||
		await sut.deleteSelectedContextAlbum();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		expect(apiMock.albumApi.deleteAlbum).not.toHaveBeenCalled();
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	it('prevents deleting an album when not previously selected', async () => {
 | 
					 | 
				
			||||||
		mockWindowConfirm(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		await sut.deleteSelectedContextAlbum();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		expect(apiMock.albumApi.deleteAlbum).not.toHaveBeenCalled();
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	it('closes album context menu, deselecting album', () => {
 | 
						it('closes album context menu, deselecting album', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -24,8 +24,6 @@ export const useAlbums = (props: AlbumsProps) => {
 | 
				
			|||||||
				if (album.albumName === 'Untitled' && album.assetCount === 0) {
 | 
									if (album.albumName === 'Untitled' && album.assetCount === 0) {
 | 
				
			||||||
					setTimeout(async () => {
 | 
										setTimeout(async () => {
 | 
				
			||||||
						await deleteAlbum(album);
 | 
											await deleteAlbum(album);
 | 
				
			||||||
						const _albums = get(albums);
 | 
					 | 
				
			||||||
						albums.set(_albums.filter((a) => a.id !== album.id));
 | 
					 | 
				
			||||||
					}, 500);
 | 
										}, 500);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -54,8 +52,13 @@ export const useAlbums = (props: AlbumsProps) => {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async function deleteAlbum(album: AlbumResponseDto): Promise<void> {
 | 
						async function deleteAlbum(albumToDelete: AlbumResponseDto): Promise<void> {
 | 
				
			||||||
		await api.albumApi.deleteAlbum({ id: album.id });
 | 
							await api.albumApi.deleteAlbum({ id: albumToDelete.id });
 | 
				
			||||||
 | 
							albums.set(
 | 
				
			||||||
 | 
								get(albums).filter(({ id }) => {
 | 
				
			||||||
 | 
									return id !== albumToDelete.id;
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async function showAlbumContextMenu(
 | 
						async function showAlbumContextMenu(
 | 
				
			||||||
@ -74,40 +77,15 @@ export const useAlbums = (props: AlbumsProps) => {
 | 
				
			|||||||
		contextMenuTargetAlbum.set(undefined);
 | 
							contextMenuTargetAlbum.set(undefined);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async function deleteSelectedContextAlbum(): Promise<void> {
 | 
					 | 
				
			||||||
		const albumToDelete = get(contextMenuTargetAlbum);
 | 
					 | 
				
			||||||
		if (!albumToDelete) {
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (
 | 
					 | 
				
			||||||
			window.confirm(
 | 
					 | 
				
			||||||
				`Are you sure you want to delete album ${albumToDelete.albumName}? If the album is shared, other users will not be able to access it.`
 | 
					 | 
				
			||||||
			)
 | 
					 | 
				
			||||||
		) {
 | 
					 | 
				
			||||||
			try {
 | 
					 | 
				
			||||||
				await api.albumApi.deleteAlbum({ id: albumToDelete.id });
 | 
					 | 
				
			||||||
				const _albums = get(albums);
 | 
					 | 
				
			||||||
				albums.set(_albums.filter((a) => a.id !== albumToDelete.id));
 | 
					 | 
				
			||||||
			} catch {
 | 
					 | 
				
			||||||
				notificationController.show({
 | 
					 | 
				
			||||||
					message: 'Error deleting album',
 | 
					 | 
				
			||||||
					type: NotificationType.Error
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		closeAlbumContextMenu();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
		albums,
 | 
							albums,
 | 
				
			||||||
		isShowContextMenu,
 | 
							isShowContextMenu,
 | 
				
			||||||
		contextMenuPosition,
 | 
							contextMenuPosition,
 | 
				
			||||||
 | 
							contextMenuTargetAlbum,
 | 
				
			||||||
		loadAlbums,
 | 
							loadAlbums,
 | 
				
			||||||
		createAlbum,
 | 
							createAlbum,
 | 
				
			||||||
		deleteAlbum,
 | 
							deleteAlbum,
 | 
				
			||||||
		showAlbumContextMenu,
 | 
							showAlbumContextMenu,
 | 
				
			||||||
		closeAlbumContextMenu,
 | 
							closeAlbumContextMenu
 | 
				
			||||||
		deleteSelectedContextAlbum
 | 
					 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -98,7 +98,7 @@
 | 
				
			|||||||
{#if deleteLinkId}
 | 
					{#if deleteLinkId}
 | 
				
			||||||
	<ConfirmDialogue
 | 
						<ConfirmDialogue
 | 
				
			||||||
		title="Delete Shared Link"
 | 
							title="Delete Shared Link"
 | 
				
			||||||
		prompt="Are you want to delete this shared link?"
 | 
							prompt="Are you sure you want to delete this shared link?"
 | 
				
			||||||
		confirmText="Delete"
 | 
							confirmText="Delete"
 | 
				
			||||||
		on:confirm={() => handleDeleteLink()}
 | 
							on:confirm={() => handleDeleteLink()}
 | 
				
			||||||
		on:cancel={() => (deleteLinkId = null)}
 | 
							on:cancel={() => (deleteLinkId = null)}
 | 
				
			||||||
 | 
				
			|||||||
@ -126,23 +126,21 @@
 | 
				
			|||||||
	{/if}
 | 
						{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{#if shouldShowDeleteConfirmDialog}
 | 
						{#if shouldShowDeleteConfirmDialog}
 | 
				
			||||||
		<FullScreenModal on:clickOutside={() => (shouldShowDeleteConfirmDialog = false)}>
 | 
							<DeleteConfirmDialog
 | 
				
			||||||
			<DeleteConfirmDialog
 | 
								user={selectedUser}
 | 
				
			||||||
				user={selectedUser}
 | 
								on:user-delete-success={onUserDeleteSuccess}
 | 
				
			||||||
				on:user-delete-success={onUserDeleteSuccess}
 | 
								on:user-delete-fail={onUserDeleteFail}
 | 
				
			||||||
				on:user-delete-fail={onUserDeleteFail}
 | 
								on:cancel={() => (shouldShowDeleteConfirmDialog = false)}
 | 
				
			||||||
			/>
 | 
							/>
 | 
				
			||||||
		</FullScreenModal>
 | 
					 | 
				
			||||||
	{/if}
 | 
						{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{#if shouldShowRestoreDialog}
 | 
						{#if shouldShowRestoreDialog}
 | 
				
			||||||
		<FullScreenModal on:clickOutside={() => (shouldShowRestoreDialog = false)}>
 | 
							<RestoreDialogue
 | 
				
			||||||
			<RestoreDialogue
 | 
								user={selectedUser}
 | 
				
			||||||
				user={selectedUser}
 | 
								on:user-restore-success={onUserRestoreSuccess}
 | 
				
			||||||
				on:user-restore-success={onUserRestoreSuccess}
 | 
								on:user-restore-fail={onUserRestoreFail}
 | 
				
			||||||
				on:user-restore-fail={onUserRestoreFail}
 | 
								on:cancel={() => (shouldShowRestoreDialog = false)}
 | 
				
			||||||
			/>
 | 
							/>
 | 
				
			||||||
		</FullScreenModal>
 | 
					 | 
				
			||||||
	{/if}
 | 
						{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{#if shouldShowInfoPanel}
 | 
						{#if shouldShowInfoPanel}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user