refactor(web): rename DayGroup to TimelineDay (#27446)

Rename DayGroup class to TimelineDay to better convey that it represents
a single day within the timeline. Updates the file, class, and all
references across 13 files.

Change-Id: I9faef9bad73cd5b11f40daaf5eb140dd6a6a6964
This commit is contained in:
Min Idzelis
2026-04-01 19:30:54 -04:00
committed by GitHub
parent c9e251c78c
commit 2166f07b1f
13 changed files with 153 additions and 147 deletions
@@ -1,64 +1,64 @@
import { setDifference, type TimelineDate } from '$lib/utils/timeline-util';
import { AssetOrder } from '@immich/sdk';
import type { DayGroup } from './day-group.svelte';
import type { MonthGroup } from './month-group.svelte';
import type { TimelineDay } from './timeline-day.svelte';
import type { TimelineAsset } from './types';
export class GroupInsertionCache {
#lookupCache: {
[year: number]: { [month: number]: { [day: number]: DayGroup } };
[year: number]: { [month: number]: { [day: number]: TimelineDay } };
} = {};
unprocessedAssets: TimelineAsset[] = [];
// eslint-disable-next-line svelte/prefer-svelte-reactivity
changedDayGroups = new Set<DayGroup>();
changedTimelineDays = new Set<TimelineDay>();
// eslint-disable-next-line svelte/prefer-svelte-reactivity
newDayGroups = new Set<DayGroup>();
newTimelineDays = new Set<TimelineDay>();
getDayGroup({ year, month, day }: TimelineDate): DayGroup | undefined {
getTimelineDay({ year, month, day }: TimelineDate): TimelineDay | undefined {
return this.#lookupCache[year]?.[month]?.[day];
}
setDayGroup(dayGroup: DayGroup, { year, month, day }: TimelineDate) {
setTimelineDay(timelineDay: TimelineDay, { year, month, day }: TimelineDate) {
if (!this.#lookupCache[year]) {
this.#lookupCache[year] = {};
}
if (!this.#lookupCache[year][month]) {
this.#lookupCache[year][month] = {};
}
this.#lookupCache[year][month][day] = dayGroup;
this.#lookupCache[year][month][day] = timelineDay;
}
get existingDayGroups() {
return setDifference(this.changedDayGroups, this.newDayGroups);
get existingTimelineDays() {
return setDifference(this.changedTimelineDays, this.newTimelineDays);
}
get updatedBuckets() {
// eslint-disable-next-line svelte/prefer-svelte-reactivity
const updated = new Set<MonthGroup>();
for (const group of this.changedDayGroups) {
for (const group of this.changedTimelineDays) {
updated.add(group.monthGroup);
}
return updated;
}
get bucketsWithNewDayGroups() {
get bucketsWithNewTimelineDays() {
// eslint-disable-next-line svelte/prefer-svelte-reactivity
const updated = new Set<MonthGroup>();
for (const group of this.newDayGroups) {
for (const group of this.newTimelineDays) {
updated.add(group.monthGroup);
}
return updated;
}
sort(monthGroup: MonthGroup, sortOrder: AssetOrder = AssetOrder.Desc) {
for (const group of this.changedDayGroups) {
for (const group of this.changedTimelineDays) {
group.sortAssets(sortOrder);
}
for (const group of this.newDayGroups) {
for (const group of this.newTimelineDays) {
group.sortAssets(sortOrder);
}
if (this.newDayGroups.size > 0) {
monthGroup.sortDayGroups();
if (this.newTimelineDays.size > 0) {
monthGroup.sortTimelineDays();
}
}
}
@@ -25,41 +25,41 @@ export function layoutMonthGroup(timelineManager: TimelineManager, month: MonthG
let cumulativeWidth = 0;
let currentRowHeight = 0;
let dayGroupRow = 0;
let dayGroupCol = 0;
let timelineDayRow = 0;
let timelineDayCol = 0;
const options = timelineManager.justifiedLayoutOptions;
for (const dayGroup of month.dayGroups) {
dayGroup.layout(options, noDefer);
for (const timelineDay of month.timelineDays) {
timelineDay.layout(options, noDefer);
// Calculate space needed for this item (including gap if not first in row)
const spaceNeeded = dayGroup.width + (dayGroupCol > 0 ? timelineManager.gap : 0);
const spaceNeeded = timelineDay.width + (timelineDayCol > 0 ? timelineManager.gap : 0);
const fitsInCurrentRow = cumulativeWidth + spaceNeeded <= timelineManager.viewportWidth;
if (fitsInCurrentRow) {
dayGroup.row = dayGroupRow;
dayGroup.col = dayGroupCol++;
dayGroup.start = cumulativeWidth;
dayGroup.top = cumulativeHeight;
timelineDay.row = timelineDayRow;
timelineDay.col = timelineDayCol++;
timelineDay.start = cumulativeWidth;
timelineDay.top = cumulativeHeight;
cumulativeWidth += dayGroup.width + timelineManager.gap;
cumulativeWidth += timelineDay.width + timelineManager.gap;
} else {
// Move to next row
cumulativeHeight += currentRowHeight;
cumulativeWidth = 0;
dayGroupRow++;
dayGroupCol = 0;
timelineDayRow++;
timelineDayCol = 0;
// Position at start of new row
dayGroup.row = dayGroupRow;
dayGroup.col = dayGroupCol;
dayGroup.start = 0;
dayGroup.top = cumulativeHeight;
timelineDay.row = timelineDayRow;
timelineDay.col = timelineDayCol;
timelineDay.start = 0;
timelineDay.top = cumulativeHeight;
dayGroupCol++;
cumulativeWidth += dayGroup.width + timelineManager.gap;
timelineDayCol++;
cumulativeWidth += timelineDay.width + timelineManager.gap;
}
currentRowHeight = dayGroup.height + timelineManager.headerHeight;
currentRowHeight = timelineDay.height + timelineManager.headerHeight;
}
// Add the height of the final row
@@ -60,10 +60,10 @@ async function getAssetByAssetOffset(
monthGroup: MonthGroup,
direction: Direction,
) {
const dayGroup = monthGroup.findDayGroupForAsset(asset);
const timelineDay = monthGroup.findTimelineDayForAsset(asset);
for await (const targetAsset of timelineManager.assetsIterator({
startMonthGroup: monthGroup,
startDayGroup: dayGroup,
startTimelineDay: timelineDay,
startAsset: asset,
direction,
})) {
@@ -79,10 +79,10 @@ async function getAssetByDayOffset(
monthGroup: MonthGroup,
direction: Direction,
) {
const dayGroup = monthGroup.findDayGroupForAsset(asset);
const timelineDay = monthGroup.findTimelineDayForAsset(asset);
for await (const targetAsset of timelineManager.assetsIterator({
startMonthGroup: monthGroup,
startDayGroup: dayGroup,
startTimelineDay: timelineDay,
startAsset: asset,
direction,
})) {
@@ -127,10 +127,10 @@ export async function retrieveRange(timelineManager: TimelineManager, start: Ass
}
const range: TimelineAsset[] = [];
const startDayGroup = startMonthGroup.findDayGroupForAsset(startAsset);
const startTimelineDay = startMonthGroup.findTimelineDayForAsset(startAsset);
for await (const targetAsset of timelineManager.assetsIterator({
startMonthGroup,
startDayGroup,
startTimelineDay,
startAsset,
})) {
range.push(targetAsset);
@@ -23,8 +23,8 @@ import {
isInViewport as isInViewportUtil,
} from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
import { SvelteSet } from 'svelte/reactivity';
import { DayGroup } from './day-group.svelte';
import { GroupInsertionCache } from './group-insertion-cache.svelte';
import { TimelineDay } from './timeline-day.svelte';
import type { TimelineManager } from './timeline-manager.svelte';
import type { AssetDescriptor, Direction, MoveAsset, TimelineAsset } from './types';
import { ViewerAsset } from './viewer-asset.svelte';
@@ -32,7 +32,7 @@ import { ViewerAsset } from './viewer-asset.svelte';
export class MonthGroup {
#viewportProximity: ViewportProximity = $state(ViewportProximity.FarFromViewport);
isLoaded: boolean = $state(false);
dayGroups: DayGroup[] = $state([]);
timelineDays: TimelineDay[] = $state([]);
readonly timelineManager: TimelineManager;
#height: number = $state(0);
@@ -44,7 +44,7 @@ export class MonthGroup {
assetsCount: number = $derived(
this.isLoaded
? this.dayGroups.reduce((accumulator, g) => accumulator + g.viewerAssets.length, 0)
? this.timelineDays.reduce((accumulator, g) => accumulator + g.viewerAssets.length, 0)
: this.#initialCount,
);
loader: CancellableTask | undefined;
@@ -72,7 +72,7 @@ export class MonthGroup {
this.isLoaded = true;
},
() => {
this.dayGroups = [];
this.timelineDays = [];
this.isLoaded = false;
},
this.#handleLoadError,
@@ -103,25 +103,28 @@ export class MonthGroup {
return isInViewportUtil(this.#viewportProximity);
}
get lastDayGroup() {
return this.dayGroups.at(-1);
get lastTimelineDay() {
return this.timelineDays.at(-1);
}
getFirstAsset() {
return this.dayGroups[0]?.getFirstAsset();
return this.timelineDays[0]?.getFirstAsset();
}
getAssets() {
// eslint-disable-next-line unicorn/no-array-reduce
return this.dayGroups.reduce((accumulator: TimelineAsset[], g: DayGroup) => accumulator.concat(g.getAssets()), []);
return this.timelineDays.reduce(
(accumulator: TimelineAsset[], g: TimelineDay) => accumulator.concat(g.getAssets()),
[],
);
}
sortDayGroups() {
sortTimelineDays() {
if (this.#sortOrder === AssetOrder.Asc) {
return this.dayGroups.sort((a, b) => a.day - b.day);
return this.timelineDays.sort((a, b) => a.day - b.day);
}
return this.dayGroups.sort((a, b) => b.day - a.day);
return this.timelineDays.sort((a, b) => b.day - a.day);
}
runAssetCallback(ids: Set<string>, callback: (asset: TimelineAsset) => void | { remove?: boolean }) {
@@ -133,15 +136,15 @@ export class MonthGroup {
changedGeometry: false,
};
}
const { dayGroups } = this;
const { timelineDays } = this;
let combinedChangedGeometry = false;
let idsToProcess = new SvelteSet(ids);
const idsProcessed = new SvelteSet<string>();
const combinedMoveAssets: MoveAsset[][] = [];
let index = dayGroups.length;
let index = timelineDays.length;
while (index--) {
if (idsToProcess.size > 0) {
const group = dayGroups[index];
const group = timelineDays[index];
const { moveAssets, processedIds, changedGeometry } = group.runAssetCallback(ids, callback);
if (moveAssets.length > 0) {
combinedMoveAssets.push(moveAssets);
@@ -152,7 +155,7 @@ export class MonthGroup {
}
combinedChangedGeometry = combinedChangedGeometry || changedGeometry;
if (group.viewerAssets.length === 0) {
dayGroups.splice(index, 1);
timelineDays.splice(index, 1);
combinedChangedGeometry = true;
}
}
@@ -215,12 +218,12 @@ export class MonthGroup {
return addContext.unprocessedAssets;
}
for (const group of addContext.existingDayGroups) {
for (const group of addContext.existingTimelineDays) {
group.sortAssets(this.#sortOrder);
}
if (addContext.newDayGroups.size > 0) {
this.sortDayGroups();
if (addContext.newTimelineDays.size > 0) {
this.sortTimelineDays();
}
addContext.sort(this, this.#sortOrder);
@@ -237,20 +240,20 @@ export class MonthGroup {
return;
}
let dayGroup = addContext.getDayGroup(localDateTime) || this.findDayGroupByDay(localDateTime.day);
if (dayGroup) {
addContext.setDayGroup(dayGroup, localDateTime);
let timelineDay = addContext.getTimelineDay(localDateTime) || this.findTimelineDayByDay(localDateTime.day);
if (timelineDay) {
addContext.setTimelineDay(timelineDay, localDateTime);
} else {
const groupTitle = formatGroupTitle(fromTimelinePlainDate(localDateTime));
dayGroup = new DayGroup(this, this.dayGroups.length, localDateTime.day, groupTitle);
this.dayGroups.push(dayGroup);
addContext.setDayGroup(dayGroup, localDateTime);
addContext.newDayGroups.add(dayGroup);
timelineDay = new TimelineDay(this, this.timelineDays.length, localDateTime.day, groupTitle);
this.timelineDays.push(timelineDay);
addContext.setTimelineDay(timelineDay, localDateTime);
addContext.newTimelineDays.add(timelineDay);
}
const viewerAsset = new ViewerAsset(dayGroup, timelineAsset);
dayGroup.viewerAssets.push(viewerAsset);
addContext.changedDayGroups.add(dayGroup);
const viewerAsset = new ViewerAsset(timelineDay, timelineAsset);
timelineDay.viewerAssets.push(viewerAsset);
addContext.changedTimelineDays.add(timelineDay);
}
get viewId() {
@@ -312,21 +315,21 @@ export class MonthGroup {
handleError(error, _$t('errors.failed_to_load_assets'));
}
findDayGroupForAsset(asset: TimelineAsset) {
for (const group of this.dayGroups) {
findTimelineDayForAsset(asset: TimelineAsset) {
for (const group of this.timelineDays) {
if (group.viewerAssets.some((viewerAsset) => viewerAsset.id === asset.id)) {
return group;
}
}
}
findDayGroupByDay(day: number) {
return this.dayGroups.find((group) => group.day === day);
findTimelineDayByDay(day: number) {
return this.timelineDays.find((group) => group.day === day);
}
findAssetAbsolutePosition(assetId: string) {
this.timelineManager.clearDeferredLayout(this);
for (const group of this.dayGroups) {
for (const group of this.timelineDays) {
const viewerAsset = group.viewerAssets.find((viewAsset) => viewAsset.id === assetId);
if (viewerAsset) {
if (!viewerAsset.position) {
@@ -341,18 +344,18 @@ export class MonthGroup {
}
}
*assetsIterator(options?: { startDayGroup?: DayGroup; startAsset?: TimelineAsset; direction?: Direction }) {
*assetsIterator(options?: { startTimelineDay?: TimelineDay; startAsset?: TimelineAsset; direction?: Direction }) {
const direction = options?.direction ?? 'earlier';
let { startAsset } = options ?? {};
const isEarlier = direction === 'earlier';
let groupIndex = options?.startDayGroup
? this.dayGroups.indexOf(options.startDayGroup)
let groupIndex = options?.startTimelineDay
? this.timelineDays.indexOf(options.startTimelineDay)
: isEarlier
? 0
: this.dayGroups.length - 1;
: this.timelineDays.length - 1;
while (groupIndex >= 0 && groupIndex < this.dayGroups.length) {
const group = this.dayGroups[groupIndex];
while (groupIndex >= 0 && groupIndex < this.timelineDays.length) {
const group = this.timelineDays[groupIndex];
yield* group.assetsIterator({ startAsset, direction });
startAsset = undefined;
groupIndex += isEarlier ? 1 : -1;
@@ -9,7 +9,7 @@ import type { MonthGroup } from './month-group.svelte';
import type { Direction, MoveAsset, TimelineAsset } from './types';
import { ViewerAsset } from './viewer-asset.svelte';
export class DayGroup {
export class TimelineDay {
readonly monthGroup: MonthGroup;
readonly index: number;
readonly groupTitle: string;
@@ -151,7 +151,7 @@ export class DayGroup {
}
}
get absoluteDayGroupTop() {
get absoluteTimelineDayTop() {
return this.monthGroup.top + this.#top;
}
}
@@ -26,9 +26,9 @@ import {
import { AssetOrder, getAssetInfo, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
import { clamp, isEqual } from 'lodash-es';
import { SvelteDate, SvelteSet } from 'svelte/reactivity';
import { DayGroup } from './day-group.svelte';
import { isMismatched, updateObject } from './internal/utils.svelte';
import { MonthGroup } from './month-group.svelte';
import { TimelineDay } from './timeline-day.svelte';
import type {
AssetDescriptor,
Direction,
@@ -138,16 +138,16 @@ export class TimelineManager extends VirtualScrollManager {
async *assetsIterator(options?: {
startMonthGroup?: MonthGroup;
startDayGroup?: DayGroup;
startTimelineDay?: TimelineDay;
startAsset?: TimelineAsset;
direction?: Direction;
}) {
const direction = options?.direction ?? 'earlier';
let { startDayGroup, startAsset } = options ?? {};
let { startTimelineDay, startAsset } = options ?? {};
for (const monthGroup of this.monthGroupIterator({ direction, startMonthGroup: options?.startMonthGroup })) {
await this.loadMonthGroup(monthGroup.yearMonth, { cancelable: false });
yield* monthGroup.assetsIterator({ startDayGroup, startAsset, direction });
startDayGroup = startAsset = undefined;
yield* monthGroup.assetsIterator({ startTimelineDay, startAsset, direction });
startTimelineDay = startAsset = undefined;
}
}
@@ -226,10 +226,10 @@ export class TimelineManager extends VirtualScrollManager {
}
clearDeferredLayout(month: MonthGroup) {
const hasDeferred = month.dayGroups.some((group) => group.deferredLayout);
const hasDeferred = month.timelineDays.some((group) => group.deferredLayout);
if (hasDeferred) {
updateGeometry(this, month, { invalidateHeight: true, noDefer: true });
for (const group of month.dayGroups) {
for (const group of month.timelineDays) {
group.deferredLayout = false;
}
}
@@ -428,8 +428,8 @@ export class TimelineManager extends VirtualScrollManager {
}
await this.loadMonthGroup(randomMonth.yearMonth, { cancelable: false });
let randomDay: DayGroup | undefined = undefined;
for (const day of randomMonth.dayGroups) {
let randomDay: TimelineDay | undefined = undefined;
for (const day of randomMonth.timelineDays) {
if (randomAssetIndex < accumulatedCount + day.viewerAssets.length) {
randomDay = day;
break;
@@ -618,16 +618,16 @@ export class TimelineManager extends VirtualScrollManager {
}
protected postUpsert(context: GroupInsertionCache): void {
for (const group of context.existingDayGroups) {
for (const group of context.existingTimelineDays) {
group.sortAssets(this.#options.order);
}
for (const monthGroup of context.bucketsWithNewDayGroups) {
monthGroup.sortDayGroups();
for (const monthGroup of context.bucketsWithNewTimelineDays) {
monthGroup.sortTimelineDays();
}
for (const month of context.updatedBuckets) {
month.sortDayGroups();
month.sortTimelineDays();
updateGeometry(this, month, { invalidateHeight: true });
}
this.updateViewportProximities();
@@ -1,15 +1,15 @@
import type { CommonPosition } from '$lib/utils/layout-utils';
import type { DayGroup } from './day-group.svelte';
import {
ViewportProximity,
calculateViewerAssetViewportProximity,
isInOrNearViewport,
} from './internal/intersection-support.svelte';
import type { TimelineDay } from './timeline-day.svelte';
import type { TimelineAsset } from './types';
export class ViewerAsset {
readonly #group: DayGroup;
readonly #group: TimelineDay;
#viewportProximity = $derived.by(() => {
if (!this.position) {
@@ -17,7 +17,7 @@ export class ViewerAsset {
}
const store = this.#group.monthGroup.timelineManager;
const positionTop = this.#group.absoluteDayGroupTop + this.position.top;
const positionTop = this.#group.absoluteTimelineDayTop + this.position.top;
return calculateViewerAssetViewportProximity(store, positionTop, this.position.height);
});
@@ -30,7 +30,7 @@ export class ViewerAsset {
asset: TimelineAsset = <TimelineAsset>$state();
id: string = $derived(this.asset.id);
constructor(group: DayGroup, asset: TimelineAsset) {
constructor(group: TimelineDay, asset: TimelineAsset) {
this.#group = group;
this.asset = asset;
}