Add support for submenus to reflect HA state.

This commit is contained in:
Dermot Duffy
2021-12-19 13:02:54 -08:00
parent 803669865f
commit 22903edf59
6 changed files with 175 additions and 98 deletions
+15 -60
View File
@@ -1,16 +1,14 @@
import { HassEntity } from 'home-assistant-js-websocket';
import { HomeAssistant, handleAction, hasAction, stateIcon } from 'custom-card-helpers';
import { HomeAssistant, handleAction, hasAction } from 'custom-card-helpers';
import {
CSSResultGroup,
LitElement,
TemplateResult,
html,
unsafeCSS,
PropertyValues,
} from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { StyleInfo, styleMap } from 'lit/directives/style-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { actionHandler } from '../action-handler-directive.js';
@@ -21,11 +19,12 @@ import type {
ExtendedHomeAssistant,
MenuButton,
MenuConfig,
StateParameters,
} from '../types.js';
import {
convertActionToFrigateCardCustomAction,
getActionConfigGivenAction,
shouldUpdateBasedOnHass,
refreshDynamicStateParameters,
} from '../common.js';
import menuStyle from '../scss/menu.scss';
@@ -86,7 +85,7 @@ export class FrigateCardMenu extends LitElement {
const interaction: string = ev.detail.action;
const action = getActionConfigGivenAction(interaction, config);
if (!config || !action || !interaction) {
if (!config || !interaction) {
return;
}
@@ -109,39 +108,6 @@ export class FrigateCardMenu extends LitElement {
handleAction(this, this.hass as HomeAssistant, config, interaction);
}
/**
* Determine whether the menu should be updated.
* @param changedProps The changed properties.
* @returns `true` if the menu should be updated, otherwise `false`.
*/
protected shouldUpdate(changedProps: PropertyValues): boolean {
const oldHass = changedProps.get('hass') as HomeAssistant | undefined;
if (changedProps.size > 1 || !oldHass) {
return true;
}
// Extract the entities the menu rendering depends on (if any).
const entities: string[] = [];
for (let i = 0; i < this.buttons.length; i++) {
const button = this.buttons[i];
if (button.type == 'custom:frigate-card-menu-state-icon') {
entities.push(button.entity);
}
}
return shouldUpdateBasedOnHass(this.hass, oldHass, entities);
}
/**
* Get the style of emphasized menu items.
* @returns A StyleInfo.
*/
public static getEmphasizedStyle(): StyleInfo {
return {
color: 'var(--primary-color, white)',
};
}
/**
* Render a button.
* @param button The button configuration to render.
@@ -150,19 +116,17 @@ export class FrigateCardMenu extends LitElement {
protected _renderButton(button: MenuButton): TemplateResult | void {
if (button.type == 'custom:frigate-card-menu-submenu') {
return html` <frigate-card-submenu
.hass=${this.hass}
.submenu=${button}
@action=${this._actionHandler.bind(this)}
>
</frigate-card-submenu>`;
}
let state: HassEntity | null = null;
let title = button.title;
let icon = button.icon;
let style = button.style || {};
let stateParameters: StateParameters = {...button};
if (icon == FRIGATE_BUTTON_MENU_ICON) {
icon =
if (stateParameters.icon == FRIGATE_BUTTON_MENU_ICON) {
stateParameters.icon =
this._menuConfig?.mode.startsWith('hidden-') && !this.expand
? 'mdi:alpha-f-box-outline'
: 'mdi:alpha-f-box';
@@ -172,16 +136,7 @@ export class FrigateCardMenu extends LitElement {
if (!this.hass) {
return;
}
state = this.hass.states[button.entity];
if (
!!state &&
button.state_color &&
['on', 'active', 'home'].includes(state.state)
) {
style = { ...style, ...FrigateCardMenu.getEmphasizedStyle() };
}
title = title ?? (state?.attributes?.friendly_name || button.entity);
icon = icon ?? stateIcon(state);
stateParameters = refreshDynamicStateParameters(this.hass, stateParameters);
}
const hasHold = hasAction(button.hold_action);
@@ -197,17 +152,17 @@ export class FrigateCardMenu extends LitElement {
// - title (replaced with .label)
return html` <ha-icon-button
class="${classMap(classes)}"
style="${styleMap(style)}"
icon=${icon || 'mdi:gesture-tap-button'}
.label=${title || ''}
title=${title || ''}
style="${styleMap(stateParameters.style || {})}"
icon=${stateParameters.icon || 'mdi:gesture-tap-button'}
.label=${stateParameters.title || ''}
title=${stateParameters.title || ''}
@action=${(ev) => this._actionHandler(ev, button)}
.actionHandler=${actionHandler({
hasHold: hasHold,
hasDoubleClick: hasDoubleClick,
})}
>
<ha-icon icon="${icon || 'mdi:gesture-tap-button'}"></ha-icon>
<ha-icon icon="${stateParameters.icon || 'mdi:gesture-tap-button'}"></ha-icon>
</ha-icon-button>`;
}
+42 -30
View File
@@ -1,22 +1,60 @@
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators';
import { hasAction, HomeAssistant } from 'custom-card-helpers';
import { styleMap } from 'lit/directives/style-map';
import { ExtendedHomeAssistant, MenuSubmenu, MenuSubmenuItem } from '../types.js';
import { actionHandler } from '../action-handler-directive.js';
import { MenuSubmenu } from '../types.js';
import { refreshDynamicStateParameters } from '../common.js';
import submenuStyle from '../scss/submenu.scss';
import { hasAction } from 'custom-card-helpers';
import { styleMap } from 'lit/directives/style-map';
@customElement('frigate-card-submenu')
export class FrigateCardSubmenu extends LitElement {
@property({ attribute: false })
public hass?: HomeAssistant & ExtendedHomeAssistant;
@property({ attribute: false })
public submenu?: MenuSubmenu;
protected _renderItem(item: MenuSubmenuItem): TemplateResult | void {
if (!this.hass) {
return;
}
const stateParameters = refreshDynamicStateParameters(this.hass, {...item});
return html`
<mwc-list-item
style="${styleMap(stateParameters.style || {})}"
graphic="icon"
aria-label="${stateParameters.title || ''}"
@action=${(ev) => {
// Attach the action config so ascendants have access to it.
ev.detail.config = item;
}}
.actionHandler=${actionHandler({
hasHold: hasAction(item.hold_action),
hasDoubleClick: hasAction(item.double_tap_action),
})}
>
${stateParameters.title || ''}
${stateParameters.icon
? html` <ha-icon
style="${styleMap(stateParameters.style || {})}"
slot="graphic"
icon="${stateParameters.icon}"
>
</ha-icon>`
: ``}
</mwc-list-item>
`;
}
protected render(): TemplateResult {
if (!this.submenu) {
return html``;
}
return html`
<ha-button-menu corner="BOTTOM_LEFT">
<ha-icon-button
@@ -31,33 +69,7 @@ export class FrigateCardSubmenu extends LitElement {
>
<ha-icon icon="${this.submenu.icon}"></ha-icon>
</ha-icon-button>
${this.submenu.items.map(
(item) => html`
<mwc-list-item
style="${styleMap(item.style || {})}"
graphic="icon"
aria-label="${item.title || ''}"
@action=${(ev) => {
// Attach the action config so ascendants have access to it.
ev.detail.config = item;
}}
.actionHandler=${actionHandler({
hasHold: hasAction(item.hold_action),
hasDoubleClick: hasAction(item.double_tap_action),
})}
>
${item.title || ''}
${item.icon
? html` <ha-icon
style="${styleMap(item.style || {})}"
slot="graphic"
icon="${item.icon}"
>
</ha-icon>`
: ``}
</mwc-list-item>
`,
)}
${this.submenu.items.map(this._renderItem.bind(this))}
</ha-button-menu>
`;
}