diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/importAdapter.ts b/apps/ai-game-creator-shell/src/features/ui-editor/importAdapter.ts
index e2f238527..15c260aa9 100644
--- a/apps/ai-game-creator-shell/src/features/ui-editor/importAdapter.ts
+++ b/apps/ai-game-creator-shell/src/features/ui-editor/importAdapter.ts
@@ -66,12 +66,6 @@ export async function prepareDesignImageBatch(
scopeId,
);
const image: UIDesignImage = {
- metadata: {
- name: '',
- description: '',
- role: null,
- slave_to: null,
- },
path: asset.localPath,
pixel_size: pixelSize,
pixels_per_unit: 1,
diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/requisites.ts b/apps/ai-game-creator-shell/src/features/ui-editor/requisites.ts
index 759792923..586c446b8 100644
--- a/apps/ai-game-creator-shell/src/features/ui-editor/requisites.ts
+++ b/apps/ai-game-creator-shell/src/features/ui-editor/requisites.ts
@@ -38,37 +38,6 @@ export function validateComponentRecognitionPrerequisites(
if (ids.length > 4) {
issues.push({ code: 'design-image-limit', message: '界面图最多 4 张' });
}
- for (const id of ids) {
- const image = state.ui_design_images[id]!;
- const { role, slave_to: slaveTo } = image.metadata;
- if (role === 'Page' && slaveTo !== null) {
- issues.push({
- code: 'page-has-slave-to',
- message: '主页面不能设置归属页面',
- resourceId: id,
- });
- } else if (role !== null && role !== 'Page') {
- if (slaveTo === null) {
- issues.push({
- code: 'missing-slave-to',
- message: '该界面角色需要选择归属主页面',
- resourceId: id,
- });
- } else if (slaveTo === id) {
- issues.push({
- code: 'self-slave-to',
- message: '界面不能归属于自身',
- resourceId: id,
- });
- } else if (state.ui_design_images[slaveTo]?.metadata.role !== 'Page') {
- issues.push({
- code: 'invalid-slave-to',
- message: '归属页面必须是有效主页面',
- resourceId: id,
- });
- }
- }
- }
return issues;
}
@@ -131,24 +100,6 @@ function visitNodes(
}
// 结果检查与进入步骤的前置条件保持分离;调用方只把结果作为非阻塞提示。
-export function validateReferenceAnalysisResult(
- state: State,
-): UiEditorPrerequisiteIssue[] {
- const images = Object.values(state.ui_design_images);
- if (images.length === 0) {
- return [{ code: 'missing-analysis-input', message: '尚未导入参考图' }];
- }
- if (images.every((image) => image.metadata.role === null)) {
- return [
- {
- code: 'missing-reference-analysis',
- message: '参考图尚未设置界面用途',
- },
- ];
- }
- return [];
-}
-
export function validateStructureRecognitionResult(
state: State,
): UiEditorPrerequisiteIssue[] {
diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImage.ts b/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImage.ts
index b1f383426..c4cf6dbd5 100644
--- a/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImage.ts
+++ b/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImage.ts
@@ -1,4 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-import type { UIDesignImageMetadata } from "./UIDesignImageMetadata";
-export type UIDesignImage = { metadata: UIDesignImageMetadata, path: string, pixel_size: [number, number], pixels_per_unit: number, };
+export type UIDesignImage = { path: string, pixel_size: [number, number], pixels_per_unit: number, };
diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImageMetadata.ts b/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImageMetadata.ts
deleted file mode 100644
index 2e9df7fd3..000000000
--- a/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImageMetadata.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-import type { UIDesignImageId } from "./UIDesignImageId";
-import type { UIDesignImageRole } from "./UIDesignImageRole";
-
-export type UIDesignImageMetadata = { name: string, description: string, role: UIDesignImageRole | null, slave_to: UIDesignImageId | null, };
diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImageRole.ts b/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImageRole.ts
deleted file mode 100644
index 131b0860b..000000000
--- a/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignImageRole.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type UIDesignImageRole = "Page" | "Section" | "Modal" | "Drawer" | "Popover" | "State" | "Scrolled" | "Detail";
diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignSuggestionTreeNode.ts b/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignSuggestionTreeNode.ts
deleted file mode 100644
index fe0b35bc1..000000000
--- a/apps/ai-game-creator-shell/src/features/ui-editor/types/UIDesignSuggestionTreeNode.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-import type { UIDesignImageId } from "./UIDesignImageId";
-import type { UIDesignImageRole } from "./UIDesignImageRole";
-
-export type UIDesignSuggestionTreeNode = { id: UIDesignImageId, name: string, description: string, role: UIDesignImageRole, children: Array, };
diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignSuggestions.ts b/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignSuggestions.ts
deleted file mode 100644
index 4727c7cdf..000000000
--- a/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignSuggestions.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { State } from './types/State';
-import type { UIDesignImageId } from './types/UIDesignImageId';
-import type { UIDesignSuggestionTreeNode } from './types/UIDesignSuggestionTreeNode';
-
-/**
- * Applies semantic suggestions conservatively. The tree carries relationships;
- * existing metadata remains authoritative whenever it is already populated.
- * Persisted `slave_to` remains the owning Page ID, so nested descendants inherit
- * their root Page rather than pointing at an intermediate Section.
- */
-export function applyUiDesignSuggestions(
- state: State,
- suggestions: readonly UIDesignSuggestionTreeNode[],
-): State {
- const next = structuredClone(state);
-
- function applyNode(
- node: UIDesignSuggestionTreeNode,
- pageId: UIDesignImageId | null,
- ): void {
- const image = next.ui_design_images[node.id];
- if (!image) return;
-
- if (image.metadata.name.trim().length === 0 && node.name.trim()) {
- image.metadata.name = node.name.trim();
- }
- if (image.metadata.role === null) {
- image.metadata.role = node.role;
- }
- if (image.metadata.slave_to === null && pageId !== null) {
- image.metadata.slave_to = pageId;
- }
- if (
- image.metadata.description.trim().length === 0 &&
- node.description.trim()
- ) {
- image.metadata.description = node.description.trim();
- }
-
- const effectiveRole = image.metadata.role ?? node.role;
- const childPageId = effectiveRole === 'Page' ? node.id : pageId;
- for (const child of node.children) {
- applyNode(child, childPageId);
- }
- }
-
- for (const root of suggestions) {
- applyNode(root, null);
- }
-
- return next;
-}
diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/useUiEditorState.ts b/apps/ai-game-creator-shell/src/features/ui-editor/useUiEditorState.ts
index 203e26d28..0a0b1782a 100644
--- a/apps/ai-game-creator-shell/src/features/ui-editor/useUiEditorState.ts
+++ b/apps/ai-game-creator-shell/src/features/ui-editor/useUiEditorState.ts
@@ -25,7 +25,6 @@ import type { SpriteBorder } from './types/SpriteBorder';
import type { State } from './types/State';
import type { UIDesignImage } from './types/UIDesignImage';
import type { UIDesignImageId } from './types/UIDesignImageId';
-import type { UIDesignImageRole } from './types/UIDesignImageRole';
import type { UiNodeMoveRequest } from './types/UiNodeMoveRequest';
export const EMPTY_UI_EDITOR_STATE: State = {
@@ -85,23 +84,6 @@ type NodeLocation = {
index: number;
};
-function wouldCreateSlaveToCycle(
- images: State['ui_design_images'],
- id: UIDesignImageId,
- slaveTo: UIDesignImageId,
-): boolean {
- let current: UIDesignImageId | null = slaveTo;
- const visited = new Set();
- while (current !== null) {
- if (current === id || visited.has(current)) return true;
- visited.add(current);
- const image: UIDesignImage | undefined = images[current];
- if (!image) return true;
- current = image.metadata.slave_to;
- }
- return false;
-}
-
function visitNodes(node: Node, visit: (node: Node) => void): void {
visit(node);
for (const child of node.children) visitNodes(child, visit);
@@ -278,7 +260,6 @@ export type DesignImageInput = {
export type RemovalImpact = {
removedResourceCount: number;
removedTreeCount: number;
- clearedSlaveToCount: number;
clearedTargetGraphicCount: number;
clearedFontCount: number;
};
@@ -495,9 +476,6 @@ export function designImageRemovalImpact(
removedResourceCount: id in state.ui_design_images ? 1 : 0,
removedTreeCount: state.ui_trees.filter((tree) => tree.src_ui_design === id)
.length,
- clearedSlaveToCount: Object.values(state.ui_design_images).filter(
- (image) => image.metadata.slave_to === id,
- ).length,
clearedTargetGraphicCount: 0,
clearedFontCount: 0,
};
@@ -518,7 +496,6 @@ export function spriteAssetRemovalImpact(
return {
removedResourceCount: id in state.sprite_assets ? 1 : 0,
removedTreeCount: 0,
- clearedSlaveToCount: 0,
clearedTargetGraphicCount,
clearedFontCount: 0,
};
@@ -539,7 +516,6 @@ export function fontAssetRemovalImpact(
return {
removedResourceCount: id in state.font_assets ? 1 : 0,
removedTreeCount: 0,
- clearedSlaveToCount: 0,
clearedTargetGraphicCount: 0,
clearedFontCount,
};
@@ -740,86 +716,6 @@ export function useUiEditorState(initialState: State = EMPTY_UI_EDITOR_STATE) {
[],
);
- const setImageName = useCallback(
- (id: UIDesignImageId, name: string): UiEditorOperationResult => {
- const blocked = guard();
- if (blocked) return blocked;
- const current = stateRef.current;
- if (!(id in current.ui_design_images)) {
- return { ok: false, reason: 'missing' };
- }
- const next = cloneState(current);
- next.ui_design_images[id]!.metadata.name = name;
- commit(next);
- return { ok: true, value: undefined };
- },
- [commit, guard],
- );
-
- const setImageDescription = useCallback(
- (id: UIDesignImageId, description: string): UiEditorOperationResult => {
- const blocked = guard();
- if (blocked) return blocked;
- const current = stateRef.current;
- if (!(id in current.ui_design_images)) {
- return { ok: false, reason: 'missing' };
- }
- const next = cloneState(current);
- next.ui_design_images[id]!.metadata.description = description;
- commit(next);
- return { ok: true, value: undefined };
- },
- [commit, guard],
- );
-
- const setImageRole = useCallback(
- (
- id: UIDesignImageId,
- role: UIDesignImageRole | null,
- ): UiEditorOperationResult => {
- const blocked = guard();
- if (blocked) return blocked;
- const current = stateRef.current;
- if (!(id in current.ui_design_images)) {
- return { ok: false, reason: 'missing' };
- }
- const next = cloneState(current);
- next.ui_design_images[id]!.metadata.role = role;
- synchronizeDesignImageTrees(next);
- commit(next);
- return { ok: true, value: undefined };
- },
- [commit, guard],
- );
-
- const setImageSlaveTo = useCallback(
- (
- id: UIDesignImageId,
- slaveTo: UIDesignImageId | null,
- ): UiEditorOperationResult => {
- const blocked = guard();
- if (blocked) return blocked;
- const current = stateRef.current;
- if (!(id in current.ui_design_images)) {
- return { ok: false, reason: 'missing' };
- }
- if (slaveTo !== null && !(slaveTo in current.ui_design_images)) {
- return { ok: false, reason: 'missing' };
- }
- if (
- slaveTo !== null &&
- wouldCreateSlaveToCycle(current.ui_design_images, id, slaveTo)
- ) {
- return { ok: false, reason: 'invalid:slave_to 不能形成循环' };
- }
- const next = cloneState(current);
- next.ui_design_images[id]!.metadata.slave_to = slaveTo;
- commit(next);
- return { ok: true, value: undefined };
- },
- [commit, guard],
- );
-
const addDesignImages = useCallback(
(entries: readonly DesignImageInput[]): UiEditorOperationResult => {
const blocked = guard();
@@ -1384,9 +1280,6 @@ export function useUiEditorState(initialState: State = EMPTY_UI_EDITOR_STATE) {
const next = cloneState(current);
delete next.ui_design_images[id];
next.ui_trees = next.ui_trees.filter((tree) => tree.src_ui_design !== id);
- for (const image of Object.values(next.ui_design_images)) {
- if (image.metadata.slave_to === id) image.metadata.slave_to = null;
- }
commit(next);
return { ok: true, value: impact };
},
@@ -1486,10 +1379,6 @@ export function useUiEditorState(initialState: State = EMPTY_UI_EDITOR_STATE) {
isLocked,
runWithStateLocked,
- setImageName,
- setImageDescription,
- setImageRole,
- setImageSlaveTo,
addDesignImages,
addSpriteAssets,
addFontAssets,
diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx
index edf2bc0d5..61e32e9c4 100644
--- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx
+++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx
@@ -6352,7 +6352,7 @@ export default function ProjectDevelopmentView({
...(result.asset.source.generationKind === 'ui-workflow.completed'
? {
initialStep: 'asset-separation',
- initialFurthestStepIndex: 2,
+ initialFurthestStepIndex: 1,
}
: {}),
});
@@ -6403,7 +6403,7 @@ export default function ProjectDevelopmentView({
)?.source.generationKind === 'ui-workflow.completed'
? {
initialStep: 'asset-separation' as const,
- initialFurthestStepIndex: 2,
+ initialFurthestStepIndex: 1,
}
: {}),
});
@@ -6508,7 +6508,7 @@ export default function ProjectDevelopmentView({
completed.localPath.split(/[\\/]/u).filter(Boolean).pop() ??
'UI 设计资源',
initialStep: 'asset-separation',
- initialFurthestStepIndex: 2,
+ initialFurthestStepIndex: 1,
});
}, [advanceFocusGeneration, manifest.assets, uiEditorRoute]);
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/EditorDialogs.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/EditorDialogs.tsx
index a4d637ecb..fad5685ef 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/components/EditorDialogs.tsx
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/EditorDialogs.tsx
@@ -74,7 +74,6 @@ export function EditorDialogs({
- 删除组件树:{impact?.removedTreeCount ?? 0}
- - 清空界面归属:{impact?.clearedSlaveToCount ?? 0}
- 清空图片组件引用:{impact?.clearedTargetGraphicCount ?? 0}
- 清空文本字体引用:{impact?.clearedFontCount ?? 0}
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/ImportOverview.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/ImportOverview.tsx
deleted file mode 100644
index 0823299bd..000000000
--- a/apps/ai-game-creator-shell/src/view/ui-editor/components/ImportOverview.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import type { Node as UiNode } from '../../../features/ui-editor/types/Node';
-import type { SpriteAsset } from '../../../features/ui-editor/types/SpriteAsset';
-import type { UIDesignImage } from '../../../features/ui-editor/types/UIDesignImage';
-import type { UITree } from '../../../features/ui-editor/types/UITree';
-
-function countUiComponents(nodes: UiNode[]): number {
- return nodes.reduce(
- (total, node) =>
- total + (node.component ? 1 : 0) + countUiComponents(node.children),
- 0,
- );
-}
-
-export function ImportOverview({
- images,
- sprites,
- uiTrees,
-}: {
- images: Record;
- sprites: Record;
- uiTrees: UITree[];
-}) {
- const componentCount = uiTrees.reduce(
- (total, tree) => total + countUiComponents([tree.root]),
- 0,
- );
-
- return (
-
-
-
-
- Overview
-
-
导入概览
-
-
-
-
-
- {Object.keys(images).length}
-
-
- 界面图数量
-
-
-
-
- {Object.keys(sprites).length}
-
-
- 素材数量
-
-
-
-
- {componentCount}
-
-
- 已有组件
-
-
-
-
- );
-}
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/InputSidebar.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/InputSidebar.tsx
index a6a28fafd..065780691 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/components/InputSidebar.tsx
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/InputSidebar.tsx
@@ -6,12 +6,17 @@ import { visitUiNodes } from '../../../features/ui-editor/treeUtils';
import type { Node as UiNode } from '../../../features/ui-editor/types/Node';
import type { NodeId } from '../../../features/ui-editor/types/NodeId';
import type { UIDesignImageId } from '../../../features/ui-editor/types/UIDesignImageId';
+import { resourceAssetDisplayName } from '../../project-development/resourceAssetDisplayName';
import type { UiEditorInputProjection } from '../useUiEditorPage';
import { CollapsibleSidebarPanel } from './CollapsibleSidebarPanel';
import { UiTreePanel } from './UiTreePanel';
const SUPER_ROOT_ID = '__ui-editor-super-root__';
+function designImageLabel(path: string, index: number) {
+ return resourceAssetDisplayName(path) || `界面图 ${index + 1}`;
+}
+
export function InputSidebar({ input }: { input: UiEditorInputProjection }) {
const {
projectPath,
@@ -130,7 +135,7 @@ export function InputSidebar({ input }: { input: UiEditorInputProjection }) {
>
{imageOrder.length > 0 ? (
- imageOrder.map((id) => {
+ imageOrder.map((id, index) => {
const image = images[id];
if (!image) return null;
return (
@@ -157,10 +162,9 @@ export function InputSidebar({ input }: { input: UiEditorInputProjection }) {
- {image.metadata.name}
+ {designImageLabel(image.path, index)}
- {image.metadata.role ?? '自动判断'} ·{' '}
{image.pixel_size[0]} × {image.pixel_size[1]}
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/InspectorSidebar.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/InspectorSidebar.tsx
index 4d17e38cb..d7005f4fa 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/InspectorSidebar.tsx
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/Inspector/InspectorSidebar.tsx
@@ -12,10 +12,8 @@ import { FontSamplePreview } from '../../../../features/ui-editor/components/Fon
import { SpriteImagePreview } from '../../../../features/ui-editor/components/SpriteImagePreview';
import type { StageStatusField } from '../../../../features/ui-editor/stageStatusOverview';
import type { NodeId } from '../../../../features/ui-editor/types/NodeId';
-import type { UIDesignImageId } from '../../../../features/ui-editor/types/UIDesignImageId';
-import type { UIDesignImageRole } from '../../../../features/ui-editor/types/UIDesignImageRole';
import { uiEditorPrivateFontFamily } from '../../../../features/ui-editor/useUiEditorFontFaces';
-import { UI_DESIGN_IMAGE_ROLES } from '../../model';
+import { resourceAssetDisplayName } from '../../../project-development/resourceAssetDisplayName';
import type { UiEditorInspectorProjection } from '../../useUiEditorPage';
import { ComponentPanel } from './Components/ComponentPanel';
import {
@@ -64,7 +62,6 @@ type InspectorView =
kind: 'image';
image: ActiveImage;
imageId: ActiveImageId;
- pageOptions: UiEditorInspectorProjection['pageOptions'];
}
| { kind: 'empty' };
@@ -155,11 +152,6 @@ export function InspectorSidebar({
inspector.requestDesignImageRemoval(view.imageId)}
deleteDisabled={inspector.isLocked}
/>
@@ -226,7 +218,6 @@ function getInspectorView(
spriteReferenceCounts,
fontReferenceCounts,
fontFaces,
- pageOptions,
} = inspector;
if (selectedNode) {
@@ -263,7 +254,6 @@ function getInspectorView(
kind: 'image',
image: activeImage,
imageId: activeImageId,
- pageOptions,
};
}
@@ -1034,21 +1024,11 @@ function InspectorReadout({ label, value }: { label: string; value: string }) {
function ImageInspector({
image,
imageId,
- pageOptions,
- onNameChange,
- onDescriptionChange,
- onRoleChange,
- onSlaveToChange,
onDelete,
deleteDisabled,
}: {
image: ActiveImage;
imageId: ActiveImageId;
- pageOptions: UiEditorInspectorProjection['pageOptions'];
- onNameChange: UiEditorInspectorProjection['setImageName'];
- onDescriptionChange: UiEditorInspectorProjection['setImageDescription'];
- onRoleChange: UiEditorInspectorProjection['setImageRole'];
- onSlaveToChange: UiEditorInspectorProjection['setImageSlaveTo'];
onDelete: () => void;
deleteDisabled: boolean;
}) {
@@ -1056,65 +1036,13 @@ function ImageInspector({
return (
-
{
- if (!readOnly) onNameChange(event.target.value);
- }}
- />
- {
- if (!readOnly) onDescriptionChange(event.target.value);
- }}
- />
+
+ {resourceAssetDisplayName(image.path)}
+
- {
- if (!readOnly) {
- onRoleChange(
- (event.target.value || null) as UIDesignImageRole | null,
- );
- }
- }}
- >
-
- {UI_DESIGN_IMAGE_ROLES.map((role) => (
-
- ))}
-
- {image.metadata.role !== 'Page' || image.metadata.slave_to !== null ? (
- {
- if (!readOnly) {
- onSlaveToChange(
- (event.target.value || null) as UIDesignImageId | null,
- );
- }
- }}
- >
-
- {pageOptions
- .filter(([id]) => id !== imageId)
- .map(([id, page]) => (
-
- ))}
-
- ) : null}
{UI_EDITOR_STEPS.map((step, index) => {
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowActionCard.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowActionCard.tsx
index 7c392369b..24d61f8e6 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowActionCard.tsx
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowActionCard.tsx
@@ -8,17 +8,14 @@ export function WorkflowActionCard({
}) {
const action = getStepAction(workflow);
const running =
- (workflow.activeStep === 'reference-analysis' && workflow.isSuggesting) ||
(workflow.activeStep === 'structure-recognition' &&
workflow.isRecognizing) ||
(workflow.activeStep === 'asset-separation' && workflow.isSeparating);
const status = {
- 'reference-analysis': workflow.suggestionStatus,
'structure-recognition': workflow.recognitionStatus,
'asset-separation': workflow.separationStatus,
}[workflow.activeStep];
const hasRun = {
- 'reference-analysis': workflow.hasSuggested,
'structure-recognition': workflow.hasRecognized,
'asset-separation': workflow.hasSeparated,
}[workflow.activeStep];
@@ -100,13 +97,6 @@ export function WorkflowActionCard({
}
function getStepAction(workflow: UiEditorWorkflowProjection) {
- if (workflow.activeStep === 'reference-analysis') {
- return {
- label: '分析参考图',
- runningLabel: '分析中…',
- action: workflow.suggestUiDesignSemantics,
- };
- }
if (workflow.activeStep === 'structure-recognition') {
return {
label: '识别界面结构',
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowChecks.ts b/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowChecks.ts
index 5df5d0d9b..4a0431922 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowChecks.ts
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/WorkflowChecks.ts
@@ -3,7 +3,6 @@ import {
validateAssetSeparationPrerequisites,
validateAssetSeparationResult,
validateComponentRecognitionPrerequisites,
- validateReferenceAnalysisResult,
validateStructureRecognitionResult,
} from '../../../features/ui-editor/requisites';
import type { State } from '../../../features/ui-editor/types/State';
@@ -16,8 +15,6 @@ export function prerequisiteIssuesForStep(
step: UiEditorStepId,
): UiEditorPrerequisiteIssue[] {
switch (step) {
- case 'reference-analysis':
- return [];
case 'structure-recognition':
return validateComponentRecognitionPrerequisites(state);
case 'asset-separation':
@@ -30,8 +27,6 @@ export function postCheckIssuesForStep(
step: UiEditorStepId,
): UiEditorPrerequisiteIssue[] {
switch (step) {
- case 'reference-analysis':
- return validateReferenceAnalysisResult(state);
case 'structure-recognition':
return validateStructureRecognitionResult(state);
case 'asset-separation':
@@ -43,7 +38,6 @@ export function postCheckIssuesForSave(
state: State,
): UiEditorPrerequisiteIssue[] {
return [
- ...validateReferenceAnalysisResult(state),
...validateStructureRecognitionResult(state),
...validateAssetSeparationResult(state),
];
@@ -54,8 +48,6 @@ export function activeStepPrerequisiteIssues(
step: UiEditorStepId,
): UiEditorPrerequisiteIssue[] {
switch (step) {
- case 'reference-analysis':
- return validateComponentRecognitionPrerequisites(state);
case 'structure-recognition':
return validateAssetSeparationPrerequisites(state);
case 'asset-separation':
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx
index 4595acef0..48d8a0bb9 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx
@@ -31,6 +31,7 @@ import {
import { findNodePageContext } from '../../../../features/ui-editor/nodeTransformGeometry';
import type { Node } from '../../../../features/ui-editor/types/Node';
import type { NodeId } from '../../../../features/ui-editor/types/NodeId';
+import { resourceAssetDisplayName } from '../../../project-development/resourceAssetDisplayName';
import type { UiEditorCanvasProjection } from '../../useUiEditorPage';
import { UiNodeContextMenu } from '../UiNodeContextMenu';
import { resolvePreviewGridStep } from './previewGrid';
@@ -694,7 +695,9 @@ export function PreviewWorkspace({
{showOriginImage && previewUrls[item.src_ui_design] ? (
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/workflowCompletionNotice.ts b/apps/ai-game-creator-shell/src/view/ui-editor/components/workflowCompletionNotice.ts
index 02c5b317a..2e370bdc7 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/components/workflowCompletionNotice.ts
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/workflowCompletionNotice.ts
@@ -14,8 +14,6 @@ export function appendWorkflowCheckPrompt(message: string): string {
export function workflowStepLabel(step: UiEditorStepId): string {
switch (step) {
- case 'reference-analysis':
- return '分析参考图';
case 'structure-recognition':
return '识别界面结构';
case 'asset-separation':
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx
index 8d82cf444..b7b49877b 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx
@@ -9,7 +9,6 @@ import {
uiDesignStateStore,
} from '../../features/ui-editor/uiDesignStateStore';
import { EditorDialogs } from './components/EditorDialogs';
-import { ImportOverview } from './components/ImportOverview';
import { InputSidebar } from './components/InputSidebar';
import { InspectorSidebar } from './components/Inspector/InspectorSidebar';
import { PreviewWorkspace } from './components/preview/PreviewWorkspace';
@@ -91,7 +90,7 @@ export default function UiEditorPage({
}}
/>
);
- } else if (session.input.activeStep === 'asset-separation') {
+ } else {
overview = (
);
- } else {
- overview = (
-
- );
}
const saveDisabled =
session.save.isSaving ||
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/model.ts b/apps/ai-game-creator-shell/src/view/ui-editor/model.ts
index f03f6ef47..4722faac6 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/model.ts
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/model.ts
@@ -1,12 +1,8 @@
import type { NodeId } from '../../features/ui-editor/types/NodeId';
import type { UIDesignImageId } from '../../features/ui-editor/types/UIDesignImageId';
-import type { UIDesignImageRole } from '../../features/ui-editor/types/UIDesignImageRole';
import type { RemovalImpact } from '../../features/ui-editor/useUiEditorState';
-export type UiEditorStepId =
- | 'reference-analysis'
- | 'structure-recognition'
- | 'asset-separation';
+export type UiEditorStepId = 'structure-recognition' | 'asset-separation';
export type UiEditorImportKind = 'design-image' | 'font' | 'sprite';
export type UiEditorNodeFocusRequest = {
@@ -25,29 +21,13 @@ export const UI_EDITOR_STEPS: Array<{
id: UiEditorStepId;
label: string;
}> = [
- { id: 'reference-analysis', label: '分析参考图' },
{ id: 'structure-recognition', label: '识别界面结构' },
{ id: 'asset-separation', label: '自动切分素材' },
];
-export const UI_DESIGN_IMAGE_ROLES: Array<{
- value: UIDesignImageRole;
- label: string;
-}> = [
- { value: 'Page', label: '主页面' },
- { value: 'Section', label: '子界面 / 页签' },
- { value: 'Modal', label: '模态弹窗' },
- { value: 'Drawer', label: '抽屉 / 侧栏' },
- { value: 'Popover', label: '局部浮层' },
- { value: 'State', label: '交互状态' },
- { value: 'Scrolled', label: '滚动后内容' },
- { value: 'Detail', label: '局部详情' },
-];
-
export function removalHasDownstreamReferences(impact: RemovalImpact) {
return (
impact.removedTreeCount +
- impact.clearedSlaveToCount +
impact.clearedTargetGraphicCount +
impact.clearedFontCount >
0
diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts
index 6a5df64c9..0d876b769 100644
--- a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts
+++ b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts
@@ -35,18 +35,13 @@ import type { SeparationDTO } from '../../features/ui-editor/types/SeparationDTO
import type { SeparationRecoveryDTO } from '../../features/ui-editor/types/SeparationRecoveryDTO';
import type { SpriteAssetId } from '../../features/ui-editor/types/SpriteAssetId';
import type { SpriteBorder } from '../../features/ui-editor/types/SpriteBorder';
-import type { State } from '../../features/ui-editor/types/State';
-import type { UIDesignImage } from '../../features/ui-editor/types/UIDesignImage';
import type { UIDesignImageId } from '../../features/ui-editor/types/UIDesignImageId';
-import type { UIDesignImageRole } from '../../features/ui-editor/types/UIDesignImageRole';
-import type { UIDesignSuggestionTreeNode } from '../../features/ui-editor/types/UIDesignSuggestionTreeNode';
import type { UiNodeMoveRequest } from '../../features/ui-editor/types/UiNodeMoveRequest';
import {
type IUiDesignStateStore,
type UiDesignCodeGenerationResult,
uiDesignStateStore,
} from '../../features/ui-editor/uiDesignStateStore';
-import { applyUiDesignSuggestions } from '../../features/ui-editor/uiDesignSuggestions';
import { useUiEditorFontFaces } from '../../features/ui-editor/useUiEditorFontFaces';
import { addSpriteAssetsToState } from '../../features/ui-editor/useUiEditorState';
import {
@@ -77,6 +72,7 @@ import {
import {
type PendingResourceRemoval,
removalHasDownstreamReferences,
+ UI_EDITOR_STEPS,
type UiEditorImportKind,
uiEditorOperationError,
type UiEditorStepId,
@@ -151,25 +147,6 @@ function isUiNodeEffectivelyVisible(
return null;
}
-function isSlaveToDescendant(
- images: State['ui_design_images'],
- candidateId: UIDesignImageId,
- ancestorId: UIDesignImageId | null,
-): boolean {
- if (ancestorId === null || candidateId === ancestorId) return true;
- let current: UIDesignImageId | null = candidateId;
- const visited = new Set();
- while (current !== null) {
- if (visited.has(current)) return true;
- visited.add(current);
- const image: UIDesignImage | undefined = images[current];
- if (!image) return true;
- current = image.metadata.slave_to;
- if (current === ancestorId) return true;
- }
- return false;
-}
-
export type PendingWorkflowStepChange = {
from: UiEditorStepId;
to: UiEditorStepId;
@@ -187,7 +164,7 @@ export function useUiEditorSession(
projectPath: string,
resourceId?: string,
stateStore: IUiDesignStateStore = uiDesignStateStore,
- initialStep: UiEditorStepId = 'reference-analysis',
+ initialStep: UiEditorStepId = 'structure-recognition',
initialFurthestStepIndex = 0,
) {
const editor = useUiEditorState(EMPTY_UI_EDITOR_STATE);
@@ -206,15 +183,16 @@ export function useUiEditorSession(
const [saveError, setSaveError] = useState(null);
const [isSaving, setIsSaving] = useState(false);
const [isGenerating, setIsGenerating] = useState(false);
- const normalizedInitialStepIndex =
- initialStep === 'reference-analysis'
- ? 0
- : initialStep === 'structure-recognition'
- ? 1
- : 2;
+ const normalizedInitialStepIndex = Math.max(
+ 0,
+ UI_EDITOR_STEPS.findIndex((step) => step.id === initialStep),
+ );
const normalizedInitialFurthestStepIndex = Math.max(
normalizedInitialStepIndex,
- Math.min(2, Math.max(0, Math.trunc(initialFurthestStepIndex))),
+ Math.min(
+ UI_EDITOR_STEPS.length - 1,
+ Math.max(0, Math.trunc(initialFurthestStepIndex)),
+ ),
);
const [activeStep, setActiveStep] = useState(initialStep);
const [furthestStepIndex, setFurthestStepIndex] = useState(
@@ -251,15 +229,9 @@ export function useUiEditorSession(
const [clearOpen, setClearOpen] = useState(false);
const [pendingRemoval, setPendingRemoval] =
useState(null);
- const suggestionOperation = useUiEditorOperation();
const recognitionOperation = useUiEditorOperation();
const mergeOperation = useUiEditorOperation();
const separationOperation = useUiEditorOperation();
- const isSuggesting = suggestionOperation.running;
- const suggestionStatus = suggestionOperation.status;
- const beginSuggestion = suggestionOperation.begin;
- const finishSuggestion = suggestionOperation.finish;
- const setSuggestionStatus = suggestionOperation.setStatus;
const isRecognizing = recognitionOperation.running;
const recognitionStatus = recognitionOperation.status;
const beginRecognition = recognitionOperation.begin;
@@ -277,7 +249,6 @@ export function useUiEditorSession(
const setSeparationStatus = separationOperation.setStatus;
const [separationRecovery, setSeparationRecovery] =
useState(null);
- const [hasSuggested, setHasSuggested] = useState(false);
const [hasRecognized, setHasRecognized] = useState(false);
const [hasSeparated, setHasSeparated] = useState(false);
const [completionNotice, setCompletionNotice] =
@@ -411,13 +382,7 @@ export function useUiEditorSession(
const activeImage = activeImageId ? images[activeImageId] : null;
const selectedSprite = selectedSpriteId ? sprites[selectedSpriteId] : null;
const selectedFont = selectedFontId ? fonts[selectedFontId] : null;
- const pageOptions = Object.entries(images).filter(
- ([id, image]) =>
- image.metadata.role === 'Page' &&
- !isSlaveToDescendant(images, id as UIDesignImageId, activeImageId),
- );
- const isAiRunning =
- isSuggesting || isRecognizing || isMerging || isSeparating;
+ const isAiRunning = isRecognizing || isMerging || isSeparating;
const isWorkflowBusy =
isAiRunning || isSaving || isGenerating || isLoading || editor.isLocked;
const stateSignature = JSON.stringify(editor.state);
@@ -426,7 +391,6 @@ export function useUiEditorSession(
savedStateSignature !== null &&
savedStateSignature !== stateSignature;
const nextStepByStep: Partial> = {
- 'reference-analysis': 'structure-recognition',
'structure-recognition': 'asset-separation',
};
const nextStep = nextStepByStep[activeStep] ?? null;
@@ -717,13 +681,10 @@ export function useUiEditorSession(
function enterStep(step: UiEditorStepId) {
setActiveStep(step);
- const index =
- step === 'reference-analysis'
- ? 0
- : step === 'structure-recognition'
- ? 1
- : 2;
- setFurthestStepIndex((current) => Math.max(current, index));
+ const index = UI_EDITOR_STEPS.findIndex(
+ (candidate) => candidate.id === step,
+ );
+ setFurthestStepIndex((current) => Math.max(current, Math.max(0, index)));
}
function requestStepChange(step: UiEditorStepId) {
@@ -953,26 +914,6 @@ export function useUiEditorSession(
setSelectedFontId(id);
}
- function setImageName(name: string) {
- if (!activeImageId) return;
- editor.setImageName(activeImageId, name);
- }
-
- function setImageDescription(description: string) {
- if (!activeImageId) return;
- editor.setImageDescription(activeImageId, description);
- }
-
- function setImageRole(role: UIDesignImageRole | null) {
- if (!activeImageId) return;
- editor.setImageRole(activeImageId, role);
- }
-
- function setImageSlaveTo(slaveTo: UIDesignImageId | null) {
- if (!activeImageId) return;
- editor.setImageSlaveTo(activeImageId, slaveTo);
- }
-
function setSpriteName(name: string) {
if (!selectedSpriteId) return;
editor.setSpriteName(selectedSpriteId, name);
@@ -988,38 +929,6 @@ export function useUiEditorSession(
editor.setSpriteBorder(selectedSpriteId, border);
}
- async function suggestUiDesignSemantics() {
- if (isSuggesting || isWorkflowBusy) return;
- setCompletionNotice(null);
- setSuggestionStatus(null);
- beginSuggestion();
- try {
- await editor.runWithStateLocked(async (snapshot) => {
- const suggestions = await invoke(
- 'suggest_ui_design_semantic',
- { projectPath, state: snapshot },
- );
- editor.replaceState(applyUiDesignSuggestions(snapshot, suggestions));
- setHasSuggested(true);
- reportWorkflowCompletion(
- 'reference-analysis',
- 'success',
- `参考图分析完成:已应用 ${suggestions.length} 条参考图语义建议`,
- setSuggestionStatus,
- );
- });
- } catch (cause) {
- reportWorkflowCompletion(
- 'reference-analysis',
- 'failure',
- cause instanceof Error ? cause.message : String(cause),
- setSuggestionStatus,
- );
- } finally {
- finishSuggestion();
- }
- }
-
async function recognizeUi() {
if (isRecognizing || isWorkflowBusy) return;
setCompletionNotice(null);
@@ -1587,16 +1496,13 @@ export function useUiEditorSession(
operations: {
isMerging,
isRecognizing,
- isSuggesting,
mergeStatus,
recognitionStatus,
- suggestionStatus,
},
checkPrerequisites,
separateUi,
mergeUi,
recognizeUi,
- suggestUiDesignSemantics,
openImporter: setImportKind,
selectDesignImage,
selectSprite,
@@ -1666,7 +1572,6 @@ export function useUiEditorSession(
sprites,
fonts,
fontFaces,
- pageOptions,
spriteReferenceCounts,
fontReferenceCounts,
keepChildrenUnchanged,
@@ -1685,10 +1590,6 @@ export function useUiEditorSession(
setSpriteBorder,
requestSpriteRemoval,
requestFontRemoval,
- setImageName,
- setImageDescription,
- setImageRole,
- setImageSlaveTo,
requestDesignImageRemoval,
},
workflow: {
@@ -1698,10 +1599,6 @@ export function useUiEditorSession(
isAiRunning,
isBusy: isWorkflowBusy,
pendingStepChange: pendingWorkflowStepChange,
- isSuggesting,
- hasSuggested,
- suggestionStatus,
- suggestUiDesignSemantics,
isRecognizing,
hasRecognized,
recognitionStatus,
diff --git a/apps/ai-game-creator-shell/tests/previewWorkspaceZoom.test.tsx b/apps/ai-game-creator-shell/tests/previewWorkspaceZoom.test.tsx
index 6f756a6c6..5ca2728b2 100644
--- a/apps/ai-game-creator-shell/tests/previewWorkspaceZoom.test.tsx
+++ b/apps/ai-game-creator-shell/tests/previewWorkspaceZoom.test.tsx
@@ -52,12 +52,6 @@ const root: UiNode = {
};
const activeImage: UIDesignImage = {
- metadata: {
- name: '测试界面',
- description: '',
- role: null,
- slave_to: null,
- },
path: 'assets/page.png',
pixel_size: [1200, 800],
pixels_per_unit: 1,
diff --git a/apps/ai-game-creator-shell/tests/uiDesignSuggestions.test.ts b/apps/ai-game-creator-shell/tests/uiDesignSuggestions.test.ts
deleted file mode 100644
index 4f16f2ec4..000000000
--- a/apps/ai-game-creator-shell/tests/uiDesignSuggestions.test.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import type { State } from '../src/features/ui-editor/types/State';
-import { applyUiDesignSuggestions } from '../src/features/ui-editor/uiDesignSuggestions';
-
-function state(): State {
- return {
- ui_trees: [],
- sprite_assets: {},
- font_assets: {},
- ui_design_images: {
- page: {
- metadata: { name: '', description: '', role: null, slave_to: null },
- path: 'page.png',
- pixel_size: [100, 100],
- pixels_per_unit: 1,
- },
- section: {
- metadata: {
- name: '已有名称',
- description: '',
- role: 'Section',
- slave_to: null,
- },
- path: 'section.png',
- pixel_size: [100, 100],
- pixels_per_unit: 1,
- },
- detail: {
- metadata: {
- name: '',
- description: '已有描述',
- role: null,
- slave_to: null,
- },
- path: 'detail.png',
- pixel_size: [100, 100],
- pixels_per_unit: 1,
- },
- },
- };
-}
-
-describe('applyUiDesignSuggestions', () => {
- it('masks existing metadata and maps descendants to their owning Page', () => {
- const before = state();
- const after = applyUiDesignSuggestions(before, [
- {
- id: 'page',
- name: '主页面',
- description: '页面描述',
- role: 'Page',
- children: [
- {
- id: 'section',
- name: '模型名称不应覆盖',
- description: '页签描述',
- role: 'Section',
- children: [
- {
- id: 'detail',
- name: '详情',
- description: '模型描述不应覆盖',
- role: 'Detail',
- children: [],
- },
- ],
- },
- ],
- },
- ]);
-
- expect(after.ui_design_images.page.metadata).toEqual({
- name: '主页面',
- description: '页面描述',
- role: 'Page',
- slave_to: null,
- });
- expect(after.ui_design_images.section.metadata).toEqual({
- name: '已有名称',
- description: '页签描述',
- role: 'Section',
- slave_to: 'page',
- });
- expect(after.ui_design_images.detail.metadata).toEqual({
- name: '详情',
- description: '已有描述',
- role: 'Detail',
- slave_to: 'page',
- });
- expect(before.ui_design_images.page.metadata.name).toBe('');
- });
-});
diff --git a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts
index c7ad62a6f..bc3bbaed1 100644
--- a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts
+++ b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts
@@ -103,12 +103,6 @@ function stateWithPages(pageIds: string[]): State {
pageIds.map((id) => [
id,
{
- metadata: {
- name: id,
- description: '',
- role: 'Page',
- slave_to: null,
- },
path: `assets/${id}.png`,
pixel_size: [320, 180],
pixels_per_unit: 1,
@@ -362,10 +356,6 @@ describe('UiEditorPage', () => {
it('renders the overview that belongs to the active workflow stage', () => {
render(createElement(UiEditorPage, { projectPath: '/tmp/ui-editor' }));
- expect(screen.getByRole('heading', { name: '导入概览' })).toBeTruthy();
-
- fireEvent.click(screen.getByRole('button', { name: /识别界面结构/ }));
- fireEvent.click(screen.getByRole('button', { name: '仍然继续' }));
expect(screen.getByRole('heading', { name: '识别概览' })).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: /自动切分素材/ }));
@@ -391,7 +381,7 @@ describe('UiEditorPage', () => {
resourceId: 'ui-resource',
stateStore,
initialStep: 'asset-separation',
- initialFurthestStepIndex: 2,
+ initialFurthestStepIndex: 1,
}),
);
@@ -456,7 +446,7 @@ describe('UiEditorPage', () => {
resourceId: 'ui-resource',
stateStore,
initialStep: 'structure-recognition',
- initialFurthestStepIndex: 1,
+ initialFurthestStepIndex: 0,
}),
);
const button = await screen.findByRole('button', {
@@ -504,7 +494,7 @@ describe('UiEditorPage', () => {
resourceId: 'ui-resource',
stateStore,
initialStep: 'structure-recognition',
- initialFurthestStepIndex: 1,
+ initialFurthestStepIndex: 0,
}),
);
diff --git a/apps/ai-game-creator-shell/tests/uiEditorState.test.ts b/apps/ai-game-creator-shell/tests/uiEditorState.test.ts
index 1c753c019..7a117eb69 100644
--- a/apps/ai-game-creator-shell/tests/uiEditorState.test.ts
+++ b/apps/ai-game-creator-shell/tests/uiEditorState.test.ts
@@ -18,7 +18,6 @@ import {
function image(name: string): UIDesignImage {
return {
- metadata: { name, role: null, slave_to: null },
path: `assets/${name}.png`,
pixel_size: [100, 80],
pixels_per_unit: 1,
@@ -264,15 +263,11 @@ describe('useUiEditorState', () => {
{ id: 'section-a', image: image('Section A') },
]),
).toEqual({ ok: true, value: undefined });
- result.current.setImageRole('page-a', 'Page');
- result.current.setImageRole('section-a', 'Section');
- result.current.setImageSlaveTo('section-a', 'page-a');
- result.current.setImageName('section-a', '任务页');
});
- expect(result.current.state.ui_design_images['section-a']).toMatchObject({
- metadata: { name: '任务页', role: 'Section', slave_to: 'page-a' },
- });
+ expect(result.current.state.ui_design_images['section-a']).toEqual(
+ image('Section A'),
+ );
expect(result.current.state.ui_trees).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -312,33 +307,6 @@ describe('useUiEditorState', () => {
});
});
- it('rejects self-references and slave_to cycles', () => {
- const { result } = renderHook(() => useUiEditorState());
-
- act(() => {
- result.current.addDesignImages([
- { id: 'page', image: image('Page') },
- { id: 'section', image: image('Section') },
- ]);
- result.current.setImageRole('page', 'Page');
- result.current.setImageSlaveTo('section', 'page');
- });
-
- act(() => {
- expect(result.current.setImageSlaveTo('page', 'page')).toEqual({
- ok: false,
- reason: 'invalid:slave_to 不能形成循环',
- });
- expect(result.current.setImageSlaveTo('page', 'section')).toEqual({
- ok: false,
- reason: 'invalid:slave_to 不能形成循环',
- });
- });
- expect(result.current.state.ui_design_images.page?.metadata.slave_to).toBe(
- null,
- );
- });
-
it('adds a batch atomically and rejects duplicates and limits', () => {
const { result } = renderHook(() => useUiEditorState());
@@ -395,7 +363,11 @@ describe('useUiEditorState', () => {
expect(result.current.isLocked).toBe(true);
act(() => {
- expect(result.current.setImageName('a', 'Locked')).toEqual({
+ expect(
+ result.current.addDesignImages([
+ { id: 'locked', image: image('Locked') },
+ ]),
+ ).toEqual({
ok: false,
reason: 'locked',
});
@@ -424,14 +396,8 @@ describe('useUiEditorState', () => {
const initial: State = {
...structuredClone(EMPTY_UI_EDITOR_STATE),
ui_design_images: {
- page: {
- ...image('Page'),
- metadata: { name: 'Page', role: 'Page', slave_to: null },
- },
- child: {
- ...image('Child'),
- metadata: { name: 'Child', role: 'Section', slave_to: 'page' },
- },
+ page: image('Page'),
+ child: image('Child'),
},
sprite_assets: { panel: sprite('panel') },
ui_trees: [
@@ -478,7 +444,6 @@ describe('useUiEditorState', () => {
value: {
removedResourceCount: 1,
removedTreeCount: 0,
- clearedSlaveToCount: 0,
clearedTargetGraphicCount: 1,
clearedFontCount: 0,
},
@@ -493,9 +458,7 @@ describe('useUiEditorState', () => {
expect(result.current.state.sprite_assets.panel).toBeUndefined();
expect(result.current.state.ui_trees).toHaveLength(1);
expect(result.current.state.ui_design_images.page).toBeUndefined();
- expect(
- result.current.state.ui_design_images.child?.metadata.slave_to,
- ).toBeNull();
+ expect(result.current.state.ui_design_images.child).toBeDefined();
});
it('merges identical sprite and font resources idempotently and rejects identity conflicts', () => {
@@ -584,7 +547,6 @@ describe('useUiEditorState', () => {
value: {
removedResourceCount: 1,
removedTreeCount: 0,
- clearedSlaveToCount: 0,
clearedTargetGraphicCount: 0,
clearedFontCount: 1,
},
@@ -597,48 +559,15 @@ describe('useUiEditorState', () => {
});
});
- it('keeps setters local and defers workflow errors to prerequisites', () => {
- const initial: State = {
- ...structuredClone(EMPTY_UI_EDITOR_STATE),
- ui_design_images: {
- page: {
- ...image('Page'),
- metadata: { name: 'Page', role: 'Page', slave_to: null },
- },
- child: {
- ...image('Child'),
- metadata: { name: 'Child', role: 'Section', slave_to: 'page' },
- },
- },
- };
- const { result } = renderHook(() => useUiEditorState(initial));
-
- act(() => {
- result.current.setImageRole('page', null);
- });
-
- expect(result.current.state.ui_design_images.child?.metadata.slave_to).toBe(
- 'page',
- );
- expect(
- validateComponentRecognitionPrerequisites(result.current.state),
- ).toEqual([
- expect.objectContaining({
- code: 'invalid-slave-to',
- resourceId: 'child',
- }),
- ]);
- });
-
it('records, undoes, redoes, and clears redo after a new edit', () => {
const initial: State = {
...structuredClone(EMPTY_UI_EDITOR_STATE),
- ui_design_images: { page: image('Page') },
+ sprite_assets: { panel: sprite('panel') },
};
const { result } = renderHook(() => useUiEditorState(initial));
act(() => {
- result.current.setImageName('page', '第一次');
+ result.current.setSpriteName('panel', '第一次');
});
expect(result.current.historyState).toEqual({
canUndo: true,
@@ -648,8 +577,8 @@ describe('useUiEditorState', () => {
act(() => {
expect(result.current.undo()).toBe(true);
});
- expect(result.current.state.ui_design_images.page?.metadata.name).toBe(
- 'Page',
+ expect(result.current.state.sprite_assets.panel?.metadata.name).toBe(
+ 'panel',
);
expect(result.current.historyState).toEqual({
canUndo: false,
@@ -659,15 +588,15 @@ describe('useUiEditorState', () => {
act(() => {
expect(result.current.redo()).toBe(true);
});
- expect(result.current.state.ui_design_images.page?.metadata.name).toBe(
+ expect(result.current.state.sprite_assets.panel?.metadata.name).toBe(
'第一次',
);
act(() => {
- result.current.setImageName('page', '第二次');
+ result.current.setSpriteName('panel', '第二次');
expect(result.current.redo()).toBe(false);
});
- expect(result.current.state.ui_design_images.page?.metadata.name).toBe(
+ expect(result.current.state.sprite_assets.panel?.metadata.name).toBe(
'第二次',
);
});
@@ -675,17 +604,17 @@ describe('useUiEditorState', () => {
it('does not record no-op edits and resets history when replacing loaded state', () => {
const initial: State = {
...structuredClone(EMPTY_UI_EDITOR_STATE),
- ui_design_images: { page: image('Page') },
+ sprite_assets: { panel: sprite('panel') },
};
const { result } = renderHook(() => useUiEditorState(initial));
act(() => {
- result.current.setImageName('page', 'Page');
+ result.current.setSpriteName('panel', 'panel');
});
expect(result.current.historyState.canUndo).toBe(false);
act(() => {
- result.current.setImageName('page', '编辑后');
+ result.current.setSpriteName('panel', '编辑后');
result.current.replaceState(initial, { history: 'reset' });
});
expect(result.current.historyState).toEqual({
@@ -694,7 +623,7 @@ describe('useUiEditorState', () => {
});
act(() => {
- result.current.setImageName('page', '清空前');
+ result.current.setSpriteName('panel', '清空前');
result.current.clearState();
});
expect(result.current.state).toEqual(EMPTY_UI_EDITOR_STATE);
@@ -707,27 +636,27 @@ describe('useUiEditorState', () => {
it('records each replacement as an independent history entry', () => {
const initial: State = {
...structuredClone(EMPTY_UI_EDITOR_STATE),
- ui_design_images: { page: image('Page') },
+ sprite_assets: { panel: sprite('panel') },
};
const { result } = renderHook(() => useUiEditorState(initial));
act(() => {
result.current.replaceState({
...initial,
- ui_design_images: { page: image('中间') },
+ sprite_assets: { panel: sprite('中间') },
});
result.current.replaceState({
...initial,
- ui_design_images: { page: image('最终') },
+ sprite_assets: { panel: sprite('最终') },
});
});
- expect(result.current.state.ui_design_images.page?.metadata.name).toBe(
+ expect(result.current.state.sprite_assets.panel?.metadata.name).toBe(
'最终',
);
act(() => {
expect(result.current.undo()).toBe(true);
});
- expect(result.current.state.ui_design_images.page?.metadata.name).toBe(
+ expect(result.current.state.sprite_assets.panel?.metadata.name).toBe(
'中间',
);
});
@@ -735,7 +664,7 @@ describe('useUiEditorState', () => {
it('records one history entry after skipped replacement batches', () => {
const initial: State = {
...structuredClone(EMPTY_UI_EDITOR_STATE),
- ui_design_images: { page: image('Page') },
+ sprite_assets: { panel: sprite('panel') },
};
const { result } = renderHook(() => useUiEditorState(initial));
@@ -743,20 +672,20 @@ describe('useUiEditorState', () => {
result.current.replaceState(
{
...initial,
- ui_design_images: { page: image('第一批') },
+ sprite_assets: { panel: sprite('第一批') },
},
{ history: 'skip' },
);
result.current.replaceState(
{
...initial,
- ui_design_images: { page: image('最终') },
+ sprite_assets: { panel: sprite('最终') },
},
{ history: 'record' },
);
});
- expect(result.current.state.ui_design_images.page?.metadata.name).toBe(
+ expect(result.current.state.sprite_assets.panel?.metadata.name).toBe(
'最终',
);
expect(result.current.historyState).toEqual({
@@ -766,8 +695,8 @@ describe('useUiEditorState', () => {
act(() => {
expect(result.current.undo()).toBe(true);
});
- expect(result.current.state.ui_design_images.page?.metadata.name).toBe(
- 'Page',
+ expect(result.current.state.sprite_assets.panel?.metadata.name).toBe(
+ 'panel',
);
expect(result.current.historyState).toEqual({
canUndo: false,