feat: save graph to localstore
This commit is contained in:
parent
3811a6bdb4
commit
b55d3d4217
@ -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;
|
||||||
|
@ -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[]>;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
90
frontend/src/lib/helpers/EventEmitter.ts
Normal file
90
frontend/src/lib/helpers/EventEmitter.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
20
frontend/src/lib/helpers/throttle.ts
Normal file
20
frontend/src/lib/helpers/throttle.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
@ -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);
|
||||||
graphManager.load(graphManager.createTemplate("grid", 5, 5));
|
|
||||||
|
let graph = localStorage.getItem("graph");
|
||||||
|
if (graph) {
|
||||||
|
graphManager.load(JSON.parse(graph));
|
||||||
|
} else {
|
||||||
|
graphManager.load(graphManager.createTemplate("grid", 5, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
graphManager.on("save", (graph) => {
|
||||||
|
localStorage.setItem("graph", JSON.stringify(graph));
|
||||||
|
});
|
||||||
|
|
||||||
let debug: undefined;
|
let debug: undefined;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user