Compare commits

..
5 Commits
Author SHA1 Message Date
m0rtus59 cc8337f6e6 Update compatibility with the latest frontend version 2026-11-04 17:34:28 +05:00
m0rtus59 b4a21ef852 update node renumber script for latest backend 2026-10-25 22:28:51 +05:00
M0rtus59 65e53946d2 fix 'properties' button dupe 2026-10-18 21:47:14 +05:00
m0rtus59 b1d784e9e4 version bump 2026-10-17 11:14:57 +05:00
m0rtus59 2b91e5ef72 update locales 2026-10-17 11:13:53 +05:00
34 changed files with 1084 additions and 210 deletions
+127 -25
View File
@@ -1,4 +1,5 @@
import { app } from "/scripts/app.js";
import { SettingIds } from "../core/constants.js";
export function findOriginalPropertiesButton() {
const byTestId = document.querySelector('[data-testid="properties-panel-toggle"]');
@@ -26,7 +27,7 @@ export function findOriginalPropertiesButton() {
}
export function findTopbarContainer() {
const container = document.querySelector('[data-testid="action-bar-buttons"], [class*="actionbar-buttons"], .actionbar-buttons, [class*="actionbar"]');
const container = document.querySelector('.actionbar-container, [data-testid="action-bar-buttons"], [class*="actionbar-buttons"], .actionbar-buttons, [class*="actionbar"]');
if (container) return container;
const orig = findOriginalPropertiesButton();
@@ -156,6 +157,11 @@ export function findNativeExtensionsPanel() {
}
export function findGraphButton() {
const byTestId = document.querySelector('[data-testid="view-mode-toggle"]');
if (byTestId && !byTestId.closest('.p-dialog, .comfy-modal, [role="dialog"], .p-sidebar, .comfy-settings, .comfyui-sidebar, .comfy-sidebar')) {
return byTestId;
}
const elements = document.querySelectorAll('.p-1.bg-base-background.rounded-lg, .bg-base-background.rounded-lg, .bg-secondary-background.rounded-lg.items-center.inline-flex.pointer-events-auto, [data-testid="graph-view-button"]');
for (const el of elements) {
if (el.closest('.p-dialog, .comfy-modal, [role="dialog"], .p-sidebar, .comfy-settings, .comfyui-sidebar, .comfy-sidebar')) continue;
@@ -230,39 +236,135 @@ export function updateSidebarBadge(count) {
}
}
export function applySidebarOverride() {
const overrideStock = app.ui.settings?.getSettingValue?.("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ?? false;
const stdBtn = findStandardQueueButton();
const ourBtn = findOurSidebarButton();
let isApplyingSidebarOverride = false;
if (stdBtn) {
if (!stdBtn._originalDisplay) {
stdBtn._originalDisplay = window.getComputedStyle(stdBtn).display || "block";
export function findSidebarMenuButton(sidebar) {
const root = sidebar || document.querySelector('[data-testid="sidebar-top-group"]') ||
document.querySelector('.comfyui-sidebar, .comfy-sidebar, .sidebar');
if (!root) return null;
// 1. Look for explicit menu or logo test ID
const byTestId = root.querySelector(
'[data-testid*="menu-button"], [data-testid*="app-menu"], [data-testid*="comfy-logo"], [data-testid="sidebar-menu-button"]'
);
if (byTestId) {
return byTestId.closest('button, [role="button"], .comfyui-sidebar-tab, .comfyui-sidebar-item') || byTestId;
}
// 2. Look for title, tooltip, or aria-label containing "menu"
const candidates = Array.from(root.querySelectorAll('button, [role="button"], a, .comfyui-sidebar-tab, .comfyui-sidebar-item'));
const menuBtn = candidates.find(el => {
if (el.classList.contains('comfy-sidebar-tab') || el.id?.includes('classic-comfy-sidebar')) return false;
const title = (el.getAttribute('title') || el.getAttribute('aria-label') || el.textContent || '').trim().toLowerCase();
return title === 'menu' || title.startsWith('menu');
});
if (menuBtn) return menuBtn;
// 3. Fallback: the first child element that is not our button and not the stock queue button
const ourBtn = findOurSidebarButton();
const stdBtn = findStandardQueueButton();
for (const child of root.children) {
if (child !== ourBtn && child !== stdBtn) {
return child;
}
}
return null;
}
export function applySidebarOverride() {
if (isApplyingSidebarOverride) return;
isApplyingSidebarOverride = true;
try {
const overrideStock = (
app.extensionManager?.setting?.get?.(SettingIds.OVERRIDE_STOCK_HISTORY) ??
app.ui?.settings?.getSettingValue?.(SettingIds.OVERRIDE_STOCK_HISTORY) ??
app.ui?.settings?.getSettingValue?.("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ??
app.ui?.settings?.getSettingValue?.("Comfy Sidebar.Override Stock History") ??
false
);
const stdBtn = findStandardQueueButton();
const ourBtn = findOurSidebarButton();
const topGroup = document.querySelector('[data-testid="sidebar-top-group"]') ||
(ourBtn ? ourBtn.closest('[data-testid="sidebar-top-group"]') : null);
if (ourBtn && !ourBtn._capturedOriginal) {
ourBtn._capturedOriginal = true;
ourBtn._originalParent = ourBtn.parentNode;
ourBtn._originalNextSibling = ourBtn.nextSibling;
}
if (overrideStock) {
stdBtn.style.setProperty("display", "none", "important");
if (!stdBtn._overrideClickListener) {
stdBtn._overrideClickListener = (e) => {
e.preventDefault();
e.stopPropagation();
const b = findOurSidebarButton();
if (b) b.click();
};
stdBtn.addEventListener('click', stdBtn._overrideClickListener, true);
if (stdBtn) {
if (!stdBtn._originalDisplay) {
stdBtn._originalDisplay = window.getComputedStyle(stdBtn).display || "block";
}
if (stdBtn.style.display !== "none") {
stdBtn.style.setProperty("display", "none", "important");
}
if (!stdBtn._overrideClickListener) {
stdBtn._overrideClickListener = (e) => {
e.preventDefault();
e.stopPropagation();
const b = findOurSidebarButton();
if (b) b.click();
};
stdBtn.addEventListener('click', stdBtn._overrideClickListener, true);
}
}
if (ourBtn && stdBtn.parentNode && ourBtn.nextSibling !== stdBtn) {
stdBtn.parentNode.insertBefore(ourBtn, stdBtn);
if (ourBtn) {
const parent = (topGroup && topGroup.contains(ourBtn)) ? topGroup : (stdBtn ? stdBtn.parentNode : ourBtn.parentNode);
if (parent) {
const menuBtn = findSidebarMenuButton(parent);
if (menuBtn && menuBtn.parentNode === parent) {
// Place Queue immediately below the Menu button
if (menuBtn.nextSibling !== ourBtn) {
parent.insertBefore(ourBtn, menuBtn.nextSibling);
}
} else if (stdBtn && stdBtn.parentNode === parent) {
// Place Queue right where stock Job History was
if (stdBtn.previousSibling !== ourBtn && stdBtn !== ourBtn) {
parent.insertBefore(ourBtn, stdBtn);
}
} else {
// Fallback: top of the group
const first = parent.firstElementChild;
if (first && first !== ourBtn) {
parent.insertBefore(ourBtn, first);
}
}
}
}
} else {
stdBtn.style.setProperty("display", stdBtn._originalDisplay === "none" ? "block" : stdBtn._originalDisplay);
if (stdBtn._overrideClickListener) {
stdBtn.removeEventListener('click', stdBtn._overrideClickListener, true);
stdBtn._overrideClickListener = null;
if (stdBtn) {
if (stdBtn.style.display === "none") {
stdBtn.style.removeProperty("display");
if (stdBtn._originalDisplay && stdBtn._originalDisplay !== "none") {
stdBtn.style.display = stdBtn._originalDisplay;
}
}
if (stdBtn._overrideClickListener) {
stdBtn.removeEventListener('click', stdBtn._overrideClickListener, true);
stdBtn._overrideClickListener = null;
}
}
if (ourBtn && stdBtn.parentNode && ourBtn.parentNode === stdBtn.parentNode && ourBtn !== stdBtn.parentNode.lastChild) {
stdBtn.parentNode.appendChild(ourBtn);
if (ourBtn) {
if (ourBtn._originalNextSibling && ourBtn._originalNextSibling.parentNode === ourBtn.parentNode) {
if (ourBtn.nextSibling !== ourBtn._originalNextSibling) {
ourBtn.parentNode.insertBefore(ourBtn, ourBtn._originalNextSibling);
}
} else if (ourBtn._originalParent && ourBtn.parentNode === ourBtn._originalParent) {
if (ourBtn !== ourBtn.parentNode.lastElementChild) {
ourBtn.parentNode.appendChild(ourBtn);
}
}
}
}
} finally {
isApplyingSidebarOverride = false;
}
}
+72 -39
View File
@@ -6,8 +6,10 @@ import {
findActiveQueueIndicator,
findNativeExtensionsPanel,
findGraphButton,
findPropertiesPanel
findPropertiesPanel,
applySidebarOverride
} from "./adapter.js";
import { SettingIds } from "../core/constants.js";
import { isRuntimePreviewEnabled, setRuntimePreviewEnabled, setRuntimePreviewStateListener } from "../utils/comparison.js";
// Listen for runtime preview state changes to keep button UI in sync
@@ -18,20 +20,22 @@ setRuntimePreviewStateListener(() => {
const STYLE_ID = "comfy-sidebar-classic-layout-override";
const CLASSIC_LAYOUT_CSS_MEDIA = `
/* Neutralize the leftover floating shell box to prevent empty dark square artifacts */
.actionbar-container,
.shadow-interface.rounded-lg.bg-comfy-menu-bg:not(.floating-panel):not([role="toolbar"]),
.shadow-interface.rounded-lg.border-interface-stroke:not(.floating-panel):not([role="toolbar"]),
.shadow-interface.border.rounded-lg:has([class*="actionbar-buttons"]),
.shadow-interface.border.rounded-lg:has(.actionbar-buttons),
div.border-interface-stroke.rounded-lg:has([class*="actionbar-buttons"]),
div.shadow-interface.py-1\\.75 {
/* Neutralize the leftover floating shell box to prevent empty dark square/circle artifacts */
[data-testid="action-bar-card"],
.floating-panel:has(.actionbar-container),
.floating-panel:has([class*="actionbar"]),
div.shadow-interface:has(.actionbar-container),
div.shadow-interface:has([class*="actionbar-buttons"]),
div:has(> .actionbar-container):not(header):not(.topbar),
div:has(> [class*="actionbar"]):not(header):not(.topbar):not(.actionbar-container) {
border: none !important;
background: transparent !important;
box-shadow: none !important;
outline: none !important;
}
/* Dock actionbar buttons in the topbar row with transparent background */
.actionbar-container,
[class*="actionbar"]:not(.actionbar),
[class*="actionbar-buttons"],
.actionbar-buttons {
@@ -102,9 +106,27 @@ export function syncStockHistoryAndProgressSettings(enable) {
let savedButtonData = null;
let domObserver = null;
let sidebarTopObserver = null;
let syncScheduled = false;
let actionbarResizeObserver = null;
function setupSidebarTopObserver() {
if (sidebarTopObserver) return;
const target = document.querySelector('[data-testid="sidebar-top-group"]') ||
document.querySelector('.comfyui-sidebar, .comfy-sidebar, .sidebar');
if (!target) return;
sidebarTopObserver = new MutationObserver(() => {
applySidebarOverride();
updateSidebarTabsVisibility();
});
sidebarTopObserver.observe(target, {
childList: true,
subtree: true
});
}
function isSidebarOnLeft() {
const sidebar = document.querySelector('.comfyui-sidebar, .comfy-sidebar, .sidebar, [class*="sidebar-nav"], [class*="sidebar"]');
if (!sidebar) return false;
@@ -371,7 +393,7 @@ function setupCanvasMenuPopoverRelocator() {
}
function updateTopMetrics() {
const bar = document.querySelector('.actionbar-buttons, [class*="actionbar-buttons"]') || findTopbarContainer();
const bar = document.querySelector('.actionbar-container, .actionbar-buttons, [class*="actionbar-buttons"]') || findTopbarContainer();
const tabsEl = document.querySelector('.workflow-tabs-container');
const topbar = tabsEl?.closest('header, .topbar, [class*="topbar"]') || tabsEl;
@@ -491,7 +513,13 @@ function updateSidebarTabsVisibility() {
}
});
const hideStockHistory = app.ui?.settings?.getSettingValue("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ?? false;
const hideStockHistory = (
app.extensionManager?.setting?.get?.(SettingIds.OVERRIDE_STOCK_HISTORY) ??
app.ui?.settings?.getSettingValue?.(SettingIds.OVERRIDE_STOCK_HISTORY) ??
app.ui?.settings?.getSettingValue?.("Comfy Sidebar.Hide Junk.Override Stock Job History Tab") ??
app.ui?.settings?.getSettingValue?.("Comfy Sidebar.Override Stock History") ??
false
);
const historyTarget = sidebar.querySelector('[data-testid="queue-tab-button"], [data-testid="job-history-tab-button"], [class*="lucide--history"]');
if (historyTarget) {
const historyBtn = historyTarget.closest('.comfyui-sidebar-tab, button, [role="tab"], li, a, .comfyui-sidebar-item') || historyTarget;
@@ -578,10 +606,6 @@ function syncPropertiesButton(isClassicLayoutEnabled) {
const openState = isPropertiesPanelOpen();
if (originalBtn) {
if (!originalBtn.classList.contains("comfy-sidebar-hide-original-properties-btn")) {
originalBtn.classList.add("comfy-sidebar-hide-original-properties-btn");
}
savedButtonData = {
className: originalBtn.className.replace("comfy-sidebar-hide-original-properties-btn", "").trim(),
innerHTML: originalBtn.innerHTML,
@@ -591,6 +615,26 @@ function syncPropertiesButton(isClassicLayoutEnabled) {
value: attr.value
}))
};
// When closed: keep originalBtn visible (ComfyUI renders error outlines & dot natively).
// When open: hide originalBtn so our customBtn takes its pinned place.
if (openState) {
if (!originalBtn.classList.contains("comfy-sidebar-hide-original-properties-btn")) {
originalBtn.classList.add("comfy-sidebar-hide-original-properties-btn");
}
} else {
if (originalBtn.classList.contains("comfy-sidebar-hide-original-properties-btn")) {
originalBtn.classList.remove("comfy-sidebar-hide-original-properties-btn");
}
}
// Clicking the native button to open the panel triggers an instant layout sync
if (!originalBtn._syncAttached) {
originalBtn._syncAttached = true;
originalBtn.addEventListener("click", () => {
requestAnimationFrame(() => syncClassicLayout());
});
}
}
let customBtn = document.querySelector(".comfy-sidebar-custom-properties-toggle");
@@ -621,31 +665,11 @@ function syncPropertiesButton(isClassicLayoutEnabled) {
}
if (customBtn) {
if (originalBtn) {
const cleanedHTML = cleanHTML(originalBtn.innerHTML);
if (cleanedHTML && customBtn.innerHTML !== cleanedHTML) {
const existingDot = customBtn.querySelector('.comfy-sidebar-error-dot');
customBtn.innerHTML = cleanedHTML;
if (existingDot) customBtn.appendChild(existingDot);
}
const titleVal = originalBtn.getAttribute("title") || originalBtn.getAttribute("aria-label") || "Toggle properties panel";
customBtn.setAttribute("title", titleVal);
customBtn.setAttribute("aria-label", titleVal);
for (const cls of originalBtn.classList) {
if (cls !== "comfy-sidebar-hide-original-properties-btn" && !customBtn.classList.contains(cls)) {
customBtn.classList.add(cls);
}
}
syncErrorBadge(originalBtn, customBtn);
} else if (!customBtn.getAttribute("title")) {
if (!customBtn.getAttribute("title")) {
customBtn.setAttribute("title", "Toggle properties panel");
customBtn.setAttribute("aria-label", "Toggle properties panel");
}
customBtn.classList.toggle("comfy-panel-open", openState);
customBtn.style.display = "inline-flex";
}
}
@@ -779,6 +803,10 @@ export function syncClassicLayout() {
syncPropertiesButton(isClassicLayoutEnabled);
syncActiveQueueIndicator(isClassicLayoutEnabled);
updateSidebarTabsVisibility();
applySidebarOverride();
if (!sidebarTopObserver) {
setupSidebarTopObserver();
}
syncRuntimePreviewButton();
} catch (err) {
@@ -796,6 +824,7 @@ export function syncClassicLayout() {
export function setupPropertiesPanelToggleFix() {
setupCanvasMenuTooltipRelocator();
setupCanvasMenuPopoverRelocator();
setupSidebarTopObserver();
if (!document.getElementById("comfy-sidebar-layout-fix-styles")) {
const style = document.createElement("style");
@@ -812,15 +841,15 @@ export function setupPropertiesPanelToggleFix() {
order: 99999 !important;
position: relative !important;
overflow: visible !important;
margin-left: -8px !important;
margin-left: 0px !important;
margin-right: 0px !important;
display: inline-flex !important;
display: none !important;
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
}
.comfy-sidebar-custom-properties-toggle.comfy-panel-open {
margin-left: 0px !important;
display: inline-flex !important;
}
.comfy-sidebar-custom-properties-toggle > span.comfy-sidebar-error-dot {
display: inline-flex !important;
@@ -1087,6 +1116,10 @@ export function destroyLayoutFix() {
domObserver.disconnect();
domObserver = null;
}
if (sidebarTopObserver) {
sidebarTopObserver.disconnect();
sidebarTopObserver = null;
}
if (actionbarResizeObserver) {
actionbarResizeObserver.disconnect();
actionbarResizeObserver = null;
+24 -37
View File
@@ -4,19 +4,28 @@ let currentLocale = "en";
let translations = {};
let fallbackTranslations = {};
/**
* Initializes the localization engine by reading ComfyUI's language setting
* and dynamically loading the corresponding JSON dictionary.
*/
async function fetchLocaleFiles(lang) {
const data = {};
for (const file of ["main.json", "settings.json"]) {
try {
const url = new URL(`../locales/${lang}/${file}`, import.meta.url).href;
const res = await fetch(url);
if (res.ok) {
const json = await res.json();
Object.assign(data, json);
}
} catch (e) {}
}
return data;
}
export async function initI18n() {
try {
// ComfyUI's internal setting ID is "Comfy.Locale"
let settingVal = app?.extensionManager?.setting?.get?.("Comfy.Locale")
|| app?.ui?.settings?.getSettingValue?.("Comfy.Locale")
|| app?.extensionManager?.setting?.get?.("Comfy.Locale.Language")
|| app?.ui?.settings?.getSettingValue?.("Comfy.Locale.Language");
// Fallback check in localStorage in case settings API hasn't populated yet
if (!settingVal) {
try {
settingVal = localStorage.getItem("Comfy.Settings.Comfy.Locale")
@@ -25,7 +34,6 @@ export async function initI18n() {
}
const raw = (settingVal || "en").toLowerCase().replace("_", "-");
// Check regional locale first (e.g. "zh-tw", "pt-br"), then base ("zh", "pt")
const candidates = [raw];
const base = raw.split("-")[0];
if (base !== raw) candidates.push(base);
@@ -33,34 +41,18 @@ export async function initI18n() {
currentLocale = candidates[0];
console.log("[ComfySidebar] Detected locale:", currentLocale);
// 1. Load English fallback dictionary
try {
const enUrl = new URL("../locales/en.json", import.meta.url).href;
const enRes = await fetch(enUrl);
if (enRes.ok) {
fallbackTranslations = await enRes.json();
}
} catch (e) {
console.warn("[ComfySidebar] Failed to load en.json fallback", e);
}
// 1. Load English fallback
fallbackTranslations = await fetchLocaleFiles("en");
// 2. If language is not English, load target locale
// 2. Load active locale if not English
if (base !== "en") {
let loaded = false;
for (const cand of candidates) {
try {
const locUrl = new URL(`../locales/${cand}.json`, import.meta.url).href;
const locRes = await fetch(locUrl);
if (locRes.ok) {
translations = await locRes.json();
console.log(`[ComfySidebar] Loaded ${cand}.json with`, Object.keys(translations).length, "translations");
loaded = true;
break;
}
} catch (e) {}
}
if (!loaded) {
console.warn(`[ComfySidebar] No translation file found for: ${candidates.join(" / ")}`);
const locData = await fetchLocaleFiles(cand);
if (Object.keys(locData).length > 0) {
translations = locData;
console.log(`[ComfySidebar] Loaded ${cand} with ${Object.keys(translations).length} translations`);
break;
}
}
}
} catch (e) {
@@ -68,11 +60,6 @@ export async function initI18n() {
}
}
/**
* Translate a phrase.
* If translation is missing in current language, falls back to English.
* If missing in English too, returns the original text.
*/
export function t(key, vars = {}) {
if (!key) return "";
let str = translations[key] || fallbackTranslations[key] || key;
@@ -96,5 +96,10 @@
"Reset View": "إعادة ضبط المنظور",
"Settings": "الإعدادات",
"Export As...": "تصدير كـ...",
"Failed to load 3D format": "فشل تحميل تنسيق 3D"
"Failed to load 3D format": "فشل تحميل تنسيق 3D",
"Toggle Comfy Queue Sidebar": "تبديل الشريط الجانبي لـ Comfy Queue",
"Toggle Ignore Selected Node(s) in Queue": "تجاهل/تضمين العقد المحددة في قائمة الانتظار",
"Renumber All Nodes (Execution Order)": "إعادة ترقيم جميع العقد (ترتيب التنفيذ)",
"Set Selected Node ID...": "تعيين معرف العقدة المحددة...",
"Set Node ID...": "تعيين معرف العقدة..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "حد أعمدة قائمة الانتظار",
"Width Threshold for Queue Columns (px)": "العرض بالبكسل لتقسيم القائمة إلى عدة أعمدة",
"Show Pending Count Only": "إظهار عدد المهام المعلقة فقط",
"If disabled, each queued job will have a separate individual card": "في حال التعطيل، ستظهر كل مهمة في القائمة ببطاقة مستقلة",
"Show Working Node Name": "إظهار اسم العقدة النشطة",
"Shows the name of the node which is currently in the process": "يعرض اسم العقدة التي تتم معالجتها حالياً",
"Auto Clear Interrupted": "مسح تلقائي للمهام المقاطعة",
"Auto-clear cancelled & failed jobs on new generation": "مسح المهام الملغاة والفاشلة تلقائياً عند بدء توليد جديد",
"Permanent Delete on Disk Deletion": "حذف دائم من القرص",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "حذف الملفات نهائياً عند المسح من القرص (دون نقلها لسلة المحذوفات)",
"Hide Tab: {tab}": "إخفاء التبويب: {tab}",
"Assets": "الأصول",
"Nodes": "العقد",
"Models": "النماذج",
"Workflows": "مخططات العمل",
"Apps": "التطبيقات",
"Templates": "القوالب",
"Override Stock History": "استبدال السجل الافتراضي",
"Replace the stock Job History sidebar with Comfy Queue": "استبدال الشريط الجانبي لسجل المهام الافتراضي بـ Comfy Queue",
"Hide Graph Button": "إخفاء زر 'المخطط'",
"Hide floating 'Graph' (Workflow/Node Map) button": "إخفاء الزر العائم 'المخطط' (خريطة العقد)",
"Hide Avatar": "إخفاء الصورة الرمزية",
"Floating Canvas Controls": "أدوات التحكم العائمة في اللوحة",
"Classic Layout": "المظهر الكلاسيكي",
"Places the controls and the open workflow tabs on a single unified top bar": "وضع عناصر التحكم وتبويبات العمل المفتوحة في شريط علوي موحد",
"Comfy Layout": "مظهر مدمج",
"Hide Junk": "إخفاء العناصر غير المرغوبة",
"Default": "افتراضي",
"Dock": "إرساء",
"Hide": "إخفاء"
}
@@ -96,5 +96,10 @@
"Reset View": "Reset View",
"Settings": "Settings",
"Export As...": "Export As...",
"Failed to load 3D format": "Failed to load 3D format"
"Failed to load 3D format": "Failed to load 3D format",
"Toggle Comfy Queue Sidebar": "Toggle Comfy Queue Sidebar",
"Toggle Ignore Selected Node(s) in Queue": "Toggle Ignore Selected Node(s) in Queue",
"Renumber All Nodes (Execution Order)": "Renumber All Nodes (Execution Order)",
"Set Selected Node ID...": "Set Selected Node ID...",
"Set Node ID...": "Set Node ID..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "Grid Columns Threshold",
"Width Threshold for Queue Columns (px)": "Width Threshold for Queue Columns (px)",
"Show Pending Count Only": "Show Pending Count Only",
"If disabled, each queued job will have a separate individual card": "If disabled, each queued job will have a separate individual card",
"Show Working Node Name": "Show Working Node Name",
"Shows the name of the node which is currently in the process": "Shows the name of the node which is currently in the process",
"Auto Clear Interrupted": "Auto Clear Interrupted",
"Auto-clear cancelled & failed jobs on new generation": "Auto-clear cancelled & failed jobs on new generation",
"Permanent Delete on Disk Deletion": "Permanent Delete on Disk Deletion",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)",
"Hide Tab: {tab}": "Hide Tab: {tab}",
"Assets": "Assets",
"Nodes": "Nodes",
"Models": "Models",
"Workflows": "Workflows",
"Apps": "Apps",
"Templates": "Templates",
"Override Stock History": "Override Stock History",
"Replace the stock Job History sidebar with Comfy Queue": "Replace the stock Job History sidebar with Comfy Queue",
"Hide Graph Button": "Hide Graph Button",
"Hide floating 'Graph' (Workflow/Node Map) button": "Hide floating 'Graph' (Workflow/Node Map) button",
"Hide Avatar": "Hide Avatar",
"Floating Canvas Controls": "Floating Canvas Controls",
"Classic Layout": "Classic Layout",
"Places the controls and the open workflow tabs on a single unified top bar": "Places the controls and the open workflow tabs on a single unified top bar",
"Comfy Layout": "Comfy Layout",
"Hide Junk": "Hide Junk",
"Default": "Default",
"Dock": "Dock",
"Hide": "Hide"
}
@@ -96,5 +96,10 @@
"Reset View": "Restablecer vista",
"Settings": "Ajustes",
"Export As...": "Exportar como...",
"Failed to load 3D format": "Error al cargar el formato 3D"
"Failed to load 3D format": "Error al cargar el formato 3D",
"Toggle Comfy Queue Sidebar": "Alternar barra lateral Comfy Queue",
"Toggle Ignore Selected Node(s) in Queue": "Ignorar/Incluir nodo(s) seleccionado(s) en la cola",
"Renumber All Nodes (Execution Order)": "Renumerar todos los nodos (orden de ejecución)",
"Set Selected Node ID...": "Establecer ID del nodo seleccionado...",
"Set Node ID...": "Establecer ID de nodo..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "Umbral de columnas de la cola",
"Width Threshold for Queue Columns (px)": "Ancho en píxeles para dividir la cola en varias columnas",
"Show Pending Count Only": "Mostrar solo total en espera",
"If disabled, each queued job will have a separate individual card": "Si se desactiva, cada tarea en cola tendrá una tarjeta individual",
"Show Working Node Name": "Mostrar nombre del nodo en proceso",
"Shows the name of the node which is currently in the process": "Muestra el nombre del nodo que se está procesando actualmente",
"Auto Clear Interrupted": "Limpieza auto de interrumpidos",
"Auto-clear cancelled & failed jobs on new generation": "Borrar automáticamente tareas canceladas y fallidas al iniciar una nueva generación",
"Permanent Delete on Disk Deletion": "Eliminación permanente del disco",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "Eliminar permanentemente archivos al borrarlos (sin pasar por la papelera)",
"Hide Tab: {tab}": "Ocultar pestaña: {tab}",
"Assets": "Recursos",
"Nodes": "Nodos",
"Models": "Modelos",
"Workflows": "Flujos",
"Apps": "Apps",
"Templates": "Plantillas",
"Override Stock History": "Reemplazar historial estándar",
"Replace the stock Job History sidebar with Comfy Queue": "Reemplaza la barra de historial estándar con Comfy Queue",
"Hide Graph Button": "Ocultar botón 'Grafo'",
"Hide floating 'Graph' (Workflow/Node Map) button": "Oculta el botón flotante 'Grafo' (mapa de nodos)",
"Hide Avatar": "Ocultar avatar",
"Floating Canvas Controls": "Controles flotantes del lienzo",
"Classic Layout": "Diseño clásico",
"Places the controls and the open workflow tabs on a single unified top bar": "Coloca los controles y las pestañas abiertas en una barra superior unificada",
"Comfy Layout": "Diseño compacto",
"Hide Junk": "Ocultar elementos innecesarios",
"Default": "Por defecto",
"Dock": "Acoplar",
"Hide": "Ocultar"
}
+105
View File
@@ -0,0 +1,105 @@
{
"Queue": "صف",
"Search History": "جستجو در تاریخچه",
"Toggle Ignore Selected Node(s) in Queue (Ctrl+Q)": "نادیده گرفتن گره‌های انتخابی در صف (Ctrl+Q)",
"No nodes selected. Click a node on the canvas first to toggle ignore.": "هیچ گرهی انتخاب نشده است. ابتدا روی یک گره در بوم کلیک کنید.",
"Renumber All Nodes by Execution Order (Alt+R)": "شماره‌گذاری مجدد گره‌ها بر اساس ترتیب اجرا (Alt+R)",
"Clear Cancelled & Failed": "پاک کردن موارد لغو شده و ناموفق",
"Clear All History": "پاک کردن تمام تاریخچه",
"Filter by text, images, nodes...": "فیلتر بر اساس متن، تصویر، گره...",
"Clear & Close Search": "پاک کردن و بستن جستجو",
"Scroll to Top": "رفتن به بالا",
"Batch of #": "دسته #",
"Go Back": "بازگشت",
"Go Back to Queue": "بازگشت به صف",
"Outputs of #": "خروجی‌های #",
"No separate intermediate outputs found.": "هیچ خروجی میانی مجزایی پیدا نشد.",
"Cancel All Pending": "لغو تمام کارهای در انتظار",
"Pending Queue:": "صف در انتظار:",
"tasks": "کار",
"Download Object": "دانلود فایل",
"Copy to Clipboard": "کپی در کلیپ‌بورد",
"Download JSON": "دانلود JSON",
"Delete Card (Hold Ctrl to delete file from disk)": "حذف کارت (Ctrl برای حذف از دیسک)",
"Delete Card": "حذف کارت",
"Delete File": "حذف فایل",
"Delete Files": "حذف فایل‌ها",
"Show Node": "نمایش گره",
"Show Stopped/Failed Node": "نمایش گره متوقف یا ناموفق",
"View all intermediate outputs": "مشاهده تمام خروجی‌های میانی",
"Delete Card (Ctrl+Click to delete from disk)": "حذف کارت (Ctrl+کلیک برای حذف از دیسک)",
"Ctrl+Click again to delete file from DISK / TRASH": "برای حذف نهایی از دیسک مجدداً Ctrl+کلیک کنید",
"Click again to confirm removing card": "برای تأیید حذف مجدداً کلیک کنید",
"Remove from batch (Hold Ctrl to delete file from disk)": "حذف از دسته (Ctrl برای حذف از دیسک)",
"Click again to confirm removing from batch": "برای تأیید حذف از دسته مجدداً کلیک کنید",
"Delete node output (Hold Ctrl to delete file from disk)": "حذف خروجی گره (Ctrl برای حذف از دیسک)",
"Ctrl+Click again to delete this node's files from DISK / TRASH": "برای حذف فایل‌های این گره از دیسک مجدداً Ctrl+کلیک کنید",
"Click again to confirm removing output": "برای تأیید حذف خروجی مجدداً کلیک کنید",
"Moved file to Recycle Bin.": "فایل به سطل بازیافت منتقل شد.",
"Permanently deleted file.": "فایل به طور دائمی حذف شد.",
"Cancelled": "لغو شد",
"Error": "خطا",
"Warning": "هشدار",
"Cancel Queued Task": "لغو کار در صف",
"Interrupt Execution": "توقف اجرا",
"Click to read full text": "برای خواندن متن کامل کلیک کنید",
"No Outputs": "بدون خروجی",
"Processing...": "در حال پردازش...",
"Finishing...": "در حال اتمام...",
"No workflow metadata found in this file.": "متاداده جریان کاری در این فایل یافت نشد.",
"Open Folder": "باز کردن پوشه",
"Open Location": "باز کردن مسیر",
"View all images in this batch": "مشاهده تمام تصاویر این دسته",
"No nodes found on canvas to renumber.": "گرهی برای شماره‌گذاری مجدد روی بوم یافت نشد.",
"Renumbering will rewrite all node IDs on the canvas in topological execution order.\n\nThis will fix intermediate node outputs showing as the final result, but may break external scripts that rely on specific node IDs\n\nDo you want to proceed?": "شماره‌گذاری مجدد، شناسه تمام گره‌ها را طبق ترتیب اجرا بازنویسی می‌کند.\n\nاین کار خروجی‌های میانی را اصلاح می‌کند اما ممکن است اسکریپت‌های وابسته به شناسه‌ها را مختل کند.\n\nآیا ادامه می‌دهید؟",
"Renumbered {count} nodes by execution order. Latest node: #{id} ({name}).": "{count} گره طبق ترتیب اجرا شماره‌گذاری شدند. آخرین گره: #{id} ({name}).",
"Failed to renumber nodes: ": "شماره‌گذاری مجدد گره‌ها ناموفق بود: ",
"Please select a node first to change its ID.": "لطفاً ابتدا یک گره را برای تغییر شناسه انتخاب کنید.",
"Enter new ID for node #{id} ({name}):": "شناسه جدید برای گره #{id} ({name}) را وارد کنید:",
"Please enter a valid positive number.": "لطفاً یک عدد صحیح مثبت معتبر وارد کنید.",
"Node ID #{id} is already used by \"{name}\". Swap their IDs?": "شناسه #{id} قبلاً توسط \"{name}\" استفاده شده است. آیا می‌خواهید شناسه‌ها جابجا شوند؟",
"Assigned node ID #{id} to \"{name}\".": "شناسه #{id} به گره \"{name}\" اختصاص یافت.",
"Failed to set node ID: ": "تنظیم شناسه گره ناموفق بود: ",
"Video playback. Press Esc to close.": "پخش ویدیو. برای بستن Esc را بزنید.",
"Click image to zoom (100%/Fit) | Shift+Click another card to compare.": "کلیک برای بزرگنمایی (100%/تناسب) | Shift+کلیک روی کارت دیگر برای مقایسه.",
"Toggle Loop": "تکرار پخش",
"Aspect ratio mismatch ({wA}x{hA} vs {wB}x{hB}). Images must have matching proportions to compare.": "عدم تطابق نسبت ابعاد ({wA}x{hA} در برابر {wB}x{hB}). تصاویر برای مقایسه باید نسبت برابری داشته باشند.",
"Click image to zoom | Drag image to compare slider | Esc to close.": "کلیک برای بزرگنمایی | کشیدن اسلایدر برای مقایسه | Esc برای بستن.",
"Copy": "کپی",
"Copied!": "کپی شد!",
"Text Output": "خروجی متنی",
"Text Output #": "خروجی متنی #",
"Raw Text": "متن خام",
"{wc} words • {chars} chars": "{wc} کلمه • {chars} نویسه",
"3D Model": "مدل 3D",
"Loading 3D Asset...": "در حال بارگذاری مدل سه‌بعدی...",
"Exporting {format}...": "در حال صدور به فرمت {format}...",
"Failed to load 3D engine.": "موتور سه‌بعدی بارگذاری نشد.",
"Unable to download 3D libraries. Make sure your computer is online, or check if <code>--disable-api-nodes</code> is blocking external network requests.": "امکان دانلود کتابخانه‌های 3D وجود ندارد. اتصال اینترنت خود را بررسی کنید.",
"Background Color": "رنگ پس‌زمینه",
"Up Direction": "جهت بالا",
"Material Mode": "حالت متریال",
"Camera Type": "نوع دوربین",
"FOV": "میدان دید (FOV)",
"Light Intensity": "شدت نور",
"Original (Y-Up)": "اصلی (Y رو به بالا)",
"Z-Up (+90°)": "Z رو به بالا (+90°)",
"Inverted (-90°)": "معکوس (-90°)",
"Original / Textured": "اصلی / بافت‌دار",
"Clay / Studio Matte": "گلی / مات استودیویی",
"Normals": "نرمال‌ها",
"Perspective": "پرسپکتیو",
"Orthographic": "ارتوگرافیک",
"Grid": "شبکه",
"Auto-Rotate": "چرخش خودکار",
"Wireframe": "قاب سیمی (Wireframe)",
"Reset View": "بازنشانی دید",
"Settings": "تنظیمات",
"Export As...": "صادرات به عنوان...",
"Failed to load 3D format": "بارگذاری فرمت 3D ناموفق بود",
"Toggle Comfy Queue Sidebar": "تغییر وضعیت نوار کناری Comfy Queue",
"Toggle Ignore Selected Node(s) in Queue": "نادیده گرفتن/فعال‌سازی گره‌های انتخاب‌شده در صف",
"Renumber All Nodes (Execution Order)": "شماره‌گذاری مجدد همه گره‌ها (ترتیب اجرا)",
"Set Selected Node ID...": "تنظیم شناسه گره انتخاب‌شده...",
"Set Node ID...": "تنظیم شناسه گره..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "آستانه ستون‌های صف",
"Width Threshold for Queue Columns (px)": "عرض بر حسب پیکسل برای تقسیم صف به چند ستون",
"Show Pending Count Only": "فقط نمایش تعداد در انتظار",
"If disabled, each queued job will have a separate individual card": "در صورت غیرفعال بودن، هر کار در صف یک کارت جداگانه خواهد داشت",
"Show Working Node Name": "نمایش نام گره در حال پردازش",
"Shows the name of the node which is currently in the process": "نام گرهی را که در حال حاضر پردازش می‌شود نشان می‌دهد",
"Auto Clear Interrupted": "پاکسازی خودکار متوقف‌شده‌ها",
"Auto-clear cancelled & failed jobs on new generation": "پاک کردن خودکار کارهای لغو شده و ناموفق در تولید جدید",
"Permanent Delete on Disk Deletion": "حذف دائمی از دیسک",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "حذف دائمی فایل‌ها هنگام پاک کردن از دیسک (بدون انتقال به سطل بازیافت)",
"Hide Tab: {tab}": "پنهان کردن برگه: {tab}",
"Assets": "دارایی‌ها",
"Nodes": "گره‌ها",
"Models": "مدل‌ها",
"Workflows": "جریان‌های کاری",
"Apps": "برنامه‌ها",
"Templates": "الگوها",
"Override Stock History": "جایگزینی تاریخچه پیش‌فرض",
"Replace the stock Job History sidebar with Comfy Queue": "نوار کناری تاریخچه کارهای پیش‌فرض را با Comfy Queue جایگزین می‌کند",
"Hide Graph Button": "پنهان کردن دکمه 'نمودار'",
"Hide floating 'Graph' (Workflow/Node Map) button": "دکمه شناور 'نمودار' (نقشه گره‌ها) را پنهان می‌کند",
"Hide Avatar": "پنهان کردن آواتار",
"Floating Canvas Controls": "کنترل‌های شناور بوم",
"Classic Layout": "طرح‌بندی کلاسیک",
"Places the controls and the open workflow tabs on a single unified top bar": "کنترل‌ها و برگه‌های باز جریان کار را در یک نوار بالایی یکپارچه قرار می‌دهد",
"Comfy Layout": "طرح‌بندی فشرده",
"Hide Junk": "پنهان کردن موارد غیرضروری",
"Default": "پیش‌فرض",
"Dock": "اتصال",
"Hide": "پنهان"
}
@@ -96,5 +96,10 @@
"Reset View": "Réinitialiser la vue",
"Settings": "Paramètres",
"Export As...": "Exporter sous...",
"Failed to load 3D format": "Échec du chargement du format 3D"
"Failed to load 3D format": "Échec du chargement du format 3D",
"Toggle Comfy Queue Sidebar": "Basculer la barre latérale Comfy Queue",
"Toggle Ignore Selected Node(s) in Queue": "Ignorer/Activer les nœuds sélectionnés dans la file",
"Renumber All Nodes (Execution Order)": "Renuméroter tous les nœuds (ordre d'exécution)",
"Set Selected Node ID...": "Définir l'ID du nœud sélectionné...",
"Set Node ID...": "Définir l'ID du nœud..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "Seuil de colonnes",
"Width Threshold for Queue Columns (px)": "Largeur en pixels pour diviser la file en plusieurs colonnes",
"Show Pending Count Only": "Afficher uniquement le total en attente",
"If disabled, each queued job will have a separate individual card": "Si désactivé, chaque tâche en file aura une carte individuelle",
"Show Working Node Name": "Afficher le nom du nœud actif",
"Shows the name of the node which is currently in the process": "Affiche le nom du nœud en cours de traitement",
"Auto Clear Interrupted": "Nettoyage auto des interruptions",
"Auto-clear cancelled & failed jobs on new generation": "Effacer automatiquement les tâches annulées et échouées lors d'une nouvelle génération",
"Permanent Delete on Disk Deletion": "Suppression définitive du disque",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "Supprimer définitivement les fichiers lors de la suppression (sans passer par la corbeille)",
"Hide Tab: {tab}": "Masquer l'onglet : {tab}",
"Assets": "Ressources",
"Nodes": "Nœuds",
"Models": "Modèles",
"Workflows": "Workflows",
"Apps": "Applications",
"Templates": "Modèles",
"Override Stock History": "Remplacer l'historique par défaut",
"Replace the stock Job History sidebar with Comfy Queue": "Remplace la barre latérale d'historique par défaut par Comfy Queue",
"Hide Graph Button": "Masquer le bouton 'Graphe'",
"Hide floating 'Graph' (Workflow/Node Map) button": "Masque le bouton flottant 'Graphe' (carte des nœuds)",
"Hide Avatar": "Masquer l'avatar",
"Floating Canvas Controls": "Contrôles flottants du canevas",
"Classic Layout": "Disposition classique",
"Places the controls and the open workflow tabs on a single unified top bar": "Regroupe les contrôles et les onglets ouverts dans une barre supérieure unique",
"Comfy Layout": "Disposition compacte",
"Hide Junk": "Masquer les éléments inutiles",
"Default": "Par défaut",
"Dock": "Ancrer",
"Hide": "Masquer"
}
@@ -96,5 +96,10 @@
"Reset View": "אפס תצוגה",
"Settings": "הגדרות",
"Export As...": "ייצא כ...",
"Failed to load 3D format": "טעינת פורמט 3D נכשלה"
"Failed to load 3D format": "טעינת פורמט 3D נכשלה",
"Toggle Comfy Queue Sidebar": "הפעל/השבת סרגל צד Comfy Queue",
"Toggle Ignore Selected Node(s) in Queue": "התעלם/כלול צמתים נבחרים בתור",
"Renumber All Nodes (Execution Order)": "מספר מחדש את כל הצמתים (סדר ביצוע)",
"Set Selected Node ID...": "הגדר מזהה צומת נבחר...",
"Set Node ID...": "הגדר מזהה צומת..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "סף עמודות תור",
"Width Threshold for Queue Columns (px)": "רוחב בפיקסלים לחלוקת התור לעמודות מרובות",
"Show Pending Count Only": "הצג רק ספירת ממתינים",
"If disabled, each queued job will have a separate individual card": "אם מושבת, לכל משימה בתור יהיה כרטיס נפרד",
"Show Working Node Name": "הצג שם צומת פעיל",
"Shows the name of the node which is currently in the process": "מציג את שם הצומת המעובד כעת",
"Auto Clear Interrupted": "ניקוי אוטומטי של משימות שהופסקו",
"Auto-clear cancelled & failed jobs on new generation": "נקה אוטומטית משימות שבוטלו או נכשלו בעת יצירה חדשה",
"Permanent Delete on Disk Deletion": "מחיקה לצמיתות מהדיסק",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "מחק קבצים לצמיתות בעת מחיקה (עוקף את סל המיחזור)",
"Hide Tab: {tab}": "הסתר לשונית: {tab}",
"Assets": "נכסים",
"Nodes": "צמתים",
"Models": "מודלים",
"Workflows": "תהליכי עבודה",
"Apps": "יישומים",
"Templates": "תבניות",
"Override Stock History": "החלף היסטוריית ברירת מחדל",
"Replace the stock Job History sidebar with Comfy Queue": "מחליף את סרגל הצד של היסטוריית העבודות הרגילה ב-Comfy Queue",
"Hide Graph Button": "הסתר כפתור 'גרף'",
"Hide floating 'Graph' (Workflow/Node Map) button": "מסתיר את הכפתור הצף 'גרף' (מפת הצמתים)",
"Hide Avatar": "הסתר אווטאר",
"Floating Canvas Controls": "פקדי לוח צפים",
"Classic Layout": "פריסה קלאסית",
"Places the controls and the open workflow tabs on a single unified top bar": "ממקם את הפקדים ואת לשוניות תהליך העבודה הפתוחות בסרגל עליון מאוחד",
"Comfy Layout": "פריסה קומפקטית",
"Hide Junk": "הסתר פריטים מיותרים",
"Default": "ברירת מחדל",
"Dock": "הצמד",
"Hide": "הסתר"
}
@@ -96,5 +96,10 @@
"Reset View": "Ripristina visuale",
"Settings": "Impostazioni",
"Export As...": "Esporta come...",
"Failed to load 3D format": "Caricamento formato 3D non riuscito"
"Failed to load 3D format": "Caricamento formato 3D non riuscito",
"Toggle Comfy Queue Sidebar": "Attiva/Disattiva barra laterale Comfy Queue",
"Toggle Ignore Selected Node(s) in Queue": "Ignora/Attiva nodi selezionati nella coda",
"Renumber All Nodes (Execution Order)": "Rinumera tutti i nodi (ordine di esecuzione)",
"Set Selected Node ID...": "Imposta ID nodo selezionato...",
"Set Node ID...": "Imposta ID nodo..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "Soglia colonne coda",
"Width Threshold for Queue Columns (px)": "Larghezza in pixel per dividere la coda in più colonne",
"Show Pending Count Only": "Mostra solo conteggio in attesa",
"If disabled, each queued job will have a separate individual card": "Se disabilitato, ogni task in coda avrà una scheda separata",
"Show Working Node Name": "Mostra nome nodo in elaborazione",
"Shows the name of the node which is currently in the process": "Mostra il nome del nodo attualmente in elaborazione",
"Auto Clear Interrupted": "Pulizia automatica interrotti",
"Auto-clear cancelled & failed jobs on new generation": "Cancella automaticamente i task annullati e falliti a ogni nuova generazione",
"Permanent Delete on Disk Deletion": "Eliminazione definitiva dal disco",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "Elimina definitivamente i file al momento della cancellazione (senza passare dal cestino)",
"Hide Tab: {tab}": "Nascondi scheda: {tab}",
"Assets": "Risorse",
"Nodes": "Nodi",
"Models": "Modelli",
"Workflows": "Flussi di lavoro",
"Apps": "Applicazioni",
"Templates": "Modelli predefiniti",
"Override Stock History": "Sostituisci cronologia standard",
"Replace the stock Job History sidebar with Comfy Queue": "Sostituisce la barra laterale della cronologia standard con Comfy Queue",
"Hide Graph Button": "Nascondi pulsante 'Grafo'",
"Hide floating 'Graph' (Workflow/Node Map) button": "Nasconde il pulsante mobile 'Grafo' (mappa dei nodi)",
"Hide Avatar": "Nascondi avatar",
"Floating Canvas Controls": "Controlli mobili dell'area di lavoro",
"Classic Layout": "Layout classico",
"Places the controls and the open workflow tabs on a single unified top bar": "Posiziona i controlli e le schede aperte su una barra superiore unificata",
"Comfy Layout": "Layout compatto",
"Hide Junk": "Nascondi elementi inutili",
"Default": "Predefinito",
"Dock": "Aggancia",
"Hide": "Nascondi"
}
@@ -96,5 +96,10 @@
"Reset View": "視点をリセット",
"Settings": "設定",
"Export As...": "エクスポート...",
"Failed to load 3D format": "3D形式の読み込みに失敗しました"
"Failed to load 3D format": "3D形式の読み込みに失敗しました",
"Toggle Comfy Queue Sidebar": "Comfy Queue サイドバーの切り替え",
"Toggle Ignore Selected Node(s) in Queue": "キューで選択したノードを無視/解除",
"Renumber All Nodes (Execution Order)": "すべてのノード番号を再割り当て (実行順)",
"Set Selected Node ID...": "選択したノードIDを設定...",
"Set Node ID...": "ノードIDを設定..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "列分割の幅しきい値",
"Width Threshold for Queue Columns (px)": "キューを複数列に分割する幅のしきい値 (px)",
"Show Pending Count Only": "待機数のみ表示",
"If disabled, each queued job will have a separate individual card": "無効にすると、キュー内の各タスクが個別のカードとして表示されます",
"Show Working Node Name": "処理中のノード名を表示",
"Shows the name of the node which is currently in the process": "現在処理されているノードの名前を表示します",
"Auto Clear Interrupted": "中断タスクの自動クリア",
"Auto-clear cancelled & failed jobs on new generation": "新規生成時にキャンセルおよび失敗したタスクを自動的に消去します",
"Permanent Delete on Disk Deletion": "ディスクから完全消去",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "ディスクからの削除時にゴミ箱を経由せず完全に消去します",
"Hide Tab: {tab}": "タブを非表示: {tab}",
"Assets": "アセット",
"Nodes": "ノード",
"Models": "モデル",
"Workflows": "ワークフロー",
"Apps": "アプリ",
"Templates": "テンプレート",
"Override Stock History": "標準の履歴を置き換える",
"Replace the stock Job History sidebar with Comfy Queue": "標準の履歴サイドバーを Comfy Queue に置き換えます",
"Hide Graph Button": "「グラフ」ボタンを非表示",
"Hide floating 'Graph' (Workflow/Node Map) button": "フローティングの「グラフ」(ノードマップ) ボタンを非表示にします",
"Hide Avatar": "アバターを非表示",
"Floating Canvas Controls": "フローティングキャンバス操作",
"Classic Layout": "クラシックレイアウト",
"Places the controls and the open workflow tabs on a single unified top bar": "操作ボタンと開いているワークフロータブを統合された単一の上部バーに配置します",
"Comfy Layout": "コンパクトレイアウト",
"Hide Junk": "不要な項目を非表示",
"Default": "デフォルト",
"Dock": "ドック",
"Hide": "非表示"
}
@@ -96,5 +96,10 @@
"Reset View": "시점 초기화",
"Settings": "설정",
"Export As...": "내보내기...",
"Failed to load 3D format": "3D 형식 로드 실패"
"Failed to load 3D format": "3D 형식 로드 실패",
"Toggle Comfy Queue Sidebar": "Comfy Queue 사이드바 전환",
"Toggle Ignore Selected Node(s) in Queue": "대기열에서 선택한 노드 무시/해제",
"Renumber All Nodes (Execution Order)": "모든 노드 재번호 매기기 (실행 순서)",
"Set Selected Node ID...": "선택한 노드 ID 설정...",
"Set Node ID...": "노드 ID 설정..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "열 분할 너비 기준값",
"Width Threshold for Queue Columns (px)": "대기열을 여러 열로 나눌 너비 기준값 (px)",
"Show Pending Count Only": "대기 수량만 표시",
"If disabled, each queued job will have a separate individual card": "비활성화 시 대기열의 각 작업이 개별 카드로 표시됩니다",
"Show Working Node Name": "진행 중인 노드 이름 표시",
"Shows the name of the node which is currently in the process": "현재 처리 중인 노드의 이름을 표시합니다",
"Auto Clear Interrupted": "중단된 작업 자동 삭제",
"Auto-clear cancelled & failed jobs on new generation": "새 생성 시 취소되거나 실패한 작업을 자동으로 삭제합니다",
"Permanent Delete on Disk Deletion": "디스크에서 완전 삭제",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "디스크에서 삭제 시 휴지통을 거치지 않고 영구 삭제합니다",
"Hide Tab: {tab}": "탭 숨기기: {tab}",
"Assets": "에셋",
"Nodes": "노드",
"Models": "모델",
"Workflows": "워크플로",
"Apps": "앱",
"Templates": "템플릿",
"Override Stock History": "기본 작업 기록 대체",
"Replace the stock Job History sidebar with Comfy Queue": "기본 작업 기록 사이드바를 Comfy Queue로 대체합니다",
"Hide Graph Button": "'그래프' 버튼 숨기기",
"Hide floating 'Graph' (Workflow/Node Map) button": "플로팅 '그래프'(워크플로 맵) 버튼을 숨깁니다",
"Hide Avatar": "아바타 숨기기",
"Floating Canvas Controls": "플로팅 캔버스 제어 도구",
"Classic Layout": "클래식 레이아웃",
"Places the controls and the open workflow tabs on a single unified top bar": "제어 도구와 열린 워크플로 탭을 하나의 상단 바로 통합합니다",
"Comfy Layout": "컴팩트 레이아웃",
"Hide Junk": "불필요한 항목 숨기기",
"Default": "기본값",
"Dock": "도킹",
"Hide": "숨기기"
}
@@ -96,5 +96,10 @@
"Reset View": "Redefinir visão",
"Settings": "Configurações",
"Export As...": "Exportar como...",
"Failed to load 3D format": "Falha ao carregar formato 3D"
"Failed to load 3D format": "Falha ao carregar formato 3D",
"Toggle Comfy Queue Sidebar": "Alternar barra lateral Comfy Queue",
"Toggle Ignore Selected Node(s) in Queue": "Ignorar/Incluir nós selecionados na fila",
"Renumber All Nodes (Execution Order)": "Renumerar todos os nós (ordem de execução)",
"Set Selected Node ID...": "Definir ID do nó selecionado...",
"Set Node ID...": "Definir ID do nó..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "Limite de colunas da fila",
"Width Threshold for Queue Columns (px)": "Largura em pixels para dividir a fila em várias colunas",
"Show Pending Count Only": "Mostrar apenas contagem pendente",
"If disabled, each queued job will have a separate individual card": "Se desativado, cada tarefa na fila terá um cartão individual",
"Show Working Node Name": "Mostrar nome do nó em execução",
"Shows the name of the node which is currently in the process": "Mostra o nome do nó que está sendo processado no momento",
"Auto Clear Interrupted": "Limpeza automática de interrompidos",
"Auto-clear cancelled & failed jobs on new generation": "Limpar automaticamente tarefas canceladas e com falha ao iniciar nova geração",
"Permanent Delete on Disk Deletion": "Exclusão permanente do disco",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "Excluir arquivos permanentemente (sem passar pela Lixeira)",
"Hide Tab: {tab}": "Ocultar aba: {tab}",
"Assets": "Recursos",
"Nodes": "Nós",
"Models": "Modelos",
"Workflows": "Fluxos de trabalho",
"Apps": "Aplicativos",
"Templates": "Modelos prévios",
"Override Stock History": "Substituir histórico padrão",
"Replace the stock Job History sidebar with Comfy Queue": "Substitui a barra lateral de histórico padrão pelo Comfy Queue",
"Hide Graph Button": "Ocultar botão 'Grafo'",
"Hide floating 'Graph' (Workflow/Node Map) button": "Oculta o botão flutuante 'Grafo' (mapa de nós)",
"Hide Avatar": "Ocultar avatar",
"Floating Canvas Controls": "Controles flutuantes da tela",
"Classic Layout": "Layout clássico",
"Places the controls and the open workflow tabs on a single unified top bar": "Posiciona os controles e as abas abertas em uma barra superior unificada",
"Comfy Layout": "Layout compacto",
"Hide Junk": "Ocultar itens desnecessários",
"Default": "Padrão",
"Dock": "Fixar",
"Hide": "Ocultar"
}
@@ -33,7 +33,7 @@
"Remove from batch (Hold Ctrl to delete file from disk)": "Удалить из пакета (Ctrl для удаления с диска)",
"Click again to confirm removing from batch": "Нажмите еще раз для удаления из пакета",
"Delete node output (Hold Ctrl to delete file from disk)": "Удалить результат ноды (Ctrl для удаления с диска)",
"Ctrl+Click again to delete this node's files from DISK / TRASH": "Ctrl+Клик еще раз для удаления с ДИСКА",
"Ctrl+Click again to delete this node's files from DISK / TRASH": "Ctrl+Клик еще раз для удаления файлов с ДИСКА",
"Click again to confirm removing output": "Нажмите еще раз для подтверждения удаления",
"Moved file to Recycle Bin.": "Файл перемещен в корзину.",
"Permanently deleted file.": "Файл удален навсегда.",
@@ -59,12 +59,12 @@
"Please enter a valid positive number.": "Пожалуйста, введите положительное число.",
"Node ID #{id} is already used by \"{name}\". Swap their IDs?": "ID ноды #{id} уже используется нодой \"{name}\". Поменять их местами?",
"Assigned node ID #{id} to \"{name}\".": "Ноде \"{name}\" присвоен ID #{id}.",
"Failed to set node ID: ": "Не удалось установить ID ноды: " ,
"Failed to set node ID: ": "Не удалось установить ID ноды: ",
"Video playback. Press Esc to close.": "Воспроизведение видео. Нажмите Esc для закрытия.",
"Click image to zoom (100%/Fit) | Shift+Click another card to compare.": "Клик для масштабирования (100%/Вписать) | Shift+Клик по другой карточке для сравнения.",
"Toggle Loop": "Зациклить",
"Aspect ratio mismatch ({wA}x{hA} vs {wB}x{hB}). Images must have matching proportions to compare.": "Несовпадение пропорций ({wA}x{hA} против {wB}x{hB}). Изображения должны иметь одинаковые пропорции для сравнения.",
"Click image to zoom | Drag image to compare slider | Esc to close.": "Клик для масштабирования | Перетащите для сдвига слайдера | Esc для закрытия.",
"Click image to zoom | Drag image to compare slider | Esc to close.": "Клик для масштабирования | Перетащите слайдер для сравнения | Esc для закрытия.",
"Copy": "Копировать",
"Copied!": "Скопировано!",
"Text Output": "Текстовый результат",
@@ -96,5 +96,10 @@
"Reset View": "Сброс вида",
"Settings": "Настройки",
"Export As...": "Экспорт...",
"Failed to load 3D format": "Не удалось загрузить 3D-формат"
"Failed to load 3D format": "Не удалось загрузить 3D-формат",
"Toggle Comfy Queue Sidebar": "Переключить боковую панель Comfy Queue",
"Toggle Ignore Selected Node(s) in Queue": "Игнорировать выбранные ноды в очереди",
"Renumber All Nodes (Execution Order)": "Перенумеровать ноды (порядок выполнения)",
"Set Selected Node ID...": "Задать ID выбранной ноды...",
"Set Node ID...": "Задать ID ноды..."
}
+33
View File
@@ -0,0 +1,33 @@
{
"Grid Columns Threshold": "Порог колонок очереди",
"Width Threshold for Queue Columns (px)": "Ширина в пикселях для разделения очереди на несколько колонок",
"Show Pending Count Only": "Только количество в очереди",
"If disabled, each queued job will have a separate individual card": "Если выключено, каждая задача в очереди будет отдельной карточкой",
"Show Working Node Name": "Имя выполняющейся ноды",
"Shows the name of the node which is currently in the process": "Показывать имя ноды, выполняющейся в данный момент",
"Auto Clear Interrupted": "Автоочистка отменённых",
"Auto-clear cancelled & failed jobs on new generation": "Автоматически очищать отменённые и неудачные задачи при новой генерации",
"Permanent Delete on Disk Deletion": "Безвозвратное удаление с диска",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "Удалять файлы навсегда при удалении с диска (минуя корзину)",
"Hide Tab: {tab}": "Скрыть вкладку: {tab}",
"Assets": "Ресурсы",
"Nodes": "Ноды",
"Models": "Модели",
"Workflows": "Рабочие процессы",
"Apps": "Приложения",
"Templates": "Шаблоны",
"Override Stock History": "Замена стандартной истории",
"Replace the stock Job History sidebar with Comfy Queue": "Заменить стандартную панель истории на Comfy Queue",
"Hide Graph Button": "Скрыть кнопку 'Граф'",
"Hide floating 'Graph' (Workflow/Node Map) button": "Скрыть плавающую кнопку 'Граф' (карта нод)",
"Hide Avatar": "Скрыть аватар",
"Floating Canvas Controls": "Плавающие элементы управления",
"Classic Layout": "Классический интерфейс",
"Places the controls and the open workflow tabs on a single unified top bar": "Разместить элементы управления и открытые вкладки на единой верхней панели",
"Comfy Layout": "Компактный интерфейс",
"Classic Layout": "Компактный интерфейс",
"Hide Junk": "Скрыть лишнее",
"Default": "По умолчанию",
"Dock": "Закрепить",
"Hide": "Скрыть"
}
@@ -96,5 +96,10 @@
"Reset View": "Görünümü sıfırla",
"Settings": "Ayarlar",
"Export As...": "Farklı dışa aktar...",
"Failed to load 3D format": "3D formatı yüklenemedi"
"Failed to load 3D format": "3D formatı yüklenemedi",
"Toggle Comfy Queue Sidebar": "Comfy Queue Kenar Çubuğunu Aç/Kapat",
"Toggle Ignore Selected Node(s) in Queue": "Kuyrukta Seçili Düğümleri Yoksay/Etkinleştir",
"Renumber All Nodes (Execution Order)": "Tüm Düğümleri Yeniden Numaralandır (Çalıştırma Sırası)",
"Set Selected Node ID...": "Seçili Düğüm Kimliğini Belirle...",
"Set Node ID...": "Düğüm Kimliğini Belirle..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "Kuyruk sütun genişliği eşiği",
"Width Threshold for Queue Columns (px)": "Kuyruğu birden fazla sütuna bölmek için piksel genişliği",
"Show Pending Count Only": "Yalnızca bekleyen sayısını göster",
"If disabled, each queued job will have a separate individual card": "Devre dışı bırakılırsa kuyruktaki her görev ayrı bir karta sahip olur",
"Show Working Node Name": "Çalışan düğüm adını göster",
"Shows the name of the node which is currently in the process": "Şu anda işlenmekte olan düğümün adını gösterir",
"Auto Clear Interrupted": "Kesilenleri otomatik temizle",
"Auto-clear cancelled & failed jobs on new generation": "Yeni üretimde iptal edilen ve başarısız olan görevleri otomatik olarak temizle",
"Permanent Delete on Disk Deletion": "Diskten kalıcı silme",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "Diskten silerken dosyaları kalıcı olarak sil (Geri Dönüşüm Kutusu'nu atlar)",
"Hide Tab: {tab}": "Sekmeyi gizle: {tab}",
"Assets": "Varlıklar",
"Nodes": "Düğümler",
"Models": "Modeller",
"Workflows": "İş Akışları",
"Apps": "Uygulamalar",
"Templates": "Şablonlar",
"Override Stock History": "Varsayılan geçmişi değiştir",
"Replace the stock Job History sidebar with Comfy Queue": "Varsayılan Görev Geçmişi kenar çubuğunu Comfy Queue ile değiştirir",
"Hide Graph Button": "'Grafik' düğmesini gizle",
"Hide floating 'Graph' (Workflow/Node Map) button": "Kayan 'Grafik' (İş akışı haritası) düğmesini gizler",
"Hide Avatar": "Avatarı gizle",
"Floating Canvas Controls": "Kayan tuval kontrolleri",
"Classic Layout": "Klasik düzen",
"Places the controls and the open workflow tabs on a single unified top bar": "Kontrolleri ve açık iş akışı sekmelerini tek bir birleşik üst çubukta toplar",
"Comfy Layout": "Kompakt Düzen",
"Hide Junk": "Gereksiz Öğeleri Gizle",
"Default": "Varsayılan",
"Dock": "Sabitle",
"Hide": "Gizle"
}
@@ -96,5 +96,10 @@
"Reset View": "重設視角",
"Settings": "設定",
"Export As...": "匯出為...",
"Failed to load 3D format": "載入 3D 格式失敗"
"Failed to load 3D format": "載入 3D 格式失敗",
"Toggle Comfy Queue Sidebar": "切換 Comfy Queue 側邊欄",
"Toggle Ignore Selected Node(s) in Queue": "在隊列中忽略選取節點",
"Renumber All Nodes (Execution Order)": "重新編號所有節點 (按執行順序)",
"Set Selected Node ID...": "設定選取節點 ID...",
"Set Node ID...": "設定節點 ID..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "隊列欄寬閾值",
"Width Threshold for Queue Columns (px)": "隊列顯示多欄的寬度閾值 (像素)",
"Show Pending Count Only": "僅顯示等待數量",
"If disabled, each queued job will have a separate individual card": "若停用,每個排隊任務將顯示為獨立卡片",
"Show Working Node Name": "顯示執行中節點名稱",
"Shows the name of the node which is currently in the process": "顯示目前正在處理的節點名稱",
"Auto Clear Interrupted": "自動清除中斷任務",
"Auto-clear cancelled & failed jobs on new generation": "新生成時自動清除已取消與失敗的任務",
"Permanent Delete on Disk Deletion": "從磁碟永久刪除",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "從磁碟刪除時永久刪除檔案 (略過資源回收筒)",
"Hide Tab: {tab}": "隱藏標籤頁: {tab}",
"Assets": "資產",
"Nodes": "節點",
"Models": "模型",
"Workflows": "工作流",
"Apps": "應用",
"Templates": "範本",
"Override Stock History": "取代原生任務歷史",
"Replace the stock Job History sidebar with Comfy Queue": "使用 Comfy Queue 取代原生任務歷史側邊欄",
"Hide Graph Button": "隱藏「圖」按鈕",
"Hide floating 'Graph' (Workflow/Node Map) button": "隱藏懸浮的「圖」(工作流/節點圖) 按鈕",
"Hide Avatar": "隱藏頭像",
"Floating Canvas Controls": "懸浮畫布控制項",
"Classic Layout": "經典版面配置",
"Places the controls and the open workflow tabs on a single unified top bar": "將控制項與已開啟的工作流標籤頁整合於統一的頂端列",
"Comfy Layout": "緊湊版面配置",
"Hide Junk": "隱藏雜項",
"Default": "預設",
"Dock": "停靠",
"Hide": "隱藏"
}
@@ -96,5 +96,10 @@
"Reset View": "重置视角",
"Settings": "设置",
"Export As...": "导出为...",
"Failed to load 3D format": "加载 3D 格式失败"
"Failed to load 3D format": "加载 3D 格式失败",
"Toggle Comfy Queue Sidebar": "切换 Comfy Queue 侧边栏",
"Toggle Ignore Selected Node(s) in Queue": "在队列中忽略所选节点",
"Renumber All Nodes (Execution Order)": "重新编号所有节点 (按执行顺序)",
"Set Selected Node ID...": "设置所选节点 ID...",
"Set Node ID...": "设置节点 ID..."
}
+32
View File
@@ -0,0 +1,32 @@
{
"Grid Columns Threshold": "队列列宽阈值",
"Width Threshold for Queue Columns (px)": "队列显示多列的宽度阈值 (像素)",
"Show Pending Count Only": "仅显示等待数量",
"If disabled, each queued job will have a separate individual card": "如果禁用,每个排队的任务将拥有单独的卡片",
"Show Working Node Name": "显示正在执行的节点名称",
"Shows the name of the node which is currently in the process": "显示当前正在处理的节点名称",
"Auto Clear Interrupted": "自动清除中断任务",
"Auto-clear cancelled & failed jobs on new generation": "新生成时自动清除已取消和失败的任务",
"Permanent Delete on Disk Deletion": "从磁盘永久删除",
"Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)": "从磁盘删除时永久删除文件 (绕过回收站)",
"Hide Tab: {tab}": "隐藏标签页: {tab}",
"Assets": "资产",
"Nodes": "节点",
"Models": "模型",
"Workflows": "工作流",
"Apps": "应用",
"Templates": "模板",
"Override Stock History": "替换原生任务历史",
"Replace the stock Job History sidebar with Comfy Queue": "使用 Comfy Queue 替换原生任务历史侧边栏",
"Hide Graph Button": "隐藏'图'按钮",
"Hide floating 'Graph' (Workflow/Node Map) button": "隐藏悬浮的'图' (工作流/节点图) 按钮",
"Hide Avatar": "隐藏头像",
"Floating Canvas Controls": "悬浮画布控件",
"Classic Layout": "经典布局",
"Places the controls and the open workflow tabs on a single unified top bar": "将控件与已打开的工作流标签页整合在统一的顶部栏",
"Comfy Layout": "紧凑布局",
"Hide Junk": "隐藏杂项",
"Default": "默认",
"Dock": "停靠",
"Hide": "隐藏"
}
+95 -20
View File
@@ -3,7 +3,7 @@ import { SettingIds } from "./core/constants.js";
import { injectStyles } from "./utils/styles.js";
import { setupDragAndDrop } from "./utils/dragdrop.js";
import { renumberNodesTopologically, setManualNodeId } from "./utils/nodeRenumber.js";
import { findOurSidebarButton } from "./comfy/adapter.js";
import { findOurSidebarButton, applySidebarOverride } from "./comfy/adapter.js";
import { syncAllNodeBadges, setupVueNodeObserver, toggleIgnoreActiveNode } from "./comfy/nodes.js";
import { applyClassicLayout, setupPropertiesPanelToggleFix, syncClassicLayout, syncStockHistoryAndProgressSettings, destroyLayoutFix } from "./comfy/layout.js";
import { initSessionAndHistory, syncQueue } from "./queue/queueService.js";
@@ -20,34 +20,35 @@ let cleanupFns = [];
function registerSettings() {
app.ui.settings.addSetting({
id: SettingIds.GRID_COLUMNS_THRESHOLD,
name: "Width Threshold for Queue Columns (px)",
name: t("Width Threshold for Queue Columns (px)"),
type: "number",
defaultValue: 350
});
app.ui.settings.addSetting({
id: SettingIds.SHOW_PENDING_COUNT_ONLY,
name: "If disabled, each queued job will have a separate individual card",
name: t("If disabled, each queued job will have a separate individual card"),
type: "boolean",
defaultValue: true
});
app.ui.settings.addSetting({
id: SettingIds.SHOW_WORKING_NODE_NAME,
name: "Shows the name of the node which is currently in the process",
name: t("Shows the name of the node which is currently in the process"),
type: "boolean",
defaultValue: true
});
app.ui.settings.addSetting({
id: SettingIds.AUTO_CLEAR_INTERRUPTED,
name: "Auto-clear cancelled & failed jobs on new generation",
name: t("Auto-clear cancelled & failed jobs on new generation"),
type: "boolean",
defaultValue: false
});
app.ui.settings.addSetting({
id: "Comfy Sidebar.Permanent Delete on Disk Deletion",
name: "Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)",
name: t("Permanently delete files on disk deletion (bypasses Recycle Bin / Trash)"),
type: "boolean",
defaultValue: false
});
@@ -56,7 +57,7 @@ function registerSettings() {
sidebarTabs.forEach(tab => {
app.ui.settings.addSetting({
id: `Comfy Sidebar.Hide Junk.${tab}`,
name: `Hide Tab: ${tab}`,
name: t("Hide Tab: {tab}", { tab: t(tab) }),
type: "boolean",
defaultValue: false,
onChange: () => {
@@ -67,20 +68,24 @@ function registerSettings() {
app.ui.settings.addSetting({
id: SettingIds.OVERRIDE_STOCK_HISTORY,
name: "Replace the stock Job History sidebar with Comfy Queue",
name: t("Replace the stock Job History sidebar with Comfy Queue"),
type: "boolean",
defaultValue: false,
onChange: (value) => {
if (isInitialized && app.ui?.settings) {
syncStockHistoryAndProgressSettings(value);
}
setTimeout(() => { syncClassicLayout(); }, 0);
applySidebarOverride();
setTimeout(() => {
applySidebarOverride();
syncClassicLayout();
}, 50);
}
});
app.ui.settings.addSetting({
id: SettingIds.HIDE_GRAPH_BUTTON,
name: "Hide floating 'Graph' (Workflow/Node Map) button",
name: t("Hide floating 'Graph' (Workflow/Node Map) button"),
type: "boolean",
defaultValue: false,
onChange: () => {
@@ -90,7 +95,7 @@ function registerSettings() {
app.ui.settings.addSetting({
id: "Comfy Sidebar.Hide Junk.Avatar",
name: "Hide Avatar",
name: t("Hide Avatar"),
type: "boolean",
defaultValue: false,
onChange: () => {
@@ -100,7 +105,7 @@ function registerSettings() {
app.ui.settings.addSetting({
id: "Comfy Sidebar.Hide Junk.Floating Canvas Controls",
name: "Floating Canvas Controls",
name: t("Floating Canvas Controls"),
type: "combo",
options: ["Default", "Dock", "Hide"],
defaultValue: "Default",
@@ -111,7 +116,7 @@ function registerSettings() {
app.ui.settings.addSetting({
id: SettingIds.CLASSIC_LAYOUT,
name: "Places the controls and the open workflow tabs on a single unified top bar",
name: t("Places the controls and the open workflow tabs on a single unified top bar"),
type: "boolean",
defaultValue: false,
onChange: (value) => {
@@ -122,6 +127,70 @@ function registerSettings() {
});
}
function setupSettingsDialogObserver() {
const targetTexts = [
"Grid Columns Threshold",
"Show Pending Count Only",
"Show Working Node Name",
"Auto Clear Interrupted",
"Permanent Delete on Disk Deletion",
"Hide Junk",
"Override Stock History",
"Hide Graph Button",
"Hide Avatar",
"Floating Canvas Controls",
"Classic Layout",
"Comfy Layout",
"Default",
"Dock",
"Hide"
];
const translateDOM = (rootEl) => {
if (!rootEl) return;
const walker = document.createTreeWalker(
rootEl,
NodeFilter.SHOW_TEXT,
{
acceptNode(node) {
const text = node.nodeValue?.trim();
return targetTexts.includes(text) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
}
);
const nodesToTranslate = [];
while (walker.nextNode()) {
nodesToTranslate.push(walker.currentNode);
}
for (const textNode of nodesToTranslate) {
const raw = textNode.nodeValue.trim();
const translated = t(raw);
if (translated && translated !== raw) {
textNode.nodeValue = textNode.nodeValue.replace(raw, translated);
}
}
};
const observer = new MutationObserver(() => {
// 1. Translate headers and selected value inside the Settings dialog
const dialog = document.querySelector('.p-dialog, .comfy-settings-dialog, [role="dialog"]');
if (dialog) {
translateDOM(dialog);
}
// 2. Translate dropdown popup menu items (floated directly on document.body)
const overlay = document.querySelector('.p-select-overlay, .p-dropdown-panel, .p-select-list, [data-pc-name="selectoverlay"]');
if (overlay) {
translateDOM(overlay);
}
});
observer.observe(document.body, { childList: true, subtree: true });
return () => observer.disconnect();
}
function setupKeydownShortcuts() {
activeKeydownHandler = (e) => {
const activeEl = document.activeElement;
@@ -258,14 +327,15 @@ function setupWorkflowTabsPositionWatcher() {
app.registerExtension({
name: "ComfySidebar.ClassicRestore",
init() {
async init() {
await initI18n();
registerSettings();
},
commands: [
{
id: "ComfySidebar.ToggleSidebar",
label: "Toggle Comfy Queue Sidebar",
label: t("Toggle Comfy Queue Sidebar"),
function: () => {
const ourBtn = findOurSidebarButton();
if (ourBtn) ourBtn.click();
@@ -273,17 +343,17 @@ app.registerExtension({
},
{
id: "ComfySidebar.ToggleIgnoreNode",
label: "Toggle Ignore Selected Node(s) in Queue",
label: t("Toggle Ignore Selected Node(s) in Queue"),
function: () => toggleIgnoreActiveNode(() => renderSidebar())
},
{
id: "ComfySidebar.RenumberNodes",
label: "Renumber All Nodes (Execution Order)",
label: t("Renumber All Nodes (Execution Order)"),
function: () => renumberNodesTopologically()
},
{
id: "ComfySidebar.SetNodeId",
label: "Set Selected Node ID...",
label: t("Set Selected Node ID..."),
function: () => setManualNodeId()
}
],
@@ -292,7 +362,7 @@ app.registerExtension({
return [
null,
{
content: "Set Node ID...",
content: t("Set Node ID..."),
callback: () => setManualNodeId(node)
}
];
@@ -302,7 +372,7 @@ app.registerExtension({
return [
null,
{
content: "Renumber All Nodes (Execution Order)",
content: t("Renumber All Nodes (Execution Order)"),
callback: () => renumberNodesTopologically()
}
];
@@ -355,6 +425,7 @@ app.registerExtension({
cleanupFns.push(setupWorkflowTabsPositionWatcher());
cleanupFns.push(setupDragAndDrop());
cleanupFns.push(setupVueNodeObserver());
cleanupFns.push(setupSettingsDialogObserver());
// Setup execution tracker with targeted fast-path update for active card
cleanupFns.push(setupExecutionTracker((pid, progress, nodeName) => {
@@ -383,6 +454,10 @@ app.registerExtension({
type: "custom",
render: (el) => { el.appendChild(sidebarContainer); }
});
requestAnimationFrame(() => {
applySidebarOverride();
});
},
destroy() {
+130 -72
View File
@@ -56,6 +56,106 @@ export function showNotification(message, type = "success", title = null) {
}, 3500);
}
function remapWidgetValue(val, oldToNew) {
if (typeof val === "string") {
const match = val.match(/^\$([^-]+)-(.*)$/);
if (match) {
const mapped = oldToNew.get(match[1]) ?? oldToNew.get(Number(match[1]));
if (mapped != null) {
return `$${mapped}-${match[2]}`;
}
}
}
return val;
}
function applyIdMappingToGraph(oldToNew) {
if (!app.graph) return;
const graphData = app.graph.serialize();
if (!graphData) return;
// 1. Remap node IDs and internal widget references
if (Array.isArray(graphData.nodes)) {
for (const node of graphData.nodes) {
const oldId = node.id;
const newId = oldToNew.get(oldId) ?? oldToNew.get(String(oldId)) ?? oldToNew.get(Number(oldId));
if (newId != null) {
node.id = newId;
}
if (Array.isArray(node.widgets_values)) {
node.widgets_values = node.widgets_values.map(val => remapWidgetValue(val, oldToNew));
} else if (node.widgets_values && typeof node.widgets_values === "object") {
for (const key of Object.keys(node.widgets_values)) {
node.widgets_values[key] = remapWidgetValue(node.widgets_values[key], oldToNew);
}
}
}
}
// 2. Remap link endpoints (both array tuples and object representations)
const linkList = Array.isArray(graphData.links)
? graphData.links
: (graphData.links && typeof graphData.links === "object" ? Object.values(graphData.links) : []);
for (const link of linkList) {
if (!link) continue;
if (Array.isArray(link)) {
// [id, origin_id, origin_slot, target_id, target_slot, type]
const originId = link[1];
const targetId = link[3];
const newOrigin = oldToNew.get(originId) ?? oldToNew.get(String(originId)) ?? oldToNew.get(Number(originId));
if (newOrigin != null) link[1] = newOrigin;
const newTarget = oldToNew.get(targetId) ?? oldToNew.get(String(targetId)) ?? oldToNew.get(Number(targetId));
if (newTarget != null) link[3] = newTarget;
} else if (typeof link === "object") {
const originId = link.origin_id;
const targetId = link.target_id;
const newOrigin = oldToNew.get(originId) ?? oldToNew.get(String(originId)) ?? oldToNew.get(Number(originId));
if (newOrigin != null) link.origin_id = newOrigin;
const newTarget = oldToNew.get(targetId) ?? oldToNew.get(String(targetId)) ?? oldToNew.get(Number(targetId));
if (newTarget != null) link.target_id = newTarget;
}
}
// 3. Remap group membership if defined
if (Array.isArray(graphData.groups)) {
for (const group of graphData.groups) {
if (Array.isArray(group.nodes)) {
group.nodes = group.nodes.map(id => oldToNew.get(id) ?? oldToNew.get(String(id)) ?? oldToNew.get(Number(id)) ?? id);
}
}
}
// 4. Update graph last_node_id counter
let maxId = 0;
for (const val of oldToNew.values()) {
const num = Number(val);
if (!isNaN(num) && num > maxId) maxId = num;
}
graphData.last_node_id = Math.max(graphData.last_node_id || 0, maxId, graphData.nodes?.length || 0);
// 5. Preserve canvas pan/zoom coordinates so the screen doesn't jump
const currentScale = app.canvas?.ds?.scale;
const currentOffset = app.canvas?.ds?.offset ? [...app.canvas.ds.offset] : null;
app.graph.configure(graphData);
if (app.canvas?.ds && currentScale != null && currentOffset != null) {
app.canvas.ds.scale = currentScale;
app.canvas.ds.offset[0] = currentOffset[0];
app.canvas.ds.offset[1] = currentOffset[1];
}
if (typeof app.graph.afterChange === "function") {
app.graph.afterChange();
}
app.graph.setDirtyCanvas(true, true);
if (app.canvas?.draw) {
app.canvas.draw(true, true);
}
syncAllNodeBadges();
}
export async function renumberNodesTopologically() {
if (!app.graph || !app.graph._nodes || app.graph._nodes.length === 0) {
showNotification(t("No nodes found on canvas to renumber."), "warn");
@@ -88,55 +188,21 @@ export async function renumberNodesTopologically() {
return posYA - posYB;
});
const isStringId = typeof sortedNodes[0]?.id === "string";
const oldToNew = new Map();
sortedNodes.forEach((node, index) => {
const newId = index + 1;
const newId = isStringId ? String(index + 1) : (index + 1);
oldToNew.set(node.id, newId);
oldToNew.set(String(node.id), newId);
oldToNew.set(Number(node.id), newId);
});
// 1. Remap node IDs in-place on app.graph
app.graph._nodes_by_id = {};
sortedNodes.forEach(node => {
const oldId = node.id;
const newId = oldToNew.get(oldId);
if (newId != null) {
// Update internal widget IDs like "$2034-0" (PreviewBridge)
if (Array.isArray(node.widgets)) {
for (const w of node.widgets) {
if (typeof w.value === "string" && w.value.startsWith(`$${oldId}-`)) {
w.value = w.value.replace(`$${oldId}-`, `$${newId}-`);
}
}
}
node.id = newId;
app.graph._nodes_by_id[newId] = node;
}
});
// 2. Remap links
if (app.graph.links) {
const links = app.graph.links instanceof Map
? Array.from(app.graph.links.values())
: Object.values(app.graph.links);
links.forEach(link => {
if (!link) return;
if (oldToNew.has(link.origin_id)) link.origin_id = oldToNew.get(link.origin_id);
if (oldToNew.has(link.target_id)) link.target_id = oldToNew.get(link.target_id);
});
}
app.graph.last_node_id = sortedNodes.length;
app.graph.setDirtyCanvas(true, true);
if (app.canvas?.draw) {
app.canvas.draw(true, true);
}
syncAllNodeBadges();
applyIdMappingToGraph(oldToNew);
const lastNode = sortedNodes[sortedNodes.length - 1];
const lastNodeName = lastNode ? (lastNode.title || lastNode.type) : "SaveImage";
showNotification(t("Renumbered {count} nodes by execution order. Latest node: #{id} ({name}).", { count: sortedNodes.length, id: lastNode ? lastNode.id : "", name: lastNodeName }), "success");
const lastNewId = lastNode ? oldToNew.get(lastNode.id) : "";
showNotification(t("Renumbered {count} nodes by execution order. Latest node: #{id} ({name}).", { count: sortedNodes.length, id: lastNewId, name: lastNodeName }), "success");
} catch (err) {
console.error("ComfySidebar: Failed to renumber nodes", err);
showNotification(t("Failed to renumber nodes: ") + err.message, "error");
@@ -159,55 +225,47 @@ export async function setManualNodeId(targetNode = null) {
const input = prompt(t("Enter new ID for node #{id} ({name}):", { id: currentId, name: node.title || node.type }), String(currentId));
if (!input) return;
const newId = parseInt(input.trim(), 10);
if (isNaN(newId) || newId <= 0) {
const newIdNum = parseInt(input.trim(), 10);
if (isNaN(newIdNum) || newIdNum <= 0) {
showNotification(t("Please enter a valid positive number."), "error");
return;
}
if (newId === currentId) return;
if (String(newIdNum) === String(currentId)) return;
try {
const existingNode = app.graph.getNodeById ? app.graph.getNodeById(newId) : null;
const existingNode = app.graph.getNodeById ? (app.graph.getNodeById(newIdNum) || app.graph.getNodeById(String(newIdNum))) : null;
if (existingNode && existingNode !== node) {
const confirmSwap = confirm(t("Node ID #{id} is already used by \"{name}\". Swap their IDs?", { id: newId, name: existingNode.title || existingNode.type }));
const confirmSwap = confirm(t("Node ID #{id} is already used by \"{name}\". Swap their IDs?", { id: newIdNum, name: existingNode.title || existingNode.type }));
if (!confirmSwap) return;
}
const isStringId = typeof currentId === "string";
const newId = isStringId ? String(newIdNum) : newIdNum;
const oldId = currentId;
const oldToNew = new Map();
if (existingNode && existingNode !== node) {
oldToNew.set(currentId, newId);
oldToNew.set(newId, currentId);
existingNode.id = currentId;
const existingId = existingNode.id;
oldToNew.set(oldId, newId);
oldToNew.set(String(oldId), newId);
oldToNew.set(Number(oldId), newId);
oldToNew.set(existingId, oldId);
oldToNew.set(String(existingId), oldId);
oldToNew.set(Number(existingId), oldId);
} else {
oldToNew.set(currentId, newId);
}
node.id = newId;
// Rebuild lookup map
app.graph._nodes_by_id = {};
for (const n of app.graph._nodes) {
app.graph._nodes_by_id[n.id] = n;
oldToNew.set(oldId, newId);
oldToNew.set(String(oldId), newId);
oldToNew.set(Number(oldId), newId);
}
// Remap links
if (app.graph.links) {
const links = app.graph.links instanceof Map
? Array.from(app.graph.links.values())
: Object.values(app.graph.links);
applyIdMappingToGraph(oldToNew);
links.forEach(link => {
if (!link) return;
if (oldToNew.has(link.origin_id)) link.origin_id = oldToNew.get(link.origin_id);
if (oldToNew.has(link.target_id)) link.target_id = oldToNew.get(link.target_id);
});
const updatedNode = app.graph.getNodeById ? (app.graph.getNodeById(newId) || app.graph.getNodeById(String(newId))) : null;
if (updatedNode && app.canvas?.selectNode) {
app.canvas.selectNode(updatedNode);
}
app.graph.last_node_id = Math.max(app.graph.last_node_id || 0, newId, app.graph._nodes.length);
app.graph.setDirtyCanvas(true, true);
if (app.canvas?.draw) {
app.canvas.draw(true, true);
}
syncAllNodeBadges();
showNotification(t("Assigned node ID #{id} to \"{name}\".", { id: newId, name: node.title || node.type }), "success");
} catch (err) {
console.error("ComfySidebar: Failed to set node ID", err);
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfy-sidebar"
description = "A compact and powerful layout overhaul for ComfyUI that makes generation results easier to browse, preview, compare, and manage."
version = "1.7.2"
version = "1.7.4"
license = { file = "LICENSE" }
dependencies = [
"send2trash>=1.8.0",