2024-05-23 12:57:25 -05:00
|
|
|
<script lang="ts">
|
|
|
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
|
|
|
import Icon from '$lib/components/elements/icon.svelte';
|
2024-06-15 22:45:20 +02:00
|
|
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
|
|
|
import DuplicateAsset from '$lib/components/utilities-page/duplicates/duplicate-asset.svelte';
|
|
|
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
2024-06-19 12:11:59 -04:00
|
|
|
import { suggestDuplicateByFileSize } from '$lib/utils';
|
2024-06-15 22:45:20 +02:00
|
|
|
import { type AssetResponseDto } from '@immich/sdk';
|
2024-05-23 12:57:25 -05:00
|
|
|
import { mdiCheck, mdiTrashCanOutline } from '@mdi/js';
|
2024-06-15 22:45:20 +02:00
|
|
|
import { onDestroy, onMount } from 'svelte';
|
2024-06-04 21:53:00 +02:00
|
|
|
import { t } from 'svelte-i18n';
|
2024-05-23 12:57:25 -05:00
|
|
|
|
2024-06-15 22:45:20 +02:00
|
|
|
export let assets: AssetResponseDto[];
|
2024-05-23 12:57:25 -05:00
|
|
|
export let onResolve: (duplicateAssetIds: string[], trashIds: string[]) => void;
|
|
|
|
|
2024-06-15 22:45:20 +02:00
|
|
|
const { isViewing: showAssetViewer, asset: viewingAsset, setAsset } = assetViewingStore;
|
|
|
|
const getAssetIndex = (id: string) => assets.findIndex((asset) => asset.id === id);
|
2024-05-23 12:57:25 -05:00
|
|
|
|
2024-06-15 22:45:20 +02:00
|
|
|
let selectedAssetIds = new Set<string>();
|
|
|
|
$: trashCount = assets.length - selectedAssetIds.size;
|
2024-05-23 12:57:25 -05:00
|
|
|
|
|
|
|
onMount(() => {
|
2024-06-19 12:11:59 -04:00
|
|
|
const suggestedAsset = suggestDuplicateByFileSize(assets);
|
2024-05-23 12:57:25 -05:00
|
|
|
|
|
|
|
if (!suggestedAsset) {
|
2024-06-15 22:45:20 +02:00
|
|
|
selectedAssetIds = new Set(assets[0].id);
|
2024-05-23 12:57:25 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
selectedAssetIds.add(suggestedAsset.id);
|
|
|
|
selectedAssetIds = selectedAssetIds;
|
|
|
|
});
|
|
|
|
|
2024-06-15 22:45:20 +02:00
|
|
|
onDestroy(() => {
|
|
|
|
assetViewingStore.showAssetViewer(false);
|
|
|
|
});
|
|
|
|
|
2024-05-23 12:57:25 -05:00
|
|
|
const onSelectAsset = (asset: AssetResponseDto) => {
|
|
|
|
if (selectedAssetIds.has(asset.id)) {
|
|
|
|
selectedAssetIds.delete(asset.id);
|
|
|
|
} else {
|
|
|
|
selectedAssetIds.add(asset.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
selectedAssetIds = selectedAssetIds;
|
|
|
|
};
|
|
|
|
|
2024-06-12 06:01:55 -07:00
|
|
|
const onSelectNone = () => {
|
|
|
|
selectedAssetIds.clear();
|
|
|
|
selectedAssetIds = selectedAssetIds;
|
|
|
|
};
|
|
|
|
|
|
|
|
const onSelectAll = () => {
|
2024-06-15 22:45:20 +02:00
|
|
|
selectedAssetIds = new Set(assets.map((asset) => asset.id));
|
2024-06-12 06:01:55 -07:00
|
|
|
};
|
|
|
|
|
2024-05-23 12:57:25 -05:00
|
|
|
const handleResolve = () => {
|
2024-06-15 22:45:20 +02:00
|
|
|
const trashIds = assets.map((asset) => asset.id).filter((id) => !selectedAssetIds.has(id));
|
|
|
|
const duplicateAssetIds = assets.map((asset) => asset.id);
|
2024-05-23 12:57:25 -05:00
|
|
|
onResolve(duplicateAssetIds, trashIds);
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
2024-06-15 22:45:20 +02:00
|
|
|
<div class="pt-4 rounded-3xl border dark:border-2 border-gray-300 dark:border-gray-700 max-w-[54rem] mx-auto mb-16">
|
2024-05-23 12:57:25 -05:00
|
|
|
<div class="flex flex-wrap gap-1 place-items-center place-content-center px-4 pt-4">
|
2024-06-15 22:45:20 +02:00
|
|
|
{#each assets as asset (asset.id)}
|
|
|
|
<DuplicateAsset
|
|
|
|
{asset}
|
|
|
|
{onSelectAsset}
|
|
|
|
isSelected={selectedAssetIds.has(asset.id)}
|
|
|
|
onViewAsset={(asset) => setAsset(asset)}
|
|
|
|
/>
|
2024-05-23 12:57:25 -05:00
|
|
|
{/each}
|
|
|
|
</div>
|
|
|
|
|
2024-06-15 22:45:20 +02:00
|
|
|
<div class="flex mt-10 mb-4 px-6 w-full place-content-end justify-between h-11">
|
2024-06-12 06:01:55 -07:00
|
|
|
<!-- MARK ALL BUTTONS -->
|
|
|
|
<div class="flex text-xs text-black">
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="px-4 flex place-items-center gap-2 rounded-tl-full rounded-bl-full dark:bg-immich-dark-primary hover:dark:bg-immich-dark-primary/90 bg-immich-primary/25 hover:bg-immich-primary/50"
|
|
|
|
on:click={onSelectAll}><Icon path={mdiCheck} size="20" />{$t('select_keep_all')}</button
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="px-4 flex place-items-center gap-2 rounded-tr-full rounded-br-full dark:bg-immich-dark-primary/50 hover:dark:bg-immich-dark-primary/70 bg-immich-primary hover:bg-immich-primary/80 text-white"
|
|
|
|
on:click={onSelectNone}><Icon path={mdiTrashCanOutline} size="20" />{$t('select_trash_all')}</button
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- CONFIRM BUTTONS -->
|
|
|
|
<div class="flex gap-4">
|
|
|
|
{#if trashCount === 0}
|
2024-06-15 22:45:20 +02:00
|
|
|
<Button size="sm" color="primary" class="flex place-items-center gap-2" on:click={handleResolve}>
|
|
|
|
<Icon path={mdiCheck} size="20" />{$t('keep_all')}
|
2024-06-12 06:01:55 -07:00
|
|
|
</Button>
|
|
|
|
{:else}
|
2024-06-15 22:45:20 +02:00
|
|
|
<Button size="sm" color="red" class="flex place-items-center gap-2" on:click={handleResolve}>
|
|
|
|
<Icon path={mdiTrashCanOutline} size="20" />{trashCount === assets.length
|
2024-06-12 06:01:55 -07:00
|
|
|
? $t('trash_all')
|
2024-06-15 22:45:20 +02:00
|
|
|
: $t('trash_count', { values: { count: trashCount } })}
|
2024-06-12 06:01:55 -07:00
|
|
|
</Button>
|
|
|
|
{/if}
|
|
|
|
</div>
|
2024-05-23 12:57:25 -05:00
|
|
|
</div>
|
|
|
|
</div>
|
2024-06-15 22:45:20 +02:00
|
|
|
|
|
|
|
{#if $showAssetViewer}
|
|
|
|
{#await import('$lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
|
|
|
<Portal target="body">
|
|
|
|
<AssetViewer
|
|
|
|
asset={$viewingAsset}
|
|
|
|
showNavigation={assets.length > 1}
|
|
|
|
on:next={() => {
|
|
|
|
const index = getAssetIndex($viewingAsset.id) + 1;
|
|
|
|
setAsset(assets[index % assets.length]);
|
|
|
|
}}
|
|
|
|
on:previous={() => {
|
|
|
|
const index = getAssetIndex($viewingAsset.id) - 1 + assets.length;
|
|
|
|
setAsset(assets[index % assets.length]);
|
|
|
|
}}
|
|
|
|
on:close={() => assetViewingStore.showAssetViewer(false)}
|
|
|
|
/>
|
|
|
|
</Portal>
|
|
|
|
{/await}
|
|
|
|
{/if}
|