o2satz commited on
Commit
ecbdee5
·
verified ·
1 Parent(s): 14385b8

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +6 -4
  2. index.html +755 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Deepsite Filesearch
3
- emoji: 👁
4
- colorFrom: red
5
  colorTo: blue
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: deepsite-filesearch
3
+ emoji: 🐳
4
+ colorFrom: blue
5
  colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,755 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced File Finder</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
11
+
12
+ body {
13
+ font-family: 'Inter', sans-serif;
14
+ }
15
+
16
+ .file-icon {
17
+ color: #6366f1;
18
+ }
19
+ .folder-icon {
20
+ color: #f59e0b;
21
+ }
22
+ .sort-active {
23
+ background-color: #e0e7ff;
24
+ }
25
+ .file-row:hover {
26
+ background-color: #f8fafc;
27
+ transform: translateY(-1px);
28
+ }
29
+ .sidebar-item:hover {
30
+ background-color: #f1f5f9;
31
+ }
32
+ .transition-all {
33
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
34
+ }
35
+ .card-shadow {
36
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
37
+ }
38
+ .modal-shadow {
39
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
40
+ }
41
+ .glow {
42
+ animation: glow 2s infinite alternate;
43
+ }
44
+ @keyframes glow {
45
+ from {
46
+ box-shadow: 0 0 0px 0px rgba(99, 102, 241, 0.3);
47
+ }
48
+ to {
49
+ box-shadow: 0 0 8px 2px rgba(99, 102, 241, 0.3);
50
+ }
51
+ }
52
+ .highlight {
53
+ background: linear-gradient(90deg, rgba(224,231,255,0) 0%, rgba(224,231,255,0.5) 50%, rgba(224,231,255,0) 100%);
54
+ }
55
+ ::-webkit-scrollbar {
56
+ width: 8px;
57
+ height: 8px;
58
+ }
59
+ ::-webkit-scrollbar-track {
60
+ background: #f1f5f9;
61
+ border-radius: 4px;
62
+ }
63
+ ::-webkit-scrollbar-thumb {
64
+ background: #cbd5e1;
65
+ border-radius: 4px;
66
+ }
67
+ ::-webkit-scrollbar-thumb:hover {
68
+ background: #94a3b8;
69
+ }
70
+ </style>
71
+ </head>
72
+ <body class="bg-slate-50">
73
+ <div id="app" class="flex h-screen">
74
+ <!-- Sidebar -->
75
+ <div class="w-64 bg-white border-r border-slate-200 flex flex-col">
76
+ <div class="p-5 border-b border-slate-200">
77
+ <div class="flex items-center space-x-3 mb-6">
78
+ <div class="w-9 h-9 rounded-lg bg-indigo-100 flex items-center justify-center">
79
+ <i class="fas fa-search text-indigo-500"></i>
80
+ </div>
81
+ <h1 class="text-xl font-semibold text-slate-800">Advanced Finder</h1>
82
+ </div>
83
+ <div class="relative">
84
+ <input
85
+ type="text"
86
+ v-model="sidebarSearch"
87
+ placeholder="Search locations..."
88
+ class="w-full px-4 py-2.5 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400"
89
+ >
90
+ <i class="fas fa-search absolute right-3 top-3 text-slate-400"></i>
91
+ </div>
92
+ </div>
93
+ <div class="flex-1 overflow-y-auto">
94
+ <div class="p-2">
95
+ <div
96
+ v-for="location in filteredLocations"
97
+ :key="location.id"
98
+ class="sidebar-item px-3 py-2.5 rounded-lg cursor-pointer flex items-center transition-all group"
99
+ :class="{'bg-indigo-50': currentLocation.id === location.id}"
100
+ @click="selectLocation(location)"
101
+ >
102
+ <div class="w-8 h-8 rounded-md flex items-center justify-center mr-2"
103
+ :class="{'bg-indigo-100': currentLocation.id === location.id, 'bg-slate-100 group-hover:bg-slate-200': currentLocation.id !== location.id}">
104
+ <i :class="location.icon" class="text-slate-600 group-hover:text-slate-800"
105
+ :class="{'text-indigo-500': currentLocation.id === location.id}"></i>
106
+ </div>
107
+ <span class="text-sm font-medium truncate"
108
+ :class="{'text-indigo-600': currentLocation.id === location.id, 'text-slate-700 group-hover:text-slate-900': currentLocation.id !== location.id}">
109
+ {{ location.name }}
110
+ </span>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ <div class="p-4 border-t border-slate-200 text-xs text-slate-500 bg-slate-50">
115
+ <div class="flex items-center mb-1">
116
+ <i class="fas fa-database mr-2"></i>
117
+ <span>Indexed: {{ indexedCount }} items</span>
118
+ </div>
119
+ <div class="flex items-center">
120
+ <i class="fas fa-sync-alt mr-2"></i>
121
+ <span>Last indexed: {{ lastIndexed }}</span>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Main Content -->
127
+ <div class="flex-1 flex flex-col overflow-hidden">
128
+ <!-- Toolbar -->
129
+ <div class="bg-white border-b border-slate-200 p-4 flex items-center justify-between">
130
+ <div class="flex items-center space-x-2">
131
+ <button
132
+ class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
133
+ @click="refreshFiles"
134
+ >
135
+ <i class="fas fa-sync-alt mr-2"></i> Refresh
136
+ </button>
137
+ <button
138
+ class="px-4 py-2 bg-white border border-slate-200 rounded-lg hover:bg-slate-50 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
139
+ @click="toggleIndexing"
140
+ :class="{'glow': isIndexing}"
141
+ >
142
+ <i class="fas fa-database mr-2" :class="{'text-indigo-600': isIndexing}"></i>
143
+ <span v-if="isIndexing">Indexing...</span>
144
+ <span v-else>Index Now</span>
145
+ </button>
146
+ <div class="relative" v-if="selectedFiles.length > 0">
147
+ <button
148
+ class="px-4 py-2 bg-red-50 text-red-600 rounded-lg hover:bg-red-100 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
149
+ @click="showDeleteConfirm = true"
150
+ >
151
+ <i class="fas fa-trash mr-2"></i> Delete ({{ selectedFiles.length }})
152
+ </button>
153
+ </div>
154
+ </div>
155
+ <div class="flex items-center space-x-3">
156
+ <div class="relative">
157
+ <input
158
+ type="text"
159
+ v-model="searchQuery"
160
+ placeholder="Search files..."
161
+ class="px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent w-64 placeholder-slate-400"
162
+ >
163
+ <i class="fas fa-search absolute right-3 top-3 text-slate-400"></i>
164
+ </div>
165
+ <button
166
+ class="px-4 py-2 bg-white border border-slate-200 rounded-lg hover:bg-slate-50 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
167
+ @click="showAdvancedSearch = !showAdvancedSearch"
168
+ >
169
+ <i class="fas fa-sliders-h mr-2"></i> Filters
170
+ </button>
171
+ </div>
172
+ </div>
173
+
174
+ <!-- Advanced Search Panel -->
175
+ <div
176
+ class="bg-white border-b border-slate-200 px-5 py-4 transition-all duration-300 ease-in-out overflow-hidden"
177
+ :style="showAdvancedSearch ? 'max-height: 240px' : 'max-height: 0'"
178
+ >
179
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-5">
180
+ <div>
181
+ <label class="block text-sm font-medium text-slate-700 mb-2">File Type</label>
182
+ <select
183
+ v-model="filters.fileType"
184
+ class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
185
+ >
186
+ <option value="all">All Files</option>
187
+ <option value="documents">Documents</option>
188
+ <option value="images">Images</option>
189
+ <option value="audio">Audio</option>
190
+ <option value="video">Video</option>
191
+ <option value="archives">Archives</option>
192
+ </select>
193
+ </div>
194
+ <div>
195
+ <label class="block text-sm font-medium text-slate-700 mb-2">Author/Creator</label>
196
+ <input
197
+ type="text"
198
+ v-model="filters.author"
199
+ placeholder="Search by author..."
200
+ class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400"
201
+ >
202
+ </div>
203
+ <div>
204
+ <label class="block text-sm font-medium text-slate-700 mb-2">Size Range</label>
205
+ <div class="flex items-center space-x-3">
206
+ <input
207
+ type="number"
208
+ v-model="filters.sizeMin"
209
+ placeholder="Min (KB)"
210
+ class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400"
211
+ >
212
+ <span class="text-sm text-slate-400">to</span>
213
+ <input
214
+ type="number"
215
+ v-model="filters.sizeMax"
216
+ placeholder="Max (KB)"
217
+ class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400"
218
+ >
219
+ </div>
220
+ </div>
221
+ <div>
222
+ <label class="block text-sm font-medium text-slate-700 mb-2">Date Modified</label>
223
+ <select
224
+ v-model="filters.dateRange"
225
+ class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
226
+ >
227
+ <option value="all">Any Time</option>
228
+ <option value="today">Today</option>
229
+ <option value="week">This Week</option>
230
+ <option value="month">This Month</option>
231
+ <option value="year">This Year</option>
232
+ <option value="custom">Custom Range</option>
233
+ </select>
234
+ </div>
235
+ <div v-if="filters.dateRange === 'custom'" class="md:col-span-3">
236
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-5">
237
+ <div>
238
+ <label class="block text-sm font-medium text-slate-700 mb-2">From</label>
239
+ <input
240
+ type="date"
241
+ v-model="filters.dateFrom"
242
+ class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
243
+ >
244
+ </div>
245
+ <div>
246
+ <label class="block text-sm font-medium text-slate-700 mb-2">To</label>
247
+ <input
248
+ type="date"
249
+ v-model="filters.dateTo"
250
+ class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
251
+ >
252
+ </div>
253
+ </div>
254
+ </div>
255
+ </div>
256
+ </div>
257
+
258
+ <!-- File Table Header -->
259
+ <div class="bg-white border-b border-slate-200 px-5 py-3">
260
+ <div class="grid grid-cols-12 gap-4 text-xs font-medium text-slate-500 uppercase tracking-wider">
261
+ <div class="col-span-5 flex items-center">
262
+ <input
263
+ type="checkbox"
264
+ class="mr-3 h-4 w-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500"
265
+ @change="toggleSelectAll"
266
+ :checked="selectedFiles.length === filteredFiles.length && filteredFiles.length > 0"
267
+ >
268
+ <span
269
+ class="cursor-pointer flex items-center hover:text-slate-700 transition-colors"
270
+ @click="sortBy('name')"
271
+ >
272
+ Name
273
+ <i
274
+ class="fas ml-1"
275
+ :class="{
276
+ 'fa-sort': activeSort !== 'name',
277
+ 'fa-sort-up': activeSort === 'name' && sortDirection === 'asc',
278
+ 'fa-sort-down': activeSort === 'name' && sortDirection === 'desc'
279
+ }"
280
+ ></i>
281
+ </span>
282
+ </div>
283
+ <div
284
+ class="col-span-2 cursor-pointer flex items-center hover:text-slate-700 transition-colors"
285
+ @click="sortBy('author')"
286
+ >
287
+ <span>Author</span>
288
+ <i
289
+ class="fas ml-1"
290
+ :class="{
291
+ 'fa-sort': activeSort !== 'author',
292
+ 'fa-sort-up': activeSort === 'author' && sortDirection === 'asc',
293
+ 'fa-sort-down': activeSort === 'author' && sortDirection === 'desc'
294
+ }"
295
+ ></i>
296
+ </div>
297
+ <div
298
+ class="col-span-2 cursor-pointer flex items-center hover:text-slate-700 transition-colors"
299
+ @click="sortBy('type')"
300
+ >
301
+ <span>Type</span>
302
+ <i
303
+ class="fas ml-1"
304
+ :class="{
305
+ 'fa-sort': activeSort !== 'type',
306
+ 'fa-sort-up': activeSort === 'type' && sortDirection === 'asc',
307
+ 'fa-sort-down': activeSort === 'type' && sortDirection === 'desc'
308
+ }"
309
+ ></i>
310
+ </div>
311
+ <div
312
+ class="col-span-2 cursor-pointer flex items-center justify-end hover:text-slate-700 transition-colors"
313
+ @click="sortBy('size')"
314
+ >
315
+ <span>Size</span>
316
+ <i
317
+ class="fas ml-1"
318
+ :class="{
319
+ 'fa-sort': activeSort !== 'size',
320
+ 'fa-sort-up': activeSort === 'size' && sortDirection === 'asc',
321
+ 'fa-sort-down': activeSort === 'size' && sortDirection === 'desc'
322
+ }"
323
+ ></i>
324
+ </div>
325
+ <div
326
+ class="col-span-1 cursor-pointer flex items-center justify-end hover:text-slate-700 transition-colors"
327
+ @click="sortBy('modified')"
328
+ >
329
+ <span>Modified</span>
330
+ <i
331
+ class="fas ml-1"
332
+ :class="{
333
+ 'fa-sort': activeSort !== 'modified',
334
+ 'fa-sort-up': activeSort === 'modified' && sortDirection === 'asc',
335
+ 'fa-sort-down': activeSort === 'modified' && sortDirection === 'desc'
336
+ }"
337
+ ></i>
338
+ </div>
339
+ </div>
340
+ </div>
341
+
342
+ <!-- File List -->
343
+ <div class="flex-1 overflow-y-auto bg-white">
344
+ <div v-if="filteredFiles.length === 0" class="flex flex-col items-center justify-center h-64 text-slate-400">
345
+ <i class="fas fa-folder-open text-5xl mb-4 opacity-30"></i>
346
+ <p class="text-lg font-medium text-slate-500">No files found</p>
347
+ <p class="text-sm mt-1 text-slate-400">Try adjusting your search or filters</p>
348
+ </div>
349
+
350
+ <template v-else>
351
+ <div
352
+ v-for="file in filteredFiles"
353
+ :key="file.id"
354
+ class="file-row px-5 py-3 border-b border-slate-100 grid grid-cols-12 gap-4 text-sm items-center transition-all"
355
+ :class="{'highlight': selectedFiles.includes(file)}"
356
+ @dblclick="openFile(file)"
357
+ >
358
+ <div class="col-span-5 flex items-center truncate">
359
+ <input
360
+ type="checkbox"
361
+ class="mr-3 h-4 w-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500"
362
+ :checked="selectedFiles.includes(file)"
363
+ @change="toggleFileSelection(file, $event)"
364
+ >
365
+ <div class="w-8 h-8 rounded-md flex items-center justify-center mr-2"
366
+ :class="{'bg-indigo-50': selectedFiles.includes(file), 'bg-slate-100': !selectedFiles.includes(file)}">
367
+ <i :class="getFileIcon(file)" class="text-slate-600"></i>
368
+ </div>
369
+ <div class="truncate">
370
+ <div class="font-medium text-slate-800 truncate">{{ file.name }}</div>
371
+ <div class="text-xs text-slate-500 truncate">{{ file.path || currentLocation.name }}</div>
372
+ </div>
373
+ <i v-if="file.isFavorite" class="fas fa-star text-yellow-400 ml-2 text-xs"></i>
374
+ </div>
375
+ <div class="col-span-2 text-slate-600 truncate">
376
+ <span class="inline-flex items-center">
377
+ <i class="fas fa-user-circle mr-2 text-slate-400"></i>
378
+ {{ file.author || 'Unknown' }}
379
+ </span>
380
+ </div>
381
+ <div class="col-span-2 text-slate-600 truncate text-sm">{{ file.type }}</div>
382
+ <div class="col-span-2 text-slate-600 text-right text-sm">{{ formatFileSize(file.size) }}</div>
383
+ <div class="col-span-1 text-slate-600 text-right text-sm">{{ formatDate(file.modified) }}</div>
384
+ </div>
385
+ </template>
386
+ </div>
387
+
388
+ <!-- Status Bar -->
389
+ <div class="bg-slate-50 border-t border-slate-200 px-5 py-2.5 text-xs text-slate-500 flex justify-between">
390
+ <div>
391
+ <span v-if="selectedFiles.length > 0" class="font-medium">{{ selectedFiles.length }} selected</span>
392
+ <span v-else>{{ filteredFiles.length }} items</span>
393
+ </div>
394
+ <div class="flex items-center space-x-5">
395
+ <div class="flex items-center">
396
+ <span class="mr-2">Index status:</span>
397
+ <span v-if="isIndexing" class="text-indigo-600 font-medium flex items-center">
398
+ <span class="w-2 h-2 rounded-full bg-indigo-500 mr-1.5 animate-pulse"></span>
399
+ Running
400
+ </span>
401
+ <span v-else class="text-slate-400">Idle</span>
402
+ </div>
403
+ <div class="flex items-center">
404
+ <i class="fas fa-circle text-xs mr-1.5" :class="{
405
+ 'text-emerald-500': performanceRating === 'fast',
406
+ 'text-amber-500': performanceRating === 'normal',
407
+ 'text-rose-500': performanceRating === 'slow'
408
+ }"></i>
409
+ <span>{{ performanceRating }} performance</span>
410
+ </div>
411
+ </div>
412
+ </div>
413
+ </div>
414
+
415
+ <!-- Delete Confirmation Modal -->
416
+ <div v-if="showDeleteConfirm" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 backdrop-blur-sm">
417
+ <div class="bg-white rounded-xl modal-shadow p-6 w-96">
418
+ <div class="flex items-start mb-5">
419
+ <div class="flex-shrink-0 mt-1">
420
+ <div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center">
421
+ <i class="fas fa-exclamation-triangle text-red-500"></i>
422
+ </div>
423
+ </div>
424
+ <div class="ml-4">
425
+ <h3 class="text-lg font-medium text-slate-800">Delete Files</h3>
426
+ <p class="mt-1 text-sm text-slate-500">
427
+ Are you sure you want to delete {{ selectedFiles.length }} file(s)? This action cannot be undone.
428
+ </p>
429
+ </div>
430
+ </div>
431
+ <div class="mt-6 flex justify-end space-x-3">
432
+ <button
433
+ type="button"
434
+ class="px-4 py-2 bg-white border border-slate-200 rounded-lg text-slate-700 hover:bg-slate-50 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
435
+ @click="showDeleteConfirm = false"
436
+ >
437
+ Cancel
438
+ </button>
439
+ <button
440
+ type="button"
441
+ class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
442
+ @click="deleteSelectedFiles"
443
+ >
444
+ Delete
445
+ </button>
446
+ </div>
447
+ </div>
448
+ </div>
449
+ </div>
450
+
451
+ <script>
452
+ class Reactive {
453
+ constructor(data) {
454
+ this.data = data;
455
+ this.subscribers = [];
456
+ }
457
+
458
+ get(prop) {
459
+ return this.data[prop];
460
+ }
461
+
462
+ set(prop, value) {
463
+ this.data[prop] = value;
464
+ this.notify();
465
+ }
466
+
467
+ subscribe(callback) {
468
+ this.subscribers.push(callback);
469
+ }
470
+
471
+ notify() {
472
+ this.subscribers.forEach(callback => callback(this.data));
473
+ }
474
+ }
475
+
476
+ function bindInput(input, state, property) {
477
+ input.value = state.get(property);
478
+
479
+ input.addEventListener('input', (e) => {
480
+ state.set(property, e.target.value);
481
+ });
482
+
483
+ state.subscribe((data) => {
484
+ input.value = data[property];
485
+ });
486
+ }
487
+
488
+ function renderList(container, template, data) {
489
+ container.innerHTML = '';
490
+ data.forEach((item, index) => {
491
+ const element = document.createElement('div');
492
+ element.innerHTML = template(item, index);
493
+ container.appendChild(element.firstChild);
494
+ });
495
+ }
496
+
497
+ document.addEventListener('DOMContentLoaded', () => {
498
+ const state = new Reactive({
499
+ sidebarSearch: '',
500
+ currentLocation: { id: 1, name: 'Home', icon: 'fas fa-home' },
501
+ indexedCount: 2847,
502
+ lastIndexed: '5 minutes ago',
503
+
504
+ searchQuery: '',
505
+ showAdvancedSearch: false,
506
+ isIndexing: false,
507
+
508
+ activeSort: 'name',
509
+ sortDirection: 'asc',
510
+
511
+ selectedFiles: [],
512
+ showDeleteConfirm: false,
513
+
514
+ filters: {
515
+ fileType: 'all',
516
+ author: '',
517
+ sizeMin: '',
518
+ sizeMax: '',
519
+ dateRange: 'all',
520
+ dateFrom: '',
521
+ dateTo: ''
522
+ },
523
+
524
+ performanceRating: 'fast',
525
+
526
+ locations: [
527
+ { id: 1, name: 'Home', icon: 'fas fa-home' },
528
+ { id: 2, name: 'Documents', icon: 'fas fa-folder' },
529
+ { id: 3, name: 'Downloads', icon: 'fas fa-download' },
530
+ { id: 4, name: 'Pictures', icon: 'fas fa-image' },
531
+ { id: 5, name: 'Music', icon: 'fas fa-music' },
532
+ { id: 6, name: 'Videos', icon: 'fas fa-video' },
533
+ { id: 7, name: 'Desktop', icon: 'fas fa-desktop' }
534
+ ],
535
+
536
+ files: [
537
+ { id: 1, name: 'Project Proposal.pdf', path: 'Documents/Projects', author: 'John Doe', type: 'PDF Document', size: 2456, modified: '2023-06-15T14:30:00', isFavorite: false },
538
+ { id: 2, name: 'Budget 2023.xlsx', path: 'Documents/Finance', author: 'Jane Smith', type: 'Excel Spreadsheet', size: 4532, modified: '2023-05-22T09:45:00', isFavorite: true },
539
+ { id: 3, name: 'Team Photo.jpg', path: 'Pictures/Work', author: 'Mike Johnson', type: 'JPEG Image', size: 3245, modified: '2023-06-10T11:20:00', isFavorite: false },
540
+ { id: 4, name: 'Meeting Notes.docx', path: 'Documents/Meetings', author: 'Sarah Wilson', type: 'Word Document', size: 876, modified: '2023-06-14T16:10:00', isFavorite: false },
541
+ { id: 5, name: 'Presentation.pptx', path: 'Documents/Presentations', author: 'David Brown', type: 'PowerPoint', size: 6543, modified: '2023-06-12T13:30:00', isFavorite: false },
542
+ { id: 6, name: 'Project Backup.zip', path: 'Downloads', author: 'Emma Davis', type: 'ZIP Archive', size: 24567, modified: '2023-06-09T18:05:00', isFavorite: false },
543
+ { id: 7, name: 'Interview.mp3', path: 'Music/Recordings', author: 'James Miller', type: 'MP3 Audio', size: 12345, modified: '2023-05-30T10:15:00', isFavorite: false },
544
+ { id: 8, name: 'Tutorial.mp4', path: 'Videos/Learning', author: 'Olivia Wilson', type: 'MP4 Video', size: 45678, modified: '2023-05-28T14:20:00', isFavorite: false },
545
+ { id: 9, name: 'Configuration.ini', path: 'System', author: 'System', type: 'Configuration File', size: 23, modified: '2023-06-13T12:40:00', isFavorite: false },
546
+ { id: 10, name: 'Code Repository', path: 'Documents/Projects', author: 'John Doe', type: 'Folder', size: 10245, modified: '2023-06-05T08:30:00', isFavorite: true }
547
+ ]
548
+ });
549
+
550
+ function computedFilteredLocations() {
551
+ const search = state.get('sidebarSearch').toLowerCase();
552
+ return state.get('locations').filter(loc =>
553
+ loc.name.toLowerCase().includes(search)
554
+ );
555
+ }
556
+
557
+ function computedFilteredFiles() {
558
+ let files = [...state.get('files')];
559
+ const filters = state.get('filters');
560
+ const search = state.get('searchQuery').toLowerCase();
561
+ const activeSort = state.get('activeSort');
562
+ const sortDirection = state.get('sortDirection');
563
+
564
+ if (search) {
565
+ files = files.filter(file =>
566
+ file.name.toLowerCase().includes(search) ||
567
+ file.type.toLowerCase().includes(search) ||
568
+ (file.author && file.author.toLowerCase().includes(search))
569
+ );
570
+ }
571
+
572
+ if (filters.fileType !== 'all') {
573
+ const typeMap = {
574
+ 'documents': ['PDF Document', 'Word Document', 'Excel Spreadsheet', 'PowerPoint'],
575
+ 'images': ['JPEG Image', 'PNG Image', 'GIF Image'],
576
+ 'audio': ['MP3 Audio', 'WAV Audio'],
577
+ 'video': ['MP4 Video', 'MOV Video'],
578
+ 'archives': ['ZIP Archive', 'RAR Archive']
579
+ };
580
+
581
+ const types = typeMap[filters.fileType] || [];
582
+ files = files.filter(file =>
583
+ types.includes(file.type) ||
584
+ (filters.fileType === 'documents' && file.type.includes('Document'))
585
+ );
586
+ }
587
+
588
+ if (filters.author) {
589
+ const authorSearch = filters.author.toLowerCase();
590
+ files = files.filter(file =>
591
+ file.author && file.author.toLowerCase().includes(authorSearch)
592
+ );
593
+ }
594
+
595
+ if (filters.sizeMin || filters.sizeMax) {
596
+ const min = filters.sizeMin ? parseInt(filters.sizeMin) : 0;
597
+ const max = filters.sizeMax ? parseInt(filters.sizeMax) : Infinity;
598
+
599
+ files = files.filter(file => file.size >= min && file.size <= max);
600
+ }
601
+
602
+ if (filters.dateRange && filters.dateRange !== 'all') {
603
+ const now = new Date();
604
+ let fromDate = new Date();
605
+
606
+ switch (filters.dateRange) {
607
+ case 'today':
608
+ fromDate.setHours(0, 0, 0, 0);
609
+ break;
610
+ case 'week':
611
+ fromDate.setDate(fromDate.getDate() - 7);
612
+ break;
613
+ case 'month':
614
+ fromDate.setMonth(fromDate.getMonth() - 1);
615
+ break;
616
+ case 'year':
617
+ fromDate.setFullYear(fromDate.getFullYear() - 1);
618
+ break;
619
+ case 'custom':
620
+ if (filters.dateFrom) {
621
+ fromDate = new Date(filters.dateFrom);
622
+ }
623
+ break;
624
+ }
625
+
626
+ files = files.filter(file => {
627
+ const fileDate = new Date(file.modified);
628
+ return fileDate >= fromDate;
629
+ });
630
+
631
+ if (filters.dateRange === 'custom' && filters.dateTo) {
632
+ const toDate = new Date(filters.dateTo);
633
+ toDate.setHours(23, 59, 59, 999);
634
+
635
+ files = files.filter(file => {
636
+ const fileDate = new Date(file.modified);
637
+ return fileDate <= toDate;
638
+ });
639
+ }
640
+ }
641
+
642
+ files.sort((a, b) => {
643
+ let aVal, bVal;
644
+
645
+ switch (activeSort) {
646
+ case 'name':
647
+ aVal = a.name.toLowerCase();
648
+ bVal = b.name.toLowerCase();
649
+ break;
650
+ case 'author':
651
+ aVal = a.author ? a.author.toLowerCase() : '';
652
+ bVal = b.author ? b.author.toLowerCase() : '';
653
+ break;
654
+ case 'type':
655
+ aVal = a.type.toLowerCase();
656
+ bVal = b.type.toLowerCase();
657
+ break;
658
+ case 'size':
659
+ aVal = a.size;
660
+ bVal = b.size;
661
+ break;
662
+ case 'modified':
663
+ aVal = new Date(a.modified);
664
+ bVal = new Date(b.modified);
665
+ break;
666
+ default:
667
+ aVal = a.name.toLowerCase();
668
+ bVal = b.name.toLowerCase();
669
+ }
670
+
671
+ if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1;
672
+ if (aVal > bVal) return sortDirection === 'asc' ? 1 : -1;
673
+ return 0;
674
+ });
675
+
676
+ return files;
677
+ }
678
+
679
+ function formatFileSize(bytes) {
680
+ if (bytes === 0) return '0 Bytes';
681
+ if (typeof bytes === 'string') bytes = parseInt(bytes);
682
+
683
+ const k = 1024;
684
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
685
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
686
+
687
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
688
+ }
689
+
690
+ function formatDate(dateString) {
691
+ const date = new Date(dateString);
692
+ return date.toLocaleDateString('en-US', {month: 'short', day: 'numeric'});
693
+ }
694
+
695
+ function getFileIcon(file) {
696
+ if (file.type === 'Folder') return 'fas fa-folder folder-icon';
697
+
698
+ const extension = file.name.split('.').pop().toLowerCase();
699
+ const iconMap = {
700
+ 'pdf': 'fas fa-file-pdf file-icon',
701
+ 'docx': 'fas fa-file-word file-icon',
702
+ 'xlsx': 'fas fa-file-excel file-icon',
703
+ 'pptx': 'fas fa-file-powerpoint file-icon',
704
+ 'jpg': 'fas fa-file-image file-icon',
705
+ 'jpeg': 'fas fa-file-image file-icon',
706
+ 'png': 'fas fa-file-image file-icon',
707
+ 'gif': 'fas fa-file-image file-icon',
708
+ 'mp3': 'fas fa-file-audio file-icon',
709
+ 'wav': 'fas fa-file-audio file-icon',
710
+ 'mp4': 'fas fa-file-video file-icon',
711
+ 'mov': 'fas fa-file-video file-icon',
712
+ 'zip': 'fas fa-file-archive file-icon',
713
+ 'rar': 'fas fa-file-archive file-icon',
714
+ 'ini': 'fas fa-file-code file-icon'
715
+ };
716
+
717
+ return iconMap[extension] || 'fas fa-file file-icon';
718
+ }
719
+
720
+ function toggleIndexing() {
721
+ state.set('isIndexing', !state.get('isIndexing'));
722
+ if (state.get('isIndexing')) {
723
+ let count = state.get('indexedCount');
724
+ const interval = setInterval(() => {
725
+ count += Math.floor(Math.random() * 10);
726
+ state.set('indexedCount', count);
727
+ state.set('lastIndexed', 'Just now');
728
+
729
+ if (!state.get('isIndexing')) {
730
+ clearInterval(interval);
731
+ }
732
+ }, 3000);
733
+
734
+ setTimeout(() => {
735
+ if (Math.random() > 0.7) {
736
+ state.set('isIndexing', false);
737
+ }
738
+ }, 15000);
739
+ }
740
+ }
741
+
742
+ function refreshFiles() {
743
+ state.set('performanceRating', ['fast', 'normal', 'slow'][Math.floor(Math.random() * 3)]);
744
+
745
+ if (Math.random() > 0.7) {
746
+ const newFiles = [...state.get('files')];
747
+ if (newFiles.length > 5 && Math.random() > 0.5) {
748
+ newFiles.splice(Math.floor(Math.random() * newFiles.length), 1);
749
+ } else {
750
+ const authors = ['John Doe', 'Jane Smith', 'Mike Johnson', 'Sarah Wilson', 'Emma Davis'];
751
+ const newFile = {
752
+ id: Math.max(...newFiles.map(f => f.id)) + 1,
753
+ name: ['New Document.docx', 'Report.pdf', 'Image.jpg', 'Archive.zip', 'Data.xlsx'][Math.floor(Math.random() * 5)],
754
+ path: ['Documents', 'Downloads', 'Pictures', 'Music', 'Videos'][Math.floor(Math
755
+ </html>