| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Download - {{ filename }} - HokoriTemp</title>
-
- <!-- SVG Favicon -->
- <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
-
- <link rel="stylesheet" href="/static/css/styles.css">
- <!-- Fancybox CSS and JS -->
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css">
- <script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script>
-
- <!-- GLightbox for videos -->
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/glightbox/dist/css/glightbox.min.css">
- <script src="https://cdn.jsdelivr.net/npm/glightbox/dist/js/glightbox.min.js"></script>
-
- <!-- PDF.js -->
- <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
-
- <!-- HEIC2ANY for iPhone images -->
- <script src="https://cdn.jsdelivr.net/npm/heic2any@0.0.4/dist/heic2any.min.js"></script>
- <script src="https://cdn.tailwindcss.com"></script>
- <script>
- (function() {
- const savedTheme = localStorage.getItem('theme') || 'light';
- if (savedTheme === 'dark') {
- document.documentElement.classList.add('dark');
- }
- })();
- tailwind.config = {
- darkMode: 'class',
- theme: {
- extend: {
- animation: {
- 'fade-in': 'fadeIn 0.3s ease-in-out',
- 'slide-up': 'slideUp 0.3s ease-out',
- 'pulse-slow': 'pulse 2s infinite'
- }
- }
- }
- }
- </script>
- </head>
- <body class="bg-gray-50 dark:bg-gray-900 min-h-screen transition-colors duration-300">
- <div class="container mx-auto px-4 py-8 max-w-4xl">
- <!-- Header -->
- <div class="flex justify-between items-center mb-8">
- <div class="text-center flex-1">
- <h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">HokoriTemp</h1>
- <p class="text-gray-600 dark:text-gray-400">File Download</p>
- </div>
-
- <!-- Theme Toggle Button -->
- <button id="themeToggle" class="p-2 rounded-lg bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors ml-4">
- <!-- Sun Icon (Light Mode) -->
- <svg id="sunIcon" class="w-5 h-5 text-gray-800 dark:text-gray-200 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
- </svg>
- <!-- Moon Icon (Dark Mode) -->
- <svg id="moonIcon" class="w-5 h-5 text-gray-800 dark:text-gray-200 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
- </svg>
- </button>
- </div>
- <!-- File Info Section -->
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6 transition-colors duration-300">
- <div class="flex items-center space-x-4 mb-4">
- <div class="flex-shrink-0">
- <svg class="w-12 h-12 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
- </svg>
- </div>
- <div class="flex-1">
- <h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-1">{{ filename }}</h2>
- <div class="flex flex-wrap gap-4 text-sm text-gray-600 dark:text-gray-400">
- <span>Size: {{ "%.2f"|format(file_size / 1024 / 1024) }} MB</span>
- {% if mime_type %}
- <span>Type: {{ mime_type }}</span>
- {% endif %}
- <span>Expires: {{ expiration_date[:19].replace('T', ' ') }}</span>
- </div>
- </div>
- </div>
-
- <!-- Download Button -->
- <div class="flex gap-3">
- <a href="/download/{{ file_id }}/direct"
- class="inline-flex items-center px-6 py-3 bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600 text-white font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
- <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
- </svg>
- Download File
- </a>
-
- <a href="/"
- class="inline-flex items-center px-6 py-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
- <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"></path>
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5a2 2 0 012-2h2a2 2 0 012 2v0H8v0z"></path>
- </svg>
- Upload More Files
- </a>
- </div>
- </div>
- <!-- Text File Preview -->
- {% if is_text_file and file_content %}
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
- <div class="flex items-center justify-between mb-4">
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white">File Preview</h3>
- <div class="flex items-center space-x-2">
- <button onclick="copyTextToClipboard()" class="inline-flex items-center px-3 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors duration-200">
- <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
- </svg>
- Copy Text
- </button>
- <span class="text-sm text-gray-500 dark:text-gray-400">Text file preview</span>
- </div>
- </div>
-
- <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 overflow-auto max-h-96">
- <pre id="file-content" class="text-sm text-gray-800 dark:text-gray-200 whitespace-pre-wrap font-mono">{{ file_content }}</pre>
- </div>
-
- <div class="mt-4 text-xs text-gray-500 dark:text-gray-400">
- <p>Preview shows the first part of the file content. Download the file to view the complete content.</p>
- </div>
- </div>
- {% elif is_text_file %}
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
- <div class="text-center py-8">
- <svg class="w-12 h-12 mx-auto mb-4 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
- </svg>
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Preview Not Available</h3>
- <p class="text-gray-600 dark:text-gray-400">This text file couldn't be previewed. Please download it to view the content.</p>
- </div>
- </div>
- <!-- Media File Preview -->
- {% elif is_media_file %}
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
- <div class="flex items-center justify-between mb-4">
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Media Preview</h3>
- <span class="text-sm text-gray-500 dark:text-gray-400">{{ media_type|title }} file preview</span>
- </div>
-
- <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 flex justify-center">
- {% if media_type == 'image' %}
- <div class="relative group">
- <a href="/preview/{{ file_id }}" data-fancybox="gallery" data-caption="{{ filename }}" id="image-link-{{ file_id }}">
- <img src="/preview/{{ file_id }}" alt="{{ filename }}" class="max-w-full max-h-96 rounded-lg shadow-sm cursor-pointer transition-transform duration-200 hover:scale-105" loading="lazy" id="preview-image-{{ file_id }}">
- </a>
- <div class="mt-3 flex flex-col items-center space-y-2">
- <p class="text-xs text-gray-500 dark:text-gray-400">Click to view with zoom, fullscreen & advanced controls (supports HEIC/HEIF)</p>
- <div class="flex space-x-2">
- <button onclick="downloadImage('{{ file_id }}', '{{ filename }}')" class="inline-flex items-center px-2 py-1 text-xs font-medium text-white bg-green-600 hover:bg-green-700 rounded transition-colors duration-200">
- <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3M3 17V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2z"></path>
- </svg>
- Save Image
- </button>
- <button onclick="showImageInfo('{{ filename }}', '{{ file_size }}', '{{ mime_type }}')" class="inline-flex items-center px-2 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded transition-colors duration-200">
- <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
- </svg>
- Info
- </button>
- </div>
- </div>
- </div>
- {% elif media_type == 'video' %}
- <div class="w-full max-w-2xl mx-auto">
- <video controls class="w-full rounded-lg shadow-lg" preload="metadata">
- <source src="/preview/{{ file_id }}" type="{{ mime_type }}">
- Your browser does not support the video tag.
- </video>
- <div class="mt-3 text-center">
- <p class="text-sm font-medium text-gray-900 dark:text-white">{{ filename }}</p>
- <p class="text-xs text-gray-500 dark:text-gray-400">{{ mime_type }}</p>
- </div>
- </div>
- {% elif media_type == 'audio' %}
- <div class="w-full max-w-md">
- <div class="flex items-center space-x-4 mb-4">
- <svg class="w-12 h-12 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
- <path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM15.657 6.343a1 1 0 011.414 0A9.972 9.972 0 0119 12a9.972 9.972 0 01-1.929 5.657 1 1 0 11-1.414-1.414A7.971 7.971 0 0017 12c0-2.21-.895-4.21-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 12a5.983 5.983 0 01-.757 2.829 1 1 0 01-1.415-1.415A3.987 3.987 0 0013 12a3.987 3.987 0 00-.172-1.414 1 1 0 010-1.415z" clip-rule="evenodd"></path>
- </svg>
- <div>
- <h4 class="font-medium text-gray-900 dark:text-white">{{ filename }}</h4>
- <p class="text-sm text-gray-500 dark:text-gray-400">Audio file</p>
- </div>
- </div>
- <audio controls class="w-full">
- <source src="/preview/{{ file_id }}" type="{{ mime_type }}">
- Your browser does not support the audio tag.
- </audio>
- </div>
- {% elif media_type == 'pdf' %}
- <div class="relative group w-full">
- <div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
- <div class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
- <div class="flex items-center space-x-3">
- <svg class="w-8 h-8 text-red-500" fill="currentColor" viewBox="0 0 20 20">
- <path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"></path>
- </svg>
- <div>
- <h4 class="font-medium text-gray-900 dark:text-white">{{ filename }}</h4>
- <p class="text-sm text-gray-500 dark:text-gray-400">PDF Document</p>
- </div>
- </div>
- <button onclick="openPDFViewer('/preview/{{ file_id }}', '{{ filename }}')" class="inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-lg transition-colors duration-200">
- <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
- </svg>
- View PDF
- </button>
- </div>
- <div id="pdf-preview-{{ file_id }}" class="p-4 bg-gray-50 dark:bg-gray-900">
- <div class="flex items-center justify-center h-48 text-gray-500 dark:text-gray-400">
- <div class="text-center">
- <svg class="w-12 h-12 mx-auto mb-2 text-red-500" fill="currentColor" viewBox="0 0 20 20">
- <path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"></path>
- </svg>
- <p class="text-sm">Click "View PDF" to open with full viewer controls</p>
- </div>
- </div>
- </div>
- </div>
- <div class="mt-3 flex flex-col items-center space-y-2">
- <p class="text-xs text-gray-500 dark:text-gray-400">PDF viewer with zoom, search, navigation & print support</p>
- </div>
- </div>
- {% endif %}
- </div>
-
- <div class="mt-4 text-xs text-gray-500 dark:text-gray-400">
- <p>Media preview is displayed above. Download the file for full quality or offline viewing.</p>
- </div>
- </div>
- <!-- Archive File Preview -->
- {% elif is_archive_file and archive_contents %}
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
- <div class="flex items-center justify-between mb-4">
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Archive Contents</h3>
- <span class="text-sm text-gray-500 dark:text-gray-400">{{ archive_contents.type|upper }} archive preview</span>
- </div>
-
- <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 max-h-96 overflow-auto">
- <div class="space-y-2">
- {% for file in archive_contents.files %}
- <div class="flex items-center justify-between py-2 px-3 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
- <div class="flex items-center space-x-3">
- {% if file.type == 'directory' %}
- <svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
- <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"></path>
- </svg>
- {% else %}
- <svg class="w-4 h-4 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
- <path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"></path>
- </svg>
- {% endif %}
- <span class="text-sm font-medium text-gray-900 dark:text-white truncate">{{ file.name }}</span>
- </div>
- <div class="flex items-center space-x-4 text-xs text-gray-500 dark:text-gray-400">
- {% if file.type != 'directory' %}
- <span>{{ "%.1f"|format(file.size / 1024) }} KB</span>
- {% if file.compressed_size %}
- <span class="text-green-600 dark:text-green-400">{{ "%.1f%%"|format((1 - file.compressed_size / file.size) * 100) }} compressed</span>
- {% endif %}
- {% endif %}
- {% if file.date_time %}
- <span>{{ "%04d-%02d-%02d"|format(file.date_time[0], file.date_time[1], file.date_time[2]) }}</span>
- {% endif %}
- </div>
- </div>
- {% endfor %}
- </div>
- </div>
-
- <div class="mt-4 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
- <p>Archive contains {{ archive_contents.files|length }} items. Download to extract files.</p>
- <div class="flex space-x-4">
- {% set total_files = archive_contents.files|selectattr('type', 'equalto', 'file')|list|length %}
- {% set total_dirs = archive_contents.files|selectattr('type', 'equalto', 'directory')|list|length %}
- <span>{{ total_files }} files</span>
- {% if total_dirs > 0 %}
- <span>{{ total_dirs }} folders</span>
- {% endif %}
- </div>
- </div>
- </div>
- {% elif is_archive_file %}
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
- <div class="text-center py-8">
- <svg class="w-12 h-12 mx-auto mb-4 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
- </svg>
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Archive Preview Not Available</h3>
- <p class="text-gray-600 dark:text-gray-400">This archive file couldn't be previewed. Please download it to view the contents.</p>
- </div>
- </div>
- {% else %}
- <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
- <div class="text-center py-8">
- <svg class="w-12 h-12 mx-auto mb-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
- </svg>
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Binary File</h3>
- <p class="text-gray-600 dark:text-gray-400">This file cannot be previewed. Click the download button above to get the file.</p>
- </div>
- </div>
- {% endif %}
- </div>
- <!-- Toast Notification -->
- <div id="toast" class="fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300 z-50">
- <p id="toastMessage"></p>
- </div>
- <script>
- // Theme management
- function initTheme() {
- const savedTheme = localStorage.getItem('theme') || 'light';
- if (savedTheme === 'dark') {
- document.documentElement.classList.add('dark');
- } else {
- document.documentElement.classList.remove('dark');
- }
- }
- function toggleTheme() {
- const isDark = document.documentElement.classList.contains('dark');
- if (isDark) {
- document.documentElement.classList.remove('dark');
- localStorage.setItem('theme', 'light');
- } else {
- document.documentElement.classList.add('dark');
- localStorage.setItem('theme', 'dark');
- }
- }
- // Toast notification
- function showToast(message, type = 'success') {
- const toast = document.getElementById('toast');
- const toastMessage = document.getElementById('toastMessage');
- toastMessage.textContent = message;
- toast.classList.remove('bg-green-500', 'bg-red-500', 'bg-blue-500');
- if (type === 'success') {
- toast.classList.add('bg-green-500');
- } else if (type === 'error') {
- toast.classList.add('bg-red-500');
- } else if (type === 'info') {
- toast.classList.add('bg-blue-500');
- }
- toast.classList.remove('translate-x-full');
- setTimeout(() => {
- toast.classList.add('translate-x-full');
- }, 3000);
- }
- // Copy text to clipboard
- function copyTextToClipboard() {
- const fileContent = document.getElementById('file-content');
- if (!fileContent) {
- showToast('No content to copy', 'error');
- return;
- }
-
- const textToCopy = fileContent.textContent || fileContent.innerText;
-
- if (navigator.clipboard && window.isSecureContext) {
- // Use modern clipboard API
- navigator.clipboard.writeText(textToCopy).then(() => {
- showToast('Text copied to clipboard!', 'success');
- }).catch(err => {
- console.error('Failed to copy text: ', err);
- fallbackCopyTextToClipboard(textToCopy);
- });
- } else {
- // Fallback for older browsers
- fallbackCopyTextToClipboard(textToCopy);
- }
- }
-
- function fallbackCopyTextToClipboard(text) {
- const textArea = document.createElement('textarea');
- textArea.value = text;
- textArea.style.position = 'fixed';
- textArea.style.left = '-999999px';
- textArea.style.top = '-999999px';
- document.body.appendChild(textArea);
- textArea.focus();
- textArea.select();
-
- try {
- const successful = document.execCommand('copy');
- if (successful) {
- showToast('Text copied to clipboard!', 'success');
- } else {
- showToast('Failed to copy text', 'error');
- }
- } catch (err) {
- console.error('Fallback: Could not copy text: ', err);
- showToast('Failed to copy text', 'error');
- }
-
- document.body.removeChild(textArea);
- }
-
- // Image functions
- function downloadImage(fileId, filename) {
- const link = document.createElement('a');
- link.href = `/download/${fileId}/direct`;
- link.download = filename;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- showToast('Image download started!', 'success');
- }
-
- function showImageInfo(filename, fileSize, mimeType) {
- const sizeInKB = Math.round(fileSize / 1024);
- const sizeInMB = (fileSize / (1024 * 1024)).toFixed(2);
- const sizeText = fileSize > 1024 * 1024 ? `${sizeInMB} MB` : `${sizeInKB} KB`;
-
- const infoMessage = `File: ${filename}\nSize: ${sizeText}\nType: ${mimeType}`;
-
- // Create a custom modal for image info
- const modal = document.createElement('div');
- modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
- modal.innerHTML = `
- <div class="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md mx-4 shadow-xl">
- <div class="flex items-center justify-between mb-4">
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Image Information</h3>
- <button onclick="this.closest('.fixed').remove()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
- </svg>
- </button>
- </div>
- <div class="space-y-3">
- <div>
- <span class="text-sm font-medium text-gray-500 dark:text-gray-400">Filename:</span>
- <p class="text-gray-900 dark:text-white break-all">${filename}</p>
- </div>
- <div>
- <span class="text-sm font-medium text-gray-500 dark:text-gray-400">File Size:</span>
- <p class="text-gray-900 dark:text-white">${sizeText}</p>
- </div>
- <div>
- <span class="text-sm font-medium text-gray-500 dark:text-gray-400">MIME Type:</span>
- <p class="text-gray-900 dark:text-white">${mimeType}</p>
- </div>
- </div>
- <div class="mt-6 flex justify-end">
- <button onclick="this.closest('.fixed').remove()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200">
- Close
- </button>
- </div>
- </div>
- `;
-
- document.body.appendChild(modal);
-
- // Close modal when clicking outside
- modal.addEventListener('click', function(e) {
- if (e.target === modal) {
- modal.remove();
- }
- });
- }
- // Initialize
- document.addEventListener('DOMContentLoaded', function() {
- initTheme();
- document.getElementById('themeToggle').addEventListener('click', toggleTheme);
- });
- </script>
-
- <!-- Fancybox Configuration -->
- <script>
- // Initialize Fancybox
- document.addEventListener('DOMContentLoaded', function() {
- if (typeof Fancybox !== 'undefined') {
- Fancybox.bind('[data-fancybox]', {
- // UI options
- animated: true,
- showClass: 'f-fadeIn',
- hideClass: 'f-fadeOut',
-
- // Toolbar options
- Toolbar: {
- display: {
- left: ['infobar'],
- middle: ['zoomIn', 'zoomOut', 'toggle1to1', 'rotateCCW', 'rotateCW', 'flipX', 'flipY'],
- right: ['slideshow', 'fullscreen', 'thumbs', 'close']
- }
- },
-
- // Image options
- Images: {
- zoom: true,
- wheel: 'zoom'
- },
-
- // Thumbs options
- Thumbs: {
- showOnStart: false
- },
-
- // Slideshow options
- Slideshow: {
- speed: 3000
- }
- });
- console.log('Fancybox initialized successfully');
- }
- });
- </script>
-
- <!-- HEIC/HEIF Support -->
- <script>
- // Function to handle HEIC/HEIF images
- async function handleHEICImage(imgElement, fileId) {
- try {
- const response = await fetch(`/preview/${fileId}`);
- const blob = await response.blob();
-
- if (blob.type === 'image/heic' || blob.type === 'image/heif') {
- // Convert HEIC to JPEG using heic2any
- const convertedBlob = await heic2any({
- blob: blob,
- toType: 'image/jpeg',
- quality: 0.8
- });
-
- const convertedUrl = URL.createObjectURL(convertedBlob);
- imgElement.src = convertedUrl;
-
- // Update the Fancybox link as well
- const linkElement = imgElement.closest('a[data-fancybox]');
- if (linkElement) {
- linkElement.href = convertedUrl;
- }
-
- console.log('HEIC image converted successfully');
- }
- } catch (error) {
- console.error('Error converting HEIC image:', error);
- // Fallback: show error message
- imgElement.alt = 'HEIC image conversion failed';
- }
- }
-
- // Check for HEIC images on page load
- document.addEventListener('DOMContentLoaded', function() {
- const images = document.querySelectorAll('img[id^="preview-image-"]');
- images.forEach(img => {
- img.addEventListener('error', function() {
- // If image fails to load, it might be HEIC
- const fileId = this.id.replace('preview-image-', '');
- handleHEICImage(this, fileId);
- });
- });
- });
- </script>
-
-
- <!-- PDF.js Support -->
- <script>
- // PDF viewer function
- function openPDFViewer(pdfUrl, filename) {
- // Create a modal for PDF viewing
- const modal = document.createElement('div');
- modal.className = 'fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50';
- modal.innerHTML = `
- <div class="bg-white dark:bg-gray-800 rounded-lg w-11/12 h-5/6 max-w-6xl flex flex-col">
- <div class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
- <h3 class="text-lg font-semibold text-gray-900 dark:text-white">${filename}</h3>
- <div class="flex items-center space-x-2">
- <button id="pdf-download" class="px-3 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200">
- Download
- </button>
- <button onclick="this.closest('.fixed').remove()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
- </svg>
- </button>
- </div>
- </div>
- <div class="flex-1 p-4">
- <iframe src="${pdfUrl}" class="w-full h-full border-0 rounded" title="${filename}"></iframe>
- </div>
- </div>
- `;
-
- document.body.appendChild(modal);
-
- // Add download functionality
- modal.querySelector('#pdf-download').addEventListener('click', function() {
- const link = document.createElement('a');
- link.href = pdfUrl;
- link.download = filename;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- });
-
- // Close modal when clicking outside
- modal.addEventListener('click', function(e) {
- if (e.target === modal) {
- modal.remove();
- }
- });
-
- // Close modal with Escape key
- const handleEscape = function(e) {
- if (e.key === 'Escape') {
- modal.remove();
- document.removeEventListener('keydown', handleEscape);
- }
- };
- document.addEventListener('keydown', handleEscape);
- }
- </script>
- </body>
- </html>
|