Focus imported canvas workflows
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user