Manage Alexa entities (#3269)

* Reorg cloud components

* Allow managing Alexa entities in the UI

* Use observer

* Update mwc version

* Tweak some UI
This commit is contained in:
Paulus Schoutsen 2019-06-13 11:57:56 -07:00 committed by GitHub
parent 03fee95f68
commit 1b441a752e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 573 additions and 299 deletions

View File

@ -17,8 +17,9 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@material/mwc-button": "^0.5.0",
"@material/mwc-ripple": "^0.5.0",
"@material/mwc-base": "^0.6.0",
"@material/mwc-button": "^0.6.0",
"@material/mwc-ripple": "^0.6.0",
"@mdi/svg": "3.5.95",
"@polymer/app-layout": "^3.0.2",
"@polymer/app-localize-behavior": "^3.0.1",
@ -77,13 +78,13 @@
"fuse.js": "^3.4.4",
"google-timezones-json": "^1.0.2",
"hls.js": "^0.12.4",
"home-assistant-js-websocket": "^4.2.1",
"home-assistant-js-websocket": "^4.2.2",
"intl-messageformat": "^2.2.0",
"jquery": "^3.3.1",
"js-yaml": "^3.13.0",
"leaflet": "^1.4.0",
"lit-element": "^2.1.0",
"lit-html": "^1.0.0",
"lit-element": "^2.2.0",
"lit-html": "^1.1.0",
"marked": "^0.6.1",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",

10
src/data/alexa.ts Normal file
View File

@ -0,0 +1,10 @@
import { HomeAssistant } from "../types";
export interface AlexaEntity {
entity_id: string;
display_categories: string[];
interfaces: string[];
}
export const fetchCloudAlexaEntities = (hass: HomeAssistant) =>
hass.callWS<AlexaEntity[]>({ type: "cloud/alexa/entities" });

View File

@ -13,6 +13,10 @@ export interface GoogleEntityConfig {
disable_2fa?: boolean;
}
export interface AlexaEntityConfig {
should_expose?: boolean;
}
export interface CertificateInformation {
common_name: string;
expire_date: string;
@ -28,6 +32,9 @@ export interface CloudPreferences {
google_entity_configs: {
[entityId: string]: GoogleEntityConfig;
};
alexa_entity_configs: {
[entityId: string]: AlexaEntityConfig;
};
}
export type CloudStatusLoggedIn = CloudStatusBase & {
@ -55,12 +62,6 @@ export interface CloudWebhook {
managed?: boolean;
}
export interface GoogleEntity {
entity_id: string;
traits: string[];
might_2fa: boolean;
}
export const fetchCloudStatus = (hass: HomeAssistant) =>
hass.callWS<CloudStatus>({ type: "cloud/status" });
@ -102,9 +103,6 @@ export const updateCloudPref = (
...prefs,
});
export const fetchCloudGoogleEntities = (hass: HomeAssistant) =>
hass.callWS<GoogleEntity[]>({ type: "cloud/google_assistant/entities" });
export const updateCloudGoogleEntityConfig = (
hass: HomeAssistant,
entityId: string,
@ -118,3 +116,14 @@ export const updateCloudGoogleEntityConfig = (
export const cloudSyncGoogleAssistant = (hass: HomeAssistant) =>
hass.callApi("POST", "cloud/google_actions/sync");
export const updateCloudAlexaEntityConfig = (
hass: HomeAssistant,
entityId: string,
values: AlexaEntityConfig
) =>
hass.callWS<AlexaEntityConfig>({
type: "cloud/alexa/entities/update",
entity_id: entityId,
...values,
});

View File

@ -0,0 +1,10 @@
import { HomeAssistant } from "../types";
export interface GoogleEntity {
entity_id: string;
traits: string[];
might_2fa: boolean;
}
export const fetchCloudGoogleEntities = (hass: HomeAssistant) =>
hass.callWS<GoogleEntity[]>({ type: "cloud/google_assistant/entities" });

View File

@ -4,30 +4,27 @@ import "@polymer/paper-toggle-button/paper-toggle-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-card";
import "../../../components/buttons/ha-call-api-button";
import "../../../layouts/hass-subpage";
import "../../../resources/ha-style";
import "../../../../components/ha-card";
import "../../../../components/buttons/ha-call-api-button";
import "../../../../layouts/hass-subpage";
import "../../../../resources/ha-style";
import "../ha-config-section";
import "../../ha-config-section";
import "./cloud-webhooks";
import formatDateTime from "../../../common/datetime/format_date_time";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
import { fireEvent } from "../../../common/dom/fire_event";
import { fetchCloudSubscriptionInfo } from "../../../data/cloud";
import formatDateTime from "../../../../common/datetime/format_date_time";
import { EventsMixin } from "../../../../mixins/events-mixin";
import LocalizeMixin from "../../../../mixins/localize-mixin";
import { fetchCloudSubscriptionInfo } from "../../../../data/cloud";
import "./cloud-alexa-pref";
import "./cloud-google-pref";
import "./cloud-remote-pref";
let registeredWebhookDialog = false;
/*
* @appliesMixin EventsMixin
* @appliesMixin LocalizeMixin
*/
class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style include="iron-flex ha-style">
@ -163,20 +160,6 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
this._fetchSubscriptionInfo();
}
connectedCallback() {
super.connectedCallback();
if (!registeredWebhookDialog) {
registeredWebhookDialog = true;
fireEvent(this, "register-dialog", {
dialogShowEvent: "manage-cloud-webhook",
dialogTag: "cloud-webhook-manage-dialog",
dialogImport: () =>
import(/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./cloud-webhook-manage-dialog"),
});
}
}
_computeRemoteConnected(connected) {
return connected ? "Connected" : "Not Connected";
}
@ -219,4 +202,4 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
}
}
customElements.define("ha-config-cloud-account", HaConfigCloudAccount);
customElements.define("cloud-account", CloudAccount);

View File

@ -11,12 +11,11 @@ import "@polymer/paper-toggle-button/paper-toggle-button";
// tslint:disable-next-line
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
import "../../../components/ha-card";
import "../../../../components/ha-card";
import { fireEvent } from "../../../common/dom/fire_event";
import { HomeAssistant } from "../../../types";
import "./cloud-exposed-entities";
import { CloudStatusLoggedIn, updateCloudPref } from "../../../data/cloud";
import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types";
import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud";
export class CloudAlexaPref extends LitElement {
public hass?: HomeAssistant;
@ -63,16 +62,12 @@ export class CloudAlexaPref extends LitElement {
>This integration requires an Alexa-enabled device like the Amazon
Echo.</em
>
${enabled
? html`
<p>Exposed entities:</p>
<cloud-exposed-entities
.hass="${this.hass}"
.filter="${this.cloudStatus!.alexa_entities}"
.supportedDomains="${this.cloudStatus!.alexa_domains}"
></cloud-exposed-entities>
`
: ""}
</div>
<div class="card-actions">
<div class="spacer"></div>
<a href="/config/cloud/alexa">
<mwc-button>Manage Entities</mwc-button>
</a>
</div>
</ha-card>
`;
@ -99,6 +94,15 @@ export class CloudAlexaPref extends LitElement {
right: 8px;
top: 32px;
}
.card-actions {
display: flex;
}
.card-actions a {
text-decoration: none;
}
.spacer {
flex-grow: 1;
}
`;
}
}

View File

@ -10,13 +10,13 @@ import "@material/mwc-button";
import "@polymer/paper-toggle-button/paper-toggle-button";
// tslint:disable-next-line
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
import "../../../components/buttons/ha-call-api-button";
import "../../../../components/buttons/ha-call-api-button";
import "../../../components/ha-card";
import "../../../../components/ha-card";
import { fireEvent } from "../../../common/dom/fire_event";
import { HomeAssistant } from "../../../types";
import { CloudStatusLoggedIn, updateCloudPref } from "../../../data/cloud";
import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types";
import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
export class CloudGooglePref extends LitElement {

View File

@ -13,16 +13,16 @@ import "@polymer/paper-item/paper-item-body";
// tslint:disable-next-line
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
import "../../../components/ha-card";
import "../../../../components/ha-card";
import { fireEvent } from "../../../common/dom/fire_event";
import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types";
import {
connectCloudRemote,
disconnectCloudRemote,
CloudStatusLoggedIn,
} from "../../../data/cloud";
import { showCloudCertificateDialog } from "./show-dialog-cloud-certificate";
} from "../../../../data/cloud";
import { showCloudCertificateDialog } from "../dialog-cloud-certificate/show-dialog-cloud-certificate";
@customElement("cloud-remote-pref")
export class CloudRemotePref extends LitElement {

View File

@ -8,17 +8,17 @@ import "@polymer/paper-toggle-button/paper-toggle-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-spinner/paper-spinner";
import "../../../components/ha-card";
import "../../../../components/ha-card";
import { HomeAssistant, WebhookError } from "../../../types";
import { Webhook, fetchWebhooks } from "../../../data/webhook";
import { HomeAssistant, WebhookError } from "../../../../types";
import { Webhook, fetchWebhooks } from "../../../../data/webhook";
import {
createCloudhook,
deleteCloudhook,
CloudWebhook,
CloudStatusLoggedIn,
} from "../../../data/cloud";
import { showManageCloudhookDialog } from "./show-cloud-webhook-manage-dialog";
} from "../../../../data/cloud";
import { showManageCloudhookDialog } from "../dialog-manage-cloudhook/show-dialog-manage-cloudhook";
export class CloudWebhooks extends LitElement {
public hass?: HomeAssistant;

View File

@ -0,0 +1,361 @@
import {
LitElement,
TemplateResult,
html,
CSSResult,
css,
customElement,
property,
} from "lit-element";
import "@polymer/paper-toggle-button";
import "@polymer/paper-icon-button";
import { observer } from "@material/mwc-base/observer";
import "../../../../layouts/hass-subpage";
import "../../../../layouts/hass-loading-screen";
import "../../../../components/ha-card";
import "../../../../components/entity/state-info";
import { HomeAssistant } from "../../../../types";
import {
CloudStatusLoggedIn,
CloudPreferences,
updateCloudAlexaEntityConfig,
AlexaEntityConfig,
} from "../../../../data/cloud";
import memoizeOne from "memoize-one";
import {
generateFilter,
isEmptyFilter,
EntityFilter,
} from "../../../../common/entity/entity_filter";
import { compare } from "../../../../common/string/compare";
import computeStateName from "../../../../common/entity/compute_state_name";
import { fireEvent } from "../../../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
import computeDomain from "../../../../common/entity/compute_domain";
import { AlexaEntity, fetchCloudAlexaEntities } from "../../../../data/alexa";
const DEFAULT_CONFIG_EXPOSE = true;
const IGNORE_INTERFACES = ["Alexa.EndpointHealth"];
const configIsExposed = (config: AlexaEntityConfig) =>
config.should_expose === undefined
? DEFAULT_CONFIG_EXPOSE
: config.should_expose;
@customElement("cloud-alexa")
class CloudAlexa extends LitElement {
@property() public hass!: HomeAssistant;
@property()
@observer(function(this: CloudAlexa, value) {
this._entityConfigs = value.prefs.alexa_entity_configs;
})
public cloudStatus!: CloudStatusLoggedIn;
@property({ type: Boolean }) public narrow!: boolean;
@property() private _entities?: AlexaEntity[];
@property()
private _entityConfigs: CloudPreferences["alexa_entity_configs"] = {};
private _popstateSyncAttached = false;
private _popstateReloadStatusAttached = false;
private _isInitialExposed?: Set<string>;
private _getEntityFilterFunc = memoizeOne((filter: EntityFilter) =>
generateFilter(
filter.include_domains,
filter.include_entities,
filter.exclude_domains,
filter.exclude_entities
)
);
protected render(): TemplateResult | void {
if (this._entities === undefined) {
return html`
<hass-loading-screen></hass-loading-screen>
`;
}
const emptyFilter = isEmptyFilter(this.cloudStatus.alexa_entities);
const filterFunc = this._getEntityFilterFunc(
this.cloudStatus.alexa_entities
);
// We will only generate `isInitialExposed` during first render.
// On each subsequent render we will use the same set so that cards
// will not jump around when we change the exposed setting.
const showInExposed = this._isInitialExposed || new Set();
const trackExposed = this._isInitialExposed === undefined;
let selected = 0;
// On first render we decide which cards show in which category.
// That way cards won't jump around when changing values.
const exposedCards: TemplateResult[] = [];
const notExposedCards: TemplateResult[] = [];
this._entities.forEach((entity) => {
const stateObj = this.hass.states[entity.entity_id];
const config = this._entityConfigs[entity.entity_id] || {};
const isExposed = emptyFilter
? configIsExposed(config)
: filterFunc(entity.entity_id);
if (isExposed) {
selected++;
if (trackExposed) {
showInExposed.add(entity.entity_id);
}
}
const target = showInExposed.has(entity.entity_id)
? exposedCards
: notExposedCards;
target.push(html`
<ha-card>
<div class="card-content">
<state-info
.hass=${this.hass}
.stateObj=${stateObj}
secondary-line
@click=${this._showMoreInfo}
>
${entity.interfaces
.filter((ifc) => !IGNORE_INTERFACES.includes(ifc))
.map((ifc) =>
ifc.replace("Alexa.", "").replace("Controller", "")
)
.join(", ")}
</state-info>
<paper-toggle-button
.entityId=${entity.entity_id}
.disabled=${!emptyFilter}
.checked=${isExposed}
@checked-changed=${this._exposeChanged}
>
Expose to Alexa
</paper-toggle-button>
</div>
</ha-card>
`);
});
if (trackExposed) {
this._isInitialExposed = showInExposed;
}
return html`
<hass-subpage header="Alexa">
<span slot="toolbar-icon">
${selected}${
!this.narrow
? html`
selected
`
: ""
}
</span>
${
emptyFilter
? html`
<paper-icon-button
slot="toolbar-icon"
icon="hass:tune"
@click=${this._openDomainToggler}
></paper-icon-button>
`
: ""
}
${
!emptyFilter
? html`
<div class="banner">
Editing which entities are exposed via this UI is disabled
because you have configured entity filters in
configuration.yaml.
</div>
`
: ""
}
${
exposedCards.length > 0
? html`
<h1>Exposed entities</h1>
<div class="content">${exposedCards}</div>
`
: ""
}
${
notExposedCards.length > 0
? html`
<h1>Not Exposed entities</h1>
<div class="content">${notExposedCards}</div>
`
: ""
}
</div>
</hass-subpage>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._fetchData();
}
private async _fetchData() {
const entities = await fetchCloudAlexaEntities(this.hass);
entities.sort((a, b) => {
const stateA = this.hass.states[a.entity_id];
const stateB = this.hass.states[b.entity_id];
return compare(
stateA ? computeStateName(stateA) : a.entity_id,
stateB ? computeStateName(stateB) : b.entity_id
);
});
this._entities = entities;
}
private _showMoreInfo(ev) {
const entityId = ev.currentTarget.stateObj.entity_id;
fireEvent(this, "hass-more-info", { entityId });
}
private async _exposeChanged(ev: PolymerChangedEvent<boolean>) {
const entityId = (ev.currentTarget as any).entityId;
const newExposed = ev.detail.value;
await this._updateExposed(entityId, newExposed);
}
private async _updateExposed(entityId: string, newExposed: boolean) {
const curExposed = configIsExposed(this._entityConfigs[entityId] || {});
if (newExposed === curExposed) {
return;
}
await this._updateConfig(entityId, {
should_expose: newExposed,
});
this._ensureEntitySync();
}
private async _updateConfig(entityId: string, values: AlexaEntityConfig) {
const updatedConfig = await updateCloudAlexaEntityConfig(
this.hass,
entityId,
values
);
this._entityConfigs = {
...this._entityConfigs,
[entityId]: updatedConfig,
};
this._ensureStatusReload();
}
private _openDomainToggler() {
showDomainTogglerDialog(this, {
domains: this._entities!.map((entity) =>
computeDomain(entity.entity_id)
).filter((value, idx, self) => self.indexOf(value) === idx),
toggleDomain: (domain, turnOn) => {
this._entities!.forEach((entity) => {
if (computeDomain(entity.entity_id) === domain) {
this._updateExposed(entity.entity_id, turnOn);
}
});
},
});
}
private _ensureStatusReload() {
if (this._popstateReloadStatusAttached) {
return;
}
this._popstateReloadStatusAttached = true;
// Cache parent because by the time popstate happens,
// this element is detached
const parent = this.parentElement!;
window.addEventListener(
"popstate",
() => fireEvent(parent, "ha-refresh-cloud-status"),
{ once: true }
);
}
private _ensureEntitySync() {
if (this._popstateSyncAttached) {
return;
}
this._popstateSyncAttached = true;
// Cache parent because by the time popstate happens,
// this element is detached
// const parent = this.parentElement!;
window.addEventListener(
"popstate",
() => {
// We don't have anything yet.
// showToast(parent, { message: "Synchronizing changes to Google." });
// cloudSyncGoogleAssistant(this.hass);
},
{ once: true }
);
}
static get styles(): CSSResult {
return css`
.banner {
color: var(--primary-text-color);
background-color: var(
--ha-card-background,
var(--paper-card-background-color, white)
);
padding: 16px 8px;
text-align: center;
}
h1 {
color: var(--primary-text-color);
font-size: 24px;
letter-spacing: -0.012em;
margin-bottom: 0;
padding: 0 8px;
}
.content {
display: flex;
flex-wrap: wrap;
padding: 4px;
--paper-toggle-button-label-spacing: 16px;
}
paper-toggle-button {
clear: both;
}
ha-card {
margin: 4px;
width: 100%;
max-width: 300px;
}
.card-content {
padding-bottom: 12px;
}
state-info {
cursor: pointer;
}
paper-toggle-button {
padding: 8px 0;
}
@media all and (max-width: 450px) {
ha-card {
max-width: 100%;
}
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"cloud-alexa": CloudAlexa;
}
}

View File

@ -1,115 +0,0 @@
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
} from "lit-element";
import { repeat } from "lit-html/directives/repeat";
import "@polymer/paper-tooltip/paper-tooltip";
import { HassEntityBase } from "home-assistant-js-websocket";
import "../../../components/entity/ha-state-icon";
import { fireEvent } from "../../../common/dom/fire_event";
import { HomeAssistant } from "../../../types";
import computeStateName from "../../../common/entity/compute_state_name";
import {
FilterFunc,
generateFilter,
EntityFilter,
} from "../../../common/entity/entity_filter";
export class CloudExposedEntities extends LitElement {
public hass?: HomeAssistant;
public filter?: EntityFilter;
public supportedDomains?: string[];
private _filterFunc?: FilterFunc;
static get properties(): PropertyDeclarations {
return {
hass: {},
filter: {},
supportedDomains: {},
_filterFunc: {},
};
}
protected render(): TemplateResult | void {
if (!this._filterFunc) {
return html``;
}
const states: Array<[string, HassEntityBase]> = [];
Object.keys(this.hass!.states).forEach((entityId) => {
if (this._filterFunc!(entityId)) {
const stateObj = this.hass!.states[entityId];
states.push([computeStateName(stateObj), stateObj]);
}
});
states.sort();
return html`
${this.renderStyle()}
${repeat(
states!,
(stateInfo) => stateInfo[1].entity_id,
(stateInfo) => html`
<span>
<ha-state-icon
.stateObj="${stateInfo[1]}"
@click="${this._handleMoreInfo}"
></ha-state-icon>
<paper-tooltip position="bottom">${stateInfo[0]}</paper-tooltip>
</span>
`
)}
`;
}
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (
changedProperties.has("filter") &&
changedProperties.get("filter") !== this.filter
) {
const filter = this.filter!;
const filterFunc = generateFilter(
filter.include_domains,
filter.include_entities,
filter.exclude_domains,
filter.exclude_entities
);
const domains = new Set(this.supportedDomains);
this._filterFunc = (entityId: string) => {
const domain = entityId.split(".")[0];
return domains.has(domain) && filterFunc(entityId);
};
}
}
private _handleMoreInfo(ev: MouseEvent) {
fireEvent(this, "hass-more-info", {
entityId: (ev.currentTarget as any).stateObj.entity_id,
});
}
private renderStyle(): TemplateResult {
return html`
<style>
ha-state-icon {
color: var(--primary-text-color);
cursor: pointer;
}
</style>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"cloud-exposed-entities": CloudExposedEntities;
}
}
customElements.define("cloud-exposed-entities", CloudExposedEntities);

View File

@ -8,15 +8,15 @@ import {
} from "lit-element";
import "@material/mwc-button";
import "../../../components/dialog/ha-paper-dialog";
import "../../../../components/dialog/ha-paper-dialog";
// This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line
import { HaPaperDialog } from "../../../components/dialog/ha-paper-dialog";
import { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
import { HomeAssistant } from "../../../types";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import { haStyle } from "../../../../resources/styles";
import { CloudCertificateParams as CloudCertificateDialogParams } from "./show-dialog-cloud-certificate";
import format_date_time from "../../../common/datetime/format_date_time";
import format_date_time from "../../../../common/datetime/format_date_time";
@customElement("dialog-cloud-certificate")
class DialogCloudCertificate extends LitElement {

View File

@ -1,5 +1,5 @@
import { fireEvent } from "../../../common/dom/fire_event";
import { CertificateInformation } from "../../../data/cloud";
import { fireEvent } from "../../../../common/dom/fire_event";
import { CertificateInformation } from "../../../../data/cloud";
export interface CloudCertificateParams {
certificateInfo: CertificateInformation;

View File

@ -9,20 +9,20 @@ import {
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "../../../components/dialog/ha-paper-dialog";
import "../../../../components/dialog/ha-paper-dialog";
// This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line
import { HaPaperDialog } from "../../../components/dialog/ha-paper-dialog";
import { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
// tslint:disable-next-line
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { HomeAssistant } from "../../../types";
import { haStyle } from "../../../resources/styles";
import { WebhookDialogParams } from "./show-cloud-webhook-manage-dialog";
import { HomeAssistant } from "../../../../types";
import { haStyle } from "../../../../resources/styles";
import { WebhookDialogParams } from "./show-dialog-manage-cloudhook";
const inputLabel = "Public URL Click to copy to clipboard";
export class CloudWebhookManageDialog extends LitElement {
export class DialogManageCloudhook extends LitElement {
protected hass?: HomeAssistant;
private _params?: WebhookDialogParams;
@ -75,9 +75,9 @@ export class CloudWebhookManageDialog extends LitElement {
</div>
<div class="paper-dialog-buttons">
<a href="${docsUrl}" target="_blank"
><mwc-button>VIEW DOCUMENTATION</mwc-button></a
>
<a href="${docsUrl}" target="_blank">
<mwc-button>VIEW DOCUMENTATION</mwc-button>
</a>
<mwc-button @click="${this._closeDialog}">CLOSE</mwc-button>
</div>
</ha-paper-dialog>
@ -136,6 +136,9 @@ export class CloudWebhookManageDialog extends LitElement {
button.link {
color: var(--primary-color);
}
.paper-dialog-buttons a {
text-decoration: none;
}
`,
];
}
@ -143,8 +146,8 @@ export class CloudWebhookManageDialog extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"cloud-webhook-manage-dialog": CloudWebhookManageDialog;
"dialog-manage-cloudhook": DialogManageCloudhook;
}
}
customElements.define("cloud-webhook-manage-dialog", CloudWebhookManageDialog);
customElements.define("dialog-manage-cloudhook", DialogManageCloudhook);

View File

@ -1,6 +1,6 @@
import { fireEvent } from "../../../common/dom/fire_event";
import { Webhook } from "../../../data/webhook";
import { CloudWebhook } from "../../../data/cloud";
import { fireEvent } from "../../../../common/dom/fire_event";
import { Webhook } from "../../../../data/webhook";
import { CloudWebhook } from "../../../../data/cloud";
export interface WebhookDialogParams {
webhook: Webhook;
@ -13,9 +13,9 @@ export const showManageCloudhookDialog = (
webhookDialogParams: WebhookDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "cloud-webhook-manage-dialog",
dialogTag: "dialog-manage-cloudhook",
dialogImport: () =>
import(/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./cloud-webhook-manage-dialog"),
import(/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./dialog-manage-cloudhook"),
dialogParams: webhookDialogParams,
});
};

View File

@ -2,16 +2,16 @@ import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-card";
import "../../../components/buttons/ha-progress-button";
import "../../../layouts/hass-subpage";
import "../../../resources/ha-style";
import { EventsMixin } from "../../../mixins/events-mixin";
import "../../../../components/ha-card";
import "../../../../components/buttons/ha-progress-button";
import "../../../../layouts/hass-subpage";
import "../../../../resources/ha-style";
import { EventsMixin } from "../../../../mixins/events-mixin";
/*
* @appliesMixin EventsMixin
*/
class HaConfigCloudForgotPassword extends EventsMixin(PolymerElement) {
class CloudForgotPassword extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
@ -141,7 +141,4 @@ class HaConfigCloudForgotPassword extends EventsMixin(PolymerElement) {
}
}
customElements.define(
"ha-config-cloud-forgot-password",
HaConfigCloudForgotPassword
);
customElements.define("cloud-forgot-password", CloudForgotPassword);

View File

@ -9,33 +9,35 @@ import {
} from "lit-element";
import "@polymer/paper-toggle-button";
import "@polymer/paper-icon-button";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-loading-screen";
import "../../../components/ha-card";
import "../../../components/entity/state-info";
import { HomeAssistant } from "../../../types";
import "../../../../layouts/hass-subpage";
import "../../../../layouts/hass-loading-screen";
import "../../../../components/ha-card";
import "../../../../components/entity/state-info";
import { HomeAssistant } from "../../../../types";
import {
GoogleEntity,
fetchCloudGoogleEntities,
CloudStatusLoggedIn,
CloudPreferences,
updateCloudGoogleEntityConfig,
cloudSyncGoogleAssistant,
GoogleEntityConfig,
} from "../../../data/cloud";
} from "../../../../data/cloud";
import memoizeOne from "memoize-one";
import {
generateFilter,
isEmptyFilter,
EntityFilter,
} from "../../../common/entity/entity_filter";
import { compare } from "../../../common/string/compare";
import computeStateName from "../../../common/entity/compute_state_name";
import { fireEvent } from "../../../common/dom/fire_event";
import { showToast } from "../../../util/toast";
import { PolymerChangedEvent } from "../../../polymer-types";
import { showDomainTogglerDialog } from "../../../dialogs/domain-toggler/show-dialog-domain-toggler";
import computeDomain from "../../../common/entity/compute_domain";
} from "../../../../common/entity/entity_filter";
import { compare } from "../../../../common/string/compare";
import computeStateName from "../../../../common/entity/compute_state_name";
import { fireEvent } from "../../../../common/dom/fire_event";
import { showToast } from "../../../../util/toast";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
import computeDomain from "../../../../common/entity/compute_domain";
import {
GoogleEntity,
fetchCloudGoogleEntities,
} from "../../../../data/google_assistant";
const DEFAULT_CONFIG_EXPOSE = true;
@ -44,7 +46,7 @@ const configIsExposed = (config: GoogleEntityConfig) =>
? DEFAULT_CONFIG_EXPOSE
: config.should_expose;
@customElement("ha-config-cloud-google-assistant")
@customElement("cloud-google-assistant")
class CloudGoogleAssistant extends LitElement {
@property() public hass!: HomeAssistant;
@property() public cloudStatus!: CloudStatusLoggedIn;
@ -159,11 +161,17 @@ class CloudGoogleAssistant extends LitElement {
: ""
}
</span>
<paper-icon-button
slot="toolbar-icon"
icon="hass:tune"
@click=${this._openDomainToggler}
></paper-icon-button>
${
emptyFilter
? html`
<paper-icon-button
slot="toolbar-icon"
icon="hass:tune"
@click=${this._openDomainToggler}
></paper-icon-button>
`
: ""
}
${
!emptyFilter
? html`
@ -175,7 +183,6 @@ class CloudGoogleAssistant extends LitElement {
`
: ""
}
${
exposedCards.length > 0
? html`
@ -369,6 +376,6 @@ class CloudGoogleAssistant extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"ha-config-cloud-google-assistant": CloudGoogleAssistant;
"cloud-google-assistant": CloudGoogleAssistant;
}
}

View File

@ -1,5 +1,5 @@
import "./ha-config-cloud-account";
import "./ha-config-cloud-login";
import "./account/cloud-account";
import "./login/cloud-login";
import {
HassRouterPage,
RouterOptions,
@ -11,7 +11,7 @@ import { CloudStatus } from "../../../data/cloud";
import { PolymerChangedEvent } from "../../../polymer-types";
import { PolymerElement } from "@polymer/polymer";
const LOGGED_IN_URLS = ["account", "google-assistant"];
const LOGGED_IN_URLS = ["account", "google-assistant", "alexa"];
const NOT_LOGGED_IN_URLS = ["login", "register", "forgot-password"];
@customElement("ha-config-cloud")
@ -41,22 +41,26 @@ class HaConfigCloud extends HassRouterPage {
},
routes: {
login: {
tag: "ha-config-cloud-login",
tag: "cloud-login",
},
register: {
tag: "ha-config-cloud-register",
load: () => import("./ha-config-cloud-register"),
tag: "cloud-register",
load: () => import("./register/cloud-register"),
},
"forgot-password": {
tag: "ha-config-cloud-forgot-password",
load: () => import("./ha-config-cloud-forgot-password"),
tag: "cloud-forgot-password",
load: () => import("./forgot-password/cloud-forgot-password"),
},
account: {
tag: "ha-config-cloud-account",
tag: "cloud-account",
},
"google-assistant": {
tag: "ha-config-cloud-google-assistant",
load: () => import("./ha-config-cloud-google-assistant"),
tag: "cloud-google-assistant",
load: () => import("./google-assistant/cloud-google-assistant"),
},
alexa: {
tag: "cloud-alexa",
load: () => import("./alexa/cloud-alexa"),
},
},
};

View File

@ -7,20 +7,20 @@ import "@polymer/paper-ripple/paper-ripple";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-card";
import "../../../components/buttons/ha-progress-button";
import "../../../layouts/hass-subpage";
import "../../../resources/ha-style";
import "../../../../components/ha-card";
import "../../../../components/buttons/ha-progress-button";
import "../../../../layouts/hass-subpage";
import "../../../../resources/ha-style";
import "../ha-config-section";
import { EventsMixin } from "../../../mixins/events-mixin";
import NavigateMixin from "../../../mixins/navigate-mixin";
import "../../../components/ha-icon-next";
import "../../ha-config-section";
import { EventsMixin } from "../../../../mixins/events-mixin";
import NavigateMixin from "../../../../mixins/navigate-mixin";
import "../../../../components/ha-icon-next";
/*
* @appliesMixin NavigateMixin
* @appliesMixin EventsMixin
*/
class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
class CloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
static get template() {
return html`
<style include="iron-flex ha-style">
@ -292,4 +292,4 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
}
}
customElements.define("ha-config-cloud-login", HaConfigCloudLogin);
customElements.define("cloud-login", CloudLogin);

View File

@ -2,17 +2,17 @@ import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-card";
import "../../../components/buttons/ha-progress-button";
import "../../../layouts/hass-subpage";
import "../../../resources/ha-style";
import "../ha-config-section";
import { EventsMixin } from "../../../mixins/events-mixin";
import "../../../../components/ha-card";
import "../../../../components/buttons/ha-progress-button";
import "../../../../layouts/hass-subpage";
import "../../../../resources/ha-style";
import "../../ha-config-section";
import { EventsMixin } from "../../../../mixins/events-mixin";
/*
* @appliesMixin EventsMixin
*/
class HaConfigCloudRegister extends EventsMixin(PolymerElement) {
class CloudRegister extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
@ -217,4 +217,4 @@ class HaConfigCloudRegister extends EventsMixin(PolymerElement) {
}
}
customElements.define("ha-config-cloud-register", HaConfigCloudRegister);
customElements.define("cloud-register", CloudRegister);

View File

@ -774,38 +774,38 @@
resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-0.44.1.tgz#afafc80294e5efab94bee31a187273d43d34979a"
integrity sha512-90cc7njn4aHbH9UxY8qgZth1W5JgOgcEdWdubH1t7sFkwqFxS5g3zgxSBt46TygFBVIXNZNq35Xmg80wgqO7Pg==
"@material/mwc-base@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@material/mwc-base/-/mwc-base-0.5.0.tgz#21f9f237acea444b07a0fb1dfd45d30ef8f62374"
integrity sha512-G0n5LCmeXs9QR/ptHgUmV043wUVN2Uq1CWxUeGisGEPCI5e/mt9ISk5/7MubO25yF9muQeQXxXdRt/kFc7xGCg==
"@material/mwc-base@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@material/mwc-base/-/mwc-base-0.6.0.tgz#2077b5f94c3d8fa2a65736b02c3d380314ea1154"
integrity sha512-AiMEoU3dkLhuEnK5HJ0yMrdcyq5rUq9LdooxOSLMzPRr/ALT8YS14/oklufYiPagehzJcL0MeiyL40OlSpkyBA==
dependencies:
"@material/base" "^1.0.0"
lit-element "^2.0.1"
lit-html "^1.0.0"
"@material/mwc-button@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@material/mwc-button/-/mwc-button-0.5.0.tgz#3456bfd9ef7d91d96c623952cca974216bf47f3e"
integrity sha512-XFzMuEGCtiT80fw5B6TibyAq3LPTUKXlbvgGg8kRSFsWJUXLfZKzCp/J3oJRGxZeSut33HiYXoQ1FAq9cygXeA==
"@material/mwc-button@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@material/mwc-button/-/mwc-button-0.6.0.tgz#5264623ab7bfc80cc0a118ae9188a2c69b4a8e8c"
integrity sha512-oZTXXtg5z7tqvbFN5gMWsya/OU1ThEQ8ZZ/KN4PzDHGoYcjGLWWYTyDtarfP2VfJn+pRL0Ql5+l3i8j1i4Vr8Q==
dependencies:
"@material/button" "^1.0.0"
"@material/mwc-base" "^0.5.0"
"@material/mwc-icon" "^0.5.0"
"@material/mwc-ripple" "^0.5.0"
"@material/mwc-base" "^0.6.0"
"@material/mwc-icon" "^0.6.0"
"@material/mwc-ripple" "^0.6.0"
"@material/mwc-icon@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@material/mwc-icon/-/mwc-icon-0.5.0.tgz#432a1c5b7d817f1d04341b3c4bb8ef514cbd8cb6"
integrity sha512-vDzNc3sYPm9I5cG//TiYM5BTt3zW283zC8F5mx2rmtK3E7/UXRZmT6b94rv9em7mEUv86DedYPzCzND8oXctxA==
"@material/mwc-icon@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@material/mwc-icon/-/mwc-icon-0.6.0.tgz#67a71d95aa9a6d2379c5896673c2d0f1f2666e4e"
integrity sha512-Pm4nalbSfsgMz0K8dRaE2tBsyiCozrlgh9iEtBvRdYV6IzPJacXjqsf8+2faW3lfmh4PRLQzVJ7Fldm+ljxzBA==
dependencies:
"@material/mwc-base" "^0.5.0"
"@material/mwc-base" "^0.6.0"
"@material/mwc-ripple@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@material/mwc-ripple/-/mwc-ripple-0.5.0.tgz#6520971df2ff067ef52121c3cc0c4d8960d61977"
integrity sha512-vOB9ZpbBKnNsE+d4GoZIUoupk2am5kHBHqGkwSXXTt4c01SO0HRWkdNxayB2QAHgN56Xnsct0z0/pdfoZz7lKA==
"@material/mwc-ripple@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@material/mwc-ripple/-/mwc-ripple-0.6.0.tgz#602eb1855acd7e02d79398290ff223f9335976e3"
integrity sha512-K0b3VtKTlUd2RLaSJd6y9lBX47A84QjsK4eMn3PhDlWG7CkfhRf5XBZrOf/wzrqNf2/0w5of+8rFkohTraLHiw==
dependencies:
"@material/mwc-base" "^0.5.0"
"@material/mwc-base" "^0.6.0"
"@material/ripple" "^1.0.0"
lit-html "^1.0.0"
@ -1050,7 +1050,7 @@
"@polymer/iron-meta" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
"@polymer/iron-image@3.0.2", "@polymer/iron-image@^3.0.0-pre.26", "@polymer/iron-image@^3.0.1":
"@polymer/iron-image@^3.0.0-pre.26", "@polymer/iron-image@^3.0.1":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@polymer/iron-image/-/iron-image-3.0.2.tgz#425ee6269634e024dbea726a91a61724ae4402b6"
integrity sha512-VyYtnewGozDb5sUeoLR1OvKzlt5WAL6b8Od7fPpio5oYL+9t061/nTV8+ZMrpMgF2WgB0zqM/3K53o3pbK5v8Q==
@ -2378,7 +2378,7 @@
resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.9.1.tgz#d769fbadfa504f11b84caeef26701f89070ec49a"
integrity sha512-IaZOnWOKXHghqk/WfPNDRIgDBi3RsVPY2IFAw6tYiL9UBGvQRy5R6uC+Fk7qTZsReTJ0xh5MTT8yAcb3MUR4mQ==
"@webcomponents/webcomponentsjs@2.2.10", "@webcomponents/webcomponentsjs@^1.0.7", "@webcomponents/webcomponentsjs@^2.0.0", "@webcomponents/webcomponentsjs@^2.2.7":
"@webcomponents/webcomponentsjs@^1.0.7", "@webcomponents/webcomponentsjs@^2.0.0", "@webcomponents/webcomponentsjs@^2.2.10", "@webcomponents/webcomponentsjs@^2.2.7":
version "2.2.10"
resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.2.10.tgz#6f6bee0277833ae98d7e5b46f1e0fdb48cd5ff44"
integrity sha512-5dzhUhP+h0qMiK0IWb7VNb0OGBoXO3AuI6Qi8t9PoKT50s5L1jv0xnwnLq+cFgPuTB8FLTNP8xIDmyoOsKBy9Q==
@ -7247,7 +7247,7 @@ hoek@6.x.x:
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
home-assistant-js-websocket@^4.2.1:
home-assistant-js-websocket@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-4.2.2.tgz#e13b058a9e200bc56080e1b48fdeaaf1ed2e4e5f"
integrity sha512-4mXYbn2DCiDVBYGZROUSWLBDerSoDRJulw1GiQbhKEyrDhzFs5KQkcLdIu6k3CSDYQiiKQez5uAhOfb0Hr/M0A==
@ -8515,14 +8515,14 @@ listr@^0.14.2:
p-map "^2.0.0"
rxjs "^6.3.3"
lit-element@^2.0.1, lit-element@^2.1.0:
lit-element@^2.0.1, lit-element@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.2.0.tgz#e853be38021f0c7743a10180affdf84b8a02400c"
integrity sha512-Mzs3H7IO4wAnpzqreHw6dQqp9IG+h/oN8X9pgNbMZbE7x6B0aNOwP5Nveox/5HE+65ZfW2PeULEjoHkrwpTnuQ==
dependencies:
lit-html "^1.0.0"
lit-html@^1.0.0:
lit-html@^1.0.0, lit-html@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.1.0.tgz#6951fb717fb48fe34d915ae163448a04da321562"
integrity sha512-ZDJHpJi09yknMpjwPI8fuSl5sUG7+pF+eE5WciFtgyX7zebvgMDBgSLq4knXa7grxM00RkQ7PBd7UZQiruA78Q==