From cc790604aa78c964686bb21f338c9f32e0546c11 Mon Sep 17 00:00:00 2001 From: midzelis Date: Wed, 8 Oct 2025 23:39:50 +0000 Subject: [PATCH] Renames --- .../lib/components/timeline/Timeline.svelte | 32 ++--- .../timeline/TimelineDateGroup.svelte | 12 +- .../actions/TimelineKeyboardActions.svelte | 2 +- .../AssetStreamManager.svelte.ts} | 122 +++++++++++------- .../AssetStreamSegment.svelte.ts} | 68 ++++++---- .../timeline-manager/day-group.svelte.ts | 2 +- .../internal/intersection-support.svelte.ts | 58 ++------- .../internal/layout-support.svelte.ts | 25 ---- .../internal/operations-support.svelte.ts | 17 +-- .../internal/search-support.svelte.ts | 6 +- .../timeline-manager/month-group.svelte.ts | 31 ++--- .../timeline-manager.svelte.spec.ts | 63 ++++----- .../timeline-manager.svelte.ts | 62 ++++----- .../timeline-manager/viewer-asset.svelte.ts | 2 +- web/src/lib/utils/asset-utils.ts | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- 16 files changed, 235 insertions(+), 271 deletions(-) rename web/src/lib/managers/{photostream-manager/PhotostreamManager.svelte.ts => AssetStreamManager/AssetStreamManager.svelte.ts} (57%) rename web/src/lib/managers/{photostream-manager/PhotostreamSegment.svelte.ts => AssetStreamManager/AssetStreamSegment.svelte.ts} (59%) diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index c04fad1896..d62ddce8d6 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -107,7 +107,7 @@ // Indicates whether the viewport is currently in the lead-out section (after all months) let isInLeadOutSection = $state(false); - const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0); + const isEmpty = $derived(timelineManager.isInitialized && timelineManager.segments.length === 0); const maxMd = $derived(mobileDevice.maxMd); const usingMobileDevice = $derived(mobileDevice.pointerCoarse); @@ -148,7 +148,7 @@ // handle any scroll compensation that may have been set const height = monthGroup!.findAssetAbsolutePosition(assetId); - while (timelineManager.scrollCompensation.monthGroup) { + while (timelineManager.scrollCompensation.segment) { handleScrollCompensation(timelineManager.scrollCompensation); timelineManager.clearScrollCompensation(); } @@ -290,13 +290,13 @@ const onScrub: ScrubberListener = (scrubberData) => { const { scrubberMonth, overallScrollPercent, scrubberMonthScrollPercent } = scrubberData; - if (!scrubberMonth || timelineManager.timelineHeight < timelineManager.viewportHeight * 2) { + if (!scrubberMonth || timelineManager.streamViewerHeight < timelineManager.viewportHeight * 2) { // edge case - scroll limited due to size of content, must adjust - use use the overall percent instead const maxScroll = getMaxScroll(); const offset = maxScroll * overallScrollPercent; scrollTop(offset); } else { - const monthGroup = timelineManager.months.find( + const monthGroup = timelineManager.segments.find( ({ yearMonth: { year, month } }) => year === scrubberMonth.year && month === scrubberMonth.month, ); if (!monthGroup) { @@ -314,7 +314,7 @@ return; } - if (timelineManager.timelineHeight < timelineManager.viewportHeight * 2) { + if (timelineManager.streamViewerHeight < timelineManager.viewportHeight * 2) { // edge case - scroll limited due to size of content, must adjust - use the overall percent instead const maxScroll = getMaxScroll(); timelineScrollPercent = Math.min(1, element.scrollTop / maxScroll); @@ -336,7 +336,7 @@ let maxScrollPercent = timelineManager.maxScrollPercent; let found = false; - const monthsLength = timelineManager.months.length; + const monthsLength = timelineManager.segments.length; for (let i = -1; i < monthsLength + 1; i++) { let monthGroup: TimelineYearMonth | undefined; let monthGroupHeight = 0; @@ -347,8 +347,8 @@ // lead-out monthGroupHeight = bottomSectionHeight; } else { - monthGroup = timelineManager.months[i].yearMonth; - monthGroupHeight = timelineManager.months[i].height; + monthGroup = timelineManager.segments[i].yearMonth; + monthGroupHeight = timelineManager.segments[i].height; } let next = top - monthGroupHeight * maxScrollPercent; @@ -361,7 +361,7 @@ // compensate for lost precision/rounding errors advance to the next bucket, if present if (viewportTopMonthScrollPercent > 0.9999 && i + 1 < monthsLength - 1) { - viewportTopMonth = timelineManager.months[i + 1].yearMonth; + viewportTopMonth = timelineManager.segments[i + 1].yearMonth; viewportTopMonthScrollPercent = 0; } @@ -468,7 +468,7 @@ // Select/deselect assets in range (start,end) let started = false; - for (const monthGroup of timelineManager.months) { + for (const monthGroup of timelineManager.segments) { if (monthGroup === endBucket) { break; } @@ -489,7 +489,7 @@ // Update date group selection in range [start,end] started = false; - for (const monthGroup of timelineManager.months) { + for (const monthGroup of timelineManager.segments) { if (monthGroup === startBucket) { started = true; } @@ -565,7 +565,7 @@ {onEscape} /> -{#if timelineManager.months.length > 0} +{#if timelineManager.segments.length > 0}
- {#each timelineManager.months as monthGroup (monthGroup.viewId)} + {#each timelineManager.segments as monthGroup (monthGroup.identifier.id)} {@const display = monthGroup.intersecting} {@const absoluteHeight = monthGroup.top} @@ -638,7 +638,7 @@ style:width="100%" > @@ -674,7 +674,7 @@ style:position="absolute" style:left="0" style:right="0" - style:transform={`translate3d(0,${timelineManager.timelineHeight}px,0)`} + style:transform={`translate3d(0,${timelineManager.streamViewerHeight}px,0)`} >
diff --git a/web/src/lib/components/timeline/TimelineDateGroup.svelte b/web/src/lib/components/timeline/TimelineDateGroup.svelte index 0a58d3f191..18ea97e45d 100644 --- a/web/src/lib/components/timeline/TimelineDateGroup.svelte +++ b/web/src/lib/components/timeline/TimelineDateGroup.svelte @@ -66,7 +66,7 @@ let hoveredDayGroup = $state(); const transitionDuration = $derived.by(() => - monthGroup.timelineManager.suspendTransitions && !$isUploading ? 0 : 150, + monthGroup.assetStreamManager.suspendTransitions && !$isUploading ? 0 : 150, ); const scaleDuration = $derived(transitionDuration === 0 ? 0 : transitionDuration + 100); const _onClick = ( @@ -125,7 +125,7 @@ } $effect.root(() => { - if (timelineManager.scrollCompensation.monthGroup === monthGroup) { + if (timelineManager.scrollCompensation.segment === monthGroup) { onScrollCompensation(timelineManager.scrollCompensation); timelineManager.clearScrollCompensation(); } @@ -138,8 +138,8 @@
assetSelectHandler(timelineManager, asset, dayGroup.getAssets(), dayGroup.groupTitle)} onMouseEvent={() => assetMouseEventHandler(dayGroup.groupTitle, assetSnapshot(asset))} selected={assetInteraction.hasSelectedAsset(asset.id) || - dayGroup.monthGroup.timelineManager.albumAssets.has(asset.id)} + dayGroup.monthGroup.assetStreamManager.albumAssets.has(asset.id)} selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)} - disabled={dayGroup.monthGroup.timelineManager.albumAssets.has(asset.id)} + disabled={dayGroup.monthGroup.assetStreamManager.albumAssets.has(asset.id)} thumbnailWidth={position.width} thumbnailHeight={position.height} /> diff --git a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte index 6d7a64d3d8..8970e627a9 100644 --- a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte +++ b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte @@ -127,7 +127,7 @@ }; const isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash); - const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0); + const isEmpty = $derived(timelineManager.isInitialized && timelineManager.segments.length === 0); const idsSelectedAssets = $derived(assetInteraction.selectedAssets.map(({ id }) => id)); let isShortcutModalOpen = false; diff --git a/web/src/lib/managers/photostream-manager/PhotostreamManager.svelte.ts b/web/src/lib/managers/AssetStreamManager/AssetStreamManager.svelte.ts similarity index 57% rename from web/src/lib/managers/photostream-manager/PhotostreamManager.svelte.ts rename to web/src/lib/managers/AssetStreamManager/AssetStreamManager.svelte.ts index 99e45bc058..1a41f0503c 100644 --- a/web/src/lib/managers/photostream-manager/PhotostreamManager.svelte.ts +++ b/web/src/lib/managers/AssetStreamManager/AssetStreamManager.svelte.ts @@ -1,28 +1,24 @@ -import type { - PhotostreamSegment, - SegmentIdentifier, -} from '$lib/managers/photostream-manager/PhotostreamSegment.svelte'; -import { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte'; -import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte'; -import type { - AssetDescriptor, - TimelineAsset, - TimelineManagerLayoutOptions, -} from '$lib/managers/timeline-manager/types'; +import type { AssetStreamSegment, SegmentIdentifier } from '$lib/managers/AssetStreamManager/AssetStreamSegment.svelte'; +import type { AssetDescriptor, TimelineAsset } from '$lib/managers/timeline-manager/types'; import { CancellableTask, TaskStatus } from '$lib/utils/cancellable-task'; +import { TUNABLES } from '$lib/utils/tunables'; import { clamp, debounce } from 'lodash-es'; -export abstract class PhotostreamManager { +const { + TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM }, +} = TUNABLES; + +export abstract class AssetStreamManager { isInitialized = $state(false); topSectionHeight = $state(0); bottomSectionHeight = $state(60); - timelineHeight = $derived.by( - () => this.months.reduce((accumulator, b) => accumulator + b.height, 0) + this.topSectionHeight, + streamViewerHeight = $derived.by( + () => this.segments.reduce((accumulator, b) => accumulator + b.height, 0) + this.topSectionHeight, ); - assetCount = $derived.by(() => this.months.reduce((accumulator, b) => accumulator + b.assetsCount, 0)); + assetCount = $derived.by(() => this.segments.reduce((accumulator, b) => accumulator + b.assetsCount, 0)); - topIntersectingMonthGroup: PhotostreamSegment | undefined = $state(); + topIntersectingSegment: AssetStreamSegment | undefined = $state(); visibleWindow = $derived.by(() => ({ top: this.#scrollTop, @@ -50,16 +46,16 @@ export abstract class PhotostreamManager { scrollCompensation: { heightDelta: number | undefined; scrollTop: number | undefined; - monthGroup: PhotostreamSegment | undefined; + segment: AssetStreamSegment | undefined; } = $state({ heightDelta: 0, scrollTop: 0, - monthGroup: undefined, + segment: undefined, }); constructor() {} - setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }: TimelineManagerLayoutOptions) { + setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }) { let changed = false; changed ||= this.#setHeaderHeight(headerHeight); changed ||= this.#setGap(gap); @@ -69,15 +65,15 @@ export abstract class PhotostreamManager { } } - abstract get months(): PhotostreamSegment[]; + abstract get segments(): AssetStreamSegment[]; get maxScrollPercent() { - const totalHeight = this.timelineHeight + this.bottomSectionHeight + this.topSectionHeight; + const totalHeight = this.streamViewerHeight + this.bottomSectionHeight + this.topSectionHeight; return (totalHeight - this.viewportHeight) / totalHeight; } get maxScroll() { - return this.topSectionHeight + this.bottomSectionHeight + (this.timelineHeight - this.viewportHeight); + return this.topSectionHeight + this.bottomSectionHeight + (this.streamViewerHeight - this.viewportHeight); } #setHeaderHeight(value: number) { @@ -175,7 +171,7 @@ export abstract class PhotostreamManager { this.scrollCompensation = { heightDelta: undefined, scrollTop: undefined, - monthGroup: undefined, + segment: undefined, }; } @@ -183,25 +179,25 @@ export abstract class PhotostreamManager { if (!this.isInitialized || this.visibleWindow.bottom === this.visibleWindow.top) { return; } - let topIntersectingMonthGroup = undefined; - for (const month of this.months) { - updateIntersectionMonthGroup(this, month); - if (!topIntersectingMonthGroup && month.actuallyIntersecting) { - topIntersectingMonthGroup = month; + let topIntersectingSegment = undefined; + for (const segment of this.segments) { + this.updateSegmentIntersections(segment); + if (!topIntersectingSegment && segment.actuallyIntersecting) { + topIntersectingSegment = segment; } } - if (topIntersectingMonthGroup !== undefined && this.topIntersectingMonthGroup !== topIntersectingMonthGroup) { - this.topIntersectingMonthGroup = topIntersectingMonthGroup; + if (topIntersectingSegment !== undefined && this.topIntersectingSegment !== topIntersectingSegment) { + this.topIntersectingSegment = topIntersectingSegment; } - for (const month of this.months) { - if (month === this.topIntersectingMonthGroup) { - this.topIntersectingMonthGroup.percent = clamp( - (this.visibleWindow.top - this.topIntersectingMonthGroup.top) / this.topIntersectingMonthGroup.height, + for (const segment of this.segments) { + if (segment === this.topIntersectingSegment) { + this.topIntersectingSegment.percent = clamp( + (this.visibleWindow.top - this.topIntersectingSegment.top) / this.topIntersectingSegment.height, 0, 1, ); } else { - month.percent = 0; + segment.percent = 0; } } } @@ -219,8 +215,8 @@ export abstract class PhotostreamManager { if (!this.isInitialized || this.hasEmptyViewport) { return; } - for (const month of this.months) { - updateGeometry(this, month, { invalidateHeight: changedWidth }); + for (const segment of this.segments) { + segment.updateGeometry({ invalidateHeight: changedWidth }); } this.updateIntersections(); } @@ -238,29 +234,29 @@ export abstract class PhotostreamManager { async loadSegment(identifier: SegmentIdentifier, options?: { cancelable: boolean }): Promise { const { cancelable = true } = options ?? {}; - const segment = this.months.find((segment) => identifier.matches(segment)); + const segment = this.segments.find((segment) => identifier.matches(segment)); if (!segment || segment.loader?.executed) { return; } const result = await segment.load(cancelable); if (result === TaskStatus.LOADED) { - updateIntersectionMonthGroup(this, segment); + this.updateSegmentIntersections(segment); } } getSegmentForAssetId(assetId: string) { - for (const month of this.months) { - const asset = month.assets.find((asset) => asset.id === assetId); + for (const segment of this.segments) { + const asset = segment.assets.find((asset) => asset.id === assetId); if (asset) { - return month; + return segment; } } } refreshLayout() { - for (const month of this.months) { - updateGeometry(this, month, { invalidateHeight: true }); + for (const segment of this.segments) { + segment.updateGeometry({ invalidateHeight: true }); } this.updateIntersections(); } @@ -269,8 +265,8 @@ export abstract class PhotostreamManager { const range: TimelineAsset[] = []; let collecting = false; - for (const month of this.months) { - for (const asset of month.assets) { + for (const segment of this.segments) { + for (const asset of segment.assets) { if (asset.id === start.id) { collecting = true; } @@ -284,4 +280,38 @@ export abstract class PhotostreamManager { } return Promise.resolve(range); } + + protected calculateSegmentIntersecting(segment: AssetStreamSegment, expandTop: number, expandBottom: number) { + const monthGroupTop = segment.top; + const monthGroupBottom = monthGroupTop + segment.height; + const topWindow = this.visibleWindow.top - expandTop; + const bottomWindow = this.visibleWindow.bottom + expandBottom; + + return isIntersecting(monthGroupTop, monthGroupBottom, topWindow, bottomWindow); + } + + protected updateSegmentIntersections(segment: AssetStreamSegment) { + const actuallyIntersecting = this.calculateSegmentIntersecting(segment, 0, 0); + let preIntersecting = false; + if (!actuallyIntersecting) { + preIntersecting = this.calculateSegmentIntersecting(segment, INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM); + } + segment.updateIntersection({ intersecting: actuallyIntersecting || preIntersecting, actuallyIntersecting }); + } +} + +/** + * General function to check if a rectangular region intersects with a window. + * @param regionTop - Top position of the region to check + * @param regionBottom - Bottom position of the region to check + * @param windowTop - Top position of the window + * @param windowBottom - Bottom position of the window + * @returns true if the region intersects with the window + */ +export function isIntersecting(regionTop: number, regionBottom: number, windowTop: number, windowBottom: number) { + return ( + (regionTop >= windowTop && regionTop < windowBottom) || + (regionBottom >= windowTop && regionBottom < windowBottom) || + (regionTop < windowTop && regionBottom >= windowBottom) + ); } diff --git a/web/src/lib/managers/photostream-manager/PhotostreamSegment.svelte.ts b/web/src/lib/managers/AssetStreamManager/AssetStreamSegment.svelte.ts similarity index 59% rename from web/src/lib/managers/photostream-manager/PhotostreamSegment.svelte.ts rename to web/src/lib/managers/AssetStreamManager/AssetStreamSegment.svelte.ts index 1c5e65cdbe..a85aea35f5 100644 --- a/web/src/lib/managers/photostream-manager/PhotostreamSegment.svelte.ts +++ b/web/src/lib/managers/AssetStreamManager/AssetStreamSegment.svelte.ts @@ -1,5 +1,5 @@ -import type { PhotostreamManager } from '$lib/managers/photostream-manager/PhotostreamManager.svelte'; -import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; +import type { AssetStreamManager } from '$lib/managers/AssetStreamManager/AssetStreamManager.svelte'; +import type { TimelineAsset, UpdateGeometryOptions } from '$lib/managers/timeline-manager/types'; import type { ViewerAsset } from '$lib/managers/timeline-manager/viewer-asset.svelte'; import { CancellableTask, TaskStatus } from '$lib/utils/cancellable-task'; import { handleError } from '$lib/utils/handle-error'; @@ -7,12 +7,12 @@ import { t } from 'svelte-i18n'; import { get } from 'svelte/store'; export type SegmentIdentifier = { - id(): string; - matches(segment: PhotostreamSegment): boolean; + get id(): string; + matches(segment: AssetStreamSegment): boolean; }; -export abstract class PhotostreamSegment { +export abstract class AssetStreamSegment { #intersecting = $state(false); - actuallyIntersecting = $state(false); + #actuallyIntersecting = $state(false); #isLoaded = $state(false); #height = $state(0); @@ -30,7 +30,7 @@ export abstract class PhotostreamSegment { ); isHeightActual = $state(false); - abstract get timelineManager(): PhotostreamManager; + abstract get assetStreamManager(): AssetStreamManager; abstract get identifier(): SegmentIdentifier; @@ -65,6 +65,10 @@ export abstract class PhotostreamSegment { return this.#intersecting; } + get actuallyIntersecting() { + return this.#actuallyIntersecting; + } + get assets(): TimelineAsset[] { return this.#assets; } @@ -73,39 +77,39 @@ export abstract class PhotostreamSegment { if (this.#height === height) { return; } - const { timelineManager: store, percent } = this; - const index = store.months.indexOf(this); + const { assetStreamManager: store, percent } = this; + const index = store.segments.indexOf(this); const heightDelta = height - this.#height; this.#height = height; - const prevMonthGroup = store.months[index - 1]; - if (prevMonthGroup) { - const newTop = prevMonthGroup.#top + prevMonthGroup.#height; + const prevSegment = store.segments[index - 1]; + if (prevSegment) { + const newTop = prevSegment.#top + prevSegment.#height; if (this.#top !== newTop) { this.#top = newTop; } } - for (let cursor = index + 1; cursor < store.months.length; cursor++) { - const monthGroup = this.timelineManager.months[cursor]; - const newTop = monthGroup.#top + heightDelta; - if (monthGroup.#top !== newTop) { - monthGroup.#top = newTop; + for (let cursor = index + 1; cursor < store.segments.length; cursor++) { + const segment = this.assetStreamManager.segments[cursor]; + const newTop = segment.#top + heightDelta; + if (segment.#top !== newTop) { + segment.#top = newTop; } } - if (store.topIntersectingMonthGroup) { - const currentIndex = store.months.indexOf(store.topIntersectingMonthGroup); + if (store.topIntersectingSegment) { + const currentIndex = store.segments.indexOf(store.topIntersectingSegment); if (currentIndex > 0) { if (index < currentIndex) { store.scrollCompensation = { heightDelta, scrollTop: undefined, - monthGroup: this, + segment: this, }; } else if (percent > 0) { const top = this.top + height * percent; store.scrollCompensation = { heightDelta: undefined, scrollTop: top, - monthGroup: this, + segment: this, }; } } @@ -117,7 +121,7 @@ export abstract class PhotostreamSegment { } get top(): number { - return this.#top + this.timelineManager.topSectionHeight; + return this.#top + this.assetStreamManager.topSectionHeight; } async load(cancelable: boolean): Promise { @@ -139,6 +143,24 @@ export abstract class PhotostreamSegment { updateIntersection({ intersecting, actuallyIntersecting }: { intersecting: boolean; actuallyIntersecting: boolean }) { this.intersecting = intersecting; - this.actuallyIntersecting = actuallyIntersecting; + this.#actuallyIntersecting = actuallyIntersecting; + } + + updateGeometry(options: UpdateGeometryOptions) { + const { invalidateHeight, noDefer = false } = options; + if (invalidateHeight) { + this.isHeightActual = false; + } + if (!this.loaded) { + const viewportWidth = this.assetStreamManager.viewportWidth; + if (!this.isHeightActual) { + const unwrappedWidth = (3 / 2) * this.assetsCount * this.assetStreamManager.rowHeight * (7 / 10); + const rows = Math.ceil(unwrappedWidth / viewportWidth); + const height = 51 + Math.max(1, rows) * this.assetStreamManager.rowHeight; + this.height = height; + } + return; + } + this.layout(noDefer); } } diff --git a/web/src/lib/managers/timeline-manager/day-group.svelte.ts b/web/src/lib/managers/timeline-manager/day-group.svelte.ts index c71401587d..b27e8373b8 100644 --- a/web/src/lib/managers/timeline-manager/day-group.svelte.ts +++ b/web/src/lib/managers/timeline-manager/day-group.svelte.ts @@ -133,7 +133,7 @@ export class DayGroup { } unprocessedIds.delete(assetId); processedIds.add(assetId); - if (remove || this.monthGroup.timelineManager.isExcluded(asset)) { + if (remove || this.monthGroup.assetStreamManager.isExcluded(asset)) { this.viewerAssets.splice(index, 1); changedGeometry = true; } diff --git a/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts index dc1d00ae6a..c90d4a0b18 100644 --- a/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts @@ -1,71 +1,29 @@ -import type { PhotostreamManager } from '$lib/managers/photostream-manager/PhotostreamManager.svelte'; -import type { PhotostreamSegment } from '$lib/managers/photostream-manager/PhotostreamSegment.svelte'; +import { isIntersecting, type AssetStreamManager } from '$lib/managers/AssetStreamManager/AssetStreamManager.svelte'; import { TUNABLES } from '$lib/utils/tunables'; const { TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM }, } = TUNABLES; -export function updateIntersectionMonthGroup(timelineManager: PhotostreamManager, month: PhotostreamSegment) { - const actuallyIntersecting = calculateSegmentIntersecting(timelineManager, month, 0, 0); - let preIntersecting = false; - if (!actuallyIntersecting) { - preIntersecting = calculateSegmentIntersecting( - timelineManager, - month, - INTERSECTION_EXPAND_TOP, - INTERSECTION_EXPAND_BOTTOM, - ); - } - month.updateIntersection({ intersecting: actuallyIntersecting || preIntersecting, actuallyIntersecting }); -} - -/** - * General function to check if a rectangular region intersects with a window. - * @param regionTop - Top position of the region to check - * @param regionBottom - Bottom position of the region to check - * @param windowTop - Top position of the window - * @param windowBottom - Bottom position of the window - * @returns true if the region intersects with the window - */ -export function isIntersecting(regionTop: number, regionBottom: number, windowTop: number, windowBottom: number) { - return ( - (regionTop >= windowTop && regionTop < windowBottom) || - (regionBottom >= windowTop && regionBottom < windowBottom) || - (regionTop < windowTop && regionBottom >= windowBottom) - ); -} - -export function calculateSegmentIntersecting( - timelineManager: PhotostreamManager, - monthGroup: PhotostreamSegment, - expandTop: number, - expandBottom: number, -) { - const monthGroupTop = monthGroup.top; - const monthGroupBottom = monthGroupTop + monthGroup.height; - const topWindow = timelineManager.visibleWindow.top - expandTop; - const bottomWindow = timelineManager.visibleWindow.bottom + expandBottom; - - return isIntersecting(monthGroupTop, monthGroupBottom, topWindow, bottomWindow); -} - /** * Calculate intersection for viewer assets with additional parameters like header height and scroll compensation */ export function calculateViewerAssetIntersecting( - timelineManager: PhotostreamManager, + assetStreamManager: AssetStreamManager, positionTop: number, positionHeight: number, expandTop: number = INTERSECTION_EXPAND_TOP, expandBottom: number = INTERSECTION_EXPAND_BOTTOM, ) { - const scrollCompensationHeightDelta = timelineManager.scrollCompensation?.heightDelta ?? 0; + const scrollCompensationHeightDelta = assetStreamManager.scrollCompensation?.heightDelta ?? 0; const topWindow = - timelineManager.visibleWindow.top - timelineManager.headerHeight - expandTop + scrollCompensationHeightDelta; + assetStreamManager.visibleWindow.top - assetStreamManager.headerHeight - expandTop + scrollCompensationHeightDelta; const bottomWindow = - timelineManager.visibleWindow.bottom + timelineManager.headerHeight + expandBottom + scrollCompensationHeightDelta; + assetStreamManager.visibleWindow.bottom + + assetStreamManager.headerHeight + + expandBottom + + scrollCompensationHeightDelta; const positionBottom = positionTop + positionHeight; diff --git a/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts index e288b7e6b1..ca84afb0b8 100644 --- a/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts @@ -1,30 +1,5 @@ -import type { PhotostreamManager } from '$lib/managers/photostream-manager/PhotostreamManager.svelte'; -import type { PhotostreamSegment } from '$lib/managers/photostream-manager/PhotostreamSegment.svelte'; import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import type { MonthGroup } from '../month-group.svelte'; -import type { UpdateGeometryOptions } from '../types'; - -export function updateGeometry( - timelineManager: PhotostreamManager, - month: PhotostreamSegment, - options: UpdateGeometryOptions, -) { - const { invalidateHeight, noDefer = false } = options; - if (invalidateHeight) { - month.isHeightActual = false; - } - if (!month.loaded) { - const viewportWidth = timelineManager.viewportWidth; - if (!month.isHeightActual) { - const unwrappedWidth = (3 / 2) * month.assetsCount * timelineManager.rowHeight * (7 / 10); - const rows = Math.ceil(unwrappedWidth / viewportWidth); - const height = 51 + Math.max(1, rows) * timelineManager.rowHeight; - month.height = height; - } - return; - } - month.layout(noDefer); -} export function layoutMonthGroup(timelineManager: TimelineManager, month: MonthGroup, noDefer: boolean = false) { let cumulativeHeight = 0; diff --git a/web/src/lib/managers/timeline-manager/internal/operations-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/operations-support.svelte.ts index aff2dd9b65..ba0e1647c7 100644 --- a/web/src/lib/managers/timeline-manager/internal/operations-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/operations-support.svelte.ts @@ -1,12 +1,10 @@ import { setDifference, type TimelineDate } from '$lib/utils/timeline-util'; import { AssetOrder } from '@immich/sdk'; - import { SvelteSet } from 'svelte/reactivity'; import { GroupInsertionCache } from '../group-insertion-cache.svelte'; import { MonthGroup } from '../month-group.svelte'; import type { TimelineManager } from '../timeline-manager.svelte'; import type { AssetOperation, TimelineAsset } from '../types'; -import { updateGeometry } from './layout-support.svelte'; import { getMonthGroupByDate } from './search-support.svelte'; export function addAssetsToMonthGroups( @@ -17,24 +15,23 @@ export function addAssetsToMonthGroups( if (assets.length === 0) { return; } - const addContext = new GroupInsertionCache(); const updatedMonthGroups = new SvelteSet(); - const monthCount = timelineManager.months.length; + const monthCount = timelineManager.segments.length; for (const asset of assets) { let month = getMonthGroupByDate(timelineManager, asset.localDateTime); if (!month) { month = new MonthGroup(timelineManager, asset.localDateTime, 1, true, options.order); - timelineManager.months.push(month); + timelineManager.segments.push(month); } month.addTimelineAsset(asset, addContext); updatedMonthGroups.add(month); } - if (timelineManager.months.length !== monthCount) { - timelineManager.months.sort((a, b) => { + if (timelineManager.segments.length !== monthCount) { + timelineManager.segments.sort((a, b) => { return a.yearMonth.year === b.yearMonth.year ? b.yearMonth.month - a.yearMonth.month : b.yearMonth.year - a.yearMonth.year; @@ -51,7 +48,7 @@ export function addAssetsToMonthGroups( for (const month of addContext.updatedBuckets) { month.sortDayGroups(); - updateGeometry(timelineManager, month, { invalidateHeight: true }); + month.updateGeometry({ invalidateHeight: true }); } timelineManager.updateIntersections(); } @@ -70,7 +67,7 @@ export function runAssetOperation( let idsToProcess = new SvelteSet(ids); const idsProcessed = new SvelteSet(); const combinedMoveAssets: { asset: TimelineAsset; date: TimelineDate }[][] = []; - for (const month of timelineManager.months) { + for (const month of timelineManager.segments) { if (idsToProcess.size > 0) { const { moveAssets, processedIds, changedGeometry } = month.runAssetOperation(idsToProcess, operation); if (moveAssets.length > 0) { @@ -94,7 +91,7 @@ export function runAssetOperation( } const changedGeometry = changedMonthGroups.size > 0; for (const month of changedMonthGroups) { - updateGeometry(timelineManager, month, { invalidateHeight: true }); + month.updateGeometry({ invalidateHeight: true }); } if (changedGeometry) { timelineManager.updateIntersections(); diff --git a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts index 7e6ae734dc..22f410e518 100644 --- a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts @@ -32,7 +32,7 @@ export async function getAssetWithOffset( } export function findMonthGroupForAsset(timelineManager: TimelineManager, id: string) { - for (const month of timelineManager.months) { + for (const month of timelineManager.segments) { const asset = month.findAssetById({ id }); if (asset) { return { monthGroup: month, asset }; @@ -44,7 +44,7 @@ export function getMonthGroupByDate( timelineManager: TimelineManager, targetYearMonth: TimelineYearMonth, ): MonthGroup | undefined { - return timelineManager.months.find( + return timelineManager.segments.find( (month) => month.yearMonth.year === targetYearMonth.year && month.yearMonth.month === targetYearMonth.month, ); } @@ -136,7 +136,7 @@ export async function retrieveRange(timelineManager: TimelineManager, start: Ass } export function findMonthGroupForDate(timelineManager: TimelineManager, targetYearMonth: TimelineYearMonth) { - for (const month of timelineManager.months) { + for (const month of timelineManager.segments) { const { year, month: monthNum } = month.yearMonth; if (monthNum === targetYearMonth.month && year === targetYearMonth.year) { return month; diff --git a/web/src/lib/managers/timeline-manager/month-group.svelte.ts b/web/src/lib/managers/timeline-manager/month-group.svelte.ts index 1da1628ade..197a690513 100644 --- a/web/src/lib/managers/timeline-manager/month-group.svelte.ts +++ b/web/src/lib/managers/timeline-manager/month-group.svelte.ts @@ -1,8 +1,5 @@ -import { - PhotostreamSegment, - type SegmentIdentifier, -} from '$lib/managers/photostream-manager/PhotostreamSegment.svelte'; -import { layoutMonthGroup, updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte'; +import { AssetStreamSegment, type SegmentIdentifier } from '$lib/managers/AssetStreamManager/AssetStreamSegment.svelte'; +import { layoutMonthGroup } from '$lib/managers/timeline-manager/internal/layout-support.svelte'; import { loadFromTimeBuckets } from '$lib/managers/timeline-manager/internal/load-support.svelte'; import { formatGroupTitle, @@ -24,7 +21,7 @@ import { getSegmentIdentifier, type TimelineManager } from './timeline-manager.s import type { AssetDescriptor, AssetOperation, Direction, MoveAsset, TimelineAsset } from './types'; import { ViewerAsset } from './viewer-asset.svelte'; -export class MonthGroup extends PhotostreamSegment { +export class MonthGroup extends AssetStreamSegment { dayGroups: DayGroup[] = $state([]); #sortOrder: AssetOrder = AssetOrder.Desc; @@ -55,7 +52,7 @@ export class MonthGroup extends PhotostreamSegment { return this.#identifier; } - get timelineManager() { + get assetStreamManager() { return this.#timelineManager; } @@ -64,7 +61,7 @@ export class MonthGroup extends PhotostreamSegment { } fetch(signal: AbortSignal): Promise { - return loadFromTimeBuckets(this.timelineManager, this, this.timelineManager.options, signal); + return loadFromTimeBuckets(this.assetStreamManager, this, this.assetStreamManager.options, signal); } get lastDayGroup() { @@ -210,15 +207,6 @@ export class MonthGroup extends PhotostreamSegment { addContext.changedDayGroups.add(dayGroup); } - get id() { - return this.viewId; - } - - get viewId() { - const { year, month } = this.yearMonth; - return year + '-' + month; - } - findDayGroupForAsset(asset: TimelineAsset) { for (const group of this.dayGroups) { if (group.viewerAssets.some((viewerAsset) => viewerAsset.id === asset.id)) { @@ -240,7 +228,7 @@ export class MonthGroup extends PhotostreamSegment { console.warn('No position for asset'); break; } - return this.top + group.top + viewerAsset.position.top + this.timelineManager.headerHeight; + return this.top + group.top + viewerAsset.position.top + this.assetStreamManager.headerHeight; } } return -1; @@ -292,13 +280,13 @@ export class MonthGroup extends PhotostreamSegment { } layout(noDefer: boolean) { - layoutMonthGroup(this.timelineManager, this, noDefer); + layoutMonthGroup(this.assetStreamManager, this, noDefer); } #clearDeferredLayout() { const hasDeferred = this.dayGroups.some((group) => group.deferredLayout); if (hasDeferred) { - updateGeometry(this.timelineManager, this, { invalidateHeight: true, noDefer: true }); + this.updateGeometry({ invalidateHeight: true, noDefer: true }); for (const group of this.dayGroups) { group.deferredLayout = false; } @@ -306,8 +294,7 @@ export class MonthGroup extends PhotostreamSegment { } updateIntersection({ intersecting, actuallyIntersecting }: { intersecting: boolean; actuallyIntersecting: boolean }) { - this.intersecting = intersecting; - this.actuallyIntersecting = actuallyIntersecting; + super.updateIntersection({ intersecting, actuallyIntersecting }); if (intersecting) { this.#clearDeferredLayout(); } diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts index ba37a753a3..e8edc9399c 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts @@ -1,10 +1,11 @@ import { sdkMock } from '$lib/__mocks__/sdk.mock'; import { getMonthGroupByDate } from '$lib/managers/timeline-manager/internal/search-support.svelte'; +import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { AbortError } from '$lib/utils'; import { fromISODateTimeUTCToObject } from '$lib/utils/timeline-util'; import { type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk'; import { timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory'; -import { TimelineManager } from './timeline-manager.svelte'; +import { getSegmentIdentifier } from './timeline-manager.svelte'; import type { TimelineAsset } from './types'; async function getAssets(timelineManager: TimelineManager) { @@ -80,7 +81,7 @@ describe('TimelineManager', () => { }); it('calculates month height', () => { - const plainMonths = timelineManager.months.map((month) => ({ + const plainMonths = timelineManager.segments.map((month) => ({ year: month.yearMonth.year, month: month.yearMonth.month, height: month.height, @@ -96,7 +97,7 @@ describe('TimelineManager', () => { }); it('calculates timeline height', () => { - expect(timelineManager.timelineHeight).toBe(12566); + expect(timelineManager.streamViewerHeight).toBe(12566); }); }); @@ -193,7 +194,7 @@ describe('TimelineManager', () => { }); it('is empty initially', () => { - expect(timelineManager.months.length).toEqual(0); + expect(timelineManager.segments.length).toEqual(0); expect(timelineManager.assetCount).toEqual(0); }); @@ -205,12 +206,12 @@ describe('TimelineManager', () => { ); timelineManager.addAssets([asset]); - expect(timelineManager.months.length).toEqual(1); + expect(timelineManager.segments.length).toEqual(1); expect(timelineManager.assetCount).toEqual(1); - expect(timelineManager.months[0].assets.length).toEqual(1); - expect(timelineManager.months[0].yearMonth.year).toEqual(2024); - expect(timelineManager.months[0].yearMonth.month).toEqual(1); - expect(timelineManager.months[0].getFirstAsset().id).toEqual(asset.id); + expect(timelineManager.segments[0].assets.length).toEqual(1); + expect(timelineManager.segments[0].yearMonth.year).toEqual(2024); + expect(timelineManager.segments[0].yearMonth.month).toEqual(1); + expect(timelineManager.segments[0].getFirstAsset().id).toEqual(asset.id); }); it('adds assets to existing month', () => { @@ -222,11 +223,11 @@ describe('TimelineManager', () => { timelineManager.addAssets([assetOne]); timelineManager.addAssets([assetTwo]); - expect(timelineManager.months.length).toEqual(1); + expect(timelineManager.segments.length).toEqual(1); expect(timelineManager.assetCount).toEqual(2); - expect(timelineManager.months[0].assets.length).toEqual(2); - expect(timelineManager.months[0].yearMonth.year).toEqual(2024); - expect(timelineManager.months[0].yearMonth.month).toEqual(1); + expect(timelineManager.segments[0].assets.length).toEqual(2); + expect(timelineManager.segments[0].yearMonth.year).toEqual(2024); + expect(timelineManager.segments[0].yearMonth.month).toEqual(1); }); it('orders assets in months by descending date', () => { @@ -273,15 +274,15 @@ describe('TimelineManager', () => { ); timelineManager.addAssets([assetOne, assetTwo, assetThree]); - expect(timelineManager.months.length).toEqual(3); - expect(timelineManager.months[0].yearMonth.year).toEqual(2024); - expect(timelineManager.months[0].yearMonth.month).toEqual(4); + expect(timelineManager.segments.length).toEqual(3); + expect(timelineManager.segments[0].yearMonth.year).toEqual(2024); + expect(timelineManager.segments[0].yearMonth.month).toEqual(4); - expect(timelineManager.months[1].yearMonth.year).toEqual(2024); - expect(timelineManager.months[1].yearMonth.month).toEqual(1); + expect(timelineManager.segments[1].yearMonth.year).toEqual(2024); + expect(timelineManager.segments[1].yearMonth.month).toEqual(1); - expect(timelineManager.months[2].yearMonth.year).toEqual(2023); - expect(timelineManager.months[2].yearMonth.month).toEqual(1); + expect(timelineManager.segments[2].yearMonth.year).toEqual(2023); + expect(timelineManager.segments[2].yearMonth.month).toEqual(1); }); it('updates existing asset', () => { @@ -320,7 +321,7 @@ describe('TimelineManager', () => { it('ignores non-existing assets', () => { timelineManager.updateAssets([deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build())]); - expect(timelineManager.months.length).toEqual(0); + expect(timelineManager.segments.length).toEqual(0); expect(timelineManager.assetCount).toEqual(0); }); @@ -330,11 +331,11 @@ describe('TimelineManager', () => { timelineManager.addAssets([asset]); expect(timelineManager.assetCount).toEqual(1); - expect(timelineManager.months[0].getFirstAsset().isFavorite).toEqual(false); + expect(timelineManager.segments[0].getFirstAsset().isFavorite).toEqual(false); timelineManager.updateAssets([updatedAsset]); expect(timelineManager.assetCount).toEqual(1); - expect(timelineManager.months[0].getFirstAsset().isFavorite).toEqual(true); + expect(timelineManager.segments[0].getFirstAsset().isFavorite).toEqual(true); }); it('asset moves months when asset date changes', () => { @@ -349,12 +350,12 @@ describe('TimelineManager', () => { }); timelineManager.addAssets([asset]); - expect(timelineManager.months.length).toEqual(1); + expect(timelineManager.segments.length).toEqual(1); expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })).not.toBeUndefined(); expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.assets.length).toEqual(1); timelineManager.updateAssets([updatedAsset]); - expect(timelineManager.months.length).toEqual(2); + expect(timelineManager.segments.length).toEqual(2); expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })).not.toBeUndefined(); expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.assets.length).toEqual(0); expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 3 })).not.toBeUndefined(); @@ -384,8 +385,8 @@ describe('TimelineManager', () => { timelineManager.removeAssets(['', 'invalid', '4c7d9acc']); expect(timelineManager.assetCount).toEqual(2); - expect(timelineManager.months.length).toEqual(1); - expect(timelineManager.months[0].assets.length).toEqual(2); + expect(timelineManager.segments.length).toEqual(1); + expect(timelineManager.segments[0].assets.length).toEqual(2); }); it('removes asset from month', () => { @@ -398,8 +399,8 @@ describe('TimelineManager', () => { timelineManager.removeAssets([assetOne.id]); expect(timelineManager.assetCount).toEqual(1); - expect(timelineManager.months.length).toEqual(1); - expect(timelineManager.months[0].assets.length).toEqual(1); + expect(timelineManager.segments.length).toEqual(1); + expect(timelineManager.segments[0].assets.length).toEqual(1); }); it('does not remove month when empty', () => { @@ -412,7 +413,7 @@ describe('TimelineManager', () => { timelineManager.removeAssets(assets.map((asset) => asset.id)); expect(timelineManager.assetCount).toEqual(0); - expect(timelineManager.months.length).toEqual(1); + expect(timelineManager.segments.length).toEqual(1); }); }); @@ -537,7 +538,7 @@ describe('TimelineManager', () => { it('returns null when no more assets', async () => { await timelineManager.loadSegment(getSegmentIdentifier({ year: 2024, month: 3 })); - expect(await timelineManager.getLaterAsset(timelineManager.months[0].getFirstAsset())).toBeUndefined(); + expect(await timelineManager.getLaterAsset(timelineManager.segments[0].getFirstAsset())).toBeUndefined(); }); }); diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts index 7da0bcf5a5..cad1a21f6b 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -1,15 +1,5 @@ -import { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk'; - +import { AssetStreamManager } from '$lib/managers/AssetStreamManager/AssetStreamManager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; - -import { CancellableTask } from '$lib/utils/cancellable-task'; -import { toTimelineAsset, type TimelineDateTime, type TimelineYearMonth } from '$lib/utils/timeline-util'; - -import { isEqual } from 'lodash-es'; -import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity'; - -import { PhotostreamManager } from '$lib/managers/photostream-manager/PhotostreamManager.svelte'; -import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte'; import { addAssetsToMonthGroups, runAssetOperation, @@ -22,6 +12,11 @@ import { retrieveRange as retrieveRangeUtil, } from '$lib/managers/timeline-manager/internal/search-support.svelte'; import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte'; +import { CancellableTask } from '$lib/utils/cancellable-task'; +import { toTimelineAsset, type TimelineDateTime, type TimelineYearMonth } from '$lib/utils/timeline-util'; +import { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk'; +import { isEqual } from 'lodash-es'; +import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity'; import { DayGroup } from './day-group.svelte'; import { isMismatched, updateObject } from './internal/utils.svelte'; import { MonthGroup } from './month-group.svelte'; @@ -34,18 +29,7 @@ import type { TimelineManagerOptions, } from './types'; -export const getSegmentIdentifier = (yearMonth: TimelineYearMonth | TimelineDateTime) => ({ - id: () => { - return yearMonth.year + '-' + yearMonth.month; - }, - matches: (segment: MonthGroup) => { - return ( - segment.yearMonth && segment.yearMonth.year === yearMonth.year && segment.yearMonth.month === yearMonth.month - ); - }, -}); - -export class TimelineManager extends PhotostreamManager { +export class TimelineManager extends AssetStreamManager { albumAssets: Set = new SvelteSet(); scrubberMonths: ScrubberMonth[] = $state([]); scrubberTimelineHeight: number = $state(0); @@ -71,7 +55,7 @@ export class TimelineManager extends PhotostreamManager { #websocketSupport: WebsocketSupport | undefined; #options: TimelineManagerOptions = TimelineManager.#INIT_OPTIONS; - get months() { + get segments() { return this.#months; } @@ -97,13 +81,13 @@ export class TimelineManager extends PhotostreamManager { *monthGroupIterator(options?: { direction?: Direction; startMonthGroup?: MonthGroup }) { const isEarlier = options?.direction === 'earlier'; let startIndex = options?.startMonthGroup - ? this.months.indexOf(options.startMonthGroup) + ? this.segments.indexOf(options.startMonthGroup) : isEarlier ? 0 - : this.months.length - 1; + : this.segments.length - 1; - while (startIndex >= 0 && startIndex < this.months.length) { - yield this.months[startIndex]; + while (startIndex >= 0 && startIndex < this.segments.length) { + yield this.segments[startIndex]; startIndex += isEarlier ? 1 : -1; } } @@ -177,14 +161,14 @@ export class TimelineManager extends PhotostreamManager { } #createScrubberMonths() { - this.scrubberMonths = this.months.map((month) => ({ + this.scrubberMonths = this.segments.map((month) => ({ assetCount: month.assetsCount, year: month.yearMonth.year, month: month.yearMonth.month, title: month.monthGroupTitle, height: month.height, })); - this.scrubberTimelineHeight = this.timelineHeight; + this.scrubberTimelineHeight = this.streamViewerHeight; } addAssets(assets: TimelineAsset[]) { @@ -238,7 +222,7 @@ export class TimelineManager extends PhotostreamManager { let accumulatedCount = 0; let randomMonth: MonthGroup | undefined = undefined; - for (const month of this.months) { + for (const month of this.segments) { if (randomAssetIndex < accumulatedCount + month.assetsCount) { randomMonth = month; break; @@ -302,14 +286,14 @@ export class TimelineManager extends PhotostreamManager { } refreshLayout() { - for (const month of this.months) { - updateGeometry(this, month, { invalidateHeight: true }); + for (const month of this.segments) { + month.updateGeometry({ invalidateHeight: true }); } this.updateIntersections(); } getFirstAsset(): TimelineAsset | undefined { - return this.months[0]?.getFirstAsset(); + return this.segments[0]?.getFirstAsset(); } async getLaterAsset( @@ -357,3 +341,13 @@ export class TimelineManager extends PhotostreamManager { return this.#options.order ?? AssetOrder.Desc; } } +export const getSegmentIdentifier = (yearMonth: TimelineYearMonth | TimelineDateTime) => ({ + get id() { + return yearMonth.year + '-' + yearMonth.month; + }, + matches: (segment: MonthGroup) => { + return ( + segment.yearMonth && segment.yearMonth.year === yearMonth.year && segment.yearMonth.month === yearMonth.month + ); + }, +}); diff --git a/web/src/lib/managers/timeline-manager/viewer-asset.svelte.ts b/web/src/lib/managers/timeline-manager/viewer-asset.svelte.ts index b6e28df576..47d303b5a7 100644 --- a/web/src/lib/managers/timeline-manager/viewer-asset.svelte.ts +++ b/web/src/lib/managers/timeline-manager/viewer-asset.svelte.ts @@ -12,7 +12,7 @@ export class ViewerAsset { return false; } - const store = this.#group.monthGroup.timelineManager; + const store = this.#group.monthGroup.assetStreamManager; const positionTop = this.#group.absoluteDayGroupTop + this.position.top; return calculateViewerAssetIntersecting(store, positionTop, this.position.height); diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index f4954b86d4..5c446e7a35 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -512,7 +512,7 @@ export const selectAllAssets = async (timelineManager: TimelineManager, assetInt isSelectingAllAssets.set(true); try { - for (const monthGroup of timelineManager.months) { + for (const monthGroup of timelineManager.segments) { await monthGroup.load(false); if (!get(isSelectingAllAssets)) { diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 8c8b84ce16..de3566761c 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -145,7 +145,7 @@ const asset = $slideshowNavigation === SlideshowNavigation.Shuffle ? await timelineManager.getRandomAsset() - : timelineManager.months[0]?.dayGroups[0]?.viewerAssets[0]?.asset; + : timelineManager.segments[0]?.dayGroups[0]?.viewerAssets[0]?.asset; if (asset) { handlePromiseError(setAssetId(asset.id).then(() => ($slideshowState = SlideshowState.PlaySlideshow))); }