Convert nfc input to a web component

This commit is contained in:
2024-11-14 12:01:57 +01:00
committed by Bartuccio Antoine
parent 346439076e
commit fce6c3d29c
6 changed files with 353 additions and 231 deletions

View File

@ -0,0 +1,39 @@
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components";
@registerComponent("nfc-input")
export class NfcInput extends inheritHtmlElement("input") {
connectedCallback() {
super.connectedCallback();
/* Disable feature if browser is not supported or if not HTTPS */
// biome-ignore lint/correctness/noUndeclaredVariables: browser API
if (typeof NDEFReader === "undefined") {
return;
}
const button = document.createElement("button");
const logo = document.createElement("i");
logo.classList.add("fa-brands", "fa-nfc-symbol");
button.setAttribute("type", "button"); // Prevent form submission on click
button.appendChild(logo);
button.addEventListener("click", async () => {
// biome-ignore lint/correctness/noUndeclaredVariables: browser API
const ndef = new NDEFReader();
await ndef.scan();
ndef.addEventListener("readingerror", () => {
window.alert(gettext("Unsupported NFC card"));
});
// biome-ignore lint/correctness/noUndeclaredVariables: browser API
ndef.addEventListener("reading", (event: NDEFReadingEvent) => {
this.node.value = event.serialNumber.replace(/:/g, "").toUpperCase();
/* Auto submit form, we need another button to not trigger our previously defined click event */
const submit = document.createElement("button");
this.node.appendChild(submit);
submit.click();
submit.remove();
});
});
this.appendChild(button);
}
}

106
core/static/webpack/types/web-nfc.d.ts vendored Normal file
View File

@ -0,0 +1,106 @@
// Type definitions for Web NFC
// Project: https://github.com/w3c/web-nfc
// Definitions by: Takefumi Yoshii <https://github.com/takefumi-yoshii>
// TypeScript Version: 3.9
// This type definitions referenced to WebIDL.
// https://w3c.github.io/web-nfc/#actual-idl-index
// This has been modified to not trigger biome linting
// biome-ignore lint/correctness/noUnusedVariables: this is the official definition
interface Window {
// biome-ignore lint/style/useNamingConvention: this is the official API name
NDEFMessage: NDEFMessage;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
declare class NDEFMessage {
constructor(messageInit: NDEFMessageInit);
records: readonly NDEFRecord[];
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
declare interface NDEFMessageInit {
records: NDEFRecordInit[];
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
declare type NDEFRecordDataSource = string | BufferSource | NDEFMessageInit;
// biome-ignore lint/correctness/noUnusedVariables: this is the official definition
interface Window {
// biome-ignore lint/style/useNamingConvention: this is the official API name
NDEFRecord: NDEFRecord;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
declare class NDEFRecord {
constructor(recordInit: NDEFRecordInit);
readonly recordType: string;
readonly mediaType?: string;
readonly id?: string;
readonly data?: DataView;
readonly encoding?: string;
readonly lang?: string;
toRecords?: () => NDEFRecord[];
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
declare interface NDEFRecordInit {
recordType: string;
mediaType?: string;
id?: string;
encoding?: string;
lang?: string;
data?: NDEFRecordDataSource;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
declare type NDEFMessageSource = string | BufferSource | NDEFMessageInit;
// biome-ignore lint/correctness/noUnusedVariables: this is the official definition
interface Window {
// biome-ignore lint/style/useNamingConvention: this is the official API name
NDEFReader: NDEFReader;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
declare class NDEFReader extends EventTarget {
constructor();
// biome-ignore lint/suspicious/noExplicitAny: who am I to doubt the w3c definitions ?
onreading: (this: this, event: NDEFReadingEvent) => any;
// biome-ignore lint/suspicious/noExplicitAny: who am I to doubt the w3c definitions ?
onreadingerror: (this: this, error: Event) => any;
scan: (options?: NDEFScanOptions) => Promise<void>;
write: (message: NDEFMessageSource, options?: NDEFWriteOptions) => Promise<void>;
makeReadOnly: (options?: NDEFMakeReadOnlyOptions) => Promise<void>;
}
// biome-ignore lint/correctness/noUnusedVariables: this is the official definition
interface Window {
// biome-ignore lint/style/useNamingConvention: this is the official API name
NDEFReadingEvent: NDEFReadingEvent;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
declare class NDEFReadingEvent extends Event {
constructor(type: string, readingEventInitDict: NDEFReadingEventInit);
serialNumber: string;
message: NDEFMessage;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
interface NDEFReadingEventInit extends EventInit {
serialNumber?: string;
message: NDEFMessageInit;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
interface NDEFWriteOptions {
overwrite?: boolean;
signal?: AbortSignal;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
interface NDEFMakeReadOnlyOptions {
signal?: AbortSignal;
}
// biome-ignore lint/style/useNamingConvention: this is the official API name
interface NDEFScanOptions {
signal: AbortSignal;
}

View File

@ -1,33 +1,5 @@
<script-once src="{{ statics.js }}" defer></script-once>
<span>
<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
<!-- NFC icon not available in fontawesome 4.7 -->
<button type="button" id="{{ widget.attrs.id }}_button"><i class="fa-brands fa-nfc-symbol"></i></button>
<nfc-input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}></nfc-input>
</span>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
let button = document.getElementById("{{ widget.attrs.id }}_button");
button.addEventListener("click", async () => {
let input = document.getElementById("{{ widget.attrs.id }}");
const ndef = new NDEFReader();
await ndef.scan();
ndef.addEventListener("readingerror", () => {
alert("{{ translations.unsupported }}")
});
ndef.addEventListener("reading", ({ message, serialNumber }) => {
input.value = serialNumber.replaceAll(":", "").toUpperCase();
/* Auto submit form */
b = document.createElement("button");
input.appendChild(b)
b.click()
b.remove()
});
});
/* Disable feature if browser is not supported or if not HTTPS */
if (typeof NDEFReader === "undefined") {
button.remove();
}
});
</script>

View File

@ -27,6 +27,9 @@ from captcha.fields import CaptchaField
from django import forms
from django.conf import settings
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.staticfiles.management.commands.collectstatic import (
staticfiles_storage,
)
from django.core.exceptions import ValidationError
from django.db import transaction
from django.forms import (
@ -72,7 +75,9 @@ class NFCTextInput(TextInput):
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context["translations"] = {"unsupported": _("Unsupported NFC card")}
context["statics"] = {
"js": staticfiles_storage.url("webpack/core/components/nfc-input-index.ts")
}
return context