Add support to add arbitrary entities to menu.

Breaking: No longer treat the motion entity special. It must be configured
under `entities` now.
This commit is contained in:
Dermot Duffy
2021-09-04 11:23:40 -07:00
parent 366c210896
commit cf9420402b
5 changed files with 104 additions and 80 deletions
+1 -27
View File
@@ -171,22 +171,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
</div>
${options.optional.show
? html` <div class="values">
<paper-dropdown-menu
label=${localize('editor.motion_entity')}
@value-changed=${this._valueChanged}
.configValue=${'motion_entity'}
>
<paper-listbox
slot="dropdown-content"
.selected=${binarySensorEntities.indexOf(
this._config?.motion_entity || '',
)}
>
${binarySensorEntities.map((entity) => {
return html` <paper-item>${entity}</paper-item> `;
})}
</paper-listbox>
</paper-dropdown-menu>
<paper-input
label=${localize('editor.frigate_camera_name')}
.value=${this._config?.frigate_camera_name || ''}
@@ -323,17 +307,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
<br />
<ha-formfield
.label=${localize('editor.show_button') + ': ' + localize('menu.motion')}
>
<ha-switch
.checked=${this._config?.menu_buttons?.frigate_ui ?? true}
.configValue=${'menu_buttons.motion'}
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
<br /> `
<br />`
: ''}
<div class="option" @click=${this._toggleOption} .option=${'advanced'}>
<div class="row">
+47 -30
View File
@@ -18,9 +18,10 @@ import { classMap } from 'lit-html/directives/class-map.js';
import {
HomeAssistant,
fireEvent,
LovelaceCardEditor,
fireEvent,
getLovelace,
stateIcon,
} from 'custom-card-helpers';
import './editor';
@@ -78,9 +79,9 @@ type FrigateCardMenuCallback = (name: string) => any;
function shouldUpdateBasedOnHass(
newHass: HomeAssistant | null,
oldHass: HomeAssistant | undefined,
entities: (string | null | undefined)[],
entities: string[] | null,
): boolean {
if (!newHass) {
if (!newHass || !entities) {
return false;
}
if (!entities.length) {
@@ -284,9 +285,13 @@ export class FrigateCard extends LitElement {
// Whether or not there is an active clip being played.
protected _clipPlaying = false;
@query(FrigateCardMenu.FRIGATE_CARD_MENU_ID)
@query(`#${FrigateCardMenu.FRIGATE_CARD_MENU_ID}`)
_menu!: FrigateCardMenu | null;
// A small cache to avoid needing to create a new list of entities every time
// a hass update arrives.
protected _entitiesToMonitor: string[] | null = 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
@@ -301,29 +306,44 @@ export class FrigateCard extends LitElement {
protected _getMenuButtons(): Map<string, MenuButton> {
const buttons: Map<string, MenuButton> = new Map();
if (this.config.menu_buttons?.frigate) {
if (this.config.menu_buttons?.frigate ?? true) {
buttons.set('frigate', { description: 'Frigate Menu' });
}
if (this.config.menu_buttons?.live) {
buttons.set('live', { icon: 'mdi:cctv', description: 'View Live' });
if (this.config.menu_buttons?.live ?? true) {
buttons.set('live', {
icon: 'mdi:cctv',
description: 'View Live',
emphasize: this._view.is('live'),
});
}
if (this.config.menu_buttons?.clips) {
buttons.set('clips', { icon: 'mdi:filmstrip', description: 'View Clips' });
if (this.config.menu_buttons?.clips ?? true) {
buttons.set('clips', {
icon: 'mdi:filmstrip',
description: 'View Clips',
emphasize: this._view.is('clips'),
});
}
if (this.config.menu_buttons?.snapshots) {
buttons.set('snapshots', { icon: 'mdi:camera', description: 'View Snapshots' });
if (this.config.menu_buttons?.snapshots ?? true) {
buttons.set('snapshots', {
icon: 'mdi:camera',
description: 'View Snapshots',
emphasize: this._view.is('snapshots'),
});
}
if (this.config.menu_buttons?.frigate_ui && this.config.frigate_url) {
if ((this.config.menu_buttons?.frigate_ui ?? true) && this.config.frigate_url) {
buttons.set('frigate_ui', { icon: 'mdi:web', description: 'View Frigate UI' });
}
if (this.config.menu_buttons?.motion && 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,
const entities = this.config.entities || [];
for (let i = 0; this._hass && i < entities.length; i++) {
if (!entities[i].show) {
continue;
}
const entity = entities[i].entity;
const state = this._hass.states[entity];
buttons.set(entity, {
description: state.attributes.friendly_name || entity,
emphasize: ['on', 'active', 'home'].includes(state.state),
icon: entities[i].icon || stateIcon(state),
});
}
return buttons;
@@ -374,6 +394,10 @@ export class FrigateCard extends LitElement {
}
this.config = config;
this._entitiesToMonitor = [
...(this.config.entities || []).map((entity) => entity.entity),
this.config.camera_entity,
];
this._changeView();
}
@@ -407,10 +431,7 @@ export class FrigateCard extends LitElement {
if (this._interactionTimerID || this._clipPlaying) {
return false;
}
return shouldUpdateBasedOnHass(this._hass, oldHass, [
this.config.camera_entity,
this.config.motion_entity,
]);
return shouldUpdateBasedOnHass(this._hass, oldHass, this._entitiesToMonitor);
}
return true;
}
@@ -623,13 +644,9 @@ export class FrigateCard extends LitElement {
window.open(frigate_url);
}
break;
case 'motion':
if (this.config.motion_entity) {
fireEvent(this, 'hass-more-info', { entityId: this.config.motion_entity });
}
break;
default:
console.warn('Unknown Frigate card menu option.');
// If it's unknown, it's assumed to be an entity_id.
fireEvent(this, 'hass-more-info', { entityId: name });
}
}
+1 -3
View File
@@ -18,7 +18,6 @@
"advanced": "Advanced",
"advanced_secondary": "Advanced options",
"camera_entity": "Camera Entity (Required)",
"motion_entity": "Motion Entity (Optional)",
"frigate_camera_name": "Frigate camera name (Optional)",
"default_view": "Default view (Optional)",
"frigate_client_id": "Frigate client id (Optional, for >1 Frigate server)",
@@ -38,8 +37,7 @@
"snapshots": "Snapshots Gallery",
"clip": "Latest Clip",
"snapshot": "Latest Snapshot",
"frigate_ui": "Frigate User Interface",
"motion": "Motion Entity"
"frigate_ui": "Frigate User Interface"
},
"menu_mode": {
"hidden-top": "Hidden Top",
+5 -9
View File
@@ -41,7 +41,6 @@ export type FrigateMenuMode = typeof FRIGATE_MENU_MODES[number];
export const frigateCardConfigSchema = z.object({
camera_entity: z.string(),
motion_entity: z.string().optional(),
// No URL validation to allow relative URLs within HA (e.g. addons).
frigate_url: z.string().optional(),
frigate_client_id: z.string().optional().default("frigate"),
@@ -68,15 +67,12 @@ export const frigateCardConfigSchema = z.object({
clips: z.boolean().default(true),
snapshots: z.boolean().default(true),
frigate_ui: z.boolean().default(true),
motion: z.boolean().default(true),
}).default({
frigate: true,
live: true,
clips: true,
snapshots: true,
frigate_ui: true,
motion: true,
}),
entities: z.object({
entity: z.string(),
show: z.boolean().default(true),
icon: z.string().optional(),
}).array().optional(),
// Stock lovelace card config.
type: z.string(),