From 34511244c88557c784683995e918cd0e7024f4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 19 Aug 2026 15:02:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=B1=E5=8C=96=20UI=20=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将载入、revision、选择、预览、步骤门禁、异步操作和保存意图收敛到 UI 编辑会话 按输入、画布、检查器、工作流、弹窗和保存用途拆分视图 projection 补充 UI 编辑会话模块边界文档 --- .../ui-editor/components/EditorDialogs.tsx | 38 +- .../ui-editor/components/InputSidebar.tsx | 77 ++-- .../components/Inspector/InspectorSidebar.tsx | 174 ++++---- .../components/WorkflowActionCard.tsx | 57 ++- .../components/preview/PreviewWorkspace.tsx | 60 ++- .../preview/useNodeTransformInteraction.ts | 34 +- .../src/view/ui-editor/index.tsx | 150 ++----- .../src/view/ui-editor/useUiEditorPage.ts | 388 ++++++++++++------ ...�前端架构】UI编辑会话模块边界-2026-08-19.md | 16 + 9 files changed, 528 insertions(+), 466 deletions(-) create mode 100644 docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md 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 4c5c27d91..a4d637ecb 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 @@ -5,36 +5,36 @@ import { SPRITE_IMPORTER_SETTINGS, } from '../../../components/AssetImporter/settings'; import { ThemedModal } from '../../../components/modal/ThemedModal'; -import type { UiEditorPageController } from '../useUiEditorPage'; +import type { UiEditorDialogsProjection } from '../useUiEditorPage'; export function EditorDialogs({ - controller, + dialogs, }: { - controller: UiEditorPageController; + dialogs: UiEditorDialogsProjection; }) { - const impact = controller.pendingRemoval?.impact; + const impact = dialogs.pendingRemoval?.impact; const importerSettings = - controller.importKind === 'font' + dialogs.importKind === 'font' ? FONT_IMPORTER_SETTINGS - : controller.importKind === 'design-image' + : dialogs.importKind === 'design-image' ? DESIGN_IMAGE_IMPORTER_SETTINGS : SPRITE_IMPORTER_SETTINGS; return ( <> { - void controller.importAssets(assets); - controller.closeImporter(); + void dialogs.importAssets(assets); + dialogs.closeImporter(); }} settings={importerSettings} /> @@ -47,14 +47,14 @@ export function EditorDialogs({ @@ -62,8 +62,8 @@ export function EditorDialogs({ @@ -82,14 +82,14 @@ export function EditorDialogs({ 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 fd622d8d0..5d2b134f1 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 @@ -5,7 +5,7 @@ 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 type { UiEditorPageController } from '../useUiEditorPage'; +import type { UiEditorInputProjection } from '../useUiEditorPage'; import { BindingOverview } from './BindingOverview'; import { ImportOverview } from './ImportOverview'; import { RecognitionOverview } from './RecognitionOverview'; @@ -13,14 +13,9 @@ import { UiTreePanel } from './UiTreePanel'; const SUPER_ROOT_ID = '__ui-editor-super-root__'; -export function InputSidebar({ - controller, -}: { - controller: UiEditorPageController; -}) { +export function InputSidebar({ input }: { input: UiEditorInputProjection }) { const { projectPath, - editor, activeStep, imageOrder, activeImageId, @@ -34,20 +29,22 @@ export function InputSidebar({ spriteReferenceCounts, selectedNodeId, focusRequest, - } = controller; + uiTrees, + isLocked, + } = input; const treeIdByNodeId = useMemo(() => { const result = new Map(); - for (const tree of editor.state.ui_trees) { + for (const tree of uiTrees) { visitUiNodes(tree.root, (node) => result.set(node.id, tree.src_ui_design), ); } return result; - }, [editor.state.ui_trees]); + }, [uiTrees]); const superRoot = useMemo(() => { - if (editor.state.ui_trees.length === 0) return null; + if (uiTrees.length === 0) return null; return { id: SUPER_ROOT_ID, layout: { @@ -74,9 +71,9 @@ export function InputSidebar({ }, components: [], children_display_mode: 'Stack', - children: editor.state.ui_trees.map((tree) => tree.root), + children: uiTrees.map((tree) => tree.root), }; - }, [editor.state.ui_trees]); + }, [uiTrees]); return ( ); 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 fea40e456..7d7a811d5 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 @@ -13,7 +13,7 @@ import type { UIDesignImageId } from '../../../../features/ui-editor/types/UIDes import type { UIDesignImageRole } from '../../../../features/ui-editor/types/UIDesignImageRole'; import { uiEditorPrivateFontFamily } from '../../../../features/ui-editor/useUiEditorFontFaces'; import { UI_DESIGN_IMAGE_ROLES } from '../../model'; -import type { UiEditorPageController } from '../../useUiEditorPage'; +import type { UiEditorInspectorProjection } from '../../useUiEditorPage'; import { ComponentPanel } from './Components/ComponentPanel'; import { InspectorReadOnlyContext, @@ -22,19 +22,25 @@ import { import { SpriteBorderEditor } from './SpriteBorder/SpriteBorderEditor'; import TransformEditor from './Transform/TransformEditor'; -type SelectedNode = NonNullable; -type SelectedSprite = NonNullable; -type SelectedFont = NonNullable; -type ActiveImage = NonNullable; -type SelectedSpriteId = NonNullable; -type SelectedFontId = NonNullable; -type ActiveImageId = NonNullable; +type SelectedNode = NonNullable; +type SelectedSprite = NonNullable< + UiEditorInspectorProjection['selectedSprite'] +>; +type SelectedFont = NonNullable; +type ActiveImage = NonNullable; +type SelectedSpriteId = NonNullable< + UiEditorInspectorProjection['selectedSpriteId'] +>; +type SelectedFontId = NonNullable< + UiEditorInspectorProjection['selectedFontId'] +>; +type ActiveImageId = NonNullable; type InspectorView = | { kind: 'node'; node: SelectedNode; - parentSize: UiEditorPageController['selectedNodeParentSize']; + parentSize: UiEditorInspectorProjection['selectedNodeParentSize']; parentIsContainer: boolean; } | { @@ -55,7 +61,7 @@ type InspectorView = kind: 'image'; image: ActiveImage; imageId: ActiveImageId; - pageOptions: UiEditorPageController['pageOptions']; + pageOptions: UiEditorInspectorProjection['pageOptions']; } | { kind: 'empty' }; @@ -73,11 +79,11 @@ const INSPECTOR_INPUT_CLASS_NAME = `${INSPECTOR_CONTROL_BASE_CLASS_NAME} h-9 px- const INSPECTOR_TEXTAREA_CLASS_NAME = `${INSPECTOR_CONTROL_BASE_CLASS_NAME} min-h-20 resize-y px-2 py-2 leading-5`; export function InspectorSidebar({ - controller, + inspector, }: { - controller: UiEditorPageController; + inspector: UiEditorInspectorProjection; }) { - const view = getInspectorView(controller); + const view = getInspectorView(inspector); let inspectorContent: ReactNode; switch (view.kind) { case 'node': @@ -86,34 +92,32 @@ export function InspectorSidebar({ node={view.node} parentSize={view.parentSize} parentIsContainer={view.parentIsContainer} - readOnly={controller.editor.isLocked} - isNodePreviewVisible={controller.isNodePreviewVisible(view.node.id)} + readOnly={inspector.isLocked} + isNodePreviewVisible={inspector.isNodePreviewVisible(view.node.id)} onSetNodePreviewVisible={(visible) => - controller.setNodePreviewVisible(view.node.id, visible) + inspector.setNodePreviewVisible(view.node.id, visible) } - onChildrenDisplayModeChange={controller.setNodeChildrenDisplayMode} + onChildrenDisplayModeChange={inspector.setNodeChildrenDisplayMode} transformReadOnly={ - controller.editor.isLocked || - view.node.id === controller.treeForActiveImage?.root.id + inspector.isLocked || view.node.id === inspector.tree?.root.id } - keepChildrenUnchanged={controller.keepChildrenUnchanged} - onKeepChildrenUnchangedChange={controller.setKeepChildrenUnchanged} - onMetadataChange={controller.setNodeMetadata} - highlightedStatusField={controller.highlightedStatusField} - onTransformChange={controller.setNodeTransform} - onLayoutChange={controller.setNodeLayout} - sprites={controller.sprites} - fonts={controller.editor.state.font_assets} - fontFaces={controller.fontFaces} - projectPath={controller.projectPath} - onSetComponents={controller.setNodeComponents} - onInsertComponent={controller.insertNodeComponent} - onDeleteComponent={controller.deleteNodeComponent} - onMoveComponent={controller.moveNodeComponent} - onDeleteNode={() => controller.deleteNode(view.node.id)} + keepChildrenUnchanged={inspector.keepChildrenUnchanged} + onKeepChildrenUnchangedChange={inspector.setKeepChildrenUnchanged} + onMetadataChange={inspector.setNodeMetadata} + highlightedStatusField={inspector.highlightedStatusField} + onTransformChange={inspector.setNodeTransform} + onLayoutChange={inspector.setNodeLayout} + sprites={inspector.sprites} + fonts={inspector.fonts} + fontFaces={inspector.fontFaces} + projectPath={inspector.projectPath} + onSetComponents={inspector.setNodeComponents} + onInsertComponent={inspector.insertNodeComponent} + onDeleteComponent={inspector.deleteNodeComponent} + onMoveComponent={inspector.moveNodeComponent} + onDeleteNode={() => inspector.deleteNode(view.node.id)} deleteDisabled={ - controller.editor.isLocked || - view.node.id === controller.treeForActiveImage?.root.id + inspector.isLocked || view.node.id === inspector.tree?.root.id } /> ); @@ -125,11 +129,11 @@ export function InspectorSidebar({ spriteId={view.spriteId} previewUrl={view.previewUrl} referenceCount={view.referenceCount} - onNameChange={controller.setSpriteName} - onAssetTypeChange={controller.setSpriteAssetType} - onBorderChange={controller.setSpriteBorder} - onDelete={() => controller.requestSpriteRemoval(view.spriteId)} - deleteDisabled={controller.editor.isLocked} + onNameChange={inspector.setSpriteName} + onAssetTypeChange={inspector.setSpriteAssetType} + onBorderChange={inspector.setSpriteBorder} + onDelete={() => inspector.requestSpriteRemoval(view.spriteId)} + deleteDisabled={inspector.isLocked} /> ); break; @@ -140,8 +144,8 @@ export function InspectorSidebar({ fontId={view.fontId} referenceCount={view.referenceCount} loadStatus={view.loadStatus} - onDelete={() => controller.requestFontRemoval(view.fontId)} - deleteDisabled={controller.editor.isLocked} + onDelete={() => inspector.requestFontRemoval(view.fontId)} + deleteDisabled={inspector.isLocked} /> ); break; @@ -151,12 +155,12 @@ export function InspectorSidebar({ image={view.image} imageId={view.imageId} pageOptions={view.pageOptions} - onNameChange={controller.setImageName} - onDescriptionChange={controller.setImageDescription} - onRoleChange={controller.setImageRole} - onSlaveToChange={controller.setImageSlaveTo} - onDelete={() => controller.requestDesignImageRemoval(view.imageId)} - deleteDisabled={controller.editor.isLocked} + onNameChange={inspector.setImageName} + onDescriptionChange={inspector.setImageDescription} + onRoleChange={inspector.setImageRole} + onSlaveToChange={inspector.setImageSlaveTo} + onDelete={() => inspector.requestDesignImageRemoval(view.imageId)} + deleteDisabled={inspector.isLocked} /> ); break; @@ -170,11 +174,11 @@ export function InspectorSidebar({ } return ( - +