diff --git a/internal/launchpad/index.html b/internal/launchpad/index.html index c8dbdcc2..8c72bb43 100644 --- a/internal/launchpad/index.html +++ b/internal/launchpad/index.html @@ -149,14 +149,57 @@ /* Mermaid diagram styling */ .mermaid-diagram-wrapper { + position: relative; display: flex; justify-content: center; align-items: center; + overflow: hidden; + cursor: grab; } + + .mermaid-diagram-wrapper:active { + cursor: grabbing; + } + .mermaid-diagram-wrapper svg { - max-width: 100%; + max-width: none; 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 */ #auth-loading { @@ -494,6 +537,9 @@ let panzoomInstance = null; let currentScale = 1; + + // Track mermaid diagram panzoom instances + const mermaidPanzoomInstances = new Map(); // Initialize Mermaid mermaid.initialize({ @@ -706,6 +752,63 @@ activeLink.classList.remove('text-gray-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 = ` + + + + `; + + 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') { setActiveNav(navLink); @@ -713,6 +816,11 @@ panzoomInstance.destroy(); panzoomInstance = null; } + + // Clean up mermaid panzoom instances when switching views + mermaidPanzoomInstances.forEach(instance => instance.destroy()); + mermaidPanzoomInstances.clear(); + diagramContainer.innerHTML = ''; documentContainer.innerHTML = ''; // Reset iframe @@ -841,10 +949,18 @@ const pre = block.parentElement; 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'); - 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; + + const svgElement = wrapper.querySelector('svg'); + if (svgElement) { + // Add zoom controls for this mermaid diagram + createMermaidZoomControls(wrapper, svgElement, diagramId); + } + pre.replaceWith(wrapper); } catch (err) { console.error('Mermaid rendering error:', err); diff --git a/makefiles/launchpad.mk b/makefiles/launchpad.mk index 87050e19..4dff436f 100644 --- a/makefiles/launchpad.mk +++ b/makefiles/launchpad.mk @@ -2,7 +2,7 @@ .PHONY: launchpad-dev deploy-launchpad-hosting -launchpad-dev: sync-prototypes +launchpad-dev: @echo "--> Starting local Launchpad server using Firebase Hosting emulator..." @echo " - Generating secure email hashes..." @node scripts/generate-allowed-hashes.js