Fix canvas visibility and workflow import

This commit is contained in:
Codex
2026-05-11 19:30:23 +08:00
parent 723d9832d5
commit 927faacc5f
3 changed files with 37 additions and 25 deletions

View File

@@ -56,8 +56,6 @@
:root {
--background: oklch(0.985 0.01 85);
--foreground: oklch(0.18 0.02 60);
--canvas-dot-color: rgb(57 48 35 / 0.18);
--canvas-dot-accent-color: rgb(196 126 30 / 0.26);
--card: oklch(0.995 0.005 85);
--card-foreground: oklch(0.18 0.02 60);
--popover: oklch(0.995 0.005 85);
@@ -93,8 +91,6 @@
.dark {
--background: oklch(0.16 0.015 260);
--foreground: oklch(0.98 0.01 80);
--canvas-dot-color: rgb(255 255 255 / 0.34);
--canvas-dot-accent-color: rgb(255 255 255 / 0.2);
--card: oklch(0.21 0.025 260);
--card-foreground: oklch(0.98 0.01 80);
--popover: oklch(0.25 0.025 260);

View File

@@ -263,6 +263,23 @@ function collectExternalAssetKeys(value: unknown, keys = new Set<string>()) {
return keys;
}
function getImportCanvasCandidate(payload: unknown) {
if (!payload || typeof payload !== 'object') return payload;
const parsed = payload as {
state?: unknown;
project_json?: unknown;
files?: {
'project.data.json'?: unknown;
projectData?: unknown;
};
};
return parsed.files?.['project.data.json']
|| parsed.files?.projectData
|| parsed.project_json
|| parsed.state
|| parsed;
}
function normalizeExternalRatio(value: unknown) {
if (typeof value !== 'string' || !value) return undefined;
const match = value.match(/^(\d+)\s*x\s*(\d+)$/i);
@@ -410,19 +427,7 @@ function convertExternalCanvasProject(value: unknown, assetMap?: ExternalAssetMa
function getImportCanvasState(payload: unknown, assetMap?: ExternalAssetMap): CanvasProjectState {
if (!payload || typeof payload !== 'object') return createEmptyCanvasState();
const parsed = payload as {
state?: unknown;
project_json?: unknown;
files?: {
'project.data.json'?: unknown;
projectData?: unknown;
};
};
const candidate = parsed.files?.['project.data.json']
|| parsed.files?.projectData
|| parsed.project_json
|| parsed.state
|| parsed;
const candidate = getImportCanvasCandidate(payload);
return isExternalCanvasProject(candidate)
? convertExternalCanvasProject(candidate, assetMap)
: normalizeCanvasProjectState(candidate);
@@ -1023,7 +1028,8 @@ export function InfiniteCanvasWorkspace() {
if (!dataFile) throw new Error('没有找到 project.data.json');
toast.info('正在读取工作流目录');
const parsed = JSON.parse(await dataFile.text()) as { title?: unknown; projectName?: unknown; state?: unknown; nodes?: unknown };
const requiredAssetKeys = collectExternalAssetKeys({ nodes: parsed.nodes });
const importCandidate = getImportCanvasCandidate(parsed);
const requiredAssetKeys = collectExternalAssetKeys(importCandidate);
const assetFiles = allFiles.filter((file) => {
const relativePath = file.webkitRelativePath || file.name;
if (!relativePath.includes('/assets/') && !relativePath.startsWith('assets/')) return false;
@@ -1041,6 +1047,10 @@ export function InfiniteCanvasWorkspace() {
const type = file.type.startsWith('video/') ? 'video' : 'image';
assetMap.set(key, { url, name, type });
}));
const importedState = getImportCanvasState(parsed, assetMap);
if (importedState.nodes.length === 0) {
throw new Error('没有识别到可导入的工作流节点');
}
applyImportedProject(parsed, assetMap);
toast.success(`工作流目录已导入,包含 ${assetMap.size} 个素材`);
} catch (error) {

View File

@@ -22,6 +22,7 @@ import {
type ReactFlowInstance,
type Viewport as FlowViewport,
} from '@xyflow/react';
import { useTheme } from 'next-themes';
import '@xyflow/react/dist/style.css';
import { Brush, Download, Eye, FileImage, Image as ImageIcon, Layers, Link2, Loader2, Maximize2, Move, Sparkles, Type, Wand2 } from 'lucide-react';
import { cn } from '@/lib/utils';
@@ -747,9 +748,14 @@ function FlowCanvasInner({
onPaneDoubleClick,
}: ReactFlowCanvasProps) {
const reactFlow = useReactFlow<CanvasFlowNode, Edge>();
const { resolvedTheme } = useTheme();
const draggingRef = useRef(false);
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 incomingNodes = useMemo<CanvasFlowNode[]>(() => nodes.map(node => ({
id: node.id,
@@ -795,13 +801,13 @@ function FlowCanvasInner({
sourceHandle: 'output',
targetHandle: 'input',
type: 'smoothstep',
markerEnd: { type: MarkerType.ArrowClosed },
markerEnd: { type: MarkerType.ArrowClosed, color: edgeStroke },
style: {
stroke: 'hsl(var(--foreground) / 0.62)',
strokeWidth: 2.25,
stroke: edgeStroke,
strokeWidth: 3,
},
interactionWidth: 18,
})), [connections]);
interactionWidth: 24,
})), [connections, edgeStroke]);
useEffect(() => {
setFlowNodes(current => {
@@ -970,8 +976,8 @@ function FlowCanvasInner({
onSelectionChange={handleSelectionChange}
onInit={handleInit}
>
<Background gap={20} size={1} color="var(--canvas-dot-color)" />
<Background gap={100} size={1.5} color="var(--canvas-dot-accent-color)" />
<Background gap={20} size={1.15} color={dotColor} />
<Background gap={100} size={1.8} color={dotAccentColor} />
<Controls showInteractive={false} position="bottom-left" />
<MiniMap
position="bottom-right"