feat: add zoom controls for Mermaid diagrams and improve diagram interaction

This commit is contained in:
Achintha Isuru
2026-02-06 15:57:29 -05:00
parent 348044b1c3
commit 682a425d0d
2 changed files with 120 additions and 4 deletions

View File

@@ -149,14 +149,57 @@
/* Mermaid diagram styling */ /* Mermaid diagram styling */
.mermaid-diagram-wrapper { .mermaid-diagram-wrapper {
position: relative;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
overflow: hidden;
cursor: grab;
} }
.mermaid-diagram-wrapper:active {
cursor: grabbing;
}
.mermaid-diagram-wrapper svg { .mermaid-diagram-wrapper svg {
max-width: 100%; max-width: none;
height: auto; height: auto;
} }
.mermaid-zoom-controls {
position: absolute;
top: 0.5rem;
right: 0.5rem;
display: flex;
gap: 0.25rem;
background: white;
border-radius: 0.5rem;
padding: 0.25rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 10;
}
.mermaid-zoom-btn {
padding: 0.375rem;
background: white;
border: 1px solid #e5e7eb;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.mermaid-zoom-btn:hover {
background-color: #f3f4f6;
}
.mermaid-zoom-btn svg {
width: 1rem;
height: 1rem;
color: #4b5563;
}
/* Loading Overlay */ /* Loading Overlay */
#auth-loading { #auth-loading {
@@ -494,6 +537,9 @@
let panzoomInstance = null; let panzoomInstance = null;
let currentScale = 1; let currentScale = 1;
// Track mermaid diagram panzoom instances
const mermaidPanzoomInstances = new Map();
// Initialize Mermaid // Initialize Mermaid
mermaid.initialize({ mermaid.initialize({
@@ -706,6 +752,63 @@
activeLink.classList.remove('text-gray-700'); activeLink.classList.remove('text-gray-700');
activeLink.classList.add('bg-primary-50', 'border', 'border-primary-200', 'text-primary-700'); activeLink.classList.add('bg-primary-50', 'border', 'border-primary-200', 'text-primary-700');
} }
// Create zoom controls for mermaid diagrams
function createMermaidZoomControls(wrapper, svgElement, diagramId) {
const controls = document.createElement('div');
controls.className = 'mermaid-zoom-controls';
controls.innerHTML = `
<button class="mermaid-zoom-btn" data-action="zoom-in" title="Zoom In">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7"></path>
</svg>
</button>
<button class="mermaid-zoom-btn" data-action="zoom-out" title="Zoom Out">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"></path>
</svg>
</button>
<button class="mermaid-zoom-btn" data-action="reset" title="Reset View">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
</button>
`;
wrapper.appendChild(controls);
// Wait for next frame to ensure DOM is fully updated before initializing Panzoom
requestAnimationFrame(() => {
// Initialize panzoom for this mermaid diagram
const panzoom = Panzoom(svgElement, {
canvas: true,
maxScale: 10,
minScale: 0.3,
startScale: 1
});
mermaidPanzoomInstances.set(diagramId, panzoom);
// Add event listeners
controls.querySelector('[data-action="zoom-in"]').addEventListener('click', (e) => {
e.stopPropagation();
panzoom.zoomIn();
});
controls.querySelector('[data-action="zoom-out"]').addEventListener('click', (e) => {
e.stopPropagation();
panzoom.zoomOut();
});
controls.querySelector('[data-action="reset"]').addEventListener('click', (e) => {
e.stopPropagation();
panzoom.reset();
});
// Add wheel zoom
wrapper.addEventListener('wheel', panzoom.zoomWithWheel);
});
}
async function showView(viewName, navLink, filePath, title, type = 'svg') { async function showView(viewName, navLink, filePath, title, type = 'svg') {
setActiveNav(navLink); setActiveNav(navLink);
@@ -713,6 +816,11 @@
panzoomInstance.destroy(); panzoomInstance.destroy();
panzoomInstance = null; panzoomInstance = null;
} }
// Clean up mermaid panzoom instances when switching views
mermaidPanzoomInstances.forEach(instance => instance.destroy());
mermaidPanzoomInstances.clear();
diagramContainer.innerHTML = ''; diagramContainer.innerHTML = '';
documentContainer.innerHTML = ''; documentContainer.innerHTML = '';
// Reset iframe // Reset iframe
@@ -841,10 +949,18 @@
const pre = block.parentElement; const pre = block.parentElement;
try { try {
const { svg } = await mermaid.render(`mermaid-doc-${Date.now()}-${i}`, mermaidCode); const diagramId = `mermaid-doc-${Date.now()}-${i}`;
const { svg } = await mermaid.render(diagramId, mermaidCode);
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.className = 'mermaid-diagram-wrapper bg-white p-4 rounded-lg border border-gray-200 my-4 overflow-x-auto'; wrapper.className = 'mermaid-diagram-wrapper bg-white p-4 rounded-lg border border-gray-200 my-4';
wrapper.innerHTML = svg; wrapper.innerHTML = svg;
const svgElement = wrapper.querySelector('svg');
if (svgElement) {
// Add zoom controls for this mermaid diagram
createMermaidZoomControls(wrapper, svgElement, diagramId);
}
pre.replaceWith(wrapper); pre.replaceWith(wrapper);
} catch (err) { } catch (err) {
console.error('Mermaid rendering error:', err); console.error('Mermaid rendering error:', err);

View File

@@ -2,7 +2,7 @@
.PHONY: launchpad-dev deploy-launchpad-hosting .PHONY: launchpad-dev deploy-launchpad-hosting
launchpad-dev: sync-prototypes launchpad-dev:
@echo "--> Starting local Launchpad server using Firebase Hosting emulator..." @echo "--> Starting local Launchpad server using Firebase Hosting emulator..."
@echo " - Generating secure email hashes..." @echo " - Generating secure email hashes..."
@node scripts/generate-allowed-hashes.js @node scripts/generate-allowed-hashes.js