mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor(web): replace intersection booleans with enum (#27306)
Change-Id: I0c9703d5960031142ae47fef23805e0a6a6a6964
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
|
import { filterIsInOrNearViewport } from '$lib/managers/timeline-manager/utils.svelte';
|
||||||
import type { ViewerAsset } from '$lib/managers/timeline-manager/viewer-asset.svelte';
|
import type { ViewerAsset } from '$lib/managers/timeline-manager/viewer-asset.svelte';
|
||||||
import type { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
|
import type { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
|
||||||
import { uploadAssetsStore } from '$lib/stores/upload';
|
import { uploadAssetsStore } from '$lib/stores/upload';
|
||||||
@@ -30,15 +31,11 @@
|
|||||||
|
|
||||||
const transitionDuration = $derived(manager.suspendTransitions && !$isUploading ? 0 : 150);
|
const transitionDuration = $derived(manager.suspendTransitions && !$isUploading ? 0 : 150);
|
||||||
const scaleDuration = $derived(transitionDuration === 0 ? 0 : transitionDuration + 100);
|
const scaleDuration = $derived(transitionDuration === 0 ? 0 : transitionDuration + 100);
|
||||||
|
|
||||||
const filterIntersecting = <T extends { intersecting: boolean }>(intersectables: T[]) => {
|
|
||||||
return intersectables.filter(({ intersecting }) => intersecting);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Image grid -->
|
<!-- Image grid -->
|
||||||
<div data-image-grid class="relative overflow-clip" style:height={height + 'px'} style:width={width + 'px'}>
|
<div data-image-grid class="relative overflow-clip" style:height={height + 'px'} style:width={width + 'px'}>
|
||||||
{#each filterIntersecting(viewerAssets) as viewerAsset (viewerAsset.id)}
|
{#each filterIsInOrNearViewport(viewerAssets) as viewerAsset (viewerAsset.id)}
|
||||||
{@const position = viewerAsset.position!}
|
{@const position = viewerAsset.position!}
|
||||||
{@const asset = viewerAsset.asset!}
|
{@const asset = viewerAsset.asset!}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
|
import { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
|
||||||
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
|
import { assetsSnapshot, filterIsInOrNearViewport } from '$lib/managers/timeline-manager/utils.svelte';
|
||||||
import type { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
|
import type { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
|
||||||
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { uploadAssetsStore } from '$lib/stores/upload';
|
import { uploadAssetsStore } from '$lib/stores/upload';
|
||||||
@@ -14,7 +14,16 @@
|
|||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
thumbnail: Snippet<[{ asset: TimelineAsset; position: CommonPosition; dayGroup: DayGroup; groupIndex: number }]>;
|
thumbnail: Snippet<
|
||||||
|
[
|
||||||
|
{
|
||||||
|
asset: TimelineAsset;
|
||||||
|
position: CommonPosition;
|
||||||
|
dayGroup: DayGroup;
|
||||||
|
groupIndex: number;
|
||||||
|
},
|
||||||
|
]
|
||||||
|
>;
|
||||||
customThumbnailLayout?: Snippet<[TimelineAsset]>;
|
customThumbnailLayout?: Snippet<[TimelineAsset]>;
|
||||||
singleSelect: boolean;
|
singleSelect: boolean;
|
||||||
assetInteraction: AssetInteraction;
|
assetInteraction: AssetInteraction;
|
||||||
@@ -37,10 +46,6 @@
|
|||||||
|
|
||||||
const transitionDuration = $derived(monthGroup.timelineManager.suspendTransitions && !$isUploading ? 0 : 150);
|
const transitionDuration = $derived(monthGroup.timelineManager.suspendTransitions && !$isUploading ? 0 : 150);
|
||||||
|
|
||||||
const filterIntersecting = <T extends { intersecting: boolean }>(intersectables: T[]) => {
|
|
||||||
return intersectables.filter(({ intersecting }) => intersecting);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDayGroupFullDate = (dayGroup: DayGroup): string => {
|
const getDayGroupFullDate = (dayGroup: DayGroup): string => {
|
||||||
const { month, year } = dayGroup.monthGroup.yearMonth;
|
const { month, year } = dayGroup.monthGroup.yearMonth;
|
||||||
const date = fromTimelinePlainDate({
|
const date = fromTimelinePlainDate({
|
||||||
@@ -52,7 +57,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each filterIntersecting(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)}
|
{#each filterIsInOrNearViewport(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)}
|
||||||
{@const isDayGroupSelected = assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
|
{@const isDayGroupSelected = assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<section
|
<section
|
||||||
|
|||||||
@@ -642,7 +642,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#each timelineManager.months as monthGroup (monthGroup.viewId)}
|
{#each timelineManager.months as monthGroup (monthGroup.viewId)}
|
||||||
{@const display = monthGroup.intersecting}
|
{@const isInOrNearViewport = monthGroup.isInOrNearViewport}
|
||||||
{@const absoluteHeight = monthGroup.top}
|
{@const absoluteHeight = monthGroup.top}
|
||||||
|
|
||||||
{#if !monthGroup.isLoaded}
|
{#if !monthGroup.isLoaded}
|
||||||
@@ -654,7 +654,7 @@
|
|||||||
>
|
>
|
||||||
<Skeleton {invisible} height={monthGroup.height} title={monthGroup.monthGroupTitle} />
|
<Skeleton {invisible} height={monthGroup.height} title={monthGroup.monthGroupTitle} />
|
||||||
</div>
|
</div>
|
||||||
{:else if display}
|
{:else if isInOrNearViewport}
|
||||||
<div
|
<div
|
||||||
class="month-group"
|
class="month-group"
|
||||||
style:height={monthGroup.height + 'px'}
|
style:height={monthGroup.height + 'px'}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export abstract class VirtualScrollManager {
|
|||||||
return this.viewportWidth === 0 || this.viewportHeight === 0;
|
return this.viewportWidth === 0 || this.viewportHeight === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateIntersections(): void {}
|
protected updateViewportProximities(): void {}
|
||||||
|
|
||||||
protected updateViewportGeometry(_: boolean) {}
|
protected updateViewportGeometry(_: boolean) {}
|
||||||
|
|
||||||
@@ -156,12 +156,12 @@ export abstract class VirtualScrollManager {
|
|||||||
const scrollTop = this.scrollTop;
|
const scrollTop = this.scrollTop;
|
||||||
if (this.#scrollTop !== scrollTop) {
|
if (this.#scrollTop !== scrollTop) {
|
||||||
this.#scrollTop = scrollTop;
|
this.#scrollTop = scrollTop;
|
||||||
this.updateIntersections();
|
this.updateViewportProximities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshLayout() {
|
refreshLayout() {
|
||||||
this.updateIntersections();
|
this.updateViewportProximities();
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {}
|
destroy(): void {}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class DayGroup {
|
|||||||
|
|
||||||
height = $state(0);
|
height = $state(0);
|
||||||
width = $state(0);
|
width = $state(0);
|
||||||
intersecting = $derived.by(() => this.viewerAssets.some((viewAsset) => viewAsset.intersecting));
|
isInOrNearViewport = $derived.by(() => this.viewerAssets.some((viewAsset) => viewAsset.isInOrNearViewport));
|
||||||
|
|
||||||
#top: number = $state(0);
|
#top: number = $state(0);
|
||||||
#start: number = $state(0);
|
#start: number = $state(0);
|
||||||
@@ -137,7 +137,7 @@ export class DayGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(options: CommonLayoutOptions, noDefer: boolean) {
|
layout(options: CommonLayoutOptions, noDefer: boolean) {
|
||||||
if (!noDefer && !this.monthGroup.intersecting && !this.monthGroup.timelineManager.isScrollingOnLoad) {
|
if (!noDefer && !this.monthGroup.isInOrNearViewport && !this.monthGroup.timelineManager.isScrollingOnLoad) {
|
||||||
this.#deferredLayout = true;
|
this.#deferredLayout = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,68 +6,64 @@ const {
|
|||||||
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
|
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
|
||||||
} = TUNABLES;
|
} = TUNABLES;
|
||||||
|
|
||||||
export function updateIntersectionMonthGroup(timelineManager: TimelineManager, month: MonthGroup) {
|
export function isIntersecting(regionTop: number, regionBottom: number, otherTop: number, otherBottom: number) {
|
||||||
const actuallyIntersecting = calculateMonthGroupIntersecting(timelineManager, month, 0, 0);
|
return (
|
||||||
let preIntersecting = false;
|
(regionTop >= otherTop && regionTop < otherBottom) ||
|
||||||
if (!actuallyIntersecting) {
|
(regionBottom >= otherTop && regionBottom < otherBottom) ||
|
||||||
preIntersecting = calculateMonthGroupIntersecting(
|
(regionTop < otherTop && regionBottom >= otherBottom)
|
||||||
timelineManager,
|
|
||||||
month,
|
|
||||||
INTERSECTION_EXPAND_TOP,
|
|
||||||
INTERSECTION_EXPAND_BOTTOM,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
month.intersecting = actuallyIntersecting || preIntersecting;
|
|
||||||
month.actuallyIntersecting = actuallyIntersecting;
|
export enum ViewportProximity {
|
||||||
if (preIntersecting || actuallyIntersecting) {
|
FarFromViewport,
|
||||||
|
NearViewport,
|
||||||
|
InViewport,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isInViewport(state: ViewportProximity): boolean {
|
||||||
|
return state === ViewportProximity.InViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isInOrNearViewport(state: ViewportProximity): boolean {
|
||||||
|
return state !== ViewportProximity.FarFromViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateViewportProximity(regionTop: number, regionBottom: number, windowTop: number, windowBottom: number) {
|
||||||
|
if (regionBottom < windowTop - INTERSECTION_EXPAND_TOP || regionTop >= windowBottom + INTERSECTION_EXPAND_BOTTOM) {
|
||||||
|
return ViewportProximity.FarFromViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regionBottom < windowTop || regionTop >= windowBottom) {
|
||||||
|
return ViewportProximity.NearViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ViewportProximity.InViewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateMonthGroupViewportProximity(timelineManager: TimelineManager, month: MonthGroup) {
|
||||||
|
const proximity = calculateViewportProximity(
|
||||||
|
month.top,
|
||||||
|
month.top + month.height,
|
||||||
|
timelineManager.visibleWindow.top,
|
||||||
|
timelineManager.visibleWindow.bottom,
|
||||||
|
);
|
||||||
|
|
||||||
|
month.viewportProximity = proximity;
|
||||||
|
if (isInOrNearViewport(proximity)) {
|
||||||
timelineManager.clearDeferredLayout(month);
|
timelineManager.clearDeferredLayout(month);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function calculateViewerAssetViewportProximity(
|
||||||
* General function to check if a rectangular region intersects with a window.
|
|
||||||
* @param regionTop - Top position of the region to check
|
|
||||||
* @param regionBottom - Bottom position of the region to check
|
|
||||||
* @param windowTop - Top position of the window
|
|
||||||
* @param windowBottom - Bottom position of the window
|
|
||||||
* @returns true if the region intersects with the window
|
|
||||||
*/
|
|
||||||
export function isIntersecting(regionTop: number, regionBottom: number, windowTop: number, windowBottom: number) {
|
|
||||||
return (
|
|
||||||
(regionTop >= windowTop && regionTop < windowBottom) ||
|
|
||||||
(regionBottom >= windowTop && regionBottom < windowBottom) ||
|
|
||||||
(regionTop < windowTop && regionBottom >= windowBottom)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateMonthGroupIntersecting(
|
|
||||||
timelineManager: TimelineManager,
|
|
||||||
monthGroup: MonthGroup,
|
|
||||||
expandTop: number,
|
|
||||||
expandBottom: number,
|
|
||||||
) {
|
|
||||||
const monthGroupTop = monthGroup.top;
|
|
||||||
const monthGroupBottom = monthGroupTop + monthGroup.height;
|
|
||||||
const topWindow = timelineManager.visibleWindow.top - expandTop;
|
|
||||||
const bottomWindow = timelineManager.visibleWindow.bottom + expandBottom;
|
|
||||||
|
|
||||||
return isIntersecting(monthGroupTop, monthGroupBottom, topWindow, bottomWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate intersection for viewer assets with additional parameters like header height
|
|
||||||
*/
|
|
||||||
export function calculateViewerAssetIntersecting(
|
|
||||||
timelineManager: TimelineManager,
|
timelineManager: TimelineManager,
|
||||||
positionTop: number,
|
positionTop: number,
|
||||||
positionHeight: number,
|
positionHeight: number,
|
||||||
expandTop: number = INTERSECTION_EXPAND_TOP,
|
|
||||||
expandBottom: number = INTERSECTION_EXPAND_BOTTOM,
|
|
||||||
) {
|
) {
|
||||||
const topWindow = timelineManager.visibleWindow.top - timelineManager.headerHeight - expandTop;
|
const headerHeight = timelineManager.headerHeight;
|
||||||
const bottomWindow = timelineManager.visibleWindow.bottom + timelineManager.headerHeight + expandBottom;
|
return calculateViewportProximity(
|
||||||
|
positionTop,
|
||||||
const positionBottom = positionTop + positionHeight;
|
positionTop + positionHeight,
|
||||||
|
timelineManager.visibleWindow.top - headerHeight,
|
||||||
return isIntersecting(positionTop, positionBottom, topWindow, bottomWindow);
|
timelineManager.visibleWindow.bottom + headerHeight,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import {
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ViewportProximity,
|
||||||
|
isInOrNearViewport as isInOrNearViewportUtil,
|
||||||
|
isInViewport as isInViewportUtil,
|
||||||
|
} from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
||||||
import { SvelteSet } from 'svelte/reactivity';
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
import { DayGroup } from './day-group.svelte';
|
import { DayGroup } from './day-group.svelte';
|
||||||
import { GroupInsertionCache } from './group-insertion-cache.svelte';
|
import { GroupInsertionCache } from './group-insertion-cache.svelte';
|
||||||
@@ -25,8 +30,7 @@ import type { AssetDescriptor, Direction, MoveAsset, TimelineAsset } from './typ
|
|||||||
import { ViewerAsset } from './viewer-asset.svelte';
|
import { ViewerAsset } from './viewer-asset.svelte';
|
||||||
|
|
||||||
export class MonthGroup {
|
export class MonthGroup {
|
||||||
#intersecting: boolean = $state(false);
|
#viewportProximity: ViewportProximity = $state(ViewportProximity.FarFromViewport);
|
||||||
actuallyIntersecting: boolean = $state(false);
|
|
||||||
isLoaded: boolean = $state(false);
|
isLoaded: boolean = $state(false);
|
||||||
dayGroups: DayGroup[] = $state([]);
|
dayGroups: DayGroup[] = $state([]);
|
||||||
readonly timelineManager: TimelineManager;
|
readonly timelineManager: TimelineManager;
|
||||||
@@ -78,21 +82,25 @@ export class MonthGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set intersecting(newValue: boolean) {
|
set viewportProximity(newValue: ViewportProximity) {
|
||||||
const old = this.#intersecting;
|
const old = this.#viewportProximity;
|
||||||
if (old === newValue) {
|
if (old === newValue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#intersecting = newValue;
|
this.#viewportProximity = newValue;
|
||||||
if (newValue) {
|
if (isInOrNearViewportUtil(newValue)) {
|
||||||
void this.timelineManager.loadMonthGroup(this.yearMonth);
|
void this.timelineManager.loadMonthGroup(this.yearMonth);
|
||||||
} else {
|
} else {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get intersecting() {
|
get isInOrNearViewport() {
|
||||||
return this.#intersecting;
|
return isInOrNearViewportUtil(this.#viewportProximity);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isInViewport() {
|
||||||
|
return isInViewportUtil(this.#viewportProximity);
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastDayGroup() {
|
get lastDayGroup() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/Virtual
|
|||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
import { GroupInsertionCache } from '$lib/managers/timeline-manager/group-insertion-cache.svelte';
|
import { GroupInsertionCache } from '$lib/managers/timeline-manager/group-insertion-cache.svelte';
|
||||||
import { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
import { updateMonthGroupViewportProximity } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
||||||
import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte';
|
import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte';
|
||||||
import { loadFromTimeBuckets } from '$lib/managers/timeline-manager/internal/load-support.svelte';
|
import { loadFromTimeBuckets } from '$lib/managers/timeline-manager/internal/load-support.svelte';
|
||||||
import {
|
import {
|
||||||
@@ -91,7 +91,7 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
static #INIT_OPTIONS = {};
|
static #INIT_OPTIONS = {};
|
||||||
#websocketSupport: WebsocketSupport | undefined;
|
#websocketSupport: WebsocketSupport | undefined;
|
||||||
#options: TimelineManagerOptions = TimelineManager.#INIT_OPTIONS;
|
#options: TimelineManagerOptions = TimelineManager.#INIT_OPTIONS;
|
||||||
#updatingIntersections = false;
|
#updatingViewportProximities = false;
|
||||||
#scrollableElement: HTMLElement | undefined = $state();
|
#scrollableElement: HTMLElement | undefined = $state();
|
||||||
#showAssetOwners = new PersistedLocalStorage<boolean>('album-show-asset-owners', false);
|
#showAssetOwners = new PersistedLocalStorage<boolean>('album-show-asset-owners', false);
|
||||||
#unsubscribes: Array<() => void> = [];
|
#unsubscribes: Array<() => void> = [];
|
||||||
@@ -198,17 +198,21 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
return clamp((this.visibleWindow.top - month.top) / month.height, 0, 1);
|
return clamp((this.visibleWindow.top - month.top) / month.height, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
override updateIntersections() {
|
override updateViewportProximities() {
|
||||||
if (this.#updatingIntersections || !this.isInitialized || this.visibleWindow.bottom === this.visibleWindow.top) {
|
if (
|
||||||
|
this.#updatingViewportProximities ||
|
||||||
|
!this.isInitialized ||
|
||||||
|
this.visibleWindow.bottom === this.visibleWindow.top
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#updatingIntersections = true;
|
this.#updatingViewportProximities = true;
|
||||||
|
|
||||||
for (const month of this.months) {
|
for (const month of this.months) {
|
||||||
updateIntersectionMonthGroup(this, month);
|
updateMonthGroupViewportProximity(this, month);
|
||||||
}
|
}
|
||||||
|
|
||||||
const month = this.months.find((month) => month.actuallyIntersecting);
|
const month = this.months.find((month) => month.isInViewport);
|
||||||
const viewportTopRatioInMonth = this.#calculateVewportTopRatioInMonth(month);
|
const viewportTopRatioInMonth = this.#calculateVewportTopRatioInMonth(month);
|
||||||
const monthBottomViewportRatio = this.#calculateMonthBottomViewportRatio(month);
|
const monthBottomViewportRatio = this.#calculateMonthBottomViewportRatio(month);
|
||||||
|
|
||||||
@@ -218,7 +222,7 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
viewportTopRatioInMonth,
|
viewportTopRatioInMonth,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.#updatingIntersections = false;
|
this.#updatingViewportProximities = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearDeferredLayout(month: MonthGroup) {
|
clearDeferredLayout(month: MonthGroup) {
|
||||||
@@ -317,7 +321,7 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
for (const month of this.months) {
|
for (const month of this.months) {
|
||||||
updateGeometry(this, month, { invalidateHeight: changedWidth });
|
updateGeometry(this, month, { invalidateHeight: changedWidth });
|
||||||
}
|
}
|
||||||
this.updateIntersections();
|
this.updateViewportProximities();
|
||||||
if (changedWidth) {
|
if (changedWidth) {
|
||||||
this.#createScrubberMonths();
|
this.#createScrubberMonths();
|
||||||
}
|
}
|
||||||
@@ -353,7 +357,7 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
}, cancelable);
|
}, cancelable);
|
||||||
if (executionStatus === 'LOADED') {
|
if (executionStatus === 'LOADED') {
|
||||||
updateGeometry(this, monthGroup, { invalidateHeight: false });
|
updateGeometry(this, monthGroup, { invalidateHeight: false });
|
||||||
this.updateIntersections();
|
this.updateViewportProximities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,7 +542,7 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
updateGeometry(this, month, { invalidateHeight: true });
|
updateGeometry(this, month, { invalidateHeight: true });
|
||||||
}
|
}
|
||||||
if (changedGeometry) {
|
if (changedGeometry) {
|
||||||
this.updateIntersections();
|
this.updateViewportProximities();
|
||||||
}
|
}
|
||||||
return { updated, notUpdated, changedGeometry };
|
return { updated, notUpdated, changedGeometry };
|
||||||
}
|
}
|
||||||
@@ -547,7 +551,7 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
for (const month of this.months) {
|
for (const month of this.months) {
|
||||||
updateGeometry(this, month, { invalidateHeight: true });
|
updateGeometry(this, month, { invalidateHeight: true });
|
||||||
}
|
}
|
||||||
this.updateIntersections();
|
this.updateViewportProximities();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstAsset(): TimelineAsset | undefined {
|
getFirstAsset(): TimelineAsset | undefined {
|
||||||
@@ -626,6 +630,6 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
month.sortDayGroups();
|
month.sortDayGroups();
|
||||||
updateGeometry(this, month, { invalidateHeight: true });
|
updateGeometry(this, month, { invalidateHeight: true });
|
||||||
}
|
}
|
||||||
this.updateIntersections();
|
this.updateViewportProximities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,7 @@ import type { TimelineAsset } from './types';
|
|||||||
|
|
||||||
export const assetSnapshot = (asset: TimelineAsset): TimelineAsset => $state.snapshot(asset);
|
export const assetSnapshot = (asset: TimelineAsset): TimelineAsset => $state.snapshot(asset);
|
||||||
export const assetsSnapshot = (assets: TimelineAsset[]) => assets.map((asset) => $state.snapshot(asset));
|
export const assetsSnapshot = (assets: TimelineAsset[]) => assets.map((asset) => $state.snapshot(asset));
|
||||||
|
|
||||||
|
export function filterIsInOrNearViewport<T extends { isInOrNearViewport: boolean }>(items: T[]) {
|
||||||
|
return items.filter(({ isInOrNearViewport }) => isInOrNearViewport);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,23 +1,31 @@
|
|||||||
import type { CommonPosition } from '$lib/utils/layout-utils';
|
import type { CommonPosition } from '$lib/utils/layout-utils';
|
||||||
|
|
||||||
import type { DayGroup } from './day-group.svelte';
|
import type { DayGroup } from './day-group.svelte';
|
||||||
import { calculateViewerAssetIntersecting } from './internal/intersection-support.svelte';
|
import {
|
||||||
|
ViewportProximity,
|
||||||
|
calculateViewerAssetViewportProximity,
|
||||||
|
isInOrNearViewport,
|
||||||
|
} from './internal/intersection-support.svelte';
|
||||||
import type { TimelineAsset } from './types';
|
import type { TimelineAsset } from './types';
|
||||||
|
|
||||||
export class ViewerAsset {
|
export class ViewerAsset {
|
||||||
readonly #group: DayGroup;
|
readonly #group: DayGroup;
|
||||||
|
|
||||||
intersecting = $derived.by(() => {
|
#viewportProximity = $derived.by(() => {
|
||||||
if (!this.position) {
|
if (!this.position) {
|
||||||
return false;
|
return ViewportProximity.FarFromViewport;
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = this.#group.monthGroup.timelineManager;
|
const store = this.#group.monthGroup.timelineManager;
|
||||||
const positionTop = this.#group.absoluteDayGroupTop + this.position.top;
|
const positionTop = this.#group.absoluteDayGroupTop + this.position.top;
|
||||||
|
|
||||||
return calculateViewerAssetIntersecting(store, positionTop, this.position.height);
|
return calculateViewerAssetViewportProximity(store, positionTop, this.position.height);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
get isInOrNearViewport() {
|
||||||
|
return isInOrNearViewport(this.#viewportProximity);
|
||||||
|
}
|
||||||
|
|
||||||
position: CommonPosition | undefined = $state.raw();
|
position: CommonPosition | undefined = $state.raw();
|
||||||
asset: TimelineAsset = <TimelineAsset>$state();
|
asset: TimelineAsset = <TimelineAsset>$state();
|
||||||
id: string = $derived(this.asset.id);
|
id: string = $derived(this.asset.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user