fix(web): edit order handling

This commit is contained in:
bwees
2026-01-24 16:44:17 -06:00
parent 1803692eab
commit 20ccbcec47
4 changed files with 72 additions and 24 deletions

3
pnpm-lock.yaml generated
View File

@@ -842,6 +842,9 @@ importers:
thumbhash:
specifier: ^0.1.1
version: 0.1.1
transformation-matrix:
specifier: ^3.1.0
version: 3.1.0
uplot:
specifier: ^1.6.32
version: 1.6.32

View File

@@ -61,6 +61,7 @@
"svelte-persisted-store": "^0.12.0",
"tabbable": "^6.2.0",
"thumbhash": "^0.1.1",
"transformation-matrix": "^3.1.0",
"uplot": "^1.6.32"
},
"devDependencies": {

View File

@@ -1,16 +1,9 @@
import { editManager, type EditActions, type EditToolManager } from '$lib/managers/edit/edit-manager.svelte';
import { getAssetMediaUrl } from '$lib/utils';
import { getDimensions } from '$lib/utils/asset-utils';
import { normalizeTransformEdits } from '$lib/utils/editor';
import { handleError } from '$lib/utils/handle-error';
import {
AssetEditAction,
AssetMediaSize,
MirrorAxis,
type AssetResponseDto,
type CropParameters,
type MirrorParameters,
type RotateParameters,
} from '@immich/sdk';
import { AssetEditAction, AssetMediaSize, MirrorAxis, type AssetResponseDto, type CropParameters } from '@immich/sdk';
import { tick } from 'svelte';
export type CropAspectRatio =
@@ -200,22 +193,14 @@ class TransformManager implements EditToolManager {
globalThis.addEventListener('mousemove', (e) => transformManager.handleMouseMove(e), { passive: true });
// set the rotation before loading the image
const rotateEdit = edits.find((e) => e.action === 'rotate');
if (rotateEdit) {
this.imageRotation = (rotateEdit.parameters as RotateParameters).angle;
}
const transformEdits = edits.filter((e) => e.action === 'rotate' || e.action === 'mirror');
// set mirror state from edits
const mirrorEdits = edits.filter((e) => e.action === 'mirror');
for (const mirrorEdit of mirrorEdits) {
const axis = (mirrorEdit.parameters as MirrorParameters).axis;
if (axis === MirrorAxis.Horizontal) {
this.mirrorHorizontal = true;
} else if (axis === MirrorAxis.Vertical) {
this.mirrorVertical = true;
}
}
// Normalize rotation and mirror edits to single rotation and mirror state
// This allows edits to be imported in any order and still produce correct state
const normalizedTransfromation = normalizeTransformEdits(transformEdits);
this.imageRotation = normalizedTransfromation.rotation;
this.mirrorHorizontal = normalizedTransfromation.mirrorHorizontal;
this.mirrorVertical = normalizedTransfromation.mirrorVertical;
await tick();

View File

@@ -0,0 +1,59 @@
import type { EditActions } from '$lib/managers/edit/edit-manager.svelte';
import type { MirrorParameters, RotateParameters } from '@immich/sdk';
import { compose, flipX, flipY, identity, rotate } from 'transformation-matrix';
export function normalizeTransformEdits(edits: EditActions): {
rotation: number;
mirrorHorizontal: boolean;
mirrorVertical: boolean;
} {
// construct an affine matrix from the edits
// this is the same approach used in the backend to combine multiple transforms
const matrix = compose(
...edits.map((edit) => {
switch (edit.action) {
case 'rotate': {
const parameters = edit.parameters as RotateParameters;
const angleInRadians = (-parameters.angle * Math.PI) / 180;
return rotate(angleInRadians);
}
case 'mirror': {
const parameters = edit.parameters as MirrorParameters;
return parameters.axis === 'horizontal' ? flipY() : flipX();
}
default: {
return identity();
}
}
}),
);
let rotation = 0;
let mirrorH = false;
let mirrorV = false;
let { a, b, c, d } = matrix;
// round to avoid floating point precision issues
a = Math.round(a);
b = Math.round(b);
c = Math.round(c);
d = Math.round(d);
// [ +/-1, 0, 0, +/-1 ] indicates a 0° or 180° rotation with possible mirrors
// [ 0, +/-1, +/-1, 0 ] indicates a 90° or 270° rotation with possible mirrors
if (Math.abs(a) == 1 && Math.abs(b) == 0 && Math.abs(c) == 0 && Math.abs(d) == 1) {
rotation = a > 0 ? 0 : 180;
mirrorH = rotation === 0 ? a < 0 : a > 0;
mirrorV = rotation === 0 ? d < 0 : d > 0;
} else if (Math.abs(a) == 0 && Math.abs(b) == 1 && Math.abs(c) == 1 && Math.abs(d) == 0) {
rotation = c > 0 ? 90 : 270;
mirrorH = rotation === 90 ? c < 0 : c > 0;
mirrorV = rotation === 90 ? b > 0 : b < 0;
}
return {
rotation,
mirrorHorizontal: mirrorH,
mirrorVertical: mirrorV,
};
}