mirror of https://github.com/requarks/wiki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
4.5 KiB
169 lines
4.5 KiB
import { LitElement, html, css } from 'lit'
|
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js'
|
|
import { renderSVG } from 'uqr'
|
|
import { DarkMode } from '../shared/theme.js'
|
|
|
|
/**
|
|
* Block QR Code
|
|
*/
|
|
export class BlockQrCodeElement extends LitElement {
|
|
/**
|
|
* Metadata for the admin area and the editor's block picker. Collected at build time into
|
|
* `compiled/blocks.manifest.json`, which the server reads to register the block. Values must be
|
|
* plain literals. See `props` in `block-index` for what the picker does with that list.
|
|
*/
|
|
static definition = {
|
|
block: 'qr-code',
|
|
name: 'QR Code',
|
|
description: 'Shows a QR code for a link or a piece of text.',
|
|
icon: 'qr',
|
|
props: [
|
|
{
|
|
name: 'value',
|
|
type: 'string',
|
|
label: 'Content',
|
|
hint: 'Text or URL to encode. The address of this page when left empty.'
|
|
},
|
|
{
|
|
name: 'size',
|
|
type: 'number',
|
|
label: 'Size',
|
|
hint: 'Width of the code in pixels.',
|
|
default: 180
|
|
},
|
|
{
|
|
name: 'caption',
|
|
type: 'string',
|
|
label: 'Caption',
|
|
hint: 'Shown under the code.'
|
|
}
|
|
]
|
|
}
|
|
|
|
static get styles() {
|
|
return css`
|
|
:host {
|
|
display: block;
|
|
}
|
|
|
|
/* -> The gap below the block. On this element rather than :host: see block-index. */
|
|
.qr {
|
|
margin-bottom: 16px;
|
|
display: inline-flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 12px;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
border-radius: 5px;
|
|
/*
|
|
White in both themes, and padded: a code is read by a camera looking for dark squares on a
|
|
light field, so inverting it for dark mode would make it harder to scan, not easier.
|
|
*/
|
|
background-color: #fff;
|
|
}
|
|
:host([dark]) .qr {
|
|
border-color: rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
/* -> The drawing is sized here, so the box grows by its own padding rather than eating into it */
|
|
.qr svg {
|
|
display: block;
|
|
width: var(--qr-size);
|
|
height: auto;
|
|
}
|
|
|
|
.caption {
|
|
max-width: var(--qr-size);
|
|
color: #424242;
|
|
font-size: 0.8em;
|
|
text-align: center;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.error {
|
|
color: var(--q-negative, #c10015);
|
|
border: 1px dashed color-mix(in srgb, currentColor 50%, transparent);
|
|
border-radius: 5px;
|
|
padding: 1rem;
|
|
margin-bottom: 16px;
|
|
}
|
|
`
|
|
}
|
|
|
|
static get properties() {
|
|
return {
|
|
/**
|
|
* Text or URL to encode
|
|
* @type {string}
|
|
*/
|
|
value: { type: String },
|
|
|
|
/**
|
|
* Width of the code in pixels
|
|
* @type {number}
|
|
*/
|
|
size: { type: Number },
|
|
|
|
/**
|
|
* Text shown under the code
|
|
* @type {string}
|
|
*/
|
|
caption: { type: String },
|
|
|
|
// Internal Properties
|
|
_svg: { state: true },
|
|
_error: { state: true }
|
|
}
|
|
}
|
|
|
|
constructor() {
|
|
super()
|
|
this.value = ''
|
|
this.size = 180
|
|
this.caption = ''
|
|
this._svg = ''
|
|
this._error = ''
|
|
// -> Puts `dark` on this element for the styles above to key off
|
|
this._darkMode = new DarkMode(this)
|
|
}
|
|
|
|
/**
|
|
* What the code stands for.
|
|
*
|
|
* An empty `value` means this page, which is the common case — a printed page, or a screen someone
|
|
* wants to carry on their phone. Taken from the address bar rather than built from the site config,
|
|
* so it is the URL the reader is actually looking at, and without the fragment, which points at a
|
|
* place on the page rather than at the page.
|
|
*/
|
|
_encoded() {
|
|
return this.value?.trim() || `${window.location.origin}${window.location.pathname}`
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback()
|
|
try {
|
|
// -> Drawn at a fixed scale and sized by CSS, so the same markup is crisp at any width
|
|
this._svg = renderSVG(this._encoded(), { border: 1, pixelSize: 8 })
|
|
} catch {
|
|
// -> Every symbol size has a ceiling, and a long enough string clears the largest of them
|
|
this._error = 'This is too long to fit in a QR code.'
|
|
}
|
|
}
|
|
|
|
render() {
|
|
if (this._error) {
|
|
return html`<div class="error">${this._error}</div>`
|
|
}
|
|
const size = `${Math.min(Math.max(Number(this.size) || 180, 80), 600)}px`
|
|
return html`
|
|
<div class="qr" style="--qr-size: ${size}">
|
|
${unsafeSVG(this._svg)}
|
|
${this.caption ? html`<div class="caption">${this.caption}</div>` : null}
|
|
</div>
|
|
`
|
|
}
|
|
}
|
|
|
|
window.customElements.define('block-qr-code', BlockQrCodeElement)
|