|
|
|
@ -1,11 +1,12 @@
|
|
|
|
import { locale } from '$lib/stores/preferences.store';
|
|
|
|
import { locale } from '$lib/stores/preferences.store';
|
|
|
|
import { getKey } from '$lib/utils';
|
|
|
|
import { getKey } from '$lib/utils';
|
|
|
|
import { AssetGridTaskManager } from '$lib/utils/asset-store-task-manager';
|
|
|
|
import { AssetGridTaskManager } from '$lib/utils/asset-store-task-manager';
|
|
|
|
|
|
|
|
import { getAssetRatio } from '$lib/utils/asset-utils';
|
|
|
|
import { generateId } from '$lib/utils/generate-id';
|
|
|
|
import { generateId } from '$lib/utils/generate-id';
|
|
|
|
import type { AssetGridRouteSearchParams } from '$lib/utils/navigation';
|
|
|
|
import type { AssetGridRouteSearchParams } from '$lib/utils/navigation';
|
|
|
|
import { fromLocalDateTime, splitBucketIntoDateGroups, type DateGroup } from '$lib/utils/timeline-util';
|
|
|
|
import { calculateWidth, fromLocalDateTime, splitBucketIntoDateGroups, type DateGroup } from '$lib/utils/timeline-util';
|
|
|
|
import type { JustifiedLayout, LayoutOptions } from '@immich/justified-layout-wasm';
|
|
|
|
|
|
|
|
import { TimeBucketSize, getAssetInfo, getTimeBucket, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
|
|
|
|
import { TimeBucketSize, getAssetInfo, getTimeBucket, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
|
|
|
|
|
|
|
|
import createJustifiedLayout from 'justified-layout';
|
|
|
|
import { throttle } from 'lodash-es';
|
|
|
|
import { throttle } from 'lodash-es';
|
|
|
|
import { DateTime } from 'luxon';
|
|
|
|
import { DateTime } from 'luxon';
|
|
|
|
import { t } from 'svelte-i18n';
|
|
|
|
import { t } from 'svelte-i18n';
|
|
|
|
@ -15,6 +16,13 @@ import { websocketEvents } from './websocket';
|
|
|
|
type AssetApiGetTimeBucketsRequest = Parameters<typeof getTimeBuckets>[0];
|
|
|
|
type AssetApiGetTimeBucketsRequest = Parameters<typeof getTimeBuckets>[0];
|
|
|
|
export type AssetStoreOptions = Omit<AssetApiGetTimeBucketsRequest, 'size'>;
|
|
|
|
export type AssetStoreOptions = Omit<AssetApiGetTimeBucketsRequest, 'size'>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const LAYOUT_OPTIONS = {
|
|
|
|
|
|
|
|
boxSpacing: 2,
|
|
|
|
|
|
|
|
containerPadding: 0,
|
|
|
|
|
|
|
|
targetRowHeightTolerance: 0.15,
|
|
|
|
|
|
|
|
targetRowHeight: 235,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface Viewport {
|
|
|
|
export interface Viewport {
|
|
|
|
width: number;
|
|
|
|
width: number;
|
|
|
|
height: number;
|
|
|
|
height: number;
|
|
|
|
@ -224,10 +232,8 @@ export class AssetStore {
|
|
|
|
albumAssets: Set<string> = new Set();
|
|
|
|
albumAssets: Set<string> = new Set();
|
|
|
|
pendingScrollBucket: AssetBucket | undefined;
|
|
|
|
pendingScrollBucket: AssetBucket | undefined;
|
|
|
|
pendingScrollAssetId: string | undefined;
|
|
|
|
pendingScrollAssetId: string | undefined;
|
|
|
|
maxBucketAssets = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
listeners: BucketListener[] = [];
|
|
|
|
listeners: BucketListener[] = [];
|
|
|
|
getJustifiedLayoutFromAssets: ((assets: AssetResponseDto[], options: LayoutOptions) => JustifiedLayout) | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
constructor(
|
|
|
|
options: AssetStoreOptions,
|
|
|
|
options: AssetStoreOptions,
|
|
|
|
@ -389,13 +395,6 @@ export class AssetStore {
|
|
|
|
);
|
|
|
|
);
|
|
|
|
this.initializedSignal();
|
|
|
|
this.initializedSignal();
|
|
|
|
this.initialized = true;
|
|
|
|
this.initialized = true;
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: move to top level import after https://github.com/sveltejs/kit/issues/7805 is fixed
|
|
|
|
|
|
|
|
import('$lib/utils/layout-utils')
|
|
|
|
|
|
|
|
.then(({ getJustifiedLayoutFromAssets }) => {
|
|
|
|
|
|
|
|
this.getJustifiedLayoutFromAssets = getJustifiedLayoutFromAssets;
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.catch(() => void 0);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateOptions(options: AssetStoreOptions) {
|
|
|
|
async updateOptions(options: AssetStoreOptions) {
|
|
|
|
@ -470,33 +469,32 @@ export class AssetStore {
|
|
|
|
assetGroup.heightActual = false;
|
|
|
|
assetGroup.heightActual = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const viewportWidth = this.viewport.width;
|
|
|
|
|
|
|
|
if (!bucket.isBucketHeightActual) {
|
|
|
|
if (!bucket.isBucketHeightActual) {
|
|
|
|
const unwrappedWidth = (3 / 2) * bucket.bucketCount * THUMBNAIL_HEIGHT * (7 / 10);
|
|
|
|
const unwrappedWidth = (3 / 2) * bucket.bucketCount * THUMBNAIL_HEIGHT * (7 / 10);
|
|
|
|
const rows = Math.ceil(unwrappedWidth / viewportWidth);
|
|
|
|
const rows = Math.ceil(unwrappedWidth / this.viewport.width);
|
|
|
|
const height = 51 + rows * THUMBNAIL_HEIGHT;
|
|
|
|
const height = 51 + rows * THUMBNAIL_HEIGHT;
|
|
|
|
bucket.bucketHeight = height;
|
|
|
|
bucket.bucketHeight = height;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const layoutOptions = {
|
|
|
|
|
|
|
|
spacing: 2,
|
|
|
|
|
|
|
|
heightTolerance: 0.15,
|
|
|
|
|
|
|
|
rowHeight: 235,
|
|
|
|
|
|
|
|
rowWidth: Math.floor(viewportWidth),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const assetGroup of bucket.dateGroups) {
|
|
|
|
for (const assetGroup of bucket.dateGroups) {
|
|
|
|
if (!assetGroup.heightActual) {
|
|
|
|
if (!assetGroup.heightActual) {
|
|
|
|
const unwrappedWidth = (3 / 2) * assetGroup.assets.length * THUMBNAIL_HEIGHT * (7 / 10);
|
|
|
|
const unwrappedWidth = (3 / 2) * assetGroup.assets.length * THUMBNAIL_HEIGHT * (7 / 10);
|
|
|
|
const rows = Math.ceil(unwrappedWidth / viewportWidth);
|
|
|
|
const rows = Math.ceil(unwrappedWidth / this.viewport.width);
|
|
|
|
const height = rows * THUMBNAIL_HEIGHT;
|
|
|
|
const height = rows * THUMBNAIL_HEIGHT;
|
|
|
|
assetGroup.height = height;
|
|
|
|
assetGroup.height = height;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.getJustifiedLayoutFromAssets) {
|
|
|
|
const layoutResult = createJustifiedLayout(
|
|
|
|
assetGroup.geometry = this.getJustifiedLayoutFromAssets(assetGroup.assets, layoutOptions);
|
|
|
|
assetGroup.assets.map((g) => getAssetRatio(g)),
|
|
|
|
}
|
|
|
|
{
|
|
|
|
|
|
|
|
...LAYOUT_OPTIONS,
|
|
|
|
|
|
|
|
containerWidth: Math.floor(this.viewport.width),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assetGroup.geometry = {
|
|
|
|
|
|
|
|
...layoutResult,
|
|
|
|
|
|
|
|
containerWidth: calculateWidth(layoutResult.boxes),
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -562,7 +560,6 @@ export class AssetStore {
|
|
|
|
|
|
|
|
|
|
|
|
bucket.assets = assets;
|
|
|
|
bucket.assets = assets;
|
|
|
|
bucket.dateGroups = splitBucketIntoDateGroups(bucket, get(locale));
|
|
|
|
bucket.dateGroups = splitBucketIntoDateGroups(bucket, get(locale));
|
|
|
|
this.maxBucketAssets = Math.max(this.maxBucketAssets, assets.length);
|
|
|
|
|
|
|
|
this.updateGeometry(bucket, true);
|
|
|
|
this.updateGeometry(bucket, true);
|
|
|
|
this.timelineHeight = this.buckets.reduce((accumulator, b) => accumulator + b.bucketHeight, 0);
|
|
|
|
this.timelineHeight = this.buckets.reduce((accumulator, b) => accumulator + b.bucketHeight, 0);
|
|
|
|
bucket.loaded();
|
|
|
|
bucket.loaded();
|
|
|
|
@ -832,7 +829,7 @@ export class AssetStore {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (changed) {
|
|
|
|
if (changed) {
|
|
|
|
bucket.dateGroups = splitBucketIntoDateGroups(bucket, get(locale));
|
|
|
|
bucket.dateGroups = splitBucketIntoDateGroups(bucket, get(locale));
|
|
|
|
void this.updateGeometry(bucket, true);
|
|
|
|
this.updateGeometry(bucket, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|