This commit is contained in:
2026-03-07 10:43:26 +01:00
parent 7e42e74a1e
commit 59224880d2
7 changed files with 164 additions and 76 deletions

View File

@@ -18,8 +18,11 @@ const KROKI_PLANTUML_SVG = '/api/kroki/plantuml/svg'
type Props = {
id: string
data?: any
style?: React.CSSProperties
}
const DEFAULT_CONFIG_NODE_STYLE = { width: 320, height: 320 }
const DARK_SKINPARAMS = `
skinparam backgroundColor #1e1e1e
skinparam defaultFontColor #e0e0e0
@@ -63,7 +66,7 @@ skinparam class {
}
`
export const RenderingNode = memo(function RenderingNode({ id }: Props) {
export const RenderingNode = memo(function RenderingNode({ id, width, height }: Props) {
const [svgContent, setSvgContent] = useState<string | null>(null)
const [error, setError] = useState<null | { kind: string; message: string }>(null)
const [loading, setLoading] = useState(false)
@@ -264,58 +267,60 @@ export const RenderingNode = memo(function RenderingNode({ id }: Props) {
}, [id, theme, plantumlText, configSignature, edgesSignature, variablesSignature, functionsSignature])
return (
<BaseNode className="w-96">
<BaseNode className="min-w-96 min-h-[320px]" style={width != null && height != null && width > 0 && height > 0 ? { width, height } : undefined} resizable nodeId={id} handles={<><InputHandle id="ain" /><OutputHandle id="out" /></>}>
<BaseNodeHeader className="border-b">
<Sparkles className="size-4" />
<BaseNodeHeaderTitle>{id}</BaseNodeHeaderTitle>
</BaseNodeHeader>
<BaseNodeContent>
{incomingIds.length === 0 ? (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<Sparkles className="size-6" />
</EmptyMedia>
<EmptyTitle>No configuration connected</EmptyTitle>
<EmptyDescription>Connect a Configuration node or create one. The renderer will display the PlantUML diagram.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<button
className="inline-flex items-center rounded bg-primary px-3 py-1 text-xs text-primary-foreground"
onClick={() => {
if (!setNodes || !setEdges) return
const genId = () => `node_${Math.random().toString(36).slice(2, 9)}`
const nid = genId()
const thisNode = nodes.find((n: any) => n.id === id)
const pos = thisNode?.position ?? { x: 0, y: 0 }
const newPos = { x: pos.x - 220, y: pos.y }
const newNode = { id: nid, type: 'config', position: newPos, data: { plantuml: '@startuml\n\n@enduml\n', title: `config-${nid}` } }
setNodes((nds: any[]) => nds.concat(newNode))
const edgeId = `e-${nid}-${id}`
setEdges((eds: any[]) => eds.concat({ id: edgeId, source: nid, target: id }))
}}
>
Create Config
</button>
</EmptyContent>
</Empty>
) : error ? (
srcData?.renderError ? (
srcData.renderError(error)
) : srcData?.errorHtml ? (
<div className="p-3 text-xs text-red-700 dark:text-red-400" dangerouslySetInnerHTML={{ __html: String(srcData.errorHtml) }} />
) : (
<div className="p-3 text-xs text-red-700 dark:text-red-400">{error.message}</div>
)
) : loading ? (
<div className="flex min-h-[120px] items-center justify-center text-xs text-muted-foreground">Rendering</div>
) : svgContent ? (
<div
className="min-h-[120px] w-full overflow-auto rounded border border-input bg-white dark:bg-gray-900 [&_svg]:max-w-full [&_svg]:h-auto"
dangerouslySetInnerHTML={{ __html: svgContent }}
/>
) : null}
<div className="min-h-0 flex-1 flex flex-col">
{incomingIds.length === 0 ? (
<Empty className="min-h-0 flex-1">
<EmptyHeader>
<EmptyMedia variant="icon">
<Sparkles className="size-6" />
</EmptyMedia>
<EmptyTitle>No configuration connected</EmptyTitle>
<EmptyDescription>Connect a Configuration node or create one. The renderer will display the PlantUML diagram.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<button
className="inline-flex items-center rounded bg-primary px-3 py-1 text-xs text-primary-foreground"
onClick={() => {
if (!setNodes || !setEdges) return
const genId = () => `node_${Math.random().toString(36).slice(2, 9)}`
const nid = genId()
const thisNode = nodes.find((n: any) => n.id === id)
const pos = thisNode?.position ?? { x: 0, y: 0 }
const newPos = { x: pos.x - 220, y: pos.y }
const newNode = { id: nid, type: 'config', position: newPos, data: { plantuml: '@startuml\n\n@enduml\n', title: `config-${nid}` }, style: DEFAULT_CONFIG_NODE_STYLE }
setNodes((nds: any[]) => nds.concat(newNode))
const edgeId = `e-${nid}-${id}`
setEdges((eds: any[]) => eds.concat({ id: edgeId, source: nid, target: id }))
}}
>
Create Config
</button>
</EmptyContent>
</Empty>
) : error ? (
srcData?.renderError ? (
srcData.renderError(error)
) : srcData?.errorHtml ? (
<div className="p-3 text-xs text-red-700 dark:text-red-400" dangerouslySetInnerHTML={{ __html: String(srcData.errorHtml) }} />
) : (
<div className="p-3 text-xs text-red-700 dark:text-red-400">{error.message}</div>
)
) : loading ? (
<div className="flex min-h-0 flex-1 items-center justify-center text-xs text-muted-foreground">Rendering</div>
) : svgContent ? (
<div
className="min-h-0 flex-1 w-full overflow-auto rounded border border-input bg-white dark:bg-gray-900 [&_svg]:max-w-full [&_svg]:h-auto"
dangerouslySetInnerHTML={{ __html: svgContent }}
/>
) : null}
</div>
</BaseNodeContent>
<BaseNodeFooter>
@@ -323,9 +328,6 @@ export const RenderingNode = memo(function RenderingNode({ id }: Props) {
{svgContent ? 'PlantUML diagram' : error ? 'Error' : '—'}
</div>
</BaseNodeFooter>
<InputHandle id="ain" />
<OutputHandle id="out" />
</BaseNode>
)
})