Compare commits

...

39 Commits

Author SHA1 Message Date
Bram Kragten c1eabeb29f Remove light card for generated mode 2020-01-17 21:53:38 +01:00
Paulus Schoutsen 5ff8fe68ba Alert user when add-on not started (#4485) 2020-01-17 17:02:56 +01:00
David F. Mulcahey a2a039ebc5 Add group binding to the ZHA config panel and misc. cleanup (#4466)
* clean up zha device card and usage

* group binding tile

* add cluster selection to group binding tile

* fix css class name

* fix filtering

* multiselect for clusters in group binding

* pass narrow to cluster table

* fix tables

* fix device page

* address remaing comments from previous PR

* fix bad cherry-pick

* css cleanup

* consistency

* use properties

* translations

* add confirmation dialog to remove button

* fix css

* review comments

* remove noise
2020-01-17 16:39:57 +01:00
Ian Richardson 1064aed1b0 📝 make some Lovelace UI text more clear (#4500) 2020-01-17 09:54:16 +01:00
Ian Richardson 7025592e8e 🐛 fix picture glance card's camera_view option in editor (#4495) 2020-01-17 09:44:57 +01:00
HomeAssistant Azure 4966354b62 [ci skip] Translation update 2020-01-17 00:32:37 +00:00
David F. Mulcahey 68d6faf4af fix selection check (#4488) 2020-01-16 18:19:01 +01:00
Paulus Schoutsen e3346483b9 Hide device trackers from generated lovelace (#4487) 2020-01-16 08:57:41 +01:00
HomeAssistant Azure e8fb79e5ce [ci skip] Translation update 2020-01-16 00:32:40 +00:00
Alexei Chetroi d612162ab1 Fix ZHA add device path. (#4486) 2020-01-15 20:05:05 +01:00
Bram Kragten 86f8ef3a70 Styling focus menus (#4483)
* Styling menus

* Update ha-config-navigation.ts
2020-01-15 19:41:56 +01:00
Bram Kragten 0e43435362 Don't ask to choose view when only 1 view (#4480) 2020-01-15 09:05:01 -08:00
Bram Kragten aaefe0b09f Handle unknown state (#4481) 2020-01-15 09:01:59 -08:00
Bram Kragten bc731a9dc3 Add edit btn to more info for scene, script and automation (#4476) 2020-01-15 09:50:16 +01:00
Bram Kragten da25701dca Disable adoptedStyleSheets in dev (#4474) 2020-01-15 09:25:17 +01:00
Bram Kragten 21ae483dc9 Styling fixes (#4475) 2020-01-15 09:25:04 +01:00
HomeAssistant Azure 38b6e9ca10 [ci skip] Translation update 2020-01-15 00:32:57 +00:00
Bram Kragten d31245866c Add DEPRECATED to states ui (#4463)
* Add DEPRECATED to states ui

* unelevated red

* target

* Add msg in info
2020-01-14 06:35:01 -08:00
Bram Kragten 4e08d8f3b3 Fix zha back btn (#4470) 2020-01-14 07:57:00 -05:00
Bram Kragten 1e717ab33e Catch undefined cloudstatus (#4465) 2020-01-14 13:52:23 +01:00
Bram Kragten 995fb4974e Fix translations (#4469) 2020-01-14 13:20:06 +01:00
HomeAssistant Azure ffb76132f8 [ci skip] Translation update 2020-01-14 00:32:29 +00:00
Bram Kragten acba3af54b Fix back btn for Polymer (#4467) 2020-01-13 18:21:43 +01:00
Paulus Schoutsen 40ac456937 Force refresh tokens if external app (#4461) 2020-01-13 05:47:08 -08:00
Bram Kragten 5c32413bf7 Onboarding core: Display error message when saving fails (#4462) 2020-01-13 05:31:53 -08:00
Bram Kragten 22792c70c5 Change config panel navigation (#4377)
* Change config panel navigation

* Show active + don't show toolbar?

* Update ha-panel-config.ts

* Change color of menu toolbar

* Update ha-config-router.ts

* Review comments
2020-01-12 17:57:38 +01:00
Krisjanis Lejejs a8ed87298a Improved map panel and map card to ignore zones when fitting map. (#4447)
* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Changed approach and created a different array for zones

* Removed zone key option for markers
2020-01-12 17:56:55 +01:00
Joakim Sørensen b15270dfe2 Use correct suffix for elevation (#4454)
* Use correct suffix for elevation

* Use correct suffix for elevation
2020-01-12 07:31:59 -08:00
Bram Kragten 58ad949bc8 Virtualize logbook (#4450)
* Virtualize logbook

* Clean

* Update ha-logbook.ts
2020-01-12 13:00:26 +01:00
HomeAssistant Azure adce40de56 [ci skip] Translation update 2020-01-12 00:33:31 +00:00
Ian Richardson 0f487ae4bf Add tabindex to lovelace elements (#4160)
* tabindex

* use action handler

* circular focus test

* address comment

* add focus styling to other elements

* add focus styling to cards

* style glance card entities

* Add back light/thermo changes that were lost in rebase

* Remove unused import

* lint

* lint

* 💄 tweak focus style for glance entities

* 💄 apply styling to focused state-label-badges
2020-01-11 11:50:43 +01:00
Joakim Sørensen 2848e3a63b Adds CCS var usage to person dialog (#4449) 2020-01-11 11:49:57 +01:00
Bram Kragten 5a172a64c5 Make entry flow dialog modal (#4440)
* Make entry flow dialog modal

* Add close button

* Update dialog-data-entry-flow.ts

* Fix aria-label
2020-01-10 16:40:19 -08:00
HomeAssistant Azure 433aa16ea6 [ci skip] Translation update 2020-01-11 00:32:34 +00:00
HomeAssistant Azure 50cb8cf3cc [ci skip] Translation update 2020-01-10 00:32:38 +00:00
Sean Mooney 4e5406b27b Typo fix in issue template (#4445)
fixes small typo, necesarry = necessary
2020-01-09 09:29:42 -06:00
Franck Nijhof 80eb80619a Add configuration for Lock Threads on closed pull requests (#4443) 2020-01-09 11:40:25 +01:00
Ian Richardson bf71b3a869 ♻️ convert ha-attributes to lit-element (#4350)
* ♻️ convert ha-attributes to lit-element

* Address comments

* inline items

* 🐛 Fix attribution display logic
2020-01-09 10:22:23 +01:00
HomeAssistant Azure ff270c4b7d [ci skip] Translation update 2020-01-09 00:32:44 +00:00
153 changed files with 4160 additions and 1258 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ Give the config of both the integration that is used, the Lovelace config, scene
**Steps to reproduce this problem:**
<!--
Sum up all steps that are necesarry to reproduce this bug.
Sum up all steps that are necessary to reproduce this bug.
For example:
1. Add a climate integration
2. Navigate to Lovelace
+27
View File
@@ -0,0 +1,27 @@
# 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
+9 -2
View File
@@ -27,6 +27,7 @@ import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-pages-with-tabs";
import { navigate } from "../../src/common/navigate";
// The register callback of the IronA11yKeysBehavior inside paper-icon-button
// is not called, causing _keyBindings to be uninitiliazed for paper-icon-button,
@@ -165,14 +166,20 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
}),
]);
if (!addon.ingress_url) {
throw new Error("Add-on does not support Ingress");
alert("Add-on does not support Ingress");
return;
}
if (addon.state !== "started") {
alert("Add-on is not running. Please start it first");
navigate(this, `/hassio/addon/${addon.slug}`, true);
return;
}
location.assign(addon.ingress_url);
// await a promise that doesn't resolve, so we show the loading screen
// while we load the next page.
await new Promise(() => undefined);
} catch (err) {
alert(`Unable to open ingress connection `);
alert("Unable to open ingress connection");
}
}
+1
View File
@@ -89,6 +89,7 @@
"leaflet": "^1.4.0",
"lit-element": "^2.2.1",
"lit-html": "^1.1.0",
"lit-virtualizer": "^0.4.2",
"marked": "^0.6.1",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",
@@ -15,6 +15,7 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
id="progress"
progress="[[progress]]"
on-click="buttonTapped"
tabindex="0"
><slot></slot
></ha-progress-button>
`;
+3 -1
View File
@@ -255,7 +255,9 @@ export class HaDataTable extends BaseElement {
<ha-checkbox
class="mdc-data-table__row-checkbox"
@change=${this._handleRowCheckboxChange}
.checked=${this._checkedRows.includes(row[this.id])}
.checked=${this._checkedRows.includes(
String(row[this.id])
)}
>
</ha-checkbox>
</td>
-91
View File
@@ -1,91 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import hassAttributeUtil from "../util/hass-attributes-util";
class HaAttributes extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
.data-entry .value {
max-width: 200px;
overflow-wrap: break-word;
}
.attribution {
color: var(--secondary-text-color);
text-align: right;
}
</style>
<div class="layout vertical">
<template
is="dom-repeat"
items="[[computeDisplayAttributes(stateObj, filtersArray)]]"
as="attribute"
>
<div class="data-entry layout justified horizontal">
<div class="key">[[formatAttribute(attribute)]]</div>
<div class="value">
[[formatAttributeValue(stateObj, attribute)]]
</div>
</div>
</template>
<div class="attribution" hidden$="[[!computeAttribution(stateObj)]]">
[[computeAttribution(stateObj)]]
</div>
</div>
`;
}
static get properties() {
return {
stateObj: {
type: Object,
},
extraFilters: {
type: String,
value: "",
},
filtersArray: {
type: Array,
computed: "computeFiltersArray(extraFilters)",
},
};
}
computeFiltersArray(extraFilters) {
return Object.keys(hassAttributeUtil.LOGIC_STATE_ATTRIBUTES).concat(
extraFilters ? extraFilters.split(",") : []
);
}
computeDisplayAttributes(stateObj, filtersArray) {
if (!stateObj) {
return [];
}
return Object.keys(stateObj.attributes).filter(function(key) {
return filtersArray.indexOf(key) === -1;
});
}
formatAttribute(attribute) {
return attribute.replace(/_/g, " ");
}
formatAttributeValue(stateObj, attribute) {
var value = stateObj.attributes[attribute];
if (value === null) return "-";
if (Array.isArray(value)) {
return value.join(", ");
}
return value instanceof Object ? JSON.stringify(value, null, 2) : value;
}
computeAttribution(stateObj) {
return stateObj.attributes.attribution;
}
}
customElements.define("ha-attributes", HaAttributes);
+97
View File
@@ -0,0 +1,97 @@
import {
property,
LitElement,
TemplateResult,
html,
CSSResult,
css,
customElement,
} from "lit-element";
import { HassEntity } from "home-assistant-js-websocket";
import hassAttributeUtil from "../util/hass-attributes-util";
@customElement("ha-attributes")
class HaAttributes extends LitElement {
@property() public stateObj?: HassEntity;
@property() public extraFilters?: string;
protected render(): TemplateResult | void {
if (!this.stateObj) {
return html``;
}
return html`
<div>
${this.computeDisplayAttributes(
Object.keys(hassAttributeUtil.LOGIC_STATE_ATTRIBUTES).concat(
this.extraFilters ? this.extraFilters.split(",") : []
)
).map(
(attribute) => html`
<div class="data-entry">
<div class="key">${attribute.replace(/_/g, " ")}</div>
<div class="value">
${this.formatAttributeValue(attribute)}
</div>
</div>
`
)}
${this.stateObj.attributes.attribution
? html`
<div class="attribution">
${this.stateObj.attributes.attribution}
</div>
`
: ""}
</div>
`;
}
static get styles(): CSSResult {
return css`
.data-entry {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.data-entry .value {
max-width: 200px;
overflow-wrap: break-word;
}
.attribution {
color: var(--secondary-text-color);
text-align: right;
}
`;
}
private computeDisplayAttributes(filtersArray: string[]): string[] {
if (!this.stateObj) {
return [];
}
return Object.keys(this.stateObj.attributes).filter((key) => {
return filtersArray.indexOf(key) === -1;
});
}
private formatAttributeValue(attribute: string): string {
if (!this.stateObj) {
return "-";
}
const value = this.stateObj.attributes[attribute];
if (value === null) {
return "-";
}
if (Array.isArray(value)) {
return value.join(", ");
}
return value instanceof Object ? JSON.stringify(value, null, 2) : value;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-attributes": HaAttributes;
}
}
+19 -4
View File
@@ -520,10 +520,13 @@ class HaSidebar extends LitElement {
}
a {
text-decoration: none;
color: var(--sidebar-text-color);
font-weight: 500;
font-size: 14px;
text-decoration: none;
position: relative;
display: block;
outline: 0;
}
paper-icon-item {
@@ -546,7 +549,8 @@ class HaSidebar extends LitElement {
color: var(--sidebar-icon-color);
}
.iron-selected paper-icon-item:before {
.iron-selected paper-icon-item::before,
a:not(.iron-selected):focus::before {
border-radius: 4px;
position: absolute;
top: 0;
@@ -555,11 +559,22 @@ class HaSidebar extends LitElement {
left: 0;
pointer-events: none;
content: "";
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
transition: opacity 15ms linear;
will-change: opacity;
}
.iron-selected paper-icon-item::before {
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
}
a:not(.iron-selected):focus::before {
background-color: currentColor;
opacity: var(--dark-divider-opacity);
margin: 4px 8px;
}
.iron-selected paper-icon-item:focus::before,
.iron-selected:focus paper-icon-item::before {
opacity: 0.2;
}
.iron-selected paper-icon-item[pressed]:before {
opacity: 0.37;
+7
View File
@@ -0,0 +1,7 @@
export interface LogbookEntry {
when: string;
name: string;
message: string;
entity_id?: string;
domain: string;
}
+26
View File
@@ -126,6 +126,32 @@ export const unbindDevices = (
target_ieee: targetIEEE,
});
export const bindDeviceToGroup = (
hass: HomeAssistant,
deviceIEEE: string,
groupId: number,
clusters: Cluster[]
): Promise<void> =>
hass.callWS({
type: "zha/groups/bind",
source_ieee: deviceIEEE,
group_id: groupId,
bindings: clusters,
});
export const unbindDeviceFromGroup = (
hass: HomeAssistant,
deviceIEEE: string,
groupId: number,
clusters: Cluster[]
): Promise<void> =>
hass.callWS({
type: "zha/groups/unbind",
source_ieee: deviceIEEE,
group_id: groupId,
bindings: clusters,
});
export const readAttributeValue = (
hass: HomeAssistant,
data: ReadAttributeServiceData
@@ -115,7 +115,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
.disabled=${this._submitting}
>
${this.hass.localize(
"ui.panel.config.entity_registry.editor.update"
"ui.panel.config.entities.editor.update"
)}
</mwc-button>
</div>
@@ -11,6 +11,7 @@ import {
import "@material/mwc-button";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-tooltip/paper-tooltip";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-spinner/paper-spinner";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
@@ -124,6 +125,7 @@ class DataEntryFlowDialog extends LitElement {
<ha-paper-dialog
with-backdrop
opened
modal
@opened-changed=${this._openedChanged}
>
${this._loading || (this._step === null && this._handlers === undefined)
@@ -134,53 +136,62 @@ class DataEntryFlowDialog extends LitElement {
? // When we are going to next step, we render 1 round of empty
// to reset the element.
""
: this._step === null
? // Show handler picker
html`
<step-flow-pick-handler
.flowConfig=${this._params.flowConfig}
.hass=${this.hass}
.handlers=${this._handlers}
.showAdvanced=${this._params.showAdvanced}
></step-flow-pick-handler>
`
: this._step.type === "form"
? html`
<step-flow-form
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-form>
`
: this._step.type === "external"
? html`
<step-flow-external
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-external>
`
: this._step.type === "abort"
? html`
<step-flow-abort
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-abort>
`
: this._devices === undefined || this._areas === undefined
? // When it's a create entry result, we will fetch device & area registry
html`
<step-flow-loading></step-flow-loading>
`
: html`
<step-flow-create-entry
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.devices=${this._devices}
.areas=${this._areas}
></step-flow-create-entry>
<paper-icon-button
aria-label=${this.hass.localize(
"ui.panel.config.integrations.config_flow.dismiss"
)}
icon="hass:close"
dialog-dismiss
></paper-icon-button>
${this._step === null
? // Show handler picker
html`
<step-flow-pick-handler
.flowConfig=${this._params.flowConfig}
.hass=${this.hass}
.handlers=${this._handlers}
.showAdvanced=${this._params.showAdvanced}
></step-flow-pick-handler>
`
: this._step.type === "form"
? html`
<step-flow-form
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-form>
`
: this._step.type === "external"
? html`
<step-flow-external
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-external>
`
: this._step.type === "abort"
? html`
<step-flow-abort
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-abort>
`
: this._devices === undefined || this._areas === undefined
? // When it's a create entry result, we will fetch device & area registry
html`
<step-flow-loading></step-flow-loading>
`
: html`
<step-flow-create-entry
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.devices=${this._devices}
.areas=${this._areas}
></step-flow-create-entry>
`}
`}
</ha-paper-dialog>
`;
@@ -318,6 +329,12 @@ class DataEntryFlowDialog extends LitElement {
display: block;
padding: 0;
}
paper-icon-button {
display: inline-block;
padding: 8px;
margin: 16px 16px 0 0;
float: right;
}
`,
];
}
@@ -19,6 +19,7 @@ import "../../components/ha-icon-next";
import "../../common/search/search-input";
import { styleMap } from "lit-html/directives/style-map";
import { FlowConfig } from "./show-dialog-data-entry-flow";
import { configFlowContentStyles } from "./styles";
interface HandlerObj {
name: string;
@@ -133,28 +134,27 @@ class StepFlowPickHandler extends LitElement {
});
}
static get styles(): CSSResult {
return css`
h2 {
margin-bottom: 2px;
padding-left: 16px;
}
div {
overflow: auto;
max-height: 600px;
}
paper-item {
cursor: pointer;
}
p {
text-align: center;
padding: 16px;
margin: 0;
}
p > a {
color: var(--primary-color);
}
`;
static get styles(): CSSResult[] {
return [
configFlowContentStyles,
css`
div {
overflow: auto;
max-height: 600px;
}
paper-item {
cursor: pointer;
}
p {
text-align: center;
padding: 16px;
margin: 0;
}
p > a {
color: var(--primary-color);
}
`,
];
}
}
@@ -117,9 +117,7 @@ class DialogDeviceRegistryDetail extends LitElement {
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<mwc-button @click="${this._updateEntry}">
${this.hass.localize(
"ui.panel.config.entity_registry.editor.update"
)}
${this.hass.localize("ui.panel.config.entities.editor.update")}
</mwc-button>
</div>
</ha-paper-dialog>
@@ -12,6 +12,7 @@ import "../../state-summary/state-card-content";
import "./controls/more-info-content";
import { navigate } from "../../common/navigate";
import { computeStateName } from "../../common/entity/compute_state_name";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
@@ -23,6 +24,9 @@ import { removeEntityRegistryEntry } from "../../data/entity_registry";
import { showConfirmationDialog } from "../confirmation/show-dialog-confirmation";
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
const EDITABLE_DOMAINS = ["script"];
/*
* @appliesMixin EventsMixin
*/
@@ -90,6 +94,13 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
on-click="_gotoSettings"
></paper-icon-button>
</template>
<template is="dom-if" if="[[_computeEdit(hass, stateObj)]]">
<paper-icon-button
aria-label$="[[localize('ui.dialogs.more_info_control.edit')]]"
icon="hass:pencil"
on-click="_gotoEdit"
></paper-icon-button>
</template>
</app-toolbar>
<template is="dom-if" if="[[_computeShowStateInfo(stateObj)]]" restamp="">
@@ -209,6 +220,16 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
return stateObj ? computeStateName(stateObj) : "";
}
_computeEdit(hass, stateObj) {
const domain = this._computeDomain(stateObj);
return (
stateObj &&
hass.user.is_admin &&
((EDITABLE_DOMAINS_WITH_ID.includes(domain) && stateObj.attributes.id) ||
EDITABLE_DOMAINS.includes(domain))
);
}
_stateObjChanged(newVal) {
if (!newVal) {
return;
@@ -241,6 +262,19 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
this.fire("more-info-page", { page: "settings" });
}
_gotoEdit() {
const domain = this._computeDomain(this.stateObj);
navigate(
this,
`/config/${domain}/edit/${
EDITABLE_DOMAINS_WITH_ID.includes(domain)
? this.stateObj.attributes.id
: this.stateObj.entity_id
}`
);
this.fire("hass-more-info", { entityId: null });
}
_computeRTL(hass) {
return computeRTL(hass);
}
@@ -54,9 +54,8 @@ class DialogZHADeviceInfo extends LitElement {
class="card"
.hass=${this.hass}
.device=${this._device}
showActions
isJoinPage
@zha-device-removed=${this._onDeviceRemoved}
.showEntityDetail=${false}
></zha-device-card>
`}
</ha-paper-dialog>
+9 -2
View File
@@ -55,8 +55,12 @@ const connProm = async (auth) => {
throw err;
}
// We can get invalid auth if auth tokens were stored that are no longer valid
// Clear stored tokens.
if (!isExternal) {
if (isExternal) {
// Tell the external app to force refresh the access tokens.
// This should trigger their unauthorized handling.
await auth.refreshAccessToken(true);
} else {
// Clear stored tokens.
saveTokens(null);
}
auth = await authProm();
@@ -66,6 +70,9 @@ const connProm = async (auth) => {
};
if (__DEV__) {
// Remove adoptedStyleSheets so style inspector works on shadow DOM.
// @ts-ignore
delete Document.prototype.adoptedStyleSheets;
performance.mark("hass-start");
}
window.hassConnection = authProm().then(connProm);
+17 -12
View File
@@ -11,6 +11,10 @@ interface BasePayload {
callback: string;
}
interface GetExternalAuthPayload extends BasePayload {
force?: boolean;
}
interface RefreshTokenResponse {
access_token: string;
expires_in: number;
@@ -26,7 +30,7 @@ declare global {
webkit?: {
messageHandlers: {
getExternalAuth: {
postMessage(payload: BasePayload);
postMessage(payload: GetExternalAuthPayload);
};
revokeExternalAuth: {
postMessage(payload: BasePayload);
@@ -60,8 +64,13 @@ class ExternalAuth extends Auth {
});
}
public async refreshAccessToken() {
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
public async refreshAccessToken(force?: boolean) {
const payload: GetExternalAuthPayload = {
callback: CALLBACK_SET_TOKEN,
};
if (force) {
payload.force = true;
}
const callbackPromise = new Promise<RefreshTokenResponse>(
(resolve, reject) => {
@@ -73,11 +82,9 @@ class ExternalAuth extends Auth {
await 0;
if (window.externalApp) {
window.externalApp.getExternalAuth(JSON.stringify(callbackPayload));
window.externalApp.getExternalAuth(JSON.stringify(payload));
} else {
window.webkit!.messageHandlers.getExternalAuth.postMessage(
callbackPayload
);
window.webkit!.messageHandlers.getExternalAuth.postMessage(payload);
}
const tokens = await callbackPromise;
@@ -87,7 +94,7 @@ class ExternalAuth extends Auth {
}
public async revoke() {
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
const payload: BasePayload = { callback: CALLBACK_REVOKE_TOKEN };
const callbackPromise = new Promise((resolve, reject) => {
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
@@ -97,11 +104,9 @@ class ExternalAuth extends Auth {
await 0;
if (window.externalApp) {
window.externalApp.revokeExternalAuth(JSON.stringify(callbackPayload));
window.externalApp.revokeExternalAuth(JSON.stringify(payload));
} else {
window.webkit!.messageHandlers.revokeExternalAuth.postMessage(
callbackPayload
);
window.webkit!.messageHandlers.revokeExternalAuth.postMessage(payload);
}
await callbackPromise;
+8 -1
View File
@@ -9,12 +9,14 @@ import {
} from "lit-element";
import "../components/ha-menu-button";
import "../components/ha-paper-icon-button-arrow-prev";
import { classMap } from "lit-html/directives/class-map";
@customElement("hass-subpage")
class HassSubpage extends LitElement {
@property()
public header?: string;
@property({ type: Boolean })
public showBackButton = true;
@property({ type: Boolean })
public hassio = false;
@@ -25,6 +27,7 @@ class HassSubpage extends LitElement {
aria-label="Back"
.hassio=${this.hassio}
@click=${this._backTapped}
class=${classMap({ hidden: !this.showBackButton })}
></ha-paper-icon-button-arrow-prev>
<div main-title>${this.header}</div>
@@ -64,6 +67,10 @@ class HassSubpage extends LitElement {
pointer-events: auto;
}
ha-paper-icon-button-arrow-prev.hidden {
visibility: hidden;
}
[main-title] {
margin: 0 0 0 24px;
line-height: 20px;
+8 -4
View File
@@ -116,9 +116,13 @@ class OnboardingCoreConfig extends LitElement {
@value-changed=${this._handleChange}
>
<span slot="suffix">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)}
${this._unitSystem === "metric"
? this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)
: this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_feet"
)}
</span>
</paper-input>
</div>
@@ -266,7 +270,7 @@ class OnboardingCoreConfig extends LitElement {
});
} catch (err) {
this._working = false;
alert("FAIL");
alert(`Failed to save: ${err.message}`);
}
}
@@ -47,9 +47,7 @@ class DialogAreaDetail extends LitElement {
<h2>
${entry
? entry.name
: this.hass.localize(
"ui.panel.config.area_registry.editor.default_name"
)}
: this.hass.localize("ui.panel.config.areas.editor.default_name")}
</h2>
<paper-dialog-scrollable>
${this._error
@@ -81,9 +79,7 @@ class DialogAreaDetail extends LitElement {
@click="${this._deleteEntry}"
.disabled=${this._submitting}
>
${this.hass.localize(
"ui.panel.config.area_registry.editor.delete"
)}
${this.hass.localize("ui.panel.config.areas.editor.delete")}
</mwc-button>
`
: html``}
@@ -92,12 +88,8 @@ class DialogAreaDetail extends LitElement {
.disabled=${nameInvalid || this._submitting}
>
${entry
? this.hass.localize(
"ui.panel.config.area_registry.editor.update"
)
: this.hass.localize(
"ui.panel.config.area_registry.editor.create"
)}
? this.hass.localize("ui.panel.config.areas.editor.update")
: this.hass.localize("ui.panel.config.areas.editor.create")}
</mwc-button>
</div>
</ha-paper-dialog>
@@ -5,6 +5,7 @@ import {
css,
CSSResult,
property,
customElement,
} from "lit-element";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
@@ -30,7 +31,8 @@ import { classMap } from "lit-html/directives/class-map";
import { computeRTL } from "../../../common/util/compute_rtl";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
class HaConfigAreaRegistry extends LitElement {
@customElement("ha-config-areas")
export class HaConfigAreas extends LitElement {
@property() public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() private _areas?: AreaRegistryEntry[];
@@ -51,24 +53,23 @@ class HaConfigAreaRegistry extends LitElement {
}
return html`
<hass-subpage
header="${this.hass.localize("ui.panel.config.area_registry.caption")}"
.header="${this.hass.localize("ui.panel.config.areas.caption")}"
.showBackButton=${!this.isWide}
>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize("ui.panel.config.area_registry.picker.header")}
${this.hass.localize("ui.panel.config.areas.picker.header")}
</span>
<span slot="introduction">
${this.hass.localize(
"ui.panel.config.area_registry.picker.introduction"
)}
${this.hass.localize("ui.panel.config.areas.picker.introduction")}
<p>
${this.hass.localize(
"ui.panel.config.area_registry.picker.introduction2"
"ui.panel.config.areas.picker.introduction2"
)}
</p>
<a href="/config/integrations/dashboard">
${this.hass.localize(
"ui.panel.config.area_registry.picker.integrations_page"
"ui.panel.config.areas.picker.integrations_page"
)}
</a>
</span>
@@ -85,13 +86,9 @@ class HaConfigAreaRegistry extends LitElement {
${this._areas.length === 0
? html`
<div class="empty">
${this.hass.localize(
"ui.panel.config.area_registry.no_areas"
)}
${this.hass.localize("ui.panel.config.areas.no_areas")}
<mwc-button @click=${this._createArea}>
${this.hass.localize(
"ui.panel.config.area_registry.create_area"
)}
${this.hass.localize("ui.panel.config.areas.create_area")}
</mwc-button>
</div>
`
@@ -103,9 +100,7 @@ class HaConfigAreaRegistry extends LitElement {
<ha-fab
?is-wide=${this.isWide}
icon="hass:plus"
title="${this.hass.localize(
"ui.panel.config.area_registry.create_area"
)}"
title="${this.hass.localize("ui.panel.config.areas.create_area")}"
@click=${this._createArea}
class="${classMap({
rtl: computeRTL(this.hass),
@@ -208,5 +203,3 @@ All devices in this area will become unassigned.`)
`;
}
}
customElements.define("ha-config-area-registry", HaConfigAreaRegistry);
@@ -42,6 +42,7 @@ class HaAutomationPicker extends LitElement {
protected render(): TemplateResult | void {
return html`
<hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.automation.caption")}
>
<ha-config-section .isWide=${this.isWide}>
@@ -63,7 +63,10 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
color: var(--primary-color);
}
</style>
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]">
<hass-subpage
show-back-button="[[!isWide]]"
header="[[localize('ui.panel.config.cloud.caption')]]"
>
<div class="content">
<ha-config-section is-wide="[[isWide]]">
<span slot="header"
+2 -2
View File
@@ -346,8 +346,8 @@ class CloudAlexa extends LitElement {
}
ha-card {
margin: 4px;
width: 100%;
max-width: 300px;
flex-basis: 300px;
flex-grow: 1;
}
.card-content {
padding-bottom: 12px;
@@ -369,8 +369,8 @@ class CloudGoogleAssistant extends LitElement {
}
ha-card {
margin: 4px;
width: 100%;
max-width: 300px;
flex-basis: 300px;
flex-grow: 1;
}
.card-content {
padding-bottom: 12px;
+4 -1
View File
@@ -72,7 +72,10 @@ class CloudLogin extends LocalizeMixin(
color: var(--secondary-text-color);
}
</style>
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]">
<hass-subpage
show-back-button="[[!isWide]]"
header="[[localize('ui.panel.config.cloud.caption')]]"
>
<div class="content">
<ha-config-section is-wide="[[isWide]]">
<span slot="header"
@@ -102,9 +102,13 @@ class ConfigCoreForm extends LitElement {
@value-changed=${this._handleChange}
>
<span slot="suffix">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)}
${this._unitSystem === "metric"
? this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)
: this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_feet"
)}
</span>
</paper-input>
</div>
+4 -1
View File
@@ -33,7 +33,10 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
}
</style>
<hass-subpage header="[[localize('ui.panel.config.core.caption')]]">
<hass-subpage
header="[[localize('ui.panel.config.core.caption')]]"
show-back-button="[[!isWide]]"
>
<div class$="[[computeClasses(isWide)]]">
<ha-config-section-core
is-wide="[[isWide]]"
@@ -23,12 +23,17 @@ import LocalizeMixin from "../../../mixins/localize-mixin";
class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style"></style>
<style include="ha-style">
ha-paper-icon-button-arrow-prev[hide] {
visibility: hidden;
}
</style>
<app-header-layout has-scrolling-region="">
<app-header slot="header" fixed="">
<app-toolbar>
<ha-paper-icon-button-arrow-prev
hide$="[[isWide]]"
on-click="_backTapped"
></ha-paper-icon-button-arrow-prev>
<div main-title="">
@@ -28,7 +28,7 @@ class HaConfigDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public cloudStatus!: CloudStatus;
@property() public cloudStatus?: CloudStatus;
@property() public showAdvanced!: boolean;
protected render(): TemplateResult | void {
@@ -53,67 +53,69 @@ class HaConfigDashboard extends LitElement {
${this.hass.localize("ui.panel.config.introduction")}
</div>
${isComponentLoaded(this.hass, "cloud")
${this.cloudStatus && isComponentLoaded(this.hass, "cloud")
? html`
<ha-card>
<a href='/config/cloud' tabindex="-1">
<paper-item>
<paper-item-body two-line="">
${this.hass.localize("ui.panel.config.cloud.caption")}
${
this.cloudStatus.logged_in
? html`
<div secondary="">
${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(this.cloudStatus as CloudStatusLoggedIn).email
)}
</div>
`
: html`
<div secondary="">
${this.hass.localize(
"ui.panel.config.cloud.description_features"
)}
</div>
`
}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</ha-card>
</a>
`
<ha-card>
<a href="/config/cloud" tabindex="-1">
<paper-item>
<paper-item-body two-line="">
${this.hass.localize("ui.panel.config.cloud.caption")}
${this.cloudStatus.logged_in
? html`
<div secondary="">
${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(this.cloudStatus as CloudStatusLoggedIn)
.email
)}
</div>
`
: html`
<div secondary="">
${this.hass.localize(
"ui.panel.config.cloud.description_features"
)}
</div>
`}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
</ha-card>
`
: ""}
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this.showAdvanced}
.pages=${[
{ page: "integrations", core: true },
{ page: "devices", core: true },
{ page: "automation" },
{ page: "script" },
{ page: "scene" },
]}
></ha-config-navigation>
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this.showAdvanced}
.pages=${[
{ page: "core", core: true },
{ page: "server_control", core: true },
{ page: "entity_registry", core: true },
{ page: "area_registry", core: true },
{ page: "person" },
{ page: "users", core: true },
{ page: "zha" },
{ page: "zwave" },
{ page: "customize", core: true, advanced: true },
]}
></ha-config-navigation>
<ha-card>
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this.showAdvanced}
.pages=${[
{ page: "integrations", core: true },
{ page: "devices", core: true },
{ page: "automation" },
{ page: "script" },
{ page: "scene" },
]}
></ha-config-navigation>
</ha-card>
<ha-card>
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this.showAdvanced}
.pages=${[
{ page: "core", core: true },
{ page: "server_control", core: true },
{ page: "entities", core: true },
{ page: "areas", core: true },
{ page: "person" },
{ page: "users", core: true },
{ page: "zha" },
{ page: "zwave" },
{ page: "customize", core: true, advanced: true },
]}
></ha-config-navigation>
</ha-card>
${!this.showAdvanced
? html`
@@ -16,11 +16,13 @@ import {
css,
} from "lit-element";
import { HomeAssistant } from "../../../types";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
export interface ConfigPageNavigation {
page: string;
core?: boolean;
advanced?: boolean;
info?: any;
}
@customElement("ha-config-navigation")
@@ -28,31 +30,56 @@ class HaConfigNavigation extends LitElement {
@property() public hass!: HomeAssistant;
@property() public showAdvanced!: boolean;
@property() public pages!: ConfigPageNavigation[];
@property() public curPage!: string;
protected render(): TemplateResult | void {
return html`
<ha-card>
${this.pages.map(({ page, core, advanced }) =>
<paper-listbox attr-for-selected="data-page" .selected=${this.curPage}>
${this.pages.map(({ page, core, advanced, info }) =>
(core || isComponentLoaded(this.hass, page)) &&
(!advanced || this.showAdvanced)
? html`
<a href=${`/config/${page}`}>
<a
href=${`/config/${page}`}
aria-role="option"
data-page="${page}"
tabindex="-1"
>
<paper-item>
<paper-item-body two-line="">
<paper-item-body two-line>
${this.hass.localize(`ui.panel.config.${page}.caption`)}
<div secondary>
${this.hass.localize(
`ui.panel.config.${page}.description`
)}
</div>
${page === "cloud" && (info as CloudStatus)
? info.logged_in
? html`
<div secondary>
${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(info as CloudStatusLoggedIn).email
)}
</div>
`
: html`
<div secondary>
${this.hass.localize(
"ui.panel.config.cloud.description_features"
)}
</div>
`
: html`
<div secondary>
${this.hass.localize(
`ui.panel.config.${page}.description`
)}
</div>
`}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
`
: ""
)}
</ha-card>
</paper-listbox>
`;
}
@@ -61,6 +88,39 @@ class HaConfigNavigation extends LitElement {
a {
text-decoration: none;
color: var(--primary-text-color);
position: relative;
display: block;
outline: 0;
}
.iron-selected paper-item::before,
a:not(.iron-selected):focus::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
content: "";
transition: opacity 15ms linear;
will-change: opacity;
}
.iron-selected paper-item::before {
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
}
a:not(.iron-selected):focus::before {
background-color: currentColor;
opacity: var(--dark-divider-opacity);
}
.iron-selected paper-item:focus::before,
.iron-selected:focus paper-item::before {
opacity: 0.2;
}
.iron-selected paper-item[pressed]::before {
opacity: 0.37;
}
paper-listbox {
padding: 0;
}
`;
}
@@ -20,7 +20,7 @@ import "@polymer/paper-item/paper-item-body";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import "../../../../components/ha-switch";
import { showEntityRegistryDetailDialog } from "../../entity_registry/show-dialog-entity-registry-detail";
import { showEntityRegistryDetailDialog } from "../../entities/show-dialog-entity-registry-detail";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { domainIcon } from "../../../../common/entity/domain_icon";
@@ -45,7 +45,7 @@ export class HaDeviceEntitiesCard extends LitElement {
?checked=${this._showDisabled}
@change=${this._showDisabledChanged}
>${this.hass.localize(
"ui.panel.config.entity_registry.picker.show_disabled"
"ui.panel.config.entities.picker.show_disabled"
)}
</ha-switch>
</paper-item>
@@ -20,6 +20,7 @@ import { AreaRegistryEntry } from "../../../data/area_registry";
export class HaConfigDeviceDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow = false;
@property() public isWide = false;
@property() public devices!: DeviceRegistryEntry[];
@property() public entries!: ConfigEntry[];
@property() public entities!: EntityRegistryEntry[];
@@ -29,7 +30,8 @@ export class HaConfigDeviceDashboard extends LitElement {
protected render(): TemplateResult {
return html`
<hass-subpage
header=${this.hass.localize("ui.panel.config.devices.caption")}
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.devices.caption")}
>
<div class="content">
<ha-devices-data-table
@@ -28,6 +28,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
class HaConfigDevices extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean;
protected routerOptions: RouterOptions = {
@@ -97,6 +98,7 @@ class HaConfigDevices extends HassRouterPage {
pageEl.devices = this._deviceRegistryEntries;
pageEl.areas = this._areas;
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced;
}
@@ -145,7 +145,7 @@ export class HaDevicesDataTable extends LitElement {
return html`
${name}<br />
${device.area} | ${device.integration}<br />
${battery
${battery && !isNaN(battery.state as any)
? html`
${battery.state}%
<ha-state-icon
@@ -205,7 +205,7 @@ export class HaDevicesDataTable extends LitElement {
const battery = batteryEntity
? this.hass.states[batteryEntity]
: undefined;
return battery
return battery && !isNaN(battery.state as any)
? html`
${battery.state}%
<ha-state-icon
@@ -76,7 +76,7 @@ class DialogEntityRegistryDetail extends LitElement {
? html`
<div>
${this.hass!.localize(
"ui.panel.config.entity_registry.editor.unavailable"
"ui.panel.config.entities.editor.unavailable"
)}
</div>
`
@@ -112,13 +112,13 @@ class DialogEntityRegistryDetail extends LitElement {
<div>
<div>
${this.hass.localize(
"ui.panel.config.entity_registry.editor.enabled_label"
"ui.panel.config.entities.editor.enabled_label"
)}
</div>
<div class="secondary">
${this._disabledBy && this._disabledBy !== "user"
? this.hass.localize(
"ui.panel.config.entity_registry.editor.enabled_cause",
"ui.panel.config.entities.editor.enabled_cause",
"cause",
this.hass.localize(
`config_entry.disabled_by.${this._disabledBy}`
@@ -126,10 +126,10 @@ class DialogEntityRegistryDetail extends LitElement {
)
: ""}
${this.hass.localize(
"ui.panel.config.entity_registry.editor.enabled_description"
"ui.panel.config.entities.editor.enabled_description"
)}
<br />${this.hass.localize(
"ui.panel.config.entity_registry.editor.note"
"ui.panel.config.entities.editor.note"
)}
</div>
</div>
@@ -144,17 +144,13 @@ class DialogEntityRegistryDetail extends LitElement {
.disabled=${this._submitting ||
!(stateObj && stateObj.attributes.restored)}
>
${this.hass.localize(
"ui.panel.config.entity_registry.editor.delete"
)}
${this.hass.localize("ui.panel.config.entities.editor.delete")}
</mwc-button>
<mwc-button
@click="${this._updateEntry}"
.disabled=${invalidDomainUpdate || this._submitting}
>
${this.hass.localize(
"ui.panel.config.entity_registry.editor.update"
)}
${this.hass.localize("ui.panel.config.entities.editor.update")}
</mwc-button>
</div>
</ha-paper-dialog>
@@ -201,7 +197,7 @@ class DialogEntityRegistryDetail extends LitElement {
private _confirmDeleteEntry(): void {
showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.entity_registry.editor.confirm_delete"
"ui.panel.config.entities.editor.confirm_delete"
),
confirm: () => this._deleteEntry(),
});
@@ -6,6 +6,7 @@ import {
CSSResult,
property,
query,
customElement,
} from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
@@ -46,7 +47,8 @@ import {
} from "../../../components/data-table/ha-data-table";
import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
class HaConfigEntityRegistry extends LitElement {
@customElement("ha-config-entities")
export class HaConfigEntities extends LitElement {
@property() public hass!: HomeAssistant;
@property() public isWide!: boolean;
@property() public narrow!: boolean;
@@ -71,7 +73,7 @@ class HaConfigEntityRegistry extends LitElement {
},
name: {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.headers.name"
"ui.panel.config.entities.picker.headers.name"
),
sortable: true,
filterable: true,
@@ -81,7 +83,7 @@ class HaConfigEntityRegistry extends LitElement {
const statusColumn: DataTableColumnData = {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.headers.status"
"ui.panel.config.entities.picker.headers.status"
),
type: "icon",
sortable: true,
@@ -104,10 +106,10 @@ class HaConfigEntityRegistry extends LitElement {
<paper-tooltip position="left">
${entity.unavailable
? this.hass.localize(
"ui.panel.config.entity_registry.picker.status.unavailable"
"ui.panel.config.entities.picker.status.unavailable"
)
: this.hass.localize(
"ui.panel.config.entity_registry.picker.status.disabled"
"ui.panel.config.entities.picker.status.disabled"
)}
</paper-tooltip>
</div>
@@ -130,14 +132,14 @@ class HaConfigEntityRegistry extends LitElement {
columns.entity_id = {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.headers.entity_id"
"ui.panel.config.entities.picker.headers.entity_id"
),
sortable: true,
filterable: true,
};
columns.platform = {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.headers.integration"
"ui.panel.config.entities.picker.headers.integration"
),
sortable: true,
filterable: true,
@@ -181,15 +183,13 @@ class HaConfigEntityRegistry extends LitElement {
unavailable,
status: unavailable
? this.hass.localize(
"ui.panel.config.entity_registry.picker.status.unavailable"
"ui.panel.config.entities.picker.status.unavailable"
)
: entry.disabled_by
? this.hass.localize(
"ui.panel.config.entity_registry.picker.status.disabled"
"ui.panel.config.entities.picker.status.disabled"
)
: this.hass.localize(
"ui.panel.config.entity_registry.picker.status.ok"
),
: this.hass.localize("ui.panel.config.entities.picker.status.ok"),
});
return result;
}, [] as any);
@@ -211,31 +211,28 @@ class HaConfigEntityRegistry extends LitElement {
}
return html`
<hass-subpage
header="${this.hass.localize(
"ui.panel.config.entity_registry.caption"
)}"
.header="${this.hass.localize("ui.panel.config.entities.caption")}"
.showBackButton=${!this.isWide}
>
<div class="content">
<div class="intro">
<h2>
${this.hass.localize(
"ui.panel.config.entity_registry.picker.header"
)}
${this.hass.localize("ui.panel.config.entities.picker.header")}
</h2>
<p>
${this.hass.localize(
"ui.panel.config.entity_registry.picker.introduction"
"ui.panel.config.entities.picker.introduction"
)}
</p>
<p>
${this.hass.localize(
"ui.panel.config.entity_registry.picker.introduction2"
"ui.panel.config.entities.picker.introduction2"
)}
</p>
<a href="/config/integrations">
${this.hass.localize(
"ui.panel.config.entity_registry.picker.integrations_page"
"ui.panel.config.entities.picker.integrations_page"
)}
</a>
</div>
@@ -257,7 +254,7 @@ class HaConfigEntityRegistry extends LitElement {
? html`
<p class="selected-txt">
${this.hass.localize(
"ui.panel.config.entity_registry.picker.selected",
"ui.panel.config.entities.picker.selected",
"number",
this._selectedEntities.length
)}
@@ -267,17 +264,17 @@ class HaConfigEntityRegistry extends LitElement {
? html`
<mwc-button @click=${this._enableSelected}
>${this.hass.localize(
"ui.panel.config.entity_registry.picker.enable_selected.button"
"ui.panel.config.entities.picker.enable_selected.button"
)}</mwc-button
>
<mwc-button @click=${this._disableSelected}
>${this.hass.localize(
"ui.panel.config.entity_registry.picker.disable_selected.button"
"ui.panel.config.entities.picker.disable_selected.button"
)}</mwc-button
>
<mwc-button @click=${this._removeSelected}
>${this.hass.localize(
"ui.panel.config.entity_registry.picker.remove_selected.button"
"ui.panel.config.entities.picker.remove_selected.button"
)}</mwc-button
>
`
@@ -289,7 +286,7 @@ class HaConfigEntityRegistry extends LitElement {
></paper-icon-button>
<paper-tooltip for="enable-btn">
${this.hass.localize(
"ui.panel.config.entity_registry.picker.enable_selected.button"
"ui.panel.config.entities.picker.enable_selected.button"
)}
</paper-tooltip>
<paper-icon-button
@@ -299,7 +296,7 @@ class HaConfigEntityRegistry extends LitElement {
></paper-icon-button>
<paper-tooltip for="disable-btn">
${this.hass.localize(
"ui.panel.config.entity_registry.picker.disable_selected.button"
"ui.panel.config.entities.picker.disable_selected.button"
)}
</paper-tooltip>
<paper-icon-button
@@ -309,7 +306,7 @@ class HaConfigEntityRegistry extends LitElement {
></paper-icon-button>
<paper-tooltip for="remove-btn">
${this.hass.localize(
"ui.panel.config.entity_registry.picker.remove_selected.button"
"ui.panel.config.entities.picker.remove_selected.button"
)}
</paper-tooltip>
`}
@@ -323,10 +320,10 @@ class HaConfigEntityRegistry extends LitElement {
<paper-menu-button no-animations horizontal-align="right">
<paper-icon-button
aria-label=${this.hass!.localize(
"ui.panel.config.entity_registry.picker.filter.filter"
"ui.panel.config.entities.picker.filter.filter"
)}
title="${this.hass!.localize(
"ui.panel.config.entity_registry.picker.filter.filter"
"ui.panel.config.entities.picker.filter.filter"
)}"
icon="hass:filter-variant"
slot="dropdown-trigger"
@@ -338,7 +335,7 @@ class HaConfigEntityRegistry extends LitElement {
slot="item-icon"
></paper-checkbox>
${this.hass!.localize(
"ui.panel.config.entity_registry.picker.filter.show_disabled"
"ui.panel.config.entities.picker.filter.show_disabled"
)}
</paper-icon-item>
<paper-icon-item @click="${this._showRestoredChanged}">
@@ -347,7 +344,7 @@ class HaConfigEntityRegistry extends LitElement {
slot="item-icon"
></paper-checkbox>
${this.hass!.localize(
"ui.panel.config.entity_registry.picker.filter.show_unavailable"
"ui.panel.config.entities.picker.filter.show_unavailable"
)}
</paper-icon-item>
</paper-listbox>
@@ -404,12 +401,12 @@ class HaConfigEntityRegistry extends LitElement {
private _enableSelected() {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.enable_selected.confirm_title",
"ui.panel.config.entities.picker.enable_selected.confirm_title",
"number",
this._selectedEntities.length
),
text: this.hass.localize(
"ui.panel.config.entity_registry.picker.enable_selected.confirm_text"
"ui.panel.config.entities.picker.enable_selected.confirm_text"
),
confirmBtnText: this.hass.localize("ui.common.yes"),
cancelBtnText: this.hass.localize("ui.common.no"),
@@ -427,12 +424,12 @@ class HaConfigEntityRegistry extends LitElement {
private _disableSelected() {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.disable_selected.confirm_title",
"ui.panel.config.entities.picker.disable_selected.confirm_title",
"number",
this._selectedEntities.length
),
text: this.hass.localize(
"ui.panel.config.entity_registry.picker.disable_selected.confirm_text"
"ui.panel.config.entities.picker.disable_selected.confirm_text"
),
confirmBtnText: this.hass.localize("ui.common.yes"),
cancelBtnText: this.hass.localize("ui.common.no"),
@@ -454,12 +451,12 @@ class HaConfigEntityRegistry extends LitElement {
});
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.remove_selected.confirm_title",
"ui.panel.config.entities.picker.remove_selected.confirm_title",
"number",
removeableEntities.length
),
text: this.hass.localize(
"ui.panel.config.entity_registry.picker.remove_selected.confirm_text"
"ui.panel.config.entities.picker.remove_selected.confirm_text"
),
confirmBtnText: this.hass.localize("ui.common.yes"),
cancelBtnText: this.hass.localize("ui.common.no"),
@@ -496,14 +493,14 @@ class HaConfigEntityRegistry extends LitElement {
}
h2 {
margin-top: 0;
font-family: var(--paper-font-display1_-_font-family);
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-display1_-_-webkit-font-smoothing
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-display1_-_font-size);
font-weight: var(--paper-font-display1_-_font-weight);
letter-spacing: var(--paper-font-display1_-_letter-spacing);
line-height: var(--paper-font-display1_-_line-height);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
p {
@@ -511,10 +508,8 @@ class HaConfigEntityRegistry extends LitElement {
-webkit-font-smoothing: var(
--paper-font-subhead_-_-webkit-font-smoothing
);
font-size: var(--paper-font-subhead_-_font-size);
font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height);
opacity: var(--dark-primary-opacity);
}
.intro {
padding: 24px 16px;
@@ -549,5 +544,3 @@ class HaConfigEntityRegistry extends LitElement {
`;
}
}
customElements.define("ha-config-entity-registry", HaConfigEntityRegistry);
+170
View File
@@ -0,0 +1,170 @@
import { property, customElement } from "lit-element";
import "../../layouts/hass-loading-screen";
import { HomeAssistant } from "../../types";
import { CloudStatus } from "../../data/cloud";
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
import { PolymerElement } from "@polymer/polymer";
declare global {
// for fire event
interface HASSDomEvents {
"ha-refresh-cloud-status": undefined;
}
}
@customElement("ha-config-router")
class HaConfigRouter extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public wideSidebar: boolean = false;
@property() public wide: boolean = false;
@property() public isWide: boolean = false;
@property() public showAdvanced: boolean = false;
@property() public cloudStatus?: CloudStatus;
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
cacheAll: true,
preloadAll: true,
routes: {
areas: {
tag: "ha-config-areas",
load: () =>
import(
/* webpackChunkName: "panel-config-areas" */ "./areas/ha-config-areas"
),
},
automation: {
tag: "ha-config-automation",
load: () =>
import(
/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"
),
},
cloud: {
tag: "ha-config-cloud",
load: () =>
import(
/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"
),
},
core: {
tag: "ha-config-core",
load: () =>
import(
/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"
),
},
devices: {
tag: "ha-config-devices",
load: () =>
import(
/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"
),
},
server_control: {
tag: "ha-config-server-control",
load: () =>
import(
/* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control"
),
},
customize: {
tag: "ha-config-customize",
load: () =>
import(
/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"
),
},
dashboard: {
tag: "ha-config-dashboard",
load: () =>
import(
/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"
),
},
entities: {
tag: "ha-config-entities",
load: () =>
import(
/* webpackChunkName: "panel-config-entities" */ "./entities/ha-config-entities"
),
},
integrations: {
tag: "ha-config-integrations",
load: () =>
import(
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
),
},
person: {
tag: "ha-config-person",
load: () =>
import(
/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"
),
},
script: {
tag: "ha-config-script",
load: () =>
import(
/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"
),
},
scene: {
tag: "ha-config-scene",
load: () =>
import(
/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"
),
},
users: {
tag: "ha-config-users",
load: () =>
import(
/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"
),
},
zha: {
tag: "zha-config-dashboard-router",
load: () =>
import(
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-dashboard-router"
),
},
zwave: {
tag: "ha-config-zwave",
load: () =>
import(
/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"
),
},
},
};
protected updatePageEl(el) {
if ("setProperties" in el) {
// As long as we have Polymer panels
(el as PolymerElement).setProperties({
route: this.routeTail,
hass: this.hass,
showAdvanced: this.showAdvanced,
isWide: this.isWide,
narrow: this.narrow,
cloudStatus: this.cloudStatus,
});
} else {
el.route = this.routeTail;
el.hass = this.hass;
el.showAdvanced = this.showAdvanced;
el.isWide = this.isWide;
el.narrow = this.narrow;
el.cloudStatus = this.cloudStatus;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-router": HaConfigRouter;
}
}
-98
View File
@@ -1,98 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../resources/ha-style";
class HaConfigSection extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
padding: 28px 20px 0;
max-width: 1040px;
margin: 0 auto;
}
.header {
@apply --paper-font-display1;
opacity: var(--dark-primary-opacity);
}
.together {
margin-top: 32px;
}
.intro {
@apply --paper-font-subhead;
width: 100%;
max-width: 400px;
margin-right: 40px;
opacity: var(--dark-primary-opacity);
}
.panel {
margin-top: -24px;
}
.panel ::slotted(*) {
margin-top: 24px;
display: block;
}
.narrow.content {
max-width: 640px;
}
.narrow .together {
margin-top: 20px;
}
.narrow .header {
@apply --paper-font-headline;
}
.narrow .intro {
font-size: 14px;
padding-bottom: 20px;
margin-right: 0;
max-width: 500px;
}
</style>
<div class$="[[computeContentClasses(isWide)]]">
<div class="header"><slot name="header"></slot></div>
<div class$="[[computeClasses(isWide)]]">
<div class="intro"><slot name="introduction"></slot></div>
<div class="panel flex-auto"><slot></slot></div>
</div>
</div>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
narrow: {
type: Boolean,
},
isWide: {
type: Boolean,
value: false,
},
};
}
computeContentClasses(isWide) {
var classes = "content ";
return isWide ? classes : classes + "narrow";
}
computeClasses(isWide) {
var classes = "together layout ";
return classes + (isWide ? "horizontal" : "vertical narrow");
}
}
customElements.define("ha-config-section", HaConfigSection);
+65
View File
@@ -0,0 +1,65 @@
import { customElement } from "lit-element";
@customElement("ha-config-section")
export class HaConfigSection extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot!.innerHTML = `
<style>
.content {
padding: 28px 20px 0;
max-width: 640px;
margin: 0 auto;
}
.header {
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
.together {
margin-top: 20px;
}
.intro {
font-family: var(--paper-font-subhead_-_font-family);
-webkit-font-smoothing: var(
--paper-font-subhead_-_-webkit-font-smoothing
);
font-weight: var(--paper-font-subhead_-_font-weight);
line-height: var(--paper-font-subhead_-_line-height);
width: 100%;
width: 100%;
max-width: 500px;
opacity: var(--dark-primary-opacity);
font-size: 14px;
padding-bottom: 20px;
}
.panel {
margin-top: -24px;
}
.panel ::slotted(*) {
margin-top: 24px;
display: block;
}
</style>
<div class="content">
<div class="header"><slot name="header"></slot></div>
<div class="together">
<div class="intro"><slot name="introduction"></slot></div>
<div class="panel"><slot></slot></div>
</div>
</div>
`;
}
}
+121 -148
View File
@@ -1,15 +1,26 @@
import { property, PropertyValues, customElement } from "lit-element";
import {
property,
PropertyValues,
customElement,
LitElement,
html,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-item/paper-item";
import "../../layouts/hass-loading-screen";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { HomeAssistant } from "../../types";
import { HomeAssistant, Route } from "../../types";
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
import { listenMediaQuery } from "../../common/dom/media_query";
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
import {
CoreFrontendUserData,
getOptimisticFrontendUserDataCollection,
CoreFrontendUserData,
} from "../../data/frontend";
import { PolymerElement } from "@polymer/polymer";
import "./ha-config-router";
import "./dashboard/ha-config-navigation";
import { classMap } from "lit-html/directives/class-map";
declare global {
// for fire event
@@ -19,133 +30,15 @@ declare global {
}
@customElement("ha-panel-config")
class HaPanelConfig extends HassRouterPage {
class HaPanelConfig extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
cacheAll: true,
preloadAll: true,
routes: {
area_registry: {
tag: "ha-config-area-registry",
load: () =>
import(
/* webpackChunkName: "panel-config-area-registry" */ "./area_registry/ha-config-area-registry"
),
},
automation: {
tag: "ha-config-automation",
load: () =>
import(
/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"
),
},
cloud: {
tag: "ha-config-cloud",
load: () =>
import(
/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"
),
},
core: {
tag: "ha-config-core",
load: () =>
import(
/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"
),
},
devices: {
tag: "ha-config-devices",
load: () =>
import(
/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"
),
},
server_control: {
tag: "ha-config-server-control",
load: () =>
import(
/* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control"
),
},
customize: {
tag: "ha-config-customize",
load: () =>
import(
/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"
),
},
dashboard: {
tag: "ha-config-dashboard",
load: () =>
import(
/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"
),
},
entity_registry: {
tag: "ha-config-entity-registry",
load: () =>
import(
/* webpackChunkName: "panel-config-entity-registry" */ "./entity_registry/ha-config-entity-registry"
),
},
integrations: {
tag: "ha-config-integrations",
load: () =>
import(
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
),
},
person: {
tag: "ha-config-person",
load: () =>
import(
/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"
),
},
script: {
tag: "ha-config-script",
load: () =>
import(
/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"
),
},
scene: {
tag: "ha-config-scene",
load: () =>
import(
/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"
),
},
users: {
tag: "ha-config-users",
load: () =>
import(
/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"
),
},
zha: {
tag: "zha-config-dashboard-router",
load: () =>
import(
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-dashboard-router"
),
},
zwave: {
tag: "ha-config-zwave",
load: () =>
import(
/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"
),
},
},
};
@property() public route!: Route;
@property() private _wideSidebar: boolean = false;
@property() private _wide: boolean = false;
@property() private _coreUserData?: CoreFrontendUserData;
@property() private _showAdvanced = false;
@property() private _cloudStatus?: CloudStatus;
private _listeners: Array<() => void> = [];
@@ -168,6 +61,9 @@ class HaPanelConfig extends HassRouterPage {
"core"
).subscribe((coreUserData) => {
this._coreUserData = coreUserData || {};
this._showAdvanced = !!(
this._coreUserData && this._coreUserData.showAdvanced
);
})
);
}
@@ -189,31 +85,60 @@ class HaPanelConfig extends HassRouterPage {
);
}
protected updatePageEl(el) {
const showAdvanced = !!(
this._coreUserData && this._coreUserData.showAdvanced
);
protected render() {
const dividerPos = this.route.path.indexOf("/", 1);
const curPage =
dividerPos === -1
? this.route.path.substr(1)
: this.route.path.substr(1, dividerPos - 1);
const isWide =
this.hass.dockedSidebar === "docked" ? this._wideSidebar : this._wide;
if ("setProperties" in el) {
// As long as we have Polymer panels
(el as PolymerElement).setProperties({
route: this.routeTail,
hass: this.hass,
showAdvanced,
isWide,
narrow: this.narrow,
cloudStatus: this._cloudStatus,
});
} else {
el.route = this.routeTail;
el.hass = this.hass;
el.showAdvanced = showAdvanced;
el.isWide = isWide;
el.narrow = this.narrow;
el.cloudStatus = this._cloudStatus;
}
return html`
${isWide
? html`
<div class="side-bar">
<div class="toolbar">Configuration</div>
<div class="navigation">
<ha-config-navigation
.hass=${this.hass}
.showAdvanced=${this._showAdvanced}
.curPage=${curPage}
.pages=${[
{ page: "cloud", info: this._cloudStatus },
{ page: "integrations", core: true },
{ page: "devices", core: true },
{ page: "entities", core: true },
{ page: "automation" },
{ page: "script" },
{ page: "scene" },
{ page: "core", core: true },
{ page: "areas", core: true },
{ page: "person" },
{ page: "users", core: true },
{ page: "server_control", core: true },
{ page: "zha" },
{ page: "zwave" },
{ page: "customize", core: true, advanced: true },
]}
></ha-config-navigation>
</div>
</div>
`
: ""}
<ha-config-router
.hass=${this.hass}
.route=${this.route}
.narrow=${this.narrow}
.isWide=${isWide}
.wide=${this._wide}
.wideSidebar=${this._wideSidebar}
.showAdvanced=${this._showAdvanced}
.cloudStatus=${this._cloudStatus}
class=${classMap({ "wide-config": isWide })}
></ha-config-router>
`;
}
private async _updateCloudStatus() {
@@ -223,6 +148,54 @@ class HaPanelConfig extends HassRouterPage {
setTimeout(() => this._updateCloudStatus(), 5000);
}
}
static get styles(): CSSResult {
return css`
:host {
display: block;
height: 100%;
background-color: var(--primary-background-color);
}
a {
text-decoration: none;
color: var(--primary-text-color);
}
.side-bar {
border-right: 1px solid #e0e0e0;
background: white;
width: 320px;
float: left;
box-sizing: border-box;
position: fixed;
}
.toolbar {
display: flex;
align-items: center;
font-size: 20px;
height: 64px;
padding: 0 16px 0 16px;
pointer-events: none;
background-color: var(--primary-background-color);
font-weight: 400;
color: var(--primary-text-color);
border-bottom: 1px solid var(--divider-color);
}
.wide-config {
float: right;
width: calc(100% - 320px);
height: 100%;
}
.navigation {
height: calc(100vh - 64px);
overflow: auto;
}
`;
}
}
declare global {
@@ -49,6 +49,7 @@ import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialo
export class HaConfigManagerDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public showAdvanced!: boolean;
@property() public isWide!: boolean;
@property() private configEntries!: ConfigEntry[];
@@ -72,7 +73,8 @@ export class HaConfigManagerDashboard extends LitElement {
protected render(): TemplateResult {
return html`
<hass-subpage
header=${this.hass.localize("ui.panel.config.integrations.caption")}
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.integrations.caption")}
>
<paper-menu-button
close-on-activate
@@ -39,6 +39,7 @@ declare global {
class HaConfigIntegrations extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean;
protected routerOptions: RouterOptions = {
@@ -101,6 +102,7 @@ class HaConfigIntegrations extends HassRouterPage {
pageEl.entityRegistryEntries = this._entityRegistryEntries;
pageEl.configEntries = this._configEntries;
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced;
if (this._currentPage === "dashboard") {
@@ -227,6 +227,7 @@ class DialogPersonDetail extends LitElement {
mwc-dialog {
min-width: 400px;
max-width: 600px;
--mdc-dialog-title-ink-color: var(--primary-text-color);
}
.form {
padding-bottom: 24px;
@@ -243,6 +244,9 @@ class DialogPersonDetail extends LitElement {
a {
color: var(--primary-color);
}
p {
color: var(--primary-text-color);
}
`,
];
}
+4 -1
View File
@@ -48,7 +48,10 @@ class HaConfigPerson extends LitElement {
}
const hass = this.hass;
return html`
<hass-subpage header=${hass.localize("ui.panel.config.person.caption")}>
<hass-subpage
.header=${hass.localize("ui.panel.config.person.caption")}
.showBackButton=${!this.isWide}
>
<ha-config-section .isWide=${this.isWide}>
<span slot="header"
>${hass.localize("ui.panel.config.person.caption")}</span
@@ -20,6 +20,7 @@ import { HassEntities } from "home-assistant-js-websocket";
class HaConfigScene extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean;
@property() public scenes: SceneEntity[] = [];
@@ -52,6 +53,7 @@ class HaConfigScene extends HassRouterPage {
protected updatePageEl(pageEl, changedProps: PropertyValues) {
pageEl.hass = this.hass;
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced;
if (this.hass) {
@@ -30,14 +30,16 @@ import { forwardHaptic } from "../../../data/haptics";
class HaSceneDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public scenes!: SceneEntity[];
protected render(): TemplateResult | void {
return html`
<hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.scene.caption")}
>
<ha-config-section .isWide=${!this.narrow}>
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize("ui.panel.config.scene.picker.header")}
</div>
+5 -5
View File
@@ -69,7 +69,7 @@ interface DeviceEntitiesLookup {
@customElement("ha-scene-editor")
export class HaSceneEditor extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public narrow?: boolean;
@property() public isWide?: boolean;
@property() public scene?: SceneEntity;
@property() public creatingNew?: boolean;
@property() public showAdvanced!: boolean;
@@ -196,7 +196,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
rtl: computeRTL(this.hass),
})}"
>
<ha-config-section .isWide=${!this.narrow}>
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
${this.scene
? computeStateName(this.scene)
@@ -222,7 +222,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
</ha-card>
</ha-config-section>
<ha-config-section .isWide=${!this.narrow}>
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize(
"ui.panel.config.scene.editor.devices.header"
@@ -293,7 +293,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
${this.showAdvanced
? html`
<ha-config-section .isWide=${!this.narrow}>
<ha-config-section .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize(
"ui.panel.config.scene.editor.entities.header"
@@ -371,7 +371,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
</div>
<ha-fab
slot="fab"
?is-wide="${!this.narrow}"
?is-wide="${this.isWide}"
?dirty="${this._dirty}"
icon="hass:content-save"
.title="${this.hass.localize("ui.panel.config.scene.editor.save")}"
+2 -1
View File
@@ -35,6 +35,7 @@ class HaScriptPicker extends LitElement {
protected render(): TemplateResult | void {
return html`
<hass-subpage
.showBackButton=${!this.isWide}
.header=${this.hass.localize("ui.panel.config.script.caption")}
>
<ha-config-section .isWide=${this.isWide}>
@@ -77,7 +78,7 @@ class HaScriptPicker extends LitElement {
)}"
@click=${this._runScript}
></paper-icon-button>
<paper-item-body>
<paper-item-body two-line>
<div>${computeStateName(script)}</div>
</paper-item-body>
<div class="actions">
@@ -35,6 +35,7 @@ class HaConfigServerControl extends LocalizeMixin(PolymerElement) {
<hass-subpage
header="[[localize('ui.panel.config.server_control.caption')]]"
show-back-button="[[!isWide]]"
>
<div class$="[[computeClasses(isWide)]]">
<ha-config-section-server-control
@@ -58,7 +58,10 @@ class HaUserPicker extends EventsMixin(
}
</style>
<hass-subpage header="[[localize('ui.panel.config.users.picker.title')]]">
<hass-subpage
header="[[localize('ui.panel.config.users.picker.title')]]"
show-back-button="[[!isWide]]"
>
<ha-card>
<template is="dom-repeat" items="[[users]]" as="user">
<a href="[[_computeUrl(user)]]">
@@ -94,7 +97,7 @@ class HaUserPicker extends EventsMixin(
return {
hass: Object,
users: Array,
isWide: Boolean,
rtl: {
type: Boolean,
reflectToAttribute: true,
@@ -27,6 +27,7 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
<ha-config-user-picker
hass="[[hass]]"
users="[[_users]]"
is-wide="[[isWide]]"
></ha-config-user-picker>
</template>
<template
@@ -45,6 +46,7 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
static get properties() {
return {
hass: Object,
isWide: Boolean,
route: {
type: Object,
observer: "_checkRoute",
+7 -1
View File
@@ -1,4 +1,4 @@
import { ZHADevice, ZHAGroup } from "../../../data/zha";
import { ZHADevice, ZHAGroup, Cluster } from "../../../data/zha";
export const formatAsPaddedHex = (value: string | number): string => {
let hex = value;
@@ -19,3 +19,9 @@ export const sortZHAGroups = (a: ZHAGroup, b: ZHAGroup): number => {
const nameb = b.name;
return nameA.localeCompare(nameb);
};
export const computeClusterKey = (cluster: Cluster): string => {
return `${cluster.name} (Endpoint id: ${
cluster.endpoint_id
}, Id: ${formatAsPaddedHex(cluster.id)}, Type: ${cluster.type})`;
};
@@ -120,7 +120,7 @@ class ZHAAddDevicesPage extends LitElement {
.narrow=${!this.isWide}
.showHelp=${this._showHelp}
.showActions=${!this._active}
isJoinPage
.showEntityDetail=${false}
></zha-device-card>
`
)}
+4 -1
View File
@@ -117,7 +117,10 @@ export class ZHAAddGroupPage extends LitElement {
private _handleAddSelectionChanged(ev: CustomEvent): void {
const changedSelection = ev.detail as SelectionChangedEvent;
const entity = changedSelection.id;
if (changedSelection.selected) {
if (
changedSelection.selected &&
!this._selectedDevicesToAdd.includes(entity)
) {
this._selectedDevicesToAdd.push(entity);
} else {
const index = this._selectedDevicesToAdd.indexOf(entity);
@@ -0,0 +1,92 @@
import "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-state-icon";
import memoizeOne from "memoize-one";
import {
LitElement,
html,
TemplateResult,
property,
customElement,
} from "lit-element";
import { HomeAssistant } from "../../../types";
// tslint:disable-next-line
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
// tslint:disable-next-line
import { Cluster } from "../../../data/zha";
import { formatAsPaddedHex } from "./functions";
export interface ClusterRowData extends Cluster {
cluster?: Cluster;
cluster_id?: string;
}
@customElement("zha-clusters-data-table")
export class ZHAClustersDataTable extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow = false;
@property() public clusters: Cluster[] = [];
private _clusters = memoizeOne((clusters: Cluster[]) => {
let outputClusters: ClusterRowData[] = clusters;
outputClusters = outputClusters.map((cluster) => {
return {
...cluster,
cluster_id: cluster.endpoint_id + "-" + cluster.id,
};
});
return outputClusters;
});
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer =>
narrow
? {
name: {
title: "Name",
sortable: true,
direction: "asc",
},
}
: {
name: {
title: "Name",
sortable: true,
direction: "asc",
},
id: {
title: "ID",
template: (id: number) => {
return html`
${formatAsPaddedHex(id)}
`;
},
sortable: true,
},
endpoint_id: {
title: "Endpoint ID",
sortable: true,
},
}
);
protected render(): TemplateResult {
return html`
<ha-data-table
.columns=${this._columns(this.narrow)}
.data=${this._clusters(this.clusters)}
.id=${"cluster_id"}
selectable
></ha-data-table>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-clusters-data-table": ZHAClustersDataTable;
}
}
+1 -7
View File
@@ -21,7 +21,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { Cluster, fetchClustersForZhaNode, ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { formatAsPaddedHex } from "./functions";
import { computeClusterKey } from "./functions";
import { ItemSelectedEvent } from "./types";
declare global {
@@ -33,12 +33,6 @@ declare global {
}
}
const computeClusterKey = (cluster: Cluster): string => {
return `${cluster.name} (Endpoint id: ${
cluster.endpoint_id
}, Id: ${formatAsPaddedHex(cluster.id)}, Type: ${cluster.type})`;
};
export class ZHAClusters extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@@ -48,7 +48,6 @@ class ZHAConfigDashboard extends LitElement {
...device,
name: device.user_given_name ? device.user_given_name : device.name,
nwk: formatAsPaddedHex(device.nwk),
id: device.ieee,
};
});
@@ -103,7 +102,10 @@ class ZHAConfigDashboard extends LitElement {
protected render(): TemplateResult | void {
return html`
<hass-subpage .header=${this.hass.localize("ui.panel.config.zha.title")}>
<hass-subpage
.header=${this.hass.localize("ui.panel.config.zha.title")}
.showBackButton=${!this.isWide}
>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize("ui.panel.config.zha.header")}
@@ -139,6 +141,7 @@ class ZHAConfigDashboard extends LitElement {
.columns=${this._columns(this.narrow)}
.data=${this._memoizeDevices(this._devices)}
@row-click=${this._handleDeviceClicked}
.id=${"ieee"}
></ha-data-table>
</ha-card>
</ha-config-section>
@@ -24,8 +24,8 @@ import { HomeAssistant } from "../../../types";
import { ItemSelectedEvent } from "./types";
import "@polymer/paper-item/paper-item";
@customElement("zha-binding-control")
export class ZHABindingControl extends LitElement {
@customElement("zha-device-binding-control")
export class ZHADeviceBindingControl extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() public selectedDevice?: ZHADevice;
@@ -175,7 +175,9 @@ export class ZHABindingControl extends LitElement {
.helpText {
color: grey;
padding: 16px;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.header {
@@ -204,6 +206,6 @@ export class ZHABindingControl extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"zha-binding-control": ZHABindingControl;
"zha-device-binding-control": ZHADeviceBindingControl;
}
}
+53 -39
View File
@@ -58,8 +58,11 @@ class ZHADeviceCard extends LitElement {
@property() public device?: ZHADevice;
@property({ type: Boolean }) public narrow?: boolean;
@property({ type: Boolean }) public showHelp?: boolean = false;
@property({ type: Boolean }) public showActions?: boolean;
@property({ type: Boolean }) public isJoinPage?: boolean;
@property({ type: Boolean }) public showActions?: boolean = true;
@property({ type: Boolean }) public showName?: boolean = true;
@property({ type: Boolean }) public showEntityDetail?: boolean = true;
@property({ type: Boolean }) public showModelInfo?: boolean = true;
@property({ type: Boolean }) public showEditableInfo?: boolean = true;
@property() private _serviceData?: NodeServiceData;
@property() private _areas: AreaRegistryEntry[] = [];
@property() private _selectedAreaIndex: number = -1;
@@ -137,9 +140,9 @@ class ZHADeviceCard extends LitElement {
protected render(): TemplateResult | void {
return html`
<ha-card header="${this.isJoinPage ? this.device!.name : ""}">
<ha-card header="${this.showName ? this.device!.name : ""}">
${
this.isJoinPage
this.showModelInfo
? html`
<div class="info">
<div class="model">${this.device!.model}</div>
@@ -202,7 +205,7 @@ class ZHADeviceCard extends LitElement {
.stateObj="${this.hass!.states[entity.entity_id]}"
slot="item-icon"
></state-badge>
${!this.isJoinPage
${this.showEntityDetail
? html`
<paper-item-body>
<div class="name">
@@ -218,40 +221,48 @@ class ZHADeviceCard extends LitElement {
`
)}
</div>
<div class="editable">
<paper-input
type="string"
@change="${this._saveCustomName}"
.value="${this._userGivenName}"
placeholder="${this.hass!.localize(
"ui.dialogs.zha_device_info.zha_device_card.device_name_placeholder"
)}"
></paper-input>
</div>
<div class="node-picker">
<paper-dropdown-menu
label="${this.hass!.localize(
"ui.dialogs.zha_device_info.zha_device_card.area_picker_label"
)}"
class="menu"
>
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedAreaIndex}"
@iron-select="${this._selectedAreaChanged}"
>
<paper-item>
${this.hass!.localize("ui.dialogs.zha_device_info.no_area")}
</paper-item>
${
this.showEditableInfo
? html`
<div class="editable">
<paper-input
type="string"
@change="${this._saveCustomName}"
.value="${this._userGivenName}"
.placeholder="${this.hass!.localize(
"ui.dialogs.zha_device_info.zha_device_card.device_name_placeholder"
)}"
></paper-input>
</div>
<div class="node-picker">
<paper-dropdown-menu
.label="${this.hass!.localize(
"ui.dialogs.zha_device_info.zha_device_card.area_picker_label"
)}"
class="menu"
>
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedAreaIndex}"
@iron-select="${this._selectedAreaChanged}"
>
<paper-item>
${this.hass!.localize(
"ui.dialogs.zha_device_info.no_area"
)}
</paper-item>
${this._areas.map(
(entry) => html`
<paper-item area="${entry}">${entry.name}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this._areas.map(
(entry) => html`
<paper-item>${entry.name}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
`
: ""
}
${
this.showActions
? html`
@@ -275,6 +286,9 @@ class ZHADeviceCard extends LitElement {
.hass="${this.hass}"
domain="zha"
service="remove"
.confirmation=${this.hass!.localize(
"ui.dialogs.zha_device_info.confirmations.remove"
)}
.serviceData="${this._serviceData}"
>
${this.hass!.localize(
@@ -376,7 +390,7 @@ class ZHADeviceCard extends LitElement {
}
private _onAddDevicesClick() {
navigate(this, "add/" + this.device!.ieee);
navigate(this, "/config/zha/add/" + this.device!.ieee);
}
static get styles(): CSSResult[] {
+42 -4
View File
@@ -1,6 +1,7 @@
import "../../../layouts/hass-subpage";
import "../../../components/ha-paper-icon-button-arrow-prev";
import "./zha-binding";
import "./zha-device-binding";
import "./zha-group-binding";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-clusters";
@@ -24,10 +25,12 @@ import {
fetchBindableDevices,
ZHADevice,
fetchZHADevice,
ZHAGroup,
fetchGroups,
} from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { sortZHADevices } from "./functions";
import { sortZHADevices, sortZHAGroups } from "./functions";
import { ZHAClusterSelectedParams } from "./types";
@customElement("zha-device-page")
@@ -36,8 +39,27 @@ export class ZHADevicePage extends LitElement {
@property() public isWide?: boolean;
@property() public ieee?: string;
@property() public device?: ZHADevice;
@property() public narrow?: boolean;
@property() private _selectedCluster?: Cluster;
@property() private _bindableDevices: ZHADevice[] = [];
@property() private _groups: ZHAGroup[] = [];
private _firstUpdatedCalled: boolean = false;
public connectedCallback(): void {
super.connectedCallback();
if (this.hass && this._firstUpdatedCalled) {
this._fetchGroups();
}
}
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this.hass) {
this._fetchGroups();
}
this._firstUpdatedCalled = true;
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("ieee")) {
@@ -50,6 +72,7 @@ export class ZHADevicePage extends LitElement {
return html`
<hass-subpage
.header=${this.hass!.localize("ui.panel.config.zha.devices.header")}
.back=${!this.isWide}
>
<zha-node
.isWide="${this.isWide}"
@@ -81,12 +104,23 @@ export class ZHADevicePage extends LitElement {
: ""}
${this._bindableDevices.length > 0
? html`
<zha-binding-control
<zha-device-binding-control
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedDevice="${this.device}"
.bindableDevices="${this._bindableDevices}"
></zha-binding-control>
></zha-device-binding-control>
`
: ""}
${this.device && this._groups.length > 0
? html`
<zha-group-binding-control
.isWide="${this.isWide}"
.narrow="${this.narrow}"
.hass="${this.hass}"
.selectedDevice="${this.device}"
.groups="${this._groups}"
></zha-group-binding-control>
`
: ""}
<div class="spacer" />
@@ -109,6 +143,10 @@ export class ZHADevicePage extends LitElement {
}
}
private async _fetchGroups() {
this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups);
}
static get styles(): CSSResult[] {
return [
haStyle,
+321
View File
@@ -0,0 +1,321 @@
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import "../../../components/ha-card";
import "../ha-config-section";
import "@material/mwc-button/mwc-button";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import {
bindDeviceToGroup,
unbindDeviceFromGroup,
ZHADevice,
ZHAGroup,
Cluster,
fetchClustersForZhaNode,
} from "../../../data/zha";
import "./zha-clusters-data-table";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { ItemSelectedEvent } from "./types";
import "@polymer/paper-item/paper-item";
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
@customElement("zha-group-binding-control")
export class ZHAGroupBindingControl extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() public narrow?: boolean;
@property() public selectedDevice?: ZHADevice;
@property() private _showHelp: boolean = false;
@property() private _bindTargetIndex: number = -1;
@property() private groups: ZHAGroup[] = [];
@property() private _selectedClusters: string[] = [];
@property() private _clusters: Cluster[] = [];
private _groupToBind?: ZHAGroup;
private _clustersToBind?: Cluster[];
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedDevice")) {
this._bindTargetIndex = -1;
this._selectedClusters = [];
this._clustersToBind = [];
this._fetchClustersForZhaNode();
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header">
<span
>${this.hass!.localize(
"ui.panel.config.zha.group_binding.header"
)}</span
>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
icon="hass:help-circle"
>
</paper-icon-button>
</div>
<span slot="introduction"
>${this.hass!.localize(
"ui.panel.config.zha.group_binding.introduction"
)}</span
>
<ha-card class="content">
<div class="command-picker">
<paper-dropdown-menu
.label=${this.hass!.localize(
"ui.panel.config.zha.group_binding.group_picker_label"
)}
class="menu"
>
<paper-listbox
slot="dropdown-content"
.selected="${this._bindTargetIndex}"
@iron-select="${this._bindTargetIndexChanged}"
>
${this.groups.map(
(group) => html`
<paper-item>${group.name}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.group_binding.group_picker_help"
)}
</div>
`
: ""}
<div class="command-picker">
<zha-clusters-data-table
.hass=${this.hass}
.narrow=${this.narrow}
.clusters=${this._clusters}
@selection-changed=${this._handleClusterSelectionChanged}
class="menu"
></zha-clusters-data-table>
</div>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.group_binding.cluster_selection_help"
)}
</div>
`
: ""}
<div class="card-actions">
<mwc-button
@click="${this._onBindGroupClick}"
.disabled="${!this._canBind}"
>${this.hass!.localize(
"ui.panel.config.zha.group_binding.bind_button_label"
)}</mwc-button
>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.group_binding.bind_button_help"
)}
</div>
`
: ""}
<mwc-button
@click="${this._onUnbindGroupClick}"
.disabled="${!this._canBind}"
>${this.hass!.localize(
"ui.panel.config.zha.group_binding.unbind_button_label"
)}</mwc-button
>
${this._showHelp
? html`
<div class="helpText">
${this.hass!.localize(
"ui.panel.config.zha.group_binding.unbind_button_help"
)}
</div>
`
: ""}
</div>
</ha-card>
</ha-config-section>
`;
}
private _bindTargetIndexChanged(event: ItemSelectedEvent): void {
this._bindTargetIndex = event.target!.selected;
this._groupToBind =
this._bindTargetIndex === -1
? undefined
: this.groups[this._bindTargetIndex];
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private async _onBindGroupClick(): Promise<void> {
if (this.hass && this._canBind) {
await bindDeviceToGroup(
this.hass,
this.selectedDevice!.ieee,
this._groupToBind!.group_id,
this._clustersToBind!
);
}
}
private async _onUnbindGroupClick(): Promise<void> {
if (this.hass && this._canBind) {
await unbindDeviceFromGroup(
this.hass,
this.selectedDevice!.ieee,
this._groupToBind!.group_id,
this._clustersToBind!
);
}
}
private _handleClusterSelectionChanged(event: CustomEvent): void {
const changedSelection = event.detail as SelectionChangedEvent;
const clusterId = changedSelection.id;
if (
changedSelection.selected &&
!this._selectedClusters.includes(clusterId)
) {
this._selectedClusters.push(clusterId);
} else {
const index = this._selectedClusters.indexOf(clusterId);
if (index !== -1) {
this._selectedClusters.splice(index, 1);
}
}
this._selectedClusters = [...this._selectedClusters];
this._clustersToBind = [];
for (const clusterIndex of this._selectedClusters) {
const selectedCluster = this._clusters.find((cluster) => {
return clusterIndex === cluster.endpoint_id + "-" + cluster.id;
});
this._clustersToBind.push(selectedCluster!);
}
}
private async _fetchClustersForZhaNode(): Promise<void> {
if (this.hass) {
this._clusters = await fetchClustersForZhaNode(
this.hass,
this.selectedDevice!.ieee
);
this._clusters = this._clusters
.filter((cluster) => {
return cluster.type.toLowerCase() === "out";
})
.sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
}
private get _canBind(): boolean {
return Boolean(
this._groupToBind &&
this._clustersToBind &&
this._clustersToBind?.length > 0 &&
this.selectedDevice
);
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.menu {
width: 100%;
}
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.card-actions.warning ha-call-service-button {
color: var(--google-red-500);
}
.command-picker {
align-items: center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.input-text {
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.sectionHeader {
flex-grow: 1;
}
.helpText {
color: grey;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.toggle-help-icon {
float: right;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-group-binding-control": ZHAGroupBindingControl;
}
}
+10 -2
View File
@@ -121,6 +121,8 @@ export class ZHAGroupPage extends LitElement {
.hass=${this.hass}
.device=${member}
.narrow=${this.narrow}
.showActions=${false}
.showEditableInfo=${false}
></zha-device-card>
`
)
@@ -224,7 +226,10 @@ export class ZHAGroupPage extends LitElement {
private _handleAddSelectionChanged(ev: CustomEvent): void {
const changedSelection = ev.detail as SelectionChangedEvent;
const entity = changedSelection.id;
if (changedSelection.selected) {
if (
changedSelection.selected &&
!this._selectedDevicesToAdd.includes(entity)
) {
this._selectedDevicesToAdd.push(entity);
} else {
const index = this._selectedDevicesToAdd.indexOf(entity);
@@ -238,7 +243,10 @@ export class ZHAGroupPage extends LitElement {
private _handleRemoveSelectionChanged(ev: CustomEvent): void {
const changedSelection = ev.detail as SelectionChangedEvent;
const entity = changedSelection.id;
if (changedSelection.selected) {
if (
changedSelection.selected &&
!this._selectedDevicesToRemove.includes(entity)
) {
this._selectedDevicesToRemove.push(entity);
} else {
const index = this._selectedDevicesToRemove.indexOf(entity);
@@ -47,9 +47,9 @@ export class ZHAGroupsDashboard extends LitElement {
protected render(): TemplateResult {
return html`
<hass-subpage
.header="${this.hass!.localize(
.header=${this.hass!.localize(
"ui.panel.config.zha.groups.groups-header"
)}"
)}
>
<paper-icon-button
slot="toolbar-icon"
@@ -105,7 +105,10 @@ export class ZHAGroupsDashboard extends LitElement {
private _handleRemoveSelectionChanged(ev: CustomEvent): void {
const changedSelection = ev.detail as SelectionChangedEvent;
const groupId = Number(changedSelection.id);
if (changedSelection.selected) {
if (
changedSelection.selected &&
!this._selectedGroupsToRemove.includes(groupId)
) {
this._selectedGroupsToRemove.push(groupId);
} else {
const index = this._selectedGroupsToRemove.indexOf(groupId);
+15 -3
View File
@@ -20,7 +20,7 @@ import { navigate } from "../../../common/navigate";
export interface GroupRowData extends ZHAGroup {
group?: GroupRowData;
id?: number;
id?: string;
}
@customElement("zha-groups-data-table")
@@ -30,6 +30,19 @@ export class ZHAGroupsDataTable extends LitElement {
@property() public groups: ZHAGroup[] = [];
@property() public selectable = false;
private _groups = memoizeOne((groups: ZHAGroup[]) => {
let outputGroups: GroupRowData[] = groups;
outputGroups = outputGroups.map((group) => {
return {
...group,
id: String(group.group_id),
};
});
return outputGroups;
});
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer =>
narrow
@@ -83,8 +96,7 @@ export class ZHAGroupsDataTable extends LitElement {
return html`
<ha-data-table
.columns=${this._columns(this.narrow)}
.data=${this.groups}
.id=${"group_id"}
.data=${this._groups(this.groups)}
.selectable=${this.selectable}
></ha-data-table>
`;
+16 -16
View File
@@ -55,16 +55,19 @@ export class ZHANode extends LitElement {
"ui.panel.config.zha.node_management.hint_wakeup"
)}
</span>
<zha-device-card
class="card"
.hass=${this.hass}
.device=${this.device}
.narrow=${!this.isWide}
.showHelp=${this._showHelp}
isJoinPage
showActions
@zha-device-removed=${this._onDeviceRemoved}
></zha-device-card>
<div class="content">
<zha-device-card
class="card"
.hass=${this.hass}
.device=${this.device}
.narrow=${!this.isWide}
.showHelp=${this._showHelp}
showName
showModelInfo
.showEntityDetail=${false}
@zha-device-removed=${this._onDeviceRemoved}
></zha-device-card>
</div>
</ha-config-section>
`;
}
@@ -93,17 +96,14 @@ export class ZHANode extends LitElement {
padding-bottom: 16px;
}
ha-card {
margin: 0 auto;
.content {
max-width: 600px;
margin: 0 auto;
}
.card {
padding: 28px 20px 0;
margin-top: 24px;
box-sizing: border-box;
display: flex;
flex: 1;
word-wrap: break-word;
}
ha-service-description {
+6 -1
View File
@@ -76,10 +76,14 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
color: grey;
}
[hidden] {
ha-service-description[hidden] {
display: none;
}
ha-paper-icon-button-arrow-prev[hide] {
visibility: hidden;
}
.toggle-help-icon {
position: absolute;
top: -6px;
@@ -91,6 +95,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
<app-header slot="header" fixed="">
<app-toolbar>
<ha-paper-icon-button-arrow-prev
hide$="[[isWide]]"
on-click="_backTapped"
></ha-paper-icon-button-arrow-prev>
<div main-title="">
@@ -32,7 +32,9 @@ class HaPanelDevInfo extends LitElement {
const nonDefaultLinkText =
localStorage.defaultPage === OPT_IN_PANEL && OPT_IN_PANEL === "states"
? this.hass.localize("ui.panel.developer-tools.tabs.info.lovelace_ui")
: this.hass.localize("ui.panel.developer-tools.tabs.info.states_ui");
: `${this.hass.localize(
"ui.panel.developer-tools.tabs.info.states_ui"
)} (DEPRECATED)`;
const defaultPageText = `${this.hass.localize(
"ui.panel.developer-tools.tabs.info.default_ui",
@@ -41,7 +43,7 @@ class HaPanelDevInfo extends LitElement {
? this.hass.localize("ui.panel.developer-tools.tabs.info.remove")
: this.hass.localize("ui.panel.developer-tools.tabs.info.set"),
"name",
OPT_IN_PANEL
`${OPT_IN_PANEL} (DEPRECATED)`
)}`;
return html`
-137
View File
@@ -1,137 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/iron-icon/iron-icon";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import formatTime from "../../common/datetime/format_time";
import formatDate from "../../common/datetime/format_date";
import { EventsMixin } from "../../mixins/events-mixin";
import LocalizeMixin from "../../mixins/localize-mixin";
import { domainIcon } from "../../common/entity/domain_icon";
import { computeRTL } from "../../common/util/compute_rtl";
/*
* @appliesMixin EventsMixin
*/
class HaLogbook extends LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() {
return html`
<style include="iron-flex"></style>
<style>
:host {
display: block;
}
:host([rtl]) {
direction: ltr;
}
.entry {
@apply --paper-font-body1;
line-height: 2em;
}
.time {
width: 55px;
font-size: 0.8em;
color: var(--secondary-text-color);
}
:host([rtl]) .date {
direction: rtl;
}
iron-icon {
margin: 0 8px 0 16px;
color: var(--primary-text-color);
}
.message {
color: var(--primary-text-color);
}
a {
color: var(--primary-color);
}
</style>
<template is="dom-if" if="[[!entries.length]]">
[[localize('ui.panel.logbook.entries_not_found')]]
</template>
<template is="dom-repeat" items="[[entries]]">
<template is="dom-if" if="{{_needHeader(entries.*, index)}}">
<h4 class="date">[[_formatDate(item.when)]]</h4>
</template>
<div class="horizontal layout entry">
<div class="time">[[_formatTime(item.when)]]</div>
<iron-icon icon="[[_computeIcon(item.domain)]]"></iron-icon>
<div class="message" flex="">
<template is="dom-if" if="[[!item.entity_id]]">
<span class="name">[[item.name]]</span>
</template>
<template is="dom-if" if="[[item.entity_id]]">
<a href="#" on-click="entityClicked" class="name"
>[[item.name]]</a
>
</template>
<span> </span> <span>[[item.message]]</span>
</div>
</div>
</template>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
entries: {
type: Array,
value: [],
},
rtl: {
type: Boolean,
reflectToAttribute: true,
computed: "_computeRTL(hass)",
},
};
}
_formatTime(date) {
return formatTime(new Date(date), this.hass.language);
}
_formatDate(date) {
return formatDate(new Date(date), this.hass.language);
}
_needHeader(change, index) {
if (!index) return true;
const current = this.get("when", change.base[index]);
const previous = this.get("when", change.base[index - 1]);
return (
current &&
previous &&
new Date(current).toDateString() !== new Date(previous).toDateString()
);
}
_computeIcon(domain) {
return domainIcon(domain);
}
_computeRTL(hass) {
return computeRTL(hass);
}
entityClicked(ev) {
ev.preventDefault();
this.fire("hass-more-info", { entityId: ev.model.item.entity_id });
}
}
customElements.define("ha-logbook", HaLogbook);
+156
View File
@@ -0,0 +1,156 @@
import "@polymer/iron-icon/iron-icon";
import formatTime from "../../common/datetime/format_time";
import formatDate from "../../common/datetime/format_date";
import { domainIcon } from "../../common/entity/domain_icon";
import { computeRTL } from "../../common/util/compute_rtl";
import {
LitElement,
html,
property,
TemplateResult,
CSSResult,
css,
PropertyValues,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
import "lit-virtualizer";
import { LogbookEntry } from "../../data/logbook";
class HaLogbook extends LitElement {
@property() public hass!: HomeAssistant;
@property() public entries: LogbookEntry[] = [];
@property({ attribute: "rtl", type: Boolean, reflect: true })
// @ts-ignore
private _rtl = false;
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!changedProps.has("hass")) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (oldHass && oldHass.language !== this.hass.language) {
this._rtl = computeRTL(this.hass);
}
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._rtl = computeRTL(this.hass);
}
protected render(): TemplateResult | void {
if (!this.entries?.length) {
return html`
${this.hass.localize("ui.panel.logbook.entries_not_found")}
`;
}
return html`
<lit-virtualizer
.items=${this.entries}
.renderItem=${(item: LogbookEntry, index: number) =>
this._renderLogbookItem(item, index)}
style="height: 100%;"
></lit-virtualizer>
`;
}
private _renderLogbookItem(
item: LogbookEntry,
index: number
): TemplateResult {
const previous = this.entries[index - 1];
return html`
<div>
${index === 0 ||
(item?.when &&
previous?.when &&
new Date(item.when).toDateString() !==
new Date(previous.when).toDateString())
? html`
<h4 class="date">
${formatDate(new Date(item.when), this.hass.language)}
</h4>
`
: html``}
<div class="entry">
<div class="time">
${formatTime(new Date(item.when), this.hass.language)}
</div>
<iron-icon .icon="${domainIcon(item.domain)}"></iron-icon>
<div class="message">
${!item.entity_id
? html`
<span class="name">${item.name}</span>
`
: html`
<a
href="#"
@click=${this._entityClicked}
.entityId=${item.entity_id}
class="name"
>
${item.name}
</a>
`}
<span>${item.message}</span>
</div>
</div>
</div>
`;
}
private _entityClicked(ev: Event) {
ev.preventDefault();
fireEvent(this, "hass-more-info", {
entityId: (ev.target as any).entityId,
});
}
static get styles(): CSSResult {
return css`
:host {
display: block;
height: 100%;
}
:host([rtl]) {
direction: ltr;
}
.entry {
display: flex;
line-height: 2em;
}
.time {
width: 55px;
font-size: 0.8em;
color: var(--secondary-text-color);
}
:host([rtl]) .date {
direction: rtl;
}
iron-icon {
margin: 0 8px 0 16px;
color: var(--primary-text-color);
}
.message {
color: var(--primary-text-color);
}
a {
color: var(--primary-color);
}
`;
}
}
customElements.define("ha-logbook", HaLogbook);
+27 -4
View File
@@ -28,7 +28,15 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
return html`
<style include="ha-style">
.content {
padding: 0 16px 16px;
padding: 0 16px 0 16px;
}
ha-logbook {
height: calc(100vh - 136px);
}
:host([narrow]) ha-logbook {
height: calc(100vh - 198px);
}
paper-spinner {
@@ -42,6 +50,15 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
margin-bottom: 24px;
}
.filters {
display: flex;
align-items: center;
}
:host([narrow]) .filters {
flex-wrap: wrap;
}
vaadin-date-picker {
max-width: 200px;
margin-right: 16px;
@@ -65,10 +82,15 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
ha-entity-picker {
display: inline-block;
width: 100%;
flex-grow: 1;
max-width: 400px;
}
:host([narrow]) ha-entity-picker {
max-width: none;
width: 100%;
}
[hidden] {
display: none !important;
}
@@ -106,7 +128,7 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
alt="[[localize('ui.common.loading')]]"
></paper-spinner>
<div class="flex layout horizontal wrap">
<div class="filters">
<vaadin-date-picker
id="picker"
value="{{_currentDate}}"
@@ -158,7 +180,8 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
static get properties() {
return {
hass: Object,
narrow: Boolean,
narrow: { type: Boolean, reflectToAttribute: true },
// ISO8601 formatted date string
_currentDate: {
@@ -4,6 +4,8 @@ import {
TemplateResult,
customElement,
property,
CSSResult,
css,
} from "lit-element";
import "../../../components/entity/ha-state-label-badge";
@@ -45,6 +47,7 @@ export class HuiStateLabelBadge extends LitElement implements LovelaceBadge {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
></ha-state-label-badge>
`;
}
@@ -52,6 +55,21 @@ export class HuiStateLabelBadge extends LitElement implements LovelaceBadge {
private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action!);
}
static get styles(): CSSResult {
return css`
ha-state-label-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 4px;
}
ha-state-label-badge {
display: inline-block;
padding: 4px;
margin: -4px 0 -4px 0;
}
`;
}
}
declare global {
@@ -134,6 +134,7 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
>
${this._config.show_icon
? html`
@@ -195,6 +196,11 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
font-size: 1.2rem;
}
ha-card:focus {
outline: none;
background: var(--divider-color);
}
ha-icon {
width: 40%;
height: auto;
@@ -104,6 +104,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return html`
<ha-card
@click="${this._handleClick}"
tabindex="0"
style=${styleMap({
"--base-unit": this._baseUnit,
})}
@@ -228,6 +229,10 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
position: relative;
cursor: pointer;
}
ha-card:focus {
outline: none;
background: var(--divider-color);
}
.container {
width: calc(var(--base-unit) * 4);
height: calc(var(--base-unit) * 2);
@@ -167,6 +167,13 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
margin-bottom: 12px;
width: var(--glance-column-width, 20%);
}
.entity:focus {
outline: none;
background: var(--divider-color);
border-radius: 14px;
padding: 4px;
margin: -4px 0;
}
.entity div {
width: 100%;
text-align: center;
@@ -207,6 +214,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
hasHold: hasAction(entityConf.hold_action),
hasDoubleClick: hasAction(entityConf.double_tap_action),
})}
tabindex="0"
>
${this._config!.show_name !== false
? html`
+3 -1
View File
@@ -94,7 +94,8 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
<paper-icon-button
icon="hass:dots-vertical"
class="more-info"
@click="${this._handleMoreInfo}"
@click=${this._handleMoreInfo}
tabindex="0"
></paper-icon-button>
<div id="controls">
@@ -121,6 +122,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
color: this._computeColor(stateObj),
})}
@click=${this._handleClick}
tabindex="0"
></paper-icon-button>
</div>
</div>
+10 -2
View File
@@ -68,6 +68,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
false
);
private _mapItems: Array<Marker | Circle> = [];
private _mapZones: Array<Marker | Circle> = [];
private _connected = false;
public setConfig(config: MapCardConfig): void {
@@ -147,6 +148,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
></div>
<paper-icon-button
@click=${this._fitMap}
tabindex="0"
icon="hass:image-filter-center-focus"
title="Reset focus"
></paper-icon-button>
@@ -283,6 +285,11 @@ class HuiMapCard extends LitElement implements LovelaceCard {
}
const mapItems: Layer[] = (this._mapItems = []);
if (this._mapZones) {
this._mapZones.forEach((marker) => marker.remove());
}
const mapZones: Layer[] = (this._mapZones = []);
const allEntities = this._configEntities!.concat();
// Calculate visible geo location sources
@@ -340,7 +347,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
}
// create marker with the icon
mapItems.push(
mapZones.push(
Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({
html: iconHTML,
@@ -353,7 +360,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
);
// create circle around it
mapItems.push(
mapZones.push(
Leaflet.circle([latitude, longitude], {
interactive: false,
color: "#FF9800",
@@ -405,6 +412,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
}
this._mapItems.forEach((marker) => map.addLayer(marker));
this._mapZones.forEach((marker) => map.addLayer(marker));
}
private _attachObserver(): void {
@@ -86,6 +86,7 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
class="${classMap({
clickable: Boolean(
this._config.tap_action || this._config.hold_action
@@ -156,6 +156,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
class=${classMap({
clickable: stateObj.state !== UNAVAILABLE,
})}
@@ -168,6 +168,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
.config=${this._config}
.hass=${this.hass}
.image=${this._config.image}
@@ -230,6 +231,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
hasHold: hasAction(entityConf.hold_action),
hasDoubleClick: hasAction(entityConf.double_tap_action),
})}
tabindex="0"
.config=${entityConf}
class="${classMap({
"state-on": !STATES_OFF.has(stateObj.state),
@@ -318,6 +320,11 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
padding-bottom: 4px;
padding-top: 4px;
}
ha-icon:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
.state {
display: block;
font-size: 12px;
@@ -21,6 +21,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { PlantStatusCardConfig, PlantAttributeTarget } from "./types";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive";
const SENSORS = {
moisture: "hass:water",
@@ -119,7 +120,9 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
(item) => html`
<div
class="attributes"
@click="${this._handleMoreInfo}"
@action=${this._handleMoreInfo}
.actionHandler=${actionHandler()}
tabindex="0"
.value="${item}"
>
<div>
@@ -206,6 +209,12 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
cursor: pointer;
}
.attributes:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
.attributes div {
text-align: center;
}
+11 -1
View File
@@ -25,6 +25,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { fetchRecent } from "../../../data/history";
import { SensorCardConfig } from "./types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { actionHandler } from "../common/directives/action-handler-directive";
const midPoint = (
_Ax: number,
@@ -241,7 +242,11 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
graph = "";
}
return html`
<ha-card @click="${this._handleClick}">
<ha-card
@action=${this._handleClick}
.actionHandler=${actionHandler()}
tabindex="0"
>
<div class="flex">
<div class="icon">
<ha-icon
@@ -353,6 +358,11 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
cursor: pointer;
}
ha-card:focus {
outline: none;
background: var(--divider-color);
}
.flex {
display: flex;
}
@@ -26,6 +26,7 @@ import {
} from "../../../data/shopping-list";
import { ShoppingListCardConfig, SensorCardConfig } from "./types";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive";
@customElement("hui-shopping-list-card")
class HuiShoppingListCard extends LitElement implements LovelaceCard {
@@ -165,7 +166,9 @@ class HuiShoppingListCard extends LitElement implements LovelaceCard {
</span>
<ha-icon
class="clearall"
@click="${this._clearItems}"
@action=${this._clearItems}
.actionHandler=${actionHandler()}
tabindex="0"
icon="hass:notification-clear-all"
.title="${this.hass!.localize(
"ui.panel.lovelace.cards.shopping-list.clear_items"
@@ -33,6 +33,7 @@ import {
CLIMATE_PRESET_NONE,
} from "../../../data/climate";
import { HassEntity } from "home-assistant-js-websocket";
import { actionHandler } from "../common/directives/action-handler-directive";
const modeIcons: { [mode in HvacMode]: string } = {
auto: "hass:calendar-repeat",
@@ -218,6 +219,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
icon="hass:dots-vertical"
class="more-info"
@click=${this._handleMoreInfo}
tabindex="0"
></paper-icon-button>
<div id="controls">
@@ -364,9 +366,10 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
class="${classMap({ "selected-icon": currentMode === mode })}"
.mode="${mode}"
.icon="${modeIcons[mode]}"
@click="${this._handleModeClick}"
@action=${this._handleAction}
.actionHandler=${actionHandler()}
tabindex="0"
></paper-icon-button>
></ha-icon>
`;
}
@@ -376,7 +379,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
});
}
private _handleModeClick(e: MouseEvent): void {
private _handleAction(e: MouseEvent): void {
this.hass!.callService("climate", "set_hvac_mode", {
entity_id: this._config!.entity,
hvac_mode: (e.currentTarget as any).mode,
@@ -23,6 +23,7 @@ import { computeRTL } from "../../../common/util/compute_rtl";
import { fireEvent } from "../../../common/dom/fire_event";
import { toggleAttribute } from "../../../common/dom/toggle_attribute";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive";
const cardinalDirections = [
"N",
@@ -141,7 +142,11 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
: undefined;
return html`
<ha-card @click="${this.handleClick}">
<ha-card
@action=${this._handleAction}
.actionHandler=${actionHandler()}
tabindex="0"
>
<div class="header">
${this.hass.localize(`state.weather.${stateObj.state}`) ||
stateObj.state}
@@ -274,7 +279,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
return hasConfigOrEntityChanged(this, changedProps);
}
private handleClick(): void {
private _handleAction(): void {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
}
@@ -39,17 +39,17 @@ import { processEditorEntities } from "../editor/process-editor-entities";
const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
const DOMAINS_BADGES = [
"binary_sensor",
"person",
"device_tracker",
"mailbox",
"person",
"sensor",
"sun",
"timer",
];
const HIDE_DOMAIN = new Set([
"persistent_notification",
"configurator",
"device_tracker",
"geo_location",
"persistent_notification",
]);
let subscribedRegistries = false;
@@ -132,11 +132,6 @@ export const computeCards = (
title: stateObj.attributes.friendly_name,
refresh_interval: stateObj.attributes.refresh,
});
} else if (domain === "light") {
cards.push({
type: "light",
entity: entityId,
});
} else if (domain === "media_player") {
cards.push({
type: "media-control",
@@ -171,6 +171,11 @@ class HuiGenericEntityRow extends LitElement {
state-badge {
flex: 0 0 40px;
}
state-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
:host([rtl]) .flex {
margin-left: 0;
margin-right: 16px;
@@ -32,23 +32,35 @@ export const addEntitiesToLovelaceView = async (
return;
}
}
if (!lovelaceConfig.views.length) {
alert(
"You don't have any Lovelace views, first create a view in Lovelace."
);
return;
}
if (!saveConfigFunc) {
saveConfigFunc = async (newConfig: LovelaceConfig): Promise<void> => {
try {
await saveConfig(hass!, newConfig);
} catch {
alert(
hass.localize("ui.panel.config.devices.add_entities.saving_failed")
);
}
};
}
if (lovelaceConfig.views.length === 1) {
showSuggestCardDialog(element, {
lovelaceConfig: lovelaceConfig!,
saveConfig: saveConfigFunc,
path: [0],
entities,
});
return;
}
showSelectViewDialog(element, {
lovelaceConfig,
viewSelectedCallback: (view) => {
if (!saveConfigFunc) {
saveConfigFunc = async (newConfig: LovelaceConfig): Promise<void> => {
try {
await saveConfig(hass!, newConfig);
} catch {
alert(
hass.localize(
"ui.panel.config.devices.add_entities.saving_failed"
)
);
}
};
}
showSuggestCardDialog(element, {
lovelaceConfig: lovelaceConfig!,
saveConfig: saveConfigFunc,
@@ -79,7 +79,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
}
get _camera_view(): string {
return this._config!.camera_view || this._camera_image ? "auto" : "";
return this._config!.camera_view || "auto";
}
get _state_image(): {} {
@@ -45,6 +45,7 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
></ha-icon>
`;
}
@@ -58,6 +59,11 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
:host {
cursor: pointer;
}
ha-icon:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`;
}
}
@@ -56,6 +56,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
></hui-image>
`;
}
@@ -70,6 +71,11 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
hui-image {
-webkit-user-select: none !important;
}
hui-image:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`;
}
@@ -70,6 +70,7 @@ export class HuiStateBadgeElement extends LitElement
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
></ha-state-label-badge>
`;
}
@@ -66,6 +66,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
.overrideIcon=${this._config.icon}
></state-badge>
`;
@@ -76,6 +77,11 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
:host {
cursor: pointer;
}
state-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`;
}
@@ -65,6 +65,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex="0"
>
${this._config.prefix}${stateObj
? computeStateDisplay(
@@ -90,6 +91,11 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
padding: 8px;
white-space: nowrap;
}
div:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`;
}
}
@@ -141,7 +141,6 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
margin-left: 16px;
flex: 1;
}
paper-item {
cursor: pointer;
min-width: 200px;
@@ -149,6 +148,11 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
.pointer {
cursor: pointer;
}
state-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
`;
}
@@ -62,7 +62,8 @@ class HuiSensorEntityRow extends LitElement implements EntityRow {
<hui-generic-entity-row .hass="${this.hass}" .config="${this._config}">
<div>
${stateObj.attributes.device_class === "timestamp" &&
stateObj.state !== "unavailable"
stateObj.state !== "unavailable" &&
stateObj.state !== "unknown"
? html`
<hui-timestamp-display
.hass="${this.hass}"

Some files were not shown because too many files have changed in this diff Show More