修正 UI 编辑器入口对 canonical ui-design 原型的判断

- packages/shared:新增 `GAME_CREATION_APP_UI_DESIGN_ASSET_KIND`(`ui-design`)并让 canonical kind 列表与分类表引用它,避免散落裸字符串。
- project-development/index.tsx:`openResourceUiEditor` 与 `selectedResourceOpensUiEditor` 的原型判断由旧值 `ui-prototype` 改为该常量,恢复原型图的「UI 编辑器」入口。
- resourceProjectionModel.ts:`isPendingUiPrototype` 同步改用该常量,恢复待视觉验收标注。
- project-development.suite.ts:候选界面图 fixture 改用 canonical kind,并补一条原型图打开 UI 编辑器桥接的回归用例(去掉修正即转红)。
This commit is contained in:
2026-09-16 23:10:31 +08:00
parent d454d3fa4b
commit f216381619
4 changed files with 91 additions and 6 deletions
@@ -81,6 +81,7 @@ import type {
ProjectResourceCanvasLayoutMode,
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
import {
GAME_CREATION_APP_UI_DESIGN_ASSET_KIND,
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
@@ -5300,7 +5301,7 @@ export default function ProjectDevelopmentView({
const openResourceUiEditor = useCallback(
(resource: ProjectResource) => {
if (resource.subtype === 'ui-prototype') {
if (resource.subtype === GAME_CREATION_APP_UI_DESIGN_ASSET_KIND) {
void openUiDesignEditor(resource);
return;
}
@@ -7471,7 +7472,7 @@ export default function ProjectDevelopmentView({
);
const selectedResourceOpensUiEditor =
selectedResource?.subtype === GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND ||
selectedResource?.subtype === 'ui-prototype';
selectedResource?.subtype === GAME_CREATION_APP_UI_DESIGN_ASSET_KIND;
const selectedToolbarStyle = selectedResourceLayer
? resolveSelectedToolbarStyle({
@@ -1,4 +1,5 @@
import {
GAME_CREATION_APP_UI_DESIGN_ASSET_KIND,
type GameCreationAppAssetCategory,
gameCreationAppAssetCategory,
gameCreationAppAssetTags,
@@ -323,7 +324,7 @@ export function projectResourcesFromReadModels(
}
const assetCategory = gameCreationAppAssetCategory(asset);
const isPendingUiPrototype =
asset.kind === 'ui-prototype' &&
asset.kind === GAME_CREATION_APP_UI_DESIGN_ASSET_KIND &&
taskById.get('design-foundation')?.status !== 'completed';
resources.push({
id: `asset:${asset.id}`,
@@ -6,6 +6,7 @@ import type {
ProjectResourceCanvasPosition,
} from '../../../../packages/shared/src/contracts/gameCreationApp';
import {
GAME_CREATION_APP_UI_DESIGN_ASSET_KIND,
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
} from '../../../../packages/shared/src/contracts/gameCreationApp';
@@ -6463,7 +6464,7 @@ export function registerProjectWorkbenchFoundationTests() {
);
manifest.assets.push({
id: 'ui-prototype-candidate',
kind: 'ui-prototype',
kind: GAME_CREATION_APP_UI_DESIGN_ASSET_KIND,
mediaType: 'image/png',
localPath: 'assets/ui-prototype.png',
source: {
@@ -6513,6 +6514,85 @@ export function registerProjectWorkbenchFoundationTests() {
).not.toBeNull();
});
it('opens the UI editor bridge from a canonical ui-design prototype image', async () => {
installResizeObserverStub();
const manifest = createGameCreationAppManifest(
'workbench-ui-prototype-entry',
'UI 原型入口测试',
);
manifest.assets = [
{
id: 'ui-prototype-asset',
kind: GAME_CREATION_APP_UI_DESIGN_ASSET_KIND,
mediaType: 'image/png',
localPath: 'assets/ui-prototype.png',
source: { kind: 'canvas', taskId: 'design-foundation' },
},
];
const invoke = vi.fn(
async (command: string, args?: Record<string, unknown>) => {
if (command === 'read_local_project_resource_graph') {
return resourceGraphForInputs(args);
}
if (command === 'read_local_project_resource_canvas_layout') {
return {
schemaVersion: 'game-creator-resource-layout.v1',
projectId: manifest.projectId,
mode: args?.mode,
revision: 0,
positions: [],
updatedAt: 0,
};
}
if (command === 'update_local_project_resource_canvas_layout') {
return {
status: 'updated',
layout: {
schemaVersion: 'game-creator-resource-layout.v1',
projectId: manifest.projectId,
mode: args?.mode,
revision: 1,
positions: args?.positions,
updatedAt: 1,
},
};
}
if (command === 'ensure_ui_design_resource_for_prototype') {
return null;
}
throw new Error(`unexpected invoke ${command}`);
},
);
window.__TAURI__ = { core: { invoke } };
render(
React.createElement(ProjectDevelopmentView, {
projectName: manifest.name,
projectPath: '/tmp/workbench-ui-prototype-entry',
manifest,
attachments: [],
recentRunStatus: null,
recentRunStopReason: null,
supervisor: React.createElement('div', null, '项目总控'),
onHomeOpen: vi.fn(),
onProjectsOpen: vi.fn(),
}),
);
await openResourceBookCategory('UI 交互');
fireEvent.click(
await findResourceSelectButton('ui-prototype.png(待视觉验收)'),
);
fireEvent.click(await screen.findByRole('button', { name: 'UI 编辑器' }));
expect(invoke).toHaveBeenCalledWith(
'ensure_ui_design_resource_for_prototype',
expect.objectContaining({
input: expect.objectContaining({
prototypeAssetId: 'ui-prototype-asset',
}),
}),
);
});
it('refuses to embed a non-loopback game preview in the client workbench', () => {
const manifest = createGameCreationAppManifest(
'workbench-remote-preview',
@@ -480,6 +480,9 @@ export interface GameCreationAppAssetManifestEntry {
tags?: string[];
}
/** UI 设计图(原型图与页面设计图)资产的唯一 kind。 */
export const GAME_CREATION_APP_UI_DESIGN_ASSET_KIND = 'ui-design' as const;
/** UI Editor JSON 文档资产的唯一 kind 与媒体类型。 */
export const GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND =
'ui-design-doc' as const;
@@ -494,7 +497,7 @@ export const GAME_CREATION_APP_CANONICAL_ASSET_KINDS = [
'icon',
'icon-spritesheet',
'icon-spec',
'ui-design',
GAME_CREATION_APP_UI_DESIGN_ASSET_KIND,
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
'publication-material',
'spec',
@@ -595,7 +598,7 @@ export const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: Record<
icon: 'ui-interaction',
'icon-spritesheet': 'ui-interaction',
'icon-spec': 'ui-interaction',
'ui-design': 'ui-interaction',
[GAME_CREATION_APP_UI_DESIGN_ASSET_KIND]: 'ui-interaction',
[GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND]: 'ui-interaction',
'publication-material': 'unclassified',
spec: 'document',