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,11 +1,12 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import type { OnArchive } from '$lib/utils/actions';
import { archiveAssets } from '$lib/utils/asset-utils';
import { AssetVisibility, Visibility } from '@immich/sdk';
import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline, mdiTimerSand } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { archiveAssets } from '$lib/utils/asset-utils';
import { t } from 'svelte-i18n';
interface Props {
onArchive?: OnArchive;
@@ -23,10 +24,10 @@
const { clearSelect, getOwnedAssets } = getAssetControlContext();
const handleArchive = async () => {
const isArchived = !unarchive;
const assets = [...getOwnedAssets()].filter((asset) => asset.isArchived !== isArchived);
const isArchived = unarchive ? Visibility.Timeline : Visibility.Archive;
const assets = [...getOwnedAssets()].filter((asset) => asset.visibility !== isArchived);
loading = true;
const ids = await archiveAssets(assets, isArchived);
const ids = await archiveAssets(assets, isArchived as unknown as AssetVisibility);
if (ids) {
onArchive?.(ids, isArchived);
clearSelect();
@@ -6,9 +6,9 @@
} from '$lib/components/shared-components/notification/notification';
import { getAssetJobIcon, getAssetJobMessage, getAssetJobName } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { AssetJobName, AssetTypeEnum, runAssetJobs } from '@immich/sdk';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { AssetJobName, runAssetJobs } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
interface Props {
jobs?: AssetJobName[];
@@ -19,7 +19,7 @@
const { clearSelect, getOwnedAssets } = getAssetControlContext();
let isAllVideos = $derived([...getOwnedAssets()].every((asset) => asset.type === AssetTypeEnum.Video));
const isAllVideos = $derived([...getOwnedAssets()].every((asset) => asset.isVideo));
const handleRunJob = async (name: AssetJobName) => {
try {
@@ -4,11 +4,11 @@
import { getSelectedAssets } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
import { updateAssets } from '@immich/sdk';
import { mdiCalendarEditOutline } from '@mdi/js';
import { DateTime } from 'luxon';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiCalendarEditOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
interface Props {
menuItem?: boolean;
}
@@ -1,11 +1,14 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { downloadArchive, downloadFile } from '$lib/utils/asset-utils';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { shortcut } from '$lib/actions/shortcut';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { downloadArchive, downloadFile } from '$lib/utils/asset-utils';
import { getAssetInfo } from '@immich/sdk';
import { mdiCloudDownloadOutline, mdiFileDownloadOutline, mdiFolderDownloadOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
interface Props {
filename?: string;
@@ -20,7 +23,8 @@
const assets = [...getAssets()];
if (assets.length === 1) {
clearSelect();
await downloadFile(assets[0]);
let asset = await getAssetInfo({ id: assets[0].id, key: authManager.key });
await downloadFile(asset);
return;
}
@@ -1,12 +1,16 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { getAssetControlContext } from '$lib/components/photos-page/asset-select-control-bar.svelte';
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import type { OnLink, OnUnlink } from '$lib/utils/actions';
import { handleError } from '$lib/utils/handle-error';
import { AssetTypeEnum, getAssetInfo, updateAsset } from '@immich/sdk';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { getAssetInfo, updateAsset } from '@immich/sdk';
import { mdiLinkOff, mdiMotionPlayOutline, mdiTimerSand } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
interface Props {
onLink: OnLink;
@@ -28,14 +32,14 @@
const handleLink = async () => {
let [still, motion] = [...getOwnedAssets()];
if (still.type === AssetTypeEnum.Video) {
if ((still as TimelineAsset).isVideo) {
[still, motion] = [motion, still];
}
try {
loading = true;
const stillResponse = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: motion.id } });
onLink({ still: stillResponse, motion });
onLink({ still: toTimelineAsset(stillResponse), motion: motion as TimelineAsset });
clearSelect();
} catch (error) {
handleError(error, $t('errors.unable_to_link_motion_video'));
@@ -46,17 +50,18 @@
const handleUnlink = async () => {
const [still] = [...getOwnedAssets()];
const motionId = still?.livePhotoVideoId;
if (!still) {
return;
}
const motionId = still.livePhotoVideoId;
if (!motionId) {
return;
}
try {
loading = true;
const stillResponse = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: null } });
const motionResponse = await getAssetInfo({ id: motionId });
onUnlink({ still: stillResponse, motion: motionResponse });
const motionResponse = await getAssetInfo({ id: motionId, key: authManager.key });
onUnlink({ still: toTimelineAsset(stillResponse), motion: toTimelineAsset(motionResponse) });
clearSelect();
} catch (error) {
handleError(error, $t('errors.unable_to_unlink_motion_video'));
@@ -1,9 +1,10 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '$lib/components/photos-page/asset-select-control-bar.svelte';
import { mdiImageMinusOutline, mdiImageMultipleOutline } from '@mdi/js';
import { stackAssets, deleteStack } from '$lib/utils/asset-utils';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import type { OnStack, OnUnstack } from '$lib/utils/actions';
import { deleteStack, stackAssets } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { mdiImageMinusOutline, mdiImageMultipleOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
interface Props {
@@ -34,7 +35,7 @@
}
const unstackedAssets = await deleteStack([stack.id]);
if (unstackedAssets) {
onUnstack?.(unstackedAssets);
onUnstack?.(unstackedAssets.map((a) => toTimelineAsset(a)));
}
clearSelect();
};