Download apps/frontend/src/app/[roomCode]/components/FileList.tsx from Husr/clip: direct link, hf CLI and curl.
- Browser
- Download file 7 kB
-
https://huggingface.co/spaces/Husr/clip/resolve/main/apps/frontend/src/app/%5BroomCode%5D/components/FileList.tsx
- Command line
-
hf download 'hf://spaces/Husr/clip/apps/frontend/src/app/[roomCode]/components/FileList.tsx'
-
curl -L -o FileList.tsx https://huggingface.co/spaces/Husr/clip/resolve/main/apps/frontend/src/app/%5BroomCode%5D/components/FileList.tsx
7 kB
| 'use client'; | |
| import { useState, useEffect } from 'react'; | |
| import { FileIcon, Image, FileText, Download, Trash2, Eye, AlertCircle } from 'lucide-react'; | |
| import { apiUrl } from '@/lib/constants'; | |
| import FilePreview from './FilePreview'; | |
| import { isPreviewable, formatFileSize } from '@/lib/utils/fileUtils'; | |
| import { appendTokenToUrl } from '@/lib/adminAuth'; | |
| interface FileEntry { | |
| id: string; | |
| filename: string; | |
| mimetype: string; | |
| size: number; | |
| createdAt: string; | |
| createdBy: string; | |
| } | |
| interface FileListProps { | |
| files: FileEntry[]; | |
| onDeleteFile: (fileId: string) => void; | |
| roomCode: string; | |
| } | |
| export default function FileList({ files, onDeleteFile, roomCode }: FileListProps) { | |
| const [deletingFile, setDeletingFile] = useState<string | null>(null); | |
| const [previewFile, setPreviewFile] = useState<FileEntry | null>(null); | |
| // Clear preview if the previewed file is deleted | |
| useEffect(() => { | |
| if (previewFile && !files.some(file => file.id === previewFile.id)) { | |
| setPreviewFile(null); | |
| } | |
| }, [files, previewFile]); | |
| if (files.length === 0) { | |
| return null; | |
| } | |
| const getFileIcon = (mimetype: string) => { | |
| if (mimetype.startsWith('image/')) { | |
| return <Image className="h-5 w-5 text-blue-500" />; | |
| } else if (mimetype.includes('pdf')) { | |
| return <FileText className="h-5 w-5 text-red-500" />; | |
| } else { | |
| return <FileIcon className="h-5 w-5 text-gray-500" />; | |
| } | |
| }; | |
| // Check if a file is previewable | |
| const canPreview = (file: FileEntry) => { | |
| return isPreviewable(file.mimetype); | |
| }; | |
| return ( | |
| <div className="mt-6 sm:mt-8"> | |
| <div className="flex items-center justify-between mb-4 sticky top-0 bg-background/90 backdrop-blur-sm py-2 z-10"> | |
| <div className="flex-1"></div> | |
| <div className="flex items-center gap-2"> | |
| </div> | |
| </div> | |
| {previewFile && ( | |
| <FilePreview | |
| fileId={previewFile.id} | |
| filename={previewFile.filename} | |
| mimetype={previewFile.mimetype} | |
| roomCode={roomCode} | |
| onClose={() => setPreviewFile(null)} | |
| /> | |
| )} | |
| <div className="space-y-2"> | |
| {files.length === 0 ? ( | |
| <div className="border border-dashed border-surface-hover rounded-lg flex flex-col items-center justify-center py-12 mt-4"> | |
| <div className="w-16 h-16 bg-surface-hover rounded-full flex items-center justify-center mb-4"> | |
| <FileIcon className="h-8 w-8 text-text-secondary/50" /> | |
| </div> | |
| <p className="text-text-secondary mb-2">No files yet</p> | |
| <p className="text-text-secondary/70 text-sm">Upload your first file using the form above</p> | |
| </div> | |
| ) : ( | |
| files.map((file) => ( | |
| <div | |
| key={file.id} | |
| className={`flex items-center p-3 bg-surface/80 rounded-lg border border-surface-hover hover:border-primary/30 transition-colors relative ${canPreview(file) ? 'cursor-pointer' : ''}`} | |
| onClick={() => canPreview(file) ? setPreviewFile(file) : null} | |
| > | |
| <div className="mr-3"> | |
| {getFileIcon(file.mimetype)} | |
| </div> | |
| <div className="flex-grow min-w-0"> | |
| <p className="text-text-primary font-medium truncate">{file.filename}</p> | |
| <p className="text-text-tertiary text-xs"> | |
| {formatFileSize(file.size)} • {new Date(file.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} | |
| </p> | |
| </div> | |
| <div className="flex space-x-2" onClick={(e) => e.stopPropagation()}> | |
| {canPreview(file) ? ( | |
| <button | |
| onClick={() => setPreviewFile(file)} | |
| className="p-1.5 text-text-secondary hover:text-primary rounded-md hover:bg-primary/10 transition-colors" | |
| title="Preview" | |
| > | |
| <Eye className="h-4 w-4" /> | |
| </button> | |
| ) : ( | |
| <span | |
| className="p-1.5 text-text-tertiary cursor-not-allowed flex items-center" | |
| title="Preview not available for this file type" | |
| > | |
| <AlertCircle className="h-4 w-4" /> | |
| </span> | |
| )} | |
| <a | |
| onClick={(e) => e.stopPropagation()} | |
| href={appendTokenToUrl(`${apiUrl}/clipboard/${roomCode}/files/${file.id}?filename=${encodeURIComponent(file.filename)}`)} | |
| download={file.filename} | |
| className="p-1.5 text-text-secondary hover:text-primary rounded-md hover:bg-primary/10 transition-colors" | |
| title="Download" | |
| > | |
| <Download className="h-4 w-4" /> | |
| </a> | |
| <button | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| setDeletingFile(file.id) | |
| }} | |
| className="p-1.5 text-text-secondary hover:text-error rounded-md hover:bg-error/10 transition-colors" | |
| title="Delete" | |
| > | |
| <Trash2 className="h-4 w-4" /> | |
| </button> | |
| </div> | |
| {/* Delete confirmation */} | |
| {deletingFile === file.id && ( | |
| <div className="absolute inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center z-10 rounded-lg"> | |
| <div className="bg-surface p-4 rounded-lg shadow-lg max-w-xs w-full"> | |
| <h4 className="text-text-primary font-medium mb-2">Delete file?</h4> | |
| <p className="text-text-secondary text-sm mb-4">This action cannot be undone.</p> | |
| <div className="flex justify-end space-x-2"> | |
| <button | |
| onClick={() => setDeletingFile(null)} | |
| className="px-3 py-1.5 text-text-secondary hover:text-text-primary bg-surface-hover rounded-md" | |
| > | |
| Cancel | |
| </button> | |
| <button | |
| onClick={() => { | |
| // If this file is being previewed, close the preview first | |
| if (previewFile && previewFile.id === file.id) { | |
| setPreviewFile(null); | |
| } | |
| // Then delete the file | |
| onDeleteFile(file.id); | |
| setDeletingFile(null); | |
| }} | |
| className="px-3 py-1.5 text-white bg-error hover:bg-error/90 rounded-md" | |
| > | |
| Delete | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| )) | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |