diff --git a/src/frigate-card.ts b/src/frigate-card.ts index 395ecbec..98a131fe 100644 --- a/src/frigate-card.ts +++ b/src/frigate-card.ts @@ -1,9 +1,9 @@ -// TODO Make editor work. -// TODO Add title to clips / snapshots playing/viewing -// TODO Add gallery item to take to media browser -// TODO Add documentation & screenshots. -// TODO Clips do not auto-play on Android. -// TODO Sometimes webrtc component shows up as not found in browser (maybe after fresh build?) +// TODO Feature: Make editor work. +// TODO Feature: Add title to clips / snapshots playing/viewing +// TODO Bug: Clips do not auto-play on Android. +// TODO Bug: Motion icon does not appear to update +// TODO Bug: 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 */ import { @@ -28,7 +28,8 @@ import { 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 type { FrigateCardConfig, FrigateEvent, FrigateGetEventsResponse, GetEventsParameters, ControlVideosParameters } from './types'; @@ -65,6 +66,142 @@ enum FrigateCardView { 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` + { + this.expand = !this.expand; + }} + >`; + } + + // 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` +
+ ${this._renderFrigateButton()} +
`; + } + + 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` +
+ ${this._renderFrigateButton()} + { + this.expand = false; + this._callAction("live"); + }} + > + { + this.expand = false; + this._callAction("clips"); + }} + > + { + this.expand = false; + this._callAction("snapshots"); + }} + > + { + this.expand = false; + this._callAction("frigate-ui"); + }} + > + ${!motionIcon ? html`` : html` + { + this.expand = false; + this._callAction("motion"); + }} + >` + } +
+ `; + } + + // Return compiled CSS styles (thus safe to use with unsafeCSS). + static get styles(): CSSResult { + return unsafeCSS(frigate_card_menu_style); + } +} + + // Main FrigateCard class. @customElement('frigate-card') export class FrigateCard extends LitElement { @@ -385,92 +522,32 @@ export class FrigateCard extends LitElement { } } - // Render the Frigate menu button. - protected _renderFrigateButton(): TemplateResult { - return html` - { - this._showMenu = !this._showMenu; - }} - >`; - } - - - // Render the menu bar. - protected _renderMenuBar(): TemplateResult { - if (!this._showMenu) { - return html` -
- ${this._renderFrigateButton()} -
`; + protected _menuActionHandler(name: string): void { + switch (name) { + case "live": + this._controlVideos({stop: true, control_clip: true}); + this._controlVideos({stop: false, control_live: true}); + this._viewMode = FrigateCardView.LIVE; + break; + case "clips": + this._controlVideos({stop: true, control_live: true}); + this._viewMode = FrigateCardView.CLIPS; + break; + case "snapshots": + this._controlVideos({stop: true, control_clip: true, control_live: true}); + this._viewMode = FrigateCardView.SNAPSHOTS; + break; + case "frigate-ui": + window.open(this.config.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.") } - - 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` -
- ${this._renderFrigateButton()} - { - this._controlVideos({stop: true, control_clip: true}); - this._controlVideos({stop: false, control_live: true}); - this._viewMode = FrigateCardView.LIVE; - this._showMenu = false; - }} - > - { - this._controlVideos({stop: true, control_live: true}); - this._viewMode = FrigateCardView.CLIPS; - this._showMenu = false; - }} - > - { - this._controlVideos({stop: true, control_clip: true, control_live: true}); - this._viewMode = FrigateCardView.SNAPSHOTS; - this._showMenu = false; - }} - > - { - window.open(this.config.frigate_url); - this._showMenu = false; - }} - > - ${!motionIcon ? html`` : html` - { - fireEvent(this, "hass-more-info", {entityId: motionEntity}); - this._showMenu = false; - }} - >` - } -
- `; } // Render the player for a saved clip. @@ -573,7 +650,12 @@ export class FrigateCard extends LitElement { } return html` - ${this._renderMenuBar()} + + ${this._viewMode == FrigateCardView.CLIPS ? html`