diff options
| author | grothedev <grothedev@gmail.com> | 2025-12-28 16:13:33 -0500 |
|---|---|---|
| committer | grothedev <grothedev@gmail.com> | 2025-12-28 16:13:33 -0500 |
| commit | f978ad7db04ced4cbcf04a82bf6f0cc3f4ce66a3 (patch) | |
| tree | ce97a8ffe34bd5907a0efdd4c453b64f10e6f568 /templates | |
| parent | 119dd4129b91d8a3437a4f4e099b74991ee0044d (diff) | |
use template files
Diffstat (limited to 'templates')
| -rw-r--r-- | templates/browse.html | 20 | ||||
| -rw-r--r-- | templates/error.html | 14 | ||||
| -rw-r--r-- | templates/gallery.html | 34 | ||||
| -rw-r--r-- | templates/layout.html | 25 | ||||
| -rw-r--r-- | templates/scripts/chunked-upload.js | 114 | ||||
| -rw-r--r-- | templates/scripts/simple-upload.js | 70 | ||||
| -rw-r--r-- | templates/scripts/slideshow.js | 66 | ||||
| -rw-r--r-- | templates/styles/gallery.css | 165 | ||||
| -rw-r--r-- | templates/upload.html | 43 |
9 files changed, 551 insertions, 0 deletions
diff --git a/templates/browse.html b/templates/browse.html new file mode 100644 index 0000000..b22fcbb --- /dev/null +++ b/templates/browse.html @@ -0,0 +1,20 @@ +<section> + <h6><a href="/">Home</a></h6> + <h5>Browse Files</h5> + <h6> + <a href="/">Simple Upload</a> | + <a href="/chunked">Chunked Upload</a> | + <a href="/browse">Browse Files</a> | + <a href="/gallery">Gallery</a> + </h6> +</section> + +<section> + <div class="widget"> + {{files-content}} + </div> +</section> + +<footer> + <p>Last updated December 28th, 2025</p> +</footer> diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..a5d8cd0 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,14 @@ +<section> + <div class="widget" style="text-align: center;"> + <div style="font-size: 64px; margin-bottom: 20px;">⚠️</div> + <h3>Error {{status}}</h3> + <p>{{error-message}}</p> + <p style="margin-top: 30px;"> + <a href="/">← Back to Home</a> + </p> + </div> +</section> + +<footer> + <p>Last updated December 28th, 2025</p> +</footer> diff --git a/templates/gallery.html b/templates/gallery.html new file mode 100644 index 0000000..550ed4f --- /dev/null +++ b/templates/gallery.html @@ -0,0 +1,34 @@ +<section> + <h6><a href="/">Home</a></h6> + <h5>Gallery</h5> + <h6> + <a href="/">Simple Upload</a> | + <a href="/chunked">Chunked Upload</a> | + <a href="/browse">Browse Files</a> | + <a href="/gallery">Gallery</a> + </h6> +</section> + +<section> + <div class="widget"> + {{gallery-content}} + </div> +</section> + +<!-- Slideshow Modal --> +<div id="slideshow-modal" class="slideshow-modal"> + <span class="slideshow-close" onclick="closeSlideshow()">×</span> + <button class="slideshow-nav slideshow-prev" onclick="changeSlide(-1)">❮</button> + <div class="slideshow-content"> + <div id="slideshow-media-container"></div> + <div class="slideshow-info"> + <div id="slideshow-filename"></div> + <div id="slideshow-counter"></div> + </div> + </div> + <button class="slideshow-nav slideshow-next" onclick="changeSlide(1)">❯</button> +</div> + +<footer> + <p>Last updated December 28th, 2025</p> +</footer> diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..a228982 --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>{{title}}</title> + <link rel="stylesheet" href="/css/skeleton.css"> + <link rel="stylesheet" href="/css/home.css"> + {{styles}} +</head> +<body> + <header> + <h2>File Upload</h2> + <section class="status"> + <p> </p> + </section> + </header> + <main> + <center>- - -</center> + {{content}} + <center>- - -</center> + </main> + {{scripts}} +</body> +</html> diff --git a/templates/scripts/chunked-upload.js b/templates/scripts/chunked-upload.js new file mode 100644 index 0000000..980c61c --- /dev/null +++ b/templates/scripts/chunked-upload.js @@ -0,0 +1,114 @@ +const fileInput = document.getElementById('fileInput'); +const fileInfo = document.getElementById('fileInfo'); +const form = document.getElementById('uploadForm'); +const result = document.getElementById('result'); +const progress = document.getElementById('progress'); +const progressFill = document.getElementById('progressFill'); +const uploadBtn = document.getElementById('uploadBtn'); +const CHUNK_SIZE = {{chunk-size}}; + +fileInput.addEventListener('change', (e) => { + const files = e.target.files; + if (files.length > 0) { + const names = Array.from(files).map(f => f.name).join(', '); + const totalSize = Array.from(files).reduce((sum, f) => sum + f.size, 0); + fileInfo.textContent = `Selected: ${files.length} file(s) - ${formatBytes(totalSize)}`; + } +}); + +form.addEventListener('submit', async (e) => { + e.preventDefault(); + const files = fileInput.files; + + if (files.length === 0) { + showResult('Please select at least one file', 'error'); + return; + } + + uploadBtn.disabled = true; + uploadBtn.textContent = 'Uploading...'; + progress.style.display = 'block'; + result.style.display = 'none'; + + try { + const uploadedFiles = []; + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const filename = await uploadFileChunked(file, i, files.length); + uploadedFiles.push(filename); + } + + const links = uploadedFiles.map(f => + `<a href="/download/${f}" target="_blank" style="display: block; margin: 5px 0;">${window.location.origin}/download/${f}</a>` + ).join(''); + showResult('Upload successful!<br>' + links, 'success'); + form.reset(); + fileInfo.textContent = 'Max size: {{max-size}}MB per file'; + } catch (error) { + showResult('Upload failed: ' + error.message, 'error'); + } finally { + uploadBtn.disabled = false; + uploadBtn.textContent = 'Upload'; + progress.style.display = 'none'; + } +}); + +async function uploadFileChunked(file, fileIndex, totalFiles) { + const totalChunks = Math.ceil(file.size / CHUNK_SIZE); + + for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { + const start = chunkIndex * CHUNK_SIZE; + const end = Math.min(start + CHUNK_SIZE, file.size); + const chunk = file.slice(start, end); + + const formData = new FormData(); + formData.append('chunk', chunk); + formData.append('filename', file.name); + formData.append('chunkIndex', chunkIndex); + formData.append('totalChunks', totalChunks); + + const response = await fetch('/upload-chunk', { + method: 'POST', + body: formData + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Upload failed'); + } + + const overallProgress = ((fileIndex * totalChunks + chunkIndex + 1) / (totalFiles * totalChunks)) * 100; + updateProgress(overallProgress); + } + + return file.name; +} + +function updateProgress(percent) { + const rounded = Math.round(percent); + progressFill.style.width = rounded + '%'; + progressFill.textContent = rounded + '%'; +} + +function showResult(message, type) { + result.innerHTML = message; + result.style.display = 'block'; + if (type === 'success') { + result.style.background = '#d4edda'; + result.style.color = '#155724'; + result.style.border = '1px solid #c3e6cb'; + } else { + result.style.background = '#f8d7da'; + result.style.color = '#721c24'; + result.style.border = '1px solid #f5c6cb'; + } +} + +function formatBytes(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; +} diff --git a/templates/scripts/simple-upload.js b/templates/scripts/simple-upload.js new file mode 100644 index 0000000..c3a0def --- /dev/null +++ b/templates/scripts/simple-upload.js @@ -0,0 +1,70 @@ +const fileInput = document.getElementById('fileInput'); +const fileInfo = document.getElementById('fileInfo'); +const form = document.getElementById('uploadForm'); +const result = document.getElementById('result'); +const uploadBtn = document.getElementById('uploadBtn'); + +fileInput.addEventListener('change', (e) => { + const files = e.target.files; + if (files.length > 0) { + const names = Array.from(files).map(f => f.name).join(', '); + fileInfo.textContent = `Selected: ${files.length} file(s) - ${names}`; + } +}); + +form.addEventListener('submit', async (e) => { + e.preventDefault(); + const files = fileInput.files; + + if (files.length === 0) { + showResult('Please select at least one file', 'error'); + return; + } + + uploadBtn.disabled = true; + uploadBtn.textContent = 'Uploading...'; + + const formData = new FormData(); + for (let i = 0; i < files.length; i++) { + formData.append('files', files[i]); + } + + try { + const response = await fetch('/upload', { + method: 'POST', + body: formData + }); + + const data = await response.json(); + + if (response.ok) { + const links = data.files.map(f => + `<a href="/download/${f}" target="_blank" style="display: block; margin: 5px 0;">${window.location.origin}/download/${f}</a>` + ).join(''); + showResult('Upload successful!<br>' + links, 'success'); + form.reset(); + fileInfo.textContent = 'Max size: {{max-size}}MB per file'; + } else { + showResult('Error: ' + data.error, 'error'); + } + } catch (error) { + showResult('Upload failed: ' + error.message, 'error'); + } finally { + uploadBtn.disabled = false; + uploadBtn.textContent = 'Upload'; + } +}); + +function showResult(message, type) { + result.innerHTML = message; + result.style.display = 'block'; + if (type === 'success') { + result.style.background = '#d4edda'; + result.style.color = '#155724'; + result.style.border = '1px solid #c3e6cb'; + } else { + result.style.background = '#f8d7da'; + result.style.color = '#721c24'; + result.style.border = '1px solid #f5c6cb'; + } +} diff --git a/templates/scripts/slideshow.js b/templates/scripts/slideshow.js new file mode 100644 index 0000000..ccf7c32 --- /dev/null +++ b/templates/scripts/slideshow.js @@ -0,0 +1,66 @@ +const mediaFiles = {{media-files-json}}; +let currentSlide = 0; + +function openSlideshow(index) { + currentSlide = index; + showSlide(currentSlide); + document.getElementById('slideshow-modal').classList.add('active'); + document.body.style.overflow = 'hidden'; +} + +function closeSlideshow() { + document.getElementById('slideshow-modal').classList.remove('active'); + document.body.style.overflow = 'auto'; + + // Pause any playing videos + const videos = document.querySelectorAll('#slideshow-media-container video'); + videos.forEach(v => v.pause()); +} + +function changeSlide(direction) { + currentSlide += direction; + + if (currentSlide >= mediaFiles.length) { + currentSlide = 0; + } + if (currentSlide < 0) { + currentSlide = mediaFiles.length - 1; + } + + showSlide(currentSlide); +} + +function showSlide(index) { + const file = mediaFiles[index]; + const container = document.getElementById('slideshow-media-container'); + + if (file.isVideo) { + container.innerHTML = `<video src="/download/${file.name}" controls autoplay style="max-width: 100%; max-height: 80vh;"></video>`; + } else { + container.innerHTML = `<img src="/download/${file.name}" alt="${file.name}" style="max-width: 100%; max-height: 80vh;">`; + } + + document.getElementById('slideshow-filename').textContent = file.name; + document.getElementById('slideshow-counter').textContent = `${index + 1} / ${mediaFiles.length}`; +} + +// Keyboard navigation +document.addEventListener('keydown', (e) => { + const modal = document.getElementById('slideshow-modal'); + if (!modal.classList.contains('active')) return; + + if (e.key === 'ArrowLeft') { + changeSlide(-1); + } else if (e.key === 'ArrowRight') { + changeSlide(1); + } else if (e.key === 'Escape') { + closeSlideshow(); + } +}); + +// Close on background click +document.getElementById('slideshow-modal').addEventListener('click', (e) => { + if (e.target.id === 'slideshow-modal') { + closeSlideshow(); + } +}); diff --git a/templates/styles/gallery.css b/templates/styles/gallery.css new file mode 100644 index 0000000..5ae5615 --- /dev/null +++ b/templates/styles/gallery.css @@ -0,0 +1,165 @@ +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 20px; + padding: 10px 0; +} +.gallery-item { + position: relative; + background: #f5f5f5; + border-radius: 4px; + overflow: hidden; + transition: transform 0.2s, box-shadow 0.2s; + cursor: pointer; +} +.gallery-item:hover { + transform: translateY(-4px); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} +.gallery-item a { + display: block; + position: relative; + text-decoration: none; +} +.video-overlay { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 48px; + color: white; + text-shadow: 0 2px 4px rgba(0,0,0,0.5); + pointer-events: none; +} +.gallery-info { + padding: 10px; + background: white; +} +.gallery-filename { + font-size: 14px; + font-weight: 600; + color: #333; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 4px; +} +.gallery-meta { + font-size: 12px; + color: #666; +} + +/* Slideshow Styles */ +.slideshow-modal { + display: none; + position: fixed; + z-index: 9999; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.95); +} +.slideshow-modal.active { + display: flex; + align-items: center; + justify-content: center; +} +.slideshow-close { + position: absolute; + top: 20px; + right: 40px; + color: #fff; + font-size: 50px; + font-weight: bold; + cursor: pointer; + z-index: 10001; + transition: color 0.3s; +} +.slideshow-close:hover { + color: #bbb; +} +.slideshow-content { + max-width: 90%; + max-height: 90%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} +#slideshow-media-container { + display: flex; + align-items: center; + justify-content: center; + max-width: 100%; + max-height: 80vh; +} +#slideshow-media-container img, +#slideshow-media-container video { + max-width: 100%; + max-height: 80vh; + object-fit: contain; + border-radius: 4px; +} +.slideshow-info { + color: white; + text-align: center; + margin-top: 20px; + font-size: 16px; +} +#slideshow-filename { + font-weight: 600; + margin-bottom: 8px; +} +#slideshow-counter { + font-size: 14px; + color: #ccc; +} +.slideshow-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + background-color: rgba(255, 255, 255, 0.2); + color: white; + border: none; + font-size: 40px; + padding: 20px; + cursor: pointer; + transition: background-color 0.3s; + z-index: 10001; +} +.slideshow-nav:hover { + background-color: rgba(255, 255, 255, 0.4); +} +.slideshow-prev { + left: 20px; +} +.slideshow-next { + right: 20px; +} + +@media (max-width: 550px) { + .gallery-grid { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 10px; + } + .gallery-item a img, + .gallery-item a video { + height: 150px !important; + } + .slideshow-close { + top: 10px; + right: 20px; + font-size: 40px; + } + .slideshow-nav { + font-size: 30px; + padding: 15px; + } + .slideshow-prev { + left: 10px; + } + .slideshow-next { + right: 10px; + } +} diff --git a/templates/upload.html b/templates/upload.html new file mode 100644 index 0000000..e3b972a --- /dev/null +++ b/templates/upload.html @@ -0,0 +1,43 @@ +<section> + <h5>Shared Files</h5> + <p>Welcome to the file sharing service</p> + <center> + <a href="/browse">Browse All Files</a> | + <a href="/gallery">View Gallery</a> + </center> +</section> + +<section id="fileupload{{section-suffix}}"> + <div class="widget"> + <h4>File Upload {{upload-type}}</h4> + <h6> + {{nav-links}} + | <a href="/browse">Browse Files</a> + | <a href="/gallery">Gallery</a> + </h6> + <form id="uploadForm" {{form-attrs}}> + <input type="file" id="fileInput" name="files" multiple {{input-attrs}}> + <div class="file-info" id="fileInfo" style="margin: 10px 0; color: #666;"> + Max size: {{max-size}}MB per file + </div> + <button type="submit" id="uploadBtn">Upload</button> + </form> + + <div class="progress" id="progress" style="display: none; margin-top: 15px;"> + <div style="width: 100%; height: 30px; background: #f0f0f0; border-radius: 4px; overflow: hidden;"> + <div id="progressFill" style="height: 100%; background: #33C3F0; width: 0%; display: flex; align-items: center; justify-content: center; color: white; font-size: 12px; font-weight: 600; transition: width 0.3s;">0%</div> + </div> + </div> + + <div class="result" id="result" style="display: none; margin-top: 15px; padding: 15px; border-radius: 4px;"></div> + + <div style="background: #e7f3ff; padding: 15px; border-radius: 4px; margin-top: 15px; font-size: 14px; color: #004085;"> + <strong>Info:</strong> Files expire after {{expiration-days}} days. + Rate limit: {{rate-limit-max}} uploads per {{rate-limit-minutes}} minutes. + </div> + </div> +</section> + +<footer> + <p>Last updated December 28th, 2025</p> +</footer> |
