From d6f02828867058c29fa307d87afd94b20cd72650 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 24 Jul 2022 19:36:32 -0700 Subject: [PATCH] Rework the error message when entity is unavailable. --- .vscode/i18n-ally-reviews.yml | 16 ++- src/card.ts | 1 - src/components/live.ts | 177 +++++++++++++++++++++--------- src/components/message.ts | 8 +- src/localize/languages/en.json | 3 +- src/localize/languages/pt-BR.json | 3 +- 6 files changed, 148 insertions(+), 60 deletions(-) diff --git a/.vscode/i18n-ally-reviews.yml b/.vscode/i18n-ally-reviews.yml index 95de2e1b..57543941 100644 --- a/.vscode/i18n-ally-reviews.yml +++ b/.vscode/i18n-ally-reviews.yml @@ -1,3 +1,17 @@ # Review comments generated by i18n-ally. Please commit this file. -{} +reviews: + error.live_camera_not_found: + locales: + pt-BR: + translation_candidate: + source: en + text: A camera_entity configurada não foi encontrada + time: '2022-07-24T05:32:49.977Z' + error.live_camera_unavailable: + locales: + pt-BR: + translation_candidate: + source: en + text: Câmera indisponível + time: '2022-07-24T05:34:22.088Z' diff --git a/src/card.ts b/src/card.ts index c30e3f47..2ddf493a 100644 --- a/src/card.ts +++ b/src/card.ts @@ -1904,7 +1904,6 @@ export class FrigateCard extends LitElement { .conditionState=${this._conditionState} .liveOverrides=${getOverridesByKey(this._getConfig().overrides, 'live')} .cameras=${this._cameras} - .preloaded=${this._getConfig().live.preload && !this._view.is('live')} class="${classMap(liveClasses)}" > diff --git a/src/components/live.ts b/src/components/live.ts index ea4a81db..d2d77d64 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -14,10 +14,12 @@ import { import { customElement, property, state } from 'lit/decorators.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; import { guard } from 'lit/directives/guard.js'; +import { keyed } from 'lit/directives/keyed.js'; import { until } from 'lit/directives/until.js'; import { ConditionState, getOverriddenConfig } from '../card-condition.js'; import { dispatchFrigateCardErrorEvent, + dispatchMessageEvent, renderProgressIndicator, } from '../components/message.js'; import { localize } from '../localize/localize.js'; @@ -37,6 +39,7 @@ import { LiveOverrides, LiveProvider, MediaShowInfo, + Message, TransitionEffect, WebRTCCardConfig, } from '../types.js'; @@ -89,33 +92,66 @@ export class FrigateCardLive extends LitElement { @property({ attribute: false, hasChanged: contentsChanged }) public liveOverrides?: LiveOverrides; - set preloaded(preloaded: boolean) { - this._preloaded = preloaded; + // Whether or not the live view is currently in the background (i.e. preloaded + // but not visible) + @state() + protected _inBackground?: boolean = true; - if (!preloaded && this._savedMediaShowInfo) { + // Intersection handler is used to detect when the live view flips between + // foreground and background (in preload mode). + protected _intersectionObserver: IntersectionObserver; + + // MediaShowInfo object and message from the underlying live object. In the + // case of pre-loading these may be propagated upwards later. + protected _savedMediaShowInfo: MediaShowInfo | null = null; + protected _messageReceivedPostRender = false; + protected _renderKey = 0; + + constructor() { + super(); + this._intersectionObserver = new IntersectionObserver( + this._intersectionHandler.bind(this), + ); + } + + /** + * Called when the live view intersects with the viewport. + * @param entries The IntersectionObserverEntry entries (should be only 1). + */ + protected _intersectionHandler(entries: IntersectionObserverEntry[]): void { + this._inBackground = entries.every((entry) => !entry.isIntersecting); + + if ( + !this._inBackground && + !this._messageReceivedPostRender && + this._savedMediaShowInfo + ) { + // If this isn't being rendered in the background, the last render did not + // generate a message and there's a saved MediaInfo, dispatch it upwards. dispatchExistingMediaShowInfoAsEvent(this, this._savedMediaShowInfo); } + + // Trigger a re-render which may be necessary if the prior render resulted + // in a message. + if (this._messageReceivedPostRender && !this._inBackground) { + this.requestUpdate(); + } } - // Whether or not the live view is currently being preloaded. - @state() - protected _preloaded?: boolean; - - // MediaShowInfo object from the underlying live object. In the case of - // pre-loading it may be propagated upwards later. - protected _savedMediaShowInfo?: MediaShowInfo; + /** + * Component connected callback. + */ + connectedCallback(): void { + this._intersectionObserver.observe(this); + super.connectedCallback(); + } /** - * Handler for media show events that special cases preloaded live views. - * @param e The media show event. + * Component disconnected callback. */ - protected _mediaShowHandler(e: CustomEvent): void { - this._savedMediaShowInfo = e.detail; - if (this._preloaded) { - // If live is being pre-loaded, don't let the event propagate upwards yet - // as the media is not really being shown. - e.stopPropagation(); - } + disconnectedCallback(): void { + super.disconnectedCallback(); + this._intersectionObserver.disconnect(); } /** @@ -148,37 +184,55 @@ export class FrigateCardLive extends LitElement { // independently override the liveConfig to reflect the camera in the // carousel (not necessarily the selected camera). // - Fetching of thumbnails is disabled as long as live view is the - // background (preloaded) rather than foreground (not preloaded). - return html` - { - if (this._preloaded) { - // Don't allow change-view events to propagate upwards if the card - // is only preloaded rather than being live displayed. These events - // could be triggered if the camera is switched and the carousel - // moves to focus on that camera -- as the card isn't actually being - // displayed, do not allow the view to actually be updated. + ?fetch=${!this._inBackground} + @frigate-card:message=${(ev: CustomEvent) => { + this._renderKey++; + this._messageReceivedPostRender = true; + if (this._inBackground) { + ev.stopPropagation(); + } + }} + @frigate-card:media-show=${(ev: CustomEvent) => { + this._savedMediaShowInfo = ev.detail; + if (this._inBackground) { + ev.stopPropagation(); + } + }} + @frigate-card:change-view=${(ev: CustomEvent) => { + if (this._inBackground) { ev.stopPropagation(); } }} > - - `; + + + `, + )}`; + + this._messageReceivedPostRender = false; + return result; } /** @@ -207,7 +261,7 @@ export class FrigateCardLiveCarousel extends LitElement { public liveOverrides?: LiveOverrides; @property({ attribute: false }) - public preloaded?: boolean; + public inBackground?: boolean; @property({ attribute: false }) public conditionState?: ConditionState; @@ -235,7 +289,10 @@ export class FrigateCardLiveCarousel extends LitElement { this.view?.camera != oldView.camera ) { const slide: number | undefined = this._cameraToSlide[this.view.camera]; - if (slide !== undefined && slide !== frigateCardCarousel.getCarouselSelected()?.index) { + if ( + slide !== undefined && + slide !== frigateCardCarousel.getCarouselSelected()?.index + ) { frigateCardCarousel.carouselScrollTo(slide); } } @@ -244,11 +301,11 @@ export class FrigateCardLiveCarousel extends LitElement { if ( frigateCardMediaCarousel && frigateCardCarousel && - changedProperties.has('preloaded') + changedProperties.has('inBackground') ) { - // If this has changed to preloaded (i.e. is now loaded but in the - // background) take the appropriate play/pause/mute/unmute actions. - if (this.preloaded) { + // If this has changed to be in the background (i.e. preloaded but not + // visible) take the appropriate play/pause/mute/unmute actions. + if (this.inBackground) { frigateCardMediaCarousel.autoPause(); frigateCardMediaCarousel.autoMute(); } else { @@ -369,8 +426,7 @@ export class FrigateCardLiveCarousel extends LitElement { protected _setViewHandler(): void { const selectedCameraIndex = this._refMediaCarousel.value ?.frigateCardCarousel() - ?.getCarouselSelected() - ?.index; + ?.getCarouselSelected()?.index; if (selectedCameraIndex === undefined || !this.view || !this.cameras) { return; } @@ -514,7 +570,7 @@ export class FrigateCardLiveCarousel extends LitElement { .titlePopupConfig=${config.controls.title} transitionEffect=${this._getTransitionEffect()} @frigate-card:carousel:settle=${this._setViewHandler.bind(this)} - > + >
- ${this.message ? html`${this.message}` : ''} + ${this.message + ? html`${this.message}${this.context && typeof this.context === 'string' + ? ': ' + this.context + : ''}` + : ''} - ${this.context + ${this.context && typeof this.context !== 'string' ? html`
${JSON.stringify(this.context, null, 2)}
` : ''}
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 7acc24f5..bb21640d 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -328,7 +328,8 @@ "invalid_response": "Received invalid response from Home Assistant for request", "jsmpeg_no_player": "Could not start JSMPEG player", "jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path", - "live_camera_unavailable": "The configured camera_entity is unavailable", + "live_camera_not_found": "The configured camera_entity was not found", + "live_camera_unavailable": "Camera unavailable", "no_camera_id": "Could not determine camera id for the following camera, may need to set 'id' parameter manually", "no_camera_name": "Could not determine a Frigate camera name for camera (or one of its dependents), please specify either 'camera_entity' or 'camera_name'", "no_cameras": "No valid cameras found, you must configure at least one camera entry", diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index 1f7f6b39..8b12f17c 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -328,7 +328,8 @@ "invalid_response": "Resposta inválida recebida do Home Assistant para a solicitação", "jsmpeg_no_player": "Não foi possível iniciar o player JSMPEG", "jsmpeg_no_sign": "Não foi possível recuperar ou assinar o caminho do websocket JSMPEG", - "live_camera_unavailable": "camera_entity configurada não está disponível", + "live_camera_not_found": "", + "live_camera_unavailable": "", "no_camera_id": "Não foi possível determinar o ID da câmera para a câmera a seguir, pode ser necessário definir o parâmetro 'id' manualmente", "no_camera_name": "Não foi possível determinar o nome da câmera da Frigate, especifique 'camera_entity' ou 'camera_name' para a câmera a seguir", "no_cameras": "Nenhuma câmera válida encontrada, você deve configurar pelo menos uma câmera",