diff options
Diffstat (limited to 'modules/ext')
| -rw-r--r-- | modules/ext/mindmap.js | 77 |
1 files changed, 75 insertions, 2 deletions
diff --git a/modules/ext/mindmap.js b/modules/ext/mindmap.js index 9f62cba..d0cab54 100644 --- a/modules/ext/mindmap.js +++ b/modules/ext/mindmap.js @@ -44,11 +44,12 @@ class MindmapModule { const self = this; this.commands['mindmap'] = (args) => { if (!args[0] || args[0] === '--help' || args[0] === '-h') { - this.terminal.print('Usage: mindmap <new|add|show|export|save|load|list|delete|rename|color|tag> [...]', 'info'); + this.terminal.print('Usage: mindmap <new|add|show|export|save|load|list|delete|rename|color|tag|svg> [...]', 'info'); this.terminal.print(' new <topic> [-t tags] — Create a new mindmap', 'info'); this.terminal.print(' add <parent-id> <text> [-t tags] — Add a child node', 'info'); this.terminal.print(' show — Display the mindmap tree', 'info'); this.terminal.print(' export — Print as indented text', 'info'); + this.terminal.print(' svg [name] — Render as inline SVG, optionally save to VFS', 'info'); this.terminal.print(' save [name] — Save to VFS', 'info'); this.terminal.print(' load <name> — Load from VFS', 'info'); this.terminal.print(' list — List saved mindmaps', 'info'); @@ -77,7 +78,8 @@ class MindmapModule { case 'rename': return this._rename(rest); case 'color': return this._color(rest); case 'tag': return this._tag(rest); - default: this.terminal.print('Unknown subcommand: ' + sub + '. Try: new, add, show, export, save, load, list, delete, rename, color, tag', 'warning'); + case 'svg': return this._svg(rest); + default: this.terminal.print('Unknown subcommand: ' + sub + '. Try: new, add, show, export, save, load, list, delete, rename, color, tag, svg', 'warning'); } }; } @@ -232,6 +234,77 @@ class MindmapModule { node.tags.push(tag); this.terminal.print('Added tag "' + tag + '" to ' + id, 'success'); } + _svg(args) { + if (!this.root) return this.terminal.print('No mindmap. Create one with mindmap new first.', 'warning'); + const saveName = args[0] || null; + + // Layout settings + const nw = 160, nh = 44, hGap = 24, vGap = 60, pad = 30; + + // Color mapping from name to hex + const colorMap = { + red: '#fb7185', green: '#34d399', yellow: '#fbbf24', blue: '#818cf8', + cyan: '#22d3ee', magenta: '#e879f9', white: '#e0e0e0' + }; + + const layout = { nodes: [], edges: [], width: 0, height: 0, _y: pad }; + + const layoutTree = (node, x) => { + const myY = layout._y; + const myX = x; + const color = colorMap[node.color] || '#818cf8'; + layout.nodes.push({ x: myX, y: myY, w: nw, h: nh, text: node.text, color, id: node.id }); + + if (node.children && node.children.length > 0) { + const totalW = node.children.length * nw + (node.children.length - 1) * hGap; + let cx = x + nw / 2 - totalW / 2; + layout._y += nh + vGap; + for (const child of node.children) { + const childX = cx; + layout.edges.push({ x1: myX + nw / 2, y1: myY, x2: childX + nw / 2, y2: layout._y - vGap + nh }); + layoutTree(child, childX); + cx += nw + hGap; + } + layout._y -= nh + vGap; + } + const rightEdge = x + nw; + if (rightEdge > layout.width) layout.width = rightEdge; + }; + + layout._y = pad; + layoutTree(this.root, pad); + layout.height = layout._y + nh + pad; + + // Build SVG + let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${layout.width + pad} ${layout.height}" style="max-width:100%;background:#0a0a14;font-family:monospace;border-radius:8px;">`; + + // Edges first (behind nodes) + for (const e of layout.edges) { + const mx = (e.x1 + e.x2) / 2; + svg += `<path d="M${e.x1},${e.y1} C${mx},${e.y1 + 20} ${mx},${e.y2 - 20} ${e.x2},${e.y2}" fill="none" stroke="#2a2a4a" stroke-width="1.5"/>`; + } + + // Nodes on top + for (const n of layout.nodes) { + svg += `<rect x="${n.x}" y="${n.y}" width="${n.w}" height="${n.h}" rx="6" fill="${n.color}18" stroke="${n.color}" stroke-width="1.5"/>`; + const text = n.text.length > 18 ? n.text.substring(0, 16) + '..' : n.text; + svg += `<text x="${n.x + n.w / 2}" y="${n.y + n.h / 2 + 4}" text-anchor="middle" fill="#e0e0e0" font-size="10" font-family="monospace">${text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}</text>`; + } + svg += '</svg>'; + + this.terminal.printHTML(svg); + + if (saveName) { + try { + const fs = this.terminal.fs; + fs.mkdirRecursive('/mindmaps'); + fs.writeFile('/mindmaps/' + saveName + '.svg', svg); + this.terminal.print('💾 Saved to /mindmaps/' + saveName + '.svg', 'success'); + } catch (e) { + this.terminal.print('Failed to save SVG: ' + e.message, 'error'); + } + } + } } if (typeof window.webx11 !== 'undefined' && window.webx11) { webx11.moduleManager.registerModule(new MindmapModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/mindmap.js'); |