Implemented sorted graph
This commit is contained in:
@@ -3,6 +3,7 @@ import {Group} from "../../src/types";
|
||||
import {useLocalStorage} from "../../src/hooks/useLocalStorage";
|
||||
import {ReactNodeLike} from "prop-types";
|
||||
import pako from "pako";
|
||||
import Dict = NodeJS.Dict;
|
||||
|
||||
interface Props {
|
||||
children: ReactNodeLike
|
||||
@@ -18,7 +19,7 @@ interface GroupContextType {
|
||||
baseFactories: string[]
|
||||
setBaseFactories(factories: string[]): void
|
||||
|
||||
groups: Record<string, Group>
|
||||
groups: Dict<Group>
|
||||
addGroup(name: string, exported?: string[], malls?: string[]): void
|
||||
removeGroup(name: string): void
|
||||
renameGroup(name: string, newName: string): void
|
||||
@@ -56,7 +57,7 @@ const GroupContext = createContext<GroupContextType>(defaultValues);
|
||||
export const useGroups = () => useContext(GroupContext)
|
||||
|
||||
interface StoredFile {
|
||||
groups: Record<string, Group>,
|
||||
groups: Dict<Group>,
|
||||
basicValues: string[],
|
||||
excludedSuggestions: string[]
|
||||
}
|
||||
@@ -64,7 +65,7 @@ interface StoredFile {
|
||||
export const GroupProvider: FC<Props> = ({children}) => {
|
||||
const [excludedSuggestions, setExcludedSuggestions] = useLocalStorage<string[]>('excludedSuggestions', [])
|
||||
const [basicValues, setBasicValues] = useLocalStorage<string[]>('basicValues', [])
|
||||
const [groups, setGroups] = useLocalStorage<Record<string, Group>>('serviceGroups', {})
|
||||
const [groups, setGroups] = useLocalStorage<Dict<Group>>('serviceGroups', {})
|
||||
|
||||
const doNotSuggest = useMemo<Set<string>>(() => {
|
||||
return new Set([...Object.values(groups).flatMap(group => [...group.exports, ...group.malls]), ...excludedSuggestions, ...basicValues])
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
border: 1px solid white;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.amount {
|
||||
|
||||
@@ -6,6 +6,7 @@ import styles from "./GroupBox.module.css"
|
||||
import {EntitySpan} from "../EntitySpan/EntitySpan";
|
||||
import {useGroups} from "../../contexts/GroupProvider";
|
||||
import {calculateInputs} from "../../../src/calculateInputs";
|
||||
import {uniquify} from "../../../src/utils";
|
||||
|
||||
interface Props {
|
||||
group: Group
|
||||
@@ -44,8 +45,8 @@ const GroupBoxBase: FC<Props> = ({ group }) => {
|
||||
}, [exports, malls, ignoredFactories, baseFactories, findFactory, exportedFactories])
|
||||
|
||||
const [suggestionsExport, suggestionMall] = useMemo<[EnrichedEntity[], EnrichedEntity[]]>(() => {
|
||||
const selectedValues = Array.from(new Set([...exports, ...malls]))
|
||||
const availableIngredients = Array.from(new Set([...selectedValues, ...intermediates, ...inputs]))
|
||||
const selectedValues = uniquify([...exports, ...malls])
|
||||
const availableIngredients = uniquify([...selectedValues, ...intermediates, ...inputs])
|
||||
return factories
|
||||
.filter(factory => {
|
||||
if (!factory.recipe) return false
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
.plane {
|
||||
border: 1px solid red;
|
||||
box-shadow: 0 0 0 1px red; /* Border left */
|
||||
padding: 2em;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5em;
|
||||
gap: 10em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.node {
|
||||
@@ -18,3 +20,7 @@
|
||||
border: 1px solid #DDDDDD;
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import {FC, HTMLProps, PropsWithChildren, useMemo} from "react";
|
||||
import {FC, HTMLProps, PropsWithChildren, useEffect, useRef, useState} from "react";
|
||||
import styles from './ProducingGraph.module.css'
|
||||
import {EntityIcon} from "../../home/EntityIcon/EntityIcon";
|
||||
import {sortByProperty} from "../../../src/utils";
|
||||
import {EnrichedEntity, Recipe} from "../../../src/types";
|
||||
|
||||
interface GraphNodeBase {
|
||||
inputs: string[]
|
||||
outputs: string[]
|
||||
name: string
|
||||
}
|
||||
|
||||
export type GraphNode<T extends Record<string, unknown>> = GraphNodeBase & T
|
||||
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>[]
|
||||
@@ -19,53 +13,116 @@ interface Props<T extends {}> {
|
||||
childType: FC<HTMLProps<HTMLDivElement> & {node: GraphNode<T>}>
|
||||
}
|
||||
|
||||
export const ProducingGraph = <T extends Record<string, unknown>,>({nodes, inputs, outputs, childType: ChildType}: PropsWithChildren<Props<T>>) => {
|
||||
const rows: GraphNode<T>[][] = useMemo(() => {
|
||||
const available = new Set(inputs)
|
||||
let todo = [...nodes]
|
||||
const result: GraphNode<T>[][] = []
|
||||
while (todo.length) {
|
||||
const amount = todo.length
|
||||
const thisRow: string[] = []
|
||||
result.push([])
|
||||
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])
|
||||
|
||||
todo = todo.filter((node) => {
|
||||
if (node.inputs.every(input => available.has(input))) {
|
||||
result[result.length - 1].push(node)
|
||||
thisRow.push(...node.outputs)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
thisRow.map(uid => available.add(uid))
|
||||
result[result.length - 1].sort(sortByProperty(val => -val.outputs.length * 1000 + -val.inputs.length))
|
||||
|
||||
if (amount === todo.length) {
|
||||
console.warn("Loop detected! Left over:", todo)
|
||||
result.pop()
|
||||
break
|
||||
/*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 result
|
||||
}, [inputs, nodes])
|
||||
return res
|
||||
}, [rows, inputs, outputs])*/
|
||||
|
||||
return <div className={styles.plane}>
|
||||
<div className={styles.row}>
|
||||
{inputs.map((input, idx) => <EntityIcon className={styles.input} key={input} value={input} />)}
|
||||
</div>
|
||||
{rows.map((row, colIdx) => {
|
||||
return <div className={styles.row} key={colIdx}>
|
||||
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((node) => <ChildType
|
||||
className={styles.node}
|
||||
key={node.name}
|
||||
node={node}
|
||||
/>)
|
||||
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>
|
||||
})}
|
||||
{outputs ? <div className={styles.row}>
|
||||
{outputs.map((input, idx) => <EntityIcon className={styles.input} key={input} value={input} />)}
|
||||
</div> : null }
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user