diff --git a/web/src/lib/modals/DuplicatesSettingsModal.svelte b/web/src/lib/modals/DuplicatesSettingsModal.svelte
index 8e89795698..2e48d3da80 100644
--- a/web/src/lib/modals/DuplicatesSettingsModal.svelte
+++ b/web/src/lib/modals/DuplicatesSettingsModal.svelte
@@ -1,99 +1,86 @@
-
-
-
-
- {$t('deduplicate_source_preference')}
+ (confirmed ? handleConfirm() : onClose())}
+>
+ {#snippet promptSnippet()}
+
+ {$t('deduplicate_source_preference')}
-
+
-
-
-
-
- {$t('reset_to_default')}
+ {$t('deduplicate_prefer_external')}
+
+
(tiePreferenceLocal = undefined)}
+ >
+ {$t('deduplicate_prefer_default')}
+
+
(tiePreferenceLocal = [makeSourcePreference('internal')])}
+ >
+ {$t('deduplicate_prefer_internal')}
-
-
-
- {$t('cancel')}
-
-
- {$t('confirm')}
-
-
-
-
-
+
+ {/snippet}
+
diff --git a/web/src/lib/stores/duplicate-tie-preferences.svelte.ts b/web/src/lib/stores/duplicate-tie-preferences-manager.svelte.ts
similarity index 66%
rename from web/src/lib/stores/duplicate-tie-preferences.svelte.ts
rename to web/src/lib/stores/duplicate-tie-preferences-manager.svelte.ts
index b4ce342fc1..075af36328 100644
--- a/web/src/lib/stores/duplicate-tie-preferences.svelte.ts
+++ b/web/src/lib/stores/duplicate-tie-preferences-manager.svelte.ts
@@ -10,20 +10,15 @@ export type DuplicateTiePreferencesSvelte = PreferenceDuplicateTieItem[];
export type PreferenceDuplicateTieItem = SourcePreference;
-export let duplicateTiePreference = $state<{
+export const duplicateTiePreference = $state<{
value: DuplicateTiePreferencesSvelte | undefined;
}>({ value: undefined });
export const findDuplicateTiePreference =
(
preference: DuplicateTiePreferencesSvelte | undefined,
variant: T,
-): Extract | undefined =>
- preference?.find(
- (preference): preference is Extract => preference.variant === variant,
- );
+) => preference?.find((preference) => preference.variant === variant);
-export function setDuplicateTiePreference(
- nextDuplicateTiePreferences: DuplicateTiePreferencesSvelte | undefined,
-): void {
+export function setDuplicateTiePreference(nextDuplicateTiePreferences: DuplicateTiePreferencesSvelte | undefined) {
duplicateTiePreference.value = nextDuplicateTiePreferences;
}
diff --git a/web/src/lib/utils/duplicate-utils.spec.ts b/web/src/lib/utils/duplicate-utils.spec.ts
index 5c6aad1cac..8774aa63ca 100644
--- a/web/src/lib/utils/duplicate-utils.spec.ts
+++ b/web/src/lib/utils/duplicate-utils.spec.ts
@@ -1,4 +1,4 @@
-import type { SourcePreference } from '$lib/stores/duplicate-tie-preferences.svelte';
+import type { SourcePreference } from '$lib/stores/duplicate-tie-preferences-manager.svelte';
import { suggestBestDuplicate } from '$lib/utils/duplicate-utils';
import type { AssetResponseDto } from '@immich/sdk';
diff --git a/web/src/lib/utils/duplicate-utils.ts b/web/src/lib/utils/duplicate-utils.ts
index d139dfaf56..a619174c67 100644
--- a/web/src/lib/utils/duplicate-utils.ts
+++ b/web/src/lib/utils/duplicate-utils.ts
@@ -1,12 +1,11 @@
import {
type DuplicateTiePreferencesSvelte,
findDuplicateTiePreference,
-} from '$lib/stores/duplicate-tie-preferences.svelte';
+} from '$lib/stores/duplicate-tie-preferences-manager.svelte';
import { getExifCount } from '$lib/utils/exif-utils';
import type { AssetResponseDto } from '@immich/sdk';
const sizeOf = (asset: AssetResponseDto) => asset.exifInfo?.fileSizeInByte ?? 0;
-const isExternal = (asset: AssetResponseDto) => Boolean(asset.libraryId);
/**
* Suggests the best duplicate asset to keep from a list of duplicates.
@@ -16,16 +15,12 @@ const isExternal = (asset: AssetResponseDto) => Boolean(asset.libraryId);
* - Largest count of exif data
* - Optional source preference (internal vs external)
*
- * @param assets List of duplicate assets
- * @param preference Preference for selecting duplicates
- * @returns The best asset to keep
- *
*/
export function suggestBestDuplicate(
assets: AssetResponseDto[],
preference: DuplicateTiePreferencesSvelte | undefined,
): AssetResponseDto | undefined {
- if (!assets.length) {
+ if (assets.length === 0) {
return;
}
let candidates = filterBySizeAndExif(assets);
@@ -38,11 +33,11 @@ export function suggestBestDuplicate(
}
const filterBySizeAndExif = (assets: AssetResponseDto[]): AssetResponseDto[] => {
- const maxSize = Math.max(...assets.map(sizeOf));
- const sizeFiltered = assets.filter((assets) => sizeOf(assets) === maxSize);
+ const maxSize = Math.max(...assets.map((asset) => sizeOf(asset)));
+ const sizeFilteredAssets = assets.filter((assets) => sizeOf(assets) === maxSize);
- const maxExif = Math.max(...sizeFiltered.map(getExifCount));
- return sizeFiltered.filter((assets) => getExifCount(assets) === maxExif);
+ const maxExif = Math.max(...sizeFilteredAssets.map((asset) => getExifCount(asset)));
+ return sizeFilteredAssets.filter((assets) => getExifCount(assets) === maxExif);
};
const filterBySource = (assets: AssetResponseDto[], priority: 'internal' | 'external'): AssetResponseDto[] => {
diff --git a/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 3625b324c9..0bef930983 100644
--- a/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -7,30 +7,30 @@
import { AppRoute } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import DuplicatesInformationModal from '$lib/modals/DuplicatesInformationModal.svelte';
+ import DuplicatesSettingsModal from '$lib/modals/DuplicatesSettingsModal.svelte';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
+ import { duplicateTiePreference } from '$lib/stores/duplicate-tie-preferences-manager.svelte';
import { locale } from '$lib/stores/preferences.store';
import { stackAssets } from '$lib/utils/asset-utils';
+ import { suggestBestDuplicate } from '$lib/utils/duplicate-utils';
import { handleError } from '$lib/utils/handle-error';
import type { AssetResponseDto } from '@immich/sdk';
import { deleteAssets, deleteDuplicates, updateAssets } from '@immich/sdk';
- import DuplicatesSettingsModal from '$lib/modals/DuplicatesSettingsModal.svelte';
import { Button, HStack, IconButton, modalManager, Text, toastManager } from '@immich/ui';
import {
mdiCheckOutline,
mdiChevronLeft,
mdiChevronRight,
+ mdiCogOutline,
mdiInformationOutline,
mdiKeyboard,
mdiPageFirst,
mdiPageLast,
mdiTrashCanOutline,
- mdiCogOutline,
} from '@mdi/js';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
- import { duplicateTiePreference } from '$lib/stores/duplicate-tie-preferences.svelte';
- import { suggestBestDuplicate } from '$lib/utils/duplicate-utils';
interface Props {
data: PageData;
@@ -127,16 +127,12 @@
};
const handleDeduplicateAll = async () => {
- const keepCandidates = duplicates.map((group) => suggestBestDuplicate(group.assets, duplicateTiePreference.value));
-
- const idsToKeep: (string | undefined)[] = keepCandidates.map((assets) => assets?.id);
+ const idsToKeep = duplicates.map((group) => suggestBestDuplicate(group.assets, duplicateTiePreference.value)?.id);
const idsToDelete = duplicates.flatMap((group, i) =>
- group.assets.map((asset) => asset.id).filter((id) => id !== idsToKeep[i]),
+ group.assets.map(({ id }) => id).filter((id) => id !== idsToKeep[i]),
);
- const keptIds = idsToKeep.filter((id): id is string => id !== undefined);
-
let prompt, confirmText;
if (featureFlagsManager.value.trash) {
prompt = $t('bulk_trash_duplicates_confirmation', { values: { count: idsToDelete.length } });
@@ -151,7 +147,7 @@
await deleteAssets({ assetBulkDeleteDto: { ids: idsToDelete, force: !featureFlagsManager.value.trash } });
await updateAssets({
assetBulkUpdateDto: {
- ids: [...idsToDelete, ...keptIds],
+ ids: [...idsToDelete, ...idsToKeep.filter((id) => id !== undefined)],
duplicateId: null,
},
});