// --- static/tree-ui.js ---
import { SESSION_USER_ID, currentEditElementId } from './edit-mode.js';
// ==========================================
// 🌟 效能終極優化:全域快取與延遲渲染 (Lazy Rendering)
// ==========================================
export const folderDataCache = {};
export const filterFolderCache = {};
// 🧹 [修復 Leak] 徹底清空樹狀圖快取,釋放記憶體
export function clearTreeCache() {
for (let key in folderDataCache) delete folderDataCache[key];
for (let key in filterFolderCache) delete filterFolderCache[key];
}
// 魔法函數:當資料夾被點擊展開時,才將 HTML 渲染進去
window.lazyLoadFolder = function(elementId, isFilter = false) {
const detailsEl = document.getElementById(`details-${elementId}`);
// 如果已經載入過,就不重複渲染
if (!detailsEl || detailsEl.dataset.loaded === 'true') return;
// 從 dataset 讀取當初存下來的屬性
const currentPath = detailsEl.dataset.path;
const isCommandGroup = detailsEl.dataset.isGroup === 'true';
const mode = detailsEl.dataset.mode;
const contentDiv = document.getElementById(isFilter ? `filter-content-${elementId}` : `content-${elementId}`);
const cache = isFilter ? filterFolderCache[elementId] : folderDataCache[elementId];
if (contentDiv && cache) {
// 🌟 統一渲染作法:單點展開也套用非同步渲染保護
contentDiv.innerHTML = `⏳ 載入中...`;
document.body.style.cursor = 'wait';
setTimeout(() => {
if (isFilter) {
contentDiv.innerHTML = buildRealFilterTree(cache.data, currentPath, cache.hiddenKeys, isCommandGroup, mode);
} else {
contentDiv.innerHTML = buildTree(cache, currentPath, isCommandGroup, mode);
}
detailsEl.dataset.loaded = 'true'; // 標記為已載入
document.body.style.cursor = 'default';
}, 10);
}
};
// ==========================================
// 🌟 輔助函數:全部展開與全部收合 (支援延遲渲染)
// ==========================================
export function expandAll(elementId) {
const details = document.getElementById(`details-${elementId}`);
if (!details) return;
const isFilter = details.id.includes('filter-');
const cache = isFilter ? filterFolderCache[elementId] : folderDataCache[elementId];
if (!cache) return;
// 🌟 讓滑鼠變成讀取狀態,給予使用者即時回饋
document.body.style.cursor = 'wait';
const contentDiv = document.getElementById(isFilter ? `filter-content-${elementId}` : `content-${elementId}`);
if (contentDiv) {
contentDiv.innerHTML = `⏳ 正在極速展開中,請稍候...`;
}
// 🌟 使用 setTimeout 讓出一個 Frame,讓瀏覽器能渲染出「讀取中」的文字與滑鼠狀態
setTimeout(() => {
const currentPath = details.dataset.path;
const isCommandGroup = details.dataset.isGroup === 'true';
const mode = details.dataset.mode;
// 🚀 核心魔法:在記憶體中一次性遞迴生成所有子節點的 HTML 字串 (傳入 forceExpand = true)
let fullHtml = '';
if (isFilter) {
fullHtml = buildRealFilterTree(cache.data, currentPath, cache.hiddenKeys, isCommandGroup, mode, true);
} else {
fullHtml = buildTree(cache, currentPath, isCommandGroup, mode, true);
}
// 🚀 終極效能:只執行 1 次 DOM 寫入!
if (contentDiv) contentDiv.innerHTML = fullHtml;
details.dataset.loaded = 'true';
details.open = true;
const iconClosed = details.querySelector('.icon-closed');
const iconOpen = details.querySelector('.icon-open');
if (iconClosed) iconClosed.style.display = 'none';
if (iconOpen) iconOpen.style.display = 'inline-block';
document.body.style.cursor = 'default';
}, 20);
}
export function collapseAll(elementId) {
const details = document.getElementById(`details-${elementId}`);
if (details) {
// 🌟 效能優化:只尋找目前是 open 狀態的 details 進行關閉,減少 DOM 操作
const openSubDetails = details.querySelectorAll('details[open]');
openSubDetails.forEach(d => {
d.open = false;
const iconClosed = d.querySelector('.icon-closed');
const iconOpen = d.querySelector('.icon-open');
if (iconClosed) iconClosed.style.display = 'inline-block';
if (iconOpen) iconOpen.style.display = 'none';
});
details.open = false;
const mainIconClosed = details.querySelector('.icon-closed');
const mainIconOpen = details.querySelector('.icon-open');
if (mainIconClosed) mainIconClosed.style.display = 'inline-block';
if (mainIconOpen) mainIconOpen.style.display = 'none';
}
}
// ==========================================
// 🌟 核心函數:生成完整設備配置樹狀圖 (Lazy 版)
// ==========================================
// 🌟 修改函數宣告,加入 renderAll 參數
export function buildTree(node, path = '', isParentCommandGroup = false, mode = 'running', forceExpand = false, renderAll = false) {
const htmlParts = [];
if (!node || typeof node !== 'object') return htmlParts.join('');
const svgFolderClosed = ``;
const svgFolderOpen = ``;
const svgGroupClosed = ``;
const svgGroupOpen = ``;
const expandAllIcon = ``;
const collapseAllIcon = ``;
const svgLeaf = ``;
const svgDisabled = ``;
for (const [key, value] of Object.entries(node)) {
const currentPath = path ? `${path}::${key}` : key;
let cliCommand = currentPath.replace(/::/g, ' ').replace(/\s*\[\d+\]/g, '');
if (typeof value === 'object' && value !== null) {
const hasNestedObject = Object.values(value).some(v => typeof v === 'object' && v !== null);
const isCommandGroup = isParentCommandGroup || !hasNestedObject;
const elementId = `${mode}-folder-${currentPath.replace(/[^a-zA-Z0-9]/g, '-')}`;
const folderSuffix = isCommandGroup ? '(指令群組)' : '';
folderDataCache[elementId] = value;
const iconClosed = isCommandGroup ? svgGroupClosed : svgFolderClosed;
const iconOpen = isCommandGroup ? svgGroupOpen : svgFolderOpen;
const expandCollapseBtns = `
${expandAllIcon}
${collapseAllIcon}
`;
const currentHost = document.getElementById('cmtsHost')?.value.trim();
let isLocked = false;
let lockTitle = "鎖定並編輯此群組";
let lockText = "✏️";
let lockOpacity = "0.3";
let lockPointer = "auto";
if (currentHost && window.globalActiveLocks && window.globalActiveLocks[currentHost]) {
const hostLocks = window.globalActiveLocks[currentHost];
if (hostLocks[currentPath]) {
const lockInfo = hostLocks[currentPath];
if (!(lockInfo.user_id === SESSION_USER_ID && elementId === currentEditElementId)) {
isLocked = true; lockText = "🔒"; lockOpacity = "0.2"; lockPointer = "none";
lockTitle = lockInfo.user_id !== SESSION_USER_ID ? `🔒 此區塊正由 [${lockInfo.username}] 編輯中` : `🔒 您正在另一個視圖編輯此項目`;
}
} else {
for (const [lockedPath, lockInfo] of Object.entries(hostLocks)) {
if (currentPath.startsWith(lockedPath + "::")) {
isLocked = true; lockText = "🔒"; lockOpacity = "0.2"; lockPointer = "none";
lockTitle = `🔒 父層級已被鎖定,無法編輯此項目`; break;
}
}
}
}
// 🚀 核心魔法:如果 forceExpand 為 true,直接在記憶體中遞迴生成子節點 HTML
const isOpenAttr = forceExpand ? 'open' : '';
const isLoadedAttr = (forceExpand || renderAll) ? 'true' : 'false';
const displayClosed = forceExpand ? 'none' : 'inline-block';
const displayOpen = forceExpand ? 'inline-block' : 'none';
let innerContent = '';
if (forceExpand || renderAll) {
// 🌟 遞迴呼叫時,把 renderAll 傳遞下去
innerContent = buildTree(value, currentPath, isCommandGroup, mode, forceExpand, renderAll);
}
htmlParts.push(`
${iconClosed}
${iconOpen}
${key}${folderSuffix}
${expandCollapseBtns}