Add 8 new tools: Hash Verifier, URL Tool, String Utils, Cron Explainer, IP Calc, Lorem Ipsum, CSV Viewer, Notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nirodan
2026-05-06 09:10:25 +02:00
parent ef03e76950
commit 75062dbf5e
20 changed files with 1727 additions and 2 deletions
+151
View File
@@ -0,0 +1,151 @@
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;