Files
JaredTherriault-ComfyUI-JNodes/web/js/common/EventManager.js
T

306 lines
8.4 KiB
JavaScript

// Centralized event system tracking canvas movement, mouse position, and pointer state for the ComfyUI UI.
// Dispatches custom events and provides helpers like getElementUnderPointer and simulateMouseClick.
import { app } from "../../../../scripts/app.js";
import { utilitiesInstance } from "./Utilities.js";
import { setting_VideoPlaybackOptions, setting_LoraLoaderNodeType } from "../common/SettingsManager.js";
import * as VideoControl from './VideoControl.js';
// Canvas movement event
let lastOffset = [0, 0];
function determineIfCanvasMoved() {
const current = app?.canvas?.ds.offset;
if (!current) return;
if (current[0] !== lastOffset[0] || current[1] !== lastOffset[1]) {
lastOffset = [...current];
// Dispatch event
const ev = new CustomEvent("canvasMoved", {
detail: { offset: current }
});
window.dispatchEvent(ev);
}
}
// Mouse tracking
let lastMouseX = 0;
let lastMouseY = 0;
window.addEventListener("pointermove", (event) => {
determineIfCanvasMoved();
lastMouseX = event.clientX;
lastMouseY = event.clientY;
});
export function getLastMousePosition() {
return [lastMouseX, lastMouseY];
}
export function isPointerDown() {
return app?.canvas?.pointer_is_down;
}
export function getElementUnderPointer() {
const mousePos = getLastMousePosition();
return document.elementFromPoint(mousePos[0], mousePos[1]);
}
export function simulateMouseClickAtPoint(x, y) {
// Create the synthetic mouse event
const event = new MouseEvent("pointerdown", {
view: window,
bubbles: true,
cancelable: true,
clientX: x,
clientY: y
});
document.dispatchEvent(event);
}
// Keyboard events
function isElementAppropriateForVideoEvent(elementUnderPointer) {
return elementUnderPointer &&
VideoControl.isElementVideo(elementUnderPointer) &&
!elementUnderPointer.hasAttribute("NoVideoControl");
}
// Keyboard events
document.addEventListener("keydown", async (event) => {
// Video shortcuts
// note that elementUnderPointer will always be the video element itself, not the container
if (event.key == "l") { // Seek forward
const delta = 1;
const elementUnderPointer = getElementUnderPointer();
if (isElementAppropriateForVideoEvent(elementUnderPointer)) {
event.preventDefault();
VideoControl.seekVideo(elementUnderPointer, delta);
}
} else if (event.key == "j") { // See backward
const delta = -1;
const elementUnderPointer = getElementUnderPointer();
if (isElementAppropriateForVideoEvent(elementUnderPointer)) {
event.preventDefault();
VideoControl.seekVideo(elementUnderPointer, delta);
}
} else if (event.key == "k") { // Pause/play
const elementUnderPointer = getElementUnderPointer();
if (isElementAppropriateForVideoEvent(elementUnderPointer)) {
event.preventDefault();
await VideoControl.toggleVideoPlayback(elementUnderPointer);
}
} else if (event.key == "m") { // Toggle mute
const elementUnderPointer = getElementUnderPointer();
if (isElementAppropriateForVideoEvent(elementUnderPointer)) {
event.preventDefault();
VideoControl.toggleVideoMute(elementUnderPointer);
}
} else if (event.key == 's') { // Toggle fullscreen
const elementUnderPointer = getElementUnderPointer();
if (isElementAppropriateForVideoEvent(elementUnderPointer)) {
event.preventDefault();
VideoControl.toggleVideoFullscreen(elementUnderPointer);
}
} else if (event.key == ',') { // Decrease playback rate
const elementUnderPointer = getElementUnderPointer();
if (isElementAppropriateForVideoEvent(elementUnderPointer)) {
event.preventDefault();
VideoControl.setVideoPlaybackRate(elementUnderPointer, elementUnderPointer.playbackRate - 0.05);
}
} else if (event.key == '.') { // Increase playback rate
const elementUnderPointer = getElementUnderPointer();
if (isElementAppropriateForVideoEvent(elementUnderPointer)) {
event.preventDefault();
VideoControl.setVideoPlaybackRate(elementUnderPointer, elementUnderPointer.playbackRate + 0.05);
}
} else if (event.key == 'U' && event.ctrlKey && event.shiftKey) { // Try Free Memory
utilitiesInstance.tryFreeMemory();
}
});
// Scroll wheel
document.addEventListener('wheel', (event) => {
if (setting_VideoPlaybackOptions.value.useWheelSeek ||
(!setting_VideoPlaybackOptions.value.useWheelSeek && event.altKey)
) {
const elementUnderPointer = getElementUnderPointer();
if (isElementAppropriateForVideoEvent(elementUnderPointer)) {
event.preventDefault();
VideoControl.onScrollVideo(elementUnderPointer, event, setting_VideoPlaybackOptions.value.invertWheelSeek);
}
}
});
document.addEventListener("dragover", (event) => {
event.preventDefault(); // REQUIRED in Firefox
});
document.addEventListener("drop", (event) => {
const afterGetDropData = (key, callback) => {
if (!callback) { return; }
for (const item of event.dataTransfer.items) {
if (item.type != 'text/plain') { continue; }
item.getAsString((itemString) => {
if (!itemString || !itemString.startsWith('{')) { return; }
try {
const asJson = JSON.parse(itemString);
if (asJson && asJson[key]) {
callback(asJson[key]);
} else {
console.log(`JNodes: Unable to parse ${key} from JSON from payload: ${itemString}`)
}
} catch {
console.log(`JNodes: Unable to parse JSON from payload: ${itemString}`)
}
});
}
};
const updateLoraNode = function(node, modelName, strengthModel, strengthClip) {
for (let widget of node.widgets) {
if (widget.name == "lora_name") {
widget.value = modelName;
}
if (widget.name == "strength_model") {
widget.value = strengthModel;
}
if (widget.name == "strength_clip") {
widget.value = strengthClip;
}
}
};
if (event.target.data == app.canvas) {
function openWorrkflowIfAvailabled() {
// Check for image drawer payload with workflow
for (const item of event.dataTransfer.items) {
if (item.type == 'text/jnodes_image_drawer_payload') {
item.getAsString((value) => {
try {
const payload = JSON.parse(value);
if (payload.metadata?.workflow) {
let workflow = payload.metadata.workflow;
if (typeof workflow === "string") {
workflow = JSON.parse(workflow);
}
event.preventDefault();
event.stopPropagation();
app.loadGraphData(workflow);
return;
}
} catch (e) {
console.error("JNodes: Error loading workflow from dropped item:", e);
}
});
break;
}
}
}
// Drop lora node data onto existing node to replace
const graph = event.target.data.graph;
if (graph) {
const pos = app.clientPosToCanvasPos([event.clientX, event.clientY]);
const hoveredNode = graph.getNodeOnPos(
pos[0],
pos[1]
);
if (hoveredNode) {
if ((hoveredNode.type == "LoraLoader" || hoveredNode.type == "LoraLoaderModelOnly")) {
afterGetDropData("modelInfo", (asJson) => {
event.preventDefault();
event.stopPropagation();
updateLoraNode(hoveredNode, asJson.modelName, asJson.strengthModel, asJson.strengthClip);
});
return;
}
} else {
openWorrkflowIfAvailabled();
}
}
// Or drop in lora node data onto canvas to place
afterGetDropData("modelInfo", (payload) => {
function addNode(name, coordinates, options) {
options = { select: true, shiftY: 0, ...(options || {}) };
const node = LiteGraph.createNode(name);
app.graph.add(node);
node.pos = [
coordinates[0] - node.size[0] / 2, coordinates[1] + options.shiftY,
];
if (options.select) {
app.canvas.selectNode(node, false);
}
return node;
};
event.preventDefault();
event.stopPropagation();
let node = addNode(setting_LoraLoaderNodeType.value, [event.canvasX, event.canvasY]);
updateLoraNode(node, payload.modelName, payload.strengthModel, payload.strengthClip);
});
} else if (event.target.tagName.toLowerCase() === 'textarea') { // Drop a1111 lora text or embedding text onto textarea
afterGetDropData("modelInsertText", (payload) => {
event.preventDefault();
event.stopPropagation();
let position = null;
if (document.caretPositionFromPoint) {
const caretPos = document.caretPositionFromPoint(event.clientX, event.clientY);
if (caretPos) position = caretPos.offset;
} else if (document.caretRangeFromPoint) {
const range = document.caretRangeFromPoint(event.clientX, event.clientY);
if (range) position = range.startOffset;
}
utilitiesInstance.pasteToTextArea(payload, event.target, position);
});
}
});