diff --git a/frontend/src/icons.jsx b/frontend/src/icons.jsx
index e6b4b15..c41b695 100644
--- a/frontend/src/icons.jsx
+++ b/frontend/src/icons.jsx
@@ -150,3 +150,6 @@ export const SkizzeIcon = ({size=20,color='currentColor',sw}) => base(<>
>, size, color, sw);
+export const WrenchIcon = ({size=20,color='currentColor',sw}) => base(<>
+
+>, size, color, sw);
diff --git a/frontend/src/toolRegistry.js b/frontend/src/toolRegistry.js
index 9b8793b..1f3e252 100644
--- a/frontend/src/toolRegistry.js
+++ b/frontend/src/toolRegistry.js
@@ -5,7 +5,8 @@ import Nachrichten from './tools/nachrichten.jsx';
import Linkliste from './tools/linkliste.jsx';
import CodeSchnipsel from './tools/codeschnipsel.jsx';
import Skizze from './tools/skizze.jsx';
-import { CalculatorIcon, ArchiveIcon, DatabaseIcon, AppsIcon, MessageIcon, LinkIcon, CodeIcon, SkizzeIcon } from './icons.jsx';
+import DevTools from './tools/devtools.jsx';
+import { CalculatorIcon, ArchiveIcon, DatabaseIcon, AppsIcon, MessageIcon, LinkIcon, CodeIcon, SkizzeIcon, WrenchIcon } from './icons.jsx';
export const TOOLS = [
{
@@ -67,11 +68,19 @@ export const TOOLS = [
{
id: 'skizze',
Icon: SkizzeIcon,
- label: 'Skizzen-Rechner',
- navLabel: 'Skizze',
+ label: 'Skizzen-Rechner (β)',
+ navLabel: 'Skizze β',
group: 'Werkzeuge',
component: Skizze,
},
+ {
+ id: 'devtools',
+ Icon: WrenchIcon,
+ label: 'Dev-Tools',
+ navLabel: 'Dev-Tools',
+ group: 'Werkzeuge',
+ component: DevTools,
+ },
// Neues Tool: { id:'...', Icon:..., label:'...', navLabel:'...', group:'...', component:... },
];
diff --git a/frontend/src/tools/devtools.jsx b/frontend/src/tools/devtools.jsx
new file mode 100644
index 0000000..119fe40
--- /dev/null
+++ b/frontend/src/tools/devtools.jsx
@@ -0,0 +1,309 @@
+import { useState, useRef } from 'react';
+import { S } from '../lib.js';
+
+// ── YAML Formatter ────────────────────────────────────────────────────────────
+function parseAndFormatYaml(input) {
+ // YAML-konformes Einrücken mit 2 Spaces
+ // Wir parsen manuell: kein externes package, aber korrekte Einrückung
+ const lines = input.split('\n');
+ const out = [];
+ let inMultiline = false;
+ let multilineIndent = 0;
+
+ for (let i = 0; i < lines.length; i++) {
+ const raw = lines[i];
+ const trimmed = raw.trim();
+
+ // Leerzeilen beibehalten
+ if (!trimmed) { out.push(''); continue; }
+
+ // Kommentare unverändert
+ if (trimmed.startsWith('#')) {
+ const depth = detectDepth(raw);
+ out.push(' '.repeat(depth) + trimmed);
+ continue;
+ }
+
+ // Multiline-Block (| oder >) – Zeilen danach unverändert einrücken
+ if (inMultiline) {
+ // Endet wenn Einrückung nicht mehr tiefer ist
+ const currentIndent = raw.search(/\S/);
+ if (currentIndent <= multilineIndent && trimmed) {
+ inMultiline = false;
+ } else {
+ out.push(raw); // Multiline-Inhalt unverändert
+ continue;
+ }
+ }
+
+ const depth = detectDepth(raw);
+ const indent = ' '.repeat(depth);
+
+ // Key: Value
+ const colonIdx = findKeyColon(trimmed);
+ if (colonIdx > 0) {
+ const key = trimmed.slice(0, colonIdx).trim();
+ const value = trimmed.slice(colonIdx + 1).trim();
+
+ if (value === '|' || value === '>' || value === '|-' || value === '>-') {
+ inMultiline = true;
+ multilineIndent = raw.search(/\S/);
+ out.push(`${indent}${key}: ${value}`);
+ } else if (value === '') {
+ out.push(`${indent}${key}:`);
+ } else {
+ out.push(`${indent}${key}: ${value}`);
+ }
+ continue;
+ }
+
+ // Listenelement (- ...)
+ if (trimmed.startsWith('- ') || trimmed === '-') {
+ out.push(`${indent}${trimmed}`);
+ continue;
+ }
+
+ // Alles andere
+ out.push(`${indent}${trimmed}`);
+ }
+
+ return out.join('\n');
+}
+
+// Berechnet die logische Einrückungstiefe (2 Spaces pro Level)
+function detectDepth(line) {
+ const spaces = line.search(/\S/);
+ if (spaces < 0) return 0;
+ return Math.round(spaces / 2);
+}
+
+// Findet den Doppelpunkt eines Keys (nicht innerhalb von Strings)
+function findKeyColon(trimmed) {
+ let inSingle = false, inDouble = false;
+ for (let i = 0; i < trimmed.length; i++) {
+ const c = trimmed[i];
+ if (c === "'" && !inDouble) inSingle = !inSingle;
+ if (c === '"' && !inSingle) inDouble = !inDouble;
+ if (c === ':' && !inSingle && !inDouble) {
+ // Muss von Leerzeichen oder End gefolgt sein
+ const next = trimmed[i+1];
+ if (next === undefined || next === ' ' || next === '\t') return i;
+ }
+ }
+ return -1;
+}
+
+function YamlTool() {
+ const [input, setInput] = useState('');
+ const [output, setOutput] = useState('');
+ const [error, setError] = useState('');
+ const [copied, setCopied] = useState(false);
+ const outputRef = useRef(null);
+
+ const format = () => {
+ if (!input.trim()) { setError('Bitte YAML eingeben.'); setOutput(''); return; }
+ try {
+ const result = parseAndFormatYaml(input);
+ setOutput(result);
+ setError('');
+ } catch(e) {
+ setError('Fehler beim Formatieren: ' + e.message);
+ setOutput('');
+ }
+ };
+
+ const copy = async () => {
+ try {
+ await navigator.clipboard.writeText(output);
+ setCopied(true);
+ setTimeout(()=>setCopied(false), 1500);
+ } catch { }
+ };
+
+ const clear = () => { setInput(''); setOutput(''); setError(''); };
+
+ // Auto-format on paste
+ const onPaste = e => {
+ setTimeout(()=>{
+ const val = e.target.value;
+ if (val.trim()) {
+ const result = parseAndFormatYaml(val);
+ setOutput(result);
+ setError('');
+ }
+ }, 0);
+ };
+
+ return (
+
+
+ {/* Input */}
+
+
+
+ {input && }
+
+
+
+ {/* Output */}
+
+
+
+ {output && (
+
+ )}
+
+ {error ? (
+
+ {error}
+
+ ) : (
+
+ {output || '# Ergebnis erscheint hier'}
+
+ )}
+
+
+
+
+
+
+
+
+ Tipp: YAML wird beim Einfügen automatisch formatiert · 2-Space-Einrückung · Schlüssel: Wert Syntax
+
+
+ );
+}
+
+// ── Tool-Definitionen ─────────────────────────────────────────────────────────
+const TOOL_DEFS = [
+ {
+ id: 'yaml',
+ label: '📄 YAML Formatter',
+ desc: 'YAML korrekt einrücken und formatieren',
+ ready: true,
+ component: YamlTool,
+ },
+ {
+ id: 'json',
+ label: '{ } JSON Formatter',
+ desc: 'JSON formatieren und validieren',
+ ready: false,
+ },
+ {
+ id: 'base64',
+ label: '🔐 Base64',
+ desc: 'Text ↔ Base64 kodieren/dekodieren',
+ ready: false,
+ },
+ {
+ id: 'regex',
+ label: '🔍 Regex Tester',
+ desc: 'Reguläre Ausdrücke live testen',
+ ready: false,
+ },
+ {
+ id: 'diff',
+ label: '± Text Diff',
+ desc: 'Zwei Texte vergleichen',
+ ready: false,
+ },
+ {
+ id: 'jwt',
+ label: '🔑 JWT Decoder',
+ desc: 'JSON Web Tokens dekodieren und prüfen',
+ ready: false,
+ },
+ {
+ id: 'timestamp',
+ label: '🕐 Timestamp',
+ desc: 'Unix Timestamps umrechnen',
+ ready: false,
+ },
+ {
+ id: 'color',
+ label: '🎨 Farben',
+ desc: 'HEX ↔ RGB ↔ HSL umrechnen',
+ ready: false,
+ },
+];
+
+// ── Main Component ────────────────────────────────────────────────────────────
+export default function DevTools({ toast, mobile }) {
+ const [activeTool, setActiveTool] = useState('yaml');
+ const active = TOOL_DEFS.find(t=>t.id===activeTool);
+
+ return (
+
+
+
Dev-Tools
+
+ Konverter · Formatter · Helfer
+
+
+
+ {/* Tool-Auswahl */}
+
+ {TOOL_DEFS.map(t=>(
+
+ ))}
+
+
+ {/* Aktives Tool */}
+
+
+
{active?.label}
+
{active?.desc}
+
+ {active?.component &&
}
+
+
+ );
+}