Add QR code element (#19155)

* Add QR code element

* fixes

* move to workflow

* limit webpack imports
This commit is contained in:
Bram Kragten 2023-12-27 17:22:09 +01:00 committed by GitHub
parent 953a3793c4
commit caece9d6ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 148 additions and 62 deletions

View File

@ -64,7 +64,7 @@ jobs:
- name: Install dependencies
run: yarn install --immutable
- name: Build resources
run: ./node_modules/.bin/gulp build-translations build-locale-data
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data
- name: Run Tests
run: yarn run test
build:

View File

@ -4,7 +4,6 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-select";
import {
extractApiErrorMessage,

View File

@ -95,6 +95,15 @@ class HaMarkdownElement extends ReactiveElement {
}
node.firstElementChild!.replaceWith(alertNote);
}
} else if (
node instanceof HTMLElement &&
["ha-alert", "ha-qr-code", "ha-icon", "ha-svg-icon"].includes(
node.localName
)
) {
import(
/* webpackInclude: /(ha-alert)|(ha-qr-code)|(ha-icon)|(ha-svg-icon)/ */ `./${node.localName}`
);
}
}
}

View File

@ -2,11 +2,6 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "./ha-markdown-element";
// Import components that are allwoed to be defined.
import "./ha-alert";
import "./ha-icon";
import "./ha-svg-icon";
@customElement("ha-markdown")
export class HaMarkdown extends LitElement {
@property() public content?;

View File

@ -0,0 +1,114 @@
import { LitElement, PropertyValues, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import QRCode from "qrcode";
@customElement("ha-qr-code")
export class HaQrCode extends LitElement {
@property() public data?: string;
@property({ attribute: "error-correction-level" })
public errorCorrectionLevel: "low" | "medium" | "quartile" | "high" =
"medium";
@property({ type: Number })
public width = 4;
@property({ type: Number })
public scale = 4;
@property({ type: Number })
public margin = 4;
@property({ type: Number }) public maskPattern?:
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7;
@property({ attribute: "center-image" }) public centerImage?: string;
@state() private _error?: string;
@query("canvas") private _canvas?: HTMLCanvasElement;
protected willUpdate(changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);
if (
(changedProperties.has("data") ||
changedProperties.has("scale") ||
changedProperties.has("width") ||
changedProperties.has("margin") ||
changedProperties.has("maskPattern") ||
changedProperties.has("errorCorrectionLevel")) &&
this._error
) {
this._error = undefined;
}
}
updated(changedProperties: PropertyValues) {
const canvas = this._canvas;
if (
canvas &&
this.data &&
(changedProperties.has("data") ||
changedProperties.has("scale") ||
changedProperties.has("width") ||
changedProperties.has("margin") ||
changedProperties.has("maskPattern") ||
changedProperties.has("errorCorrectionLevel") ||
changedProperties.has("centerImage"))
) {
const computedStyles = getComputedStyle(this);
QRCode.toCanvas(canvas, this.data, {
errorCorrectionLevel: this.errorCorrectionLevel,
width: this.width,
scale: this.scale,
margin: this.margin,
maskPattern: this.maskPattern,
color: {
light: computedStyles.getPropertyValue("--card-background-color"),
dark: computedStyles.getPropertyValue("--primary-text-color"),
},
}).catch((err) => {
this._error = err.message;
});
if (this.centerImage) {
const context = this._canvas!.getContext("2d");
const imageObj = new Image();
imageObj.src = this.centerImage;
imageObj.onload = () => {
context?.drawImage(
imageObj,
canvas.width * 0.375,
canvas.height * 0.375,
canvas.width / 4,
canvas.height / 4
);
};
}
}
}
render() {
if (!this.data) {
return nothing;
}
if (this._error) {
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
}
return html`<canvas></canvas>`;
}
static styles = css`
:host {
display: block;
}
`;
}

View File

@ -7,6 +7,7 @@ import { ensureArray } from "../../../common/array/ensure-array";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-markdown";
import {
Condition,
ManualAutomationConfig,

View File

@ -5,7 +5,6 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import "../../../components/ha-circular-progress";
import "../../../components/ha-markdown";
import "../../../components/ha-select";
import {
extractApiErrorMessage,

View File

@ -1,17 +1,11 @@
import "@material/mwc-button";
import {
css,
CSSResultGroup,
html,
LitElement,
TemplateResult,
nothing,
} from "lit";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-alert";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-formfield";
import "../../../components/ha-qr-code";
import "../../../components/ha-switch";
import "../../../components/ha-textfield";
import { Tag, UpdateTagParams } from "../../../data/tag";
@ -20,8 +14,6 @@ import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { TagDetailDialogParams } from "./show-dialog-tag-detail";
const QR_LOGO_URL = "/static/icons/favicon-192x192.png";
@customElement("dialog-tag-detail")
class DialogTagDetail
extends LitElement
@ -39,8 +31,6 @@ class DialogTagDetail
@state() private _submitting = false;
@state() private _qrCode?: TemplateResult;
public showDialog(params: TagDetailDialogParams): void {
this._params = params;
this._error = undefined;
@ -50,13 +40,10 @@ class DialogTagDetail
this._id = "";
this._name = "";
}
this._generateQR();
}
public closeDialog(): void {
this._params = undefined;
this._qrCode = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@ -130,9 +117,15 @@ class DialogTagDetail
})}
</p>
</div>
${this._qrCode
? html` <div id="qr">${this._qrCode}</div> `
: ""}
<div id="qr">
<ha-qr-code
.data=${this._params!.entry!.id}
center-image="/static/icons/favicon-192x192.png"
error-correction-level="quartile"
scale="5"
>
</ha-qr-code>
</div>
`
: ``}
</div>
@ -158,7 +151,7 @@ class DialogTagDetail
: this.hass!.localize("ui.panel.config.tag.detail.create")}
</mwc-button>
${this._params.openWrite && !this._params.entry
? html` <mwc-button
? html`<mwc-button
slot="primaryAction"
@click=${this._updateWriteEntry}
.disabled=${this._submitting || !this._name}
@ -221,41 +214,6 @@ class DialogTagDetail
}
}
private async _generateQR() {
const qrcode = await import("qrcode");
const canvas = await qrcode.toCanvas(
`https://www.home-assistant.io/tag/${this._params!.entry!.id}`,
{
width: 180,
errorCorrectionLevel: "Q",
color: {
light: "#fff",
},
}
);
const context = canvas.getContext("2d");
const imageObj = new Image();
imageObj.src = QR_LOGO_URL;
await new Promise((resolve) => {
imageObj.onload = resolve;
});
context?.drawImage(
imageObj,
canvas.width / 3,
canvas.height / 3,
canvas.width / 3,
canvas.height / 3
);
this._qrCode = html`<img
alt=${this.hass.localize("ui.panel.config.tag.qr_code_image", {
name: this._name,
})}
src=${canvas.toDataURL()}
></img>`;
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
@ -270,6 +228,9 @@ class DialogTagDetail
display: block;
margin: 8px 0;
}
::slotted(img) {
height: 100%;
}
`,
];
}

View File

@ -42,6 +42,14 @@ const renderMarkdown = async (
"ha-icon": ["icon"],
"ha-svg-icon": ["path"],
"ha-alert": ["alert-type", "title"],
"ha-qr-code": [
"data",
"scale",
"width",
"margin",
"error-correction-level",
"center-image",
],
};
}