mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
Merge branch 'main' into feat/toggle-video-auto-play
This commit is contained in:
commit
31156b9669
22 changed files with 2763 additions and 2600 deletions
|
|
@ -17,9 +17,9 @@
|
|||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "~3.8.0",
|
||||
"@docusaurus/preset-classic": "~3.8.0",
|
||||
"@docusaurus/theme-common": "~3.8.0",
|
||||
"@docusaurus/core": "~3.9.0",
|
||||
"@docusaurus/preset-classic": "~3.9.0",
|
||||
"@docusaurus/theme-common": "~3.9.0",
|
||||
"@mdi/js": "^7.3.67",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"url": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "~3.8.0",
|
||||
"@docusaurus/module-type-aliases": "~3.9.0",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.7.0",
|
||||
"prettier": "^3.2.4",
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
"pngjs": "^7.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"sharp": "^0.34.3",
|
||||
"sharp": "^0.34.4",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"supertest": "^7.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tools]
|
||||
node = "22.20.0"
|
||||
flutter = "3.35.5"
|
||||
pnpm = "10.15.1"
|
||||
pnpm = "10.18.0"
|
||||
|
||||
[tools."github:CQLabs/homebrew-dcm"]
|
||||
version = "1.30.0"
|
||||
|
|
|
|||
|
|
@ -221,10 +221,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
context.scaffoldMessenger.hideCurrentSnackBar();
|
||||
|
||||
// send image to casting if the server has it
|
||||
if (asset.hasRemote) {
|
||||
final remoteAsset = asset as RemoteAsset;
|
||||
|
||||
ref.read(castProvider.notifier).loadMedia(remoteAsset, false);
|
||||
if (asset is RemoteAsset) {
|
||||
ref.read(castProvider.notifier).loadMedia(asset, false);
|
||||
} else {
|
||||
// casting cannot show local assets
|
||||
context.scaffoldMessenger.clearSnackBars();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.0.1",
|
||||
"description": "Monorepo for Immich",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67",
|
||||
"packageManager": "pnpm@10.18.0+sha512.e804f889f1cecc40d572db084eec3e4881739f8dec69c0ff10d2d1beff9a4e309383ba27b5b750059d7f4c149535b6cd0d2cb1ed3aeb739239a4284a68f40cfa",
|
||||
"engines": {
|
||||
"pnpm": ">=10.0.0"
|
||||
}
|
||||
|
|
|
|||
5155
pnpm-lock.yaml
generated
5155
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -27,7 +27,7 @@ onlyBuiltDependencies:
|
|||
- '@tailwindcss/oxide'
|
||||
overrides:
|
||||
canvas: 2.11.2
|
||||
sharp: ^0.34.3
|
||||
sharp: ^0.34.4
|
||||
packageExtensions:
|
||||
nestjs-kysely:
|
||||
dependencies:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM ghcr.io/immich-app/base-server-dev:202509210934@sha256:b5ce2d7eaf379d4cf15efd4bab180d8afc8a80d20b36c9800f4091aca6ae267e AS builder
|
||||
FROM ghcr.io/immich-app/base-server-dev:202510092146@sha256:124ec9659cba4a206924de5e3691f84acde16d75fa2b10b7007542424b696b96 AS builder
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
CI=1 \
|
||||
COREPACK_HOME=/tmp \
|
||||
|
|
@ -48,7 +48,7 @@ RUN --mount=type=cache,id=pnpm-cli,target=/buildcache/pnpm-store \
|
|||
pnpm --filter @immich/sdk --filter @immich/cli build && \
|
||||
pnpm --filter @immich/cli --prod --no-optional deploy /output/cli-pruned
|
||||
|
||||
FROM ghcr.io/immich-app/base-server-prod:202509210934@sha256:0c7eacf0ba88ca52e1a267cfc62d20d07792ea2c604818c2cbd37dc7dcefdac9
|
||||
FROM ghcr.io/immich-app/base-server-prod:202510092146@sha256:c39b9ad949e7777bce415e6931334aeff7331e04cb7f9df93f9ae44f6ff36b9e
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
ENV NODE_ENV=production \
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# dev build
|
||||
FROM ghcr.io/immich-app/base-server-dev:202509210934@sha256:b5ce2d7eaf379d4cf15efd4bab180d8afc8a80d20b36c9800f4091aca6ae267e AS dev
|
||||
FROM ghcr.io/immich-app/base-server-dev:202510092146@sha256:124ec9659cba4a206924de5e3691f84acde16d75fa2b10b7007542424b696b96 AS dev
|
||||
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
CI=1 \
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
"@opentelemetry/exporter-prometheus": "^0.205.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.205.0",
|
||||
"@opentelemetry/instrumentation-ioredis": "^0.53.0",
|
||||
"@opentelemetry/instrumentation-nestjs-core": "^0.51.0",
|
||||
"@opentelemetry/instrumentation-nestjs-core": "^0.52.0",
|
||||
"@opentelemetry/instrumentation-pg": "^0.58.0",
|
||||
"@opentelemetry/resources": "^2.0.1",
|
||||
"@opentelemetry/sdk-metrics": "^2.0.1",
|
||||
|
|
@ -67,7 +67,7 @@
|
|||
"compression": "^1.8.0",
|
||||
"cookie": "^1.0.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cron": "4.3.0",
|
||||
"cron": "4.3.3",
|
||||
"exiftool-vendored": "^28.8.0",
|
||||
"express": "^5.1.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
"multer": "^2.0.2",
|
||||
"nest-commander": "^3.16.0",
|
||||
"nestjs-cls": "^5.0.0",
|
||||
"nestjs-kysely": "^3.0.0",
|
||||
"nestjs-kysely": "3.0.0",
|
||||
"nestjs-otel": "^7.0.0",
|
||||
"nodemailer": "^7.0.0",
|
||||
"openid-client": "^6.3.3",
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
"sanitize-filename": "^1.6.3",
|
||||
"sanitize-html": "^2.14.0",
|
||||
"semver": "^7.6.2",
|
||||
"sharp": "^0.34.3",
|
||||
"sharp": "^0.34.4",
|
||||
"sirv": "^3.0.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"tailwindcss-preset-email": "^1.4.0",
|
||||
|
|
@ -164,6 +164,6 @@
|
|||
"node": "22.20.0"
|
||||
},
|
||||
"overrides": {
|
||||
"sharp": "^0.34.3"
|
||||
"sharp": "^0.34.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@immich/ui": "^0.29.0",
|
||||
"@immich/ui": "^0.34.0",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.11.5",
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
"geo-coordinates-parser": "^1.7.4",
|
||||
"geojson": "^0.5.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"happy-dom": "^18.0.1",
|
||||
"happy-dom": "^20.0.0",
|
||||
"intl-messageformat": "^10.7.11",
|
||||
"justified-layout": "^4.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/enhanced-img": "^0.8.0",
|
||||
"@sveltejs/kit": "^2.27.1",
|
||||
"@sveltejs/vite-plugin-svelte": "6.2.0",
|
||||
"@sveltejs/vite-plugin-svelte": "6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
|
|
@ -89,13 +89,13 @@
|
|||
"eslint-plugin-unicorn": "^61.0.2",
|
||||
"factory.ts": "^1.4.1",
|
||||
"globals": "^16.0.0",
|
||||
"happy-dom": "^18.0.1",
|
||||
"happy-dom": "^20.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^6.0.0",
|
||||
"svelte": "5.38.10",
|
||||
"svelte": "5.39.11",
|
||||
"svelte-check": "^4.1.5",
|
||||
"svelte-eslint-parser": "^1.3.3",
|
||||
"tailwindcss": "^4.1.7",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<Label size="small" color="primary" for={id}>{title}</Label>
|
||||
<Text size="small" color="muted" {id}>
|
||||
{#if versionHref}
|
||||
<Link external href={versionHref}>{version}</Link>
|
||||
<Link href={versionHref}>{version}</Link>
|
||||
{:else}
|
||||
{version}
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
import {
|
||||
AssetJobName,
|
||||
AssetTypeEnum,
|
||||
getAssetInfo,
|
||||
getAllAlbums,
|
||||
getAssetInfo,
|
||||
getStack,
|
||||
runAssetJobs,
|
||||
type AlbumResponseDto,
|
||||
|
|
@ -303,10 +303,8 @@
|
|||
|
||||
const handleStopSlideshow = async () => {
|
||||
try {
|
||||
// eslint-disable-next-line tscompat/tscompat
|
||||
if (document.fullscreenElement) {
|
||||
document.body.style.cursor = '';
|
||||
// eslint-disable-next-line tscompat/tscompat
|
||||
await document.exitFullscreen();
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -100,9 +100,7 @@
|
|||
};
|
||||
|
||||
const onShowSettings = async () => {
|
||||
// eslint-disable-next-line tscompat/tscompat
|
||||
if (document.fullscreenElement) {
|
||||
// eslint-disable-next-line tscompat/tscompat
|
||||
await document.exitFullscreen();
|
||||
}
|
||||
await modalManager.show(SlideshowSettingsModal);
|
||||
|
|
@ -114,11 +112,7 @@
|
|||
webkitIsFullScreen?: boolean;
|
||||
};
|
||||
|
||||
if (
|
||||
// eslint-disable-next-line tscompat/tscompat
|
||||
!document.fullscreenElement &&
|
||||
!doc.webkitIsFullScreen
|
||||
) {
|
||||
if (!document.fullscreenElement && !doc.webkitIsFullScreen) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -341,7 +341,6 @@
|
|||
scrubberMonthScrollPercent: monthGroupPercentY,
|
||||
});
|
||||
};
|
||||
/* eslint-disable tscompat/tscompat */
|
||||
const getTouch = (event: TouchEvent) => {
|
||||
if (event.touches.length === 1) {
|
||||
return event.touches[0];
|
||||
|
|
@ -386,7 +385,6 @@
|
|||
isHover = false;
|
||||
}
|
||||
};
|
||||
/* eslint-enable tscompat/tscompat */
|
||||
onMount(() => {
|
||||
document.addEventListener('touchmove', onTouchMove, { capture: true, passive: true });
|
||||
return () => {
|
||||
|
|
|
|||
|
|
@ -134,26 +134,12 @@
|
|||
element.scrollTop = top;
|
||||
}
|
||||
};
|
||||
const scrollBy = (y: number) => {
|
||||
if (element) {
|
||||
element.scrollBy(0, y);
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToTop = () => {
|
||||
scrollTo(0);
|
||||
};
|
||||
|
||||
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 = monthGroup!.findAssetAbsolutePosition(assetId);
|
||||
|
||||
while (timelineManager.scrollCompensation.monthGroup) {
|
||||
handleScrollCompensation(timelineManager.scrollCompensation);
|
||||
timelineManager.clearScrollCompensation();
|
||||
}
|
||||
return height;
|
||||
};
|
||||
const getAssetHeight = (assetId: string, monthGroup: MonthGroup) => monthGroup.findAssetAbsolutePosition(assetId);
|
||||
|
||||
const assetIsVisible = (assetTop: number): boolean => {
|
||||
if (!element) {
|
||||
|
|
@ -246,19 +232,6 @@
|
|||
// note: don't throttle, debounch, or otherwise do this function async - it causes flicker
|
||||
const updateSlidingWindow = () => timelineManager.updateSlidingWindow(element?.scrollTop || 0);
|
||||
|
||||
const handleScrollCompensation = ({ heightDelta, scrollTop }: { heightDelta?: number; scrollTop?: number }) => {
|
||||
if (heightDelta !== undefined) {
|
||||
scrollBy(heightDelta);
|
||||
} else if (scrollTop !== undefined) {
|
||||
scrollTo(scrollTop);
|
||||
}
|
||||
// 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 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 }) => (timelineManager.topSectionHeight = height);
|
||||
|
||||
onMount(() => {
|
||||
|
|
@ -666,7 +639,6 @@
|
|||
onSelect={({ title, assets }) => handleGroupSelect(timelineManager, title, assets)}
|
||||
onSelectAssetCandidates={handleSelectAssetCandidates}
|
||||
onSelectAssets={handleSelectAssets}
|
||||
onScrollCompensation={handleScrollCompensation}
|
||||
{customLayout}
|
||||
{onThumbnailClick}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@
|
|||
onSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void;
|
||||
onSelectAssets: (asset: TimelineAsset) => void;
|
||||
onSelectAssetCandidates: (asset: TimelineAsset | null) => void;
|
||||
onScrollCompensation: (compensation: { heightDelta?: number; scrollTop?: number }) => void;
|
||||
onThumbnailClick?: (
|
||||
asset: TimelineAsset,
|
||||
timelineManager: TimelineManager,
|
||||
|
|
@ -59,7 +58,7 @@
|
|||
onSelect,
|
||||
onSelectAssets,
|
||||
onSelectAssetCandidates,
|
||||
onScrollCompensation,
|
||||
|
||||
onThumbnailClick,
|
||||
}: Props = $props();
|
||||
|
||||
|
|
@ -134,13 +133,6 @@
|
|||
});
|
||||
return getDateLocaleString(date);
|
||||
};
|
||||
|
||||
$effect.root(() => {
|
||||
if (timelineManager.scrollCompensation.monthGroup === monthGroup) {
|
||||
onScrollCompensation(timelineManager.scrollCompensation);
|
||||
timelineManager.clearScrollCompensation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#each filterIntersecting(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export function calculateMonthGroupIntersecting(
|
|||
}
|
||||
|
||||
/**
|
||||
* Calculate intersection for viewer assets with additional parameters like header height and scroll compensation
|
||||
* Calculate intersection for viewer assets with additional parameters like header height
|
||||
*/
|
||||
export function calculateViewerAssetIntersecting(
|
||||
timelineManager: TimelineManager,
|
||||
|
|
@ -64,12 +64,8 @@ export function calculateViewerAssetIntersecting(
|
|||
expandTop: number = INTERSECTION_EXPAND_TOP,
|
||||
expandBottom: number = INTERSECTION_EXPAND_BOTTOM,
|
||||
) {
|
||||
const scrollCompensationHeightDelta = timelineManager.scrollCompensation?.heightDelta ?? 0;
|
||||
|
||||
const topWindow =
|
||||
timelineManager.visibleWindow.top - timelineManager.headerHeight - expandTop + scrollCompensationHeightDelta;
|
||||
const bottomWindow =
|
||||
timelineManager.visibleWindow.bottom + timelineManager.headerHeight + expandBottom + scrollCompensationHeightDelta;
|
||||
const topWindow = timelineManager.visibleWindow.top - timelineManager.headerHeight - expandTop;
|
||||
const bottomWindow = timelineManager.visibleWindow.bottom + timelineManager.headerHeight + expandBottom;
|
||||
|
||||
const positionBottom = positionTop + positionHeight;
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ export class MonthGroup {
|
|||
|
||||
#initialCount: number = 0;
|
||||
#sortOrder: AssetOrder = AssetOrder.Desc;
|
||||
percent: number = $state(0);
|
||||
|
||||
assetsCount: number = $derived(
|
||||
this.isLoaded
|
||||
|
|
@ -242,42 +241,31 @@ export class MonthGroup {
|
|||
if (this.#height === height) {
|
||||
return;
|
||||
}
|
||||
const { timelineManager: store, percent } = this;
|
||||
const index = store.months.indexOf(this);
|
||||
let needsIntersectionUpdate = false;
|
||||
const timelineManager = this.timelineManager;
|
||||
const index = timelineManager.months.indexOf(this);
|
||||
const heightDelta = height - this.#height;
|
||||
this.#height = height;
|
||||
const prevMonthGroup = store.months[index - 1];
|
||||
const prevMonthGroup = timelineManager.months[index - 1];
|
||||
if (prevMonthGroup) {
|
||||
const newTop = prevMonthGroup.#top + prevMonthGroup.#height;
|
||||
if (this.#top !== newTop) {
|
||||
this.#top = newTop;
|
||||
}
|
||||
}
|
||||
for (let cursor = index + 1; cursor < store.months.length; cursor++) {
|
||||
if (heightDelta === 0) {
|
||||
return;
|
||||
}
|
||||
for (let cursor = index + 1; cursor < timelineManager.months.length; cursor++) {
|
||||
const monthGroup = this.timelineManager.months[cursor];
|
||||
const newTop = monthGroup.#top + heightDelta;
|
||||
if (monthGroup.#top !== newTop) {
|
||||
monthGroup.#top = newTop;
|
||||
needsIntersectionUpdate = true;
|
||||
}
|
||||
}
|
||||
if (store.topIntersectingMonthGroup) {
|
||||
const currentIndex = store.months.indexOf(store.topIntersectingMonthGroup);
|
||||
if (currentIndex > 0) {
|
||||
if (index < currentIndex) {
|
||||
store.scrollCompensation = {
|
||||
heightDelta,
|
||||
scrollTop: undefined,
|
||||
monthGroup: this,
|
||||
};
|
||||
} else if (percent > 0) {
|
||||
const top = this.top + height * percent;
|
||||
store.scrollCompensation = {
|
||||
heightDelta: undefined,
|
||||
scrollTop: top,
|
||||
monthGroup: this,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (needsIntersectionUpdate) {
|
||||
timelineManager.updateIntersections();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ describe('TimelineManager', () => {
|
|||
|
||||
it('should load months in viewport', () => {
|
||||
expect(sdkMock.getTimeBuckets).toBeCalledTimes(1);
|
||||
expect(sdkMock.getTimeBucket).toHaveBeenCalledTimes(2);
|
||||
expect(sdkMock.getTimeBucket).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('calculates month height', () => {
|
||||
|
|
@ -82,13 +82,13 @@ describe('TimelineManager', () => {
|
|||
expect.arrayContaining([
|
||||
expect.objectContaining({ year: 2024, month: 3, height: 165.5 }),
|
||||
expect.objectContaining({ year: 2024, month: 2, height: 11_996 }),
|
||||
expect.objectContaining({ year: 2024, month: 1, height: 286 }),
|
||||
expect.objectContaining({ year: 2024, month: 1, height: 48 }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('calculates timeline height', () => {
|
||||
expect(timelineManager.timelineHeight).toBe(12_447.5);
|
||||
expect(timelineManager.timelineHeight).toBe(12_209.5);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ 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 { debounce, isEqual } from 'lodash-es';
|
||||
import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
import { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
||||
|
|
@ -49,8 +49,6 @@ export class TimelineManager {
|
|||
scrubberMonths: ScrubberMonth[] = $state([]);
|
||||
scrubberTimelineHeight: number = $state(0);
|
||||
|
||||
topIntersectingMonthGroup: MonthGroup | undefined = $state();
|
||||
|
||||
visibleWindow = $derived.by(() => ({
|
||||
top: this.#scrollTop,
|
||||
bottom: this.#scrollTop + this.viewportHeight,
|
||||
|
|
@ -87,15 +85,6 @@ export class TimelineManager {
|
|||
#suspendTransitions = $state(false);
|
||||
#resetScrolling = debounce(() => (this.#scrolling = false), 1000);
|
||||
#resetSuspendTransitions = debounce(() => (this.suspendTransitions = false), 1000);
|
||||
scrollCompensation: {
|
||||
heightDelta: number | undefined;
|
||||
scrollTop: number | undefined;
|
||||
monthGroup: MonthGroup | undefined;
|
||||
} = $state({
|
||||
heightDelta: 0,
|
||||
scrollTop: 0,
|
||||
monthGroup: undefined,
|
||||
});
|
||||
|
||||
constructor() {}
|
||||
|
||||
|
|
@ -241,38 +230,12 @@ export class TimelineManager {
|
|||
}
|
||||
}
|
||||
|
||||
clearScrollCompensation() {
|
||||
this.scrollCompensation = {
|
||||
heightDelta: undefined,
|
||||
scrollTop: undefined,
|
||||
monthGroup: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
updateIntersections() {
|
||||
if (!this.isInitialized || this.visibleWindow.bottom === this.visibleWindow.top) {
|
||||
return;
|
||||
}
|
||||
let topIntersectingMonthGroup = undefined;
|
||||
for (const month of this.months) {
|
||||
updateIntersectionMonthGroup(this, month);
|
||||
if (!topIntersectingMonthGroup && month.actuallyIntersecting) {
|
||||
topIntersectingMonthGroup = month;
|
||||
}
|
||||
}
|
||||
if (topIntersectingMonthGroup !== undefined && this.topIntersectingMonthGroup !== topIntersectingMonthGroup) {
|
||||
this.topIntersectingMonthGroup = topIntersectingMonthGroup;
|
||||
}
|
||||
for (const month of this.months) {
|
||||
if (month === this.topIntersectingMonthGroup) {
|
||||
this.topIntersectingMonthGroup.percent = clamp(
|
||||
(this.visibleWindow.top - this.topIntersectingMonthGroup.top) / this.topIntersectingMonthGroup.height,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
} else {
|
||||
month.percent = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -401,10 +364,10 @@ export class TimelineManager {
|
|||
return;
|
||||
}
|
||||
|
||||
const result = await monthGroup.loader?.execute(async (signal: AbortSignal) => {
|
||||
const executionStatus = await monthGroup.loader?.execute(async (signal: AbortSignal) => {
|
||||
await loadFromTimeBuckets(this, monthGroup, this.#options, signal);
|
||||
}, cancelable);
|
||||
if (result === 'LOADED') {
|
||||
if (executionStatus === 'LOADED') {
|
||||
updateIntersectionMonthGroup(this, monthGroup);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { init } from 'svelte-i18n';
|
|||
|
||||
beforeAll(async () => {
|
||||
await init({ fallbackLocale: 'dev' });
|
||||
Element.prototype.animate = vi.fn().mockImplementation(() => ({ cancel: () => {}, finished: Promise.resolve() }));
|
||||
});
|
||||
|
||||
Object.defineProperty(globalThis, 'matchMedia', {
|
||||
|
|
@ -16,3 +17,11 @@ Object.defineProperty(globalThis, 'matchMedia', {
|
|||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
vi.mock('$env/dynamic/public', () => {
|
||||
return {
|
||||
env: {
|
||||
PUBLIC_IMMICH_HOSTNAME: '',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue