fix: resolve 50+ bugs across editor store, canvas, panels, and tools

- Store: loadImage resets state, applyCrop updates sourceImageSize,
  setTool clears stale cropState, layer ordering preserved across
  layers, rotateCanvas/flipCanvas account for object dimensions,
  trimCanvas implemented, resizeCanvas supports anchor positioning
- Canvas: crop overlay interactive, move tool drag events wired,
  hand tool panning, stage ref effect mount-only, checkerboard
  tracks pan offset, filter cache clearing
- Panels: hslToHex color fix for hue 240-360, adjustments single
  setAdjustment call, curves draggingIndex after sort, history
  panel fresh temporal reads, export transparent setting, canvas
  resize dialog re-sync, hex input respects picker target
- Tools: text tool fixed positioning and event cleanup, shape tool
  deferred addObject, selection mode wired to store, magic wand
  tolerance controlled, eyedropper coordinate transform
- Options: dodge-burn/shape/clone/fill/gradient/pixel-brush use
  proper store actions instead of raw setState
- Added 20+ store actions for tool settings
- Updated tests for corrected rotation/flip/trim behavior
- Excluded e2e-editor from vitest config
This commit is contained in:
SnapOtter
2026-05-07 23:47:08 +08:00
parent d5a03f2a98
commit e99bae428a
39 changed files with 772 additions and 391 deletions
@@ -157,9 +157,10 @@ export function alignObjects(
objectIds: string[],
objects: { id: string; attrs: Record<string, unknown> }[],
updateObject: (id: string, attrs: Record<string, unknown>) => void,
canvasSize?: { width: number; height: number },
): void {
const selected = objects.filter((o) => objectIds.includes(o.id));
if (selected.length < 2 && !direction.startsWith("distribute")) return;
if (selected.length === 0) return;
if (selected.length < 3 && direction.startsWith("distribute")) return;
const bounds = selected.map((o) => ({
@@ -170,6 +171,34 @@ export function alignObjects(
h: (o.attrs.height as number) ?? 0,
}));
// Single object: align relative to canvas bounds
if (selected.length === 1 && canvasSize) {
const b = bounds[0];
switch (direction) {
case "left":
updateObject(b.id, { x: 0 });
break;
case "center-h":
updateObject(b.id, { x: canvasSize.width / 2 - b.w / 2 });
break;
case "right":
updateObject(b.id, { x: canvasSize.width - b.w });
break;
case "top":
updateObject(b.id, { y: 0 });
break;
case "center-v":
updateObject(b.id, { y: canvasSize.height / 2 - b.h / 2 });
break;
case "bottom":
updateObject(b.id, { y: canvasSize.height - b.h });
break;
}
return;
}
if (selected.length < 2) return;
switch (direction) {
case "left": {
const minX = Math.min(...bounds.map((b) => b.x));