feat: save graph to localstore

This commit is contained in:
max_richter 2024-03-19 16:56:42 +01:00
parent 3811a6bdb4
commit b55d3d4217
7 changed files with 128 additions and 5 deletions

View File

@ -524,7 +524,7 @@
$edges = $edges; $edges = $edges;
}); });
graph.history.save(); graph.save();
} else if ($hoveredSocket && $activeSocket) { } else if ($hoveredSocket && $activeSocket) {
if ( if (
typeof $hoveredSocket.index === "number" && typeof $hoveredSocket.index === "number" &&
@ -547,7 +547,7 @@
$hoveredSocket.index, $hoveredSocket.index,
); );
} }
graph.history.save(); graph.save();
} }
mouseDown = null; mouseDown = null;

View File

@ -5,7 +5,7 @@
import Node from "../Node.svelte"; import Node from "../Node.svelte";
import { getContext, onMount } from "svelte"; import { getContext, onMount } from "svelte";
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import { activeSocket, colors } from "./stores"; import { activeSocket } from "./stores";
export let nodes: Writable<Map<number, NodeType>>; export let nodes: Writable<Map<number, NodeType>>;
export let edges: Writable<EdgeType[]>; export let edges: Writable<EdgeType[]>;

View File

@ -2,8 +2,9 @@ import { writable, type Writable } from "svelte/store";
import { type Graph, type Node, type Edge, type Socket, type NodeRegistry, type RuntimeExecutor } from "./types"; import { type Graph, type Node, type Edge, type Socket, type NodeRegistry, type RuntimeExecutor } from "./types";
import { HistoryManager } from "./history-manager"; import { HistoryManager } from "./history-manager";
import * as templates from "./graphs"; import * as templates from "./graphs";
import EventEmitter from "./helpers/EventEmitter";
export class GraphManager { export class GraphManager extends EventEmitter<{ "save": Graph }> {
status: Writable<"loading" | "idle" | "error"> = writable("loading"); status: Writable<"loading" | "idle" | "error"> = writable("loading");
@ -19,6 +20,7 @@ export class GraphManager {
history: HistoryManager = new HistoryManager(this); history: HistoryManager = new HistoryManager(this);
constructor(private nodeRegistry: NodeRegistry, private runtime: RuntimeExecutor) { constructor(private nodeRegistry: NodeRegistry, private runtime: RuntimeExecutor) {
super();
this.nodes.subscribe((nodes) => { this.nodes.subscribe((nodes) => {
this._nodes = nodes; this._nodes = nodes;
}); });
@ -187,6 +189,7 @@ export class GraphManager {
} }
save() { save() {
this.emit("save", this.serialize());
this.history.save(); this.history.save();
} }

View File

@ -0,0 +1,90 @@
import throttle from './throttle';
const debug = { amountEmitters: 0, amountCallbacks: 0, emitters: [] };
type EventMap = Record<string, unknown>;
type EventKey<T extends EventMap> = string & keyof T;
type EventReceiver<T> = (params: T, stuff?: Record<string, unknown>) => unknown;
export default class EventEmitter<T extends EventMap = { [key: string]: unknown }> {
index = 0;
public eventMap: T = {} as T;
constructor() {
this.index = debug.amountEmitters;
debug.amountEmitters++;
}
private cbs: { [key: string]: ((data?: unknown) => unknown)[] } = {};
private cbsOnce: { [key: string]: ((data?: unknown) => unknown)[] } = {};
/**
* Emit an event with optional data to all the listeners
* @param {string} event Name of the event to emit
* @param data Data to send along
*/
public emit(event: string, data?: unknown) {
if (event in this.cbs) {
this.cbs[event].forEach((c) => c(data));
}
if (event in this.cbsOnce) {
this.cbsOnce[event].forEach((c) => c(data));
delete this.cbsOnce[event];
}
}
public on<K extends EventKey<T>>(event: K, cb: EventReceiver<T[K]>, throttleTimer = 0) {
if (throttleTimer > 0) cb = throttle(cb, throttleTimer);
const cbs = Object.assign(this.cbs, {
[event]: [...(this.cbs[event] || []), cb],
});
this.cbs = cbs;
debug.emitters[this.index] = {
name: this.constructor.name,
cbs: Object.fromEntries(
Object.keys(this.cbs).map((key) => [key, this.cbs[key].length]),
),
};
debug.amountCallbacks++;
// console.log('New EventEmitter ', this.constructor.name);
return () => {
debug.amountCallbacks--;
cbs[event]?.splice(cbs[event].indexOf(cb), 1);
debug.emitters[this.index] = {
name: this.constructor.name,
cbs: Object.fromEntries(
Object.keys(this.cbs).map((key) => [key, this.cbs[key].length]),
),
};
};
}
/**
* Register a special listener which only gets called once
* @param {string} event Name of the event to listen to
* @param {function} cb Listener, gets called everytime the event is emitted
* @returns {function} Returns a function which removes the listener when called
*/
public once<K extends EventKey<T>>(event: K, cb: EventReceiver<T[K]>): () => void {
this.cbsOnce[event] = [...(this.cbsOnce[event] || []), cb];
return () => {
this.cbsOnce[event].splice(this.cbsOnce[event].indexOf(cb), 1);
};
}
public destroyEventEmitter() {
debug.amountEmitters--;
Object.keys(this.cbs).forEach((key) => {
debug.amountCallbacks -= this.cbs[key].length;
delete this.cbs[key];
});
Object.keys(this.cbsOnce).forEach((key) => delete this.cbsOnce[key]);
this.cbs = {};
this.cbsOnce = {};
delete debug.emitters[this.index];
}
}

View File

@ -0,0 +1,20 @@
export default <R, A extends any[]>(
fn: (...args: A) => R,
delay: number
): ((...args: A) => R) => {
let wait = false;
return (...args: A) => {
if (wait) return undefined;
const val = fn(...args);
wait = true;
setTimeout(() => {
wait = false;
}, delay);
return val;
}
};

View File

@ -9,7 +9,17 @@
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry); const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
const graphManager = new GraphManager(nodeRegistry, runtimeExecutor); const graphManager = new GraphManager(nodeRegistry, runtimeExecutor);
let graph = localStorage.getItem("graph");
if (graph) {
graphManager.load(JSON.parse(graph));
} else {
graphManager.load(graphManager.createTemplate("grid", 5, 5)); graphManager.load(graphManager.createTemplate("grid", 5, 5));
}
graphManager.on("save", (graph) => {
localStorage.setItem("graph", JSON.stringify(graph));
});
let debug: undefined; let debug: undefined;