Skip to content

Commit 9656801

Browse files
Pierre-Luc GagnéCopilot
andcommitted
Align Visual Studio extension status feature with VS Code extension
- Replace class-based status approach with data-status attribute - Add CSS icons (::before) with correct positioning per node level - Add applyAllStatuses() + debounced linkDiv listener for load/refresh - Add context menu items (In Progress, Completed, Clear Status) - Add input-box guard to c key handler - Trigger autosave on status change via c key and context menu - Move status tutorial node inside Mind Map group, update descriptions - scheduleApplyAllStatuses() called on selectNode and operation events Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 27b39b8 commit 9656801

1 file changed

Lines changed: 172 additions & 47 deletions

File tree

Visual Studio/CodeMindMap/CodeMindMapHtml.cs

Lines changed: 172 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,52 @@ public class CodeMindMapHtml
2828
margin: 0;
2929
padding: 0;
3030
}
31-
/* Status indicator styles */
32-
.node-completed {
33-
opacity: 0.6;
31+
/* Root node: match the same padding-to-font ratio as level-1 nodes.
32+
Level-1 uses 8px/25px padding with 14px font (ratios 0.57v / 1.79h).
33+
Root has 25px font, so target: 14px vertical / 45px horizontal. */
34+
.map-container me-root me-tpc {
35+
padding: 14px 45px !important;
36+
}
37+
38+
/* Status indicator styles */
39+
/* Root (45px horizontal padding) and level-1 (25px padding) already have enough room for the icon */
40+
.map-container me-tpc {
41+
position: relative;
42+
}
43+
/* Level-2+ nodes: always reserve icon space so text never shifts when status changes. */
44+
.map-container me-children me-parent me-tpc {
45+
padding-left: 20px;
46+
box-sizing: border-box;
47+
}
48+
.map-container me-tpc[data-status=""completed""] {
3449
text-decoration: line-through;
3550
}
36-
.node-in-progress::before {
37-
content: '⟳ ';
38-
color: #ff9800;
51+
.map-container me-tpc[data-status=""completed""] > .text {
52+
text-decoration: line-through !important;
53+
}
54+
.map-container me-tpc[data-status]::before {
55+
position: absolute;
56+
left: 2px; /* level-2+: within the 20px padding we add */
57+
top: 50%;
58+
transform: translateY(-50%);
59+
width: 14px;
60+
text-align: center;
3961
font-weight: bold;
4062
}
41-
.node-completed::before {
42-
content: '✓ ';
63+
/* Center icon in the existing left padding for root (45px) and level-1 (25px) */
64+
.map-container me-root me-tpc[data-status]::before {
65+
left: 16px; /* (45px - 14px) / 2 */
66+
}
67+
.map-container me-main > me-wrapper > me-parent > me-tpc[data-status]::before {
68+
left: 6px; /* (25px - 14px) / 2 */
69+
}
70+
.map-container me-tpc[data-status=""in-progress""]::before {
71+
content: '⟳';
72+
color: #ff9800;
73+
}
74+
.map-container me-tpc[data-status=""completed""]::before {
75+
content: '✓';
4376
color: #4caf50;
44-
font-weight: bold;
4577
}
4678
</style>
4779
</head>
@@ -52,12 +84,55 @@ public class CodeMindMapHtml
5284
import MindElixir from ""http://codemindmap.vsext/MindElixir.js"";
5385
5486
let mind, themeManager;
87+
let linkDivDebounceTimer = null;
88+
let scheduleRafHandle = null;
89+
let scheduleTimerHandle = null;
5590
5691
function initMindMap() {
5792
const options = {
5893
el: '#map',
5994
allowUndo: true,
6095
toolBar: true,
96+
contextMenu: {
97+
extend: [
98+
{
99+
name: '⟳ In Progress',
100+
onclick: () => {
101+
const node = mind.currentNode?.nodeObj;
102+
if (!node) return;
103+
node.data = node.data || {};
104+
node.data.status = 'in-progress';
105+
updateNodeStatus(node);
106+
window.chrome.webview.postMessage({ action: 'mindMapOperation', operationName: 'updateNodeStatus' });
107+
const cm = document.querySelector('.map-container > .context-menu'); if (cm) cm.hidden = true;
108+
}
109+
},
110+
{
111+
name: '✓ Completed',
112+
onclick: () => {
113+
const node = mind.currentNode?.nodeObj;
114+
if (!node) return;
115+
node.data = node.data || {};
116+
node.data.status = 'completed';
117+
updateNodeStatus(node);
118+
window.chrome.webview.postMessage({ action: 'mindMapOperation', operationName: 'updateNodeStatus' });
119+
const cm = document.querySelector('.map-container > .context-menu'); if (cm) cm.hidden = true;
120+
}
121+
},
122+
{
123+
name: '✕ Clear Status',
124+
onclick: () => {
125+
const node = mind.currentNode?.nodeObj;
126+
if (!node) return;
127+
node.data = node.data || {};
128+
delete node.data.status;
129+
updateNodeStatus(node);
130+
window.chrome.webview.postMessage({ action: 'mindMapOperation', operationName: 'updateNodeStatus' });
131+
const cm = document.querySelector('.map-container > .context-menu'); if (cm) cm.hidden = true;
132+
}
133+
},
134+
]
135+
},
61136
view: {
62137
beforeSelect(el, node) {
63138
mind.currentNode = node;
@@ -200,23 +275,27 @@ function initMindMap() {
200275
topic: 'space - Expand/collapse nodes',
201276
id: 'bd1bb2ac4bbab458',
202277
},
203-
],
204-
},
205-
{
206-
topic: 'C - Toggle node status (Not Started, In Progress, Completed)',
207-
id: 'bd1bb2ac4bbab460',
208-
children: [
209-
{
210-
topic: 'Press C to cycle through status states',
211-
id: 'bd1bb2ac4bbab461',
212-
},
213278
{
214-
topic: 'Completed nodes show ✓ and appear faded',
215-
id: 'bd1bb2ac4bbab462',
216-
},
217-
{
218-
topic: 'In Progress nodes show ⟳ in orange',
219-
id: 'bd1bb2ac4bbab463',
279+
topic: 'c - Cycle node status: (none) → In Progress → Completed → (none)',
280+
id: 'bd1bb2ac4bbab460',
281+
children: [
282+
{
283+
topic: 'In Progress nodes show ⟳ in orange',
284+
id: 'bd1bb2ac4bbab461',
285+
},
286+
{
287+
topic: 'Completed nodes show ✓ with strikethrough text',
288+
id: 'bd1bb2ac4bbab462',
289+
},
290+
{
291+
topic: 'Right-click a node to set status directly from the context menu',
292+
id: 'bd1bb2ac4bbab463',
293+
},
294+
{
295+
topic: 'Status is saved automatically with the diagram',
296+
id: 'bd1bb2ac4bbab464',
297+
},
298+
],
220299
},
221300
],
222301
},
@@ -240,26 +319,59 @@ function initMindMap() {
240319
};
241320
});
242321
322+
scheduleApplyAllStatuses();
243323
// Helper function to update node visual status
244324
function updateNodeStatus(nodeObj) {
245325
if (!nodeObj || !nodeObj.id) return;
246326
const nodeElement = MindElixir.E(nodeObj.id);
247327
if (!nodeElement) return;
248-
328+
249329
const status = nodeObj.data?.status || null;
250-
const domEl = nodeElement.getEl?.();
330+
const domEl = nodeElement.getEl?.() || nodeElement;
251331
if (!domEl) return;
252-
253-
// Remove all status classes
254-
domEl.classList.remove('node-completed', 'node-in-progress');
255-
256-
// Apply new status class
257-
if (status === 'completed') {
258-
domEl.classList.add('node-completed');
259-
} else if (status === 'in-progress') {
260-
domEl.classList.add('node-in-progress');
332+
333+
const topicEl = (() => {
334+
if (domEl.tagName === 'ME-TPC') return domEl;
335+
const byQuery = domEl.querySelector?.('me-tpc');
336+
if (byQuery) return byQuery;
337+
const byTag = domEl.getElementsByTagName?.('me-tpc')?.[0];
338+
if (byTag) return byTag;
339+
return domEl;
340+
})();
341+
342+
if (!topicEl) return;
343+
344+
if (!status) {
345+
topicEl.removeAttribute('data-status');
346+
return;
347+
}
348+
349+
topicEl.setAttribute('data-status', status);
350+
}
351+
352+
function applyAllStatuses() {
353+
const root = mind?.nodeData;
354+
if (!root) return;
355+
const stack = [root];
356+
while (stack.length > 0) {
357+
const node = stack.pop();
358+
if (!node) continue;
359+
updateNodeStatus(node);
360+
if (Array.isArray(node.children)) {
361+
for (const child of node.children) {
362+
stack.push(child);
363+
}
364+
}
261365
}
262366
}
367+
368+
function scheduleApplyAllStatuses() {
369+
if (!mind) return;
370+
if (scheduleRafHandle !== null) cancelAnimationFrame(scheduleRafHandle);
371+
if (scheduleTimerHandle !== null) clearTimeout(scheduleTimerHandle);
372+
scheduleRafHandle = requestAnimationFrame(() => { applyAllStatuses(); scheduleRafHandle = null; });
373+
scheduleTimerHandle = setTimeout(() => { applyAllStatuses(); scheduleTimerHandle = null; }, 50);
374+
}
263375
264376
mind.bus.addListener('selectNode', node => {
265377
window.chrome.webview.postMessage({
@@ -268,13 +380,22 @@ function updateNodeStatus(nodeObj) {
268380
nodeTopic: node.topic,
269381
nodeData: node.data,
270382
});
383+
scheduleApplyAllStatuses();
384+
});
385+
386+
// Debounced linkDiv listener: MindElixir fires linkDiv after every layout pass.
387+
// Wait for 50ms of silence before applying statuses so we run after the final DOM state.
388+
mind.bus.addListener('linkDiv', () => {
389+
clearTimeout(linkDivDebounceTimer);
390+
linkDivDebounceTimer = setTimeout(applyAllStatuses, 50);
271391
});
272392
273-
mind.bus.addListener('operation', operation => {
274-
window.chrome.webview.postMessage({
275-
action: 'mindMapOperation',
276-
operationName: operation.name,
277-
});
393+
mind.bus.addListener('operation', operation => {
394+
window.chrome.webview.postMessage({
395+
action: 'mindMapOperation',
396+
operationName: operation.name,
397+
});
398+
scheduleApplyAllStatuses();
278399
});
279400
280401
document.addEventListener('click', (e) => {
@@ -311,11 +432,14 @@ function updateNodeStatus(nodeObj) {
311432
}
312433
}
313434
else if ((e.key === 'c' || e.key === 'C') && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) {
435+
// Skip if MindElixir's inline editor is open
436+
if (document.getElementById('input-box')) return;
314437
e.preventDefault();
315438
const currentNode = mind.currentNode?.nodeObj;
316-
if (!currentNode || !currentNode.data) return;
317-
318-
// Cycle through status: (none) -> in-progress -> completed -> (none)
439+
if (!currentNode) return;
440+
currentNode.data = currentNode.data || {};
441+
442+
// Cycle: (none) -> in-progress -> completed -> (none)
319443
const statuses = [null, 'in-progress', 'completed'];
320444
const currentStatus = currentNode.data.status || null;
321445
const currentIndex = statuses.indexOf(currentStatus);
@@ -325,9 +449,9 @@ function updateNodeStatus(nodeObj) {
325449
} else {
326450
currentNode.data.status = next;
327451
}
328-
329-
// Update visual appearance
452+
330453
updateNodeStatus(currentNode);
454+
window.chrome.webview.postMessage({ action: 'mindMapOperation', operationName: 'updateNodeStatus' });
331455
}
332456
});
333457
@@ -441,10 +565,11 @@ function getThemeName(mindElixirData) {
441565
mind.refresh(mindData);
442566
443567
const dataThemeName = getThemeName(mindData);
444-
445568
if (dataThemeName != '' && themeManager.contains(dataThemeName) && dataThemeName != mind.theme?.name) {
446569
mind.changeTheme(themeManager.getTheme(dataThemeName));
447570
}
571+
// Statuses are applied via the debounced linkDiv bus listener
572+
// which fires after MindElixir's layout settles.
448573
449574
return { success: true, error: """" };
450575
} catch (e) {

0 commit comments

Comments
 (0)