fix: 將 God-Mode 的唯讀預覽變更成平坦化 (Flatten) 設計

This commit is contained in:
swpa 2026-05-22 18:34:19 +08:00
parent 05fdccf235
commit d53f90efff
1 changed files with 95 additions and 55 deletions

View File

@ -24,7 +24,7 @@ function escapeHTML(str) {
export const SESSION_USER_ID = crypto.randomUUID ? crypto.randomUUID() : 'user-' + Math.random().toString(36).substr(2, 9); export const SESSION_USER_ID = crypto.randomUUID ? crypto.randomUUID() : 'user-' + Math.random().toString(36).substr(2, 9);
const ACTIVE_HEARTBEATS = {}; const ACTIVE_HEARTBEATS = {};
// 💡 追蹤當前編輯狀態 // 💡 追蹤當前編輯狀態
export let currentEditPath = null; export let currentEditPath = null;
export let currentEditElementId = null; export let currentEditElementId = null;
export let currentEditIsSuccess = false; export let currentEditIsSuccess = false;
@ -400,10 +400,17 @@ function showCliPreviewModal(cliScript) {
document.getElementById('btn-side-cancel').style.display = 'inline-block'; document.getElementById('btn-side-cancel').style.display = 'inline-block';
document.getElementById('btn-side-confirm').style.display = 'inline-block'; document.getElementById('btn-side-confirm').style.display = 'inline-block';
document.getElementById('btn-side-close').style.display = 'none'; document.getElementById('btn-side-close').style.display = 'none';
document.getElementById('btn-side-close').textContent = '關閉';
document.getElementById('side-cli-textarea').style.display = 'block'; document.getElementById('side-cli-textarea').style.display = 'block';
document.getElementById('side-execution-result').style.display = 'none'; document.getElementById('side-execution-result').style.display = 'none';
document.getElementById('side-cli-textarea').value = cliScript; const textarea = document.getElementById('side-cli-textarea');
textarea.value = cliScript;
// 👇 確保正常模式可以編輯,並明確恢復深色主題
textarea.readOnly = false;
textarea.style.backgroundColor = '#1e1e1e'; // 恢復成標準的深色背景 (您可以依喜好微調顏色碼)
textarea.style.color = '#ecf0f1'; // 確保字體是清晰的淺灰色
// 🌟 修改:遍歷所有樹狀圖容器進行縮放 // 🌟 修改:遍歷所有樹狀圖容器進行縮放
leftPanes.forEach(pane => pane.style.width = 'calc(50% - 11px)'); leftPanes.forEach(pane => pane.style.width = 'calc(50% - 11px)');
@ -631,81 +638,114 @@ function transformNoToActive(elementId, newCommand) {
// ============================================================================ // ============================================================================
// 💻 開發者工具:預覽節點完整指令 (唯讀 Debug 模式) // 💻 開發者工具:預覽節點完整指令 (唯讀 Debug 模式)
// ============================================================================ // ============================================================================
export function previewNodeCLI(btnElement, rootPath) { export async function previewNodeCLI(btnElement, rootPath) {
// 1. 定位當前點擊的容器 // 1. 定位當前點擊的容器
let container = btnElement.closest('details'); let container = btnElement.closest('details');
if (!container) { if (container) {
const leafNode = btnElement.closest('.tree-leaf-node'); const elementId = container.id.replace('details-', '');
if (leafNode) container = leafNode.querySelector('.leaf-container'); // 確保延遲渲染的內容已經載入 (Lazy Load)
if (container.dataset.loaded !== 'true') {
await window.lazyLoadFolder(elementId, false);
}
} }
if (!container) return; const targetNode = container || btnElement.closest('.tree-leaf-node');
if (!targetNode) return;
let cliOutput = `! =========================================\n`; // 2. 收集所有子節點資料
cliOutput += `! 🔍 [Debug] 節點預覽: ${rootPath}\n`; const leafNodes = targetNode.querySelectorAll('.leaf-container');
cliOutput += `! =========================================\n`; const interfaceGroups = {};
cliOutput += `${rootPath.replace(/::/g, ' ')}\n`;
// 2. 找出該節點下所有的葉節點 (包含自己) leafNodes.forEach(node => {
const allLeaves = container.classList.contains('leaf-container') const path = node.getAttribute('data-path') || "";
? [container] const val = node.getAttribute('data-original');
: Array.from(container.querySelectorAll('.leaf-container')); if (!path) return;
if (allLeaves.length === 1 && allLeaves[0] === container) { const parts = path.split('::');
// 情況 A它自己就是最底層的葉節點 const paramName = parts.pop();
const val = container.getAttribute('data-original'); // 移除虛擬陣列索引 [0], [1] 等,還原真實介面路徑
if (val) cliOutput += ` ${val}\n`; let interfacePath = parts.join(' ').replace(/\s*\[\d+\]/g, '');
} else {
// 情況 B它是一個資料夾遞迴列出所有子項目的相對路徑與值 if (!interfaceGroups[interfacePath]) {
allLeaves.forEach(leaf => { interfaceGroups[interfacePath] = [];
const leafPath = leaf.getAttribute('data-path'); }
const leafVal = leaf.getAttribute('data-original'); interfaceGroups[interfacePath].push({ param: paramName, val: val });
});
if (leafPath && leafVal !== null) {
const relativePath = leafPath.replace(rootPath, '').replace(/^::/, '').replace(/::/g, ' '); // 補強邏輯:處理使用者直接點擊葉節點的情況
cliOutput += ` ${relativePath} ${leafVal}\n`; if (Object.keys(interfaceGroups).length === 0 && targetNode.classList.contains('tree-leaf-node')) {
const singleNode = targetNode.querySelector('.leaf-container');
if (singleNode) {
const path = singleNode.getAttribute('data-path');
const val = singleNode.getAttribute('data-original');
const parts = path.split('::');
const paramName = parts.pop();
let interfacePath = parts.join(' ').replace(/\s*\[\d+\]/g, '');
interfaceGroups[interfacePath] = [{ param: paramName, val: val }];
}
}
if (Object.keys(interfaceGroups).length === 0) {
return alert("此節點下沒有任何數值可供預覽!");
}
// 3. 在前端組裝平坦化 CLI (模擬真實設備指令)
let cliLines = [];
cliLines.push("! --- Read-Only Configuration Preview ---");
for (const [interfacePath, params] of Object.entries(interfaceGroups)) {
cliLines.push(`! Node: ${interfacePath || 'Global'}`);
params.forEach(({param, val}) => {
if (/^\[\d+\]$/.test(param)) {
// 陣列元素本身就是值
if (val) {
cliLines.push(interfacePath ? `${interfacePath} ${val}` : val);
}
} else if (param === 'no') {
// 處理 no 指令
cliLines.push(interfacePath ? `${interfacePath} no ${val}` : `no ${val}`);
} else {
// 一般鍵值對 (若 val 為空,代表是單純的 flag 指令)
let line = interfacePath ? `${interfacePath} ${param}` : param;
if (val) line += ` ${val}`;
cliLines.push(line);
} }
}); });
cliLines.push("!");
} }
const finalScript = cliLines.join('\n');
// 4. 顯示唯讀預覽面板
showReadOnlyCliPreviewModal(finalScript, rootPath);
}
cliOutput += `exit\n`; // 專為 God-Mode 設計的唯讀面板 UI
function showReadOnlyCliPreviewModal(cliScript, rootPath) {
// 3. 呼叫現有的樹狀圖側邊欄 (Side Pane) 顯示
// 🌟 修改:改用 class 統一控制所有樹狀圖容器
const leftPanes = document.querySelectorAll('.tree-view-instance'); const leftPanes = document.querySelectorAll('.tree-view-instance');
const resizer = document.getElementById('drag-resizer'); const resizer = document.getElementById('drag-resizer');
const rightPane = document.getElementById('side-cli-preview'); const rightPane = document.getElementById('side-cli-preview');
if (!rightPane) {
console.error("找不到側邊欄元素 'side-cli-preview'");
return;
}
// 設定標題與樣式 document.getElementById('side-pane-title').innerHTML = `🔍 [唯讀預覽] ${rootPath}`;
document.getElementById('side-pane-title').innerHTML = '🔍 節點指令預覽 (唯讀)';
document.getElementById('side-pane-title').style.color = '#3498db'; document.getElementById('side-pane-title').style.color = '#3498db';
// 隱藏「確認寫入」按鈕,只顯示「取消/關閉」按鈕 // 隱藏執行按鈕,只保留關閉
document.getElementById('btn-side-confirm').style.display = 'none'; document.getElementById('btn-side-confirm').style.display = 'none';
document.getElementById('btn-side-cancel').style.display = 'inline-block'; document.getElementById('btn-side-cancel').style.display = 'none';
document.getElementById('btn-side-close').style.display = 'none'; const closeBtn = document.getElementById('btn-side-close');
closeBtn.style.display = 'inline-block';
// 顯示 Textarea 並填入內容 closeBtn.textContent = '關閉預覽';
const textarea = document.getElementById('side-cli-textarea'); const textarea = document.getElementById('side-cli-textarea');
textarea.style.display = 'block'; textarea.style.display = 'block';
textarea.value = cliOutput; textarea.value = cliScript;
textarea.style.backgroundColor = '#1e1e1e'; // 終端機深色背景 textarea.readOnly = true; // 強制唯讀
textarea.style.color = '#d4d4d4'; textarea.style.backgroundColor = '#2c3e50'; // 改變背景色提示唯讀狀態
document.getElementById('side-execution-result').style.display = 'none'; document.getElementById('side-execution-result').style.display = 'none';
// 展開側邊欄
// 🌟 修改:遍歷所有樹狀圖容器進行縮放
leftPanes.forEach(pane => pane.style.width = 'calc(50% - 11px)'); leftPanes.forEach(pane => pane.style.width = 'calc(50% - 11px)');
rightPane.style.width = 'calc(50% - 11px)'; rightPane.style.width = 'calc(50% - 11px)';
if (resizer) resizer.style.display = 'flex'; if (resizer) resizer.style.display = 'flex';
rightPane.style.display = 'block'; if (rightPane) rightPane.style.display = 'block';
// 初始化拖拉分隔線 (如果尚未初始化)
if (!window.isResizerInitialized && typeof initResizer === 'function') { if (!window.isResizerInitialized && typeof initResizer === 'function') {
initResizer(); initResizer();
window.isResizerInitialized = true; window.isResizerInitialized = true;