Files
immich/web/src/lib/managers/timeline-manager/day-group.svelte.ts
midzelis 379678dbf3 refactor(web): rename intersecting/actuallyIntersecting to nearby/intersecting
Change-Id: Id6f63247441fe290e7732e489f73e8c16a6a6964
2026-03-21 04:35:53 +00:00

158 lines
4.7 KiB
TypeScript

import { AssetOrder } from '@immich/sdk';
import type { CommonLayoutOptions } from '$lib/utils/layout-utils';
import { getJustifiedLayoutFromAssets } from '$lib/utils/layout-utils';
import { plainDateTimeCompare } from '$lib/utils/timeline-util';
import { SvelteSet } from 'svelte/reactivity';
import type { MonthGroup } from './month-group.svelte';
import type { Direction, MoveAsset, TimelineAsset } from './types';
import { ViewerAsset } from './viewer-asset.svelte';
export class DayGroup {
readonly monthGroup: MonthGroup;
readonly index: number;
readonly groupTitle: string;
readonly day: number;
viewerAssets: ViewerAsset[] = $state([]);
height = $state(0);
width = $state(0);
renderable = $derived.by(() => this.viewerAssets.some((viewAsset) => viewAsset.renderable));
#top: number = $state(0);
#start: number = $state(0);
#row = $state(0);
#col = $state(0);
#deferredLayout = false;
constructor(monthGroup: MonthGroup, index: number, day: number, groupTitle: string) {
this.index = index;
this.monthGroup = monthGroup;
this.day = day;
this.groupTitle = groupTitle;
}
get top() {
return this.#top;
}
set top(value: number) {
this.#top = value;
}
get start() {
return this.#start;
}
set start(value: number) {
this.#start = value;
}
get row() {
return this.#row;
}
set row(value: number) {
this.#row = value;
}
get col() {
return this.#col;
}
set col(value: number) {
this.#col = value;
}
get deferredLayout() {
return this.#deferredLayout;
}
set deferredLayout(value: boolean) {
this.#deferredLayout = value;
}
sortAssets(sortOrder: AssetOrder = AssetOrder.Desc) {
const sortFn = plainDateTimeCompare.bind(undefined, sortOrder === AssetOrder.Asc);
this.viewerAssets.sort((a, b) => sortFn(a.asset.fileCreatedAt, b.asset.fileCreatedAt));
}
getFirstAsset() {
return this.viewerAssets[0]?.asset;
}
*assetsIterator(options: { startAsset?: TimelineAsset; direction?: Direction } = {}) {
const isEarlier = (options?.direction ?? 'earlier') === 'earlier';
let assetIndex = options?.startAsset
? this.viewerAssets.findIndex((viewerAsset) => viewerAsset.asset.id === options.startAsset!.id)
: isEarlier
? 0
: this.viewerAssets.length - 1;
while (assetIndex >= 0 && assetIndex < this.viewerAssets.length) {
const viewerAsset = this.viewerAssets[assetIndex];
yield viewerAsset.asset;
assetIndex += isEarlier ? 1 : -1;
}
}
getAssets() {
return this.viewerAssets.map((viewerAsset) => viewerAsset.asset);
}
runAssetCallback(ids: Set<string>, callback: (asset: TimelineAsset) => void | { remove?: boolean }) {
const unprocessedIds = new SvelteSet<string>(ids);
const processedIds = new SvelteSet<string>();
const moveAssets: MoveAsset[] = [];
let changedGeometry = false;
if (ids.size === 0) {
return { moveAssets, processedIds, unprocessedIds, changedGeometry };
}
for (let index = this.viewerAssets.length - 1; index >= 0; index--) {
const { id: assetId, asset } = this.viewerAssets[index];
if (!ids.has(assetId)) {
continue;
}
const oldTime = { ...asset.localDateTime };
const callbackResult = callback(asset);
let remove = (callbackResult as { remove?: boolean } | undefined)?.remove ?? false;
const newTime = asset.localDateTime;
if (oldTime.year !== newTime.year || oldTime.month !== newTime.month || oldTime.day !== newTime.day) {
const { year, month, day } = newTime;
remove = true;
moveAssets.push({ asset, date: { year, month, day } });
}
unprocessedIds.delete(assetId);
processedIds.add(assetId);
if (remove || this.monthGroup.timelineManager.isExcluded(asset)) {
this.viewerAssets.splice(index, 1);
changedGeometry = true;
}
}
return { moveAssets, processedIds, unprocessedIds, changedGeometry };
}
layout(options: CommonLayoutOptions, noDefer: boolean) {
if (!noDefer && !this.monthGroup.renderable && !this.monthGroup.timelineManager.isScrollingOnLoad) {
this.#deferredLayout = true;
return;
}
const assets = this.viewerAssets.map((viewerAsset) => viewerAsset.asset!);
const geometry = getJustifiedLayoutFromAssets(assets, options);
this.width = geometry.containerWidth;
this.height = assets.length === 0 ? 0 : geometry.containerHeight;
// TODO: lazily get positions instead of loading them all here
for (let i = 0; i < this.viewerAssets.length; i++) {
this.viewerAssets[i].position = geometry.getPosition(i);
}
}
get absoluteDayGroupTop() {
return this.monthGroup.top + this.#top;
}
}