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({

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] ? ( {image?.metadata.name 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,