diff --git a/src/islands/dev/ClipboardInspector.tsx b/src/islands/dev/ClipboardInspector.tsx new file mode 100644 index 0000000..41c7b14 --- /dev/null +++ b/src/islands/dev/ClipboardInspector.tsx @@ -0,0 +1,301 @@ +import { useEffect, useRef, useState } from 'react'; +import { ClipboardPaste, Download, Trash2, RefreshCw, FileVideo, FileAudio, FileImage, FileText, File, Code } from 'lucide-react'; +import { Button } from '@/components/ui/Button'; +import { Alert } from '@/components/ui/Alert'; +import { + readClipboard, parseDataTransfer, previewKindOf, formatSize, mimeToExtension, + type ClipboardSnapshot, type ClipboardItemEntry, type PreviewKind, +} from '@/tools/dev/clipboard.lib'; +import { downloadService } from '@/services/download.service'; +import type { Lang } from '@/i18n/config'; + +let _snapshotCounter = 0; + +const KIND_ICON: Record = { + text: FileText, + html: Code, + image: FileImage, + video: FileVideo, + audio: FileAudio, + pdf: File, + binary: File, +}; + +const KIND_LABEL: Record = { + text: 'Text', + html: 'HTML', + image: 'Image', + video: 'Video', + audio: 'Audio', + pdf: 'PDF', + binary: 'Binary', +}; + +function TypeBadge({ kind, type }: { kind: PreviewKind; type: string }) { + const Icon = KIND_ICON[kind]; + const colors: Record = { + text: 'bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300', + html: 'bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-300', + image: 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300', + video: 'bg-purple-100 text-purple-800 dark:bg-purple-900/40 dark:text-purple-300', + audio: 'bg-pink-100 text-pink-800 dark:bg-pink-900/40 dark:text-pink-300', + pdf: 'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300', + binary: 'bg-muted text-muted-foreground', + }; + return ( + + + {KIND_LABEL[kind]} + {type} + + ); +} + +function ItemPreview({ item }: { item: ClipboardItemEntry }) { + const [showHtml, setShowHtml] = useState<'source' | 'render'>('render'); + + function handleDownload() { + const ext = item.filename + ? item.filename.split('.').pop() ?? mimeToExtension(item.type) + : mimeToExtension(item.type); + const name = item.filename ?? `clipboard.${ext}`; + if (item.blobUrl) { + downloadService.download(item.blobUrl, name); + } else if (item.text != null) { + const blob = new Blob([item.text], { type: item.type }); + const url = URL.createObjectURL(blob); + downloadService.download(url, name); + setTimeout(() => URL.revokeObjectURL(url), 5000); + } + } + + return ( +
+ {/* Item header */} +
+
+ + + {formatSize(item.size)} + {item.filename && <> · {item.filename}} + +
+
+ {item.kind === 'html' && ( + + )} + +
+
+ + {/* Preview */} +
+ {item.kind === 'text' && ( +
+            {item.text}
+          
+ )} + + {item.kind === 'html' && showHtml === 'render' && ( +