mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
refactor(web): Extract VirtualScrollManager base class from TimelineManager (#23017)
Some checks failed
CLI Build / CLI Publish (push) Has been cancelled
CLI Build / Docker (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Docker / pre-job (push) Has been cancelled
Docker / Re-Tag ML (push) Has been cancelled
Docker / Re-Tag Server (push) Has been cancelled
Docker / Build and Push ML (push) Has been cancelled
Docker / Build and Push Server (push) Has been cancelled
Docker / Docker Build & Push Server Success (push) Has been cancelled
Docker / Docker Build & Push ML Success (push) Has been cancelled
Docs build / pre-job (push) Has been cancelled
Docs build / Docs Build (push) Has been cancelled
Zizmor / Zizmor (push) Has been cancelled
Static Code Analysis / pre-job (push) Has been cancelled
Static Code Analysis / Run Dart Code Analysis (push) Has been cancelled
Test / pre-job (push) Has been cancelled
Test / Test & Lint Server (push) Has been cancelled
Test / Unit Test CLI (push) Has been cancelled
Test / Unit Test CLI (Windows) (push) Has been cancelled
Test / Lint Web (push) Has been cancelled
Test / Test Web (push) Has been cancelled
Test / Test i18n (push) Has been cancelled
Test / End-to-End Lint (push) Has been cancelled
Test / Medium Tests (Server) (push) Has been cancelled
Test / End-to-End Tests (Server & CLI) (push) Has been cancelled
Test / End-to-End Tests (Web) (push) Has been cancelled
Test / End-to-End Tests Success (push) Has been cancelled
Test / Unit Test Mobile (push) Has been cancelled
Test / Unit Test ML (push) Has been cancelled
Test / .github Files Formatting (push) Has been cancelled
Test / ShellCheck (push) Has been cancelled
Test / OpenAPI Clients (push) Has been cancelled
Test / SQL Schema Checks (push) Has been cancelled
Some checks failed
CLI Build / CLI Publish (push) Has been cancelled
CLI Build / Docker (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Docker / pre-job (push) Has been cancelled
Docker / Re-Tag ML (push) Has been cancelled
Docker / Re-Tag Server (push) Has been cancelled
Docker / Build and Push ML (push) Has been cancelled
Docker / Build and Push Server (push) Has been cancelled
Docker / Docker Build & Push Server Success (push) Has been cancelled
Docker / Docker Build & Push ML Success (push) Has been cancelled
Docs build / pre-job (push) Has been cancelled
Docs build / Docs Build (push) Has been cancelled
Zizmor / Zizmor (push) Has been cancelled
Static Code Analysis / pre-job (push) Has been cancelled
Static Code Analysis / Run Dart Code Analysis (push) Has been cancelled
Test / pre-job (push) Has been cancelled
Test / Test & Lint Server (push) Has been cancelled
Test / Unit Test CLI (push) Has been cancelled
Test / Unit Test CLI (Windows) (push) Has been cancelled
Test / Lint Web (push) Has been cancelled
Test / Test Web (push) Has been cancelled
Test / Test i18n (push) Has been cancelled
Test / End-to-End Lint (push) Has been cancelled
Test / Medium Tests (Server) (push) Has been cancelled
Test / End-to-End Tests (Server & CLI) (push) Has been cancelled
Test / End-to-End Tests (Web) (push) Has been cancelled
Test / End-to-End Tests Success (push) Has been cancelled
Test / Unit Test Mobile (push) Has been cancelled
Test / Unit Test ML (push) Has been cancelled
Test / .github Files Formatting (push) Has been cancelled
Test / ShellCheck (push) Has been cancelled
Test / OpenAPI Clients (push) Has been cancelled
Test / SQL Schema Checks (push) Has been cancelled
Extract common virtual scrolling functionality from TimelineManager into a new abstract VirtualScrollManager base class. This refactoring improves code organization and enables reuse of virtual scrolling logic. Changes: - Create new VirtualScrollManager abstract base class with common virtual scrolling state and methods - Refactor TimelineManager to extend VirtualScrollManager - Rename 'assetsHeight' to 'bodySectionHeight' for semantic clarity - Convert methods to use override keyword where appropriate - Enable noImplicitOverride in tsconfig for better type safety - Fix ApiError and AbortError class definitions with override keywords
This commit is contained in:
parent
e7d6a066f8
commit
3174a27902
6 changed files with 215 additions and 173 deletions
|
|
@ -250,7 +250,7 @@
|
||||||
scrollToSegmentPercentage(0, timelineManager.topSectionHeight, scrubberMonthScrollPercent);
|
scrollToSegmentPercentage(0, timelineManager.topSectionHeight, scrubberMonthScrollPercent);
|
||||||
} else if (leadOut) {
|
} else if (leadOut) {
|
||||||
scrollToSegmentPercentage(
|
scrollToSegmentPercentage(
|
||||||
timelineManager.topSectionHeight + timelineManager.assetsHeight,
|
timelineManager.topSectionHeight + timelineManager.bodySectionHeight,
|
||||||
timelineManager.bottomSectionHeight,
|
timelineManager.bottomSectionHeight,
|
||||||
scrubberMonthScrollPercent,
|
scrubberMonthScrollPercent,
|
||||||
);
|
);
|
||||||
|
|
@ -611,7 +611,7 @@
|
||||||
style:position="absolute"
|
style:position="absolute"
|
||||||
style:left="0"
|
style:left="0"
|
||||||
style:right="0"
|
style:right="0"
|
||||||
style:transform={`translate3d(0,${timelineManager.topSectionHeight + timelineManager.assetsHeight}px,0)`}
|
style:transform={`translate3d(0,${timelineManager.topSectionHeight + timelineManager.bodySectionHeight}px,0)`}
|
||||||
></div>
|
></div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
|
|
||||||
|
type LayoutOptions = {
|
||||||
|
headerHeight: number;
|
||||||
|
rowHeight: number;
|
||||||
|
gap: number;
|
||||||
|
};
|
||||||
|
export abstract class VirtualScrollManager {
|
||||||
|
topSectionHeight = $state(0);
|
||||||
|
bodySectionHeight = $state(0);
|
||||||
|
bottomSectionHeight = $state(0);
|
||||||
|
totalViewerHeight = $derived.by(() => this.topSectionHeight + this.bodySectionHeight + this.bottomSectionHeight);
|
||||||
|
|
||||||
|
visibleWindow = $derived.by(() => ({
|
||||||
|
top: this.#scrollTop,
|
||||||
|
bottom: this.#scrollTop + this.viewportHeight,
|
||||||
|
}));
|
||||||
|
|
||||||
|
#viewportHeight = $state(0);
|
||||||
|
#viewportWidth = $state(0);
|
||||||
|
#scrollTop = $state(0);
|
||||||
|
#rowHeight = $state(235);
|
||||||
|
#headerHeight = $state(48);
|
||||||
|
#gap = $state(12);
|
||||||
|
#scrolling = $state(false);
|
||||||
|
#suspendTransitions = $state(false);
|
||||||
|
#resetScrolling = debounce(() => (this.#scrolling = false), 1000);
|
||||||
|
#resetSuspendTransitions = debounce(() => (this.suspendTransitions = false), 1000);
|
||||||
|
#justifiedLayoutOptions = $derived.by(() => ({
|
||||||
|
spacing: 2,
|
||||||
|
heightTolerance: 0.15,
|
||||||
|
rowHeight: this.#rowHeight,
|
||||||
|
rowWidth: Math.floor(this.viewportWidth),
|
||||||
|
}));
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.setLayoutOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
get scrollTop() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get justifiedLayoutOptions() {
|
||||||
|
return this.#justifiedLayoutOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
get maxScrollPercent() {
|
||||||
|
const totalHeight = this.totalViewerHeight;
|
||||||
|
return (totalHeight - this.viewportHeight) / totalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
get maxScroll() {
|
||||||
|
return this.totalViewerHeight - this.viewportHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
#setHeaderHeight(value: number) {
|
||||||
|
if (this.#headerHeight == value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.#headerHeight = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headerHeight() {
|
||||||
|
return this.#headerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
#setGap(value: number) {
|
||||||
|
if (this.#gap == value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.#gap = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get gap() {
|
||||||
|
return this.#gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#setRowHeight(value: number) {
|
||||||
|
if (this.#rowHeight == value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.#rowHeight = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get rowHeight() {
|
||||||
|
return this.#rowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
set scrolling(value: boolean) {
|
||||||
|
this.#scrolling = value;
|
||||||
|
if (value) {
|
||||||
|
this.suspendTransitions = true;
|
||||||
|
this.#resetScrolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get scrolling() {
|
||||||
|
return this.#scrolling;
|
||||||
|
}
|
||||||
|
|
||||||
|
set suspendTransitions(value: boolean) {
|
||||||
|
this.#suspendTransitions = value;
|
||||||
|
if (value) {
|
||||||
|
this.#resetSuspendTransitions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get suspendTransitions() {
|
||||||
|
return this.#suspendTransitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
set viewportWidth(value: number) {
|
||||||
|
const changed = value !== this.#viewportWidth;
|
||||||
|
this.#viewportWidth = value;
|
||||||
|
this.suspendTransitions = true;
|
||||||
|
void this.updateViewportGeometry(changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
get viewportWidth() {
|
||||||
|
return this.#viewportWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
set viewportHeight(value: number) {
|
||||||
|
this.#viewportHeight = value;
|
||||||
|
this.#suspendTransitions = true;
|
||||||
|
void this.updateViewportGeometry(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
get viewportHeight() {
|
||||||
|
return this.#viewportHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasEmptyViewport() {
|
||||||
|
return this.viewportWidth === 0 || this.viewportHeight === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateIntersections(): void {}
|
||||||
|
|
||||||
|
protected updateViewportGeometry(_: boolean) {}
|
||||||
|
|
||||||
|
setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }: Partial<LayoutOptions> = {}) {
|
||||||
|
let changed = false;
|
||||||
|
changed ||= this.#setHeaderHeight(headerHeight);
|
||||||
|
changed ||= this.#setGap(gap);
|
||||||
|
changed ||= this.#setRowHeight(rowHeight);
|
||||||
|
if (changed) {
|
||||||
|
this.refreshLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSlidingWindow() {
|
||||||
|
const scrollTop = this.scrollTop;
|
||||||
|
if (this.#scrollTop !== scrollTop) {
|
||||||
|
this.#scrollTop = scrollTop;
|
||||||
|
this.updateIntersections();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshLayout() {
|
||||||
|
this.updateIntersections();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {}
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ export function layoutMonthGroup(timelineManager: TimelineManager, month: MonthG
|
||||||
let dayGroupRow = 0;
|
let dayGroupRow = 0;
|
||||||
let dayGroupCol = 0;
|
let dayGroupCol = 0;
|
||||||
|
|
||||||
const options = timelineManager.createLayoutOptions();
|
const options = timelineManager.justifiedLayoutOptions;
|
||||||
for (const dayGroup of month.dayGroups) {
|
for (const dayGroup of month.dayGroups) {
|
||||||
dayGroup.layout(options, noDefer);
|
dayGroup.layout(options, noDefer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
import { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk';
|
import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
|
||||||
|
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
|
|
||||||
import { CancellableTask } from '$lib/utils/cancellable-task';
|
|
||||||
import { toTimelineAsset, type TimelineDateTime, type TimelineYearMonth } from '$lib/utils/timeline-util';
|
|
||||||
|
|
||||||
import { clamp, debounce, isEqual } from 'lodash-es';
|
|
||||||
import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
||||||
|
|
||||||
import { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
import { updateIntersectionMonthGroup } 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';
|
||||||
|
|
@ -24,6 +16,11 @@ import {
|
||||||
retrieveRange as retrieveRangeUtil,
|
retrieveRange as retrieveRangeUtil,
|
||||||
} from '$lib/managers/timeline-manager/internal/search-support.svelte';
|
} from '$lib/managers/timeline-manager/internal/search-support.svelte';
|
||||||
import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte';
|
import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte';
|
||||||
|
import { CancellableTask } from '$lib/utils/cancellable-task';
|
||||||
|
import { toTimelineAsset, type TimelineDateTime, type TimelineYearMonth } from '$lib/utils/timeline-util';
|
||||||
|
import { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk';
|
||||||
|
import { clamp, isEqual } from 'lodash-es';
|
||||||
|
import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||||
import { DayGroup } from './day-group.svelte';
|
import { DayGroup } from './day-group.svelte';
|
||||||
import { isMismatched, updateObject } from './internal/utils.svelte';
|
import { isMismatched, updateObject } from './internal/utils.svelte';
|
||||||
import { MonthGroup } from './month-group.svelte';
|
import { MonthGroup } from './month-group.svelte';
|
||||||
|
|
@ -33,7 +30,6 @@ import type {
|
||||||
Direction,
|
Direction,
|
||||||
ScrubberMonth,
|
ScrubberMonth,
|
||||||
TimelineAsset,
|
TimelineAsset,
|
||||||
TimelineManagerLayoutOptions,
|
|
||||||
TimelineManagerOptions,
|
TimelineManagerOptions,
|
||||||
Viewport,
|
Viewport,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
@ -45,28 +41,32 @@ type ViewportTopMonthIntersection = {
|
||||||
// Where month bottom is in viewport (0 = viewport top, 1 = viewport bottom)
|
// Where month bottom is in viewport (0 = viewport top, 1 = viewport bottom)
|
||||||
monthBottomViewportRatio: number;
|
monthBottomViewportRatio: number;
|
||||||
};
|
};
|
||||||
export class TimelineManager {
|
export class TimelineManager extends VirtualScrollManager {
|
||||||
|
override bottomSectionHeight = $state(60);
|
||||||
|
|
||||||
|
override bodySectionHeight = $derived.by(() => {
|
||||||
|
let height = 0;
|
||||||
|
for (const month of this.months) {
|
||||||
|
height += month.height;
|
||||||
|
}
|
||||||
|
return height;
|
||||||
|
});
|
||||||
|
|
||||||
|
assetCount = $derived.by(() => {
|
||||||
|
let count = 0;
|
||||||
|
for (const month of this.months) {
|
||||||
|
count += month.assetsCount;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
|
||||||
isInitialized = $state(false);
|
isInitialized = $state(false);
|
||||||
months: MonthGroup[] = $state([]);
|
months: MonthGroup[] = $state([]);
|
||||||
topSectionHeight = $state(0);
|
|
||||||
bottomSectionHeight = $state(60);
|
|
||||||
assetsHeight = $derived(this.months.reduce((accumulator, b) => accumulator + b.height, 0));
|
|
||||||
totalViewerHeight = $derived(this.topSectionHeight + this.assetsHeight + this.bottomSectionHeight);
|
|
||||||
assetCount = $derived(this.months.reduce((accumulator, b) => accumulator + b.assetsCount, 0));
|
|
||||||
|
|
||||||
albumAssets: Set<string> = new SvelteSet();
|
albumAssets: Set<string> = new SvelteSet();
|
||||||
|
|
||||||
scrubberMonths: ScrubberMonth[] = $state([]);
|
scrubberMonths: ScrubberMonth[] = $state([]);
|
||||||
scrubberTimelineHeight: number = $state(0);
|
scrubberTimelineHeight: number = $state(0);
|
||||||
|
|
||||||
viewportTopMonthIntersection: ViewportTopMonthIntersection | undefined;
|
viewportTopMonthIntersection: ViewportTopMonthIntersection | undefined;
|
||||||
|
|
||||||
visibleWindow = $derived.by(() => ({
|
|
||||||
top: this.#scrollTop,
|
|
||||||
bottom: this.#scrollTop + this.viewportHeight,
|
|
||||||
}));
|
|
||||||
limitedScroll = $derived(this.maxScrollPercent < 0.5);
|
limitedScroll = $derived(this.maxScrollPercent < 0.5);
|
||||||
|
|
||||||
initTask = new CancellableTask(
|
initTask = new CancellableTask(
|
||||||
() => {
|
() => {
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
|
|
@ -83,32 +83,17 @@ export class TimelineManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
static #INIT_OPTIONS = {};
|
static #INIT_OPTIONS = {};
|
||||||
#viewportHeight = $state(0);
|
|
||||||
#viewportWidth = $state(0);
|
|
||||||
#scrollTop = $state(0);
|
|
||||||
#websocketSupport: WebsocketSupport | undefined;
|
#websocketSupport: WebsocketSupport | undefined;
|
||||||
|
|
||||||
#rowHeight = $state(235);
|
|
||||||
#headerHeight = $state(48);
|
|
||||||
#gap = $state(12);
|
|
||||||
|
|
||||||
#options: TimelineManagerOptions = TimelineManager.#INIT_OPTIONS;
|
#options: TimelineManagerOptions = TimelineManager.#INIT_OPTIONS;
|
||||||
|
|
||||||
#scrolling = $state(false);
|
|
||||||
#suspendTransitions = $state(false);
|
|
||||||
#resetScrolling = debounce(() => (this.#scrolling = false), 1000);
|
|
||||||
#resetSuspendTransitions = debounce(() => (this.suspendTransitions = false), 1000);
|
|
||||||
#updatingIntersections = false;
|
#updatingIntersections = false;
|
||||||
#scrollableElement: HTMLElement | undefined = $state();
|
#scrollableElement: HTMLElement | undefined = $state();
|
||||||
|
|
||||||
setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }: TimelineManagerLayoutOptions) {
|
constructor() {
|
||||||
let changed = false;
|
super();
|
||||||
changed ||= this.#setHeaderHeight(headerHeight);
|
}
|
||||||
changed ||= this.#setGap(gap);
|
|
||||||
changed ||= this.#setRowHeight(rowHeight);
|
override get scrollTop(): number {
|
||||||
if (changed) {
|
return this.#scrollableElement?.scrollTop ?? 0;
|
||||||
this.refreshLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set scrollableElement(element: HTMLElement | undefined) {
|
set scrollableElement(element: HTMLElement | undefined) {
|
||||||
|
|
@ -125,87 +110,6 @@ export class TimelineManager {
|
||||||
this.updateSlidingWindow();
|
this.updateSlidingWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
#setHeaderHeight(value: number) {
|
|
||||||
if (this.#headerHeight == value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.#headerHeight = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get headerHeight() {
|
|
||||||
return this.#headerHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
#setGap(value: number) {
|
|
||||||
if (this.#gap == value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.#gap = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get gap() {
|
|
||||||
return this.#gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#setRowHeight(value: number) {
|
|
||||||
if (this.#rowHeight == value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.#rowHeight = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get rowHeight() {
|
|
||||||
return this.#rowHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
set scrolling(value: boolean) {
|
|
||||||
this.#scrolling = value;
|
|
||||||
if (value) {
|
|
||||||
this.suspendTransitions = true;
|
|
||||||
this.#resetScrolling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get scrolling() {
|
|
||||||
return this.#scrolling;
|
|
||||||
}
|
|
||||||
|
|
||||||
set suspendTransitions(value: boolean) {
|
|
||||||
this.#suspendTransitions = value;
|
|
||||||
if (value) {
|
|
||||||
this.#resetSuspendTransitions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get suspendTransitions() {
|
|
||||||
return this.#suspendTransitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
set viewportWidth(value: number) {
|
|
||||||
const changed = value !== this.#viewportWidth;
|
|
||||||
this.#viewportWidth = value;
|
|
||||||
this.suspendTransitions = true;
|
|
||||||
this.#updateViewportGeometry(changed);
|
|
||||||
this.updateSlidingWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewportWidth() {
|
|
||||||
return this.#viewportWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
set viewportHeight(value: number) {
|
|
||||||
this.#viewportHeight = value;
|
|
||||||
this.#suspendTransitions = true;
|
|
||||||
void this.#updateViewportGeometry(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
get viewportHeight() {
|
|
||||||
return this.#viewportHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
async *assetsIterator(options?: {
|
async *assetsIterator(options?: {
|
||||||
startMonthGroup?: MonthGroup;
|
startMonthGroup?: MonthGroup;
|
||||||
startDayGroup?: DayGroup;
|
startDayGroup?: DayGroup;
|
||||||
|
|
@ -251,14 +155,6 @@ export class TimelineManager {
|
||||||
this.#websocketSupport = undefined;
|
this.#websocketSupport = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSlidingWindow() {
|
|
||||||
const scrollTop = this.#scrollableElement?.scrollTop ?? 0;
|
|
||||||
if (this.#scrollTop !== scrollTop) {
|
|
||||||
this.#scrollTop = scrollTop;
|
|
||||||
this.updateIntersections();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#calculateMonthBottomViewportRatio(month: MonthGroup | undefined) {
|
#calculateMonthBottomViewportRatio(month: MonthGroup | undefined) {
|
||||||
if (!month) {
|
if (!month) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -276,7 +172,7 @@ export class TimelineManager {
|
||||||
return clamp((this.visibleWindow.top - month.top) / month.height, 0, 1);
|
return clamp((this.visibleWindow.top - month.top) / month.height, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIntersections() {
|
override updateIntersections() {
|
||||||
if (this.#updatingIntersections || !this.isInitialized || this.visibleWindow.bottom === this.visibleWindow.top) {
|
if (this.#updatingIntersections || !this.isInitialized || this.visibleWindow.bottom === this.visibleWindow.top) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -325,7 +221,7 @@ export class TimelineManager {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.albumAssets.clear();
|
this.albumAssets.clear();
|
||||||
this.#updateViewportGeometry(false);
|
this.updateViewportGeometry(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOptions(options: TimelineManagerOptions) {
|
async updateOptions(options: TimelineManagerOptions) {
|
||||||
|
|
@ -337,7 +233,7 @@ export class TimelineManager {
|
||||||
}
|
}
|
||||||
await this.initTask.reset();
|
await this.initTask.reset();
|
||||||
await this.#init(options);
|
await this.#init(options);
|
||||||
this.#updateViewportGeometry(false);
|
this.updateViewportGeometry(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #init(options: TimelineManagerOptions) {
|
async #init(options: TimelineManagerOptions) {
|
||||||
|
|
@ -350,9 +246,10 @@ export class TimelineManager {
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public override destroy() {
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateViewport(viewport: Viewport) {
|
async updateViewport(viewport: Viewport) {
|
||||||
|
|
@ -371,14 +268,11 @@ export class TimelineManager {
|
||||||
const changedWidth = viewport.width !== this.viewportWidth;
|
const changedWidth = viewport.width !== this.viewportWidth;
|
||||||
this.viewportHeight = viewport.height;
|
this.viewportHeight = viewport.height;
|
||||||
this.viewportWidth = viewport.width;
|
this.viewportWidth = viewport.width;
|
||||||
this.#updateViewportGeometry(changedWidth);
|
this.updateViewportGeometry(changedWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateViewportGeometry(changedWidth: boolean) {
|
protected override updateViewportGeometry(changedWidth: boolean) {
|
||||||
if (!this.isInitialized) {
|
if (!this.isInitialized || this.hasEmptyViewport) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.viewportWidth === 0 || this.viewportHeight === 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const month of this.months) {
|
for (const month of this.months) {
|
||||||
|
|
@ -401,27 +295,6 @@ export class TimelineManager {
|
||||||
this.scrubberTimelineHeight = this.totalViewerHeight;
|
this.scrubberTimelineHeight = this.totalViewerHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
createLayoutOptions() {
|
|
||||||
const viewportWidth = this.viewportWidth;
|
|
||||||
|
|
||||||
return {
|
|
||||||
spacing: 2,
|
|
||||||
heightTolerance: 0.15,
|
|
||||||
rowHeight: this.#rowHeight,
|
|
||||||
rowWidth: Math.floor(viewportWidth),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get maxScrollPercent() {
|
|
||||||
const totalHeight = this.totalViewerHeight;
|
|
||||||
const max = (totalHeight - this.viewportHeight) / totalHeight;
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
get maxScroll() {
|
|
||||||
return this.totalViewerHeight - this.viewportHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadMonthGroup(yearMonth: TimelineYearMonth, options?: { cancelable: boolean }): Promise<void> {
|
async loadMonthGroup(yearMonth: TimelineYearMonth, options?: { cancelable: boolean }): Promise<void> {
|
||||||
let cancelable = true;
|
let cancelable = true;
|
||||||
if (options) {
|
if (options) {
|
||||||
|
|
@ -559,7 +432,7 @@ export class TimelineManager {
|
||||||
return [...unprocessedIds];
|
return [...unprocessedIds];
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshLayout() {
|
override refreshLayout() {
|
||||||
for (const month of this.months) {
|
for (const month of this.months) {
|
||||||
updateGeometry(this, month, { invalidateHeight: true });
|
updateGeometry(this, month, { invalidateHeight: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,14 +60,14 @@ interface UploadRequestOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AbortError extends Error {
|
export class AbortError extends Error {
|
||||||
name = 'AbortError';
|
override name = 'AbortError';
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiError extends Error {
|
class ApiError extends Error {
|
||||||
name = 'ApiError';
|
override name = 'ApiError';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public message: string,
|
public override message: string,
|
||||||
public statusCode: number,
|
public statusCode: number,
|
||||||
public details: string,
|
public details: string,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"module": "es2022",
|
"module": "es2022",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"noImplicitOverride": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue