diff --git a/app.css b/app.css index c19d428..32be8f7 100644 --- a/app.css +++ b/app.css @@ -352,6 +352,30 @@ body { border-color: #fff; } +/* Auswahl-Mode in der Dokumenten-Liste (PDFs etc.) */ +.doc-item .doc-check { + display: none; + width: 22px; + height: 22px; + border-radius: 50%; + background: transparent; + border: 2px solid #888; + color: transparent; + font-size: 14px; + font-weight: bold; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.doc-list.selecting .doc-item .doc-check { display: flex; } +.doc-list.selecting .doc-item .doc-open { pointer-events: none; opacity: 0.4; } +.doc-list.selecting .doc-item.selected { background: rgba(90,176,255,0.15); outline: 2px solid #5ab0ff; } +.doc-list.selecting .doc-item.selected .doc-check { + background: #5ab0ff; + border-color: #5ab0ff; + color: #fff; +} + /* Sticky Aktions-Bar am unteren Bildschirmrand */ .select-bar { position: fixed; diff --git a/app.js b/app.js index 8f71bdb..a137082 100644 --- a/app.js +++ b/app.js @@ -45,6 +45,9 @@ function setBack(visible, hash) { const modalStack = []; let _skipNextPopstate = false; let _lastBackAt = 0; +// Aktiver Select-Mode-Cleanup: wird von popstate gerufen, damit Android-Back +// eine laufende Mehrfachauswahl beendet statt die Seite zu verlassen. +let selectModeCleanup = null; function pushModal(el, cleanup) { modalStack.push({ el, cleanup: cleanup || null }); @@ -83,6 +86,12 @@ window.addEventListener('popstate', () => { try { top.cleanup && top.cleanup(); } catch {} return; } + if (selectModeCleanup) { + const fn = selectModeCleanup; + selectModeCleanup = null; + try { fn(); } catch {} + return; + } if (isTopLevelHash(location.hash)) { const now = Date.now(); if (now - _lastBackAt < 2000) return; @@ -535,7 +544,7 @@ router.on('/orders/:id', async (args) => {

Hochgeladene Fotos (${imagePhotos.length})

- ${imagePhotos.length ? '' : ''} + ${(imagePhotos.length + otherDocs.length) ? '' : ''}
${imagePhotos.map(p => `
`).join('')} @@ -558,6 +567,7 @@ router.on('/orders/:id', async (args) => {

Weitere Dokumente (${otherDocs.length})

${otherDocs.map(p => `
+
${docIconFor(p.filename, p.mime)}
${escapeHtml(p.filename)}
@@ -571,6 +581,7 @@ router.on('/orders/:id', async (args) => { // Foto-Thumbnails: Tap = Vollbild-Modal (oder Auswahl im Select-Modus) const photoGrid = document.getElementById('photo-grid'); + const docList = document.querySelector('.doc-list'); photoGrid.querySelectorAll('.thumb').forEach(t => { t.addEventListener('click', () => { if (photoGrid.classList.contains('selecting')) { @@ -586,17 +597,33 @@ router.on('/orders/:id', async (args) => { const selectBtn = document.getElementById('btn-select-mode'); if (selectBtn) selectBtn.onclick = () => startSelectMode(); + function allSelectables() { + const thumbs = Array.from(photoGrid.querySelectorAll('.thumb[data-relpath]')); + const docs = docList ? Array.from(docList.querySelectorAll('.doc-item[data-relpath]')) : []; + return thumbs.concat(docs); + } function startSelectMode() { + if (photoGrid.classList.contains('selecting')) return; photoGrid.classList.add('selecting'); + if (docList) docList.classList.add('selecting'); renderSelectBar(); updateSelectBar(); + // Android-Back soll nur die Auswahl aufheben + history.pushState({ _selecting: true }, '', location.hash); + selectModeCleanup = endSelectMode; } function endSelectMode() { + selectModeCleanup = null; photoGrid.classList.remove('selecting'); - photoGrid.querySelectorAll('.thumb.selected').forEach(x => x.classList.remove('selected')); + if (docList) docList.classList.remove('selecting'); + allSelectables().forEach(x => x.classList.remove('selected')); const bar = document.getElementById('select-bar'); if (bar) bar.remove(); } + function cancelViaBack() { + if (history.state && history.state._selecting) history.back(); + else endSelectMode(); + } function renderSelectBar() { if (document.getElementById('select-bar')) return; const bar = document.createElement('div'); @@ -609,42 +636,50 @@ router.on('/orders/:id', async (args) => { `; document.body.appendChild(bar); - bar.querySelector('#sb-cancel').onclick = endSelectMode; + bar.querySelector('#sb-cancel').onclick = cancelViaBack; bar.querySelector('#sb-all').onclick = () => { - const thumbs = photoGrid.querySelectorAll('.thumb[data-relpath]'); - const anyUn = Array.from(thumbs).some(t => !t.classList.contains('selected')); - thumbs.forEach(t => t.classList.toggle('selected', anyUn)); + const items = allSelectables(); + const anyUn = items.some(t => !t.classList.contains('selected')); + items.forEach(t => t.classList.toggle('selected', anyUn)); updateSelectBar(); }; bar.querySelector('#sb-share').onclick = () => shareSelected(); } function updateSelectBar() { - const n = photoGrid.querySelectorAll('.thumb.selected').length; + const n = allSelectables().filter(x => x.classList.contains('selected')).length; const info = document.getElementById('sb-info'); const share = document.getElementById('sb-share'); if (info) info.textContent = n + ' ausgewählt'; if (share) share.disabled = n === 0; } async function shareSelected() { - const sel = Array.from(photoGrid.querySelectorAll('.thumb.selected')); + const sel = allSelectables().filter(x => x.classList.contains('selected')); if (!sel.length) return; - showToast('Lade ' + sel.length + ' Foto' + (sel.length === 1 ? '' : 's') + '…'); + showToast('Lade ' + sel.length + ' Datei' + (sel.length === 1 ? '' : 'en') + '…'); const items = []; for (const t of sel) { const rp = t.dataset.relpath; try { const f = await api.getFileBlobUrl(rp); - if (f) items.push({ blob: f.blob, filename: rp.split('/').pop(), mime: f.mime }); + if (f) items.push({ blob: f.blob, filename: t.dataset.filename || rp.split('/').pop(), mime: f.mime }); } catch (e) { console.warn('Share-Load', rp, e); } } if (!items.length) { showToast('Keine Dateien geladen', 'error'); return; } - const ok = await shareFiles(items, 'Fotos vom Auftrag'); - if (ok) endSelectMode(); + const ok = await shareFiles(items, 'Dateien vom Auftrag'); + if (ok) cancelViaBack(); } - // Weitere Dokumente: Tap = Öffnen (PDF inline, sonst Download) + // Weitere Dokumente: Tap = Öffnen (oder Auswahl im Select-Modus) document.querySelectorAll('.doc-list .doc-item').forEach(el => { - el.addEventListener('click', async () => { + el.addEventListener('click', async (e) => { + if (docList && docList.classList.contains('selecting')) { + // Auch Klick auf Öffnen-Button soll nur selektieren + e.preventDefault(); + e.stopPropagation(); + el.classList.toggle('selected'); + updateSelectBar(); + return; + } const rel = el.dataset.relpath; const filename = el.dataset.filename; el.classList.add('loading');