Files
vendoo/public/mobile-upload.js
Masterluke77andGitHub 6f48827f4d Vendoo 1.41.2 – Git Ignore Boundary & Module Tracking Hotfix (#18)
* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix

* fix: track native source modules with root-anchored runtime ignores
2026-07-09 17:09:00 +02:00

170 lines
6.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
'use strict';
const token = decodeURIComponent(location.pathname.split('/').filter(Boolean).pop() || '');
const cameraInput = document.getElementById('camera-input');
const libraryInput = document.getElementById('library-input');
const preview = document.getElementById('mobile-preview');
const uploadButton = document.getElementById('upload-btn');
const uploadPanel = document.getElementById('upload-panel');
const successPanel = document.getElementById('success-panel');
const errorPanel = document.getElementById('error-panel');
const status = document.getElementById('session-status');
const count = document.getElementById('photo-count');
const expiresCopy = document.getElementById('expires-copy');
const progress = document.getElementById('upload-progress');
const sourceButtons = [
document.getElementById('open-camera-btn'),
document.getElementById('open-library-btn'),
document.getElementById('add-camera-btn'),
document.getElementById('add-library-btn'),
document.getElementById('success-camera-btn'),
document.getElementById('success-library-btn'),
].filter(Boolean);
let selected = [];
let serverFiles = [];
let remaining = 10;
function showError(message) {
uploadPanel.classList.add('hidden');
successPanel.classList.add('hidden');
errorPanel.classList.remove('hidden');
document.getElementById('error-copy').textContent = message;
status.className = 'session-status';
status.innerHTML = '<span></span>Upload nicht verfügbar';
}
function openCamera() {
if (remaining - selected.length <= 0) return;
cameraInput.click();
}
function openLibrary() {
if (remaining - selected.length <= 0) return;
libraryInput.click();
}
function render() {
preview.innerHTML = '';
selected.forEach((file, index) => {
const figure = document.createElement('figure');
const image = document.createElement('img');
image.alt = file.name || `Foto ${index + 1}`;
const objectUrl = URL.createObjectURL(file);
image.src = objectUrl;
image.addEventListener('load', () => URL.revokeObjectURL(objectUrl), { once: true });
image.addEventListener('error', () => {
URL.revokeObjectURL(objectUrl);
image.remove();
const fallback = document.createElement('div');
fallback.className = 'mobile-preview-fallback';
fallback.innerHTML = `<strong>Bild ${index + 1}</strong><span>${file.name || 'Foto ausgewählt'}</span>`;
figure.prepend(fallback);
}, { once: true });
const remove = document.createElement('button');
remove.type = 'button';
remove.textContent = '×';
remove.setAttribute('aria-label', `${file.name || 'Bild'} entfernen`);
remove.addEventListener('click', () => {
selected.splice(index, 1);
render();
});
figure.append(image, remove);
preview.appendChild(figure);
});
const total = serverFiles.length + selected.length;
count.textContent = `${total} von 10 Bildern`;
uploadButton.disabled = selected.length === 0;
uploadButton.textContent = selected.length
? `${selected.length} ${selected.length === 1 ? 'Foto' : 'Fotos'} zu Vendoo senden`
: 'Fotos zu Vendoo senden';
const noSlots = remaining - selected.length <= 0;
sourceButtons.forEach(button => { button.disabled = noSlots; });
}
function addFiles(files) {
const candidates = [...files].filter(file => file.type.startsWith('image/') || /\.(heic|heif)$/i.test(file.name || ''));
const slots = Math.max(0, remaining - selected.length);
selected.push(...candidates.slice(0, slots));
render();
}
async function loadSession() {
try {
const response = await fetch(`/mobile-upload-api/${encodeURIComponent(token)}`, { cache: 'no-store' });
const data = await response.json();
if (!response.ok) throw new Error(data.error || 'Upload-Link konnte nicht geladen werden.');
serverFiles = data.files || [];
remaining = data.remaining ?? Math.max(0, 10 - serverFiles.length);
status.className = 'session-status ok';
status.innerHTML = '<span></span>Mit Vendoo verbunden';
uploadPanel.classList.remove('hidden');
const expires = new Date(String(data.expires_at).replace(' ', 'T') + (String(data.expires_at).includes('Z') ? '' : 'Z'));
expiresCopy.textContent = Number.isNaN(expires.getTime())
? ''
: `gültig bis ${expires.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`;
render();
} catch (error) {
showError(error.message);
}
}
async function upload() {
if (!selected.length) return;
uploadButton.disabled = true;
sourceButtons.forEach(button => { button.disabled = true; });
progress.classList.remove('hidden');
try {
const form = new FormData();
selected.forEach(file => form.append('photos', file, file.name || `mobile-${Date.now()}.jpg`));
const response = await fetch(`/mobile-upload-api/${encodeURIComponent(token)}/photos?platform=mobile`, {
method: 'POST',
body: form,
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || 'Upload fehlgeschlagen.');
serverFiles = data.files || serverFiles;
remaining = data.remaining ?? Math.max(0, 10 - serverFiles.length);
selected = [];
render();
uploadPanel.classList.add('hidden');
successPanel.classList.remove('hidden');
} catch (error) {
showError(error.message);
} finally {
progress.classList.add('hidden');
uploadButton.disabled = false;
sourceButtons.forEach(button => { button.disabled = false; });
}
}
function returnToUploadAndOpen(opener) {
successPanel.classList.add('hidden');
uploadPanel.classList.remove('hidden');
window.setTimeout(opener, 50);
}
cameraInput.addEventListener('change', () => {
addFiles(cameraInput.files || []);
cameraInput.value = '';
});
libraryInput.addEventListener('change', () => {
addFiles(libraryInput.files || []);
libraryInput.value = '';
});
document.getElementById('open-camera-btn').addEventListener('click', openCamera);
document.getElementById('open-library-btn').addEventListener('click', openLibrary);
document.getElementById('add-camera-btn').addEventListener('click', openCamera);
document.getElementById('add-library-btn').addEventListener('click', openLibrary);
document.getElementById('success-camera-btn').addEventListener('click', () => returnToUploadAndOpen(openCamera));
document.getElementById('success-library-btn').addEventListener('click', () => returnToUploadAndOpen(openLibrary));
uploadButton.addEventListener('click', upload);
loadSession();
})();