Renames
parent
14dad831ee
commit
cc790604aa
|
|
@ -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}
|
||||
<Scrubber
|
||||
{timelineManager}
|
||||
height={timelineManager.viewportHeight}
|
||||
|
|
@ -610,7 +610,7 @@
|
|||
bind:this={timelineElement}
|
||||
id="virtual-timeline"
|
||||
class:invisible={showSkeleton}
|
||||
style:height={timelineManager.timelineHeight + 'px'}
|
||||
style:height={timelineManager.streamViewerHeight + 'px'}
|
||||
>
|
||||
<section
|
||||
use:resizeObserver={topSectionResizeObserver}
|
||||
|
|
@ -626,7 +626,7 @@
|
|||
{/if}
|
||||
</section>
|
||||
|
||||
{#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%"
|
||||
>
|
||||
<Skeleton
|
||||
height={monthGroup.height - monthGroup.timelineManager.headerHeight}
|
||||
height={monthGroup.height - monthGroup.assetStreamManager.headerHeight}
|
||||
title={monthGroup.monthGroupTitle}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -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)`}
|
||||
></div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<section
|
||||
class={[
|
||||
{ 'transition-all': !monthGroup.timelineManager.suspendTransitions },
|
||||
!monthGroup.timelineManager.suspendTransitions && `delay-${transitionDuration}`,
|
||||
{ 'transition-all': !monthGroup.assetStreamManager.suspendTransitions },
|
||||
!monthGroup.assetStreamManager.suspendTransitions && `delay-${transitionDuration}`,
|
||||
]}
|
||||
data-group
|
||||
style:position="absolute"
|
||||
|
|
@ -217,9 +217,9 @@
|
|||
onSelect={(asset) => 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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
|
@ -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<TaskStatus> {
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<MonthGroup>();
|
||||
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<string>();
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string> = 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
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue