Files
k88936 0797410eef
Project CI / Repository checks (push) Successful in 3m18s
Project CI / Frontend tests (push) Successful in 3m32s
Project CI / Backend tests (push) Successful in 4m24s
Project CI / Native shell tests (push) Successful in 15m34s
Feat/UI自动识别和布局编辑器简化版 (#170)
* 支持从项目, 本地电脑, 主站导入/引用素材.
* 页面复杂, 需要足够空间, 作为资源打开时会占满右侧
* 编辑结果作为一种新的美术资源.
* 新增素材按钮现在是空的实现, 临时在旁边加了一个新增UI设计的按钮

临时入口:
![shotmd-1787128060.jpg](/attachments/349a4fa8-7abe-4bbb-80e0-73b1717b6488)

简化:
* 组件实现了最基本的图片和文本
* 容器式布局未接入llm编辑
* 不同阶段统一维护一个状态State, 任何阶段都可以人工调整, 不设单独的步骤
* 多图的UI合并功能暂时不要求, 隐藏入口
* 因为生成工具尚未从主站迁移, 主站地图子画布未实现, 以tab栏的形式容纳素材的子画布需求搁置
* 未实现llm绑定字体功能
* 识别会替换现有UI树, 暂时不是增量的

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/170
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-08-20 19:07:25 +08:00

120 lines
3.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
findNodePageContext,
pageRectFromSize,
resolveAnchorPresetTransform,
resolveChildrenTransformsForParentRect,
resolvePageRect,
resolveReparentTransform,
} from '../src/features/ui-editor/nodeTransformGeometry';
import type { Node } from '../src/features/ui-editor/types/Node';
import type { Transform } from '../src/features/ui-editor/types/Transform';
const ROOT_TRANSFORM: Transform = {
anchor_min: [0, 0],
anchor_max: [1, 1],
offset_min: [0, 0],
offset_max: [0, 0],
};
function node(id: string, transform: Transform, children: Node[] = []): Node {
return {
id,
layout: { transform } as Node['layout'],
metadata: {} as Node['metadata'],
components: [],
children_display_mode: 'Stack',
children,
};
}
describe('nodeTransformGeometry', () => {
it('resolves nested context and inverts page offsets at the same seam', () => {
const childTransform: Transform = {
anchor_min: [0.5, 0.5],
anchor_max: [0.5, 0.5],
offset_min: [-10, -15],
offset_max: [30, 25],
};
const root = node('root', ROOT_TRANSFORM, [
node(
'parent',
{
anchor_min: [0, 0],
anchor_max: [0, 0],
offset_min: [20, 30],
offset_max: [220, 130],
},
[node('child', childTransform)],
),
]);
const context = findNodePageContext(
root,
'child',
pageRectFromSize([400, 300]),
);
expect(context).toMatchObject({
parentRect: { min: [20, 30], max: [220, 130] },
rect: { min: [110, 65], max: [150, 105] },
});
expect(
resolveReparentTransform(
childTransform,
context!.rect,
context!.parentRect,
),
).toEqual(childTransform);
});
it('preserves direct child page rectangles when a parent changes', () => {
const child = node('child', {
anchor_min: [0.25, 0],
anchor_max: [0.75, 1],
offset_min: [5, 10],
offset_max: [-10, -15],
});
const oldParentRect = {
min: [20, 30] as [number, number],
max: [220, 130] as [number, number],
};
const newParentRect = {
min: [40, 50] as [number, number],
max: [340, 250] as [number, number],
};
const oldChildRect = resolvePageRect(child.layout.transform, oldParentRect);
const [{ transform }] = resolveChildrenTransformsForParentRect(
[child],
oldParentRect,
newParentRect,
);
expect(resolvePageRect(transform, newParentRect)).toEqual(oldChildRect);
});
it('keeps the page rectangle stable when inspector anchors change', () => {
const transform: Transform = {
anchor_min: [0.5, 0.5],
anchor_max: [0.5, 0.5],
offset_min: [-60, -20],
offset_max: [40, 30],
};
const parentSize = { width: 300, height: 200 };
const pageRect = pageRectFromSize([parentSize.width, parentSize.height]);
const next = resolveAnchorPresetTransform(
transform,
[0, 0],
[1, 1],
parentSize,
);
expect(resolvePageRect(next, pageRect)).toEqual(
resolvePageRect(transform, pageRect),
);
});
});