mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-25 10:37:13 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c1eabeb29f | |||
| 5ff8fe68ba | |||
| a2a039ebc5 | |||
| 1064aed1b0 | |||
| 7025592e8e | |||
| 4966354b62 | |||
| 68d6faf4af | |||
| e3346483b9 | |||
| e8fb79e5ce | |||
| d612162ab1 | |||
| 86f8ef3a70 | |||
| 0e43435362 | |||
| aaefe0b09f | |||
| bc731a9dc3 | |||
| da25701dca | |||
| 21ae483dc9 | |||
| 38b6e9ca10 | |||
| d31245866c | |||
| 4e08d8f3b3 | |||
| 1e717ab33e | |||
| 995fb4974e | |||
| ffb76132f8 | |||
| acba3af54b | |||
| 40ac456937 | |||
| 5c32413bf7 | |||
| 22792c70c5 | |||
| a8ed87298a | |||
| b15270dfe2 | |||
| 58ad949bc8 | |||
| adce40de56 | |||
| 0f487ae4bf | |||
| 2848e3a63b | |||
| 5a172a64c5 | |||
| 433aa16ea6 | |||
| 50cb8cf3cc | |||
| 4e5406b27b | |||
| 80eb80619a | |||
| bf71b3a869 | |||
| ff270c4b7d |
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface LogbookEntry {
|
||||
when: string;
|
||||
name: string;
|
||||
message: string;
|
||||
entity_id?: string;
|
||||
domain: string;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-12
@@ -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>
|
||||
+12
-19
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
-12
@@ -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(),
|
||||
});
|
||||
+41
-48
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")}"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
)}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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`
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user