diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a28fd83e4d8a..8b35690bb4d1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,8 +18,8 @@ ## Type of change @@ -56,7 +56,7 @@ --> - This PR fixes or closes issue: fixes # -- This PR is related to issue: +- This PR is related to issue or discussion: - Link to documentation pull request: ## Checklist diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index 2566272cb30a..000000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Configuration for Lock Threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 1 - -# Skip issues and pull requests created before a given timestamp. Timestamp must -# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable -skipCreatedBefore: 2020-01-01 - -# Issues and pull requests with these labels will be ignored. Set to `[]` to disable -exemptLabels: [] - -# Label to add before locking, such as `outdated`. Set to `false` to disable -lockLabel: false - -# Comment to post before locking. Set to `false` to disable -lockComment: false - -# Assign `resolved` as the reason for locking. Set to `false` to disable -setLockReason: false - -# Limit to only `issues` or `pulls` -only: pulls - -# Optionally, specify configuration settings just for `issues` or `pulls` -issues: - daysUntilLock: 30 diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index dc0896c22c6f..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,56 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 90 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 7 - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - feature request - - Help wanted - - to do - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: true - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: true - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: stale - -# Comment to post when marking as stale. Set to `false` to disable -markComment: > - There hasn't been any activity on this issue recently. Due to the high number - of incoming GitHub notifications, we have to clean some of the old issues, - as many of them have already been resolved with the latest updates. - - Please make sure to update to the latest Home Assistant version and check - if that solves the issue. Let us know if that works for you by adding a - comment 👍 - - This issue now has been marked as stale and will be closed if no further - activity occurs. Thank you for your contributions. - -# Comment to post when removing the stale label. -# unmarkComment: > -# Your comment here. - -# Comment to post when closing a stale Issue or Pull Request. -# closeComment: > -# Your comment here. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 - -# Limit to only `issues` or `pulls` -only: issues diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 000000000000..4f7a0efb2d74 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,20 @@ +name: Lock + +# yamllint disable-line rule:truthy +on: + schedule: + - cron: "0 * * * *" + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2.0.1 + with: + github-token: ${{ github.token }} + issue-lock-inactive-days: "30" + issue-exclude-created-before: "2020-10-01T00:00:00Z" + issue-lock-reason: "" + pr-lock-inactive-days: "1" + pr-exclude-created-before: "2020-11-01T00:00:00Z" + pr-lock-reason: "" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..bcb543cf49c5 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,42 @@ +name: Stale + +# yamllint disable-line rule:truthy +on: + schedule: + - cron: "0 * * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - name: 90 days stale policy + uses: actions/stale@v3.0.13 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 90 + days-before-close: 7 + operations-per-run: 25 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "no-stale,Help%20wanted,help-wanted,feature-request,feature%20request" + stale-issue-message: > + There hasn't been any activity on this issue recently. Due to the + high number of incoming GitHub notifications, we have to clean some + of the old issues, as many of them have already been resolved with + the latest updates. + + Please make sure to update to the latest Home Assistant version and + check if that solves the issue. Let us know if that works for you by + adding a comment 👍 + + This issue has now been marked as stale and will be closed if no + further activity occurs. Thank you for your contributions. + + stale-pr-label: "stale" + exempt-pr-labels: "no-stale" + stale-pr-message: > + There hasn't been any activity on this pull request recently. This + pull request has been automatically marked as stale because of that + and will be closed if no further activity occurs within 7 days. + + Thank you for your contributions. diff --git a/hassio/src/ingress-view/hassio-ingress-view.ts b/hassio/src/ingress-view/hassio-ingress-view.ts index ec899c70948b..6a56522e9a11 100644 --- a/hassio/src/ingress-view/hassio-ingress-view.ts +++ b/hassio/src/ingress-view/hassio-ingress-view.ts @@ -13,7 +13,10 @@ import { fetchHassioAddonInfo, HassioAddonDetails, } from "../../../src/data/hassio/addon"; -import { createHassioSession } from "../../../src/data/hassio/supervisor"; +import { + createHassioSession, + validateHassioSession, +} from "../../../src/data/hassio/ingress"; import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-subpage"; import { HomeAssistant, Route } from "../../../src/types"; @@ -35,6 +38,17 @@ class HassioIngressView extends LitElement { @property({ type: Boolean }) public narrow = false; + private _sessionKeepAlive?: number; + + public disconnectedCallback() { + super.disconnectedCallback(); + + if (this._sessionKeepAlive) { + clearInterval(this._sessionKeepAlive); + this._sessionKeepAlive = undefined; + } + } + protected render(): TemplateResult { if (!this._addon) { return html` `; @@ -83,10 +97,7 @@ class HassioIngressView extends LitElement { } private async _fetchData(addonSlug: string) { - const createSessionPromise = createHassioSession(this.hass).then( - () => true, - () => false - ); + const createSessionPromise = createHassioSession(this.hass); let addon; @@ -119,7 +130,11 @@ class HassioIngressView extends LitElement { return; } - if (!(await createSessionPromise)) { + let session; + + try { + session = await createSessionPromise; + } catch (err) { await showAlertDialog(this, { text: "Unable to create an Ingress session", title: addon.name, @@ -128,6 +143,17 @@ class HassioIngressView extends LitElement { return; } + if (this._sessionKeepAlive) { + clearInterval(this._sessionKeepAlive); + } + this._sessionKeepAlive = window.setInterval(async () => { + try { + await validateHassioSession(this.hass, session); + } catch (err) { + session = await createHassioSession(this.hass); + } + }, 60000); + this._addon = addon; } diff --git a/public/static/images/conference.png b/public/static/images/conference.png new file mode 100644 index 000000000000..591028a8b797 Binary files /dev/null and b/public/static/images/conference.png differ diff --git a/src/common/util/copy-clipboard.ts b/src/common/util/copy-clipboard.ts new file mode 100644 index 000000000000..9d7e6885fa65 --- /dev/null +++ b/src/common/util/copy-clipboard.ts @@ -0,0 +1,8 @@ +export const copyToClipboard = (str) => { + const el = document.createElement("textarea"); + el.value = str; + document.body.appendChild(el); + el.select(); + document.execCommand("copy"); + document.body.removeChild(el); +}; diff --git a/src/components/ha-circular-progress.ts b/src/components/ha-circular-progress.ts index 1e70908291a5..c3e188d267bd 100644 --- a/src/components/ha-circular-progress.ts +++ b/src/components/ha-circular-progress.ts @@ -11,7 +11,7 @@ export class HaCircularProgress extends CircularProgress { public alt = "Loading"; @property() - public size: "small" | "medium" | "large" = "medium"; + public size: "tiny" | "small" | "medium" | "large" = "medium"; // @ts-ignore public set density(_) { @@ -20,6 +20,8 @@ export class HaCircularProgress extends CircularProgress { public get density() { switch (this.size) { + case "tiny": + return -8; case "small": return -5; case "medium": diff --git a/src/components/ha-cover-tilt-controls.js b/src/components/ha-cover-tilt-controls.js deleted file mode 100644 index 08f992380a09..000000000000 --- a/src/components/ha-cover-tilt-controls.js +++ /dev/null @@ -1,106 +0,0 @@ -import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "./ha-icon-button"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { UNAVAILABLE } from "../data/entity"; -import CoverEntity from "../util/cover-model"; - -class HaCoverTiltControls extends PolymerElement { - static get template() { - return html` - - - - - - `; - } - - static get properties() { - return { - hass: { - type: Object, - }, - stateObj: { - type: Object, - }, - entityObj: { - type: Object, - computed: "computeEntityObj(hass, stateObj)", - }, - }; - } - - computeEntityObj(hass, stateObj) { - return new CoverEntity(hass, stateObj); - } - - computeStopDisabled(stateObj) { - if (stateObj.state === UNAVAILABLE) { - return true; - } - return false; - } - - computeOpenDisabled(stateObj, entityObj) { - if (stateObj.state === UNAVAILABLE) { - return true; - } - const assumedState = stateObj.attributes.assumed_state === true; - return entityObj.isFullyOpenTilt && !assumedState; - } - - computeClosedDisabled(stateObj, entityObj) { - if (stateObj.state === UNAVAILABLE) { - return true; - } - const assumedState = stateObj.attributes.assumed_state === true; - return entityObj.isFullyClosedTilt && !assumedState; - } - - onOpenTiltTap(ev) { - ev.stopPropagation(); - this.entityObj.openCoverTilt(); - } - - onCloseTiltTap(ev) { - ev.stopPropagation(); - this.entityObj.closeCoverTilt(); - } - - onStopTiltTap(ev) { - ev.stopPropagation(); - this.entityObj.stopCoverTilt(); - } -} - -customElements.define("ha-cover-tilt-controls", HaCoverTiltControls); diff --git a/src/components/ha-cover-tilt-controls.ts b/src/components/ha-cover-tilt-controls.ts new file mode 100644 index 000000000000..843eaa659888 --- /dev/null +++ b/src/components/ha-cover-tilt-controls.ts @@ -0,0 +1,122 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { + LitElement, + property, + internalProperty, + CSSResult, + css, + customElement, + TemplateResult, + html, + PropertyValues, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; + +import { UNAVAILABLE } from "../data/entity"; +import { HomeAssistant } from "../types"; +import CoverEntity from "../util/cover-model"; + +import "./ha-icon-button"; + +@customElement("ha-cover-tilt-controls") +class HaCoverTiltControls extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) stateObj!: HassEntity; + + @internalProperty() private _entityObj?: CoverEntity; + + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + + if (changedProperties.has("stateObj")) { + this._entityObj = new CoverEntity(this.hass, this.stateObj); + } + } + + protected render(): TemplateResult { + if (!this._entityObj) { + return html``; + } + + return html` + + `; + } + + private _computeOpenDisabled(): boolean { + if (this.stateObj.state === UNAVAILABLE) { + return true; + } + const assumedState = this.stateObj.attributes.assumed_state === true; + return this._entityObj.isFullyOpenTilt && !assumedState; + } + + private _computeClosedDisabled(): boolean { + if (this.stateObj.state === UNAVAILABLE) { + return true; + } + const assumedState = this.stateObj.attributes.assumed_state === true; + return this._entityObj.isFullyClosedTilt && !assumedState; + } + + private _onOpenTiltTap(ev): void { + ev.stopPropagation(); + this._entityObj.openCoverTilt(); + } + + private _onCloseTiltTap(ev): void { + ev.stopPropagation(); + this._entityObj.closeCoverTilt(); + } + + private _onStopTiltTap(ev): void { + ev.stopPropagation(); + this._entityObj.stopCoverTilt(); + } + + static get styles(): CSSResult { + return css` + :host { + white-space: nowrap; + } + .invisible { + visibility: hidden !important; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-cover-tilt-controls": HaCoverTiltControls; + } +} diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index 7504d38dcbce..bdccf06cb33b 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -1,127 +1,70 @@ -import "@polymer/paper-input/paper-input"; -import type { PaperInputElement } from "@polymer/paper-input/paper-input"; -import { - css, - customElement, - html, - LitElement, - property, - TemplateResult, -} from "lit-element"; +import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker"; -@customElement("ha-date-input") -export class HaDateInput extends LitElement { - @property() public year?: string; +const VaadinDatePicker = customElements.get("vaadin-date-picker"); - @property() public month?: string; +export class HaDateInput extends VaadinDatePicker { + constructor() { + super(); - @property() public day?: string; - - @property({ type: Boolean }) public disabled = false; + this.i18n.formatDate = this._formatISODate; + this.i18n.parseDate = this._parseISODate; + } - static get styles() { - return css` + ready() { + super.ready(); + const styleEl = document.createElement("style"); + styleEl.innerHTML = ` :host { - display: block; - font-family: var(--paper-font-common-base_-_font-family); - -webkit-font-smoothing: var( - --paper-font-common-base_-_-webkit-font-smoothing - ); - } - - paper-input { - width: 30px; - text-align: center; - --paper-input-container-shared-input-style_-_-webkit-appearance: textfield; - --paper-input-container-input_-_-moz-appearance: textfield; - --paper-input-container-shared-input-style_-_appearance: textfield; - --paper-input-container-input-webkit-spinner_-_-webkit-appearance: none; - --paper-input-container-input-webkit-spinner_-_margin: 0; - --paper-input-container-input-webkit-spinner_-_display: none; - } - - paper-input#year { - width: 50px; - } - - .date-input-wrap { - display: flex; - flex-direction: row; + width: 12ex; + margin-top: -6px; + --material-body-font-size: 16px; + --_material-text-field-input-line-background-color: var(--primary-text-color); + --_material-text-field-input-line-opacity: 1; + --material-primary-color: var(--primary-text-color); } `; + this.shadowRoot.appendChild(styleEl); + this._inputElement.querySelector("[part='toggle-button']").style.display = + "none"; } - protected render(): TemplateResult { - return html` -
- - - - - - - - - - -
- `; - } - - private _formatYear() { - const yearElement = this.shadowRoot!.getElementById( - "year" - ) as PaperInputElement; - this.year = yearElement.value!; - } - - private _formatMonth() { - const monthElement = this.shadowRoot!.getElementById( - "month" - ) as PaperInputElement; - this.month = ("0" + monthElement.value!).slice(-2); + private _formatISODate(d) { + return [ + ("0000" + String(d.year)).slice(-4), + ("0" + String(d.month + 1)).slice(-2), + ("0" + String(d.day)).slice(-2), + ].join("-"); } - private _formatDay() { - const dayElement = this.shadowRoot!.getElementById( - "day" - ) as PaperInputElement; - this.day = ("0" + dayElement.value!).slice(-2); - } + private _parseISODate(text) { + const parts = text.split("-"); + const today = new Date(); + let date; + let month = today.getMonth(); + let year = today.getFullYear(); + if (parts.length === 3) { + year = parseInt(parts[0]); + if (parts[0].length < 3 && year >= 0) { + year += year < 50 ? 2000 : 1900; + } + month = parseInt(parts[1]) - 1; + date = parseInt(parts[2]); + } else if (parts.length === 2) { + month = parseInt(parts[0]) - 1; + date = parseInt(parts[1]); + } else if (parts.length === 1) { + date = parseInt(parts[0]); + } - get value() { - return `${this.year}-${this.month}-${this.day}`; + if (date !== undefined) { + return { day: date, month, year }; + } + return undefined; } } +customElements.define("ha-date-input", HaDateInput as any); + declare global { interface HTMLElementTagNameMap { "ha-date-input": HaDateInput; diff --git a/src/components/ha-markdown-element.ts b/src/components/ha-markdown-element.ts index b6e78c99b907..cd07cf0a012e 100644 --- a/src/components/ha-markdown-element.ts +++ b/src/components/ha-markdown-element.ts @@ -47,7 +47,6 @@ class HaMarkdownElement extends UpdatingElement { node.host !== document.location.host ) { node.target = "_blank"; - node.rel = "noreferrer"; // protect referrer on external links and deny window.opener access for security reasons // (see https://mathiasbynens.github.io/rel-noopener/) diff --git a/src/data/data_entry_flow.ts b/src/data/data_entry_flow.ts index 189d13336e85..a9a7afbf698c 100644 --- a/src/data/data_entry_flow.ts +++ b/src/data/data_entry_flow.ts @@ -58,8 +58,18 @@ export interface DataEntryFlowStepAbort { description_placeholders: { [key: string]: string }; } +export interface DataEntryFlowStepProgress { + type: "progress"; + flow_id: string; + handler: string; + step_id: string; + progress_action: string; + description_placeholders: { [key: string]: string }; +} + export type DataEntryFlowStep = | DataEntryFlowStepForm | DataEntryFlowStepExternal | DataEntryFlowStepCreateEntry - | DataEntryFlowStepAbort; + | DataEntryFlowStepAbort + | DataEntryFlowStepProgress; diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 4c5271e82d23..98ff504f22d8 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -20,6 +20,12 @@ export interface ExtEntityRegistryEntry extends EntityRegistryEntry { original_icon?: string; } +export interface UpdateEntityRegistryEntryResult { + entity_entry: ExtEntityRegistryEntry; + reload_delay?: number; + require_restart?: boolean; +} + export interface EntityRegistryEntryUpdateParams { name?: string | null; icon?: string | null; @@ -72,7 +78,7 @@ export const updateEntityRegistryEntry = ( hass: HomeAssistant, entityId: string, updates: Partial -): Promise => +): Promise => hass.callWS({ type: "config/entity_registry/update", entity_id: entityId, diff --git a/src/data/hassio/ingress.ts b/src/data/hassio/ingress.ts new file mode 100644 index 000000000000..ced84a2698ba --- /dev/null +++ b/src/data/hassio/ingress.ts @@ -0,0 +1,26 @@ +import { HomeAssistant } from "../../types"; +import { HassioResponse } from "./common"; +import { CreateSessionResponse } from "./supervisor"; + +export const createHassioSession = async (hass: HomeAssistant) => { + const response = await hass.callApi>( + "POST", + "hassio/ingress/session" + ); + document.cookie = `ingress_session=${ + response.data.session + };path=/api/hassio_ingress/;SameSite=Strict${ + location.protocol === "https:" ? ";Secure" : "" + }`; + return response.data.session; +}; + +export const validateHassioSession = async ( + hass: HomeAssistant, + session: string +) => + await hass.callApi>( + "POST", + "hassio/ingress/validate_session", + { session } + ); diff --git a/src/data/hassio/supervisor.ts b/src/data/hassio/supervisor.ts index 9d2845f7761a..b21039bb1bad 100644 --- a/src/data/hassio/supervisor.ts +++ b/src/data/hassio/supervisor.ts @@ -111,18 +111,6 @@ export const fetchHassioLogs = async ( return hass.callApi("GET", `hassio/${provider}/logs`); }; -export const createHassioSession = async (hass: HomeAssistant) => { - const response = await hass.callApi>( - "POST", - "hassio/ingress/session" - ); - document.cookie = `ingress_session=${ - response.data.session - };path=/api/hassio_ingress/;SameSite=Strict${ - location.protocol === "https:" ? ";Secure" : "" - }`; -}; - export const setSupervisorOption = async ( hass: HomeAssistant, data: SupervisorOptions diff --git a/src/data/system_health.ts b/src/data/system_health.ts index fdf5007d104e..72d5e200687f 100644 --- a/src/data/system_health.ts +++ b/src/data/system_health.ts @@ -1,24 +1,111 @@ import { HomeAssistant } from "../types"; -export interface HomeAssistantSystemHealthInfo { - version: string; - dev: boolean; - hassio: boolean; - virtualenv: string; - python_version: string; - docker: boolean; - arch: string; - timezone: string; - os_name: string; +interface SystemCheckValueDateObject { + type: "date"; + value: string; } +interface SystemCheckValueErrorObject { + type: "failed"; + error: string; + more_info?: string; +} + +interface SystemCheckValuePendingObject { + type: "pending"; +} + +export type SystemCheckValueObject = + | SystemCheckValueDateObject + | SystemCheckValueErrorObject + | SystemCheckValuePendingObject; + +export type SystemCheckValue = + | string + | number + | boolean + | SystemCheckValueObject; + export interface SystemHealthInfo { - [domain: string]: { [key: string]: string | number | boolean }; + [domain: string]: { + manage_url?: string; + info: { + [key: string]: SystemCheckValue; + }; + }; +} + +interface SystemHealthEventInitial { + type: "initial"; + data: SystemHealthInfo; +} +interface SystemHealthEventUpdateSuccess { + type: "update"; + success: true; + domain: string; + key: string; + data: SystemCheckValue; } -export const fetchSystemHealthInfo = ( - hass: HomeAssistant -): Promise => - hass.callWS({ - type: "system_health/info", - }); +interface SystemHealthEventUpdateError { + type: "update"; + success: false; + domain: string; + key: string; + error: { + msg: string; + }; +} + +interface SystemHealthEventFinish { + type: "finish"; +} + +type SystemHealthEvent = + | SystemHealthEventInitial + | SystemHealthEventUpdateSuccess + | SystemHealthEventUpdateError + | SystemHealthEventFinish; + +export const subscribeSystemHealthInfo = ( + hass: HomeAssistant, + callback: (info: SystemHealthInfo) => void +) => { + let data = {}; + + const unsubProm = hass.connection.subscribeMessage( + (updateEvent) => { + if (updateEvent.type === "initial") { + data = updateEvent.data; + callback(data); + return; + } + if (updateEvent.type === "finish") { + unsubProm.then((unsub) => unsub()); + return; + } + + data = { + ...data, + [updateEvent.domain]: { + ...data[updateEvent.domain], + info: { + ...data[updateEvent.domain].info, + [updateEvent.key]: updateEvent.success + ? updateEvent.data + : { + error: true, + value: updateEvent.error.msg, + }, + }, + }, + }; + callback(data); + }, + { + type: "system_health/info", + } + ); + + return unsubProm; +}; diff --git a/src/dialogs/config-flow/dialog-data-entry-flow.ts b/src/dialogs/config-flow/dialog-data-entry-flow.ts index 79a95c2bc2be..2b5660bf89db 100644 --- a/src/dialogs/config-flow/dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/dialog-data-entry-flow.ts @@ -36,6 +36,7 @@ import "./step-flow-external"; import "./step-flow-form"; import "./step-flow-loading"; import "./step-flow-pick-handler"; +import "./step-flow-progress"; let instance = 0; @@ -195,6 +196,14 @@ class DataEntryFlowDialog extends LitElement { .hass=${this.hass} > ` + : this._step.type === "progress" + ? html` + + ` : this._devices === undefined || this._areas === undefined ? // When it's a create entry result, we will fetch device & area registry html` ` diff --git a/src/dialogs/config-flow/show-dialog-config-flow.ts b/src/dialogs/config-flow/show-dialog-config-flow.ts index 92bbb215976d..a42663ea6b9c 100644 --- a/src/dialogs/config-flow/show-dialog-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-config-flow.ts @@ -160,4 +160,21 @@ export const showConfigFlowDialog = (

`; }, + + renderShowFormProgressHeader(hass, step) { + return hass.localize(`component.${step.handler}.title`); + }, + + renderShowFormProgressDescription(hass, step) { + const description = localizeKey( + hass.localize, + `component.${step.handler}.config.progress.${step.progress_action}`, + step.description_placeholders + ); + return description + ? html` + + ` + : ""; + }, }); diff --git a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts index 401c2a43e7f8..8c18bfb28242 100644 --- a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts @@ -7,6 +7,7 @@ import { DataEntryFlowStepCreateEntry, DataEntryFlowStepExternal, DataEntryFlowStepForm, + DataEntryFlowStepProgress, } from "../../data/data_entry_flow"; import { HomeAssistant } from "../../types"; @@ -68,6 +69,16 @@ export interface FlowConfig { hass: HomeAssistant, step: DataEntryFlowStepCreateEntry ): TemplateResult | ""; + + renderShowFormProgressHeader( + hass: HomeAssistant, + step: DataEntryFlowStepProgress + ): string; + + renderShowFormProgressDescription( + hass: HomeAssistant, + step: DataEntryFlowStepProgress + ): TemplateResult | ""; } export interface DataEntryFlowDialogParams { diff --git a/src/dialogs/config-flow/show-dialog-options-flow.ts b/src/dialogs/config-flow/show-dialog-options-flow.ts index c3f228e56807..1113b939a6e5 100644 --- a/src/dialogs/config-flow/show-dialog-options-flow.ts +++ b/src/dialogs/config-flow/show-dialog-options-flow.ts @@ -110,5 +110,13 @@ export const showOptionsFlowDialog = (

${hass.localize(`ui.dialogs.options_flow.success.description`)}

`; }, + + renderShowFormProgressHeader(_hass, _step) { + return ""; + }, + + renderShowFormProgressDescription(_hass, _step) { + return ""; + }, } ); diff --git a/src/dialogs/config-flow/step-flow-progress.ts b/src/dialogs/config-flow/step-flow-progress.ts new file mode 100644 index 000000000000..57ba59560c19 --- /dev/null +++ b/src/dialogs/config-flow/step-flow-progress.ts @@ -0,0 +1,82 @@ +import "@material/mwc-button"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import { fireEvent } from "../../common/dom/fire_event"; +import "../../components/ha-circular-progress"; +import { + DataEntryFlowProgressedEvent, + DataEntryFlowStepProgress, +} from "../../data/data_entry_flow"; +import { HomeAssistant } from "../../types"; +import { FlowConfig } from "./show-dialog-data-entry-flow"; +import { configFlowContentStyles } from "./styles"; + +@customElement("step-flow-progress") +class StepFlowProgress extends LitElement { + public flowConfig!: FlowConfig; + + @property({ attribute: false }) + public hass!: HomeAssistant; + + @property({ attribute: false }) + private step!: DataEntryFlowStepProgress; + + protected render(): TemplateResult { + return html` +

+ ${this.flowConfig.renderShowFormProgressHeader(this.hass, this.step)} +

+
+ + ${this.flowConfig.renderShowFormProgressDescription( + this.hass, + this.step + )} +
+ `; + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this.hass.connection.subscribeEvents( + async (ev) => { + if (ev.data.flow_id !== this.step.flow_id) { + return; + } + + fireEvent(this, "flow-update", { + stepPromise: this.flowConfig.fetchFlow(this.hass, this.step.flow_id), + }); + }, + "data_entry_flow_progressed" + ); + } + + static get styles(): CSSResult[] { + return [ + configFlowContentStyles, + css` + .content { + padding: 50px 100px; + text-align: center; + } + ha-circular-progress { + margin-bottom: 16px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "step-flow-progress": StepFlowProgress; + } +} diff --git a/src/dialogs/more-info/controls/more-info-input_datetime.js b/src/dialogs/more-info/controls/more-info-input_datetime.js index f00a36282033..b2e8c4453ec4 100644 --- a/src/dialogs/more-info/controls/more-info-input_datetime.js +++ b/src/dialogs/more-info/controls/more-info-input_datetime.js @@ -3,7 +3,7 @@ import "@polymer/paper-input/paper-input"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker"; +import "../../../components/ha-date-input"; import { attributeClassNames } from "../../../common/entity/attribute_class_names"; import "../../../components/ha-relative-time"; import "../../../components/paper-time-input"; @@ -14,12 +14,12 @@ class DatetimeInput extends PolymerElement {