Compare commits

...

14 Commits

Author SHA1 Message Date
Yaros 7cdc252384 refactor: use eventmanager for onundoarchive 2026-06-11 21:46:27 +02:00
Yaros 3c43b7240b fix(web): ignore unknown assets in album timelines 2026-06-08 19:12:57 +02:00
Yaros b966940264 chore: formatting 2026-06-08 19:02:47 +02:00
Yaros 72cca30272 feat: go back to asset on undo archive in viewer 2026-06-08 19:00:09 +02:00
Yaros bcd01a2464 fix: restore from asset viewer 2026-06-08 18:53:42 +02:00
Yaros a5d8415c5f fix(web): correct timeline position on undo 2026-06-08 18:48:10 +02:00
Yaros 74e9ec4872 fix: import conflicts 2026-06-08 18:29:44 +02:00
Yaros 411487dfd7 Merge branch 'main' into feat/undo-archive 2026-06-08 18:13:43 +02:00
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
6 changed files with 83 additions and 11 deletions
@@ -310,12 +310,12 @@
untrack(() => map?.jumpTo({ center, zoom }));
});
const onAssetsDelete = async () => {
const onAssetsChanged = async () => {
mapMarkers = await loadMapMarkers();
};
</script>
<OnEvents {onAssetsDelete} />
<OnEvents onAssetsDelete={onAssetsChanged} onAssetsArchive={onAssetsChanged} onAssetsUnarchive={onAssetsChanged} />
<!-- We handle style loading ourselves so we set style blank here -->
<MapLibre
@@ -7,6 +7,7 @@
import { authManager } from '$lib/managers/auth-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { websocketEvents } from '$lib/stores/websocket';
import { handlePromiseError } from '$lib/utils';
import { updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
@@ -219,6 +220,17 @@
const unsubscribes = [
websocketEvents.on('on_upload_success', (asset: AssetResponseDto) => handleUpdateOrUpload(asset)),
websocketEvents.on('on_asset_update', (asset: AssetResponseDto) => handleUpdateOrUpload(asset)),
eventManager.on({
AssetsUndoArchive: async (assets) => {
if (assets.length === 0) {
return;
}
const restoredAsset = assets[0];
const asset = await getAssetInfo({ ...authManager.params, id: restoredAsset.id });
assetViewerManager.setAsset(asset);
await navigate({ targetRoute: 'current', assetId: restoredAsset.id });
},
}),
];
return () => {
for (const unsubscribe of unsubscribes) {
@@ -14,6 +14,7 @@ import type {
UserAdminResponseDto,
WorkflowResponseDto,
} from '@immich/sdk';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { BaseEventManager } from '$lib/utils/base-event-manager.svelte';
import type { TreeNode } from '$lib/utils/tree-utils';
@@ -33,6 +34,8 @@ export type Events = {
AssetUpdate: [AssetResponseDto];
AssetsArchive: [string[]];
AssetsUnarchive: [TimelineAsset[]];
AssetsUndoArchive: [TimelineAsset[]];
AssetsDelete: [string[]];
AssetEditsApplied: [string];
AssetsTag: [string[]];
@@ -1,4 +1,4 @@
import { AssetOrder, getAssetInfo, getTimeBuckets, AssetOrderBy, type AssetResponseDto } from '@immich/sdk';
import { AssetOrder, AssetOrderBy, getAssetInfo, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
import { clamp, isEqual } from 'lodash-es';
import { SvelteDate, SvelteSet } from 'svelte/reactivity';
import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
@@ -114,7 +114,15 @@ export class TimelineManager extends VirtualScrollManager {
this.#unsubscribes.push(
eventManager.on({
AssetUpdate: (asset: AssetResponseDto) => this.#updateAssets([toTimelineAsset(asset)]),
AssetUpdate: (asset: AssetResponseDto) => {
const timelineAsset = toTimelineAsset(asset);
if (this.#options.albumId || this.#options.personId) {
this.#updateAssets([timelineAsset]);
} else {
this.upsertAssets([timelineAsset]);
}
},
AssetsUnarchive: (assets) => this.upsertAssets(assets),
}),
);
}
+55 -7
View File
@@ -24,6 +24,7 @@ import { get } from 'svelte/store';
import type { AssetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
import { authManager } from '$lib/managers/auth-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 type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { downloadBlob, downloadRequest, withError } from '$lib/utils';
@@ -31,6 +32,7 @@ import { getByteUnitString } from '$lib/utils/byte-units';
import { getFormatter } from '$lib/utils/i18n';
import { navigate } from '$lib/utils/navigation';
import { asQueryString } from '$lib/utils/shared-links';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { handleError } from './handle-error';
export const tagAssets = async ({
@@ -400,7 +402,21 @@ export const toggleArchive = async (asset: AssetResponseDto) => {
});
asset.isArchived = data.isArchived;
toastManager.primary(asset.isArchived ? $t(`added_to_archive`) : $t(`removed_from_archive`));
if (asset.isArchived) {
const timelineAsset = toTimelineAsset(asset);
toastManager.primary(
{
description: $t('added_to_archive'),
button: {
label: $t('undo'),
onclick: () => undoArchiveAssets([timelineAsset]),
},
},
{ timeout: 5000 },
);
} else {
toastManager.primary($t('removed_from_archive'));
}
} catch (error) {
handleError(error, $t('errors.unable_to_add_remove_archive', { values: { archived: asset.isArchived } }));
}
@@ -408,7 +424,30 @@ export const toggleArchive = async (asset: AssetResponseDto) => {
return asset;
};
export const archiveAssets = async (assets: { id: string }[], visibility: AssetVisibility) => {
const undoArchiveAssets = async (assets: TimelineAsset[]) => {
const $t = get(t);
try {
const ids = assets.map((a) => a.id);
if (ids.length > 0) {
await updateAssets({
assetBulkUpdateDto: {
ids,
visibility: AssetVisibility.Timeline,
},
});
}
for (const asset of assets) {
asset.visibility = AssetVisibility.Timeline;
}
eventManager.emit('AssetsUnarchive', assets);
eventManager.emit('AssetsUndoArchive', assets);
} catch (error) {
handleError(error, $t('errors.unable_to_archive_unarchive', { values: { archived: false } }));
}
};
export const archiveAssets = async (assets: TimelineAsset[], visibility: AssetVisibility) => {
const ids = assets.map(({ id }) => id);
const $t = get(t);
@@ -419,11 +458,20 @@ export const archiveAssets = async (assets: { id: string }[], visibility: AssetV
});
}
toastManager.primary(
visibility === AssetVisibility.Archive
? $t('archived_count', { values: { count: ids.length } })
: $t('unarchived_count', { values: { count: ids.length } }),
);
if (visibility === AssetVisibility.Archive) {
toastManager.primary(
{
description: $t('archived_count', { values: { count: ids.length } }),
button: {
label: $t('undo'),
onclick: () => undoArchiveAssets(assets),
},
},
{ timeout: 5000 },
);
} else {
toastManager.primary($t('unarchived_count', { values: { count: ids.length } }));
}
} catch (error) {
handleError(
error,
@@ -326,6 +326,7 @@
onPersonAssetDelete={handlePersonAssetDelete}
onAssetsDelete={updateAssetCount}
onAssetsArchive={updateAssetCount}
onAssetsUnarchive={updateAssetCount}
/>
<main