summaryrefslogtreecommitdiff
path: root/templates
diff options
context:
space:
mode:
authorgrothedev <grothedev@gmail.com>2025-12-28 16:13:33 -0500
committergrothedev <grothedev@gmail.com>2025-12-28 16:13:33 -0500
commitf978ad7db04ced4cbcf04a82bf6f0cc3f4ce66a3 (patch)
treece97a8ffe34bd5907a0efdd4c453b64f10e6f568 /templates
parent119dd4129b91d8a3437a4f4e099b74991ee0044d (diff)
use template files
Diffstat (limited to 'templates')
-rw-r--r--templates/browse.html20
-rw-r--r--templates/error.html14
-rw-r--r--templates/gallery.html34
-rw-r--r--templates/layout.html25
-rw-r--r--templates/scripts/chunked-upload.js114
-rw-r--r--templates/scripts/simple-upload.js70
-rw-r--r--templates/scripts/slideshow.js66
-rw-r--r--templates/styles/gallery.css165
-rw-r--r--templates/upload.html43
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()">&times;</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>