From caece9d6ad0c41b628c7592a84b49c8427caf9e2 Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Wed, 27 Dec 2023 17:22:09 +0100
Subject: [PATCH] Add QR code element (#19155)
* Add QR code element
* fixes
* move to workflow
* limit webpack imports
---
.github/workflows/ci.yaml | 2 +-
.../datadisk/dialog-hassio-datadisk.ts | 1 -
src/components/ha-markdown-element.ts | 9 ++
src/components/ha-markdown.ts | 5 -
src/components/ha-qr-code.ts | 114 ++++++++++++++++++
.../automation/manual-automation-editor.ts | 1 +
.../config/storage/dialog-move-datadisk.ts | 1 -
src/panels/config/tags/dialog-tag-detail.ts | 69 +++--------
src/resources/markdown-worker.ts | 8 ++
9 files changed, 148 insertions(+), 62 deletions(-)
create mode 100644 src/components/ha-qr-code.ts
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 49839b27f6..af8a338903 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -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:
diff --git a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts
index 8a1c3c6702..4dcfde2ec2 100644
--- a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts
+++ b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts
@@ -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,
diff --git a/src/components/ha-markdown-element.ts b/src/components/ha-markdown-element.ts
index e00bfceb94..0e806b7c7d 100644
--- a/src/components/ha-markdown-element.ts
+++ b/src/components/ha-markdown-element.ts
@@ -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}`
+ );
}
}
}
diff --git a/src/components/ha-markdown.ts b/src/components/ha-markdown.ts
index c248a320e8..f81ddd2860 100644
--- a/src/components/ha-markdown.ts
+++ b/src/components/ha-markdown.ts
@@ -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?;
diff --git a/src/components/ha-qr-code.ts b/src/components/ha-qr-code.ts
new file mode 100644
index 0000000000..f6a686d068
--- /dev/null
+++ b/src/components/ha-qr-code.ts
@@ -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`${this._error}`;
+ }
+ return html``;
+ }
+
+ static styles = css`
+ :host {
+ display: block;
+ }
+ `;
+}
diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts
index 291d308540..811ea2c3fd 100644
--- a/src/panels/config/automation/manual-automation-editor.ts
+++ b/src/panels/config/automation/manual-automation-editor.ts
@@ -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,
diff --git a/src/panels/config/storage/dialog-move-datadisk.ts b/src/panels/config/storage/dialog-move-datadisk.ts
index 456c9884e7..7f5495a8e9 100644
--- a/src/panels/config/storage/dialog-move-datadisk.ts
+++ b/src/panels/config/storage/dialog-move-datadisk.ts
@@ -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,
diff --git a/src/panels/config/tags/dialog-tag-detail.ts b/src/panels/config/tags/dialog-tag-detail.ts
index 7f402f2d4a..62c0e6fe3f 100644
--- a/src/panels/config/tags/dialog-tag-detail.ts
+++ b/src/panels/config/tags/dialog-tag-detail.ts
@@ -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
})}
- ${this._qrCode
- ? html` ${this._qrCode}
`
- : ""}
+
+
+
+
`
: ``}
@@ -158,7 +151,7 @@ class DialogTagDetail
: this.hass!.localize("ui.panel.config.tag.detail.create")}
${this._params.openWrite && !this._params.entry
- ? html` {
- imageObj.onload = resolve;
- });
- context?.drawImage(
- imageObj,
- canvas.width / 3,
- canvas.height / 3,
- canvas.width / 3,
- canvas.height / 3
- );
-
- this._qrCode = html`
`;
- }
-
static get styles(): CSSResultGroup {
return [
haStyleDialog,
@@ -270,6 +228,9 @@ class DialogTagDetail
display: block;
margin: 8px 0;
}
+ ::slotted(img) {
+ height: 100%;
+ }
`,
];
}
diff --git a/src/resources/markdown-worker.ts b/src/resources/markdown-worker.ts
index a2e2234eca..85949e3f23 100644
--- a/src/resources/markdown-worker.ts
+++ b/src/resources/markdown-worker.ts
@@ -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",
+ ],
};
}