mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 16:26:43 +00:00
Merge dialog with more info
This commit is contained in:
parent
3269fd3c5b
commit
9e4835107d
@ -0,0 +1,225 @@
|
||||
import { mdiLightbulb, mdiLightbulbOff } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../../common/entity/compute_state_domain";
|
||||
import { computeGroupEntitiesState } from "../../../../common/entity/group_entities";
|
||||
import "../../../../components/ha-control-button";
|
||||
import "../../../../components/ha-control-button-group";
|
||||
import "../../../../components/ha-domain-icon";
|
||||
import { isFullyClosed, isFullyOpen } from "../../../../data/cover";
|
||||
import { UNAVAILABLE } from "../../../../data/entity";
|
||||
import { forwardHaptic } from "../../../../data/haptics";
|
||||
import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { TileCardConfig } from "../../../../panels/lovelace/cards/types";
|
||||
import "../../../../panels/lovelace/sections/hui-section";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
export interface GroupToggleDialogParams {
|
||||
entityIds: string[];
|
||||
}
|
||||
|
||||
@customElement("ha-more-info-view-toggle-group")
|
||||
class HaMoreInfoViewToggleGroup extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public params?: GroupToggleDialogParams;
|
||||
|
||||
private _sectionConfig = memoizeOne(
|
||||
(entities: string[]): LovelaceSectionConfig => ({
|
||||
type: "grid",
|
||||
cards: entities.map<TileCardConfig>((entity) => ({
|
||||
type: "tile",
|
||||
entity: entity,
|
||||
icon_tap_action: {
|
||||
action: "toggle",
|
||||
},
|
||||
tap_action: {
|
||||
action: "more-info",
|
||||
},
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
})),
|
||||
})
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const sectionConfig = this._sectionConfig(this.params.entityIds);
|
||||
|
||||
const entities = this.params.entityIds
|
||||
.map((entityId) => this.hass!.states[entityId] as HassEntity | undefined)
|
||||
.filter((v): v is HassEntity => Boolean(v));
|
||||
|
||||
const mainStateObj = entities[0];
|
||||
|
||||
const groupState = computeGroupEntitiesState(entities);
|
||||
const formattedGroupState = this.hass.formatEntityState(
|
||||
mainStateObj,
|
||||
groupState
|
||||
);
|
||||
|
||||
const domain = computeStateDomain(mainStateObj);
|
||||
|
||||
const deviceClass = mainStateObj.attributes.device_class;
|
||||
|
||||
const isGroup = this.params.entityIds.length > 1;
|
||||
|
||||
const isAllOn = entities.every((entity) =>
|
||||
entity.state === UNAVAILABLE ||
|
||||
computeDomain(entity.entity_id) === "cover"
|
||||
? isFullyOpen(entity)
|
||||
: entity.state === "on"
|
||||
);
|
||||
const isAllOff = entities.every((entity) =>
|
||||
entity.state === UNAVAILABLE ||
|
||||
computeDomain(entity.entity_id) === "cover"
|
||||
? isFullyClosed(entity)
|
||||
: entity.state === "off"
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${mainStateObj}
|
||||
.stateOverride=${formattedGroupState}
|
||||
></ha-more-info-state-header>
|
||||
<ha-control-button-group vertical>
|
||||
<ha-control-button
|
||||
vertical
|
||||
@click=${this._turnAllOn}
|
||||
.disabled=${isAllOn}
|
||||
>
|
||||
${domain !== "light"
|
||||
? html`<ha-domain-icon
|
||||
.hass=${this.hass}
|
||||
.domain=${domain}
|
||||
.state=${domain === "cover" ? "open" : "on"}
|
||||
.deviceClass=${deviceClass}
|
||||
></ha-domain-icon>`
|
||||
: html` <ha-svg-icon .path=${mdiLightbulb}></ha-svg-icon> `}
|
||||
<p>
|
||||
${domain === "cover"
|
||||
? isGroup
|
||||
? "Open all"
|
||||
: "Open"
|
||||
: isGroup
|
||||
? "Turn all on"
|
||||
: "Turn on"}
|
||||
</p>
|
||||
</ha-control-button>
|
||||
<ha-control-button
|
||||
vertical
|
||||
@click=${this._turnAllOff}
|
||||
.disabled=${isAllOff}
|
||||
>
|
||||
${domain !== "light"
|
||||
? html`
|
||||
<ha-domain-icon
|
||||
.hass=${this.hass}
|
||||
.domain=${domain}
|
||||
.state=${domain === "cover" ? "closed" : "off"}
|
||||
.deviceClass=${deviceClass}
|
||||
></ha-domain-icon>
|
||||
`
|
||||
: html` <ha-svg-icon .path=${mdiLightbulbOff}></ha-svg-icon>`}
|
||||
<p>
|
||||
${domain === "cover"
|
||||
? isGroup
|
||||
? "Close all"
|
||||
: "Close"
|
||||
: isGroup
|
||||
? "Turn all off"
|
||||
: "Turn off"}
|
||||
</p>
|
||||
</ha-control-button>
|
||||
</ha-control-button-group>
|
||||
<hui-section .config=${sectionConfig} .hass=${this.hass}></hui-section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _turnAllOff() {
|
||||
if (!this.params) {
|
||||
return;
|
||||
}
|
||||
|
||||
forwardHaptic("light");
|
||||
const domain = computeDomain(this.params.entityIds[0]);
|
||||
if (domain === "cover") {
|
||||
this.hass.callService("cover", "close_cover", {
|
||||
entity_id: this.params.entityIds,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.hass.callService("homeassistant", "turn_off", {
|
||||
entity_id: this.params.entityIds,
|
||||
});
|
||||
}
|
||||
|
||||
private _turnAllOn() {
|
||||
if (!this.params) {
|
||||
return;
|
||||
}
|
||||
|
||||
forwardHaptic("light");
|
||||
const domain = computeDomain(this.params.entityIds[0]);
|
||||
if (domain === "cover") {
|
||||
this.hass.callService("cover", "open_cover", {
|
||||
entity_id: this.params.entityIds,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.hass.callService("homeassistant", "turn_on", {
|
||||
entity_id: this.params.entityIds,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
.content {
|
||||
padding: 24px;
|
||||
padding-bottom: max(var(--safe-area-inset-bottom), 24px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
ha-control-button-group {
|
||||
--control-button-group-spacing: 12px;
|
||||
--control-button-group-thickness: 130px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
ha-control-button {
|
||||
--control-button-border-radius: 16px;
|
||||
--mdc-icon-size: 24px;
|
||||
color: #006787;
|
||||
--control-button-padding: 16px 8px;
|
||||
--control-button-icon-color: #006787;
|
||||
--control-button-background-color: #eff9fe;
|
||||
--control-button-background-opacity: 1;
|
||||
--control-button-focus-color: #006787;
|
||||
--ha-ripple-color: #006787;
|
||||
}
|
||||
ha-control-button p {
|
||||
margin: 0;
|
||||
}
|
||||
hui-section {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-view-toggle-group": HaMoreInfoViewToggleGroup;
|
||||
}
|
||||
}
|
@ -8,9 +8,9 @@ export const showVoiceAssistantsView = (
|
||||
title: string
|
||||
): void => {
|
||||
fireEvent(element, "show-child-view", {
|
||||
viewTag: "ha-more-info-view-voice-assistants",
|
||||
viewImport: loadVoiceAssistantsView,
|
||||
viewTitle: title,
|
||||
viewParams: {},
|
||||
tag: "ha-more-info-view-voice-assistants",
|
||||
import: loadVoiceAssistantsView,
|
||||
title: title,
|
||||
params: {},
|
||||
});
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
mdiPencilOutline,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { cache } from "lit/directives/cache";
|
||||
@ -68,15 +68,24 @@ export interface MoreInfoDialogParams {
|
||||
view?: View;
|
||||
/** @deprecated Use `view` instead */
|
||||
tab?: View;
|
||||
parentView?: ParentView;
|
||||
}
|
||||
|
||||
type View = "info" | "history" | "settings" | "related";
|
||||
type View = "info" | "history" | "settings" | "related" | "parent";
|
||||
|
||||
interface ParentView {
|
||||
tag: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
import?: () => Promise<unknown>;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
interface ChildView {
|
||||
viewTag: string;
|
||||
viewTitle?: string;
|
||||
viewImport?: () => Promise<unknown>;
|
||||
viewParams?: any;
|
||||
tag: string;
|
||||
title?: string;
|
||||
import?: () => Promise<unknown>;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
declare global {
|
||||
@ -88,7 +97,8 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_VIEW: View = "info";
|
||||
const INFO_VIEW: View = "info";
|
||||
const PARENT_VIEW: View = "parent";
|
||||
|
||||
@customElement("ha-more-info-dialog")
|
||||
export class MoreInfoDialog extends LitElement {
|
||||
@ -98,9 +108,11 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
@state() private _entityId?: string | null;
|
||||
|
||||
@state() private _currView: View = DEFAULT_VIEW;
|
||||
@state() private _currView: View = INFO_VIEW;
|
||||
|
||||
@state() private _initialView: View = DEFAULT_VIEW;
|
||||
@state() private _initialView: View = INFO_VIEW;
|
||||
|
||||
@state() private _parentView?: ParentView;
|
||||
|
||||
@state() private _childView?: ChildView;
|
||||
|
||||
@ -114,17 +126,29 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
public showDialog(params: MoreInfoDialogParams) {
|
||||
this._entityId = params.entityId;
|
||||
if (!this._entityId) {
|
||||
if (!this._entityId && !params.parentView) {
|
||||
this.closeDialog();
|
||||
return;
|
||||
}
|
||||
this._currView = params.view || DEFAULT_VIEW;
|
||||
this._initialView = params.view || DEFAULT_VIEW;
|
||||
this._parentView = params.parentView;
|
||||
if (this._parentView?.import) {
|
||||
this._parentView.import();
|
||||
this._currView = PARENT_VIEW;
|
||||
} else {
|
||||
this._currView = params.view || INFO_VIEW;
|
||||
}
|
||||
this._initialView = params.view || INFO_VIEW;
|
||||
this._childView = undefined;
|
||||
this.large = false;
|
||||
this._loadEntityRegistryEntry();
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("_entityId")) {
|
||||
this._loadEntityRegistryEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadEntityRegistryEntry() {
|
||||
if (!this._entityId) {
|
||||
return;
|
||||
@ -143,19 +167,18 @@ export class MoreInfoDialog extends LitElement {
|
||||
this._entityId = undefined;
|
||||
this._entry = undefined;
|
||||
this._childView = undefined;
|
||||
this._parentView = undefined;
|
||||
this._currView = INFO_VIEW;
|
||||
this._infoEditMode = false;
|
||||
this._initialView = DEFAULT_VIEW;
|
||||
this._initialView = INFO_VIEW;
|
||||
this._isEscapeEnabled = true;
|
||||
window.removeEventListener("dialog-closed", this._enableEscapeKeyClose);
|
||||
window.removeEventListener("show-dialog", this._disableEscapeKeyClose);
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _shouldShowEditIcon(
|
||||
domain: string,
|
||||
stateObj: HassEntity | undefined
|
||||
): boolean {
|
||||
if (__DEMO__ || !stateObj) {
|
||||
private _shouldShowEditIcon(domain?: string, stateObj?: HassEntity): boolean {
|
||||
if (__DEMO__ || !stateObj || !domain) {
|
||||
return false;
|
||||
}
|
||||
if (EDITABLE_DOMAINS_WITH_ID.includes(domain) && stateObj.attributes.id) {
|
||||
@ -171,8 +194,9 @@ export class MoreInfoDialog extends LitElement {
|
||||
return false;
|
||||
}
|
||||
|
||||
private _shouldShowHistory(domain: string): boolean {
|
||||
private _shouldShowHistory(domain?: string): boolean {
|
||||
return (
|
||||
domain !== undefined &&
|
||||
DOMAINS_WITH_MORE_INFO.includes(domain) &&
|
||||
(computeShowHistoryComponent(this.hass, this._entityId!) ||
|
||||
computeShowLogBookComponent(
|
||||
@ -207,14 +231,30 @@ export class MoreInfoDialog extends LitElement {
|
||||
private _goBack() {
|
||||
if (this._childView) {
|
||||
this._childView = undefined;
|
||||
} else {
|
||||
this._setView(this._initialView);
|
||||
return;
|
||||
}
|
||||
const previousView = this._previousView();
|
||||
if (previousView) {
|
||||
this._setView(previousView);
|
||||
}
|
||||
}
|
||||
|
||||
private _previousView(): View | undefined {
|
||||
if (this._currView === PARENT_VIEW) {
|
||||
return undefined;
|
||||
}
|
||||
if (this._currView !== this._initialView) {
|
||||
return this._initialView;
|
||||
}
|
||||
if (this._parentView) {
|
||||
return PARENT_VIEW;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _resetInitialView() {
|
||||
this._initialView = DEFAULT_VIEW;
|
||||
this._setView(DEFAULT_VIEW);
|
||||
this._initialView = INFO_VIEW;
|
||||
this._setView(INFO_VIEW);
|
||||
}
|
||||
|
||||
private _goToHistory() {
|
||||
@ -227,8 +267,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
private _showChildView(ev: CustomEvent): void {
|
||||
const view = ev.detail as ChildView;
|
||||
if (view.viewImport) {
|
||||
view.viewImport();
|
||||
if (view.import) {
|
||||
view.import();
|
||||
}
|
||||
this._childView = view;
|
||||
}
|
||||
@ -286,45 +326,99 @@ export class MoreInfoDialog extends LitElement {
|
||||
this._sensorNumericDeviceClasses = deviceClasses.numeric_device_classes;
|
||||
}
|
||||
|
||||
private _handleMoreInfoEvent(ev: CustomEvent) {
|
||||
// If the parent view has a `show-dialog` event to open more info, we handle it here to set the entity ID and view.
|
||||
const detail = ev.detail as MoreInfoDialogParams;
|
||||
if (detail.entityId) {
|
||||
this._entityId = detail.entityId;
|
||||
this._setView(detail.view || INFO_VIEW);
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private _renderHeader = (): TemplateResult | typeof nothing => {
|
||||
if (this._parentView && this._currView === PARENT_VIEW) {
|
||||
return html`
|
||||
${this._parentView
|
||||
? html`<p class="breadcrumb">${this._parentView.subtitle}</p>`
|
||||
: nothing}
|
||||
<p class="main">${this._parentView.title}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
const entityId = this._entityId;
|
||||
|
||||
if (entityId) {
|
||||
const stateObj = this.hass.states[entityId] as HassEntity | undefined;
|
||||
const context = stateObj
|
||||
? getEntityContext(stateObj, this.hass)
|
||||
: this._entry
|
||||
? getEntityEntryContext(this._entry, this.hass)
|
||||
: undefined;
|
||||
|
||||
const entityName = stateObj
|
||||
? computeEntityName(stateObj, this.hass)
|
||||
: this._entry
|
||||
? computeEntityEntryName(this._entry, this.hass)
|
||||
: entityId;
|
||||
|
||||
const deviceName = context?.device
|
||||
? computeDeviceName(context.device)
|
||||
: undefined;
|
||||
const areaName = context?.area
|
||||
? computeAreaName(context.area)
|
||||
: undefined;
|
||||
|
||||
const breadcrumb = [areaName, deviceName, entityName].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
);
|
||||
const title = this._childView?.title || breadcrumb.pop() || entityId;
|
||||
const isAdmin = this.hass.user!.is_admin;
|
||||
|
||||
return html`
|
||||
${breadcrumb.length > 0
|
||||
? !__DEMO__ && isAdmin
|
||||
? html`
|
||||
<button
|
||||
class="breadcrumb"
|
||||
@click=${this._breadcrumbClick}
|
||||
aria-label=${breadcrumb.join(" > ")}
|
||||
>
|
||||
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
<p class="breadcrumb">
|
||||
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
|
||||
</p>
|
||||
`
|
||||
: nothing}
|
||||
<p class="main">${title}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
return nothing;
|
||||
};
|
||||
|
||||
protected render() {
|
||||
if (!this._entityId) {
|
||||
if (!this._entityId && !this._parentView) {
|
||||
return nothing;
|
||||
}
|
||||
const entityId = this._entityId;
|
||||
const stateObj = this.hass.states[entityId] as HassEntity | undefined;
|
||||
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||
|
||||
const domain = computeDomain(entityId);
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
|
||||
const isAdmin = this.hass.user!.is_admin;
|
||||
|
||||
const deviceId = this._getDeviceId();
|
||||
|
||||
const isDefaultView = this._currView === DEFAULT_VIEW && !this._childView;
|
||||
const previousView = this._previousView();
|
||||
const isDefaultView = this._currView === INFO_VIEW && !this._childView;
|
||||
const isSpecificInitialView =
|
||||
this._initialView !== DEFAULT_VIEW && !this._childView;
|
||||
const showCloseIcon = isDefaultView || isSpecificInitialView;
|
||||
|
||||
const context = stateObj
|
||||
? getEntityContext(stateObj, this.hass)
|
||||
: this._entry
|
||||
? getEntityEntryContext(this._entry, this.hass)
|
||||
: undefined;
|
||||
|
||||
const entityName = stateObj
|
||||
? computeEntityName(stateObj, this.hass)
|
||||
: this._entry
|
||||
? computeEntityEntryName(this._entry, this.hass)
|
||||
: entityId;
|
||||
|
||||
const deviceName = context?.device
|
||||
? computeDeviceName(context.device)
|
||||
: undefined;
|
||||
const areaName = context?.area ? computeAreaName(context.area) : undefined;
|
||||
|
||||
const breadcrumb = [areaName, deviceName, entityName].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
);
|
||||
const title = this._childView?.viewTitle || breadcrumb.pop() || entityId;
|
||||
this._initialView !== INFO_VIEW && !this._childView;
|
||||
const showCloseIcon = !previousView && !this._childView;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
@ -332,7 +426,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
@closed=${this.closeDialog}
|
||||
@opened=${this._handleOpened}
|
||||
.escapeKeyAction=${this._isEscapeEnabled ? undefined : ""}
|
||||
.heading=${title}
|
||||
.heading=${" "}
|
||||
hideActions
|
||||
flexContent
|
||||
>
|
||||
@ -356,24 +450,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
></ha-icon-button-prev>
|
||||
`}
|
||||
<span slot="title" @click=${this._enlarge} class="title">
|
||||
${breadcrumb.length > 0
|
||||
? !__DEMO__ && isAdmin
|
||||
? html`
|
||||
<button
|
||||
class="breadcrumb"
|
||||
@click=${this._breadcrumbClick}
|
||||
aria-label=${breadcrumb.join(" > ")}
|
||||
>
|
||||
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
<p class="breadcrumb">
|
||||
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
|
||||
</p>
|
||||
`
|
||||
: nothing}
|
||||
<p class="main">${title}</p>
|
||||
${this._renderHeader()}
|
||||
</span>
|
||||
${isDefaultView
|
||||
? html`
|
||||
@ -521,54 +598,62 @@ export class MoreInfoDialog extends LitElement {
|
||||
@show-child-view=${this._showChildView}
|
||||
@entity-entry-updated=${this._entryUpdated}
|
||||
@toggle-edit-mode=${this._handleToggleInfoEditModeEvent}
|
||||
@hass-more-info=${this._handleMoreInfoEvent}
|
||||
>
|
||||
${cache(
|
||||
this._childView
|
||||
? html`
|
||||
<div class="child-view">
|
||||
${dynamicElement(this._childView.viewTag, {
|
||||
${dynamicElement(this._childView.tag, {
|
||||
hass: this.hass,
|
||||
entry: this._entry,
|
||||
params: this._childView.viewParams,
|
||||
params: this._childView.params,
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: this._currView === "info"
|
||||
? html`
|
||||
<ha-more-info-info
|
||||
dialogInitialFocus
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
.editMode=${this._infoEditMode}
|
||||
></ha-more-info-info>
|
||||
`
|
||||
: this._currView === "history"
|
||||
: this._currView === "parent"
|
||||
? dynamicElement(this._parentView!.tag, {
|
||||
hass: this.hass,
|
||||
entry: this._entry,
|
||||
params: this._parentView!.params,
|
||||
})
|
||||
: this._currView === "info"
|
||||
? html`
|
||||
<ha-more-info-history-and-logbook
|
||||
<ha-more-info-info
|
||||
dialogInitialFocus
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-history-and-logbook>
|
||||
.entry=${this._entry}
|
||||
.editMode=${this._infoEditMode}
|
||||
></ha-more-info-info>
|
||||
`
|
||||
: this._currView === "settings"
|
||||
: this._currView === "history"
|
||||
? html`
|
||||
<ha-more-info-settings
|
||||
<ha-more-info-history-and-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
></ha-more-info-settings>
|
||||
></ha-more-info-history-and-logbook>
|
||||
`
|
||||
: this._currView === "related"
|
||||
: this._currView === "settings"
|
||||
? html`
|
||||
<ha-related-items
|
||||
<ha-more-info-settings
|
||||
.hass=${this.hass}
|
||||
.itemId=${entityId}
|
||||
.itemType=${SearchableDomains.has(domain)
|
||||
? (domain as ItemType)
|
||||
: "entity"}
|
||||
></ha-related-items>
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
></ha-more-info-settings>
|
||||
`
|
||||
: nothing
|
||||
: this._currView === "related"
|
||||
? html`
|
||||
<ha-related-items
|
||||
.hass=${this.hass}
|
||||
.itemId=${entityId}
|
||||
.itemType=${domain &&
|
||||
SearchableDomains.has(domain)
|
||||
? (domain as ItemType)
|
||||
: "entity"}
|
||||
></ha-related-items>
|
||||
`
|
||||
: nothing
|
||||
)}
|
||||
</div>
|
||||
</ha-dialog>
|
||||
|
@ -15,10 +15,11 @@ import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-domain-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import type { GroupToggleDialogParams } from "../../../dialogs/more-info/components/voice/ha-more-info-view-toggle-group";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import { computeCssVariable } from "../../../resources/css-variables";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { AreaCardFeatureContext } from "../cards/hui-area-card";
|
||||
import { showGroupControlDialog } from "../dialogs/show-group-toggle-dialog";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
import type {
|
||||
@ -176,10 +177,20 @@ class HuiAreaControlsCardFeature
|
||||
|
||||
const domain = AREA_CONTROLS_BUTTONS[control].filter.domain;
|
||||
|
||||
showGroupControlDialog(this, {
|
||||
title: computeAreaName(this._area!) || "",
|
||||
subtitle: domain,
|
||||
entityIds: entitiesIds,
|
||||
showMoreInfoDialog(this, {
|
||||
entityId: null,
|
||||
parentView: {
|
||||
title: computeAreaName(this._area!) || "",
|
||||
subtitle: domain,
|
||||
tag: "ha-more-info-view-toggle-group",
|
||||
import: () =>
|
||||
import(
|
||||
"../../../dialogs/more-info/components/voice/ha-more-info-view-toggle-group"
|
||||
),
|
||||
params: {
|
||||
entityIds: entitiesIds,
|
||||
} as GroupToggleDialogParams,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,332 +0,0 @@
|
||||
import { mdiClose, mdiLightbulb, mdiLightbulbOff } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeGroupEntitiesState } from "../../../common/entity/group_entities";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-header";
|
||||
import "../../../components/ha-domain-icon";
|
||||
import "../../../components/ha-header-bar";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import "../../../dialogs/more-info/components/ha-more-info-state-header";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { TileCardConfig } from "../cards/types";
|
||||
import "../sections/hui-section";
|
||||
import type { GroupToggleDialogParams } from "./show-group-toggle-dialog";
|
||||
import { isFullyClosed, isFullyOpen } from "../../../data/cover";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
@customElement("hui-dialog-group-toggle")
|
||||
class HuiGroupToggleDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: GroupToggleDialogParams;
|
||||
|
||||
public async showDialog(params: GroupToggleDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
public async closeDialog(): Promise<void> {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _sectionConfig = memoizeOne(
|
||||
(entities: string[]): LovelaceSectionConfig => ({
|
||||
type: "grid",
|
||||
cards: entities.map<TileCardConfig>((entity) => ({
|
||||
type: "tile",
|
||||
entity: entity,
|
||||
icon_tap_action: {
|
||||
action: "toggle",
|
||||
},
|
||||
tap_action: {
|
||||
action: "more-info",
|
||||
},
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
})),
|
||||
})
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const sectionConfig = this._sectionConfig(this._params.entityIds);
|
||||
|
||||
const entities = this._params.entityIds
|
||||
.map((entityId) => this.hass!.states[entityId] as HassEntity | undefined)
|
||||
.filter((v): v is HassEntity => Boolean(v));
|
||||
|
||||
const mainStateObj = entities[0];
|
||||
|
||||
const groupState = computeGroupEntitiesState(entities);
|
||||
const formattedGroupState = this.hass.formatEntityState(
|
||||
mainStateObj,
|
||||
groupState
|
||||
);
|
||||
|
||||
const domain = computeStateDomain(mainStateObj);
|
||||
|
||||
const deviceClass = mainStateObj.attributes.device_class;
|
||||
|
||||
const isGroup = this._params.entityIds.length > 1;
|
||||
|
||||
const isAllOn = entities.every((entity) =>
|
||||
entity.state === UNAVAILABLE ||
|
||||
computeDomain(entity.entity_id) === "cover"
|
||||
? isFullyOpen(entity)
|
||||
: entity.state === "on"
|
||||
);
|
||||
const isAllOff = entities.every((entity) =>
|
||||
entity.state === UNAVAILABLE ||
|
||||
computeDomain(entity.entity_id) === "cover"
|
||||
? isFullyClosed(entity)
|
||||
: entity.state === "off"
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${this._params.title}
|
||||
hideActions
|
||||
flexContent
|
||||
>
|
||||
<ha-dialog-header slot="heading">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
dialogAction="cancel"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
<span slot="title" class="title">
|
||||
${this._params.subtitle
|
||||
? html`<span class="subtitle">${this._params.subtitle}</span>`
|
||||
: nothing}
|
||||
<p class="main">${this._params.title}</p>
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
<div class="content">
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${mainStateObj}
|
||||
.stateOverride=${formattedGroupState}
|
||||
></ha-more-info-state-header>
|
||||
<ha-control-button-group vertical>
|
||||
<ha-control-button
|
||||
vertical
|
||||
@click=${this._turnAllOn}
|
||||
.disabled=${isAllOn}
|
||||
>
|
||||
${domain !== "light"
|
||||
? html`<ha-domain-icon
|
||||
.hass=${this.hass}
|
||||
.domain=${domain}
|
||||
.state=${domain === "cover" ? "open" : "on"}
|
||||
.deviceClass=${deviceClass}
|
||||
></ha-domain-icon>`
|
||||
: html` <ha-svg-icon .path=${mdiLightbulb}></ha-svg-icon> `}
|
||||
<p>
|
||||
${domain === "cover"
|
||||
? isGroup
|
||||
? "Open all"
|
||||
: "Open"
|
||||
: isGroup
|
||||
? "Turn all on"
|
||||
: "Turn on"}
|
||||
</p>
|
||||
</ha-control-button>
|
||||
<ha-control-button
|
||||
vertical
|
||||
@click=${this._turnAllOff}
|
||||
.disabled=${isAllOff}
|
||||
>
|
||||
${domain !== "light"
|
||||
? html`
|
||||
<ha-domain-icon
|
||||
.hass=${this.hass}
|
||||
.domain=${domain}
|
||||
.state=${domain === "cover" ? "closed" : "off"}
|
||||
.deviceClass=${deviceClass}
|
||||
></ha-domain-icon>
|
||||
`
|
||||
: html` <ha-svg-icon .path=${mdiLightbulbOff}></ha-svg-icon>`}
|
||||
<p>
|
||||
${domain === "cover"
|
||||
? isGroup
|
||||
? "Close all"
|
||||
: "Close"
|
||||
: isGroup
|
||||
? "Turn all off"
|
||||
: "Turn off"}
|
||||
</p>
|
||||
</ha-control-button>
|
||||
</ha-control-button-group>
|
||||
<hui-section
|
||||
.config=${sectionConfig}
|
||||
.hass=${this.hass}
|
||||
></hui-section>
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _turnAllOff() {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
|
||||
forwardHaptic("light");
|
||||
const domain = computeDomain(this._params.entityIds[0]);
|
||||
if (domain === "cover") {
|
||||
this.hass.callService("cover", "close_cover", {
|
||||
entity_id: this._params.entityIds,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.hass.callService("homeassistant", "turn_off", {
|
||||
entity_id: this._params.entityIds,
|
||||
});
|
||||
}
|
||||
|
||||
private _turnAllOn() {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
|
||||
forwardHaptic("light");
|
||||
const domain = computeDomain(this._params.entityIds[0]);
|
||||
if (domain === "cover") {
|
||||
this.hass.callService("cover", "open_cover", {
|
||||
entity_id: this._params.entityIds,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.hass.callService("homeassistant", "turn_on", {
|
||||
entity_id: this._params.entityIds,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
/* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */
|
||||
--vertical-align-dialog: flex-start;
|
||||
--dialog-surface-margin-top: 40px;
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
/* When in fullscreen dialog should be attached to top */
|
||||
ha-dialog {
|
||||
--dialog-surface-margin-top: 0px;
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 600px) and (min-height: 501px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 580px;
|
||||
--mdc-dialog-max-width: 580px;
|
||||
--mdc-dialog-max-height: calc(100% - 72px);
|
||||
}
|
||||
|
||||
.main-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:host([large]) ha-dialog {
|
||||
--mdc-dialog-min-width: 90vw;
|
||||
--mdc-dialog-max-width: 90vw;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.title p {
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title .main {
|
||||
color: var(--primary-text-color);
|
||||
font-size: var(--ha-font-size-xl);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
|
||||
.title .subtitle {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: var(--ha-font-size-m);
|
||||
line-height: 16px;
|
||||
--mdc-icon-size: 16px;
|
||||
padding: 4px;
|
||||
margin: -4px;
|
||||
margin-top: -10px;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
display: inline;
|
||||
border-radius: 6px;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 16px 16px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
ha-control-button-group {
|
||||
--control-button-group-spacing: 12px;
|
||||
--control-button-group-thickness: 130px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
ha-control-button {
|
||||
--control-button-border-radius: 16px;
|
||||
--mdc-icon-size: 24px;
|
||||
color: #006787;
|
||||
--control-button-padding: 16px 8px;
|
||||
--control-button-icon-color: #006787;
|
||||
--control-button-background-color: #eff9fe;
|
||||
--control-button-background-opacity: 1;
|
||||
--control-button-focus-color: #006787;
|
||||
--ha-ripple-color: #006787;
|
||||
}
|
||||
ha-control-button p {
|
||||
margin: 0;
|
||||
}
|
||||
hui-section {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-dialog-group-toggle": HuiGroupToggleDialog;
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
export interface GroupToggleDialogParams {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
entityIds: string[];
|
||||
}
|
||||
|
||||
export const showGroupControlDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: GroupToggleDialogParams
|
||||
) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "hui-dialog-group-toggle",
|
||||
dialogImport: () => import("./hui-dialog-group-toggle"),
|
||||
dialogParams: dialogParams,
|
||||
});
|
||||
};
|
@ -30,6 +30,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
{
|
||||
entityId: ev.detail.entityId,
|
||||
view: ev.detail.view || ev.detail.tab,
|
||||
parentView: ev.detail.parentView,
|
||||
},
|
||||
() => import("../dialogs/more-info/ha-more-info-dialog")
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user