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"> | <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