Allow dynamic menu contents.

This commit is contained in:
Dermot Duffy
2021-08-28 23:35:47 -07:00
parent b1ab849981
commit e0fe908357
3 changed files with 93 additions and 113 deletions
+5
View File
@@ -43,3 +43,8 @@ ha-icon-button.button {
/* Buttons can always be clicked */ /* Buttons can always be clicked */
pointer-events: auto; pointer-events: auto;
} }
ha-icon-button.emphasized-button {
@extend ha-icon-button, .button;
color: var(--primary-color, white);
}
+82 -113
View File
@@ -1,4 +1,3 @@
// TODO Don't show button if no Frigate url.
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { import {
LitElement, LitElement,
@@ -10,6 +9,7 @@ import {
PropertyValues, PropertyValues,
state, state,
unsafeCSS, unsafeCSS,
query,
} from 'lit-element'; } from 'lit-element';
import { NodePart } from 'lit-html'; import { NodePart } from 'lit-html';
@@ -30,6 +30,7 @@ import frigate_card_menu_style from './frigate-hass-card-menu.scss';
import { import {
browseMediaSourceSchema, browseMediaSourceSchema,
frigateCardConfigSchema, frigateCardConfigSchema,
MenuButton,
resolvedMediaSchema, resolvedMediaSchema,
} from './types'; } from './types';
import type { import type {
@@ -111,59 +112,47 @@ export class FrigateCardMenu extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
protected expand = false; protected expand = false;
@property({ attribute: false })
protected motionEntity: string | null = null;
@property({ attribute: false })
public hass: HomeAssistant | null = null;
@property({ attribute: false }) @property({ attribute: false })
protected actionCallback: FrigateCardMenuCallback | null = null; protected actionCallback: FrigateCardMenuCallback | null = null;
protected shouldUpdate(changedProps: PropertyValues): boolean { @property({ attribute: false })
const oldHass = changedProps.get('hass') as HomeAssistant | undefined; public buttons: Map<string, MenuButton> = new Map();
if (oldHass) {
return shouldUpdateBasedOnHass(this.hass, oldHass, [this.motionEntity]);
}
return true;
}
// Render the Frigate menu button.
protected _renderFrigateButton(): TemplateResult {
return html` <ha-icon-button
class="button"
icon=${this.menuMode != 'hidden' || this.expand
? 'mdi:alpha-f-box'
: 'mdi:alpha-f-box-outline'}
data-toggle="tooltip"
title="Frigate menu"
@click=${() => {
if (this.menuMode == 'hidden') {
this.expand = !this.expand;
} else {
this._callAction('default');
}
}}
></ha-icon-button>`;
}
// Call the callback. // Call the callback.
protected _callAction(name: string): void { protected _callAction(name: string): void {
if (name == 'frigate' && this.menuMode == 'hidden') {
this.expand = !this.expand;
return;
}
if (this.actionCallback) { if (this.actionCallback) {
this.actionCallback(name); this.actionCallback(name);
} }
} }
// Render the menu. // Render a menu button.
protected render(): TemplateResult | void | ((part: NodePart) => Promise<void>) { protected _renderButton(name: string, button: MenuButton): TemplateResult {
let motionIcon: string | null = null; return html` <ha-icon-button
if (this.motionEntity && this.hass) { class=${button.emphasize ? "emphasized-button" : "button"}
motionIcon = icon=${button.icon || 'mdi:gesture-tap-button'}
this.hass.states[this.motionEntity]?.state == 'on' data-toggle="tooltip"
? 'mdi:motion-sensor' title=${button.description}
: 'mdi:walk'; @click=${() => this._callAction(name)}
></ha-icon-button>`;
} }
// Render the Frigate menu button.
protected _renderFrigateButton(name: string, button: MenuButton): TemplateResult {
const icon =
this.menuMode != 'hidden' || this.expand
? 'mdi:alpha-f-box'
: 'mdi:alpha-f-box-outline';
return this._renderButton(name, Object.assign({}, button, { icon: icon }));
}
// Render the menu.
protected render(): TemplateResult | void | ((part: NodePart) => Promise<void>) {
let menuClass = 'frigate-card-menu-full'; let menuClass = 'frigate-card-menu-full';
if (['hidden', 'overlay'].includes(this.menuMode)) { if (['hidden', 'overlay'].includes(this.menuMode)) {
if (this.menuMode == 'overlay' || this.expand) { if (this.menuMode == 'overlay' || this.expand) {
@@ -175,63 +164,15 @@ export class FrigateCardMenu extends LitElement {
return html` return html`
<div class=${menuClass}> <div class=${menuClass}>
${this._renderFrigateButton()} ${Array.from(this.buttons.keys()).map((name) => {
${this.menuMode != 'hidden' || this.expand const button = this.buttons.get(name);
? html` if (button) {
<ha-icon-button return name === 'frigate'
class="button" ? this._renderFrigateButton(name, button)
icon="mdi:cctv" : this._renderButton(name, button);
data-toggle="tooltip" }
title="View live" return html``;
@click=${() => { })}
this.expand = false;
this._callAction('live');
}}
></ha-icon-button>
<ha-icon-button
class="button"
icon="mdi:filmstrip"
data-toggle="tooltip"
title="View clips"
@click=${() => {
this.expand = false;
this._callAction('clips');
}}
></ha-icon-button>
<ha-icon-button
class="button"
icon="mdi:camera"
data-toggle="tooltip"
title="View snapshots"
@click=${() => {
this.expand = false;
this._callAction('snapshots');
}}
></ha-icon-button>
<ha-icon-button
class="button"
icon="mdi:web"
data-toggle="tooltip"
title="View Frigate UI"
@click=${() => {
this.expand = false;
this._callAction('frigate-ui');
}}
></ha-icon-button>
${!motionIcon
? html``
: html` <ha-icon-button
data-toggle="tooltip"
title="View motion sensor"
class="button"
icon="${motionIcon}"
@click=${() => {
this.expand = false;
this._callAction('motion');
}}
></ha-icon-button>`}
`
: ``}
</div> </div>
`; `;
} }
@@ -259,16 +200,7 @@ export class FrigateCard extends LitElement {
this._webrtcElement.hass = hass; this._webrtcElement.hass = hass;
} }
this._hass = hass; this._hass = hass;
this._updateMenu();
// Manually set hass in the menu. This is to allow the menu to update,
// without necessarily re-rendering the entire card (re-rendering interrupts
// clip playing).
const menu = this.shadowRoot?.getElementById(
FrigateCardMenu.FRIGATE_CARD_MENU_ID,
) as FrigateCardMenu;
if (menu) {
menu.hass = hass;
}
} }
@property({ attribute: false }) @property({ attribute: false })
@@ -297,6 +229,44 @@ export class FrigateCard extends LitElement {
// Whether or not there is an active clip being played. // Whether or not there is an active clip being played.
protected _clipPlaying = false; protected _clipPlaying = false;
@query(FrigateCardMenu.FRIGATE_CARD_MENU_ID)
_menu!: FrigateCardMenu | null;
protected _updateMenu(): void {
// Manually set hass in the menu. This is to allow the menu to update,
// without necessarily re-rendering the entire card (re-rendering interrupts
// clip playing).
if (!this._menu || !this._hass) {
return;
}
this._menu.buttons = this._getMenuButtons();
}
protected _getMenuButtons(): Map<string, MenuButton> {
const buttons: Map<string, MenuButton> = new Map();
buttons.set('frigate', { description: 'Frigate Menu' });
buttons.set('live', { icon: 'mdi:cctv', description: 'View Live' });
buttons.set('clips', { icon: 'mdi:filmstrip', description: 'View Clips' });
buttons.set('snapshots', { icon: 'mdi:camera', description: 'View Snapshots' });
if (this.config.frigate_url) {
buttons.set('frigate_ui', { icon: 'mdi:web', description: 'View Frigate UI' });
}
if (this._hass && this.config.motion_entity) {
const on = this._hass.states[this.config.motion_entity]?.state == 'on';
const motionIcon = on ? 'mdi:motion-sensor' : 'mdi:walk';
buttons.set('motion', {
icon: motionIcon,
description: 'View Motion Sensor',
emphasize: on,
});
}
return buttons;
}
protected _getParseErrorKeys(error: z.ZodError): string[] { protected _getParseErrorKeys(error: z.ZodError): string[] {
const errors = error.format(); const errors = error.format();
return Object.keys(errors).filter((v) => !v.startsWith('_')); return Object.keys(errors).filter((v) => !v.startsWith('_'));
@@ -579,7 +549,7 @@ export class FrigateCard extends LitElement {
protected _menuActionHandler(name: string): void { protected _menuActionHandler(name: string): void {
switch (name) { switch (name) {
case 'default': case 'frigate':
this._controlVideos({ stop: true, control_clip: true }); this._controlVideos({ stop: true, control_clip: true });
this._controlVideos({ stop: true, control_live: true }); this._controlVideos({ stop: true, control_live: true });
this._changeView(); this._changeView();
@@ -597,7 +567,7 @@ export class FrigateCard extends LitElement {
this._controlVideos({ stop: true, control_clip: true, control_live: true }); this._controlVideos({ stop: true, control_clip: true, control_live: true });
this._changeView(name); this._changeView(name);
break; break;
case 'frigate-ui': case 'frigate_ui':
const frigate_url = this._getFrigateURLFromContext(); const frigate_url = this._getFrigateURLFromContext();
if (frigate_url) { if (frigate_url) {
window.open(frigate_url); window.open(frigate_url);
@@ -806,7 +776,7 @@ export class FrigateCard extends LitElement {
if (relatedClip) { if (relatedClip) {
this._changeView('clip', relatedClip); this._changeView('clip', relatedClip);
} }
}) });
}} }}
/>`; />`;
} }
@@ -848,10 +818,9 @@ export class FrigateCard extends LitElement {
return html` return html`
<frigate-card-menu <frigate-card-menu
id="${FrigateCardMenu.FRIGATE_CARD_MENU_ID}" id="${FrigateCardMenu.FRIGATE_CARD_MENU_ID}"
.motionEntity=${this.config.motion_entity}
.hass=${this._hass}
.actionCallback=${this._menuActionHandler.bind(this)} .actionCallback=${this._menuActionHandler.bind(this)}
.menuMode=${this.config.menu_mode} .menuMode=${this.config.menu_mode}
.buttons=${this._getMenuButtons()}
></frigate-card-menu> ></frigate-card-menu>
`; `;
} }
+6
View File
@@ -77,6 +77,12 @@ export interface MediaBeingShown {
resolvedMedia: ResolvedMedia; resolvedMedia: ResolvedMedia;
} }
export interface MenuButton {
icon?: string;
description: string;
emphasize?: boolean;
}
/** /**
* Media Browser API types. * Media Browser API types.
*/ */