From f3c0dee1e7073be82c11e86e92a54934244704d6 Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Thu, 13 Aug 2026 12:51:31 +0200 Subject: [PATCH 1/7] First shot at NFC support --- .../pages/barcode_scan_controller.js | 54 ++++++++++-- assets/controllers/pages/nfc_helpers.js | 29 +++++++ .../controllers/pages/nfc_write_controller.js | 83 +++++++++++++++++++ docs/usage/scanner.md | 16 +++- src/Controller/PartController.php | 3 + .../label_system/scanner/scanner.html.twig | 18 +++- templates/parts/info/_tools.html.twig | 28 ++++++- tests/Controller/PartControllerTest.php | 2 + tests/Controller/ScanControllerTest.php | 9 ++ translations/messages.en.xlf | 45 ++++++++++ 10 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 assets/controllers/pages/nfc_helpers.js create mode 100644 assets/controllers/pages/nfc_write_controller.js diff --git a/assets/controllers/pages/barcode_scan_controller.js b/assets/controllers/pages/barcode_scan_controller.js index bdc9c78c0..7241e00ca 100644 --- a/assets/controllers/pages/barcode_scan_controller.js +++ b/assets/controllers/pages/barcode_scan_controller.js @@ -21,7 +21,7 @@ import {Controller} from "@hotwired/stimulus"; //import * as ZXing from "@zxing/library"; import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode"; -import { generateCsrfToken, generateCsrfHeaders } from "../csrf_protection_controller"; +import {decodeNdefMessage, isWebNfcAvailable, setScanInputAndSubmit} from "./nfc_helpers"; /* stimulusFetch: 'lazy' */ @@ -30,6 +30,9 @@ export default class extends Controller { _submitting = false; _lastDecodedText = ""; _onInfoChange = null; + _nfcAbortController = null; + + static targets = ["reader", "nfcControls", "nfcButton", "nfcStatus"]; connect() { @@ -63,7 +66,7 @@ export default class extends Controller { document.getElementById("scanner-warning")?.classList.remove("d-none"); }); - this._scanner = new Html5QrcodeScanner(this.element.id, { + this._scanner = new Html5QrcodeScanner(this.readerTarget.id, { fps: 10, qrbox: qrboxFunction, // Key change: shrink preview height on mobile @@ -75,6 +78,10 @@ export default class extends Controller { }, false); this._scanner.render(this.onScanSuccess.bind(this)); + + if (isWebNfcAvailable() && this.hasNfcControlsTarget) { + this.nfcControlsTarget.classList.remove("d-none"); + } } disconnect() { @@ -83,6 +90,8 @@ export default class extends Controller { const scanner = this._scanner; this._scanner = null; this._lastDecodedText = ""; + this._nfcAbortController?.abort(); + this._nfcAbortController = null; // Unbind info-mode change handler (always do this, even if scanner is null) const info = document.getElementById("scan_dialog_info_mode"); @@ -114,12 +123,41 @@ export default class extends Controller { // Mark as handled immediately (prevents spam even if callback fires repeatedly) this._lastDecodedText = normalized; - const input = document.getElementById('scan_dialog_input'); - input.value = decodedText; - //Trigger nonprintable char input controller to update the hidden input value - input.dispatchEvent(new Event('input', { bubbles: true })); + setScanInputAndSubmit(decodedText); + } + + async startNfcScan() { + if (!isWebNfcAvailable() || this._nfcAbortController) return; + + this._nfcAbortController = new AbortController(); + this.nfcButtonTarget.disabled = true; + this.nfcStatusTarget.textContent = this.nfcStatusTarget.dataset.waiting; - //Submit form - document.getElementById('scan_dialog_form').requestSubmit(); + try { + const reader = new NDEFReader(); + await reader.scan({signal: this._nfcAbortController.signal}); + reader.addEventListener("readingerror", () => { + this.nfcStatusTarget.textContent = this.nfcStatusTarget.dataset.readError; + }); + reader.addEventListener("reading", ({message}) => { + const value = decodeNdefMessage(message); + if (!value) { + this.nfcStatusTarget.textContent = this.nfcStatusTarget.dataset.unsupportedRecord; + return; + } + + this._nfcAbortController?.abort(); + this._nfcAbortController = null; + setScanInputAndSubmit(value); + }); + } catch (error) { + if (error.name !== "AbortError") { + this.nfcStatusTarget.textContent = error.name === "NotAllowedError" + ? this.nfcStatusTarget.dataset.permissionDenied + : this.nfcStatusTarget.dataset.failed; + } + this._nfcAbortController = null; + this.nfcButtonTarget.disabled = false; + } } } diff --git a/assets/controllers/pages/nfc_helpers.js b/assets/controllers/pages/nfc_helpers.js new file mode 100644 index 000000000..7e2f4d00f --- /dev/null +++ b/assets/controllers/pages/nfc_helpers.js @@ -0,0 +1,29 @@ +export function isWebNfcAvailable() { + return window.isSecureContext && "NDEFReader" in window; +} + +export function decodeNdefMessage(message) { + for (const record of message.records) { + if (!["url", "absolute-url", "text"].includes(record.recordType) || !record.data) continue; + + try { + const value = new TextDecoder(record.encoding || "utf-8").decode(record.data).trim(); + if (value) return value; + } catch (_) { + // Ignore records with unsupported encodings and try the next record. + } + } + + return null; +} + +export function setScanInputAndSubmit(value) { + const input = document.getElementById("scan_dialog_input"); + const form = document.getElementById("scan_dialog_form"); + if (!input || !form) return false; + + input.value = value; + input.dispatchEvent(new Event("input", {bubbles: true})); + form.requestSubmit(); + return true; +} diff --git a/assets/controllers/pages/nfc_write_controller.js b/assets/controllers/pages/nfc_write_controller.js new file mode 100644 index 000000000..b45465e9d --- /dev/null +++ b/assets/controllers/pages/nfc_write_controller.js @@ -0,0 +1,83 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {Controller} from "@hotwired/stimulus"; +import {isWebNfcAvailable} from "./nfc_helpers"; + +/* stimulusFetch: 'lazy' */ + +export default class extends Controller { + static targets = ["button", "overwriteButton", "status"]; + static values = {url: String}; + _abortController = null; + + connect() { + if (isWebNfcAvailable()) this.element.classList.remove("d-none"); + } + + disconnect() { + this._abortController?.abort(); + this._abortController = null; + } + + async write(event) { + await this._write(event.currentTarget.dataset.overwrite === "true"); + } + + async _write(overwrite) { + if (this._abortController) return; + + this._abortController = new AbortController(); + this.buttonTarget.disabled = true; + this.overwriteButtonTarget.disabled = true; + this.overwriteButtonTarget.classList.add("d-none"); + this.statusTarget.className = "small text-muted mt-2"; + this.statusTarget.textContent = this.statusTarget.dataset.waiting; + + try { + const writer = new NDEFReader(); + await writer.write( + {records: [{recordType: "url", data: this.urlValue}]}, + {overwrite, signal: this._abortController.signal}, + ); + this.statusTarget.className = "small text-success mt-2"; + this.statusTarget.textContent = this.statusTarget.dataset.success; + } catch (error) { + await this._showError(error, overwrite); + } finally { + this._abortController = null; + this.buttonTarget.disabled = false; + } + } + + async _showError(error, overwrite) { + this.statusTarget.className = "small text-danger mt-2"; + + if (error.name === "NotAllowedError" && !overwrite) { + try { + const permission = await navigator.permissions.query({name: "nfc"}); + if (permission.state === "denied") { + this.statusTarget.textContent = this.statusTarget.dataset.permissionDenied; + return; + } + } catch (_) { + // The NFC permission descriptor is not exposed by every supporting browser. + } + + this.statusTarget.textContent = this.statusTarget.dataset.overwriteWarning; + this.overwriteButtonTarget.classList.remove("d-none"); + this.overwriteButtonTarget.disabled = false; + return; + } + + const messageKey = { + NotAllowedError: "permissionDenied", + NotSupportedError: "unsupportedTag", + NetworkError: "writeFailed", + AbortError: "cancelled", + }[error.name] || "writeFailed"; + this.statusTarget.textContent = this.statusTarget.dataset[messageKey]; + } +} diff --git a/docs/usage/scanner.md b/docs/usage/scanner.md index 47b3feff2..4774abf1b 100644 --- a/docs/usage/scanner.md +++ b/docs/usage/scanner.md @@ -1,10 +1,10 @@ --- -title: Barcode Scanner +title: Barcode and NFC Scanner layout: default parent: Usage --- -# Barcode scanner +# Barcode and NFC scanner When the user has the correct permission there will be a barcode scanner button in the navbar. On this page you can either input a barcode code by hand, use an external barcode scanner, or use your devices camera to @@ -49,3 +49,15 @@ of the scanned barcode, Part-DB will automatically scan the barcode that comes a and redirects you to the corresponding page. This allows you to quickly scan a barcode from anywhere in Part-DB without the need to first open the scanner page. If an input field is focused, the barcode will be entered into the field as usual and no redirection will happen. + +## Using NFC stickers + +On devices with Web NFC support, the scanner page also shows a **Scan NFC tag** button. NFC access requires an NFC-capable +Android device, a supporting browser, HTTPS, and permission from the user. Part-DB reads URL and text records from NDEF tags +and processes their content in exactly the same way as a camera or external barcode scan. Camera and manual input remain +available on devices without Web NFC. + +Users with permission to create labels can enroll a sticker for a saved part from the part's **Tools** tab. **Write NFC tag** +writes the same Part-DB URL used by an internal QR label. The first write protects existing tag contents; if the tag is already +programmed, Part-DB asks for confirmation and requires the tag to be tapped again before overwriting it. Tags remain writable, +and Part-DB does not store their hardware identifiers or enrollment state. diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index c4c0e5260..0ed996c3b 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -42,6 +42,7 @@ use App\Services\EntityMergers\Mergers\PartMerger; use App\Services\InfoProviderSystem\PartInfoRetriever; use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; +use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; use App\Services\LogSystem\EventCommentHelper; use App\Services\LogSystem\HistoryHelper; use App\Services\LogSystem\TimeTravel; @@ -98,6 +99,7 @@ public function show( DataTableFactory $dataTable, ParameterExtractor $parameterExtractor, PartLotWithdrawAddHelper $withdrawAddHelper, + BarcodeContentGenerator $barcodeContentGenerator, ?string $timestamp = null ): Response { $this->denyAccessUnlessGranted('read', $part); @@ -153,6 +155,7 @@ public function show( 'withdraw_add_helper' => $withdrawAddHelper, 'highlightLotId' => $request->query->getInt('highlightLot', 0), 'add_lot_form' => $addLotForm, + 'nfc_url' => $timeTravel_timestamp === null ? $barcodeContentGenerator->getURLContent($part) : null, ] ); } diff --git a/templates/label_system/scanner/scanner.html.twig b/templates/label_system/scanner/scanner.html.twig index 63654a85c..99c13cbc0 100644 --- a/templates/label_system/scanner/scanner.html.twig +++ b/templates/label_system/scanner/scanner.html.twig @@ -11,7 +11,23 @@
-
+
+
+ +
+
+
+
diff --git a/templates/parts/info/_tools.html.twig b/templates/parts/info/_tools.html.twig index 455d51b7d..3488f03eb 100644 --- a/templates/parts/info/_tools.html.twig +++ b/templates/parts/info/_tools.html.twig @@ -68,8 +68,34 @@ {{ dropdown.profile_dropdown('part', part.id) }} +{% if nfc_url is not null and is_granted('@labels.create_labels') %} +
+ + +
+
+{% endif %} + {% trans %}part.info.add_part_to_project{% endtrans %} - \ No newline at end of file + diff --git a/tests/Controller/PartControllerTest.php b/tests/Controller/PartControllerTest.php index c15bdd518..673b99dc3 100644 --- a/tests/Controller/PartControllerTest.php +++ b/tests/Controller/PartControllerTest.php @@ -57,6 +57,8 @@ public function testShowPart(): void $this->assertResponseStatusCodeSame(Response::HTTP_OK); $this->assertResponseStatusCodeSame(Response::HTTP_OK); + $this->assertSelectorExists('[data-controller~="pages--nfc-write"]'); + $this->assertSelectorExists('[data-pages--nfc-write-url-value$="/scan/part/' . $part->getId() . '"]'); } public function testShowPartWithTimestamp(): void diff --git a/tests/Controller/ScanControllerTest.php b/tests/Controller/ScanControllerTest.php index b504cd292..9e3466fef 100644 --- a/tests/Controller/ScanControllerTest.php +++ b/tests/Controller/ScanControllerTest.php @@ -51,4 +51,13 @@ public function testScanQRCode(): void $this->client->request('GET', '/scan/part/1'); $this->assertResponseRedirects('/en/part/1'); } + + public function testScanDialogContainsProgressiveNfcControls(): void + { + $this->client->request('GET', '/en/scan'); + + $this->assertResponseIsSuccessful(); + $this->assertSelectorExists('[data-pages--barcode-scan-target~="nfcControls"]'); + $this->assertSelectorTextContains('[data-pages--barcode-scan-target~="nfcControls"]', 'Scan NFC tag'); + } } diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index fa3998b54..9cb8386dd 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -14261,5 +14261,50 @@ Buerklin-API Authentication server: OAuth2 client updated successfully! + + label_scanner.nfc.scanScan NFC tag + + + label_scanner.nfc.waitingHold an NFC tag near your device… + + + label_scanner.nfc.read_errorThe NFC tag could not be read. Try again. + + + label_scanner.nfc.unsupported_recordThe tag contains no supported URL or text record. + + + label_scanner.nfc.permission_deniedNFC permission was denied. + + + label_scanner.nfc.failedNFC scanning could not be started. Check that NFC is enabled. + + + nfc.write.buttonWrite NFC tag + + + nfc.write.overwrite_buttonConfirm overwrite + + + nfc.write.waitingHold the sticker near your device until writing completes… + + + nfc.write.successThe NFC sticker was enrolled successfully. + + + nfc.write.overwrite_warningThis tag already contains data. Confirm overwrite, then tap the tag again. + + + nfc.write.permission_deniedNFC permission was denied. + + + nfc.write.unsupported_tagThis tag is read-only or does not support writable NDEF data. + + + nfc.write.failedThe tag could not be written. Check its capacity and try again. + + + nfc.write.cancelledNFC writing was cancelled. + From dcdefdded58ae35d47f5013e249452624bf9883f Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Wed, 19 Aug 2026 09:32:49 +0000 Subject: [PATCH 2/7] Add translation keys for NFC --- templates/parts/info/_tools.html.twig | 18 ++-- translations/messages.en.xlf | 135 +++++++++++++++++--------- 2 files changed, 99 insertions(+), 54 deletions(-) diff --git a/templates/parts/info/_tools.html.twig b/templates/parts/info/_tools.html.twig index 3488f03eb..702cdf3a4 100644 --- a/templates/parts/info/_tools.html.twig +++ b/templates/parts/info/_tools.html.twig @@ -75,22 +75,22 @@ {{ stimulus_target('pages/nfc_write', 'button') }} {{ stimulus_action('pages/nfc_write', 'write') }}> - {% trans %}nfc.write.button{% endtrans %} + {% trans %}part.info.nfc.write.button{% endtrans %}
+ data-waiting="{{ 'part.info.nfc.write.waiting'|trans }}" + data-success="{{ 'part.info.nfc.write.success'|trans }}" + data-overwrite-warning="{{ 'part.info.nfc.write.overwrite_warning'|trans }}" + data-permission-denied="{{ 'part.info.nfc.write.permission_denied'|trans }}" + data-unsupported-tag="{{ 'part.info.nfc.write.unsupported_tag'|trans }}" + data-write-failed="{{ 'part.info.nfc.write.failed'|trans }}" + data-cancelled="{{ 'part.info.nfc.write.cancelled'|trans }}"> {% endif %} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 9cb8386dd..d832ff9e8 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -921,6 +921,42 @@ Sub elements will be moved upwards. Select source + + + label_scanner.nfc.scan + Scan NFC tag + + + + + label_scanner.nfc.waiting + Hold an NFC tag near your device… + + + + + label_scanner.nfc.read_error + The NFC tag could not be read. Try again. + + + + + label_scanner.nfc.unsupported_record + The tag contains no supported URL or text record. + + + + + label_scanner.nfc.permission_denied + NFC permission was denied. + + + + + label_scanner.nfc.failed + NFC scanning could not be started. Check that NFC is enabled. + + log.list.title @@ -7140,6 +7176,60 @@ Element 1 -> Element 1.2 Add this [part] to a [project] + + + part.info.nfc.write.button + Write NFC tag + + + + + part.info.nfc.write.overwrite_button + Confirm overwrite + + + + + part.info.nfc.write.waiting + Hold the sticker near your device until writing completes… + + + + + part.info.nfc.write.success + The NFC sticker was enrolled successfully. + + + + + part.info.nfc.write.overwrite_warning + This tag already contains data. Confirm overwrite, then tap the tag again. + + + + + part.info.nfc.write.permission_denied + NFC permission was denied. + + + + + part.info.nfc.write.unsupported_tag + This tag is read-only or does not support writable NDEF data. + + + + + part.info.nfc.write.failed + The tag could not be written. Check its capacity and try again. + + + + + part.info.nfc.write.cancelled + NFC writing was cancelled. + + project_bom_entry.label @@ -14261,50 +14351,5 @@ Buerklin-API Authentication server: OAuth2 client updated successfully! - - label_scanner.nfc.scanScan NFC tag - - - label_scanner.nfc.waitingHold an NFC tag near your device… - - - label_scanner.nfc.read_errorThe NFC tag could not be read. Try again. - - - label_scanner.nfc.unsupported_recordThe tag contains no supported URL or text record. - - - label_scanner.nfc.permission_deniedNFC permission was denied. - - - label_scanner.nfc.failedNFC scanning could not be started. Check that NFC is enabled. - - - nfc.write.buttonWrite NFC tag - - - nfc.write.overwrite_buttonConfirm overwrite - - - nfc.write.waitingHold the sticker near your device until writing completes… - - - nfc.write.successThe NFC sticker was enrolled successfully. - - - nfc.write.overwrite_warningThis tag already contains data. Confirm overwrite, then tap the tag again. - - - nfc.write.permission_deniedNFC permission was denied. - - - nfc.write.unsupported_tagThis tag is read-only or does not support writable NDEF data. - - - nfc.write.failedThe tag could not be written. Check its capacity and try again. - - - nfc.write.cancelledNFC writing was cancelled. - From 1a38ddaf32111765066f215b3193bdf198f8a3a2 Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Wed, 19 Aug 2026 09:40:20 +0000 Subject: [PATCH 3/7] Enforce @labels.create_labels permission --- src/Controller/PartController.php | 4 +++- templates/parts/info/_tools.html.twig | 2 +- tests/Controller/PartControllerTest.php | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 0ed996c3b..5044f512a 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -155,7 +155,9 @@ public function show( 'withdraw_add_helper' => $withdrawAddHelper, 'highlightLotId' => $request->query->getInt('highlightLot', 0), 'add_lot_form' => $addLotForm, - 'nfc_url' => $timeTravel_timestamp === null ? $barcodeContentGenerator->getURLContent($part) : null, + 'nfc_url' => $timeTravel_timestamp === null && $this->isGranted('@labels.create_labels') + ? $barcodeContentGenerator->getURLContent($part) + : null, ] ); } diff --git a/templates/parts/info/_tools.html.twig b/templates/parts/info/_tools.html.twig index 702cdf3a4..3fdec2fe4 100644 --- a/templates/parts/info/_tools.html.twig +++ b/templates/parts/info/_tools.html.twig @@ -68,7 +68,7 @@ {{ dropdown.profile_dropdown('part', part.id) }} -{% if nfc_url is not null and is_granted('@labels.create_labels') %} +{% if nfc_url is not null %}
-
{% trans %}part.info.nfc.write.overwrite_button{% endtrans %} -
Date: Wed, 19 Aug 2026 09:46:40 +0000 Subject: [PATCH 5/7] Add license text --- assets/controllers/pages/nfc_helpers.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/assets/controllers/pages/nfc_helpers.js b/assets/controllers/pages/nfc_helpers.js index 7e2f4d00f..04cb437c8 100644 --- a/assets/controllers/pages/nfc_helpers.js +++ b/assets/controllers/pages/nfc_helpers.js @@ -1,3 +1,22 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + export function isWebNfcAvailable() { return window.isSecureContext && "NDEFReader" in window; } From 5cbb91660936740eaceb8807c8f06392e0181a19 Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Wed, 19 Aug 2026 10:23:22 +0000 Subject: [PATCH 6/7] Handle ambiguous NFC write permission errors --- assets/controllers/pages/nfc_write_controller.js | 6 +++--- templates/parts/info/_tools.html.twig | 3 ++- translations/messages.en.xlf | 14 ++++++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/assets/controllers/pages/nfc_write_controller.js b/assets/controllers/pages/nfc_write_controller.js index b45465e9d..bd5ad5033 100644 --- a/assets/controllers/pages/nfc_write_controller.js +++ b/assets/controllers/pages/nfc_write_controller.js @@ -57,7 +57,7 @@ export default class extends Controller { if (error.name === "NotAllowedError" && !overwrite) { try { - const permission = await navigator.permissions.query({name: "nfc"}); + const permission = await navigator.permissions?.query({name: "nfc"}); if (permission.state === "denied") { this.statusTarget.textContent = this.statusTarget.dataset.permissionDenied; return; @@ -66,14 +66,14 @@ export default class extends Controller { // The NFC permission descriptor is not exposed by every supporting browser. } - this.statusTarget.textContent = this.statusTarget.dataset.overwriteWarning; + this.statusTarget.textContent = this.statusTarget.dataset.overwriteConfirmation; this.overwriteButtonTarget.classList.remove("d-none"); this.overwriteButtonTarget.disabled = false; return; } const messageKey = { - NotAllowedError: "permissionDenied", + NotAllowedError: "notAllowed", NotSupportedError: "unsupportedTag", NetworkError: "writeFailed", AbortError: "cancelled", diff --git a/templates/parts/info/_tools.html.twig b/templates/parts/info/_tools.html.twig index 87655e01b..d38b214dc 100644 --- a/templates/parts/info/_tools.html.twig +++ b/templates/parts/info/_tools.html.twig @@ -87,8 +87,9 @@ {{ stimulus_target('pages/nfc_write', 'status') }} data-waiting="{{ 'part.info.nfc.write.waiting'|trans }}" data-success="{{ 'part.info.nfc.write.success'|trans }}" - data-overwrite-warning="{{ 'part.info.nfc.write.overwrite_warning'|trans }}" + data-overwrite-confirmation="{{ 'part.info.nfc.write.overwrite_confirmation'|trans }}" data-permission-denied="{{ 'part.info.nfc.write.permission_denied'|trans }}" + data-not-allowed="{{ 'part.info.nfc.write.not_allowed'|trans }}" data-unsupported-tag="{{ 'part.info.nfc.write.unsupported_tag'|trans }}" data-write-failed="{{ 'part.info.nfc.write.failed'|trans }}" data-cancelled="{{ 'part.info.nfc.write.cancelled'|trans }}">
diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index d832ff9e8..b5f20aada 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -7185,7 +7185,7 @@ Element 1 -> Element 1.2 part.info.nfc.write.overwrite_button - Confirm overwrite + Retry and allow overwrite @@ -7200,10 +7200,10 @@ Element 1 -> Element 1.2 The NFC sticker was enrolled successfully. - + - part.info.nfc.write.overwrite_warning - This tag already contains data. Confirm overwrite, then tap the tag again. + part.info.nfc.write.overwrite_confirmation + Writing without overwriting was not allowed. The tag may already contain data. Continuing may replace its contents. @@ -7212,6 +7212,12 @@ Element 1 -> Element 1.2 NFC permission was denied. + + + part.info.nfc.write.not_allowed + NFC writing was not allowed. Check browser permission and whether the tag is writable. + + part.info.nfc.write.unsupported_tag From b83bbfec3a6d7a4bdabb823411eb73c77585e4b4 Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Wed, 19 Aug 2026 10:54:02 +0000 Subject: [PATCH 7/7] Bring back unused import due to side effects --- assets/controllers/pages/barcode_scan_controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/controllers/pages/barcode_scan_controller.js b/assets/controllers/pages/barcode_scan_controller.js index 7241e00ca..0107ea843 100644 --- a/assets/controllers/pages/barcode_scan_controller.js +++ b/assets/controllers/pages/barcode_scan_controller.js @@ -21,6 +21,7 @@ import {Controller} from "@hotwired/stimulus"; //import * as ZXing from "@zxing/library"; import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode"; +import { generateCsrfToken, generateCsrfHeaders } from "../csrf_protection_controller"; import {decodeNdefMessage, isWebNfcAvailable, setScanInputAndSubmit} from "./nfc_helpers"; /* stimulusFetch: 'lazy' */