Add QR code element (#19155)
* Add QR code element * fixes * move to workflow * limit webpack imports
This commit is contained in:
parent
953a3793c4
commit
caece9d6ad
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
`;
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue