Improve GraphFX UI editing and image previews
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { MarkerType, type Edge, type Node } from '@vue-flow/core'
|
||||
import { ioTypeFor, type GraphFxIoType } from './nodeDefinitions'
|
||||
import { ioOptionsFor, ioTypeFor, type GraphFxIoType } from './nodeDefinitions'
|
||||
|
||||
export type GraphFxIoValue = {
|
||||
label?: string | null
|
||||
value?: unknown
|
||||
output?: string | null
|
||||
type?: GraphFxIoType
|
||||
definition?: { type?: GraphFxIoType }
|
||||
definition?: { type?: GraphFxIoType; enum?: string[]; options?: string[] }
|
||||
}
|
||||
|
||||
export type GraphFxSerializedNode = {
|
||||
@@ -32,7 +32,25 @@ export type GraphFxPort = {
|
||||
label: string
|
||||
type: GraphFxIoType
|
||||
output?: string | null
|
||||
connectedDisplay?: string | null
|
||||
value?: unknown
|
||||
options?: string[]
|
||||
previewUrl?: string | null
|
||||
}
|
||||
|
||||
export type GraphFxImageOption = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type GraphFxNodeActions = {
|
||||
renameNodeId: (nodeId: string, nextId: string) => void
|
||||
updateIoLabel: (nodeId: string, direction: 'in' | 'out', ioName: string, label: string) => void
|
||||
updateInputValue: (nodeId: string, ioName: string, value: string | boolean) => void
|
||||
disconnectInput: (nodeId: string, ioName: string) => void
|
||||
deleteNode: (nodeId: string) => void
|
||||
duplicateNode: (nodeId: string) => void
|
||||
selectInputImage: (nodeId: string, ioName: string, imageId: string) => void | Promise<void>
|
||||
}
|
||||
|
||||
export type GraphFxNodeData = {
|
||||
@@ -42,6 +60,11 @@ export type GraphFxNodeData = {
|
||||
outputs: GraphFxPort[]
|
||||
hasSavedPosition: boolean
|
||||
raw: GraphFxSerializedNode
|
||||
actions?: GraphFxNodeActions
|
||||
imageOptions?: GraphFxImageOption[]
|
||||
selectedImages?: Record<string, string>
|
||||
imagePreviews?: Record<string, string>
|
||||
runtimeReady?: boolean
|
||||
}
|
||||
|
||||
export type GraphFxFlowWarning = {
|
||||
@@ -89,14 +112,35 @@ function labelFor(name: string, io?: GraphFxIoValue) {
|
||||
return io?.label || name
|
||||
}
|
||||
|
||||
function imagePreviewUrl(value: unknown): string | null {
|
||||
if (!value) return null
|
||||
if (typeof value === 'string') {
|
||||
if (value.startsWith('data:image/')) return value
|
||||
if (/^[A-Za-z0-9+/=\r\n]+$/.test(value) && value.length > 80) return `data:image/png;base64,${value.replace(/\s+/g, '')}`
|
||||
return null
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
const record = value as { src?: unknown; dataUrl?: unknown; base64?: unknown }
|
||||
if (typeof record.src === 'string') return imagePreviewUrl(record.src)
|
||||
if (typeof record.dataUrl === 'string') return imagePreviewUrl(record.dataUrl)
|
||||
if (typeof record.base64 === 'string') return imagePreviewUrl(record.base64)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function toPorts(nodeName: string, record: Record<string, GraphFxIoValue> | undefined, direction: 'in' | 'out') {
|
||||
return Object.entries(record || {}).map(([name, io]) => ({
|
||||
name,
|
||||
label: labelFor(name, io),
|
||||
type: ioTypeFor(nodeName, direction, name, io?.type || io?.definition?.type),
|
||||
output: direction === 'in' ? io?.output || null : null,
|
||||
value: direction === 'in' ? io?.value : undefined,
|
||||
}))
|
||||
return Object.entries(record || {}).map(([name, io]) => {
|
||||
const type = ioTypeFor(nodeName, direction, name, io?.type || io?.definition?.type)
|
||||
return {
|
||||
name,
|
||||
label: labelFor(name, io),
|
||||
type,
|
||||
output: direction === 'in' ? io?.output || null : null,
|
||||
value: direction === 'in' ? io?.value : undefined,
|
||||
options: ioOptionsFor(nodeName, direction, name, io?.definition),
|
||||
previewUrl: type === 'Image' ? imagePreviewUrl(io?.value) : null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function savedPosition(item: GraphFxSerializedNode, index: number) {
|
||||
@@ -105,7 +149,7 @@ function savedPosition(item: GraphFxSerializedNode, index: number) {
|
||||
|
||||
return {
|
||||
hasSavedPosition: hasX && hasY,
|
||||
position: hasX && hasY ? { x: item.x as number, y: item.y as number } : { x: index * 280, y: 0 },
|
||||
position: hasX && hasY ? { x: item.x as number, y: item.y as number } : { x: index * 430, y: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +233,11 @@ export function graphFxToVueFlow(graph: GraphFxSerializedGraph): GraphFxFlowMode
|
||||
|
||||
const sourceNode = nodes.find((node) => node.id === source.nodeId)
|
||||
const sourcePort = sourceNode?.data?.outputs.find((output) => output.name === source.outputName)
|
||||
const targetNode = nodes.find((node) => node.id === item.node.id)
|
||||
const targetPort = targetNode?.data?.inputs.find((port) => port.name === inputName)
|
||||
if (targetPort) {
|
||||
targetPort.connectedDisplay = `${sourceNode?.data?.name || source.nodeId}.${sourcePort?.label || source.outputName}`
|
||||
}
|
||||
const edgeColor = ioTypeColors[sourcePort?.type || 'Unknown']
|
||||
|
||||
edges.push({
|
||||
|
||||
Reference in New Issue
Block a user