Bootstrap lokal einbetten und Stil der AG anlegen (#27)

- werkzeuge/bootstrap/: Bootstrap 5.3.8 (CSS, JS-Bundle) und Bootstrap
  Icons 1.13.1 (CSS, woff2/woff) byte-gleich aus den npm-Paketen,
  Integrität geprüft; README mit Version, Quelle, Integrität, Lizenz
  und Aktualisierung; Lizenzen (MIT) daneben; .gitattributes: binary,
  die README bleibt Text mit Diff.
- werkzeuge/stil/ag_prozesse.css: eigene kleine Stilschicht (ag-*):
  Seite in voller Höhe, dunkle Navigationsleiste, hellgrauer
  Hintergrund, Karten mit farbigem Rand, Tabellenziffern, Druck.
  Vorbild VLN_SN/EA-Bilanz, nichts übernommen.
- Vorschau: Kopfzeile als dunkle Navigationsleiste mit Icon-Knöpfen;
  JavaScript, IDs und Zeichenfläche (Wasserzeichen) unverändert.
- scripts/vorschau.py bettet Bootstrap, Icons und Stil ein und setzt die
  Icon-Schrift als data:-URI ein (_schriften_einbetten); ein nicht
  einbettbarer Schriftverweis bricht laut ab.
- Tests: drei neue in test_vorschau.py; der Aufbau von
  test_bibliothek_mit_script_ende_wird_abgelehnt kopiert jetzt ganz
  werkzeuge/ (Erwartung unverändert).
- AGENTS.md: eingebettete Fremdbibliotheken sind bpmn-js und Bootstrap.
- Tests: python -m pytest -q -> 256 bestanden, 1 übersprungen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 20:11:29 +02:00
co-authored by Claude Opus 5.5
parent 73a7f41492
commit b341a658bc
17 changed files with 248 additions and 58 deletions
+4 -1
View File
@@ -12,5 +12,8 @@
*.docx binary
*.vsdx binary
# Eingebettete Fremdbibliothek (werkzeuge/vorschau/README.md): Bytes unverändert lassen, keine Diffs
# Eingebettete Fremdbibliotheken (werkzeuge/vorschau/README.md, werkzeuge/bootstrap/README.md):
# Bytes unverändert lassen, keine Diffs. Die README daneben bleibt Text.
werkzeuge/vorschau/bpmn-js/** binary
werkzeuge/bootstrap/** binary
werkzeuge/bootstrap/README.md text eol=lf diff merge
+3 -2
View File
@@ -61,8 +61,9 @@ Sie bleibt bewusst kurz: Details liegen in `docs/` und werden nur bei Bedarf gel
- Rollen und Verantwortliche in Prozessmodellen als Funktion benennen („Sachbearbeitung Flurbereinigung"),
nie als Person.
- Keine Python-Abhängigkeiten außer der Standardbibliothek und `pytest` ohne Issue-Absprache und Eintrag in
`fallstricke.md`. Einzige eingebettete Fremdbibliothek ist bpmn-js unter `werkzeuge/vorschau/bpmn-js/` –
nicht von Hand ändern, Aktualisierung nach `werkzeuge/vorschau/README.md`.
`fallstricke.md`. Eingebettete Fremdbibliotheken sind nur bpmn-js (`werkzeuge/vorschau/bpmn-js/`) und Bootstrap
mit Bootstrap Icons (`werkzeuge/bootstrap/`) – nicht von Hand ändern, Aktualisierung nach der jeweiligen
`README.md`. Eigene Gestaltung gehört nach `werkzeuge/stil/ag_prozesse.css`. Weitere nur per Issue.
- **Die Markdown-Syntax und das Ablageformat der Prozessbibliothek sind Verträge.** Wer sie ändert, prüft vorher
alle Modelle der Bibliothek, zieht `Anleitung.md` bzw. `docs/leitfaden/08_prozessbibliothek.md` nach und nennt die Änderung im Pull
Request unter „Änderungen an Syntax und Ablageformat“.
+4
View File
@@ -31,6 +31,10 @@ Skills sind übernommen, alles QGIS- und Plugin-Spezifische ist entfernt. Kern i
## Erledigt
- 25.09.2026 **Bootstrap lokal eingebettet, Stil der AG** (#27): Bootstrap 5.3.8 und Bootstrap Icons 1.13.1
unter `werkzeuge/bootstrap/`, eigene Regeln in `werkzeuge/stil/ag_prozesse.css`; die BPMN-Vorschau hat jetzt eine
dunkle Navigationsleiste wie die Anwendungen in VLN_SN und bleibt offline.
- 25.09.2026 **Vorwärtskanten weichen Elementen aus** (#22): Umweg über freie Linien, nur wenn der
Standardweg ein Element schneidet; Belastungstest (126 Fälle) als dauerhafter Test. Beispiele unverändert.
+5
View File
@@ -116,6 +116,11 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
## Umgebung und Werkzeuge
- **Relative Verweise in eingebetteten Stylesheets gehen in der Einzeldatei verloren.** Die CSS der Bootstrap Icons
lädt ihre Schrift über `url("fonts/bootstrap-icons.woff2?…")`. Beim direkten Öffnen von `vorschau.html` klappt
das, in der von `scripts/vorschau.py` erzeugten Einzeldatei nicht. Deshalb setzt `_schriften_einbetten()` die
woff2-Schrift als `data:`-URI ein und lässt den woff-Rückfall weg. Ein Test verlangt, dass jede `url(...)` der
erzeugten Seite `data:` oder ein Fragment (`#…`, SVG-Verweise aus bpmn-js) ist. (#27, 25.09.2026)
- **Öffentliche Organisation heißt nicht öffentliches Repo.** In Gitea haben Organisation und Repo getrennte
Sichtbarkeit. `AG_Prozesse` war öffentlich, `Projektwissen` privat: Die Weboberfläche zeigte es angemeldeten
Personen normal, die API ohne Anmeldung lieferte 404. `scripts/gitea.py` liest bewusst ohne Token und braucht deshalb
+31 -3
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env python3
"""Zeigt eine BPMN-Datei oder eine Arbeitsschritt-.md als Diagramm im Browser – offline, ohne PICTURE.
Grundlage ist werkzeuge/vorschau/vorschau.html mit der lokal eingebetteten Bibliothek bpmn-js (bpmn.io).
Das Skript erzeugt daraus eine einzelne, in sich geschlossene HTML-Datei (Bibliothek, Stile und Diagramm
eingebettet) und öffnet sie im Standardbrowser. Es wird nichts übertragen; die Seite lädt nichts aus dem Netz.
Grundlage ist werkzeuge/vorschau/vorschau.html mit der lokal eingebetteten Bibliothek bpmn-js (bpmn.io), gestaltet
mit Bootstrap und dem Stil der AG (werkzeuge/bootstrap/, werkzeuge/stil/). Das Skript erzeugt daraus eine einzelne,
in sich geschlossene HTML-Datei (Bibliotheken, Stile, Icon-Schrift und Diagramm eingebettet) und öffnet sie im
Standardbrowser. Es wird nichts übertragen; die Seite lädt nichts aus dem Netz.
Aufrufe (aus dem Workspace-Root):
python scripts/vorschau.py Prozessbibliothek/antrag-pruefen/antrag-pruefen.bpmn
@@ -19,7 +20,9 @@ Exit-Code: 0 = in Ordnung, 1 = Datei fehlt oder lässt sich nicht umwandeln.
from __future__ import annotations
import argparse
import base64
import json
import re
import sys
import tempfile
import webbrowser
@@ -32,6 +35,10 @@ SKILL = ROOT / ".agents" / "skills" / "sn-bpmn"
# Stellen in vorschau.html, die beim Einbetten ersetzt werden. Ein Test prüft, dass es sie gibt.
EINBETTUNGEN = {
'<link rel="stylesheet" href="../bootstrap/bootstrap.min.css">': ("style", "../bootstrap/bootstrap.min.css"),
'<link rel="stylesheet" href="../bootstrap/icons/bootstrap-icons.min.css">':
("style", "../bootstrap/icons/bootstrap-icons.min.css"),
'<link rel="stylesheet" href="../stil/ag_prozesse.css">': ("style", "../stil/ag_prozesse.css"),
'<link rel="stylesheet" href="bpmn-js/diagram-js.css">': ("style", "bpmn-js/diagram-js.css"),
'<link rel="stylesheet" href="bpmn-js/bpmn-js.css">': ("style", "bpmn-js/bpmn-js.css"),
'<script src="bpmn-js/bpmn-navigated-viewer.production.min.js"></script>':
@@ -47,6 +54,25 @@ PLATZHALTER_NAME = "const DATEINAME = null;"
VERBOTEN = {"script": ("</script", "<script"), "style": ("</style",)}
# Die Icon-CSS verweist relativ auf ihre Schrift (url("fonts/bootstrap-icons.woff2?…")). In der erzeugten Einzeldatei
# gäbe es diese Datei nicht: die woff2-Schrift wird als data:-URI eingesetzt, der woff-Rückfall entfällt (#27).
SCHRIFT = re.compile(r'url\("fonts/(?P<datei>[^"?]+)(?:\?[^"]*)?"\)\s*format\("(?P<format>[\w-]+)"\)')
def _schriften_einbetten(inhalt: str, ordner: Path) -> str:
"""Ersetzt in einer CSS die relativen Schriftverweise: woff2 als data:-URI, alle übrigen Formate fallen weg."""
def ersetze(treffer: re.Match) -> str:
if treffer["format"] != "woff2":
return ""
daten = base64.b64encode((ordner / "fonts" / treffer["datei"]).read_bytes()).decode("ascii")
return f'url("data:font/woff2;base64,{daten}") format("woff2")'
ergebnis = SCHRIFT.sub(ersetze, inhalt)
ergebnis = re.sub(r',\s*(?=[;}])', '', ergebnis) # Komma vor dem entfallenen Rückfall
if 'url("fonts/' in ergebnis:
raise RuntimeError("Icon-CSS enthält einen Schriftverweis, der sich nicht einbetten lässt")
return ergebnis
def _js_wert(text: str | None) -> str:
"""Als JavaScript-Literal, sicher innerhalb von <script>: jedes '<' wird als \\u003c geschrieben."""
return json.dumps(text, ensure_ascii=False).replace("<", "\\u003c")
@@ -59,6 +85,8 @@ def baue_html(bpmn_xml: str, name: str | None = None) -> str:
if fundstelle not in html:
raise RuntimeError(f"{VORLAGE.name}: '{fundstelle}' fehlt – Vorlage und scripts/vorschau.py passen nicht zusammen")
inhalt = (VORSCHAU / datei).read_text(encoding="utf-8")
if 'url("fonts/' in inhalt:
inhalt = _schriften_einbetten(inhalt, (VORSCHAU / datei).parent)
for sequenz in VERBOTEN[art]:
if sequenz in inhalt.lower():
raise RuntimeError(f"{datei} enthält '{sequenz}' und kann nicht sicher eingebettet werden")
+38 -2
View File
@@ -33,6 +33,42 @@ def test_lizenz_von_bpmn_js_liegt_bei():
assert "watermark" in lizenz
BOOTSTRAP = ROOT / "werkzeuge" / "bootstrap"
STIL = ROOT / "werkzeuge" / "stil" / "ag_prozesse.css"
def test_bootstrap_und_icons_liegen_mit_lizenz_bei():
"""Prüft, dass Bootstrap (CSS, JS-Bundle) und Bootstrap Icons (CSS, Schrift) lokal mit ihren MIT-Lizenzen und einer
README zu Version und Quelle eingebettet sind (Issue #27)."""
for datei in ("bootstrap.min.css", "bootstrap.bundle.min.js", "LICENSE", "README.md",
"icons/bootstrap-icons.min.css", "icons/fonts/bootstrap-icons.woff2", "icons/LICENSE"):
assert (BOOTSTRAP / datei).is_file(), f"fehlt: werkzeuge/bootstrap/{datei}"
for lizenz in ("LICENSE", "icons/LICENSE"):
assert "MIT License" in (BOOTSTRAP / lizenz).read_text(encoding="utf-8")
def test_vorlage_nutzt_bootstrap_und_den_stil_der_ag():
"""Prüft, dass die Vorschau Bootstrap, die Bootstrap Icons und das gemeinsame Stylesheet der AG einbindet
(Issue #27)."""
html = vorschau.VORLAGE.read_text(encoding="utf-8")
for verweis in ("../bootstrap/bootstrap.min.css", "../bootstrap/icons/bootstrap-icons.min.css",
"../stil/ag_prozesse.css"):
assert f'href="{verweis}"' in html, f"nicht eingebunden: {verweis}"
assert STIL.is_file()
def test_erzeugte_seite_laedt_auch_die_icon_schrift_nicht_nach():
"""Prüft, dass in der erzeugten Seite jede CSS-Adresse url(...) eine data:-Quelle oder ein Fragment im selben
Dokument ist – auch die Schrift der Bootstrap Icons, die im Original relativ auf fonts/ verweist (Issue #27)."""
html = vorschau.baue_html(BEISPIEL_BPMN.read_text(encoding="utf-8"), BEISPIEL_BPMN.name)
adressen = re.findall(r"url\(\s*['\"]?([^'\")]+)", html)
assert any(a.startswith("data:font/woff2;base64,") for a in adressen), "Icon-Schrift nicht eingebettet"
# erlaubt sind data:-Quellen und Verweise auf SVG-Fragmente im selben Dokument (url(#…), aus bpmn-js)
fremd = [a for a in adressen if not a.startswith(("data:", "#"))]
assert fremd == [], fremd[:3]
assert ".ag-nav" in html, "Stil der AG nicht eingebettet"
def test_erzeugte_seite_ist_in_sich_geschlossen():
"""Prüft, dass die erzeugte Seite Bibliothek und Stile eingebettet hat und keine Datei mehr nachlädt."""
html = vorschau.baue_html(BEISPIEL_BPMN.read_text(encoding="utf-8"), BEISPIEL_BPMN.name)
@@ -62,8 +98,8 @@ def test_script_ende_im_diagramm_bricht_die_seite_nicht_auf():
def test_bibliothek_mit_script_ende_wird_abgelehnt(tmp_path, monkeypatch):
"""Prüft, dass eine (etwa nach einem Update) nicht sicher einbettbare Bibliothek einen Fehler auslöst."""
kopie = tmp_path / "vorschau"
shutil.copytree(vorschau.VORSCHAU, kopie)
shutil.copytree(vorschau.VORSCHAU.parent, tmp_path / "werkzeuge") # Vorlage braucht auch ../bootstrap, ../stil
kopie = tmp_path / "werkzeuge" / "vorschau"
js = kopie / "bpmn-js" / "bpmn-navigated-viewer.production.min.js"
js.write_text(js.read_text(encoding="utf-8") + '\nvar x = "</script>";', encoding="utf-8")
monkeypatch.setattr(vorschau, "VORSCHAU", kopie)
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2011-2025 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+37
View File
@@ -0,0 +1,37 @@
# Bootstrap und Bootstrap Icons (lokal eingebettet)
Grundlage für das Aussehen der HTML-Seiten der AG, zuerst der BPMN-Vorschau (`werkzeuge/vorschau/`). Vorbild ist die
Anmutung der Anwendungen in VLN_SN (`docs/wissen/vln_sn.md`). Die Bibliotheken liegen lokal, weil unsere Werkzeuge
offline arbeiten. Die eigenen Regeln stehen in `werkzeuge/stil/ag_prozesse.css`, nicht hier.
## Eingebettete Dateien
| | Bootstrap | Bootstrap Icons |
|---|---|---|
| Paket (npm) | `bootstrap` 5.3.8 | `bootstrap-icons` 1.13.1 |
| Quelle | `https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz` | `https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz` |
| Integrität (npm) | `sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==` | `sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==` |
| Dateien | `dist/css/bootstrap.min.css` → `bootstrap.min.css`, `dist/js/bootstrap.bundle.min.js` → `bootstrap.bundle.min.js` | `font/bootstrap-icons.min.css` → `icons/bootstrap-icons.min.css`, `font/fonts/bootstrap-icons.woff2` und `.woff` → `icons/fonts/` |
| Lizenz | MIT, `LICENSE` | MIT, `icons/LICENSE` |
Die Dateien sind **byte-gleich** mit dem Paket (`.gitattributes`: `binary`). Nicht von Hand ändern.
## Hinweise
- **Nichts aus dem Netz.** Die CSS verweist nur auf eingebettete `data:`-Grafiken bzw. auf `icons/fonts/`.
Die `sourceMappingURL`-Kommentare zeigen auf Dateien, die bewusst fehlen. Browser fragen sie höchstens bei
geöffneten Entwicklerwerkzeugen an.
- **Einzeldatei der Vorschau.** `scripts/vorschau.py` bettet die Stylesheets in die erzeugte Seite ein und setzt
dabei die woff2-Schrift der Icons als `data:`-URI ein. Der woff-Rückfall entfällt dort, er wird nur beim direkten
Öffnen von `vorschau.html` in sehr alten Browsern gebraucht.
- **JavaScript-Bundle** (mit Popper) liegt für Bedienelemente wie Modal oder Tooltip bereit. Die Vorschau braucht
es derzeit nicht und bindet es nicht ein.
## Aktualisieren
1. In der npm-Registry die neue Version nachsehen (`https://registry.npmjs.org/bootstrap/latest`, entsprechend
`bootstrap-icons`) und das Paket (`dist.tarball`) herunterladen.
2. Integrität prüfen: `sha512-` + Base64 der SHA-512 des Pakets muss `dist.integrity` entsprechen.
3. Die Dateien aus der Tabelle unverändert übernehmen, Tabelle (Version, Quelle, Integrität) nachziehen.
4. `python -m pytest -q` (prüft u. a., dass die Vorschau nichts nachlädt) und die Vorschau im Browser ansehen.
5. Pull Request mit Verweis auf die Release-Hinweise der neuen Version.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019-2024 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because one or more lines are too long
Binary file not shown.
+34
View File
@@ -0,0 +1,34 @@
/* Gemeinsamer Stil der HTML-Seiten der AG Prozesse, aufbauend auf Bootstrap 5 (werkzeuge/bootstrap/).
Anmutung wie die Anwendungen in VLN_SN (Vorbild EA-Bilanz, siehe docs/wissen/vln_sn.md) – eigene Regeln,
nichts übernommen. Klassen beginnen mit "ag-", damit sie sich nicht mit Bootstrap überschneiden. */
:root {
--ag-akzent: var(--bs-primary, #0d6efd);
--ag-schrift: .9rem;
}
/* Seite in voller Höhe: dunkle Navigationsleiste oben, Arbeitsbereich darunter */
html.ag-seite, html.ag-seite body { height: 100%; }
.ag-app {
display: flex; flex-direction: column; height: 100vh; overflow: hidden;
font-size: var(--ag-schrift);
background: var(--bs-tertiary-bg, #f4f5f7);
}
.ag-nav { flex: 0 0 auto; }
.ag-nav .navbar-brand { font-size: 1rem; }
.ag-nav .btn-group { flex-wrap: nowrap; }
.ag-inhalt { flex: 1 1 auto; min-height: 0; position: relative; background: var(--bs-body-bg, #fff); }
/* Karten mit farbigem linken Rand je Kategorie */
.ag-karte { border-left: 4px solid var(--ag-akzent); }
.ag-karte-erfolg { border-left-color: var(--bs-success, #198754); }
.ag-karte-warnung { border-left-color: var(--bs-warning, #ffc107); }
.ag-karte-fehler { border-left-color: var(--bs-danger, #dc3545); }
/* Zahlen in Tabellenziffern, rechtsbündig */
.ag-zahl { font-variant-numeric: tabular-nums; text-align: right; }
@media print {
.ag-nav { display: none !important; }
.ag-app { height: auto; overflow: visible; background: #fff; }
}
+6
View File
@@ -24,6 +24,12 @@ berechnet hat. Damit eignet sie sich, um Entwürfe vor der Freigabe und Modellä
PICTURE kann dieselbe Datei anders darstellen (eigene Schriften, eigene Behandlung von Anschlusspunkten). Der Import
in PICTURE bleibt deshalb der letzte Nachweis.
## Aussehen
Die Seite nutzt Bootstrap mit Bootstrap Icons (`werkzeuge/bootstrap/`, eigene README) und den gemeinsamen Stil der
AG (`werkzeuge/stil/ag_prozesse.css`): dunkle Navigationsleiste mit Knopfgruppe wie in den Anwendungen von VLN_SN.
`scripts/vorschau.py` bettet alles in die erzeugte Einzeldatei ein, auch die Icon-Schrift.
## Eingebettete Bibliothek
| | |
+26 -50
View File
@@ -1,70 +1,46 @@
<!DOCTYPE html>
<html lang="de">
<html lang="de" class="ag-seite">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BPMN-Vorschau</title>
<!-- bpmn-js (bpmn.io), lokal eingebettet – siehe README.md. Nichts wird aus dem Netz geladen. -->
<!-- Bootstrap, Bootstrap Icons und bpmn-js (bpmn.io), lokal eingebettet – siehe README.md und
werkzeuge/bootstrap/README.md. Nichts wird aus dem Netz geladen. -->
<link rel="stylesheet" href="../bootstrap/bootstrap.min.css">
<link rel="stylesheet" href="../bootstrap/icons/bootstrap-icons.min.css">
<link rel="stylesheet" href="../stil/ag_prozesse.css">
<link rel="stylesheet" href="bpmn-js/diagram-js.css">
<link rel="stylesheet" href="bpmn-js/bpmn-js.css">
<style>
:root {
--hintergrund: #ffffff;
--leiste: #f3f5f7;
--rand: #d0d7de;
--text: #1f2328;
--gedaempft: #59636e;
--akzent: #0b6bcb;
--fehler: #b42318;
--warnung: #9a6700;
}
html, body { height: 100%; margin: 0; }
body {
display: flex; flex-direction: column;
font-family: "Segoe UI", system-ui, sans-serif; font-size: 14px;
color: var(--text); background: var(--hintergrund);
}
header {
display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
padding: 8px 16px; background: var(--leiste); border-bottom: 1px solid var(--rand);
}
header h1 { font-size: 15px; font-weight: 600; margin: 0 12px 0 0; }
button, label.knopf {
font: inherit; padding: 4px 10px; border: 1px solid var(--rand); border-radius: 6px;
background: #fff; color: var(--text); cursor: pointer;
}
button:hover, label.knopf:hover { border-color: var(--akzent); }
button:disabled { opacity: .5; cursor: default; }
#datei { display: none; }
#dateiname { color: var(--gedaempft); overflow-wrap: anywhere; }
#meldung { flex-basis: 100%; margin: 0; }
#meldung { margin: 0; padding: .25rem 1rem; border-bottom: 1px solid var(--bs-border-color); background: var(--bs-body-bg); }
#meldung:empty { display: none; }
#meldung.fehler { color: var(--fehler); }
#meldung.warnung { color: var(--warnung); }
#meldung.fehler { color: var(--bs-danger); }
#meldung.warnung { color: var(--bs-warning-text-emphasis); }
/* Zeichenfläche: das bpmn.io-Wasserzeichen unten rechts muss laut Lizenz sichtbar und unverdeckt bleiben. */
main { position: relative; flex: 1; min-height: 0; }
#flaeche { position: absolute; inset: 0; }
#hinweis {
position: absolute; inset: 24px 24px 64px 24px; display: flex; align-items: center; justify-content: center;
text-align: center; color: var(--gedaempft); border: 2px dashed var(--rand); border-radius: 12px;
padding: 16px; pointer-events: none;
text-align: center; color: var(--bs-secondary-color); border: 2px dashed var(--bs-border-color);
border-radius: .75rem; padding: 1rem; pointer-events: none;
}
main.ziehen #hinweis { border-color: var(--akzent); color: var(--akzent); }
main.ziehen #hinweis { border-color: var(--ag-akzent); color: var(--ag-akzent); }
main.geladen #hinweis { display: none; }
</style>
</head>
<body>
<header>
<h1>BPMN-Vorschau</h1>
<label class="knopf" for="datei">Datei öffnen …</label>
<input type="file" id="datei" accept=".bpmn,.xml">
<button type="button" id="einpassen" disabled>Einpassen</button>
<button type="button" id="groesser" disabled title="Vergrößern">+</button>
<button type="button" id="kleiner" disabled title="Verkleinern">−</button>
<span id="dateiname"></span>
<p id="meldung" role="status"></p>
</header>
<main id="bereich">
<body class="ag-app">
<nav class="navbar navbar-dark bg-dark navbar-expand px-3 py-1 ag-nav">
<span class="navbar-brand mb-0"><i class="bi bi-diagram-3 me-1"></i>BPMN-Vorschau</span>
<span id="dateiname" class="text-secondary small text-truncate me-auto"></span>
<div class="btn-group btn-group-sm">
<label class="btn btn-outline-light" for="datei" title="Datei öffnen"><i class="bi bi-folder2-open"></i><span class="d-none d-md-inline"> Datei öffnen …</span></label>
<button type="button" class="btn btn-outline-light" id="einpassen" disabled title="Einpassen"><i class="bi bi-arrows-angle-contract"></i><span class="d-none d-md-inline"> Einpassen</span></button>
<button type="button" class="btn btn-outline-light" id="groesser" disabled title="Vergrößern"><i class="bi bi-zoom-in"></i></button>
<button type="button" class="btn btn-outline-light" id="kleiner" disabled title="Verkleinern"><i class="bi bi-zoom-out"></i></button>
</div>
<input type="file" id="datei" accept=".bpmn,.xml" hidden>
</nav>
<p id="meldung" role="status"></p>
<main id="bereich" class="ag-inhalt">
<div id="flaeche"></div>
<div id="hinweis">.bpmn-Datei hierher ziehen oder über „Datei öffnen …“ wählen.<br>
Die Datei wird nur in diesem Browser angezeigt und nirgendwohin übertragen.</div>