mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
Feature - Delete asset on the web (#436)
* Added selection mechanism to photos page * Added control app bar * Refactor AlbumAppBar into ControlAppBar * Added addtional micro interactions when in multi selection mode * Implemented delete selected asset and rerender
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { setAssetInfo } from '$lib/stores/assets';
|
||||
|
||||
export const load: Load = async ({ fetch, session }) => {
|
||||
if (!browser && !session.user) {
|
||||
return {
|
||||
@@ -39,20 +40,31 @@
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
|
||||
import { assetsGroupByDate, flattenAssetGroupByDate, assets } from '$lib/stores/assets';
|
||||
import ImmichThumbnail from '$lib/components/shared-components/immich-thumbnail.svelte';
|
||||
import moment from 'moment';
|
||||
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
||||
import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader';
|
||||
import { AssetResponseDto, UserResponseDto } from '@api';
|
||||
import { api, AssetResponseDto, UserResponseDto } from '@api';
|
||||
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||
import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
|
||||
import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte';
|
||||
import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
|
||||
import Close from 'svelte-material-icons/Close.svelte';
|
||||
import { browser } from '$app/env';
|
||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||
|
||||
export let user: UserResponseDto;
|
||||
|
||||
let selectedGroupThumbnail: number | null;
|
||||
let isMouseOverGroup: boolean;
|
||||
|
||||
let multiSelectedAssets = new Set<AssetResponseDto>();
|
||||
$: isMultiSelectionMode = multiSelectedAssets.size > 0;
|
||||
|
||||
let selectedGroup: Set<number> = new Set();
|
||||
let existingGroup: Set<number> = new Set();
|
||||
|
||||
$: if (isMouseOverGroup == false) {
|
||||
selectedGroupThumbnail = null;
|
||||
}
|
||||
@@ -110,6 +122,91 @@
|
||||
isShowAssetViewer = false;
|
||||
history.pushState(null, '', `/photos`);
|
||||
};
|
||||
|
||||
const selectAssetHandler = (asset: AssetResponseDto, groupIndex: number) => {
|
||||
let temp = new Set(multiSelectedAssets);
|
||||
|
||||
if (multiSelectedAssets.has(asset)) {
|
||||
temp.delete(asset);
|
||||
|
||||
const tempSelectedGroup = new Set(selectedGroup);
|
||||
tempSelectedGroup.delete(groupIndex);
|
||||
selectedGroup = tempSelectedGroup;
|
||||
} else {
|
||||
temp.add(asset);
|
||||
}
|
||||
|
||||
multiSelectedAssets = temp;
|
||||
|
||||
// Check if all assets are selected in a group to toggle the group selection's icon
|
||||
if (!selectedGroup.has(groupIndex)) {
|
||||
const assetsInGroup = $assetsGroupByDate[groupIndex];
|
||||
let selectedAssetsInGroupCount = 0;
|
||||
|
||||
assetsInGroup.forEach((asset) => {
|
||||
if (multiSelectedAssets.has(asset)) {
|
||||
selectedAssetsInGroupCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// if all assets are selected in a group, add the group to selected group
|
||||
if (selectedAssetsInGroupCount == assetsInGroup.length) {
|
||||
selectedGroup = selectedGroup.add(groupIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const clearMultiSelectAssetAssetHandler = () => {
|
||||
multiSelectedAssets = new Set();
|
||||
selectedGroup = new Set();
|
||||
existingGroup = new Set();
|
||||
};
|
||||
|
||||
const selectAssetGroupHandler = (groupIndex: number) => {
|
||||
if (existingGroup.has(groupIndex)) return;
|
||||
|
||||
let tempSelectedGroup = new Set(selectedGroup);
|
||||
let tempSelectedAsset = new Set(multiSelectedAssets);
|
||||
|
||||
if (selectedGroup.has(groupIndex)) {
|
||||
tempSelectedGroup.delete(groupIndex);
|
||||
tempSelectedAsset.forEach((asset) => {
|
||||
if ($assetsGroupByDate[groupIndex].find((a) => a.id == asset.id)) {
|
||||
tempSelectedAsset.delete(asset);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
tempSelectedGroup.add(groupIndex);
|
||||
tempSelectedAsset = new Set([...multiSelectedAssets, ...$assetsGroupByDate[groupIndex]]);
|
||||
}
|
||||
|
||||
multiSelectedAssets = tempSelectedAsset;
|
||||
selectedGroup = tempSelectedGroup;
|
||||
};
|
||||
|
||||
const deleteSelectedAssetHandler = async () => {
|
||||
try {
|
||||
if (
|
||||
window.confirm(
|
||||
`Are you sure you want to delete ${multiSelectedAssets.size} assets? This action cannot be undone.`
|
||||
)
|
||||
) {
|
||||
const { data: deletedAssets } = await api.assetApi.deleteAsset({
|
||||
ids: Array.from(multiSelectedAssets).map((a) => a.id)
|
||||
});
|
||||
|
||||
for (const asset of deletedAssets) {
|
||||
if (asset.status == 'SUCCESS') {
|
||||
$assets = $assets.filter((a) => a.id !== asset.id);
|
||||
}
|
||||
}
|
||||
|
||||
clearMultiSelectAssetAssetHandler();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error deleteSelectedAssetHandler', e);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -117,7 +214,28 @@
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
<NavigationBar {user} on:uploadClicked={() => openFileUploadDialog(UploadType.GENERAL)} />
|
||||
{#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">Selected {multiSelectedAssets.size}</p>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="trailing">
|
||||
<CircleIconButton
|
||||
title="Delete"
|
||||
logo={DeleteOutline}
|
||||
on:click={deleteSelectedAssetHandler}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
|
||||
{#if !isMultiSelectionMode}
|
||||
<NavigationBar {user} on:uploadClicked={() => openFileUploadDialog(UploadType.GENERAL)} />
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
|
||||
@@ -136,13 +254,20 @@
|
||||
>
|
||||
<!-- Date group title -->
|
||||
<p class="font-medium text-sm text-immich-fg mb-2 flex place-items-center h-6">
|
||||
{#if selectedGroupThumbnail === groupIndex && isMouseOverGroup}
|
||||
{#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || isMultiSelectionMode}
|
||||
<div
|
||||
in:fly={{ x: -24, duration: 200, opacity: 0.5 }}
|
||||
out:fly={{ x: -24, duration: 200 }}
|
||||
class="inline-block px-2 hover:cursor-pointer"
|
||||
on:click={() => selectAssetGroupHandler(groupIndex)}
|
||||
>
|
||||
<CheckCircle size="24" color="#757575" />
|
||||
{#if selectedGroup.has(groupIndex)}
|
||||
<CheckCircle size="24" color="#4250af" />
|
||||
{:else if existingGroup.has(groupIndex)}
|
||||
<CheckCircle size="24" color="#757575" />
|
||||
{:else}
|
||||
<CircleOutline size="24" color="#757575" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -156,7 +281,12 @@
|
||||
<ImmichThumbnail
|
||||
{asset}
|
||||
on:mouseEvent={thumbnailMouseEventHandler}
|
||||
on:click={viewAssetHandler}
|
||||
on:click={(event) =>
|
||||
isMultiSelectionMode
|
||||
? selectAssetHandler(asset, groupIndex)
|
||||
: viewAssetHandler(event)}
|
||||
on:select={() => selectAssetHandler(asset, groupIndex)}
|
||||
selected={multiSelectedAssets.has(asset)}
|
||||
{groupIndex}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
Reference in New Issue
Block a user