Browse Source

Adds several keybinds that interact with ComfyUI (#491)

* adds keybinds that interact w/ comfy menu

* adds remaining keybinds

* adds keybinds to readme and converts to table

* ctrl s and o save and open workflow

* moves keybinds to sep file, update readme

* remap load default, support keycodes

* update keybinds table, prepends comfy to ids

* escape exits out of modals

* modifier keys also use map

* adds setting for filename prompt

* better handle filename prompt

Co-authored-by: missionfloyd <missionfloyd@users.noreply.github.com>
pull/525/head
Karun 2 years ago committed by GitHub
parent
commit
bc16b70bde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      README.md
  2. 76
      web/extensions/core/keybinds.js
  3. 6
      web/scripts/app.js
  4. 32
      web/scripts/ui.js

30
README.md

@ -32,14 +32,28 @@ This ui will let you design and execute advanced stable diffusion pipelines usin
Workflow examples can be found on the [Examples page](https://comfyanonymous.github.io/ComfyUI_examples/) Workflow examples can be found on the [Examples page](https://comfyanonymous.github.io/ComfyUI_examples/)
## Shortcuts ## Shortcuts
- **Ctrl + A** select all nodes
- **Ctrl + M** mute/unmute selected nodes | Keybind | Explanation |
- **Delete** or **Backspace** delete selected nodes | - | - |
- **Space** Holding space key while moving the cursor moves the canvas around. It works when holding the mouse button down so it is easier to connect different nodes when the canvas gets too large. | Ctrl + Enter | Queue up current graph for generation |
- **Ctrl/Shift + Click** Add clicked node to selection. | Ctrl + Shift + Enter | Queue up current graph as first for generation |
- **Ctrl + C/Ctrl + V** - Copy and paste selected nodes, without maintaining the connection to the outputs of unselected nodes. | Ctrl + S | Save workflow |
- **Ctrl + C/Ctrl + Shift + V** - Copy and paste selected nodes, and maintaining the connection from the outputs of unselected nodes to the inputs of the newly pasted nodes. | Ctrl + O | Load workflow |
- Holding **Shift** and drag selected nodes - Move multiple selected nodes at the same time. | Ctrl + A | Select all nodes |
| Ctrl + M | Mute/unmute selected nodes |
| Delete/Backspace | Delete selected nodes |
| Ctrl + Delete/Backspace | Delete the current graph |
| Space | Move the canvas around when held and moving the cursor |
| Ctrl/Shift + Click | Add clicked node to selection |
| Ctrl + C/Ctrl + V | Copy and paste selected nodes (without maintaining connections to outputs of unselected nodes) |
| Ctrl + C/Ctrl + Shift + V| Copy and paste selected nodes (maintaining connections from outputs of unselected nodes to inputs of pasted nodes) |
| Shift + Drag | Move multiple selected nodes at the same time |
| Ctrl + D | Load default graph |
| Q | Toggle visibility of the queue |
| H | Toggle visibility of history |
| R | Refresh graph |
Ctrl can also be replaced with Cmd instead for MacOS users
# Installing # Installing

76
web/extensions/core/keybinds.js

@ -0,0 +1,76 @@
import { app } from "/scripts/app.js";
const id = "Comfy.Keybinds";
app.registerExtension({
name: id,
init() {
const keybindListener = function(event) {
const target = event.composedPath()[0];
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
return;
}
const modifierPressed = event.ctrlKey || event.metaKey;
// Queue prompt using ctrl or command + enter
if (modifierPressed && (event.key === "Enter" || event.keyCode === 13 || event.keyCode === 10)) {
app.queuePrompt(event.shiftKey ? -1 : 0);
return;
}
const modifierKeyIdMap = {
"s": "#comfy-save-button",
83: "#comfy-save-button",
"o": "#comfy-file-input",
79: "#comfy-file-input",
"Backspace": "#comfy-clear-button",
8: "#comfy-clear-button",
"Delete": "#comfy-clear-button",
46: "#comfy-clear-button",
"d": "#comfy-load-default-button",
68: "#comfy-load-default-button",
};
const modifierKeybindId = modifierKeyIdMap[event.key] || modifierKeyIdMap[event.keyCode];
if (modifierPressed && modifierKeybindId) {
event.preventDefault();
const elem = document.querySelector(modifierKeybindId);
elem.click();
return;
}
// Finished Handling all modifier keybinds, now handle the rest
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
// Close out of modals using escape
if (event.key === "Escape" || event.keyCode === 27) {
const modals = document.querySelectorAll(".comfy-modal");
const modal = Array.from(modals).find(modal => window.getComputedStyle(modal).getPropertyValue("display") !== "none");
if (modal) {
modal.style.display = "none";
}
}
const keyIdMap = {
"q": "#comfy-view-queue-button",
81: "#comfy-view-queue-button",
"h": "#comfy-view-history-button",
72: "#comfy-view-history-button",
"r": "#comfy-refresh-button",
82: "#comfy-refresh-button",
};
const buttonId = keyIdMap[event.key] || keyIdMap[event.keyCode];
if (buttonId) {
const button = document.querySelector(buttonId);
button.click();
}
}
window.addEventListener("keydown", keybindListener, true);
}
});

6
web/scripts/app.js

@ -35,7 +35,6 @@ export class ComfyApp {
*/ */
this.nodeOutputs = {}; this.nodeOutputs = {};
/** /**
* If the shift key on the keyboard is pressed * If the shift key on the keyboard is pressed
* @type {boolean} * @type {boolean}
@ -713,11 +712,6 @@ export class ComfyApp {
#addKeyboardHandler() { #addKeyboardHandler() {
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
this.shiftDown = e.shiftKey; this.shiftDown = e.shiftKey;
// Queue prompt using ctrl or command + enter
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) {
this.queuePrompt(e.shiftKey ? -1 : 0);
}
}); });
window.addEventListener("keyup", (e) => { window.addEventListener("keyup", (e) => {
this.shiftDown = e.shiftKey; this.shiftDown = e.shiftKey;

32
web/scripts/ui.js

@ -431,7 +431,15 @@ export class ComfyUI {
defaultValue: true, defaultValue: true,
}); });
const promptFilename = this.settings.addSetting({
id: "Comfy.PromptFilename",
name: "Prompt for filename when saving workflow",
type: "boolean",
defaultValue: true,
});
const fileInput = $el("input", { const fileInput = $el("input", {
id: "comfy-file-input",
type: "file", type: "file",
accept: ".json,image/png", accept: ".json,image/png",
style: { display: "none" }, style: { display: "none" },
@ -448,6 +456,7 @@ export class ComfyUI {
$el("button.comfy-settings-btn", { textContent: "⚙", onclick: () => this.settings.show() }), $el("button.comfy-settings-btn", { textContent: "⚙", onclick: () => this.settings.show() }),
]), ]),
$el("button.comfy-queue-btn", { $el("button.comfy-queue-btn", {
id: "queue-button",
textContent: "Queue Prompt", textContent: "Queue Prompt",
onclick: () => app.queuePrompt(0, this.batchCount), onclick: () => app.queuePrompt(0, this.batchCount),
}), }),
@ -496,9 +505,10 @@ export class ComfyUI {
]), ]),
]), ]),
$el("div.comfy-menu-btns", [ $el("div.comfy-menu-btns", [
$el("button", { textContent: "Queue Front", onclick: () => app.queuePrompt(-1, this.batchCount) }), $el("button", { id: "queue-front-button", textContent: "Queue Front", onclick: () => app.queuePrompt(-1, this.batchCount) }),
$el("button", { $el("button", {
$: (b) => (this.queue.button = b), $: (b) => (this.queue.button = b),
id: "comfy-view-queue-button",
textContent: "View Queue", textContent: "View Queue",
onclick: () => { onclick: () => {
this.history.hide(); this.history.hide();
@ -507,6 +517,7 @@ export class ComfyUI {
}), }),
$el("button", { $el("button", {
$: (b) => (this.history.button = b), $: (b) => (this.history.button = b),
id: "comfy-view-history-button",
textContent: "View History", textContent: "View History",
onclick: () => { onclick: () => {
this.queue.hide(); this.queue.hide();
@ -517,14 +528,23 @@ export class ComfyUI {
this.queue.element, this.queue.element,
this.history.element, this.history.element,
$el("button", { $el("button", {
id: "comfy-save-button",
textContent: "Save", textContent: "Save",
onclick: () => { onclick: () => {
let filename = "workflow.json";
if (promptFilename.value) {
filename = prompt("Save workflow as:", filename);
if (!filename) return;
if (!filename.toLowerCase().endsWith(".json")) {
filename += ".json";
}
}
const json = JSON.stringify(app.graph.serialize(), null, 2); // convert the data to a JSON string const json = JSON.stringify(app.graph.serialize(), null, 2); // convert the data to a JSON string
const blob = new Blob([json], { type: "application/json" }); const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = $el("a", { const a = $el("a", {
href: url, href: url,
download: "workflow.json", download: filename,
style: { display: "none" }, style: { display: "none" },
parent: document.body, parent: document.body,
}); });
@ -535,15 +555,15 @@ export class ComfyUI {
}, 0); }, 0);
}, },
}), }),
$el("button", { textContent: "Load", onclick: () => fileInput.click() }), $el("button", { id: "comfy-load-button", textContent: "Load", onclick: () => fileInput.click() }),
$el("button", { textContent: "Refresh", onclick: () => app.refreshComboInNodes() }), $el("button", { id: "comfy-refresh-button", textContent: "Refresh", onclick: () => app.refreshComboInNodes() }),
$el("button", { textContent: "Clear", onclick: () => { $el("button", { id: "comfy-clear-button", textContent: "Clear", onclick: () => {
if (!confirmClear.value || confirm("Clear workflow?")) { if (!confirmClear.value || confirm("Clear workflow?")) {
app.clean(); app.clean();
app.graph.clear(); app.graph.clear();
} }
}}), }}),
$el("button", { textContent: "Load Default", onclick: () => { $el("button", { id: "comfy-load-default-button", textContent: "Load Default", onclick: () => {
if (!confirmClear.value || confirm("Load default workflow?")) { if (!confirmClear.value || confirm("Load default workflow?")) {
app.loadGraphData() app.loadGraphData()
} }

Loading…
Cancel
Save