diff --git a/graphfx-ui/src/App.vue b/graphfx-ui/src/App.vue index 4f3b295..81f1f57 100644 --- a/graphfx-ui/src/App.vue +++ b/graphfx-ui/src/App.vue @@ -7,7 +7,7 @@ import '@vue-flow/core/dist/style.css' import '@vue-flow/core/dist/theme-default.css' import '@vue-flow/controls/dist/style.css' import GraphFxNode from './components/GraphFxNode.vue' -import { countSavedPositions, graphFxToVueFlow, parseGraphFxJson, type GraphFxFlowWarning, type GraphFxSerializedGraph } from './lib/graphfxToVueFlow' +import { countSavedPositions, graphFxToVueFlow, parseGraphFxJson, type GraphFxFlowWarning, type GraphFxImageInspection, type GraphFxSerializedGraph } from './lib/graphfxToVueFlow' import { layoutGraph } from './lib/layout' import { coerceIoValue, createSerializedNode, nodeCatalog, removeNodeAndConnections } from './lib/editorHelpers' import { ioTypeFor } from './lib/nodeDefinitions' @@ -46,6 +46,7 @@ const runtimeStatus = ref('Graph not running') const runtimeError = ref(null) const selectedOutputKey = ref('') const outputPreviewUrl = ref(null) +const imageInspector = ref(null) let unsubscribeOutput: (() => void) | null = null let unsubscribeRuntimeOutputPreviews: Array<() => void> = [] @@ -76,6 +77,11 @@ const nodeActions = { deleteNode, duplicateNode, selectInputImage, + inspectImage, +} + +function inspectImage(inspection: GraphFxImageInspection) { + imageInspector.value = inspection } function loadImageSelections() { @@ -191,8 +197,33 @@ function refreshFromGraph(options: { fit?: boolean; layout?: boolean } = {}) { if (options.fit) nextTick(() => fitView({ padding: 0.2, duration: 250 })) } -function applyAutoLayout() { - refreshFromGraph({ layout: true, fit: true }) +function measureNodeDimensions() { + const dimensions: Record = {} + for (const node of nodes.value) { + const element = document.querySelector(`.vue-flow__node[data-id="${CSS.escape(node.id)}"]`) as HTMLElement | null + const rect = element?.getBoundingClientRect() + if (rect?.width && rect?.height) { + dimensions[node.id] = { width: rect.width, height: rect.height } + } + } + return dimensions +} + +async function applyAutoLayout() { + refreshFlow() + await nextTick() + await new Promise((resolve) => requestAnimationFrame(() => resolve())) + const dimensions = measureNodeDimensions() + const model = graphFxToVueFlow(graph.value) + const flowNodes = layoutGraph(model.nodes, model.edges, { direction: 'LR', useSavedPositions: false, dimensions }) + for (const flowNode of flowNodes) { + const entry = graph.value.find((item) => item.node.id === flowNode.id) + if (entry) { + entry.x = Math.round(flowNode.position.x) + entry.y = Math.round(flowNode.position.y) + } + } + refreshFromGraph({ fit: true }) } function fitGraph() { @@ -717,7 +748,7 @@ refreshFlow() @edge-double-click="onEdgeDoubleClick" > @@ -750,5 +781,19 @@ refreshFlow() + +