mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 18:47:09 -04: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"> | ||||
| 	import { api, UserResponseDto } from '@api'; | ||||
| 	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'; | ||||
| 
 | ||||
| 	export let user: UserResponseDto; | ||||
| @ -23,25 +23,14 @@ | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <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" | ||||
| > | ||||
| 	<div | ||||
| 		class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary" | ||||
| 	> | ||||
| 		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium"> | ||||
| 			Confirm User Deletion | ||||
| 		</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 ? | ||||
| <ConfirmDialogue title="Delete User" confirmText="Delete" on:confirm={deleteUser} on:cancel> | ||||
| 	<svelte:fragment slot="prompt"> | ||||
| 		<div class="flex flex-col gap-4"> | ||||
| 			<p> | ||||
| 				<b>{user.firstName} {user.lastName}</b>'s account and assets will be permanently deleted | ||||
| 				after 7 days. | ||||
| 			</p> | ||||
| 
 | ||||
| 		<div class="flex w-full px-4 gap-4 mt-8"> | ||||
| 			<Button fullwidth color="red" on:click={deleteUser}>Confirm</Button> | ||||
| 			<p>Are you sure you want to continue?</p> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| 	</svelte:fragment> | ||||
| </ConfirmDialogue> | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| <script lang="ts"> | ||||
| 	import { api, UserResponseDto } from '@api'; | ||||
| 	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; | ||||
| 
 | ||||
| @ -14,24 +14,14 @@ | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <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" | ||||
| <ConfirmDialogue | ||||
| 	title="Restore User" | ||||
| 	confirmText="Continue" | ||||
| 	confirmColor="green" | ||||
| 	on:confirm={restoreUser} | ||||
| 	on:cancel | ||||
| > | ||||
| 	<div | ||||
| 		class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary" | ||||
| 	> | ||||
| 		<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium"> | ||||
| 			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> | ||||
| 	<svelte:fragment slot="prompt"> | ||||
| 		<p><b>{user.firstName} {user.lastName}</b>'s account will be restored.</p> | ||||
| 	</svelte:fragment> | ||||
| </ConfirmDialogue> | ||||
|  | ||||
| @ -4,12 +4,9 @@ | ||||
| 
 | ||||
| <ConfirmDialogue title="Disable Login" on:cancel on:confirm> | ||||
| 	<svelte:fragment slot="prompt"> | ||||
| 		<div class="flex flex-col gap-4 p-3"> | ||||
| 			<p class="text-md text-center"> | ||||
| 				Are you sure you want to disable all login methods? Login will be completely disabled. | ||||
| 			</p> | ||||
| 
 | ||||
| 			<p class="text-md text-center"> | ||||
| 		<div class="flex flex-col gap-4"> | ||||
| 			<p>Are you sure you want to disable all login methods? Login will be completely disabled.</p> | ||||
| 			<p> | ||||
| 				To re-enable, use a | ||||
| 				<a | ||||
| 					href="https://immich.app/docs/administration/server-commands" | ||||
|  | ||||
| @ -43,6 +43,7 @@ | ||||
| 	import ShareInfoModal from './share-info-modal.svelte'; | ||||
| 	import ThumbnailSelection from './thumbnail-selection.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 { downloadArchive } from '../../utils/asset-utils'; | ||||
| 
 | ||||
| @ -71,6 +72,7 @@ | ||||
| 	let isShowShareInfoModal = false; | ||||
| 	let isShowAlbumOptions = false; | ||||
| 	let isShowThumbnailSelection = false; | ||||
| 	let isShowDeleteConfirmation = false; | ||||
| 
 | ||||
| 	let backUrl = '/albums'; | ||||
| 	let currentAlbumName = ''; | ||||
| @ -223,11 +225,6 @@ | ||||
| 	}; | ||||
| 
 | ||||
| 	const removeAlbum = async () => { | ||||
| 		if ( | ||||
| 			window.confirm( | ||||
| 				`Are you sure you want to delete album ${album.albumName}? If the album is shared, other users will not be able to access it.` | ||||
| 			) | ||||
| 		) { | ||||
| 		try { | ||||
| 			await api.albumApi.deleteAlbum({ id: album.id }); | ||||
| 			goto(backUrl); | ||||
| @ -237,7 +234,8 @@ | ||||
| 				type: NotificationType.Error, | ||||
| 				message: 'Error deleting album, check console for more details' | ||||
| 			}); | ||||
| 			} | ||||
| 		} finally { | ||||
| 			isShowDeleteConfirmation = false; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| @ -348,7 +346,11 @@ | ||||
| 							on:click={() => (isShowShareUserSelection = true)} | ||||
| 							logo={ShareVariantOutline} | ||||
| 						/> | ||||
| 						<CircleIconButton title="Remove album" on:click={removeAlbum} logo={DeleteOutline} /> | ||||
| 						<CircleIconButton | ||||
| 							title="Remove album" | ||||
| 							on:click={() => (isShowDeleteConfirmation = true)} | ||||
| 							logo={DeleteOutline} | ||||
| 						/> | ||||
| 					{/if} | ||||
| 				{/if} | ||||
| 
 | ||||
| @ -515,3 +517,17 @@ | ||||
| 		on:thumbnail-selected={setAlbumThumbnailHandler} | ||||
| 	/> | ||||
| {/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 PhotoViewer from './photo-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 { isShowDetail } from '$lib/stores/preferences.store'; | ||||
| @ -37,6 +38,7 @@ | ||||
| 	let halfRightHover = false; | ||||
| 	let appearsInAlbums: AlbumResponseDto[] = []; | ||||
| 	let isShowAlbumPicker = false; | ||||
| 	let isShowDeleteConfirmation = false; | ||||
| 	let addToSharedAlbum = true; | ||||
| 	let shouldPlayMotionPhoto = false; | ||||
| 	let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true; | ||||
| @ -77,7 +79,7 @@ | ||||
| 				closeViewer(); | ||||
| 				return; | ||||
| 			case 'Delete': | ||||
| 				deleteAsset(); | ||||
| 				isShowDeleteConfirmation = true; | ||||
| 				return; | ||||
| 			case 'i': | ||||
| 				$isShowDetail = !$isShowDetail; | ||||
| @ -116,11 +118,6 @@ | ||||
| 
 | ||||
| 	const deleteAsset = async () => { | ||||
| 		try { | ||||
| 			if ( | ||||
| 				window.confirm( | ||||
| 					`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!` | ||||
| 				) | ||||
| 			) { | ||||
| 			const { data: deletedAssets } = await api.assetApi.deleteAsset({ | ||||
| 				deleteAssetDto: { | ||||
| 					ids: [asset.id] | ||||
| @ -134,13 +131,14 @@ | ||||
| 					assetStore.removeAsset(asset.id); | ||||
| 				} | ||||
| 			} | ||||
| 			} | ||||
| 		} catch (e) { | ||||
| 			notificationController.show({ | ||||
| 				type: NotificationType.Error, | ||||
| 				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> | ||||
| 
 | ||||
| <section | ||||
| @ -244,7 +253,7 @@ | ||||
| 			on:goBack={closeViewer} | ||||
| 			on:showDetail={showDetailInfoHandler} | ||||
| 			on:download={() => downloadFile(asset, publicSharedKey)} | ||||
| 			on:delete={deleteAsset} | ||||
| 			on:delete={() => (isShowDeleteConfirmation = true)} | ||||
| 			on:favorite={toggleFavorite} | ||||
| 			on:addToAlbum={() => openAlbumPicker(false)} | ||||
| 			on:addToSharedAlbum={() => openAlbumPicker(true)} | ||||
| @ -358,6 +367,23 @@ | ||||
| 			on:close={() => (isShowAlbumPicker = false)} | ||||
| 		/> | ||||
| 	{/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> | ||||
| 
 | ||||
| <style> | ||||
|  | ||||
| @ -7,6 +7,7 @@ | ||||
| 		NotificationType | ||||
| 	} from '../shared-components/notification/notification'; | ||||
| 	import Button from '../elements/buttons/button.svelte'; | ||||
| 	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; | ||||
| 	import { handleError } from '../../utils/handle-error'; | ||||
| 
 | ||||
| 	export let user: UserResponseDto; | ||||
| @ -15,6 +16,8 @@ | ||||
| 	let error: string; | ||||
| 	let success: string; | ||||
| 
 | ||||
| 	let isShowResetPasswordConfirmation = false; | ||||
| 
 | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	const editUser = async () => { | ||||
| @ -41,7 +44,6 @@ | ||||
| 
 | ||||
| 	const resetPassword = async () => { | ||||
| 		try { | ||||
| 			if (window.confirm('Do you want to reset the user password?')) { | ||||
| 			const defaultPassword = 'password'; | ||||
| 
 | ||||
| 			const { status } = await api.userApi.updateUser({ | ||||
| @ -55,13 +57,14 @@ | ||||
| 			if (status == 200) { | ||||
| 				dispatch('reset-password-success'); | ||||
| 			} | ||||
| 			} | ||||
| 		} catch (e) { | ||||
| 			console.error('Error reseting user password', e); | ||||
| 			notificationController.show({ | ||||
| 				message: 'Error reseting user password, check console for more details', | ||||
| 				type: NotificationType.Error | ||||
| 			}); | ||||
| 		} finally { | ||||
| 			isShowResetPasswordConfirmation = false; | ||||
| 		} | ||||
| 	}; | ||||
| </script> | ||||
| @ -125,9 +128,9 @@ | ||||
| 			/> | ||||
| 
 | ||||
| 			<p> | ||||
| 				Note: To apply the Storage Label to previously uploaded assets, run the <a | ||||
| 					href="/admin/jobs-status" | ||||
| 					class="text-immich-primary dark:text-immich-dark-primary">Storage Migration Job</a | ||||
| 				Note: To apply the Storage Label to previously uploaded assets, run the | ||||
| 				<a href="/admin/jobs-status" class="text-immich-primary dark:text-immich-dark-primary"> | ||||
| 					Storage Migration Job</a | ||||
| 				> | ||||
| 			</p> | ||||
| 		</div> | ||||
| @ -157,9 +160,28 @@ | ||||
| 		{/if} | ||||
| 		<div class="flex w-full px-4 gap-4 mt-8"> | ||||
| 			{#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} | ||||
| 			<Button type="submit" fullwidth>Confirm</Button> | ||||
| 		</div> | ||||
| 	</form> | ||||
| </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; | ||||
| 	const { getAssets, clearSelect } = getAssetControlContext(); | ||||
| 
 | ||||
| 	let confirm = false; | ||||
| 	let isShowConfirmation = false; | ||||
| 
 | ||||
| 	const handleDelete = async () => { | ||||
| 		try { | ||||
| @ -32,24 +32,43 @@ | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			notificationController.show({ message: `Deleted ${count}`, type: NotificationType.Info }); | ||||
| 			notificationController.show({ | ||||
| 				message: `Deleted ${count}`, | ||||
| 				type: NotificationType.Info | ||||
| 			}); | ||||
| 
 | ||||
| 			clearSelect(); | ||||
| 		} catch (e) { | ||||
| 			handleError(e, 'Error deleting assets'); | ||||
| 		} finally { | ||||
| 			isShowConfirmation = false; | ||||
| 		} | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <CircleIconButton title="Delete" logo={DeleteOutline} on:click={() => (confirm = true)} /> | ||||
| <CircleIconButton | ||||
| 	title="Delete" | ||||
| 	logo={DeleteOutline} | ||||
| 	on:click={() => (isShowConfirmation = true)} | ||||
| /> | ||||
| 
 | ||||
| {#if confirm} | ||||
| {#if isShowConfirmation} | ||||
| 	<ConfirmDialogue | ||||
| 		prompt="Are you sure you want to delete {getAssets() | ||||
| 			.size} assets? This step also deletes assets in the album(s) to which they belong. You can not undo this action!" | ||||
| 		title="Delete assets?" | ||||
| 		title="Delete Asset{getAssets().size > 1 ? 's' : ''}" | ||||
| 		confirmText="Delete" | ||||
| 		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} | ||||
|  | ||||
| @ -7,13 +7,15 @@ | ||||
| 	import { AlbumResponseDto, api } from '@api'; | ||||
| 	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte'; | ||||
| 	import { getAssetControlContext } from '../asset-select-control-bar.svelte'; | ||||
| 	import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; | ||||
| 
 | ||||
| 	export let album: AlbumResponseDto; | ||||
| 
 | ||||
| 	const { getAssets, clearSelect } = getAssetControlContext(); | ||||
| 
 | ||||
| 	const handleRemoveFromAlbum = async () => { | ||||
| 		if (window.confirm('Do you want to remove selected assets from the album?')) { | ||||
| 	let isShowConfirmation = false; | ||||
| 
 | ||||
| 	const removeFromAlbum = async () => { | ||||
| 		try { | ||||
| 			const { data } = await api.albumApi.removeAssetFromAlbum({ | ||||
| 				id: album.id, | ||||
| @ -30,9 +32,35 @@ | ||||
| 				type: NotificationType.Error, | ||||
| 				message: 'Error removing assets from album, check console for more details' | ||||
| 			}); | ||||
| 			} | ||||
| 		} finally { | ||||
| 			isShowConfirmation = false; | ||||
| 		} | ||||
| 	}; | ||||
| </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 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 title = 'Confirm'; | ||||
| 	export let prompt = 'Are you sure you want to do this?'; | ||||
| 	export let confirmText = 'Confirm'; | ||||
| 	export let confirmColor: Color = 'red'; | ||||
| 	export let cancelText = 'Cancel'; | ||||
| 	export let cancelColor: Color = 'primary'; | ||||
| 	export let hideCancelButton = false; | ||||
| 
 | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	let isConfirmButtonDisabled = false; | ||||
| 
 | ||||
| 	const handleCancel = () => dispatch('cancel'); | ||||
| 	const handleConfirm = () => dispatch('confirm'); | ||||
| 
 | ||||
| 	const handleConfirm = () => { | ||||
| 		isConfirmButtonDisabled = true; | ||||
| 		dispatch('confirm'); | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <FullScreenModal on:clickOutside={() => handleCancel()}> | ||||
| <FullScreenModal on:clickOutside={handleCancel}> | ||||
| 	<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" | ||||
| 	> | ||||
| @ -25,13 +36,26 @@ | ||||
| 			</h1> | ||||
| 		</div> | ||||
| 		<div> | ||||
| 			<div class="px-4 py-5 text-md text-center"> | ||||
| 				<slot name="prompt"> | ||||
| 				<p class="ml-4 text-md py-5 text-center">{prompt}</p> | ||||
| 					<p>{prompt}</p> | ||||
| 				</slot> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div class="flex w-full px-4 gap-4 mt-4"> | ||||
| 				<Button fullwidth on:click={() => handleCancel()}>{cancelText}</Button> | ||||
| 				<Button color="red" fullwidth on:click={() => handleConfirm()}>{confirmText}</Button> | ||||
| 				{#if !hideCancelButton} | ||||
| 					<Button color={cancelColor} fullwidth on:click={handleCancel}> | ||||
| 						{cancelText} | ||||
| 					</Button> | ||||
| 				{/if} | ||||
| 				<Button | ||||
| 					color={confirmColor} | ||||
| 					fullwidth | ||||
| 					on:click={handleConfirm} | ||||
| 					disabled={isConfirmButtonDisabled} | ||||
| 				> | ||||
| 					{confirmText} | ||||
| 				</Button> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | ||||
| @ -14,6 +14,12 @@ | ||||
| 	import { onMount } from 'svelte'; | ||||
| 	import { flip } from 'svelte/animate'; | ||||
| 	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; | ||||
| 
 | ||||
| @ -23,14 +29,36 @@ | ||||
| 		albums: unsortedAlbums, | ||||
| 		isShowContextMenu, | ||||
| 		contextMenuPosition, | ||||
| 		contextMenuTargetAlbum, | ||||
| 		createAlbum, | ||||
| 		deleteAlbum, | ||||
| 		deleteSelectedContextAlbum, | ||||
| 		showAlbumContextMenu, | ||||
| 		closeAlbumContextMenu | ||||
| 	} = useAlbums({ albums: data.albums }); | ||||
| 
 | ||||
| 	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 aDate = new Date(a); | ||||
| @ -69,7 +97,6 @@ | ||||
| 			for (const album of $albums) { | ||||
| 				if (album.assetCount == 0 && album.albumName == 'Untitled') { | ||||
| 					await deleteAlbum(album); | ||||
| 					$albums = $albums.filter((a) => a.id !== album.id); | ||||
| 				} | ||||
| 			} | ||||
| 		} catch (error) { | ||||
| @ -120,7 +147,7 @@ | ||||
| <!-- Context Menu --> | ||||
| {#if $isShowContextMenu} | ||||
| 	<ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu}> | ||||
| 		<MenuOption on:click={deleteSelectedContextAlbum}> | ||||
| 		<MenuOption on:click={() => setAlbumToDelete()}> | ||||
| 			<span class="flex place-items-center place-content-center gap-2"> | ||||
| 				<DeleteOutline size="18" /> | ||||
| 				<p>Delete album</p> | ||||
| @ -128,3 +155,17 @@ | ||||
| 		</MenuOption> | ||||
| 	</ContextMenu> | ||||
| {/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>; | ||||
| 
 | ||||
| function mockWindowConfirm(result: boolean) { | ||||
| 	jest.spyOn(global, 'confirm').mockReturnValueOnce(result); | ||||
| } | ||||
| 
 | ||||
| describe('Albums BLoC', () => { | ||||
| 	let sut: ReturnType<typeof useAlbums>; | ||||
| 	const _albums = albumFactory.buildList(5); | ||||
| @ -115,8 +111,6 @@ describe('Albums BLoC', () => { | ||||
| 			statusText: '' | ||||
| 		}); | ||||
| 
 | ||||
| 		mockWindowConfirm(true); | ||||
| 
 | ||||
| 		const albumToDelete = get(sut.albums)[2]; // delete third album
 | ||||
| 		const albumToDeleteId = albumToDelete.id; | ||||
| 		const contextMenuCoords = { x: 100, y: 150 }; | ||||
| @ -125,52 +119,15 @@ describe('Albums BLoC', () => { | ||||
| 		sut.showAlbumContextMenu(contextMenuCoords, albumToDelete); | ||||
| 		expect(get(sut.contextMenuPosition)).toEqual(contextMenuCoords); | ||||
| 		expect(get(sut.isShowContextMenu)).toBe(true); | ||||
| 		expect(get(sut.contextMenuTargetAlbum)).toEqual(albumToDelete); | ||||
| 
 | ||||
| 		await sut.deleteSelectedContextAlbum(); | ||||
| 		await sut.deleteAlbum(albumToDelete); | ||||
| 		const updatedAlbums = get(sut.albums); | ||||
| 
 | ||||
| 		expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledTimes(1); | ||||
| 		expect(apiMock.albumApi.deleteAlbum).toHaveBeenCalledWith({ id: albumToDeleteId }); | ||||
| 		expect(updatedAlbums).toHaveLength(4); | ||||
| 		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', () => { | ||||
|  | ||||
| @ -24,8 +24,6 @@ export const useAlbums = (props: AlbumsProps) => { | ||||
| 				if (album.albumName === 'Untitled' && album.assetCount === 0) { | ||||
| 					setTimeout(async () => { | ||||
| 						await deleteAlbum(album); | ||||
| 						const _albums = get(albums); | ||||
| 						albums.set(_albums.filter((a) => a.id !== album.id)); | ||||
| 					}, 500); | ||||
| 				} | ||||
| 			} | ||||
| @ -54,8 +52,13 @@ export const useAlbums = (props: AlbumsProps) => { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	async function deleteAlbum(album: AlbumResponseDto): Promise<void> { | ||||
| 		await api.albumApi.deleteAlbum({ id: album.id }); | ||||
| 	async function deleteAlbum(albumToDelete: AlbumResponseDto): Promise<void> { | ||||
| 		await api.albumApi.deleteAlbum({ id: albumToDelete.id }); | ||||
| 		albums.set( | ||||
| 			get(albums).filter(({ id }) => { | ||||
| 				return id !== albumToDelete.id; | ||||
| 			}) | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	async function showAlbumContextMenu( | ||||
| @ -74,40 +77,15 @@ export const useAlbums = (props: AlbumsProps) => { | ||||
| 		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 { | ||||
| 		albums, | ||||
| 		isShowContextMenu, | ||||
| 		contextMenuPosition, | ||||
| 		contextMenuTargetAlbum, | ||||
| 		loadAlbums, | ||||
| 		createAlbum, | ||||
| 		deleteAlbum, | ||||
| 		showAlbumContextMenu, | ||||
| 		closeAlbumContextMenu, | ||||
| 		deleteSelectedContextAlbum | ||||
| 		closeAlbumContextMenu | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| @ -98,7 +98,7 @@ | ||||
| {#if deleteLinkId} | ||||
| 	<ConfirmDialogue | ||||
| 		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" | ||||
| 		on:confirm={() => handleDeleteLink()} | ||||
| 		on:cancel={() => (deleteLinkId = null)} | ||||
|  | ||||
| @ -126,23 +126,21 @@ | ||||
| 	{/if} | ||||
| 
 | ||||
| 	{#if shouldShowDeleteConfirmDialog} | ||||
| 		<FullScreenModal on:clickOutside={() => (shouldShowDeleteConfirmDialog = false)}> | ||||
| 		<DeleteConfirmDialog | ||||
| 			user={selectedUser} | ||||
| 			on:user-delete-success={onUserDeleteSuccess} | ||||
| 			on:user-delete-fail={onUserDeleteFail} | ||||
| 			on:cancel={() => (shouldShowDeleteConfirmDialog = false)} | ||||
| 		/> | ||||
| 		</FullScreenModal> | ||||
| 	{/if} | ||||
| 
 | ||||
| 	{#if shouldShowRestoreDialog} | ||||
| 		<FullScreenModal on:clickOutside={() => (shouldShowRestoreDialog = false)}> | ||||
| 		<RestoreDialogue | ||||
| 			user={selectedUser} | ||||
| 			on:user-restore-success={onUserRestoreSuccess} | ||||
| 			on:user-restore-fail={onUserRestoreFail} | ||||
| 			on:cancel={() => (shouldShowRestoreDialog = false)} | ||||
| 		/> | ||||
| 		</FullScreenModal> | ||||
| 	{/if} | ||||
| 
 | ||||
| 	{#if shouldShowInfoPanel} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user