From 85ba8f3a935a3bd45d3381520ca48d5f60bcde74 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Thu, 23 Dec 2021 08:54:56 -0800 Subject: [PATCH] Convert camera configuration to YAML dictionary. --- src/card.ts | 66 +++++++++++++++++----------------- src/common.ts | 34 ++++++++++++------ src/components/live.ts | 18 +++++----- src/localize/languages/en.json | 1 - src/types.ts | 6 ++-- 5 files changed, 68 insertions(+), 57 deletions(-) diff --git a/src/card.ts b/src/card.ts index 1d640274..d0dec3a9 100644 --- a/src/card.ts +++ b/src/card.ts @@ -1,5 +1,3 @@ -// TODO change url to frigate_url? - /* eslint-disable @typescript-eslint/no-explicit-any */ import { CSSResultGroup, @@ -33,7 +31,6 @@ import { CameraConfig, } from './types.js'; import type { - BrowseMediaQueryParameters, Entity, ExtendedHomeAssistant, FrigateCardConfig, @@ -53,6 +50,7 @@ import { homeAssistantSignPath, homeAssistantWSRequest, isValidMediaShowInfo, + refreshCameraConfigDynamicParameters, shouldUpdateBasedOnHass, } from './common.js'; import { localize } from './localize/localize.js'; @@ -280,18 +278,24 @@ export class FrigateCard extends LitElement { } if (this.config.menu.buttons.cameras && this._cameras && this._cameras.size > 1) { - const menuItems = Array.from(this._cameras, ([camera, config]) => ({ - icon: config.icon || 'mdi:cctv', - entity: config.camera_entity, - state_color: true, - title: config.title, - tap_action: createFrigateCardCustomAction('camera_select', camera), - })); + const menuItems = Array.from(this._cameras, ([camera, config]) => { + const dynamicConfig = refreshCameraConfigDynamicParameters( + { ...config }, + this._hass, + ); + return { + icon: dynamicConfig.icon || 'mdi:cctv', + entity: dynamicConfig.camera_entity, + state_color: true, + title: dynamicConfig.title, + tap_action: createFrigateCardCustomAction('camera_select', camera), + }; + }); buttons.push({ type: 'custom:frigate-card-menu-submenu', title: localize('config.menu.buttons.cameras'), - icon: 'mdi:camera-switch', + icon: 'mdi:video-switch', items: menuItems, }); } @@ -399,8 +403,13 @@ export class FrigateCard extends LitElement { protected async _loadCameras(): Promise { const cameras: Map = new Map(); - const addCameraConfig = async (config: CameraConfig) => { - if (!config.camera_name && config.camera_entity) { + const addCameraConfig = async (id: string, config: CameraConfig) => { + if (!config.camera_name && (config.camera_entity || id.startsWith('camera.'))) { + if (!config.camera_entity) { + // If the camera_entity isn't explicitly set and the id looks like + // one, use that. + config.camera_entity = id; + } const resolvedName = await this._getFrigateCameraNameFromEntity( config.camera_entity, ); @@ -408,30 +417,17 @@ export class FrigateCard extends LitElement { config.camera_name = resolvedName; } } - - if (config.camera_name) { - const id = config.id || config.camera_name; - if (cameras.has(id)) { - this._setMessageAndUpdate( - { - message: localize('error.duplicate_frigate_camera_name'), - type: 'error', - }, - true, - ); - } else { - cameras.set(config.id || config.camera_name, config); - } + if (!config.camera_name) { + config.camera_name = id; } + cameras.set(id, config); }; - if (this.config.camera) { - if (Array.isArray(this.config.camera)) { - await Promise.all(this.config.camera.map(addCameraConfig.bind(this))); - } else { - await addCameraConfig(this.config.camera); - } - } + await Promise.all( + Object.keys(this.config.cameras).map((key) => + addCameraConfig(key, this.config.cameras[key]), + ), + ); if (!cameras.size) { return this._setMessageAndUpdate( @@ -945,6 +941,8 @@ export class FrigateCard extends LitElement { if (requestRefresh) { this.requestUpdate(); } + // TODO: Remove + console.info(`Showing media: ${JSON.stringify(mediaShowInfo)}`); } /** diff --git a/src/common.ts b/src/common.ts index 024d28d4..fb69a3c9 100644 --- a/src/common.ts +++ b/src/common.ts @@ -287,7 +287,8 @@ export function convertActionToFrigateCardCustomAction( */ export function createFrigateCardCustomAction( action: FrigateCardAction, - camera?: string): FrigateCardCustomAction | undefined { + camera?: string, +): FrigateCardCustomAction | undefined { if (action == 'camera_select') { if (!camera) { return undefined; @@ -296,12 +297,12 @@ export function createFrigateCardCustomAction( action: 'fire-dom-event', frigate_card_action: action, camera: camera, - } + }; } return { action: 'fire-dom-event', frigate_card_action: action, - } + }; } /** @@ -397,13 +398,24 @@ export function refreshDynamicStateParameters( return params; } +function prettifyCameraName(input: string): string { + const words = input.split(/[_\s]+/); + return words + .map((word) => { + return word[0].toUpperCase() + word.substring(1); + }) + .join(' '); +} + export function refreshCameraConfigDynamicParameters( - hass: HomeAssistant, - config: CameraConfig): CameraConfig { - if (hass && config.camera_entity) { - const state = hass.states[config.camera_entity]; - config.title = config.title ?? (state?.attributes?.friendly_name || config.camera_entity); - config.icon = config.icon ?? stateIcon(state); - } + config: CameraConfig, + hass?: HomeAssistant, +): CameraConfig { + const state = + hass && config.camera_entity ? hass.states[config.camera_entity] : undefined; + config.title = + config.title ?? + (state?.attributes?.friendly_name || prettifyCameraName(config.camera_name || '')); + config.icon = config.icon ?? (state ? stateIcon(state) : 'mdi:video'); return config; -} \ No newline at end of file +} diff --git a/src/components/live.ts b/src/components/live.ts index efe1b8c4..31f50a88 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -1,4 +1,8 @@ // TODO editor +// TODO webrtc entities in camera section? +// TODO conditional elements based on camera name (requires event changed to propagate upwards) +// TODO change url to frigate_url? +// TODO Remove media load event warning // TODO readme // TODO search for TODOs @@ -237,12 +241,10 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { } return Array.from(this.cameras.values()).map((cameraConfig, index) => { let refreshedConfig = { ...cameraConfig }; - if (this.hass) { - refreshedConfig = refreshCameraConfigDynamicParameters( - this.hass, - refreshedConfig, - ); - } + refreshedConfig = refreshCameraConfigDynamicParameters( + refreshedConfig, + this.hass, + ); return this._renderLive(refreshedConfig, index); }); } @@ -303,13 +305,13 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { if (currentIndex > 0) { prev = this.cameras.get(keys[currentIndex - 1]) ?? null; if (prev) { - prev = refreshCameraConfigDynamicParameters(this.hass, { ...prev }); + prev = refreshCameraConfigDynamicParameters({ ...prev }, this.hass); } } if (currentIndex + 1 < this.cameras.size) { next = this.cameras.get(keys[currentIndex + 1]) ?? null; if (next) { - next = refreshCameraConfigDynamicParameters(this.hass, { ...next }); + next = refreshCameraConfigDynamicParameters({ ...next }, this.hass); } } return [prev, next]; diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index e598d2e7..84c65829 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -154,7 +154,6 @@ "upgrade_available": "An automated card configuration upgrade is available, please visit the visual card editor", "missing_webrtc": "WebRTC component not found", "no_cameras": "No cameras found, you must configure at least one camera configured with a camera_entity or camera_name", - "duplicate_frigate_camera_name": "Duplicate Frigate camera name, use the 'id' parameter to uniquely identify them", "could_not_render_elements": "Could not render picture elements", "invalid_elements_config": "Invalid picture elements configuration", "jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path", diff --git a/src/types.ts b/src/types.ts index 8241319c..3834ccb1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -368,12 +368,12 @@ const cameraConfigDefaultSchema = z // specified). icon: z.string().optional(), title: z.string().optional(), - - id: z.string().optional(), }) .default(cameraConfigDefault); export type CameraConfig = z.infer; +const camerasConfigDefaultSchema = z.record(cameraConfigDefaultSchema); + /** * View configuration section. */ @@ -650,7 +650,7 @@ const dimensionsConfigSchema = z */ export const frigateCardConfigSchema = z.object({ // Main configuration sections. - camera: cameraConfigDefaultSchema.or(cameraConfigDefaultSchema.array().nonempty()), + cameras: camerasConfigDefaultSchema, view: viewConfigSchema, menu: menuConfigSchema, live: liveConfigSchema,