修复 UI 编辑器字体绑定
允许文本组件绑定项目字体资源 修正字体引用计数与删除回退 补充字体绑定状态回归测试
This commit is contained in:
@@ -292,6 +292,28 @@ function asRecord(value: unknown): Record<string, unknown> | 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';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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' },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user