refactor: timeline manager renames (#19007)

* refactor: timeline manager renames

* refactor(web): improve timeline manager naming consistency

- Rename AddContext → GroupInsertionCache for clearer purpose
- Rename TimelineDay → DayGroup for better clarity
- Rename TimelineMonth → MonthGroup for better clarity
- Replace all "bucket" references with "monthGroup" terminology
- Update all component props, method names, and variable references
- Maintain consistent naming patterns across TypeScript and Svelte files

* refactor(web): rename buckets to months in timeline manager

- Rename TimelineManager.buckets property to months
- Update all store.buckets references to store.months
- Use 'month' shorthand for monthGroup arguments (not method names)
- Update component templates and test files for consistency
- Maintain API-related 'bucket' terminology (bucketHeight, getTimeBucket)

* refactor(web): rename assetStore to timelineManager and update types

- Rename assetStore variables to timelineManager in all .svelte files
- Update parameter names in actions.ts and asset-utils.ts functions
- Rename AssetStoreLayoutOptions to TimelineManagerLayoutOptions
- Rename AssetStoreOptions to TimelineManagerOptions
- Move assets-store.spec.ts to timeline-manager.spec.ts

* refactor(web): rename intersectingAssets to viewerAssets and fix property references

- Rename intersectingAssets to viewerAssets in DayGroup and MonthGroup classes
- Update arrow function parameters to use viewerAsset/viewAsset shorthand
- Rename topIntersectingBucket to topIntersectingMonthGroup
- Fix dateGroups references to dayGroups in asset-utils.ts and album page
- Update template loops and variable names in Svelte components

* refactor(web): rename #initializeTimeBuckets to #initializeMonthGroups and bucketDateFormatted to monthGroupTitle

* refactor(web): rename monthGroupHeight to height

* refactor(web): rename bucketCount to assetsCount, bucketsIterator to monthGroupIterator, and related properties

* refactor(web): rename count to assetCount in TimelineManager

* refactor(web): rename LiteBucket to ScrubberMonth and update scrubber variables

- Rename LiteBucket type to ScrubberMonth
- Rename bucketDateFormattted to title in ScrubberMonth type
- Rename bucketPercentY to monthGroupPercentY in scrubber component
- Rename scrubBucket to scrubberMonth and scrubBucketPercent to scrubberMonthPercent

* fix remaining refs to bucket

* reset submodule to correct commit

* reset submodule to correct commit

* refactor(web): extract TimelineManager internals into separate modules

- Move search-related functions to internal/search-support.svelte.ts
- Extract websocket event handling into WebsocketSupport class
- Move utility functions (updateObject, isMismatched) to internal/utils.svelte.ts
- Update imports in tests to use new module structure

* refactor(web): extract intersection logic from TimelineManager

- Create intersection-support.svelte.ts with updateIntersection and calculateIntersecting functions
- Remove private intersection methods from TimelineManager
- Export findMonthGroupForAsset from search-support for reuse
- Update TimelineManager to use the extracted intersection functions

* refactor(web): rename a few methods in intersecting

* refactor(web): rename a few methods in intersecting

* refactor(web): extract layout logic from TimelineManager

- Create layout-support.svelte.ts with updateGeometry and layoutMonthGroup functions
- Remove private layout methods from TimelineManager
- Update TimelineManager to use the extracted layout functions
- Remove unused UpdateGeometryOptions import

* refactor(web): extract asset operations from TimelineManager

- Create operations-support.svelte.ts with addAssetsToMonthGroups and runAssetOperation functions
- Remove private asset operation methods from TimelineManager
- Update TimelineManager to use extracted operation functions with proper AssetOrder handling
- Rename getMonthGroupIndexByAssetId to getMonthGroupByAssetId for consistency
- Move utility functions from utils.svelte.ts to internal/utils.svelte.ts
- Fix method name references in asset-grid.svelte and tests

* refactor(web): extract loading logic from TimelineManager

- Create load-support.svelte.ts with loadFromTimeBuckets function
- Extract time bucket loading, album asset handling, and error logging
- Simplify TimelineManager's loadMonthGroup method to use extracted function

* refresh timeline after archive keyboard shortcut

* remove debugger

* rename

* Review comments - remove shadowed var

* reduce indents - early return

* review comment

* refactor: simplify asset filtering in addAssets method

Replace for loop with filter operation for better readability

* fix: bad merge

* refactor(web): simplify timeline layout algorithm

- Replace rowSpaceRemaining array with direct cumulative width tracking
- Invert logic from tracking remaining space to tracking used space
- Fix spelling: cummulative to cumulative
- Rename lastRowHeight to currentRowHeight for clarity
- Remove confusing lastRow variable and simplify final height calculation
- Add explanatory comments for clarity
- Rename loop variable assetGroup to dayGroup for consistency

* simplify assetsIterator usage

* merge/lint

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Min Idzelis
2025-06-10 10:30:13 -04:00
committed by GitHub
parent 6499057b4c
commit 4b4ee5abf3
39 changed files with 2288 additions and 2139 deletions

View File

@@ -15,18 +15,18 @@
import { albumMapViewManager } from '$lib/managers/album-view-map.manager.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
import { AssetStore } from '$lib/managers/timeline-manager/asset-store.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { showDeleteModal } from '$lib/stores/preferences.store';
import { searchStore } from '$lib/stores/search.svelte';
import { featureFlags } from '$lib/stores/server-config.store';
import type { AssetBucket } from '$lib/managers/timeline-manager/asset-bucket.svelte';
import { handlePromiseError } from '$lib/utils';
import { deleteAssets, updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
import { archiveAssets, cancelMultiselect, selectAllAssets, stackAssets } from '$lib/utils/asset-utils';
@@ -47,7 +47,7 @@
`AssetViewingStore.gridScrollTarget` and load and scroll to the asset specified, and
additionally, update the page location/url with the asset as the asset-grid is scrolled */
enableRouting: boolean;
assetStore: AssetStore;
timelineManager: TimelineManager;
assetInteraction: AssetInteraction;
removeAction?:
| AssetAction.UNARCHIVE
@@ -72,7 +72,7 @@
isSelectionMode = false,
singleSelect = false,
enableRouting,
assetStore = $bindable(),
timelineManager = $bindable(),
assetInteraction,
removeAction = null,
withStacked = false,
@@ -94,8 +94,8 @@
let timelineElement: HTMLElement | undefined = $state();
let showSkeleton = $state(true);
let isShowSelectDate = $state(false);
let scrubBucketPercent = $state(0);
let scrubBucket: TimelinePlainYearMonth | undefined = $state();
let scrubberMonthPercent = $state(0);
let scrubberMonth: { year: number; month: number } | undefined = $state(undefined);
let scrubOverallPercent: number = $state(0);
let scrubberWidth = $state(0);
@@ -116,7 +116,7 @@
rowHeight: 235,
headerHeight: 48,
};
assetStore.setLayoutOptions(layoutOptions);
timelineManager.setLayoutOptions(layoutOptions);
});
const scrollTo = (top: number) => {
@@ -138,35 +138,35 @@
scrollTo(0);
};
const getAssetHeight = (assetId: string, bucket: AssetBucket) => {
const getAssetHeight = (assetId: string, monthGroup: MonthGroup) => {
// the following method may trigger any layouts, so need to
// handle any scroll compensation that may have been set
const height = bucket!.findAssetAbsolutePosition(assetId);
const height = monthGroup!.findAssetAbsolutePosition(assetId);
while (assetStore.scrollCompensation.bucket) {
handleScrollCompensation(assetStore.scrollCompensation);
assetStore.clearScrollCompensation();
while (timelineManager.scrollCompensation.monthGroup) {
handleScrollCompensation(timelineManager.scrollCompensation);
timelineManager.clearScrollCompensation();
}
return height;
};
const scrollToAssetId = async (assetId: string) => {
const bucket = await assetStore.findBucketForAsset(assetId);
if (!bucket) {
const monthGroup = await timelineManager.findMonthGroupForAsset(assetId);
if (!monthGroup) {
return false;
}
const height = getAssetHeight(assetId, bucket);
const height = getAssetHeight(assetId, monthGroup);
scrollTo(height);
updateSlidingWindow();
return true;
};
const scrollToAsset = (asset: TimelineAsset) => {
const bucket = assetStore.getBucketIndexByAssetId(asset.id);
if (!bucket) {
const monthGroup = timelineManager.getMonthGroupByAssetId(asset.id);
if (!monthGroup) {
return false;
}
const height = getAssetHeight(asset.id, bucket);
const height = getAssetHeight(asset.id, monthGroup);
scrollTo(height);
updateSlidingWindow();
return true;
@@ -185,7 +185,7 @@
showSkeleton = false;
};
beforeNavigate(() => (assetStore.suspendTransitions = true));
beforeNavigate(() => (timelineManager.suspendTransitions = true));
afterNavigate((nav) => {
const { complete } = nav;
@@ -224,7 +224,7 @@
import.meta.hot?.on('vite:beforeUpdate', (payload) => {
const assetGridUpdate = payload.updates.some((update) => update.path.endsWith('asset-grid.svelte'));
if (assetGridUpdate) {
assetStore.destroy();
timelineManager.destroy();
}
});
@@ -233,9 +233,9 @@
return () => void 0;
};
const updateIsScrolling = () => (assetStore.scrolling = true);
const updateIsScrolling = () => (timelineManager.scrolling = true);
// note: don't throttle, debounch, or otherwise do this function async - it causes flicker
const updateSlidingWindow = () => assetStore.updateSlidingWindow(element?.scrollTop || 0);
const updateSlidingWindow = () => timelineManager.updateSlidingWindow(element?.scrollTop || 0);
const handleScrollCompensation = ({ heightDelta, scrollTop }: { heightDelta?: number; scrollTop?: number }) => {
if (heightDelta !== undefined) {
@@ -245,12 +245,12 @@
}
// Yes, updateSlideWindow() is called by the onScroll event triggered as a result of
// the above calls. However, this delay is enough time to set the intersecting property
// of the bucket to false, then true, which causes the DOM nodes to be recreated,
// of the monthGroup to false, then true, which causes the DOM nodes to be recreated,
// causing bad perf, and also, disrupting focus of those elements.
updateSlidingWindow();
};
const topSectionResizeObserver: OnResizeCallback = ({ height }) => (assetStore.topSectionHeight = height);
const topSectionResizeObserver: OnResizeCallback = ({ height }) => (timelineManager.topSectionHeight = height);
onMount(() => {
if (!enableRouting) {
@@ -263,21 +263,23 @@
});
const getMaxScrollPercent = () => {
const totalHeight = assetStore.timelineHeight + bottomSectionHeight + assetStore.topSectionHeight;
return (totalHeight - assetStore.viewportHeight) / totalHeight;
const totalHeight = timelineManager.timelineHeight + bottomSectionHeight + timelineManager.topSectionHeight;
return (totalHeight - timelineManager.viewportHeight) / totalHeight;
};
const getMaxScroll = () => {
if (!element || !timelineElement) {
return 0;
}
return assetStore.topSectionHeight + bottomSectionHeight + (timelineElement.clientHeight - element.clientHeight);
return (
timelineManager.topSectionHeight + bottomSectionHeight + (timelineElement.clientHeight - element.clientHeight)
);
};
const scrollToBucketAndOffset = (bucket: AssetBucket, bucketScrollPercent: number) => {
const topOffset = bucket.top;
const scrollToMonthGroupAndOffset = (monthGroup: MonthGroup, monthGroupScrollPercent: number) => {
const topOffset = monthGroup.top;
const maxScrollPercent = getMaxScrollPercent();
const delta = bucket.bucketHeight * bucketScrollPercent;
const delta = monthGroup.height * monthGroupScrollPercent;
const scrollToTop = (topOffset + delta) * maxScrollPercent;
scrollTop(scrollToTop);
@@ -285,23 +287,23 @@
// note: don't throttle, debounch, or otherwise make this function async - it causes flicker
const onScrub: ScrubberListener = (
bucketDate: { year: number; month: number } | undefined,
scrollPercent: number,
bucketScrollPercent: number,
scrubMonth: { year: number; month: number },
overallScrollPercent: number,
scrubberMonthScrollPercent: number,
) => {
if (!bucketDate || assetStore.timelineHeight < assetStore.viewportHeight * 2) {
if (!scrubMonth || timelineManager.timelineHeight < timelineManager.viewportHeight * 2) {
// edge case - scroll limited due to size of content, must adjust - use use the overall percent instead
const maxScroll = getMaxScroll();
const offset = maxScroll * scrollPercent;
const offset = maxScroll * overallScrollPercent;
scrollTop(offset);
} else {
const bucket = assetStore.buckets.find(
(bucket) => bucket.yearMonth.year === bucketDate.year && bucket.yearMonth.month === bucketDate.month,
const monthGroup = timelineManager.months.find(
({ yearMonth: { year, month } }) => year === scrubMonth.year && month === scrubMonth.month,
);
if (!bucket) {
if (!monthGroup) {
return;
}
scrollToBucketAndOffset(bucket, bucketScrollPercent);
scrollToMonthGroupAndOffset(monthGroup, scrubberMonthScrollPercent);
}
};
@@ -313,19 +315,19 @@
return;
}
if (assetStore.timelineHeight < assetStore.viewportHeight * 2) {
if (timelineManager.timelineHeight < timelineManager.viewportHeight * 2) {
// edge case - scroll limited due to size of content, must adjust - use the overall percent instead
const maxScroll = getMaxScroll();
scrubOverallPercent = Math.min(1, element.scrollTop / maxScroll);
scrubBucket = undefined;
scrubBucketPercent = 0;
scrubberMonth = undefined;
scrubberMonthPercent = 0;
} else {
let top = element.scrollTop;
if (top < assetStore.topSectionHeight) {
if (top < timelineManager.topSectionHeight) {
// in the lead-in area
scrubBucket = undefined;
scrubBucketPercent = 0;
scrubberMonth = undefined;
scrubberMonthPercent = 0;
const maxScroll = getMaxScroll();
scrubOverallPercent = Math.min(1, element.scrollTop / maxScroll);
@@ -335,33 +337,33 @@
let maxScrollPercent = getMaxScrollPercent();
let found = false;
const bucketsLength = assetStore.buckets.length;
for (let i = -1; i < bucketsLength + 1; i++) {
let bucket: TimelinePlainYearMonth | undefined;
let bucketHeight = 0;
const monthsLength = timelineManager.months.length;
for (let i = -1; i < monthsLength + 1; i++) {
let monthGroup: TimelinePlainYearMonth | undefined;
let monthGroupHeight = 0;
if (i === -1) {
// lead-in
bucketHeight = assetStore.topSectionHeight;
} else if (i === bucketsLength) {
monthGroupHeight = timelineManager.topSectionHeight;
} else if (i === monthsLength) {
// lead-out
bucketHeight = bottomSectionHeight;
monthGroupHeight = bottomSectionHeight;
} else {
bucket = assetStore.buckets[i].yearMonth;
bucketHeight = assetStore.buckets[i].bucketHeight;
monthGroup = timelineManager.months[i].yearMonth;
monthGroupHeight = timelineManager.months[i].height;
}
let next = top - bucketHeight * maxScrollPercent;
let next = top - monthGroupHeight * maxScrollPercent;
// instead of checking for < 0, add a little wiggle room for subpixel resolution
if (next < -1 && bucket) {
scrubBucket = bucket;
if (next < -1 && monthGroup) {
scrubberMonth = monthGroup;
// allowing next to be at least 1 may cause percent to go negative, so ensure positive percentage
scrubBucketPercent = Math.max(0, top / (bucketHeight * maxScrollPercent));
scrubberMonthPercent = Math.max(0, top / (monthGroupHeight * maxScrollPercent));
// compensate for lost precision/rounding errors advance to the next bucket, if present
if (scrubBucketPercent > 0.9999 && i + 1 < bucketsLength - 1) {
scrubBucket = assetStore.buckets[i + 1].yearMonth;
scrubBucketPercent = 0;
if (scrubberMonthPercent > 0.9999 && i + 1 < monthsLength - 1) {
scrubberMonth = timelineManager.months[i + 1].yearMonth;
scrubberMonthPercent = 0;
}
found = true;
@@ -371,8 +373,8 @@
}
if (!found) {
leadout = true;
scrubBucket = undefined;
scrubBucketPercent = 0;
scrubberMonth = undefined;
scrubberMonthPercent = 0;
scrubOverallPercent = 1;
}
}
@@ -382,9 +384,9 @@
isShowDeleteConfirmation = false;
await deleteAssets(
!(isTrashEnabled && !force),
(assetIds) => assetStore.removeAssets(assetIds),
(assetIds) => timelineManager.removeAssets(assetIds),
assetInteraction.selectedAssets,
!isTrashEnabled || force ? undefined : (assets) => assetStore.addAssets(assets),
!isTrashEnabled || force ? undefined : (assets) => timelineManager.addAssets(assets),
);
assetInteraction.clearMultiselect();
};
@@ -410,31 +412,32 @@
const onStackAssets = async () => {
const result = await stackAssets(assetInteraction.selectedAssets);
updateStackedAssetInTimeline(assetStore, result);
updateStackedAssetInTimeline(timelineManager, result);
onEscape();
};
const toggleArchive = async () => {
await archiveAssets(
assetInteraction.selectedAssets,
assetInteraction.isAllArchived ? AssetVisibility.Timeline : AssetVisibility.Archive,
);
assetStore.updateAssets(assetInteraction.selectedAssets);
const visibility = assetInteraction.isAllArchived ? AssetVisibility.Timeline : AssetVisibility.Archive;
const ids = await archiveAssets(assetInteraction.selectedAssets, visibility);
timelineManager.updateAssetOperation(ids, (asset) => {
asset.visibility = visibility;
return { remove: false };
});
deselectAllAssets();
};
const handleSelectAsset = (asset: TimelineAsset) => {
if (!assetStore.albumAssets.has(asset.id)) {
if (!timelineManager.albumAssets.has(asset.id)) {
assetInteraction.selectAsset(asset);
}
};
const handlePrevious = async () => {
const laterAsset = await assetStore.getLaterAsset($viewingAsset);
const laterAsset = await timelineManager.getLaterAsset($viewingAsset);
if (laterAsset) {
const preloadAsset = await assetStore.getLaterAsset(laterAsset);
const preloadAsset = await timelineManager.getLaterAsset(laterAsset);
const asset = await getAssetInfo({ id: laterAsset.id, key: authManager.key });
assetViewingStore.setAsset(asset, preloadAsset ? [preloadAsset] : []);
await navigate({ targetRoute: 'current', assetId: laterAsset.id });
@@ -444,9 +447,9 @@
};
const handleNext = async () => {
const earlierAsset = await assetStore.getEarlierAsset($viewingAsset);
const earlierAsset = await timelineManager.getEarlierAsset($viewingAsset);
if (earlierAsset) {
const preloadAsset = await assetStore.getEarlierAsset(earlierAsset);
const preloadAsset = await timelineManager.getEarlierAsset(earlierAsset);
const asset = await getAssetInfo({ id: earlierAsset.id, key: authManager.key });
assetViewingStore.setAsset(asset, preloadAsset ? [preloadAsset] : []);
await navigate({ targetRoute: 'current', assetId: earlierAsset.id });
@@ -456,7 +459,7 @@
};
const handleRandom = async () => {
const randomAsset = await assetStore.getRandomAsset();
const randomAsset = await timelineManager.getRandomAsset();
if (randomAsset) {
const asset = await getAssetInfo({ id: randomAsset.id, key: authManager.key });
@@ -487,7 +490,7 @@
(await handleNext()) || (await handlePrevious()) || (await handleClose(action.asset));
// delete after find the next one
assetStore.removeAssets([action.asset.id]);
timelineManager.removeAssets([action.asset.id]);
break;
}
}
@@ -498,26 +501,26 @@
case AssetAction.UNARCHIVE:
case AssetAction.FAVORITE:
case AssetAction.UNFAVORITE: {
assetStore.updateAssets([action.asset]);
timelineManager.updateAssets([action.asset]);
break;
}
case AssetAction.ADD: {
assetStore.addAssets([action.asset]);
timelineManager.addAssets([action.asset]);
break;
}
case AssetAction.UNSTACK: {
updateUnstackedAssetInTimeline(assetStore, action.assets);
updateUnstackedAssetInTimeline(timelineManager, action.assets);
break;
}
case AssetAction.SET_STACK_PRIMARY_ASSET: {
//Have to unstack then restack assets in timeline in order for the currently removed new primary asset to be made visible.
updateUnstackedAssetInTimeline(
assetStore,
timelineManager,
action.stack.assets.map((asset) => toTimelineAsset(asset)),
);
updateStackedAssetInTimeline(assetStore, {
updateStackedAssetInTimeline(timelineManager, {
stack: action.stack,
toDeleteIds: action.stack.assets
.filter((asset) => asset.id !== action.stack.primaryAssetId)
@@ -565,7 +568,7 @@
lastAssetMouseEvent = asset;
};
const handleGroupSelect = (assetStore: AssetStore, group: string, assets: TimelineAsset[]) => {
const handleGroupSelect = (timelineManager: TimelineManager, group: string, assets: TimelineAsset[]) => {
if (assetInteraction.selectedGroup.has(group)) {
assetInteraction.removeGroupFromMultiselectGroup(group);
for (const asset of assets) {
@@ -578,7 +581,7 @@
}
}
if (assetStore.count == assetInteraction.selectedAssets.length) {
if (timelineManager.assetCount == assetInteraction.selectedAssets.length) {
isSelectingAllAssets.set(true);
} else {
isSelectingAllAssets.set(false);
@@ -615,8 +618,8 @@
assetInteraction.clearAssetSelectionCandidates();
if (assetInteraction.assetSelectionStart && rangeSelection) {
let startBucket = assetStore.getBucketIndexByAssetId(assetInteraction.assetSelectionStart.id);
let endBucket = assetStore.getBucketIndexByAssetId(asset.id);
let startBucket = timelineManager.getMonthGroupByAssetId(assetInteraction.assetSelectionStart.id);
let endBucket = timelineManager.getMonthGroupByAssetId(asset.id);
if (startBucket === null || endBucket === null) {
return;
@@ -624,13 +627,13 @@
// Select/deselect assets in range (start,end)
let started = false;
for (const bucket of assetStore.buckets) {
if (bucket === endBucket) {
for (const monthGroup of timelineManager.months) {
if (monthGroup === endBucket) {
break;
}
if (started) {
await assetStore.loadBucket(bucket.yearMonth);
for (const asset of bucket.assetsIterator()) {
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
for (const asset of monthGroup.assetsIterator()) {
if (deselect) {
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
} else {
@@ -638,29 +641,29 @@
}
}
}
if (bucket === startBucket) {
if (monthGroup === startBucket) {
started = true;
}
}
// Update date group selection in range [start,end]
started = false;
for (const bucket of assetStore.buckets) {
if (bucket === startBucket) {
for (const monthGroup of timelineManager.months) {
if (monthGroup === startBucket) {
started = true;
}
if (started) {
// Split bucket into date groups and check each group
for (const dateGroup of bucket.dateGroups) {
const dateGroupTitle = dateGroup.groupTitle;
if (dateGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
assetInteraction.addGroupToMultiselectGroup(dateGroupTitle);
// 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);
} else {
assetInteraction.removeGroupFromMultiselectGroup(dateGroupTitle);
assetInteraction.removeGroupFromMultiselectGroup(dayGroupTitle);
}
}
}
if (bucket === endBucket) {
if (monthGroup === endBucket) {
break;
}
}
@@ -679,7 +682,7 @@
return;
}
const assets = assetsSnapshot(await assetStore.retrieveRange(startAsset, endAsset));
const assets = assetsSnapshot(await timelineManager.retrieveRange(startAsset, endAsset));
assetInteraction.setAssetSelectionCandidates(assets);
};
@@ -690,7 +693,7 @@
};
let isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash);
let isEmpty = $derived(assetStore.isInitialized && assetStore.buckets.length === 0);
let isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
let idsSelectedAssets = $derived(assetInteraction.selectedAssets.map(({ id }) => id));
let isShortcutModalOpen = false;
@@ -710,7 +713,7 @@
}
});
const setFocusTo = setFocusToInit.bind(undefined, scrollToAsset, assetStore);
const setFocusTo = setFocusToInit.bind(undefined, scrollToAsset, timelineManager);
const setFocusAsset = setFocusAssetInit.bind(undefined, scrollToAsset);
let shortcutList = $derived(
@@ -723,7 +726,7 @@
{ shortcut: { key: 'Escape' }, onShortcut: onEscape },
{ shortcut: { key: '?', shift: true }, onShortcut: handleOpenShortcutModal },
{ shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) },
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(assetStore, assetInteraction) },
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(timelineManager, assetInteraction) },
{ shortcut: { key: 'ArrowRight' }, onShortcut: () => setFocusTo('earlier', 'asset') },
{ shortcut: { key: 'ArrowLeft' }, onShortcut: () => setFocusTo('later', 'asset') },
{ shortcut: { key: 'D' }, onShortcut: () => setFocusTo('earlier', 'day') },
@@ -785,7 +788,9 @@
timezoneInput={false}
onConfirm={async (dateString: string) => {
isShowSelectDate = false;
const asset = await assetStore.getClosestAssetToDate((DateTime.fromISO(dateString) as DateTime<true>).toObject());
const asset = await timelineManager.getClosestAssetToDate(
(DateTime.fromISO(dateString) as DateTime<true>).toObject(),
);
if (asset) {
setFocusAsset(asset);
}
@@ -794,16 +799,16 @@
/>
{/if}
{#if assetStore.buckets.length > 0}
{#if timelineManager.months.length > 0}
<Scrubber
{assetStore}
height={assetStore.viewportHeight}
timelineTopOffset={assetStore.topSectionHeight}
{timelineManager}
height={timelineManager.viewportHeight}
timelineTopOffset={timelineManager.topSectionHeight}
timelineBottomOffset={bottomSectionHeight}
{leadout}
{scrubOverallPercent}
{scrubBucketPercent}
{scrubBucket}
{scrubberMonthPercent}
{scrubberMonth}
{onScrub}
bind:scrubberWidth
onScrubKeyDown={(evt) => {
@@ -824,14 +829,14 @@
/>
{/if}
<!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
<!-- Right margin MUST be equal to the width of scrubber -->
<section
id="asset-grid"
class={['scrollbar-hidden h-full overflow-y-auto outline-none', { 'm-0': isEmpty }, { 'ms-0': !isEmpty }]}
style:margin-right={(usingMobileDevice ? 0 : scrubberWidth) + 'px'}
tabindex="-1"
bind:clientHeight={assetStore.viewportHeight}
bind:clientWidth={null, (v) => ((assetStore.viewportWidth = v), updateSlidingWindow())}
bind:clientHeight={timelineManager.viewportHeight}
bind:clientWidth={null, (v) => ((timelineManager.viewportWidth = v), updateSlidingWindow())}
bind:this={element}
onscroll={() => (handleTimelineScroll(), updateSlidingWindow(), updateIsScrolling())}
>
@@ -839,7 +844,7 @@
bind:this={timelineElement}
id="virtual-timeline"
class:invisible={showSkeleton}
style:height={assetStore.timelineHeight + 'px'}
style:height={timelineManager.timelineHeight + 'px'}
>
<section
use:resizeObserver={topSectionResizeObserver}
@@ -855,23 +860,26 @@
{/if}
</section>
{#each assetStore.buckets as bucket (bucket.viewId)}
{@const display = bucket.intersecting}
{@const absoluteHeight = bucket.top}
{#each timelineManager.months as monthGroup (monthGroup.viewId)}
{@const display = monthGroup.intersecting}
{@const absoluteHeight = monthGroup.top}
{#if !bucket.isLoaded}
{#if !monthGroup.isLoaded}
<div
style:height={bucket.bucketHeight + 'px'}
style:height={monthGroup.height + 'px'}
style:position="absolute"
style:transform={`translate3d(0,${absoluteHeight}px,0)`}
style:width="100%"
>
<Skeleton height={bucket.bucketHeight - bucket.store.headerHeight} title={bucket.bucketDateFormatted} />
<Skeleton
height={monthGroup.height - monthGroup.timelineManager.headerHeight}
title={monthGroup.monthGroupTitle}
/>
</div>
{:else if display}
<div
class="bucket"
style:height={bucket.bucketHeight + 'px'}
class="month-group"
style:height={monthGroup.height + 'px'}
style:position="absolute"
style:transform={`translate3d(0,${absoluteHeight}px,0)`}
style:width="100%"
@@ -880,11 +888,11 @@
{withStacked}
{showArchiveIcon}
{assetInteraction}
{assetStore}
{timelineManager}
{isSelectionMode}
{singleSelect}
{bucket}
onSelect={({ title, assets }) => handleGroupSelect(assetStore, title, assets)}
{monthGroup}
onSelect={({ title, assets }) => handleGroupSelect(timelineManager, title, assets)}
onSelectAssetCandidates={handleSelectAssetCandidates}
onSelectAssets={handleSelectAssets}
onScrollCompensation={handleScrollCompensation}
@@ -898,7 +906,7 @@
style:position="absolute"
style:left="0"
style:right="0"
style:transform={`translate3d(0,${assetStore.timelineHeight}px,0)`}
style:transform={`translate3d(0,${timelineManager.timelineHeight}px,0)`}
></div>
</section>
</section>
@@ -932,7 +940,7 @@
scrollbar-width: none;
}
.bucket {
.month-group {
contain: layout size paint;
transform-style: flat;
backface-visibility: hidden;