Allow dynamic menu contents.
This commit is contained in:
@@ -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
@@ -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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user