qrcode: move data items to a new element (#86853)
This commit is contained in:
parent
8e7056f4de
commit
631fd54f30
|
@ -91,7 +91,7 @@ qrcode-reader {
|
|||
}
|
||||
|
||||
&.invalid-qrcode {
|
||||
qrcode-reader-validity {
|
||||
qrcode-reader-validity, qrcode-reader-data {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -116,28 +116,6 @@ qrcode-reader {
|
|||
margin: 10px;
|
||||
}
|
||||
|
||||
&--data-items, &--metadata-items {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
margin-top: 5px;
|
||||
padding-top: 5px;
|
||||
border-top: 1px solid var(--gray-light);
|
||||
&.error {
|
||||
color: #{$red};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&--data-item-label {
|
||||
font-weight: bold;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
&--data-item-value {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&--close-popup-button {
|
||||
margin: 5px 10px 10px 10px;
|
||||
padding: 5px;
|
||||
|
@ -174,6 +152,7 @@ qrcode-reader {
|
|||
--background-color: #{$green-light};
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&--header {
|
||||
padding: 0 5px;
|
||||
|
@ -191,10 +170,19 @@ qrcode-reader {
|
|||
fill: var(--color);
|
||||
}
|
||||
|
||||
&--ok-icon, &--error-icon {
|
||||
&--spinner, &--ok-icon, &--error-icon {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
}
|
||||
|
||||
&--spinner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&--spinner-animation {
|
||||
transform-origin:center;
|
||||
animation:spinner-keyframes .75s infinite linear;
|
||||
}
|
||||
|
||||
&--error-icon {
|
||||
display: none;
|
||||
}
|
||||
|
@ -221,6 +209,7 @@ qrcode-reader {
|
|||
.qrcode-reader-section--errors {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.qrcode-reader-section--ok-icon {
|
||||
display: none;
|
||||
}
|
||||
|
@ -229,6 +218,23 @@ qrcode-reader {
|
|||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
--color: black;
|
||||
--background-color: #{$gray-light};
|
||||
|
||||
.qrcode-reader-section--ok-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.qrcode-reader-section--error-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.qrcode-reader-section--spinner {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qrcode-reader-validity {
|
||||
|
@ -243,3 +249,22 @@ qrcode-reader-validity {
|
|||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
qrcode-reader-data {
|
||||
.qrcode-reader-section--content {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.qrcode-reader-data {
|
||||
&--item-label {
|
||||
font-weight: bold;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
&--item-value {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,22 +47,16 @@ const readerTemplate = template(`
|
|||
<div class="qrcode-reader--popup closed">
|
||||
<div class="qrcode-reader--popup-errors"></div>
|
||||
<qrcode-reader-validity></qrcode-reader-validity>
|
||||
<qrcode-reader-data></qrcode-reader-data>
|
||||
<div class="qrcode-reader--popup-content"></div>
|
||||
<button class="qrcode-reader--close-popup-button">${translate('close')}</button>
|
||||
</div>
|
||||
`)
|
||||
|
||||
const dataTemplate = template(`
|
||||
<div class="qrcode-reader--data-items"></div>
|
||||
<span class="qrcode-reader--metadata-items"></span>
|
||||
<span class="qrcode-reader--tally-status"></span>
|
||||
`)
|
||||
|
||||
const dataItemTemplate = template(`
|
||||
<span class="qrcode-reader--data-item-label">{label} : </span>
|
||||
<span class="qrcode-reader--data-item-value">{value}</span>
|
||||
`)
|
||||
|
||||
const spinnerTemplate = template(`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="qrcode-reader--spinner">
|
||||
<path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/>
|
||||
|
@ -146,6 +140,7 @@ class QRCodeReader extends window.HTMLElement {
|
|||
#errors
|
||||
#currentCertificate
|
||||
#validity
|
||||
#data
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
|
@ -161,6 +156,7 @@ class QRCodeReader extends window.HTMLElement {
|
|||
this.#popupContent = this.querySelector('.qrcode-reader--popup-content')
|
||||
this.#errors = this.querySelector('.qrcode-reader--popup-errors')
|
||||
this.#validity = this.querySelector('qrcode-reader-validity')
|
||||
this.#data = this.querySelector('qrcode-reader-data')
|
||||
|
||||
const closePopupButton = this.querySelector('.qrcode-reader--close-popup-button')
|
||||
closePopupButton.addEventListener('click', () => {
|
||||
|
@ -251,22 +247,15 @@ class QRCodeReader extends window.HTMLElement {
|
|||
|
||||
this.#validity.refresh(validityStart, validityEnd)
|
||||
|
||||
const dataElement = dataTemplate.cloneNode(true)
|
||||
|
||||
const dataItems = dataElement.content.querySelector('.qrcode-reader--data-items')
|
||||
const metadataItems = dataElement.content.querySelector('.qrcode-reader--metadata-items')
|
||||
const dataElement = dataTemplate.cloneNode(true)
|
||||
const tallyStatus = dataElement.content.querySelector('.qrcode-reader--tally-status')
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const dataItem = dataItemTemplate.cloneNode(true)
|
||||
dataItem.innerHTML = dataItem.innerHTML.replace('{label}', key).replace('{value}', value)
|
||||
dataItems.append(dataItem.content)
|
||||
}
|
||||
|
||||
this.#popupContent.append(dataElement.content)
|
||||
|
||||
await Promise.all([
|
||||
this.#showMetadata(metadataItems, certificateUUID),
|
||||
this.#data.refresh(data, this.getAttribute('metadata-url'), certificateUUID),
|
||||
this.#showTally(tallyStatus, certificateUUID)
|
||||
])
|
||||
|
||||
|
@ -275,56 +264,6 @@ class QRCodeReader extends window.HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
async #showMetadata(itemsElement, certificateUUID) {
|
||||
if(this.getAttribute('metadata-url') === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const spinner = spinnerTemplate.cloneNode(true)
|
||||
itemsElement.appendChild(spinner.content)
|
||||
|
||||
const metadata = await this.#fetchMetadata(itemsElement, certificateUUID)
|
||||
if(!metadata) {
|
||||
return
|
||||
}
|
||||
|
||||
itemsElement.innerText = ""
|
||||
|
||||
for (const [key, value] of Object.entries(metadata)) {
|
||||
const dataItem = dataItemTemplate.cloneNode(true)
|
||||
dataItem.innerHTML = dataItem.innerHTML.replace('{label}', key).replace('{value}', value)
|
||||
itemsElement.append(dataItem.content)
|
||||
}
|
||||
}
|
||||
|
||||
async #fetchMetadata(itemsElement, certificateUUID) {
|
||||
const url = `${this.getAttribute('metadata-url')}?certificate=${certificateUUID}`
|
||||
let metadata = undefined
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
const content = await response.json()
|
||||
|
||||
if(response.ok && content.err == 0) {
|
||||
metadata = content.data
|
||||
}
|
||||
else {
|
||||
itemsElement.classList.add('error')
|
||||
itemsElement.innerHTML = `
|
||||
<p>${translate('metadata_api_error')}</p>
|
||||
`
|
||||
}
|
||||
} catch(err) {
|
||||
if(err.name == 'NetworkError') {
|
||||
itemsElement.classList.add('error')
|
||||
itemsElement.innerHTML = `<p>${translate('metadata_network_error')}</p>`
|
||||
}
|
||||
else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
async #showTally(statusElement, certificateUUID) {
|
||||
if(this.getAttribute('tally-url') === null) {
|
||||
|
@ -405,6 +344,17 @@ window.customElements.define('qrcode-reader', QRCodeReader)
|
|||
const sectionTemplate = template(`
|
||||
<div class="qrcode-reader-section">
|
||||
<div class="qrcode-reader-section--header">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="16"
|
||||
height="16"
|
||||
class="qrcode-reader-section--spinner">
|
||||
<path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/>
|
||||
<path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="qrcode-reader-section--spinner-animation"/>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
|
@ -448,6 +398,8 @@ class QRCodeReaderSection extends HTMLElement {
|
|||
this.#errors = this.querySelector('.qrcode-reader-section--errors')
|
||||
}
|
||||
|
||||
get contentElement() { return this.#content }
|
||||
|
||||
setTitle(title) {
|
||||
this.#title.innerText = title
|
||||
}
|
||||
|
@ -461,10 +413,21 @@ class QRCodeReaderSection extends HTMLElement {
|
|||
this.#wrapper.classList.add('error')
|
||||
this.#errors.innerHTML = message
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#wrapper.classList.remove('error')
|
||||
this.#content.innerHTML = ''
|
||||
}
|
||||
|
||||
async load(callback) {
|
||||
this.#wrapper.classList.add('loading')
|
||||
try {
|
||||
await callback()
|
||||
}
|
||||
finally {
|
||||
this.#wrapper.classList.remove('loading')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validityTemplate = template(`
|
||||
|
@ -511,3 +474,78 @@ class QRCodeReaderValidity extends QRCodeReaderSection {
|
|||
}
|
||||
|
||||
window.customElements.define('qrcode-reader-validity', QRCodeReaderValidity)
|
||||
|
||||
|
||||
const dataItemTemplate = template(`
|
||||
<span class="qrcode-reader-data--item-label">{label} : </span>
|
||||
<span class="qrcode-reader-data--item-value">{value}</span>
|
||||
`)
|
||||
|
||||
class QRCodeReaderData extends QRCodeReaderSection {
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
this.setTitle(translate('data_section_title'))
|
||||
}
|
||||
|
||||
async refresh(data, metadataUrl, certificateUUID) {
|
||||
this.reset()
|
||||
this.contentElement.innerHTML = ''
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
this.#addItem(key, value)
|
||||
}
|
||||
|
||||
await this.load(async () => {
|
||||
await this.#showMetadata(metadataUrl, certificateUUID)
|
||||
})
|
||||
}
|
||||
|
||||
#addItem(label, value) {
|
||||
const dataItem = dataItemTemplate.cloneNode(true)
|
||||
dataItem.innerHTML = dataItem.innerHTML.replace('{label}', label).replace('{value}', value)
|
||||
this.contentElement.append(dataItem.content)
|
||||
}
|
||||
|
||||
async #showMetadata(metadataUrl, certificateUUID) {
|
||||
if(metadataUrl === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const metadata = await this.#fetchMetadata(metadataUrl, certificateUUID)
|
||||
if(!metadata) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(metadata)) {
|
||||
this.#addItem(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
async #fetchMetadata(metadataUrl, certificateUUID) {
|
||||
const url = `${metadataUrl}?certificate=${certificateUUID}`
|
||||
let metadata = undefined
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
const content = await response.json()
|
||||
|
||||
if(response.ok && content.err === 0) {
|
||||
metadata = content.data
|
||||
}
|
||||
else {
|
||||
this.showError(translate('metadata_api_error'))
|
||||
}
|
||||
} catch(err) {
|
||||
if(err.name === 'NetworkError') {
|
||||
this.showError(translate('metadata_network_error'))
|
||||
}
|
||||
else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window.customElements.define('qrcode-reader-data', QRCodeReaderData)
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"to": "{% trans 'To' %}",
|
||||
"valid": "{% trans 'Valid QR code' %}",
|
||||
"validity_section_title": "{% trans 'Validity' %}",
|
||||
"data_section_title": "{% trans 'Data' %}"
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="{% static 'qrcode/css/style.css' %}">
|
||||
|
|
|
@ -110,14 +110,14 @@ qrcodeReaderTest('qrcode reader shows valid qrcode informations', async ({mock})
|
|||
|
||||
expect(validity.innerText).toMatch(/from :\s*10\/31\/2023, 11:00:00 PM\s*to :\s*12\/1\/2023, 10:59:59 PM/)
|
||||
|
||||
const labels = popup.querySelectorAll('.qrcode-reader--data-item-label')
|
||||
const labels = popup.querySelectorAll('.qrcode-reader-data--item-label')
|
||||
|
||||
expect(labels.length).toBe(3)
|
||||
expect(labels[0].innerText).toMatch('last_name')
|
||||
expect(labels[1].innerText).toMatch('first_name')
|
||||
expect(labels[2].innerText).toMatch('license_plate')
|
||||
|
||||
const values = popup.querySelectorAll('.qrcode-reader--data-item-value')
|
||||
const values = popup.querySelectorAll('.qrcode-reader-data--item-value')
|
||||
|
||||
expect(values.length).toBe(3)
|
||||
expect(values[0].innerText).toMatch('Abitbol')
|
||||
|
@ -261,8 +261,8 @@ qrcodeReaderMetadataTest('qrcode reader fetches metadata', async ({mock, loadMet
|
|||
const {reader} = mock
|
||||
await loadMetadata(okCodeData, async (url) => {
|
||||
expect(url).toBe('https://orson-welles.io?certificate=853b9497-6c1a-4ff1-8604-6dc7cca9003c')
|
||||
const spinner = reader.querySelector('.qrcode-reader--spinner')
|
||||
expect(spinner).not.toBe(null)
|
||||
const section = reader.querySelector('qrcode-reader-data .qrcode-reader-section')
|
||||
expect(section.classList.contains('loading')).toBe(true)
|
||||
return {
|
||||
ok: true,
|
||||
json: () => new Promise((resolve) => {
|
||||
|
@ -271,20 +271,20 @@ qrcodeReaderMetadataTest('qrcode reader fetches metadata', async ({mock, loadMet
|
|||
}
|
||||
})
|
||||
|
||||
const spinner = reader.querySelector('.qrcode-reader--spinner')
|
||||
expect(spinner).toBe(null)
|
||||
const section = reader.querySelector('qrcode-reader-data .qrcode-reader-section')
|
||||
expect(section.classList.contains('loading')).toBe(false)
|
||||
|
||||
const labels = reader.querySelectorAll('.qrcode-reader--metadata-items .qrcode-reader--data-item-label')
|
||||
const labels = reader.querySelectorAll('.qrcode-reader-data--item-label')
|
||||
|
||||
expect(labels.length).toBe(2)
|
||||
expect(labels[0].innerText).toMatch('Classe')
|
||||
expect(labels[1].innerText).toMatch('Ravioles')
|
||||
expect(labels.length).toBe(5)
|
||||
expect(labels[3].innerText).toMatch('Classe')
|
||||
expect(labels[4].innerText).toMatch('Ravioles')
|
||||
|
||||
const values = reader.querySelectorAll('.qrcode-reader--metadata-items .qrcode-reader--data-item-value')
|
||||
const values = reader.querySelectorAll('.qrcode-reader-data--item-value')
|
||||
|
||||
expect(values.length).toBe(2)
|
||||
expect(values[0].innerText).toMatch('Oui')
|
||||
expect(values[1].innerText).toMatch('Non')
|
||||
expect(values.length).toBe(5)
|
||||
expect(values[3].innerText).toMatch('Oui')
|
||||
expect(values[4].innerText).toMatch('Non')
|
||||
})
|
||||
|
||||
|
||||
|
@ -297,9 +297,10 @@ qrcodeReaderMetadataTest('qrcode reader show feedback on metadata network error'
|
|||
const spinner = reader.querySelector('.qrcode-reader--spinner')
|
||||
expect(spinner).toBe(null)
|
||||
|
||||
const items = reader.querySelector('.qrcode-reader--metadata-items')
|
||||
expect(items.classList.contains('error')).toBe(true)
|
||||
expect(items.innerText).toBe('metadata_network_error')
|
||||
const section = reader.querySelector('qrcode-reader-data .qrcode-reader-section')
|
||||
const errors = reader.querySelector('qrcode-reader-data .qrcode-reader-section--errors')
|
||||
expect(section.classList.contains('error')).toBe(true)
|
||||
expect(errors.innerText).toBe('metadata_network_error')
|
||||
})
|
||||
|
||||
qrcodeReaderMetadataTest('qrcode reader show feedback on http error', async ({mock, loadMetadata}) => {
|
||||
|
@ -314,9 +315,10 @@ qrcodeReaderMetadataTest('qrcode reader show feedback on http error', async ({mo
|
|||
const spinner = reader.querySelector('.qrcode-reader--spinner')
|
||||
expect(spinner).toBe(null)
|
||||
|
||||
const items = reader.querySelector('.qrcode-reader--metadata-items')
|
||||
expect(items.classList.contains('error')).toBe(true)
|
||||
expect(items.innerText.trim()).toBe('metadata_api_error')
|
||||
const section = reader.querySelector('qrcode-reader-data .qrcode-reader-section')
|
||||
const errors = reader.querySelector('qrcode-reader-data .qrcode-reader-section--errors')
|
||||
expect(section.classList.contains('error')).toBe(true)
|
||||
expect(errors.innerText.trim()).toBe('metadata_api_error')
|
||||
})
|
||||
|
||||
qrcodeReaderMetadataTest('qrcode reader show feedback on api error', async ({mock, loadMetadata}) => {
|
||||
|
@ -334,9 +336,10 @@ qrcodeReaderMetadataTest('qrcode reader show feedback on api error', async ({moc
|
|||
const spinner = reader.querySelector('.qrcode-reader--spinner')
|
||||
expect(spinner).toBe(null)
|
||||
|
||||
const items = reader.querySelector('.qrcode-reader--metadata-items')
|
||||
expect(items.classList.contains('error')).toBe(true)
|
||||
expect(items.innerText.trim()).toBe('metadata_api_error')
|
||||
const section = reader.querySelector('qrcode-reader-data .qrcode-reader-section')
|
||||
const errors = reader.querySelector('qrcode-reader-data .qrcode-reader-section--errors')
|
||||
expect(section.classList.contains('error')).toBe(true)
|
||||
expect(errors.innerText.trim()).toBe('metadata_api_error')
|
||||
})
|
||||
|
||||
qrcodeReaderMetadataTest("qrcode reader doesn't refetch metadata for the same certificate if popup is opened", async ({mock, loadMetadata}) => {
|
||||
|
@ -349,14 +352,14 @@ qrcodeReaderMetadataTest("qrcode reader doesn't refetch metadata for the same ce
|
|||
}
|
||||
})
|
||||
|
||||
let items = reader.querySelector('.qrcode-reader--metadata-items')
|
||||
expect(reader.querySelectorAll('.qrcode-reader-data--item-value').length).toStrictEqual(5)
|
||||
|
||||
// loading the same certificate doesn't refeches metadata
|
||||
await loadMetadata(okCodeData, async (url) => {
|
||||
expect.unreachable()
|
||||
})
|
||||
|
||||
expect(reader.querySelector('.qrcode-reader--metadata-items')).toBe(items)
|
||||
expect(reader.querySelectorAll('.qrcode-reader-data--item-value').length).toStrictEqual(5)
|
||||
|
||||
// loading another certificate refetches metadata
|
||||
await loadMetadata(certificateWithoutValidity, async (url) => {
|
||||
|
@ -366,8 +369,7 @@ qrcodeReaderMetadataTest("qrcode reader doesn't refetch metadata for the same ce
|
|||
}
|
||||
})
|
||||
|
||||
expect(reader.querySelector('.qrcode-reader--metadata-items')).not.toBe(items)
|
||||
items = reader.querySelector('.qrcode-reader--metadata-items')
|
||||
expect(reader.querySelectorAll('.qrcode-reader-data--item-value').length).toStrictEqual(3)
|
||||
|
||||
const closeButton = reader.querySelector('.qrcode-reader--close-popup-button')
|
||||
closeButton.dispatchEvent(new Event('click'))
|
||||
|
@ -380,7 +382,7 @@ qrcodeReaderMetadataTest("qrcode reader doesn't refetch metadata for the same ce
|
|||
}
|
||||
})
|
||||
|
||||
expect(reader.querySelector('.qrcode-reader--metadata-items')).not.toBe(items)
|
||||
expect(reader.querySelectorAll('.qrcode-reader-data--item-value').length).toStrictEqual(3)
|
||||
})
|
||||
|
||||
const qrcodeReaderTallyTest = qrcodeReaderTest.extend({
|
||||
|
|
Loading…
Reference in New Issue