统一引用 UI 文档资产类型常量
前端入口、资源桥接与引用预览统一使用共享常量 Rust 持久化与测试复用共享契约常量 补齐 UI 编辑器入口 fixture 的新资产类型
This commit is contained in:
@@ -2031,8 +2031,8 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() {
|
||||
let registered = register_local_asset_at(
|
||||
&root,
|
||||
"ui/UI 设计 1.json",
|
||||
"UI",
|
||||
"application/json",
|
||||
shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
"ui-workflow",
|
||||
source(),
|
||||
)
|
||||
@@ -2053,8 +2053,8 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() {
|
||||
register_local_asset_at(
|
||||
&root,
|
||||
"ui/UI 设计 1.json",
|
||||
"UI",
|
||||
"application/json",
|
||||
shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
"ui-workflow",
|
||||
source(),
|
||||
)
|
||||
@@ -2063,7 +2063,10 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() {
|
||||
let manifest: Value =
|
||||
serde_json::from_str(&fs::read_to_string(root.join(".agent/manifest.json")).unwrap())
|
||||
.expect("manifest json");
|
||||
assert_eq!(manifest["assets"][0]["kind"], "UI");
|
||||
assert_eq!(
|
||||
manifest["assets"][0]["kind"],
|
||||
shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND
|
||||
);
|
||||
assert_eq!(manifest["assets"][0]["category"], "audio");
|
||||
assert_eq!(manifest["assets"][0]["tags"], serde_json::json!(["界面"]));
|
||||
|
||||
@@ -2074,7 +2077,7 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() {
|
||||
///
|
||||
/// 这里的字面量与写入侧逐字一致:UI 设计资产是
|
||||
/// `ui_editor/resource_bridge.rs` / `workflow.rs` / `persistence.rs` 的
|
||||
/// `register_local_asset_at(root, path, "UI", "application/json", ...)`,
|
||||
/// `register_local_asset_at` 使用 UI 文档 kind/media 常量,
|
||||
/// 字体是 `commands.rs` 字体上传的 `register_local_asset_entry(root, path, "font", ...)`。
|
||||
/// 只要别名表漏掉它们,真机资产就会永远停在「待归类」且读时自愈也救不回来。
|
||||
#[test]
|
||||
@@ -2099,8 +2102,8 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() {
|
||||
register_local_asset_at(
|
||||
&root,
|
||||
"ui/UI 设计 1.json",
|
||||
"UI",
|
||||
"application/json",
|
||||
shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
"ui-workflow",
|
||||
source(),
|
||||
)
|
||||
@@ -2133,7 +2136,11 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() {
|
||||
assert_eq!(
|
||||
categories,
|
||||
vec![
|
||||
("UI".to_string(), "ui-interaction".to_string()),
|
||||
(
|
||||
shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND
|
||||
.to_string(),
|
||||
"ui-interaction".to_string(),
|
||||
),
|
||||
("font".to_string(), "document".to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -19,8 +19,10 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use typed_floats::tf32::StrictlyPositiveFinite;
|
||||
|
||||
const UI_DESIGN_STATE_SCHEMA_VERSION: &str = "game-creator-ui-design-state.v1";
|
||||
pub(crate) const UI_DESIGN_DOC_ASSET_KIND: &str = "ui-design-doc";
|
||||
pub(crate) const UI_DESIGN_DOC_MEDIA_TYPE: &str = "application/json";
|
||||
pub(crate) use shared_contracts::game_creation_app::{
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND as UI_DESIGN_DOC_ASSET_KIND,
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE as UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
};
|
||||
const UI_DESIGN_STATE_MAX_BYTES: usize = 2 * 1024 * 1024;
|
||||
const UI_DESIGN_CODE_MAX_BYTES: usize = UI_DESIGN_STATE_MAX_BYTES * 8;
|
||||
const UI_DESIGN_STATE_MAX_IMAGES: usize = 4;
|
||||
|
||||
+5
-1
@@ -53,6 +53,7 @@ import { createPortal, flushSync } from 'react-dom';
|
||||
import { PlatformResourceFilterBar } from '../../../../../packages/shared/src/components/PlatformResourceFilterBar';
|
||||
import { PlatformSegmentedTabs } from '../../../../../packages/shared/src/components/PlatformSegmentedTabs';
|
||||
import {
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
type GameCreationAppAssetManifestEntry,
|
||||
gameCreationAppAssetTags,
|
||||
type GameIterationVersion,
|
||||
@@ -1253,7 +1254,10 @@ function ResourcePickerThumbnail({
|
||||
if (mediaType.startsWith('image/')) {
|
||||
return <Loader2 size={18} className="animate-spin" aria-hidden="true" />;
|
||||
}
|
||||
if (kind === 'ui' || mediaType === 'application/json') {
|
||||
if (
|
||||
kind === 'ui' ||
|
||||
mediaType === GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE
|
||||
) {
|
||||
return <FileText size={18} aria-hidden="true" />;
|
||||
}
|
||||
return <ImageIcon size={18} aria-hidden="true" />;
|
||||
|
||||
@@ -2,6 +2,10 @@ import type {
|
||||
GameCreationAppAssetManifestEntry,
|
||||
GameCreationAppManifest,
|
||||
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import {
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
|
||||
export type UiDesignResourceBridgeResult = {
|
||||
asset: GameCreationAppAssetManifestEntry;
|
||||
@@ -48,8 +52,8 @@ export function findLinkedUiDesignResource(
|
||||
manifest.assets
|
||||
.filter(
|
||||
(asset) =>
|
||||
asset.kind === 'ui-design-doc' &&
|
||||
asset.mediaType === 'application/json' &&
|
||||
asset.kind === GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND &&
|
||||
asset.mediaType === GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE &&
|
||||
asset.source.referenceResourceIds?.some((reference) =>
|
||||
referenceIds.has(reference),
|
||||
),
|
||||
|
||||
@@ -73,6 +73,10 @@ import type {
|
||||
GameIterationVersion,
|
||||
ProjectResourceCanvasLayoutMode,
|
||||
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import {
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import { ImageCanvasCharacterAnimationPanelView } from '../../../../../src/components/image-editor/ImageCanvasCharacterAnimationPanelView';
|
||||
import type {
|
||||
CanvasLayer,
|
||||
@@ -5040,8 +5044,8 @@ export default function ProjectDevelopmentView({
|
||||
if (canvasOpenEpochRef.current !== openEpoch) return;
|
||||
if (
|
||||
result.manifest.projectId !== manifest.projectId ||
|
||||
result.asset.kind !== 'ui-design-doc' ||
|
||||
result.asset.mediaType !== 'application/json'
|
||||
result.asset.kind !== GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND ||
|
||||
result.asset.mediaType !== GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE
|
||||
) {
|
||||
throw new Error('UI 编辑资源结果与当前项目不一致');
|
||||
}
|
||||
@@ -5112,8 +5116,8 @@ export default function ProjectDevelopmentView({
|
||||
if (uiEditorRoute) return;
|
||||
const completed = manifest.assets.find(
|
||||
(asset) =>
|
||||
asset.kind === 'ui-design-doc' &&
|
||||
asset.mediaType === 'application/json' &&
|
||||
asset.kind === GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND &&
|
||||
asset.mediaType === GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE &&
|
||||
asset.source.generationKind === 'ui-workflow.completed',
|
||||
);
|
||||
if (
|
||||
@@ -6812,7 +6816,7 @@ export default function ProjectDevelopmentView({
|
||||
[manifest, selectedResource],
|
||||
);
|
||||
const selectedResourceOpensUiEditor =
|
||||
selectedResource?.subtype === 'ui-design-doc' ||
|
||||
selectedResource?.subtype === GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND ||
|
||||
selectedResource?.subtype === 'ui-prototype';
|
||||
|
||||
const selectedToolbarStyle = selectedResourceLayer
|
||||
|
||||
@@ -4,6 +4,10 @@ import type {
|
||||
ProjectResourceCanvasLayout,
|
||||
ProjectResourceCanvasPosition,
|
||||
} from '../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import {
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
} from '../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import { RESOURCE_REFERENCE_INSERT_EVENT } from '../../src/features/project-workspace/resourceReferences';
|
||||
import { RESOURCE_BOOK_OVERVIEW_STACK_LIMIT } from '../../src/view/project-development/resourceBookLayout';
|
||||
import {
|
||||
@@ -5908,8 +5912,8 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
manifest.assets = [
|
||||
{
|
||||
id: 'ui-design-resource',
|
||||
kind: 'ui-design-doc',
|
||||
mediaType: 'application/json',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
localPath: 'assets/ui-design.json',
|
||||
source: { kind: 'generated' },
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
canonicalGameCreationAppAssetKind,
|
||||
GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND,
|
||||
GAME_CREATION_APP_CANONICAL_ASSET_KINDS,
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
type GameCreationAppAssetCategory,
|
||||
gameCreationAppAssetCategory,
|
||||
gameCreationAppAssetCategoryForKind,
|
||||
@@ -222,8 +223,8 @@ describe('真机出现的 kind 归类口径', () => {
|
||||
// UI 文档与字体 kind,两者都必须落进明确栏目。
|
||||
{ kind: 'UI', canonical: 'ui-design', category: 'ui-interaction' },
|
||||
{
|
||||
kind: 'ui-design-doc',
|
||||
canonical: 'ui-design-doc',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
canonical: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
category: 'ui-interaction',
|
||||
},
|
||||
{ kind: 'font', canonical: 'document', category: 'document' },
|
||||
@@ -286,9 +287,11 @@ describe('写侧 kind 字面量 → 分类的端到端口径', () => {
|
||||
for (const file of UI_EDITOR_WRITE_SITES) {
|
||||
expect(readFileSync(file, 'utf8')).toContain('UI_DESIGN_DOC_ASSET_KIND');
|
||||
}
|
||||
expect(gameCreationAppAssetCategoryForKind('ui-design-doc')).toBe(
|
||||
'ui-interaction',
|
||||
);
|
||||
expect(
|
||||
gameCreationAppAssetCategoryForKind(
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
),
|
||||
).toBe('ui-interaction');
|
||||
});
|
||||
|
||||
test('UI 编辑器写点写出的每个 kind 都落明确栏目', () => {
|
||||
|
||||
@@ -4,6 +4,10 @@ import type {
|
||||
GameCreationAppAssetManifestEntry,
|
||||
GameCreationAppManifest,
|
||||
} from '../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import {
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
} from '../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import {
|
||||
ensureUiDesignResourceForPrototype,
|
||||
findLinkedUiDesignResource,
|
||||
@@ -37,8 +41,8 @@ describe('ui design resource bridge', () => {
|
||||
const manifest = manifestWithPrototype();
|
||||
manifest.assets.push({
|
||||
id: 'unrelated-ui',
|
||||
kind: 'UI',
|
||||
mediaType: 'application/json',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
localPath: 'ui/unrelated.json',
|
||||
source: { kind: 'generated', referenceResourceIds: ['other'] },
|
||||
imageSequenceFrames: null,
|
||||
@@ -47,8 +51,8 @@ describe('ui design resource bridge', () => {
|
||||
expect(findLinkedUiDesignResource(manifest, 'prototype-asset')).toBeNull();
|
||||
manifest.assets.push({
|
||||
id: 'linked-ui',
|
||||
kind: 'UI',
|
||||
mediaType: 'application/json',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
localPath: 'ui/linked.json',
|
||||
source: { kind: 'generated', referenceResourceIds: ['prototype-asset'] },
|
||||
imageSequenceFrames: null,
|
||||
@@ -64,8 +68,8 @@ describe('ui design resource bridge', () => {
|
||||
manifest.assets[0]!.source.resourceId = 'canvas-resource-1';
|
||||
manifest.assets.push({
|
||||
id: 'workflow-ui',
|
||||
kind: 'UI',
|
||||
mediaType: 'application/json',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
localPath: 'ui/workflow.json',
|
||||
source: {
|
||||
kind: 'generated',
|
||||
@@ -85,8 +89,8 @@ describe('ui design resource bridge', () => {
|
||||
manifest.assets.push(
|
||||
{
|
||||
id: 'bridge-ui',
|
||||
kind: 'UI',
|
||||
mediaType: 'application/json',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
localPath: 'ui/UI 设计 1.json',
|
||||
source: {
|
||||
kind: 'generated',
|
||||
@@ -97,8 +101,8 @@ describe('ui design resource bridge', () => {
|
||||
},
|
||||
{
|
||||
id: 'completed-ui',
|
||||
kind: 'UI',
|
||||
mediaType: 'application/json',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
localPath: 'ui/ui-workflow-page.json',
|
||||
source: {
|
||||
kind: 'generated',
|
||||
@@ -118,8 +122,8 @@ describe('ui design resource bridge', () => {
|
||||
const manifest = manifestWithPrototype();
|
||||
const linked = {
|
||||
id: 'linked-ui',
|
||||
kind: 'UI',
|
||||
mediaType: 'application/json',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
localPath: 'ui/linked.json',
|
||||
source: {
|
||||
kind: 'generated' as const,
|
||||
@@ -146,8 +150,8 @@ describe('ui design resource bridge', () => {
|
||||
const result = {
|
||||
asset: {
|
||||
id: 'new-ui',
|
||||
kind: 'UI',
|
||||
mediaType: 'application/json',
|
||||
kind: GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
mediaType: GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
localPath: 'ui/UI 设计 1.json',
|
||||
source: {
|
||||
kind: 'generated' as const,
|
||||
|
||||
@@ -549,7 +549,7 @@ const GAME_CREATION_APP_LEGACY_ASSET_KINDS: Record<
|
||||
export function canonicalGameCreationAppAssetKind(
|
||||
value: string,
|
||||
): GameCreationAppCanonicalAssetKind {
|
||||
/** kind 词汇大小写不敏感;UI Editor 文档写入侧统一使用 `ui-design-doc`。 */
|
||||
/** kind 词汇大小写不敏感;UI Editor 文档写入侧使用 UI 文档常量。 */
|
||||
const normalized = value.trim().toLowerCase();
|
||||
/**
|
||||
* `kind` 是外部输入,可能正好是 `Object.prototype` 上的键(`constructor` / `__proto__` /
|
||||
@@ -596,7 +596,7 @@ export const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: Record<
|
||||
'icon-spritesheet': 'ui-interaction',
|
||||
'icon-spec': 'ui-interaction',
|
||||
'ui-design': 'ui-interaction',
|
||||
'ui-design-doc': 'ui-interaction',
|
||||
[GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND]: 'ui-interaction',
|
||||
'publication-material': 'unclassified',
|
||||
spec: 'document',
|
||||
video: 'unclassified',
|
||||
|
||||
@@ -5,6 +5,8 @@ pub const GAME_CREATION_APP_MANIFEST_SCHEMA_VERSION: &str = "game-creation-app.m
|
||||
pub const GAME_CREATION_AGENT_RUN_SCHEMA_VERSION: &str = "game-creator-agent-run.v1";
|
||||
pub const GAME_CREATION_AGENT_RUN_MAX_PASSES: u8 = 3;
|
||||
pub const GAME_CREATION_AGENT_TOOL_CALL_MAX: u16 = 128;
|
||||
pub const GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND: &str = "ui-design-doc";
|
||||
pub const GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE: &str = "application/json";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -574,7 +576,7 @@ pub const GAME_CREATION_APP_CANONICAL_ASSET_KINDS: [&str; 17] = [
|
||||
"icon-spritesheet",
|
||||
"icon-spec",
|
||||
"ui-design",
|
||||
"ui-design-doc",
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
"publication-material",
|
||||
"spec",
|
||||
"video",
|
||||
@@ -652,7 +654,10 @@ pub const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: [(&str, GameCreationAppAsset
|
||||
),
|
||||
("icon-spec", GameCreationAppAssetCategory::UiInteraction),
|
||||
("ui-design", GameCreationAppAssetCategory::UiInteraction),
|
||||
("ui-design-doc", GameCreationAppAssetCategory::UiInteraction),
|
||||
(
|
||||
GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND,
|
||||
GameCreationAppAssetCategory::UiInteraction,
|
||||
),
|
||||
(
|
||||
"publication-material",
|
||||
GameCreationAppAssetCategory::Unclassified,
|
||||
|
||||
Reference in New Issue
Block a user