From ef1fa2acf874f93760e8bce3c2078e95161d6660 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 30 Aug 2025 10:38:23 -0400 Subject: [PATCH 1/6] feat: link double-click to `create suitable node` menu during connection creation, instead of using the normal menu --- src/main.ts | 216 ++++++++++++++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 110 deletions(-) diff --git a/src/main.ts b/src/main.ts index b109a55..e72ae74 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,22 +1,23 @@ -import { SankeyNode } from "./Sankey/SankeyNode"; -import { Point } from "./Geometry/Point"; -import { MouseHandler } from "./MouseHandler"; -import { GameRecipe } from "./GameData/GameRecipe"; -import { GameMachine } from "./GameData/GameMachine"; -import { Settings } from "./Settings"; -import { CanvasContextMenu } from "./ContextMenu/CanvasContextMenu"; -import { ResourcesSummary } from "./ResourcesSummary"; -import { PanZoomConfiguration } from "./PanZoomConfiguration"; -import { SvgIcons } from './DomUtils/SvgIcons'; -import { HelpModal } from './HelpWindow/HelpModal'; -import { RecipeSelectionModal } from './RecipeSelectionModal'; -import { CanvasGrid } from "./CanvasGrid"; -import { AppData } from "./DataSaves/AppData"; -import { loadSatisfactoryResource, loadSingleSatisfactoryRecipe } from "./GameData/GameData"; -import { SankeyLink } from "./Sankey/SankeyLink"; -import { SlotsGroup } from "./Sankey/SlotsGroup"; -import { SavesLoaderMenu } from "./DataSaves/SavesLoaderMenu"; -import { HtmlUtils } from "./DomUtils/HtmlUtils"; +import {SankeyNode} from "./Sankey/SankeyNode"; +import {Point} from "./Geometry/Point"; +import {MouseHandler} from "./MouseHandler"; +import {GameRecipe} from "./GameData/GameRecipe"; +import {GameMachine} from "./GameData/GameMachine"; +import {Settings} from "./Settings"; +import {CanvasContextMenu} from "./ContextMenu/CanvasContextMenu"; +import {ResourcesSummary} from "./ResourcesSummary"; +import {PanZoomConfiguration} from "./PanZoomConfiguration"; +import {SvgIcons} from './DomUtils/SvgIcons'; +import {HelpModal} from './HelpWindow/HelpModal'; +import {RecipeSelectionModal} from './RecipeSelectionModal'; +import {CanvasGrid} from "./CanvasGrid"; +import {AppData} from "./DataSaves/AppData"; +import {loadSatisfactoryResource, loadSingleSatisfactoryRecipe} from "./GameData/GameData"; +import {SankeyLink} from "./Sankey/SankeyLink"; +import {SavesLoaderMenu} from "./DataSaves/SavesLoaderMenu"; +import {HtmlUtils} from "./DomUtils/HtmlUtils"; +import {SankeySlotMissing} from "./Sankey/Slots/SankeySlotMissing"; +import {SankeySlotExceeding} from "./Sankey/Slots/SankeySlotExceeding"; async function main() { @@ -101,7 +102,7 @@ async function main() } return node; - }; + } recipeSelectionModal.addEventListener(RecipeSelectionModal.recipeConfirmedEvent, () => { @@ -192,10 +193,86 @@ async function main() } }); + function createSuitableNode( + slot: SankeySlotMissing | SankeySlotExceeding | undefined, + position: Point, + mouseStatus: MouseHandler.MouseStatus + ) { + // Helper to open the recipe selection modal with only options that make sense for the currently creating connection + + if (!slot) return; + + let type: "input" | "output"; + if (mouseStatus === MouseHandler.MouseStatus.ConnectingInputSlot) { + type = "output"; + } else if (mouseStatus === MouseHandler.MouseStatus.ConnectingOutputSlot) { + type = "input"; + } else { + return; // Not linking + } + + nodeCreationPosition = position; + + let suitableRecipe = loadSingleSatisfactoryRecipe({ id: slot.resourceId, type }); + + onceNodeCreated = (node: SankeyNode) => { + let resourcesAmount = slot.resourcesAmount; + let group = type === "input" + ? node.inputSlotGroups.find(g => g.resourceId === slot.resourceId) + : node.outputSlotGroups.find(g => g.resourceId === slot.resourceId); + + if (!group) return; + + node.machinesAmount = resourcesAmount / group.resourcesAmount; + + let newSlot1 = slot.splitOffSlot(resourcesAmount); + + if (type === "input") { + let newSlot2 = node.addInputSlot(slot.resourceId, resourcesAmount); + SankeyLink.connect(newSlot1, newSlot2); + } else { + let newSlot2 = node.addOutputSlot(slot.resourceId, resourcesAmount); + SankeyLink.connect(newSlot1, newSlot2); + } + }; + + if (suitableRecipe) { + createNode(suitableRecipe.recipe, suitableRecipe.machine); + } else { + recipeSelectionModal.openWithSearch( + loadSatisfactoryResource(slot.resourceId).displayName, + { + ingredients: type === "input", + products: type === "output", + recipeNames: false, + exactMatch: true, + } + ); + } + + MouseHandler.getInstance().cancelConnectingSlots(); + } + canvas.addEventListener("dblclick", (event) => { - let nodePosition = { x: event.clientX, y: event.clientY }; - openNodeCreation(MouseHandler.clientToCanvasPosition(nodePosition)); + const mouseHandler = MouseHandler.getInstance(); + let canvasPos = MouseHandler.clientToCanvasPosition({ x: event.clientX, y: event.clientY }); + + if (mouseHandler.firstConnectingSlot !== undefined && + (mouseHandler.mouseStatus === MouseHandler.MouseStatus.ConnectingInputSlot || + mouseHandler.mouseStatus === MouseHandler.MouseStatus.ConnectingOutputSlot)) + { + // Creating a connection, so use the menu thats only the valid options + createSuitableNode( + mouseHandler.firstConnectingSlot, + canvasPos, + mouseHandler.mouseStatus + ); + } + else { + // Not connecting, use normal node creation thing with all options + openNodeCreation(canvasPos); + } }); let canvasContextMenu = new CanvasContextMenu(canvas); @@ -250,96 +327,15 @@ async function main() canvasContextMenu.addEventListener(CanvasContextMenu.nodeFromLinkOptionClickedEvent, () => { let slot = MouseHandler.getInstance().firstConnectingSlot; + let pos = canvasContextMenu.openingPosition; - if (slot == undefined) - { - return; - } - - let contextMenuPos = canvasContextMenu.openingPosition; - - if (contextMenuPos == undefined) - { - throw Error("Context menu position undefined"); - } - - contextMenuPos = MouseHandler.clientToCanvasPosition(contextMenuPos); - - let type: "input" | "output"; + if (!pos) throw Error("Context menu position undefined"); - if (MouseHandler.getInstance().mouseStatus === MouseHandler.MouseStatus.ConnectingInputSlot) - { - type = "output"; - } - else if (MouseHandler.getInstance().mouseStatus === MouseHandler.MouseStatus.ConnectingOutputSlot) - { - type = "input"; - } - else - { - return; - } - - nodeCreationPosition = contextMenuPos; - - let suitableRecipe = loadSingleSatisfactoryRecipe({ id: slot.resourceId, type: type }); - - onceNodeCreated = (node: SankeyNode) => - { - let resourcesAmount = slot.resourcesAmount; - - let group: SlotsGroup | undefined; - - if (type === "input") - { - group = node.inputSlotGroups.find(group => group.resourceId === slot.resourceId); - } - else - { - group = node.outputSlotGroups.find(group => group.resourceId === slot.resourceId); - } - - if (group == undefined) - { - return; - } - - let resourcesMultiplier = resourcesAmount / group.resourcesAmount; - - node.machinesAmount = resourcesMultiplier; - - let newSlot1 = slot.splitOffSlot(resourcesAmount); - - if (type === "input") - { - let newSlot2 = node.addInputSlot(slot.resourceId, resourcesAmount); - SankeyLink.connect(newSlot1, newSlot2); - } - else - { - let newSlot2 = node.addOutputSlot(slot.resourceId, resourcesAmount); - SankeyLink.connect(newSlot1, newSlot2); - } - }; - - if (suitableRecipe != undefined) - { - createNode(suitableRecipe.recipe, suitableRecipe.machine); - } - else - { - recipeSelectionModal.openWithSearch( - loadSatisfactoryResource(slot.resourceId).displayName, - { - ingredients: type === "input", - products: type === "output", - recipeNames: false, - exactMatch: true, - } - ); - } - - MouseHandler.getInstance().cancelConnectingSlots(); + createSuitableNode( + slot, + MouseHandler.clientToCanvasPosition(pos), + MouseHandler.getInstance().mouseStatus + ); }); window.addEventListener("keypress", (event) => From ee607394d34f1cf02e7be405cdc40a15725972d6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 30 Aug 2025 10:39:51 -0400 Subject: [PATCH 2/6] Fix typo in a comment. --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index e72ae74..904d1d0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -262,7 +262,7 @@ async function main() (mouseHandler.mouseStatus === MouseHandler.MouseStatus.ConnectingInputSlot || mouseHandler.mouseStatus === MouseHandler.MouseStatus.ConnectingOutputSlot)) { - // Creating a connection, so use the menu thats only the valid options + // Creating a connection, so use the menu that's only the valid options createSuitableNode( mouseHandler.firstConnectingSlot, canvasPos, From bfbe5dd24b372d2fb503b09a05d7fb09a1813e3e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 30 Aug 2025 10:47:25 -0400 Subject: [PATCH 3/6] feat: normal node creation at mouse position rather than center of screen --- src/main.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main.ts b/src/main.ts index 904d1d0..f387b9c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -54,6 +54,8 @@ async function main() let recipeSelectionModal = new RecipeSelectionModal(); let nodeCreationPosition: Point; + let lastMousePos = { x: window.innerWidth / 2, y: window.innerHeight / 2 }; + let registerNode = (node: SankeyNode) => { node.nodeSvg.onmousedown = (event) => @@ -116,14 +118,16 @@ async function main() onceNodeCreated = undefined; }); - function openNodeCreation(nodePosition?: Point) - { + function openNodeCreation(nodePosition?: Point) { let pageCenter = { x: document.documentElement.clientWidth / 2, y: document.documentElement.clientHeight / 2 }; - nodeCreationPosition = nodePosition ?? MouseHandler.clientToCanvasPosition(pageCenter); + // Prefer mouse pos over center + let preferredPosition = nodePosition ?? lastMousePos ?? pageCenter; + + nodeCreationPosition = MouseHandler.clientToCanvasPosition(preferredPosition); recipeSelectionModal.openModal(); } @@ -372,6 +376,10 @@ async function main() window.addEventListener("mouseup", () => MouseHandler.getInstance().handleMouseUp()); window.addEventListener("touchend", () => MouseHandler.getInstance().handleMouseUp()); window.addEventListener("mousemove", e => MouseHandler.getInstance().handleMouseMove(e)); + window.addEventListener("mousemove", (event) => { + lastMousePos.x = event.clientX; + lastMousePos.y = event.clientY; + }); window.addEventListener("touchmove", e => MouseHandler.getInstance().handleTouchMove(e)); AppData.instance.addEventListener(AppData.dataLoadedEvent, () => From 32e1883149063f7cacdd5bf094e3870a0545a508 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 30 Aug 2025 10:54:47 -0400 Subject: [PATCH 4/6] feat: node creation with `n` key now uses suitable node menu where relevant --- src/main.ts | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/main.ts b/src/main.ts index f387b9c..05c03f5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,6 +19,15 @@ import {HtmlUtils} from "./DomUtils/HtmlUtils"; import {SankeySlotMissing} from "./Sankey/Slots/SankeySlotMissing"; import {SankeySlotExceeding} from "./Sankey/Slots/SankeySlotExceeding"; +function checkIfConnecting() { + // Helper to check if a connection is being created + const mouseHandler = MouseHandler.getInstance(); + + return mouseHandler.firstConnectingSlot !== undefined && + (mouseHandler.mouseStatus === MouseHandler.MouseStatus.ConnectingInputSlot || + mouseHandler.mouseStatus === MouseHandler.MouseStatus.ConnectingOutputSlot); +} + async function main() { SvgIcons.replaceAllPlaceholders(); @@ -54,7 +63,7 @@ async function main() let recipeSelectionModal = new RecipeSelectionModal(); let nodeCreationPosition: Point; - let lastMousePos = { x: window.innerWidth / 2, y: window.innerHeight / 2 }; + let lastMousePos: Point = { x: window.innerWidth / 2, y: window.innerHeight / 2 }; let registerNode = (node: SankeyNode) => { @@ -262,9 +271,7 @@ async function main() const mouseHandler = MouseHandler.getInstance(); let canvasPos = MouseHandler.clientToCanvasPosition({ x: event.clientX, y: event.clientY }); - if (mouseHandler.firstConnectingSlot !== undefined && - (mouseHandler.mouseStatus === MouseHandler.MouseStatus.ConnectingInputSlot || - mouseHandler.mouseStatus === MouseHandler.MouseStatus.ConnectingOutputSlot)) + if (checkIfConnecting()) { // Creating a connection, so use the menu that's only the valid options createSuitableNode( @@ -364,7 +371,21 @@ async function main() if (event.code === "KeyN" && !anyOverlayOpened) { - openNodeCreation(); + const mouseHandler = MouseHandler.getInstance(); + + if (checkIfConnecting()) + { + // Creating a connection, so use the menu that's only the valid options + createSuitableNode( + mouseHandler.firstConnectingSlot, + lastMousePos, + mouseHandler.mouseStatus + ); + } + else { + // Not connecting, use normal node creation thing with all options + openNodeCreation(lastMousePos); + } } if (event.code === "KeyL") From e22b689e5613aad3a4cc4e90a428765d18e93b33 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 30 Aug 2025 10:58:36 -0400 Subject: [PATCH 5/6] fix: new node position when using `n` is now at mouse position just before the menu opens --- src/main.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main.ts b/src/main.ts index 05c03f5..67d310c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -349,25 +349,26 @@ async function main() ); }); - window.addEventListener("keypress", (event) => - { + function checkIfInModal() { let anyOverlayOpened = false; // Modal window or context menu. - document.querySelectorAll(".modal-window-container").forEach((modal) => - { - if (!modal.classList.contains("hidden")) - { + document.querySelectorAll(".modal-window-container").forEach((modal) => { + if (!modal.classList.contains("hidden")) { anyOverlayOpened = true; } }); - document.querySelectorAll(".context-menu-container").forEach((modal) => - { - if (!modal.classList.contains("hidden")) - { + document.querySelectorAll(".context-menu-container").forEach((modal) => { + if (!modal.classList.contains("hidden")) { anyOverlayOpened = true; } }); + return anyOverlayOpened; + } + + window.addEventListener("keypress", (event) => + { + let anyOverlayOpened = checkIfInModal(); if (event.code === "KeyN" && !anyOverlayOpened) { @@ -398,8 +399,10 @@ async function main() window.addEventListener("touchend", () => MouseHandler.getInstance().handleMouseUp()); window.addEventListener("mousemove", e => MouseHandler.getInstance().handleMouseMove(e)); window.addEventListener("mousemove", (event) => { - lastMousePos.x = event.clientX; - lastMousePos.y = event.clientY; + if (!checkIfInModal()) { + lastMousePos.x = event.clientX; + lastMousePos.y = event.clientY; + } }); window.addEventListener("touchmove", e => MouseHandler.getInstance().handleTouchMove(e)); From 9e932fcaa79393b8cb63b7fd5103d6abda7cb90a Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 30 Aug 2025 11:30:31 -0400 Subject: [PATCH 6/6] Fix a couple formatting changes I accidentally made. --- src/main.ts | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/main.ts b/src/main.ts index 67d310c..5664842 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,23 +1,23 @@ -import {SankeyNode} from "./Sankey/SankeyNode"; -import {Point} from "./Geometry/Point"; -import {MouseHandler} from "./MouseHandler"; -import {GameRecipe} from "./GameData/GameRecipe"; -import {GameMachine} from "./GameData/GameMachine"; -import {Settings} from "./Settings"; -import {CanvasContextMenu} from "./ContextMenu/CanvasContextMenu"; -import {ResourcesSummary} from "./ResourcesSummary"; -import {PanZoomConfiguration} from "./PanZoomConfiguration"; -import {SvgIcons} from './DomUtils/SvgIcons'; -import {HelpModal} from './HelpWindow/HelpModal'; -import {RecipeSelectionModal} from './RecipeSelectionModal'; -import {CanvasGrid} from "./CanvasGrid"; -import {AppData} from "./DataSaves/AppData"; -import {loadSatisfactoryResource, loadSingleSatisfactoryRecipe} from "./GameData/GameData"; -import {SankeyLink} from "./Sankey/SankeyLink"; -import {SavesLoaderMenu} from "./DataSaves/SavesLoaderMenu"; -import {HtmlUtils} from "./DomUtils/HtmlUtils"; -import {SankeySlotMissing} from "./Sankey/Slots/SankeySlotMissing"; -import {SankeySlotExceeding} from "./Sankey/Slots/SankeySlotExceeding"; +import { SankeyNode } from "./Sankey/SankeyNode"; +import { Point } from "./Geometry/Point"; +import { MouseHandler } from "./MouseHandler"; +import { GameRecipe } from "./GameData/GameRecipe"; +import { GameMachine } from "./GameData/GameMachine"; +import { Settings } from "./Settings"; +import { CanvasContextMenu } from "./ContextMenu/CanvasContextMenu"; +import { ResourcesSummary } from "./ResourcesSummary"; +import { PanZoomConfiguration } from "./PanZoomConfiguration"; +import { SvgIcons } from './DomUtils/SvgIcons'; +import { HelpModal } from './HelpWindow/HelpModal'; +import { RecipeSelectionModal } from './RecipeSelectionModal'; +import { CanvasGrid } from "./CanvasGrid"; +import { AppData } from "./DataSaves/AppData"; +import { loadSatisfactoryResource, loadSingleSatisfactoryRecipe } from "./GameData/GameData"; +import { SankeyLink } from "./Sankey/SankeyLink"; +import { SavesLoaderMenu } from "./DataSaves/SavesLoaderMenu"; +import { HtmlUtils } from "./DomUtils/HtmlUtils"; +import { SankeySlotMissing } from "./Sankey/Slots/SankeySlotMissing"; +import { SankeySlotExceeding } from "./Sankey/Slots/SankeySlotExceeding"; function checkIfConnecting() { // Helper to check if a connection is being created @@ -127,7 +127,8 @@ async function main() onceNodeCreated = undefined; }); - function openNodeCreation(nodePosition?: Point) { + function openNodeCreation(nodePosition?: Point) + { let pageCenter = { x: document.documentElement.clientWidth / 2, y: document.documentElement.clientHeight / 2