Display of the image grid in the batch mode of the RS Decode Save Image node has been changed
This commit is contained in:
+103
-117
@@ -1,8 +1,8 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
const NODE_TYPE = "RS_VAE_Decode_Save";
|
||||
const MIN_WIDTH = 320;
|
||||
const MIN_HEIGHT = 500;
|
||||
const MIN_WIDTH = 240;
|
||||
const MIN_HEIGHT = 320;
|
||||
const PREVIEW_GAP = 5;
|
||||
const CLOSE_BTN_SIZE = 24;
|
||||
|
||||
@@ -22,14 +22,12 @@ app.registerExtension({
|
||||
|
||||
const self = this;
|
||||
|
||||
// Скрываем стандартные виджеты ComfyUI
|
||||
if (this.widgets) {
|
||||
for (let i = 0; i < this.widgets.length; i++) {
|
||||
this.widgets[i].hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация состояния
|
||||
this.rs_data = { save_path: "", file_prefix: "img", format: "png" };
|
||||
|
||||
const dataW = this.widgets?.find(w => w.name === "node_data");
|
||||
@@ -37,6 +35,15 @@ app.registerExtension({
|
||||
const prefixW = this.widgets?.find(w => w.name === "file_prefix");
|
||||
const formatW = this.widgets?.find(w => w.name === "format");
|
||||
|
||||
this.activePopup = null;
|
||||
|
||||
this.closeActivePopup = function() {
|
||||
if (this.activePopup) {
|
||||
this.activePopup.remove();
|
||||
this.activePopup = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.applyState = function() {
|
||||
if (pathW) pathW.value = self.rs_data.save_path;
|
||||
if (prefixW) prefixW.value = self.rs_data.file_prefix;
|
||||
@@ -60,26 +67,22 @@ app.registerExtension({
|
||||
return path;
|
||||
};
|
||||
|
||||
// Настройки размеров и отступов
|
||||
this.rowHeight = 24;
|
||||
this.padding = 20;
|
||||
this.labelWidth = 70;
|
||||
this.clickZones = [];
|
||||
this.widgetsHeight = 0;
|
||||
|
||||
// Хранилище для всех кадров батча
|
||||
this.imgs = [];
|
||||
this.imageIndex = 0;
|
||||
this.previewMode = 'grid'; // 'grid' или 'view'
|
||||
this.previewMode = 'grid';
|
||||
|
||||
this.outputFolders = [];
|
||||
this.foldersLoaded = false;
|
||||
|
||||
// Устанавливаем дефолтный И минимальный размер ноды
|
||||
this.setSize([MIN_WIDTH, MIN_HEIGHT]);
|
||||
this.min_size = [MIN_WIDTH, MIN_HEIGHT];
|
||||
|
||||
// Защита от сжатия ниже минимума
|
||||
this.onResize = function() {
|
||||
if (this.size[0] < MIN_WIDTH) this.size[0] = MIN_WIDTH;
|
||||
if (this.size[1] < MIN_HEIGHT) this.size[1] = MIN_HEIGHT;
|
||||
@@ -101,7 +104,6 @@ app.registerExtension({
|
||||
};
|
||||
this.loadOutputFolders();
|
||||
|
||||
// Обработка данных от сервера (ПОЛНЫЙ БАТЧ)
|
||||
const onExecuted = this.onExecuted;
|
||||
this.onExecuted = function (message) {
|
||||
const r = onExecuted ? onExecuted.apply(this, arguments) : undefined;
|
||||
@@ -109,16 +111,13 @@ app.registerExtension({
|
||||
if (message?.images && message.images.length > 0) {
|
||||
this.imgs = [];
|
||||
this.imageIndex = 0;
|
||||
this.previewMode = 'grid'; // Сброс в режим сетки при новом батче
|
||||
this.previewMode = 'grid';
|
||||
|
||||
// Загружаем ВСЕ изображения из батча
|
||||
for (const image of message.images) {
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
img.onerror = () => {};
|
||||
img.src = `/view?filename=${encodeURIComponent(image.filename)}&type=${image.type}&subfolder=${encodeURIComponent(image.subfolder || '')}`;
|
||||
this.imgs.push(img);
|
||||
@@ -127,7 +126,29 @@ app.registerExtension({
|
||||
return r;
|
||||
};
|
||||
|
||||
// Отрисовка превью НА ЗАДНЕМ ПЛАНЕ
|
||||
function calcGrid(count, availW, availH) {
|
||||
if (count <= 1) {
|
||||
return { cols: 1, rows: 1, cellSize: Math.min(availW, availH) };
|
||||
}
|
||||
const maxCols = Math.min(count, Math.floor((availW + PREVIEW_GAP) / (1 + PREVIEW_GAP)));
|
||||
let bestCols = 1;
|
||||
let bestSize = 0;
|
||||
for (let c = 1; c <= maxCols; c++) {
|
||||
const rows = Math.ceil(count / c);
|
||||
const cellW = (availW - PREVIEW_GAP * (c - 1)) / c;
|
||||
const cellH = (availH - PREVIEW_GAP * (rows - 1)) / rows;
|
||||
const size = Math.min(cellW, cellH);
|
||||
if (size > bestSize) {
|
||||
bestSize = size;
|
||||
bestCols = c;
|
||||
} else if (size === bestSize && c > bestCols) {
|
||||
bestCols = c;
|
||||
}
|
||||
}
|
||||
const rows = Math.ceil(count / bestCols);
|
||||
return { cols: bestCols, rows: rows, cellSize: bestSize };
|
||||
}
|
||||
|
||||
this.onDrawBackground = function(ctx) {
|
||||
ctx.save();
|
||||
try {
|
||||
@@ -139,62 +160,47 @@ app.registerExtension({
|
||||
|
||||
if (availableW <= 0 || availableH <= 0) return;
|
||||
|
||||
// --- ПРОСТОЕ ПРЕВЬЮ ДЛЯ ОДНОГО ИЗОБРАЖЕНИЯ ---
|
||||
if (this.imgs.length === 1) {
|
||||
const img = this.imgs[0];
|
||||
if (!img || !img.complete) return;
|
||||
|
||||
// Масштабируем изображение под доступную зону
|
||||
const scale = Math.min(availableW / img.width, availableH / img.height);
|
||||
const drawW = img.width * scale;
|
||||
const drawH = img.height * scale;
|
||||
const offsetX = (availableW - drawW) / 2;
|
||||
const offsetY = (availableH - drawH) / 2;
|
||||
|
||||
const imgX = this.padding + offsetX;
|
||||
const imgY = startY + offsetY;
|
||||
|
||||
// Рисуем изображение (без рамок ячеек и без крестика)
|
||||
try {
|
||||
ctx.drawImage(img, imgX, imgY, drawW, drawH);
|
||||
ctx.drawImage(img, this.padding + offsetX, startY + offsetY, drawW, drawH);
|
||||
} catch (e) {}
|
||||
return; // Выходим, дальше идет логика только для батча
|
||||
return;
|
||||
}
|
||||
|
||||
// --- ЛОГИКА ТОЛЬКО ДЛЯ ДВУХ И БОЛЕЕ ИЗОБРАЖЕНИЙ ---
|
||||
if (this.previewMode === 'grid') {
|
||||
// --- АДАПТИВНАЯ СЕТКА (формула из RS Image Selector) ---
|
||||
const count = this.imgs.length;
|
||||
const approxSide = Math.sqrt((availableW * availableH) / count);
|
||||
let cols = Math.floor(availableW / approxSide);
|
||||
if (cols < 1) cols = 1;
|
||||
const rows = Math.ceil(count / cols);
|
||||
const grid = calcGrid(count, availableW, availableH);
|
||||
const cols = grid.cols;
|
||||
const cellSize = grid.cellSize;
|
||||
|
||||
const cellW = (availableW - PREVIEW_GAP * (cols - 1)) / cols;
|
||||
const cellH = (availableH - PREVIEW_GAP * (rows - 1)) / rows;
|
||||
const totalWidth = cols * cellSize + (cols - 1) * PREVIEW_GAP;
|
||||
const offsetX_extra = (availableW - totalWidth) / 2;
|
||||
|
||||
for (let i = 0; i < this.imgs.length; i++) {
|
||||
const img = this.imgs[i];
|
||||
const col = i % cols;
|
||||
const row = Math.floor(i / cols);
|
||||
|
||||
const x = this.padding + col * (cellW + PREVIEW_GAP);
|
||||
const y = startY + row * (cellH + PREVIEW_GAP);
|
||||
const x = this.padding + offsetX_extra + col * (cellSize + PREVIEW_GAP);
|
||||
const y = startY + row * (cellSize + PREVIEW_GAP);
|
||||
|
||||
// Фон ячейки
|
||||
ctx.fillStyle = "#232323";
|
||||
ctx.fillRect(x, y, cellW, cellH);
|
||||
ctx.strokeStyle = "#444";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(x, y, cellW, cellH);
|
||||
|
||||
// Рисуем миниатюру с сохранением пропорций (letterbox)
|
||||
if (img.complete && img.naturalWidth > 0) {
|
||||
const scale = Math.min(cellW / img.width, cellH / img.height);
|
||||
const maxDim = Math.max(img.width, img.height);
|
||||
const scale = cellSize / maxDim;
|
||||
const drawW = img.width * scale;
|
||||
const drawH = img.height * scale;
|
||||
const offsetX = (cellW - drawW) / 2;
|
||||
const offsetY = (cellH - drawH) / 2;
|
||||
|
||||
const offsetX = (cellSize - drawW) / 2;
|
||||
const offsetY = (cellSize - drawH) / 2;
|
||||
|
||||
try {
|
||||
ctx.drawImage(img, x + offsetX, y + offsetY, drawW, drawH);
|
||||
@@ -202,36 +208,27 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// --- РЕЖИМ ПРОСМОТРА (только для батча) ---
|
||||
const img = this.imgs[this.imageIndex];
|
||||
if (!img || !img.complete) return;
|
||||
|
||||
// Масштабируем изображение под доступную зону
|
||||
const scale = Math.min(availableW / img.width, availableH / img.height);
|
||||
const drawW = img.width * scale;
|
||||
const drawH = img.height * scale;
|
||||
const offsetX = (availableW - drawW) / 2;
|
||||
const offsetY = (availableH - drawH) / 2;
|
||||
|
||||
const imgX = this.padding + offsetX;
|
||||
const imgY = startY + offsetY;
|
||||
|
||||
// Рисуем изображение
|
||||
try {
|
||||
ctx.drawImage(img, imgX, imgY, drawW, drawH);
|
||||
ctx.drawImage(img, this.padding + offsetX, startY + offsetY, drawW, drawH);
|
||||
} catch (e) {}
|
||||
|
||||
// Рисуем кнопку закрытия [×] в правом верхнем углу зоны превью
|
||||
const btnX = this.size[0] - this.padding - CLOSE_BTN_SIZE;
|
||||
const btnY = startY;
|
||||
|
||||
// Фон кнопки для кликабельности
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.6)";
|
||||
ctx.beginPath();
|
||||
ctx.arc(btnX + CLOSE_BTN_SIZE/2, btnY + CLOSE_BTN_SIZE/2, CLOSE_BTN_SIZE/2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Сам крестик
|
||||
ctx.strokeStyle = "#fff";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineCap = "round";
|
||||
@@ -248,7 +245,6 @@ app.registerExtension({
|
||||
}
|
||||
};
|
||||
|
||||
// Отрисовка виджетов НА ПЕРЕДНЕМ ПЛАНЕ
|
||||
const origODF = this.onDrawForeground;
|
||||
this.onDrawForeground = function (ctx, vr) {
|
||||
ctx.save();
|
||||
@@ -286,7 +282,6 @@ app.registerExtension({
|
||||
}
|
||||
};
|
||||
|
||||
// Вспомогательные методы отрисовки UI
|
||||
this.drawLabel = function (ctx, t, x, y, w, h) {
|
||||
ctx.fillStyle = "#aaa";
|
||||
ctx.font = "11px sans-serif";
|
||||
@@ -334,33 +329,28 @@ app.registerExtension({
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
// Обработка кликов
|
||||
this.onMouseDown = function (e, pos, canvas) {
|
||||
const availableW = this.size[0] - this.padding * 2;
|
||||
const availableH = this.size[1] - this.widgetsHeight - this.padding * 2;
|
||||
const startY = this.widgetsHeight + this.padding;
|
||||
|
||||
// Для одного изображения никаких действий с превью не требуется
|
||||
if (this.imgs.length === 1) {
|
||||
// Просто проверяем виджеты
|
||||
for (const z of this.clickZones) {
|
||||
if (pos[0] >= z.x && pos[0] <= z.x + z.w &&
|
||||
pos[1] >= z.y && pos[1] <= z.y + z.h) {
|
||||
if (z.type === "path") { self.showPathInput(e); return true; }
|
||||
if (z.type === "browse") { self.showFolderSelector(e); return true; }
|
||||
if (z.type === "prefix") { self.showPrefixInput(e); return true; }
|
||||
if (z.type === "format") { self.showFormatSelector(e); return true; }
|
||||
}
|
||||
for (const z of this.clickZones) {
|
||||
if (pos[0] >= z.x && pos[0] <= z.x + z.w &&
|
||||
pos[1] >= z.y && pos[1] <= z.y + z.h) {
|
||||
if (z.type === "path") { self.showPathInput(e); return true; }
|
||||
if (z.type === "browse") { self.showFolderSelector(e); return true; }
|
||||
if (z.type === "prefix") { self.showPrefixInput(e); return true; }
|
||||
if (z.type === "format") { self.showFormatSelector(e); return true; }
|
||||
}
|
||||
}
|
||||
|
||||
if (this.imgs.length === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- ЛОГИКА ТОЛЬКО ДЛЯ ДВУХ И БОЛЕЕ ИЗОБРАЖЕНИЙ ---
|
||||
if (this.previewMode === 'view' && this.imgs.length > 0) {
|
||||
// Проверка клика по кнопке закрытия [×]
|
||||
const btnX = this.size[0] - this.padding - CLOSE_BTN_SIZE;
|
||||
const btnY = startY;
|
||||
|
||||
const centerX = btnX + CLOSE_BTN_SIZE / 2;
|
||||
const centerY = btnY + CLOSE_BTN_SIZE / 2;
|
||||
const dist = Math.sqrt(Math.pow(pos[0] - centerX, 2) + Math.pow(pos[1] - centerY, 2));
|
||||
@@ -373,25 +363,23 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
if (this.previewMode === 'grid' && this.imgs.length > 0) {
|
||||
// Проверка клика по ячейке сетки (та же формула, что и в отрисовке)
|
||||
const count = this.imgs.length;
|
||||
const approxSide = Math.sqrt((availableW * availableH) / count);
|
||||
let cols = Math.floor(availableW / approxSide);
|
||||
if (cols < 1) cols = 1;
|
||||
const rows = Math.ceil(count / cols);
|
||||
const grid = calcGrid(count, availableW, availableH);
|
||||
const cols = grid.cols;
|
||||
const cellSize = grid.cellSize;
|
||||
|
||||
const cellW = (availableW - PREVIEW_GAP * (cols - 1)) / cols;
|
||||
const cellH = (availableH - PREVIEW_GAP * (rows - 1)) / rows;
|
||||
const totalWidth = cols * cellSize + (cols - 1) * PREVIEW_GAP;
|
||||
const offsetX_extra = (availableW - totalWidth) / 2;
|
||||
|
||||
for (let i = 0; i < this.imgs.length; i++) {
|
||||
const col = i % cols;
|
||||
const row = Math.floor(i / cols);
|
||||
|
||||
const x = this.padding + col * (cellW + PREVIEW_GAP);
|
||||
const y = startY + row * (cellH + PREVIEW_GAP);
|
||||
const x = this.padding + offsetX_extra + col * (cellSize + PREVIEW_GAP);
|
||||
const y = startY + row * (cellSize + PREVIEW_GAP);
|
||||
|
||||
if (pos[0] >= x && pos[0] <= x + cellW &&
|
||||
pos[1] >= y && pos[1] <= y + cellH) {
|
||||
if (pos[0] >= x && pos[0] <= x + cellSize &&
|
||||
pos[1] >= y && pos[1] <= y + cellSize) {
|
||||
this.imageIndex = i;
|
||||
this.previewMode = 'view';
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
@@ -400,21 +388,12 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка зон виджетов
|
||||
for (const z of this.clickZones) {
|
||||
if (pos[0] >= z.x && pos[0] <= z.x + z.w &&
|
||||
pos[1] >= z.y && pos[1] <= z.y + z.h) {
|
||||
if (z.type === "path") { self.showPathInput(e); return true; }
|
||||
if (z.type === "browse") { self.showFolderSelector(e); return true; }
|
||||
if (z.type === "prefix") { self.showPrefixInput(e); return true; }
|
||||
if (z.type === "format") { self.showFormatSelector(e); return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Меню выбора папки
|
||||
this.showFolderSelector = function (ev) {
|
||||
self.closeActivePopup();
|
||||
|
||||
if (!self.foldersLoaded) {
|
||||
self.loadOutputFolders().then(() => self.showFolderSelector(ev));
|
||||
return;
|
||||
@@ -422,6 +401,7 @@ app.registerExtension({
|
||||
|
||||
const menu = document.createElement("div");
|
||||
menu.style.cssText = 'position:fixed;background:#1a1a1a;border:1px solid #444;border-radius:6px;overflow:hidden;z-index:10001;box-shadow:0 4px 20px rgba(0,0,0,0.5);min-width:200px;max-height:350px;overflow-y:auto;';
|
||||
self.activePopup = menu;
|
||||
|
||||
const rootItem = document.createElement("div");
|
||||
rootItem.textContent = "ComfyUI";
|
||||
@@ -435,7 +415,7 @@ app.registerExtension({
|
||||
rootItem.onclick = (e) => {
|
||||
e.stopPropagation(); e.preventDefault();
|
||||
self.rs_data.save_path = "";
|
||||
self.persistState(); self.updateUI(); menu.remove();
|
||||
self.persistState(); self.updateUI(); self.closeActivePopup();
|
||||
};
|
||||
menu.appendChild(rootItem);
|
||||
|
||||
@@ -446,7 +426,7 @@ app.registerExtension({
|
||||
customItem.onmouseout = () => customItem.style.background = "#1a1a1a";
|
||||
customItem.onclick = (e) => {
|
||||
e.stopPropagation(); e.preventDefault();
|
||||
menu.remove();
|
||||
self.closeActivePopup();
|
||||
self.showPathInput(ev);
|
||||
};
|
||||
menu.appendChild(customItem);
|
||||
@@ -474,7 +454,7 @@ app.registerExtension({
|
||||
item.onclick = (e) => {
|
||||
e.stopPropagation(); e.preventDefault();
|
||||
self.rs_data.save_path = folder;
|
||||
self.persistState(); self.updateUI(); menu.remove();
|
||||
self.persistState(); self.updateUI(); self.closeActivePopup();
|
||||
};
|
||||
menu.appendChild(item);
|
||||
});
|
||||
@@ -487,23 +467,22 @@ app.registerExtension({
|
||||
document.body.appendChild(menu);
|
||||
|
||||
setTimeout(() => {
|
||||
const closeHandler = (e) => { if (!menu.contains(e.target)) { cleanup(); } };
|
||||
const mouseLeaveHandler = () => { cleanup(); };
|
||||
const cleanup = () => {
|
||||
menu.remove();
|
||||
document.removeEventListener("mousedown", closeHandler);
|
||||
menu.removeEventListener("mouseleave", mouseLeaveHandler);
|
||||
const closeHandler = (e) => {
|
||||
if (self.activePopup === menu && !menu.contains(e.target)) {
|
||||
self.closeActivePopup();
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", closeHandler);
|
||||
menu.addEventListener("mouseleave", mouseLeaveHandler);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// Поле ввода пути
|
||||
this.showPathInput = function (ev) {
|
||||
self.closeActivePopup();
|
||||
const cv = self.rs_data.save_path || '';
|
||||
const pop = document.createElement('div');
|
||||
pop.style.cssText = 'position:fixed;z-index:10002;background:#1a1a1a;border:1px solid #444;border-radius:6px;padding:8px 12px;box-shadow:0 4px 20px rgba(0,0,0,0.5);display:flex;align-items:center;gap:8px;';
|
||||
self.activePopup = pop;
|
||||
|
||||
const inp = document.createElement('input');
|
||||
inp.type = 'text';
|
||||
inp.value = cv;
|
||||
@@ -520,19 +499,23 @@ app.registerExtension({
|
||||
document.body.appendChild(pop);
|
||||
setTimeout(() => { inp.focus(); if (cv.length) inp.select(); }, 50);
|
||||
|
||||
const save = () => { self.rs_data.save_path = inp.value; self.persistState(); self.updateUI(); cleanup(); };
|
||||
const cleanup = () => { pop.remove(); document.removeEventListener("mousedown", cl); };
|
||||
const save = () => { self.rs_data.save_path = inp.value; self.persistState(); self.updateUI(); self.closeActivePopup(); };
|
||||
btn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); save(); };
|
||||
inp.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); save(); } };
|
||||
const cl = (e) => { if (!pop.contains(e.target)) { cleanup(); } };
|
||||
setTimeout(() => { document.addEventListener("mousedown", cl); }, 50);
|
||||
|
||||
setTimeout(() => {
|
||||
const cl = (e) => { if (self.activePopup === pop && !pop.contains(e.target)) { self.closeActivePopup(); } };
|
||||
document.addEventListener("mousedown", cl);
|
||||
}, 50);
|
||||
};
|
||||
|
||||
// Поле ввода префикса
|
||||
this.showPrefixInput = function (ev) {
|
||||
self.closeActivePopup();
|
||||
const cv = self.rs_data.file_prefix || 'img';
|
||||
const pop = document.createElement('div');
|
||||
pop.style.cssText = 'position:fixed;z-index:10002;background:#1a1a1a;border:1px solid #444;border-radius:6px;padding:8px 12px;box-shadow:0 4px 20px rgba(0,0,0,0.5);display:flex;align-items:center;gap:8px;';
|
||||
self.activePopup = pop;
|
||||
|
||||
const inp = document.createElement('input');
|
||||
inp.type = 'text';
|
||||
inp.value = cv;
|
||||
@@ -548,19 +531,23 @@ app.registerExtension({
|
||||
document.body.appendChild(pop);
|
||||
setTimeout(() => { inp.focus(); if (cv.length) inp.select(); }, 50);
|
||||
|
||||
const save = () => { self.rs_data.file_prefix = inp.value; self.persistState(); self.updateUI(); cleanup(); };
|
||||
const cleanup = () => { pop.remove(); document.removeEventListener("mousedown", cl); };
|
||||
const save = () => { self.rs_data.file_prefix = inp.value; self.persistState(); self.updateUI(); self.closeActivePopup(); };
|
||||
btn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); save(); };
|
||||
inp.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); save(); } };
|
||||
const cl = (e) => { if (!pop.contains(e.target)) { cleanup(); } };
|
||||
setTimeout(() => { document.addEventListener("mousedown", cl); }, 50);
|
||||
|
||||
setTimeout(() => {
|
||||
const cl = (e) => { if (self.activePopup === pop && !pop.contains(e.target)) { self.closeActivePopup(); } };
|
||||
document.addEventListener("mousedown", cl);
|
||||
}, 50);
|
||||
};
|
||||
|
||||
// Выбор формата
|
||||
this.showFormatSelector = function (ev) {
|
||||
self.closeActivePopup();
|
||||
const FMTS = ["png", "jpg", "webp"];
|
||||
const menu = document.createElement("div");
|
||||
menu.style.cssText = 'position:fixed;background:#1a1a1a;border:1px solid #444;border-radius:6px;overflow:hidden;z-index:10001;box-shadow:0 4px 20px rgba(0,0,0,0.5);min-width:120px;';
|
||||
self.activePopup = menu;
|
||||
|
||||
FMTS.forEach(f => {
|
||||
const it = document.createElement("div");
|
||||
it.textContent = f.toUpperCase();
|
||||
@@ -573,14 +560,14 @@ app.registerExtension({
|
||||
self.rs_data.format = f;
|
||||
self.persistState();
|
||||
self.updateUI();
|
||||
menu.remove();
|
||||
self.closeActivePopup();
|
||||
};
|
||||
menu.appendChild(it);
|
||||
});
|
||||
if (ev) { menu.style.left = (ev.clientX + 8) + "px"; menu.style.top = (ev.clientY + 8) + "px"; }
|
||||
document.body.appendChild(menu);
|
||||
setTimeout(() => {
|
||||
const cl = (e) => { if (!menu.contains(e.target)) { menu.remove(); document.removeEventListener("mousedown", cl); } };
|
||||
const cl = (e) => { if (self.activePopup === menu && !menu.contains(e.target)) { self.closeActivePopup(); } };
|
||||
document.addEventListener("mousedown", cl);
|
||||
}, 100);
|
||||
};
|
||||
@@ -589,7 +576,6 @@ app.registerExtension({
|
||||
if (self.graph) self.graph.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
// Восстановление состояния при загрузке воркфлоу
|
||||
const originalOnConfigure = this.onConfigure;
|
||||
this.onConfigure = function(info) {
|
||||
const r = originalOnConfigure ? originalOnConfigure.apply(this, arguments) : undefined;
|
||||
|
||||
Reference in New Issue
Block a user