jsfs11 commited on
Commit
0e5135e
·
verified ·
1 Parent(s): cf27778

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +107 -42
index.html CHANGED
@@ -4,7 +4,20 @@
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 {
@@ -77,19 +90,19 @@
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
 
@@ -101,20 +114,20 @@
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>
@@ -123,12 +136,12 @@
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 -->
@@ -146,17 +159,17 @@
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>
@@ -178,23 +191,23 @@
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>
@@ -296,7 +309,8 @@
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 => {
@@ -306,12 +320,14 @@
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
@@ -379,13 +395,26 @@
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
 
@@ -443,6 +472,7 @@
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">
@@ -459,6 +489,15 @@
459
  showImage(index);
460
  });
461
 
 
 
 
 
 
 
 
 
 
462
  fileList.appendChild(listItem);
463
 
464
  // Load thumbnail
@@ -484,8 +523,10 @@
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
 
@@ -504,6 +545,7 @@
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;
@@ -545,11 +587,11 @@
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);
@@ -559,6 +601,27 @@
559
  nextBtn.disabled = index === files.length - 1;
560
  }
561
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  // Navigation functions
563
  function showPreviousImage() {
564
  if (currentFileIndex > 0) {
@@ -612,11 +675,13 @@
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() {
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Local Image Viewer</title>
7
+ <!-- Local Tailwind CSS -->
8
  <script src="https://cdn.tailwindcss.com"></script>
9
+ <script>
10
+ tailwind.config = {
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ primary: '#3B82F6',
15
+ }
16
+ }
17
+ }
18
+ }
19
+ </script>
20
+ <!-- Local Font Awesome -->
21
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
22
  <style>
23
  .dropzone {
 
90
  <div class="container mx-auto px-4 py-8">
91
  <div class="max-w-6xl mx-auto">
92
  <h1 class="text-3xl font-bold text-center text-gray-800 mb-2">Local Image Viewer</h1>
93
+ <p class="text-center text-gray-600 mb-8">View your local WebP, PNG, JPEG, AVIF, and HEIC files with ease</p>
94
 
95
  <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8">
96
  <!-- Dropzone area -->
97
+ <div id="dropzone" class="dropzone p-12 text-center cursor-pointer" role="region" aria-label="File drop zone">
98
  <div class="flex flex-col items-center justify-center">
99
+ <i class="fas fa-images text-5xl text-gray-400 mb-4" aria-hidden="true"></i>
100
  <h3 class="text-xl font-semibold text-gray-700 mb-2">Drag & Drop Images Here</h3>
101
  <p class="text-gray-500 mb-4">or</p>
102
+ <button id="browseBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-lg transition" aria-label="Browse files">
103
  Browse Files
104
  </button>
105
+ <input type="file" id="fileInput" class="hidden" accept=".webp,.png,.jpg,.jpeg,.avif,.heic,.heif" multiple>
106
  </div>
107
  </div>
108
 
 
114
  <div class="p-4 border-b border-gray-200 flex justify-between items-center">
115
  <h3 class="font-medium text-gray-700">Files (<span id="fileCount">0</span>)</h3>
116
  <div class="relative">
117
+ <button id="sortBtn" class="text-gray-600 hover:text-gray-800" aria-label="Sort options" aria-haspopup="true" aria-expanded="false">
118
+ <i class="fas fa-sort" aria-hidden="true"></i>
119
  </button>
120
  <div id="sortDropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10 py-1">
121
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="name-asc" role="menuitem">Name (A-Z)</div>
122
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="name-desc" role="menuitem">Name (Z-A)</div>
123
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="size-asc" role="menuitem">Size (Small to Large)</div>
124
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="size-desc" role="menuitem">Size (Large to Small)</div>
125
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="date-asc" role="menuitem">Date (Oldest First)</div>
126
+ <div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" data-sort="date-desc" role="menuitem">Date (Newest First)</div>
127
  </div>
128
  </div>
129
  </div>
130
+ <ul id="fileList" class="divide-y divide-gray-200" role="list">
131
  <!-- Files will be listed here -->
132
  </ul>
133
  </div>
 
136
  <div class="w-full md:w-3/4 p-4 flex flex-col items-center justify-center">
137
  <div class="relative w-full max-w-3xl">
138
  <!-- Navigation buttons -->
139
+ <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" aria-label="Previous image">
140
+ <i class="fas fa-chevron-left text-xl" aria-hidden="true"></i>
141
  </button>
142
 
143
+ <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" aria-label="Next image">
144
+ <i class="fas fa-chevron-right text-xl" aria-hidden="true"></i>
145
  </button>
146
 
147
  <!-- Image display area -->
 
159
  <p id="fileInfo" class="text-sm text-gray-500">-</p>
160
  </div>
161
  <div class="flex space-x-2">
162
+ <button id="zoomInBtn" class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded" aria-label="Zoom in">
163
+ <i class="fas fa-search-plus" aria-hidden="true"></i>
164
  </button>
165
+ <button id="zoomOutBtn" class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded" aria-label="Zoom out">
166
+ <i class="fas fa-search-minus" aria-hidden="true"></i>
167
  </button>
168
+ <button id="resetZoomBtn" class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded" aria-label="Reset zoom">
169
+ <i class="fas fa-expand" aria-hidden="true"></i>
170
  </button>
171
+ <button id="fullscreenBtn" class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded" aria-label="Fullscreen">
172
+ <i class="fas fa-expand-arrows-alt" aria-hidden="true"></i>
173
  </button>
174
  </div>
175
  </div>
 
191
 
192
  <div class="text-center text-gray-500 text-sm mt-8">
193
  <p>Use arrow keys to navigate between images</p>
194
+ <p class="mt-1">Supported formats: WebP, PNG, JPEG, AVIF, HEIC</p>
195
  </div>
196
  </div>
197
  </div>
198
 
199
  <!-- Fullscreen container -->
200
+ <div id="fullscreenContainer" role="dialog" aria-modal="true" aria-label="Fullscreen image viewer">
201
  <img id="fullscreenImage" src="" alt="Fullscreen Image">
202
  <div id="fullscreenControls">
203
+ <button id="fsPrevBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full" aria-label="Previous image">
204
+ <i class="fas fa-chevron-left text-xl" aria-hidden="true"></i>
205
  </button>
206
+ <button id="fsCloseBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full" aria-label="Close fullscreen">
207
+ <i class="fas fa-times text-xl" aria-hidden="true"></i>
208
  </button>
209
+ <button id="fsNextBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full" aria-label="Next image">
210
+ <i class="fas fa-chevron-right text-xl" aria-hidden="true"></i>
211
  </button>
212
  </div>
213
  </div>
 
309
  // Sort functionality
310
  sortBtn.addEventListener('click', (e) => {
311
  e.stopPropagation();
312
+ const isExpanded = sortDropdown.classList.toggle('hidden');
313
+ sortBtn.setAttribute('aria-expanded', !isExpanded);
314
  });
315
 
316
  document.querySelectorAll('.sort-option').forEach(option => {
 
320
  updateFileList();
321
  showImage(currentFileIndex);
322
  sortDropdown.classList.add('hidden');
323
+ sortBtn.setAttribute('aria-expanded', 'false');
324
  });
325
  });
326
 
327
  // Close dropdown when clicking outside
328
  document.addEventListener('click', () => {
329
  sortDropdown.classList.add('hidden');
330
+ sortBtn.setAttribute('aria-expanded', 'false');
331
  });
332
 
333
  // Fullscreen functionality
 
395
  // Handle dropped or selected files
396
  function handleFiles(newFiles) {
397
  // Filter for supported image types
398
+ const supportedTypes = [
399
+ 'image/webp',
400
+ 'image/png',
401
+ 'image/jpeg',
402
+ 'image/avif',
403
+ 'image/heic',
404
+ 'image/heif'
405
+ ];
406
+
407
+ const imageFiles = Array.from(newFiles).filter(file => {
408
+ // Check MIME type first
409
+ if (supportedTypes.includes(file.type)) return true;
410
+
411
+ // Fallback for file extensions (some browsers might not recognize HEIC/AVIF MIME types)
412
+ const extension = file.name.split('.').pop().toLowerCase();
413
+ return ['webp', 'png', 'jpg', 'jpeg', 'avif', 'heic', 'heif'].includes(extension);
414
+ });
415
 
416
  if (imageFiles.length === 0) {
417
+ alert('No supported image files found. Please upload WebP, PNG, JPEG, AVIF, or HEIC files.');
418
  return;
419
  }
420
 
 
472
  files.forEach((file, index) => {
473
  const listItem = document.createElement('li');
474
  listItem.className = `file-item cursor-pointer ${index === currentFileIndex ? 'active' : ''}`;
475
+ listItem.setAttribute('role', 'listitem');
476
  listItem.innerHTML = `
477
  <div class="flex items-center p-3">
478
  <div class="flex-shrink-0 h-10 w-10 rounded bg-gray-200 overflow-hidden">
 
489
  showImage(index);
490
  });
491
 
492
+ // Keyboard navigation for file list items
493
+ listItem.addEventListener('keydown', (e) => {
494
+ if (e.key === 'Enter' || e.key === ' ') {
495
+ e.preventDefault();
496
+ showImage(index);
497
+ }
498
+ });
499
+
500
+ listItem.setAttribute('tabindex', '0');
501
  fileList.appendChild(listItem);
502
 
503
  // Load thumbnail
 
523
  document.querySelectorAll('.file-item').forEach((item, i) => {
524
  if (i === index) {
525
  item.classList.add('active');
526
+ item.setAttribute('aria-selected', 'true');
527
  } else {
528
  item.classList.remove('active');
529
+ item.setAttribute('aria-selected', 'false');
530
  }
531
  });
532
 
 
545
  img.style.transform = `scale(${zoomLevel})`;
546
  img.style.transformOrigin = 'center center';
547
  img.style.transition = 'transform 0.2s ease';
548
+ img.setAttribute('alt', `Preview of ${file.name}`);
549
 
550
  // Add drag to pan functionality
551
  let isDragging = false;
 
587
 
588
  // Update file info
589
  fileName.textContent = file.name;
590
+ fileInfo.textContent = `${getFileType(file)} • ${formatFileSize(file.size)}`;
591
 
592
  // Load image dimensions after the image is loaded
593
  img.onload = () => {
594
+ fileInfo.textContent = `${img.naturalWidth}×${img.naturalHeight} • ${getFileType(file)} • ${formatFileSize(file.size)}`;
595
  };
596
  };
597
  reader.readAsDataURL(file);
 
601
  nextBtn.disabled = index === files.length - 1;
602
  }
603
 
604
+ // Helper to get file type
605
+ function getFileType(file) {
606
+ if (file.type) {
607
+ const type = file.type.split('/')[1];
608
+ if (type) return type.toUpperCase();
609
+ }
610
+
611
+ // Fallback for file extension
612
+ const extension = file.name.split('.').pop().toLowerCase();
613
+ switch(extension) {
614
+ case 'jpg':
615
+ case 'jpeg': return 'JPEG';
616
+ case 'png': return 'PNG';
617
+ case 'webp': return 'WEBP';
618
+ case 'avif': return 'AVIF';
619
+ case 'heic':
620
+ case 'heif': return 'HEIC';
621
+ default: return extension.toUpperCase();
622
+ }
623
+ }
624
+
625
  // Navigation functions
626
  function showPreviousImage() {
627
  if (currentFileIndex > 0) {
 
675
  fullscreenImage.src = img.src;
676
  fullscreenContainer.style.display = 'flex';
677
  document.body.style.overflow = 'hidden';
678
+ fullscreenContainer.setAttribute('aria-hidden', 'false');
679
  }
680
 
681
  function closeFullscreen() {
682
  fullscreenContainer.style.display = 'none';
683
  document.body.style.overflow = '';
684
+ fullscreenContainer.setAttribute('aria-hidden', 'true');
685
  }
686
 
687
  function updateFullscreenImage() {