diff --git a/src/components/target-picker/dialog/dialog-target-details.ts b/src/components/target-picker/dialog/dialog-target-details.ts new file mode 100644 index 0000000000..eb2d4ad13e --- /dev/null +++ b/src/components/target-picker/dialog/dialog-target-details.ts @@ -0,0 +1,118 @@ +import { ContextProvider } from "@lit/context"; +import { mdiClose } from "@mdi/js"; +import type { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { labelsContext } from "../../../data/context"; +import { subscribeLabelRegistry } from "../../../data/label_registry"; +import type { HassDialog } from "../../../dialogs/make-dialog-manager"; +import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; +import type { HomeAssistant } from "../../../types"; +import "../../ha-dialog-header"; +import "../../ha-icon-button"; +import "../../ha-icon-next"; +import "../../ha-md-dialog"; +import type { HaMdDialog } from "../../ha-md-dialog"; +import "../../ha-md-list"; +import "../../ha-md-list-item"; +import "../../ha-svg-icon"; +import "../ha-target-picker-item-row"; +import type { TargetDetailsDialogParams } from "./show-dialog-target-details"; + +@customElement("ha-dialog-target-details") +class DialogTargetDetails + extends SubscribeMixin(LitElement) + implements HassDialog +{ + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _params?: TargetDetailsDialogParams; + + @query("ha-md-dialog") private _dialog?: HaMdDialog; + + private _labelsContext = new ContextProvider(this, { + context: labelsContext, + initialValue: [], + }); + + public showDialog(params: TargetDetailsDialogParams): void { + this._params = params; + } + + public closeDialog() { + this._dialog?.close(); + return true; + } + + private _dialogClosed() { + fireEvent(this, "dialog-closed", { dialog: this.localName }); + this._params = undefined; + } + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeLabelRegistry(this.hass.connection!, (labels) => { + this._labelsContext.setValue(labels); + }), + ]; + } + + protected render() { + if (!this._params) { + return nothing; + } + + return html` + + + + ${this._params.title} + ${this.hass.localize("ui.components.target-picker.overview")} + +
+ +
+
+ `; + } + + static styles = css` + ha-md-dialog { + min-width: 400px; + max-height: 90%; + --dialog-content-padding: 8px 24px + max(var(--safe-area-inset-bottom, 0px), 32px); + } + + @media all and (max-width: 600px), all and (max-height: 500px) { + ha-md-dialog { + --md-dialog-container-shape: 0; + min-width: 100%; + min-height: 100%; + } + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog-target-details": DialogTargetDetails; + } +} diff --git a/src/components/target-picker/dialog/show-dialog-target-details.ts b/src/components/target-picker/dialog/show-dialog-target-details.ts new file mode 100644 index 0000000000..4127abbcff --- /dev/null +++ b/src/components/target-picker/dialog/show-dialog-target-details.ts @@ -0,0 +1,28 @@ +import { fireEvent } from "../../../common/dom/fire_event"; +import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker"; +import type { HaEntityPickerEntityFilterFunc } from "../../entity/ha-entity-picker"; +import type { TargetType } from "../ha-target-picker-item-row"; + +export type NewBackupType = "automatic" | "manual"; + +export interface TargetDetailsDialogParams { + title: string; + type: TargetType; + itemId: string; + deviceFilter?: HaDevicePickerDeviceFilterFunc; + entityFilter?: HaEntityPickerEntityFilterFunc; + includeDomains?: string[]; + includeDeviceClasses?: string[]; +} + +export const loadTargetDetailsDialog = () => import("./dialog-target-details"); + +export const showTargetDetailsDialog = ( + element: HTMLElement, + params: TargetDetailsDialogParams +) => + fireEvent(element, "show-dialog", { + dialogTag: "ha-dialog-target-details", + dialogImport: loadTargetDetailsDialog, + dialogParams: params, + }); diff --git a/src/components/target-picker/ha-target-picker-item-row.ts b/src/components/target-picker/ha-target-picker-item-row.ts index 954ab5e338..55c3de5e31 100644 --- a/src/components/target-picker/ha-target-picker-item-row.ts +++ b/src/components/target-picker/ha-target-picker-item-row.ts @@ -1,6 +1,5 @@ import { consume } from "@lit/context"; import { - mdiChevronDown, mdiClose, mdiDevices, mdiHome, @@ -31,6 +30,7 @@ import { extractFromTarget, type ExtractFromTargetResult, } from "../../data/target"; +import { buttonLinkStyle } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import { brandsUrl } from "../../util/brands-url"; import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker"; @@ -43,6 +43,7 @@ import "../ha-md-list-item"; import type { HaMdListItem } from "../ha-md-list-item"; import "../ha-state-icon"; import "../ha-svg-icon"; +import { showTargetDetailsDialog } from "./dialog/show-dialog-target-details"; export type TargetType = "entity" | "device" | "area" | "label" | "floor"; @@ -54,6 +55,10 @@ export class HaTargetPickerItemRow extends LitElement { @property({ attribute: "item-id" }) public itemId!: string; + @property({ type: Boolean }) public expand = false; + + @property({ type: Boolean, attribute: "last" }) public lastItem = false; + @property({ type: Boolean, attribute: "sub-entry", reflect: true }) public subEntry = false; @@ -85,8 +90,6 @@ export class HaTargetPickerItemRow extends LitElement { @property({ type: Array, attribute: "include-device-classes" }) public includeDeviceClasses?: string[]; - @state() private _expanded = false; - @state() private _iconImg?: string; @state() private _domainName?: string; @@ -106,7 +109,6 @@ export class HaTargetPickerItemRow extends LitElement { protected willUpdate(changedProps: PropertyValues) { if (!this.subEntry && changedProps.has("itemId")) { this._updateItemData(); - this._expanded = false; } } @@ -129,63 +131,68 @@ export class HaTargetPickerItemRow extends LitElement { } return html` - - ${this.type !== "entity" - ? html`` - : nothing} - ${iconPath - ? html`` - : this._iconImg - ? html`${this._domainName` - : fallbackIconPath - ? html`` - : stateObject - ? html` - - - ` - : nothing} + +
+ ${this.subEntry + ? html` +
+
+
+ ` + : nothing} + ${iconPath + ? html`` + : this._iconImg + ? html`${this._domainName` + : fallbackIconPath + ? html`` + : stateObject + ? html` + + + ` + : nothing} +
+
${name}
${context && !this.hideContext ? html`${context}` - : nothing} + : this._domainName && this.subEntry + ? html`${this._domainName}` + : nothing} ${!this.subEntry && ((entries && (showEntities || showDevices)) || this._domainName) ? html`
- ${showEntities - ? html`${this.hass.localize( + ${showEntities && !this.expand + ? html`` + : showEntities + ? html` + ${this.hass.localize( + "ui.components.target-picker.entities_count", + { + count: entries?.referenced_entities.length, + } + )} + ` + : nothing} ${showDevices ? html`${this.hass.localize( @@ -204,7 +211,7 @@ export class HaTargetPickerItemRow extends LitElement {
` : nothing} - ${!this.subEntry + ${!this.expand && !this.subEntry ? html` - ${this._expanded && entries && entries.referenced_entities + ${this.expand && entries && entries.referenced_entities ? this._renderEntries() : nothing} `; @@ -305,31 +312,39 @@ export class HaTargetPickerItemRow extends LitElement { : []; return html` - - ${rows1.map( - (itemId, index) => html` - - ` - )} - ${rows2.map( - (itemId) => html` - - ` - )} - +
+
+
+
+ + ${rows1.map( + (itemId, index) => html` + + ` + )} + ${rows2.map( + (itemId, index) => html` + + ` + )} + +
`; } @@ -528,76 +543,103 @@ export class HaTargetPickerItemRow extends LitElement { } } - private _toggleExpand() { - const entries = this.parentEntries || this._entries; - - if ( - this.type === "entity" || - !entries || - entries.referenced_entities.length === 0 - ) { - return; - } - this._expanded = !this._expanded; + private _openDetails() { + showTargetDetailsDialog(this, { + title: this._itemData(this.type, this.itemId).name, + type: this.type, + itemId: this.itemId, + deviceFilter: this.deviceFilter, + entityFilter: this.entityFilter, + includeDomains: this.includeDomains, + includeDeviceClasses: this.includeDeviceClasses, + }); } - static styles = css` - :host { - --md-list-item-top-space: 0; - --md-list-item-bottom-space: 0; - --md-list-item-leading-space: 8px; - --md-list-item-trailing-space: 8px; - --md-list-item-two-line-container-height: 56px; - } + static styles = [ + buttonLinkStyle, + css` + :host { + --md-list-item-top-space: 0; + --md-list-item-bottom-space: 0; + --md-list-item-leading-space: 8px; + --md-list-item-trailing-space: 8px; + --md-list-item-two-line-container-height: 56px; + } - state-badge { - color: var(--ha-color-on-neutral-quiet); - } - img { - width: 24px; - height: 24px; - } - .expand-button { - transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); - } - .expand-button.expanded { - transform: rotate(180deg); - } - ha-icon-button { - --mdc-icon-button-size: 32px; - } - .summary { - display: flex; - flex-direction: column; - align-items: flex-end; - line-height: var(--ha-line-height-condensed); - } - :host([sub-entry]) .summary { - margin-right: 48px; - } - .summary .main { - font-weight: var(--ha-font-weight-medium); - } - .summary .secondary { - font-size: var(--ha-font-size-s); - color: var(--secondary-text-color); - } - .summary .secondary.domain { - font-family: var(--ha-font-family-code); - } + :host([expand]:not([sub-entry])) ha-md-list-item { + border: 2px solid var(--ha-color-border-neutral-loud); + background-color: var(--ha-color-fill-neutral-quiet-resting); + border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); + } - .entries { - padding: 0; - padding-left: 40px; - overflow: hidden; - transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); - border-bottom: 1px solid var(--ha-color-border-neutral-quiet); - } + state-badge { + color: var(--ha-color-on-neutral-quiet); + } + img { + width: 24px; + height: 24px; + } + ha-icon-button { + --mdc-icon-button-size: 32px; + } + .summary { + display: flex; + flex-direction: column; + align-items: flex-end; + line-height: var(--ha-line-height-condensed); + } + :host([sub-entry]) .summary { + margin-right: 48px; + } + .summary .main { + font-weight: var(--ha-font-weight-medium); + } + .summary .secondary { + font-size: var(--ha-font-size-s); + color: var(--secondary-text-color); + } + .domain { + font-family: var(--ha-font-family-code); + } - :host([sub-entry]) .entries { - border-bottom: none; - } - `; + .entries-tree { + display: flex; + position: relative; + } + + .entries-tree .line-wrapper { + padding: 20px; + } + + .entries-tree .line-wrapper .line { + border-left: 2px dashed var(--divider-color); + height: 100%; + position: absolute; + top: 0; + } + + :host([sub-entry]) .entries-tree .line-wrapper .line { + height: calc(100% + 18px); + top: -18px; + } + + .entries { + padding: 0; + --md-item-overflow: visible; + } + + .horizontal-line-wrapper { + position: relative; + } + .horizontal-line-wrapper .horizontal-line { + position: absolute; + top: 11px; + margin-inline-start: -28px; + width: 29px; + border-top: 2px dashed var(--divider-color); + } + `, + ]; } declare global { diff --git a/src/translations/en.json b/src/translations/en.json index cf8f1d20c5..4d5e39a6f4 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -683,6 +683,7 @@ "add_label_id": "Choose label", "devices_count": "{count} {count, plural,\n one {device}\n other {devices}\n}", "entities_count": "{count} {count, plural,\n one {entity}\n other {entities}\n}", + "overview": "Overview", "selected": { "entity": "Entities: {count}", "device": "Devices: {count}",