Enhance bookmarks to allow for multiple-key shortcuts, and make the key check exclusive (except for shift). Helps #227

This commit is contained in:
rgthree
2024-05-15 22:04:15 -04:00
parent ad6029145c
commit f4eba67933
5 changed files with 184 additions and 79 deletions
+4 -1
View File
@@ -70,8 +70,11 @@ Note, you can right-click on a bunch of the rgthree-comfy nodes and select `🛟
> <details>
> <summary>ℹ️ <i>See More Information</i></summary>
>
> - Define the `sahortvut_key` to press to go right to that bookmark node, anchored in the top left
> - Define the `shortcut_key` to press to go right to that bookmark node, anchored in the top left.
> - You can also define the zoom level as well!
> - Pro tip: `shortcut_key` can be multiple keys. For instance "alt + shift + !" would require
> pressing the alt key, the shift key, and the "!" (as in the "1" key, but with shift pressed)
> in order to trigger.
> </details>
+30 -27
View File
@@ -2,15 +2,15 @@ import { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js";
// @ts-ignore
import { app } from "../../scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { rgthree } from "./rgthree.js";
import { NodeTypesString } from "./constants.js";
import type {
LGraph as TLGraph,
LiteGraph as TLiteGraph,
LGraphCanvas as TLGraphCanvas,
ISliderWidget,
INumberWidget,
Vector2,
} from "typings/litegraph.js";
import { getClosestOrSelf } from "rgthree/common/utils_dom.js";
declare const LiteGraph: typeof TLiteGraph;
@@ -19,7 +19,6 @@ declare const LiteGraph: typeof TLiteGraph;
* 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;
@@ -32,7 +31,6 @@ export class Bookmark extends RgthreeBaseVirtualNode {
// 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;
@@ -43,10 +41,10 @@ export class Bookmark extends RgthreeBaseVirtualNode {
override set _collapsed_width(width: number) {
const canvas = app.canvas as TLGraphCanvas;
const ctx = canvas.canvas.getContext('2d')!;
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;
this.___collapsed_width = 40 + ctx.measureText(this.title).width;
ctx.font = oldFont;
}
@@ -54,14 +52,18 @@ export class Bookmark extends RgthreeBaseVirtualNode {
constructor(title = Bookmark.title) {
super(title);
this.addWidget('text', 'shortcut_key', '1', (value: string, ...args) => {
value = value.trim()[0] || '1';
},{
y: 8,
});
this.addWidget<INumberWidget>('number', 'zoom', 1, (value: number) => {
}, {
this.addWidget(
"text",
"shortcut_key",
"1",
(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,
@@ -78,28 +80,29 @@ export class Bookmark extends RgthreeBaseVirtualNode {
// }
get shortcutKey(): string {
return this.widgets[0]?.value?.toLocaleLowerCase() ?? '';
return this.widgets[0]?.value?.toLocaleLowerCase() ?? "";
}
override onAdded(graph: TLGraph): void {
window.addEventListener("keydown", this.keypressBound);
rgthree.addEventListener("keydown", this.keypressBound as EventListener);
}
override onRemoved(): void {
window.removeEventListener("keydown", this.keypressBound);
rgthree.removeEventListener("keydown", this.keypressBound as EventListener);
}
async onKeypress(event: KeyboardEvent) {
const target = (event.target as HTMLElement)!;
// Span because the properties panel uses a contenteditable <span>
if (['input','textarea', 'span'].includes(target.localName)) {
onKeypress(event: CustomEvent<{ originalEvent: KeyboardEvent }>) {
const originalEvent = event.detail.originalEvent;
const target = (originalEvent.target as HTMLElement)!;
if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) {
return;
}
if (event.ctrlKey || event.metaKey || event.altKey) {
return;
}
if (event.key.toLocaleLowerCase() === this.shortcutKey) {
// Only the shortcut keys are held down, otionally including "shift".
if (rgthree.areOnlyKeysDown(this.widgets[0]!.value, true)) {
this.canvasToBookmark();
originalEvent.preventDefault();
originalEvent.stopPropagation();
}
}
@@ -108,8 +111,8 @@ export class Bookmark extends RgthreeBaseVirtualNode {
// 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;
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);
+90 -28
View File
@@ -22,7 +22,7 @@ import { NodeTypesString } from "./constants.js";
import { RgthreeProgressBar } from "rgthree/common/progress_bar.js";
import { RgthreeConfigDialog } from "./config.js";
import { iconGear, iconReplace, iconStarFilled, logoRgthree } from "rgthree/common/media/svgs.js";
import type { Bookmark } from './bookmark';
import type { Bookmark } from "./bookmark";
declare const LiteGraph: typeof TLiteGraph;
declare const LGraphCanvas: typeof TLGraphCanvas;
@@ -121,14 +121,12 @@ class Logger {
}
}
/**
* A log session, with the name as the prefix. A new session will stack prefixes.
*/
class LogSession {
readonly logger = new Logger();
readonly logsCache: {[key: string]: {lastShownTime: number}} = {};
readonly logsCache: { [key: string]: { lastShownTime: number } } = {};
constructor(readonly name?: string) {}
@@ -139,16 +137,17 @@ class LogSession {
* const [logMethod, logArgs] = logger.logParts(LogLevel.DEBUG, message, ...args);
* console[logMethod]?.(...logArgs);
*/
logParts(level: LogLevel, message?: string, ...args: any[]) : [ConsoleLogFns, any[]] {
logParts(level: LogLevel, message?: string, ...args: any[]): [ConsoleLogFns, any[]] {
message = `${this.name || ""}${message ? " " + message : ""}`;
return this.logger.logParts(
level,
message,
...args,
);
return this.logger.logParts(level, message, ...args);
}
logPartsOnceForTime(level: LogLevel, time: number, message?: string, ...args: any[]) : [ConsoleLogFns, any[]] {
logPartsOnceForTime(
level: LogLevel,
time: number,
message?: string,
...args: any[]
): [ConsoleLogFns, any[]] {
message = `${this.name || ""}${message ? " " + message : ""}`;
const cacheKey = `${level}:${message}`;
const cacheEntry = this.logsCache[cacheKey];
@@ -156,17 +155,12 @@ class LogSession {
if (cacheEntry && cacheEntry.lastShownTime + time > now) {
return ["none" as "info", []];
}
const parts = this.logger.logParts(
level,
message,
...args,
);
const parts = this.logger.logParts(level, message, ...args);
if (console[parts[0]]) {
this.logsCache[cacheKey] = this.logsCache[cacheKey] || {} as {lastShownTime: number};
this.logsCache[cacheKey] = this.logsCache[cacheKey] || ({} as { lastShownTime: number });
this.logsCache[cacheKey]!.lastShownTime = now;
}
return parts;
}
debugParts(message?: string, ...args: any[]) {
@@ -251,6 +245,18 @@ class Rgthree extends EventTarget {
this.handleKeyup(e);
});
// If we get a visibilitychange, then clear the keys since we can't listen for keys up/down when
// not visible.
document.addEventListener("visibilitychange", (e) => {
this.clearKeydowns();
});
// If we get a blur, then also clear the keys since we can't listen for keys up/down when
// blurred. This can happen w/o a visibilitychange, like a browser alert.
window.addEventListener("blur", (e) => {
this.clearKeydowns();
});
this.initializeGraphAndCanvasHooks();
this.initializeComfyUIHooks();
this.initializeContextMenu();
@@ -852,8 +858,19 @@ class Rgthree extends EventTarget {
container && (container.innerHTML = "");
}
private clearKeydowns() {
this.ctrlKey = false;
this.altKey = false;
this.metaKey = false;
this.shiftKey = false;
for (const key in this.downKeys) delete this.downKeys[key];
}
/**
* Handle keydown. Pulled out because sometimes a node will get a keydown before rgthree.
* Handle keydown. Pulled out because sometimes a node will get a keydown before rgthree and call
* into this..
*
* Note: ComfyUI blocks Space, Esc, Delete, and Backspace.
*/
handleKeydown(e: KeyboardEvent) {
this.ctrlKey = !!e.ctrlKey;
@@ -861,33 +878,78 @@ class Rgthree extends EventTarget {
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
this.downKeys[e.key.toLocaleUpperCase()] = true;
this.downKeys["^" + e.key.toLocaleUpperCase()] = true;
this.dispatchCustomEvent("keydown", { originalEvent: e });
}
/**
* Handle keyup. Pulled out because sometimes a node will get a keyup before rgthree.
* Handle keyup. Pulled out because sometimes a node will get a keyup before rgthree and call
* into this.
*/
handleKeyup(e: KeyboardEvent) {
this.ctrlKey = !!e.ctrlKey;
this.altKey = !!e.altKey;
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
this.downKeys[e.key.toLocaleUpperCase()] = false;
this.downKeys["^" + e.key.toLocaleUpperCase()] = false;
delete this.downKeys[e.key.toLocaleUpperCase()];
this.dispatchCustomEvent("keyup", { originalEvent: e });
}
/**
* Parses a shortcut string.
*
* - 's' => ['S']
* - 'shift + c' => ['SHIFT', 'C']
* - 'shift + meta + @' => ['SHIFT', 'META', '@']
* - 'shift + + + @' => ['SHIFT', '__PLUS__', '=']
* - '+ + p' => ['__PLUS__', 'P']
*/
private getKeysFromShortcut(shortcut: string | string[]) {
let keys;
if (typeof shortcut === "string") {
// Rip all spaces out. Note, Comfy swallows space, so we don't have to handle it. Otherwise,
// we would require space to be fed as "Space" or "Spacebar" instead of " ".
shortcut = shortcut.replace(/\s/g, "");
// Change a real "+" to something we can encode.
shortcut = shortcut.replace(/^\+/, "__PLUS__").replace(/\+\+/, "+__PLUS__");
keys = shortcut.split("+").map((i) => i.replace("__PLUS__", "+"));
} else {
keys = [...shortcut];
}
return keys.map((k) => k.toLocaleUpperCase());
}
/**
* Checks if all keys passed in are down.
*/
areAllKeysDown(keys: string[], caseSensitive = false) {
areAllKeysDown(keys: string | string[]) {
keys = this.getKeysFromShortcut(keys);
return keys.every((k) => {
if (caseSensitive) {
return rgthree.downKeys["^" + k.trim()];
}
return rgthree.downKeys[k.trim().toUpperCase()];
return rgthree.downKeys[k];
});
}
/**
* Checks if only the keys passed in are down; optionally and additionally allowing "shift" key.
*/
areOnlyKeysDown(keys: string | string[], alsoAllowShift = false) {
keys = this.getKeysFromShortcut(keys);
const allKeysDown = this.areAllKeysDown(keys);
const downKeysLength = Object.values(rgthree.downKeys).length;
// All keys are down and they're the only ones.
if (allKeysDown && keys.length === downKeysLength) {
return true;
}
// Special case allowing the shift key in addition to the shortcut keys. This helps when a user
// may had originally defined "$" as a shortcut, but needs to press "shift + $" since it's an
// upper key character, etc.
if (alsoAllowShift && !keys.includes("SHIFT") && keys.length === downKeysLength - 1) {
// If we're holding down shift, have one extra key held down, and the original keys don't
// include shift, then we're good to go.
return this.areAllKeysDown(["SHIFT"]);
}
return false;
}
/**
* Injects the rgthree.css file into the app.
*/
+16 -15
View File
@@ -1,13 +1,15 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { rgthree } from "./rgthree.js";
import { NodeTypesString } from "./constants.js";
import { getClosestOrSelf } from "../../rgthree/common/utils_dom.js";
export class Bookmark extends RgthreeBaseVirtualNode {
get _collapsed_width() {
return this.___collapsed_width;
}
set _collapsed_width(width) {
const canvas = app.canvas;
const ctx = canvas.canvas.getContext('2d');
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;
@@ -19,13 +21,12 @@ export class Bookmark extends RgthreeBaseVirtualNode {
this.___collapsed_width = 0;
this.isVirtualNode = true;
this.serialize_widgets = true;
this.addWidget('text', 'shortcut_key', '1', (value, ...args) => {
value = value.trim()[0] || '1';
this.addWidget("text", "shortcut_key", "1", (value, ...args) => {
value = value.trim()[0] || "1";
}, {
y: 8,
});
this.addWidget('number', 'zoom', 1, (value) => {
}, {
this.addWidget("number", "zoom", 1, (value) => { }, {
y: 8 + LiteGraph.NODE_WIDGET_HEIGHT + 4,
max: 2,
min: 0.5,
@@ -36,24 +37,24 @@ export class Bookmark extends RgthreeBaseVirtualNode {
}
get shortcutKey() {
var _a, _b, _c;
return (_c = (_b = (_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.toLocaleLowerCase()) !== null && _c !== void 0 ? _c : '';
return (_c = (_b = (_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.toLocaleLowerCase()) !== null && _c !== void 0 ? _c : "";
}
onAdded(graph) {
window.addEventListener("keydown", this.keypressBound);
rgthree.addEventListener("keydown", this.keypressBound);
}
onRemoved() {
window.removeEventListener("keydown", this.keypressBound);
rgthree.removeEventListener("keydown", this.keypressBound);
}
async onKeypress(event) {
const target = event.target;
if (['input', 'textarea', 'span'].includes(target.localName)) {
onKeypress(event) {
const originalEvent = event.detail.originalEvent;
const target = originalEvent.target;
if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) {
return;
}
if (event.ctrlKey || event.metaKey || event.altKey) {
return;
}
if (event.key.toLocaleLowerCase() === this.shortcutKey) {
if (rgthree.areOnlyKeysDown(this.widgets[0].value, true)) {
this.canvasToBookmark();
originalEvent.preventDefault();
originalEvent.stopPropagation();
}
}
canvasToBookmark() {
+44 -8
View File
@@ -138,6 +138,12 @@ class Rgthree extends EventTarget {
window.addEventListener("keyup", (e) => {
this.handleKeyup(e);
});
document.addEventListener("visibilitychange", (e) => {
this.clearKeydowns();
});
window.addEventListener("blur", (e) => {
this.clearKeydowns();
});
this.initializeGraphAndCanvasHooks();
this.initializeComfyUIHooks();
this.initializeContextMenu();
@@ -584,30 +590,60 @@ class Rgthree extends EventTarget {
let container = document.querySelector(".rgthree-top-messages-container");
container && (container.innerHTML = "");
}
clearKeydowns() {
this.ctrlKey = false;
this.altKey = false;
this.metaKey = false;
this.shiftKey = false;
for (const key in this.downKeys)
delete this.downKeys[key];
}
handleKeydown(e) {
this.ctrlKey = !!e.ctrlKey;
this.altKey = !!e.altKey;
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
this.downKeys[e.key.toLocaleUpperCase()] = true;
this.downKeys["^" + e.key.toLocaleUpperCase()] = true;
this.dispatchCustomEvent("keydown", { originalEvent: e });
}
handleKeyup(e) {
this.ctrlKey = !!e.ctrlKey;
this.altKey = !!e.altKey;
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
this.downKeys[e.key.toLocaleUpperCase()] = false;
this.downKeys["^" + e.key.toLocaleUpperCase()] = false;
delete this.downKeys[e.key.toLocaleUpperCase()];
this.dispatchCustomEvent("keyup", { originalEvent: e });
}
areAllKeysDown(keys, caseSensitive = false) {
getKeysFromShortcut(shortcut) {
let keys;
if (typeof shortcut === "string") {
shortcut = shortcut.replace(/\s/g, "");
shortcut = shortcut.replace(/^\+/, "__PLUS__").replace(/\+\+/, "+__PLUS__");
keys = shortcut.split("+").map((i) => i.replace("__PLUS__", "+"));
}
else {
keys = [...shortcut];
}
return keys.map((k) => k.toLocaleUpperCase());
}
areAllKeysDown(keys) {
keys = this.getKeysFromShortcut(keys);
return keys.every((k) => {
if (caseSensitive) {
return rgthree.downKeys["^" + k.trim()];
}
return rgthree.downKeys[k.trim().toUpperCase()];
return rgthree.downKeys[k];
});
}
areOnlyKeysDown(keys, alsoAllowShift = false) {
keys = this.getKeysFromShortcut(keys);
const allKeysDown = this.areAllKeysDown(keys);
const downKeysLength = Object.values(rgthree.downKeys).length;
if (allKeysDown && keys.length === downKeysLength) {
return true;
}
if (alsoAllowShift && !keys.includes("SHIFT") && keys.length === downKeysLength - 1) {
return this.areAllKeysDown(["SHIFT"]);
}
return false;
}
injectRgthreeCss() {
let link = document.createElement("link");
link.rel = "stylesheet";