From 12486d586d60b2b10bed059ea78df60240d8f517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:42:35 -0300 Subject: [PATCH 1/7] refactor: move GraphData save/load to DataGraph --- src/index.ts | 12 +++++++++ src/types/graphs/datagraph.ts | 50 ++++++++++++++++++++++++++++++----- src/types/graphs/viewgraph.ts | 13 +-------- src/types/viewportManager.ts | 47 ++------------------------------ 4 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/index.ts b/src/index.ts index f7df97c0..273891d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,6 +39,12 @@ export class GlobalContext { this.viewgraph = new ViewGraph(this.datagraph, this.viewport); } + load(datagraph: DataGraph) { + this.datagraph = datagraph; + this.viewport.clear(); + this.viewgraph = new ViewGraph(this.datagraph, this.viewport); + } + getViewport() { return this.viewport; } @@ -90,6 +96,12 @@ export class Viewport extends pixi_viewport.Viewport { }); } + clear() { + this.removeChildren(); + this.addChild(new Background()); + this.moveCenter(WORLD_WIDTH / 2, WORLD_HEIGHT / 2); + } + private initializeMovement() { this.drag() .pinch() diff --git a/src/types/graphs/datagraph.ts b/src/types/graphs/datagraph.ts index 7f69b163..8940cc00 100644 --- a/src/types/graphs/datagraph.ts +++ b/src/types/graphs/datagraph.ts @@ -5,10 +5,54 @@ export interface GraphNode { connections: Set; } +export type GraphDataNode = { + id: number; + x: number; + y: number; + type: string; + connections: number[]; +}; + +export type GraphData = GraphDataNode[]; + export class DataGraph { private devices = new Map(); private idCounter = 1; + static fromData(data: GraphData): DataGraph { + const dataGraph = new DataGraph(); + data.forEach((nodeData: GraphDataNode) => { + // ADD DATAGRAPH AND EDGES + console.log(nodeData); + const connections = new Set(nodeData.connections); + const graphNode: GraphNode = { + x: nodeData.x, + y: nodeData.y, + type: nodeData.type, + connections: connections, + }; + dataGraph.addDevice(nodeData.id, graphNode); + }); + return dataGraph; + } + + toData(): GraphData { + const graphData: GraphData = []; + + // Serialize nodes + this.getDevices().forEach(([id, info]) => { + graphData.push({ + id: id, + x: info.x, + y: info.y, + type: info.type, // Save the device type (Router, Server, PC) + connections: Array.from(info.connections.values()), + }); + }); + + return graphData; + } + // Add a new device to the graph addNewDevice(deviceInfo: { x: number; y: number; type: string }): number { const id = this.idCounter++; @@ -144,10 +188,4 @@ export class DataGraph { `Connection removed between devices ID: ${n1Id} and ID: ${n2Id}`, ); } - - // Clear the graph - clear() { - this.devices.clear(); - this.idCounter = 1; - } } diff --git a/src/types/graphs/viewgraph.ts b/src/types/graphs/viewgraph.ts index aac0cdd2..8f25d647 100644 --- a/src/types/graphs/viewgraph.ts +++ b/src/types/graphs/viewgraph.ts @@ -16,7 +16,7 @@ export class ViewGraph { this.constructView(); } - constructView() { + private constructView() { // TODO: Adjust construction based on the selected layer in the future console.log("Constructing ViewGraph from DataGraph"); const connections = new Set<{ deviceId: number; adyacentId: number }>(); @@ -180,17 +180,6 @@ export class ViewGraph { return this.devices.size; } - // Clear the graph - clear() { - this.devices.forEach((device) => { - device.delete(); - }); - // no edges should remain to delete - this.devices.clear(); - this.edges.clear(); - this.idCounter = 1; - } - // Method to remove a device and its connections (edges) removeDevice(id: number) { const device = this.devices.get(id); diff --git a/src/types/viewportManager.ts b/src/types/viewportManager.ts index 7c7cb793..26996278 100644 --- a/src/types/viewportManager.ts +++ b/src/types/viewportManager.ts @@ -178,28 +178,7 @@ function setDevice( // Function to save the current graph in JSON format export function saveGraph(ctx: GlobalContext) { - const datagraph: DataGraph = ctx.getDataGraph(); - - const graphData: { - id: number; - x: number; - y: number; - type: string; - connections: number[]; - }[] = []; - - // Serialize nodes - datagraph.getDevices().forEach((deviceInfo) => { - const id = deviceInfo[0]; - const info = deviceInfo[1]; - graphData.push({ - id: id, - x: info.x, - y: info.y, - type: info.type, // Save the device type (Router, Server, PC) - connections: Array.from(info.connections.values()), - }); - }); + const graphData = ctx.getDataGraph().toData(); // Convert to JSON and download const jsonString = JSON.stringify(graphData, null, 2); @@ -217,29 +196,7 @@ export function saveGraph(ctx: GlobalContext) { // Function to load a graph from a JSON file export function loadGraph(jsonData: string, ctx: GlobalContext) { const graphData = JSON.parse(jsonData); - const viewgraph = ctx.getViewGraph(); - const datagraph = ctx.getDataGraph(); - - // Clear current nodes and connections - viewgraph.clear(); - datagraph.clear(); - - // Deserialize and recreate nodes - graphData.forEach( - (nodeData: { - id: number; - x: number; - y: number; - type: string; - connections: number[]; - }) => { - // ADD DATAGRAPH AND EDGES - console.log(nodeData); - setDevice(datagraph, nodeData); - }, - ); - - viewgraph.constructView(); + ctx.load(DataGraph.fromData(graphData)); console.log("Graph loaded successfully."); } From 9742f4af9fc258b0996684ff72906485dccaa1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:44:36 -0300 Subject: [PATCH 2/7] refactor: move file loading to viewportManager --- src/index.ts | 17 +---------------- src/types/viewportManager.ts | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/index.ts b/src/index.ts index 273891d5..0356d52e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -366,22 +366,7 @@ export class RightBar { }; loadButton.onclick = () => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".json"; - - input.onchange = (event) => { - const file = (event.target as HTMLInputElement).files[0]; - const reader = new FileReader(); - reader.readAsText(file); - - reader.onload = (readerEvent) => { - const jsonData = readerEvent.target.result as string; - loadGraph(jsonData, ctx); - }; - }; - - input.click(); + loadGraph(ctx); }; const pauseButton = document.getElementById("pause-button"); diff --git a/src/types/viewportManager.ts b/src/types/viewportManager.ts index 26996278..a332093e 100644 --- a/src/types/viewportManager.ts +++ b/src/types/viewportManager.ts @@ -194,9 +194,24 @@ export function saveGraph(ctx: GlobalContext) { } // Function to load a graph from a JSON file -export function loadGraph(jsonData: string, ctx: GlobalContext) { - const graphData = JSON.parse(jsonData); - ctx.load(DataGraph.fromData(graphData)); +export function loadGraph(ctx: GlobalContext) { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".json"; + + input.onchange = (event) => { + const file = (event.target as HTMLInputElement).files[0]; + const reader = new FileReader(); + reader.readAsText(file); + + reader.onload = (readerEvent) => { + const jsonData = readerEvent.target.result as string; + const graphData = JSON.parse(jsonData); + ctx.load(DataGraph.fromData(graphData)); + + console.log("Graph loaded successfully."); + }; + }; - console.log("Graph loaded successfully."); + input.click(); } From 12d76e61ef89e86b967d185c6eb1577049aed89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:45:06 -0300 Subject: [PATCH 3/7] chore: remove dead code --- src/types/viewportManager.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/types/viewportManager.ts b/src/types/viewportManager.ts index a332093e..f640b8c9 100644 --- a/src/types/viewportManager.ts +++ b/src/types/viewportManager.ts @@ -155,27 +155,6 @@ export function AddServer(ctx: GlobalContext) { ); } -function setDevice( - datagraph: DataGraph, - nodeData: { - id: number; - x: number; - y: number; - type: string; - connections: number[]; - }, -) { - const connections = new Set(nodeData.connections); - const graphNode: GraphNode = { - x: nodeData.x, - y: nodeData.y, - type: nodeData.type, - connections: connections, - }; - datagraph.addDevice(nodeData.id, graphNode); - console.log(`Device set with ID ${nodeData.id}`); -} - // Function to save the current graph in JSON format export function saveGraph(ctx: GlobalContext) { const graphData = ctx.getDataGraph().toData(); From 9094ec0f98d7430cda7165628b19ff4b4611e346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:45:35 -0300 Subject: [PATCH 4/7] chore: fix lint --- src/types/graphs/datagraph.ts | 4 ++-- src/types/viewportManager.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/graphs/datagraph.ts b/src/types/graphs/datagraph.ts index 8940cc00..41981836 100644 --- a/src/types/graphs/datagraph.ts +++ b/src/types/graphs/datagraph.ts @@ -5,13 +5,13 @@ export interface GraphNode { connections: Set; } -export type GraphDataNode = { +export interface GraphDataNode { id: number; x: number; y: number; type: string; connections: number[]; -}; +} export type GraphData = GraphDataNode[]; diff --git a/src/types/viewportManager.ts b/src/types/viewportManager.ts index f640b8c9..6c0f0084 100644 --- a/src/types/viewportManager.ts +++ b/src/types/viewportManager.ts @@ -1,5 +1,5 @@ import { GlobalContext } from "./../index"; -import { DataGraph, GraphNode } from "./graphs/datagraph"; +import { DataGraph } from "./graphs/datagraph"; import { Device, Pc, Router, Server } from "./devices/index"; import { Edge } from "./edge"; import { RightBar } from "../index"; // Ensure the path is correct From f5fe10a8a5adf02ad333b3a15e1a89bdd26de5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:54:55 -0300 Subject: [PATCH 5/7] feat: store graph data in localStorage --- src/index.ts | 49 ++++++++++++------------------------ src/types/viewportManager.ts | 20 +++++++++++++-- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0356d52e..93a74dc7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,8 +16,10 @@ import { AddPc, AddRouter, AddServer, - loadGraph, - saveGraph, + loadFromFile, + loadFromLocalStorage, + saveToFile, + saveToLocalStorage, selectElement, } from "./types/viewportManager"; import { DataGraph } from "./types/graphs/datagraph"; @@ -310,31 +312,13 @@ export class RightBar { RightBar.getInstance(); // Add router button - leftBar.addButton( - RouterSvg, - () => { - AddRouter(ctx); - }, - "Add Router", - ); + leftBar.addButton(RouterSvg, () => AddRouter(ctx), "Add Router"); // Add server button - leftBar.addButton( - ServerSvg, - () => { - AddServer(ctx); - }, - "Add Server", - ); + leftBar.addButton(ServerSvg, () => AddServer(ctx), "Add Server"); // Add PC button - leftBar.addButton( - ComputerSvg, - () => { - AddPc(ctx); - }, - "Add PC", - ); + leftBar.addButton(ComputerSvg, () => AddPc(ctx), "Add PC"); ctx.initialize(viewport); @@ -361,13 +345,8 @@ export class RightBar { const loadButton = document.getElementById("load-button"); const saveButton = document.getElementById("save-button"); - saveButton.onclick = () => { - saveGraph(ctx); - }; - - loadButton.onclick = () => { - loadGraph(ctx); - }; + saveButton.onclick = () => saveToFile(ctx); + loadButton.onclick = () => loadFromFile(ctx); const pauseButton = document.getElementById("pause-button"); let paused = false; @@ -394,9 +373,7 @@ export class RightBar { } }; - pauseButton.onclick = () => { - triggerPause(); - }; + pauseButton.onclick = triggerPause; document.body.onkeyup = function (e) { if (e.key === " " || e.code === "Space") { @@ -405,5 +382,11 @@ export class RightBar { } }; + // TODO: load from local storage directly, without first generating a context + loadFromLocalStorage(ctx); + + // TODO: do this only when changes are made + setInterval(() => saveToLocalStorage(ctx), 5000); + console.log("initialized!"); })(); diff --git a/src/types/viewportManager.ts b/src/types/viewportManager.ts index 6c0f0084..3b135e3d 100644 --- a/src/types/viewportManager.ts +++ b/src/types/viewportManager.ts @@ -156,7 +156,7 @@ export function AddServer(ctx: GlobalContext) { } // Function to save the current graph in JSON format -export function saveGraph(ctx: GlobalContext) { +export function saveToFile(ctx: GlobalContext) { const graphData = ctx.getDataGraph().toData(); // Convert to JSON and download @@ -173,7 +173,7 @@ export function saveGraph(ctx: GlobalContext) { } // Function to load a graph from a JSON file -export function loadGraph(ctx: GlobalContext) { +export function loadFromFile(ctx: GlobalContext) { const input = document.createElement("input"); input.type = "file"; input.accept = ".json"; @@ -194,3 +194,19 @@ export function loadGraph(ctx: GlobalContext) { input.click(); } + +const LOCAL_STORAGE_KEY = "graphData"; + +export function saveToLocalStorage(ctx: GlobalContext) { + const graphData = JSON.stringify(ctx.getDataGraph().toData()); + localStorage.setItem(LOCAL_STORAGE_KEY, graphData); + + console.log("Graph saved in local storage."); +} + +export function loadFromLocalStorage(ctx: GlobalContext) { + const graphData = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"); + ctx.load(DataGraph.fromData(graphData)); + + console.log("Graph loaded from local storage."); +} From 3d4b38aa4853d29970129b3a14360707325f9675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:00:20 -0300 Subject: [PATCH 6/7] feat: add button to clear graph --- src/index.ejs | 1 + src/index.ts | 2 ++ src/style.css | 1 + 3 files changed, 4 insertions(+) diff --git a/src/index.ejs b/src/index.ejs index bc121e91..285d90d5 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -9,6 +9,7 @@
+ diff --git a/src/index.ts b/src/index.ts index 93a74dc7..cb830dbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -342,9 +342,11 @@ export class RightBar { window.addEventListener("resize", resize); + const newButton = document.getElementById("new-button"); const loadButton = document.getElementById("load-button"); const saveButton = document.getElementById("save-button"); + newButton.onclick = () => ctx.load(new DataGraph()); saveButton.onclick = () => saveToFile(ctx); loadButton.onclick = () => loadFromFile(ctx); diff --git a/src/style.css b/src/style.css index f2f98821..1cc1e545 100644 --- a/src/style.css +++ b/src/style.css @@ -104,6 +104,7 @@ canvas { } /* Styles for save and load buttons */ +.new-button, .save-button, .load-button { background-color: #007bff; /* Blue background */ From 4d2dd0914f7397993162d0368b4508d2b39394af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:06:06 -0300 Subject: [PATCH 7/7] perf: save graph only when changes were made --- src/index.ts | 14 +++++++-- src/types/graphs/datagraph.ts | 56 ++++++++++++++++++++++++----------- src/types/viewportManager.ts | 6 ++-- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/index.ts b/src/index.ts index cb830dbf..c7c18fbc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -387,8 +387,18 @@ export class RightBar { // TODO: load from local storage directly, without first generating a context loadFromLocalStorage(ctx); - // TODO: do this only when changes are made - setInterval(() => saveToLocalStorage(ctx), 5000); + let saveIntervalId: NodeJS.Timeout | null = null; + + ctx.getDataGraph().subscribeChanges(() => { + // Wait a bit after the last change to save + if (saveIntervalId) { + clearInterval(saveIntervalId); + } + saveIntervalId = setInterval(() => { + saveToLocalStorage(ctx); + clearInterval(saveIntervalId); + }, 100); + }); console.log("initialized!"); })(); diff --git a/src/types/graphs/datagraph.ts b/src/types/graphs/datagraph.ts index 41981836..03664875 100644 --- a/src/types/graphs/datagraph.ts +++ b/src/types/graphs/datagraph.ts @@ -18,6 +18,7 @@ export type GraphData = GraphDataNode[]; export class DataGraph { private devices = new Map(); private idCounter = 1; + private onChanges: (() => void)[] = []; static fromData(data: GraphData): DataGraph { const dataGraph = new DataGraph(); @@ -49,7 +50,6 @@ export class DataGraph { connections: Array.from(info.connections.values()), }); }); - return graphData; } @@ -62,20 +62,22 @@ export class DataGraph { }; this.devices.set(id, graphnode); console.log(`Device added with ID ${id}`); + this.notifyChanges(); return id; } // Add a device to the graph addDevice(idDevice: number, deviceInfo: GraphNode) { - if (!this.devices.has(idDevice)) { - this.devices.set(idDevice, deviceInfo); - if (this.idCounter <= idDevice) { - this.idCounter = idDevice + 1; - } - console.log(`Device added with ID ${idDevice}`); - } else { + if (this.devices.has(idDevice)) { console.warn(`Device with ID ${idDevice} already exists in the graph.`); + return; } + this.devices.set(idDevice, deviceInfo); + if (this.idCounter <= idDevice) { + this.idCounter = idDevice + 1; + } + console.log(`Device added with ID ${idDevice}`); + this.notifyChanges(); } // Add a connection between two devices @@ -84,23 +86,30 @@ export class DataGraph { console.warn( `Cannot create a connection between the same device (ID ${n1Id}).`, ); - } else if (!this.devices.has(n1Id)) { + return; + } + if (!this.devices.has(n1Id)) { console.warn(`Device with ID ${n1Id} does not exist in devices.`); - } else if (!this.devices.has(n2Id)) { + return; + } + if (!this.devices.has(n2Id)) { console.warn(`Device with ID ${n2Id} does not exist in devices.`); + return; // Check if an edge already exists between these two devices - } else if (this.devices.get(n1Id).connections.has(n2Id)) { + } + if (this.devices.get(n1Id).connections.has(n2Id)) { console.warn( `Connection between ID ${n1Id} and ID ${n2Id} already exists.`, ); - } else { - this.devices.get(n1Id).connections.add(n2Id); - this.devices.get(n2Id).connections.add(n1Id); - - console.log( - `Connection created between devices ID: ${n1Id} and ID: ${n2Id}`, - ); + return; } + this.devices.get(n1Id).connections.add(n2Id); + this.devices.get(n2Id).connections.add(n1Id); + + console.log( + `Connection created between devices ID: ${n1Id} and ID: ${n2Id}`, + ); + this.notifyChanges(); } updateDevicePosition(id: number, newValues: { x?: number; y?: number }) { @@ -110,6 +119,7 @@ export class DataGraph { return; } this.devices.set(id, { ...deviceGraphNode, ...newValues }); + this.notifyChanges(); } getDevice(id: number): GraphNode | undefined { @@ -155,6 +165,7 @@ export class DataGraph { // Remove the node from the graph this.devices.delete(id); console.log(`Device with ID ${id} and its connections were removed.`); + this.notifyChanges(); } // Method to remove a connection (edge) between two devices by their IDs @@ -187,5 +198,14 @@ export class DataGraph { console.log( `Connection removed between devices ID: ${n1Id} and ID: ${n2Id}`, ); + this.notifyChanges(); + } + + subscribeChanges(callback: () => void) { + this.onChanges.push(callback); + } + + notifyChanges() { + this.onChanges.forEach((callback) => callback()); } } diff --git a/src/types/viewportManager.ts b/src/types/viewportManager.ts index 3b135e3d..56ab99d8 100644 --- a/src/types/viewportManager.ts +++ b/src/types/viewportManager.ts @@ -198,14 +198,16 @@ export function loadFromFile(ctx: GlobalContext) { const LOCAL_STORAGE_KEY = "graphData"; export function saveToLocalStorage(ctx: GlobalContext) { - const graphData = JSON.stringify(ctx.getDataGraph().toData()); + const dataGraph = ctx.getDataGraph(); + const graphData = JSON.stringify(dataGraph.toData()); localStorage.setItem(LOCAL_STORAGE_KEY, graphData); console.log("Graph saved in local storage."); } export function loadFromLocalStorage(ctx: GlobalContext) { - const graphData = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"); + const jsonData = localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"; + const graphData = JSON.parse(jsonData); ctx.load(DataGraph.fromData(graphData)); console.log("Graph loaded from local storage.");