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 f7df97c0..c7c18fbc 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"; @@ -39,6 +41,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 +98,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() @@ -298,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); @@ -346,31 +342,13 @@ 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"); - saveButton.onclick = () => { - saveGraph(ctx); - }; - - 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(); - }; + newButton.onclick = () => ctx.load(new DataGraph()); + saveButton.onclick = () => saveToFile(ctx); + loadButton.onclick = () => loadFromFile(ctx); const pauseButton = document.getElementById("pause-button"); let paused = false; @@ -397,9 +375,7 @@ export class RightBar { } }; - pauseButton.onclick = () => { - triggerPause(); - }; + pauseButton.onclick = triggerPause; document.body.onkeyup = function (e) { if (e.key === " " || e.code === "Space") { @@ -408,5 +384,21 @@ export class RightBar { } }; + // TODO: load from local storage directly, without first generating a context + loadFromLocalStorage(ctx); + + 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/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 */ diff --git a/src/types/graphs/datagraph.ts b/src/types/graphs/datagraph.ts index 7f69b163..03664875 100644 --- a/src/types/graphs/datagraph.ts +++ b/src/types/graphs/datagraph.ts @@ -5,9 +5,53 @@ export interface GraphNode { connections: Set; } +export interface 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; + private onChanges: (() => void)[] = []; + + 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 { @@ -18,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 @@ -40,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 }) { @@ -66,6 +119,7 @@ export class DataGraph { return; } this.devices.set(id, { ...deviceGraphNode, ...newValues }); + this.notifyChanges(); } getDevice(id: number): GraphNode | undefined { @@ -111,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 @@ -143,11 +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); } - // Clear the graph - clear() { - this.devices.clear(); - this.idCounter = 1; + notifyChanges() { + this.onChanges.forEach((callback) => callback()); } } 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..56ab99d8 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 @@ -155,51 +155,9 @@ 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 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()), - }); - }); +export function saveToFile(ctx: GlobalContext) { + const graphData = ctx.getDataGraph().toData(); // Convert to JSON and download const jsonString = JSON.stringify(graphData, null, 2); @@ -215,31 +173,42 @@ 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(); +export function loadFromFile(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."); + }; + }; - // 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); - }, - ); + input.click(); +} + +const LOCAL_STORAGE_KEY = "graphData"; + +export function saveToLocalStorage(ctx: GlobalContext) { + const dataGraph = ctx.getDataGraph(); + const graphData = JSON.stringify(dataGraph.toData()); + localStorage.setItem(LOCAL_STORAGE_KEY, graphData); - viewgraph.constructView(); + console.log("Graph saved in local storage."); +} + +export function loadFromLocalStorage(ctx: GlobalContext) { + const jsonData = localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"; + const graphData = JSON.parse(jsonData); + ctx.load(DataGraph.fromData(graphData)); - console.log("Graph loaded successfully."); + console.log("Graph loaded from local storage."); }