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`
+
`;
+ }
+
+ 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`
- `;
+ 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`
-
- `;
}
// 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`
${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
- // safe to be piped through `unsafeCSS`.
+ // Return compiled CSS styles (thus safe to use with unsafeCSS).
static get styles(): CSSResult {
- return unsafeCSS(style);
+ return unsafeCSS(frigate_card_style);
}
// Get the Lovelace card size.