Editor support for dependent cameras.

This commit is contained in:
Dermot Duffy
2022-04-23 15:15:35 -07:00
parent 5631850e6d
commit f8784bfc32
6 changed files with 117 additions and 62 deletions
+2 -6
View File
@@ -48,6 +48,7 @@ import {
frigateCardHasAction, frigateCardHasAction,
getActionConfigGivenAction, getActionConfigGivenAction,
getCameraIcon, getCameraIcon,
getCameraID,
getCameraTitle, getCameraTitle,
homeAssistantSignPath, homeAssistantSignPath,
homeAssistantWSRequest, homeAssistantWSRequest,
@@ -467,12 +468,7 @@ export class FrigateCard extends LitElement {
} }
} }
const id = const id = getCameraID(config);
config.id ||
config.camera_entity ||
config.webrtc_card?.entity ||
config.camera_name;
if (!id) { if (!id) {
this._setMessageAndUpdate({ this._setMessageAndUpdate({
message: localize('error.no_camera_id'), message: localize('error.no_camera_id'),
+39 -6
View File
@@ -28,6 +28,7 @@ import {
FrigateEvent, FrigateEvent,
MediaShowInfo, MediaShowInfo,
Message, Message,
RawFrigateCardConfig,
SignedPath, SignedPath,
signedPathSchema, signedPathSchema,
StateParameters, StateParameters,
@@ -463,21 +464,53 @@ export function getEntityIcon(
return hass && entity ? stateIcon(hass.states[entity]) : undefined; return hass && entity ? stateIcon(hass.states[entity]) : undefined;
} }
/**
* Get a camera id.
* @param config The camera config (either parsed or raw).
* @returns A camera id.
*/
export function getCameraID(
config?: CameraConfig | RawFrigateCardConfig | null,
): string {
return (
(typeof config?.id === 'string' && config.id) ||
(typeof config?.camera_entity === 'string' && config.camera_entity) ||
(typeof config?.webrtc_card === 'object' && config.webrtc_card && typeof config.webrtc_card['entity'] === 'string' && config.webrtc_card['entity']) ||
(typeof config?.camera_name === 'string' && config.camera_name) ||
''
);
}
/** /**
* Get a camera text title. * Get a camera text title.
* @param hass The Home Assistant object. * @param hass The Home Assistant object.
* @param config The camera config. * @param config The camera config (either parsed or raw).
* @returns A title string. * @returns A title string.
*/ */
export function getCameraTitle( export function getCameraTitle(
hass?: HomeAssistant, hass?: HomeAssistant,
config?: CameraConfig | null, config?: CameraConfig | RawFrigateCardConfig | null,
): string { ): string {
// Attempt to render a recognizable name for the camera,
// starting with the most likely to be useful and working our
// ways towards the least useful. Extra type checking here since this is also
// used on raw configuration in the editor.
return ( return (
config?.title || (typeof config?.title === 'string' && config.title) ||
(config?.camera_entity ? getEntityTitle(hass, config.camera_entity) : '') || (typeof config?.id === 'string' && config.id) ||
(config?.camera_name ? prettifyFrigateName(config.camera_name) : '') || [
config?.id || typeof config?.camera_entity === 'string'
? getEntityTitle(hass, config.camera_entity)
: '',
typeof config?.client_id === 'string' ? config.client_id : '',
typeof config?.camera_name === 'string'
? prettifyFrigateName(config.camera_name)
: '',
typeof config?.label === 'string' ? prettifyFrigateName(config.label) : '',
typeof config?.zone === 'string' ? prettifyFrigateName(config.zone) : '',
]
.filter(Boolean)
.join(' / ') ||
'' ''
); );
} }
+2
View File
@@ -18,6 +18,8 @@ export const CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY =
export const CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL = `${CONF_CAMERAS}.#.webrtc_card.url` as const; export const CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL = `${CONF_CAMERAS}.#.webrtc_card.url` as const;
export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER = export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER =
`${CONF_CAMERAS}.#.live_provider` as const; `${CONF_CAMERAS}.#.live_provider` as const;
export const CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS =
`${CONF_CAMERAS}.#.dependent_cameras` as const;
export const CONF_VIEW = 'view' as const; export const CONF_VIEW = 'view' as const;
export const CONF_VIEW_CAMERA_SELECT = `${CONF_VIEW}.camera_select` as const; export const CONF_VIEW_CAMERA_SELECT = `${CONF_VIEW}.camera_select` as const;
+67 -45
View File
@@ -6,6 +6,7 @@ import { HomeAssistant, LovelaceCardEditor, fireEvent } from 'custom-card-helper
import { localize } from './localize/localize.js'; import { localize } from './localize/localize.js';
import { import {
BUTTON_SIZE_MIN, BUTTON_SIZE_MIN,
CameraConfig,
RawFrigateCardConfig, RawFrigateCardConfig,
RawFrigateCardConfigArray, RawFrigateCardConfigArray,
THUMBNAIL_WIDTH_MAX, THUMBNAIL_WIDTH_MAX,
@@ -18,6 +19,7 @@ import {
CONF_CAMERAS_ARRAY_CAMERA_ENTITY, CONF_CAMERAS_ARRAY_CAMERA_ENTITY,
CONF_CAMERAS_ARRAY_CAMERA_NAME, CONF_CAMERAS_ARRAY_CAMERA_NAME,
CONF_CAMERAS_ARRAY_CLIENT_ID, CONF_CAMERAS_ARRAY_CLIENT_ID,
CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS,
CONF_CAMERAS_ARRAY_ICON, CONF_CAMERAS_ARRAY_ICON,
CONF_CAMERAS_ARRAY_ID, CONF_CAMERAS_ARRAY_ID,
CONF_CAMERAS_ARRAY_LABEL, CONF_CAMERAS_ARRAY_LABEL,
@@ -88,7 +90,13 @@ import {
CONF_VIEW_UPDATE_FORCE, CONF_VIEW_UPDATE_FORCE,
CONF_VIEW_UPDATE_SECONDS, CONF_VIEW_UPDATE_SECONDS,
} from './const.js'; } from './const.js';
import { arrayMove, getEntityTitle, prettifyFrigateName } from './common.js'; import {
arrayMove,
getCameraID,
getCameraTitle,
getEntityTitle,
prettifyFrigateName,
} from './common.js';
import { import {
copyConfig, copyConfig,
deleteConfigValue, deleteConfigValue,
@@ -119,6 +127,11 @@ interface EditorOptionSetTarget {
optionSetName: string; optionSetName: string;
} }
interface EditorSelectOption {
value: string;
label: string;
}
const options: EditorOptions = { const options: EditorOptions = {
cameras: { cameras: {
icon: 'video', icon: 'video',
@@ -193,7 +206,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
@property({ attribute: false }) @property({ attribute: false })
protected _expandedCameraIndex: number | null = null; protected _expandedCameraIndex: number | null = null;
protected _viewModes = [ protected _viewModes: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'live', label: localize('config.view.views.live') }, { value: 'live', label: localize('config.view.views.live') },
{ value: 'clips', label: localize('config.view.views.clips') }, { value: 'clips', label: localize('config.view.views.clips') },
@@ -204,12 +217,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'timeline', label: localize('config.view.views.timeline') }, { value: 'timeline', label: localize('config.view.views.timeline') },
]; ];
protected _cameraSelectViewModes = [ protected _cameraSelectViewModes: EditorSelectOption[] = [
...this._viewModes, ...this._viewModes,
{ value: 'current', label: localize('config.view.views.current') }, { value: 'current', label: localize('config.view.views.current') },
]; ];
protected _menuModes = [ protected _menuModes: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'none', label: localize('config.menu.modes.none') }, { value: 'none', label: localize('config.menu.modes.none') },
{ value: 'hidden-top', label: localize('config.menu.modes.hidden-top') }, { value: 'hidden-top', label: localize('config.menu.modes.hidden-top') },
@@ -228,7 +241,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'below', label: localize('config.menu.modes.below') }, { value: 'below', label: localize('config.menu.modes.below') },
]; ];
protected _eventViewerNextPreviousControlStyles = [ protected _eventViewerNextPreviousControlStyles: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ {
value: 'thumbnails', value: 'thumbnails',
@@ -244,7 +257,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
}, },
]; ];
protected _liveNextPreviousControlStyles = [ protected _liveNextPreviousControlStyles: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ {
value: 'chevrons', value: 'chevrons',
@@ -257,7 +270,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'none', label: localize('config.live.controls.next_previous.styles.none') }, { value: 'none', label: localize('config.live.controls.next_previous.styles.none') },
]; ];
protected _aspectRatioModes = [ protected _aspectRatioModes: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ {
value: 'dynamic', value: 'dynamic',
@@ -270,7 +283,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
}, },
]; ];
protected _thumbnailModes = [ protected _thumbnailModes: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ {
value: 'none', value: 'none',
@@ -294,7 +307,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
}, },
]; ];
protected _thumbnailMedias = [ protected _thumbnailMedias: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'clips', label: localize('config.live.controls.thumbnails.medias.clips') }, { value: 'clips', label: localize('config.live.controls.thumbnails.medias.clips') },
{ {
@@ -303,7 +316,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
}, },
]; ];
protected _titleModes = [ protected _titleModes: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'none', label: localize('config.event_viewer.controls.title.modes.none') }, { value: 'none', label: localize('config.event_viewer.controls.title.modes.none') },
{ {
@@ -324,27 +337,27 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
}, },
]; ];
protected _transitionEffects = [ protected _transitionEffects: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'none', label: localize('config.event_viewer.transition_effects.none') }, { value: 'none', label: localize('config.event_viewer.transition_effects.none') },
{ value: 'slide', label: localize('config.event_viewer.transition_effects.slide') }, { value: 'slide', label: localize('config.event_viewer.transition_effects.slide') },
]; ];
protected _imageModes = [ protected _imageModes: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'camera', label: localize('config.image.modes.camera') }, { value: 'camera', label: localize('config.image.modes.camera') },
{ value: 'screensaver', label: localize('config.image.modes.screensaver') }, { value: 'screensaver', label: localize('config.image.modes.screensaver') },
{ value: 'url', label: localize('config.image.modes.url') }, { value: 'url', label: localize('config.image.modes.url') },
]; ];
protected _timelineMediaTypes = [ protected _timelineMediaTypes: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'all', label: localize('config.timeline.medias.all') }, { value: 'all', label: localize('config.timeline.medias.all') },
{ value: 'clips', label: localize('config.timeline.medias.clips') }, { value: 'clips', label: localize('config.timeline.medias.clips') },
{ value: 'snapshots', label: localize('config.timeline.medias.snapshots') }, { value: 'snapshots', label: localize('config.timeline.medias.snapshots') },
]; ];
protected _darkModes = [ protected _darkModes: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'on', label: localize('config.view.dark_modes.on') }, { value: 'on', label: localize('config.view.dark_modes.on') },
{ value: 'off', label: localize('config.view.dark_modes.off') }, { value: 'off', label: localize('config.view.dark_modes.off') },
@@ -456,6 +469,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
protected _renderOptionSelector( protected _renderOptionSelector(
configPath: string, configPath: string,
options: string[] | { value: string; label: string }[], options: string[] | { value: string; label: string }[],
multiple?: boolean,
): TemplateResult | void { ): TemplateResult | void {
if (!this._config) { if (!this._config) {
return; return;
@@ -464,7 +478,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
return html` return html`
<ha-selector <ha-selector
.hass=${this.hass} .hass=${this.hass}
.selector=${{ select: { options: options } }} .selector=${{
select: { mode: 'dropdown', multiple: !!multiple, options: options },
}}
.label=${this._getLabel(configPath)} .label=${this._getLabel(configPath)}
.value=${getConfigValue(this._config, configPath, '')} .value=${getConfigValue(this._config, configPath, '')}
.required=${false} .required=${false}
@@ -516,6 +532,22 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
return html` <span class="info">${info}</span>`; return html` <span class="info">${info}</span>`;
} }
/**
* Get an editor title for the camera.
* @param cameraIndex The index of the camera in the cameras array.
* @param cameraConfig The raw camera configuration object.
* @returns A string title.
*/
protected _getEditorCameraTitle(
cameraIndex: number,
cameraConfig: RawFrigateCardConfig,
): string {
return (
getCameraTitle(this.hass, cameraConfig) ||
localize('editor.camera') + ' #' + cameraIndex
);
}
/** /**
* Render a camera header. * Render a camera header.
* @param cameraIndex The index of the camera to edit/add. * @param cameraIndex The index of the camera to edit/add.
@@ -540,31 +572,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
? html` <span class="new-camera"> ? html` <span class="new-camera">
[${localize('editor.add_new_camera')}...] [${localize('editor.add_new_camera')}...]
</span>` </span>`
: // Attempt to render a recognizable name for the camera, : html`<span
// starting with the most likely to be useful and working our >${this._getEditorCameraTitle(cameraIndex, cameraConfig || {})}</span
// ways towards the least useful. >`}
html` <span>
${cameraConfig?.title ||
cameraConfig?.id ||
[
cameraConfig?.camera_entity
? getEntityTitle(this.hass, String(cameraConfig.camera_entity))
: '',
cameraConfig?.client_id,
cameraConfig?.camera_name
? prettifyFrigateName(String(cameraConfig.camera_name))
: '',
cameraConfig?.label
? prettifyFrigateName(String(cameraConfig.label))
: '',
cameraConfig?.zone
? prettifyFrigateName(String(cameraConfig.zone))
: '',
]
.filter(Boolean)
.join(' / ') ||
localize('editor.camera') + ' #' + cameraIndex}
</span>`}
</span> </span>
</div> </div>
`; `;
@@ -582,7 +592,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
cameraIndex: number, cameraIndex: number,
addNewCamera?: boolean, addNewCamera?: boolean,
): TemplateResult | void { ): TemplateResult | void {
const liveProviders = [ const liveProviders: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'auto', label: localize('config.cameras.live_providers.auto') }, { value: 'auto', label: localize('config.cameras.live_providers.auto') },
{ value: 'ha', label: localize('config.cameras.live_providers.ha') }, { value: 'ha', label: localize('config.cameras.live_providers.ha') },
@@ -596,6 +606,16 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
}, },
]; ];
const dependentCameras: EditorSelectOption[] = [];
cameras.forEach((camera, index) => {
if (index !== cameraIndex) {
dependentCameras.push({
value: getCameraID(camera),
label: this._getEditorCameraTitle(index, camera),
});
}
});
// Make a new config and update the editor with changes on it, // Make a new config and update the editor with changes on it,
const modifyConfig = (func: (config: RawFrigateCardConfig) => boolean): void => { const modifyConfig = (func: (config: RawFrigateCardConfig) => boolean): void => {
if (this._config) { if (this._config) {
@@ -711,6 +731,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderStringInput( ${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, cameraIndex), getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, cameraIndex),
)} )}
${this._renderOptionSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_DEPENDENT_CAMERAS, cameraIndex),
dependentCameras,
true,
)}
</div>` </div>`
: ``} : ``}
`; `;
@@ -841,10 +866,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
CONF_VIEW_CAMERA_SELECT, CONF_VIEW_CAMERA_SELECT,
this._cameraSelectViewModes, this._cameraSelectViewModes,
)} )}
${this._renderOptionSelector( ${this._renderOptionSelector(CONF_VIEW_DARK_MODE, this._darkModes)}
CONF_VIEW_DARK_MODE,
this._darkModes,
)}
${this._renderNumberInput(CONF_VIEW_TIMEOUT_SECONDS)} ${this._renderNumberInput(CONF_VIEW_TIMEOUT_SECONDS)}
${this._renderNumberInput(CONF_VIEW_UPDATE_SECONDS)} ${this._renderNumberInput(CONF_VIEW_UPDATE_SECONDS)}
${this._renderSwitch(CONF_VIEW_UPDATE_FORCE, defaults.view.update_force)} ${this._renderSwitch(CONF_VIEW_UPDATE_FORCE, defaults.view.update_force)}
+1
View File
@@ -14,6 +14,7 @@
"camera_entity": "Camera Entity", "camera_entity": "Camera Entity",
"camera_name": "Frigate camera name (Autodetected from entity)", "camera_name": "Frigate camera name (Autodetected from entity)",
"client_id": "Frigate client id (For >1 Frigate server)", "client_id": "Frigate client id (For >1 Frigate server)",
"dependent_cameras": "Dependent cameras to also show events for",
"id": "Unique id for this camera in this card", "id": "Unique id for this camera in this card",
"label": "Frigate label/object filter", "label": "Frigate label/object filter",
"frigate_url": "Frigate server URL", "frigate_url": "Frigate server URL",
+6 -5
View File
@@ -28,10 +28,6 @@
display: grid; display: grid;
margin-bottom: 10px; margin-bottom: 10px;
} }
ha-formfield {
padding-bottom: 8px;
}
div.upgrade { div.upgrade {
width: auto; width: auto;
border: 1px dotted var(--primary-color); border: 1px dotted var(--primary-color);
@@ -78,4 +74,9 @@ div.upgrade span {
} }
span.info { span.info {
padding: 4px; padding: 4px;
} }
ha-selector {
padding: 10px;
border: 1px solid var(--divider-color);
}