From abeed5ae8825c6527849d688961d4e4b9b889557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Mon, 25 May 2026 07:57:42 +0300 Subject: [PATCH] feat: enhance Mermaid diagram interaction with improved activation, hint display, and click-to-zoom functionality --- resources/views/proposals/show.blade.php | 114 ++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/resources/views/proposals/show.blade.php b/resources/views/proposals/show.blade.php index 9163b47..6ccf47a 100644 --- a/resources/views/proposals/show.blade.php +++ b/resources/views/proposals/show.blade.php @@ -255,11 +255,14 @@ .mermaid-canvas { width: 100%; height: 520px; - cursor: grab; + cursor: pointer; position: relative; overflow: hidden; } - .mermaid-canvas:active { cursor: grabbing; } + .mermaid-canvas.mermaid-active { + cursor: grab; + } + .mermaid-canvas.mermaid-active:active { cursor: grabbing; } .mermaid-canvas .mermaid { width: 100%; height: 100%; @@ -267,6 +270,42 @@ .mermaid-canvas svg { display: block; } + .mermaid-container.mermaid-active { + box-shadow: 0 0 0 2px var(--accent-color, #4f46e5); + } + .mermaid-canvas-hint { + position: absolute; + inset: 0; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + background: rgba(248, 250, 252, 0.55); + backdrop-filter: blur(2px); + transition: opacity 0.2s ease; + } + .dark .mermaid-canvas-hint { + background: rgba(15, 23, 42, 0.55); + } + .mermaid-canvas-hint span { + font-size: 0.75rem; + font-weight: 600; + color: #64748b; + background: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(0, 0, 0, 0.08); + padding: 6px 14px; + border-radius: 999px; + } + .dark .mermaid-canvas-hint span { + color: #94a3b8; + background: rgba(30, 41, 59, 0.9); + border-color: rgba(255, 255, 255, 0.08); + } + .mermaid-container.mermaid-active .mermaid-canvas-hint { + opacity: 0; + visibility: hidden; + } /* Fullscreen overlay */ .mermaid-fullscreen-overlay { @@ -944,6 +983,59 @@ // Mermaid zoom state store const mermaidZoomInstances = {}; + let mermaidActiveIndex = null; + + function setMermaidActive(index) { + if (mermaidActiveIndex !== null && mermaidActiveIndex !== index) { + deactivateMermaid(mermaidActiveIndex); + } + mermaidActiveIndex = index; + + const wrapper = document.getElementById('mermaid-wrapper-' + index); + const canvas = document.getElementById('mermaid-canvas-' + index); + const pz = mermaidZoomInstances[index]; + if (!wrapper || !canvas || !pz) return; + + wrapper.classList.add('mermaid-active'); + canvas.classList.add('mermaid-active'); + pz.enablePan(); + } + + function deactivateMermaid(index) { + const wrapper = document.getElementById('mermaid-wrapper-' + index); + const canvas = document.getElementById('mermaid-canvas-' + index); + const pz = mermaidZoomInstances[index]; + if (wrapper) wrapper.classList.remove('mermaid-active'); + if (canvas) canvas.classList.remove('mermaid-active'); + if (pz) pz.disablePan(); + if (mermaidActiveIndex === index) mermaidActiveIndex = null; + } + + function deactivateAllMermaid() { + if (mermaidActiveIndex !== null) { + deactivateMermaid(mermaidActiveIndex); + } + } + + function setupMermaidActivation(index) { + const canvas = document.getElementById('mermaid-canvas-' + index); + if (!canvas || canvas.dataset.activationBound) return; + canvas.dataset.activationBound = '1'; + + canvas.addEventListener('click', function() { + setMermaidActive(index); + }); + + canvas.addEventListener('wheel', function(e) { + if (!canvas.classList.contains('mermaid-active')) return; + e.preventDefault(); + const pz = mermaidZoomInstances[index]; + if (!pz) return; + if (e.deltaY < 0) pz.zoomIn(); + else pz.zoomOut(); + updatePct(index, pz.getZoom()); + }, { passive: false }); + } // Scan code blocks for mermaid and convert them const codeElements = contentDiv.querySelectorAll('pre code.language-mermaid'); @@ -981,6 +1073,10 @@ const canvas = document.createElement('div'); canvas.className = 'mermaid-canvas'; canvas.id = 'mermaid-canvas-' + index; + + const hint = document.createElement('div'); + hint.className = 'mermaid-canvas-hint'; + hint.innerHTML = 'Zoom ve kaydırma için tıklayın'; const graphDiv = document.createElement('div'); graphDiv.className = 'mermaid w-full'; @@ -988,6 +1084,7 @@ graphDiv.textContent = rawMermaid; canvas.appendChild(graphDiv); + canvas.appendChild(hint); wrapper.appendChild(toolbar); wrapper.appendChild(canvas); preContainer.parentNode.replaceChild(wrapper, preContainer); @@ -1021,6 +1118,12 @@ document.body.appendChild(fsOverlay); fsOverlay.addEventListener('click', function(e) { if (e.target === fsOverlay) closeMermaidFullscreen(); }); + document.addEventListener('click', function(e) { + if (!e.target.closest('.mermaid-container')) { + deactivateAllMermaid(); + } + }); + // Run mermaid parser setTimeout(() => { mermaid.run().then(() => { @@ -1071,18 +1174,21 @@ try { const panZoom = svgPanZoom('#' + svg.id, { zoomEnabled: true, + panEnabled: false, controlIconsEnabled: false, fit: true, center: true, minZoom: 0.3, maxZoom: 8, zoomScaleSensitivity: 0.3, + mouseWheelZoomEnabled: false, onZoom: (zoom) => { const pctEl = document.getElementById('mermaid-pct-' + index); if (pctEl) pctEl.textContent = Math.round(zoom * 100) + '%'; } }); mermaidZoomInstances[index] = panZoom; + setupMermaidActivation(index); } catch(e) { console.warn('svg-pan-zoom init failed for mermaid-' + index, e); } @@ -1090,14 +1196,17 @@ // Zoom controls function mermaidZoomIn(index) { + setMermaidActive(index); const pz = mermaidZoomInstances[index]; if (pz) { pz.zoomIn(); updatePct(index, pz.getZoom()); } } function mermaidZoomOut(index) { + setMermaidActive(index); const pz = mermaidZoomInstances[index]; if (pz) { pz.zoomOut(); updatePct(index, pz.getZoom()); } } function mermaidZoomReset(index) { + setMermaidActive(index); const pz = mermaidZoomInstances[index]; if (pz) { pz.resetZoom(); pz.center(); updatePct(index, pz.getZoom()); } } @@ -1185,6 +1294,7 @@ } async function mermaidFullscreen(index) { + deactivateAllMermaid(); const wrapper = document.getElementById('mermaid-wrapper-' + index); if (!wrapper) return; const rawMermaid = wrapper.dataset.mermaidSource;