Compare commits

...

6 Commits

Author SHA1 Message Date
Yaros 524b191ccc refactor: use event-manager 2026-03-27 13:55:42 +01:00
Yaros 075f7d507b refactor: use primary toast 2026-03-26 19:20:11 +01:00
Yaros c4df4d7852 Merge branch 'main' into feat/undo-archive 2026-03-26 19:11:36 +01:00
Yaros 0eaa2c3419 refactor: remove ternary 2026-03-20 17:43:22 +01:00
Yaros 554e7b28a2 refactor: remove unnecessary checks 2026-03-20 17:13:18 +01:00
Yaros 167aad7ac2 feat(web): undo archive from toast 2026-03-19 22:13:34 +01:00
5 changed files with 53 additions and 9 deletions
@@ -318,12 +318,12 @@
untrack(() => map?.jumpTo({ center, zoom })); untrack(() => map?.jumpTo({ center, zoom }));
}); });
const onAssetsDelete = async () => { const onAssetsChanged = async () => {
mapMarkers = await loadMapMarkers(); mapMarkers = await loadMapMarkers();
}; };
</script> </script>
<OnEvents {onAssetsDelete} /> <OnEvents onAssetsDelete={onAssetsChanged} onAssetsArchive={onAssetsChanged} onAssetsUnarchive={onAssetsChanged} />
<!-- We handle style loading ourselves so we set style blank here --> <!-- We handle style loading ourselves so we set style blank here -->
<MapLibre <MapLibre
@@ -34,6 +34,7 @@ export type Events = {
AssetUpdate: [AssetResponseDto]; AssetUpdate: [AssetResponseDto];
AssetsArchive: [string[]]; AssetsArchive: [string[]];
AssetsUnarchive: [string[]];
AssetsDelete: [string[]]; AssetsDelete: [string[]];
AssetEditsApplied: [string]; AssetEditsApplied: [string];
AssetsTag: [string[]]; AssetsTag: [string[]];
@@ -23,7 +23,7 @@ import {
type TimelineDateTime, type TimelineDateTime,
type TimelineYearMonth, type TimelineYearMonth,
} from '$lib/utils/timeline-util'; } from '$lib/utils/timeline-util';
import { AssetOrder, getAssetInfo, getTimeBuckets, type AssetResponseDto } from '@immich/sdk'; import { AssetOrder, AssetVisibility, getAssetInfo, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
import { clamp, isEqual } from 'lodash-es'; import { clamp, isEqual } from 'lodash-es';
import { SvelteDate, SvelteSet } from 'svelte/reactivity'; import { SvelteDate, SvelteSet } from 'svelte/reactivity';
import { DayGroup } from './day-group.svelte'; import { DayGroup } from './day-group.svelte';
@@ -114,6 +114,7 @@ export class TimelineManager extends VirtualScrollManager {
this.#unsubscribes.push( this.#unsubscribes.push(
eventManager.on({ eventManager.on({
AssetUpdate: (asset: AssetResponseDto) => this.upsertAssets([toTimelineAsset(asset)]), AssetUpdate: (asset: AssetResponseDto) => this.upsertAssets([toTimelineAsset(asset)]),
AssetsUnarchive: (ids) => this.update(ids, (asset) => (asset.visibility = AssetVisibility.Timeline)),
}), }),
); );
} }
+47 -6
View File
@@ -1,5 +1,6 @@
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { downloadManager } from '$lib/managers/download-manager.svelte'; import { downloadManager } from '$lib/managers/download-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
@@ -434,7 +435,20 @@ export const toggleArchive = async (asset: AssetResponseDto) => {
}); });
asset.isArchived = data.isArchived; asset.isArchived = data.isArchived;
toastManager.primary(asset.isArchived ? $t(`added_to_archive`) : $t(`removed_from_archive`)); if (asset.isArchived) {
toastManager.primary(
{
description: $t('added_to_archive'),
button: {
label: $t('undo'),
onclick: () => undoArchiveAssets([asset.id]),
},
},
{ timeout: 5000 },
);
} else {
toastManager.primary($t('removed_from_archive'));
}
} catch (error) { } catch (error) {
handleError(error, $t('errors.unable_to_add_remove_archive', { values: { archived: asset.isArchived } })); handleError(error, $t('errors.unable_to_add_remove_archive', { values: { archived: asset.isArchived } }));
} }
@@ -442,6 +456,24 @@ export const toggleArchive = async (asset: AssetResponseDto) => {
return asset; return asset;
}; };
const undoArchiveAssets = async (ids: string[]) => {
const $t = get(t);
try {
if (ids.length > 0) {
await updateAssets({
assetBulkUpdateDto: {
ids,
visibility: AssetVisibility.Timeline,
},
});
}
eventManager.emit('AssetsUnarchive', ids);
} catch (error) {
handleError(error, $t('errors.unable_to_archive_unarchive', { values: { archived: false } }));
}
};
export const archiveAssets = async (assets: { id: string }[], visibility: AssetVisibility) => { export const archiveAssets = async (assets: { id: string }[], visibility: AssetVisibility) => {
const ids = assets.map(({ id }) => id); const ids = assets.map(({ id }) => id);
const $t = get(t); const $t = get(t);
@@ -453,11 +485,20 @@ export const archiveAssets = async (assets: { id: string }[], visibility: AssetV
}); });
} }
toastManager.primary( if (visibility === AssetVisibility.Archive) {
visibility === AssetVisibility.Archive toastManager.primary(
? $t('archived_count', { values: { count: ids.length } }) {
: $t('unarchived_count', { values: { count: ids.length } }), description: $t('archived_count', { values: { count: ids.length } }),
); button: {
label: $t('undo'),
onclick: () => undoArchiveAssets(ids),
},
},
{ timeout: 5000 },
);
} else {
toastManager.primary($t('unarchived_count', { values: { count: ids.length } }));
}
} catch (error) { } catch (error) {
handleError( handleError(
error, error,
@@ -330,6 +330,7 @@
onPersonAssetDelete={handlePersonAssetDelete} onPersonAssetDelete={handlePersonAssetDelete}
onAssetsDelete={updateAssetCount} onAssetsDelete={updateAssetCount}
onAssetsArchive={updateAssetCount} onAssetsArchive={updateAssetCount}
onAssetsUnarchive={updateAssetCount}
/> />
<main <main