Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> | |
| <title>Minimalist File Uploader</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;700&family=Poppins:wght@400;500;600&display=swap" rel="stylesheet" /> | |
| <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | |
| <style> | |
| :root { | |
| /* Core Palette - Nothing OS Inspired */ | |
| --primary-dark-ref: #1B1B1D; | |
| --accent-red: #D71921; | |
| --accent-red-hover: #b01017; | |
| --text-on-accent: #FFFFFF; | |
| /* Light Theme */ | |
| --bg-main: #F7F7F7; | |
| --surface-primary: #FFFFFF; | |
| --surface-secondary: #ECECEC; | |
| --border-light: #E0E0E0; | |
| --border-medium: #C2C2C2; | |
| --text-primary: var(--primary-dark-ref); | |
| --text-secondary: #666666; | |
| --text-placeholder: #999999; | |
| --text-email-on-light: rgba(27, 27, 29, 0.7); | |
| /* Typography */ | |
| --font-main: 'Noto Sans', 'Poppins', sans-serif; | |
| --font-mono: 'ui-monospace', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; | |
| /* UI Elements */ | |
| --radius-sharp: 4px; | |
| --radius-softer: 6px; | |
| --radius-profile-card: 12px; | |
| --shadow-none: none; | |
| --shadow-profile-card: 0 4px 10px rgba(0,0,0,0.1); | |
| --nav-height: 60px; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: var(--font-main); | |
| background: var(--bg-main); | |
| color: var(--text-primary); | |
| padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom)); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-x: hidden; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| } | |
| main { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 16px; | |
| } | |
| section { | |
| display: none; | |
| flex: 1; | |
| overflow-y: auto; | |
| animation: fadeIn 0.2s ease-out; | |
| } | |
| section.active { display: flex; flex-direction: column; } | |
| button, .button-like { | |
| background: var(--accent-red); | |
| color: var(--text-on-accent); | |
| border: 1px solid var(--accent-red); | |
| border-radius: var(--radius-sharp); | |
| padding: 10px 18px; | |
| font-weight: 500; | |
| font-size: 0.9rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| box-shadow: var(--shadow-none); | |
| transition: background-color 0.15s ease, border-color 0.15s ease; | |
| cursor: pointer; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| outline: none; | |
| } | |
| button:hover, .button-like:hover { | |
| background-color: var(--accent-red-hover); | |
| border-color: var(--accent-red-hover); | |
| } | |
| button:focus-visible { | |
| outline: 2px solid var(--accent-red); | |
| outline-offset: 2px; | |
| } | |
| button:disabled { | |
| background-color: var(--surface-secondary); | |
| color: var(--text-placeholder); | |
| border-color: var(--border-light); | |
| cursor: not-allowed; | |
| } | |
| .uploader { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 480px; margin: auto; } | |
| .file-input-area { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 25px 20px; border: 2px dashed var(--border-medium); border-radius: var(--radius-softer); background-color: var(--surface-primary); cursor: pointer; transition: border-color 0.2s, background-color 0.2s; text-align: center; } | |
| .file-input-area:hover, .file-input-area.dragover { border-color: var(--accent-red); background-color: #fff5f5; } | |
| .file-input-area input[type=file] { display: none; } | |
| .file-input-area .material-icons { font-size: 40px; color: var(--text-secondary); margin-bottom: 10px; transition: color 0.2s ease; } | |
| .file-input-area:hover .material-icons { color: var(--accent-red); } | |
| .file-input-label { color: var(--text-primary); font-weight: 500; font-size: 1rem; margin-bottom: 4px; } | |
| .file-input-hint, #fileNameDisplay { font-size: 0.8rem; color: var(--text-secondary); } | |
| #fileNameDisplay { margin-top: 6px; font-weight: 500; color: var(--text-primary); word-break: break-all; font-family: var(--font-mono); } | |
| #uploadBtn { width: 100%; padding: 12px 18px; } | |
| .loader { width: 28px; height: 28px; border: 3px solid var(--surface-secondary); border-top: 3px solid var(--accent-red); border-radius: 50%; animation: spin 0.8s linear infinite; margin: 16px auto; display: none; } | |
| .progress-container { width: 100%; margin-top: 8px; display: none; } | |
| .progress { width: 100%; height: 6px; background: var(--surface-secondary); border-radius: var(--radius-sharp); overflow: hidden; } | |
| .progress-bar { height: 100%; width: 0; background: var(--accent-red); transition: width 0.2s; } | |
| .info { text-align: center; margin-top: 8px; font-size: 0.85em; color: var(--text-secondary); min-height: 1.2em; font-family: var(--font-mono); } | |
| .info.error-message { color: var(--accent-red); font-weight: 500; } | |
| .info.success-message { color: #28a745; font-weight: 500; } | |
| #shareLinkContainer { margin-top: 16px; padding: 12px; background-color: var(--surface-primary); border: 1px solid var(--border-light); border-radius: var(--radius-softer); display: none; } | |
| #shareLinkContainer p { margin-bottom: 8px; font-weight: 500; font-size: 0.9rem; color: var(--text-primary); } | |
| .share-link-display { display: flex; gap: 8px; align-items: center; } | |
| .share-link-display input[type="text"] { flex-grow: 1; padding: 8px 10px; border: 1px solid var(--border-medium); border-radius: var(--radius-sharp); font-size: 0.85em; background-color: var(--bg-main); color: var(--text-primary); font-family: var(--font-mono); outline: none; } | |
| .share-link-display input[type="text"]:focus { border-color: var(--accent-red); } | |
| .share-link-display button { padding: 8px 12px; font-size: 0.8rem; } | |
| .history-list { display: flex; flex-direction: column; gap: 10px; } | |
| .history-item { background: var(--surface-primary); padding: 12px 16px; border-radius: var(--radius-softer); border: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; transition: border-color 0.2s ease; } | |
| .history-item:hover { border-color: var(--border-medium); } | |
| .history-item-info { flex-grow: 1; margin-right: 12px; overflow: hidden; } | |
| .history-item-name { font-weight: 500; font-size: 0.9rem; color: var(--text-primary); margin-bottom: 3px; font-family: var(--font-mono); letter-spacing: 0.2px; word-break: break-all; } | |
| .history-item-url { font-size: 0.75em; color: var(--text-secondary); word-break: break-all; display: block; text-decoration: none; font-family: var(--font-mono); } | |
| .history-item-url:hover { color: var(--accent-red); text-decoration: underline;} | |
| .history-item button { padding: 7px 10px; font-size: 0.75rem; min-width: 80px; } | |
| .profile-container { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 24px; | |
| padding: 16px 0; | |
| width: 100%; | |
| max-width: 450px; | |
| margin: auto; | |
| } | |
| .profile-header { text-align: center; margin-bottom: 16px; } | |
| .profile-avatar-wrapper { position: relative; width: 120px; height: 120px; margin: 0 auto 20px auto; } | |
| .profile-avatar { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; border: 3px solid var(--surface-primary); box-shadow: 0 2px 4px rgba(0,0,0,0.1); position: relative; z-index: 2; } | |
| .profile-avatar-ring { position: absolute; top: -5px; left: -5px; right: -5px; bottom: -5px; border-radius: 50%; border: 2px dotted var(--accent-red); animation: rotateRing 20s linear infinite; z-index: 1; } | |
| @keyframes rotateRing { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } | |
| .profile-name { font-family: var(--font-mono); font-size: 1.8rem; color: var(--text-primary); margin-bottom: 4px; letter-spacing: 0.5px; } | |
| .profile-email { font-family: var(--font-main); font-size: 0.95rem; color: var(--text-email-on-light); } | |
| .profile-action-card { background-color: var(--surface-primary); border-radius: var(--radius-profile-card); padding: 16px; width: 100%; box-shadow: var(--shadow-profile-card); text-align: center; } | |
| .portfolio-button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; background-color: var(--accent-red); color: var(--text-on-accent); font-family: var(--font-main); font-size: 1rem; font-weight: 500; padding: 12px 24px; border-radius: var(--radius-sharp); text-decoration: none; text-transform: uppercase; letter-spacing: 0.5px; transition: background-color 0.15s ease; width: auto; min-width: 200px; } | |
| .portfolio-button:hover { background-color: var(--accent-red-hover); } | |
| .portfolio-button .material-icons { font-size: 20px; } | |
| .profile-social-links { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 16px; width: 100%; padding: 0 8px; } | |
| .social-link-card { background-color: var(--surface-primary); border-radius: var(--radius-profile-card); padding: 16px; box-shadow: var(--shadow-profile-card); display: flex; flex-direction: column; align-items: center; justify-content: center; text-decoration: none; color: var(--text-primary); gap: 8px; transition: transform 0.2s ease, box-shadow 0.2s ease, color 0.2s ease; min-height: 100px; } | |
| .social-link-card:hover { transform: translateY(-3px) scale(1.03); box-shadow: 0 6px 12px rgba(0,0,0,0.12); color: var(--accent-red); } | |
| .social-link-card .social-icon { | |
| width: 28px; /* SVG size */ | |
| height: 28px; | |
| fill: currentColor; /* Inherits color from .social-link-card for hover effect */ | |
| transition: fill 0.2s ease; | |
| } | |
| .social-link-card span { font-family: var(--font-main); font-size: 0.8rem; font-weight: 500; text-align: center; } | |
| .bottom-nav { position: fixed; bottom: 0; left: 0; right: 0; width: 100%; background: var(--surface-primary); display: flex; justify-content: space-around; align-items: stretch; border-top: 1px solid var(--border-light); padding-bottom: env(safe-area-inset-bottom); height: calc(var(--nav-height) + env(safe-area-inset-bottom)); box-shadow: var(--shadow-none); } | |
| .nav-item { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 0.7rem; color: var(--text-secondary); cursor: pointer; transition: color 0.15s ease; padding-top: 5px; padding-bottom: 3px; position: relative; } | |
| .nav-item:hover { color: var(--text-primary); } | |
| .nav-item.active { color: var(--accent-red); font-weight: 700; } | |
| .nav-item.active::after { content: ''; position: absolute; bottom: 5px; left: 50%; transform: translateX(-50%); width: 5px; height: 5px; background-color: var(--accent-red); border-radius: 50%; } | |
| .nav-item .material-icons { font-size: 24px; margin-bottom: 2px; } | |
| @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <section id="uploadSection" class="active"> | |
| <div class="uploader"> | |
| <label for="fileInput" class="file-input-area"> | |
| <i class="material-icons">upload_file</i> | |
| <span class="file-input-label">Choose File</span> | |
| <input type="file" id="fileInput" /> | |
| <span class="file-input-hint">Max. 100MB</span> | |
| <div id="fileNameDisplay">No file selected</div> | |
| </label> | |
| <button id="uploadBtn" disabled>Upload</button> | |
| <div class="loader" id="loader"></div> | |
| <div class="progress-container" id="progressContainer"> | |
| <div class="progress"><div id="progressBar" class="progress-bar"></div></div> | |
| </div> | |
| <div class="info" id="uploadInfo"></div> | |
| <div id="shareLinkContainer"></div> | |
| </div> | |
| </section> | |
| <section id="historySection"> | |
| <div class="history-list" id="historyList"></div> | |
| </section> | |
| <section id="profileSection"> | |
| <div class="profile-container"> | |
| <div class="profile-header"> | |
| <div class="profile-avatar-wrapper"> | |
| <div class="profile-avatar-ring"></div> | |
| <img src="https://www.adityadevarshi.online/static/media/hrishi2.26eee295fbf7bdd9fb2d.png" alt="Aditya Devarshi" class="profile-avatar"> | |
| </div> | |
| <h3 class="profile-name">Aditya Devarshi</h3> | |
| <p class="profile-email">devarshia5@gmail.com</p> | |
| </div> | |
| <div class="profile-action-card portfolio-cta-card"> | |
| <a href="https://www.adityadevarshi.online/" target="_blank" rel="noopener noreferrer" class="portfolio-button"> | |
| <i class="material-icons">visibility</i> | |
| <span>View My Work</span> | |
| </a> | |
| </div> | |
| <div class="profile-social-links"> | |
| <a href="https://github.com/devarshiadi/" target="_blank" rel="noopener noreferrer" class="social-link-card" aria-label="GitHub Profile"> | |
| <svg class="social-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg> | |
| <span>GitHub</span> | |
| </a> | |
| <a href="https://www.linkedin.com/in/aditya-devarshi/" target="_blank" rel="noopener noreferrer" class="social-link-card" aria-label="LinkedIn Profile"> | |
| <svg class="social-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/></svg> | |
| <span>LinkedIn</span> | |
| </a> | |
| <a href="https://www.instagram.com/curseofwitcher/" target="_blank" rel="noopener noreferrer" class="social-link-card" aria-label="Instagram Profile"> | |
| <svg class="social-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c.001-3.403-2.758-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg> | |
| <span>Instagram</span> | |
| </a> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <nav class="bottom-nav"> | |
| <div class="nav-item active" data-target="uploadSection"> | |
| <i class="material-icons">home</i> Home | |
| </div> | |
| <div class="nav-item" data-target="historySection"> | |
| <i class="material-icons">history</i> History | |
| </div> | |
| <div class="nav-item" data-target="profileSection"> | |
| <i class="material-icons">person</i> Profile | |
| </div> | |
| </nav> | |
| <script> | |
| const BASE_URL = 'https://triflix-uploadkro2.hf.space'; // Define Base URL | |
| const navItems = document.querySelectorAll('.nav-item'); | |
| const sections = document.querySelectorAll('section'); | |
| const uploadBtn = document.getElementById('uploadBtn'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileInputArea = document.querySelector('.file-input-area'); | |
| const fileNameDisplay = document.getElementById('fileNameDisplay'); | |
| const loader = document.getElementById('loader'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const uploadInfo = document.getElementById('uploadInfo'); | |
| const shareLinkContainer = document.getElementById('shareLinkContainer'); | |
| const historyList = document.getElementById('historyList'); | |
| const STORAGE_KEY = 'upload_history'; | |
| let infoTimeout = null; | |
| function displayInfoMessage(message, type = 'info', persistent = false) { | |
| if (uploadInfo) { | |
| if (infoTimeout && !persistent) clearTimeout(infoTimeout); | |
| uploadInfo.textContent = message; | |
| uploadInfo.className = 'info'; | |
| if (type === 'error') { | |
| uploadInfo.classList.add('error-message'); | |
| } else if (type === 'success') { | |
| uploadInfo.classList.add('success-message'); | |
| } | |
| if (message && !persistent && (type === 'error' || message === "Please select a file first.")) { | |
| infoTimeout = setTimeout(() => { | |
| if (uploadInfo.textContent === message) { | |
| uploadInfo.textContent = ""; | |
| uploadInfo.className = 'info'; | |
| } | |
| }, 3000); | |
| } | |
| } | |
| } | |
| function resetUploadUI(clearMessage = true) { | |
| if (fileInput) fileInput.value = ''; | |
| if (fileNameDisplay) fileNameDisplay.textContent = 'No file selected'; | |
| if (uploadBtn) uploadBtn.disabled = true; | |
| if (progressContainer) progressContainer.style.display = 'none'; | |
| if (progressBar) progressBar.style.width = '0%'; | |
| if (clearMessage) { | |
| displayInfoMessage(""); | |
| } | |
| if (loader) loader.style.display = 'none'; | |
| if (shareLinkContainer) { | |
| shareLinkContainer.style.display = 'none'; | |
| shareLinkContainer.innerHTML = ''; | |
| } | |
| if (progressBar) progressBar.style.background = 'var(--accent-red)'; | |
| } | |
| if (navItems.length) { | |
| navItems.forEach(item => { | |
| item.addEventListener('click', () => { | |
| navItems.forEach(i => i.classList.remove('active')); | |
| item.classList.add('active'); | |
| const targetId = item.dataset.target; | |
| sections.forEach(sec => { | |
| sec.classList.toggle('active', sec.id === targetId); | |
| }); | |
| if (targetId === 'historySection') { | |
| loadHistory(); | |
| } | |
| }); | |
| }); | |
| } | |
| if (fileInput && fileInputArea && fileNameDisplay && uploadBtn && progressContainer && progressBar && uploadInfo && shareLinkContainer && loader) { | |
| fileInput.addEventListener('change', () => { | |
| if (fileInput.files.length > 0) { | |
| fileNameDisplay.textContent = fileInput.files[0].name; | |
| uploadBtn.disabled = false; | |
| progressContainer.style.display = 'none'; | |
| progressBar.style.width = '0%'; | |
| if (uploadInfo.textContent === "Please select a file first.") { | |
| displayInfoMessage(""); | |
| } | |
| shareLinkContainer.style.display = 'none'; | |
| loader.style.display = 'none'; | |
| } else { | |
| fileNameDisplay.textContent = 'No file selected'; | |
| uploadBtn.disabled = true; | |
| } | |
| }); | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| fileInputArea.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| fileInputArea.addEventListener(eventName, () => fileInputArea.classList.add('dragover'), false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| fileInputArea.addEventListener(eventName, () => fileInputArea.classList.remove('dragover'), false); | |
| }); | |
| fileInputArea.addEventListener('drop', (e) => { | |
| fileInput.files = e.dataTransfer.files; | |
| fileInput.dispatchEvent(new Event('change')); | |
| }, false); | |
| } | |
| function copyToClipboard(text, buttonElement) { | |
| navigator.clipboard.writeText(text).then(() => { | |
| const originalText = buttonElement ? buttonElement.textContent : null; | |
| const originalBg = buttonElement ? buttonElement.style.backgroundColor : null; | |
| if (buttonElement) { | |
| buttonElement.textContent = 'Copied!'; | |
| buttonElement.style.backgroundColor = '#28a745'; | |
| setTimeout(() => { | |
| if (buttonElement) { | |
| buttonElement.textContent = originalText; | |
| buttonElement.style.backgroundColor = originalBg; | |
| } | |
| }, 2000); | |
| } | |
| }).catch(err => { | |
| console.error('Failed to copy: ', err); | |
| displayInfoMessage('Copy failed.', 'error'); | |
| }); | |
| } | |
| function loadHistory() { | |
| if (!historyList) return; | |
| const data = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); | |
| historyList.innerHTML = ''; | |
| if (data.length === 0) { | |
| historyList.innerHTML = '<p style="text-align:center; color: var(--text-secondary); padding: 20px 0;">No upload history.</p>'; | |
| return; | |
| } | |
| data.forEach(entry => { | |
| const el = document.createElement('div'); | |
| el.className = 'history-item'; | |
| const dateStr = entry.timestamp ? new Date(entry.timestamp).toLocaleDateString() : ''; | |
| // Ensure entry.url is a full URL (new entries will be, old ones might not if not migrated) | |
| const fullUrl = entry.url.startsWith('http') ? entry.url : `${BASE_URL}${entry.url}`; | |
| el.innerHTML = ` | |
| <div class="history-item-info"> | |
| <div class="history-item-name">${entry.name}</div> | |
| <a href="${fullUrl}" target="_blank" rel="noopener noreferrer" class="history-item-url">${fullUrl.length > 50 ? fullUrl.substring(0,50)+'...' : fullUrl}</a> | |
| ${dateStr ? `<span style="font-size:0.7em; color:var(--text-placeholder); display:block; margin-top:2px;">${dateStr}</span>` : ''} | |
| </div> | |
| <button data-url="${fullUrl}">Copy</button> | |
| `; | |
| historyList.appendChild(el); | |
| el.querySelector('button').addEventListener('click', function() { | |
| copyToClipboard(this.dataset.url, this); | |
| }); | |
| }); | |
| } | |
| if (uploadBtn && fileInput && loader && progressContainer && progressBar && uploadInfo && shareLinkContainer) { | |
| uploadBtn.addEventListener('click', () => { | |
| const file = fileInput.files[0]; | |
| if (!file) { | |
| displayInfoMessage("Please select a file first.", "error"); | |
| return; | |
| } | |
| const xhr = new XMLHttpRequest(); | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| loader.style.display = 'block'; | |
| progressContainer.style.display = 'block'; | |
| progressBar.style.width = '0%'; | |
| progressBar.style.background = 'var(--accent-red)'; | |
| displayInfoMessage('0%'); | |
| uploadBtn.disabled = true; | |
| shareLinkContainer.style.display = 'none'; | |
| xhr.upload.onprogress = event => { | |
| if (event.lengthComputable) { | |
| const percentComplete = Math.round((event.loaded / event.total) * 100); | |
| progressBar.style.width = percentComplete + '%'; | |
| displayInfoMessage(percentComplete + '%'); | |
| } | |
| }; | |
| xhr.onload = () => { | |
| loader.style.display = 'none'; | |
| if (xhr.status === 200) { | |
| try { | |
| const response = JSON.parse(xhr.responseText); | |
| // Prepend BASE_URL if download_url is relative | |
| const fullDownloadUrl = response.download_url.startsWith('http') ? response.download_url : `${BASE_URL}${response.download_url}`; | |
| progressBar.style.width = '100%'; | |
| displayInfoMessage('Upload Complete!', 'success', true); | |
| shareLinkContainer.innerHTML = ` | |
| <p>Share Link:</p> | |
| <div class="share-link-display"> | |
| <input type="text" value="${fullDownloadUrl}" id="shareableLinkInput-${Date.now()}" readonly> | |
| <button data-url="${fullDownloadUrl}">Copy</button> | |
| </div> | |
| `; | |
| shareLinkContainer.style.display = 'block'; | |
| shareLinkContainer.querySelector('button').addEventListener('click', function() { | |
| const inputId = this.previousElementSibling.id; | |
| const linkInput = document.getElementById(inputId); | |
| if (linkInput) { | |
| linkInput.select(); | |
| linkInput.setSelectionRange(0, 99999); | |
| copyToClipboard(this.dataset.url, this); | |
| } | |
| }); | |
| const historyData = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); | |
| historyData.unshift({ | |
| name: response.filename || file.name, | |
| url: fullDownloadUrl, // Store the full URL | |
| timestamp: Date.now() | |
| }); | |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(historyData.slice(0, 20))); | |
| fileInput.value = ''; | |
| fileNameDisplay.textContent = 'No file selected'; | |
| uploadBtn.disabled = true; | |
| } catch (e) { | |
| console.error("Error parsing server response:", e); | |
| displayInfoMessage('Error processing response.', 'error', true); | |
| if(progressBar) progressBar.style.width = '0%'; | |
| } | |
| } else { | |
| displayInfoMessage(`Upload failed. Status: ${xhr.status}`, 'error', true); | |
| if(progressBar) progressBar.style.background = '#dc3545'; | |
| } | |
| }; | |
| xhr.onerror = () => { | |
| if(loader) loader.style.display = 'none'; | |
| displayInfoMessage('Upload error. Check connection.', 'error', true); | |
| if(progressBar) progressBar.style.background = '#dc3545'; | |
| }; | |
| xhr.open('POST', '/upload/', true); // DO NOT CHANGE BACKEND | |
| xhr.send(formData); | |
| }); | |
| } | |
| // Initial load | |
| if (typeof resetUploadUI === 'function') resetUploadUI(); | |
| document.querySelector('.nav-item[data-target="uploadSection"]')?.click(); | |
| </script> | |
| </body> | |
| </html> |