This commit is contained in:
Tomas Dvorak
2025-11-21 08:44:44 +01:00
parent c941313fd5
commit f5b6f83974
108 changed files with 8642 additions and 5871 deletions
+31 -19
View File
@@ -20,7 +20,7 @@
.btn.primary{background:var(--primary);border-color:var(--primary);color:#fff}
.btn.ghost{background:transparent}
main{padding:16px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:16px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(640px,1fr));gap:16px}
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
.card header{display:flex;align-items:center;gap:8px;justify-content:space-between;background:#0f131f;border-bottom:1px solid var(--border);padding:10px 12px;position:static}
.title{display:flex;flex-direction:column;gap:4px}
@@ -39,14 +39,15 @@
</style>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({ startOnLoad:false, securityLevel:'loose', theme:'dark', flowchart:{ curve:'basis', useMaxWidth:true } });
mermaid.initialize({ startOnLoad:false, securityLevel:'loose', theme:'dark', flowchart:{ curve:'linear', useMaxWidth:true } });
async function renderMermaidFile(mmdPath, container){
try{
container.innerHTML = '<div style="padding:16px;color:#9aa3b2">Loading '+mmdPath+'…</div>';
const res = await fetch(mmdPath, { cache: 'no-store' });
const res = await fetch(mmdPath + '?v=' + Date.now(), { cache: 'no-store' });
if(!res.ok) throw new Error('Failed to load '+mmdPath+': '+res.status);
const code = await res.text();
const raw = await res.text();
const code = raw.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
const id = 'm-'+Math.random().toString(36).slice(2);
const { svg } = await mermaid.render(id, code);
container.innerHTML = svg;
@@ -82,10 +83,13 @@
if(!source.match(/^<svg[^>]+xmlns=/)) source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
source = '<?xml version="1.0" standalone="no"?>\n'+source;
// Inject white background and readable styles for new tab view
const firstGt = source.indexOf('>');
if(firstGt > 0){
const inject = '<rect width="100%" height="100%" fill="#ffffff"/><style>text{fill:#111827}.edgePath path,.flowchart-link{stroke:#334155}</style>';
source = source.slice(0, firstGt+1) + inject + source.slice(firstGt+1);
const svgStart = source.indexOf('<svg');
if(svgStart !== -1){
const svgTagEnd = source.indexOf('>', svgStart);
if(svgTagEnd !== -1){
const inject = '<rect width="100%" height="100%" fill="#ffffff"/><style>text{fill:#111827}.edgePath path,.flowchart-link{stroke:#334155}</style>';
source = source.slice(0, svgTagEnd+1) + inject + source.slice(svgTagEnd+1);
}
}
const blob = new Blob([source], { type:'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(blob);
@@ -95,8 +99,7 @@
const ALL_DIAGRAMS = [
// System & DB
{ id:'system-clean', label:'System Overview (Clean)', file:'system-overall-clean.mmd', cat:'System', tags:['overview','recommended','big'] },
{ id:'system', label:'System Overview (Classic)', file:'system-overall.mmd', cat:'System', tags:['overview','big'], defaultWires:'faint' },
{ id:'system-clean', label:'System Overview', file:'system-overall-clean.mmd', cat:'System', tags:['overview','recommended','big'] },
{ id:'db-er', label:'Database ER', file:'db-er.mmd', cat:'System', tags:['db'] },
{ id:'db-models', label:'Database Models', file:'db-models.mmd', cat:'System', tags:['db'] },
// Backend
@@ -107,15 +110,13 @@
{ id:'auth', label:'Auth Flow', file:'auth-flow.mmd', cat:'Backend', tags:['auth','flow'] },
{ id:'err-flow', label:'Error Tracking Flow', file:'error-tracking-flow.mmd', cat:'Backend', tags:['errors','flow'] },
// Frontend
{ id:'fe-everything', label:'Frontend — Everything (Big)', file:'frontend-everything.mmd', cat:'Frontend', tags:['overview','big'], defaultWires:'faint' },
{ id:'fe-overall', label:'Frontend — Overall', file:'frontend-overall.mmd', cat:'Frontend', tags:['architecture'] },
{ id:'fe-everything', label:'Frontend — Everything (Big)', file:'frontend-everything.mmd', cat:'Frontend', tags:['overview','big','recommended'], defaultWires:'faint' },
{ id:'fe-overall', label:'Frontend — Overall', file:'frontend-overall.mmd', cat:'Frontend', tags:['architecture','recommended'] },
{ id:'fe-routes', label:'Frontend — Routes', file:'frontend-routes.mmd', cat:'Frontend', tags:['routes'] },
{ id:'fe-home', label:'Frontend — Homepage', file:'frontend-homepage.mmd', cat:'Frontend', tags:['homepage'] },
{ id:'fe-modules', label:'Frontend — Modules', file:'frontend-modules.mmd', cat:'Frontend', tags:['modules'] },
{ id:'fe-arch', label:'Frontend — Provider Tree', file:'frontend-architecture.mmd', cat:'Frontend', tags:['providers'] },
{ id:'fe-api', label:'Frontend — API Map', file:'frontend-api-map.mmd', cat:'Frontend', tags:['api'] },
// Admin
{ id:'admin-overall', label:'Admin — Overall', file:'admin-overall.mmd', cat:'Admin', tags:['admin','overview'], defaultWires:'faint' },
{ id:'admin-overall', label:'Admin — Overall', file:'admin-overall.mmd', cat:'Admin', tags:['admin','overview','recommended'], defaultWires:'faint' },
{ id:'scoreboard', label:'Scoreboard Flow', file:'scoreboard-flow.mmd', cat:'Admin', tags:['scoreboard','flow'] },
{ id:'newsletter', label:'Newsletter Flow', file:'newsletter-flow.mmd', cat:'Admin', tags:['newsletter','flow'] },
{ id:'comments', label:'Comments Flow', file:'comments-flow.mmd', cat:'Admin', tags:['comments','flow'] },
@@ -145,7 +146,8 @@
const tb = document.createElement('div'); tb.className='toolbar';
tb.innerHTML = `
<label><input type="checkbox" class="fit" checked> Fit width</label>
<label style="display:inline-flex;align-items:center;gap:8px"><input type="checkbox" class="fit" checked> Fit width</label>
<label style="display:inline-flex;align-items:center;gap:6px">Zoom <input class="zoom" type="range" min="50" max="300" value="100" style="width:140px"></label>
<a class="btn ghost src" href="${d.file}" target="_blank">Source</a>
<span class="sp"></span>
<button class="btn open">Open SVG in new tab</button>
@@ -161,9 +163,17 @@
const svg = container?.querySelector('svg');
if(!svg) return;
const fit = card.querySelector('.fit');
if(fit && fit.checked){ svg.style.width='100%'; svg.style.height='auto'; } else { svg.style.width=''; svg.style.height=''; }
svg.style.transformOrigin = '';
svg.style.transform = '';
const zoom = card.querySelector('.zoom');
if(fit && fit.checked){
svg.style.width='100%'; svg.style.height='auto';
svg.style.transformOrigin = '';
svg.style.transform = '';
} else {
svg.style.width=''; svg.style.height='';
const z = Math.max(50, Math.min(300, parseInt(zoom?.value || '100', 10)));
svg.style.transformOrigin = 'top left';
svg.style.transform = 'scale('+(z/100)+')';
}
}
function wireCardControls(card, file){
@@ -173,7 +183,9 @@
const openBtn = card.querySelector('.open');
const refresh = card.querySelector('.refresh');
const download = card.querySelector('.download');
const zoom = card.querySelector('.zoom');
fit.addEventListener('change', () => applyFitZoomFor(card));
zoom.addEventListener('input', () => applyFitZoomFor(card));
openBtn.addEventListener('click', () => openSVGInNewTab(diag));
refresh.addEventListener('click', async () => { diag.dataset.rendered=''; await renderMermaidFile(file, diag); diag.dataset.rendered='1'; applyFitZoomFor(card); });
download.addEventListener('click', () => downloadSVGOf(diag, (file.replace('.mmd','')||'diagram')+'.svg'));