Compare commits
2 Commits
feat/timel
...
feat/timel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7806fe7d86 | ||
|
|
e95ca39e4f |
@@ -230,7 +230,7 @@
|
||||
|
||||
<div
|
||||
class="month-group"
|
||||
style:margin-bottom={timelineManager.createLayoutOptions().spacing + 'px'}
|
||||
style:margin-bottom={timelineManager.layoutOptions.spacing + 'px'}
|
||||
style:position="absolute"
|
||||
style:transform={`translate3d(0,${absoluteHeight}px,0)`}
|
||||
style:height={`${monthGroup.height}px`}
|
||||
|
||||
@@ -30,6 +30,13 @@ export abstract class PhotostreamManager {
|
||||
bottom: this.#scrollTop + this.viewportHeight,
|
||||
}));
|
||||
|
||||
layoutOptions = $derived({
|
||||
spacing: 2,
|
||||
heightTolerance: 0.15,
|
||||
rowHeight: this.rowHeight,
|
||||
rowWidth: Math.floor(this.viewportWidth),
|
||||
});
|
||||
|
||||
protected initTask = new CancellableTask(
|
||||
() => (this.isInitialized = true),
|
||||
() => (this.isInitialized = false),
|
||||
@@ -53,13 +60,9 @@ export abstract class PhotostreamManager {
|
||||
abstract get months(): PhotostreamSegment[];
|
||||
|
||||
setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }: TimelineManagerLayoutOptions) {
|
||||
let changed = false;
|
||||
changed ||= this.#setHeaderHeight(headerHeight);
|
||||
changed ||= this.#setGap(gap);
|
||||
changed ||= this.#setRowHeight(rowHeight);
|
||||
if (changed) {
|
||||
this.refreshLayout();
|
||||
}
|
||||
this.#setHeaderHeight(headerHeight);
|
||||
this.#setGap(gap);
|
||||
this.#setRowHeight(rowHeight);
|
||||
}
|
||||
|
||||
#setHeaderHeight(value: number) {
|
||||
@@ -203,17 +206,6 @@ export abstract class PhotostreamManager {
|
||||
this.updateIntersections();
|
||||
}
|
||||
|
||||
createLayoutOptions() {
|
||||
const viewportWidth = this.viewportWidth;
|
||||
|
||||
return {
|
||||
spacing: 2,
|
||||
heightTolerance: 0.15,
|
||||
rowHeight: this.#rowHeight,
|
||||
rowWidth: Math.floor(viewportWidth),
|
||||
};
|
||||
}
|
||||
|
||||
async loadSegment(identifier: SegmentIdentifier, options?: { cancelable: boolean }): Promise<void> {
|
||||
let cancelable = true;
|
||||
if (options) {
|
||||
@@ -248,13 +240,6 @@ export abstract class PhotostreamManager {
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
|
||||
refreshLayout() {
|
||||
for (const month of this.months) {
|
||||
updateGeometry(this, month, { invalidateHeight: true });
|
||||
}
|
||||
this.updateIntersections();
|
||||
}
|
||||
|
||||
getMaxScrollPercent() {
|
||||
const totalHeight = this.timelineHeight + this.bottomSectionHeight + this.topSectionHeight;
|
||||
return (totalHeight - this.viewportHeight) / totalHeight;
|
||||
@@ -320,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.createLayoutOptions();
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export function layoutMonthGroup(timelineManager: TimelineManager, month: MonthG
|
||||
let dayGroupRow = 0;
|
||||
let dayGroupCol = 0;
|
||||
|
||||
const options = timelineManager.createLayoutOptions();
|
||||
const options = timelineManager.layoutOptions;
|
||||
for (const dayGroup of month.dayGroups) {
|
||||
dayGroup.layout(options, noDefer);
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ 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,
|
||||
@@ -264,13 +263,6 @@ export class TimelineManager extends PhotostreamManager {
|
||||
return [...unprocessedIds];
|
||||
}
|
||||
|
||||
refreshLayout() {
|
||||
for (const month of this.months) {
|
||||
updateGeometry(this, month, { invalidateHeight: true });
|
||||
}
|
||||
this.updateIntersections();
|
||||
}
|
||||
|
||||
getFirstAsset(): TimelineAsset | undefined {
|
||||
return this.months[0]?.getFirstAsset();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user