- ${this._renderOptionSetHeader('basic')}
- ${options.basic.show
- ? html`
-
- ${this._renderDropdown(CONF_CAMERA_ENTITY, cameraEntities)}
-
- `
- : ''}
- ${this._renderOptionSetHeader('frigate')}
- ${options.frigate.show
- ? html`
-
- ${this._renderStringInput(CONF_FRIGATE_CAMERA_NAME)}
- ${this._renderStringInput(CONF_FRIGATE_URL)}
- ${this._renderStringInput(CONF_FRIGATE_LABEL)}
- ${this._renderStringInput(CONF_FRIGATE_ZONE)}
- ${this._renderStringInput(CONF_FRIGATE_CLIENT_ID)}
-
- `
+ ${this._renderOptionSetHeader('cameras')}
+ ${options.cameras.show
+ ? html`
+ ${cameras.map((_, index) =>
+ this._renderCamera(cameras, index, cameraEntities),
+ )}
+ ${this._renderCamera(cameras, cameras.length, cameraEntities, true)}
+
`
: ''}
${this._renderOptionSetHeader('view')}
${options.view.show
@@ -404,9 +622,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderDropdown(CONF_VIEW_DEFAULT, viewModes)}
${this._renderStringInput(CONF_VIEW_TIMEOUT, '[0-9]')}
- ${this._renderSwitch(
- CONF_VIEW_UPDATE_FORCE,
- defaults.view.update_force)}
+ ${this._renderSwitch(CONF_VIEW_UPDATE_FORCE, defaults.view.update_force)}
`
: ''}
@@ -464,9 +680,19 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
? html`
${this._renderSwitch(CONF_LIVE_PRELOAD, defaults.live.preload)}
- ${this._renderDropdown(CONF_LIVE_PROVIDER, liveProviders)}
- ${this._renderDropdown(CONF_LIVE_WEBRTC_ENTITY, cameraEntities)}
- ${this._renderStringInput(CONF_LIVE_WEBRTC_URL)}
+ ${this._renderSwitch(
+ CONF_LIVE_DRAGGABLE,
+ defaults.live.draggable,
+ )}
+ ${this._renderSwitch(
+ CONF_LIVE_LAZY_LOAD,
+ defaults.live.lazy_load,
+ )}
+ ${this._renderDropdown(
+ CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
+ liveNextPreviousControlStyles,
+ )}
+ ${this._renderStringInput(CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE)}
${this._renderDropdown(
CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
thumbnailModes,
@@ -541,6 +767,19 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
this._helpers = await (window as any).loadCardHelpers();
}
+ /**
+ * Display/hide a camera section.
+ * @param ev The event triggering the change.
+ */
+ protected _toggleCameraHandler(ev: { target: EditorCameraTarget | null }): void {
+ if (ev && ev.target) {
+ this._expandedCameraIndex =
+ this._expandedCameraIndex == ev.target.cameraIndex
+ ? null
+ : ev.target.cameraIndex;
+ }
+ }
+
/**
* Handle a toggled set of options.
* @param ev The event triggering the change.
@@ -573,7 +812,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
* @param ev Event triggering the change.
*/
protected _valueChangedHandler(ev: {
- target: (EditorOptionTarget & HTMLElement) | null;
+ target: (ConfigValueTarget & HTMLElement) | null;
}): void {
const target = ev.target;
if (!this._config || !this.hass || !target) {
@@ -591,22 +830,17 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
return;
}
+ if (getConfigValue(this._config, key) === value) {
+ return;
+ }
+
const newConfig = copyConfig(this._config);
if (value === '' || typeof value === 'undefined') {
- // Don't delete empty properties that are from a dropdown menu. An empty
- // property in that context may just be a user-entered value that is not
- // in the valid choices in the dropdown. This probably won't end well for
- // the user anyway, but having the whole property deleted the moment they
- // press a key is very jarring.
- if (target.tagName != 'PAPER-DROPDOWN-MENU') {
- deleteConfigValue(newConfig, key);
- }
+ deleteConfigValue(newConfig, key);
} else {
setConfigValue(newConfig, key, value);
}
- trimConfig(newConfig);
- this._config = newConfig;
- fireEvent(this, 'config-changed', { config: this._config });
+ this._updateConfig(newConfig);
}
/**
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json
index f5270e82..f9f7b294 100644
--- a/src/localize/languages/en.json
+++ b/src/localize/languages/en.json
@@ -9,12 +9,26 @@
"no_clip": "No recent clip"
},
"config": {
- "camera_entity": "Camera Entity",
- "frigate": {
- "camera_name": "Frigate camera name (Optional, autodetected from entity)",
+ "cameras": {
+ "camera_entity": "Camera Entity",
+ "camera_name": "Frigate camera name (Autodetected from entity)",
"client_id": "Frigate client id (For >1 Frigate server)",
+ "id": "Unique id for this camera in this card",
"label": "Frigate label/object filter",
- "url": "Frigate server URL",
+ "frigate_url": "Frigate server URL",
+ "title": "Title for this camera (Autodetected from entity)",
+ "icon": "Icon for this camera (Autodetected from entity)",
+ "live_provider": "Live view provider for this camera",
+ "live_providers": {
+ "auto": "Automatic",
+ "frigate": "Frigate",
+ "frigate-jsmpeg": "Frigate JSMpeg",
+ "webrtc": "WebRTC"
+ },
+ "webrtc": {
+ "entity": "WebRTC Camera Entity (Not a Frigate camera)",
+ "url": "WebRTC Camera URL"
+ },
"zone": "Frigate zone"
},
"view": {
@@ -31,13 +45,13 @@
"update_force": "Force card updates (ignore media playing / interaction)"
},
"event_viewer": {
- "autoplay_clip": "Autoplay most recent clip (In 'clip' view)",
- "draggable": "Event viewer can be dragged/swiped",
- "lazy_load": "Lazily load event media",
+ "autoplay_clip": "Autoplay clips",
+ "draggable": "Event Viewer can be dragged/swiped",
+ "lazy_load": "Event Viewer media is lazily loaded in carousel",
"controls": {
"next_previous": {
- "style": "Event Viewer Next & Previous Control style",
- "size": "Event Viewer Next & Previous Control size (e.g. '48px')",
+ "style": "Event Viewer next & previous control style",
+ "size": "Event Viewer next & previous control size (e.g. '48px')",
"styles": {
"thumbnails": "Thumbnails",
"chevrons": "Chevrons",
@@ -56,18 +70,19 @@
}
},
"live": {
- "preload": "Preload live view",
- "provider": "Live view provider",
- "providers": {
- "frigate": "Frigate",
- "frigate-jsmpeg": "Frigate JSMpeg",
- "webrtc": "WebRTC"
- },
- "webrtc": {
- "entity": "WebRTC Camera Entity (Not a Frigate camera)",
- "url": "WebRTC Camera URL"
- },
+ "preload": "Preload live view in the background",
+ "draggable": "Live cameras view can be dragged/swiped",
+ "lazy_load": "Live cameras are lazily loaded in carousel",
"controls": {
+ "next_previous": {
+ "style": "Live view next & previous control style",
+ "size": "Live view next & previous control size (e.g. '48px')",
+ "styles": {
+ "chevrons": "Chevrons",
+ "icons": "Icons",
+ "none": "None"
+ }
+ },
"thumbnails": {
"mode": "Live thumbnails mode",
"size": "Live thumbnails size (e.g. '100px')",
@@ -87,7 +102,8 @@
"frigate": "Frigate menu / Default view",
"frigate_ui": "Frigate user Interface",
"fullscreen": "Fullscreen",
- "download": "Download event media"
+ "download": "Download event media",
+ "cameras": "Select camera"
},
"mode": "Menu mode",
"modes": {
@@ -120,10 +136,8 @@
}
},
"editor": {
- "basic": "Basic",
- "basic_secondary": "Options for most users",
- "frigate": "Frigate",
- "frigate_secondary": "Frigate server options",
+ "cameras": "Cameras",
+ "cameras_secondary": "What cameras to render on this card",
"view": "View",
"view_secondary": "What the card should show and how to show it",
"menu": "Menu",
@@ -138,7 +152,12 @@
"dimensions_secondary": "Dimensions & shape options",
"show_button": "Show button",
"upgrade": "Upgrade",
- "upgrade_available": "An automatic card configuration upgrade is available"
+ "upgrade_available": "An automatic card configuration upgrade is available",
+ "delete": "Delete",
+ "move_up": "Move up",
+ "move_down": "Move down",
+ "add_new_camera": "Add new camera",
+ "camera": "Camera"
},
"error": {
"empty_response": "Received empty response from Home Assistant for request",
@@ -151,12 +170,16 @@
"invalid_configuration": "Invalid configuration",
"invalid_configuration_no_hint": "No location hint available (bad or missing type?)",
"upgrade_available": "An automated card configuration upgrade is available, please visit the visual card editor",
- "missing_webrtc": "WebRTC component not found",
- "no_frigate_camera_name": "Cannot autodetect Frigate camera name, you need to either set camera_entity and / or frigate.camera_name",
+ "webrtc_missing": "WebRTC component not found",
+ "webrtc_reported_error": "WebRTC component reported an error",
+ "no_cameras": "No valid cameras found, you must configure at least one camera entry",
+ "no_camera_id": "Could not determine camera id for the following camera, may need to set 'id' parameter manually",
+ "duplicate_camera_id": "Duplicate Frigate camera id for the following camera, use the 'id' parameter to uniquely identify cameras",
"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",
"jsmpeg_no_player": "Could not start JSMPEG player",
+ "no_camera_name": "Could not determine Frigate camera name for camera, please specify either 'camera_entity' or 'camera_name' for the following camera",
"download_no_media": "No media to download",
"download_no_event_id": "Could not extract Frigate event id from media",
"download_sign_failed": "Could not sign media URL for download"
diff --git a/src/patches/ha-camera-stream.ts b/src/patches/ha-camera-stream.ts
index 6aec018a..83408a43 100644
--- a/src/patches/ha-camera-stream.ts
+++ b/src/patches/ha-camera-stream.ts
@@ -48,10 +48,6 @@ customElements.whenDefined('ha-camera-stream').then(() => {
? html`
![]()
{
- // TODO: This block can be removed a safe distance from HA 2021.11.
- if (typeof this._elementResized != 'undefined') {
- this._elementResized();
- }
dispatchMediaShowEvent(this, e);
}}
.src=${typeof this._connected == 'undefined' || this._connected
diff --git a/src/patches/ha-hls-player.ts b/src/patches/ha-hls-player.ts
index 4e8cbce5..13648fec 100644
--- a/src/patches/ha-hls-player.ts
+++ b/src/patches/ha-hls-player.ts
@@ -10,6 +10,7 @@
// ====================================================================
import { TemplateResult, css, html } from 'lit';
+import { Ref, createRef, ref } from 'lit/directives/ref';
import { customElement } from 'lit/decorators.js';
import {
@@ -22,6 +23,17 @@ customElements.whenDefined('ha-hls-player').then(() => {
@customElement('frigate-card-ha-hls-player')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FrigateCardHaHlsPlayer extends customElements.get('ha-hls-player') {
+ protected _videoRef: Ref
= createRef();
+
+ /**
+ * Play the video.
+ */
+ public play(): void {
+ if (this._videoRef.value) {
+ this._videoRef.value.play();
+ }
+ }
+
// =====================================================================================
// Minor modifications from:
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-hls-player.ts
@@ -29,15 +41,12 @@ customElements.whenDefined('ha-hls-player').then(() => {
protected render(): TemplateResult {
return html`