feat (ui): netwerken en files verfraaid

This commit is contained in:
kodi
2026-02-25 10:07:35 +01:00
parent ec13059437
commit ebb6d755a0
3 changed files with 318 additions and 47 deletions
+143 -43
View File
@@ -174,16 +174,23 @@
</div>
</div>
<div id="networksMapHost" class="mapHost">
</div>
<div id="networksDetailPanel" class="mapDetail" style="display:none;">
<div class="mapDetailHeader">
<button class="btn small ghost" type="button" id="networksMapBackBtn">← Terug</button>
<div class="mapDetailTitle" id="networksDetailTitle">Netwerk</div>
<div class="mapSplit">
<div class="mapMain">
<div id="networksMapHost" class="mapHost">
</div>
</div>
<div class="mapDetailBody" id="networksDetailBody"></div>
<div class="mapSide">
<div id="networksDetailPanel" class="mapDetail" style="display:none;">
<div class="mapDetailHeader">
<button class="btn small ghost" type="button" id="networksMapBackBtn">← Terug</button>
<div class="mapDetailTitle" id="networksDetailTitle">Netwerk</div>
</div>
<div class="mapDetailBody" id="networksDetailBody"></div>
</div>
</div>
</div>
<div id="networksMapLegend" class="mapLegend" style="display:none;">
<div class="legendTitle">Legenda</div>
<div class="legendRow"><span class="legendSwatch net"></span> Netwerk</div>
@@ -1022,49 +1029,142 @@
return;
}
// Render simpel: folders met files eronder
const parts = [];
for (const folder of scoped) {
const apiFolderPath = (folder.path || '').replace(/^\/+/, '');
const uiFolderPath = filesToUiPath(apiFolderPath); // zonder systemd/
const folderLabel = uiFolderPath || 'root';
const folderKey = apiFolderPath; // unieke key (met systemd/..)
const collapsed = _isFolderCollapsed(folderKey);
// Bouw een geneste folder-tree uit de "platte" API response.
const folderByPath = new Map();
for (const f of scoped) {
const apiPath = (f.path || '').replace(/^\/+/, '');
folderByPath.set(apiPath, f);
}
parts.push(`
<div class="mono file-folder-row" data-folder="${esc(folderKey)}" style="margin:8px 0 6px 0; font-weight:600;">
<span class="file-folder-left">
<span class="folder-toggle">${collapsed ? '▶' : '▼'}</span>
<span>📂 ${esc(folderLabel)}</span>
</span>
<span class="flex" onclick="event.stopPropagation();">
<button class="btn small ok" title="Nieuw bestand in ${esc(folderLabel)}" onclick="filesNewFileInFolder(decodeURIComponent('${encodeURIComponent(uiFolderPath)}'))">+</button>
<button class="btn small bad" title="Verwijder map (alleen als leeg)" onclick="filesDeleteFolder(decodeURIComponent('${encodeURIComponent(uiFolderPath)}'))">🗑️</button>
</span>
</div>
`);
function getOrCreateChild(parent, name) {
if (!parent.children.has(name)) {
const apiPath = parent.apiPath ? `${parent.apiPath}/${name}` : name;
parent.children.set(name, {
name,
apiPath,
uiPath: filesToUiPath(apiPath),
children: new Map(),
});
}
return parent.children.get(name);
}
const files = folder.files || [];
if (!files.length) {
parts.push(`<div class="muted" style="margin-left:18px;">(leeg)</div>`);
continue;
}
const root = { name: FILES_ROOT, apiPath: FILES_ROOT, uiPath: '', children: new Map() };
parts.push(`<div class="file-folder-files" data-folder-files="${esc(folderKey)}" style="${collapsed ? 'display:none;' : ''}">`);
// 1) Nodes aanmaken op basis van bekende folder paths
for (const apiPath of folderByPath.keys()) {
if (apiPath === FILES_ROOT) continue;
if (!apiPath.startsWith(FILES_ROOT + '/')) continue;
const rel = apiPath.slice((FILES_ROOT + '/').length);
const segs = rel.split('/').filter(Boolean);
let cur = root;
for (const s of segs) cur = getOrCreateChild(cur, s);
}
for (const f of files) {
const fullUi = uiFolderPath ? `${uiFolderPath}/${f}` : f;
parts.push(`
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 0;border-bottom:1px dashed rgba(36,52,95,.35)">
// 2) Nodes aanvullen op basis van dirs-lijsten (zodat lege tussenfolders ook verschijnen)
for (const [apiPath, folder] of folderByPath.entries()) {
if (apiPath !== FILES_ROOT && !apiPath.startsWith(FILES_ROOT + '/')) continue;
let base = root;
if (apiPath !== FILES_ROOT) {
const rel = apiPath.slice((FILES_ROOT + '/').length);
const segs = rel.split('/').filter(Boolean);
for (const s of segs) base = getOrCreateChild(base, s);
}
for (const d of (folder.dirs || [])) getOrCreateChild(base, d);
}
function renderNode(node, level) {
const folderKey = node.apiPath;
const collapsed = _isFolderCollapsed(folderKey);
const label = node.uiPath || 'root';
const indent = Math.max(0, level) * 14;
const folder = folderByPath.get(folderKey);
const files = (folder && folder.files) ? folder.files : [];
// subfolders (NU AL beschikbaar voor de badges)
const childNames = Array.from(node.children.keys()).sort((a,b) => a.localeCompare(b));
// files (NU AL beschikbaar voor de badges)
const sortedFiles = (files || []).slice().sort((a,b) => a.localeCompare(b));
const out = [];
out.push(`
<div class="mono file-folder-row" data-folder="${esc(folderKey)}" style="margin:${level === 0 ? '8px' : '6px'} 0 6px 0; padding-left:${indent}px; font-weight:600;">
<span class="file-folder-left">
<span class="folder-toggle">${collapsed ? '▶' : '▼'}</span>
<span>📂 ${esc(label)}</span>
</span>
<span class="file-folder-meta" onclick="event.stopPropagation();">
<span class="file-badge" title="Subfolders in deze map">📁 ${childNames.length}</span>
<span class="file-badge" title="Bestanden in deze map">📄 ${sortedFiles.length}</span>
<span class="flex file-folder-actions">
<button class="btn small ok" title="Nieuw bestand in ${esc(label)}" onclick="filesNewFileInFolder(decodeURIComponent('${encodeURIComponent(node.uiPath)}'))">+</button>
<button class="btn small bad" title="Verwijder map (alleen als leeg)" onclick="filesDeleteFolder(decodeURIComponent('${encodeURIComponent(node.uiPath)}'))">🗑️</button>
</span>
</span>
</div>
`);
out.push(`<div class="file-folder-files" data-folder-files="${esc(folderKey)}" style="${collapsed ? 'display:none;' : ''}">`);
for (const name of childNames) {
out.push(renderNode(node.children.get(name), level + 1));
}
for (const f of sortedFiles) {
const fullUi = node.uiPath ? `${node.uiPath}/${f}` : f;
out.push(`
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 0 4px ${indent + 18}px;border-bottom:1px dashed rgba(36,52,95,.35)">
<span class="mono" style="cursor:pointer" onclick="filesOpen(decodeURIComponent('${encodeURIComponent(fullUi)}'))">📄 ${esc(f)}</span>
</div>
`);
}
if (!childNames.length && !sortedFiles.length) {
out.push(`<div class="muted" style="padding-left:${indent + 18}px;">(leeg)</div>`);
}
out.push(`</div>`);
return out.join('');
}
const parts = [];
const topNames = Array.from(root.children.keys()).sort((a,b) => a.localeCompare(b));
for (const n of topNames) parts.push(renderNode(root.children.get(n), 0));
// Files direct onder "systemd/" (root) tonen bovenaan
const rootFolder = folderByPath.get(FILES_ROOT);
if (rootFolder && (rootFolder.files || []).length) {
const folderKey = FILES_ROOT;
const collapsed = _isFolderCollapsed(folderKey);
parts.unshift(`
<div class="mono file-folder-row" data-folder="${esc(folderKey)}" style="margin:8px 0 6px 0; font-weight:600;">
<span class="file-folder-left">
<span class="folder-toggle">${collapsed ? '▶' : '▼'}</span>
<span>📂 root</span>
</span>
<span class="flex" onclick="event.stopPropagation();">
<button class="btn small ok" title="Nieuw bestand in root" onclick="filesNewFileInFolder('')">+</button>
</span>
</div>
<div class="file-folder-files" data-folder-files="${esc(folderKey)}" style="${collapsed ? 'display:none;' : ''}">
${(rootFolder.files || []).slice().sort((a,b)=>a.localeCompare(b)).map(f => {
const fullUi = f;
return `
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 0 4px 18px;border-bottom:1px dashed rgba(36,52,95,.35)">
<span class="mono" style="cursor:pointer" onclick="filesOpen(decodeURIComponent('${encodeURIComponent(fullUi)}'))">📄 ${esc(f)}</span>
</div>
`);
}
`;
}).join('')}
</div>
`);
}
parts.push(`</div>`);
}
treeEl.innerHTML = parts.join('');
treeEl.innerHTML = parts.join('');
treeEl.onclick = (ev) => {
const row = ev.target.closest('.file-folder-row');
if (!row) return;