Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (push) Successful in 2m48s
Project CI / Frontend tests (push) Successful in 3m26s
Project CI / Backend tests (push) Successful in 7m17s
Project CI / Native shell tests (push) Successful in 19m27s
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (push) Successful in 2m48s
Project CI / Frontend tests (push) Successful in 3m26s
Project CI / Backend tests (push) Successful in 7m17s
Project CI / Native shell tests (push) Successful in 19m27s
This commit was merged in pull request #305.
This commit is contained in:
@@ -5869,7 +5869,7 @@ iframe.preview-frame {
|
||||
height: var(--resource-card-height);
|
||||
min-height: var(--resource-card-height);
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
border: 1px solid #eadbd4;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
@@ -5912,6 +5912,7 @@ iframe.preview-frame {
|
||||
inset: 0;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(145deg, #fffaf6, #f6ece6);
|
||||
color: #c46a40;
|
||||
place-items: center;
|
||||
@@ -5973,6 +5974,13 @@ iframe.preview-frame {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 8px 18px rgb(96 62 47 / 20%);
|
||||
transform: scale(var(--genarrative-image-canvas-inverse-scale, 1));
|
||||
transform-origin: top right;
|
||||
}
|
||||
|
||||
.game-resource-card-media-control {
|
||||
transform: scale(var(--genarrative-image-canvas-inverse-scale, 1));
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
.game-resource-card-open {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
type CanvasViewport,
|
||||
resolveViewportFromWheel,
|
||||
} from '@genarrative/image-canvas-core';
|
||||
import { CanvasWorld } from '@genarrative/image-canvas-react';
|
||||
import {
|
||||
Code2,
|
||||
FileText,
|
||||
@@ -4906,17 +4907,12 @@ export default function ProjectDevelopmentView({
|
||||
>
|
||||
<RotateCcw size={15} aria-hidden="true" />
|
||||
</button>
|
||||
<div
|
||||
<CanvasWorld
|
||||
className="game-resource-page-world"
|
||||
viewport={activeResourceCanvasViewport}
|
||||
data-resource-boundary={`${resourceCanvasNavigationBounds.x},${resourceCanvasNavigationBounds.y},${resourceCanvasNavigationBounds.width},${resourceCanvasNavigationBounds.height}`}
|
||||
data-resource-fit-boundary={`${resourceCanvasFitBounds.x},${resourceCanvasFitBounds.y},${resourceCanvasFitBounds.width},${resourceCanvasFitBounds.height}`}
|
||||
data-resource-viewport={`${activeResourceCanvasViewport.x},${activeResourceCanvasViewport.y},${activeResourceCanvasViewport.scale}`}
|
||||
style={{
|
||||
width: `${resourceCanvasNavigationBounds.width}px`,
|
||||
height: `${resourceCanvasNavigationBounds.height}px`,
|
||||
transform: `translate(${activeResourceCanvasViewport.x}px, ${activeResourceCanvasViewport.y}px) scale(${activeResourceCanvasViewport.scale})`,
|
||||
transformOrigin: '0 0',
|
||||
}}
|
||||
>
|
||||
{sortMode === 'dependency' ? (
|
||||
<ResourceDependencyOverlay
|
||||
@@ -5016,7 +5012,7 @@ export default function ProjectDevelopmentView({
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CanvasWorld>
|
||||
{(
|
||||
visibleResourcesByCategory.get(
|
||||
activePageCategory,
|
||||
|
||||
@@ -1986,6 +1986,9 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
expect(styles).toMatch(
|
||||
/\.game-resource-card-open\s*\{[^}]*cursor:\s*pointer[^}]*touch-action:\s*manipulation/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/\.game-resource-card-media-control\s*\{[^}]*transform:\s*scale\(var\(--genarrative-image-canvas-inverse-scale,\s*1\)\)[^}]*transform-origin:\s*bottom\s+right/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/\.game-resource-canvas\s*\{[^}]*user-select:\s*none/s,
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
ImageCanvasHostScope,
|
||||
ImageCanvasProjectPort,
|
||||
} from '@genarrative/image-canvas-core';
|
||||
import { canvasViewportToWorldTransform } from '@genarrative/image-canvas-react';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
@@ -1125,7 +1126,7 @@ describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
expect(
|
||||
document.querySelector<HTMLElement>('.genarrative-image-canvas__world')
|
||||
?.style.transform,
|
||||
).toBe('translate(-2570px, -2495px) scale(1)');
|
||||
).toBe(canvasViewportToWorldTransform({ x: -2570, y: -2495, scale: 1 }));
|
||||
expect(screen.getByText('已定位当前最终图')).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -1170,7 +1171,7 @@ describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
expect(
|
||||
document.querySelector<HTMLElement>('.genarrative-image-canvas__world')
|
||||
?.style.transform,
|
||||
).toBe('translate(-2570px, -2495px) scale(1)');
|
||||
).toBe(canvasViewportToWorldTransform({ x: -2570, y: -2495, scale: 1 }));
|
||||
expect(screen.getByText('已定位当前最终图')).toBeTruthy();
|
||||
|
||||
fireEvent.click(layerButton);
|
||||
@@ -2062,7 +2063,9 @@ describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
clientY: 60,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(world.style.transform).toBe('translate(30px, 30px) scale(0.5)'),
|
||||
expect(world.style.transform).toBe(
|
||||
canvasViewportToWorldTransform({ x: 30, y: 30, scale: 0.5 }),
|
||||
),
|
||||
);
|
||||
fireEvent.pointerMove(window, {
|
||||
pointerId: 16,
|
||||
@@ -2070,7 +2073,9 @@ describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
clientY: 30,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(world.style.transform).toBe('translate(0px, 0px) scale(0.5)'),
|
||||
expect(world.style.transform).toBe(
|
||||
canvasViewportToWorldTransform({ x: 0, y: 0, scale: 0.5 }),
|
||||
),
|
||||
);
|
||||
fireEvent.pointerUp(window, {
|
||||
pointerId: 16,
|
||||
@@ -2315,7 +2320,9 @@ describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
const world = document.querySelector(
|
||||
'.genarrative-image-canvas__world',
|
||||
) as HTMLElement;
|
||||
expect(world.style.transform).toBe('translate(0px, 0px) scale(0.5)');
|
||||
expect(world.style.transform).toBe(
|
||||
canvasViewportToWorldTransform({ x: 0, y: 0, scale: 0.5 }),
|
||||
);
|
||||
|
||||
fireEvent.wheel(viewport, {
|
||||
deltaX: 4,
|
||||
@@ -2324,7 +2331,9 @@ describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
clientY: 80,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(world.style.transform).toBe('translate(-4px, -10px) scale(0.5)'),
|
||||
expect(world.style.transform).toBe(
|
||||
canvasViewportToWorldTransform({ x: -4, y: -10, scale: 0.5 }),
|
||||
),
|
||||
);
|
||||
fireEvent.wheel(viewport, {
|
||||
deltaX: 0,
|
||||
@@ -2334,7 +2343,9 @@ describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
shiftKey: true,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(world.style.transform).toBe('translate(-14px, -10px) scale(0.5)'),
|
||||
expect(world.style.transform).toBe(
|
||||
canvasViewportToWorldTransform({ x: -14, y: -10, scale: 0.5 }),
|
||||
),
|
||||
);
|
||||
fireEvent.wheel(viewport, {
|
||||
deltaX: 0,
|
||||
@@ -2344,7 +2355,13 @@ describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
ctrlKey: true,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(world.style.transform).not.toContain('scale(0.5)'),
|
||||
expect(world.style.transform).toBe(
|
||||
canvasViewportToWorldTransform({
|
||||
x: 100 - 228 * 0.55,
|
||||
y: 80 - 180 * 0.55,
|
||||
scale: 0.55,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -87,13 +87,17 @@ function createCanvas(
|
||||
};
|
||||
}
|
||||
|
||||
function renderedScale(container: HTMLElement) {
|
||||
const transform = (
|
||||
container.querySelector('.genarrative-image-canvas__world') as HTMLElement
|
||||
).style.transform;
|
||||
const scale = /scale\(([^)]+)\)/.exec(transform)?.[1];
|
||||
if (!scale) throw new Error(`无法从 ${transform} 读取缩放值`);
|
||||
return Number(scale);
|
||||
function logicalViewportScale(container: HTMLElement) {
|
||||
const world = container.querySelector(
|
||||
'.genarrative-image-canvas__world',
|
||||
) as HTMLElement;
|
||||
const inverseScale = Number(
|
||||
world.style.getPropertyValue('--genarrative-image-canvas-inverse-scale'),
|
||||
);
|
||||
if (!Number.isFinite(inverseScale) || inverseScale <= 0) {
|
||||
throw new Error(`无法从 world 读取逻辑缩放值`);
|
||||
}
|
||||
return 1 / inverseScale;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -110,7 +114,7 @@ describe('PreviewWorkspace quick zoom', () => {
|
||||
it('uses the toolbar zoom step while the preview is hovered', () => {
|
||||
const rendered = render(<PreviewWorkspace canvas={createCanvas()} />);
|
||||
const preview = screen.getByRole('region', { name: 'UI 预览画布' });
|
||||
const before = renderedScale(rendered.container);
|
||||
const before = logicalViewportScale(rendered.container);
|
||||
|
||||
fireEvent.pointerEnter(preview);
|
||||
const wasNotCancelled = fireEvent.keyDown(window, {
|
||||
@@ -121,14 +125,14 @@ describe('PreviewWorkspace quick zoom', () => {
|
||||
});
|
||||
|
||||
expect(wasNotCancelled).toBe(false);
|
||||
expect(renderedScale(rendered.container)).toBeCloseTo(before * 1.16);
|
||||
expect(logicalViewportScale(rendered.container)).toBeCloseTo(before * 1.16);
|
||||
});
|
||||
|
||||
it('requires hover or focus and ignores interactive targets', () => {
|
||||
const rendered = render(<PreviewWorkspace canvas={createCanvas()} />);
|
||||
const preview = screen.getByRole('region', { name: 'UI 预览画布' });
|
||||
const zoomButton = screen.getByRole('button', { name: '放大画布' });
|
||||
const initial = renderedScale(rendered.container);
|
||||
const initial = logicalViewportScale(rendered.container);
|
||||
|
||||
expect(
|
||||
fireEvent.keyDown(window, {
|
||||
@@ -137,7 +141,7 @@ describe('PreviewWorkspace quick zoom', () => {
|
||||
cancelable: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(renderedScale(rendered.container)).toBe(initial);
|
||||
expect(logicalViewportScale(rendered.container)).toBe(initial);
|
||||
|
||||
fireEvent.pointerEnter(preview);
|
||||
expect(
|
||||
@@ -147,7 +151,7 @@ describe('PreviewWorkspace quick zoom', () => {
|
||||
cancelable: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(renderedScale(rendered.container)).toBe(initial);
|
||||
expect(logicalViewportScale(rendered.container)).toBe(initial);
|
||||
|
||||
fireEvent.pointerLeave(preview);
|
||||
fireEvent.focus(preview);
|
||||
@@ -157,13 +161,15 @@ describe('PreviewWorkspace quick zoom', () => {
|
||||
ctrlKey: true,
|
||||
cancelable: true,
|
||||
});
|
||||
expect(renderedScale(rendered.container)).toBeCloseTo(initial * 0.86);
|
||||
expect(logicalViewportScale(rendered.container)).toBeCloseTo(
|
||||
initial * 0.86,
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps actual-size and fit shortcuts within the preview scope', () => {
|
||||
const rendered = render(<PreviewWorkspace canvas={createCanvas()} />);
|
||||
const preview = screen.getByRole('region', { name: 'UI 预览画布' });
|
||||
const fitted = renderedScale(rendered.container);
|
||||
const fitted = logicalViewportScale(rendered.container);
|
||||
|
||||
fireEvent.focus(preview);
|
||||
fireEvent.keyDown(window, {
|
||||
@@ -171,14 +177,14 @@ describe('PreviewWorkspace quick zoom', () => {
|
||||
ctrlKey: true,
|
||||
cancelable: true,
|
||||
});
|
||||
expect(renderedScale(rendered.container)).toBe(1);
|
||||
expect(logicalViewportScale(rendered.container)).toBe(1);
|
||||
|
||||
fireEvent.keyDown(window, {
|
||||
key: '0',
|
||||
ctrlKey: true,
|
||||
cancelable: true,
|
||||
});
|
||||
expect(renderedScale(rendered.container)).toBe(fitted);
|
||||
expect(logicalViewportScale(rendered.container)).toBe(fitted);
|
||||
});
|
||||
|
||||
it('leaves browser zoom untouched when the preview has no content', () => {
|
||||
|
||||
Reference in New Issue
Block a user