aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/mindmap.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/ext/mindmap.js')
-rw-r--r--modules/ext/mindmap.js77
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</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');