jagenjo / litegraph.js

A graph node engine and editor written in Javascript similar to PD or UDK Blueprints, comes with its own editor in HTML5 Canvas2D. The engine can run client side or server side using Node. It allows to export graphs as JSONs to be included in applications independently.
MIT License
5.33k stars 602 forks source link

is there an onGraphChange() or onCanvasChange() event? #458

Open Weixuanf opened 2 months ago

Weixuanf commented 2 months ago

I need to listen for when user change the graph or canvas. If an event can be emitted and listened for it would be great!

Weixuanf commented 2 months ago

LGraph.onchange() will be fired even when zooming and dragging canvas, without graph nodes values change

LGraph.onAfterChange() will not be fired when a node widget value change, either not fired when configure() a new graph....🫠

this is what I'm doing now but I don't think this is accurate:

    document.addEventListener("click", (e) => {
      if (
        app.canvas.node_over != null ||
        app.canvas.node_capturing_input != null ||
        app.canvas.node_widget != null
      ) {
        onIsDirty();
      }
    });
    document.addEventListener("keydown", async function (event) {
      if (document.visibilityState === "hidden") return;
      const matchResult = await matchShortcut(event);
      if (matchResult) {
        shortcutListener(matchResult);
      } else if (
        // @ts-expect-error
        event.target?.matches("input, textarea") &&
        Object.keys(app.canvas.selected_nodes ?? {}).length
      ) {
        onIsDirty();
      }
    });
zhzLuke96 commented 3 days ago

Perhaps someone needs it, I'll leave the code here that I'm sure can be used (there might be performance issues, but it can ensure that the graph change event is triggered correctly):

class LGraphPro extends LGraph {
  private _graph_cache = {} as serializedLGraph;

  emit(...args: any[]) { /* ... */ }

  change(): void {
    super.change();
    this.emit("change");

    this.checkGraphChange();
  }

  connectionChange(node: LGraphNode): void {
    super.connectionChange?.(node);
    this.emit("connection_change", node);

    this.checkGraphChange();
  }

  serializeDeep(options?: {
    skip_inputs?: boolean;
    skip_outputs?: boolean;
    skip_properties?: boolean;
    skip_links?: boolean;
    skip_nodes?: boolean;
  }) {
    const graph_obj = this.serialize();
    graph_obj.nodes.sort((a, b) => a.id - b.id);

    if (options?.skip_inputs) {
      graph_obj.nodes.map((x) => (x.inputs = []));
    }
    if (options?.skip_outputs) {
      graph_obj.nodes.map((x) => (x.outputs = []));
    }
    if (options?.skip_properties) {
      graph_obj.nodes.map((x) => (x.properties = {}));
    }
    if (options?.skip_links) {
      graph_obj.links = [];
    }
    if (options?.skip_nodes) {
      graph_obj.nodes = [];
    }

    return cloneDeep(graph_obj);
  }

  private checkGraphChange() {
    const graph_obj = this.serializeDeep({
      skip_inputs: true,
      skip_outputs: true,
      skip_properties: true,
    });
    if (deepEqual(graph_obj, this._graph_cache)) {
      return;
    }
    this._graph_cache = graph_obj;
    this.events?.emit("graph_change", this.serializeDeep());
  }
}