feat(web): add asset navigation and simplify photostream architecture
- Add findNextAsset, findPreviousAsset, and findRandomAsset methods to PhotostreamManager - Move layout() and findAssetAbsolutePosition() to PhotostreamSegment base class - Create SimplePhotostreamManager for basic photostream functionalityw
This commit is contained in:
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user