import { useState, useCallback } from 'react';
import { S } from '../lib.js';
import jsyaml from 'js-yaml';
// ── YAML Tree Node (collapsible, mit Einrück-Linien) ─────────────────────────
function YamlNode({ data, depth = 0, keyName = null, collapsed, onToggle, path }) {
const indent = depth * 18; // px pro Level
const isObj = data !== null && typeof data === 'object' && !Array.isArray(data);
const isArr = Array.isArray(data);
const isLeaf = !isObj && !isArr;
const nodeKey = path;
const isCollapsed = collapsed.has(nodeKey);
const hasChildren = isObj || isArr;
const keyColor = '#7ecaff';
const strColor = '#ce9178';
const numColor = '#b5cea8';
const boolColor = '#569cd6';
const nullColor = '#808080';
const indentColor= 'rgba(255,255,255,0.08)';
const valueColor = (v) => {
if (v === null || v === undefined) return nullColor;
if (typeof v === 'boolean') return boolColor;
if (typeof v === 'number') return numColor;
return strColor;
};
const renderValue = (v) => {
if (v === null) return null;
if (v === undefined) return ~;
if (typeof v === 'boolean') return {String(v)};
if (typeof v === 'number') return {v};
const s = String(v);
// Quote strings that need it
const needsQuotes = /[:#\[\]{}&*!|>'"%@`]/.test(s) || s.trim()!==s || s==='' || /^(true|false|null|yes|no|on|off)$/i.test(s);
return {needsQuotes ? `"${s.replace(/"/g,'\\"')}"` : s};
};
// Leaf: key: value
if (isLeaf) {
return (
{/* Vertical indent lines */}
{Array.from({length:depth},(_,i)=>(
))}
{keyName!==null&&{keyName}: }
{renderValue(data)}
);
}
const count = isObj ? Object.keys(data).length : data.length;
const summary = isObj ? `{${count}}` : `[${count}]`;
return (
{/* Vertical indent lines for this level */}
{Array.from({length:depth},(_,i)=>(
))}
{/* Header row */}
hasChildren&&onToggle(nodeKey)}>
{hasChildren&&(
▼
)}
{keyName!==null&&{keyName}: }
{isCollapsed&&{summary}}
{!isCollapsed&&isArr&&# {count} Einträge}
{/* Children */}
{!isCollapsed && (
{isObj && Object.entries(data).map(([k,v],i)=>(
))}
{isArr && data.map((v,i)=>(
-
{typeof v === 'object' && v !== null
?
:
{v===null?null:typeof v==='boolean'?{String(v)}:typeof v==='number'?{v}:{String(v)}}
}
))}
)}
);
}
function collectPaths(data, path='root', paths=[]) {
if (data !== null && typeof data === 'object') {
paths.push(path);
if (Array.isArray(data)) data.forEach((_,i)=>collectPaths(data[i],`${path}[${i}]`,paths));
else Object.entries(data).forEach(([k,v])=>collectPaths(v,`${path}.${k}`,paths));
}
return paths;
}
// ── YAML Auto-Fix: normalisiert Einrückung bevor js-yaml parst ───────────────
function fixYamlIndentation(text) {
const lines = text.split('\n');
// 1. Tabs → Spaces (4 oder 2)
const detabbed = lines.map(l => l.replace(/\t/g, ' '));
// 2. Finde die kleinste vorkommende Einrückung > 0 (das ist die Einheit)
const indents = detabbed
.filter(l => l.trim() && !l.trim().startsWith('#'))
.map(l => l.search(/\S/))
.filter(n => n > 0);
const unit = indents.length ? Math.min(...indents) : 2;
// 3. Remap: jede Einrückung → Niveau * 2 Spaces
const remapped = detabbed.map(line => {
if (!line.trim()) return '';
if (line.trim().startsWith('#')) return line.trimEnd();
const spaces = line.search(/\S/);
if (spaces <= 0) return line.trimEnd();
const level = Math.round(spaces / unit);
return ' '.repeat(level) + line.trimStart().trimEnd();
});
return remapped.join('\n');
}
// ── YAML Tool ─────────────────────────────────────────────────────────────────
function YamlTool({ mobile }) {
const [input, setInput] = useState('');
const [parsed, setParsed] = useState(null);
const [formatted,setFormatted]= useState('');
const [error, setError] = useState('');
const [warning, setWarning] = useState('');
const [copied, setCopied] = useState(false);
const [view, setView] = useState('tree');
const [collapsed,setCollapsed]= useState(new Set());
const process = useCallback((text) => {
if (!text.trim()) { setParsed(null); setFormatted(''); setError(''); setWarning(''); return; }
let obj, parseError = null;
// Versuch 1: direkt parsen
try { obj = jsyaml.load(text); }
catch(e) { parseError = e; }
// Versuch 2: wenn Fehler → Auto-Fix, dann nochmal
if (parseError) {
try {
const fixed = fixYamlIndentation(text);
obj = jsyaml.load(fixed);
setWarning(`⚠ Einrückung automatisch korrigiert: ${parseError.message.split('\n')[0]}`);
} catch(e2) {
// Fix hat auch nicht geholfen – originalen Fehler zeigen
setError(parseError.message);
setWarning('');
setParsed(null); setFormatted('');
return;
}
} else {
setWarning('');
}
try {
setParsed(obj);
setFormatted(jsyaml.dump(obj, { indent:2, lineWidth:-1, noRefs:true }));
setError('');
setCollapsed(new Set());
} catch(e) {
setError(e.message);
setParsed(null); setFormatted('');
}
},[]);
const onInput = v => { setInput(v); process(v); };
const copy = async () => {
try { await navigator.clipboard.writeText(formatted); setCopied(true); setTimeout(()=>setCopied(false),1500); } catch {}
};
const toggleCollapse = path => {
setCollapsed(prev => {
const n = new Set(prev);
n.has(path) ? n.delete(path) : n.add(path);
return n;
});
};
const collapseAll = () => { if(parsed) setCollapsed(new Set(collectPaths(parsed))); };
const expandAll = () => setCollapsed(new Set());
const colCols = mobile ? '1fr' : '1fr 1fr';
return (
{/* Input */}
{input&&}
{/* Output */}
{/* View toggle */}
{['tree','raw'].map(v=>(
))}
{parsed&&view==='tree'&&<>
>}
{formatted&&}
{error ? (
⚠ {error}
) : (<>
{warning && (
{warning}
)}
{view==='raw' ? (
{formatted||'# Ergebnis erscheint hier'}
) : parsed!==null ? (
) : (
YAML eingeben → wird live formatiert
)}
>)}
Powered by js-yaml · 2-Space-Einrückung · YAML 1.2 kompatibel
);
}
// ── 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', ready:false },
{ id:'timestamp', label:'🕐 Timestamp', desc:'Unix Timestamps umrechnen', ready:false },
{ id:'color', label:'🎨 Farben', desc:'HEX ↔ RGB ↔ HSL umrechnen', ready:false },
];
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_DEFS.map(t=>(
))}
{active?.label}
{active?.desc}
{active?.component &&
}
);
}