Spaces:
Running
on
Zero
Running
on
Zero
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); | |
} | |
}; | |
}, | |
}); | |