feat(web): bulk deduplicate (#10448)

* bulk deduplicate

* notification for keeping all duplicates

* fix notification

* remove unused text

* pr feedback

* wording

* formatting
This commit is contained in:
Mert
2024-06-19 12:11:59 -04:00
committed by GitHub
parent 86cbc6e125
commit b3f9641edf
4 changed files with 123 additions and 24 deletions

View File

@@ -11,42 +11,129 @@
import { deleteAssets, updateAssets } from '@immich/sdk';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
import { suggestDuplicateByFileSize } from '$lib/utils';
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
import { mdiCheckOutline, mdiTrashCanOutline } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte';
export let data: PageData;
const handleResolve = async (duplicateId: string, duplicateAssetIds: string[], trashIds: string[]) => {
try {
if (!$featureFlags.trash && trashIds.length > 0) {
const isConfirmed = await dialogController.show({
prompt: $t('delete_duplicates_confirmation'),
confirmText: $t('permanently_delete'),
});
$: hasDuplicates = data.duplicates.length > 0;
if (!isConfirmed) {
return;
}
}
await updateAssets({ assetBulkUpdateDto: { ids: duplicateAssetIds, duplicateId: null } });
await deleteAssets({ assetBulkDeleteDto: { ids: trashIds, force: !$featureFlags.trash } });
data.duplicates = data.duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
if (trashIds.length === 0) {
const withConfirmation = async (callback: () => Promise<void>, prompt?: string, confirmText?: string) => {
if (prompt && confirmText) {
const isConfirmed = await dialogController.show({ prompt, confirmText });
if (!isConfirmed) {
return;
}
}
notificationController.show({
message: $t('assets_moved_to_trash', { values: { count: trashIds.length } }),
type: NotificationType.Info,
});
try {
return await callback();
} catch (error) {
handleError(error, $t('errors.unable_to_resolve_duplicate'));
}
};
const deletedNotification = (trashedCount: number) => {
if (!trashedCount) {
return;
}
notificationController.show({
message: $featureFlags.trash
? $t('assets_moved_to_trash', { values: { count: trashedCount } })
: $t('permanently_deleted_assets', { values: { count: trashedCount } }),
type: NotificationType.Info,
});
};
const handleResolve = async (duplicateId: string, duplicateAssetIds: string[], trashIds: string[]) => {
return withConfirmation(
async () => {
await deleteAssets({ assetBulkDeleteDto: { ids: trashIds, force: !$featureFlags.trash } });
await updateAssets({ assetBulkUpdateDto: { ids: duplicateAssetIds, duplicateId: null } });
data.duplicates = data.duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
deletedNotification(trashIds.length);
},
trashIds.length > 0 && !$featureFlags.trash ? $t('delete_duplicates_confirmation') : undefined,
trashIds.length > 0 && !$featureFlags.trash ? $t('permanently_delete') : undefined,
);
};
const handleDeduplicateAll = async () => {
const idsToKeep = data.duplicates
.map((group) => suggestDuplicateByFileSize(group.assets))
.map((asset) => asset?.id);
const idsToDelete = data.duplicates.flatMap((group, i) =>
group.assets.map((asset) => asset.id).filter((asset) => asset !== idsToKeep[i]),
);
let prompt, confirmText;
if ($featureFlags.trash) {
prompt = $t('bulk_trash_duplicates_confirmation', { values: { count: idsToDelete.length } });
confirmText = $t('confirm');
} else {
prompt = $t('bulk_delete_duplicates_confirmation', { values: { count: idsToDelete.length } });
confirmText = $t('permanently_delete');
}
return withConfirmation(
async () => {
await deleteAssets({ assetBulkDeleteDto: { ids: idsToDelete, force: !$featureFlags.trash } });
await updateAssets({
assetBulkUpdateDto: {
ids: [...idsToDelete, ...idsToKeep.filter((id): id is string => !!id)],
duplicateId: null,
},
});
data.duplicates = [];
deletedNotification(idsToDelete.length);
},
prompt,
confirmText,
);
};
const handleKeepAll = async () => {
const ids = data.duplicates.flatMap((group) => group.assets.map((asset) => asset.id));
return withConfirmation(
async () => {
await updateAssets({ assetBulkUpdateDto: { ids, duplicateId: null } });
data.duplicates = [];
notificationController.show({
message: $t('resolved_all_duplicates'),
type: NotificationType.Info,
});
},
$t('bulk_keep_duplicates_confirmation', { values: { count: ids.length } }),
$t('confirm'),
);
};
</script>
<UserPageLayout title={data.meta.title + ` (${data.duplicates.length})`} scrollbar={true}>
<div class="flex place-items-center gap-2" slot="buttons">
<LinkButton on:click={() => handleDeduplicateAll()} disabled={!hasDuplicates}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiTrashCanOutline} size="18" />
{$t('deduplicate_all')}
</div>
</LinkButton>
<LinkButton on:click={() => handleKeepAll()} disabled={!hasDuplicates}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiCheckOutline} size="18" />
{$t('keep_all')}
</div>
</LinkButton>
</div>
<div class="mt-4">
{#if data.duplicates && data.duplicates.length > 0}
<div class="mb-4 text-sm dark:text-white">