feat: Rot-Dialog fuer mehrdeutige Erkennung
This commit is contained in:
+8
-7
@@ -1,15 +1,16 @@
|
|||||||
import { renderScanView } from './ui/scan-view.js';
|
import { renderScanView } from './ui/scan-view.js';
|
||||||
import { showResult } from './ui/result-overlay.js';
|
import { askForStack } from './ui/ambiguous-dialog.js';
|
||||||
import { startCamera } from './camera.js';
|
import { startCamera } from './camera.js';
|
||||||
|
|
||||||
const app = document.querySelector('#app');
|
const app = document.querySelector('#app');
|
||||||
const view = renderScanView(app, {
|
const view = renderScanView(app, {
|
||||||
onCapture: () =>
|
onCapture: async () => {
|
||||||
showResult(app, {
|
const answer = await askForStack(app, {
|
||||||
stackId: 'B',
|
spec: { capacityGb: 64, rank: null, speed: null, formFactor: null, partNumber: null },
|
||||||
spec: { capacityGb: 64, rank: '4DRx4', speed: 'PC4-2400', formFactor: 'LRDIMM', partNumber: 'M386A8K40BM1-CRC4Y' },
|
candidates: ['A', 'B'],
|
||||||
confidence: 'green',
|
});
|
||||||
}),
|
view.setLast(`Antwort: ${JSON.stringify(answer)}`);
|
||||||
|
},
|
||||||
onUndo: () => view.setLast('zurueckgenommen'),
|
onUndo: () => view.setLast('zurueckgenommen'),
|
||||||
onOpenList: () => {},
|
onOpenList: () => {},
|
||||||
onPickFile: () => {},
|
onPickFile: () => {},
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Fasst die erkannten Merkmale zu einer kurzen Lesehilfe zusammen.
|
||||||
|
* Rein darstellend - keine fachliche Bewertung.
|
||||||
|
*/
|
||||||
|
function describeReading(spec) {
|
||||||
|
const parts = [
|
||||||
|
spec.capacityGb ? `${spec.capacityGb}GB` : null,
|
||||||
|
spec.rank,
|
||||||
|
spec.speed,
|
||||||
|
spec.formFactor,
|
||||||
|
spec.partNumber,
|
||||||
|
].filter(Boolean);
|
||||||
|
return parts.length ? parts.join(' ') : 'nichts Verwertbares';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Haelt den Ablauf an und laesst den Nutzer entscheiden.
|
||||||
|
* Der einzige Punkt der App, an dem eine Eingabe verlangt wird.
|
||||||
|
* Reine Darstellung: kennt weder Stapel-Logik noch Erkennung,
|
||||||
|
* bekommt die zur Wahl stehenden Stapel als Liste herein.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} root
|
||||||
|
* @param {{spec: object, candidates: string[]}} args
|
||||||
|
* @returns {Promise<{action: 'stack', stackId: string}|{action: 'new'}|{action: 'retry'}>}
|
||||||
|
*/
|
||||||
|
export function askForStack(root, { spec, candidates }) {
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'overlay red';
|
||||||
|
overlay.setAttribute('role', 'dialog');
|
||||||
|
overlay.setAttribute('aria-modal', 'true');
|
||||||
|
overlay.setAttribute('aria-labelledby', 'ambiguous-dialog-heading');
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="stack-id" id="ambiguous-dialog-heading" style="font-size:34px">Nicht eindeutig</div>
|
||||||
|
<div class="detail">Gelesen: <span id="read"></span></div>
|
||||||
|
<div class="choices" id="choices"></div>
|
||||||
|
`;
|
||||||
|
// Der gelesene Wert stammt aus dem Etikett (Fremdeingabe) - als Text setzen, nicht als Auszeichnung.
|
||||||
|
overlay.querySelector('#read').textContent = describeReading(spec);
|
||||||
|
|
||||||
|
const choices = overlay.querySelector('#choices');
|
||||||
|
root.appendChild(overlay);
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Schuetzt vor Mehrfachausloesung bei hastigem Mehrfachtippen:
|
||||||
|
// das Versprechen darf genau einmal eingeloest werden.
|
||||||
|
let settled = false;
|
||||||
|
const finish = (result) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
overlay.remove();
|
||||||
|
resolve(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const stackId of candidates) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'action';
|
||||||
|
button.textContent = `Stapel ${stackId}`;
|
||||||
|
button.addEventListener('click', () => finish({ action: 'stack', stackId }));
|
||||||
|
choices.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStackButton = document.createElement('button');
|
||||||
|
newStackButton.className = 'action secondary';
|
||||||
|
newStackButton.textContent = 'neuer Stapel';
|
||||||
|
newStackButton.addEventListener('click', () => finish({ action: 'new' }));
|
||||||
|
choices.appendChild(newStackButton);
|
||||||
|
|
||||||
|
const retryButton = document.createElement('button');
|
||||||
|
retryButton.className = 'action secondary';
|
||||||
|
retryButton.textContent = 'nochmal scannen';
|
||||||
|
retryButton.addEventListener('click', () => finish({ action: 'retry' }));
|
||||||
|
choices.appendChild(retryButton);
|
||||||
|
|
||||||
|
// Auch ohne Stapel-Kandidaten kommt der Nutzer ueber "neuer Stapel"
|
||||||
|
// oder "nochmal scannen" weiter - der Dialog blockiert nie.
|
||||||
|
choices.querySelector('button').focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user