198 lines
9.2 KiB
JavaScript
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);
|
|
}
|
|
};
|
|
}
|
|
}); |