feat(web): lighter timeline buckets (#17719)

* feat(web): lighter timeline buckets

* GalleryViewer

* weird ssr

* Remove generics from AssetInteraction

* ensure keys on getAssetInfo, alt-text

* empty - trigger ci

* re-add alt-text

* test fix

* update tests

* tests

* missing import

* fix: flappy e2e test

* lint

* revert settings

* unneeded cast

* fix after merge

* missing import

* lint

* review

* lint

* avoid abbreviations

* review comment - type safety in test

* merge conflicts

* lint

* lint/abbreviations

* fix: left-over migration

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Min Idzelis
2025-05-17 22:57:08 -04:00
committed by GitHub
parent a65c905621
commit 0bbe70e6a3
53 changed files with 725 additions and 471 deletions
@@ -1,20 +1,21 @@
import type { AssetAction } from '$lib/constants';
import type { AlbumResponseDto, AssetResponseDto } from '@immich/sdk';
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
import type { AlbumResponseDto } from '@immich/sdk';
type ActionMap = {
[AssetAction.ARCHIVE]: { asset: AssetResponseDto };
[AssetAction.UNARCHIVE]: { asset: AssetResponseDto };
[AssetAction.FAVORITE]: { asset: AssetResponseDto };
[AssetAction.UNFAVORITE]: { asset: AssetResponseDto };
[AssetAction.TRASH]: { asset: AssetResponseDto };
[AssetAction.DELETE]: { asset: AssetResponseDto };
[AssetAction.RESTORE]: { asset: AssetResponseDto };
[AssetAction.ADD]: { asset: AssetResponseDto };
[AssetAction.ADD_TO_ALBUM]: { asset: AssetResponseDto; album: AlbumResponseDto };
[AssetAction.UNSTACK]: { assets: AssetResponseDto[] };
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: AssetResponseDto };
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: AssetResponseDto };
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: AssetResponseDto };
[AssetAction.ARCHIVE]: { asset: TimelineAsset };
[AssetAction.UNARCHIVE]: { asset: TimelineAsset };
[AssetAction.FAVORITE]: { asset: TimelineAsset };
[AssetAction.UNFAVORITE]: { asset: TimelineAsset };
[AssetAction.TRASH]: { asset: TimelineAsset };
[AssetAction.DELETE]: { asset: TimelineAsset };
[AssetAction.RESTORE]: { asset: TimelineAsset };
[AssetAction.ADD]: { asset: TimelineAsset };
[AssetAction.ADD_TO_ALBUM]: { asset: TimelineAsset; album: AlbumResponseDto };
[AssetAction.UNSTACK]: { assets: TimelineAsset[] };
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: TimelineAsset };
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
};
export type Action = {
@@ -6,6 +6,7 @@
import Portal from '$lib/components/shared-components/portal/portal.svelte';
import { AssetAction } from '$lib/constants';
import { addAssetsToAlbum, addAssetsToNewAlbum } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import type { AlbumResponseDto, AssetResponseDto } from '@immich/sdk';
import { mdiImageAlbum, mdiShareVariantOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -24,14 +25,14 @@
showSelectionModal = false;
const album = await addAssetsToNewAlbum(albumName, [asset.id]);
if (album) {
onAction({ type: AssetAction.ADD_TO_ALBUM, asset, album });
onAction({ type: AssetAction.ADD_TO_ALBUM, asset: toTimelineAsset(asset), album });
}
};
const handleAddToAlbum = async (album: AlbumResponseDto) => {
showSelectionModal = false;
await addAssetsToAlbum(album.id, [asset.id]);
onAction({ type: AssetAction.ADD_TO_ALBUM, asset, album });
onAction({ type: AssetAction.ADD_TO_ALBUM, asset: toTimelineAsset(asset), album });
};
</script>
@@ -4,6 +4,7 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants';
import { toggleArchive } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk';
import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -18,11 +19,11 @@
const onArchive = async () => {
if (!asset.isArchived) {
preAction({ type: AssetAction.ARCHIVE, asset });
preAction({ type: AssetAction.ARCHIVE, asset: toTimelineAsset(asset) });
}
const updatedAsset = await toggleArchive(asset);
if (updatedAsset) {
onAction({ type: asset.isArchived ? AssetAction.ARCHIVE : AssetAction.UNARCHIVE, asset });
onAction({ type: asset.isArchived ? AssetAction.ARCHIVE : AssetAction.UNARCHIVE, asset: toTimelineAsset(asset) });
}
};
</script>
@@ -11,6 +11,7 @@
import { showDeleteModal } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store';
import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { deleteAssets, type AssetResponseDto } from '@immich/sdk';
import { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -42,9 +43,9 @@
const trashAsset = async () => {
try {
preAction({ type: AssetAction.TRASH, asset });
preAction({ type: AssetAction.TRASH, asset: toTimelineAsset(asset) });
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } });
onAction({ type: AssetAction.TRASH, asset });
onAction({ type: AssetAction.TRASH, asset: toTimelineAsset(asset) });
notificationController.show({
message: $t('moved_to_trash'),
@@ -58,7 +59,7 @@
const deleteAsset = async () => {
try {
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } });
onAction({ type: AssetAction.DELETE, asset });
onAction({ type: AssetAction.DELETE, asset: toTimelineAsset(asset) });
notificationController.show({
message: $t('permanently_deleted_asset'),
@@ -2,19 +2,21 @@
import { shortcut } from '$lib/actions/shortcut';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
import { downloadFile } from '$lib/utils/asset-utils';
import type { AssetResponseDto } from '@immich/sdk';
import { getAssetInfo } from '@immich/sdk';
import { mdiFolderDownloadOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
interface Props {
asset: AssetResponseDto;
asset: TimelineAsset;
menuItem?: boolean;
}
let { asset, menuItem = false }: Props = $props();
const onDownloadFile = () => downloadFile(asset);
const onDownloadFile = async () => downloadFile(await getAssetInfo({ id: asset.id, key: authManager.key }));
</script>
<svelte:window use:shortcut={{ shortcut: { key: 'd', shift: true }, onShortcut: onDownloadFile }} />
@@ -7,6 +7,7 @@
} from '$lib/components/shared-components/notification/notification';
import { AssetAction } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
import { mdiHeart, mdiHeartOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -30,7 +31,10 @@
asset = { ...asset, isFavorite: data.isFavorite };
onAction({ type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE, asset });
onAction({
type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE,
asset: toTimelineAsset(asset),
});
notificationController.show({
type: NotificationType.Info,
@@ -3,6 +3,7 @@
import { AssetAction } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { keepThisDeleteOthers } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto, StackResponseDto } from '@immich/sdk';
import { mdiPinOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -29,7 +30,7 @@
const keptAsset = await keepThisDeleteOthers(asset, stack);
if (keptAsset) {
onAction({ type: AssetAction.UNSTACK, assets: [keptAsset] });
onAction({ type: AssetAction.UNSTACK, assets: [toTimelineAsset(keptAsset)] });
}
};
</script>
@@ -6,6 +6,7 @@
} from '$lib/components/shared-components/notification/notification';
import { AssetAction } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { restoreAssets, type AssetResponseDto } from '@immich/sdk';
import { mdiHistory } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -23,7 +24,7 @@
await restoreAssets({ bulkIdsDto: { ids: [asset.id] } });
asset.isTrashed = false;
onAction({ type: AssetAction.RESTORE, asset });
onAction({ type: AssetAction.RESTORE, asset: toTimelineAsset(asset) });
notificationController.show({
type: NotificationType.Info,
@@ -3,14 +3,15 @@
import { AssetAction } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
import { handleError } from '$lib/utils/handle-error';
import { AssetVisibility, updateAssets, Visibility, type AssetResponseDto } from '@immich/sdk';
import { AssetVisibility, updateAssets, Visibility } from '@immich/sdk';
import { mdiEyeOffOutline, mdiFolderMoveOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { OnAction, PreAction } from './action';
interface Props {
asset: AssetResponseDto;
asset: TimelineAsset;
onAction: OnAction;
preAction: PreAction;
}
@@ -2,6 +2,7 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants';
import { deleteStack } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import type { StackResponseDto } from '@immich/sdk';
import { mdiImageMinusOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -17,7 +18,7 @@
const handleUnstack = async () => {
const unstackedAssets = await deleteStack([stack.id]);
if (unstackedAssets) {
onAction({ type: AssetAction.UNSTACK, assets: unstackedAssets });
onAction({ type: AssetAction.UNSTACK, assets: unstackedAssets.map((asset) => toTimelineAsset(asset)) });
}
};
</script>