refactor(web): extract common timeline functionality into PhotostreamManager base classes

Create abstract PhotostreamManager and PhotostreamSegment base classes to enable reusable      
timeline-like components. This refactoring extracts common viewport management, scroll         
handling, and segment operations from TimelineManager and MonthGroup into reusable             
abstractions.                                                                                  
                                                                                                  
Changes:                                                                                       
 - Add PhotostreamManager.svelte.ts with viewport and scroll management                         
 - Add PhotostreamSegment.svelte.ts with segment positioning and intersection logic             
 - Refactor TimelineManager to extend PhotostreamManager                                        
 - Refactor MonthGroup to extend PhotostreamSegment                                             
 - Add utility functions for segment identification and date formatting                         
 - Update tests to reflect new inheritance structure
This commit is contained in:
midzelis 2025-09-21 16:19:30 +00:00
parent e600cf64b0
commit 3a468a3f50
13 changed files with 652 additions and 447 deletions

View file

@ -19,7 +19,12 @@
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { navigate } from '$lib/utils/navigation';
import { getTimes, type ScrubberListener, type TimelineYearMonth } from '$lib/utils/timeline-util';
import {
getSegmentIdentifier,
getTimes,
type ScrubberListener,
type TimelineYearMonth,
} from '$lib/utils/timeline-util';
import { type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
import { DateTime } from 'luxon';
import { onMount, type Snippet } from 'svelte';
@ -478,7 +483,7 @@
break;
}
if (started) {
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
await timelineManager.loadSegment(monthGroup.identifier);
for (const asset of monthGroup.assetsIterator()) {
if (deselect) {
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
@ -553,7 +558,7 @@
$effect(() => {
if ($showAssetViewer) {
const { localDateTime } = getTimes($viewingAsset.fileCreatedAt, DateTime.local().offset / 60);
void timelineManager.loadMonthGroup({ year: localDateTime.year, month: localDateTime.month });
void timelineManager.loadSegment(getSegmentIdentifier({ year: localDateTime.year, month: localDateTime.month }));
}
});
</script>

View file

@ -12,7 +12,6 @@
import { mdiCheckCircle, mdiCircleOutline } from '@mdi/js';
import { fromTimelinePlainDate, getDateLocaleString } from '$lib/utils/timeline-util';
import { Icon } from '@immich/ui';
import type { Snippet } from 'svelte';
import { flip } from 'svelte/animate';
@ -125,16 +124,6 @@
return intersectable.filter((int) => int.intersecting);
}
const getDayGroupFullDate = (dayGroup: DayGroup): string => {
const { month, year } = dayGroup.monthGroup.yearMonth;
const date = fromTimelinePlainDate({
year,
month,
day: dayGroup.day,
});
return getDateLocaleString(date);
};
$effect.root(() => {
if (timelineManager.scrollCompensation.monthGroup === monthGroup) {
onScrollCompensation(timelineManager.scrollCompensation);
@ -185,7 +174,7 @@
</div>
{/if}
<span class="w-full truncate first-letter:capitalize" title={getDayGroupFullDate(dayGroup)}>
<span class="w-full truncate first-letter:capitalize" title={dayGroup.groupTitleFull}>
{dayGroup.groupTitle}
</span>
</div>