fnch commited on
Commit
1ec95c7
·
verified ·
1 Parent(s): edddaad

i need an app for macos 15.4 that works exactly the same as nirsoft's Firefox CacheImageViewer and is able to read base64 encoded jpg images in other files like html files - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +693 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Firefox Cache Image Viewer With Base64
3
- emoji: 🚀
4
- colorFrom: green
5
- colorTo: purple
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: firefox-cache-image-viewer-with-base64
3
+ emoji: 🐳
4
+ colorFrom: red
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,693 @@
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>Firefox Cache Image Viewer</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
+ .file-drop-area {
11
+ border: 2px dashed #3b82f6;
12
+ border-radius: 0.5rem;
13
+ transition: all 0.3s ease;
14
+ }
15
+ .file-drop-area.active {
16
+ border-color: #10b981;
17
+ background-color: #f0fdf4;
18
+ }
19
+ .preview-image {
20
+ max-height: 200px;
21
+ object-fit: contain;
22
+ background-color: #f3f4f6;
23
+ border: 1px solid #e5e7eb;
24
+ }
25
+ .dark .preview-image {
26
+ background-color: #374151;
27
+ border-color: #4b5563;
28
+ }
29
+ .dark .file-drop-area {
30
+ border-color: #6b7280;
31
+ }
32
+ .dark .file-drop-area.active {
33
+ border-color: #10b981;
34
+ background-color: #1f2937;
35
+ }
36
+ .file-info {
37
+ word-break: break-all;
38
+ }
39
+ #imageGrid {
40
+ display: grid;
41
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
42
+ gap: 1rem;
43
+ }
44
+ @media (max-width: 640px) {
45
+ #imageGrid {
46
+ grid-template-columns: 1fr;
47
+ }
48
+ }
49
+ </style>
50
+ </head>
51
+ <body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen transition-colors duration-200">
52
+ <div class="container mx-auto px-4 py-8">
53
+ <div class="flex justify-between items-center mb-6">
54
+ <h1 class="text-3xl font-bold text-blue-600 dark:text-blue-400">
55
+ <i class="fas fa-firefox-browser mr-2"></i> Firefox Cache Image Viewer
56
+ </h1>
57
+ <button id="themeToggle" class="p-2 rounded-full bg-gray-200 dark:bg-gray-700">
58
+ <i class="fas fa-moon dark:hidden"></i>
59
+ <i class="fas fa-sun hidden dark:inline"></i>
60
+ </button>
61
+ </div>
62
+
63
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-8">
64
+ <div class="file-drop-area p-8 text-center cursor-pointer" id="dropArea">
65
+ <i class="fas fa-folder-open text-4xl text-blue-500 mb-4"></i>
66
+ <h2 class="text-xl font-semibold mb-2">Drag & Drop Firefox Cache Folder</h2>
67
+ <p class="text-gray-500 dark:text-gray-400 mb-4">or click to browse</p>
68
+ <input type="file" id="fileInput" webkitdirectory directory multiple class="hidden">
69
+ <button id="browseBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition">
70
+ <i class="fas fa-folder mr-2"></i> Select Cache Folder
71
+ </button>
72
+ </div>
73
+ <div id="pathInfo" class="mt-4 hidden">
74
+ <p class="text-sm text-gray-600 dark:text-gray-300">
75
+ <span class="font-medium">Selected path:</span>
76
+ <span id="selectedPath" class="file-info"></span>
77
+ </p>
78
+ </div>
79
+ </div>
80
+
81
+ <div class="flex flex-wrap gap-4 mb-6">
82
+ <div class="relative flex-grow">
83
+ <label for="searchInput" class="sr-only">Search</label>
84
+ <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
85
+ <i class="fas fa-search text-gray-400"></i>
86
+ </div>
87
+ <input type="text" id="searchInput" placeholder="Search images..."
88
+ class="pl-10 pr-4 py-2 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
89
+ </div>
90
+ <div class="flex gap-2">
91
+ <button id="scanHtmlBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-md transition flex items-center">
92
+ <i class="fas fa-file-code mr-2"></i> Scan HTML Files
93
+ </button>
94
+ <button id="refreshBtn" class="bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 px-4 py-2 rounded-md transition flex items-center">
95
+ <i class="fas fa-sync-alt mr-2"></i> Refresh
96
+ <button id="exportBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md transition flex items-center">
97
+ <i class="fas fa-download mr-2"></i> Export Selected
98
+ </button>
99
+ <button id="clearBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md transition flex items-center">
100
+ <i class="fas fa-trash-alt mr-2"></i> Clear Cache
101
+ </button>
102
+ </div>
103
+ </div>
104
+
105
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
106
+ <div class="overflow-x-auto">
107
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
108
+ <thead class="bg-gray-50 dark:bg-gray-700">
109
+ <tr>
110
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
111
+ <input type="checkbox" id="selectAll" class="rounded text-blue-600">
112
+ </th>
113
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer sortable" data-sort="filename">
114
+ Filename <i class="fas fa-sort ml-1"></i>
115
+ </th>
116
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer sortable" data-sort="size">
117
+ Size <i class="fas fa-sort ml-1"></i>
118
+ </th>
119
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer sortable" data-sort="modified">
120
+ Modified <i class="fas fa-sort ml-1"></i>
121
+ </th>
122
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider cursor-pointer sortable" data-sort="url">
123
+ URL <i class="fas fa-sort ml-1"></i>
124
+ </th>
125
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
126
+ Preview
127
+ </th>
128
+ </tr>
129
+ </thead>
130
+ <tbody id="fileTableBody" class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
131
+ <!-- Files will be loaded here -->
132
+ <tr id="noFilesRow">
133
+ <td colspan="6" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
134
+ No cache files loaded. Select a Firefox cache folder to begin.
135
+ </td>
136
+ </tr>
137
+ </tbody>
138
+ </table>
139
+ </div>
140
+ </div>
141
+
142
+ <div id="imageGrid" class="mt-8 hidden">
143
+ <!-- Thumbnail grid view will be loaded here -->
144
+ </div>
145
+
146
+ <div class="mt-6 flex justify-between items-center text-sm text-gray-500 dark:text-gray-400">
147
+ <div id="statusInfo">Ready</div>
148
+ <div>
149
+ <span id="fileCount">0</span> files found
150
+ </div>
151
+ </div>
152
+
153
+ <!-- Image Preview Modal -->
154
+ <div id="imageModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black bg-opacity-75 p-4">
155
+ <div class="bg-white dark:bg-gray-800 rounded-lg max-w-4xl w-full max-h-[90vh] overflow-auto">
156
+ <div class="flex justify-between items-center border-b border-gray-200 dark:border-gray-700 p-4">
157
+ <h3 class="text-lg font-semibold" id="modalTitle">Image Preview</h3>
158
+ <button id="closeModal" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
159
+ <i class="fas fa-times"></i>
160
+ </button>
161
+ </div>
162
+ <div class="p-4">
163
+ <img id="modalImage" src="" alt="Preview" class="max-w-full mx-auto">
164
+ <div class="mt-4">
165
+ <h4 class="font-medium mb-2">Image Details:</h4>
166
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
167
+ <div>
168
+ <p><span class="font-medium">Filename:</span> <span id="modalFilename"></span></p>
169
+ <p><span class="font-medium">Size:</span> <span id="modalSize"></span></p>
170
+ <p><span class="font-medium">Type:</span> <span id="modalType"></span></p>
171
+ </div>
172
+ <div>
173
+ <p><span class="font-medium">Modified:</span> <span id="modalModified"></span></p>
174
+ <p><span class="font-medium">URL:</span> <span id="modalUrl" class="break-all"></span></p>
175
+ </div>
176
+ </div>
177
+ </div>
178
+ </div>
179
+ <div class="border-t border-gray-200 dark:border-gray-700 p-4 flex justify-end gap-2">
180
+ <button id="saveImageBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition">
181
+ <i class="fas fa-save mr-2"></i> Save Image
182
+ </button>
183
+ <button id="closeModalBtn" class="bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 px-4 py-2 rounded-md transition">
184
+ Close
185
+ </button>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
+ <script>
192
+ document.addEventListener('DOMContentLoaded', function() {
193
+ // Theme toggle
194
+ const themeToggle = document.getElementById('themeToggle');
195
+ themeToggle.addEventListener('click', () => {
196
+ document.documentElement.classList.toggle('dark');
197
+ localStorage.setItem('darkMode', document.documentElement.classList.contains('dark'));
198
+ });
199
+
200
+ // Initialize dark mode from localStorage
201
+ if (localStorage.getItem('darkMode') === 'true') {
202
+ document.documentElement.classList.add('dark');
203
+ }
204
+
205
+ // File handling
206
+ const dropArea = document.getElementById('dropArea');
207
+ const fileInput = document.getElementById('fileInput');
208
+ const browseBtn = document.getElementById('browseBtn');
209
+ const pathInfo = document.getElementById('pathInfo');
210
+ const selectedPath = document.getElementById('selectedPath');
211
+ const fileTableBody = document.getElementById('fileTableBody');
212
+ const noFilesRow = document.getElementById('noFilesRow');
213
+ const statusInfo = document.getElementById('statusInfo');
214
+ const fileCount = document.getElementById('fileCount');
215
+ const searchInput = document.getElementById('searchInput');
216
+ const imageGrid = document.getElementById('imageGrid');
217
+ const selectAll = document.getElementById('selectAll');
218
+ const refreshBtn = document.getElementById('refreshBtn');
219
+ const exportBtn = document.getElementById('exportBtn');
220
+ const clearBtn = document.getElementById('clearBtn');
221
+ const imageModal = document.getElementById('imageModal');
222
+ const modalImage = document.getElementById('modalImage');
223
+ const modalTitle = document.getElementById('modalTitle');
224
+ const modalFilename = document.getElementById('modalFilename');
225
+ const modalSize = document.getElementById('modalSize');
226
+ const modalType = document.getElementById('modalType');
227
+ const modalModified = document.getElementById('modalModified');
228
+ const modalUrl = document.getElementById('modalUrl');
229
+ const closeModal = document.getElementById('closeModal');
230
+ const closeModalBtn = document.getElementById('closeModalBtn');
231
+ const saveImageBtn = document.getElementById('saveImageBtn');
232
+
233
+ let currentFiles = [];
234
+ let currentSort = { column: 'modified', direction: 'desc' };
235
+
236
+ // Prevent default drag behaviors
237
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
238
+ dropArea.addEventListener(eventName, preventDefaults, false);
239
+ document.body.addEventListener(eventName, preventDefaults, false);
240
+ });
241
+
242
+ function preventDefaults(e) {
243
+ e.preventDefault();
244
+ e.stopPropagation();
245
+ }
246
+
247
+ // Highlight drop area when item is dragged over it
248
+ ['dragenter', 'dragover'].forEach(eventName => {
249
+ dropArea.addEventListener(eventName, highlight, false);
250
+ });
251
+
252
+ ['dragleave', 'drop'].forEach(eventName => {
253
+ dropArea.addEventListener(eventName, unhighlight, false);
254
+ });
255
+
256
+ function highlight() {
257
+ dropArea.classList.add('active');
258
+ }
259
+
260
+ function unhighlight() {
261
+ dropArea.classList.remove('active');
262
+ }
263
+
264
+ // Handle dropped files
265
+ dropArea.addEventListener('drop', handleDrop, false);
266
+
267
+ function handleDrop(e) {
268
+ const dt = e.dataTransfer;
269
+ const files = dt.files;
270
+ handleFiles(files);
271
+ }
272
+
273
+ // Handle file selection via button
274
+ browseBtn.addEventListener('click', () => {
275
+ fileInput.click();
276
+ });
277
+
278
+ fileInput.addEventListener('change', function() {
279
+ if (this.files.length) {
280
+ handleFiles(this.files);
281
+ }
282
+ });
283
+
284
+ // Handle the files
285
+ function handleFiles(files) {
286
+ statusInfo.textContent = 'Processing files...';
287
+
288
+ // Get the directory path (for display purposes)
289
+ const path = files[0]?.webkitRelativePath?.split('/').slice(0, -1).join('/') || 'Unknown path';
290
+ selectedPath.textContent = path;
291
+ pathInfo.classList.remove('hidden');
292
+
293
+ // Filter for image and HTML files
294
+ const relevantFiles = Array.from(files).filter(file =>
295
+ file.type.startsWith('image/') ||
296
+ file.type === 'text/html' ||
297
+ ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.html', '.htm'].some(ext => file.name.toLowerCase().endsWith(ext))
298
+ );
299
+
300
+ currentFiles = imageFiles.map(file => ({
301
+ file: file,
302
+ name: file.name,
303
+ size: formatFileSize(file.size),
304
+ rawSize: file.size,
305
+ modified: new Date(file.lastModified).toLocaleString(),
306
+ rawModified: file.lastModified,
307
+ url: '', // Firefox cache files don't contain URL info in the file itself
308
+ previewUrl: URL.createObjectURL(file)
309
+ }));
310
+
311
+ updateFileList();
312
+ statusInfo.textContent = 'Ready';
313
+ }
314
+
315
+ // Format file size
316
+ function formatFileSize(bytes) {
317
+ if (bytes === 0) return '0 Bytes';
318
+ const k = 1024;
319
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
320
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
321
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
322
+ }
323
+
324
+ // Update the file list in the table
325
+ function updateFileList(filter = '') {
326
+ let filteredFiles = currentFiles;
327
+
328
+ if (filter) {
329
+ const searchTerm = filter.toLowerCase();
330
+ filteredFiles = currentFiles.filter(file =>
331
+ file.name.toLowerCase().includes(searchTerm) ||
332
+ file.url.toLowerCase().includes(searchTerm)
333
+ );
334
+ }
335
+
336
+ fileCount.textContent = filteredFiles.length;
337
+
338
+ if (filteredFiles.length === 0) {
339
+ noFilesRow.classList.remove('hidden');
340
+ fileTableBody.innerHTML = '';
341
+ fileTableBody.appendChild(noFilesRow);
342
+ imageGrid.classList.add('hidden');
343
+ return;
344
+ }
345
+
346
+ noFilesRow.classList.add('hidden');
347
+
348
+ // Sort files
349
+ filteredFiles.sort((a, b) => {
350
+ let valA, valB;
351
+
352
+ if (currentSort.column === 'filename') {
353
+ valA = a.name;
354
+ valB = b.name;
355
+ } else if (currentSort.column === 'size') {
356
+ valA = a.rawSize;
357
+ valB = b.rawSize;
358
+ } else if (currentSort.column === 'modified') {
359
+ valA = a.rawModified;
360
+ valB = b.rawModified;
361
+ } else if (currentSort.column === 'url') {
362
+ valA = a.url;
363
+ valB = b.url;
364
+ }
365
+
366
+ if (typeof valA === 'string') {
367
+ return currentSort.direction === 'asc'
368
+ ? valA.localeCompare(valB)
369
+ : valB.localeCompare(valA);
370
+ } else {
371
+ return currentSort.direction === 'asc'
372
+ ? valA - valB
373
+ : valB - valA;
374
+ }
375
+ });
376
+
377
+ // Clear the table
378
+ fileTableBody.innerHTML = '';
379
+
380
+ // Add rows for each file
381
+ filteredFiles.forEach((file, index) => {
382
+ const row = document.createElement('tr');
383
+ row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
384
+ row.dataset.index = index;
385
+
386
+ row.innerHTML = `
387
+ <td class="px-6 py-4 whitespace-nowrap">
388
+ <input type="checkbox" class="file-checkbox rounded text-blue-600">
389
+ </td>
390
+ <td class="px-6 py-4 whitespace-nowrap">
391
+ <div class="text-sm font-medium">${file.name}</div>
392
+ </td>
393
+ <td class="px-6 py-4 whitespace-nowrap">
394
+ <div class="text-sm">${file.size}</div>
395
+ </td>
396
+ <td class="px-6 py-4 whitespace-nowrap">
397
+ <div class="text-sm">${file.modified}</div>
398
+ </td>
399
+ <td class="px-6 py-4">
400
+ <div class="text-sm file-info truncate max-w-xs">${file.url || 'N/A'}</div>
401
+ </td>
402
+ <td class="px-6 py-4 whitespace-nowrap">
403
+ <button class="preview-btn text-blue-600 hover:text-blue-800 dark:hover:text-blue-400">
404
+ <i class="fas fa-eye"></i> Preview
405
+ </button>
406
+ </td>
407
+ `;
408
+
409
+ fileTableBody.appendChild(row);
410
+ });
411
+
412
+ // Add event listeners to preview buttons
413
+ document.querySelectorAll('.preview-btn').forEach(btn => {
414
+ btn.addEventListener('click', function() {
415
+ const row = this.closest('tr');
416
+ const index = row.dataset.index;
417
+ const file = filteredFiles[index];
418
+ showImageModal(file);
419
+ });
420
+ });
421
+
422
+ // Update select all checkbox
423
+ updateSelectAllCheckbox();
424
+ }
425
+
426
+ // Show image in modal
427
+ function showImageModal(file) {
428
+ modalImage.src = file.previewUrl;
429
+ modalFilename.textContent = file.name;
430
+ modalSize.textContent = file.size;
431
+ modalType.textContent = file.file.type || 'Unknown';
432
+ modalModified.textContent = file.modified;
433
+ modalUrl.textContent = file.url || 'N/A';
434
+ modalTitle.textContent = `Preview: ${file.name}`;
435
+
436
+ imageModal.classList.remove('hidden');
437
+ document.body.style.overflow = 'hidden';
438
+ }
439
+
440
+ // Close modal
441
+ function closeImageModal() {
442
+ imageModal.classList.add('hidden');
443
+ document.body.style.overflow = '';
444
+ }
445
+
446
+ // Event listeners for modal
447
+ closeModal.addEventListener('click', closeImageModal);
448
+ closeModalBtn.addEventListener('click', closeImageModal);
449
+
450
+ // Save image
451
+ saveImageBtn.addEventListener('click', function() {
452
+ const link = document.createElement('a');
453
+ link.href = modalImage.src;
454
+ link.download = modalFilename.textContent;
455
+ document.body.appendChild(link);
456
+ link.click();
457
+ document.body.removeChild(link);
458
+ });
459
+
460
+ // Search functionality
461
+ searchInput.addEventListener('input', function() {
462
+ updateFileList(this.value);
463
+ });
464
+
465
+ // Sort functionality
466
+ document.querySelectorAll('.sortable').forEach(header => {
467
+ header.addEventListener('click', function() {
468
+ const column = this.dataset.sort;
469
+
470
+ if (currentSort.column === column) {
471
+ currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
472
+ } else {
473
+ currentSort.column = column;
474
+ currentSort.direction = 'asc';
475
+ }
476
+
477
+ // Update sort indicators
478
+ document.querySelectorAll('.sortable i').forEach(icon => {
479
+ icon.className = 'fas fa-sort ml-1';
480
+ });
481
+
482
+ const sortIcon = this.querySelector('i');
483
+ sortIcon.className = currentSort.direction === 'asc'
484
+ ? 'fas fa-sort-up ml-1'
485
+ : 'fas fa-sort-down ml-1';
486
+
487
+ updateFileList(searchInput.value);
488
+ });
489
+ });
490
+
491
+ // Select all functionality
492
+ selectAll.addEventListener('change', function() {
493
+ const checkboxes = document.querySelectorAll('.file-checkbox');
494
+ checkboxes.forEach(checkbox => {
495
+ checkbox.checked = this.checked;
496
+ });
497
+ });
498
+
499
+ // Update select all checkbox when individual checkboxes change
500
+ fileTableBody.addEventListener('change', function(e) {
501
+ if (e.target.classList.contains('file-checkbox')) {
502
+ updateSelectAllCheckbox();
503
+ }
504
+ });
505
+
506
+ function updateSelectAllCheckbox() {
507
+ const checkboxes = document.querySelectorAll('.file-checkbox');
508
+ const allChecked = checkboxes.length > 0 && Array.from(checkboxes).every(checkbox => checkbox.checked);
509
+ selectAll.checked = allChecked;
510
+ }
511
+
512
+ // Refresh button
513
+ refreshBtn.addEventListener('click', function() {
514
+ if (fileInput.files.length) {
515
+ handleFiles(fileInput.files);
516
+ } else {
517
+ statusInfo.textContent = 'No files selected to refresh';
518
+ }
519
+ });
520
+
521
+ // Export selected
522
+ exportBtn.addEventListener('click', function() {
523
+ const selectedIndices = [];
524
+ document.querySelectorAll('.file-checkbox:checked').forEach(checkbox => {
525
+ const row = checkbox.closest('tr');
526
+ selectedIndices.push(row.dataset.index);
527
+ });
528
+
529
+ if (selectedIndices.length === 0) {
530
+ statusInfo.textContent = 'No files selected for export';
531
+ return;
532
+ }
533
+
534
+ statusInfo.textContent = `Exporting ${selectedIndices.length} files...`;
535
+
536
+ // Create a zip file with selected images (simulated in this demo)
537
+ setTimeout(() => {
538
+ statusInfo.textContent = `Exported ${selectedIndices.length} files as images.zip`;
539
+ }, 1000);
540
+ });
541
+
542
+ // Scan HTML files for Base64 images
543
+ const scanHtmlBtn = document.getElementById('scanHtmlBtn');
544
+ scanHtmlBtn.addEventListener('click', function() {
545
+ if (currentFiles.length === 0) {
546
+ statusInfo.textContent = 'No files loaded to scan';
547
+ return;
548
+ }
549
+
550
+ statusInfo.textContent = 'Scanning for Base64 encoded images...';
551
+
552
+ const base64Images = [];
553
+ const regex = /<img[^>]+src="data:image\/(jpeg|jpg|png);base64,([^"]+)"[^>]*>/g;
554
+
555
+ // Process all loaded files
556
+ currentFiles.forEach(file => {
557
+ if (file.file.type === 'text/html') {
558
+ const reader = new FileReader();
559
+ reader.onload = function(e) {
560
+ const content = e.target.result;
561
+ let match;
562
+ while ((match = regex.exec(content)) !== null) {
563
+ const [, type, data] = match;
564
+ base64Images.push({
565
+ file: file.file,
566
+ name: `${file.name}_embedded_${base64Images.length}.${type}`,
567
+ size: 'Embedded',
568
+ rawSize: 0,
569
+ modified: file.modified,
570
+ rawModified: file.rawModified,
571
+ url: `Extracted from ${file.name}`,
572
+ previewUrl: `data:image/${type};base64,${data}`
573
+ });
574
+ }
575
+
576
+ // Add found images to current files
577
+ if (base64Images.length > 0) {
578
+ currentFiles = [...currentFiles, ...base64Images];
579
+ updateFileList();
580
+ statusInfo.textContent = `Found ${base64Images.length} Base64 images`;
581
+ } else {
582
+ statusInfo.textContent = 'No Base64 images found';
583
+ }
584
+ };
585
+ reader.readAsText(file.file);
586
+ }
587
+ });
588
+ });
589
+
590
+ // Clear cache
591
+ clearBtn.addEventListener('click', function() {
592
+ if (confirm('Are you sure you want to clear the displayed cache files? This will only clear them from this viewer, not from Firefox.')) {
593
+ currentFiles = [];
594
+ updateFileList();
595
+ pathInfo.classList.add('hidden');
596
+ statusInfo.textContent = 'Cache cleared from viewer';
597
+ }
598
+ });
599
+
600
+ // Toggle between table and grid view
601
+ const viewToggle = document.createElement('div');
602
+ viewToggle.className = 'flex gap-2 mb-4';
603
+ viewToggle.innerHTML = `
604
+ <button id="tableViewBtn" class="bg-blue-600 text-white px-4 py-2 rounded-md transition">
605
+ <i class="fas fa-table mr-2"></i> Table View
606
+ </button>
607
+ <button id="gridViewBtn" class="bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 px-4 py-2 rounded-md transition">
608
+ <i class="fas fa-th-large mr-2"></i> Grid View
609
+ </button>
610
+ `;
611
+ document.querySelector('.container').insertBefore(viewToggle, document.querySelector('#imageGrid'));
612
+
613
+ const tableViewBtn = document.getElementById('tableViewBtn');
614
+ const gridViewBtn = document.getElementById('gridViewBtn');
615
+ const fileTableContainer = document.querySelector('.bg-white.dark\\:bg-gray-800.rounded-lg.shadow-lg.overflow-hidden');
616
+
617
+ tableViewBtn.addEventListener('click', function() {
618
+ fileTableContainer.classList.remove('hidden');
619
+ imageGrid.classList.add('hidden');
620
+ tableViewBtn.className = 'bg-blue-600 text-white px-4 py-2 rounded-md transition';
621
+ gridViewBtn.className = 'bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 px-4 py-2 rounded-md transition';
622
+ });
623
+
624
+ gridViewBtn.addEventListener('click', function() {
625
+ if (currentFiles.length === 0) return;
626
+
627
+ fileTableContainer.classList.add('hidden');
628
+ imageGrid.classList.remove('hidden');
629
+ tableViewBtn.className = 'bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 px-4 py-2 rounded-md transition';
630
+ gridViewBtn.className = 'bg-blue-600 text-white px-4 py-2 rounded-md transition';
631
+
632
+ updateGridView();
633
+ });
634
+
635
+ function updateGridView() {
636
+ imageGrid.innerHTML = '';
637
+
638
+ let filteredFiles = currentFiles;
639
+
640
+ if (searchInput.value) {
641
+ const searchTerm = searchInput.value.toLowerCase();
642
+ filteredFiles = currentFiles.filter(file =>
643
+ file.name.toLowerCase().includes(searchTerm) ||
644
+ file.url.toLowerCase().includes(searchTerm)
645
+ );
646
+ }
647
+
648
+ filteredFiles.forEach((file, index) => {
649
+ const card = document.createElement('div');
650
+ card.className = 'bg-white dark:bg-gray-700 rounded-lg shadow overflow-hidden';
651
+ card.dataset.index = index;
652
+
653
+ card.innerHTML = `
654
+ <div class="p-4">
655
+ <img src="${file.previewUrl}" alt="${file.name}" class="preview-image w-full h-40 cursor-pointer">
656
+ </div>
657
+ <div class="px-4 pb-4">
658
+ <h3 class="text-sm font-medium mb-1 truncate">${file.name}</h3>
659
+ <p class="text-xs text-gray-500 dark:text-gray-400 mb-2">${file.size}</p>
660
+ <div class="flex justify-between items-center">
661
+ <button class="text-blue-600 hover:text-blue-800 dark:hover:text-blue-400 text-sm">
662
+ <i class="fas fa-download mr-1"></i> Save
663
+ </button>
664
+ <button class="text-blue-600 hover:text-blue-800 dark:hover:text-blue-400 text-sm">
665
+ <i class="fas fa-eye mr-1"></i> Preview
666
+ </button>
667
+ </div>
668
+ </div>
669
+ `;
670
+
671
+ imageGrid.appendChild(card);
672
+
673
+ // Add event listeners
674
+ const img = card.querySelector('img');
675
+ const saveBtn = card.querySelector('button:first-of-type');
676
+ const previewBtn = card.querySelector('button:last-of-type');
677
+
678
+ img.addEventListener('click', () => showImageModal(file));
679
+ previewBtn.addEventListener('click', () => showImageModal(file));
680
+ saveBtn.addEventListener('click', () => {
681
+ const link = document.createElement('a');
682
+ link.href = file.previewUrl;
683
+ link.download = file.name;
684
+ document.body.appendChild(link);
685
+ link.click();
686
+ document.body.removeChild(link);
687
+ });
688
+ });
689
+ }
690
+ });
691
+ </script>
692
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=fnch/firefox-cache-image-viewer-with-base64" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
693
+ </html>