From b79e32fc2024f224d7cd0d0d7c79d91633f05a20 Mon Sep 17 00:00:00 2001 From: Dicken Date: Fri, 5 Jun 2026 00:48:37 +0200 Subject: [PATCH] Fix: CAD-Skizzen Baustellenschild zurueck, Key-Gen wieder in TOOL_DEFS, QR-Code browser-kompatibel --- frontend/src/toolRegistry.js | 4 +- frontend/src/tools/devtools.jsx | 110 ++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 43 deletions(-) diff --git a/frontend/src/toolRegistry.js b/frontend/src/toolRegistry.js index 13b9ce3..439866f 100644 --- a/frontend/src/toolRegistry.js +++ b/frontend/src/toolRegistry.js @@ -68,8 +68,8 @@ export const TOOLS = [ { id: 'skizze', Icon: SkizzeIcon, - label: 'CAD-Skizzen', - navLabel: 'CAD-Skizzen', + label: 'CAD-Skizzen 🚧', + navLabel: 'CAD-Skizzen 🚧', group: 'Werkzeuge', component: Skizze, }, diff --git a/frontend/src/tools/devtools.jsx b/frontend/src/tools/devtools.jsx index 3024597..1c9f6d5 100644 --- a/frontend/src/tools/devtools.jsx +++ b/frontend/src/tools/devtools.jsx @@ -2701,69 +2701,96 @@ const CAP_POS = [ function drawQrToCanvas(canvas, data, opts) { const { size, fgColor, bgColor, margin, dotStyle, cornerStyle, caption, captionPos, captionColor, captionSize } = opts; - const capH = caption && captionPos !== 'none' ? (captionSize + 16) : 0; - const totalH = size + capH; + const capH = caption && captionPos !== 'none' ? (Number(captionSize) + 16) : 0; + const totalH = size + capH; canvas.width = size; canvas.height = totalH; const ctx = canvas.getContext('2d'); - ctx.fillStyle = bgColor; - ctx.fillRect(0, 0, size, totalH); - return QRCode.create(data, { errorCorrectionLevel: 'M' }).then(qr => { - const modules = qr.modules; - const n = modules.size; - const cellSize = (size - margin * 2) / n; - const qrTop = captionPos === 'top' ? capH : 0; + // Step 1: generate QR to temporary canvas (black on white, no margin) + const tmp = document.createElement('canvas'); + return QRCode.toCanvas(tmp, data, { + width: size, + margin: 0, + color: { dark: '#000000ff', light: '#ffffffff' }, + errorCorrectionLevel: 'M', + }).then(() => { + // Step 2: read pixel data to detect module grid + const tctx = tmp.getContext('2d'); + const pixels = tctx.getImageData(0, 0, size, size).data; + + // Find module size by scanning first dark run + let firstDark = -1, runEnd = -1; + for (let x = 0; x < size; x++) { + const idx = x * 4; + const dark = pixels[idx] < 128; + if (dark && firstDark < 0) firstDark = x; + if (!dark && firstDark >= 0 && runEnd < 0) { runEnd = x; break; } + } + const moduleSize = (runEnd > 0 && firstDark >= 0) ? (runEnd - firstDark) : Math.round(size / 25); + const n = Math.round(size / moduleSize); + + // Step 3: draw background + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, size, totalH); + + const qrTop = captionPos === 'top' ? capH : 0; + const pad = Math.round(margin * moduleSize); + const cellSize = (size - pad * 2) / n; ctx.fillStyle = fgColor; for (let row = 0; row < n; row++) { for (let col = 0; col < n; col++) { - if (!modules.get(row, col)) continue; - const x = margin + col * cellSize; - const y = qrTop + margin + row * cellSize; + // Sample the center pixel of this module from the temp canvas + const px = Math.round((col + 0.5) * moduleSize); + const py = Math.round((row + 0.5) * moduleSize); + const idx = (py * size + px) * 4; + if (pixels[idx] > 128) continue; // light module + + const x = pad + col * cellSize; + const y = qrTop + pad + row * cellSize; const w = cellSize; - // Corner squares (finder patterns): rows/cols 0-7 and last 7 - const isCorner = (row < 7 && col < 7) || (row < 7 && col >= n-7) || (row >= n-7 && col < 7); + // Finder pattern corners: rows/cols 0-6 and last 6 + const isCorner = (row < 7 && col < 7) || (row < 7 && col >= n - 7) || (row >= n - 7 && col < 7); - if (isCorner) { - if (cornerStyle === 'dot') { - const r = w * 0.5; - ctx.beginPath(); ctx.arc(x+r, y+r, r, 0, Math.PI*2); ctx.fill(); - } else if (cornerStyle === 'rounded') { - const r = w * 0.35; - ctx.beginPath(); ctx.roundRect(x, y, w, w, r); ctx.fill(); - } else { - ctx.fillRect(x, y, w, w); - } + const style = isCorner ? cornerStyle : dotStyle; + + if (style === 'dots') { + const r = w * 0.45; + ctx.beginPath(); ctx.arc(x + w/2, y + w/2, r, 0, Math.PI * 2); ctx.fill(); + } else if (style === 'rounded' || style === 'classy') { + const r = style === 'classy' ? w * 0.42 : w * 0.32; + ctx.beginPath(); ctx.roundRect(x + 0.5, y + 0.5, w - 1, w - 1, r); ctx.fill(); + } else if (style === 'dot') { + const r = w * 0.45; + ctx.beginPath(); ctx.arc(x + w/2, y + w/2, r, 0, Math.PI * 2); ctx.fill(); } else { - if (dotStyle === 'dots') { - const r = w * 0.45; - ctx.beginPath(); ctx.arc(x+r, y+r, r, 0, Math.PI*2); ctx.fill(); - } else if (dotStyle === 'rounded') { - const r = w * 0.3; - ctx.beginPath(); ctx.roundRect(x, y, w, w, r); ctx.fill(); - } else if (dotStyle === 'classy') { - const r = w * 0.4; - ctx.beginPath(); ctx.roundRect(x+1, y+1, w-2, w-2, r); ctx.fill(); - } else { - ctx.fillRect(x, y, w, w); - } + ctx.fillRect(x, y, w, w); } } } - // Caption + // Step 4: caption if (caption && captionPos !== 'none') { - ctx.fillStyle = captionColor; - ctx.font = `${captionSize}px sans-serif`; - ctx.textAlign = 'center'; + ctx.fillStyle = captionColor; + ctx.font = `${captionSize}px sans-serif`; + ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; const cy = captionPos === 'top' ? capH / 2 : size + capH / 2; ctx.fillText(caption, size / 2, cy, size - 16); } - }).catch(() => {}); + }).catch(err => { + // Show error message on canvas + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, size, totalH); + ctx.fillStyle = '#ff6b9d'; + ctx.font = '13px monospace'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('Ungültige URL', size/2, size/2); + }); } function QrGenerator({ toast, mobile }) { @@ -3086,6 +3113,7 @@ const TOOL_DEFS = [ {id:'netzwerk', label:'🌐 Netzwerk', desc:'IP, Subnetz, Geschwindigkeit, CIDR', ready:true, component:NetzwerkRechner}, {id:'datetime', label:'📅 Datetime', desc:'Zeiten umrechnen · Geburtstag · Differenz', ready:true, component:DatetimeRechner}, {id:'farben', label:'🎨 Farben', desc:'HEX, RGB, HSL, OKLCH umrechnen', ready:true, component:FarbRechner}, + {id:'keygen', label:'🔐 Key-Gen', desc:'Sichere Keys & Secrets generieren', ready:true, component:KeyGen}, {id:'qrcode', label:'◻ QR-Code', desc:'QR-Codes erstellen & verwalten', ready:true, component:QrGenerator}, ];