* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix * fix: track native source modules with root-anchored runtime ignores
170 lines
6.7 KiB
JavaScript
170 lines
6.7 KiB
JavaScript
(() => {
|
||
'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();
|
||
})();
|