mirror of https://github.com/immich-app/immich.git
refactor(web): Extract VirtualScrollManager base class from TimelineManager (#23017)
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 keywordspull/22632/head
parent
e7d6a066f8
commit
3174a27902
@ -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 {}
|
||||
}
|
||||
Loading…
Reference in New Issue