Stylings / Restructuring
This commit is contained in:
@@ -1,32 +0,0 @@
|
|||||||
.plane {
|
|
||||||
position: relative;
|
|
||||||
overflow: scroll;
|
|
||||||
overflow-scrolling: touch;
|
|
||||||
background-color: lightsalmon;
|
|
||||||
padding: 2em;
|
|
||||||
height: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tiny {
|
|
||||||
font-size: 0.5em;
|
|
||||||
margin-top: -1.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node {
|
|
||||||
display: inline-block;
|
|
||||||
position: absolute;
|
|
||||||
width: 200px;
|
|
||||||
height: fit-content;
|
|
||||||
padding: 0.2em;
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkOut {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.5em;
|
|
||||||
right: 0.5em;
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import {FC, useMemo} from "react";
|
|
||||||
import {EnrichedEntity, Recipe} from "../../src/types";
|
|
||||||
import styles from './ProducingGraph.module.css'
|
|
||||||
import {EntityIcon} from "../home/EntityIcon/EntityIcon";
|
|
||||||
import {sortByProperty} from "../../src/utils";
|
|
||||||
import Link from "next/link";
|
|
||||||
import {RecipeSpan} from "../home/Recipe/Recipe";
|
|
||||||
|
|
||||||
export interface ProducingNode {
|
|
||||||
inputs: string[]
|
|
||||||
outputs: string[]
|
|
||||||
name: string
|
|
||||||
icons?: (EnrichedEntity|string)[]
|
|
||||||
linkOut?: string
|
|
||||||
recipe?: Recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
nodes: ProducingNode[]
|
|
||||||
inputs: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProducingGraph: FC<Props> = ({nodes, inputs}) => {
|
|
||||||
const rows: ProducingNode[][] = useMemo(() => {
|
|
||||||
const available = new Set(inputs)
|
|
||||||
let todo = [...nodes]
|
|
||||||
const result: ProducingNode[][] = []
|
|
||||||
while (todo.length) {
|
|
||||||
const amount = todo.length
|
|
||||||
const thisRow: string[] = []
|
|
||||||
result.push([])
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}, [inputs, nodes])
|
|
||||||
|
|
||||||
return <div className={styles.plane}>
|
|
||||||
{inputs.map((input, idx) => <EntityIcon className={styles.input} style={{left: 75 * idx}} key={input} value={input} />)}
|
|
||||||
{rows.map((row, colIdx) => row.map((node, idx) => (
|
|
||||||
<div
|
|
||||||
className={styles.node}
|
|
||||||
key={node.name}
|
|
||||||
style={{left: 220*idx, top: 320*colIdx+100}}
|
|
||||||
>
|
|
||||||
{ node.linkOut && <Link className={styles.linkOut} href={node.linkOut}>🔗</Link> }
|
|
||||||
<h3>{node.name}</h3>
|
|
||||||
{ node.icons?.length ? <div className={styles.tiny}>
|
|
||||||
{node.icons.map((input) => <EntityIcon key={typeof input === "string" ? input : input.href} value={input} />)}
|
|
||||||
</div> : null }
|
|
||||||
{
|
|
||||||
node.recipe
|
|
||||||
? <RecipeSpan recipe={node.recipe}/>
|
|
||||||
: <>
|
|
||||||
<h4>Inputs</h4>
|
|
||||||
<div className={styles.small}>
|
|
||||||
{node.inputs.map((input) => <EntityIcon key={input} value={input} />)}
|
|
||||||
</div>
|
|
||||||
{node.outputs.length ? <>
|
|
||||||
<h4>Outputs</h4>
|
|
||||||
<div className={styles.small}>
|
|
||||||
{node.outputs.map((input) => <EntityIcon key={input} value={input} />)}
|
|
||||||
</div>
|
|
||||||
</>: null}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)))}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
20
components/shared/ProducingGraph/ProducingGraph.module.css
Normal file
20
components/shared/ProducingGraph/ProducingGraph.module.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.plane {
|
||||||
|
border: 1px solid red;
|
||||||
|
padding: 2em;
|
||||||
|
width: fit-content;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #DDDDDD;
|
||||||
|
background-color: #EEE;
|
||||||
|
}
|
||||||
71
components/shared/ProducingGraph/ProducingGraph.tsx
Normal file
71
components/shared/ProducingGraph/ProducingGraph.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import {FC, HTMLProps, PropsWithChildren, useMemo} 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
|
||||||
|
|
||||||
|
interface Props<T extends {}> {
|
||||||
|
nodes: GraphNode<T>[]
|
||||||
|
inputs: string[]
|
||||||
|
outputs?: string[]
|
||||||
|
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([])
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}, [inputs, nodes])
|
||||||
|
|
||||||
|
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}>
|
||||||
|
{
|
||||||
|
row.map((node) => <ChildType
|
||||||
|
className={styles.node}
|
||||||
|
key={node.name}
|
||||||
|
node={node}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
{outputs ? <div className={styles.row}>
|
||||||
|
{outputs.map((input, idx) => <EntityIcon className={styles.input} key={input} value={input} />)}
|
||||||
|
</div> : null }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
5
components/visualize/NodeDetails/NodeDetails.module.css
Normal file
5
components/visualize/NodeDetails/NodeDetails.module.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.root {
|
||||||
|
height: fit-content;
|
||||||
|
width: fit-content;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
21
components/visualize/NodeDetails/NodeDetails.tsx
Normal file
21
components/visualize/NodeDetails/NodeDetails.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import {FC, HTMLProps} from "react";
|
||||||
|
import {GraphNode} from "../../shared/ProducingGraph/ProducingGraph";
|
||||||
|
import {Recipe} from "../../../src/types";
|
||||||
|
import cx from "classnames";
|
||||||
|
import styles from "./NodeDetails.module.css";
|
||||||
|
import {RecipeSpan} from "../../home/Recipe/Recipe";
|
||||||
|
|
||||||
|
export type DetailGraphNode = GraphNode<{
|
||||||
|
recipes: Recipe[]
|
||||||
|
}>
|
||||||
|
|
||||||
|
interface Props extends HTMLProps<HTMLDivElement> {
|
||||||
|
node: DetailGraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NodeDetails: FC<Props> = ({node, className, ...props}) => {
|
||||||
|
return <div {...props} className={cx(className, styles.root)}>
|
||||||
|
<h3>{node.name}</h3>
|
||||||
|
{node.recipes.map((recipe, idx) => <RecipeSpan key={idx} recipe={recipe}/>)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
18
components/visualize/NodeOverview/NodeOverview.module.css
Normal file
18
components/visualize/NodeOverview/NodeOverview.module.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.root {
|
||||||
|
height: fit-content;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiny {
|
||||||
|
font-size: 0.5em;
|
||||||
|
margin-top: -1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkOut {
|
||||||
|
margin-inline-end: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
36
components/visualize/NodeOverview/NodeOverview.tsx
Normal file
36
components/visualize/NodeOverview/NodeOverview.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {FC, HTMLProps} from "react";
|
||||||
|
import {GraphNode} from "../../shared/ProducingGraph/ProducingGraph";
|
||||||
|
import {EnrichedEntity, Recipe} from "../../../src/types";
|
||||||
|
import cx from "classnames";
|
||||||
|
import styles from "./NodeOverview.module.css";
|
||||||
|
import {RecipeSpan} from "../../home/Recipe/Recipe";
|
||||||
|
import Link from "next/link";
|
||||||
|
import {EntityIcon} from "../../home/EntityIcon/EntityIcon";
|
||||||
|
|
||||||
|
export type OverviewGraphNode = GraphNode<{
|
||||||
|
icons: (EnrichedEntity|string)[]
|
||||||
|
linkOut: string
|
||||||
|
}>
|
||||||
|
|
||||||
|
interface Props extends HTMLProps<HTMLDivElement> {
|
||||||
|
node: OverviewGraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NodeOverview: FC<Props> = ({node, className, ...props}) => {
|
||||||
|
return <div {...props} className={cx(className, styles.root)}>
|
||||||
|
<h3><span className={styles.linkOut}><Link href={node.linkOut}>🔗</Link></span>{node.name}</h3>
|
||||||
|
{ node.icons?.length ? <div className={styles.tiny}>
|
||||||
|
{node.icons.map((input) => <EntityIcon key={typeof input === "string" ? input : input.href} value={input} />)}
|
||||||
|
</div> : null }
|
||||||
|
<h4>Inputs</h4>
|
||||||
|
<div className={styles.small}>
|
||||||
|
{node.inputs.map((input) => <EntityIcon key={input} value={input} />)}
|
||||||
|
</div>
|
||||||
|
{node.outputs.length ? <>
|
||||||
|
<h4>Outputs</h4>
|
||||||
|
<div className={styles.small}>
|
||||||
|
{node.outputs.map((input) => <EntityIcon key={input} value={input} />)}
|
||||||
|
</div>
|
||||||
|
</>: null}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
import {GroupProvider} from "../components/contexts/GroupProvider";
|
import {GroupProvider} from "../components/contexts/GroupProvider";
|
||||||
|
import {FC} from "react";
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
const MyApp: FC<AppProps> = ({ Component, pageProps }) => {
|
||||||
return <GroupProvider>
|
return <GroupProvider>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</GroupProvider>
|
</GroupProvider>
|
||||||
|
|||||||
17
pages/_document.tsx
Normal file
17
pages/_document.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import {Html, Main, NextScript, DocumentProps, Head} from 'next/document';
|
||||||
|
import {FC} from "react";
|
||||||
|
|
||||||
|
const MyDocument: FC<DocumentProps> = ({ __NEXT_DATA__ }) => {
|
||||||
|
const pageProps: Record<string, unknown>|undefined = __NEXT_DATA__?.props?.pageProps;
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<body className={pageProps?.bodyClassName as string}>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyDocument
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
import type { NextPage } from 'next'
|
import type { NextPage } from 'next'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import {Home} from "../components/home/Home";
|
import {Home} from "../components/home/Home";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
|
||||||
const Page: NextPage = () => {
|
const Page: NextPage = () => {
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
document.body.classList.add("scroll");
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Factorio Microservices</title>
|
<title>Factorio Microservices</title>
|
||||||
<meta name="description" content="Create Factorio microservices" />
|
<meta name="description" content="Create Factorio microservices" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<Home/>
|
<Home/>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ import type { NextPage } from 'next'
|
|||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import {useGroups} from "../../components/contexts/GroupProvider";
|
import {useGroups} from "../../components/contexts/GroupProvider";
|
||||||
import {useFactories} from "../../src/hooks/useFactories";
|
import {useFactories} from "../../src/hooks/useFactories";
|
||||||
import {ProducingGraph, ProducingNode} from "../../components/shared/ProducingGraph";
|
import {GraphNode, ProducingGraph} from "../../components/shared/ProducingGraph/ProducingGraph";
|
||||||
import {useMemo} from "react";
|
import {useMemo} from "react";
|
||||||
import {calculateInputs} from "../../src/calculateInputs";
|
import {calculateInputs} from "../../src/calculateInputs";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
import {isNonNullable} from "../../src/utils";
|
import {groupBy, isNonNullable} from "../../src/utils";
|
||||||
import {EnrichedEntity} from "../../src/types";
|
import {EnrichedEntity, Recipe} from "../../src/types";
|
||||||
|
import {DetailGraphNode, NodeDetails} from "../../components/visualize/NodeDetails/NodeDetails";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Page: NextPage = () => {
|
const Page: NextPage = () => {
|
||||||
const {query: {name}} = useRouter()
|
const {query: {name}} = useRouter()
|
||||||
@@ -34,21 +37,29 @@ const Page: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}, [baseFactories, exportedFactories, findFactory, group, ignoredFactories])
|
}, [baseFactories, exportedFactories, findFactory, group, ignoredFactories])
|
||||||
|
|
||||||
const producingNodes: ProducingNode[] = useMemo(() => {
|
const producingNodes: DetailGraphNode[] = useMemo(() => {
|
||||||
if (!group) return []
|
if (!group) return []
|
||||||
return Array.from(new Set([...intermediateFactories, ...group.exports, ...group.malls]))
|
const nodes = Array.from(new Set([...intermediateFactories, ...group.exports, ...group.malls]))
|
||||||
.map(findFactory)
|
.map(findFactory)
|
||||||
.filter(isNonNullable)
|
.filter(isNonNullable)
|
||||||
.map((factory: EnrichedEntity) => ({
|
.map((factory: EnrichedEntity) => ({
|
||||||
inputs: Object.keys(factory.recipe?.prerequisites ?? {}).sort((a, b) => a.localeCompare(b)),
|
inputs: Object.keys(factory.recipe?.prerequisites ?? {}).sort((a, b) => a.localeCompare(b)),
|
||||||
outputs: Object.keys(factory.recipe?.output ?? {}).sort((a, b) => a.localeCompare(b)),
|
outputs: Object.keys(factory.recipe?.output ?? {}).sort((a, b) => a.localeCompare(b)),
|
||||||
name: factory.name,
|
name: factory.name,
|
||||||
recipe: factory.recipe
|
recipes: [factory.recipe]
|
||||||
}))
|
} as DetailGraphNode))
|
||||||
|
return Object
|
||||||
|
.values(groupBy(nodes, node => node.inputs.join()))
|
||||||
|
.map(nodesOfInput => ({
|
||||||
|
inputs: nodesOfInput[0].inputs,
|
||||||
|
outputs: Array.from(new Set(nodesOfInput.flatMap(node => node.outputs))),
|
||||||
|
name: nodesOfInput.map(node => node.name).join(', '),
|
||||||
|
recipes: nodesOfInput.flatMap(node => node.recipes)
|
||||||
|
} as DetailGraphNode))
|
||||||
}, [findFactory, group, intermediateFactories])
|
}, [findFactory, group, intermediateFactories])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Factorio Microservices</title>
|
<title>Factorio Microservices</title>
|
||||||
<meta name="description" content="Create Factorio microservices" />
|
<meta name="description" content="Create Factorio microservices" />
|
||||||
@@ -56,10 +67,19 @@ const Page: NextPage = () => {
|
|||||||
</Head>
|
</Head>
|
||||||
<main>
|
<main>
|
||||||
<h1>Factorio Microservices</h1>
|
<h1>Factorio Microservices</h1>
|
||||||
<ProducingGraph nodes={producingNodes} inputs={inputFactories}></ProducingGraph>
|
<ProducingGraph
|
||||||
|
nodes={producingNodes}
|
||||||
|
inputs={inputFactories}
|
||||||
|
outputs={group?.exports}
|
||||||
|
childType={NodeDetails}
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getServerSideProps() {
|
||||||
|
return { props: { bodyClassName: 'scroll' } };
|
||||||
|
}
|
||||||
|
|
||||||
export default Page
|
export default Page
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import type { NextPage } from 'next'
|
|||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import {useGroups} from "../../components/contexts/GroupProvider";
|
import {useGroups} from "../../components/contexts/GroupProvider";
|
||||||
import {useFactories} from "../../src/hooks/useFactories";
|
import {useFactories} from "../../src/hooks/useFactories";
|
||||||
import {ProducingGraph, ProducingNode} from "../../components/shared/ProducingGraph";
|
import {GraphNode, ProducingGraph} from "../../components/shared/ProducingGraph/ProducingGraph";
|
||||||
import {useMemo} from "react";
|
import {useMemo} from "react";
|
||||||
import {calculateInputs} from "../../src/calculateInputs";
|
import {calculateInputs} from "../../src/calculateInputs";
|
||||||
|
import {NodeOverview, OverviewGraphNode} from "../../components/visualize/NodeOverview/NodeOverview";
|
||||||
|
|
||||||
const Page: NextPage = () => {
|
const Page: NextPage = () => {
|
||||||
const {
|
const {
|
||||||
@@ -17,7 +18,7 @@ const Page: NextPage = () => {
|
|||||||
findFactory
|
findFactory
|
||||||
} = useFactories()
|
} = useFactories()
|
||||||
|
|
||||||
const producingNodes: ProducingNode[] = useMemo(() => {
|
const producingNodes: OverviewGraphNode[] = useMemo(() => {
|
||||||
return Object.values(groups).map(group => ({
|
return Object.values(groups).map(group => ({
|
||||||
inputs: calculateInputs(
|
inputs: calculateInputs(
|
||||||
[...group.exports, ...group.malls],
|
[...group.exports, ...group.malls],
|
||||||
@@ -34,7 +35,7 @@ const Page: NextPage = () => {
|
|||||||
}, [baseFactories, exportedFactories, findFactory, groups, ignoredFactories])
|
}, [baseFactories, exportedFactories, findFactory, groups, ignoredFactories])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Factorio Microservices</title>
|
<title>Factorio Microservices</title>
|
||||||
<meta name="description" content="Create Factorio microservices" />
|
<meta name="description" content="Create Factorio microservices" />
|
||||||
@@ -42,12 +43,16 @@ const Page: NextPage = () => {
|
|||||||
</Head>
|
</Head>
|
||||||
<main>
|
<main>
|
||||||
<h1>Factorio Microservices</h1>
|
<h1>Factorio Microservices</h1>
|
||||||
<ProducingGraph nodes={producingNodes} inputs={baseFactories}></ProducingGraph>
|
<ProducingGraph nodes={producingNodes} inputs={baseFactories} childType={NodeOverview}></ProducingGraph>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps() {
|
||||||
|
return { props: { bodyClassName: 'scroll' } };
|
||||||
|
}
|
||||||
|
|
||||||
function fixedEncodeURIComponent(str: string): string {
|
function fixedEncodeURIComponent(str: string): string {
|
||||||
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16));
|
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16));
|
||||||
}
|
}
|
||||||
|
|||||||
75
res/manual.json
Normal file
75
res/manual.json
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Empty barrel",
|
||||||
|
"href": "/Empty_barrel",
|
||||||
|
"image": "/images/thumb/Empty_barrel.png/32px-Empty_barrel.png",
|
||||||
|
"recipe": {
|
||||||
|
"prerequisites": {
|
||||||
|
"/Steel_plate": 1
|
||||||
|
},
|
||||||
|
"time": 1,
|
||||||
|
"output": {
|
||||||
|
"/Empty_barrel": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Light oil",
|
||||||
|
"href": "/Light_oil",
|
||||||
|
"image": "/images/thumb/Light_oil.png/32px-Light_oil.png",
|
||||||
|
"recipe": {
|
||||||
|
"prerequisites": {
|
||||||
|
"/Heavy_oil": 40,
|
||||||
|
"/Water": 30
|
||||||
|
},
|
||||||
|
"time": 2,
|
||||||
|
"output": {
|
||||||
|
"/Light_oil": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Heavy oil",
|
||||||
|
"href": "/Heavy_oil",
|
||||||
|
"image": "/images/thumb/Heavy_oil.png/32px-Heavy_oil.png",
|
||||||
|
"recipe": {
|
||||||
|
"prerequisites": {
|
||||||
|
"/Crude_oil": 100,
|
||||||
|
"/Water": 50
|
||||||
|
},
|
||||||
|
"time": 5,
|
||||||
|
"output": {
|
||||||
|
"/Heavy_oil": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Petroleum gas",
|
||||||
|
"href": "/Petroleum_gas",
|
||||||
|
"image": "/images/thumb/Petroleum_gas.png/32px-Petroleum_gas.png",
|
||||||
|
"recipe": {
|
||||||
|
"prerequisites": {
|
||||||
|
"/Light_oil": 30,
|
||||||
|
"/Water": 50
|
||||||
|
},
|
||||||
|
"time": 2,
|
||||||
|
"output": {
|
||||||
|
"/Petroleum_gas": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Solid fuel",
|
||||||
|
"href": "/Solid_fuel",
|
||||||
|
"image": "/images/thumb/Solid_fuel.png/32px-Solid_fuel.png",
|
||||||
|
"recipe": {
|
||||||
|
"prerequisites": {
|
||||||
|
"/Light_oil": 10
|
||||||
|
},
|
||||||
|
"time": 2,
|
||||||
|
"output": {
|
||||||
|
"/Solid_fuel": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import {EnrichedEntity, Entity} from "../types";
|
import {EnrichedEntity, Entity} from "../types";
|
||||||
import details from "../../res/details.json";
|
import details from "../../res/details.json";
|
||||||
|
import manual from "../../res/manual.json";
|
||||||
|
|
||||||
const factories = (details as Entity[]).map((detail: EnrichedEntity) => {
|
const joined = [...details, ...manual] as Entity[]
|
||||||
detail.usedBy = (details as Entity[])
|
|
||||||
|
const factories = joined.map((detail: EnrichedEntity) => {
|
||||||
|
detail.usedBy = joined
|
||||||
.filter(f => Object
|
.filter(f => Object
|
||||||
.keys(f.recipe?.prerequisites ?? {})
|
.keys(f.recipe?.prerequisites ?? {})
|
||||||
.includes(detail.href)
|
.includes(detail.href)
|
||||||
|
|||||||
11
src/utils.ts
11
src/utils.ts
@@ -1,3 +1,5 @@
|
|||||||
|
import {FC, PropsWithChildren} from "react";
|
||||||
|
|
||||||
export function isNonNullable<T>(any: T): any is NonNullable<T> {
|
export function isNonNullable<T>(any: T): any is NonNullable<T> {
|
||||||
return any !== undefined && any !== null
|
return any !== undefined && any !== null
|
||||||
}
|
}
|
||||||
@@ -10,3 +12,12 @@ export function sortByProperty<T>(transform: (val: T) => number | string): (a: T
|
|||||||
return a2 === b2 ? 0 : -1
|
return a2 === b2 ? 0 : -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function groupBy<T>(arr: T[], transform: (val: T) => string): Record<string, T[]> {
|
||||||
|
const result: Record<string, T[]> = {}
|
||||||
|
for (const elem of arr) {
|
||||||
|
const key = transform(elem)
|
||||||
|
result[key] = [...(result[key] ?? []), elem]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ body {
|
|||||||
padding: 2em;
|
padding: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.scroll {
|
||||||
|
overflow: auto;
|
||||||
|
overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user