// --- static/tree-ui.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) { if (isFilter) { contentDiv.innerHTML = buildRealFilterTree(cache.data, currentPath, cache.hiddenKeys, isCommandGroup, mode); } else { contentDiv.innerHTML = buildTree(cache, currentPath, isCommandGroup, mode); } detailsEl.dataset.loaded = 'true'; // 標記為已載入 } }; // ========================================== // 🌟 輔助函數:全部展開與全部收合 (支援延遲渲染) // ========================================== export function expandAll(elementId) { const details = document.getElementById(`details-${elementId}`); if (details) { // 如果還沒載入,先強制載入它 if (!details.dataset.loaded) { const isFilter = details.id.includes('filter-'); window.lazyLoadFolder(elementId, isFilter); } details.open = true; // 找出剛剛渲染出來的第一層子資料夾,遞迴展開 const contentDiv = details.querySelector(':scope > div'); if (contentDiv) { const childDetails = contentDiv.querySelectorAll(':scope > details'); childDetails.forEach(d => { const subId = d.id.replace('details-', ''); expandAll(subId); // 遞迴展開 }); } } } export function collapseAll(elementId) { const details = document.getElementById(`details-${elementId}`); if (details) { const subDetails = details.querySelectorAll('details'); subDetails.forEach(d => d.open = false); details.open = false; } } // ========================================== // 🌟 核心函數:生成完整設備配置樹狀圖 (Lazy 版) // ========================================== export function buildTree(node, path = '', isParentCommandGroup = false, mode = 'running') { let html = ''; if (!node || typeof node !== 'object') return html; 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 svgFolderClosed = ``; const svgFolderOpen = ``; const svgGroupClosed = ``; const svgGroupOpen = ``; const iconClosed = isCommandGroup ? svgGroupClosed : svgFolderClosed; const iconOpen = isCommandGroup ? svgGroupOpen : svgFolderOpen; const expandAllIcon = ``; const collapseAllIcon = ``; const expandCollapseBtns = ` ${expandAllIcon} ${collapseAllIcon} `; // 🌟 關鍵修改:加入 data-* 屬性,並在 ontoggle 時呼叫 lazyLoadFolder html += `
${iconClosed} ${key}${folderSuffix} ${expandCollapseBtns}
✏️
`; } else { // 葉節點邏輯保持不變 const isVirtualIndex = /^\[\d+\]$/.test(key); const isNoCommand = (key === 'no'); const safeValue = (value === null ? '' : value).toString().replace(/"/g, """); const elementId = `${mode}-leaf-${currentPath.replace(/[^a-zA-Z0-9]/g, '-')}`; if (isNoCommand) { let baseCmd = cliCommand.replace(/(^|\s)no$/, '').trim(); cliCommand = baseCmd ? `${baseCmd} no ${safeValue}` : `no ${safeValue}`; } const svgLeaf = ``; const svgDisabled = ``; const iconHtml = (icon) => `${icon}`; const valueStyle = `color: #16a085; margin-left: 5px; font-family: Courier New, monospace; display: inline-block; transform: translateY(1.5px);`; let displayContent = ''; if (isVirtualIndex) { displayContent = ` ${iconHtml(svgLeaf)} ${safeValue} `; } else if (isNoCommand) { displayContent = ` ${iconHtml(svgDisabled)} no: ${safeValue} `; } else { displayContent = ` ${iconHtml(svgLeaf)} ${key}: ${safeValue} `; } html += `
${displayContent}
✏️
`; } } return html; } // ========================================== // 🌟 核心函數:生成系統設定的過濾樹狀圖 (Lazy 版) // ========================================== export function buildRealFilterTree(data, parentPath = '', hiddenKeys = [], isParentCommandGroup = false, mode = 'running') { if (typeof data !== 'object' || data === null) return ''; let html = `
`; for (const key in data) { const value = data[key]; const currentPath = parentPath ? `${parentPath}::${key}` : key; const isChecked = hiddenKeys.includes(currentPath) ? "checked" : ""; let cliCommand = currentPath.replace(/::/g, ' ').replace(/\s*\[\d+\]/g, ''); const svgFolderClosed = ``; const svgFolderOpen = ``; const svgGroupClosed = ``; const svgGroupOpen = ``; const svgLeaf = ``; const isFolder = typeof value === 'object' && value !== null && Object.keys(value).length > 0; const onChangeEvent = isFolder ? 'toggleChildCheckboxes(this)' : 'updateParentCheckboxState(this)'; const checkboxHtml = ``; if (isFolder) { const hasNestedObject = Object.values(value).some(v => typeof v === 'object' && v !== null); const isCommandGroup = isParentCommandGroup || !hasNestedObject; const elementId = `filter-${mode}-folder-${currentPath.replace(/[^a-zA-Z0-9]/g, '-')}`; // 🌟 存入過濾器專用快取 filterFolderCache[elementId] = { data: value, hiddenKeys: hiddenKeys }; const iconClosed = isCommandGroup ? svgGroupClosed : svgFolderClosed; const iconOpen = isCommandGroup ? svgGroupOpen : svgFolderOpen; const folderSuffix = isCommandGroup ? `(指令群組)` : ''; const expandAllIcon = ``; const collapseAllIcon = ``; const expandCollapseBtns = ` ${expandAllIcon} ${collapseAllIcon} `; // 🌟 加入 lazyLoadFolder 觸發 html += `
${checkboxHtml} ${iconClosed} ${key}${folderSuffix} ${expandCollapseBtns}
`; } else { // 葉節點邏輯保持不變 const isVirtualIndex = /^\[\d+\]$/.test(key); let displayName = isVirtualIndex ? `項目 ${key}` : `${key}`; const safeValue = (value === null ? '' : value).toString().replace(/"/g, """); const isNoCommand = (key === 'no'); let valueHtml = `${safeValue}`; let colonHtml = `:`; let currentIcon = ``; if (isNoCommand) { let baseCmd = cliCommand.replace(/(^|\s)no$/, '').trim(); cliCommand = baseCmd ? `${baseCmd} no ${safeValue}` : `no ${safeValue}`; displayName = `no`; colonHtml = `:`; valueHtml = `${safeValue}`; currentIcon = ``; } html += `
${checkboxHtml} ${currentIcon} ${displayName}${colonHtml} ${valueHtml}
`; } } html += '
'; return html; }