完善全类型资源非破坏性编辑
Project CI / Repository checks (pull_request) Failing after 1m9s
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Backend tests (pull_request) Successful in 4m5s
Project CI / Native shell tests (pull_request) Failing after 6m38s

禁用新增资源并为所有现役资源提供按类型编辑入口。

图片、视频、音频、文本、任务产物和项目版本派生新资源且保留原资源。

完善 External operation 私有账本、幂等恢复、旧指纹迁移与安全校验。

修复画布生成期间交互冻结、滚轮平移缩放与 Shift 选择行为。

同步资源编辑合同、决策记录与排障经验。
This commit is contained in:
2026-08-10 21:19:57 +08:00
parent f37c84234c
commit cf81a71dce
20 changed files with 1835 additions and 122 deletions
@@ -243,6 +243,40 @@ export function zoomViewportFromWheel({
});
}
export function resolveViewportFromWheel({
viewport,
deltaX,
deltaY,
shiftKey,
ctrlKey,
metaKey,
screenPoint,
}: {
viewport: CanvasViewport;
deltaX: number;
deltaY: number;
shiftKey: boolean;
ctrlKey: boolean;
metaKey: boolean;
screenPoint: CanvasPoint;
}): { mode: 'pan' | 'zoom'; viewport: CanvasViewport } {
if (ctrlKey || metaKey) {
return {
mode: 'zoom',
viewport: zoomViewportFromWheel({ viewport, deltaY, screenPoint }),
};
}
const useShiftWheelHorizontalFallback = shiftKey && deltaX === 0;
return {
mode: 'pan',
viewport: panViewportFromWheel({
viewport,
deltaX: useShiftWheelHorizontalFallback ? deltaY : deltaX,
deltaY: useShiftWheelHorizontalFallback ? 0 : deltaY,
}),
};
}
export function moveViewportFromPan(
dragState: Extract<DragState, { kind: 'pan' }>,
pointer: CanvasPoint,
@@ -10,6 +10,7 @@ import {
panViewportFromWheel,
resizeCanvasLayerBounds,
resolvePointerSelection,
resolveViewportFromWheel,
scaleViewportFromScreenPoint,
selectLayersInsideMarquee,
transformCanvasLayers,
@@ -62,6 +63,43 @@ describe('shared image canvas core', () => {
});
});
it('shares pan, shift-pan and modifier-zoom wheel semantics', () => {
const viewport = { x: 20, y: 30, scale: 1 };
expect(
resolveViewportFromWheel({
viewport,
deltaX: 4,
deltaY: 10,
shiftKey: false,
ctrlKey: false,
metaKey: false,
screenPoint: { x: 100, y: 100 },
}),
).toEqual({ mode: 'pan', viewport: { x: 16, y: 20, scale: 1 } });
expect(
resolveViewportFromWheel({
viewport,
deltaX: 0,
deltaY: 10,
shiftKey: true,
ctrlKey: false,
metaKey: false,
screenPoint: { x: 100, y: 100 },
}),
).toEqual({ mode: 'pan', viewport: { x: 10, y: 30, scale: 1 } });
expect(
resolveViewportFromWheel({
viewport,
deltaX: 0,
deltaY: -10,
shiftKey: false,
ctrlKey: true,
metaKey: false,
screenPoint: { x: 100, y: 100 },
}).mode,
).toBe('zoom');
});
it('supports additive selection, marquee and multi-layer movement', () => {
expect(
resolvePointerSelection({
@@ -12,9 +12,8 @@ import {
getWorldPointFromClient,
moveViewportFromMinimapDrag as resolveViewportFromMinimapDrag,
moveViewportFromMinimapPointer as resolveViewportFromMinimapPointer,
panViewportFromWheel,
resolveViewportFromWheel,
scaleViewportFromScreenPoint,
zoomViewportFromWheel,
} from '@genarrative/image-canvas-core';
import {
type Dispatch,
@@ -369,32 +368,22 @@ export function useImageCanvasViewportControls({
event.preventDefault();
if (!event.ctrlKey && !event.metaKey) {
const useShiftWheelHorizontalFallback =
event.shiftKey && event.deltaX === 0;
setViewport((currentViewport) =>
panViewportFromWheel({
viewport: currentViewport,
deltaX: useShiftWheelHorizontalFallback
? event.deltaY
: event.deltaX,
deltaY: useShiftWheelHorizontalFallback ? 0 : event.deltaY,
}),
);
return;
}
const rect = viewportElement.getBoundingClientRect();
const screenPoint = {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
};
setViewport((currentViewport) =>
zoomViewportFromWheel({
viewport: currentViewport,
deltaY: event.deltaY,
screenPoint,
}),
setViewport(
(currentViewport) =>
resolveViewportFromWheel({
viewport: currentViewport,
deltaX: event.deltaX,
deltaY: event.deltaY,
shiftKey: event.shiftKey,
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
screenPoint,
}).viewport,
);
},
[canvasViewportRef, setViewport],