qrcode: move tally to a new element (#86853)

This commit is contained in:
Corentin Sechet 2024-02-22 18:06:10 +01:00 committed by Corentin Sechet
parent 6fc97253ff
commit 7330fce543
4 changed files with 85 additions and 133 deletions

View File

@ -91,7 +91,9 @@ qrcode-reader {
}
&.invalid-qrcode {
qrcode-reader-validity, qrcode-reader-data {
qrcode-reader-validity,
qrcode-reader-data,
qrcode-reader-tally {
display: none;
}
}
@ -112,35 +114,11 @@ qrcode-reader {
}
}
&--popup-content {
margin: 10px;
}
&--close-popup-button {
margin: 5px 10px 10px 10px;
padding: 5px;
font-size: 1.2rem;
}
&--tally-status {
display: grid;
margin-top: 5px;
padding-top: 5px;
border-top: 1px solid var(--gray-light);
&.error {
color: #{$red};
}
}
&--spinner {
grid-area: 1 / 1 / 2 / 3;
justify-self: center;
}
&--spinner-animation {
transform-origin:center;
animation:spinner-keyframes .75s infinite linear;
}
}
@keyframes spinner-keyframes {

View File

@ -48,24 +48,11 @@ const readerTemplate = template(`
<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>
<qrcode-reader-tally></qrcode-reader-tally>
<button class="qrcode-reader--close-popup-button">${translate('close')}</button>
</div>
`)
const dataTemplate = template(`
<span class="qrcode-reader--tally-status"></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"/>
<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--spinner-animation"/>
</svg>
`)
function decodeMimeLike (value) {
const chunks = value.split('\n')
const data = {}
@ -136,11 +123,11 @@ function decodeBase45 (str) {
class QRCodeReader extends window.HTMLElement {
#popup
#popupContent
#errors
#currentCertificate
#validity
#data
#tally
constructor () {
super()
@ -153,14 +140,13 @@ class QRCodeReader extends window.HTMLElement {
this.appendChild(readerTemplate.content.cloneNode(true))
this.#popup = this.querySelector('.qrcode-reader--popup')
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')
this.#tally = this.querySelector('qrcode-reader-tally')
const closePopupButton = this.querySelector('.qrcode-reader--close-popup-button')
closePopupButton.addEventListener('click', () => {
this.#popupContent.innerHTML = ''
this.#currentCertificate = undefined
this.#popup.classList.add('closed')
})
@ -180,6 +166,10 @@ class QRCodeReader extends window.HTMLElement {
return
}
if(this.getAttribute('tally-url') === null) {
this.#tally.setAttribute('hidden', true)
}
this.#startScan()
}
@ -234,8 +224,6 @@ class QRCodeReader extends window.HTMLElement {
}
this.#currentCertificate = certificateUUID
this.#popupContent.innerHTML = ''
delete data.uuid
@ -247,16 +235,9 @@ class QRCodeReader extends window.HTMLElement {
this.#validity.refresh(validityStart, validityEnd)
const dataElement = dataTemplate.cloneNode(true)
const tallyStatus = dataElement.content.querySelector('.qrcode-reader--tally-status')
this.#popupContent.append(dataElement.content)
await Promise.all([
this.#data.refresh(data, this.getAttribute('metadata-url'), certificateUUID),
this.#showTally(tallyStatus, certificateUUID)
this.#tally.refresh(this.getAttribute('tally-url'), certificateUUID)
])
if(this.querySelector('.qrcode-reader-section.error')) {
@ -264,58 +245,6 @@ class QRCodeReader extends window.HTMLElement {
}
}
async #showTally(statusElement, certificateUUID) {
if(this.getAttribute('tally-url') === null) {
return
}
const spinner = spinnerTemplate.cloneNode(true)
statusElement.appendChild(spinner.content)
const tallyUrl = this.getAttribute('tally-url')
try {
const response = await fetch(
tallyUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
events: [{
certificate: certificateUUID,
timestamp: Math.floor(Date.now() / 1000)}
]}
),
})
statusElement.innerHTML = ''
if(!response.ok) {
statusElement.classList.add('error')
statusElement.innerHTML = `<p>${translate('tally_api_error')}</p>`
}
else {
const json = await response.json()
if(json.data.stamps[certificateUUID] == 'duplicate') {
statusElement.classList.add('error')
statusElement.innerHTML = `<p>${translate('tally_status_already_seen')}</p>`
}
else {
statusElement.innerHTML = `<p>${translate('tally_status_ok')}</p>`
}
}
} catch(err) {
if(err.name == 'NetworkError') {
statusElement.classList.add('error')
statusElement.innerHTML = `<p>${translate('tally_network_error')}</p>`
}
else {
throw err
}
}
}
#showError (message) {
this.#popup.classList.remove('closed')
this.#popup.classList.add('error')
@ -549,3 +478,61 @@ class QRCodeReaderData extends QRCodeReaderSection {
}
window.customElements.define('qrcode-reader-data', QRCodeReaderData)
class QRCodeReaderTally extends QRCodeReaderSection {
connectedCallback() {
super.connectedCallback()
this.setTitle(translate('tally_section_title'))
}
async refresh(tallyUrl, certificateUUID) {
if(tallyUrl === null) {
return
}
this.reset()
await this.load(async () => await this.#showTally(tallyUrl, certificateUUID))
}
async #showTally(tallyUrl, certificateUUID) {
try {
const response = await fetch(
tallyUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
events: [{
certificate: certificateUUID,
timestamp: Math.floor(Date.now() / 1000)}
]}
),
})
if(!response.ok) {
this.showError(translate('tally_api_error'))
}
else {
const json = await response.json()
if(json.data.stamps[certificateUUID] == 'duplicate') {
this.showError(translate('tally_status_already_seen'))
}
else {
this.contentElement.innerText = translate('tally_status_ok')
}
}
} catch(err) {
if(err.name == 'NetworkError') {
this.showError(translate('tally_network_error'))
}
else {
throw err
}
}
}
}
window.customElements.define('qrcode-reader-tally', QRCodeReaderTally)

View File

@ -28,7 +28,8 @@
"to": "{% trans 'To' %}",
"valid": "{% trans 'Valid QR code' %}",
"validity_section_title": "{% trans 'Validity' %}",
"data_section_title": "{% trans 'Data' %}"
"data_section_title": "{% trans 'Data' %}",
"tally_section_title": "{% trans 'Tallying' %}"
}
</script>
<link rel="stylesheet" href="{% static 'qrcode/css/style.css' %}">

View File

@ -421,12 +421,11 @@ qrcodeReaderTallyTest('qrcode reader show tally status for unstamped certificate
})
Date.now = nowBackup
const spinner = reader.querySelector('.qrcode-reader--spinner')
expect(spinner).toBe(null)
const section = reader.querySelector('qrcode-reader-tally .qrcode-reader-section')
expect(section.classList.contains('error')).toBe(false)
const items = reader.querySelector('.qrcode-reader--tally-status')
expect(items.classList.contains('error')).toBe(false)
expect(items.innerText.trim()).toBe('tally_status_ok')
const content = reader.querySelector('qrcode-reader-tally .qrcode-reader-section--content')
expect(content.innerText.trim()).toBe('tally_status_ok')
})
qrcodeReaderTallyTest('qrcode reader show error for stamped certificates', async ({mock, loadTally}) => {
@ -439,12 +438,11 @@ qrcodeReaderTallyTest('qrcode reader show error for stamped certificates', async
}
})
const spinner = reader.querySelector('.qrcode-reader--spinner')
expect(spinner).toBe(null)
const section = reader.querySelector('qrcode-reader-tally .qrcode-reader-section')
expect(section.classList.contains('error')).toBe(true)
const items = reader.querySelector('.qrcode-reader--tally-status')
expect(items.classList.contains('error')).toBe(true)
expect(items.innerText.trim()).toBe('tally_status_already_seen')
const errors = reader.querySelector('qrcode-reader-tally .qrcode-reader-section--errors')
expect(errors.innerText.trim()).toBe('tally_status_already_seen')
})
qrcodeReaderTallyTest('qrcode reader show tally when request fails ', async ({mock, loadTally}) => {
@ -454,12 +452,11 @@ qrcodeReaderTallyTest('qrcode reader show tally when request fails ', async ({mo
throw { name: 'NetworkError' }
})
const spinner = reader.querySelector('.qrcode-reader--spinner')
expect(spinner).toBe(null)
const section = reader.querySelector('qrcode-reader-tally .qrcode-reader-section')
expect(section.classList.contains('error')).toBe(true)
let items = reader.querySelector('.qrcode-reader--tally-status')
expect(items.classList.contains('error')).toBe(true)
expect(items.innerText.trim()).toBe('tally_network_error')
const errors = reader.querySelector('qrcode-reader-tally .qrcode-reader-section--errors')
expect(errors.innerText.trim()).toBe('tally_network_error')
const closeButton = reader.querySelector('.qrcode-reader--close-popup-button')
closeButton.dispatchEvent(new Event('click'))
@ -470,9 +467,8 @@ qrcodeReaderTallyTest('qrcode reader show tally when request fails ', async ({mo
}
})
items = reader.querySelector('.qrcode-reader--tally-status')
expect(items.classList.contains('error')).toBe(true)
expect(items.innerText.trim()).toBe('tally_api_error')
expect(section.classList.contains('error')).toBe(true)
expect(errors.innerText.trim()).toBe('tally_api_error')
})
qrcodeReaderTallyTest("qrcode reader doesn't refetch tally for the same certificate if popup is opened", async ({mock, loadTally}) => {
@ -485,15 +481,11 @@ qrcodeReaderTallyTest("qrcode reader doesn't refetch tally for the same certific
}
})
let tallyStatus = reader.querySelector('.qrcode-reader--tally-status')
// loading the same certificate doesn't refeches metadata
await loadTally(okCodeData, async () => {
expect.unreachable()
})
expect(reader.querySelector('.qrcode-reader--tally-status')).toBe(tallyStatus)
// loading another certificate refetches tally status
await loadTally(certificateWithoutValidity, async (url, body) => {
return {
@ -502,9 +494,6 @@ qrcodeReaderTallyTest("qrcode reader doesn't refetch tally for the same certific
}
})
expect(reader.querySelector('.qrcode-reader--tally-status')).not.toBe(tallyStatus)
tallyStatus = reader.querySelector('.qrcode-reader--tally-status')
const closeButton = reader.querySelector('.qrcode-reader--close-popup-button')
closeButton.dispatchEvent(new Event('click'))
@ -515,7 +504,4 @@ qrcodeReaderTallyTest("qrcode reader doesn't refetch tally for the same certific
json: async () => ({'err': 0, 'data': {'stamps': {[body.events[0].certificate]: 'ok'}}})
}
})
expect(reader.querySelector('.qrcode-reader--tally-status')).not.toBe(tallyStatus)
tallyStatus = reader.querySelector('.qrcode-reader--tally-status')
})