feat(web): wasm justified layout, sync edition (#23194)

* the invisible wasm

use npm version

* deterministic tests

* add todo

* linting

* bump library, add helpers

* use target height for unfilled rows

* update tests
This commit is contained in:
Mert
2025-10-25 01:06:05 -04:00
committed by GitHub
parent 328380cfda
commit 3f03a88767
12 changed files with 93 additions and 117 deletions

View File

@@ -1,16 +1,15 @@
// import { TUNABLES } from '$lib/utils/tunables';
// note: it's important that this is not imported in more than one file due to https://github.com/sveltejs/kit/issues/7805
// import { JustifiedLayout, type LayoutOptions } from '@immich/justified-layout-wasm';
import { TUNABLES } from '$lib/utils/tunables';
import { JustifiedLayout, type LayoutOptions } from '@immich/justified-layout-wasm';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { getAssetRatio } from '$lib/utils/asset-utils';
import { isTimelineAsset } from '$lib/utils/timeline-util';
import { isTimelineAsset, isTimelineAssets } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk';
import createJustifiedLayout from 'justified-layout';
export type getJustifiedLayoutFromAssetsFunction = typeof getJustifiedLayoutFromAssets;
// let useWasm = TUNABLES.LAYOUT.WASM;
const useWasm = TUNABLES.LAYOUT.WASM;
export type CommonJustifiedLayout = {
containerWidth: number;
@@ -19,6 +18,7 @@ export type CommonJustifiedLayout = {
getLeft(boxIdx: number): number;
getWidth(boxIdx: number): number;
getHeight(boxIdx: number): number;
getPosition(boxIdx: number): { top: number; left: number; width: number; height: number };
};
export type CommonLayoutOptions = {
@@ -29,25 +29,31 @@ export type CommonLayoutOptions = {
};
export function getJustifiedLayoutFromAssets(
assets: (TimelineAsset | AssetResponseDto)[],
assets: TimelineAsset[] | AssetResponseDto[],
options: CommonLayoutOptions,
): CommonJustifiedLayout {
// if (useWasm) {
// return wasmJustifiedLayout(assets, options);
// }
if (useWasm) {
return isTimelineAssets(assets) ? wasmLayoutFromTimeline(assets, options) : wasmLayoutFromDto(assets, options);
}
return justifiedLayout(assets, options);
}
// commented out until a solution for top level awaits on safari is fixed
// function wasmJustifiedLayout(assets: AssetResponseDto[], options: LayoutOptions) {
// const aspectRatios = new Float32Array(assets.length);
// // eslint-disable-next-line unicorn/no-for-loop
// for (let i = 0; i < assets.length; i++) {
// const { width, height } = getAssetRatio(assets[i]);
// aspectRatios[i] = width / height;
// }
// return new JustifiedLayout(aspectRatios, options);
// }
function wasmLayoutFromTimeline(assets: TimelineAsset[], options: LayoutOptions) {
const aspectRatios = new Float32Array(assets.length);
for (let i = 0; i < assets.length; i++) {
aspectRatios[i] = assets[i].ratio;
}
return new JustifiedLayout(aspectRatios, options);
}
function wasmLayoutFromDto(assets: AssetResponseDto[], options: LayoutOptions) {
const aspectRatios = new Float32Array(assets.length);
for (let i = 0; i < assets.length; i++) {
const { width, height } = getAssetRatio(assets[i]);
aspectRatios[i] = width / height;
}
return new JustifiedLayout(aspectRatios, options);
}
type Geometry = ReturnType<typeof createJustifiedLayout>;
class Adapter {
@@ -88,6 +94,11 @@ class Adapter {
getHeight(boxIdx: number) {
return this.result.boxes[boxIdx]?.height;
}
getPosition(boxIdx: number) {
const box = this.result.boxes[boxIdx];
return { top: box.top, left: box.left, width: box.width, height: box.height };
}
}
export function justifiedLayout(assets: (TimelineAsset | AssetResponseDto)[], options: CommonLayoutOptions) {
@@ -119,12 +130,3 @@ export type CommonPosition = {
width: number;
height: number;
};
export function getPosition(geometry: CommonJustifiedLayout, boxIdx: number): CommonPosition {
const top = geometry.getTop(boxIdx);
const left = geometry.getLeft(boxIdx);
const width = geometry.getWidth(boxIdx);
const height = geometry.getHeight(boxIdx);
return { top, left, width, height };
}

View File

@@ -195,6 +195,9 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
export const isTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): unknownAsset is TimelineAsset =>
(unknownAsset as TimelineAsset).ratio !== undefined;
export const isTimelineAssets = (assets: AssetResponseDto[] | TimelineAsset[]): assets is TimelineAsset[] =>
assets.length === 0 || 'ratio' in assets[0];
export const plainDateTimeCompare = (ascending: boolean, a: TimelineDateTime, b: TimelineDateTime) => {
const [aDateTime, bDateTime] = ascending ? [a, b] : [b, a];

View File

@@ -19,7 +19,7 @@ const storage = browser
};
export const TUNABLES = {
LAYOUT: {
WASM: getBoolean(storage.getItem('LAYOUT.WASM'), false),
WASM: getBoolean(storage.getItem('LAYOUT.WASM'), true),
},
TIMELINE: {
INTERSECTION_EXPAND_TOP: getNumber(storage.getItem('TIMELINE_INTERSECTION_EXPAND_TOP'), 500),