125 lines
4.3 KiB
TypeScript
125 lines
4.3 KiB
TypeScript
import { FC, HTMLProps, PropsWithChildren, useEffect, useRef, useState } from 'react'
|
|
import styles from './ProducingGraph.module.css'
|
|
import { EntityIcon } from '../../home/EntityIcon/EntityIcon'
|
|
import { createPath, drawLine } from '../../../src/svg'
|
|
import {
|
|
AdditionalNode,
|
|
GraphNode,
|
|
GraphNodeWithIds,
|
|
isAdditionalNode
|
|
} from '../../../src/graph-untangle/types'
|
|
import { graphUntangled } from '../../../src/graph-untangle'
|
|
import { Dict } from '../../../src/types'
|
|
|
|
interface Props<T extends Dict<unknown>> {
|
|
nodes: GraphNode<T>[]
|
|
inputs: string[]
|
|
outputs?: string[]
|
|
childType: FC<HTMLProps<HTMLDivElement> & { node: GraphNode<T> }>
|
|
mergeHidden?: boolean
|
|
}
|
|
|
|
export const ProducingGraph = <T extends Dict<unknown>>({
|
|
nodes,
|
|
inputs,
|
|
outputs,
|
|
childType: ChildType,
|
|
mergeHidden
|
|
}: PropsWithChildren<Props<T>>) => {
|
|
const planeRef = useRef<HTMLDivElement>(null)
|
|
const [[rows, nodeMap]] = useState<[string[][], Dict<GraphNodeWithIds<T | AdditionalNode>>]>(
|
|
graphUntangled(nodes, inputs, outputs ?? [], { timeLimit: 3000, mergeHidden })
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (!planeRef.current) return
|
|
const plane = planeRef.current
|
|
function createSvgElement() {
|
|
const elem = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
elem.id = 'arrows'
|
|
elem.setAttribute('class', styles.svg)
|
|
elem.setAttributeNS(null, 'fill', 'none')
|
|
plane.appendChild(elem)
|
|
return elem
|
|
}
|
|
const svg = document.getElementById('arrows') ?? createSvgElement()
|
|
const width = Math.round(plane.getBoundingClientRect().width)
|
|
const height = Math.round(plane.getBoundingClientRect().height)
|
|
svg.setAttribute('width', `${width}`)
|
|
svg.setAttribute('height', `${height}`)
|
|
svg.setAttributeNS(null, 'viewBox', `0 0 ${width} ${height}`)
|
|
svg.replaceChildren()
|
|
|
|
const paths: string[] = []
|
|
plane.querySelectorAll('[data-outputs]').forEach(startNode => {
|
|
if (!(startNode instanceof HTMLElement)) return
|
|
;(startNode.dataset.outputs?.split(' ') ?? []).forEach(output => {
|
|
const endNode = plane.querySelector(`[data-name="${output}"]`)
|
|
if (!(endNode instanceof HTMLElement)) return
|
|
paths.push(
|
|
createPath(
|
|
plane.getBoundingClientRect(),
|
|
startNode.getBoundingClientRect(),
|
|
endNode.getBoundingClientRect()
|
|
)
|
|
)
|
|
})
|
|
})
|
|
plane.querySelectorAll('[data-hidden="true"][data-outputs]').forEach(elem => {
|
|
if (!(elem instanceof HTMLElement)) return
|
|
paths.push(drawLine(plane.getBoundingClientRect(), elem.getBoundingClientRect()))
|
|
})
|
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
|
path.setAttributeNS(null, 'd', paths.join(' '))
|
|
svg.appendChild(path)
|
|
})
|
|
|
|
return (
|
|
<div className={styles.plane} ref={planeRef}>
|
|
{rows.map((row, colIdx) => (
|
|
<div className={styles.row} key={colIdx} data-row={true}>
|
|
{row.map(uid => {
|
|
const node = nodeMap[uid]
|
|
return !isAdditionalNode(node) ? (
|
|
<span
|
|
data-name={node.___uid}
|
|
data-inputs={node.___uidInputs.join(' ')}
|
|
data-outputs={node.___uidOutputs.join(' ')}
|
|
key={node.___uid}
|
|
data-hidden={node.___uidOutputs.length > 0}
|
|
>
|
|
<ChildType className={styles.node} node={node} />
|
|
</span>
|
|
) : node.___type === 'h' ? (
|
|
<span
|
|
className={styles.hidden}
|
|
key={node.___uid}
|
|
data-name={node.___uid}
|
|
data-inputs={node.___uidInputs.join(' ')}
|
|
data-outputs={node.___uidOutputs.join(' ')}
|
|
data-hidden={true}
|
|
></span>
|
|
) : node.___type === 'i' ? (
|
|
<span
|
|
key={node.___uid}
|
|
data-name={node.___uid}
|
|
data-outputs={node.___uidOutputs.join(' ')}
|
|
data-hidden={true}
|
|
>
|
|
<EntityIcon value={node.name} />
|
|
</span>
|
|
) : (
|
|
<EntityIcon
|
|
key={node.___uid}
|
|
value={node.name}
|
|
data-name={node.___uid}
|
|
data-inputs={node.___uidInputs.join(' ')}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|