jsfs11 commited on
Commit
cf27778
·
verified ·
1 Parent(s): 14b5942

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +642 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Local Image Viewer
3
- emoji: 🌖
4
- colorFrom: pink
5
- colorTo: indigo
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: local-image-viewer
3
+ emoji: 🐳
4
+ colorFrom: yellow
5
+ colorTo: yellow
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,642 @@
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>Local 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
+ .dropzone {
11
+ border: 3px dashed #9CA3AF;
12
+ transition: all 0.3s ease;
13
+ }
14
+ .dropzone.active {
15
+ border-color: #3B82F6;
16
+ background-color: rgba(59, 130, 246, 0.1);
17
+ }
18
+ .image-container {
19
+ transition: transform 0.3s ease;
20
+ }
21
+ .image-container:hover {
22
+ transform: scale(1.01);
23
+ }
24
+ .nav-btn {
25
+ transition: all 0.2s ease;
26
+ }
27
+ .nav-btn:hover {
28
+ transform: scale(1.1);
29
+ }
30
+ .nav-btn:active {
31
+ transform: scale(0.95);
32
+ }
33
+ .file-item:hover {
34
+ background-color: rgba(59, 130, 246, 0.1);
35
+ }
36
+ .file-item.active {
37
+ background-color: rgba(59, 130, 246, 0.2);
38
+ border-left: 3px solid #3B82F6;
39
+ }
40
+ @keyframes fadeIn {
41
+ from { opacity: 0; }
42
+ to { opacity: 1; }
43
+ }
44
+ .fade-in {
45
+ animation: fadeIn 0.3s ease-in-out;
46
+ }
47
+ #fullscreenContainer {
48
+ display: none;
49
+ position: fixed;
50
+ top: 0;
51
+ left: 0;
52
+ width: 100%;
53
+ height: 100%;
54
+ background-color: rgba(0, 0, 0, 0.9);
55
+ z-index: 1000;
56
+ justify-content: center;
57
+ align-items: center;
58
+ flex-direction: column;
59
+ }
60
+ #fullscreenImage {
61
+ max-width: 90%;
62
+ max-height: 90%;
63
+ object-fit: contain;
64
+ }
65
+ #fullscreenControls {
66
+ position: absolute;
67
+ bottom: 20px;
68
+ display: flex;
69
+ gap: 10px;
70
+ }
71
+ .sort-option:hover {
72
+ background-color: rgba(59, 130, 246, 0.1);
73
+ }
74
+ </style>
75
+ </head>
76
+ <body class="bg-gray-100 min-h-screen">
77
+ <div class="container mx-auto px-4 py-8">
78
+ <div class="max-w-6xl mx-auto">
79
+ <h1 class="text-3xl font-bold text-center text-gray-800 mb-2">Local Image Viewer</h1>
80
+ <p class="text-center text-gray-600 mb-8">View your local WebP, PNG, and JPEG files with ease</p>
81
+
82
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8">
83
+ <!-- Dropzone area -->
84
+ <div id="dropzone" class="dropzone p-12 text-center cursor-pointer">
85
+ <div class="flex flex-col items-center justify-center">
86
+ <i class="fas fa-images text-5xl text-gray-400 mb-4"></i>
87
+ <h3 class="text-xl font-semibold text-gray-700 mb-2">Drag & Drop Images Here</h3>
88
+ <p class="text-gray-500 mb-4">or</p>
89
+ <button id="browseBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-lg transition">
90
+ Browse Files
91
+ </button>
92
+ <input type="file" id="fileInput" class="hidden" accept=".webp,.png,.jpg,.jpeg" multiple>
93
+ </div>
94
+ </div>
95
+
96
+ <!-- Main viewer area (hidden initially) -->
97
+ <div id="viewerArea" class="hidden">
98
+ <div class="flex flex-col md:flex-row">
99
+ <!-- Sidebar with file list -->
100
+ <div class="w-full md:w-1/4 bg-gray-50 border-r border-gray-200 max-h-[70vh] overflow-y-auto">
101
+ <div class="p-4 border-b border-gray-200 flex justify-between items-center">
102
+ <h3 class="font-medium text-gray-700">Files (<span id="fileCount">0</span>)</h3>
103
+ <div class="relative">
104
+ <button id="sortBtn" class="text-gray-600 hover:text-gray-800">
105
+ <i class="fas fa-sort"></i>
106
+ </button>
107
+ <div id="sortDropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10 py-1">
108
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="name-asc">Name (A-Z)</div>
109
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="name-desc">Name (Z-A)</div>
110
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="size-asc">Size (Small to Large)</div>
111
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="size-desc">Size (Large to Small)</div>
112
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="date-asc">Date (Oldest First)</div>
113
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="date-desc">Date (Newest First)</div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ <ul id="fileList" class="divide-y divide-gray-200">
118
+ <!-- Files will be listed here -->
119
+ </ul>
120
+ </div>
121
+
122
+ <!-- Main image display -->
123
+ <div class="w-full md:w-3/4 p-4 flex flex-col items-center justify-center">
124
+ <div class="relative w-full max-w-3xl">
125
+ <!-- Navigation buttons -->
126
+ <button id="prevBtn" class="nav-btn absolute left-0 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white text-gray-800 p-3 rounded-full shadow-md ml-4 z-10">
127
+ <i class="fas fa-chevron-left text-xl"></i>
128
+ </button>
129
+
130
+ <button id="nextBtn" class="nav-btn absolute right-0 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white text-gray-800 p-3 rounded-full shadow-md mr-4 z-10">
131
+ <i class="fas fa-chevron-right text-xl"></i>
132
+ </button>
133
+
134
+ <!-- Image display area -->
135
+ <div class="image-container bg-gray-100 rounded-lg overflow-hidden flex items-center justify-center" style="min-height: 400px;">
136
+ <div id="imageDisplay" class="p-4 w-full h-full flex items-center justify-center">
137
+ <p class="text-gray-500">Select an image to view</p>
138
+ </div>
139
+ </div>
140
+
141
+ <!-- Image info -->
142
+ <div class="mt-4 bg-gray-50 rounded-lg p-3">
143
+ <div class="flex justify-between items-center">
144
+ <div>
145
+ <h4 id="fileName" class="font-medium text-gray-800 truncate">No image selected</h4>
146
+ <p id="fileInfo" class="text-sm text-gray-500">-</p>
147
+ </div>
148
+ <div class="flex space-x-2">
149
+ <button id="zoomInBtn" class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded">
150
+ <i class="fas fa-search-plus"></i>
151
+ </button>
152
+ <button id="zoomOutBtn" class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded">
153
+ <i class="fas fa-search-minus"></i>
154
+ </button>
155
+ <button id="resetZoomBtn" class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded">
156
+ <i class="fas fa-expand"></i>
157
+ </button>
158
+ <button id="fullscreenBtn" class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded">
159
+ <i class="fas fa-expand-arrows-alt"></i>
160
+ </button>
161
+ </div>
162
+ </div>
163
+ <div class="mt-2">
164
+ <div class="w-full bg-gray-200 rounded-full h-2">
165
+ <div id="progressBar" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div>
166
+ </div>
167
+ <div class="flex justify-between text-xs text-gray-500 mt-1">
168
+ <span id="currentIndex">0</span>
169
+ <span id="totalImages">0</span>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <div class="text-center text-gray-500 text-sm mt-8">
180
+ <p>Use arrow keys to navigate between images</p>
181
+ <p class="mt-1">Supported formats: WebP, PNG, JPEG</p>
182
+ </div>
183
+ </div>
184
+ </div>
185
+
186
+ <!-- Fullscreen container -->
187
+ <div id="fullscreenContainer">
188
+ <img id="fullscreenImage" src="" alt="Fullscreen Image">
189
+ <div id="fullscreenControls">
190
+ <button id="fsPrevBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full">
191
+ <i class="fas fa-chevron-left text-xl"></i>
192
+ </button>
193
+ <button id="fsCloseBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full">
194
+ <i class="fas fa-times text-xl"></i>
195
+ </button>
196
+ <button id="fsNextBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full">
197
+ <i class="fas fa-chevron-right text-xl"></i>
198
+ </button>
199
+ </div>
200
+ </div>
201
+
202
+ <script>
203
+ document.addEventListener('DOMContentLoaded', function() {
204
+ // DOM elements
205
+ const dropzone = document.getElementById('dropzone');
206
+ const browseBtn = document.getElementById('browseBtn');
207
+ const fileInput = document.getElementById('fileInput');
208
+ const viewerArea = document.getElementById('viewerArea');
209
+ const fileList = document.getElementById('fileList');
210
+ const imageDisplay = document.getElementById('imageDisplay');
211
+ const fileName = document.getElementById('fileName');
212
+ const fileInfo = document.getElementById('fileInfo');
213
+ const fileCount = document.getElementById('fileCount');
214
+ const prevBtn = document.getElementById('prevBtn');
215
+ const nextBtn = document.getElementById('nextBtn');
216
+ const zoomInBtn = document.getElementById('zoomInBtn');
217
+ const zoomOutBtn = document.getElementById('zoomOutBtn');
218
+ const resetZoomBtn = document.getElementById('resetZoomBtn');
219
+ const fullscreenBtn = document.getElementById('fullscreenBtn');
220
+ const progressBar = document.getElementById('progressBar');
221
+ const currentIndex = document.getElementById('currentIndex');
222
+ const totalImages = document.getElementById('totalImages');
223
+ const sortBtn = document.getElementById('sortBtn');
224
+ const sortDropdown = document.getElementById('sortDropdown');
225
+ const fullscreenContainer = document.getElementById('fullscreenContainer');
226
+ const fullscreenImage = document.getElementById('fullscreenImage');
227
+ const fsPrevBtn = document.getElementById('fsPrevBtn');
228
+ const fsNextBtn = document.getElementById('fsNextBtn');
229
+ const fsCloseBtn = document.getElementById('fsCloseBtn');
230
+
231
+ // State variables
232
+ let files = [];
233
+ let currentFileIndex = -1;
234
+ let zoomLevel = 1;
235
+ const maxZoom = 3;
236
+ const minZoom = 0.5;
237
+ const zoomStep = 0.1;
238
+ let currentSortMethod = 'name-asc';
239
+
240
+ // Event listeners for dropzone
241
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
242
+ dropzone.addEventListener(eventName, preventDefaults, false);
243
+ });
244
+
245
+ function preventDefaults(e) {
246
+ e.preventDefault();
247
+ e.stopPropagation();
248
+ }
249
+
250
+ ['dragenter', 'dragover'].forEach(eventName => {
251
+ dropzone.addEventListener(eventName, highlight, false);
252
+ });
253
+
254
+ ['dragleave', 'drop'].forEach(eventName => {
255
+ dropzone.addEventListener(eventName, unhighlight, false);
256
+ });
257
+
258
+ function highlight() {
259
+ dropzone.classList.add('active');
260
+ }
261
+
262
+ function unhighlight() {
263
+ dropzone.classList.remove('active');
264
+ }
265
+
266
+ dropzone.addEventListener('drop', handleDrop, false);
267
+
268
+ function handleDrop(e) {
269
+ const dt = e.dataTransfer;
270
+ const droppedFiles = dt.files;
271
+ handleFiles(droppedFiles);
272
+ }
273
+
274
+ // Browse button click
275
+ browseBtn.addEventListener('click', () => {
276
+ fileInput.click();
277
+ });
278
+
279
+ // File input change
280
+ fileInput.addEventListener('change', () => {
281
+ if (fileInput.files.length > 0) {
282
+ handleFiles(fileInput.files);
283
+ }
284
+ });
285
+
286
+ // Navigation buttons
287
+ prevBtn.addEventListener('click', showPreviousImage);
288
+ nextBtn.addEventListener('click', showNextImage);
289
+
290
+ // Zoom buttons
291
+ zoomInBtn.addEventListener('click', zoomIn);
292
+ zoomOutBtn.addEventListener('click', zoomOut);
293
+ resetZoomBtn.addEventListener('click', resetZoom);
294
+ fullscreenBtn.addEventListener('click', openFullscreen);
295
+
296
+ // Sort functionality
297
+ sortBtn.addEventListener('click', (e) => {
298
+ e.stopPropagation();
299
+ sortDropdown.classList.toggle('hidden');
300
+ });
301
+
302
+ document.querySelectorAll('.sort-option').forEach(option => {
303
+ option.addEventListener('click', (e) => {
304
+ currentSortMethod = e.target.dataset.sort;
305
+ sortFiles();
306
+ updateFileList();
307
+ showImage(currentFileIndex);
308
+ sortDropdown.classList.add('hidden');
309
+ });
310
+ });
311
+
312
+ // Close dropdown when clicking outside
313
+ document.addEventListener('click', () => {
314
+ sortDropdown.classList.add('hidden');
315
+ });
316
+
317
+ // Fullscreen functionality
318
+ fsPrevBtn.addEventListener('click', () => {
319
+ showPreviousImage();
320
+ updateFullscreenImage();
321
+ });
322
+
323
+ fsNextBtn.addEventListener('click', () => {
324
+ showNextImage();
325
+ updateFullscreenImage();
326
+ });
327
+
328
+ fsCloseBtn.addEventListener('click', closeFullscreen);
329
+
330
+ // Keyboard navigation
331
+ document.addEventListener('keydown', (e) => {
332
+ if (files.length === 0) return;
333
+
334
+ switch(e.key) {
335
+ case 'ArrowLeft':
336
+ if (fullscreenContainer.style.display === 'flex') {
337
+ showPreviousImage();
338
+ updateFullscreenImage();
339
+ } else {
340
+ showPreviousImage();
341
+ }
342
+ break;
343
+ case 'ArrowRight':
344
+ if (fullscreenContainer.style.display === 'flex') {
345
+ showNextImage();
346
+ updateFullscreenImage();
347
+ } else {
348
+ showNextImage();
349
+ }
350
+ break;
351
+ case '+':
352
+ case '=':
353
+ zoomIn();
354
+ break;
355
+ case '-':
356
+ zoomOut();
357
+ break;
358
+ case '0':
359
+ resetZoom();
360
+ break;
361
+ case 'Escape':
362
+ if (fullscreenContainer.style.display === 'flex') {
363
+ closeFullscreen();
364
+ }
365
+ break;
366
+ case 'f':
367
+ case 'F':
368
+ if (imageDisplay.querySelector('img')) {
369
+ if (fullscreenContainer.style.display === 'flex') {
370
+ closeFullscreen();
371
+ } else {
372
+ openFullscreen();
373
+ }
374
+ }
375
+ break;
376
+ }
377
+ });
378
+
379
+ // Handle dropped or selected files
380
+ function handleFiles(newFiles) {
381
+ // Filter for supported image types
382
+ const supportedTypes = ['image/webp', 'image/png', 'image/jpeg'];
383
+ const imageFiles = Array.from(newFiles).filter(file =>
384
+ supportedTypes.includes(file.type)
385
+ );
386
+
387
+ if (imageFiles.length === 0) {
388
+ alert('No supported image files found. Please upload WebP, PNG, or JPEG files.');
389
+ return;
390
+ }
391
+
392
+ files = imageFiles;
393
+ currentFileIndex = 0;
394
+ zoomLevel = 1;
395
+
396
+ // Sort files
397
+ sortFiles();
398
+
399
+ // Update UI
400
+ updateFileList();
401
+ showImage(currentFileIndex);
402
+ viewerArea.classList.remove('hidden');
403
+
404
+ // Scroll to top
405
+ window.scrollTo(0, 0);
406
+ }
407
+
408
+ // Sort files based on current sort method
409
+ function sortFiles() {
410
+ switch(currentSortMethod) {
411
+ case 'name-asc':
412
+ files.sort((a, b) => a.name.localeCompare(b.name));
413
+ break;
414
+ case 'name-desc':
415
+ files.sort((a, b) => b.name.localeCompare(a.name));
416
+ break;
417
+ case 'size-asc':
418
+ files.sort((a, b) => a.size - b.size);
419
+ break;
420
+ case 'size-desc':
421
+ files.sort((a, b) => b.size - a.size);
422
+ break;
423
+ case 'date-asc':
424
+ files.sort((a, b) => a.lastModified - b.lastModified);
425
+ break;
426
+ case 'date-desc':
427
+ files.sort((a, b) => b.lastModified - a.lastModified);
428
+ break;
429
+ }
430
+
431
+ // Update current file index to maintain selection
432
+ if (currentFileIndex >= 0 && files.length > 0) {
433
+ currentFileIndex = 0;
434
+ }
435
+ }
436
+
437
+ // Update the file list sidebar
438
+ function updateFileList() {
439
+ fileList.innerHTML = '';
440
+ fileCount.textContent = files.length;
441
+ totalImages.textContent = files.length;
442
+
443
+ files.forEach((file, index) => {
444
+ const listItem = document.createElement('li');
445
+ listItem.className = `file-item cursor-pointer ${index === currentFileIndex ? 'active' : ''}`;
446
+ listItem.innerHTML = `
447
+ <div class="flex items-center p-3">
448
+ <div class="flex-shrink-0 h-10 w-10 rounded bg-gray-200 overflow-hidden">
449
+ <img src="#" alt="Thumbnail" class="h-full w-full object-cover thumbnail" data-index="${index}">
450
+ </div>
451
+ <div class="ml-3 overflow-hidden">
452
+ <p class="text-sm font-medium text-gray-900 truncate">${file.name}</p>
453
+ <p class="text-sm text-gray-500">${formatFileSize(file.size)}</p>
454
+ </div>
455
+ </div>
456
+ `;
457
+
458
+ listItem.addEventListener('click', () => {
459
+ showImage(index);
460
+ });
461
+
462
+ fileList.appendChild(listItem);
463
+
464
+ // Load thumbnail
465
+ const reader = new FileReader();
466
+ reader.onload = (e) => {
467
+ const thumbnails = document.querySelectorAll(`.thumbnail[data-index="${index}"]`);
468
+ thumbnails.forEach(thumb => {
469
+ thumb.src = e.target.result;
470
+ });
471
+ };
472
+ reader.readAsDataURL(file);
473
+ });
474
+ }
475
+
476
+ // Show image at specified index
477
+ function showImage(index) {
478
+ if (index < 0 || index >= files.length) return;
479
+
480
+ currentFileIndex = index;
481
+ const file = files[index];
482
+
483
+ // Update active item in file list
484
+ document.querySelectorAll('.file-item').forEach((item, i) => {
485
+ if (i === index) {
486
+ item.classList.add('active');
487
+ } else {
488
+ item.classList.remove('active');
489
+ }
490
+ });
491
+
492
+ // Update progress
493
+ currentIndex.textContent = index + 1;
494
+ progressBar.style.width = `${((index + 1) / files.length) * 100}%`;
495
+
496
+ // Display the image
497
+ const reader = new FileReader();
498
+ reader.onload = (e) => {
499
+ imageDisplay.innerHTML = '';
500
+
501
+ const img = document.createElement('img');
502
+ img.src = e.target.result;
503
+ img.className = 'max-w-full max-h-[70vh] object-contain fade-in';
504
+ img.style.transform = `scale(${zoomLevel})`;
505
+ img.style.transformOrigin = 'center center';
506
+ img.style.transition = 'transform 0.2s ease';
507
+
508
+ // Add drag to pan functionality
509
+ let isDragging = false;
510
+ let startX, startY, translateX = 0, translateY = 0;
511
+
512
+ img.addEventListener('mousedown', (e) => {
513
+ if (zoomLevel <= 1) return;
514
+
515
+ isDragging = true;
516
+ startX = e.clientX - translateX;
517
+ startY = e.clientY - translateY;
518
+ img.style.cursor = 'grabbing';
519
+ });
520
+
521
+ document.addEventListener('mousemove', (e) => {
522
+ if (!isDragging) return;
523
+
524
+ translateX = e.clientX - startX;
525
+ translateY = e.clientY - startY;
526
+ img.style.transform = `scale(${zoomLevel}) translate(${translateX}px, ${translateY}px)`;
527
+ });
528
+
529
+ document.addEventListener('mouseup', () => {
530
+ isDragging = false;
531
+ img.style.cursor = 'grab';
532
+ });
533
+
534
+ img.addEventListener('mouseenter', () => {
535
+ if (zoomLevel > 1) {
536
+ img.style.cursor = 'grab';
537
+ }
538
+ });
539
+
540
+ img.addEventListener('mouseleave', () => {
541
+ img.style.cursor = 'default';
542
+ });
543
+
544
+ imageDisplay.appendChild(img);
545
+
546
+ // Update file info
547
+ fileName.textContent = file.name;
548
+ fileInfo.textContent = `${file.type.split('/')[1].toUpperCase()} • ${formatFileSize(file.size)}`;
549
+
550
+ // Load image dimensions after the image is loaded
551
+ img.onload = () => {
552
+ fileInfo.textContent = `${img.naturalWidth}×${img.naturalHeight} • ${file.type.split('/')[1].toUpperCase()} • ${formatFileSize(file.size)}`;
553
+ };
554
+ };
555
+ reader.readAsDataURL(file);
556
+
557
+ // Enable/disable navigation buttons
558
+ prevBtn.disabled = index === 0;
559
+ nextBtn.disabled = index === files.length - 1;
560
+ }
561
+
562
+ // Navigation functions
563
+ function showPreviousImage() {
564
+ if (currentFileIndex > 0) {
565
+ showImage(currentFileIndex - 1);
566
+ }
567
+ }
568
+
569
+ function showNextImage() {
570
+ if (currentFileIndex < files.length - 1) {
571
+ showImage(currentFileIndex + 1);
572
+ }
573
+ }
574
+
575
+ // Zoom functions
576
+ function zoomIn() {
577
+ if (zoomLevel < maxZoom) {
578
+ zoomLevel += zoomStep;
579
+ applyZoom();
580
+ }
581
+ }
582
+
583
+ function zoomOut() {
584
+ if (zoomLevel > minZoom) {
585
+ zoomLevel -= zoomStep;
586
+ applyZoom();
587
+ }
588
+ }
589
+
590
+ function resetZoom() {
591
+ zoomLevel = 1;
592
+ applyZoom();
593
+ }
594
+
595
+ function applyZoom() {
596
+ const img = imageDisplay.querySelector('img');
597
+ if (img) {
598
+ img.style.transform = `scale(${zoomLevel})`;
599
+
600
+ // Reset pan position when zooming
601
+ if (zoomLevel <= 1) {
602
+ img.style.transform = `scale(${zoomLevel}) translate(0, 0)`;
603
+ }
604
+ }
605
+ }
606
+
607
+ // Fullscreen functions
608
+ function openFullscreen() {
609
+ const img = imageDisplay.querySelector('img');
610
+ if (!img) return;
611
+
612
+ fullscreenImage.src = img.src;
613
+ fullscreenContainer.style.display = 'flex';
614
+ document.body.style.overflow = 'hidden';
615
+ }
616
+
617
+ function closeFullscreen() {
618
+ fullscreenContainer.style.display = 'none';
619
+ document.body.style.overflow = '';
620
+ }
621
+
622
+ function updateFullscreenImage() {
623
+ const img = imageDisplay.querySelector('img');
624
+ if (img) {
625
+ fullscreenImage.src = img.src;
626
+ }
627
+ }
628
+
629
+ // Helper function to format file size
630
+ function formatFileSize(bytes) {
631
+ if (bytes === 0) return '0 Bytes';
632
+
633
+ const k = 1024;
634
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
635
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
636
+
637
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
638
+ }
639
+ });
640
+ </script>
641
+ <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=jsfs11/local-image-viewer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
642
+ </html>