diff --git a/web/src/lib/managers/photostream-manager/PhotostreamManager.svelte.ts b/web/src/lib/managers/photostream-manager/PhotostreamManager.svelte.ts index d9b8827c60..dba14d5e25 100644 --- a/web/src/lib/managers/photostream-manager/PhotostreamManager.svelte.ts +++ b/web/src/lib/managers/photostream-manager/PhotostreamManager.svelte.ts @@ -305,4 +305,63 @@ export abstract class PhotostreamManager { // Return the IDs that were not found/removed return assetIds.filter((id) => !removedIds.includes(id)); } + + findNextAsset(currentAssetId: string): { id: string } | undefined { + for (let segmentIndex = 0; segmentIndex < this.months.length; segmentIndex++) { + const segment = this.months[segmentIndex]; + const assetIndex = segment.assets.findIndex((asset) => asset.id === currentAssetId); + + if (assetIndex !== -1) { + // Found the current asset + if (assetIndex < segment.assets.length - 1) { + // Next asset is in the same segment + return segment.assets[assetIndex + 1]; + } else if (segmentIndex < this.months.length - 1) { + // Next asset is in the next segment + const nextSegment = this.months[segmentIndex + 1]; + if (nextSegment.assets.length > 0) { + return nextSegment.assets[0]; + } + } + break; + } + } + return undefined; + } + + findPreviousAsset(currentAssetId: string): { id: string } | undefined { + for (let segmentIndex = 0; segmentIndex < this.months.length; segmentIndex++) { + const segment = this.months[segmentIndex]; + const assetIndex = segment.assets.findIndex((asset) => asset.id === currentAssetId); + + if (assetIndex !== -1) { + // Found the current asset + if (assetIndex > 0) { + // Previous asset is in the same segment + return segment.assets[assetIndex - 1]; + } else if (segmentIndex > 0) { + // Previous asset is in the previous segment + const previousSegment = this.months[segmentIndex - 1]; + if (previousSegment.assets.length > 0) { + return previousSegment.assets.at(-1); + } + } + break; + } + } + return undefined; + } + + findRandomAsset(): { id: string } | undefined { + // Get all loaded assets across all segments + const allAssets = this.months.flatMap((segment) => segment.assets); + + if (allAssets.length === 0) { + return undefined; + } + + // Return a random asset + const randomIndex = Math.floor(Math.random() * allAssets.length); + return allAssets[randomIndex]; + } } diff --git a/web/src/lib/managers/photostream-manager/PhotostreamSegment.svelte.ts b/web/src/lib/managers/photostream-manager/PhotostreamSegment.svelte.ts index f9a958a12c..6dd1ad93a8 100644 --- a/web/src/lib/managers/photostream-manager/PhotostreamSegment.svelte.ts +++ b/web/src/lib/managers/photostream-manager/PhotostreamSegment.svelte.ts @@ -7,6 +7,7 @@ import type { PhotostreamManager } from '$lib/managers/photostream-manager/Photo import { getTestHook } from '$lib/managers/photostream-manager/TestHooks.svelte'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { ViewerAsset } from '$lib/managers/timeline-manager/viewer-asset.svelte'; +import { getJustifiedLayoutFromAssets, getPosition } from '$lib/utils/layout-utils'; export type SegmentIdentifier = { matches(segment: PhotostreamSegment): boolean; @@ -144,12 +145,31 @@ export abstract class PhotostreamSegment { this.loader?.cancel(); } - layout(_?: boolean) {} + layout(): void { + const timelineAssets = this.viewerAssets.map((viewerAsset) => viewerAsset.asset); + const layoutOptions = this.timelineManager.layoutOptions; + const geometry = getJustifiedLayoutFromAssets(timelineAssets, layoutOptions); + this.height = timelineAssets.length === 0 ? 0 : geometry.containerHeight + this.timelineManager.headerHeight; + for (let i = 0; i < this.viewerAssets.length; i++) { + const position = getPosition(geometry, i); + this.viewerAssets[i].position = position; + } + } updateIntersection({ intersecting, actuallyIntersecting }: { intersecting: boolean; actuallyIntersecting: boolean }) { this.intersecting = intersecting; this.actuallyIntersecting = actuallyIntersecting; } - abstract findAssetAbsolutePosition(assetId: string): number; + findAssetAbsolutePosition(assetId: string) { + const viewerAsset = this.viewerAssets.find((viewAsset) => viewAsset.id === assetId); + if (viewerAsset) { + if (!viewerAsset.position) { + console.warn('No position for asset'); + return -1; + } + return this.top + viewerAsset.position.top + this.timelineManager.headerHeight; + } + return -1; + } } diff --git a/web/src/lib/managers/searchresults-manager/SearchResultsSegment.svelte.ts b/web/src/lib/managers/searchresults-manager/SearchResultsSegment.svelte.ts index 0ced29d4fd..fd6175c141 100644 --- a/web/src/lib/managers/searchresults-manager/SearchResultsSegment.svelte.ts +++ b/web/src/lib/managers/searchresults-manager/SearchResultsSegment.svelte.ts @@ -7,7 +7,6 @@ import type { SearchTerms, } from '$lib/managers/searchresults-manager/SearchResultsManager.svelte'; import { ViewerAsset } from '$lib/managers/timeline-manager/viewer-asset.svelte'; -import { getJustifiedLayoutFromAssets, getPosition } from '$lib/utils/layout-utils'; import { toTimelineAsset } from '$lib/utils/timeline-util'; import { TUNABLES } from '$lib/utils/tunables'; import { searchAssets, searchSmart } from '@immich/sdk'; @@ -72,18 +71,6 @@ export class SearchResultsSegment extends PhotostreamSegment { this.layout(); } - layout(): void { - const timelineAssets = this.#viewerAssets.map((viewerAsset) => viewerAsset.asset); - const layoutOptions = this.timelineManager.layoutOptions; - const geometry = getJustifiedLayoutFromAssets(timelineAssets, layoutOptions); - - this.height = timelineAssets.length === 0 ? 0 : geometry.containerHeight + this.timelineManager.headerHeight; - for (let i = 0; i < this.#viewerAssets.length; i++) { - const position = getPosition(geometry, i); - this.#viewerAssets[i].position = position; - } - } - get viewerAssets(): ViewerAsset[] { return this.#viewerAssets; } diff --git a/web/src/lib/managers/simple-photostream-manager/SimplePhotostreamManager.ts b/web/src/lib/managers/simple-photostream-manager/SimplePhotostreamManager.ts new file mode 100644 index 0000000000..34c5fa58db --- /dev/null +++ b/web/src/lib/managers/simple-photostream-manager/SimplePhotostreamManager.ts @@ -0,0 +1,50 @@ +import { PhotostreamManager } from '$lib/managers/photostream-manager/PhotostreamManager.svelte'; +import { + PhotostreamSegment, + type SegmentIdentifier, +} from '$lib/managers/photostream-manager/PhotostreamSegment.svelte'; +import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; +import { ViewerAsset } from '$lib/managers/timeline-manager/viewer-asset.svelte'; + +function createSimpleSegment(manager: SimplePhotostreamManager, assets: TimelineAsset[]) { + class SimpleSegment extends PhotostreamSegment { + #viewerAssets = $derived(assets.map((asset) => new ViewerAsset(this, asset))); + #identifier = { + matches: () => true, + }; + get timelineManager(): PhotostreamManager { + return manager; + } + get identifier(): SegmentIdentifier { + return this.#identifier; + } + get id(): string { + return 'one'; + } + protected fetch(): Promise { + return Promise.resolve(); + } + get viewerAssets(): ViewerAsset[] { + return this.#viewerAssets; + } + } + return new SimpleSegment(); +} + +export class SimplePhotostreamManager extends PhotostreamManager { + #assets: TimelineAsset[] = $state([]); + #segment: PhotostreamSegment; + + constructor() { + super(); + this.#segment = createSimpleSegment(this, this.#assets); + } + + set assets(assets: TimelineAsset[]) { + this.#assets = assets; + } + + get months(): PhotostreamSegment[] { + return [this.#segment]; + } +}