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

583 lines
14 KiB
JavaScript

import { $el } from "/scripts/ui.js";
import { api } from "/scripts/api.js";
class JNodesUtilities {
isElementInViewport(element) {
var rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
getSuiteName() {
return "JNodes"
}
getCurrentTimeAsString() {
function addZero(num) {
return (num >= 0 && num < 10) ? "0" + num : num;
}
const now = new Date();
const timeAsString = [
addZero(now.getHours()),
addZero(now.getMinutes()),
addZero(now.getSeconds())
].join(":");
return timeAsString;
}
isHrefVideo(href) {
if (typeof(href) !== "string") {
return false;
}
return href.includes(".mp4") || href.includes(".webm");
}
getDarkColor() {
return "rgba(0,0,0,0.5)";
}
getRandomColor() {
return '#' + Math.floor(Math.random()*16777215).toString(16);
}
createDarkContainer(identifier, paddingOverride) {
return $el("div", {
id: identifier,
style: {
position: "absolute",
backgroundColor: utilitiesInstance.getDarkColor(),
display: "inline-block", // Size to content, "-block" to allow vertical margin and padding
padding: paddingOverride ? paddingOverride : "1%",
},
});
}
createLongPressableButton(buttonParams, clickFunction, longPressFunction, classNames = [], longPressDurationMs = 500) {
let timer;
let bLongPressTriggered = false;
const button = $el("button", buttonParams);
for (const className of classNames) {
button.classList.add(className);
}
const startTimer = () => {
bLongPressTriggered = false; // Reset the flag
timer = setTimeout(() => {
longPressFunction();
bLongPressTriggered = true; // Set the flag when long press is triggered
}, longPressDurationMs);
};
const clearTimer = () => {
clearTimeout(timer);
timer = 0;
};
const inputRelease = () => {
if (timer) {
clearTimer();
if (!bLongPressTriggered) {
clickFunction();
}
}
}
button.addEventListener('mousedown', startTimer);
button.addEventListener('mouseup', inputRelease);
button.addEventListener('mouseleave', clearTimer);
button.addEventListener('touchstart', startTimer);
button.addEventListener('touchend', inputRelease);
button.addEventListener('touchcancel', clearTimer);
return button;
}
hasAncestor(element, ancestor) {
let currentElement = element.parentNode;
while (currentElement) {
if (currentElement == ancestor) {
return true;
}
currentElement = currentElement.parentNode;
}
return false; // No matching ancestor was found
}
setElementVisible(element, bNewVisible, customVisibleType = "unset") {
if (!element) { return; }
element.style.display = bNewVisible ? customVisibleType : "none";
}
getVideoElements(parentElement) {
return parentElement ? parentElement.querySelectorAll("video") : [];
}
getImgElements(parentElement) {
return parentElement ? parentElement.querySelectorAll("img") : [];
}
getVisualElements(parentElement) {
return parentElement ? parentElement.querySelectorAll("video, img") : [];
}
pasteToTextArea(newText, textarea) {
// Focus the textarea to make sure execCommand is working with the right selection
textarea.focus();
let pasted = true;
try {
if (!document.execCommand("insertText", false, newText)) {
pasted = false;
}
} catch (e) {
console.error("Error caught during execCommand:", e);
pasted = false;
}
if (!pasted) {
console.error(
"execCommand unsuccessful; not supported. Adding text manually, no undo support.");
textarea.setRangeText(newText, textarea.selectionStart, textarea.selectionEnd, "end");
}
}
joinPaths(paths) {
if (paths.length == 0) {
return "";
}
const bStartWithSlash = paths[0].startsWith("/");
let returnValue = paths
.map(path => path.replace(/(^\/+|\/+$)/g, '')) // Remove leading and trailing slashes
.filter(Boolean) // Remove empty strings
.join('/'); // Join with single slashes
if (bStartWithSlash) {
returnValue = "/" + returnValue;
}
return returnValue;
}
getRandomInt(min, max) {
// Ensure that min and max are integers
min = Math.ceil(min);
max = Math.floor(max);
// Generate a random number between min (inclusive) and max (inclusive)
return Math.floor(Math.random() * (max - min + 1)) + min;
}
convertToTitleCase(input) {
return input
.replace(/([A-Z])/g, ' $1') // Add space before each uppercase letter
.replace(/^./, str => str.toUpperCase()) // Capitalize the first letter
.trim(); // Remove leading/trailing spaces
}
stringifyDisplayData(inDisplayData) {
const Stringifier = (key, value) => {
// Check if the key is "FileDimensions"
if (key === "FileDimensions") {
// Serialize the value of "FileDimensions" as a single line string
return JSON.stringify(value);
}
if (key === "FileSize") {
// Convert 1000000 to "1 MiB" for example
return this.formatBytesToString(value);
}
if (typeof value == "number") {
return this.toFixedWithoutTrailingZeroesAndDecimal(value, 3);
}
// Return the original value for other keys
return value;
};
inDisplayData = this.sortJsonObjectByKeys(inDisplayData);
return JSON.stringify(inDisplayData, Stringifier, "\t"); // Pretty formatting
}
removeCurlyBracesFromJsonString(jsonString) {
return jsonString.substring(1, jsonString.length - 1);
}
unindentJsonString(jsonString) {
const lines = jsonString.split("\n")
const unindentedLines = lines.map(line => {
// Use a regular expression to match the first tab or leading whitespace
const unindentedLine = line.replace(/^( {4}|\t)/, '');
return unindentedLine;
});
return unindentedLines.join("\n").trim();
}
sanitizeMetadataForJson(metadata) {
metadata = metadata.replace(/NaN/g, "0");
return metadata;
}
formatBytesToString(bytes, decimalPlaces = 3) {
if (bytes === 0) return "0";
const k = 1024;
const dm = decimalPlaces < 0 ? 0 : decimalPlaces;
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const num = bytes / Math.pow(k, i);
let formattedNumber = new Intl.NumberFormat("en-US", {
minimumFractionDigits: dm,
maximumFractionDigits: dm
}).format(num);
return `${formattedNumber.replace(/\.?0+$/, '')} ${sizes[i]}`;
}
toFixedWithoutTrailingZeroesAndDecimal(value, decimalPlaces = 2) {
// Remove trailing zeros and possibly the decimal point
return value.toFixed(decimalPlaces).replace(/\.?0+$/, '');
}
getMaxZIndex(element) {
let maxZIndex = element.style.zIndex;
let parent = element.parentElement;
while (parent) {
maxZIndex = Math.max(maxZIndex, parent.style.zIndex);
parent = parent.parentElement;
}
return maxZIndex;
}
async asyncWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
isInvalidObject(obj) {
return !obj || Object.keys(obj).length === 0;
}
addStylesheet(url) {
if (url.endsWith(".js")) {
url = url.substr(0, url.length - 2) + "css";
}
$el("link", {
parent: document.head,
rel: "stylesheet",
type: "text/css",
href: url.startsWith("http") ? url : getUrl(url),
});
}
copyToClipboard(text) {
const textArea = document.createElement("textarea");
textArea.value = text.replace(/\\n/g, "\n") // Ensure newlines are proper
.split("\n") // Split into lines
.map(line => line.trim()) // Trim each line
.join("\n") // Join them back with newlines
.replace(/\n{3,}/g, "\n\n"); // Ensure no more than two newlines in a row
// Make the textarea hidden
textArea.style.position = "fixed";
textArea.style.top = 0;
textArea.style.left = 0;
textArea.style.opacity = 0;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
}
simulateDrag(element, startX, startY, endX, endY) {
// Dispatch mousedown event at the starting position
let mousedownEvent = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
clientX: startX,
clientY: startY
});
element.dispatchEvent(mousedownEvent);
// Dispatch mousemove event to simulate dragging
let mousemoveEvent = new MouseEvent('mousemove', {
bubbles: true,
cancelable: true,
clientX: endX,
clientY: endY
});
document.dispatchEvent(mousemoveEvent);
// Dispatch mouseup event to release the drag
let mouseupEvent = new MouseEvent('mouseup', {
bubbles: true,
cancelable: true
});
document.dispatchEvent(mouseupEvent);
}
jsonStringifyWithoutCircularErrors(object) {
function removeCircularReferences() {
const seen = new WeakSet();
return function (key, value) {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return; // Circular reference found, don't include it in the JSON
}
seen.add(value);
}
return value;
};
}
return JSON.stringify(object, removeCircularReferences());
}
checkIfAllImagesAreComplete(images) {
for (const image of images) {
if (!image.complete) {
return false;
}
}
return true; // Set to true if all images are complete
}
async waitForImageCompletion(newImages) {
function removeCompleteImages(newImages) {
return newImages.filter(image => !image.complete);
}
let images = removeCompleteImages(newImages);
let bAreAllImagesComplete = false;
while (!bAreAllImagesComplete) {
bAreAllImagesComplete = this.checkIfAllImagesAreComplete(images);
images = removeCompleteImages(images);
if (!bAreAllImagesComplete) {
await utilitiesInstance.asyncWait(1); // Introduce a 1ms delay using asynchronous sleep
}
}
}
async decodeReadableStream(readableStream) {
const reader = readableStream.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
}
// Assuming the stream is text data
const concatenatedChunks = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
let offset = 0;
for (const chunk of chunks) {
concatenatedChunks.set(chunk, offset);
offset += chunk.length;
}
const text = new TextDecoder().decode(concatenatedChunks);
return text;
}
getCurrentSecondsFromEpoch() {
// Get the current date/time
const currentDate = new Date();
// Get the number of milliseconds since the epoch (January 1, 1970)
const millisecondsSinceEpoch = currentDate.getTime();
// Convert milliseconds to seconds
const secondsSinceEpoch = Math.floor(millisecondsSinceEpoch / 1000);
return secondsSinceEpoch;
}
// Function to load a file from a URL using fetch
async loadFileFromURL(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const fileData = await response.blob(); // Get the response as a Blob
// Now you can use the `fileData` Blob for further processing (e.g., displaying or downloading)
console.log("File loaded successfully:", fileData);
return fileData; // Return the loaded file data (Blob)
} catch (error) {
console.error("Error loading file:", error);
throw error; // Rethrow the error for handling at the caller level
}
}
sortJsonObjectByKeys(JsonObject) {
// Convert JSON object to array of key-value pairs
const Entries = Object.entries(JsonObject);
// Sort the array of key-value pairs by keys (property names)
Entries.sort((a, b) => a[0].localeCompare(b[0])); // Sort alphabetically by keys (case-sensitive)
// Reconstruct the sorted object from the sorted array of key-value pairs
const SortedObject = Object.fromEntries(Entries);
return SortedObject;
}
async tryFreeMemory(bShowError = false, bUnloadModels = true, bFreeMemory = true) {
const response = await api.fetchApi(`/free`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: `{"unload_models": ${bUnloadModels}, "free_memory": ${bFreeMemory}}`
});
if(bShowError && response.status != 200) {
show_message(`Unable to free memory using "/free" api call! Status: ${response.status}`)
}
}
getKeyList() {
return [
"ArrowDown",
"ArrowLeft",
"ArrowRight",
"ArrowUp",
"Backquote",
"Backslash",
"Backspace",
"BracketLeft",
"BracketRight",
"Comma",
"Digit0",
"Digit1",
"Digit2",
"Digit3",
"Digit4",
"Digit5",
"Digit6",
"Digit7",
"Digit8",
"Digit9",
"Enter",
"Equal",
"Escape",
"F1",
"F10",
"F11",
"F12",
"F13",
"F13",
"F14",
"F15",
"F16",
"F17",
"F18",
"F19",
"F2",
"F20",
"F21",
"F22",
"F23",
"F24",
"F25",
"F26",
"F27",
"F28",
"F29",
"F3",
"F30",
"F31",
"F32",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"KeyA",
"KeyB",
"KeyC",
"KeyD",
"KeyE",
"KeyF",
"KeyG",
"KeyH",
"KeyI",
"KeyJ",
"KeyK",
"KeyL",
"KeyM",
"KeyN",
"KeyO",
"KeyP",
"KeyQ",
"KeyR",
"KeyS",
"KeyT",
"KeyU",
"KeyV",
"KeyW",
"KeyX",
"KeyY",
"KeyZ",
"Minus",
"Numpad0",
"Numpad1",
"Numpad2",
"Numpad3",
"Numpad4",
"Numpad5",
"Numpad6",
"Numpad7",
"Numpad8",
"Numpad9",
"NumpadAdd",
"NumpadComma",
"NumpadDecimal",
"NumpadDivide",
"NumpadMultiply",
"NumpadSubtract",
"Period",
"Quote",
"Semicolon",
"Slash",
"Space"
];
}
}
export const utilitiesInstance = new JNodesUtilities();