Files

466 lines
15 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { getStorageValue, setStorageValue } from "../../scripts/utils.js";
const hiddenLinkMode = -1;
const slotOffset = 5;
const nodeOptionsWhitelist = [
"Open Image",
"Save Image",
"Copy Image",
"Open in MaskEditor"
];
const classDisplayDisabledBlacklist = [
"Note",
"MarkdownNote"
];
const classLockedDisableBlacklist = [
"PrimitiveNode"
];
const classLockedEnableBlacklist = [
"Reroute",
"Reroute (rgthree)"
];
const nodeDefaultAllowInteractionValue = true;
const pageCss = `
.disabled {
pointer-events: none !important;
cursor: not-allowed !important;
}
.disabled a {
pointer-events: auto !important;
}
.display_disabled {
opacity: 0.5 !important;
}
.display_enabled {
opacity: 1 !important;
}
`;
function updateNodesWidgetsDisabledState(disabled) {
const nodes = app.graph.nodes;
nodes.forEach((node) => {
const force_allow_interaction = (classLockedDisableBlacklist.indexOf(node.type) > -1);
const allow_interaction = force_allow_interaction || (node.flags.allow_interaction ?? false);
const widget_disabled = disabled && !allow_interaction;
const display_as_disabled = widget_disabled && !(classDisplayDisabledBlacklist.indexOf(node.type) > -1);
const widgets = node.widgets ?? [];
widgets.forEach((widget) => {
widget.disabled = widget_disabled;
if (widget.classList) {
if (display_as_disabled) {
widget.classList.remove('display_enabled');
} else {
widget.classList.add('display_enabled');
}
}
if (widget.element) {
widget.element.disabled = widget_disabled;
if (widget.element.classList) {
if (widget_disabled) {
widget.element.classList.add('disabled');
} else {
widget.element.classList.remove('disabled');
}
if (display_as_disabled) {
widget.element.classList.add('display_disabled');
} else {
widget.element.classList.remove('display_disabled');
}
}
}
});
});
}
function updateSelectionOverlayDisabledState(disabled) {
const overlays = document.querySelectorAll(".selection-overlay-container,.selection-toolbox");
[].forEach.call(overlays, (element) => {
if (disabled) {
element.classList.add('hidden');
} else {
element.classList.remove('hidden');
}
});
}
function updateNodesLinksDisabledState(disabled) {
const linkRenderMode = app.extensionManager.setting.get("Comfy.LinkRenderMode");
const linkHiddenState = (linkRenderMode === hiddenLinkMode);
// Set node links visibility state
if (disabled !== linkHiddenState) {
app.extensionManager.command.execute("Comfy.Canvas.ToggleLinkVisibility");
}
}
function getSelectedNodes() {
const selectedNodes = app.canvas.selected_nodes;
const result = [];
if (selectedNodes) {
for (const i in selectedNodes) {
const node = selectedNodes[i];
result.push(node);
}
}
return result;
}
function setSelectedNodesInteractionState(value) {
getSelectedNodes().forEach((node) => {
node.flags.allow_interaction = value;
})
}
app.registerExtension({
name: "Comfy.LockMode",
init() {
if (getStorageValue("LockMode.Enabled") == null) {
setStorageValue("LockMode.Enabled", true);
}
const $style = document.createElement("style");
$style.innerHTML = pageCss;
document.head.appendChild($style);
},
beforeRegisterNodeDef(nodeType, nodeData, app) {
// Add allow/deny in locked mode switch
const original_getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function(_, options) {
original_getExtraMenuOptions?.apply(this, arguments);
options.push({
content: this.flags.allow_interaction ? "Deny in locked mode" : "Allow in locked mode",
callback: async () => {
const mode = !this.flags.allow_interaction;
for (const item of app.canvas.selectedItems) {
if (item instanceof LGraphNode) item.flags.allow_interaction = mode;
}
app.graph.change();
}
})
}
},
beforeConfigureGraph(graphData, missingNodeTypes) {
// Allow node creation on entire graph loading
app.graph._loading = true;
},
afterConfigureGraph() {
// And deny after it, if we are in locked mode
app.graph._loading = false;
// Lock UI widgets, especially multiline text
const isLockModeEnabled = (getStorageValue("LockMode.Enabled") === "true");
updateNodesWidgetsDisabledState(isLockModeEnabled);
updateSelectionOverlayDisabledState(isLockModeEnabled);
updateNodesLinksDisabledState(isLockModeEnabled);
// Reset view after graph load
app.resetView();
},
nodeCreated(node) {
// Add default allow_interaction value
node.flags.allow_interaction = nodeDefaultAllowInteractionValue;
// Add lock icon to node title
const original_getTitle = node.getTitle;
node.getTitle = function() {
const locked = !this.flags.allow_interaction;
return original_getTitle.call(this) + (locked ? "🔒" : "");
}
},
commands: [
{
id: 'lockMode.selection.unlock',
label: 'Allow in locked mode',
icon: 'pi pi-lock-open',
function: () => {
setSelectedNodesInteractionState(true);
app.canvas.deselectAllNodes();
}
},
{
id: 'lockMode.selection.lock',
label: 'Deny in locked mode',
icon: 'pi pi-lock',
function: () => {
setSelectedNodesInteractionState(false);
app.canvas.deselectAllNodes();
}
}
],
getSelectionToolboxCommands: (selectedItem) => {
if (selectedItem.flags.allow_interaction) {
return ['lockMode.selection.lock'];
} else {
return ['lockMode.selection.unlock'];
}
},
async setup() {
let lockButton;
let unlockButton;
let canvasPasteFromClipboard = app.canvas._pasteFromClipboard;
let canvasShowLinkMenu = app.canvas.showLinkMenu;
let canvasGetNodeMenuOptions = app.canvas.getNodeMenuOptions;
let canvasDeleteSelected = app.canvas.deleteSelected;
let graphAdd = app.graph.add;
let graphClear = app.graph.clear;
function canvasOnMouse(e2) {
const { pointer, graph } = this;
const x2 = e2.canvasX;
const y2 = e2.canvasY;
// Allow dragging canvas
pointer.finally = () => this.dragging_canvas = false
this.dragging_canvas = true
// Stop double clicks
pointer.eLastDown = void 0;
// Stop mouse interaction with ctrl/meta keys
const ctrlOrMeta = e2.ctrlKey || e2.metaKey;
if (ctrlOrMeta) {
return true;
}
// Stop mouse interaction with alt key
if (e2.altKey) {
return true;
}
// Stop mouse interaction with shift key
if (e2.shiftKey) {
return true;
}
const node = graph.getNodeOnPos(x2, y2, this.visible_nodes);
if (node) {
// Stop interaction with collapsed nodes
if (node.flags.collapsed) {
return true;
}
// Do not block interaction with this nodes
if (!(classLockedDisableBlacklist.indexOf(node.type) > -1)) {
// Stop interaction with disabled nodes
if (!node.flags.allow_interaction) {
return true;
}
// Stop interaction with blacklisted nodes
if ((classLockedEnableBlacklist.indexOf(node.type) > -1)) {
return true;
}
}
// Stop interaction with node resize corner
if (node.resizable !== false && node.inResizeCorner(x2, y2)) {
return true;
}
// Stop interaction with node name and collapse button
const posY = y2 - node.pos[1];
if (posY < 0) {
return true;
}
// Stop interaction with node links (slots)
const slot = node.getSlotOnPos([x2, y2]);
if (slot) {
const minX = node.pos[0] + slot.boundingRect[0];
const maxX = minX + slot.boundingRect[2];
if (x2 >= (minX - slotOffset) && x2 <= (maxX + slotOffset)) {
return true;
}
}
} else {
// Stop interaction with groups outside the nodes
const group = graph.getGroupOnPos(x2, y2);
if (group) {
return true;
}
// Stop right clicks outside the nodes
if (e2.button === 2) {
return true;
}
}
// Pass all other clicks
return false;
}
function canvasGetNodeMenuOptionsLocked(node) {
let options = [];
// Get only extra menu options
const extra = node.getExtraMenuOptions?.(this, options);
if (Array.isArray(extra) && extra.length > 0) {
extra.push(null);
options = extra.concat(options);
}
// And filter it with whitelist
options = options.filter(option => option && (nodeOptionsWhitelist.indexOf(option.content) > -1));
// Return only if not empty
if (options.length > 0) {
return options;
}
return null;
}
function graphAddLocked(obj) {
// Only allow adding if in initial loading state
if (this._loading) {
return graphAdd.call(this, obj);
}
return null;
}
function graphClearLocked() {
// Only allow adding if in initial loading state
if (this._loading) {
return graphClear.call(this);
}
return null;
}
function setLockMode(mode) {
const isLockModeEnabled = (getStorageValue("LockMode.Enabled") === "true");
if (mode != isLockModeEnabled) {
try {
setStorageValue("LockMode.Enabled", mode)
updateButtons();
updateMode();
updateWidgets();
updateSelectionOverlay();
updateNodeLinks();
} catch(exception) {
console.log('Unexpected error when switching modes.');
}
}
}
function updateButtons() {
const isLockModeEnabled = (getStorageValue("LockMode.Enabled") === "true");
// Set buttons style
if (isLockModeEnabled) {
lockButton.classList.add("primary");
unlockButton.classList.remove("primary");
} else {
lockButton.classList.remove("primary");
unlockButton.classList.add("primary");
}
}
function updateMode() {
const isLockModeEnabled = (getStorageValue("LockMode.Enabled") === "true");
if (isLockModeEnabled) {
app.canvas.allow_dragnodes = false;
app.canvas.allow_reconnect_links = false;
app.canvas.onMouse = canvasOnMouse;
app.canvas._pasteFromClipboard = () => {};
app.canvas.showLinkMenu = () => {};
app.canvas.getNodeMenuOptions = canvasGetNodeMenuOptionsLocked;
app.canvas.deleteSelected = () => {};
app.graph.add = graphAddLocked;
app.graph.clear = graphClearLocked;
} else {
app.canvas.allow_dragnodes = true;
app.canvas.allow_reconnect_links = true;
app.canvas.onMouse = null;
app.canvas._pasteFromClipboard = canvasPasteFromClipboard;
app.canvas.showLinkMenu = canvasShowLinkMenu;
app.canvas.getNodeMenuOptions = canvasGetNodeMenuOptions;
app.canvas.deleteSelected = canvasDeleteSelected;
app.graph.add = graphAdd;
app.graph.clear = graphClear;
}
}
function updateWidgets() {
const isLockModeEnabled = (getStorageValue("LockMode.Enabled") === "true");
// Set widgets disabled state
updateNodesWidgetsDisabledState(isLockModeEnabled);
}
function updateSelectionOverlay() {
const isLockModeEnabled = (getStorageValue("LockMode.Enabled") === "true");
// Set selection overlay disabled state
updateSelectionOverlayDisabledState(isLockModeEnabled);
}
function updateNodeLinks() {
const isLockModeEnabled = (getStorageValue("LockMode.Enabled") === "true");
// Set node links visibility state
updateNodesLinksDisabledState(isLockModeEnabled);
}
try {
// new style Manager buttons
// unload models button into new style Manager button
lockButton = new(await import("/scripts/ui/components/button.js")).ComfyButton({
icon: "lock",
action: () => {
setLockMode(true);
},
tooltip: "Lock nodes",
classList: "comfyui-button comfyui-menu-mobile-collapse"
}).element
unlockButton = new(await import("/scripts/ui/components/button.js")).ComfyButton({
icon: "lock-open-variant",
action: () => {
setLockMode(false);
},
tooltip: "Unlock nodes",
classList: "comfyui-button comfyui-menu-mobile-collapse"
}).element
let cmGroup = new (await import("/scripts/ui/components/buttonGroup.js")).ComfyButtonGroup(
lockButton, unlockButton
);
app.menu?.settingsGroup.element.before(cmGroup.element);
updateButtons();
updateMode();
} catch(exception) {
console.log('ComfyUI is outdated. New style menu based features are disabled.');
}
},
});