responsive-app v1

This commit is contained in:
MEGASOL\simon.adams
2025-09-15 18:03:48 +02:00
parent 8ab6d4547f
commit 938cee689c
4 changed files with 60 additions and 32 deletions

View File

@@ -173,12 +173,14 @@ async function runQueue(){
// --- DOM refs --- // --- DOM refs ---
const dz = document.getElementById('dropzone'); const dz = document.getElementById('dropzone');
const fi = document.getElementById('fileInput'); const fi = document.getElementById('fileInput');
const btnMobilePick = document.getElementById('btnMobilePick');
const btnClearFinished = document.getElementById('btnClearFinished'); const btnClearFinished = document.getElementById('btnClearFinished');
const btnClearAll = document.getElementById('btnClearAll'); const btnClearAll = document.getElementById('btnClearAll');
const btnPing = document.getElementById('btnPing'); const btnPing = document.getElementById('btnPing');
const pingStatus = document.getElementById('pingStatus'); const pingStatus = document.getElementById('pingStatus');
const banner = document.getElementById('topBanner'); const banner = document.getElementById('topBanner');
const btnTheme = document.getElementById('btnTheme'); const btnTheme = document.getElementById('btnTheme');
const dropHint = document.getElementById('dropHint');
// --- Simple banner helper --- // --- Simple banner helper ---
function showBanner(text, kind='ok'){ function showBanner(text, kind='ok'){
@@ -245,6 +247,9 @@ dz.addEventListener('drop', (e)=>{
// --- Mobile-safe file input change handler --- // --- Mobile-safe file input change handler ---
const isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); const isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
let suppressClicksUntil = 0; let suppressClicksUntil = 0;
if (isTouch && dropHint) {
try { dropHint.classList.add('hidden'); } catch {}
}
fi.addEventListener('click', (e) => { fi.addEventListener('click', (e) => {
// prevent bubbling to parents (extra safety) // prevent bubbling to parents (extra safety)
@@ -278,6 +283,14 @@ if (!isTouch) {
}); });
} }
// Mobile sticky CTA: trigger system file picker
if (btnMobilePick) {
btnMobilePick.onclick = () => {
try { fi.value = ''; } catch {}
fi.click();
};
}
// --- Clear buttons --- // --- Clear buttons ---
btnClearFinished.onclick = ()=>{ btnClearFinished.onclick = ()=>{
items = items.filter(i => !['done','duplicate'].includes(i.status)); items = items.filter(i => !['done','duplicate'].includes(i.status));

View File

@@ -7,21 +7,20 @@
<link rel="icon" type="image/png" href="/static/favicon.png" /> <link rel="icon" type="image/png" href="/static/favicon.png" />
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script> <script>
tailwind.config = { tailwind.config = { darkMode: 'class' };
darkMode: 'class'
}
</script> </script>
<style> body { padding-bottom: env(safe-area-inset-bottom); } </style>
</head> </head>
<body class="min-h-screen bg-gray-50 text-gray-900 dark:bg-gray-900 dark:text-gray-100 transition-colors duration-200"> <body class="min-h-screen bg-gray-50 text-gray-900 dark:bg-gray-900 dark:text-gray-100 transition-colors duration-200">
<div class="mx-auto max-w-4xl p-6 space-y-6"> <div class="mx-auto max-w-4xl p-6 space-y-6">
<!-- Ephemeral top banner --> <!-- Ephemeral top banner -->
<div id="topBanner" class="hidden rounded-2xl border border-green-200 bg-green-50 p-3 text-green-700 text-center dark:bg-green-900 dark:border-green-700 dark:text-green-300"></div> <div id="topBanner" class="hidden rounded-2xl border border-green-200 bg-green-50 p-3 text-green-700 text-center dark:bg-green-900 dark:border-green-700 dark:text-green-300"></div>
<header class="flex items-center justify-between"> <header class="flex items-center justify-between flex-wrap gap-2">
<h1 class="text-2xl font-semibold tracking-tight">Immich Drop Uploader</h1> <h1 class="text-2xl font-semibold tracking-tight">Immich Drop Uploader</h1>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<a href="/login" class="rounded-xl border px-3 py-1 text-sm dark:border-gray-600 dark:hover:bg-gray-800 hover:bg-gray-100 transition-colors">Login</a> <a href="/login" class="rounded-xl border px-4 py-2 text-sm dark:border-gray-600 dark:hover:bg-gray-800 hover:bg-gray-100 transition-colors" aria-label="Login">Login</a>
<button id="btnTheme" class="rounded-xl border px-3 py-1 text-sm dark:border-gray-600 dark:hover:bg-gray-800 hover:bg-gray-100 transition-colors" title="Toggle dark mode"> <button id="btnTheme" class="rounded-xl border px-3 py-2 text-sm dark:border-gray-600 dark:hover:bg-gray-800 hover:bg-gray-100 transition-colors" title="Toggle dark mode" aria-label="Toggle theme">
<svg id="iconLight" class="w-4 h-4 hidden" fill="currentColor" viewBox="0 0 20 20"> <svg id="iconLight" class="w-4 h-4 hidden" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/>
</svg> </svg>
@@ -29,28 +28,29 @@
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/> <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
</svg> </svg>
</button> </button>
<button id="btnPing" class="rounded-xl border px-3 py-1 text-sm dark:border-gray-600 dark:hover:bg-gray-800 hover:bg-gray-100 transition-colors">Test connection</button> <button id="btnPing" class="rounded-xl border px-3 py-2 text-sm dark:border-gray-600 dark:hover:bg-gray-800 hover:bg-gray-100 transition-colors" aria-label="Test connection">Test connection</button>
<span id="pingStatus" class="ml-2 text-sm text-gray-500 dark:text-gray-400"></span> <span id="pingStatus" class="ml-2 text-sm text-gray-500 dark:text-gray-400 hidden sm:inline"></span>
</div> </div>
</header> </header>
<!-- Dropzone --> <!-- Dropzone -->
<section id="dropzone" class="rounded-2xl border-2 border-dashed p-10 text-center bg-white dark:bg-gray-800 dark:border-gray-600 transition-colors"> <section id="dropzone" class="rounded-2xl border-2 border-dashed p-8 md:p-10 text-center bg-white dark:bg-gray-800 dark:border-gray-600 transition-colors">
<div class="mx-auto h-12 w-12 opacity-70"> <div id="dropHint" class="mx-auto h-12 w-12 opacity-70 hidden md:block">
<!-- upload icon --> <!-- upload icon -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 16V8m0 0l-3 3m3-3 3 3M4 16a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-1a1 1 0 1 0-2 0v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-1a1 1 0 1 0-2 0v1z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 16V8m0 0l-3 3m3-3 3 3M4 16a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-1a1 1 0 1 0-2 0v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-1a1 1 0 1 0-2 0v1z"/></svg>
</div> </div>
<p class="mt-3 font-medium">Drop images or videos here</p> <p class="mt-3 font-medium hidden md:block">Drop images or videos here</p>
<p class="text-sm text-gray-600 dark:text-gray-400">...or</p> <p class="text-sm text-gray-600 dark:text-gray-400 hidden md:block">...or</p>
<!-- Mobile-safe choose control: label wraps the hidden input --> <!-- Mobile-safe choose control: label wraps the hidden input -->
<div class="mt-3 relative inline-block"> <div class="mt-3 relative inline-block">
<label class="rounded-2xl bg-black text-white dark:bg-white dark:text-black px-4 py-2 hover:opacity-90 cursor-pointer select-none transition-colors"> <label class="rounded-2xl bg-black text-white dark:bg-white dark:text-black px-5 py-3 hover:opacity-90 cursor-pointer select-none transition-colors" aria-label="Choose files">
Choose files Choose files
<input id="fileInput" <input id="fileInput"
type="file" type="file"
multiple multiple
accept="image/*,video/*" accept="image/*,video/*"
capture="environment"
class="absolute inset-0 opacity-0 cursor-pointer" /> class="absolute inset-0 opacity-0 cursor-pointer" />
</label> </label>
</div> </div>
@@ -85,6 +85,13 @@
</footer> </footer>
</div> </div>
<!-- Sticky mobile upload bar -->
<div class="md:hidden fixed left-0 right-0 bottom-0 z-20 p-3 bg-white/90 dark:bg-gray-900/90 border-t border-gray-200 dark:border-gray-700 backdrop-blur" style="padding-bottom: calc(env(safe-area-inset-bottom) + 12px)">
<div class="mx-auto max-w-4xl">
<button id="btnMobilePick" class="w-full rounded-2xl bg-black text-white dark:bg-white dark:text-black px-5 py-3" aria-label="Choose files">Choose files</button>
</div>
</div>
<script src="/static/app.js"></script> <script src="/static/app.js"></script>
</body> </body>
</html> </html>

View File

@@ -9,7 +9,9 @@
<script> <script>
tailwind.config = { darkMode: 'class' }; tailwind.config = { darkMode: 'class' };
</script> </script>
<style> body { transition: background-color .2s ease, color .2s ease; } </style> <style>
body { transition: background-color .2s ease, color .2s ease; padding-bottom: env(safe-area-inset-bottom); }
</style>
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<meta http-equiv="Cache-Control" content="no-store" /> <meta http-equiv="Cache-Control" content="no-store" />
<meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Pragma" content="no-cache" />
@@ -18,7 +20,7 @@
<div class="mx-auto max-w-4xl p-6 space-y-6"> <div class="mx-auto max-w-4xl p-6 space-y-6">
<div id="topBanner" class="hidden rounded-2xl border border-green-200 bg-green-50 p-3 text-green-700 text-center dark:bg-green-900 dark:border-green-700 dark:text-green-300"></div> <div id="topBanner" class="hidden rounded-2xl border border-green-200 bg-green-50 p-3 text-green-700 text-center dark:bg-green-900 dark:border-green-700 dark:text-green-300"></div>
<header class="flex items-center justify-between"> <header class="flex items-center justify-between flex-wrap gap-2">
<h1 class="text-2xl font-semibold tracking-tight">Immich Drop Uploader</h1> <h1 class="text-2xl font-semibold tracking-tight">Immich Drop Uploader</h1>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<a id="linkHome" href="/" class="hidden rounded-xl border px-3 py-1 text-sm dark:border-gray-600">Home</a> <a id="linkHome" href="/" class="hidden rounded-xl border px-3 py-1 text-sm dark:border-gray-600">Home</a>
@@ -52,16 +54,16 @@
</section> </section>
<!-- Dropzone and queue copied from index.html --> <!-- Dropzone and queue copied from index.html -->
<section id="dropzone" class="rounded-2xl border-2 border-dashed p-10 text-center bg-white dark:bg-gray-800 dark:border-gray-600"> <section id="dropzone" class="rounded-2xl border-2 border-dashed p-8 md:p-10 text-center bg-white dark:bg-gray-800 dark:border-gray-600">
<div class="mx-auto h-12 w-12 opacity-70"> <div id="dropHint" class="mx-auto h-12 w-12 opacity-70 hidden md:block">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 16V8m0 0l-3 3m3-3 3 3M4 16a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-1a1 1 0 1 0-2 0v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-1a1 1 0 1 0-2 0v1z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 16V8m0 0l-3 3m3-3 3 3M4 16a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-1a1 1 0 1 0-2 0v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-1a1 1 0 1 0-2 0v1z"/></svg>
</div> </div>
<p class="mt-3 font-medium">Drop images or videos here</p> <p class="mt-3 font-medium hidden md:block">Drop images or videos here</p>
<p class="text-sm text-gray-600 dark:text-gray-400">...or</p> <p class="text-sm text-gray-600 dark:text-gray-400 hidden md:block">...or</p>
<div class="mt-3 relative inline-block"> <div class="mt-3 relative inline-block">
<label class="rounded-2xl bg-black text-white dark:bg-white dark:text-black px-4 py-2 hover:opacity-90 cursor-pointer select-none"> <label class="rounded-2xl bg-black text-white dark:bg-white dark:text-black px-5 py-3 hover:opacity-90 cursor-pointer select-none" aria-label="Choose files">
Choose files Choose files
<input id="fileInput" type="file" multiple accept="image/*,video/*" class="absolute inset-0 opacity-0 cursor-pointer" /> <input id="fileInput" type="file" multiple accept="image/*,video/*" capture="environment" class="absolute inset-0 opacity-0 cursor-pointer" />
</label> </label>
</div> </div>
<div class="mt-4 text-sm text-gray-500 dark:text-gray-400"> <div class="mt-4 text-sm text-gray-500 dark:text-gray-400">
@@ -86,6 +88,12 @@
Invite upload page Invite upload page
</footer> </footer>
</div> </div>
<!-- Sticky mobile upload bar -->
<div class="md:hidden fixed left-0 right-0 bottom-0 z-20 p-3 bg-white/90 dark:bg-gray-900/90 border-t border-gray-200 dark:border-gray-700 backdrop-blur" style="padding-bottom: calc(env(safe-area-inset-bottom) + 12px)">
<div class="mx-auto max-w-4xl">
<button id="btnMobilePick" class="w-full rounded-2xl bg-black text-white dark:bg-white dark:text-black px-5 py-3" aria-label="Choose files">Choose files</button>
</div>
</div>
<script src="/static/header.js"></script> <script src="/static/header.js"></script>
<script src="/static/app.js"></script> <script src="/static/app.js"></script>
</body> </body>

View File

@@ -31,16 +31,16 @@
</div> </div>
</header> </header>
<section class="rounded-2xl border bg-white dark:bg-gray-800 dark:border-gray-700 p-4 space-y-3"> <section class="rounded-2xl border bg-white dark:bg-gray-800 dark:border-gray-700 p-4 space-y-4">
<div> <div>
<div class="text-sm font-medium mb-1">Target album</div> <div class="text-sm font-medium mb-1">Target album</div>
<div id="albumControls" class="space-y-2"> <div id="albumControls" class="space-y-2">
<div id="albumSelectWrap" class="hidden"> <div id="albumSelectWrap" class="hidden">
<select id="albumSelect" class="w-full rounded-lg border px-3 py-2 bg-white dark:bg-gray-900 dark:border-gray-700"></select> <select id="albumSelect" class="w-full rounded-lg border px-3 py-3 bg-white dark:bg-gray-900 dark:border-gray-700"></select>
</div> </div>
<div id="albumInputWrap" class="hidden"> <div id="albumInputWrap" class="hidden">
<input id="albumInput" placeholder="Album name" class="w-full rounded-lg border px-3 py-2 bg-white dark:bg-gray-900 dark:border-gray-700" /> <input id="albumInput" placeholder="Album name" class="w-full rounded-lg border px-3 py-3 bg-white dark:bg-gray-900 dark:border-gray-700" />
<button id="btnCreateAlbum" class="mt-2 rounded-xl bg-black text-white px-3 py-1 dark:bg-white dark:text-black">Create album</button> <button id="btnCreateAlbum" class="mt-2 w-full sm:w-auto rounded-xl bg-black text-white px-4 py-3 dark:bg-white dark:text-black">Create album</button>
</div> </div>
<div id="albumHint" class="text-sm text-gray-500"></div> <div id="albumHint" class="text-sm text-gray-500"></div>
</div> </div>
@@ -48,26 +48,26 @@
<div class="grid grid-cols-1 md:grid-cols-3 gap-3"> <div class="grid grid-cols-1 md:grid-cols-3 gap-3">
<div> <div>
<div class="text-sm font-medium mb-1">Usage</div> <div class="text-sm font-medium mb-1">Usage</div>
<select id="usage" class="w-full rounded-lg border px-3 py-2 bg-white dark:bg-gray-900 dark:border-gray-700"> <select id="usage" class="w-full rounded-lg border px-3 py-3 bg-white dark:bg-gray-900 dark:border-gray-700">
<option value="1">One-time</option> <option value="1">One-time</option>
<option value="-1">Indefinite</option> <option value="-1">Indefinite</option>
</select> </select>
</div> </div>
<div> <div>
<div class="text-sm font-medium mb-1">Expires in days</div> <div class="text-sm font-medium mb-1">Expires in days</div>
<input id="days" type="number" min="0" placeholder="Leave empty for no expiry" class="w-full rounded-lg border px-3 py-2 bg-white dark:bg-gray-900 dark:border-gray-700" /> <input id="days" type="number" min="0" placeholder="Leave empty for no expiry" class="w-full rounded-lg border px-3 py-3 bg-white dark:bg-gray-900 dark:border-gray-700" />
</div> </div>
</div> </div>
<div> <div>
<button id="btnCreate" class="rounded-xl bg-black text-white px-4 py-2 dark:bg-white dark:text-black">Create link</button> <button id="btnCreate" class="w-full sm:w-auto rounded-xl bg-black text-white px-5 py-3 dark:bg-white dark:text-black">Create link</button>
</div> </div>
<div id="result" class="hidden rounded-xl border p-3 text-sm space-y-2"> <div id="result" class="hidden rounded-xl border p-3 text-sm space-y-2">
<div id="linkRow" class="flex items-center gap-2"> <div id="linkRow" class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
<input id="linkOut" class="flex-1 rounded-lg border px-3 py-2 bg-white dark:bg-gray-900 dark:border-gray-700" readonly /> <input id="linkOut" class="flex-1 rounded-lg border px-3 py-3 bg-white dark:bg-gray-900 dark:border-gray-700" readonly />
<button id="btnCopy" type="button" class="rounded-xl border px-3 py-2 text-sm dark:border-gray-600">Copy</button> <button id="btnCopy" type="button" class="rounded-xl border px-4 py-3 text-sm dark:border-gray-600" aria-label="Copy link">Copy</button>
</div> </div>
<div> <div>
<img id="qrImg" alt="QR" class="h-40 w-40" /> <img id="qrImg" alt="QR" class="h-32 w-32 md:h-40 md:w-40" />
</div> </div>
</div> </div>
</section> </section>