diff --git a/dist/index.html b/dist/index.html index c35907a..863ea44 100644 --- a/dist/index.html +++ b/dist/index.html @@ -112,7 +112,7 @@ <h2 class="title">Select a recipe</h2> <div class="help"> <p>N - create new node</p> - <p>Alt + Mouse drag - move</p> + <p>Alt (Option) + Mouse drag - move</p> <p>Mouse drag - move node under cursor</p> <p>Mouse wheel - zoom</p> <p>LMB on slot - connect slot</p> diff --git a/dist/style.css b/dist/style.css index f465301..d09554f 100644 --- a/dist/style.css +++ b/dist/style.css @@ -165,6 +165,88 @@ div.help { fill: #74ce73; } +/* Recipe display on a node */ + +.node foreignObject { + pointer-events: none; +} + +.node foreignObject .recipe-container { + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: center; + + height: 100%; + + gap: 2px; + + padding: 2px; +} + +.node .recipe-container * { + font-size: 10px; +} + +.node .recipe-container div.property { + display: flex; + flex-direction: column; + + gap: 1px; +} + +.node .recipe-container .property>div { + display: flex; + justify-content: center; + align-items: center; + + background-color: var(--dimming); + + padding: 2px 4px; +} + +.node .recipe-container .property>div:first-child { + border-radius: 4px 4px 0 0; +} + +.node .recipe-container .property>div:last-child { + border-radius: 0 0 4px 4px; +} + +.node .recipe-container .property div.title { + text-align: center; + + font-weight: 500; +} + +.node .recipe-container .property div.text { + text-align: center; +} + +.node .recipe-container .property div.resource { + display: flex; + + gap: 4px; +} + +.node .recipe-container .property .resource img.icon { + width: 16px; + height: 16px; + + min-width: 0; +} + +.node .recipe-container .property .resource p.amount { + margin: 0; +} + +.node .recipe-container .property .machine img.icon { + width: 20px; + height: 20px; + + min-width: 0; +} + /* Node links */ line.link-hint { diff --git a/src/SVG/SvgFactory.ts b/src/SVG/SvgFactory.ts index b08202d..4bf243b 100644 --- a/src/SVG/SvgFactory.ts +++ b/src/SVG/SvgFactory.ts @@ -43,6 +43,13 @@ export abstract class SvgFactory return result; } + public static createSvgForeignObject(...classes: string[]): SVGForeignObjectElement + { + let result = this.createSvgElement("foreignObject", ...classes) as SVGForeignObjectElement; + + return result; + } + private static createSvgElement(tag: string, ...classes: string[]): SVGElement { let result = document.createElementNS("http://www.w3.org/2000/svg", tag); diff --git a/src/Sankey/SankeyNode.ts b/src/Sankey/SankeyNode.ts index e7c8484..094949e 100644 --- a/src/Sankey/SankeyNode.ts +++ b/src/Sankey/SankeyNode.ts @@ -3,14 +3,17 @@ import { SankeySlot } from "./Slots/SankeySlot"; import { SlotsGroup } from "./SlotsGroup"; import { SvgFactory } from "../SVG/SvgFactory"; import { GameRecipe } from "../GameData/GameRecipe"; -import { GameMachine } from "../GameData/GameMachine"; + +// Ignore import error as the file only appears on launch of the exporting tool. +// @ts-ignore +import satisfactoryData from '../../dist/GameData/Satisfactory.json'; export class SankeyNode { public nodeSvg: SVGElement; public nodeSvgGroup: SVGGElement; - public static readonly nodeHeight = 240; + public static readonly nodeHeight = 260; public static readonly nodeWidth = 60; constructor( @@ -73,7 +76,113 @@ export class SankeyNode nextOutputGroupY += newGroup.maxHeight; } + let foreignObject = SvgFactory.createSvgForeignObject(); + + foreignObject.setAttribute("x", "10"); + foreignObject.setAttribute("y", "0"); + foreignObject.setAttribute("width", `${SankeyNode.nodeWidth}`); + foreignObject.setAttribute("height", `${SankeyNode.nodeHeight}`); + + + let recipeContainer = document.createElement("div"); + recipeContainer.classList.add("recipe-container"); + + + let recipeNameProp = document.createElement("div"); + recipeNameProp.classList.add("property"); + + let recipeNameTitle = document.createElement("div"); + recipeNameTitle.classList.add("title"); + recipeNameTitle.innerText = "Recipe"; + + let recipeNameText = document.createElement("div"); + recipeNameText.classList.add("text"); + + + let recipeInputsProp = document.createElement("div"); + recipeInputsProp.classList.add("property"); + + let recipeInputsTitle = document.createElement("div"); + recipeInputsTitle.classList.add("title"); + recipeInputsTitle.innerText = "Input/min"; + + + let recipeOutputsProp = document.createElement("div"); + recipeOutputsProp.classList.add("property"); + + let recipeOutputsTitle = document.createElement("div"); + recipeOutputsTitle.classList.add("title"); + recipeOutputsTitle.innerText = "Output/min"; + + + + recipeNameProp.appendChild(recipeNameTitle); + recipeNameProp.appendChild(recipeNameText); + + recipeInputsProp.appendChild(recipeInputsTitle); + + recipeOutputsProp.appendChild(recipeOutputsTitle); + + recipeContainer.appendChild(recipeNameProp); + recipeContainer.appendChild(recipeInputsProp); + recipeContainer.appendChild(recipeOutputsProp); + + foreignObject.appendChild(recipeContainer); + + + + let createResourceDisplay = (parentDiv: HTMLDivElement, craftingTime: number) => + { + return (recipeResource: RecipeResource) => + { + let resource = satisfactoryData.resources.find( + (el: typeof satisfactoryData.resources[0]) => + { + return el.id === recipeResource.id; + } + ); + + let resourceDiv = document.createElement("div"); + resourceDiv.classList.add("resource"); + + let icon = document.createElement("img"); + icon.classList.add("icon"); + icon.loading = "lazy"; + icon.alt = resource!.displayName; + icon.src = `GameData/SatisfactoryIcons/${resource!.iconPath}`; + icon.title = resource!.displayName; + + let amount = document.createElement("p"); + amount.classList.add("amount"); + amount.innerText = `${+((60 / craftingTime) * recipeResource.amount).toPrecision(3)}`; + + resourceDiv.appendChild(icon); + resourceDiv.appendChild(amount); + parentDiv.appendChild(resourceDiv); + }; + }; + + + + recipeNameText.innerText = recipe.displayName; + + // let selectedRecipeMachine = document.querySelector("#selected-recipe-machine>div.machine>img.icon") as HTMLImageElement; + // selectedRecipeMachine.src = `GameData/SatisfactoryIcons/${machine.iconPath}`; + // selectedRecipeMachine.title = machine.displayName; + + recipe.ingredients.forEach(createResourceDisplay(recipeInputsProp, recipe.manufacturingDuration)); + + recipe.products.forEach(createResourceDisplay(recipeOutputsProp, recipe.manufacturingDuration)); + + // let selectedRecipePower = document.querySelector("#selected-recipe-power>div.text") as HTMLDivElement; + // selectedRecipePower.innerText = `${machine.powerConsumption} MW`; + + + this.nodeSvgGroup.appendChild(this.nodeSvg); + + this.nodeSvgGroup.appendChild(foreignObject); + parentGroup.appendChild(this.nodeSvgGroup); } diff --git a/src/main.ts b/src/main.ts index 93fc369..38070cd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,11 +2,11 @@ import panzoom from "panzoom"; import { SankeyNode } from "./Sankey/SankeyNode"; import { Point } from "./Point"; import { MouseHandler } from "./MouseHandler"; +import { GameRecipe, GameRecipeEvent } from "./GameData/GameRecipe"; // Ignore import error as the file only appears on launch of the exporting tool. // @ts-ignore import satisfactoryData from '../dist/GameData/Satisfactory.json'; -import { GameRecipe, GameRecipeEvent } from "./GameData/GameRecipe"; async function main() {