Add out-of-the-box Frigate PTZ support.

This commit is contained in:
Dermot Duffy
2023-10-29 14:10:41 -07:00
parent fa43d2e439
commit e12912fa85
74 changed files with 3981 additions and 1514 deletions
+8 -1
View File
@@ -69,7 +69,14 @@ export class FrigateCardCarousel extends LitElement {
this.setAttribute('direction', this.direction);
}
const destroyProperties = ['direction', 'dragFree', 'transitionEffect'] as const;
const destroyProperties = [
'direction',
'dragEnabled',
'dragFree',
'loop',
'plugins',
'transitionEffect',
] as const;
if (destroyProperties.some((prop) => changedProps.has(prop))) {
this._carousel?.destroy();
this._carousel = null;
+8 -128
View File
@@ -1,19 +1,18 @@
import { HASSDomEvent, HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import {
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
html,
unsafeCSS,
} from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { actionHandler } from '../action-handler-directive.js';
import {
Actions,
ActionsConfig,
FrigateCardPTZConfig,
ConditionsManagerEpoch,
evaluateConditionViaEvent,
} from '../card-controller/conditions-manager.js';
import {
FrigateConditional,
MenuIcon,
MenuItem,
@@ -23,19 +22,9 @@ import {
PictureElements,
} from '../config/types.js';
import { localize } from '../localize/localize.js';
import ptzStyle from '../scss/elements-ptz.scss';
import elementsStyle from '../scss/elements.scss';
import { FrigateCardError } from '../types.js';
import {
frigateCardHandleActionConfig,
frigateCardHasAction,
getActionConfigGivenAction,
} from '../utils/action.js';
import { dispatchFrigateCardEvent } from '../utils/basic.js';
import {
ConditionsManagerEpoch,
evaluateConditionViaEvent,
} from '../card-controller/conditions-manager.js';
import { dispatchFrigateCardEvent, errorToConsole } from '../utils/basic.js';
import { dispatchFrigateCardErrorEvent } from './message.js';
/* A note on picture element rendering:
@@ -122,7 +111,7 @@ export class FrigateCardElementsCore extends LitElement {
try {
element.setConfig(config);
} catch (e) {
console.error(e);
errorToConsole(e as Error, console.error);
throw new FrigateCardError(localize('error.invalid_elements_config'));
}
return element;
@@ -342,114 +331,6 @@ export class FrigateCardElementsMenuSubmenu extends FrigateCardElementsBaseMenuI
@customElement('frigate-card-menu-submenu-select')
export class FrigateCardElementsMenuSubmenuSelect extends FrigateCardElementsBaseMenuIcon<MenuSubmenuSelect> {}
@customElement('frigate-card-ptz')
export class FrigateCardPTZ extends LitElement {
@property({ attribute: false })
public hass?: HomeAssistant;
@state()
protected _config: FrigateCardPTZConfig | null = null;
/**
* Set the card config.
* @param config The configuration.
*/
public setConfig(config: FrigateCardPTZConfig): void {
this._config = config;
}
/**
* Called before each update.
*/
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('_config')) {
this.setAttribute('data-orientation', this._config?.orientation ?? 'vertical');
}
}
/**
* Handle a PTZ action.
* @param ev The actionHandler event.
* @param config The action configuration.
*/
protected _actionHandler(
ev: HASSDomEvent<{ action: string }>,
config?: ActionsConfig,
): void {
// Nothing else has the configuration for this action, so don't let it
// propagate further.
ev.stopPropagation();
const interaction: string = ev.detail.action;
const action = getActionConfigGivenAction(interaction, config);
if (config && action && this.hass) {
frigateCardHandleActionConfig(this, this.hass, config, interaction, action);
}
}
/**
* Render the elements.
* @returns A rendered template or void.
*/
protected render(): TemplateResult | void {
if (!this._config) {
return;
}
const renderIcon = (
name: string,
icon: string,
actions?: Actions,
): TemplateResult => {
const hasHold = frigateCardHasAction(actions?.hold_action);
const hasDoubleClick = frigateCardHasAction(actions?.double_tap_action);
const classes = {
[name]: true,
disabled: !actions,
};
return html`<ha-icon
class=${classMap(classes)}
icon=${icon}
.actionHandler=${actionHandler({
hasHold: hasHold,
hasDoubleClick: hasDoubleClick,
})}
.title=${localize(`elements.ptz.${name}`)}
@action=${(ev) => this._actionHandler(ev, actions)}
></ha-icon>`;
};
return html` <div class="ptz">
<div class="ptz-move">
${renderIcon('right', 'mdi:arrow-right', this._config.actions_right)}
${renderIcon('left', 'mdi:arrow-left', this._config.actions_left)}
${renderIcon('up', 'mdi:arrow-up', this._config.actions_up)}
${renderIcon('down', 'mdi:arrow-down', this._config.actions_down)}
</div>
${this._config.actions_zoom_in || this._config.actions_zoom_out
? html` <div class="ptz-zoom">
${renderIcon('zoom_in', 'mdi:plus', this._config.actions_zoom_in)}
${renderIcon('zoom_out', 'mdi:minus', this._config.actions_zoom_out)}
</div>`
: html``}
${this._config.actions_home
? html`
<div class="ptz-home">
${renderIcon('home', 'mdi:home', this._config.actions_home)}
</div>
`
: html``}
</div>`;
}
/**
* Return compiled CSS styles.
*/
static get styles(): CSSResultGroup {
return unsafeCSS(ptzStyle);
}
}
declare global {
interface HTMLElementTagNameMap {
'frigate-card-conditional': FrigateCardElementsConditional;
@@ -459,6 +340,5 @@ declare global {
'frigate-card-menu-state-icon': FrigateCardElementsMenuStateIcon;
'frigate-card-menu-icon': FrigateCardElementsMenuIcon;
'frigate-card-elements-core': FrigateCardElementsCore;
'frigate-card-ptz': FrigateCardPTZ;
}
}
+47 -39
View File
@@ -13,7 +13,7 @@ import { ifDefined } from 'lit/directives/if-defined.js';
import { keyed } from 'lit/directives/keyed.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import { CameraManager } from '../../camera-manager/manager.js';
import { CameraConfigs, CameraEndpoints } from '../../camera-manager/types.js';
import { CameraEndpoints } from '../../camera-manager/types.js';
import {
ConditionsManagerEpoch,
getOverriddenConfig,
@@ -45,6 +45,7 @@ import { AutoLazyLoad } from '../../utils/embla/plugins/auto-lazy-load/auto-lazy
import { AutoMediaActions } from '../../utils/embla/plugins/auto-media-actions/auto-media-actions.js';
import AutoMediaLoadedInfo from '../../utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.js';
import AutoSize from '../../utils/embla/plugins/auto-size/auto-size.js';
import { getStateObjOrDispatchError } from '../../utils/get-state-obj.js';
import {
dispatchExistingMediaLoadedInfoAsEvent,
dispatchMediaUnloadedEvent,
@@ -55,18 +56,20 @@ import { dispatchViewContextChangeEvent, View } from '../../view/view.js';
import { EmblaCarouselPlugins } from '../carousel.js';
import { renderMessage } from '../message.js';
import '../next-prev-control.js';
import '../ptz.js';
import { FrigateCardPTZ } from '../ptz.js';
import '../surround.js';
import '../title-control.js';
import {
FrigateCardTitleControl,
getDefaultTitleConfigForView,
} from '../title-control.js';
import { getStateObjOrDispatchError } from '../../utils/get-state-obj.js';
interface LiveViewContext {
// A cameraID override (used for dependencies/substreams to force a different
// camera to be live rather than the camera selected in the view).
overrides?: Map<string, string>;
ptzVisible?: boolean;
}
declare module 'view' {
@@ -387,6 +390,10 @@ export class FrigateCardLiveCarousel extends LitElement {
// Index between camera name and slide number.
protected _cameraToSlide: Record<string, number> = {};
protected _refTitleControl: Ref<FrigateCardTitleControl> = createRef();
protected _refPTZControl: Ref<FrigateCardPTZ> = createRef();
@state()
protected _mediaHasLoaded = false;
protected _getTransitionEffect(): TransitionEffect {
return (
@@ -449,25 +456,20 @@ export class FrigateCardLiveCarousel extends LitElement {
}
protected _getSlides(): [TemplateResult[], Record<string, number>] {
let cameras: CameraConfigs | null = null;
if (this.viewFilterCameraID) {
const config = this.cameraManager
?.getStore()
.getCameraConfig(this.viewFilterCameraID);
if (config) {
cameras = new Map([[this.viewFilterCameraID, config]]);
}
} else {
cameras = this.cameraManager?.getStore().getVisibleCameras() ?? null;
}
if (!cameras) {
if (!this.cameraManager) {
return [[], {}];
}
const cameraIDs = this.viewFilterCameraID
? new Set([this.viewFilterCameraID])
: this.cameraManager.getStore().getVisibleCameraIDs();
const slides: TemplateResult[] = [];
const cameraToSlide: Record<string, number> = {};
for (const [cameraID, cameraConfig] of cameras) {
for (const [cameraID, cameraConfig] of this.cameraManager
.getStore()
.getCameraConfigEntries(cameraIDs)) {
const liveCameraID =
this.view?.context?.live?.overrides?.get(cameraID) ?? cameraID;
const liveCameraConfig =
@@ -487,9 +489,9 @@ export class FrigateCardLiveCarousel extends LitElement {
}
protected _setViewHandler(ev: CustomEvent<CarouselSelected>): void {
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (cameras && ev.detail.index !== this._getSelectedCameraIndex()) {
this._setViewCameraID(Array.from(cameras.keys())[ev.detail.index]);
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
if (cameraIDs && ev.detail.index !== this._getSelectedCameraIndex()) {
this._setViewCameraID([...cameraIDs][ev.detail.index]);
}
}
@@ -575,22 +577,23 @@ export class FrigateCardLiveCarousel extends LitElement {
}
protected _getCameraIDsOfNeighbors(): [string | null, string | null] {
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (this.viewFilterCameraID || !cameras || !this.view || !this.hass) {
const cameraIDs = this.cameraManager
? [...this.cameraManager.getStore().getVisibleCameraIDs()]
: [];
if (this.viewFilterCameraID || cameraIDs.length <= 1 || !this.view || !this.hass) {
return [null, null];
}
const cameraID = this.viewFilterCameraID ?? this.view.camera;
const keys = Array.from(cameras.keys());
const currentIndex = keys.indexOf(cameraID);
const currentIndex = cameraIDs.indexOf(cameraID);
if (currentIndex < 0 || cameras.size <= 1) {
if (currentIndex < 0) {
return [null, null];
}
return [
keys[currentIndex > 0 ? currentIndex - 1 : cameras.size - 1],
keys[currentIndex + 1 < cameras.size ? currentIndex + 1 : 0],
cameraIDs[currentIndex > 0 ? currentIndex - 1 : cameraIDs.length - 1],
cameraIDs[currentIndex + 1 < cameraIDs.length ? currentIndex + 1 : 0],
];
}
@@ -608,18 +611,19 @@ export class FrigateCardLiveCarousel extends LitElement {
const hasMultipleCameras = slides.length > 1;
const [prevID, nextID] = this._getCameraIDsOfNeighbors();
const overrideCameraID = (cameraID: string): string => {
const getOverrideCameraID = (cameraID: string): string => {
return this.view?.context?.live?.overrides?.get(cameraID) ?? cameraID;
};
const cameraMetadataPrevious = prevID
? this.cameraManager.getCameraMetadata(overrideCameraID(prevID))
? this.cameraManager.getCameraMetadata(getOverrideCameraID(prevID))
: null;
const cameraID = this.viewFilterCameraID ?? this.view.camera;
const cameraMetadataCurrent = this.cameraManager.getCameraMetadata(
overrideCameraID(this.viewFilterCameraID ?? this.view.camera),
getOverrideCameraID(cameraID),
);
const cameraMetadataNext = nextID
? this.cameraManager.getCameraMetadata(overrideCameraID(nextID))
? this.cameraManager.getCameraMetadata(getOverrideCameraID(nextID))
: null;
const titleConfig = getDefaultTitleConfigForView(
@@ -656,6 +660,10 @@ export class FrigateCardLiveCarousel extends LitElement {
if (this._refTitleControl.value) {
this._refTitleControl.value.show();
}
this._mediaHasLoaded = true;
}}
@frigate-card:media:unloaded=${() => {
this._mediaHasLoaded = false;
}}
>
<frigate-card-next-previous-control
@@ -688,6 +696,14 @@ export class FrigateCardLiveCarousel extends LitElement {
>
</frigate-card-next-previous-control>
</frigate-card-carousel>
<frigate-card-ptz
.hass=${this.hass}
.config=${this.overriddenLiveConfig.controls.ptz}
.cameraManager=${this.cameraManager}
.cameraID=${cameraID}
.forceVisibility=${this._mediaHasLoaded && this.view.context?.live?.ptzVisible}
>
</frigate-card-ptz>
${cameraMetadataCurrent && titleConfig
? html`<frigate-card-title-control
${ref(this._refTitleControl)}
@@ -846,9 +862,6 @@ export class FrigateCardLiveProvider
);
}
/**
* Component disconnected callback.
*/
disconnectedCallback(): void {
this._isVideoMediaLoaded = false;
}
@@ -860,9 +873,6 @@ export class FrigateCardLiveProvider
this._isVideoMediaLoaded = true;
}
/**
* Called before each update.
*/
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('load')) {
if (!this.load) {
@@ -915,10 +925,6 @@ export class FrigateCardLiveProvider
: template;
}
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult | void {
if (!this.load || !this.hass || !this.liveConfig || !this.cameraConfig) {
return;
@@ -941,6 +947,8 @@ export class FrigateCardLiveProvider
return;
}
if (stateObj.state === 'unavailable') {
dispatchMediaUnloadedEvent(this);
// An unavailable camera gets a message rendered in place vs dispatched,
// as this may be a common occurrence (e.g. Frigate cameras that stop
// receiving frames). Otherwise a single temporarily unavailable camera
+8 -8
View File
@@ -167,8 +167,8 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_ev: CustomEvent<{ value: unknown }>,
): Promise<void> {
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (!this.hass || !cameras || !this.cameraManager || !this.view) {
const visibleCameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
if (!this.hass || !visibleCameraIDs || !this.cameraManager || !this.view) {
return;
}
@@ -182,7 +182,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
};
const cameraIDs =
getArrayValueAsSet(this._refCamera.value?.value) ?? new Set(cameras.keys());
getArrayValueAsSet(this._refCamera.value?.value) ?? visibleCameraIDs;
const mediaType = this._refMediaType.value?.value as
| MediaFilterMediaType
| undefined;
@@ -268,9 +268,9 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('cameraManager')) {
const cameras = this.cameraManager?.getStore().getVisibleCameras();
const cameras = this.cameraManager?.getStore().getVisibleCameraIDs();
if (cameras) {
this._cameraOptions = Array.from(cameras.keys()).map((cameraID) => ({
this._cameraOptions = [...cameras].map((cameraID) => ({
value: cameraID,
label: this.hass
? this.cameraManager?.getCameraMetadata(cameraID)?.title ?? ''
@@ -319,8 +319,8 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
protected _getDefaultsFromView(): MediaFilterCoreDefaults | null {
const queries = this.view?.query?.getQueries();
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (!this.view || !queries || !cameras) {
const visibleCameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
if (!this.view || !queries || !visibleCameraIDs) {
return null;
}
@@ -337,7 +337,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
);
// Special note: If all visible cameras are selected, this is the same as no
// selector at all.
if (cameraIDSets.length === 1 && !isEqual(queries[0].cameraIDs, cameras)) {
if (cameraIDSets.length === 1 && !isEqual(queries[0].cameraIDs, visibleCameraIDs)) {
cameraIDs = [...queries[0].cameraIDs];
}
+1 -1
View File
@@ -97,7 +97,7 @@ export class FrigateCardMenu extends LitElement {
* @param action The action to check.
* @returns `true` if the action toggles the menu, `false` otherwise.
*/
protected _isMenuToggleAction(action: ActionType | undefined): boolean {
protected _isMenuToggleAction(action: ActionType | null): boolean {
// Determine if this action is a Frigate card action, if so handle it
// internally.
if (!action) {
+131
View File
@@ -0,0 +1,131 @@
import { HASSDomEvent, HomeAssistant } from '@dermotduffy/custom-card-helpers';
import {
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
unsafeCSS
} from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { actionHandler } from '../action-handler-directive.js';
import { CameraManager } from '../camera-manager/manager.js';
import { PTZController } from '../components-lib/ptz-controller.js';
import { Actions, FrigateCardPTZConfig } from '../config/types.js';
import { localize } from '../localize/localize.js';
import ptzStyle from '../scss/ptz.scss';
import { frigateCardHasAction } from '../utils/action.js';
@customElement('frigate-card-ptz')
export class FrigateCardPTZ extends LitElement {
@property({ attribute: false })
public hass?: HomeAssistant;
@property({ attribute: false })
public config?: FrigateCardPTZConfig;
@property({ attribute: false })
public cameraManager?: CameraManager;
@property({ attribute: false })
public cameraID?: string;
@property({ attribute: false })
public forceVisibility?: boolean;
protected _controller = new PTZController(this);
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('config')) {
this._controller.setConfig(this.config);
}
if (changedProps.has('hass')) {
this._controller.setHASS(this.hass);
}
if (changedProps.has('cameraManager') || changedProps.has('cameraID')) {
this._controller.setCamera(this.cameraManager, this.cameraID);
}
if (changedProps.has('forceVisibility')) {
this._controller.setForceVisibility(this.forceVisibility);
}
}
protected render(): TemplateResult | void {
if (!this._controller.shouldDisplay()) {
return;
}
const renderIcon = (
name: string,
icon: string,
actions: Actions | null,
): TemplateResult => {
const classes = {
[name]: true,
disabled: !actions,
};
return html`<ha-icon
class=${classMap(classes)}
icon=${icon}
.actionHandler=${actionHandler({
hasHold: frigateCardHasAction(actions?.hold_action),
hasDoubleClick: frigateCardHasAction(actions?.double_tap_action),
})}
.title=${localize(`elements.ptz.${name}`)}
@action=${(ev: HASSDomEvent<{ action: string }>) =>
this._controller.handleAction(ev, actions)}
></ha-icon>`;
};
const config = this._controller.getConfig();
const actionsZoomIn = this._controller.getPTZActions('zoom_in');
const actionsZoomOut = this._controller.getPTZActions('zoom_out');
const actionsHome = this._controller.getPTZActions('home');
return html` <div class="ptz">
${!config?.hide_pan_tilt
? html`<div class="ptz-move">
${renderIcon(
'right',
'mdi:arrow-right',
this._controller.getPTZActions('right'),
)}
${renderIcon(
'left',
'mdi:arrow-left',
this._controller.getPTZActions('left'),
)}
${renderIcon('up', 'mdi:arrow-up', this._controller.getPTZActions('up'))}
${renderIcon(
'down',
'mdi:arrow-down',
this._controller.getPTZActions('down'),
)}
</div>`
: ''}
${!config?.hide_zoom && (actionsZoomIn || actionsZoomOut)
? html` <div class="ptz-zoom">
${renderIcon('zoom_in', 'mdi:plus', actionsZoomIn)}
${renderIcon('zoom_out', 'mdi:minus', actionsZoomOut)}
</div>`
: html``}
${!config?.hide_home && actionsHome
? html`
<div class="ptz-home">${renderIcon('home', 'mdi:home', actionsHome)}</div>
`
: html``}
</div>`;
}
static get styles(): CSSResultGroup {
return unsafeCSS(ptzStyle);
}
}
declare global {
interface HTMLElementTagNameMap {
'frigate-card-ptz': FrigateCardPTZ;
}
}
+2 -2
View File
@@ -16,7 +16,6 @@ import {
import basicBlockStyle from '../scss/basic-block.scss';
import { ClipsOrSnapshotsOrAll, ExtendedHomeAssistant } from '../types.js';
import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js';
import { getAllDependentCameras } from '../utils/camera.js';
import { changeViewToRecentEventsForCameraAndDependents } from '../utils/media-to-view';
import { View } from '../view/view.js';
import './surround-basic.js';
@@ -140,7 +139,8 @@ export class FrigateCardSurround extends LitElement {
if (this.view?.is('live')) {
return this.view.isGrid()
? this.cameraManager?.getStore().getVisibleCameraIDs() ?? null
: getAllDependentCameras(this.cameraManager, this.view.camera);
: this.cameraManager?.getStore().getAllDependentCameras(this.view.camera) ??
null;
}
if (this.view.isViewerView()) {
return this.view.query?.getQueryCameraIDs() ?? null;
+2
View File
@@ -15,6 +15,7 @@ import { ExtendedHomeAssistant } from '../types.js';
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
import { dispatchFrigateCardEvent } from '../utils/basic.js';
import { CarouselDirection } from '../utils/embla/carousel-controller.js';
import AutoSize from '../utils/embla/plugins/auto-size/auto-size.js';
import { MediaQueriesResults } from '../view/media-queries-results';
import { View } from '../view/view.js';
import './carousel.js';
@@ -136,6 +137,7 @@ export class FrigateCardThumbnailCarousel extends LitElement {
return html`<frigate-card-carousel
direction=${direction}
.plugins=${[AutoSize()]}
.selected=${this._getSelectedSlide() ?? 0}
.dragFree=${true}
>
+48 -29
View File
@@ -9,10 +9,17 @@ import {
import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { CameraManager } from '../camera-manager/manager.js';
import { ConditionsManagerEpoch, getOverridesByKey } from '../card-controller/conditions-manager.js';
import {
ConditionsManagerEpoch,
getOverridesByKey,
} from '../card-controller/conditions-manager.js';
import {
CardWideConfig,
FrigateCardConfig,
RawFrigateCardConfig,
} from '../config/types.js';
import viewsStyle from '../scss/views.scss';
import { ExtendedHomeAssistant } from '../types.js';
import { ConfigManager } from '../card-controller/config-manager.js';
import { ResolvedMediaCache } from '../utils/ha/resolved-media.js';
import { View } from '../view/view.js';
import './surround.js';
@@ -32,7 +39,16 @@ export class FrigateCardViews extends LitElement {
public cameraManager?: CameraManager;
@property({ attribute: false })
public configManager?: ConfigManager;
public nonOverriddenConfig?: FrigateCardConfig;
@property({ attribute: false })
public overriddenConfig?: FrigateCardConfig;
@property({ attribute: false })
public cardWideConfig?: CardWideConfig;
@property({ attribute: false })
public rawConfig?: RawFrigateCardConfig;
@property({ attribute: false })
public resolvedMediaCache?: ResolvedMediaCache;
@@ -94,21 +110,21 @@ export class FrigateCardViews extends LitElement {
return (
// Special case: Never preload for diagnostics -- we want that to be as
// minimal as possible.
!!this.configManager?.getConfig()?.live.preload && !this.view?.is('diagnostics')
!!this.overriddenConfig?.live.preload && !this.view?.is('diagnostics')
);
}
protected render(): TemplateResult | void {
const config = this.configManager?.getConfig();
const nonOverriddenConfig = this.configManager?.getNonOverriddenConfig();
const cardWideConfig = this.configManager?.getCardWideConfig();
const rawConfig = this.configManager?.getRawConfig();
// Only essential items should be added to the below list, since we want the
// overall views pane to render in ~almost all cases (e.g. for a camera
// initialization error to display, `view` and `cameraConfig` may both be
// undefined, but we still want to render).
if (!this.hass || !config || !nonOverriddenConfig || !cardWideConfig) {
if (
!this.hass ||
!this.overriddenConfig ||
!this.nonOverriddenConfig ||
!this.cardWideConfig
) {
return html``;
}
@@ -122,17 +138,17 @@ export class FrigateCardViews extends LitElement {
};
const thumbnailConfig = this.view?.is('live')
? config.live.controls.thumbnails
? this.overriddenConfig.live.controls.thumbnails
: this.view?.isViewerView()
? config.media_viewer.controls.thumbnails
? this.overriddenConfig.media_viewer.controls.thumbnails
: this.view?.is('timeline')
? config.timeline.controls.thumbnails
? this.overriddenConfig.timeline.controls.thumbnails
: undefined;
const miniTimelineConfig = this.view?.is('live')
? config.live.controls.timeline
? this.overriddenConfig.live.controls.timeline
: this.view?.isViewerView()
? config.media_viewer.controls.timeline
? this.overriddenConfig.media_viewer.controls.timeline
: undefined;
const cameraConfig = this.view
@@ -144,16 +160,16 @@ export class FrigateCardViews extends LitElement {
.hass=${this.hass}
.view=${this.view}
.fetchMedia=${this.view?.is('live')
? config.live.controls.thumbnails.media
? this.overriddenConfig.live.controls.thumbnails.media
: undefined}
.thumbnailConfig=${!this.hide ? thumbnailConfig : undefined}
.timelineConfig=${!this.hide ? miniTimelineConfig : undefined}
.cameraManager=${this.cameraManager}
.cardWideConfig=${cardWideConfig}
.cardWideConfig=${this.cardWideConfig}
>
${!this.hide && this.view?.is('image') && cameraConfig
? html` <frigate-card-image
.imageConfig=${config.image}
.imageConfig=${this.overriddenConfig.image}
.view=${this.view}
.hass=${this.hass}
.cameraConfig=${cameraConfig}
@@ -165,9 +181,9 @@ export class FrigateCardViews extends LitElement {
? html` <frigate-card-gallery
.hass=${this.hass}
.view=${this.view}
.galleryConfig=${config.media_gallery}
.galleryConfig=${this.overriddenConfig.media_gallery}
.cameraManager=${this.cameraManager}
.cardWideConfig=${cardWideConfig}
.cardWideConfig=${this.cardWideConfig}
>
</frigate-card-gallery>`
: ``}
@@ -176,10 +192,10 @@ export class FrigateCardViews extends LitElement {
<frigate-card-viewer
.hass=${this.hass}
.view=${this.view}
.viewerConfig=${config.media_viewer}
.viewerConfig=${this.overriddenConfig.media_viewer}
.resolvedMediaCache=${this.resolvedMediaCache}
.cameraManager=${this.cameraManager}
.cardWideConfig=${cardWideConfig}
.cardWideConfig=${this.cardWideConfig}
>
</frigate-card-viewer>
`
@@ -188,16 +204,16 @@ export class FrigateCardViews extends LitElement {
? html` <frigate-card-timeline
.hass=${this.hass}
.view=${this.view}
.timelineConfig=${config.timeline}
.timelineConfig=${this.overriddenConfig.timeline}
.cameraManager=${this.cameraManager}
.cardWideConfig=${cardWideConfig}
.cardWideConfig=${this.cardWideConfig}
>
</frigate-card-timeline>`
: ``}
${!this.hide && this.view?.is('diagnostics')
? html` <frigate-card-diagnostics
.hass=${this.hass}
.rawConfig=${rawConfig}
.rawConfig=${this.rawConfig}
>
</frigate-card-diagnostics>`
: ``}
@@ -213,12 +229,15 @@ export class FrigateCardViews extends LitElement {
<frigate-card-live
.hass=${this.hass}
.view=${this.view}
.nonOverriddenLiveConfig=${nonOverriddenConfig.live}
.overriddenLiveConfig=${config.live}
.nonOverriddenLiveConfig=${this.nonOverriddenConfig.live}
.overriddenLiveConfig=${this.overriddenConfig.live}
.conditionsManagerEpoch=${this.conditionsManagerEpoch}
.liveOverrides=${getOverridesByKey('live', config.overrides)}
.liveOverrides=${getOverridesByKey(
'live',
this.overriddenConfig.overrides,
)}
.cameraManager=${this.cameraManager}
.cardWideConfig=${cardWideConfig}
.cardWideConfig=${this.cardWideConfig}
.microphoneStream=${this.microphoneStream}
class="${classMap(liveClasses)}"
>