优化 UI 编辑器前置条件检查和资源预览管理:
- 更新 UI 树遍历逻辑,改用 `root` 替代 `children`。 - 添加资源预览作用域和请求 ID,防止并发冲突。 - 调整资源导入模块以支持作用域级预览管理。 - 优化界面设计建议合并逻辑,新增节点继承规则。
This commit is contained in:
@@ -11,6 +11,11 @@ import {
|
||||
readClientAssetBytes,
|
||||
resolveClientAssetReadUrl,
|
||||
} from '../../services/clientApi';
|
||||
import {
|
||||
cancelLocalProjectResourcePreviewScope,
|
||||
createProjectResourcePreviewRequestId,
|
||||
createProjectResourcePreviewScopeId,
|
||||
} from '../../services/projectResourcePreviewTransport';
|
||||
import { ThemedModal } from '../modal/ThemedModal';
|
||||
import {
|
||||
buildProjectFiles,
|
||||
@@ -71,6 +76,16 @@ export function AssetImporter({
|
||||
const [currentPath, setCurrentPath] = useState(ROOT_PATH);
|
||||
const currentPathRef = useRef(ROOT_PATH);
|
||||
const remoteLoadedRef = useRef(false);
|
||||
const previewScopeIdRef = useRef(createProjectResourcePreviewScopeId());
|
||||
|
||||
useEffect(() => {
|
||||
const previousScopeId = previewScopeIdRef.current;
|
||||
cancelLocalProjectResourcePreviewScope(previousScopeId);
|
||||
previewScopeIdRef.current = createProjectResourcePreviewScopeId();
|
||||
return () => {
|
||||
cancelLocalProjectResourcePreviewScope(previewScopeIdRef.current);
|
||||
};
|
||||
}, [projectPath]);
|
||||
|
||||
const loadLocal = useCallback(async () => {
|
||||
setError(null);
|
||||
@@ -213,6 +228,8 @@ export function AssetImporter({
|
||||
void invoke<ImagePreviewResponse>('read_local_project_image_preview', {
|
||||
projectPath,
|
||||
relativePath: item.asset?.localPath,
|
||||
scopeId: previewScopeIdRef.current,
|
||||
requestId: createProjectResourcePreviewRequestId(),
|
||||
})
|
||||
.then((value) => setPreview(value.dataUrl))
|
||||
.catch(() => setPreview(null));
|
||||
@@ -226,7 +243,9 @@ export function AssetImporter({
|
||||
title: '选择图片素材',
|
||||
multiple: true,
|
||||
directory: false,
|
||||
filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'webp'] }],
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'webp'] },
|
||||
],
|
||||
});
|
||||
const sourcePaths = Array.isArray(paths) ? paths : paths ? [paths] : [];
|
||||
if (!sourcePaths.length) return;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
import type { ImportedAsset } from '../../components/AssetImporter';
|
||||
import {
|
||||
createProjectResourcePreviewRequestId,
|
||||
createProjectResourcePreviewScopeId,
|
||||
} from '../../services/projectResourcePreviewTransport';
|
||||
import type { SpriteAsset } from './types/SpriteAsset';
|
||||
import type { UIDesignImage } from './types/UIDesignImage';
|
||||
import type { UIDesignImageId } from './types/UIDesignImageId';
|
||||
@@ -35,10 +39,16 @@ export function decodeImageSize(src: string): Promise<[number, number]> {
|
||||
async function readImportedImage(
|
||||
projectPath: string,
|
||||
asset: ImportedAsset,
|
||||
scopeId: string,
|
||||
) {
|
||||
const preview = await invoke<ImagePreviewResponse>(
|
||||
'read_local_project_image_preview',
|
||||
{ projectPath, relativePath: asset.localPath },
|
||||
{
|
||||
projectPath,
|
||||
relativePath: asset.localPath,
|
||||
scopeId,
|
||||
requestId: createProjectResourcePreviewRequestId(),
|
||||
},
|
||||
);
|
||||
return {
|
||||
previewUrl: preview.dataUrl,
|
||||
@@ -50,11 +60,13 @@ export async function prepareDesignImageBatch(
|
||||
projectPath: string,
|
||||
assets: readonly ImportedAsset[],
|
||||
): Promise<Array<PreparedImageAsset<DesignImageInput>>> {
|
||||
const scopeId = createProjectResourcePreviewScopeId();
|
||||
return Promise.all(
|
||||
assets.map(async (asset) => {
|
||||
const { previewUrl, pixelSize } = await readImportedImage(
|
||||
projectPath,
|
||||
asset,
|
||||
scopeId,
|
||||
);
|
||||
const image: UIDesignImage = {
|
||||
metadata: {
|
||||
@@ -79,11 +91,13 @@ export async function prepareSpriteAssetBatch(
|
||||
projectPath: string,
|
||||
assets: readonly ImportedAsset[],
|
||||
): Promise<Array<PreparedImageAsset<SpriteAsset>>> {
|
||||
const scopeId = createProjectResourcePreviewScopeId();
|
||||
return Promise.all(
|
||||
assets.map(async (asset) => {
|
||||
const { previewUrl, pixelSize } = await readImportedImage(
|
||||
projectPath,
|
||||
asset,
|
||||
scopeId,
|
||||
);
|
||||
const resource: SpriteAsset = {
|
||||
asset_id: asset.id,
|
||||
|
||||
@@ -107,7 +107,7 @@ export function validateLayoutGenerationPrerequisites(
|
||||
message: '请先导入并绑定独立素材',
|
||||
});
|
||||
}
|
||||
const visit = (nodes: State['ui_trees'][number]['children']) => {
|
||||
const visit = (nodes: State['ui_trees'][number]['root']['children']) => {
|
||||
for (const node of nodes) {
|
||||
for (const component of node.components) {
|
||||
if (
|
||||
@@ -136,7 +136,7 @@ export function validateLayoutGenerationPrerequisites(
|
||||
visit(node.children);
|
||||
}
|
||||
};
|
||||
for (const tree of state.ui_trees) visit(tree.children);
|
||||
for (const tree of state.ui_trees) visit([tree.root]);
|
||||
return issues;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,50 @@
|
||||
import type { State } from './types/State';
|
||||
import type { UIDesignSuggestion } from './types/UIDesignSuggestion';
|
||||
import type { UIDesignImageId } from './types/UIDesignImageId';
|
||||
import type { UIDesignSuggestionTreeNode } from './types/UIDesignSuggestionTreeNode';
|
||||
|
||||
/**
|
||||
* Applies semantic suggestions conservatively: null means "do not modify",
|
||||
* an already curated value wins, and suggestions for resources that disappeared
|
||||
* from the current State are ignored.
|
||||
* Applies semantic suggestions conservatively. The tree carries relationships;
|
||||
* existing metadata remains authoritative whenever it is already populated.
|
||||
* Persisted `slave_to` remains the owning Page ID, so nested descendants inherit
|
||||
* their root Page rather than pointing at an intermediate Section.
|
||||
*/
|
||||
export function applyUiDesignSuggestions(
|
||||
state: State,
|
||||
suggestions: readonly UIDesignSuggestion[],
|
||||
suggestions: readonly UIDesignSuggestionTreeNode[],
|
||||
): State {
|
||||
const next = structuredClone(state);
|
||||
|
||||
for (const suggestion of suggestions) {
|
||||
const image = next.ui_design_images[suggestion.ui_design_image_id];
|
||||
if (!image) continue;
|
||||
function applyNode(
|
||||
node: UIDesignSuggestionTreeNode,
|
||||
pageId: UIDesignImageId | null,
|
||||
): void {
|
||||
const image = next.ui_design_images[node.id];
|
||||
if (!image) return;
|
||||
|
||||
if (image.metadata.name.trim().length === 0 && suggestion.name?.trim()) {
|
||||
image.metadata.name = suggestion.name.trim();
|
||||
if (image.metadata.name.trim().length === 0 && node.name.trim()) {
|
||||
image.metadata.name = node.name.trim();
|
||||
}
|
||||
if (image.metadata.role === null && suggestion.role !== null) {
|
||||
image.metadata.role = suggestion.role;
|
||||
if (image.metadata.role === null) {
|
||||
image.metadata.role = node.role;
|
||||
}
|
||||
if (image.metadata.slave_to === null && suggestion.slave_to !== null) {
|
||||
image.metadata.slave_to = suggestion.slave_to;
|
||||
if (image.metadata.slave_to === null && pageId !== null) {
|
||||
image.metadata.slave_to = pageId;
|
||||
}
|
||||
if (
|
||||
image.metadata.description.trim().length === 0 &&
|
||||
suggestion.description?.trim()
|
||||
node.description.trim()
|
||||
) {
|
||||
image.metadata.description = suggestion.description.trim();
|
||||
image.metadata.description = node.description.trim();
|
||||
}
|
||||
|
||||
const childPageId = node.role === 'Page' ? node.id : pageId;
|
||||
for (const child of node.children) {
|
||||
applyNode(child, childPageId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const root of suggestions) {
|
||||
applyNode(root, null);
|
||||
}
|
||||
|
||||
return next;
|
||||
|
||||
@@ -111,7 +111,7 @@ export async function requestClientApiBytes(
|
||||
}
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(resolveClientApiUrl(url), {
|
||||
response = await fetchClientHttp(url, {
|
||||
...init,
|
||||
credentials: 'same-origin',
|
||||
headers,
|
||||
|
||||
Reference in New Issue
Block a user