Files
SencneS eb293c53f3 Updates
Added realtime updates to excel file using shadowcopy because of locked files.
2026-06-14 15:22:43 -04:00

198 lines
9.2 KiB
JavaScript

import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "ExcelStylesLoader.DynamicDropdown",
async beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
if (nodeData.name !== "ExcelStylesLoader") return;
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
origOnNodeCreated?.apply(this, arguments);
const tabWidget = this.widgets.find(w => w.name === "tab_name");
const styleWidget = this.widgets.find(w => w.name === "style_name");
if (!tabWidget || !styleWidget) return;
// ── State ──────────────────────────────────────────────────────
const _tabCache = new Map(); // tabName -> [styleNames]
let _currentTab = null;
let _isRefreshing = false; // lock to prevent concurrent fetches
// ── Helpers ────────────────────────────────────────────────────
const applyStyleOptions = (styles, preserveValue = false) => {
styleWidget.options.values = styles;
if (!preserveValue || !styles.includes(styleWidget.value)) {
styleWidget.value = styles.length > 0 ? styles[0] : "";
}
app.canvas.setDirty(true, true);
};
const fetchJson = async (url) => {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
};
// ── Core: Load styles for a tab ──────────────────────────────
const loadStyles = async (tabName, {preserveValue = false, force = false} = {}) => {
if (!tabName || tabName === "No Tabs Found") {
applyStyleOptions([]);
return;
}
const cacheKey = `${tabName}|${force ? Date.now() : 'cached'}`;
const useCache = !force && _tabCache.has(tabName);
if (useCache) {
applyStyleOptions(_tabCache.get(tabName), preserveValue);
return;
}
try {
const forceParam = force ? "&force=1" : "";
const data = await fetchJson(
`/custom_nodes/excel_styles_loader/get_styles?tab_name=${encodeURIComponent(tabName)}${forceParam}`
);
const styles = data.styles || [];
_tabCache.set(tabName, styles);
applyStyleOptions(styles, preserveValue);
} catch (err) {
console.error("[ExcelStylesLoader] Failed to load styles:", err);
applyStyleOptions([]);
}
};
// ── Core: Full refresh (tabs + styles) ────────────────────────
const fullRefresh = async ({fromUser = false} = {}) => {
if (_isRefreshing) return;
_isRefreshing = true;
try {
const forceParam = fromUser ? "?force=1" : "";
const data = await fetchJson(`/custom_nodes/excel_styles_loader/get_tabs${forceParam}`);
const tabs = data.tabs || [];
const oldTab = tabWidget.value;
const oldTabs = [...tabWidget.options.values];
// Update tab widget
tabWidget.options.values = tabs.length > 0 ? tabs : ["No Tabs Found"];
// Restore or pick best tab
if (tabs.includes(oldTab)) {
tabWidget.value = oldTab;
} else {
tabWidget.value = tabs.length > 0 ? tabs[0] : "No Tabs Found";
}
// Clear style cache if tabs changed
const tabsChanged = oldTabs.length !== tabs.length ||
oldTabs.some((t, i) => t !== tabs[i]);
if (tabsChanged || fromUser) {
_tabCache.clear();
}
// Load styles for current tab
_currentTab = tabWidget.value;
await loadStyles(_currentTab, {
preserveValue: !tabsChanged,
force: fromUser
});
app.canvas.setDirty(true, true);
} catch (err) {
console.error("[ExcelStylesLoader] Refresh failed:", err);
} finally {
_isRefreshing = false;
}
};
// ── Hook: Tab dropdown opened ────────────────────────────────
// Detect when user clicks the tab dropdown
const origTabMouseDown = tabWidget.mouse ? tabWidget.mouse.bind(tabWidget) : null;
// LiteGraph combo widgets expose onMenuShown or we can hook the element
// More reliable: override the widget's onDraw to detect interaction,
// but simplest is to hook the canvas mousedown near the widget
const hookWidgetOpen = (widget, callback) => {
// Store original mousedown handler if any
const origMouseDown = widget.onMouseDown;
widget.onMouseDown = function(e, localPos, node) {
// e is canvas event; check if clicking the dropdown arrow area
const result = origMouseDown?.apply(this, arguments);
// LiteGraph: clicking anywhere on combo opens it
// We trigger refresh when user interacts with widget
callback();
return result;
};
};
// Hook tab widget: refresh tabs when opened
hookWidgetOpen(tabWidget, () => {
fullRefresh({fromUser: true});
});
// Hook style widget: refresh styles when opened
hookWidgetOpen(styleWidget, () => {
loadStyles(tabWidget.value, {preserveValue: true, force: true});
});
// ── Hook: Tab selection change ───────────────────────────────
const origTabCallback = tabWidget.callback;
tabWidget.callback = async (value) => {
origTabCallback?.call(tabWidget, value);
_currentTab = value;
await loadStyles(value, {preserveValue: false, force: true});
};
// ── Hook: Double-click anywhere on node ──────────────────────
const origDblClick = this.onDblClick;
this.onDblClick = function(e) {
console.log("[ExcelStylesLoader] Manual refresh triggered");
fullRefresh({fromUser: true});
return origDblClick?.apply(this, arguments);
};
// ── Hook: Right-click context menu ───────────────────────────
const origGetMenuOptions = this.getExtraMenuOptions;
this.getExtraMenuOptions = function(_, options) {
options.unshift({
content: "🔄 Refresh Excel Styles",
callback: () => fullRefresh({fromUser: true})
});
return origGetMenuOptions?.apply(this, arguments) || options;
};
// ── Initialization ─────────────────────────────────────────
const init = async () => {
_currentTab = tabWidget.value;
// Initial load without force (use cache if valid)
await fullRefresh({fromUser: false});
};
if (appInstance.graph) {
init();
} else {
setTimeout(init, 100);
}
};
}
});