Files
Genarrative/apps/ai-game-creator-shell/src/features/ui-editor/bindingOverview.ts
T
k88936 cb96e6c6cd 新增UI编辑器绑定概览与循环定位
统计图像和文本组件的素材槽位绑定状态。
新增绑定待处理、待检查和必须修复的循环定位。
复用识别概览的前序候选查询与导航逻辑。
2026-08-18 21:10:27 +08:00

112 lines
3.1 KiB
TypeScript

import {
collectUiTreeNodeTargets,
isNeedReview,
type UiTreeNodeTarget,
} from './stageStatusOverview';
import type { Component } from './types/Component';
import type { SpriteAsset } from './types/SpriteAsset';
import type { UITree } from './types/UITree';
export type ComponentBindingCounts = {
componentsNeedingAssets: number;
assetSlots: number;
boundSlots: number;
pendingSlots: number;
};
export type BindingOverview = ComponentBindingCounts & {
needsAttention: number;
blocked: number;
independentAssets: number;
};
const EMPTY_COMPONENT_BINDING_COUNTS: ComponentBindingCounts = {
componentsNeedingAssets: 0,
assetSlots: 0,
boundSlots: 0,
pendingSlots: 0,
};
function countRequiredAssetSlot(isBound: boolean): ComponentBindingCounts {
return {
componentsNeedingAssets: 1,
assetSlots: 1,
boundSlots: isBound ? 1 : 0,
pendingSlots: isBound ? 0 : 1,
};
}
export function getImageBindingCounts(
component: Extract<Component, { Image: unknown }>['Image'],
): ComponentBindingCounts {
return countRequiredAssetSlot(component.target_graphic !== null);
}
export function getTextBindingCounts(
component: Extract<Component, { Text: unknown }>['Text'],
): ComponentBindingCounts {
return countRequiredAssetSlot(component.font !== null);
}
export function getComponentBindingCounts(
component: Component,
): ComponentBindingCounts {
if ('Image' in component) {
return getImageBindingCounts(component.Image);
}
if ('Text' in component) {
return getTextBindingCounts(component.Text);
}
return EMPTY_COMPONENT_BINDING_COUNTS;
}
function addBindingCounts(
overview: ComponentBindingCounts,
counts: ComponentBindingCounts,
) {
overview.componentsNeedingAssets += counts.componentsNeedingAssets;
overview.assetSlots += counts.assetSlots;
overview.boundSlots += counts.boundSlots;
overview.pendingSlots += counts.pendingSlots;
}
export function getBindingOverview(
uiTrees: UITree[],
spriteAssets: Record<string, SpriteAsset>,
): BindingOverview {
const overview: BindingOverview = {
...EMPTY_COMPONENT_BINDING_COUNTS,
needsAttention: 0,
blocked: 0,
independentAssets: Object.keys(spriteAssets).length,
};
for (const { node } of collectUiTreeNodeTargets(uiTrees)) {
const status = node.metadata.components_status;
if (status === 'Blocked') overview.blocked += 1;
if (status === 'Blocked' || isNeedReview(status)) {
overview.needsAttention += 1;
}
for (const component of node.components) {
addBindingCounts(overview, getComponentBindingCounts(component));
}
}
return overview;
}
export function nodeHasPendingBinding(target: UiTreeNodeTarget): boolean {
return target.node.components.some(
(component) => getComponentBindingCounts(component).pendingSlots > 0,
);
}
export function nodeNeedsComponentReview(target: UiTreeNodeTarget): boolean {
const status = target.node.metadata.components_status;
return status === 'Blocked' || isNeedReview(status);
}
export function nodeHasBlockedComponents(target: UiTreeNodeTarget): boolean {
return target.node.metadata.components_status === 'Blocked';
}