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 { showResult } from './ui/result-overlay.js';
|
||||
import { askForStack } from './ui/ambiguous-dialog.js';
|
||||
import { startCamera } from './camera.js';
|
||||
|
||||
const app = document.querySelector('#app');
|
||||
const view = renderScanView(app, {
|
||||
onCapture: () =>
|
||||
showResult(app, {
|
||||
stackId: 'B',
|
||||
spec: { capacityGb: 64, rank: '4DRx4', speed: 'PC4-2400', formFactor: 'LRDIMM', partNumber: 'M386A8K40BM1-CRC4Y' },
|
||||
confidence: 'green',
|
||||
}),
|
||||
onCapture: async () => {
|
||||
const answer = await askForStack(app, {
|
||||
spec: { capacityGb: 64, rank: null, speed: null, formFactor: null, partNumber: null },
|
||||
candidates: ['A', 'B'],
|
||||
});
|
||||
view.setLast(`Antwort: ${JSON.stringify(answer)}`);
|
||||
},
|
||||
onUndo: () => view.setLast('zurueckgenommen'),
|
||||
onOpenList: () => {},
|
||||
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