From 52f7ee5448085be3d0d84304dad24f55b59a2d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 18 Aug 2026 21:22:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20UI=20=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=E5=AD=97=E4=BD=93=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 允许文本组件绑定项目字体资源 修正字体引用计数与删除回退 补充字体绑定状态回归测试 --- .../features/ui-editor/useUiEditorState.ts | 28 +++++++++++++++++-- .../tests/uiEditorState.test.ts | 17 +++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) 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 0c151489a..e9034474a 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 @@ -292,6 +292,28 @@ function asRecord(value: unknown): Record | null { : null; } +function isBoundFontSource(value: unknown): value is { Bound: FontAssetId } { + const record = asRecord(value); + return ( + record !== null && + Object.keys(record).length === 1 && + typeof record.Bound === 'string' && + record.Bound.length > 0 && + record.Bound.trim() === record.Bound + ); +} + +function isValidFontSource(value: unknown): boolean { + return value === 'SystemFont' || isBoundFontSource(value); +} + +function isBoundToFont( + value: unknown, + id: FontAssetId, +): value is { Bound: FontAssetId } { + return isBoundFontSource(value) && value.Bound === id; +} + function isValidFillMethod(method: unknown): boolean { const record = asRecord(method); if (!record) return false; @@ -377,7 +399,7 @@ function isValidComponent(component: Component): boolean { text.font_sizing.BestFit.min <= text.font_sizing.BestFit.max); return ( typeof text.content === 'string' && - (text.font === null || typeof text.font === 'string') && + isValidFontSource(text.font) && sizingValid && colorValid && isEnumValue(text.alignment, [ @@ -443,7 +465,7 @@ export function fontAssetRemovalImpact( let clearedFontCount = 0; for (const tree of state.ui_trees) { visitComponents([tree.root], (component) => { - if ('Text' in component && component.Text.font === id) { + if ('Text' in component && isBoundToFont(component.Text.font, id)) { clearedFontCount += 1; } }); @@ -1359,7 +1381,7 @@ export function useUiEditorState(initialState: State = EMPTY_UI_EDITOR_STATE) { delete next.font_assets[id]; for (const tree of next.ui_trees) { visitComponents([tree.root], (component) => { - if ('Text' in component && component.Text.font === id) { + if ('Text' in component && isBoundToFont(component.Text.font, id)) { component.Text.font = 'SystemFont'; } }); diff --git a/apps/ai-game-creator-shell/tests/uiEditorState.test.ts b/apps/ai-game-creator-shell/tests/uiEditorState.test.ts index 73b7f1da6..8c9f3e6ea 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorState.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorState.test.ts @@ -456,7 +456,7 @@ describe('useUiEditorState', () => { ); }); - it('dry-runs and clears Text font references without deleting project files', () => { + it('accepts and clears bound Text font references without deleting project files', () => { const initial: State = { ...structuredClone(EMPTY_UI_EDITOR_STATE), font_assets: { body: font('body') }, @@ -471,7 +471,8 @@ describe('useUiEditorState', () => { { Text: { content: '你好', - font: 'body', + font: { Bound: 'body' }, + font_style: 'Normal', font_sizing: { Fixed: 14 }, color: [255, 255, 255, 255], alignment: 'UpperLeft', @@ -488,6 +489,16 @@ describe('useUiEditorState', () => { const { result } = renderHook(() => useUiEditorState(initial)); act(() => { + expect( + result.current.setNodeComponents('page', 'text-root', [ + { + Text: { + ...initial.ui_trees[0]!.root.components[0]!.Text!, + font: { Bound: 'body' }, + }, + }, + ]), + ).toEqual({ ok: true, value: undefined }); expect(result.current.removeFontAsset('body', { dryRun: true })).toEqual({ ok: true, value: { @@ -502,7 +513,7 @@ describe('useUiEditorState', () => { }); expect(result.current.state.font_assets.body).toBeUndefined(); expect(result.current.state.ui_trees[0]?.root.components[0]).toMatchObject({ - Text: { font: null }, + Text: { font: 'SystemFont' }, }); });