-Added new batch selection and batch deletion features. All imageElements now have a checkbox and each selected element is tracked within a context or folder. When you've selected all you want to delete, click the recycle button on the toolbar, then again to confirm. All checked items will be recylced/deleted. You can also click on the selection widget next to the batch delete button to toggle select/deselect all. -Renamed ImageDrawer.js to ImageDrawerMain.js -Separated ImageListAndSearch out into separate scripts and classes named ImageDrawerList and ImageDrawerSearch -Changed order of some initialized functions and variables in imageElements to better accommodate batch selection - -Moved some scripts over to a classy architecture. More scripts will be moved over time. This will eventually allow for multiple image drawer instances in the same window. -Added a new module script that manages all class instances. See ImageDrawerModule.js for an explanation of the new architecture. Basically, it makes cyclical dependencies impossible. -Updated imports -Updated various scripts to use class instances where applicable -Updated some outdated comments -Added option to prepend or append labels to Labeled widgets -Parameter optimizations -
337 lines
6.9 KiB
JavaScript
337 lines
6.9 KiB
JavaScript
import { $el } from "../../../../scripts/ui.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"
|
|
}
|
|
|
|
getDarkColor() {
|
|
return 'rgba(0,0,0,0.5)';
|
|
}
|
|
|
|
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%',
|
|
},
|
|
});
|
|
}
|
|
|
|
setElementVisible = (element, bNewVisible) => {
|
|
if (!element) { return; }
|
|
element.style.display = bNewVisible ? "unset" : "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, selectionStart, selectionEnd) {
|
|
|
|
// 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, selectionStart, selectionEnd, 'end');
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
isValid(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;
|
|
|
|
// 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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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(); |