diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 02544e3e07..8b5b2bff8b 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -4,7 +4,7 @@ import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader'; import type { AlbumResponseDto, SharedLinkResponseDto, UserResponseDto } from '@immich/sdk'; - import { AssetStore } from '$lib/stores/assets.store'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; import { cancelMultiselect, downloadAlbum } from '$lib/utils/asset-utils'; import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; import DownloadAction from '../photos-page/actions/download-action.svelte'; diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index 24701685ef..70467ccb82 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -19,7 +19,7 @@ import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { handleError } from '$lib/utils/handle-error'; import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte'; - import { photoViewerImgElement } from '$lib/stores/assets.store'; + import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; interface Props { diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index f34c141f52..49d6e3dbf4 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -22,7 +22,7 @@ import ImageThumbnail from './image-thumbnail.svelte'; import VideoThumbnail from './video-thumbnail.svelte'; import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; - import { AssetStore } from '$lib/stores/assets.store'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; import type { DateGroup } from '$lib/utils/timeline-util'; diff --git a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte index 9188ab9a4f..09b1d1f0d7 100644 --- a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte @@ -3,7 +3,7 @@ import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import { mdiAlertCircleOutline, mdiPauseCircleOutline, mdiPlayCircleOutline } from '@mdi/js'; import Icon from '$lib/components/elements/icon.svelte'; - import { AssetStore } from '$lib/stores/assets.store'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; import { generateId } from '$lib/utils/generate-id'; import { onDestroy } from 'svelte'; diff --git a/web/src/lib/components/faces-page/assign-face-side-panel.svelte b/web/src/lib/components/faces-page/assign-face-side-panel.svelte index 59bcf6a84c..19a99fdb94 100644 --- a/web/src/lib/components/faces-page/assign-face-side-panel.svelte +++ b/web/src/lib/components/faces-page/assign-face-side-panel.svelte @@ -6,7 +6,7 @@ import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js'; import { linear } from 'svelte/easing'; import { fly } from 'svelte/transition'; - import { photoViewerImgElement } from '$lib/stores/assets.store'; + import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import LoadingSpinner from '../shared-components/loading-spinner.svelte'; import SearchPeople from '$lib/components/faces-page/people-search.svelte'; diff --git a/web/src/lib/components/faces-page/person-side-panel.svelte b/web/src/lib/components/faces-page/person-side-panel.svelte index b0e0f8fcc4..73c3ea7ae5 100644 --- a/web/src/lib/components/faces-page/person-side-panel.svelte +++ b/web/src/lib/components/faces-page/person-side-panel.svelte @@ -25,7 +25,7 @@ import AssignFaceSidePanel from './assign-face-side-panel.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import { zoomImageToBase64 } from '$lib/utils/people-utils'; - import { photoViewerImgElement } from '$lib/stores/assets.store'; + import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { t } from 'svelte-i18n'; import { dialogController } from '$lib/components/shared-components/dialog/dialog'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index 28ed90ba82..6d0336dabf 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -26,7 +26,7 @@ import { AppRoute, QueryParameter } from '$lib/constants'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { type Viewport } from '$lib/stores/assets.store'; + import { type Viewport } from '$lib/stores/assets-store.svelte'; import { loadMemories, memoryStore } from '$lib/stores/memory.store'; import { locale, videoViewerMuted } from '$lib/stores/preferences.store'; import { preferences } from '$lib/stores/user.store'; diff --git a/web/src/lib/components/photos-page/actions/archive-action.svelte b/web/src/lib/components/photos-page/actions/archive-action.svelte index 868a5ddd6d..ddc5f3cae4 100644 --- a/web/src/lib/components/photos-page/actions/archive-action.svelte +++ b/web/src/lib/components/photos-page/actions/archive-action.svelte @@ -8,7 +8,7 @@ import { t } from 'svelte-i18n'; interface Props { - onArchive: OnArchive; + onArchive?: OnArchive; menuItem?: boolean; unarchive?: boolean; } @@ -28,7 +28,7 @@ loading = true; const ids = await archiveAssets(assets, isArchived); if (ids) { - onArchive(ids, isArchived); + onArchive?.(ids, isArchived); clearSelect(); } loading = false; diff --git a/web/src/lib/components/photos-page/actions/favorite-action.svelte b/web/src/lib/components/photos-page/actions/favorite-action.svelte index 1bc6764157..c06bbcdc6d 100644 --- a/web/src/lib/components/photos-page/actions/favorite-action.svelte +++ b/web/src/lib/components/photos-page/actions/favorite-action.svelte @@ -13,7 +13,7 @@ import { t } from 'svelte-i18n'; interface Props { - onFavorite: OnFavorite; + onFavorite?: OnFavorite; menuItem?: boolean; removeFavorite: boolean; } @@ -44,7 +44,7 @@ asset.isFavorite = isFavorite; } - onFavorite(ids, isFavorite); + onFavorite?.(ids, isFavorite); notificationController.show({ message: isFavorite diff --git a/web/src/lib/components/photos-page/actions/select-all-assets.svelte b/web/src/lib/components/photos-page/actions/select-all-assets.svelte index 9e7c2b9163..cc3f75ab56 100644 --- a/web/src/lib/components/photos-page/actions/select-all-assets.svelte +++ b/web/src/lib/components/photos-page/actions/select-all-assets.svelte @@ -1,6 +1,6 @@
{#each dateGroups as dateGroup, groupIndex (dateGroup.date)} {@const display = - dateGroup.intersecting || !!dateGroup.assets.some((asset) => asset.id === $assetStore.pendingScrollAssetId)} + dateGroup.intersecting || !!dateGroup.assets.some((asset) => asset.id === assetStore.pendingScrollAssetId)} + {@const geometry = dateGroup.geometry!}
{ - $assetStore.taskManager.intersectedDateGroup(componentId, dateGroup, () => + assetStore.taskManager.intersectedDateGroup(componentId, dateGroup, () => assetStore.updateBucketDateGroup(bucket, dateGroup, { intersecting: true }), ); }, onSeparate: () => { - $assetStore.taskManager.separatedDateGroup(componentId, dateGroup, () => + assetStore.taskManager.separatedDateGroup(componentId, dateGroup, () => assetStore.updateBucketDateGroup(bucket, dateGroup, { intersecting: false }), ); }, @@ -118,7 +119,7 @@ data-display={display} data-date-group={dateGroup.date} style:height={dateGroup.height + 'px'} - style:width={dateGroup.geometry.containerWidth + 'px'} + style:width={geometry.containerWidth + 'px'} style:overflow="clip" > {#if !display} @@ -129,7 +130,7 @@
- $assetStore.taskManager.queueScrollSensitiveTask({ + assetStore.taskManager.queueScrollSensitiveTask({ componentId, task: () => { isMouseOverGroup = true; @@ -137,7 +138,7 @@ }, })} on:mouseleave={() => { - $assetStore.taskManager.queueScrollSensitiveTask({ + assetStore.taskManager.queueScrollSensitiveTask({ componentId, task: () => { isMouseOverGroup = false; @@ -149,7 +150,7 @@
{#if !singleSelect && ((hoveredDateGroup == dateGroup.groupTitle && isMouseOverGroup) || assetInteraction.selectedGroup.has(dateGroup.groupTitle))}
- {#each dateGroup.assets as asset, index (asset.id)} - {@const box = dateGroup.geometry.boxes[index]} + {#each dateGroup.assets as asset, i (asset.id)} + + {@const top = geometry.getTop(i)} + {@const left = geometry.getLeft(i)} + {@const width = geometry.getWidth(i)} + {@const height = geometry.getHeight(i)}
onRetrieveElement(dateGroup, asset, element)} showStackedIcon={withStacked} {showArchiveIcon} @@ -212,11 +217,11 @@ onClick={(asset) => onClick(dateGroup.assets, dateGroup.groupTitle, asset)} onSelect={(asset) => assetSelectHandler(asset, dateGroup.assets, dateGroup.groupTitle)} onMouseEvent={() => assetMouseEventHandler(dateGroup.groupTitle, asset)} - selected={assetInteraction.selectedAssets.has(asset) || $assetStore.albumAssets.has(asset.id)} + selected={assetInteraction.selectedAssets.has(asset) || assetStore.albumAssets.has(asset.id)} selectionCandidate={assetInteraction.assetSelectionCandidates.has(asset)} - disabled={$assetStore.albumAssets.has(asset.id)} - thumbnailWidth={box.width} - thumbnailHeight={box.height} + disabled={assetStore.albumAssets.has(asset.id)} + thumbnailWidth={width} + thumbnailHeight={height} />
{/each} diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index db2c491dbd..ea862cdd1a 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -4,7 +4,7 @@ import type { Action } from '$lib/components/asset-viewer/actions/action'; import { AppRoute, AssetAction } from '$lib/constants'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { AssetBucket, AssetStore, type BucketListener, type ViewportXY } from '$lib/stores/assets.store'; + import { AssetBucket, AssetStore, type BucketListener, type ViewportXY } from '$lib/stores/assets-store.svelte'; import { locale, showDeleteModal } from '$lib/stores/preferences.store'; import { isSearchEnabled } from '$lib/stores/search.store'; import { featureFlags } from '$lib/stores/server-config.store'; @@ -117,7 +117,6 @@ const isViewportOrigin = () => { return viewport.height === 0 && viewport.width === 0; }; - const isEqual = (a: ViewportXY, b: ViewportXY) => { return a.height == b.height && a.width == b.width && a.x === b.x && a.y === b.y; }; @@ -130,7 +129,7 @@ } if ($gridScrollTarget?.at) { - void $assetStore.scheduleScrollToAssetId($gridScrollTarget, () => { + void assetStore.scheduleScrollToAssetId($gridScrollTarget, () => { element?.scrollTo({ top: 0 }); showSkeleton = false; }); @@ -166,7 +165,7 @@ if (assetGridUpdate) { setTimeout(() => { - void $assetStore.updateViewport(safeViewport, true); + void assetStore.updateViewport(safeViewport, true); const asset = $page.url.searchParams.get('at'); if (asset) { $gridScrollTarget = { at: asset }; @@ -194,31 +193,10 @@ return () => void 0; }; - const _updateLastIntersectedBucketDate = () => { - let elem = document.elementFromPoint(safeViewport.x + 1, safeViewport.y + 1); - - while (elem != null) { - if (elem.id === 'bucket') { - break; - } - elem = elem.parentElement; - } - if (elem) { - lastIntersectedBucketDate = (elem as HTMLElement).dataset.bucketDate; - } - }; - const updateLastIntersectedBucketDate = throttle(_updateLastIntersectedBucketDate, 16, { - leading: false, - trailing: true, - }); - const scrollTolastIntersectedBucket = (adjustedBucket: AssetBucket, delta: number) => { - if (!lastIntersectedBucketDate) { - _updateLastIntersectedBucketDate(); - } if (lastIntersectedBucketDate) { - const currentIndex = $assetStore.buckets.findIndex((b) => b.bucketDate === lastIntersectedBucketDate); - const deltaIndex = $assetStore.buckets.indexOf(adjustedBucket); + const currentIndex = assetStore.buckets.findIndex((b) => b.bucketDate === lastIntersectedBucketDate); + const deltaIndex = assetStore.buckets.indexOf(adjustedBucket); if (deltaIndex < currentIndex) { element?.scrollBy(0, delta); @@ -235,20 +213,23 @@ }; onMount(() => { - void $assetStore + void assetStore .init({ bucketListener }) - .then(() => ($assetStore.connect(), $assetStore.updateViewport(safeViewport))); + .then(() => (assetStore.connect(), assetStore.updateViewport(safeViewport))); if (!enableRouting) { showSkeleton = false; } const dispose = hmrSupport(); return () => { - $assetStore.disconnect(); - $assetStore.destroy(); + assetStore.disconnect(); + assetStore.destroy(); dispose(); }; }); + const _updateViewport = () => void assetStore.updateViewport(safeViewport); + const updateViewport = throttle(_updateViewport, 16); + function getOffset(bucketDate: string) { let offset = 0; for (let a = 0; a < assetStore.buckets.length; a++) { @@ -259,12 +240,10 @@ } return offset; } - const _updateViewport = () => void $assetStore.updateViewport(safeViewport); - const updateViewport = throttle(_updateViewport, 16); const getMaxScrollPercent = () => - ($assetStore.timelineHeight + bottomSectionHeight + topSectionHeight - safeViewport.height) / - ($assetStore.timelineHeight + bottomSectionHeight + topSectionHeight); + (assetStore.timelineHeight + bottomSectionHeight + topSectionHeight - safeViewport.height) / + (assetStore.timelineHeight + bottomSectionHeight + topSectionHeight); const getMaxScroll = () => { if (!element || !timelineElement) { @@ -292,7 +271,7 @@ scrollPercent: number, bucketScrollPercent: number, ) => { - if (!bucketDate || $assetStore.timelineHeight < safeViewport.height * 2) { + if (!bucketDate || assetStore.timelineHeight < safeViewport.height * 2) { // edge case - scroll limited due to size of content, must adjust - use use the overall percent instead const maxScroll = getMaxScroll(); @@ -318,7 +297,7 @@ _scrollPercent: number, bucketScrollPercent: number, ) => { - if (!bucketDate || $assetStore.timelineHeight < safeViewport.height * 2) { + if (!bucketDate || assetStore.timelineHeight < safeViewport.height * 2) { // edge case - scroll limited due to size of content, must adjust - use use the overall percent instead return; } @@ -328,10 +307,7 @@ } if (bucket && !bucket.measured) { preMeasure.push(bucket); - if (!bucket.loaded) { - await assetStore.loadBucket(bucket.bucketDate); - } - // Wait here, and collect the deltas that are above offset, which affect offset position + await assetStore.loadBucket(bucketDate, { preventCancel: true, pending: true }); await bucket.measuredPromise; scrollToBucketAndOffset(bucket, bucketScrollPercent); } @@ -354,7 +330,7 @@ return; } - if ($assetStore.timelineHeight < safeViewport.height * 2) { + if (assetStore.timelineHeight < safeViewport.height * 2) { // edge case - scroll limited due to size of content, must adjust - use the overall percent instead const maxScroll = getMaxScroll(); scrubOverallPercent = Math.min(1, element.scrollTop / maxScroll); @@ -424,19 +400,15 @@ preMeasure.push(bucket); } showSkeleton = false; - $assetStore.clearPendingScroll(); + assetStore.clearPendingScroll(); // set intersecting true manually here, to reduce flicker that happens when // clearing pending scroll, but the intersection observer hadn't yet had time to run - $assetStore.updateBucket(bucket.bucketDate, { intersecting: true }); + assetStore.updateBucket(bucket.bucketDate, { intersecting: true }); }; const trashOrDelete = async (force: boolean = false) => { isShowDeleteConfirmation = false; - await deleteAssets( - !(isTrashEnabled && !force), - (assetIds) => $assetStore.removeAssets(assetIds), - idsSelectedAssets, - ); + await deleteAssets(!(isTrashEnabled && !force), (assetIds) => assetStore.removeAssets(assetIds), idsSelectedAssets); assetInteraction.clearMultiselect(); }; @@ -461,7 +433,7 @@ const onStackAssets = async () => { const ids = await stackAssets(assetInteraction.selectedAssetsArray); if (ids) { - $assetStore.removeAssets(ids); + assetStore.removeAssets(ids); onEscape(); } }; @@ -469,7 +441,7 @@ const toggleArchive = async () => { const ids = await archiveAssets(assetInteraction.selectedAssetsArray, !assetInteraction.isAllArchived); if (ids) { - $assetStore.removeAssets(ids); + assetStore.removeAssets(ids); deselectAllAssets(); } }; @@ -481,33 +453,33 @@ }; const handleSelectAsset = (asset: AssetResponseDto) => { - if (!$assetStore.albumAssets.has(asset.id)) { + if (!assetStore.albumAssets.has(asset.id)) { assetInteraction.selectAsset(asset); } }; function handleIntersect(bucket: AssetBucket) { - updateLastIntersectedBucketDate(); + // updateLastIntersectedBucketDate(); const task = () => { - $assetStore.updateBucket(bucket.bucketDate, { intersecting: true }); - void $assetStore.loadBucket(bucket.bucketDate); + assetStore.updateBucket(bucket.bucketDate, { intersecting: true }); + void assetStore.loadBucket(bucket.bucketDate); }; - $assetStore.taskManager.intersectedBucket(componentId, bucket, task); + assetStore.taskManager.intersectedBucket(componentId, bucket, task); } function handleSeparate(bucket: AssetBucket) { const task = () => { - $assetStore.updateBucket(bucket.bucketDate, { intersecting: false }); + assetStore.updateBucket(bucket.bucketDate, { intersecting: false }); bucket.cancel(); }; - $assetStore.taskManager.separatedBucket(componentId, bucket, task); + assetStore.taskManager.separatedBucket(componentId, bucket, task); } const handlePrevious = async () => { - const previousAsset = await $assetStore.getPreviousAsset($viewingAsset); + const previousAsset = await assetStore.getPreviousAsset($viewingAsset); if (previousAsset) { - const preloadAsset = await $assetStore.getPreviousAsset(previousAsset); + const preloadAsset = await assetStore.getPreviousAsset(previousAsset); assetViewingStore.setAsset(previousAsset, preloadAsset ? [preloadAsset] : []); await navigate({ targetRoute: 'current', assetId: previousAsset.id }); } @@ -516,9 +488,10 @@ }; const handleNext = async () => { - const nextAsset = await $assetStore.getNextAsset($viewingAsset); + const nextAsset = await assetStore.getNextAsset($viewingAsset); + if (nextAsset) { - const preloadAsset = await $assetStore.getNextAsset(nextAsset); + const preloadAsset = await assetStore.getNextAsset(nextAsset); assetViewingStore.setAsset(nextAsset, preloadAsset ? [preloadAsset] : []); await navigate({ targetRoute: 'current', assetId: nextAsset.id }); } @@ -527,10 +500,10 @@ }; const handleRandom = async () => { - const randomAsset = await $assetStore.getRandomAsset(); + const randomAsset = await assetStore.getRandomAsset(); if (randomAsset) { - const preloadAsset = await $assetStore.getNextAsset(randomAsset); + const preloadAsset = await assetStore.getNextAsset(randomAsset); assetViewingStore.setAsset(randomAsset, preloadAsset ? [preloadAsset] : []); await navigate({ targetRoute: 'current', assetId: randomAsset.id }); } @@ -664,8 +637,8 @@ assetInteraction.clearAssetSelectionCandidates(); if (assetInteraction.assetSelectionStart && rangeSelection) { - let startBucketIndex = $assetStore.getBucketIndexByAssetId(assetInteraction.assetSelectionStart.id); - let endBucketIndex = $assetStore.getBucketIndexByAssetId(asset.id); + let startBucketIndex = assetStore.getBucketIndexByAssetId(assetInteraction.assetSelectionStart.id); + let endBucketIndex = assetStore.getBucketIndexByAssetId(asset.id); if (startBucketIndex === null || endBucketIndex === null) { return; @@ -677,8 +650,8 @@ // Select/deselect assets in all intermediate buckets for (let bucketIndex = startBucketIndex + 1; bucketIndex < endBucketIndex; bucketIndex++) { - const bucket = $assetStore.buckets[bucketIndex]; - await $assetStore.loadBucket(bucket.bucketDate); + const bucket = assetStore.buckets[bucketIndex]; + await assetStore.loadBucket(bucket.bucketDate); for (const asset of bucket.assets) { if (deselect) { assetInteraction.removeAssetFromMultiselectGroup(asset); @@ -690,7 +663,7 @@ // Update date group selection for (let bucketIndex = startBucketIndex; bucketIndex <= endBucketIndex; bucketIndex++) { - const bucket = $assetStore.buckets[bucketIndex]; + const bucket = assetStore.buckets[bucketIndex]; // Split bucket into date groups and check each group const assetsGroupByDate = splitBucketIntoDateGroups(bucket, $locale); @@ -718,14 +691,14 @@ return; } - let start = $assetStore.assets.findIndex((a) => a.id === startAsset.id); - let end = $assetStore.assets.findIndex((a) => a.id === endAsset.id); + let start = assetStore.assets.findIndex((a) => a.id === startAsset.id); + let end = assetStore.assets.findIndex((a) => a.id === endAsset.id); if (start > end) { [start, end] = [end, start]; } - assetInteraction.setAssetSelectionCandidates($assetStore.assets.slice(start, end + 1)); + assetInteraction.setAssetSelectionCandidates(assetStore.assets.slice(start, end + 1)); }; const onSelectStart = (e: Event) => { @@ -737,7 +710,7 @@ assetStore.taskManager.removeAllTasksForComponent(componentId); }); let isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash); - let isEmpty = $derived($assetStore.initialized && $assetStore.buckets.length === 0); + let isEmpty = $derived(assetStore.initialized && assetStore.buckets.length === 0); let idsSelectedAssets = $derived(assetInteraction.selectedAssetsArray.map(({ id }) => id)); $effect(() => { @@ -773,7 +746,7 @@ { shortcut: { key: 'Escape' }, onShortcut: onEscape }, { shortcut: { key: '?', shift: true }, onShortcut: () => (showShortcuts = !showShortcuts) }, { shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) }, - { shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets($assetStore, assetInteraction) }, + { shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(assetStore, assetInteraction) }, { shortcut: { key: 'PageDown' }, preventDefault: false, onShortcut: focusElement }, { shortcut: { key: 'PageUp' }, preventDefault: false, onShortcut: focusElement }, ]; @@ -824,7 +797,7 @@ {#if showShortcuts} (showShortcuts = !showShortcuts)} /> {/if} -{#if $assetStore.buckets.length > 0} +{#if assetStore.buckets.length > 0} - {#each $assetStore.buckets as bucket (bucket.viewId)} + {#each assetStore.buckets as bucket (bucket.viewId)} {@const isPremeasure = preMeasure.includes(bucket)} - {@const display = bucket.intersecting || bucket === $assetStore.pendingScrollBucket || isPremeasure} + {@const display = bucket.intersecting || bucket === assetStore.pendingScrollBucket || isPremeasure} +
handleIntersect(bucket), - onSeparate: () => handleSeparate(bucket), - top: BUCKET_INTERSECTION_ROOT_TOP, - bottom: BUCKET_INTERSECTION_ROOT_BOTTOM, - root: element, - }} + style:overflow={bucket.measured ? 'visible' : 'clip'} + use:intersectionObserver={[ + { + key: bucket.viewId, + onIntersect: () => handleIntersect(bucket), + onSeparate: () => handleSeparate(bucket), + top: BUCKET_INTERSECTION_ROOT_TOP, + bottom: BUCKET_INTERSECTION_ROOT_BOTTOM, + root: element, + }, + { + key: bucket.viewId + '.bucketintersection', + onIntersect: () => (lastIntersectedBucketDate = bucket.bucketDate), + top: '0px', + bottom: '-' + Math.max(0, safeViewport.height - 1) + 'px', + left: '0px', + right: '0px', + }, + ]} data-bucket-display={bucket.intersecting} data-bucket-date={bucket.bucketDate} style:height={bucket.bucketHeight + 'px'} @@ -949,6 +934,5 @@ .bucket { contain: layout size; - transition: height 0.2s ease-out; } diff --git a/web/src/lib/components/photos-page/measure-date-group.svelte b/web/src/lib/components/photos-page/measure-date-group.svelte index a435f89fb5..d3dabaa51d 100644 --- a/web/src/lib/components/photos-page/measure-date-group.svelte +++ b/web/src/lib/components/photos-page/measure-date-group.svelte @@ -18,7 +18,7 @@