Files
Trung0246-ComfyUI-0246/web/js/utils.js
T

679 lines
16 KiB
JavaScript

import { ComfyDialog } from "../../../scripts/ui.js";
export async function try_import(name) {
try {
return await import(name);
} catch (e) {
return null;
}
}
export async function load_script(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.onload = () => {
resolve();
};
script.onerror = () => {
reject(new Error(`Script load error for ${url}`));
};
document.head.appendChild(script);
});
}
export function compare_version(a, b) {
for (let i = 0; i < a.length; ++ i) {
if (a[i] > b[i])
return 1;
if (a[i] < b[i])
return -1;
}
return 0;
}
export function error_popup(msg) {
let dialog = new ComfyDialog();
dialog.show(`<p>${msg}</p>`);
}
export function is_promise(obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
export function is_async(func) {
return func.constructor.name === "AsyncFunction";
}
export const HIJACK_MARK = Symbol("hijack_mark");
function hijack_evt(evt_obj, pass_obj, mode) {
for (let i = 0; i < evt_obj.evt.length; ++ i)
evt_obj.evt[i].call(pass_obj, mode);
}
export function hijack(obj, key, func, evt) {
// The most convoluted hijacking mechanism ever
const old_func = obj[key] ?? (() => {}),
evt_obj = old_func[HIJACK_MARK] ?? {
evt: [],
};
obj[key] = function(...args) {
const self = this;
const pass_obj = {
self: self,
stop: false,
func: func,
old: old_func,
args: args,
wait: is_async(old_func) || is_async(func),
};
const exec_after = () => {
hijack_evt(evt_obj, pass_obj, 0b10000);
const after_result = func.apply(pass_obj, args);
hijack_evt(evt_obj, pass_obj, 0b100000);
if (is_promise(after_result))
// return after_result.then(() => pass_obj.res);
return after_result.then(() => {
hijack_evt(evt_obj, pass_obj, 0b100000000);
return pass_obj.res;
});
return pass_obj.res;
};
const exec_old = () => {
if (pass_obj.old !== old_func || pass_obj.stop) {
hijack_evt(evt_obj, pass_obj, 0b1000000);
if (is_promise(pass_obj.res))
// return pass_obj.res.then(exec_after);
return pass_obj.res.then(res => {
pass_obj.res = res;
hijack_evt(evt_obj, pass_obj, 0b10000000);
return exec_after();
});
return exec_after();
}
hijack_evt(evt_obj, pass_obj, 0b100);
const result = old_func.apply(self, args);
hijack_evt(evt_obj, pass_obj, 0b1000);
if (is_promise(result))
return result.then(res => {
pass_obj.res = res;
return exec_after();
});
pass_obj.res = result;
return exec_after();
};
hijack_evt(evt_obj, pass_obj, 0b1);
const before_result = func.apply(pass_obj, args);
hijack_evt(evt_obj, pass_obj, 0b10);
pass_obj.mark = true;
if (is_promise(before_result))
return before_result.then(exec_old);
return exec_old();
};
obj[key][HIJACK_MARK] = evt_obj;
if (evt)
obj[key][HIJACK_MARK].evt.push(evt);
return old_func;
}
export function clone_class(original) {
return class extends original {
constructor(...args) {
super(...args);
}
};
}
export function remove_elem_arr(array, chk_fn) {
let shift = 0;
for (let i = 0; i < array.length; ++i) {
if (chk_fn(array[i], i, array))
++ shift;
else if (shift > 0)
array[i - shift] = array[i];
}
array.length -= shift;
}
export function reverse_array(arr, start, end) {
// Function to reverse a portion of the array
while (start < end) {
let temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
}
export function rotate_array(arr, n) {
const length = arr.length;
// Normalize the rotation count
n = n % length;
if (n < 0)
// Convert negative rotation to positive equivalent
n += length;
// Reverse the whole array
reverse_array(arr, 0, length - 1);
// Reverse the first part
reverse_array(arr, 0, n - 1);
// Reverse the second part
reverse_array(arr, n, length - 1);
return arr;
}
export function equal_array(a, b, m = false, w = null) {
if (a.length !== b.length)
return false;
if (m) {
for (let i = 0; i < a.length; ++ i)
if (b.indexOf(a[i]) === -1)
return false;
} else for (let i = 0; i < a.length; ++ i) {
if (a[i] === w || b[i] === w)
continue;
if (a[i] !== b[i])
return false;
}
return true;
}
export function equal_dict(a, b, m = false, w = null) {
if (!m && Object.keys(a).length !== Object.keys(b).length)
return false;
for (let key in a) {
if (m && !(key in b))
continue;
if (w !== null && (a[key] === w || b[key] === w))
continue;
if (a[key] !== b[key])
return false;
}
return true;
}
export function find_key_dict(dict, key) {
const key_list = Object.keys(dict);
let res = [];
for (let i = 0; i < key_list.length; ++ i)
if (typeof key === "string" && key_list[i].indexOf(key) != -1)
res.push(key_list[i]);
else if (typeof key === "function" && key(key_list[i]))
res.push(key_list[i]);
return res;
}
export function update_dict(a, b) {
for (let key in a)
if (b.hasOwnProperty(key))
a[key] = b[key];
}
export class RingBuffer {
constructor(size) {
this.buffer = [];
this.prev = [];
this.index = 0;
this.size = size;
this.count = 0;
this.dirty = false;
}
push(value) {
this.buffer[this.index] = value;
this.index = (this.index + 1) % this.size;
++ this.count;
if (this.count > this.size) {
this.count = this.size;
this.dirty = true;
}
}
at(index) {
if (index >= this.count)
return undefined;
index = (this.index - index - 1 + this.size) % this.size;
return this.buffer[index];
}
*[Symbol.iterator]() {
for (let i = 0; i < this.count; ++ i)
yield this.at(i);
}
clean() {
if (!this.dirty) return;
this.dirty = false;
this.prev.length = 0;
for (let i = 0; i < this.count; ++ i)
this.prev[i] = this.at(i);
let temp = this.buffer;
this.buffer = this.prev;
this.prev = temp;
this.index = this.count % this.size;
}
resize(size) {
if (this.dirty)
this.clean();
this.buffer.length = size;
this.prev.length = 0;
this.size = size;
if (this.size < this.count)
this.count = this.size;
this.index = this.count % size;
}
pull(index) {
if (index >= this.count) return undefined;
this.clean();
let actual_index = (this.index - index - 1 + this.size) % this.size,
value = this.buffer[actual_index];
for (let i = actual_index; i != this.index; i = (i + 1) % this.size)
this.buffer[i] = this.buffer[(i + 1) % this.size];
-- this.count;
this.index = (this.index - 1 + this.size) % this.size;
return value;
}
}
export function random_id() {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
}
export function rand_int(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export function indent_str(strings, ...values) {
// Build the string as normal, combining all parts
let fullString = strings.reduce((acc, str, i) => acc + (values[i - 1] || '') + str);
// Split the string into lines
let lines = fullString.split('\n');
// Remove the first line if it is empty (caused by a newline at the beginning of a template literal)
if (lines[0].match(/^\s*$/))
lines.shift();
// Find the smallest indentation (spaces or tabs) from the remaining lines that have content
const smallestIndent = lines.length > 0
? Math.min(...lines.filter(line => line.trim()).map(line => line.match(/^[ \t]*/)[0].length))
: 0;
// Remove the smallest indentation from all lines
lines = lines.map(line => line.substring(smallestIndent));
// Combine the trimmed lines
return lines.join('\n');
}
export function hex_to_rgb(hex) {
if (hex.startsWith("#"))
hex = hex.slice(1);
if (hex.length === 3)
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
let bigint = parseInt(hex, 16);
return [
(bigint >> 16) & 255,
(bigint >> 8) & 255,
bigint & 255
];
}
export function rgb_to_hex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
export function rgb_to_hsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
let max = Math.max(r, g, b), min = Math.min(r, g, b),
h, s, l = (max + min) / 2;
if (max === min)
h = s = 0; // achromatic
else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
export function hue_to_rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
export function hsl_to_rgb(h, s, l) {
let r, g, b;
if (s === 0)
r = g = b = l; // achromatic
else {
let q = l < 0.5 ? l * (1 + s) : l + s - l * s,
p = 2 * l - q;
r = hue_to_rgb(p, q, h + 1/3);
g = hue_to_rgb(p, q, h);
b = hue_to_rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
export function mix_color_hue(color1, color2) {
let hsl1 = rgb_to_hsl(...hex_to_rgb(color1)),
hsl2 = rgb_to_hsl(...hex_to_rgb(color2));
return rgb_to_hex(...hsl_to_rgb(
(hsl1[0] + hsl2[0]) / 2,
(hsl1[1] + hsl2[1]) / 2,
(hsl1[2] + hsl2[2]) / 2
));
}
export function mix_color(color1, color2) {
let rgb1 = hex_to_rgb(color1), rgb2 = hex_to_rgb(color2);
return rgb_to_hex(
Math.round(Math.sqrt(rgb1[0] * rgb1[0] + rgb2[0] * rgb2[0])),
Math.round(Math.sqrt(rgb1[1] * rgb1[1] + rgb2[1] * rgb2[1])),
Math.round(Math.sqrt(rgb1[2] * rgb1[2] + rgb2[2] * rgb2[2]))
);
}
export function norm(val, min, max) {
return (val - min) / (max - min);
}
export function lerp(norm, min, max) {
return (max - min) * norm + min;
}
export function map(val, src_min, src_max, dst_min, dst_max) {
return lerp(norm(val, src_min, src_max), dst_min, dst_max);
}
export function is_inside_rect(
this_x, this_y,
other_x, other_y, other_w, other_h
) {
return this_x >= other_x && this_x <= other_x + other_w && this_y >= other_y && this_y <= other_y + other_h;
}
export function is_inside_rect_rect(
this_x, this_y, this_w, this_h,
other_x, other_y, other_w, other_h
) {
return this_x >= other_x && this_x + this_w <= other_x + other_w && this_y >= other_y && this_y + this_h <= other_y + other_h;
}
export function is_inside_circ(
this_x, this_y,
other_x, other_y, other_r,
) {
return Math.pow(this_x - other_x, 2) + Math.pow(this_y - other_y, 2) <= (other_r * other_r);
}
export function calc_resize(
x, y, w, h,
other_x, other_y
) {
if (
other_x >= x &&
other_y >= y
) {
// Case 1: Mouse moves right-bottom
w = other_x - x;
h = other_y - y;
} else if (
other_x < x &&
other_y < y
) {
// Case 2: Mouse moves left-top
w = x - other_x;
h = y - other_y;
x = other_x;
y = other_y;
} else if (
other_x < x &&
other_y >= y
) {
// Case 3: Mouse moves left-bottom
w = x - other_x;
h = other_y - y;
x = other_x;
} else if (
other_x >= x &&
other_y < y
) {
// Case 4: Mouse moves right-top
w = other_x - x;
h = y - other_y;
y = other_y;
}
return [x, y, w, h];
}
export function calc_flex_norm(
x, y, w, h,
this_x, this_y, this_w, this_h,
other_x, other_y, other_w, other_h,
) {
return [
(x - this_x) / this_w * other_w + other_x,
(y - this_y) / this_h * other_h + other_y,
w / this_w * other_w,
h / this_h * other_h,
];
}
export function calc_area(margin_x, margin_head_y, margin_tail_y, width, height, max_height, ratio, center, at_y, flag = false, area = []) {
area[0] = margin_x;
area[1] = margin_head_y + at_y;
area[2] = width - margin_x * 2;
area[3] = height - margin_head_y - margin_tail_y;// - at_y;
area[3] = Math.min(area[3], max_height);
if (flag && area[2] < 0)
area[2] = 0;
if (ratio !== 0) {
if (ratio < 1) {
if (area[3] * ratio > area[2])
area[3] = area[2] / ratio;
else
area[2] = area[3] * ratio;
} else {
if (area[2] / ratio > area[3])
area[2] = area[3] * ratio;
else
area[3] = area[2] / ratio;
}
if (center)
area[0] = (width - area[2]) / 2;
}
if (flag && area[3] < 0) {
area[2] = 0;
area[3] = 0;
}
return area;
}
export function calc_spread(count, total, weight, min, max) {
let total_weight = weight.reduce((a, b) => a + b, 0),
res = new Array(count).fill(0),
allow = new Array(count).fill(true),
remain = total;
for (let i = 0; i < count; ++ i) {
res[i] = weight[i] / (total_weight === 0 ? 1 : total_weight) * total;
if (min[i] !== null && res[i] < min[i]) {
res[i] = min[i];
remain -= min[i];
total_weight -= weight[i];
allow[i] = false;
}
if (max[i] !== null && res[i] > max[i]) {
res[i] = max[i];
remain -= max[i];
total_weight -= weight[i];
allow[i] = false;
}
}
for (let i = 0; i < count; ++ i)
if (allow[i])
res[i] = weight[i] / (total_weight === 0 ? 1 : total_weight) * remain;
return res;
}
export function round(num, dec) {
const mult = Math.pow(10, dec);
return Math.round(num * mult) / mult;
}
export function floor(num, dec) {
const mult = Math.pow(10, dec);
return Math.floor(num * mult) / mult;
}
export function snap(num, step) {
if (num > 0)
return Math.floor(num / step) * step;
return Math.ceil(num / step) * step;
}
export function multi_snap(num, len, fn) {
if (num < fn(0) || num > fn(len - 1))
return null;
let closest = 0,
min_diff = Math.abs(num - fn(0));
for (let i = 1; i < len; ++ i) {
let diff = Math.abs(num - fn(i));
if (diff < min_diff) {
min_diff = diff;
closest = i;
}
}
return closest;
}
export function rem(a, b) {
return (a % b + b) % b;
}
export function text_snip(text, max_len) {
if (text.length <= max_len)
return text;
return text.slice(0, 3) + "..." + text.slice(-3);
}
export async function safe_eval(code) {
// https://github.com/Prendus/secure-eval/blob/master/secure-eval.ts
const secureEvalIframe = document.createElement('iframe');
secureEvalIframe.setAttribute('sandbox', 'allow-scripts');
secureEvalIframe.setAttribute('style', 'display: none;');
secureEvalIframe.setAttribute('src', 'data:text/html;base64,' + btoa(`
<script type="module">
try {
${norm.toString()}
${lerp.toString()}
${is_inside_rect.toString()}
${is_inside_rect_rect.toString()}
${is_inside_circ.toString()}
${calc_resize.toString()}
${calc_flex_norm.toString()}
${calc_area.toString()}
${round.toString()}
${floor.toString()}
${snap.toString()}
${rem.toString()}
window.parent.postMessage({
type: 'secure-eval-iframe-result',
data: (function() {
${code}
})()
}, '*');
}
catch(error) {
window.parent.postMessage({
type: 'secure-eval-iframe-result',
error: error.toString()
}, '*');
}
</script>
`));
document.body.appendChild(secureEvalIframe);
return new Promise((resolve, reject) => {
const windowListener = event => {
if (event.data.type !== 'secure-eval-iframe-result')
return;
window.removeEventListener('message', windowListener);
document.body.removeChild(secureEvalIframe);
if (event.data.error)
reject(new Error(event.data.error));
else
resolve(event.data.data);
};
window.addEventListener('message', windowListener);
secureEvalIframe.addEventListener('load', () => {
secureEvalIframe.contentWindow.postMessage(code, '*');
});
});
}
export function dummy(...args) {
return args[0];
}