Focus imported canvas workflows

This commit is contained in:
Codex
2026-05-11 20:10:20 +08:00
parent 96555bdf51
commit 0a1bce06b0
2 changed files with 38 additions and 13 deletions

View File

@@ -324,13 +324,16 @@ function getImportCanvasCandidate(payload: unknown) {
function normalizeExternalNodeType(type: string, node: Record<string, unknown>, settings: Record<string, unknown>): CanvasNodeType {
const normalized = type.toLowerCase().replace(/[_\s-]+/g, '');
if (normalized.includes('text')) return 'text';
const hasImageContent = !!firstString(node.content, node.imageUrl, node.url, node.src);
const hasPrompt = !!firstString(node.prompt, settings.prompt);
if (normalized.includes('img2img') || normalized.includes('imagetoimage')) return 'img2img';
if (normalized.includes('text2img') || normalized.includes('txt2img') || normalized.includes('texttoimage')) return 'text2img';
if (normalized.includes('gen') || normalized.includes('generate')) return 'image';
if (normalized.includes('gen') || normalized.includes('generate')) return hasImageContent ? 'img2img' : 'text2img';
if (hasPrompt) return hasImageContent ? 'img2img' : 'text2img';
if (normalized.includes('text')) return 'text';
if (normalized.includes('image') || normalized.includes('asset')) return 'image';
if (firstString(settings.text, node.text, node.content) && !firstString(node.content, node.imageUrl, node.url, node.src)) return 'text';
return Array.isArray(settings.generatedImages) || firstString(node.prompt, settings.prompt) ? 'image' : 'text';
if (firstString(settings.text, node.text, node.content) && !hasImageContent) return 'text';
return Array.isArray(settings.generatedImages) && hasImageContent ? 'image' : 'text';
}
function normalizeExternalRatio(value: unknown) {
@@ -421,7 +424,8 @@ function convertExternalCanvasProject(value: unknown, assetMap?: ExternalAssetMa
zIndex: index + 1,
title,
prompt,
imageUrl,
imageUrl: type === 'image' ? imageUrl : undefined,
referenceImage: type === 'img2img' ? imageUrl || undefined : undefined,
outputImages: outputImages.length > 0 ? outputImages : imageUrl ? [imageUrl] : undefined,
selectedOutput: imageUrl || undefined,
status: settings.error ? 'failed' : imageUrl ? 'succeeded' : 'idle',
@@ -1037,6 +1041,24 @@ export function InfiniteCanvasWorkspace() {
}, `${safeTitle}.miaojing-canvas.json`);
}, [project, state]);
const focusImportedState = useCallback((nextState: CanvasProjectState) => {
const importedNodeIds = nextState.nodes.map(node => node.id);
setSelectedNodeIds(importedNodeIds);
setSelectedNodeId(importedNodeIds[0] || null);
const bounds = getCanvasBounds(nextState.nodes);
const controls = flowControlsRef.current;
if (bounds && controls) {
window.setTimeout(() => {
void controls.fitBounds({
x: bounds.left - 120,
y: bounds.top - 120,
width: bounds.width + 240,
height: bounds.height + 240,
}, { padding: 0, duration: 220 });
}, 80);
}
}, []);
const applyImportedProject = useCallback((parsed: { title?: unknown; projectName?: unknown; state?: unknown; nodes?: unknown }, assetMap?: ExternalAssetMap) => {
const nextState = getImportCanvasState(parsed, assetMap);
replaceCanvasState(nextState, { dirty: true });
@@ -1049,9 +1071,8 @@ export function InfiniteCanvasWorkspace() {
if (project && title) {
setProject({ ...project, title });
}
setSelectedNodeId(null);
setSelectedNodeIds([]);
}, [project, replaceCanvasState, resetHistory]);
focusImportedState(nextState);
}, [focusImportedState, project, replaceCanvasState, resetHistory]);
const importProjectJson = useCallback(async (file: File) => {
setImportingProject(true);
@@ -1059,8 +1080,12 @@ export function InfiniteCanvasWorkspace() {
toast.info('正在导入工作流');
const text = await file.text();
const parsed = JSON.parse(text) as { title?: unknown; projectName?: unknown; state?: unknown; nodes?: unknown };
const importedState = getImportCanvasState(parsed);
if (importedState.nodes.length === 0) {
throw new Error('没有识别到可导入的工作流节点');
}
applyImportedProject(parsed);
toast.success('画布已导入');
toast.success(`画布已导入,包含 ${importedState.nodes.length} 个节点`);
} catch (error) {
toast.error(error instanceof Error ? error.message : '导入失败');
} finally {
@@ -1100,7 +1125,7 @@ export function InfiniteCanvasWorkspace() {
throw new Error('没有识别到可导入的工作流节点');
}
applyImportedProject(parsed, assetMap);
toast.success(`工作流目录已导入,包含 ${assetMap.size}素材`);
toast.success(`工作流目录已导入,包含 ${importedState.nodes.length}节点`);
} catch (error) {
toast.error(error instanceof Error ? error.message : '导入失败');
} finally {

View File

@@ -753,9 +753,9 @@ function FlowCanvasInner({
const movingRef = useRef(false);
const paneClickTimerRef = useRef<number | null>(null);
const isDarkTheme = resolvedTheme === 'dark';
const edgeStroke = isDarkTheme ? 'rgb(255 255 255 / 0.72)' : 'rgb(57 48 35 / 0.62)';
const dotColor = isDarkTheme ? 'rgb(255 255 255 / 0.42)' : 'rgb(57 48 35 / 0.18)';
const dotAccentColor = isDarkTheme ? 'rgb(255 255 255 / 0.24)' : 'rgb(196 126 30 / 0.26)';
const edgeStroke = isDarkTheme ? 'rgb(255 255 255 / 0.72)' : 'hsl(var(--foreground) / 0.62)';
const dotColor = isDarkTheme ? 'rgb(255 255 255 / 0.42)' : 'hsl(var(--foreground) / 0.16)';
const dotAccentColor = isDarkTheme ? 'rgb(255 255 255 / 0.24)' : 'hsl(var(--primary) / 0.28)';
const incomingNodes = useMemo<CanvasFlowNode[]>(() => nodes.map(node => ({
id: node.id,