456 files

This commit is contained in:
Home Assistant Version Control
2026-10-01 05:38:21 +00:00
parent 4192ecbaae
commit 5d6d845ebf
456 changed files with 64951 additions and 3828 deletions
@@ -1,7 +1,9 @@
/** Dialog for generating, printing, and downloading QR codes.
*
* For tasks: shows two QR codes side-by-side — "Info" (ℹ) and "Complete" (✓)
* with embedded icons. For objects: shows a single "Info" QR.
* with embedded icons, plus the lightning-bolt "Quick-complete" code when the
* task has quick-complete defaults (#192: the code existed since 1.3.0 but no
* dialog ever offered it). For objects: shows a single "Info" QR.
*/
import { LitElement, html, css, nothing } from "lit";
@@ -48,6 +50,7 @@ export class MaintenanceQrDialog extends LitElement {
@state() private _error = "";
@state() private _viewResult: QrResult | null = null;
@state() private _completeResult: QrResult | null = null;
@state() private _quickResult: QrResult | null = null;
@state() private _urlMode: "companion" | "local" | "server" = "companion";
private _entryId = "";
@@ -65,6 +68,7 @@ export class MaintenanceQrDialog extends LitElement {
this._error = "";
this._viewResult = null;
this._completeResult = null;
this._quickResult = null;
this._open = true;
this._generate();
}
@@ -83,6 +87,7 @@ export class MaintenanceQrDialog extends LitElement {
this._error = "";
this._viewResult = null;
this._completeResult = null;
this._quickResult = null;
this._open = true;
this._generate();
}
@@ -93,6 +98,7 @@ export class MaintenanceQrDialog extends LitElement {
this._error = "";
this._viewResult = null;
this._completeResult = null;
this._quickResult = null;
try {
const base: Record<string, unknown> = {
type: "maintenance_supporter/qr/generate",
@@ -109,6 +115,13 @@ export class MaintenanceQrDialog extends LitElement {
promises.push(
this.hass.connection.sendMessagePromise({ ...base, action: "complete" }),
);
// The one-tap code only when the task has defaults to record — for
// any other task it would just open the complete dialog.
if (await this._hasQuickCompleteDefaults()) {
promises.push(
this.hass.connection.sendMessagePromise({ ...base, action: "quick_complete" }),
);
}
}
const results = await Promise.all(promises);
@@ -118,6 +131,9 @@ export class MaintenanceQrDialog extends LitElement {
if (results.length > 1) {
this._completeResult = results[1] as QrResult;
}
if (results.length > 2) {
this._quickResult = results[2] as QrResult;
}
} catch (err: unknown) {
if (seq !== this._generateSeq) return;
const code = (err as Record<string, unknown>)?.code;
@@ -130,6 +146,18 @@ export class MaintenanceQrDialog extends LitElement {
}
}
private async _hasQuickCompleteDefaults(): Promise<boolean> {
try {
const obj = await this.hass.connection.sendMessagePromise<{
tasks?: Array<{ id: string; quick_complete_defaults?: Record<string, unknown> | null }>;
}>({ type: "maintenance_supporter/object", entry_id: this._entryId });
const task = (obj.tasks || []).find((tk) => tk.id === this._taskId);
return !!task?.quick_complete_defaults && Object.keys(task.quick_complete_defaults).length > 0;
} catch {
return false;
}
}
private _setUrlMode(mode: "companion" | "local" | "server"): void {
if (this._urlMode === mode) return;
this._urlMode = mode;
@@ -152,8 +180,10 @@ export class MaintenanceQrDialog extends LitElement {
const safeSub = escapeHtml(subtitle);
const hasComplete = !!this._completeResult;
const hasQuick = !!this._quickResult;
const viewLabel = escapeHtml(t("qr_action_view", L));
const completeLabel = escapeHtml(t("qr_action_complete", L));
const quickLabel = escapeHtml(t("qr_action_quick_complete", L));
w.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
<meta name="color-scheme" content="light">
@@ -169,7 +199,7 @@ export class MaintenanceQrDialog extends LitElement {
.sub{color:#666;font-size:14px;margin-bottom:16px}
.qr-row{display:flex;justify-content:center;gap:24px;margin:12px 0}
.qr-col{display:flex;flex-direction:column;align-items:center;gap:6px}
.qr-col img{width:${hasComplete ? "200px" : "280px"}}
.qr-col img{width:${hasQuick ? "170px" : hasComplete ? "200px" : "280px"}}
.qr-label{font-size:13px;font-weight:500;color:#333}
.url{font-size:10px;color:#999;word-break:break-all;margin-top:8px;max-width:480px}
</style></head><body>
@@ -184,6 +214,10 @@ ${safeSub ? `<div class="sub">${safeSub}</div>` : ""}
<img src="${sanitizeDataUri(this._completeResult!.svg_data_uri)}" alt="QR Complete" />
<div class="qr-label">${completeLabel}</div>
</div>` : ""}
${hasQuick ? `<div class="qr-col">
<img src="${sanitizeDataUri(this._quickResult!.svg_data_uri)}" alt="QR Quick-complete" />
<div class="qr-label">${quickLabel}</div>
</div>` : ""}
</div>
<div class="url">${escapeHtml(this._viewResult.url)}</div>
<script>setTimeout(()=>window.print(),300)<\/script>
@@ -208,6 +242,7 @@ ${safeSub ? `<div class="sub">${safeSub}</div>` : ""}
this._open = false;
this._viewResult = null;
this._completeResult = null;
this._quickResult = null;
this._error = "";
this._loading = false;
}
@@ -260,6 +295,23 @@ ${safeSub ? `<div class="sub">${safeSub}</div>` : ""}
</div>
`
: nothing}
${this._quickResult
? html`
<div class="qr-item quick">
<img
class="qr-image small"
src="${this._quickResult.svg_data_uri}"
alt="QR Quick-complete"
/>
<div class="qr-item-label">${t("qr_action_quick_complete", L)}</div>
<button class="dl-btn"
@click=${() => this._downloadSvg(this._quickResult!, "quick-complete")}>
<ha-icon icon="mdi:download"></ha-icon>
${t("qr_download", L)}
</button>
</div>
`
: nothing}
</div>
<div class="url-display">${this._viewResult!.url}</div>
`
@@ -312,6 +364,7 @@ ${safeSub ? `<div class="sub">${safeSub}</div>` : ""}
}
.qr-pair {
display: flex;
flex-wrap: wrap; /* three codes (quick-complete, #192) wrap on a phone */
gap: 20px;
justify-content: center;
width: 100%;
@@ -332,6 +385,7 @@ ${safeSub ? `<div class="sub">${safeSub}</div>` : ""}
height: 180px;
}
.qr-item-label {
max-width: 180px;
font-size: 12px;
font-weight: 500;
color: var(--secondary-text-color);