完成素材无限画布最终审计与恢复安全收口
同步资源管理最新稳定的 manifest relay、Runner 重挂与 MCP 域名修复 统一网站与 Tauri 的共享画布 history、缩放恢复和宿主边界 移除生成账本中的临时上传凭证与 Provider 敏感字段 补齐事务快照发布前故障恢复和同幂等身份重放 更新最终 PRD、技术方案、decision-log 与 pitfalls 补齐前端、Tauri Rust、竞态、锁、恢复和原生壳门禁
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
import type {
|
||||
EditorAssetLibrarySnapshot,
|
||||
EditorCharacterAnimationGenerationResult,
|
||||
EditorProjectLayerSnapshot,
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import {
|
||||
canvasDisplayScaleToViewportScale,
|
||||
clamp,
|
||||
resolveSnappedItemPosition,
|
||||
viewportScaleToCanvasDisplayScale,
|
||||
} from '@genarrative/image-canvas-core';
|
||||
|
||||
import type {
|
||||
EditorAssetLibrarySnapshot,
|
||||
EditorCharacterAnimationGenerationResult,
|
||||
EditorProjectLayerSnapshot,
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import type {
|
||||
CanvasAssetKind,
|
||||
CanvasContextMenuState,
|
||||
@@ -26,23 +27,23 @@ export {
|
||||
CANVAS_DISPLAY_SCALE_BASE,
|
||||
CANVAS_WORLD_ORIGIN,
|
||||
CANVAS_WORLD_SIZE,
|
||||
canvasDisplayScaleToViewportScale,
|
||||
clamp,
|
||||
DEFAULT_CANVAS_SIZE,
|
||||
findNearestSnap,
|
||||
FIT_VIEW_PADDING,
|
||||
formatCanvasDisplayScalePercent,
|
||||
formatPercent,
|
||||
getLayerBounds,
|
||||
MAX_HISTORY_STEPS,
|
||||
MAX_SCALE,
|
||||
MIN_SCALE,
|
||||
MINIMAP_DRAG_SENSITIVITY,
|
||||
MINIMAP_PADDING,
|
||||
MINIMAP_SIZE,
|
||||
resolveSnappedItemPosition,
|
||||
SNAP_DISTRIBUTION_OVERLAP_TOLERANCE,
|
||||
SNAP_THRESHOLD_SCREEN_PX,
|
||||
canvasDisplayScaleToViewportScale,
|
||||
clamp,
|
||||
findNearestSnap,
|
||||
formatCanvasDisplayScalePercent,
|
||||
formatPercent,
|
||||
getLayerBounds,
|
||||
resolveSnappedItemPosition,
|
||||
viewportScaleToCanvasDisplayScale,
|
||||
} from '@genarrative/image-canvas-core';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { CanvasPortal } from '@genarrative/image-canvas-react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
import type {
|
||||
CanvasAssetKind,
|
||||
CanvasGenerationInputs,
|
||||
CanvasHistoryAction,
|
||||
CanvasLayer as SharedCanvasLayer,
|
||||
CanvasMediaType,
|
||||
CanvasSourceType,
|
||||
CanvasViewport,
|
||||
} from '@genarrative/image-canvas-core';
|
||||
|
||||
import type {
|
||||
EditorAssetSnapshot,
|
||||
EditorCharacterAnimationFrameCount,
|
||||
@@ -11,26 +21,6 @@ import type {
|
||||
EditorVideoResolution,
|
||||
EditorVideoSoundMode,
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import type {
|
||||
CanvasAssetKind,
|
||||
CanvasContextMenuState,
|
||||
CanvasGenerationInputField,
|
||||
CanvasGenerationInputReference,
|
||||
CanvasGenerationInputs,
|
||||
CanvasHistoryAction,
|
||||
CanvasHistoryActionType,
|
||||
CanvasHistoryApplyResult,
|
||||
CanvasLayer as SharedCanvasLayer,
|
||||
CanvasMarqueeState,
|
||||
CanvasMediaType,
|
||||
CanvasSnapItem,
|
||||
CanvasSourceType,
|
||||
CanvasTool,
|
||||
CanvasViewport,
|
||||
DragState,
|
||||
SnapCandidate,
|
||||
SnapGuide,
|
||||
} from '@genarrative/image-canvas-core';
|
||||
|
||||
export type {
|
||||
CanvasAssetKind,
|
||||
|
||||
@@ -199,6 +199,19 @@ describe('ImageCanvasHistoryModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('restores layer size only for layers targeted by a resize action', () => {
|
||||
const targetLayer = createLayer({ width: 320, height: 240 });
|
||||
const currentLayer = createLayer({ width: 640, height: 480 });
|
||||
|
||||
const merged = mergeCanvasHistorySnapshotForRestore({
|
||||
current: createSnapshot([currentLayer]),
|
||||
target: createSnapshot([targetLayer]),
|
||||
layerSizeLayerIds: new Set([targetLayer.id]),
|
||||
});
|
||||
|
||||
expect(merged.layers[0]).toMatchObject({ width: 320, height: 240 });
|
||||
});
|
||||
|
||||
it('restores the complete target layer after that layer was deleted', () => {
|
||||
const targetLayer = createLayer({
|
||||
resourceId: 'deleted-resource',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Minimap, ZoomControls } from '@genarrative/image-canvas-react';
|
||||
import {
|
||||
ImagePlus,
|
||||
Layers,
|
||||
@@ -14,7 +15,6 @@ import type {
|
||||
PointerEvent as ReactPointerEvent,
|
||||
} from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Minimap, ZoomControls } from '@genarrative/image-canvas-react';
|
||||
|
||||
import {
|
||||
PlatformFloatingMenu,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CanvasViewport as SharedCanvasViewport } from '@genarrative/image-canvas-react';
|
||||
import type {
|
||||
CSSProperties,
|
||||
DragEvent as ReactDragEvent,
|
||||
@@ -9,7 +10,6 @@ import type {
|
||||
|
||||
import type { ExternalGenerationTaskRecord } from '@/packages/shared/src';
|
||||
import { EditorAgentConversationPanelView } from '@/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx';
|
||||
import { CanvasViewport as SharedCanvasViewport } from '@genarrative/image-canvas-react';
|
||||
|
||||
import { PlatformRuntimeStatusToast } from '../common/PlatformRuntimeStatusToast';
|
||||
import { ImageCanvasBottomToolbarView } from './ImageCanvasBottomToolbarView';
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
CanvasWorld as SharedCanvasWorld,
|
||||
LayerRenderer,
|
||||
SelectionOverlay,
|
||||
} from '@genarrative/image-canvas-react';
|
||||
import {
|
||||
AppWindow,
|
||||
Clapperboard,
|
||||
@@ -24,11 +29,6 @@ import type {
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useResolvedAssetReadUrl } from '../../hooks/useResolvedAssetReadUrl';
|
||||
import {
|
||||
CanvasWorld as SharedCanvasWorld,
|
||||
LayerRenderer,
|
||||
SelectionOverlay,
|
||||
} from '@genarrative/image-canvas-react';
|
||||
import {
|
||||
PlatformFloatingMenu,
|
||||
PlatformFloatingMenuItem,
|
||||
|
||||
@@ -128,6 +128,9 @@ function HistoryHarness({
|
||||
<span data-testid="layers">
|
||||
{layers.map((layer) => `${layer.id}:${layer.x}`).join(',')}
|
||||
</span>
|
||||
<span data-testid="layer-size">
|
||||
{layers.map((layer) => `${layer.width}x${layer.height}`).join(',')}
|
||||
</span>
|
||||
<span data-testid="layer-relations">
|
||||
{layers
|
||||
.map(
|
||||
@@ -171,6 +174,14 @@ function HistoryHarness({
|
||||
>
|
||||
capture
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
history.captureCanvasHistory({ type: 'resize-image', count: 1 });
|
||||
}}
|
||||
>
|
||||
capture-resize
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -212,6 +223,20 @@ function HistoryHarness({
|
||||
<button type="button" onClick={() => setLayers([])}>
|
||||
delete
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setLayers((currentLayers) =>
|
||||
currentLayers.map((layer) => ({
|
||||
...layer,
|
||||
width: 240,
|
||||
height: 160,
|
||||
})),
|
||||
)
|
||||
}
|
||||
>
|
||||
resize
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
@@ -310,6 +335,26 @@ function HistoryHarness({
|
||||
}
|
||||
|
||||
describe('useCanvasHistory', () => {
|
||||
it('restores shared layer bounds for resize history', () => {
|
||||
render(<HistoryHarness onClearDrag={() => {}} />);
|
||||
|
||||
act(() => {
|
||||
screen.getByRole('button', { name: 'capture-resize' }).click();
|
||||
screen.getByRole('button', { name: 'resize' }).click();
|
||||
});
|
||||
expect(screen.getByTestId('layer-size').textContent).toBe('240x160');
|
||||
|
||||
act(() => {
|
||||
screen.getByRole('button', { name: 'undo' }).click();
|
||||
});
|
||||
expect(screen.getByTestId('layer-size').textContent).toBe('100x80');
|
||||
|
||||
act(() => {
|
||||
screen.getByRole('button', { name: 'redo' }).click();
|
||||
});
|
||||
expect(screen.getByTestId('layer-size').textContent).toBe('240x160');
|
||||
});
|
||||
|
||||
it('captures, undoes, and redoes safe canvas history snapshots', () => {
|
||||
const clearDragState = vi.fn();
|
||||
render(<HistoryHarness onClearDrag={clearDragState} />);
|
||||
|
||||
Reference in New Issue
Block a user