diff --git a/.prettierrc.js b/.prettierrc.js index 93f1e1c1..933e929f 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -2,6 +2,7 @@ module.exports = { semi: true, trailingComma: 'all', singleQuote: true, - printWidth: 120, + printWidth: 89, tabWidth: 2, -}; \ No newline at end of file + embeddedLanguageFormatting: 'auto', +}; diff --git a/rollup.config.js b/rollup.config.js index 5412ea58..d40720cb 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -5,7 +5,7 @@ import babel from 'rollup-plugin-babel'; import { terser } from 'rollup-plugin-terser'; import serve from 'rollup-plugin-serve'; import json from '@rollup/plugin-json'; -import styles from "rollup-plugin-styles"; +import styles from 'rollup-plugin-styles'; const dev = process.env.ROLLUP_WATCH; @@ -24,10 +24,10 @@ const plugins = [ modules: false, // Behavior of inject mode, without actually injecting style // into . - mode: ["inject", () => undefined], + mode: ['inject', () => undefined], sass: { - includePaths: ["./node_modules/"] - } + includePaths: ['./node_modules/'], + }, }), nodeResolve({}), commonjs(), diff --git a/src/action-handler-directive.ts b/src/action-handler-directive.ts index addf7b93..38968d55 100644 --- a/src/action-handler-directive.ts +++ b/src/action-handler-directive.ts @@ -1,9 +1,15 @@ import { directive, PropertyPart } from 'lit-html'; -import { ActionHandlerDetail, ActionHandlerOptions } from 'custom-card-helpers/dist/types'; +import { + ActionHandlerDetail, + ActionHandlerOptions, +} from 'custom-card-helpers/dist/types'; import { fireEvent } from 'custom-card-helpers'; -const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; +const isTouch = + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0; interface ActionHandler extends HTMLElement { holdTime: number; @@ -49,7 +55,15 @@ class ActionHandler extends HTMLElement implements ActionHandler { this.appendChild(this.ripple); this.ripple.primary = true; - ['touchcancel', 'mouseout', 'mouseup', 'touchmove', 'mousewheel', 'wheel', 'scroll'].forEach(ev => { + [ + 'touchcancel', + 'mouseout', + 'mouseup', + 'touchmove', + 'mousewheel', + 'wheel', + 'scroll', + ].forEach((ev) => { document.addEventListener( ev, () => { @@ -111,7 +125,10 @@ class ActionHandler extends HTMLElement implements ActionHandler { if (this.held) { fireEvent(element, 'action', { action: 'hold' }); } else if (options.hasDoubleClick) { - if ((ev.type === 'click' && (ev as MouseEvent).detail < 2) || !this.dblClickTimeout) { + if ( + (ev.type === 'click' && (ev as MouseEvent).detail < 2) || + !this.dblClickTimeout + ) { this.dblClickTimeout = window.setTimeout(() => { this.dblClickTimeout = undefined; fireEvent(element, 'action', { action: 'tap' }); @@ -175,7 +192,10 @@ const getActionHandler = (): ActionHandler => { return actionhandler as ActionHandler; }; -export const actionHandlerBind = (element: ActionHandlerElement, options: ActionHandlerOptions): void => { +export const actionHandlerBind = ( + element: ActionHandlerElement, + options: ActionHandlerOptions, +): void => { const actionhandler: ActionHandler = getActionHandler(); if (!actionhandler) { return; @@ -183,6 +203,9 @@ export const actionHandlerBind = (element: ActionHandlerElement, options: Action actionhandler.bind(element, options); }; -export const actionHandler = directive((options: ActionHandlerOptions = {}) => (part: PropertyPart): void => { - actionHandlerBind(part.committer.element as ActionHandlerElement, options); -}); +export const actionHandler = directive( + (options: ActionHandlerOptions = {}) => + (part: PropertyPart): void => { + actionHandlerBind(part.committer.element as ActionHandlerElement, options); + }, +); diff --git a/src/const.ts b/src/const.ts index 59fb347d..8d745ee2 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1 +1 @@ -export const CARD_VERSION = '0.0.1'; \ No newline at end of file +export const CARD_VERSION = '0.0.1'; diff --git a/src/declarations.d.ts b/src/declarations.d.ts index caaba6a2..d5cf927a 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -1 +1 @@ -declare module '*.scss'; \ No newline at end of file +declare module '*.scss'; diff --git a/src/editor.ts b/src/editor.ts index fb91a5e3..585259f9 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -13,7 +13,7 @@ import { HomeAssistant, fireEvent, LovelaceCardEditor } from 'custom-card-helper import { FrigateCardConfig } from './types'; -import frigate_card_editor_style from './frigate-card-editor.scss' +import frigate_card_editor_style from './frigate-card-editor.scss'; const options = { required: { @@ -63,12 +63,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor return true; } - protected _getEntities(domain: string) : string[] { + protected _getEntities(domain: string): string[] { if (!this.hass) { return []; } - const entities = Object.keys( - this.hass.states).filter(eid => eid.substr(0, eid.indexOf('.')) === domain); + const entities = Object.keys(this.hass.states).filter( + (eid) => eid.substr(0, eid.indexOf('.')) === domain, + ); entities.sort(); // Add a blank entry to unset a selection. @@ -87,26 +88,26 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor // You can restrict on domain type const cameraEntities = this._getEntities('camera'); const binarySensorEntities = this._getEntities('binary_sensor'); - - const webrtcCameraEntity = - this._config?.webrtc && (this._config?.webrtc as any).entity ? - (this._config?.webrtc as any).entity : - ''; + + const webrtcCameraEntity = + this._config?.webrtc && (this._config?.webrtc as any).entity + ? (this._config?.webrtc as any).entity + : ''; const viewModes = { - "": "", - "live": "Live view", - "clips": "Clip gallery", - "snapshots": "Snapshot gallery", - "clip": "Latest clip", - "snapshot": "Latest snapshot", - } + '': '', + live: 'Live view', + clips: 'Clip gallery', + snapshots: 'Snapshot gallery', + clip: 'Latest clip', + snapshot: 'Latest snapshot', + }; const liveProvider = { - "": "", - "frigate": "Frigate", - "webrtc": "WebRTC", - } + '': '', + frigate: 'Frigate', + webrtc: 'WebRTC', + }; return html`
@@ -125,11 +126,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor @value-changed=${this._valueChanged} .configValue=${'camera_entity'} > - - ${cameraEntities.map(entity => { - return html` - ${entity} - `; + + ${cameraEntities.map((entity) => { + return html` ${entity} `; })} @@ -150,18 +154,20 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${options.optional.secondary}
${options.optional.show - ? html` -
+ ? html`
- - ${binarySensorEntities.map(entity => { - return html` - ${entity} - `; + + ${binarySensorEntities.map((entity) => { + return html` ${entity} `; })} @@ -172,16 +178,19 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor @value-changed=${this._valueChanged} > - - ${Object.keys(viewModes).map(key => { + + ${Object.keys(viewModes).map((key) => { return html` - - ${viewModes[key]} - + ${viewModes[key]} `; })} @@ -190,13 +199,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor label="View timeout (seconds)" prevent-invalid-input allowed-pattern="[0-9]" - .value=${this._config?.view_timeout ? String(this._config.view_timeout) : ''} + .value=${this._config?.view_timeout + ? String(this._config.view_timeout) + : ''} .configValue=${'view_timeout'} @value-changed=${this._valueChanged} >
` - : '' - } + : ''}
@@ -205,25 +215,23 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${options.advanced.secondary}
${options.advanced.show - ? html` -
- -
-
- -
` - : '' - } + ? html`
+ +
+
+ +
` + : ''}
@@ -232,39 +240,41 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${options.webrtc.secondary}
${options.webrtc.show - ? html` -
+ ? html`
- - ${Object.keys(liveProvider).map(key => { + + ${Object.keys(liveProvider).map((key) => { return html` - ${liveProvider[key]} - + ${liveProvider[key]} `; })} - - ${cameraEntities.map(entity => { - return html` - ${entity} - `; + + ${cameraEntities.map((entity) => { + return html` ${entity} `; })}
` - : '' - } + : ''}
`; } @@ -307,14 +317,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor } // Need to deep copy the config so cannot use Object.assign. - const newConfig = JSON.parse(JSON.stringify(this._config)); + const newConfig = JSON.parse(JSON.stringify(this._config)); let objectTarget = newConfig; if (key.includes('.')) { const parts = key.split('.', 2); const configName = parts[0]; if (!(configName in newConfig)) { - newConfig[configName] = {} + newConfig[configName] = {}; } objectTarget = newConfig[configName]; key = parts[1]; @@ -327,7 +337,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor } else if (target.value === '') { delete objectTarget[key]; } else { - objectTarget[key] = (target.checked !== undefined ? target.checked : target.value); + objectTarget[key] = target.checked !== undefined ? target.checked : target.value; } this._config = newConfig; fireEvent(this, 'config-changed', { config: this._config }); diff --git a/src/frigate-card-editor.scss b/src/frigate-card-editor.scss index f32ca7c1..c1d167e6 100644 --- a/src/frigate-card-editor.scss +++ b/src/frigate-card-editor.scss @@ -24,4 +24,4 @@ } ha-formfield { padding-bottom: 8px; -} \ No newline at end of file +} diff --git a/src/frigate-card-menu.scss b/src/frigate-card-menu.scss index dfddae97..c5bc5d2a 100644 --- a/src/frigate-card-menu.scss +++ b/src/frigate-card-menu.scss @@ -25,7 +25,7 @@ height: 1em; padding-bottom: 0.5em; padding-left: 0.5em; - color: rgba(255,255,255,0.7); + color: rgba(255, 255, 255, 0.7); background-color: rgba(0, 0, 0, 0.5); } @@ -38,4 +38,4 @@ ha-icon-button.button { border-radius: 50%; padding: 0px; margin: 3px; -} \ No newline at end of file +} diff --git a/src/frigate-card.scss b/src/frigate-card.scss index 071527a5..cd3fba56 100644 --- a/src/frigate-card.scss +++ b/src/frigate-card.scss @@ -16,8 +16,8 @@ top: 0px; height: 100%; - -ms-overflow-style: none; // Hide scrollbar: IE and Edge - scrollbar-width: none; // Hide scrollbar: Firefox + -ms-overflow-style: none; // Hide scrollbar: IE and Edge + scrollbar-width: none; // Hide scrollbar: Firefox } .frigate-card-attention { @@ -29,7 +29,7 @@ /* Hide scrollbar for Chrome, Safari and Opera */ .frigate-card-gallery::-webkit-scrollbar { - display: none; + display: none; } .frigate-card-image-list { @@ -70,4 +70,4 @@ ha-card { width: 100%; height: 100%; position: relative; -} \ No newline at end of file +} diff --git a/src/frigate-card.ts b/src/frigate-card.ts index 0ff8e248..42d3eb1e 100644 --- a/src/frigate-card.ts +++ b/src/frigate-card.ts @@ -11,7 +11,7 @@ import { unsafeCSS, } from 'lit-element'; -import { NodePart } from "lit-html"; +import { NodePart } from 'lit-html'; import { until } from 'lit-html/directives/until.js'; import { @@ -23,11 +23,18 @@ import { import './editor'; -import frigate_card_style from './frigate-card.scss' -import frigate_card_menu_style from './frigate-card-menu.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 { FrigateCardView, FrigateCardConfig, FrigateEvent, FrigateGetEventsResponse, GetEventsParameters, ControlVideosParameters } from './types'; +import type { + FrigateCardView, + FrigateCardConfig, + FrigateEvent, + FrigateGetEventsResponse, + GetEventsParameters, + ControlVideosParameters, +} from './types'; import { CARD_VERSION } from './const'; import { localize } from './localize/localize'; import dayjs from 'dayjs'; @@ -67,9 +74,9 @@ function shouldUpdateBasedOnHass( if (!entities.length) { return false; } - + if (oldHass) { - for(let i = 0; i < entities.length; i++) { + for (let i = 0; i < entities.length; i++) { const entity = entities[i]; if (!entity) { continue; @@ -105,20 +112,21 @@ export class FrigateCardMenu extends LitElement { return shouldUpdateBasedOnHass( this.hass, changedProps.get('hass') as HomeAssistant | undefined, - [this.motionEntity]); + [this.motionEntity], + ); } // Render the Frigate menu button. protected _renderFrigateButton(): TemplateResult { - return html` - { - this.expand = !this.expand; - }} - >`; + return html` { + this.expand = !this.expand; + }} + >`; } // Call the callback. @@ -131,76 +139,77 @@ export class FrigateCardMenu extends LitElement { // Render the menu. protected render(): TemplateResult | void | ((part: NodePart) => Promise) { if (!this.expand) { - return html` -
- ${this._renderFrigateButton()} -
`; + 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"; + motionIcon = + this.hass.states[this.motionEntity]?.state == 'on' + ? 'mdi:motion-sensor' + : 'mdi:walk'; } return html`
-
+
${this._renderFrigateButton()} { this.expand = false; - this._callAction("live"); + this._callAction('live'); }} > { this.expand = false; - this._callAction("clips"); + this._callAction('clips'); }} > { this.expand = false; - this._callAction("snapshots"); + this._callAction('snapshots'); }} > { this.expand = false; - this._callAction("frigate-ui"); + this._callAction('frigate-ui'); }} > - ${!motionIcon ? html`` : html` - { - this.expand = false; - this._callAction("motion"); - }} - >` - } + ${!motionIcon + ? html`` + : html` { + this.expand = false; + this._callAction('motion'); + }} + >`}
- ${this.heading ? html` -
- ${this.heading} -
- ` : ``} + ${this.heading + ? html`
${this.heading}
` + : ``}
`; } @@ -211,11 +220,9 @@ export class FrigateCardMenu extends LitElement { } } - // Main FrigateCard class. @customElement('frigate-card') export class FrigateCard extends LitElement { - // Get the configuration element. public static async getConfigElement(): Promise { return document.createElement('frigate-card-editor'); @@ -239,7 +246,7 @@ export class FrigateCard extends LitElement { public config!: FrigateCardConfig; @property({ attribute: false }) - protected _viewMode: FrigateCardView = "live"; + protected _viewMode: FrigateCardView = 'live'; @property({ attribute: false }) protected _viewEvent: FrigateEvent | null = null; @@ -248,7 +255,7 @@ export class FrigateCard extends LitElement { protected _showMenu = false; @state() - protected _heading: string | null = null; + protected _heading: string | null = null; protected _interactionTimerID: number | null = null; protected _webrtcElement: any | null = null; @@ -261,9 +268,9 @@ export class FrigateCard extends LitElement { const parseResult = frigateCardConfigSchema.safeParse(inputConfig); if (!parseResult.success) { - const errors = parseResult.error.format() - const keys = Object.keys(errors).filter(v => !v.startsWith("_")); - throw new Error(localize('common.invalid_configuration') + ": " + keys.join(", ")); + const errors = parseResult.error.format(); + const keys = Object.keys(errors).filter((v) => !v.startsWith('_')); + throw new Error(localize('common.invalid_configuration') + ': ' + keys.join(', ')); } const config = parseResult.data; @@ -273,14 +280,14 @@ export class FrigateCard extends LitElement { if (!config.frigate_camera_name) { // No camera name specified, so just assume it's the same as the entity name. - if (config.camera_entity.includes(".")) { - config.frigate_camera_name = config.camera_entity.split('.', 2)[1] + if (config.camera_entity.includes('.')) { + config.frigate_camera_name = config.camera_entity.split('.', 2)[1]; } else { - throw new Error(localize('common.invalid_configuration') + ": camera_entity"); + throw new Error(localize('common.invalid_configuration') + ': camera_entity'); } } - if (config.live_provider == "webrtc") { + if (config.live_provider == 'webrtc') { // Create a WebRTC element (https://github.com/AlexxIT/WebRTC) const webrtcElement = customElements.get('webrtc-camera'); if (webrtcElement) { @@ -300,8 +307,8 @@ export class FrigateCard extends LitElement { // Set the view mode to the configured default. protected _setViewModeToDefault(): void { this._viewMode = this.config.view_default; - if (["clip", "snapshot"].includes(this._viewMode)) { - this._viewEvent = null; + if (['clip', 'snapshot'].includes(this._viewMode)) { + this._viewEvent = null; } } @@ -317,7 +324,8 @@ export class FrigateCard extends LitElement { return shouldUpdateBasedOnHass( this._hass, changedProps.get('_hass') as HomeAssistant | undefined, - [this.config.camera_entity, this.config.motion_entity]); + [this.config.camera_entity, this.config.motion_entity], + ); } // Get FrigateEvents from the Frigate server API. @@ -328,13 +336,13 @@ export class FrigateCard extends LitElement { }: GetEventsParameters): Promise { let url = `${this.config.frigate_url}/api/events?camera=${this.config.frigate_camera_name}`; if (has_clip) { - url += `&has_clip=1` + url += `&has_clip=1`; } if (has_snapshot) { - url += `&has_snapshot=1` + url += `&has_snapshot=1`; } if (limit > 0) { - url += `&limit=${limit}` + url += `&limit=${limit}`; } if (this.config.label) { @@ -349,13 +357,13 @@ export class FrigateCard extends LitElement { let raw_json; try { raw_json = await response.json(); - } catch(e) { + } catch (e) { console.warn(e); throw new Error(`Could not JSON decode Frigate API response: ${e}`); } try { return frigateGetEventsResponseSchema.parse(raw_json); - } catch(e) { + } catch (e) { console.warn(e); throw new Error(`Frigate events were malformed: ${e}`); } @@ -367,28 +375,27 @@ export class FrigateCard extends LitElement { } protected _renderAttentionIcon(icon: string): TemplateResult { - return html` -
- - -
`; + return html`
+ +
`; } // Render an embedded error situation. // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected _renderError(_error: string) : TemplateResult { - return this._renderAttentionIcon("mdi:alert-circle"); + protected _renderError(_error: string): TemplateResult { + return this._renderAttentionIcon('mdi:alert-circle'); } // Generate a human-readable title from an event. // MediaBrowser title: 2021-08-12 19:20:14 [10s, Person 76%] - protected _getEventTitle(event: FrigateEvent) : string { - const date = dayjs.unix(event.end_time).tz("UTC").local(); + protected _getEventTitle(event: FrigateEvent): string { + const date = dayjs.unix(event.end_time).tz('UTC').local(); - const iso_datetime = date.format("YYYY-MM-DD HH:mm:ss"); - const duration = Math.trunc(event.end_time > event.start_time ? - event.end_time - event.start_time : 0); - const score = Math.trunc(event.top_score*100); + const iso_datetime = date.format('YYYY-MM-DD HH:mm:ss'); + const duration = Math.trunc( + event.end_time > event.start_time ? event.end_time - event.start_time : 0, + ); + const score = Math.trunc(event.top_score * 100); // Capitalize the label. const label = event.label.charAt(0).toUpperCase() + event.label.slice(1); @@ -397,8 +404,8 @@ export class FrigateCard extends LitElement { } // Render Frigate events into a card gallery. - protected async _renderEvents() : Promise { - const want_clips = (this._viewMode == "clips"); + protected async _renderEvents(): Promise { + const want_clips = this._viewMode == 'clips'; let events; try { events = await this._getEvents({ @@ -410,45 +417,45 @@ export class FrigateCard extends LitElement { } if (!events.length) { - return this._renderAttentionIcon(want_clips ? "mdi:filmstrip-off" : "mdi:camera-off"); + return this._renderAttentionIcon( + want_clips ? 'mdi:filmstrip-off' : 'mdi:camera-off', + ); } - return html` -
    - ${events.map(event => html` -
  • -
    - { - this._showMenu = false; - this._viewEvent = event; - this._viewMode = want_clips ? "clip" : "snapshot"; - }} - > -
    -
  • `)} -
`; + return html`
    + ${events.map( + (event) => html`
  • +
    + { + this._showMenu = false; + this._viewEvent = event; + this._viewMode = want_clips ? 'clip' : 'snapshot'; + }} + /> +
    +
  • `, + )} +
`; } // Render a progress spinner while content loads. protected _renderProgressIndicator(): TemplateResult { - return html` -
- -
` + return html`
+ +
`; } // Stop/Play video controls. protected _controlVideos({ - stop, - control_live = false, - control_clip = false}: ControlVideosParameters): void { + stop, + control_live = false, + control_clip = false, + }: ControlVideosParameters): void { const controlVideo = (stop: boolean, is_live: boolean, video: HTMLVideoElement) => { if (video) { if (stop) { @@ -464,7 +471,7 @@ export class FrigateCard extends LitElement { video.play(); } } - } + }; if (!this.shadowRoot) { return; } @@ -472,8 +479,8 @@ export class FrigateCard extends LitElement { controlVideo( stop, false, - this.shadowRoot?. - querySelector('video.frigate-card-viewer') as HTMLVideoElement); + this.shadowRoot?.querySelector('video.frigate-card-viewer') as HTMLVideoElement, + ); } if (control_live) { // Don't have direct access to the live video player as it is buried in @@ -482,50 +489,49 @@ export class FrigateCard extends LitElement { controlVideo( stop, true, - this.shadowRoot?. - querySelector('webrtc-camera video') as HTMLVideoElement - ) + this.shadowRoot?.querySelector('webrtc-camera video') as HTMLVideoElement, + ); } else { controlVideo( stop, true, - this.shadowRoot?. - querySelector('ha-camera-stream')?.shadowRoot?. - querySelector('ha-hls-player')?.shadowRoot?. - querySelector('video') as HTMLVideoElement - ) + this.shadowRoot + ?.querySelector('ha-camera-stream') + ?.shadowRoot?.querySelector('ha-hls-player') + ?.shadowRoot?.querySelector('video') as HTMLVideoElement, + ); } } } protected _menuActionHandler(name: string): void { switch (name) { - case "live": - this._controlVideos({stop: true, control_clip: true}); - this._controlVideos({stop: false, control_live: true}); + case 'live': + this._controlVideos({ stop: true, control_clip: true }); + this._controlVideos({ stop: false, control_live: true }); this._viewMode = name; this._heading = null; break; - case "clips": - this._controlVideos({stop: true, control_live: true}); + case 'clips': + this._controlVideos({ stop: true, control_live: true }); this._viewMode = name; this._heading = null; break; - case "snapshots": - this._controlVideos({stop: true, control_clip: true, control_live: true}); + case 'snapshots': + this._controlVideos({ stop: true, control_clip: true, control_live: true }); this._viewMode = name; this._heading = null; break; - case "frigate-ui": + case 'frigate-ui': window.open(this.config.frigate_url); break; - case "motion": + case 'motion': if (this.config.motion_entity) { - fireEvent(this, "hass-more-info", {entityId: this.config.motion_entity}); + fireEvent(this, 'hass-more-info', { entityId: this.config.motion_entity }); } break; default: - console.warn("Unknown Frigate card menu option.") + console.warn('Unknown Frigate card menu option.'); } } @@ -538,23 +544,21 @@ export class FrigateCard extends LitElement { try { events = await this._getEvents({ has_clip: true, - limit: 1 + limit: 1, }); } catch (e) { return this._renderError(e); } if (!events.length) { - return this._renderAttentionIcon("mdi:camera-off"); + return this._renderAttentionIcon('mdi:camera-off'); } event = events[0]; } this._heading = this._getEventTitle(event); - const url = `${this.config.frigate_url}/clips/` + - `${event.camera}-${event.id}.mp4`; - return html` - ` + const url = `${this.config.frigate_url}/clips/` + `${event.camera}-${event.id}.mp4`; + return html` `; } // Render a snapshot. @@ -566,19 +570,19 @@ export class FrigateCard extends LitElement { try { events = await this._getEvents({ has_snapshot: true, - limit: 1 + limit: 1, }); } catch (e) { return this._renderError(e); } if (!events.length) { - return this._renderAttentionIcon("mdi:filmstrip-off"); + return this._renderAttentionIcon('mdi:filmstrip-off'); } event = events[0]; } this._heading = this._getEventTitle(event); const url = `${this.config.frigate_url}/clips/${event.camera}-${event.id}.jpg`; - return html`` + return html``; } // Render the live viewer. @@ -586,25 +590,21 @@ export class FrigateCard extends LitElement { // is always rendered (but sometimes hidden). protected _renderLiveViewer(): TemplateResult { if (!this._hass || !(this.config.camera_entity in this._hass.states)) { - return this._renderError("mdi:camera-off") + return this._renderError('mdi:camera-off'); } if (this._webrtcElement) { - return html` -
- ${this._webrtcElement} -
`; + return html`
+ ${this._webrtcElement} +
`; } - return html` - - `; + return html` + `; } // Record interactions with the card. @@ -633,25 +633,33 @@ export class FrigateCard extends LitElement { return html` - ${this._viewMode == "clips" ? - html`` : `` + ${ + this._viewMode == 'clips' + ? html`` + : `` } - ${this._viewMode == "snapshots" ? - html`` : `` + ${ + this._viewMode == 'snapshots' + ? html`` + : `` } - ${this._viewMode == "clip" ? - html`
- ${until(this._renderClipPlayer(), this._renderProgressIndicator())} -
` : `` + ${ + this._viewMode == 'clip' + ? html`
+ ${until(this._renderClipPlayer(), this._renderProgressIndicator())} +
` + : `` } - ${this._viewMode == "snapshot" ? - html`
- ${until(this._renderSnapshotViewer(), this._renderProgressIndicator())} -
` : `` + ${ + this._viewMode == 'snapshot' + ? html`
+ ${until(this._renderSnapshotViewer(), this._renderProgressIndicator())} +
` + : `` } ${this._renderLiveViewer()} ${warning} - `; + return html` ${warning} `; } // Show an error card. @@ -679,9 +685,7 @@ export class FrigateCard extends LitElement { origConfig: this.config, }); - return html` - ${errorCard} - `; + return html` ${errorCard} `; } // Return compiled CSS styles (thus safe to use with unsafeCSS). @@ -693,4 +697,4 @@ export class FrigateCard extends LitElement { static getCardSize(): number { return 5; } -} \ No newline at end of file +} diff --git a/src/types.ts b/src/types.ts index 23fa74df..e854fd5d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,10 @@ -import { ActionConfig, LovelaceCard, LovelaceCardConfig, LovelaceCardEditor } from 'custom-card-helpers'; -import { number, z } from "zod"; +import { + ActionConfig, + LovelaceCard, + LovelaceCardConfig, + LovelaceCardEditor, +} from 'custom-card-helpers'; +import { number, z } from 'zod'; declare global { interface HTMLElementTagNameMap { @@ -12,7 +17,13 @@ declare global { * Internal types. */ -export const FRIGATE_CARD_VIEWS = ["live", "clip", "clips", "snapshot", "snapshots"] as const; +export const FRIGATE_CARD_VIEWS = [ + 'live', + 'clip', + 'clips', + 'snapshot', + 'snapshots', +] as const; export type FrigateCardView = typeof FRIGATE_CARD_VIEWS[number]; export const frigateCardConfigSchema = z.object({ @@ -20,10 +31,18 @@ export const frigateCardConfigSchema = z.object({ motion_entity: z.string().optional(), frigate_url: z.string().url(), frigate_camera_name: z.string().optional(), - view_default: z.enum(FRIGATE_CARD_VIEWS).optional().default("live"), - - view_timeout: z.number().or(z.string().regex(/^\d+$/).transform(val => Number(val))).optional(), - live_provider: z.enum(["frigate", "webrtc"]).default("frigate"), + view_default: z.enum(FRIGATE_CARD_VIEWS).optional().default('live'), + + view_timeout: z + .number() + .or( + z + .string() + .regex(/^\d+$/) + .transform((val) => Number(val)), + ) + .optional(), + live_provider: z.enum(['frigate', 'webrtc']).default('frigate'), webrtc: z.object({}).passthrough().optional(), label: z.string().optional(), zone: z.string().optional(), @@ -33,10 +52,9 @@ export const frigateCardConfigSchema = z.object({ show_warning: z.boolean().optional(), show_error: z.boolean().optional(), test_gui: z.boolean().optional(), -}) +}); export type FrigateCardConfig = z.infer; - export interface GetEventsParameters { has_clip?: boolean; has_snapshot?: boolean; @@ -65,8 +83,8 @@ export const frigateEventSchema = z.object({ thumbnail: z.string(), top_score: z.number(), zones: z.string().array(), -}) +}); export type FrigateEvent = z.infer; export const frigateGetEventsResponseSchema = z.array(frigateEventSchema); -export type FrigateGetEventsResponse = z.infer; \ No newline at end of file +export type FrigateGetEventsResponse = z.infer;