Tap the card, and that is the whole enrolment
The reader already filled the number in; adding it still meant walking back to the keyboard and pressing Add. That walk was the entire cost of the two-step version, and the card is in your hand at the charger. One press now opens the reader and writes whatever is held against it. The name is the server's own convention — RFID and the card's last four digits — because the name is left empty and rfidSaveCard fills it in. Deriving the same pattern here would have been a second place for it to drift; every card already on the account reads that way. A name typed into the box still wins, since throwing away what somebody typed is worse than the convention. The reader is a value now rather than a side effect on the form, and the write is shared with the typed path so the two cannot judge their answers differently. The caller holds the busy flag across both halves: nothing re-enables in the gap, where a second press would have opened a second twenty-second window. The number lands in the box on the way past, so a write that fails leaves something to retry rather than a card nobody can name. The old button stays for the times the number is wanted without the card being added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e190364c77
commit
7b339d3dac
@@ -72,6 +72,8 @@
|
||||
"tap": "Hold kortet mod laderen",
|
||||
"tapping": "Hold kortet mod læseren… {n}s",
|
||||
"tapHint": "Læseren er åben. Hold kortet mod laderen.",
|
||||
"tapSave": "Hold kortet mod laderen, og tilføj det",
|
||||
"tapSaveHint": "Tilføjer kortet, så snart det holdes mod læseren, med navnet RFID og kortets sidste fire cifre.",
|
||||
"tapNone": "Der blev ikke holdt et kort mod læseren, før den lukkede.",
|
||||
"addTitle": "Tilføj et kort",
|
||||
"remove": "Fjern",
|
||||
|
||||
@@ -353,6 +353,8 @@
|
||||
"tap": "Tap card at the charger",
|
||||
"tapping": "Hold the card against the reader… {n}s",
|
||||
"tapHint": "The reader is open. Hold the card against the charger.",
|
||||
"tapSave": "Tap card and add it",
|
||||
"tapSaveHint": "Adds the card as soon as it is tapped, named RFID and its last four digits.",
|
||||
"tapNone": "No card was tapped before the reader closed.",
|
||||
"addTitle": "Add a card",
|
||||
"remove": "Remove",
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
"tap": "Przyłóż kartę do ładowarki",
|
||||
"tapping": "Przytrzymaj kartę przy czytniku… {n}s",
|
||||
"tapHint": "Czytnik jest otwarty. Przytrzymaj kartę przy ładowarce.",
|
||||
"tapSave": "Przyłóż kartę i dodaj ją",
|
||||
"tapSaveHint": "Dodaje kartę zaraz po przyłożeniu, pod nazwą RFID i cztery ostatnie znaki numeru.",
|
||||
"tapNone": "Nie przyłożono karty, zanim czytnik się zamknął.",
|
||||
"addTitle": "Dodaj kartę",
|
||||
"remove": "Usuń",
|
||||
|
||||
@@ -1501,14 +1501,7 @@ async function addRfidCard() {
|
||||
rfidBusy.value = "new";
|
||||
rfidError.value = "";
|
||||
try {
|
||||
const res = await api.saveAnkerRfidCard(sn, number, newCardName.value.trim());
|
||||
rfidWritten.value = { ...rfidWritten.value, [sn]: res?.cards || [] };
|
||||
if (res?.present === false) {
|
||||
rfidError.value = t("charging.rfid.notAdded");
|
||||
} else {
|
||||
newCardNumber.value = "";
|
||||
newCardName.value = "";
|
||||
}
|
||||
await writeCard(sn, number);
|
||||
} catch (e) {
|
||||
rfidError.value = e.message;
|
||||
} finally {
|
||||
@@ -1516,6 +1509,44 @@ async function addRfidCard() {
|
||||
}
|
||||
}
|
||||
|
||||
// Writing one card and then reading the list back, which is the only thing that
|
||||
// says whether the write landed. Shared by both ways of adding one, so the tap
|
||||
// and the typed number cannot end up judging their answers differently.
|
||||
//
|
||||
// The name is whatever is in the box, and an empty box is not a missing name: it
|
||||
// is the server's own convention — "RFID" and the card's last four digits — and
|
||||
// leaving it to the server is what keeps the two ways of adding a card from
|
||||
// drifting into two naming conventions.
|
||||
async function writeCard(sn, number) {
|
||||
const res = await api.saveAnkerRfidCard(sn, number, newCardName.value.trim());
|
||||
rfidWritten.value = { ...rfidWritten.value, [sn]: res?.cards || [] };
|
||||
if (res?.present === false) {
|
||||
rfidError.value = t("charging.rfid.notAdded");
|
||||
return false;
|
||||
}
|
||||
newCardNumber.value = "";
|
||||
newCardName.value = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
// The reader's own twenty seconds, as a value rather than as a side effect on
|
||||
// the form: one caller wants the number in the box, the other wants to write it.
|
||||
// The caller owns rfidBusy, so the tap-and-save button can hold it across the
|
||||
// write that follows and nothing re-enables between the two.
|
||||
async function readCardAtCharger(sn) {
|
||||
rfidCountdown.value = 20;
|
||||
const tick = setInterval(() => {
|
||||
rfidCountdown.value = Math.max(0, rfidCountdown.value - 1);
|
||||
}, 1000);
|
||||
try {
|
||||
const res = await api.scanAnkerRfidCard(sn);
|
||||
return res?.tapped && res.card ? res.card : "";
|
||||
} finally {
|
||||
clearInterval(tick);
|
||||
rfidCountdown.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Asking the charger to read a card, which is what the Anker app's second way of
|
||||
// adding one does: the reader opens for twenty seconds, and whatever is held
|
||||
// against it comes back as a number. Nothing is written by this — the card lands
|
||||
@@ -1525,22 +1556,42 @@ async function scanRfidCard() {
|
||||
if (!sn || rfidBusy.value) return;
|
||||
rfidBusy.value = "scan";
|
||||
rfidError.value = "";
|
||||
rfidCountdown.value = 20;
|
||||
const tick = setInterval(() => {
|
||||
rfidCountdown.value = Math.max(0, rfidCountdown.value - 1);
|
||||
}, 1000);
|
||||
try {
|
||||
const res = await api.scanAnkerRfidCard(sn);
|
||||
if (res?.tapped && res.card) {
|
||||
newCardNumber.value = res.card;
|
||||
} else {
|
||||
const card = await readCardAtCharger(sn);
|
||||
if (card) newCardNumber.value = card;
|
||||
else rfidError.value = t("charging.rfid.tapNone");
|
||||
} catch (e) {
|
||||
rfidError.value = e.message;
|
||||
} finally {
|
||||
rfidBusy.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
// The same tap, carried through to the end: the reader opens, and whatever is
|
||||
// held against it is written without a second press. Enrolling a card happens at
|
||||
// the charger with the card in your hand — the walk back to the keyboard to
|
||||
// press Add was the whole cost of the two-step version.
|
||||
//
|
||||
// Nothing new is written by this that the two buttons above could not write
|
||||
// between them; it is the same read and the same write, with nothing to do in
|
||||
// between. The number lands in the box on the way past, so a write that fails
|
||||
// leaves something to look at and retry rather than a card nobody can name.
|
||||
async function tapAndSaveRfidCard() {
|
||||
const sn = detailSn.value;
|
||||
if (!sn || rfidBusy.value) return;
|
||||
rfidBusy.value = "tapSave";
|
||||
rfidError.value = "";
|
||||
try {
|
||||
const card = await readCardAtCharger(sn);
|
||||
if (!card) {
|
||||
rfidError.value = t("charging.rfid.tapNone");
|
||||
return;
|
||||
}
|
||||
newCardNumber.value = card;
|
||||
await writeCard(sn, card);
|
||||
} catch (e) {
|
||||
rfidError.value = e.message;
|
||||
} finally {
|
||||
clearInterval(tick);
|
||||
rfidCountdown.value = 0;
|
||||
rfidBusy.value = "";
|
||||
}
|
||||
}
|
||||
@@ -2271,7 +2322,22 @@ onMounted(async () => {
|
||||
>
|
||||
{{ rfidBusy === "scan" ? t("charging.rfid.tapping", { n: rfidCountdown }) : t("charging.rfid.tap") }}
|
||||
</button>
|
||||
<p v-if="rfidBusy === 'scan'" class="mt-1 text-[11px] text-muted">{{ t("charging.rfid.tapHint") }}</p>
|
||||
<!-- The same tap without the second press. Primary, because it is
|
||||
the one somebody standing at the charger wants; the button
|
||||
above stays for the times the number is wanted without the
|
||||
card being added. -->
|
||||
<button
|
||||
type="button"
|
||||
class="dh-btn dh-btn-primary mt-2 w-full text-xs"
|
||||
:disabled="rfidBusy !== ''"
|
||||
@click="tapAndSaveRfidCard"
|
||||
>
|
||||
{{ rfidBusy === "tapSave" ? t("charging.rfid.tapping", { n: rfidCountdown }) : t("charging.rfid.tapSave") }}
|
||||
</button>
|
||||
<p v-if="rfidBusy === 'scan' || rfidBusy === 'tapSave'" class="mt-1 text-[11px] text-muted">
|
||||
{{ t("charging.rfid.tapHint") }}
|
||||
</p>
|
||||
<p v-else class="mt-1 text-[11px] text-muted">{{ t("charging.rfid.tapSaveHint") }}</p>
|
||||
</form>
|
||||
<p class="mt-3 text-[11px] text-muted">{{ t("charging.rfid.inferred") }}</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user