From 340c7e71956c8311494a922109d37aac9474cc45 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Tue, 21 Jul 2026 13:03:15 +0200 Subject: [PATCH] =?UTF-8?q?frontend:=20Zweiter=20Download-Button=20f=C3=BC?= =?UTF-8?q?r=20PNG=20(neben=20SVG)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LibreOffice liest weder das Browser-Bild-Clipboard noch das SVG zuverlässig. Neben dem SVG-Download gibt es daher jetzt einen zweiten Button, der das Diagramm als PNG-Datei (werkbaum-diagramm.png, Raster) speichert — überall per "Bild einfügen" importierbar. Beide Download-Buttons tragen ein Format-Label (SVG/PNG). Tooltip in allen neun Sprachen; SPEC §9 ergänzt. Co-Authored-By: Claude Opus 4.8 --- docs/SPEC.md | 11 ++++++----- frontend/index.html | 46 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/docs/SPEC.md b/docs/SPEC.md index faa3732..756084b 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -142,11 +142,12 @@ Das Diagramm wird aus der Live-Geometrie in ein eigenständiges SVG (nur Formen geschrieben: `image/png` (das eigentliche Bild) und `text/html` mit eingebettetem PNG. Fällt der Bild-Clipboard ganz aus (fehlende `ClipboardItem`-Unterstützung), wird der **SVG-Quelltext** kopiert. -- **Herunterladen** — als **SVG-Datei** (`werkbaum-diagramm.svg`, Vektor). - Verlässlicher Weg für Programme, die das Browser-Bild-Clipboard nicht - erkennen (z. B. LibreOffice Writer: Einfügen → Bild → Datei). Das - Browser-Clipboard erlaubt nur `image/png`/Text als Bildtypen, kein SVG — - daher der Datei-Weg für Vektor. +- **Herunterladen** — als Datei, zwei Schaltflächen mit Format-Label: + **SVG** (`werkbaum-diagramm.svg`, Vektor) und **PNG** + (`werkbaum-diagramm.png`, Raster). Der Datei-Weg ist der verlässliche Weg + für Programme, die das Browser-Bild-Clipboard nicht erkennen. Manche + Programme lesen auch das SVG nicht (z. B. LibreOffice) — dafür gibt es die + PNG-Datei, die überall per „Bild einfügen“ importierbar ist. - Übernommen werden Knotenfarben (Status §4), Größen-Badge, Tags und der Geister-Knoten; die Verbindungslinien werden je Gate neu gezogen (und = durchgezogen Tinte, oder = gestrichelt Grau) und treffen die Knoten diff --git a/frontend/index.html b/frontend/index.html index faf0f72..6547f68 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -247,6 +247,8 @@ .copybtn .ic-done{display:none} .copybtn.done .ic-copy{display:none} .copybtn.done .ic-done{display:block} + .dlbtn{gap:3px} + .dl-label{font-family:'IBM Plex Mono',monospace;font-size:.62rem;font-weight:600;letter-spacing:.02em} textarea{ display:block;width:100%;min-height:0; border:0;outline:none;resize:none; @@ -572,8 +574,11 @@ - +
@@ -869,15 +874,23 @@ async function copyDiagramImage(){ /* Als Datei speichern (SVG, Vektor). Verlässlicher Weg z. B. für LibreOffice Writer: Einfügen → Bild → die Datei; das Bild-Clipboard aus dem Browser erkennt LibreOffice nicht zuverlässig. */ -function downloadDiagramSvg(){ - const {svg} = diagramToSvg(); - const blob = new Blob([`\n` + svg], {type:'image/svg+xml'}); +function saveBlob(blob, filename){ const url = URL.createObjectURL(blob); const a = document.createElement('a'); - a.href = url; a.download = 'werkbaum-diagramm.svg'; + a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); } +function downloadDiagramSvg(){ + const {svg} = diagramToSvg(); + saveBlob(new Blob([`\n` + svg], {type:'image/svg+xml'}), + 'werkbaum-diagramm.svg'); +} +async function downloadDiagramPng(){ + const {svg, W, H} = diagramToSvg(); + const png = await svgToPng(svg, W, H, 2); + if(png && png.blob) saveBlob(png.blob, 'werkbaum-diagramm.png'); +} /* Tab-Taste rückt ein statt den Fokus zu wechseln */ src.addEventListener('keydown', e => { @@ -1044,11 +1057,17 @@ document.getElementById('copyDiagram').addEventListener('click', async () => { await copyDiagramImage(); flashCopied(document.getElementById('copyDiagram'), 'copyDiagramTooltip'); }); +function flashBtn(btn){ /* kurzer Petrol-Blitz als Rückmeldung, Tooltip bleibt */ + btn.classList.add('done'); + setTimeout(() => btn.classList.remove('done'), 1500); +} document.getElementById('dlDiagram').addEventListener('click', () => { downloadDiagramSvg(); - const b = document.getElementById('dlDiagram'); /* kurzer Petrol-Blitz, Tooltip bleibt */ - b.classList.add('done'); - setTimeout(() => b.classList.remove('done'), 1500); + flashBtn(document.getElementById('dlDiagram')); +}); +document.getElementById('dlDiagramPng').addEventListener('click', async () => { + await downloadDiagramPng(); + flashBtn(document.getElementById('dlDiagramPng')); }); /* ---------- Internationalisierung (DE/EN/ES/FR) ---------- */ @@ -1063,6 +1082,7 @@ const I18N = { copy:"kopieren", copyDone:"kopiert ✓", copyTooltip:"Text in die Zwischenablage kopieren", copyDiagramTooltip:"Diagramm als PNG-Bild in die Zwischenablage kopieren", downloadDiagramTooltip:"Diagramm als SVG-Datei herunterladen (z. B. für LibreOffice: Einfügen → Bild)", + downloadPngTooltip:"Diagramm als PNG-Datei herunterladen (Rasterbild, überall einfügbar)", minimize:"minimieren", normal:"normal", maximize:"maximieren", agenda:"Agenda", discarded:"verworfene", modeHorizontal:"Horizontal – Organigramm, Diagramm über dem Editor", @@ -1093,6 +1113,7 @@ const I18N = { copy:"copy", copyDone:"copied ✓", copyTooltip:"Copy text to clipboard", copyDiagramTooltip:"Copy diagram as a PNG image to the clipboard", downloadDiagramTooltip:"Download diagram as an SVG file (e.g. for LibreOffice: Insert → Image)", + downloadPngTooltip:"Download diagram as a PNG file (raster image, insertable anywhere)", minimize:"minimize", normal:"normal", maximize:"maximize", agenda:"Legend", discarded:"discarded", modeHorizontal:"Horizontal – org chart, diagram above the editor", @@ -1123,6 +1144,7 @@ const I18N = { copy:"copiar", copyDone:"copiado ✓", copyTooltip:"Copiar el texto al portapapeles", copyDiagramTooltip:"Copiar el diagrama como imagen PNG al portapapeles", downloadDiagramTooltip:"Descargar el diagrama como archivo SVG (p. ej. para LibreOffice: Insertar → Imagen)", + downloadPngTooltip:"Descargar el diagrama como archivo PNG (imagen de trama, insertable en cualquier lugar)", minimize:"minimizar", normal:"normal", maximize:"maximizar", agenda:"Leyenda", discarded:"descartados", modeHorizontal:"Horizontal – organigrama, diagrama sobre el editor", @@ -1153,6 +1175,7 @@ const I18N = { copy:"copier", copyDone:"copié ✓", copyTooltip:"Copier le texte dans le presse-papiers", copyDiagramTooltip:"Copier le diagramme comme image PNG dans le presse-papiers", downloadDiagramTooltip:"Télécharger le diagramme en fichier SVG (p. ex. pour LibreOffice : Insertion → Image)", + downloadPngTooltip:"Télécharger le diagramme en fichier PNG (image matricielle, insérable partout)", minimize:"réduire", normal:"normal", maximize:"agrandir", agenda:"Légende", discarded:"abandonnés", modeHorizontal:"Horizontal – organigramme, diagramme au-dessus de l'éditeur", @@ -1183,6 +1206,7 @@ const I18N = { copy:"kopiuj", copyDone:"skopiowano ✓", copyTooltip:"Kopiuj tekst do schowka", copyDiagramTooltip:"Kopiuj diagram jako obraz PNG do schowka", downloadDiagramTooltip:"Pobierz diagram jako plik SVG (np. dla LibreOffice: Wstaw → Obraz)", + downloadPngTooltip:"Pobierz diagram jako plik PNG (obraz rastrowy, wszędzie do wstawienia)", minimize:"minimalizuj", normal:"normalny", maximize:"maksymalizuj", agenda:"Legenda", discarded:"odrzucone", modeHorizontal:"Poziomo – schemat organizacyjny, diagram nad edytorem", @@ -1213,6 +1237,7 @@ const I18N = { copy:"копировать", copyDone:"скопировано ✓", copyTooltip:"Скопировать текст в буфер обмена", copyDiagramTooltip:"Скопировать диаграмму как изображение PNG в буфер обмена", downloadDiagramTooltip:"Скачать диаграмму как файл SVG (напр. для LibreOffice: Вставка → Изображение)", + downloadPngTooltip:"Скачать диаграмму как файл PNG (растровое изображение, вставляется везде)", minimize:"свернуть", normal:"обычный", maximize:"развернуть", agenda:"Легенда", discarded:"отклонённые", modeHorizontal:"Горизонтально – оргсхема, диаграмма над редактором", @@ -1243,6 +1268,7 @@ const I18N = { copy:"कॉपी करें", copyDone:"कॉपी हो गया ✓", copyTooltip:"टेक्स्ट को क्लिपबोर्ड पर कॉपी करें", copyDiagramTooltip:"आरेख को PNG छवि के रूप में क्लिपबोर्ड पर कॉपी करें", downloadDiagramTooltip:"आरेख को SVG फ़ाइल के रूप में डाउनलोड करें (जैसे LibreOffice: सम्मिलित करें → छवि)", + downloadPngTooltip:"आरेख को PNG फ़ाइल के रूप में डाउनलोड करें (रास्टर छवि, कहीं भी सम्मिलित करने योग्य)", minimize:"छोटा करें", normal:"सामान्य", maximize:"बड़ा करें", agenda:"लेजेंड", discarded:"अस्वीकृत", modeHorizontal:"क्षैतिज – संगठन-चार्ट, संपादक के ऊपर आरेख", @@ -1273,6 +1299,7 @@ const I18N = { copy:"复制", copyDone:"已复制 ✓", copyTooltip:"将文本复制到剪贴板", copyDiagramTooltip:"将图表作为 PNG 图片复制到剪贴板", downloadDiagramTooltip:"将图表下载为 SVG 文件(例如用于 LibreOffice:插入 → 图像)", + downloadPngTooltip:"将图表下载为 PNG 文件(位图,可在任何地方插入)", minimize:"最小化", normal:"正常", maximize:"最大化", agenda:"图例", discarded:"已放弃", modeHorizontal:"横向——组织结构图,图表在编辑器上方", @@ -1303,6 +1330,7 @@ const I18N = { copy:"コピー", copyDone:"コピーしました ✓", copyTooltip:"テキストをクリップボードにコピー", copyDiagramTooltip:"ダイアグラムを PNG 画像としてクリップボードにコピー", downloadDiagramTooltip:"ダイアグラムを SVG ファイルとしてダウンロード(例:LibreOffice の 挿入 → 画像)", + downloadPngTooltip:"ダイアグラムを PNG ファイルとしてダウンロード(ラスター画像、どこにでも挿入可能)", minimize:"最小化", normal:"標準", maximize:"最大化", agenda:"凡例", discarded:"破棄", modeHorizontal:"横 — 組織図、ダイアグラムはエディターの上",