129 lines
5.5 KiB
TypeScript
129 lines
5.5 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 {}> {
|
|
nodes: GraphNode<T>[]
|
|
inputs: string[]
|
|
outputs?: string[]
|
|
childType: FC<HTMLProps<HTMLDivElement> & {node: GraphNode<T>}>
|
|
}
|
|
|
|
export const ProducingGraph = <T extends Dict<unknown>,>({nodes, inputs, outputs, childType: ChildType}: PropsWithChildren<Props<T>>) => {
|
|
const planeRef = useRef<HTMLDivElement>(null)
|
|
const [[rows, nodeMap], setGraph] = useState<[string[][], Dict<GraphNodeWithIds<T|AdditionalNode>>]>([[], {}])
|
|
useEffect(() => {
|
|
setGraph(graphUntangled(nodes, inputs, outputs ?? []))
|
|
setTimeout(() => setGraph(graphUntangled(nodes, inputs, outputs ?? [], 5000)), 0)
|
|
}, [inputs, nodes, outputs])
|
|
|
|
/*const rowsWithInOut: (GraphNode<T>|AdditionalNode)[][] = useMemo(() => {
|
|
const addedInputs = new Set<string>()
|
|
const res: (GraphNode<T>|AdditionalNode)[][] = deepcopy([[], ...rows, []])
|
|
for (let i = 0; i < rows.length; i++) {
|
|
const rowOutputs = uniquify(rows[i].flatMap(row => row.outputs))
|
|
for (let rowOutput of rowOutputs) {
|
|
outputs?.includes(rowOutput) && res[i+2].push({___type: 'o', value: rowOutput})
|
|
}
|
|
const rowInputs = uniquify(rows[i].flatMap(row => row.inputs))
|
|
for (let rowInput of rowInputs) {
|
|
if (addedInputs.has(rowInput))
|
|
!res[i].some(node => isAdditionalNode(node) && node.value === rowInput) && res[i].push({___type: 'h', value: rowInput})
|
|
else if (inputs.includes(rowInput))
|
|
res[i].push({___type: 'i', value: rowInput})
|
|
addedInputs.add(rowInput)
|
|
}
|
|
}
|
|
return res
|
|
}, [rows, inputs, outputs])*/
|
|
|
|
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('style', 'position: absolute; inset: 0 0 0 0; pointer-events: none; z-index: -10;')
|
|
elem.setAttributeNS(null, 'stroke', 'black')
|
|
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()
|
|
|
|
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
|
|
const path = document.createElementNS('http://www.w3.org/2000/svg',"path")
|
|
path.setAttributeNS(null, 'd', createPath(plane.getBoundingClientRect(), startNode.getBoundingClientRect(), endNode.getBoundingClientRect()))
|
|
svg.appendChild(path)
|
|
})
|
|
})
|
|
plane.querySelectorAll('[data-hidden][data-outputs]').forEach(elem => {
|
|
if (!(elem instanceof HTMLElement)) return
|
|
const path = document.createElementNS('http://www.w3.org/2000/svg',"path")
|
|
path.setAttributeNS(null, 'd', drawLine(plane.getBoundingClientRect(), elem.getBoundingClientRect()))
|
|
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)
|
|
? <ChildType
|
|
data-name={node.___uid}
|
|
data-inputs={node.___uidInputs.join(' ')}
|
|
data-outputs={node.___uidOutputs.join(' ')}
|
|
className={styles.node}
|
|
key={node.name}
|
|
node={node}
|
|
/>
|
|
: 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
|
|
className={styles.input}
|
|
key={node.___uid}
|
|
data-name={node.___uid}
|
|
data-outputs={node.___uidOutputs.join(' ')}
|
|
data-hidden={true}>
|
|
<EntityIcon value={node.name}/>
|
|
</span>
|
|
: <EntityIcon
|
|
className={styles.input}
|
|
key={node.___uid}
|
|
value={node.name}
|
|
data-name={node.___uid}
|
|
data-inputs={node.___uidInputs.join(' ')}
|
|
/>
|
|
);
|
|
})
|
|
}
|
|
</div>
|
|
))}
|
|
</div>
|
|
}
|