summaryrefslogtreecommitdiff
path: root/frontend/components/FileTable.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/components/FileTable.tsx')
-rw-r--r--frontend/components/FileTable.tsx179
1 files changed, 0 insertions, 179 deletions
diff --git a/frontend/components/FileTable.tsx b/frontend/components/FileTable.tsx
deleted file mode 100644
index 97660f3..0000000
--- a/frontend/components/FileTable.tsx
+++ /dev/null
@@ -1,179 +0,0 @@
1'use client'
2
3import { useState } from 'react'
4import { DriveLsEntry } from '@/lib/drive_types'
5import { Drive_basename } from '@/lib/drive_shared'
6import { formatSize } from '@/lib/utils'
7import Link from 'next/link'
8
9interface FileTableEntry extends DriveLsEntry {
10 isParent?: boolean
11 parentPath?: string
12}
13
14interface FileTableProps {
15 entries: DriveLsEntry[]
16 currentPath?: string
17 showParent?: boolean
18 parentPath?: string
19 onSelectedFilesChange?: (selectedFiles: string[]) => void
20}
21
22export default function FileTable({
23 entries,
24 currentPath = '',
25 showParent = false,
26 parentPath,
27 onSelectedFilesChange
28}: FileTableProps) {
29 const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set())
30
31 // Create entries with optional parent directory at top
32 const allEntries: FileTableEntry[] = []
33 if (showParent && parentPath !== undefined) {
34 allEntries.push({
35 path: '(parent)',
36 type: 'dir' as const,
37 lastmod: 0,
38 blob: null,
39 size: null,
40 author: '',
41 isParent: true,
42 parentPath
43 })
44 }
45
46 // Sort entries: directories first, then files, both alphabetically
47 const sortedEntries = entries.sort((a, b) => {
48 // First sort by type (directories before files)
49 if (a.type !== b.type) {
50 return a.type === 'dir' ? -1 : 1
51 }
52 // Then sort alphabetically by path
53 return a.path.localeCompare(b.path)
54 })
55
56 allEntries.push(...sortedEntries)
57
58 const handleFileSelection = (filePath: string, isSelected: boolean) => {
59 const newSelectedFiles = new Set(selectedFiles)
60 if (isSelected) {
61 newSelectedFiles.add(filePath)
62 } else {
63 newSelectedFiles.delete(filePath)
64 }
65 setSelectedFiles(newSelectedFiles)
66 onSelectedFilesChange?.(Array.from(newSelectedFiles))
67 }
68
69 const handleSelectAll = (isSelected: boolean) => {
70 if (isSelected) {
71 // Select all files (not directories or parent)
72 const fileEntries = allEntries.filter(entry =>
73 entry.type === 'file' && !entry.isParent
74 )
75 const newSelectedFiles = new Set(fileEntries.map(entry => entry.path))
76 setSelectedFiles(newSelectedFiles)
77 onSelectedFilesChange?.(Array.from(newSelectedFiles))
78 } else {
79 setSelectedFiles(new Set())
80 onSelectedFilesChange?.([])
81 }
82 }
83
84 const selectableFiles = allEntries.filter(entry =>
85 entry.type === 'file' && !entry.isParent
86 )
87 const allFilesSelected = selectableFiles.length > 0 &&
88 selectableFiles.every(entry => selectedFiles.has(entry.path))
89
90 return (
91 <div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
92 <div className="overflow-x-auto">
93 <table className="w-full">
94 <thead className="bg-gray-50 dark:bg-gray-700">
95 <tr>
96 <th className="px-4 py-3 text-left">
97 <input
98 type="checkbox"
99 checked={allFilesSelected}
100 onChange={(e) => handleSelectAll(e.target.checked)}
101 className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
102 disabled={selectableFiles.length === 0}
103 />
104 </th>
105 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
106 Name
107 </th>
108 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
109 Size
110 </th>
111 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
112 Author
113 </th>
114 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
115 Modified
116 </th>
117 </tr>
118 </thead>
119 <tbody className="divide-y divide-gray-200 dark:divide-gray-600">
120 {allEntries.map((entry) => (
121 <tr
122 key={entry.path}
123 className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
124 >
125 <td className="px-4 py-4 whitespace-nowrap">
126 {entry.type === 'file' && !entry.isParent ? (
127 <input
128 type="checkbox"
129 checked={selectedFiles.has(entry.path)}
130 onChange={(e) => handleFileSelection(entry.path, e.target.checked)}
131 className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
132 />
133 ) : (
134 <div className="w-4 h-4" /> // Placeholder to maintain alignment
135 )}
136 </td>
137 <td className="px-4 py-4 whitespace-nowrap">
138 <div className="flex items-center">
139 <div className="flex-shrink-0 h-5 w-5 mr-3">
140 {entry.type === 'dir' ? (
141 <div className="h-5 w-5 text-blue-500">📁</div>
142 ) : (
143 <div className="h-5 w-5 text-gray-400">📄</div>
144 )}
145 </div>
146 {entry.type === 'dir' ? (
147 <Link
148 href={entry.isParent ? entry.parentPath! : `/drive${entry.path}`}
149 className="text-sm font-medium text-blue-600 dark:text-blue-400 hover:underline"
150 >
151 {entry.isParent ? '(parent)' : Drive_basename(entry.path)}
152 </Link>
153 ) : (
154 <Link
155 href={`/blob/${entry.blob}?filename=${encodeURIComponent(Drive_basename(entry.path))}`}
156 className="text-sm font-medium text-blue-600 dark:text-blue-400 hover:underline"
157 >
158 {Drive_basename(entry.path)}
159 </Link>
160 )}
161 </div>
162 </td>
163 <td className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
164 {formatSize(entry.size)}
165 </td>
166 <td className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
167 {entry.author}
168 </td>
169 <td className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
170 {entry.lastmod > 0 ? new Date(entry.lastmod * 1000).toLocaleString() : ''}
171 </td>
172 </tr>
173 ))}
174 </tbody>
175 </table>
176 </div>
177 </div>
178 )
179} \ No newline at end of file