Spaces:
Sleeping
Sleeping
File size: 5,682 Bytes
4450790 |
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 |
import { app } from "scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
import { NodeTypesString } from "./constants.js";
import type {
LGraph,
LGraphCanvas,
INumberWidget,
LGraphNode,
Vector2,
} from "typings/litegraph.js";
import { getClosestOrSelf, queryOne } from "rgthree/common/utils_dom.js";
/**
* A bookmark node. Can be placed anywhere in the workflow, and given a shortcut key that will
* navigate to that node, with it in the top-left corner.
*/
export class Bookmark extends RgthreeBaseVirtualNode {
static override type = NodeTypesString.BOOKMARK;
static override title = NodeTypesString.BOOKMARK;
override comfyClass = NodeTypesString.BOOKMARK;
// Really silly, but Litegraph assumes we have at least one input/output... so we need to
// counteract it's computeSize calculation by offsetting the start.
static slot_start_y = -20;
// LiteGraph adds mroe spacing than we want when calculating a nodes' `_collapsed_width`, so we'll
// override it with a setter and re-set it measured exactly as we want.
___collapsed_width: number = 0;
override isVirtualNode = true;
override serialize_widgets = true;
//@ts-ignore - TS Doesn't like us overriding a property with accessors but, too bad.
override get _collapsed_width() {
return this.___collapsed_width;
}
override set _collapsed_width(width: number) {
const canvas = app.canvas as LGraphCanvas;
const ctx = canvas.canvas.getContext("2d")!;
const oldFont = ctx.font;
ctx.font = canvas.title_text_font;
this.___collapsed_width = 40 + ctx.measureText(this.title).width;
ctx.font = oldFont;
}
readonly keypressBound;
constructor(title = Bookmark.title) {
super(title);
const nextShortcutChar = getNextShortcut();
this.addWidget(
"text",
"shortcut_key",
nextShortcutChar,
(value: string, ...args) => {
value = value.trim()[0] || "1";
},
{
y: 8,
},
);
this.addWidget<INumberWidget>("number", "zoom", 1, (value: number) => {}, {
y: 8 + LiteGraph.NODE_WIDGET_HEIGHT + 4,
max: 2,
min: 0.5,
precision: 2,
});
this.keypressBound = this.onKeypress.bind(this);
this.title = "🔖";
this.onConstructed();
}
// override computeSize(out?: Vector2 | undefined): Vector2 {
// super.computeSize(out);
// const minHeight = (this.widgets?.length || 0) * (LiteGraph.NODE_WIDGET_HEIGHT + 4) + 16;
// this.size[1] = Math.max(minHeight, this.size[1]);
// }
get shortcutKey(): string {
return this.widgets[0]?.value?.toLocaleLowerCase() ?? "";
}
override onAdded(graph: LGraph): void {
KEY_EVENT_SERVICE.addEventListener("keydown", this.keypressBound as EventListener);
}
override onRemoved(): void {
KEY_EVENT_SERVICE.removeEventListener("keydown", this.keypressBound as EventListener);
}
onKeypress(event: CustomEvent<{ originalEvent: KeyboardEvent }>) {
const originalEvent = event.detail.originalEvent;
const target = (originalEvent.target as HTMLElement)!;
if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) {
return;
}
// Only the shortcut keys are held down, otionally including "shift".
if (KEY_EVENT_SERVICE.areOnlyKeysDown(this.widgets[0]!.value, true)) {
this.canvasToBookmark();
originalEvent.preventDefault();
originalEvent.stopPropagation();
}
}
/**
* Called from LiteGraph's `processMouseDown` after it would invoke the input box for the
* shortcut_key, so we check if it exists and then add our own event listener so we can track the
* keys down for the user.
*/
override onMouseDown(event: MouseEvent, pos: Vector2, graphCanvas: LGraphCanvas): void {
const input = queryOne<HTMLInputElement>(".graphdialog > input.value");
if (input && input.value === this.widgets[0]?.value) {
input.addEventListener("keydown", (e) => {
// ComfyUI swallows keydown on inputs, so we need to call out to rgthree to use downkeys.
KEY_EVENT_SERVICE.handleKeyDownOrUp(e);
e.preventDefault();
e.stopPropagation();
input.value = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" + ");
});
}
}
canvasToBookmark() {
const canvas = app.canvas as LGraphCanvas;
// ComfyUI seemed to break us again, but couldn't repro. No reason to not check, I guess.
// https://github.com/rgthree/rgthree-comfy/issues/71
if (canvas?.ds?.offset) {
canvas.ds.offset[0] = -this.pos[0] + 16;
canvas.ds.offset[1] = -this.pos[1] + 40;
}
if (canvas?.ds?.scale != null) {
canvas.ds.scale = Number(this.widgets[1]!.value || 1);
}
canvas.setDirty(true, true);
}
}
app.registerExtension({
name: "rgthree.Bookmark",
registerCustomNodes() {
Bookmark.setUp();
},
});
function isBookmark(node: LGraphNode): node is Bookmark {
return node.type === NodeTypesString.BOOKMARK;
}
function getExistingShortcuts() {
const graph: LGraph = app.graph;
const bookmarkNodes = graph._nodes.filter(isBookmark);
const usedShortcuts = new Set(bookmarkNodes.map((n) => n.shortcutKey));
return usedShortcuts;
}
const SHORTCUT_DEFAULTS = "1234567890abcdefghijklmnopqrstuvwxyz".split("");
function getNextShortcut() {
const existingShortcuts = getExistingShortcuts();
return SHORTCUT_DEFAULTS.find((char) => !existingShortcuts.has(char)) ?? "1";
}
|