import { useCallback, useMemo, useState } from 'react'; import { ArrowLeftRight } from 'lucide-react'; import TextInputArea, { type ToolbarAction } from '@/components/TextInputArea'; import { useTranslation } from 'react-i18next'; import CopyButton from '@/components/CopyButton'; import { textToBase64, base64ToText } from '@/utils/base64Converter'; import SwitchButtonGroup from '@/components/SwitchButtonGroup'; import { useContextMenuData } from '@/utils/useContextMenuData'; const IMAGE_DATA_URI_PATTERN = /^\s*data:image\//i; const ERROR_MESSAGE_TO_I18N: Record = { 'Invalid Base64 string': 'invalidBase64', 'Input appears to be binary data (e.g. an image). Please use the Image tab instead.': 'binaryDataDetected', }; interface TextModeProps { onSwitchToImageMode?: () => void; } export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) { const { t } = useTranslation('base64Converter'); const [input, setInput] = useState(''); const [output, setOutput] = useState(''); const [error, setError] = useState(null); const [direction, setDirection] = useState<'encode' | 'decode'>('encode'); const handleContextMenuData = useCallback( (payload: string) => { setInput(payload); setDirection('decode'); setError(null); try { const decoded = base64ToText(payload); setOutput(decoded); } catch (e) { const message = e instanceof Error ? e.message : ''; const i18nKey = ERROR_MESSAGE_TO_I18N[message]; setError(i18nKey ? t(i18nKey) : message || t('conversionFailed')); } }, [t], ); useContextMenuData({ featureKey: 'base64Converter', onData: handleContextMenuData }); const actionLabel = direction === 'encode' ? t('encode') : t('decode'); const placeholder = direction === 'encode' ? t('textInputPlaceholder') : t('base64InputPlaceholder'); const outputLabel = direction === 'encode' ? t('base64Output') : t('textOutput'); const showImageHint = useMemo( () => direction === 'decode' && IMAGE_DATA_URI_PATTERN.test(input), [direction, input], ); const handleDirectionChange = useCallback( (value: 'encode' | 'decode') => { if (value === direction) return; setDirection(value); setOutput(''); setError(null); }, [direction], ); const actions: ToolbarAction[] = useMemo( () => [ { key: 'convert', label: actionLabel, icon: , type: 'primary', position: 'bottom', disabled: (value: string) => !value.trim(), onClick: (value: string) => { setError(null); try { if (direction === 'encode') { const result = textToBase64(value); setOutput(result.output); } else { const decoded = base64ToText(value); setOutput(decoded); } } catch (e) { const message = e instanceof Error ? e.message : ''; const i18nKey = ERROR_MESSAGE_TO_I18N[message]; setError(i18nKey ? t(i18nKey) : message || t('conversionFailed')); } }, }, ], [direction, t, actionLabel], ); return ( <> { setInput(v); setError(null); }} actions={actions} externalError={error || undefined} onClear={() => setOutput('')} /> {showImageHint && (
{t('imageDataUriHint')}
)} {output && (
{outputLabel}
2000 ? `${output.substring(0, 2000)}...` : output} showClear={false} showCount />
)} ); }