import { FRAME_INSET } from '../camera.js'; /** * Baut die Hauptansicht auf und liefert Aktualisierungsfunktionen zurueck. * Kennt weder Kamera noch Erkennung - alles kommt ueber die Rueckrufe. */ export function renderScanView(root, { onCapture, onUndo, onOpenList, onPickFile, onChangeMode }) { root.innerHTML = `
Noch nichts erfasst
`; const stacksEl = root.querySelector('#stacks'); const lastEl = root.querySelector('#last'); const statusEl = root.querySelector('#status'); const fileEl = root.querySelector('#file'); const pickFileButtonEl = root.querySelector('#pick-file-button'); const frameEl = root.querySelector('.frame'); const captureEl = root.querySelector('#capture'); const modeButtonEl = root.querySelector('#mode-button'); const countDisplayEl = root.querySelector('#count-display'); const countNumberEl = root.querySelector('#count-number'); const countDiagnosticsEl = root.querySelector('#count-diagnostics'); // Ob die Diagnosezeile unter der grossen Zahl gerade eingeblendet ist - // rein lokaler Anzeigezustand dieser Ansicht, siehe setCountVisible() // (Zuruecksetzen beim Verlassen der Funktion "Zählen") und den Klick- // Handler auf die grosse Zahl unten (Umschalten). let countDiagnosticsVisible = false; // FRAME_INSET (camera.js) ist die einzige Quelle der Rahmenmasse - sowohl // fuer diesen sichtbaren Rahmen als auch fuer den nativen Ausschnitt // (grabFrameRegion). Damit koennen beide nicht mehr auseinanderlaufen; // das Stilblatt selbst legt keinen Abstand mehr fest. frameEl.style.inset = `${FRAME_INSET.top * 100}% ${FRAME_INSET.right * 100}% ` + `${FRAME_INSET.bottom * 100}% ${FRAME_INSET.left * 100}%`; captureEl.addEventListener('click', onCapture); root.querySelector('#undo').addEventListener('click', onUndo); modeButtonEl.addEventListener('click', onChangeMode); // Klick auf die sichtbare Schaltflaeche ist selbst die Nutzergeste, die // die versteckte Dateiauswahl oeffnen darf - anders als ein Aufruf aus dem // Code heraus, den Handy-Browser blockieren. pickFileButtonEl.addEventListener('click', () => fileEl.click()); fileEl.addEventListener('change', () => { if (fileEl.files[0]) onPickFile(fileEl.files[0]); fileEl.value = ''; }); // Antippen der grossen Zahl blendet die Diagnosezeile ein, nochmaliges // Antippen wieder aus (siehe Moduldoku zu setCount()/setCountDiagnostics() // unten) - reiner Anzeigezustand dieser Ansicht, main.js muss davon // nichts wissen. Die grosse Zahl selbst bleibt dabei an derselben Stelle // stehen: die Diagnosezeile ist eigenstaendig positioniert (siehe // styles.css), ihr Ein-/Ausblenden nimmt der Zahl nicht den Platz weg. countNumberEl.addEventListener('click', () => { countDiagnosticsVisible = !countDiagnosticsVisible; countDiagnosticsEl.hidden = !countDiagnosticsVisible; }); return { video: root.querySelector('#preview'), /** @param {{id: string, count: number}[]} stacks */ setStacks(stacks) { stacksEl.innerHTML = ''; if (stacks.length === 0) { // Auch ohne erfasste Stapel muss die Sitzungsliste erreichbar // bleiben - darueber liegt der einzige Weg zu "Sitzung beenden". const button = document.createElement('button'); button.textContent = 'Noch keine Stapel'; button.addEventListener('click', onOpenList); stacksEl.appendChild(button); return; } for (const stack of stacks) { const button = document.createElement('button'); button.textContent = `${stack.id}: ${stack.count}`; button.addEventListener('click', onOpenList); stacksEl.appendChild(button); } }, setLast(text) { lastEl.textContent = text; }, /** * Zeigt die gewaehlte Scan-Funktion in der Ansicht an - antippbar, um zu * wechseln (siehe onChangeMode). Beschriftung kommt vom Aufrufer, der sie * seinerseits aus scan-modes.js liest; diese Ansicht kennt die Funktionen * selbst nicht. * @param {string} label */ setMode(label) { modeButtonEl.textContent = `${label} ändern ⌄`; modeButtonEl.setAttribute('aria-label', `Scan-Funktion wechseln (aktuell: ${label})`); }, /** * Diese eine Schaltflaeche bedient beide Knopfdruck-Funktionen ("Modul * scannen" in Funktion 3 "Text erkennen", "Zählen"/"Nochmal zählen" in * Funktion 4 "Zählen") - nur ihr Wortlaut und ihr Rueckruf unterscheiden * sich, siehe setCaptureLabel() unten und main.js/applyMode(). In * Funktionen mit laufender Codesuche sucht die App ohnehin durchgehend, * ein Knopf dafuer waere wirkungslos. * @param {boolean} visible */ setCaptureVisible(visible) { captureEl.hidden = !visible; }, /** * Setzt den Wortlaut der Aufnahme-Schaltflaeche - siehe setCaptureVisible() * oben fuer den Grund, warum es dafuer nur eine gemeinsame Flaeche statt * einer zweiten daneben gibt. main.js setzt hier "Modul scannen" (Funktion * "Text erkennen"), "Zählen" (Funktion "Zählen", vor der ersten Aufnahme * seit Betreten der Funktion) bzw. "Nochmal zählen" (danach). * @param {string} text */ setCaptureLabel(text) { captureEl.textContent = text; }, /** @param {string} text @param {boolean} warn */ setStatus(text, warn = false) { statusEl.textContent = text; statusEl.classList.toggle('warn', warn); }, openFilePicker() { fileEl.click(); }, /** * Blendet die Ersatzweg-Schaltflaeche "Bild auswaehlen" ein oder aus. * Wird eingeblendet, wenn die Kamera nicht zur Verfuegung steht, und * bleibt dann dauerhaft sichtbar. * @param {boolean} visible */ setFilePickerVisible(visible) { pickFileButtonEl.hidden = !visible; }, /** * Hebt den Zielrahmen hervor, solange die Dauersuche einen Barcode im * Ausschnitt erkennt - schlichte Farbaenderung, keine Animation. Der * Nutzer sieht damit beim Hinhalten sofort, ob es klappt. * @param {boolean} detected */ setFrameDetected(detected) { frameEl.classList.toggle('detected', detected); }, /** * Blendet die grosse Zaehlanzeige ein oder aus (nur in Funktion * "Zählen" sichtbar, siehe applyMode() in main.js). Beim Ausblenden wird * die zuletzt angezeigte Zahl geloescht, damit beim naechsten Wechsel in * diese Funktion nicht kurz eine veraltete Zahl aufblitzt - ebenso die * Diagnosezeile: sie faellt dabei wieder in ihren Ausgangszustand * (verborgen, leer) zurueck, damit sie nach einem Funktionswechsel nicht * ungefragt (weil sie beim letzten Mal offen war) oder mit veralteten * Werten wieder auftaucht. Das gilt sowohl beim Verlassen der Funktion * "Zählen" als auch beim erneuten Betreten - ein stehendes Ergebnis * einer frueheren Aufnahme (andere Teile, andere Stelle) darf nicht * hinueberwirken (siehe main.js/applyMode()). * @param {boolean} visible */ setCountVisible(visible) { countDisplayEl.hidden = !visible; if (!visible) { countNumberEl.textContent = ''; countDiagnosticsEl.textContent = ''; countDiagnosticsVisible = false; countDiagnosticsEl.hidden = true; } }, /** * Zeigt das Ergebnis der zuletzt angetippten Zaehl-Aufnahme (Median der * fuenf Einzelmessungen dieser Aufnahme, siehe count-capture.js und * runCountCapture() in main.js) - gross und aus Armlaenge lesbar. Bleibt * unveraendert stehen, bis der Nutzer erneut auf die Schaltflaeche * "Nochmal zählen" tippt (keine fortlaufende Aktualisierung mehr). Ein * Antippen der Zahl selbst blendet die Diagnosezeile darunter ein oder * aus (siehe Klick-Handler oben). * @param {number} count */ setCount(count) { countNumberEl.textContent = String(count); }, /** * Setzt den Text der kleinen Diagnosezeile unter der grossen Zahl (siehe * main.js fuer den genauen Wortlaut, u. a. die fuenf Einzelmessungen der * zuletzt angetippten Aufnahme) - unabhaengig davon, ob sie gerade * eingeblendet ist. Reine Textaktualisierung, die Sichtbarkeit steuert * ausschliesslich der Klick auf die grosse Zahl (siehe oben). * @param {string} text */ setCountDiagnostics(text) { countDiagnosticsEl.textContent = text; }, }; }