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.");
}