Forráskód Böngészése

refactor(web): asset select actions (#2444)

* refactor(web): asset select actions

* remaining pages/components + data flow changes

* fix check
Michel Heusschen 2 éve
szülő
commit
ab86d0a18d
21 módosított fájl, 645 hozzáadás és 918 törlés
  1. 34 82
      web/src/lib/components/album-page/album-viewer.svelte
  2. 23 0
      web/src/lib/components/photos-page/actions/create-shared-link.svelte
  3. 45 0
      web/src/lib/components/photos-page/actions/delete-assets.svelte
  4. 17 0
      web/src/lib/components/photos-page/actions/download-files.svelte
  5. 40 0
      web/src/lib/components/photos-page/actions/move-to-archive.svelte
  6. 36 0
      web/src/lib/components/photos-page/actions/remove-favorite.svelte
  7. 35 0
      web/src/lib/components/photos-page/actions/remove-from-album.svelte
  8. 39 0
      web/src/lib/components/photos-page/actions/remove-from-archive.svelte
  9. 34 0
      web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte
  10. 35 0
      web/src/lib/components/photos-page/asset-select-context-menu.svelte
  11. 39 0
      web/src/lib/components/photos-page/asset-select-control-bar.svelte
  12. 67 0
      web/src/lib/components/photos-page/menu-options/option-add-to-album.svelte
  13. 41 0
      web/src/lib/components/photos-page/menu-options/option-add-to-favorites.svelte
  14. 19 0
      web/src/lib/components/photos-page/menu-options/option-remove-from-favorites.svelte
  15. 19 62
      web/src/lib/components/share-page/individual-shared-viewer.svelte
  16. 2 2
      web/src/lib/utils/asset-utils.ts
  17. 32 223
      web/src/routes/(user)/archive/+page.svelte
  18. 12 68
      web/src/routes/(user)/favorites/+page.svelte
  19. 15 33
      web/src/routes/(user)/partners/[userId]/+page.svelte
  20. 25 212
      web/src/routes/(user)/photos/+page.svelte
  21. 36 236
      web/src/routes/(user)/search/+page.svelte

+ 34 - 82
web/src/lib/components/album-page/album-viewer.svelte

@@ -1,49 +1,48 @@
 <script lang="ts">
+	import { browser } from '$app/environment';
 	import { afterNavigate, goto } from '$app/navigation';
+	import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
+	import { downloadAssets } from '$lib/stores/download';
+	import { locale } from '$lib/stores/preferences.store';
+	import { clickOutside } from '$lib/utils/click-outside';
+	import { openFileUploadDialog } from '$lib/utils/file-uploader';
 	import {
 		AlbumResponseDto,
-		api,
 		AssetResponseDto,
 		SharedLinkResponseDto,
 		SharedLinkType,
-		UserResponseDto
+		UserResponseDto,
+		api
 	} from '@api';
 	import { onMount } from 'svelte';
 	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
+	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
+	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
+	import Plus from 'svelte-material-icons/Plus.svelte';
 	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
-	import CircleAvatar from '../shared-components/circle-avatar.svelte';
-	import AssetSelection from './asset-selection.svelte';
-	import UserSelectionModal from './user-selection-modal.svelte';
-	import ShareInfoModal from './share-info-modal.svelte';
+	import Button from '../elements/buttons/button.svelte';
 	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
-	import { downloadAssets } from '$lib/stores/download';
-	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
+	import DownloadFiles from '../photos-page/actions/download-files.svelte';
+	import RemoveFromAlbum from '../photos-page/actions/remove-from-album.svelte';
+	import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
+	import CircleAvatar from '../shared-components/circle-avatar.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
 	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
-	import ThumbnailSelection from './thumbnail-selection.svelte';
 	import ControlAppBar from '../shared-components/control-app-bar.svelte';
-	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
-
-	import {
-		notificationController,
-		NotificationType
-	} from '../shared-components/notification/notification';
-	import { browser } from '$app/environment';
-	import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
 	import CreateSharedLinkModal from '../shared-components/create-share-link-modal/create-shared-link-modal.svelte';
-	import ThemeButton from '../shared-components/theme-button.svelte';
-	import { openFileUploadDialog } from '$lib/utils/file-uploader';
-	import { bulkDownload } from '$lib/utils/asset-utils';
-	import { locale } from '$lib/stores/preferences.store';
 	import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
 	import ImmichLogo from '../shared-components/immich-logo.svelte';
-	import Button from '../elements/buttons/button.svelte';
-	import { clickOutside } from '$lib/utils/click-outside';
+	import {
+		NotificationType,
+		notificationController
+	} from '../shared-components/notification/notification';
+	import ThemeButton from '../shared-components/theme-button.svelte';
+	import AssetSelection from './asset-selection.svelte';
+	import ShareInfoModal from './share-info-modal.svelte';
+	import ThumbnailSelection from './thumbnail-selection.svelte';
+	import UserSelectionModal from './user-selection-modal.svelte';
 
 	export let album: AlbumResponseDto;
 	export let sharedLink: SharedLinkResponseDto | undefined = undefined;
@@ -125,25 +124,6 @@
 		multiSelectAsset = new Set();
 	};
 
-	const removeSelectedAssetFromAlbum = async () => {
-		if (window.confirm('Do you want to remove selected assets from the album?')) {
-			try {
-				const { data } = await api.albumApi.removeAssetFromAlbum(album.id, {
-					assetIds: Array.from(multiSelectAsset).map((a) => a.id)
-				});
-
-				album = data;
-				multiSelectAsset = new Set();
-			} 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'
-				});
-			}
-		}
-	};
-
 	// Update Album Name
 	$: {
 		if (!isEditingTitle && currentAlbumName != album.albumName && isOwned) {
@@ -353,48 +333,20 @@
 		isShowShareUserSelection = false;
 		isShowShareLinkModal = true;
 	};
-
-	const handleDownloadSelectedAssets = async () => {
-		await bulkDownload(
-			album.albumName,
-			Array.from(multiSelectAsset),
-			() => {
-				isMultiSelectionMode = false;
-				clearMultiSelectAssetAssetHandler();
-			},
-			sharedLink?.key
-		);
-	};
 </script>
 
 <section class="bg-immich-bg dark:bg-immich-dark-bg" class:hidden={isShowThumbnailSelection}>
 	<!-- Multiselection mode app bar -->
 	{#if isMultiSelectionMode}
-		<ControlAppBar
-			on:close-button-click={clearMultiSelectAssetAssetHandler}
-			backIcon={Close}
-			tailwindClasses={'bg-white shadow-md'}
+		<AssetSelectControlBar
+			assets={multiSelectAsset}
+			clearSelect={clearMultiSelectAssetAssetHandler}
 		>
-			<svelte:fragment slot="leading">
-				<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
-					Selected {multiSelectAsset.size.toLocaleString($locale)}
-				</p>
-			</svelte:fragment>
-			<svelte:fragment slot="trailing">
-				<CircleIconButton
-					title="Download"
-					on:click={handleDownloadSelectedAssets}
-					logo={CloudDownloadOutline}
-				/>
-				{#if isOwned}
-					<CircleIconButton
-						title="Remove from album"
-						on:click={removeSelectedAssetFromAlbum}
-						logo={DeleteOutline}
-					/>
-				{/if}
-			</svelte:fragment>
-		</ControlAppBar>
+			<DownloadFiles filename={album.albumName} sharedLinkKey={sharedLink?.key} />
+			{#if isOwned}
+				<RemoveFromAlbum bind:album />
+			{/if}
+		</AssetSelectControlBar>
 	{/if}
 
 	<!-- Default app bar -->

+ 23 - 0
web/src/lib/components/photos-page/actions/create-shared-link.svelte

@@ -0,0 +1,23 @@
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
+	import { SharedLinkType } from '@api';
+	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
+	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+
+	let showModal = false;
+	const { getAssets, clearSelect } = getAssetControlContext();
+</script>
+
+<CircleIconButton title="Share" logo={ShareVariantOutline} on:click={() => (showModal = true)} />
+
+{#if showModal}
+	<CreateSharedLinkModal
+		sharedAssets={Array.from(getAssets())}
+		shareType={SharedLinkType.Individual}
+		on:close={() => {
+			showModal = false;
+			clearSelect();
+		}}
+	/>
+{/if}

+ 45 - 0
web/src/lib/components/photos-page/actions/delete-assets.svelte

@@ -0,0 +1,45 @@
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import {
+		NotificationType,
+		notificationController
+	} from '$lib/components/shared-components/notification/notification';
+	import { api } from '@api';
+	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+	import { OnAssetDelete, getAssetControlContext } from '../asset-select-control-bar.svelte';
+
+	export let onAssetDelete: OnAssetDelete;
+	const { getAssets, clearSelect } = getAssetControlContext();
+
+	const deleteSelectedAssetHandler = async () => {
+		try {
+			if (
+				window.confirm(
+					`Caution! 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!`
+				)
+			) {
+				const { data: deletedAssets } = await api.assetApi.deleteAsset({
+					ids: Array.from(getAssets()).map((a) => a.id)
+				});
+
+				for (const asset of deletedAssets) {
+					if (asset.status === 'SUCCESS') {
+						onAssetDelete(asset.id);
+					}
+				}
+
+				clearSelect();
+			}
+		} catch (e) {
+			notificationController.show({
+				type: NotificationType.Error,
+				message: 'Error deleting assets, check console for more details'
+			});
+			console.error('Error deleteSelectedAssetHandler', e);
+		}
+	};
+</script>
+
+<CircleIconButton title="Delete" logo={DeleteOutline} on:click={deleteSelectedAssetHandler} />

+ 17 - 0
web/src/lib/components/photos-page/actions/download-files.svelte

@@ -0,0 +1,17 @@
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import { bulkDownload } from '$lib/utils/asset-utils';
+	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
+	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+
+	export let filename = 'immich';
+	export let sharedLinkKey: string | undefined = undefined;
+
+	const { getAssets, clearSelect } = getAssetControlContext();
+
+	const handleDownloadFiles = async () => {
+		await bulkDownload(filename, Array.from(getAssets()), clearSelect, sharedLinkKey);
+	};
+</script>
+
+<CircleIconButton title="Download" logo={CloudDownloadOutline} on:click={handleDownloadFiles} />

+ 40 - 0
web/src/lib/components/photos-page/actions/move-to-archive.svelte

@@ -0,0 +1,40 @@
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import {
+		NotificationType,
+		notificationController
+	} from '$lib/components/shared-components/notification/notification';
+	import { api } from '@api';
+	import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
+	import { OnAssetArchive, getAssetControlContext } from '../asset-select-control-bar.svelte';
+
+	export let onAssetArchive: OnAssetArchive = (asset, archive) => {
+		asset.isArchived = archive;
+	};
+
+	const { getAssets, clearSelect } = getAssetControlContext();
+
+	const handleArchive = async () => {
+		let cnt = 0;
+
+		for (const asset of getAssets()) {
+			if (!asset.isArchived) {
+				api.assetApi.updateAsset(asset.id, {
+					isArchived: true
+				});
+
+				onAssetArchive(asset, true);
+				cnt = cnt + 1;
+			}
+		}
+
+		notificationController.show({
+			message: `Archived ${cnt}`,
+			type: NotificationType.Info
+		});
+
+		clearSelect();
+	};
+</script>
+
+<CircleIconButton title="Archive" logo={ArchiveArrowDownOutline} on:click={handleArchive} />

+ 36 - 0
web/src/lib/components/photos-page/actions/remove-favorite.svelte

@@ -0,0 +1,36 @@
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import { handleError } from '$lib/utils/handle-error';
+	import { api } from '@api';
+	import HeartMinusOutline from 'svelte-material-icons/HeartMinusOutline.svelte';
+	import { getAssetControlContext, OnAssetFavorite } from '../asset-select-control-bar.svelte';
+
+	export let onAssetFavorite: OnAssetFavorite = (asset, favorite) => {
+		asset.isFavorite = favorite;
+	};
+
+	const { getAssets, clearSelect } = getAssetControlContext();
+
+	const handleRemoveFavorite = async () => {
+		for (const asset of getAssets()) {
+			try {
+				await api.assetApi.updateAsset(asset.id, {
+					isFavorite: false
+				});
+				onAssetFavorite(asset, false);
+			} catch {
+				handleError(Error, 'Error updating asset favorite state');
+			}
+		}
+
+		clearSelect();
+	};
+</script>
+
+<slot {handleRemoveFavorite}>
+	<CircleIconButton
+		title="Remove Favorite"
+		logo={HeartMinusOutline}
+		on:click={handleRemoveFavorite}
+	/>
+</slot>

+ 35 - 0
web/src/lib/components/photos-page/actions/remove-from-album.svelte

@@ -0,0 +1,35 @@
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import {
+		NotificationType,
+		notificationController
+	} from '$lib/components/shared-components/notification/notification';
+	import { AlbumResponseDto, api } from '@api';
+	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+	import { getAssetControlContext } from '../asset-select-control-bar.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?')) {
+			try {
+				const { data } = await api.albumApi.removeAssetFromAlbum(album.id, {
+					assetIds: Array.from(getAssets()).map((a) => a.id)
+				});
+
+				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'
+				});
+			}
+		}
+	};
+</script>
+
+<CircleIconButton title="Remove from album" on:click={handleRemoveFromAlbum} logo={DeleteOutline} />

+ 39 - 0
web/src/lib/components/photos-page/actions/remove-from-archive.svelte

@@ -0,0 +1,39 @@
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import {
+		NotificationType,
+		notificationController
+	} from '$lib/components/shared-components/notification/notification';
+	import { api } from '@api';
+	import ArchiveArrowUpOutline from 'svelte-material-icons/ArchiveArrowUpOutline.svelte';
+	import { OnAssetArchive, getAssetControlContext } from '../asset-select-control-bar.svelte';
+
+	export let onAssetArchive: OnAssetArchive = (asset, archived) => {
+		asset.isArchived = archived;
+	};
+
+	const { getAssets, clearSelect } = getAssetControlContext();
+
+	const handleUnarchive = async () => {
+		let cnt = 0;
+		for (const asset of getAssets()) {
+			if (asset.isArchived) {
+				api.assetApi.updateAsset(asset.id, {
+					isArchived: false
+				});
+
+				onAssetArchive(asset, false);
+				cnt = cnt + 1;
+			}
+		}
+
+		notificationController.show({
+			message: `Removed ${cnt} from archive`,
+			type: NotificationType.Info
+		});
+
+		clearSelect();
+	};
+</script>
+
+<CircleIconButton title="Unarchive" logo={ArchiveArrowUpOutline} on:click={handleUnarchive} />

+ 34 - 0
web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte

@@ -0,0 +1,34 @@
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import { AssetResponseDto, SharedLinkResponseDto, api } from '@api';
+	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+
+	export let sharedLink: SharedLinkResponseDto;
+	export let allAssets: AssetResponseDto[];
+
+	const { getAssets, clearSelect } = getAssetControlContext();
+
+	const handleRemoveAssetsFromSharedLink = async () => {
+		if (window.confirm('Do you want to remove selected assets from the shared link?')) {
+			// TODO: Rename API method or change functionality. The assetIds passed
+			// in are kept instead of removed.
+			const assetsToKeep = allAssets.filter((a) => !getAssets().has(a));
+			await api.assetApi.removeAssetsFromSharedLink(
+				{
+					assetIds: assetsToKeep.map((a) => a.id)
+				},
+				sharedLink?.key
+			);
+
+			sharedLink.assets = assetsToKeep;
+			clearSelect();
+		}
+	};
+</script>
+
+<CircleIconButton
+	title="Remove from album"
+	on:click={handleRemoveAssetsFromSharedLink}
+	logo={DeleteOutline}
+/>

+ 35 - 0
web/src/lib/components/photos-page/asset-select-context-menu.svelte

@@ -0,0 +1,35 @@
+<script lang="ts" context="module">
+	import { createContext } from '$lib/utils/context';
+
+	const { get: getMenuContext, set: setContext } = createContext<() => void>();
+	export { getMenuContext };
+</script>
+
+<script lang="ts">
+	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
+	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
+	import type Icon from 'svelte-material-icons/AbTesting.svelte';
+
+	export let icon: typeof Icon;
+	export let title: string;
+
+	let showContextMenu = false;
+	let contextMenuPosition = { x: 0, y: 0 };
+
+	const handleShowMenu = ({ x, y }: MouseEvent) => {
+		contextMenuPosition = { x, y };
+		showContextMenu = !showContextMenu;
+	};
+
+	setContext(() => (showContextMenu = false));
+</script>
+
+<CircleIconButton {title} logo={icon} on:click={handleShowMenu} />
+
+{#if showContextMenu}
+	<ContextMenu {...contextMenuPosition} on:clickoutside={() => (showContextMenu = false)}>
+		<div class="flex flex-col rounded-lg">
+			<slot />
+		</div>
+	</ContextMenu>
+{/if}

+ 39 - 0
web/src/lib/components/photos-page/asset-select-control-bar.svelte

@@ -0,0 +1,39 @@
+<script lang="ts" context="module">
+	import { createContext } from '$lib/utils/context';
+
+	export type OnAssetDelete = (assetId: string) => void;
+	export type OnAssetArchive = (asset: AssetResponseDto, archived: boolean) => void;
+	export type OnAssetFavorite = (asset: AssetResponseDto, favorite: boolean) => void;
+
+	export interface AssetControlContext {
+		// Wrap assets in a function, because context isn't reactive.
+		getAssets: () => Set<AssetResponseDto>;
+		clearSelect: () => void;
+	}
+
+	const { get: getAssetControlContext, set: setContext } = createContext<AssetControlContext>();
+	export { getAssetControlContext };
+</script>
+
+<script lang="ts">
+	import { locale } from '$lib/stores/preferences.store';
+	import { AssetResponseDto } from '@api';
+	import Close from 'svelte-material-icons/Close.svelte';
+	import ControlAppBar from '../shared-components/control-app-bar.svelte';
+
+	export let assets: Set<AssetResponseDto>;
+	export let clearSelect: () => void;
+
+	setContext({ getAssets: () => assets, clearSelect });
+</script>
+
+<ControlAppBar
+	on:close-button-click={clearSelect}
+	backIcon={Close}
+	tailwindClasses="bg-white shadow-md"
+>
+	<p class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
+		Selected {assets.size.toLocaleString($locale)}
+	</p>
+	<slot slot="trailing" />
+</ControlAppBar>

+ 67 - 0
web/src/lib/components/photos-page/menu-options/option-add-to-album.svelte

@@ -0,0 +1,67 @@
+<script lang="ts">
+	import { goto } from '$app/navigation';
+	import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
+	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
+	import {
+		NotificationType,
+		notificationController
+	} from '$lib/components/shared-components/notification/notification';
+	import { addAssetsToAlbum } from '$lib/utils/asset-utils';
+	import { AlbumResponseDto, api } from '@api';
+	import { getMenuContext } from '../asset-select-context-menu.svelte';
+	import { getAssetControlContext } from '../asset-select-control-bar.svelte';
+
+	export let shared = false;
+	let showAlbumPicker = false;
+
+	const { getAssets, clearSelect } = getAssetControlContext();
+	const closeMenu = getMenuContext();
+
+	const handleHideAlbumPicker = () => {
+		showAlbumPicker = false;
+		closeMenu();
+	};
+
+	const handleAddToNewAlbum = (event: CustomEvent) => {
+		showAlbumPicker = false;
+
+		const { albumName }: { albumName: string } = event.detail;
+		const assetIds = Array.from(getAssets()).map((asset) => asset.id);
+		api.albumApi.createAlbum({ albumName, assetIds }).then((response) => {
+			const { id, albumName } = response.data;
+
+			notificationController.show({
+				message: `Added ${assetIds.length} to ${albumName}`,
+				type: NotificationType.Info
+			});
+
+			clearSelect();
+
+			goto('/albums/' + id);
+		});
+	};
+
+	const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
+		showAlbumPicker = false;
+		const album = event.detail.album;
+
+		const assetIds = Array.from(getAssets()).map((asset) => asset.id);
+
+		addAssetsToAlbum(album.id, assetIds).then(clearSelect);
+	};
+</script>
+
+<MenuOption
+	on:click={() => (showAlbumPicker = true)}
+	text={shared ? 'Add to Shared Album' : 'Add to Album'}
+/>
+
+{#if showAlbumPicker}
+	<AlbumSelectionModal
+		{shared}
+		on:newAlbum={handleAddToNewAlbum}
+		on:newSharedAlbum={handleAddToNewAlbum}
+		on:album={handleAddToAlbum}
+		on:close={handleHideAlbumPicker}
+	/>
+{/if}

+ 41 - 0
web/src/lib/components/photos-page/menu-options/option-add-to-favorites.svelte

@@ -0,0 +1,41 @@
+<script lang="ts">
+	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
+	import {
+		NotificationType,
+		notificationController
+	} from '$lib/components/shared-components/notification/notification';
+	import { api } from '@api';
+	import { getMenuContext } from '../asset-select-context-menu.svelte';
+	import { OnAssetFavorite, getAssetControlContext } from '../asset-select-control-bar.svelte';
+
+	export let onAssetFavorite: OnAssetFavorite = (asset, favorite) => {
+		asset.isFavorite = favorite;
+	};
+
+	const { getAssets, clearSelect } = getAssetControlContext();
+	const closeMenu = getMenuContext();
+
+	const handleAddToFavorites = () => {
+		closeMenu();
+
+		let cnt = 0;
+		for (const asset of getAssets()) {
+			if (!asset.isFavorite) {
+				api.assetApi.updateAsset(asset.id, {
+					isFavorite: true
+				});
+				onAssetFavorite(asset, true);
+				cnt = cnt + 1;
+			}
+		}
+
+		notificationController.show({
+			message: `Added ${cnt} to favorites`,
+			type: NotificationType.Info
+		});
+
+		clearSelect();
+	};
+</script>
+
+<MenuOption on:click={handleAddToFavorites} text="Add to Favorites" />

+ 19 - 0
web/src/lib/components/photos-page/menu-options/option-remove-from-favorites.svelte

@@ -0,0 +1,19 @@
+<script lang="ts">
+	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
+	import RemoveFavorite from '../actions/remove-favorite.svelte';
+	import { getMenuContext } from '../asset-select-context-menu.svelte';
+	import { OnAssetFavorite } from '../asset-select-control-bar.svelte';
+
+	export let onAssetFavorite: OnAssetFavorite | undefined = undefined;
+	const closeMenu = getMenuContext();
+</script>
+
+<RemoveFavorite let:handleRemoveFavorite {onAssetFavorite}>
+	<MenuOption
+		on:click={() => {
+			closeMenu();
+			handleRemoveFavorite();
+		}}
+		text="Remove from favorites"
+	/>
+</RemoveFavorite>

+ 19 - 62
web/src/lib/components/share-page/individual-shared-viewer.svelte

@@ -1,47 +1,37 @@
 <script lang="ts">
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-
-	import { api, AssetResponseDto, SharedLinkResponseDto } from '@api';
-	import ControlAppBar from '../shared-components/control-app-bar.svelte';
 	import { goto } from '$app/navigation';
-	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+	import { bulkDownload } from '$lib/utils/asset-utils';
+	import { openFileUploadDialog } from '$lib/utils/file-uploader';
+	import { api, AssetResponseDto, SharedLinkResponseDto } from '@api';
+	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
 	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
 	import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
-	import { openFileUploadDialog } from '$lib/utils/file-uploader';
-	import { bulkDownload } from '$lib/utils/asset-utils';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
+	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
+	import DownloadFiles from '../photos-page/actions/download-files.svelte';
+	import RemoveFromSharedLink from '../photos-page/actions/remove-from-shared-link.svelte';
+	import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
+	import ControlAppBar from '../shared-components/control-app-bar.svelte';
 	import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
 	import ImmichLogo from '../shared-components/immich-logo.svelte';
 	import {
 		notificationController,
 		NotificationType
 	} from '../shared-components/notification/notification';
-	import { locale } from '$lib/stores/preferences.store';
 
 	export let sharedLink: SharedLinkResponseDto;
 	export let isOwned: boolean;
 
-	let assets = sharedLink.assets;
 	let selectedAssets: Set<AssetResponseDto> = new Set();
 
+	$: assets = sharedLink.assets;
 	$: isMultiSelectionMode = selectedAssets.size > 0;
 
 	const clearMultiSelectAssetAssetHandler = () => {
 		selectedAssets = new Set();
 	};
 
-	const downloadAssets = async (isAll: boolean) => {
-		await bulkDownload(
-			'immich-shared',
-			isAll ? assets : Array.from(selectedAssets),
-			() => {
-				isMultiSelectionMode = false;
-				clearMultiSelectAssetAssetHandler();
-			},
-			sharedLink?.key
-		);
+	const downloadAssets = async () => {
+		await bulkDownload('immich-shared', assets, undefined, sharedLink?.key);
 	};
 
 	const handleUploadAssets = async () => {
@@ -65,49 +55,16 @@
 			console.error('handleUploadAssets', e);
 		}
 	};
-
-	const handleRemoveAssetsFromSharedLink = async () => {
-		if (window.confirm('Do you want to remove selected assets from the shared link?')) {
-			await api.assetApi.removeAssetsFromSharedLink(
-				{
-					assetIds: assets.filter((a) => !selectedAssets.has(a)).map((a) => a.id)
-				},
-				sharedLink?.key
-			);
-
-			assets = assets.filter((a) => !selectedAssets.has(a));
-			clearMultiSelectAssetAssetHandler();
-		}
-	};
 </script>
 
 <section class="bg-immich-bg dark:bg-immich-dark-bg">
 	{#if isMultiSelectionMode}
-		<ControlAppBar
-			on:close-button-click={clearMultiSelectAssetAssetHandler}
-			backIcon={Close}
-			tailwindClasses={'bg-white shadow-md'}
-		>
-			<svelte:fragment slot="leading">
-				<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
-					Selected {selectedAssets.size.toLocaleString($locale)}
-				</p>
-			</svelte:fragment>
-			<svelte:fragment slot="trailing">
-				<CircleIconButton
-					title="Download"
-					on:click={() => downloadAssets(false)}
-					logo={CloudDownloadOutline}
-				/>
-				{#if isOwned}
-					<CircleIconButton
-						title="Remove from album"
-						on:click={handleRemoveAssetsFromSharedLink}
-						logo={DeleteOutline}
-					/>
-				{/if}
-			</svelte:fragment>
-		</ControlAppBar>
+		<AssetSelectControlBar assets={selectedAssets} clearSelect={clearMultiSelectAssetAssetHandler}>
+			<DownloadFiles filename="immich-shared" sharedLinkKey={sharedLink.key} />
+			{#if isOwned}
+				<RemoveFromSharedLink bind:sharedLink allAssets={assets} />
+			{/if}
+		</AssetSelectControlBar>
 	{:else}
 		<ControlAppBar
 			on:close-button-click={() => goto('/photos')}
@@ -139,7 +96,7 @@
 				{#if sharedLink?.allowDownload}
 					<CircleIconButton
 						title="Download"
-						on:click={() => downloadAssets(true)}
+						on:click={downloadAssets}
 						logo={FolderDownloadOutline}
 					/>
 				{/if}

+ 2 - 2
web/src/lib/utils/asset-utils.ts

@@ -25,7 +25,7 @@ export const addAssetsToAlbum = async (
 export async function bulkDownload(
 	fileName: string,
 	assets: AssetResponseDto[],
-	onDone: () => void,
+	onDone?: () => void,
 	key?: string
 ) {
 	const assetIds = assets.map((asset) => asset.id);
@@ -63,7 +63,7 @@ export async function bulkDownload(
 			if (isNotComplete && fileCount > 0) {
 				// skip += fileCount;
 			} else {
-				onDone();
+				onDone?.();
 				done = true;
 			}
 

+ 32 - 223
web/src/routes/(user)/archive/+page.svelte

@@ -1,34 +1,27 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
-	import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
-	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
-	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { addAssetsToAlbum, bulkDownload } from '$lib/utils/asset-utils';
-	import { AlbumResponseDto, api, AssetResponseDto, SharedLinkType } from '@api';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
-	import ArchiveArrowUpOutline from 'svelte-material-icons/ArchiveArrowUpOutline.svelte';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
-	import Plus from 'svelte-material-icons/Plus.svelte';
-	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
-	import { locale } from '$lib/stores/preferences.store';
-	import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
 	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
-	import type { PageData } from './$types';
-	import { onMount } from 'svelte';
-	import { handleError } from '$lib/utils/handle-error';
+	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+	import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
+	import DownloadFiles from '$lib/components/photos-page/actions/download-files.svelte';
+	import RemoveFromArchive from '$lib/components/photos-page/actions/remove-from-archive.svelte';
+	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+	import OptionAddToAlbum from '$lib/components/photos-page/menu-options/option-add-to-album.svelte';
+	import OptionAddToFavorites from '$lib/components/photos-page/menu-options/option-add-to-favorites.svelte';
+	import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
 	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
 	import { archivedAsset } from '$lib/stores/archived-asset.store';
+	import { handleError } from '$lib/utils/handle-error';
+	import { api, AssetResponseDto } from '@api';
+	import { onMount } from 'svelte';
+	import Plus from 'svelte-material-icons/Plus.svelte';
+	import type { PageData } from './$types';
 
 	export let data: PageData;
 
+	let selectedAssets: Set<AssetResponseDto> = new Set();
+	$: isMultiSelectionMode = selectedAssets.size > 0;
+
 	onMount(async () => {
 		try {
 			const { data: assets } = await api.assetApi.getAllAssets(undefined, true);
@@ -38,144 +31,8 @@
 		}
 	});
 
-	const clearMultiSelectAssetAssetHandler = () => {
-		selectedAssets = new Set();
-	};
-
-	const deleteSelectedAssetHandler = async () => {
-		try {
-			if (
-				window.confirm(
-					`Caution! Are you sure you want to delete ${selectedAssets.size} assets? This step also deletes assets in the album(s) to which they belong. You can not undo this action!`
-				)
-			) {
-				const { data: deletedAssets } = await api.assetApi.deleteAsset({
-					ids: Array.from(selectedAssets).map((a) => a.id)
-				});
-
-				for (const asset of deletedAssets) {
-					if (asset.status == 'SUCCESS') {
-						$archivedAsset = $archivedAsset.filter((a) => a.id != asset.id);
-					}
-				}
-
-				clearMultiSelectAssetAssetHandler();
-			}
-		} catch (e) {
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error deleting assets, check console for more details'
-			});
-			console.error('Error deleteSelectedAssetHandler', e);
-		}
-	};
-
-	$: isMultiSelectionMode = selectedAssets.size > 0;
-
-	let selectedAssets: Set<AssetResponseDto> = new Set();
-
-	let contextMenuPosition = { x: 0, y: 0 };
-	let isShowCreateSharedLinkModal = false;
-	let isShowAddMenu = false;
-	let isShowAlbumPicker = false;
-	let addToSharedAlbum = false;
-
-	const handleShowMenu = ({ x, y }: MouseEvent) => {
-		contextMenuPosition = { x, y };
-		isShowAddMenu = !isShowAddMenu;
-	};
-
-	const handleAddToFavorites = () => {
-		isShowAddMenu = false;
-
-		let cnt = 0;
-		for (const asset of selectedAssets) {
-			if (!asset.isFavorite) {
-				api.assetApi.updateAsset(asset.id, {
-					isFavorite: true
-				});
-				cnt = cnt + 1;
-			}
-		}
-
-		notificationController.show({
-			message: `Added ${cnt} to favorites`,
-			type: NotificationType.Info
-		});
-
-		clearMultiSelectAssetAssetHandler();
-	};
-
-	const handleShowAlbumPicker = (shared: boolean) => {
-		isShowAddMenu = false;
-		isShowAlbumPicker = true;
-		addToSharedAlbum = shared;
-	};
-
-	const handleAddToNewAlbum = (event: CustomEvent) => {
-		isShowAlbumPicker = false;
-
-		const { albumName }: { albumName: string } = event.detail;
-		const assetIds = Array.from(selectedAssets).map((asset) => asset.id);
-		api.albumApi.createAlbum({ albumName, assetIds }).then((response) => {
-			const { id, albumName } = response.data;
-
-			notificationController.show({
-				message: `Added ${assetIds.length} to ${albumName}`,
-				type: NotificationType.Info
-			});
-
-			clearMultiSelectAssetAssetHandler();
-
-			goto('/albums/' + id);
-		});
-	};
-
-	const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
-		isShowAlbumPicker = false;
-		const album = event.detail.album;
-
-		const assetIds = Array.from(selectedAssets).map((asset) => asset.id);
-
-		addAssetsToAlbum(album.id, assetIds).then(() => {
-			clearMultiSelectAssetAssetHandler();
-		});
-	};
-
-	const handleDownloadFiles = async () => {
-		await bulkDownload('immich', Array.from(selectedAssets), () => {
-			clearMultiSelectAssetAssetHandler();
-		});
-	};
-
-	const handleUnarchive = async () => {
-		let cnt = 0;
-		for (const asset of selectedAssets) {
-			if (asset.isArchived) {
-				api.assetApi.updateAsset(asset.id, {
-					isArchived: false
-				});
-				cnt = cnt + 1;
-
-				$archivedAsset = $archivedAsset.filter((a) => a.id != asset.id);
-			}
-		}
-
-		notificationController.show({
-			message: `Removed ${cnt} from archive`,
-			type: NotificationType.Info
-		});
-
-		clearMultiSelectAssetAssetHandler();
-	};
-
-	const handleCreateSharedLink = async () => {
-		isShowCreateSharedLinkModal = true;
-	};
-
-	const handleCloseSharedLinkModal = () => {
-		clearMultiSelectAssetAssetHandler();
-		isShowCreateSharedLinkModal = false;
+	const onAssetDelete = (assetId: string) => {
+		$archivedAsset = $archivedAsset.filter((a) => a.id !== assetId);
 	};
 </script>
 
@@ -190,68 +47,20 @@
 
 	<svelte:fragment slot="header">
 		{#if isMultiSelectionMode}
-			<ControlAppBar
-				on:close-button-click={clearMultiSelectAssetAssetHandler}
-				backIcon={Close}
-				tailwindClasses={'bg-white shadow-md'}
+			<AssetSelectControlBar
+				assets={selectedAssets}
+				clearSelect={() => (selectedAssets = new Set())}
 			>
-				<svelte:fragment slot="leading">
-					<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
-						Selected {selectedAssets.size.toLocaleString($locale)}
-					</p>
-				</svelte:fragment>
-				<svelte:fragment slot="trailing">
-					<CircleIconButton
-						title="Share"
-						logo={ShareVariantOutline}
-						on:click={handleCreateSharedLink}
-					/>
-					<CircleIconButton
-						title="Unarchive"
-						logo={ArchiveArrowUpOutline}
-						on:click={handleUnarchive}
-					/>
-					<CircleIconButton
-						title="Download"
-						logo={CloudDownloadOutline}
-						on:click={handleDownloadFiles}
-					/>
-					<CircleIconButton title="Add" logo={Plus} on:click={handleShowMenu} />
-					<CircleIconButton
-						title="Delete"
-						logo={DeleteOutline}
-						on:click={deleteSelectedAssetHandler}
-					/>
-				</svelte:fragment>
-			</ControlAppBar>
-		{/if}
-
-		{#if isShowAddMenu}
-			<ContextMenu {...contextMenuPosition} on:clickoutside={() => (isShowAddMenu = false)}>
-				<div class="flex flex-col rounded-lg">
-					<MenuOption on:click={handleAddToFavorites} text="Add to Favorites" />
-					<MenuOption on:click={() => handleShowAlbumPicker(false)} text="Add to Album" />
-					<MenuOption on:click={() => handleShowAlbumPicker(true)} text="Add to Shared Album" />
-				</div>
-			</ContextMenu>
-		{/if}
-
-		{#if isShowAlbumPicker}
-			<AlbumSelectionModal
-				shared={addToSharedAlbum}
-				on:newAlbum={handleAddToNewAlbum}
-				on:newSharedAlbum={handleAddToNewAlbum}
-				on:album={handleAddToAlbum}
-				on:close={() => (isShowAlbumPicker = false)}
-			/>
-		{/if}
-
-		{#if isShowCreateSharedLinkModal}
-			<CreateSharedLinkModal
-				sharedAssets={Array.from(selectedAssets)}
-				shareType={SharedLinkType.Individual}
-				on:close={handleCloseSharedLinkModal}
-			/>
+				<CreateSharedLink />
+				<RemoveFromArchive onAssetArchive={(asset) => onAssetDelete(asset.id)} />
+				<DownloadFiles />
+				<AssetSelectContextMenu icon={Plus} title="Add">
+					<OptionAddToFavorites />
+					<OptionAddToAlbum />
+					<OptionAddToAlbum shared />
+				</AssetSelectContextMenu>
+				<DeleteAssets {onAssetDelete} />
+			</AssetSelectControlBar>
 		{/if}
 	</svelte:fragment>
 

+ 12 - 68
web/src/routes/(user)/favorites/+page.svelte

@@ -1,21 +1,17 @@
 <script lang="ts">
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
-	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
+	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+	import RemoveFavorite from '$lib/components/photos-page/actions/remove-favorite.svelte';
+	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+	import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
 	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
 	import { handleError } from '$lib/utils/handle-error';
-	import { api, AssetResponseDto, SharedLinkType } from '@api';
+	import { api, AssetResponseDto } from '@api';
 	import { onMount } from 'svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
-	import HeartMinusOutline from 'svelte-material-icons/HeartMinusOutline.svelte';
 	import Error from '../../+error.svelte';
-	import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
 	import type { PageData } from './$types';
 
 	let favorites: AssetResponseDto[] = [];
-	let isShowCreateSharedLinkModal = false;
 	let selectedAssets: Set<AssetResponseDto> = new Set();
 
 	export let data: PageData;
@@ -31,69 +27,17 @@
 		}
 	});
 
-	const clearMultiSelectAssetAssetHandler = () => {
-		selectedAssets = new Set();
-	};
-
-	const handleCreateSharedLink = async () => {
-		isShowCreateSharedLinkModal = true;
-	};
-
-	const handleCloseSharedLinkModal = () => {
-		clearMultiSelectAssetAssetHandler();
-		isShowCreateSharedLinkModal = false;
-	};
-
-	const handleRemoveFavorite = async () => {
-		for (const asset of selectedAssets) {
-			try {
-				await api.assetApi.updateAsset(asset.id, {
-					isFavorite: false
-				});
-				favorites = favorites.filter((a) => a.id != asset.id);
-			} catch {
-				handleError(Error, 'Error updating asset favorite state');
-			}
-		}
-
-		clearMultiSelectAssetAssetHandler();
+	const onAssetDelete = (assetId: string) => {
+		favorites = favorites.filter((a) => a.id !== assetId);
 	};
 </script>
 
 <!-- Multiselection mode app bar -->
 {#if isMultiSelectionMode}
-	<ControlAppBar
-		on:close-button-click={clearMultiSelectAssetAssetHandler}
-		backIcon={Close}
-		tailwindClasses={'bg-white shadow-md'}
-	>
-		<svelte:fragment slot="leading">
-			<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
-				Selected {selectedAssets.size}
-			</p>
-		</svelte:fragment>
-		<svelte:fragment slot="trailing">
-			<CircleIconButton
-				title="Share"
-				logo={ShareVariantOutline}
-				on:click={handleCreateSharedLink}
-			/>
-			<CircleIconButton
-				title="Remove Favorite"
-				logo={HeartMinusOutline}
-				on:click={handleRemoveFavorite}
-			/>
-		</svelte:fragment>
-	</ControlAppBar>
-{/if}
-
-<!-- Create shared link modal -->
-{#if isShowCreateSharedLinkModal}
-	<CreateSharedLinkModal
-		sharedAssets={Array.from(selectedAssets)}
-		shareType={SharedLinkType.Individual}
-		on:close={handleCloseSharedLinkModal}
-	/>
+	<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
+		<CreateSharedLink />
+		<RemoveFavorite onAssetFavorite={(asset) => onAssetDelete(asset.id)} />
+	</AssetSelectControlBar>
 {/if}
 
 <UserPageLayout user={data.user} hideNavbar={isMultiSelectionMode}>

+ 15 - 33
web/src/routes/(user)/partners/[userId]/+page.svelte

@@ -1,52 +1,34 @@
 <script lang="ts">
-	import type { PageData } from './$types';
-	import { AppRoute } from '$lib/constants';
-	import { locale } from '$lib/stores/preferences.store';
 	import { goto } from '$app/navigation';
-	import { bulkDownload } from '$lib/utils/asset-utils';
-	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
+	import DownloadFiles from '$lib/components/photos-page/actions/download-files.svelte';
 	import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
+	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
+	import { AppRoute } from '$lib/constants';
 	import {
 		assetInteractionStore,
 		isMultiSelectStoreState,
 		selectedAssets
 	} from '$lib/stores/asset-interaction.store';
+	import { onDestroy } from 'svelte';
+	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+	import type { PageData } from './$types';
 
 	export let data: PageData;
 
-	const handleDownloadFiles = async () => {
-		await bulkDownload('immich', Array.from($selectedAssets), () => {
-			assetInteractionStore.clearMultiselect();
-		});
-	};
+	onDestroy(() => {
+		assetInteractionStore.clearMultiselect();
+	});
 </script>
 
 <main class="grid h-screen pt-[4.25rem] bg-immich-bg dark:bg-immich-dark-bg">
 	{#if $isMultiSelectStoreState}
-		<ControlAppBar
-			showBackButton
-			backIcon={Close}
-			on:close-button-click={() => assetInteractionStore.clearMultiselect()}
-			tailwindClasses={'bg-white shadow-md'}
+		<AssetSelectControlBar
+			assets={$selectedAssets}
+			clearSelect={assetInteractionStore.clearMultiselect}
 		>
-			<svelte:fragment slot="leading">
-				<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
-					Selected {$selectedAssets.size.toLocaleString($locale)}
-				</p>
-			</svelte:fragment>
-
-			<svelte:fragment slot="trailing">
-				<CircleIconButton
-					title="Download"
-					logo={CloudDownloadOutline}
-					on:click={handleDownloadFiles}
-				/>
-			</svelte:fragment>
-		</ControlAppBar>
+			<DownloadFiles />
+		</AssetSelectControlBar>
 	{:else}
 		<ControlAppBar
 			showBackButton

+ 25 - 212
web/src/routes/(user)/photos/+page.svelte

@@ -1,235 +1,48 @@
 <script lang="ts">
-	import { goto } from '$app/navigation';
+	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
+	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+	import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
+	import DownloadFiles from '$lib/components/photos-page/actions/download-files.svelte';
+	import MoveToArchive from '$lib/components/photos-page/actions/move-to-archive.svelte';
 	import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
-	import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
-	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
-	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
-	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
+	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+	import OptionAddToAlbum from '$lib/components/photos-page/menu-options/option-add-to-album.svelte';
+	import OptionAddToFavorites from '$lib/components/photos-page/menu-options/option-add-to-favorites.svelte';
 	import {
 		assetInteractionStore,
 		isMultiSelectStoreState,
 		selectedAssets
 	} from '$lib/stores/asset-interaction.store';
 	import { assetStore } from '$lib/stores/assets.store';
-	import { addAssetsToAlbum, bulkDownload } from '$lib/utils/asset-utils';
-	import { AlbumResponseDto, api, SharedLinkType } from '@api';
-	import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
+	import { onDestroy } from 'svelte';
 	import Plus from 'svelte-material-icons/Plus.svelte';
-	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
-	import { locale } from '$lib/stores/preferences.store';
-	import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
 	import type { PageData } from './$types';
 
 	export let data: PageData;
 
-	let isShowCreateSharedLinkModal = false;
-	const deleteSelectedAssetHandler = async () => {
-		try {
-			if (
-				window.confirm(
-					`Caution! Are you sure you want to delete ${$selectedAssets.size} assets? This step also deletes assets in the album(s) to which they belong. You can not undo this action!`
-				)
-			) {
-				const { data: deletedAssets } = await api.assetApi.deleteAsset({
-					ids: Array.from($selectedAssets).map((a) => a.id)
-				});
-
-				for (const asset of deletedAssets) {
-					if (asset.status == 'SUCCESS') {
-						assetStore.removeAsset(asset.id);
-					}
-				}
-
-				assetInteractionStore.clearMultiselect();
-			}
-		} catch (e) {
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error deleting assets, check console for more details'
-			});
-			console.error('Error deleteSelectedAssetHandler', e);
-		}
-	};
-
-	let contextMenuPosition = { x: 0, y: 0 };
-	let isShowAddMenu = false;
-	let isShowAlbumPicker = false;
-	let addToSharedAlbum = false;
-
-	const handleShowMenu = ({ x, y }: MouseEvent) => {
-		contextMenuPosition = { x, y };
-		isShowAddMenu = !isShowAddMenu;
-	};
-
-	const handleArchive = async () => {
-		let cnt = 0;
-		for (const asset of $selectedAssets) {
-			if (!asset.isArchived) {
-				api.assetApi.updateAsset(asset.id, {
-					isArchived: true
-				});
-
-				assetStore.removeAsset(asset.id);
-				cnt = cnt + 1;
-			}
-		}
-
-		notificationController.show({
-			message: `Archived ${cnt}`,
-			type: NotificationType.Info
-		});
-
+	onDestroy(() => {
 		assetInteractionStore.clearMultiselect();
-	};
-
-	const handleAddToFavorites = () => {
-		isShowAddMenu = false;
-
-		let cnt = 0;
-		for (const asset of $selectedAssets) {
-			if (!asset.isFavorite) {
-				api.assetApi.updateAsset(asset.id, {
-					isFavorite: true
-				});
-				assetStore.updateAsset(asset.id, true);
-				cnt = cnt + 1;
-			}
-		}
-
-		notificationController.show({
-			message: `Added ${cnt} to favorites`,
-			type: NotificationType.Info
-		});
-
-		assetInteractionStore.clearMultiselect();
-	};
-
-	const handleShowAlbumPicker = (shared: boolean) => {
-		isShowAddMenu = false;
-		isShowAlbumPicker = true;
-		addToSharedAlbum = shared;
-	};
-
-	const handleAddToNewAlbum = (event: CustomEvent) => {
-		isShowAlbumPicker = false;
-
-		const { albumName }: { albumName: string } = event.detail;
-		const assetIds = Array.from($selectedAssets).map((asset) => asset.id);
-		api.albumApi.createAlbum({ albumName, assetIds }).then((response) => {
-			const { id, albumName } = response.data;
-
-			notificationController.show({
-				message: `Added ${assetIds.length} to ${albumName}`,
-				type: NotificationType.Info
-			});
-
-			assetInteractionStore.clearMultiselect();
-
-			goto('/albums/' + id);
-		});
-	};
-
-	const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
-		isShowAlbumPicker = false;
-		const album = event.detail.album;
-
-		const assetIds = Array.from($selectedAssets).map((asset) => asset.id);
-
-		addAssetsToAlbum(album.id, assetIds).then(() => {
-			assetInteractionStore.clearMultiselect();
-		});
-	};
-
-	const handleDownloadFiles = async () => {
-		await bulkDownload('immich', Array.from($selectedAssets), () => {
-			assetInteractionStore.clearMultiselect();
-		});
-	};
-
-	const handleCreateSharedLink = async () => {
-		isShowCreateSharedLinkModal = true;
-	};
-
-	const handleCloseSharedLinkModal = () => {
-		assetInteractionStore.clearMultiselect();
-		isShowCreateSharedLinkModal = false;
-	};
+	});
 </script>
 
 <UserPageLayout user={data.user} hideNavbar={$isMultiSelectStoreState} showUploadButton>
 	<svelte:fragment slot="header">
 		{#if $isMultiSelectStoreState}
-			<ControlAppBar
-				on:close-button-click={() => assetInteractionStore.clearMultiselect()}
-				backIcon={Close}
-				tailwindClasses={'bg-white shadow-md'}
+			<AssetSelectControlBar
+				assets={$selectedAssets}
+				clearSelect={assetInteractionStore.clearMultiselect}
 			>
-				<svelte:fragment slot="leading">
-					<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
-						Selected {$selectedAssets.size.toLocaleString($locale)}
-					</p>
-				</svelte:fragment>
-				<svelte:fragment slot="trailing">
-					<CircleIconButton
-						title="Share"
-						logo={ShareVariantOutline}
-						on:click={handleCreateSharedLink}
-					/>
-					<CircleIconButton
-						title="Archive"
-						logo={ArchiveArrowDownOutline}
-						on:click={handleArchive}
-					/>
-					<CircleIconButton
-						title="Download"
-						logo={CloudDownloadOutline}
-						on:click={handleDownloadFiles}
-					/>
-					<CircleIconButton title="Add" logo={Plus} on:click={handleShowMenu} />
-					<CircleIconButton
-						title="Delete"
-						logo={DeleteOutline}
-						on:click={deleteSelectedAssetHandler}
-					/>
-				</svelte:fragment>
-			</ControlAppBar>
-		{/if}
-
-		{#if isShowAddMenu}
-			<ContextMenu {...contextMenuPosition} on:clickoutside={() => (isShowAddMenu = false)}>
-				<div class="flex flex-col rounded-lg">
-					<MenuOption on:click={handleAddToFavorites} text="Add to Favorites" />
-					<MenuOption on:click={() => handleShowAlbumPicker(false)} text="Add to Album" />
-					<MenuOption on:click={() => handleShowAlbumPicker(true)} text="Add to Shared Album" />
-				</div>
-			</ContextMenu>
-		{/if}
-
-		{#if isShowAlbumPicker}
-			<AlbumSelectionModal
-				shared={addToSharedAlbum}
-				on:newAlbum={handleAddToNewAlbum}
-				on:newSharedAlbum={handleAddToNewAlbum}
-				on:album={handleAddToAlbum}
-				on:close={() => (isShowAlbumPicker = false)}
-			/>
-		{/if}
-
-		{#if isShowCreateSharedLinkModal}
-			<CreateSharedLinkModal
-				sharedAssets={Array.from($selectedAssets)}
-				shareType={SharedLinkType.Individual}
-				on:close={handleCloseSharedLinkModal}
-			/>
+				<CreateSharedLink />
+				<MoveToArchive onAssetArchive={(asset) => assetStore.removeAsset(asset.id)} />
+				<DownloadFiles />
+				<AssetSelectContextMenu icon={Plus} title="Add">
+					<OptionAddToFavorites />
+					<OptionAddToAlbum />
+					<OptionAddToAlbum shared />
+				</AssetSelectContextMenu>
+				<DeleteAssets onAssetDelete={assetStore.removeAsset} />
+			</AssetSelectControlBar>
 		{/if}
 	</svelte:fragment>
 

+ 36 - 236
web/src/routes/(user)/search/+page.svelte

@@ -1,31 +1,25 @@
 <script lang="ts">
+	import { afterNavigate, goto } from '$app/navigation';
 	import { page } from '$app/stores';
+	import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
+	import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
+	import DownloadFiles from '$lib/components/photos-page/actions/download-files.svelte';
+	import MoveToArchive from '$lib/components/photos-page/actions/move-to-archive.svelte';
+	import RemoveFromArchive from '$lib/components/photos-page/actions/remove-from-archive.svelte';
+	import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
+	import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
+	import OptionAddToAlbum from '$lib/components/photos-page/menu-options/option-add-to-album.svelte';
+	import OptionAddToFavorites from '$lib/components/photos-page/menu-options/option-add-to-favorites.svelte';
+	import OptionRemoveFromFavorites from '$lib/components/photos-page/menu-options/option-remove-from-favorites.svelte';
 	import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
 	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
-	import type { PageData } from './$types';
+	import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
+	import { AssetResponseDto } from '@api';
 	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
 	import ImageOffOutline from 'svelte-material-icons/ImageOffOutline.svelte';
-	import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
-	import { afterNavigate, goto } from '$app/navigation';
-	import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
-	import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
-	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
-	import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
-	import {
-		notificationController,
-		NotificationType
-	} from '$lib/components/shared-components/notification/notification';
-	import { addAssetsToAlbum, bulkDownload } from '$lib/utils/asset-utils';
-	import { AlbumResponseDto, api, AssetResponseDto, SharedLinkType } from '@api';
-	import Close from 'svelte-material-icons/Close.svelte';
-	import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
-	import ArchiveArrowUpOutline from 'svelte-material-icons/ArchiveArrowUpOutline.svelte';
-	import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
-	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
 	import Plus from 'svelte-material-icons/Plus.svelte';
-	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
-	import { locale } from '$lib/stores/preferences.store';
+	import type { PageData } from './$types';
+
 	export let data: PageData;
 
 	// The GalleryViewer pushes it's own history state, which causes weird
@@ -46,197 +40,34 @@
 	$: isMultiSelectionMode = selectedAssets.size > 0;
 	$: isAllArchived = Array.from(selectedAssets).every((asset) => asset.isArchived);
 	$: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
-
-	let contextMenuPosition = { x: 0, y: 0 };
-	let isShowCreateSharedLinkModal = false;
-	let isShowAddMenu = false;
-	let isShowAlbumPicker = false;
-	let addToSharedAlbum = false;
 	$: searchResultAssets = data.results.assets.items;
 
-	const handleShowMenu = ({ x, y }: MouseEvent) => {
-		contextMenuPosition = { x, y };
-		isShowAddMenu = !isShowAddMenu;
-	};
-
-	const handleShowAlbumPicker = (shared: boolean) => {
-		isShowAddMenu = false;
-		isShowAlbumPicker = true;
-		addToSharedAlbum = shared;
-	};
-
-	const handleAddToNewAlbum = (event: CustomEvent) => {
-		isShowAlbumPicker = false;
-
-		const { albumName }: { albumName: string } = event.detail;
-		const assetIds = Array.from(selectedAssets).map((asset) => asset.id);
-		api.albumApi.createAlbum({ albumName, assetIds }).then((response) => {
-			const { id, albumName } = response.data;
-
-			notificationController.show({
-				message: `Added ${assetIds.length} to ${albumName}`,
-				type: NotificationType.Info
-			});
-
-			clearMultiSelectAssetAssetHandler();
-
-			goto('/albums/' + id);
-		});
-	};
-
-	const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
-		isShowAlbumPicker = false;
-		const album = event.detail.album;
-
-		const assetIds = Array.from(selectedAssets).map((asset) => asset.id);
-
-		addAssetsToAlbum(album.id, assetIds).then(() => {
-			clearMultiSelectAssetAssetHandler();
-		});
-	};
-
-	const handleDownloadFiles = async () => {
-		await bulkDownload('immich', Array.from(selectedAssets), () => {
-			clearMultiSelectAssetAssetHandler();
-		});
-	};
-
-	const toggleArchive = async () => {
-		let cnt = 0;
-		for (const asset of selectedAssets) {
-			api.assetApi.updateAsset(asset.id, {
-				isArchived: !isAllArchived
-			});
-			cnt = cnt + 1;
-
-			asset.isArchived = !isAllArchived;
-
-			searchResultAssets = searchResultAssets.map((a: AssetResponseDto) => {
-				if (a.id === asset.id) {
-					a = asset;
-				}
-
-				return a;
-			});
-		}
-
-		notificationController.show({
-			message: `${isAllArchived ? `Remove ${cnt} from` : `Add ${cnt} to`} archive`,
-			type: NotificationType.Info
-		});
-
-		clearMultiSelectAssetAssetHandler();
-	};
-
-	const toggleFavorite = () => {
-		isShowAddMenu = false;
-
-		let cnt = 0;
-		for (const asset of selectedAssets) {
-			api.assetApi.updateAsset(asset.id, {
-				isFavorite: !isAllFavorite
-			});
-			cnt = cnt + 1;
-
-			asset.isFavorite = !isAllFavorite;
-
-			searchResultAssets = searchResultAssets.map((a: AssetResponseDto) => {
-				if (a.id === asset.id) {
-					a = asset;
-				}
-				return a;
-			});
-		}
-
-		notificationController.show({
-			message: `${isAllFavorite ? `Remove ${cnt} from` : `Add ${cnt} to`} favorites`,
-			type: NotificationType.Info
-		});
-
-		clearMultiSelectAssetAssetHandler();
-	};
-
-	const clearMultiSelectAssetAssetHandler = () => {
-		selectedAssets = new Set();
-	};
-
-	const deleteSelectedAssetHandler = async () => {
-		try {
-			if (
-				window.confirm(
-					`Caution! Are you sure you want to delete ${selectedAssets.size} assets? This step also deletes assets in the album(s) to which they belong. You can not undo this action!`
-				)
-			) {
-				const { data: deletedAssets } = await api.assetApi.deleteAsset({
-					ids: Array.from(selectedAssets).map((a) => a.id)
-				});
-
-				for (const asset of deletedAssets) {
-					if (asset.status == 'SUCCESS') {
-						searchResultAssets = searchResultAssets.filter(
-							(a: AssetResponseDto) => a.id != asset.id
-						);
-					}
-				}
-
-				clearMultiSelectAssetAssetHandler();
-			}
-		} catch (e) {
-			notificationController.show({
-				type: NotificationType.Error,
-				message: 'Error deleting assets, check console for more details'
-			});
-			console.error('Error deleteSelectedAssetHandler', e);
-		}
-	};
-	const handleCreateSharedLink = async () => {
-		isShowCreateSharedLinkModal = true;
-	};
-
-	const handleCloseSharedLinkModal = () => {
-		clearMultiSelectAssetAssetHandler();
-		isShowCreateSharedLinkModal = false;
+	const onAssetDelete = (assetId: string) => {
+		searchResultAssets = searchResultAssets.filter((a: AssetResponseDto) => a.id !== assetId);
 	};
 </script>
 
 <section>
 	{#if isMultiSelectionMode}
-		<ControlAppBar
-			on:close-button-click={clearMultiSelectAssetAssetHandler}
-			backIcon={Close}
-			tailwindClasses={'bg-white shadow-md'}
-		>
-			<svelte:fragment slot="leading">
-				<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
-					Selected {selectedAssets.size.toLocaleString($locale)}
-				</p>
-			</svelte:fragment>
-			<svelte:fragment slot="trailing">
-				<CircleIconButton
-					title="Share"
-					logo={ShareVariantOutline}
-					on:click={handleCreateSharedLink}
-				/>
-
-				<CircleIconButton
-					title={isAllArchived ? 'Unarchive' : 'Archive'}
-					logo={isAllArchived ? ArchiveArrowUpOutline : ArchiveArrowDownOutline}
-					on:click={toggleArchive}
-				/>
-
-				<CircleIconButton
-					title="Download"
-					logo={CloudDownloadOutline}
-					on:click={handleDownloadFiles}
-				/>
-				<CircleIconButton title="Add" logo={Plus} on:click={handleShowMenu} />
-				<CircleIconButton
-					title="Delete"
-					logo={DeleteOutline}
-					on:click={deleteSelectedAssetHandler}
-				/>
-			</svelte:fragment>
-		</ControlAppBar>
+		<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
+			<CreateSharedLink />
+			{#if isAllArchived}
+				<RemoveFromArchive />
+			{:else}
+				<MoveToArchive />
+			{/if}
+			<DownloadFiles />
+			<AssetSelectContextMenu icon={Plus} title="Add">
+				{#if isAllFavorite}
+					<OptionRemoveFromFavorites />
+				{:else}
+					<OptionAddToFavorites />
+				{/if}
+				<OptionAddToAlbum />
+				<OptionAddToAlbum shared />
+			</AssetSelectContextMenu>
+			<DeleteAssets {onAssetDelete} />
+		</AssetSelectControlBar>
 	{:else}
 		<ControlAppBar on:close-button-click={() => goto(previousRoute)} backIcon={ArrowLeft}>
 			<div class="w-full max-w-2xl flex-1 pl-4">
@@ -271,35 +102,4 @@
 			{/if}
 		</section>
 	</section>
-
-	{#if isShowAddMenu}
-		<ContextMenu {...contextMenuPosition} on:clickoutside={() => (isShowAddMenu = false)}>
-			<div class="flex flex-col rounded-lg">
-				<MenuOption
-					on:click={toggleFavorite}
-					text={isAllFavorite ? 'Remove from favorites' : 'Add to favorites'}
-				/>
-				<MenuOption on:click={() => handleShowAlbumPicker(false)} text="Add to Album" />
-				<MenuOption on:click={() => handleShowAlbumPicker(true)} text="Add to Shared Album" />
-			</div>
-		</ContextMenu>
-	{/if}
-
-	{#if isShowAlbumPicker}
-		<AlbumSelectionModal
-			shared={addToSharedAlbum}
-			on:newAlbum={handleAddToNewAlbum}
-			on:newSharedAlbum={handleAddToNewAlbum}
-			on:album={handleAddToAlbum}
-			on:close={() => (isShowAlbumPicker = false)}
-		/>
-	{/if}
-
-	{#if isShowCreateSharedLinkModal}
-		<CreateSharedLinkModal
-			sharedAssets={Array.from(selectedAssets)}
-			shareType={SharedLinkType.Individual}
-			on:close={handleCloseSharedLinkModal}
-		/>
-	{/if}
 </section>