用 Tailwind CSS 重写 Base64Converter 页面
This commit is contained in:
@@ -1,18 +1,7 @@
|
|||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import { Upload, Trash2 } from 'lucide-react';
|
||||||
Alert,
|
|
||||||
alpha,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
CircularProgress,
|
|
||||||
Paper,
|
|
||||||
Stack,
|
|
||||||
Typography,
|
|
||||||
} from '@mui/material';
|
|
||||||
import TextInputArea from '@/components/TextInputArea';
|
import TextInputArea from '@/components/TextInputArea';
|
||||||
import type { ToolbarAction } from '@/components/TextInputArea';
|
import type { ToolbarAction } from '@/components/TextInputArea';
|
||||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
|
||||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CopyButton from '@/components/CopyButton';
|
import CopyButton from '@/components/CopyButton';
|
||||||
import DecodeResultPaper from '@/components/DecodeResultPaper';
|
import DecodeResultPaper from '@/components/DecodeResultPaper';
|
||||||
@@ -183,34 +172,18 @@ export default function FileMode() {
|
|||||||
|
|
||||||
{direction === 'encode' && (
|
{direction === 'encode' && (
|
||||||
<>
|
<>
|
||||||
<Box
|
<div
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
sx={{
|
className={`flex flex-col items-center justify-center min-h-[180px] border-2 border-dashed rounded-xl p-8 cursor-pointer transition-all duration-200 ${
|
||||||
display: 'flex',
|
isDragging
|
||||||
flexDirection: 'column',
|
? 'border-blue-500 bg-blue-50'
|
||||||
alignItems: 'center',
|
: info
|
||||||
justifyContent: 'center',
|
? 'border-blue-500 bg-blue-50/50'
|
||||||
minHeight: 180,
|
: 'border-gray-300 bg-gray-50 hover:border-blue-500 hover:bg-blue-50/50'
|
||||||
border: '2px dashed',
|
}`}
|
||||||
borderColor: isDragging ? 'info.main' : info ? 'info.main' : 'divider',
|
|
||||||
borderRadius: 3,
|
|
||||||
p: 4,
|
|
||||||
bgcolor: (theme) =>
|
|
||||||
isDragging
|
|
||||||
? alpha(theme.palette.info.main, 0.08)
|
|
||||||
: info
|
|
||||||
? alpha(theme.palette.info.main, 0.04)
|
|
||||||
: 'action.hover',
|
|
||||||
cursor: 'pointer',
|
|
||||||
transition: 'all 0.2s',
|
|
||||||
'&:hover': {
|
|
||||||
borderColor: 'info.main',
|
|
||||||
bgcolor: (theme) => alpha(theme.palette.info.main, 0.04),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
@@ -222,60 +195,47 @@ export default function FileMode() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<CircularProgress size={40} />
|
<div className="w-10 h-10 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin" />
|
||||||
) : info ? (
|
) : info ? (
|
||||||
<Stack spacing={1} alignItems="center">
|
<div className="flex flex-col items-center gap-1">
|
||||||
<UploadFileIcon sx={{ fontSize: 40, color: 'info.main' }} />
|
<Upload className="w-10 h-10 text-blue-600" />
|
||||||
<Typography variant="body2" fontWeight={700}>
|
<span className="text-sm font-bold">{info.name}</span>
|
||||||
{info.name}
|
<span className="text-xs text-gray-500">
|
||||||
</Typography>
|
|
||||||
<Typography variant="caption" color="text.secondary">
|
|
||||||
{formatFileSize(info.size)} · {info.type}
|
{formatFileSize(info.size)} · {info.type}
|
||||||
</Typography>
|
</span>
|
||||||
<Typography variant="caption" color="text.disabled">
|
<span className="text-xs text-gray-400">{t('clickOrDropToReplace')}</span>
|
||||||
{t('clickOrDropToReplace')}
|
</div>
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
) : (
|
) : (
|
||||||
<Stack spacing={1} alignItems="center">
|
<div className="flex flex-col items-center gap-1">
|
||||||
<UploadFileIcon sx={{ fontSize: 40, color: 'text.disabled' }} />
|
<Upload className="w-10 h-10 text-gray-400" />
|
||||||
<Typography variant="body2" color="text.secondary" fontWeight={600}>
|
<span className="text-sm text-gray-500 font-semibold">
|
||||||
{t('clickOrDropToFile')}
|
{t('clickOrDropToFile')}
|
||||||
</Typography>
|
</span>
|
||||||
<Typography variant="caption" color="text.disabled">
|
<span className="text-xs text-gray-400">
|
||||||
{t('maxFileSize', { max: `${MAX_FILE_SIZE / 1024 / 1024} MB` })}
|
{t('maxFileSize', { max: `${MAX_FILE_SIZE / 1024 / 1024} MB` })}
|
||||||
</Typography>
|
</span>
|
||||||
</Stack>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</div>
|
||||||
|
|
||||||
{error && <Alert severity="error">{error}</Alert>}
|
{error && (
|
||||||
|
<div
|
||||||
|
role="alert"
|
||||||
|
className="p-3 rounded-lg bg-red-50 border border-red-200 text-sm text-red-700"
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{result && (
|
{result && (
|
||||||
<Paper
|
<div className="p-4 rounded-xl bg-blue-50 border border-blue-200">
|
||||||
elevation={0}
|
<div className="flex justify-between items-center mb-2">
|
||||||
sx={{
|
<span className="text-xs font-bold text-gray-500">{t('base64Output')}</span>
|
||||||
p: 2,
|
<div className="flex gap-1">
|
||||||
borderRadius: 3,
|
|
||||||
bgcolor: (theme) => alpha(theme.palette.info.main, 0.04),
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: (theme) => alpha(theme.palette.info.main, 0.15),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
sx={{ mb: 1 }}
|
|
||||||
>
|
|
||||||
<Typography variant="caption" fontWeight={700} color="text.secondary">
|
|
||||||
{t('base64Output')}
|
|
||||||
</Typography>
|
|
||||||
<Stack direction="row" spacing={1}>
|
|
||||||
<CopyButton text={result.rawBase64} tooltip={t('copyRaw')} />
|
<CopyButton text={result.rawBase64} tooltip={t('copyRaw')} />
|
||||||
<CopyButton text={result.output} tooltip={t('copyDataUri')} color="info" />
|
<CopyButton text={result.output} tooltip={t('copyDataUri')} color="info" />
|
||||||
</Stack>
|
</div>
|
||||||
</Stack>
|
</div>
|
||||||
<TextInputArea
|
<TextInputArea
|
||||||
readOnly
|
readOnly
|
||||||
value={
|
value={
|
||||||
@@ -286,36 +246,35 @@ export default function FileMode() {
|
|||||||
showClear={false}
|
showClear={false}
|
||||||
showCount
|
showCount
|
||||||
/>
|
/>
|
||||||
<Stack direction="row" alignItems="center" spacing={2} sx={{ mt: 1 }}>
|
<div className="flex items-center gap-4 mt-2">
|
||||||
<Typography variant="caption" color="text.disabled">
|
<span className="text-xs text-gray-400">
|
||||||
{t('originalSize')}: {formatFileSize(result.originalBytes)}
|
{t('originalSize')}: {formatFileSize(result.originalBytes)}
|
||||||
</Typography>
|
</span>
|
||||||
<Typography variant="caption" color="text.disabled">
|
<span className="text-xs text-gray-400">
|
||||||
{t('encodedSize')}: {formatFileSize(result.outputBytes)}
|
{t('encodedSize')}: {formatFileSize(result.outputBytes)}
|
||||||
</Typography>
|
</span>
|
||||||
<Box sx={{ flex: 1 }} />
|
<div className="flex-1" />
|
||||||
<Button
|
<button
|
||||||
variant="text"
|
type="button"
|
||||||
size="small"
|
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
startIcon={<DeleteOutlineIcon />}
|
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 hover:bg-gray-100 rounded-md transition-colors"
|
||||||
sx={{ borderRadius: 2, minWidth: 0 }}
|
|
||||||
>
|
>
|
||||||
|
<Trash2 className="w-3 h-3" />
|
||||||
{t('clear')}
|
{t('clear')}
|
||||||
</Button>
|
</button>
|
||||||
</Stack>
|
</div>
|
||||||
</Paper>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{info && !result && (
|
{info && !result && (
|
||||||
<Button
|
<button
|
||||||
variant="text"
|
type="button"
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
startIcon={<DeleteOutlineIcon />}
|
className="flex items-center gap-1 px-3 py-2 text-sm text-gray-500 hover:bg-gray-100 rounded-lg transition-colors"
|
||||||
sx={{ borderRadius: 3 }}
|
|
||||||
>
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
{t('clear')}
|
{t('clear')}
|
||||||
</Button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,17 +1,6 @@
|
|||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import { Image, Trash2 } from 'lucide-react';
|
||||||
Alert,
|
|
||||||
alpha,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
CircularProgress,
|
|
||||||
Paper,
|
|
||||||
Stack,
|
|
||||||
Typography,
|
|
||||||
} from '@mui/material';
|
|
||||||
import TextInputArea, { type ToolbarAction } from '@/components/TextInputArea';
|
import TextInputArea, { type ToolbarAction } from '@/components/TextInputArea';
|
||||||
import ImageIcon from '@mui/icons-material/Image';
|
|
||||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CopyButton from '@/components/CopyButton';
|
import CopyButton from '@/components/CopyButton';
|
||||||
import DecodeResultPaper from '@/components/DecodeResultPaper';
|
import DecodeResultPaper from '@/components/DecodeResultPaper';
|
||||||
@@ -189,34 +178,18 @@ export default function ImageMode() {
|
|||||||
|
|
||||||
{direction === 'encode' && (
|
{direction === 'encode' && (
|
||||||
<>
|
<>
|
||||||
<Box
|
<div
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
onClick={() => imageInputRef.current?.click()}
|
onClick={() => imageInputRef.current?.click()}
|
||||||
sx={{
|
className={`flex flex-col items-center justify-center min-h-[180px] border-2 border-dashed rounded-xl p-8 cursor-pointer transition-all duration-200 ${
|
||||||
display: 'flex',
|
isDragging
|
||||||
flexDirection: 'column',
|
? 'border-blue-500 bg-blue-50'
|
||||||
alignItems: 'center',
|
: info
|
||||||
justifyContent: 'center',
|
? 'border-blue-500 bg-blue-50/50'
|
||||||
minHeight: 180,
|
: 'border-gray-300 bg-gray-50 hover:border-blue-500 hover:bg-blue-50/50'
|
||||||
border: '2px dashed',
|
}`}
|
||||||
borderColor: isDragging ? 'info.main' : info ? 'info.main' : 'divider',
|
|
||||||
borderRadius: 3,
|
|
||||||
p: 4,
|
|
||||||
bgcolor: (theme) =>
|
|
||||||
isDragging
|
|
||||||
? alpha(theme.palette.info.main, 0.08)
|
|
||||||
: info
|
|
||||||
? alpha(theme.palette.info.main, 0.04)
|
|
||||||
: 'action.hover',
|
|
||||||
cursor: 'pointer',
|
|
||||||
transition: 'all 0.2s',
|
|
||||||
'&:hover': {
|
|
||||||
borderColor: 'info.main',
|
|
||||||
bgcolor: (theme) => alpha(theme.palette.info.main, 0.04),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref={imageInputRef}
|
ref={imageInputRef}
|
||||||
@@ -229,108 +202,76 @@ export default function ImageMode() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<CircularProgress size={40} />
|
<div className="w-10 h-10 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin" />
|
||||||
) : info ? (
|
) : info ? (
|
||||||
<Stack spacing={1} alignItems="center">
|
<div className="flex flex-col items-center gap-1">
|
||||||
{result && (
|
{result && (
|
||||||
<Box
|
<img
|
||||||
component="img"
|
|
||||||
src={result.output}
|
src={result.output}
|
||||||
alt="preview"
|
alt="preview"
|
||||||
sx={{
|
className="max-w-full max-h-40 rounded-lg object-contain mb-2"
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: 160,
|
|
||||||
borderRadius: 2,
|
|
||||||
objectFit: 'contain',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Typography variant="body2" fontWeight={700}>
|
<span className="text-sm font-bold">{info.name}</span>
|
||||||
{info.name}
|
<span className="text-xs text-gray-500">
|
||||||
</Typography>
|
|
||||||
<Typography variant="caption" color="text.secondary">
|
|
||||||
{formatFileSize(info.size)} · {info.type}
|
{formatFileSize(info.size)} · {info.type}
|
||||||
</Typography>
|
</span>
|
||||||
<Typography variant="caption" color="text.disabled">
|
<span className="text-xs text-gray-400">{t('clickOrDropToReplace')}</span>
|
||||||
{t('clickOrDropToReplace')}
|
</div>
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
) : (
|
) : (
|
||||||
<Stack spacing={1} alignItems="center">
|
<div className="flex flex-col items-center gap-1">
|
||||||
<ImageIcon sx={{ fontSize: 40, color: 'text.disabled' }} />
|
<Image className="w-10 h-10 text-gray-400" />
|
||||||
<Typography variant="body2" color="text.secondary" fontWeight={600}>
|
<span className="text-sm text-gray-500 font-semibold">
|
||||||
{t('clickOrDropToImage')}
|
{t('clickOrDropToImage')}
|
||||||
</Typography>
|
</span>
|
||||||
<Typography variant="caption" color="text.disabled">
|
<span className="text-xs text-gray-400">{t('supportedFormats')}</span>
|
||||||
{t('supportedFormats')}
|
</div>
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</div>
|
||||||
|
|
||||||
{error && <Alert severity="error">{error}</Alert>}
|
{error && (
|
||||||
|
<div
|
||||||
|
role="alert"
|
||||||
|
className="p-3 rounded-lg bg-red-50 border border-red-200 text-sm text-red-700"
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{result && (
|
{result && (
|
||||||
<Paper
|
<div className="p-4 rounded-xl bg-blue-50 border border-blue-200">
|
||||||
elevation={0}
|
<div className="flex justify-between items-center mb-2">
|
||||||
sx={{
|
<span className="text-xs font-bold text-gray-500">{t('base64Output')}</span>
|
||||||
p: 2,
|
<div className="flex gap-1">
|
||||||
borderRadius: 3,
|
|
||||||
bgcolor: (theme) => alpha(theme.palette.info.main, 0.04),
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: (theme) => alpha(theme.palette.info.main, 0.15),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
sx={{ mb: 1 }}
|
|
||||||
>
|
|
||||||
<Typography variant="caption" fontWeight={700} color="text.secondary">
|
|
||||||
{t('base64Output')}
|
|
||||||
</Typography>
|
|
||||||
<Stack direction="row" spacing={1}>
|
|
||||||
<CopyButton text={result.rawBase64} tooltip={t('copyRaw')} />
|
<CopyButton text={result.rawBase64} tooltip={t('copyRaw')} />
|
||||||
<CopyButton text={result.output} tooltip={t('copyDataUri')} color="info" />
|
<CopyButton text={result.output} tooltip={t('copyDataUri')} color="info" />
|
||||||
</Stack>
|
</div>
|
||||||
</Stack>
|
</div>
|
||||||
<Box
|
<div className="font-mono text-xs break-all max-h-[200px] overflow-y-auto leading-relaxed text-blue-600 font-semibold">
|
||||||
sx={{
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
wordBreak: 'break-all',
|
|
||||||
maxHeight: 200,
|
|
||||||
overflowY: 'auto',
|
|
||||||
lineHeight: 1.6,
|
|
||||||
color: 'info.main',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{result.output.length > 2000
|
{result.output.length > 2000
|
||||||
? `${result.output.substring(0, 2000)}...`
|
? `${result.output.substring(0, 2000)}...`
|
||||||
: result.output}
|
: result.output}
|
||||||
</Box>
|
</div>
|
||||||
<Stack direction="row" spacing={2} sx={{ mt: 1 }}>
|
<div className="flex gap-4 mt-2">
|
||||||
<Typography variant="caption" color="text.disabled">
|
<span className="text-xs text-gray-400">
|
||||||
{t('originalSize')}: {formatFileSize(result.originalBytes)}
|
{t('originalSize')}: {formatFileSize(result.originalBytes)}
|
||||||
</Typography>
|
</span>
|
||||||
<Typography variant="caption" color="text.disabled">
|
<span className="text-xs text-gray-400">
|
||||||
{t('encodedSize')}: {formatFileSize(result.outputBytes)}
|
{t('encodedSize')}: {formatFileSize(result.outputBytes)}
|
||||||
</Typography>
|
</span>
|
||||||
</Stack>
|
</div>
|
||||||
</Paper>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{info && (
|
{info && (
|
||||||
<Button
|
<button
|
||||||
variant="text"
|
type="button"
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
startIcon={<DeleteOutlineIcon />}
|
className="flex items-center gap-1 px-3 py-2 text-sm text-gray-500 hover:bg-gray-100 rounded-lg transition-colors"
|
||||||
sx={{ borderRadius: 3 }}
|
|
||||||
>
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
{t('clear')}
|
{t('clear')}
|
||||||
</Button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -361,17 +302,10 @@ export default function ImageMode() {
|
|||||||
onFileNameChange={setDecodedFileName}
|
onFileNameChange={setDecodedFileName}
|
||||||
onDownload={handleDownload}
|
onDownload={handleDownload}
|
||||||
>
|
>
|
||||||
<Box
|
<img
|
||||||
component="img"
|
|
||||||
src={`data:${decoded.mimeType};base64,${decoded.rawBase64}`}
|
src={`data:${decoded.mimeType};base64,${decoded.rawBase64}`}
|
||||||
alt="decoded preview"
|
alt="decoded preview"
|
||||||
sx={{
|
className="max-w-full max-h-40 rounded-lg object-contain mb-3"
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: 160,
|
|
||||||
borderRadius: 2,
|
|
||||||
objectFit: 'contain',
|
|
||||||
mb: 1.5,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</DecodeResultPaper>
|
</DecodeResultPaper>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { Alert, alpha, Button, Paper, Stack, Typography } from '@mui/material';
|
import { ArrowLeftRight } from 'lucide-react';
|
||||||
import TextInputArea, { type ToolbarAction } from '@/components/TextInputArea';
|
import TextInputArea, { type ToolbarAction } from '@/components/TextInputArea';
|
||||||
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CopyButton from '@/components/CopyButton';
|
import CopyButton from '@/components/CopyButton';
|
||||||
import { textToBase64, base64ToText } from '@/utils/base64Converter';
|
import { textToBase64, base64ToText } from '@/utils/base64Converter';
|
||||||
@@ -71,7 +70,7 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
|
|||||||
{
|
{
|
||||||
key: 'convert',
|
key: 'convert',
|
||||||
label: actionLabel,
|
label: actionLabel,
|
||||||
icon: <SwapHorizIcon />,
|
icon: <ArrowLeftRight />,
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
disabled: (value: string) => !value.trim(),
|
disabled: (value: string) => !value.trim(),
|
||||||
@@ -121,42 +120,31 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{showImageHint && (
|
{showImageHint && (
|
||||||
<Alert
|
<div className="flex items-center justify-between p-3 rounded-lg bg-blue-50 border border-blue-200">
|
||||||
severity="info"
|
<span className="text-sm text-blue-700">{t('imageDataUriHint')}</span>
|
||||||
action={
|
<button
|
||||||
<Button color="info" size="small" onClick={onSwitchToImageMode}>
|
type="button"
|
||||||
{t('switchToImageMode')}
|
onClick={onSwitchToImageMode}
|
||||||
</Button>
|
className="px-3 py-1 text-sm font-medium text-blue-600 hover:bg-blue-100 rounded-md transition-colors"
|
||||||
}
|
>
|
||||||
>
|
{t('switchToImageMode')}
|
||||||
{t('imageDataUriHint')}
|
</button>
|
||||||
</Alert>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{output && (
|
{output && (
|
||||||
<Paper
|
<div className="p-4 rounded-xl bg-blue-50 border border-blue-200">
|
||||||
elevation={0}
|
<div className="flex justify-between items-center mb-2">
|
||||||
sx={{
|
<span className="text-xs font-bold text-gray-500">{outputLabel}</span>
|
||||||
p: 2,
|
|
||||||
borderRadius: 3,
|
|
||||||
bgcolor: (theme) => alpha(theme.palette.info.main, 0.04),
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: (theme) => alpha(theme.palette.info.main, 0.15),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 1 }}>
|
|
||||||
<Typography variant="caption" fontWeight={700} color="text.secondary">
|
|
||||||
{outputLabel}
|
|
||||||
</Typography>
|
|
||||||
<CopyButton text={output} />
|
<CopyButton text={output} />
|
||||||
</Stack>
|
</div>
|
||||||
<TextInputArea
|
<TextInputArea
|
||||||
readOnly
|
readOnly
|
||||||
value={output.length > 2000 ? `${output.substring(0, 2000)}...` : output}
|
value={output.length > 2000 ? `${output.substring(0, 2000)}...` : output}
|
||||||
showClear={false}
|
showClear={false}
|
||||||
showCount
|
showCount
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import { Box, Container, Stack } from '@mui/material';
|
import { Type, Upload, Image } from 'lucide-react';
|
||||||
import TextFieldsIcon from '@mui/icons-material/TextFields';
|
|
||||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
|
||||||
import ImageIcon from '@mui/icons-material/Image';
|
|
||||||
import { useLazyTranslation } from '@/utils/useLazyTranslation';
|
import { useLazyTranslation } from '@/utils/useLazyTranslation';
|
||||||
import PageHeader from '@/components/PageHeader';
|
import PageHeader from '@/components/PageHeader';
|
||||||
import { base64ConverterPageStyles } from '@/config/pageTheme';
|
import { base64ConverterPageStyles } from '@/config/pageTheme';
|
||||||
@@ -28,14 +25,14 @@ export default function Index() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const modeIcon: Record<PageMode, React.ReactNode> = {
|
const modeIcon: Record<PageMode, React.ReactNode> = {
|
||||||
text: <TextFieldsIcon />,
|
text: <Type />,
|
||||||
file: <UploadFileIcon />,
|
file: <Upload />,
|
||||||
image: <ImageIcon />,
|
image: <Image />,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<div>
|
||||||
<Container sx={{ p: 2 }}>
|
<div className="p-2">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={t('base64Converter:pageTitle')}
|
title={t('base64Converter:pageTitle')}
|
||||||
subtitle={t('base64Converter:pageSubtitle')}
|
subtitle={t('base64Converter:pageSubtitle')}
|
||||||
@@ -43,7 +40,7 @@ export default function Index() {
|
|||||||
iconColor={base64ConverterPageStyles.primaryColor}
|
iconColor={base64ConverterPageStyles.primaryColor}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack spacing={2.5}>
|
<div className="flex flex-col gap-6">
|
||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
value={pageMode}
|
value={pageMode}
|
||||||
options={[
|
options={[
|
||||||
@@ -58,8 +55,8 @@ export default function Index() {
|
|||||||
{pageMode === 'text' && <TextMode onSwitchToImageMode={() => setPageMode('image')} />}
|
{pageMode === 'text' && <TextMode onSwitchToImageMode={() => setPageMode('image')} />}
|
||||||
{pageMode === 'file' && <FileMode />}
|
{pageMode === 'file' && <FileMode />}
|
||||||
{pageMode === 'image' && <ImageMode />}
|
{pageMode === 'image' && <ImageMode />}
|
||||||
</Stack>
|
</div>
|
||||||
</Container>
|
</div>
|
||||||
</Box>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user