mirror of
https://github.com/immich-app/immich.git
synced 2026-04-04 08:12:02 -04:00
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:
parent
c9e251c78c
commit
2166f07b1f
@ -48,7 +48,7 @@
|
||||
const asset =
|
||||
$slideshowNavigation === SlideshowNavigation.Shuffle
|
||||
? await timelineManager.getRandomAsset()
|
||||
: timelineManager.months[0]?.dayGroups[0]?.viewerAssets[0]?.asset;
|
||||
: timelineManager.months[0]?.timelineDays[0]?.viewerAssets[0]?.asset;
|
||||
if (asset) {
|
||||
handlePromiseError(
|
||||
assetViewerManager.setAssetId(asset.id).then(() => ($slideshowState = SlideshowState.PlaySlideshow)),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import AssetLayout from '$lib/components/timeline/AssetLayout.svelte';
|
||||
import type { AssetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
|
||||
import { TimelineDay } from '$lib/managers/timeline-manager/timeline-day.svelte';
|
||||
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { assetsSnapshot, filterIsInOrNearViewport } from '$lib/managers/timeline-manager/utils.svelte';
|
||||
@ -19,7 +19,7 @@
|
||||
{
|
||||
asset: TimelineAsset;
|
||||
position: CommonPosition;
|
||||
dayGroup: DayGroup;
|
||||
timelineDay: TimelineDay;
|
||||
groupIndex: number;
|
||||
},
|
||||
]
|
||||
@ -29,7 +29,7 @@
|
||||
assetInteraction: AssetMultiSelectManager;
|
||||
monthGroup: MonthGroup;
|
||||
manager: VirtualScrollManager;
|
||||
onDayGroupSelect: (dayGroup: DayGroup, assets: TimelineAsset[]) => void;
|
||||
onTimelineDaySelect: (timelineDay: TimelineDay, assets: TimelineAsset[]) => void;
|
||||
};
|
||||
let {
|
||||
thumbnail: thumbnailWithGroup,
|
||||
@ -38,27 +38,27 @@
|
||||
assetInteraction,
|
||||
monthGroup,
|
||||
manager,
|
||||
onDayGroupSelect,
|
||||
onTimelineDaySelect,
|
||||
}: Props = $props();
|
||||
|
||||
let { isUploading } = uploadAssetsStore;
|
||||
let hoveredDayGroup = $state<string | null>(null);
|
||||
let hoveredTimelineDay = $state<string | null>(null);
|
||||
|
||||
const transitionDuration = $derived(monthGroup.timelineManager.suspendTransitions && !$isUploading ? 0 : 150);
|
||||
|
||||
const getDayGroupFullDate = (dayGroup: DayGroup): string => {
|
||||
const { month, year } = dayGroup.monthGroup.yearMonth;
|
||||
const getTimelineDayFullDate = (timelineDay: TimelineDay): string => {
|
||||
const { month, year } = timelineDay.monthGroup.yearMonth;
|
||||
const date = fromTimelinePlainDate({
|
||||
year,
|
||||
month,
|
||||
day: dayGroup.day,
|
||||
day: timelineDay.day,
|
||||
});
|
||||
return getDateLocaleString(date);
|
||||
};
|
||||
</script>
|
||||
|
||||
{#each filterIsInOrNearViewport(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)}
|
||||
{@const isDayGroupSelected = assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
|
||||
{#each filterIsInOrNearViewport(monthGroup.timelineDays) as timelineDay, groupIndex (timelineDay.day)}
|
||||
{@const isTimelineDaySelected = assetInteraction.selectedGroup.has(timelineDay.groupTitle)}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<section
|
||||
class={[
|
||||
@ -67,24 +67,25 @@
|
||||
]}
|
||||
data-group
|
||||
style:position="absolute"
|
||||
style:inset-inline-start={dayGroup.start + 'px'}
|
||||
style:top={dayGroup.top + 'px'}
|
||||
onmouseenter={() => (hoveredDayGroup = dayGroup.groupTitle)}
|
||||
onmouseleave={() => (hoveredDayGroup = null)}
|
||||
style:inset-inline-start={timelineDay.start + 'px'}
|
||||
style:top={timelineDay.top + 'px'}
|
||||
onmouseenter={() => (hoveredTimelineDay = timelineDay.groupTitle)}
|
||||
onmouseleave={() => (hoveredTimelineDay = null)}
|
||||
>
|
||||
<!-- Day title -->
|
||||
<div
|
||||
class="flex pt-7 pb-5 max-md:pt-5 max-md:pb-3 h-6 place-items-center text-xs font-medium text-immich-fg dark:text-immich-dark-fg md:text-sm"
|
||||
style:width={dayGroup.width + 'px'}
|
||||
style:width={timelineDay.width + 'px'}
|
||||
>
|
||||
{#if !singleSelect}
|
||||
<div
|
||||
class="hover:cursor-pointer transition-all duration-200 ease-out overflow-hidden w-0"
|
||||
class:w-8={hoveredDayGroup === dayGroup.groupTitle || assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
|
||||
onclick={() => onDayGroupSelect(dayGroup, assetsSnapshot(dayGroup.getAssets()))}
|
||||
onkeydown={() => onDayGroupSelect(dayGroup, assetsSnapshot(dayGroup.getAssets()))}
|
||||
class:w-8={hoveredTimelineDay === timelineDay.groupTitle ||
|
||||
assetInteraction.selectedGroup.has(timelineDay.groupTitle)}
|
||||
onclick={() => onTimelineDaySelect(timelineDay, assetsSnapshot(timelineDay.getAssets()))}
|
||||
onkeydown={() => onTimelineDaySelect(timelineDay, assetsSnapshot(timelineDay.getAssets()))}
|
||||
>
|
||||
{#if isDayGroupSelected}
|
||||
{#if isTimelineDaySelected}
|
||||
<Icon icon={mdiCheckCircle} size="24" class="text-primary" />
|
||||
{:else}
|
||||
<Icon icon={mdiCircleOutline} size="24" class="text-light-500" />
|
||||
@ -92,20 +93,20 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<span class="w-full truncate first-letter:capitalize" title={getDayGroupFullDate(dayGroup)}>
|
||||
{dayGroup.groupTitle}
|
||||
<span class="w-full truncate first-letter:capitalize" title={getTimelineDayFullDate(timelineDay)}>
|
||||
{timelineDay.groupTitle}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<AssetLayout
|
||||
{manager}
|
||||
viewerAssets={dayGroup.viewerAssets}
|
||||
height={dayGroup.height}
|
||||
width={dayGroup.width}
|
||||
viewerAssets={timelineDay.viewerAssets}
|
||||
height={timelineDay.height}
|
||||
width={timelineDay.width}
|
||||
{customThumbnailLayout}
|
||||
>
|
||||
{#snippet thumbnail({ asset, position })}
|
||||
{@render thumbnailWithGroup({ asset, position, dayGroup, groupIndex })}
|
||||
{@render thumbnailWithGroup({ asset, position, timelineDay, groupIndex })}
|
||||
{/snippet}
|
||||
</AssetLayout>
|
||||
</section>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
import Skeleton from '$lib/elements/Skeleton.svelte';
|
||||
import type { AssetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
|
||||
import type { TimelineDay } from '$lib/managers/timeline-manager/timeline-day.svelte';
|
||||
import { isIntersecting } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
||||
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
@ -52,7 +52,7 @@
|
||||
onThumbnailClick?: (
|
||||
asset: TimelineAsset,
|
||||
timelineManager: TimelineManager,
|
||||
dayGroup: DayGroup,
|
||||
timelineDay: TimelineDay,
|
||||
onClick: (
|
||||
timelineManager: TimelineManager,
|
||||
assets: TimelineAsset[],
|
||||
@ -390,8 +390,8 @@
|
||||
lastAssetMouseEvent = asset;
|
||||
};
|
||||
|
||||
const handleGroupSelect = (dayGroup: DayGroup, assets: TimelineAsset[]) => {
|
||||
const group = dayGroup.groupTitle;
|
||||
const handleGroupSelect = (timelineDay: TimelineDay, assets: TimelineAsset[]) => {
|
||||
const group = timelineDay.groupTitle;
|
||||
if (assetInteraction.selectedGroup.has(group)) {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(group);
|
||||
for (const asset of assets) {
|
||||
@ -468,12 +468,12 @@
|
||||
const monthGroup = monthGroups[index];
|
||||
|
||||
// Split month group into day groups and check each group
|
||||
for (const dayGroup of monthGroup.dayGroups) {
|
||||
const dayGroupTitle = dayGroup.groupTitle;
|
||||
if (dayGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
|
||||
assetInteraction.addGroupToMultiselectGroup(dayGroupTitle);
|
||||
for (const timelineDay of monthGroup.timelineDays) {
|
||||
const timelineDayTitle = timelineDay.groupTitle;
|
||||
if (timelineDay.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
|
||||
assetInteraction.addGroupToMultiselectGroup(timelineDayTitle);
|
||||
} else {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(dayGroupTitle);
|
||||
assetInteraction.removeGroupFromMultiselectGroup(timelineDayTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -524,16 +524,18 @@
|
||||
const assetSelectHandler = (
|
||||
timelineManager: TimelineManager,
|
||||
asset: TimelineAsset,
|
||||
assetsInDayGroup: TimelineAsset[],
|
||||
assetsInTimelineDay: TimelineAsset[],
|
||||
groupTitle: string,
|
||||
) => {
|
||||
void onSelectAssets(asset);
|
||||
|
||||
// Check if all assets are selected in a group to toggle the group selection's icon
|
||||
let selectedAssetsInGroupCount = assetsInDayGroup.filter(({ id }) => assetInteraction.hasSelectedAsset(id)).length;
|
||||
let selectedAssetsInGroupCount = assetsInTimelineDay.filter(({ id }) =>
|
||||
assetInteraction.hasSelectedAsset(id),
|
||||
).length;
|
||||
|
||||
// if all assets are selected in a group, add the group to selected group
|
||||
if (selectedAssetsInGroupCount === assetsInDayGroup.length) {
|
||||
if (selectedAssetsInGroupCount === assetsInTimelineDay.length) {
|
||||
assetInteraction.addGroupToMultiselectGroup(groupTitle);
|
||||
} else {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(groupTitle);
|
||||
@ -668,9 +670,9 @@
|
||||
{singleSelect}
|
||||
{monthGroup}
|
||||
manager={timelineManager}
|
||||
onDayGroupSelect={handleGroupSelect}
|
||||
onTimelineDaySelect={handleGroupSelect}
|
||||
>
|
||||
{#snippet thumbnail({ asset, position, dayGroup, groupIndex })}
|
||||
{#snippet thumbnail({ asset, position, timelineDay, groupIndex })}
|
||||
{@const isAssetSelectionCandidate = assetInteraction.hasSelectionCandidate(asset.id)}
|
||||
{@const isAssetSelected =
|
||||
assetInteraction.hasSelectedAsset(asset.id) || timelineManager.albumAssets.has(asset.id)}
|
||||
@ -683,14 +685,14 @@
|
||||
{groupIndex}
|
||||
onClick={(asset) => {
|
||||
if (typeof onThumbnailClick === 'function') {
|
||||
onThumbnailClick(asset, timelineManager, dayGroup, _onClick);
|
||||
onThumbnailClick(asset, timelineManager, timelineDay, _onClick);
|
||||
} else {
|
||||
_onClick(timelineManager, dayGroup.getAssets(), dayGroup.groupTitle, asset);
|
||||
_onClick(timelineManager, timelineDay.getAssets(), timelineDay.groupTitle, asset);
|
||||
}
|
||||
}}
|
||||
onSelect={() => {
|
||||
if (isSelectionMode || assetInteraction.selectionActive) {
|
||||
assetSelectHandler(timelineManager, asset, dayGroup.getAssets(), dayGroup.groupTitle);
|
||||
assetSelectHandler(timelineManager, asset, timelineDay.getAssets(), timelineDay.groupTitle);
|
||||
return;
|
||||
}
|
||||
void onSelectAssets(asset);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -407,7 +407,7 @@ export const selectAllAssets = async (timelineManager: TimelineManager, assetInt
|
||||
}
|
||||
assetInteraction.selectAssets([...monthGroup.assetsIterator()]);
|
||||
|
||||
for (const dateGroup of monthGroup.dayGroups) {
|
||||
for (const dateGroup of monthGroup.timelineDays) {
|
||||
assetInteraction.addGroupToMultiselectGroup(dateGroup.groupTitle);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
const asset =
|
||||
$slideshowNavigation === SlideshowNavigation.Shuffle
|
||||
? await timelineManager.getRandomAsset()
|
||||
: timelineManager.months[0]?.dayGroups[0]?.viewerAssets[0]?.asset;
|
||||
: timelineManager.months[0]?.timelineDays[0]?.viewerAssets[0]?.asset;
|
||||
if (asset) {
|
||||
handlePromiseError(
|
||||
assetViewerManager.setAssetId(asset.id).then(() => ($slideshowState = SlideshowState.PlaySlideshow)),
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
|
||||
import type { TimelineDay } from '$lib/managers/timeline-manager/timeline-day.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import GeolocationPointPickerModal from '$lib/modals/GeolocationPointPickerModal.svelte';
|
||||
@ -109,7 +109,7 @@
|
||||
const handleThumbnailClick = (
|
||||
asset: TimelineAsset,
|
||||
timelineManager: TimelineManager,
|
||||
dayGroup: DayGroup,
|
||||
timelineDay: TimelineDay,
|
||||
onClick: (
|
||||
timelineManager: TimelineManager,
|
||||
assets: TimelineAsset[],
|
||||
@ -125,7 +125,7 @@
|
||||
point = { lat: asset.latitude, lng: asset.longitude };
|
||||
void setQueryValue('at', asset.id);
|
||||
} else {
|
||||
onClick(timelineManager, dayGroup.getAssets(), dayGroup.groupTitle, asset);
|
||||
onClick(timelineManager, timelineDay.getAssets(), timelineDay.groupTitle, asset);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user