it-praxisportal/assets/ihk_tools/uml_editor.js

346 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 🔷 IT-PRAXISPORTAL – UML DIAGRAMM STUDIO (KLASSEN, SEQUENZ, USE CASE)
* Standard für IHK FIAE & FISI:
* - Klassendiagramme (Attribute +, -, #, Methoden, Vererbung, Assoziation, Komposition)
* - Sequenzdiagramme (Lebenslinien, synchrone/asynchrone Aufrufe, Return)
* - Use Case Diagramme (Akteure, Systemgrenze, <<include>>, <<extend>>)
* Synchronisierte Mermaid-Live-Vorschau mit interaktiver Generierung!
*/
class UMLEditor {
constructor(containerId, options = {}) {
this.container = typeof containerId === 'string' ? document.getElementById(containerId) : containerId;
this.options = options;
this.diagramType = 'class'; // 'class' | 'sequence' | 'usecase'
this.mermaidCode = '';
this.init();
}
init() {
if (!this.container) return;
this.container.innerHTML = `
<div class="ds-editor-card">
<div class="ds-toolbar">
<div class="ds-toolbar-left">
<div class="ds-select-wrap">
<label>Diagrammtyp:</label>
<select id="uml-select-type" class="ds-select">
<option value="class">UML 2.5 Klassendiagramm</option>
<option value="sequence">UML 2.5 Sequenzdiagramm</option>
<option value="usecase">UML 2.5 Anwendungsfalldiagramm (Use Case)</option>
</select>
</div>
<div class="ds-select-wrap">
<label>IHK-Vorlage:</label>
<select id="uml-select-template" class="ds-select">
<option value="">-- Vorlage laden --</option>
<option value="class_ecommerce">IHK Klassendiagramm: E-Commerce & Zahlung</option>
<option value="class_ticket">IHK Klassendiagramm: IT-Helpdesk & Rollen</option>
<option value="seq_login">IHK Sequenz: 2FA Benutzer-Authentifizierung</option>
<option value="seq_order">IHK Sequenz: Warenkorb-Bestellprozess</option>
<option value="uc_hotel">IHK Use-Case: Hotel-Reservierungssystem</option>
</select>
</div>
</div>
<div class="ds-toolbar-right">
<button type="button" class="ds-btn ds-btn-success" id="uml-btn-render">
<span>▶</span> <span>Neu Rendern</span>
</button>
<button type="button" class="ds-btn ds-btn-secondary" id="uml-btn-export-svg">
<span>🖼️</span> <span>SVG Export</span>
</button>
<button type="button" class="ds-btn ds-btn-danger" id="uml-btn-clear" title="Code leeren">
<span>🗑️</span>
</button>
</div>
</div>
<div class="ds-workspace-grid">
<div class="ds-canvas-wrap" style="display:flex; align-items:center; justify-content:center; overflow:auto; background:#090d16;" id="uml-canvas-container">
<div id="uml-render-target" style="width:100%; min-height:100%; display:flex; align-items:center; justify-content:center; padding:24px;"></div>
</div>
<div class="ds-sidebar" style="width:340px;">
<div class="ds-sidebar-header">
<h4>UML Quellcode (Mermaid / PlantUML Standard)</h4>
</div>
<div class="ds-sidebar-body" style="padding:10px; display:flex; flex-direction:column; height:calc(100% - 46px);">
<textarea id="uml-code-input" class="ds-textarea" style="flex:1; font-family:monospace; font-size:0.84rem; line-height:1.45; resize:none; background:#060911; color:#f8fafc; border:1px solid #334155; padding:12px; border-radius:6px;"></textarea>
<div style="margin-top:10px; font-size:0.75rem; color:#94a3b8; line-height:1.4;">
💡 Tipp: Bearbeite den Diagrammcode direkt links in der Textbox oder lade eine IHK-Vorlage. Klicke auf <strong>Neu Rendern</strong> zur Aktualisierung.
</div>
</div>
</div>
</div>
</div>
`;
this.bindEvents();
this.loadTemplate('class_ecommerce');
}
bindEvents() {
document.getElementById('uml-select-type').addEventListener('change', (e) => {
this.diagramType = e.target.value;
if (this.diagramType === 'class') this.loadTemplate('class_ecommerce');
else if (this.diagramType === 'sequence') this.loadTemplate('seq_login');
else if (this.diagramType === 'usecase') this.loadTemplate('uc_hotel');
});
document.getElementById('uml-select-template').addEventListener('change', (e) => {
if (e.target.value) this.loadTemplate(e.target.value);
});
document.getElementById('uml-btn-render').addEventListener('click', () => {
this.mermaidCode = document.getElementById('uml-code-input').value;
this.render();
});
document.getElementById('uml-btn-clear').addEventListener('click', () => {
if (confirm('Möchtest du das aktuelle UML-Diagramm leeren?')) {
document.getElementById('uml-code-input').value = 'classDiagram\n class MeineKlasse {\n +int id\n }';
this.mermaidCode = document.getElementById('uml-code-input').value;
this.render();
}
});
document.getElementById('uml-btn-export-svg').addEventListener('click', () => this.exportSvg());
}
loadTemplate(tpl) {
let code = '';
if (tpl === 'class_ecommerce') {
this.diagramType = 'class';
document.getElementById('uml-select-type').value = 'class';
code = `classDiagram
direction TB
class Benutzer {
<<abstract>>
#int id
#String email
#String passwortHash
+anmelden() boolean
+abmelden() void
}
class Kunde {
-String vorname
-String nachname
-String lieferadresse
+bestellen(Warenkorb w) Bestellung
}
class Administrator {
-int rechteStufe
+artikelAnlegen(Artikel a) void
+sperreBenutzer(int userId) void
}
class Bestellung {
-int bestellId
-Date datum
-String status
+berechneGesamtsumme() double
}
class Bestellposition {
-int menge
-double einzelpreis
}
class Artikel {
-int artikelId
-String bezeichnung
-double preis
-int bestand
}
class IPaymentMethod {
<<interface>>
+processPayment(double amount) boolean
}
Benutzer <|-- Kunde : Generalisierung
Benutzer <|-- Administrator : Generalisierung
Kunde "1" --> "*" Bestellung : gibt auf
Bestellung "1" *-- "1..*" Bestellposition : Komposition
Bestellposition "*" --> "1" Artikel : Assoziation
Bestellung ..> IPaymentMethod : Realisierung`;
} else if (tpl === 'class_ticket') {
this.diagramType = 'class';
document.getElementById('uml-select-type').value = 'class';
code = `classDiagram
class Ticket {
-int ticketNummer
-String titel
-String prioritaet
-Date erstelltAm
+statusAendern(String status) void
+zuweisen(Mitarbeiter m) void
}
class Mitarbeiter {
-int mitarbeiterId
-String name
-String abteilung
}
class HardwareAsset {
-String seriennummer
-String hostname
-String standort
}
Ticket "*" --> "1" Mitarbeiter : Melder
Ticket "*" --> "0..1" Mitarbeiter : Bearbeiter
Ticket "*" --> "1" HardwareAsset : Betrifft`;
} else if (tpl === 'seq_login') {
this.diagramType = 'sequence';
document.getElementById('uml-select-type').value = 'sequence';
code = `sequenceDiagram
autonumber
actor User as Benutzer
participant Browser as Client / Browser
participant AuthAPI as Auth-Service
participant DB as Datenbank
participant Mail as 2FA-Mail-Service
User->>Browser: Login-Formular ausfüllen (Email, Passwort)
Browser->>AuthAPI: POST /api/v1/auth/login
activate AuthAPI
AuthAPI->>DB: SELECT * FROM users WHERE email = ?
activate DB
DB-->>AuthAPI: User-Record mit Password-Hash
deactivate DB
AuthAPI->>AuthAPI: Passwort-Hash validieren (Argon2id)
alt Passwort korrekt
AuthAPI->>Mail: 2FA-Einmalcode senden (SMS/Email)
AuthAPI-->>Browser: 200 OK (Requires 2FA)
User->>Browser: 2FA-Code eingeben
Browser->>AuthAPI: POST /api/v1/auth/verify-otp
AuthAPI-->>Browser: 200 OK (JWT Access Token & Cookie)
else Passwort falsch
AuthAPI-->>Browser: 401 Unauthorized (Ungültige Anmeldedaten)
end
deactivate AuthAPI`;
} else if (tpl === 'seq_order') {
this.diagramType = 'sequence';
document.getElementById('uml-select-type').value = 'sequence';
code = `sequenceDiagram
autonumber
actor Kunde
participant Webshop
participant Lager as ERP / Lager
participant Bank as Payment-Gateway
Kunde->>Webshop: "Jetzt kaufen" klicken
activate Webshop
Webshop->>Lager: Bestand prüfen & reservieren
activate Lager
Lager-->>Webshop: Bestand bestätigt
deactivate Lager
Webshop->>Bank: Kreditkarte autorisieren (129.90 EUR)
activate Bank
Bank-->>Webshop: Zahlung genehmigt (Auth-Token)
deactivate Bank
Webshop->>Lager: Kommissionierauftrag erstellen
Webshop-->>Kunde: Bestellbestätigung per E-Mail
deactivate Webshop`;
} else if (tpl === 'uc_hotel') {
this.diagramType = 'usecase';
document.getElementById('uml-select-type').value = 'usecase';
code = `flowchart LR
subgraph Hotelverwaltungssystem [Systemgrenze: Hotel-Reservierung]
UC1((Zimmer suchen))
UC2((Zimmer reservieren))
UC3((Zahlung durchführen))
UC4((Reservierung stornieren))
UC5((Zimmerrechnung erstellen))
end
Gast[Gast / Kunde] --> UC1
Gast --> UC2
Gast --> UC4
Rezeption[Rezeptionist] --> UC2
Rezeption --> UC4
Rezeption --> UC5
UC2 -.->|<<include>>| UC3
UC4 -.->|<<extend>>| UC5`;
}
document.getElementById('uml-code-input').value = code;
this.mermaidCode = code;
this.render();
}
async render() {
const target = document.getElementById('uml-render-target');
if (!target) return;
// Load mermaid if not already on window
if (!window.mermaid) {
target.innerHTML = `<div style="color:#94a3b8; font-size:0.9rem;">Lade Diagramm-Renderer...</div>`;
await this.loadMermaidScript();
}
try {
window.mermaid.initialize({
startOnLoad: false,
theme: 'dark',
themeVariables: {
darkMode: true,
background: '#090d16',
primaryColor: '#1e293b',
primaryTextColor: '#f8fafc',
primaryBorderColor: '#38bdf8',
lineColor: '#38bdf8',
secondaryColor: '#0f172a',
tertiaryColor: '#1e1b4b'
}
});
const id = 'mermaid_svg_' + Date.now();
const { svg } = await window.mermaid.render(id, this.mermaidCode);
target.innerHTML = svg;
} catch (err) {
target.innerHTML = `
<div style="background:rgba(239,68,68,0.1); border:1px solid #ef4444; color:#fca5a5; padding:14px; border-radius:8px; font-size:0.85rem; max-width:600px;">
<strong>⚠️ Syntaxfehler im UML-Code:</strong>
<pre style="margin-top:8px; font-family:monospace; font-size:0.8rem; white-space:pre-wrap;">${err.message || err}</pre>
</div>
`;
}
}
loadMermaidScript() {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js';
script.onload = () => resolve();
script.onerror = () => reject(new Error('Mermaid CDN konnte nicht geladen werden.'));
document.head.appendChild(script);
});
}
exportSvg() {
const svgEl = document.querySelector('#uml-render-target svg');
if (!svgEl) {
alert('Kein gerendertes Diagramm zum Exportieren vorhanden.');
return;
}
const serializer = new XMLSerializer();
const source = serializer.serializeToString(svgEl);
const blob = new Blob([source], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `uml_${this.diagramType}_ihk.svg`;
a.click();
URL.revokeObjectURL(url);
}
}
window.UMLEditor = UMLEditor;