| import { app } from "../../scripts/app.js"; |
| import { api } from "../../scripts/api.js" |
|
|
| const MAX_HISTORY = 50; |
|
|
| let undo = []; |
| let redo = []; |
| let activeState = null; |
| let isOurLoad = false; |
| function checkState() { |
| const currentState = app.graph.serialize(); |
| if (!graphEqual(activeState, currentState)) { |
| undo.push(activeState); |
| if (undo.length > MAX_HISTORY) { |
| undo.shift(); |
| } |
| activeState = clone(currentState); |
| redo.length = 0; |
| api.dispatchEvent(new CustomEvent("graphChanged", { detail: activeState })); |
| } |
| } |
|
|
| const loadGraphData = app.loadGraphData; |
| app.loadGraphData = async function () { |
| const v = await loadGraphData.apply(this, arguments); |
| if (isOurLoad) { |
| isOurLoad = false; |
| } else { |
| checkState(); |
| } |
| return v; |
| }; |
|
|
| function clone(obj) { |
| try { |
| if (typeof structuredClone !== "undefined") { |
| return structuredClone(obj); |
| } |
| } catch (error) { |
| |
| } |
|
|
| return JSON.parse(JSON.stringify(obj)); |
| } |
|
|
| function graphEqual(a, b, root = true) { |
| if (a === b) return true; |
|
|
| if (typeof a == "object" && a && typeof b == "object" && b) { |
| const keys = Object.getOwnPropertyNames(a); |
|
|
| if (keys.length != Object.getOwnPropertyNames(b).length) { |
| return false; |
| } |
|
|
| for (const key of keys) { |
| let av = a[key]; |
| let bv = b[key]; |
| if (root && key === "nodes") { |
| |
| av = [...av].sort((a, b) => a.id - b.id); |
| bv = [...bv].sort((a, b) => a.id - b.id); |
| } |
| if (!graphEqual(av, bv, false)) { |
| return false; |
| } |
| } |
|
|
| return true; |
| } |
|
|
| return false; |
| } |
|
|
| const undoRedo = async (e) => { |
| const updateState = async (source, target) => { |
| const prevState = source.pop(); |
| if (prevState) { |
| target.push(activeState); |
| isOurLoad = true; |
| await app.loadGraphData(prevState, false); |
| activeState = prevState; |
| } |
| } |
| if (e.ctrlKey || e.metaKey) { |
| if (e.key === "y") { |
| updateState(redo, undo); |
| return true; |
| } else if (e.key === "z") { |
| updateState(undo, redo); |
| return true; |
| } |
| } |
| }; |
|
|
| const bindInput = (activeEl) => { |
| if (activeEl && activeEl.tagName !== "CANVAS" && activeEl.tagName !== "BODY") { |
| for (const evt of ["change", "input", "blur"]) { |
| if (`on${evt}` in activeEl) { |
| const listener = () => { |
| checkState(); |
| activeEl.removeEventListener(evt, listener); |
| }; |
| activeEl.addEventListener(evt, listener); |
| return true; |
| } |
| } |
| } |
| }; |
|
|
| let keyIgnored = false; |
| window.addEventListener( |
| "keydown", |
| (e) => { |
| requestAnimationFrame(async () => { |
| let activeEl; |
| |
| if (!app.ui.autoQueueEnabled || app.ui.autoQueueMode === "instant") { |
| activeEl = document.activeElement; |
| if (activeEl?.tagName === "INPUT" || activeEl?.type === "textarea") { |
| |
| return; |
| } |
| } |
| |
| keyIgnored = e.key === "Control" || e.key === "Shift" || e.key === "Alt" || e.key === "Meta"; |
| if (keyIgnored) return; |
|
|
| |
| if (await undoRedo(e)) return; |
|
|
| |
| if (bindInput(activeEl)) return; |
| checkState(); |
| }); |
| }, |
| true |
| ); |
|
|
| window.addEventListener("keyup", (e) => { |
| if (keyIgnored) { |
| keyIgnored = false; |
| checkState(); |
| } |
| }); |
|
|
| |
| window.addEventListener("mouseup", () => { |
| checkState(); |
| }); |
|
|
| |
| api.addEventListener("promptQueued", () => { |
| checkState(); |
| }); |
|
|
| |
| const processMouseUp = LGraphCanvas.prototype.processMouseUp; |
| LGraphCanvas.prototype.processMouseUp = function (e) { |
| const v = processMouseUp.apply(this, arguments); |
| checkState(); |
| return v; |
| }; |
| const processMouseDown = LGraphCanvas.prototype.processMouseDown; |
| LGraphCanvas.prototype.processMouseDown = function (e) { |
| const v = processMouseDown.apply(this, arguments); |
| checkState(); |
| return v; |
| }; |
|
|
| |
| const close = LiteGraph.ContextMenu.prototype.close; |
| LiteGraph.ContextMenu.prototype.close = function(e) { |
| const v = close.apply(this, arguments); |
| checkState(); |
| return v; |
| } |