75062dbf5e
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
152 lines
5.4 KiB
React
152 lines
5.4 KiB
React
import { useState } from 'react';
|
||
import axios from '../services/api';
|
||
|
||
const resultBox = {
|
||
marginTop: '12px',
|
||
padding: '12px 14px',
|
||
background: 'var(--surface-2)',
|
||
border: '1px solid var(--border)',
|
||
borderRadius: '12px',
|
||
display: 'flex',
|
||
alignItems: 'flex-start',
|
||
gap: '10px',
|
||
justifyContent: 'space-between',
|
||
};
|
||
|
||
const statCard = {
|
||
padding: '12px 16px',
|
||
background: 'var(--surface-2)',
|
||
border: '1px solid var(--border)',
|
||
borderRadius: '12px',
|
||
textAlign: 'center',
|
||
flex: '1 1 120px',
|
||
};
|
||
|
||
const TRANSFORMS = [
|
||
{ op: 'uppercase', label: 'Großbuchstaben' },
|
||
{ op: 'lowercase', label: 'Kleinbuchstaben' },
|
||
{ op: 'titlecase', label: 'Titelschreibung' },
|
||
{ op: 'reverse', label: 'Umkehren' },
|
||
{ op: 'trim', label: 'Leerzeichen trim' },
|
||
{ op: 'remove_spaces', label: 'Leerzeichen entfernen' },
|
||
];
|
||
|
||
function StringUtilsTool() {
|
||
const [input, setInput] = useState('');
|
||
const [result, setResult] = useState(null);
|
||
const [error, setError] = useState('');
|
||
const [copied, setCopied] = useState(false);
|
||
|
||
const run = async (op) => {
|
||
setError('');
|
||
setResult(null);
|
||
if (!input) {
|
||
setError('Bitte Text eingeben.');
|
||
return;
|
||
}
|
||
try {
|
||
const res = await axios.post('/api/string/analyze', { text: input, operation: op });
|
||
setResult(res.data);
|
||
} catch (err) {
|
||
setError(err.response?.data?.message || 'Fehler bei der Verarbeitung');
|
||
}
|
||
};
|
||
|
||
const copy = (text) => {
|
||
navigator.clipboard.writeText(text);
|
||
setCopied(true);
|
||
setTimeout(() => setCopied(false), 1500);
|
||
};
|
||
|
||
return (
|
||
<div className="main-content">
|
||
<h2>String Utilities</h2>
|
||
|
||
<textarea
|
||
rows={6}
|
||
value={input}
|
||
onChange={(e) => { setInput(e.target.value); setResult(null); setError(''); }}
|
||
placeholder="Text eingeben..."
|
||
style={{ resize: 'vertical' }}
|
||
/>
|
||
|
||
<div style={{ marginTop: '12px' }}>
|
||
<p style={{ color: 'var(--muted)', fontWeight: 600, marginBottom: '8px', fontSize: '0.9rem' }}>Analyse</p>
|
||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||
<button onClick={() => run('stats')}>Statistiken</button>
|
||
<button onClick={() => run('count_words')}>Worthäufigkeit</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ marginTop: '12px' }}>
|
||
<p style={{ color: 'var(--muted)', fontWeight: 600, marginBottom: '8px', fontSize: '0.9rem' }}>Transformation</p>
|
||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||
{TRANSFORMS.map(({ op, label }) => (
|
||
<button key={op} className="ghost" onClick={() => run(op)}>{label}</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{error && <p className="error" style={{ marginTop: '12px' }}>{error}</p>}
|
||
|
||
{result && result.operation === 'stats' && (
|
||
<div style={{ marginTop: '16px' }}>
|
||
<p style={{ color: 'var(--muted)', fontWeight: 600, marginBottom: '10px', fontSize: '0.9rem' }}>Statistiken</p>
|
||
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
||
{[
|
||
{ label: 'Zeichen', value: result.chars },
|
||
{ label: 'Ohne Leerzeichen', value: result.chars_no_spaces },
|
||
{ label: 'Wörter', value: result.words },
|
||
{ label: 'Zeilen', value: result.lines },
|
||
{ label: 'Leerzeichen', value: result.spaces },
|
||
].map(({ label, value }) => (
|
||
<div key={label} style={statCard}>
|
||
<div style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--accent)' }}>{value}</div>
|
||
<div style={{ fontSize: '0.8rem', color: 'var(--muted)', marginTop: '4px' }}>{label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{result && result.operation === 'count_words' && (
|
||
<div style={{ marginTop: '16px' }}>
|
||
<p style={{ color: 'var(--muted)', fontWeight: 600, marginBottom: '10px', fontSize: '0.9rem' }}>Top-Wörter</p>
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||
{result.words.map(({ word, count }, i) => (
|
||
<div key={word} style={{
|
||
display: 'flex', alignItems: 'center', gap: '10px',
|
||
padding: '8px 12px',
|
||
background: 'var(--surface-2)',
|
||
border: '1px solid var(--border)',
|
||
borderRadius: '10px',
|
||
}}>
|
||
<span style={{ color: 'var(--muted)', width: '20px', fontSize: '0.85rem' }}>#{i + 1}</span>
|
||
<span style={{ fontFamily: 'monospace', color: 'var(--text)', flex: 1 }}>{word}</span>
|
||
<span style={{
|
||
background: 'var(--accent)', color: '#0b1224',
|
||
borderRadius: '20px', padding: '2px 10px',
|
||
fontSize: '0.8rem', fontWeight: 700,
|
||
}}>{count}×</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{result && result.result !== undefined && (
|
||
<div style={resultBox}>
|
||
<span style={{ wordBreak: 'break-all', color: 'var(--text)', fontFamily: 'monospace', fontSize: '0.9rem', flex: 1, whiteSpace: 'pre-wrap' }}>
|
||
{result.result}
|
||
</span>
|
||
<button className="ghost" onClick={() => copy(result.result)} style={{ flexShrink: 0, margin: 0 }}>
|
||
{copied ? 'Kopiert!' : 'Kopieren'}
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default StringUtilsTool;
|