Render the menu as its own component part 2.

This commit is contained in:
Dermot Duffy
2021-08-14 08:10:34 -07:00
parent cde325f30d
commit 3692a2bb08
+169 -88
View File
@@ -1,9 +1,9 @@
// TODO Make editor work. // TODO Feature: Make editor work.
// TODO Add title to clips / snapshots playing/viewing // TODO Feature: Add title to clips / snapshots playing/viewing
// TODO Add gallery item to take to media browser // TODO Bug: Clips do not auto-play on Android.
// TODO Add documentation & screenshots. // TODO Bug: Motion icon does not appear to update
// TODO Clips do not auto-play on Android. // TODO Bug: Sometimes webrtc component shows up as not found in browser (maybe after fresh build?)
// TODO Sometimes webrtc component shows up as not found in browser (maybe after fresh build?) // TODO Last step: Add documentation & screenshots.
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { import {
@@ -28,7 +28,8 @@ import {
import './editor'; import './editor';
import style from './frigate-card.scss' import frigate_card_style from './frigate-card.scss'
import frigate_card_menu_style from './frigate-card-menu.scss'
import { frigateCardConfigSchema, frigateGetEventsResponseSchema } from './types'; import { frigateCardConfigSchema, frigateGetEventsResponseSchema } from './types';
import type { FrigateCardConfig, FrigateEvent, FrigateGetEventsResponse, GetEventsParameters, ControlVideosParameters } from './types'; import type { FrigateCardConfig, FrigateEvent, FrigateGetEventsResponse, GetEventsParameters, ControlVideosParameters } from './types';
@@ -65,6 +66,142 @@ enum FrigateCardView {
SNAPSHOTS, // Show the snapshots gallery. SNAPSHOTS, // Show the snapshots gallery.
} }
type FrigateCardMenuCallback = (name: string) => any;
// A menu for the Frigate card.
@customElement('frigate-card-menu')
export class FrigateCardMenu extends LitElement {
constructor() {
super();
this.expand = false;
this.motionEntity = null;
this.hass = null;
this.actionCallback = null;
}
@property({ attribute: false })
protected expand: boolean;
@property({ attribute: false })
protected motionEntity: string | null;
@property({ attribute: false })
protected hass: HomeAssistant | null;
@property({ attribute: false })
protected actionCallback: FrigateCardMenuCallback | null;
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.hass) {
return false;
}
const oldHass = changedProps.get('hass') as HomeAssistant | undefined;
if (oldHass && this.motionEntity) {
if (oldHass.states[this.motionEntity] !== this.hass.states[this.motionEntity]) {
return true;
}
return false;
}
return true;
}
// Render the Frigate menu button.
protected _renderFrigateButton(): TemplateResult {
return html`
<ha-icon-button
class="button"
icon="mdi:alpha-f-box"
data-toggle="tooltip" title="Frigate menu"
@click=${() => {
this.expand = !this.expand;
}}
></ha-icon-button>`;
}
// Call the callback.
protected _callAction(name: string): void {
if (this.actionCallback) {
this.actionCallback(name);
}
}
// Render the menu.
protected render(): TemplateResult | void {
if (!this.expand) {
return html`
<div class="frigate-card-menu">
${this._renderFrigateButton()}
</div>`;
}
let motionIcon: string | null = null;
if (this.motionEntity && this.hass) {
motionIcon = this.hass.states[this.motionEntity]?.state == "on" ? "mdi:motion-sensor" : "mdi:walk";
}
return html`
<div class="frigate-card-menu-expanded">
${this._renderFrigateButton()}
<ha-icon-button
class="button"
icon="mdi:cctv"
data-toggle="tooltip" title="View live"
@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>
`;
}
// Return compiled CSS styles (thus safe to use with unsafeCSS).
static get styles(): CSSResult {
return unsafeCSS(frigate_card_menu_style);
}
}
// Main FrigateCard class. // Main FrigateCard class.
@customElement('frigate-card') @customElement('frigate-card')
export class FrigateCard extends LitElement { export class FrigateCard extends LitElement {
@@ -385,92 +522,32 @@ export class FrigateCard extends LitElement {
} }
} }
// Render the Frigate menu button. protected _menuActionHandler(name: string): void {
protected _renderFrigateButton(): TemplateResult { switch (name) {
return html` case "live":
<ha-icon-button
class="button"
icon="mdi:alpha-f-box"
data-toggle="tooltip" title="Frigate menu"
@click=${() => {
this._showMenu = !this._showMenu;
}}
></ha-icon-button>`;
}
// Render the menu bar.
protected _renderMenuBar(): TemplateResult {
if (!this._showMenu) {
return html`
<div class="frigate-card-menubar">
${this._renderFrigateButton()}
</div>`;
}
let motionIcon: string | null = null;
const motionEntity = this.config.motion_entity ? this.config.motion_entity : null;
if (motionEntity && this._hass && motionEntity in this._hass.states) {
motionIcon = (this._hass.states[motionEntity].state == "on" ?
"mdi:motion-sensor" : "mdi:walk");
}
return html`
<div class="frigate-card-menubar-full">
${this._renderFrigateButton()}
<ha-icon-button
class="button"
icon="mdi:cctv"
data-toggle="tooltip" title="View live"
@click=${() => {
this._controlVideos({stop: true, control_clip: true}); this._controlVideos({stop: true, control_clip: true});
this._controlVideos({stop: false, control_live: true}); this._controlVideos({stop: false, control_live: true});
this._viewMode = FrigateCardView.LIVE; this._viewMode = FrigateCardView.LIVE;
this._showMenu = false; break;
}} case "clips":
></ha-icon-button>
<ha-icon-button
class="button"
icon = "mdi:filmstrip"
data-toggle="tooltip" title="View clips"
@click=${() => {
this._controlVideos({stop: true, control_live: true}); this._controlVideos({stop: true, control_live: true});
this._viewMode = FrigateCardView.CLIPS; this._viewMode = FrigateCardView.CLIPS;
this._showMenu = false; break;
}} case "snapshots":
></ha-icon-button>
<ha-icon-button
class="button"
icon = "mdi:camera"
data-toggle="tooltip" title="View snapshots"
@click=${() => {
this._controlVideos({stop: true, control_clip: true, control_live: true}); this._controlVideos({stop: true, control_clip: true, control_live: true});
this._viewMode = FrigateCardView.SNAPSHOTS; this._viewMode = FrigateCardView.SNAPSHOTS;
this._showMenu = false; break;
}} case "frigate-ui":
></ha-icon-button>
<ha-icon-button
class="button"
icon = "mdi:web"
data-toggle="tooltip" title="View Frigate UI"
@click=${() => {
window.open(this.config.frigate_url); window.open(this.config.frigate_url);
this._showMenu = false; break;
}} case "motion":
></ha-icon-button> if (this.config.motion_entity) {
${!motionIcon ? html`` : html` fireEvent(this, "hass-more-info", {entityId: this.config.motion_entity});
<ha-icon-button }
data-toggle="tooltip" title="View motion sensor" break;
class="button" default:
icon="${motionIcon}" console.warn("Unknown Frigate card menu option.")
@click=${() => {
fireEvent(this, "hass-more-info", {entityId: motionEntity});
this._showMenu = false;
}}
></ha-icon-button>`
} }
</div>
`;
} }
// Render the player for a saved clip. // Render the player for a saved clip.
@@ -573,7 +650,12 @@ export class FrigateCard extends LitElement {
} }
return html` return html`
<ha-card @click=${this._interactionHandler}> <ha-card @click=${this._interactionHandler}>
${this._renderMenuBar()} <frigate-card-menu
.motionEntity=${this.config.motion_entity},
.hass=${this._hass}
.actionCallback=${this._menuActionHandler.bind(this)}
>
</frigate-card-menu>
${this._viewMode == FrigateCardView.CLIPS ? ${this._viewMode == FrigateCardView.CLIPS ?
html`<div class="frigate-card-gallery"> html`<div class="frigate-card-gallery">
${until(this._renderEvents(), this._renderProgressIndicator())} ${until(this._renderEvents(), this._renderProgressIndicator())}
@@ -619,10 +701,9 @@ export class FrigateCard extends LitElement {
`; `;
} }
// Get the CSS styles. CSS is compiled from frigate-card.scss, so this is // Return compiled CSS styles (thus safe to use with unsafeCSS).
// safe to be piped through `unsafeCSS`.
static get styles(): CSSResult { static get styles(): CSSResult {
return unsafeCSS(style); return unsafeCSS(frigate_card_style);
} }
// Get the Lovelace card size. // Get the Lovelace card size.