File size: 4,605 Bytes
3d5837a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import { app } from "../../../scripts/app.js";

// Adds lock/unlock menu item for nodes + groups to prevent moving / resizing them

const LOCKED = Symbol();

function lockArray(arr, isLocked) {
	const v = [];

	for (let i = 0; i < 2; i++) {
		v[i] = arr[i];

		Object.defineProperty(arr, i, {
			get() {
				return v[i];
			},
			set(value) {
				if (!isLocked()) {
					v[i] = value;
				}
			},
		});
	}
}

app.registerExtension({
	name: "pysssss.Locking",
	init() {
		function lockGroup(node) {
			node[LOCKED] = true;
		}

		// Add the locked flag to serialization
		const serialize = LGraphGroup.prototype.serialize;
		LGraphGroup.prototype.serialize = function () {
			const o = serialize.apply(this, arguments);
			o.locked = !!this[LOCKED];
			return o;
		};

		// On initial configure lock group if required
		const configure = LGraphGroup.prototype.configure;
		LGraphGroup.prototype.configure = function (o) {
			configure.apply(this, arguments);
			if (o.locked) {
				lockGroup(this);
			}
		};

		// Allow click through locked groups
		const getGroupOnPos = LGraph.prototype.getGroupOnPos;
		LGraph.prototype.getGroupOnPos = function () {
			const r = getGroupOnPos.apply(this, arguments);
			if (r && r[LOCKED] && !new Error().stack.includes("processContextMenu")) return null;
			return r;
		};

		// Add menu options for lock/unlock
		const getGroupMenuOptions = LGraphCanvas.prototype.getGroupMenuOptions;
		LGraphCanvas.prototype.getGroupMenuOptions = function (node) {
			const opts = getGroupMenuOptions.apply(this, arguments);

			opts.unshift(
				node[LOCKED]
					? {
							content: "Unlock",
							callback: () => {
								delete node[LOCKED];
							},
					  }
					: {
							content: "Lock",
							callback: () => lockGroup(node),
					  },
				null
			);

			return opts;
		};
	},
	setup() {
		const drawNodeShape = LGraphCanvas.prototype.drawNodeShape;
		LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
			const res = drawNodeShape.apply(this, arguments);

			if (node[LOCKED]) {
				ctx.fillText("🔒", node.getBounding()[2] - 20, -10);
			}

			return res;
		};
	},
	async beforeRegisterNodeDef(nodeType) {
		const nodesArray = (nodes) => {
			if (nodes) {
				if (nodes instanceof Array) {
					return nodes;
				}
				return [nodes];
			}
			return Object.values(app.canvas.selected_nodes);
		};
		function unlockNode(nodes) {
			nodes = nodesArray(nodes);
			for (const node of nodes) {
				delete node[LOCKED];
			}
			app.graph.setDirtyCanvas(true, false);
		}
		function lockNode(nodes) {
			nodes = nodesArray(nodes);
			for (const node of nodes) {
				if (node[LOCKED]) continue;

				node[LOCKED] = true;
				// Same hack as above
				lockArray(node.pos, () => !!node[LOCKED]);

				// Size is set by both replacing the value and setting individual values
				// So define a new property that can prevent reassignment
				const sz = [node.size[0], node.size[1]];
				Object.defineProperty(node, "size", {
					get() {
						return sz;
					},
					set(value) {
						if (!node[LOCKED]) {
							sz[0] = value[0];
							sz[1] = value[1];
						}
					},
				});
				// And then lock each element if required
				lockArray(sz, () => !!node[LOCKED]);
			}

			app.graph.setDirtyCanvas(true, false);
		}

		// Add menu options for lock/unlock
		const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
		nodeType.prototype.getExtraMenuOptions = function (_, options) {
			const r = getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined;

			options.splice(
				options.findIndex((o) => o?.content === "Properties") + 1,
				0,
				null,
				this[LOCKED]
					? {
							content: "Unlock",
							callback: () => {
								unlockNode();
							},
					  }
					: {
							content: "Lock",
							callback: () => lockNode(),
					  }
			);

			return r;
		};

		// Add the locked flag to serialization
		const onSerialize = nodeType.prototype.onSerialize;
		nodeType.prototype.onSerialize = function (o) {
			if (onSerialize) {
				onSerialize.apply(this, arguments);
			}
			o.locked = this[LOCKED];
		};

		// On initial configure lock node if required
		const onConfigure = nodeType.prototype.onConfigure;
		nodeType.prototype.onConfigure = function (o) {
			if (onConfigure) {
				onConfigure.apply(this, arguments);
			}
			if (o.locked) {
				lockNode(this);
			}
		};
	},
});