diff --git a/src/components/ha-chip.ts b/src/components/ha-chip.ts index a310b3b02e..5cd63a6165 100644 --- a/src/components/ha-chip.ts +++ b/src/components/ha-chip.ts @@ -93,7 +93,10 @@ export class HaChip extends LitElement { .mdc-chip.outline { background: none; - border: 1px solid rgba(var(--rgb-primary-text-color), 0.5); + border: var( + --ha-chip-border, + 1px solid rgba(var(--rgb-primary-text-color), 0.5) + ); } `; } diff --git a/src/components/ha-target-chip.ts b/src/components/ha-target-chip.ts new file mode 100644 index 0000000000..6aeedfcb38 --- /dev/null +++ b/src/components/ha-target-chip.ts @@ -0,0 +1,218 @@ +// @ts-ignore +import chipStyles from "@material/chips/dist/mdc.chips.min.css"; +import { mdiUnfoldMoreVertical, mdiClose } from "@mdi/js"; +import { HassEntity } from "home-assistant-js-websocket"; +import { + css, + CSSResultGroup, + html, + LitElement, + TemplateResult, + unsafeCSS, +} from "lit"; +import { customElement, property } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { HomeAssistant } from "../types"; +import "@polymer/paper-tooltip/paper-tooltip"; +import "./ha-icon-button"; +import "./ha-state-icon"; +import { fireEvent } from "../common/dom/fire_event"; + +@customElement("ha-target-chip") +export class HaTargetChip extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public type: "entity_id" | "area_id" | "device_id" = "entity_id"; + + @property() public name!: string; + + @property() public iconPath?: string; + + @property({ attribute: false }) public entityState?: HassEntity; + + @property({ type: Boolean }) public canExpand = false; + + @property({ type: Boolean }) public canRemove = false; + + @property({ type: Boolean }) public noClick = false; + + @property({ type: Boolean }) public filled = false; + + protected render(): TemplateResult { + return html` +
+ ${this.iconPath + ? html`` + : ""} + ${this.entityState + ? html`` + : ""} + ${this.noClick ? "" : html`
`} + + + ${this.name} + + + ${this.canExpand && this.type !== "entity_id" + ? html` + + ${this.hass.localize( + `ui.components.target-picker.expand_${this.type}` + )} + ` + : ""} + ${this.canRemove + ? html` + + ${this.hass.localize( + `ui.components.target-picker.remove_${this.type}` + )} + ` + : ""} +
+ `; + } + + private _handleExpand(ev) { + const target = ev.currentTarget as any; + fireEvent(this, "target-expand", { type: target.type, id: target.id }); + } + + private _handleRemove(ev) { + const target = ev.currentTarget as any; + fireEvent(this, "target-remove", { type: target.type, id: target.id }); + } + + static get styles(): CSSResultGroup { + return css` + ${unsafeCSS(chipStyles)} + .mdc-chip { + color: var(--primary-text-color); + } + .mdc-chip.filled { + color: rgba(0, 0, 0, 0.87); + } + .mdc-chip.noClick { + cursor: default; + } + .mdc-chip ha-icon-button { + --mdc-icon-button-size: 24px; + display: flex; + align-items: center; + outline: none; + } + .mdc-chip ha-icon-button ha-svg-icon { + border-radius: 50%; + background: var(--secondary-text-color); + } + .mdc-chip__icon.mdc-chip__icon--trailing { + width: 16px; + height: 16px; + --mdc-icon-size: 14px; + color: var(--secondary-text-color); + margin-inline-start: 4px !important; + margin-inline-end: -4px !important; + direction: var(--direction); + } + .mdc-chip__icon--leading { + display: flex; + align-items: center; + justify-content: center; + --mdc-icon-size: 20px; + border-radius: 50%; + padding: 6px; + margin-left: -14px !important; + margin-inline-start: -14px !important; + margin-inline-end: 4px !important; + direction: var(--direction); + } + .expand-btn { + margin-right: 0; + } + .mdc-chip.area_id:not(.filled) { + border: 2px solid #fed6a4; + background: var(--card-background-color); + } + .mdc-chip.area_id:not(.filled) .mdc-chip__icon--leading, + .mdc-chip.area_id.filled { + background: #fed6a4; + } + .mdc-chip.device_id:not(.filled) { + border: 2px solid #a8e1fb; + background: var(--card-background-color); + } + .mdc-chip.device_id:not(.filled) .mdc-chip__icon--leading, + .mdc-chip.device_id.filled { + background: #a8e1fb; + } + .mdc-chip.entity_id:not(.filled) { + border: 2px solid #d2e7b9; + background: var(--card-background-color); + } + .mdc-chip.entity_id:not(.filled) .mdc-chip__icon--leading, + .mdc-chip.entity_id.filled { + background: #d2e7b9; + } + .mdc-chip:hover { + z-index: 5; + } + :host([disabled]) .mdc-chip { + opacity: var(--light-disabled-opacity); + pointer-events: none; + } + `; + } +} + +declare global { + interface HASSDomEvents { + "target-expand": { type: "area_id" | "device_id"; id: string }; + "target-remove": { + type: "entity_id" | "area_id" | "device_id"; + id: string; + }; + } + interface HTMLElementTagNameMap { + "ha-target-chip": HaTargetChip; + } +} diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index d5dd2c8630..21d9f79f6b 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -1,12 +1,9 @@ // @ts-ignore -import chipStyles from "@material/chips/dist/mdc.chips.min.css"; import "@material/mwc-button/mwc-button"; import { - mdiClose, mdiDevices, mdiPlus, mdiSofa, - mdiUnfoldMoreVertical, } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; import { @@ -14,9 +11,8 @@ import { HassServiceTarget, UnsubscribeFunc, } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit"; +import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../common/dom/fire_event"; import { ensureArray } from "../common/array/ensure-array"; import { computeDomain } from "../common/entity/compute_domain"; @@ -44,6 +40,7 @@ import "./ha-area-picker"; import "./ha-icon-button"; import "./ha-input-helper-text"; import "./ha-svg-icon"; +import "./ha-target-chip"; @customElement("ha-target-picker") export class HaTargetPicker extends SubscribeMixin(LitElement) { @@ -138,7 +135,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { private _renderItems() { return html` -
+
${this.value?.area_id ? ensureArray(this.value.area_id).map((area_id) => { const area = this._areas![area_id]; @@ -180,67 +177,40 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { private _renderChips() { return html` -
-
+ -
- - - - ${this.hass.localize( - "ui.components.target-picker.add_area_id" - )} - - -
-
+ + -
- - - - ${this.hass.localize( - "ui.components.target-picker.add_device_id" - )} - - -
-
+ + -
- - - - ${this.hass.localize( - "ui.components.target-picker.add_entity_id" - )} - - -
+ filled + class="add" + >
${this.helper ? html`${this.helper}` @@ -262,71 +232,19 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { entityState?: HassEntity, iconPath?: string ) { - return html` -
- ${iconPath - ? html`` - : ""} - ${entityState - ? html`` - : ""} - - - ${name} - - - ${type === "entity_id" - ? "" - : html` - - ${this.hass.localize( - `ui.components.target-picker.expand_${type}` - )} - `} - - - ${this.hass.localize( - `ui.components.target-picker.remove_${type}` - )} - -
- `; + return html``; } private _renderPicker() { @@ -405,14 +323,15 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { }); } - private _handleExpand(ev) { - const target = ev.currentTarget as any; + private _handleExpand(ev: CustomEvent) { + const type = ev.detail.type; + const id = ev.detail.id; const newDevices: string[] = []; const newEntities: string[] = []; - if (target.type === "area_id") { + if (type === "area_id") { Object.values(this._devices!).forEach((device) => { if ( - device.area_id === target.id && + device.area_id === id && !this.value!.device_id?.includes(device.id) && this._deviceMeetsFilter(device) ) { @@ -421,17 +340,17 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { }); this._entities!.forEach((entity) => { if ( - entity.area_id === target.id && + entity.area_id === id && !this.value!.entity_id?.includes(entity.entity_id) && this._entityRegMeetsFilter(entity) ) { newEntities.push(entity.entity_id); } }); - } else if (target.type === "device_id") { + } else if (type === "device_id") { this._entities!.forEach((entity) => { if ( - entity.device_id === target.id && + entity.device_id === id && !this.value!.entity_id?.includes(entity.entity_id) && this._entityRegMeetsFilter(entity) ) { @@ -448,14 +367,15 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { if (newDevices.length) { value = this._addItems(value, "device_id", newDevices); } - value = this._removeItem(value, target.type, target.id); + value = this._removeItem(value, type, id); fireEvent(this, "value-changed", { value }); } - private _handleRemove(ev) { - const target = ev.currentTarget as any; + private _handleRemove(ev: CustomEvent) { + const type = ev.detail.type; + const id = ev.detail.id; fireEvent(this, "value-changed", { - value: this._removeItem(this.value, target.type, target.id), + value: this._removeItem(this.value, type, id), }); } @@ -567,96 +487,30 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { static get styles(): CSSResultGroup { return css` - ${unsafeCSS(chipStyles)} .horizontal-container { display: flex; flex-wrap: wrap; min-height: 56px; align-items: center; } - .mdc-chip { - color: var(--primary-text-color); - } .items { z-index: 2; } - .mdc-chip-set { + .chip-set { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; padding: 4px 0; } - .mdc-chip.add { - color: rgba(0, 0, 0, 0.87); + ha-target-chip { + margin: 4px; } - .mdc-chip:not(.add) { - cursor: default; - } - .mdc-chip ha-icon-button { - --mdc-icon-button-size: 24px; - display: flex; - align-items: center; - outline: none; - } - .mdc-chip ha-icon-button ha-svg-icon { - border-radius: 50%; - background: var(--secondary-text-color); - } - .mdc-chip__icon.mdc-chip__icon--trailing { - width: 16px; - height: 16px; - --mdc-icon-size: 14px; - color: var(--secondary-text-color); - margin-inline-start: 4px !important; - margin-inline-end: -4px !important; - direction: var(--direction); - } - .mdc-chip__icon--leading { - display: flex; - align-items: center; - justify-content: center; - --mdc-icon-size: 20px; - border-radius: 50%; - padding: 6px; - margin-left: -14px !important; - margin-inline-start: -14px !important; - margin-inline-end: 4px !important; - direction: var(--direction); - } - .expand-btn { - margin-right: 0; - } - .mdc-chip.area_id:not(.add) { - border: 2px solid #fed6a4; - background: var(--card-background-color); - } - .mdc-chip.area_id:not(.add) .mdc-chip__icon--leading, - .mdc-chip.area_id.add { - background: #fed6a4; - } - .mdc-chip.device_id:not(.add) { - border: 2px solid #a8e1fb; - background: var(--card-background-color); - } - .mdc-chip.device_id:not(.add) .mdc-chip__icon--leading, - .mdc-chip.device_id.add { - background: #a8e1fb; - } - .mdc-chip.entity_id:not(.add) { - border: 2px solid #d2e7b9; - background: var(--card-background-color); - } - .mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading, - .mdc-chip.entity_id.add { - background: #d2e7b9; - } - .mdc-chip:hover { - z-index: 5; + ha-target-chip.add { + cursor: pointer; } paper-tooltip.expand { min-width: 200px; } - :host([disabled]) .mdc-chip { - opacity: var(--light-disabled-opacity); - pointer-events: none; - } `; } } diff --git a/src/dialogs/more-info/ha-more-info-device-entities-shortcuts.ts b/src/dialogs/more-info/ha-more-info-device-entities-shortcuts.ts index b15d44408d..802a207340 100644 --- a/src/dialogs/more-info/ha-more-info-device-entities-shortcuts.ts +++ b/src/dialogs/more-info/ha-more-info-device-entities-shortcuts.ts @@ -1,25 +1,21 @@ +import { mdiDevices } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; -import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; +import { SENSOR_ENTITIES } from "../../common/const"; import { fireEvent } from "../../common/dom/fire_event"; +import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateDisplay } from "../../common/entity/compute_state_display"; -import { stateActive } from "../../common/entity/state_active"; -import { stateColor } from "../../common/entity/state_color"; -import { stateIconPath } from "../../common/entity/state_icon_path"; -import "../../components/ha-chip"; -import "../../components/ha-chip-set"; -import "../../components/ha-icon"; -import "../../components/ha-svg-icon"; +import { navigate } from "../../common/navigate"; +import { groupBy } from "../../common/util/group-by"; +import "../../components/ha-target-chip"; +import { computeDeviceName } from "../../data/device_registry"; +import { isUnavailableState } from "../../data/entity"; +import { EntityRegistryEntry } from "../../data/entity_registry"; +import { EntityRegistryStateEntry } from "../../panels/config/devices/ha-config-device-page"; import { HomeAssistant } from "../../types"; -declare global { - interface HASSDomEvents { - "shortcut-clicked": { entityId: string }; - } -} - @customElement("ha-more-info-device-entities-shortcuts") class MoreInfoDevicesEntitiesShortcuts extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; @@ -45,10 +41,22 @@ class MoreInfoDevicesEntitiesShortcuts extends LitElement { if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") { return; } - const entityId = (ev.target as any).entityId as string; + const entityId = (ev.target as any).id as string; fireEvent(this, "shortcut-clicked", { entityId }); } + private _handleDeviceChipClick(ev) { + if (ev.defaultPrevented) { + return; + } + if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") { + return; + } + const deviceId = (ev.target as any).id as string; + fireEvent(this, "hass-more-info", { entityId: null }); + navigate(`/config/devices/device/${deviceId}`); + } + protected render(): TemplateResult | null { if (!this.hass || !this.stateObj) { return null; @@ -63,22 +71,47 @@ class MoreInfoDevicesEntitiesShortcuts extends LitElement { (entry) => this.hass!.states[entry.entity_id] ); - const displayedEntities = deviceEntities.filter( + let displayedEntities = deviceEntities.filter( (entity) => entity.entity_id !== this.stateObj!.entity_id ); // Do not display device entities if the current entity is not inside - if (displayedEntities.length === deviceEntities.length) { + if ( + !displayedEntities.length || + displayedEntities.length === deviceEntities.length + ) { return null; } + if (displayedEntities.length > 3) { + const result = groupBy(displayedEntities, (entry) => + entry.entity_category + ? entry.entity_category + : SENSOR_ENTITIES.includes(computeDomain(entry.entity_id)) + ? "sensor" + : "control" + ) as Record< + | "control" + | "sensor" + | NonNullable, + EntityRegistryStateEntry[] + >; + + if (result.control?.length > 3) { + displayedEntities = result.control.slice(0, 3); + } else { + displayedEntities = (result.control || []) + .concat(result.sensor) + .slice(0, 3); + } + } + return html`
${displayedEntities.map((entry) => { const stateObj = this.hass!.states[entry.entity_id]; - const icon = stateObj.attributes.icon; - const iconPath = stateIconPath(stateObj); + const iconPath = ""; const state = computeStateDisplay( this.hass!.localize, @@ -86,40 +119,37 @@ class MoreInfoDevicesEntitiesShortcuts extends LitElement { this.hass!.locale, this.hass!.entities ); - const color = stateColor(stateObj); - const active = stateActive(stateObj); - - const iconStyle = styleMap({ - "--ha-chip-icon-color": - color && active - ? `rgb(var(--rgb-state-${color}-color))` - : undefined, - }); const name = stateObj.attributes.friendly_name ?? ""; return html` - - ${icon - ? html`` - : html` - - `} - ${state} - + `; })} + +
`; } @@ -137,19 +167,14 @@ class MoreInfoDevicesEntitiesShortcuts extends LitElement { .container > * { margin: 4px; } - ha-chip { - cursor: pointer; - --ha-chip-icon-color: rgb(var(--rgb-state-default-color)); - } - ha-chip ha-icon, - ha-chip ha-svg-icon { - pointer-events: none; - } `; } } declare global { + interface HASSDomEvents { + "shortcut-clicked": { entityId: string }; + } interface HTMLElementTagNameMap { "ha-more-info-device-entities-shortcuts": MoreInfoDevicesEntitiesShortcuts; }