Merge pull request #683 from dermotduffy/dark-mode-revisit

Allow the provided URL to be used as the stock loading image.
This commit is contained in:
Dermot Duffy
2022-06-15 20:03:18 -07:00
committed by GitHub
3 changed files with 55 additions and 38 deletions
+2 -2
View File
@@ -217,7 +217,7 @@ See the [fully expanded view configuration example](#config-expanded-view) for h
| - | - | - | - |
| `default` | `live` | :white_check_mark: | The view to show in the card by default. The default camera is the first one listed. See [views](#views) below.|
| `camera_select` | `current` | :white_check_mark: | The view to show when a new camera is selected (e.g. in the camera menu). If `current` the view is unchanged when a new camera is selected. Other acceptable values may be seen at [views](#views) below.|
| `dark_mode` | `off` | :white_check_mark: | Whether or not to turn dark mode `on`, `off` or `auto` to automatically turn on if the card `timeout_seconds` has expired (i.e. card has been left unattended for that period of time) or if dark mode is enabled in the HA profile theme setting. Dark mode dims the brightness by `50%`.|
| `dark_mode` | `off` | :white_check_mark: | Whether or not to turn dark mode `on`, `off` or `auto` to automatically turn on if the card `timeout_seconds` has expired (i.e. card has been left unattended for that period of time) or if dark mode is enabled in the HA profile theme setting. Dark mode dims the brightness by `25%`.|
| `timeout_seconds` | `300` | :white_check_mark: | A numbers of seconds of inactivity after user interaction, after which the card will reset to the default configured view (i.e. 'screensaver' functionality). Inactivity is defined as lack of mouse/touch interaction with the Frigate card. If the default view occurs sooner (e.g. via `update_seconds` or manually) the timer will be stopped. `0` means disable this functionality. |
| `update_seconds` | `0` | :white_check_mark: | A number of seconds after which to automatically update/refresh the default view. See [card updates](#card-updates) below for behavior and usecases. If the default view occurs sooner (e.g. manually) the timer will start over. `0` disables this functionality.|
| `update_force` | `false` | :white_check_mark: | Whether automated card updates/refreshes should ignore user interaction. See [card updates](#card-updates) below for behavior and usecases.|
@@ -515,7 +515,7 @@ See the [fully expanded image configuration example](#config-expanded-image) for
| Option | Default | Overridable | Description |
| - | - | - | - |
| `mode` | `url` | :white_check_mark: | Mode of the the `image` [view](#views). Value must be one of `url` (to fetch an arbitrary image URL), `camera` (to show a still of the currently selected camera using either `camera_entity` or `webrtc_card.entity` in that order of precedence), or `screensaver` (to show an [embedded stock Frigate card logo](https://github.com/dermotduffy/frigate-hass-card/blob/main/src/images/frigate-bird-in-sky.jpg)). In either `url` or `camera` mode, the `screensaver` content is used as a fallback if a URL is not specified or cannot be derived. |
| `url` | | :white_check_mark: | A static image URL to be used when the `mode` is set to `url`. Note that a `_t=[timestsamp]` query parameter will be automatically added to all URLs such that the image will not be cached by the browser.|
| `url` | | :white_check_mark: | A static image URL to be used when the `mode` is set to `url` or when a temporary image is required (e.g. may appear momentarily prior to load of a camera snapshot in the `camera` mode). Note that a `_t=[timestsamp]` query parameter will be automatically added to all URLs such that the image will not be cached by the browser.|
| `refresh_seconds` | 0 | :white_check_mark: | The image will be refreshed at least every `refresh_seconds` (it may refresh more frequently, e.g. whenever Home Assistant updates its camera security token). `0` implies no refreshing. |
| `actions` | | :white_check_mark: | Actions to use for the `image` view. See [actions](#actions) below.|
+52 -35
View File
@@ -1,4 +1,5 @@
import { HomeAssistant } from 'custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
import {
CSSResultGroup,
html,
@@ -45,8 +46,11 @@ export class FrigateCardImage extends LitElement {
* Get the camera entity for the current camera configuration.
* @returns The entity or undefined if no camera entity is available.
*/
protected _getCameraEntity(): string | undefined {
return this.cameraConfig?.camera_entity || this.cameraConfig?.webrtc_card?.entity;
protected _getCameraEntity(): string | null {
return (
(this.cameraConfig?.camera_entity || this.cameraConfig?.webrtc_card?.entity) ??
null
);
}
/**
@@ -59,23 +63,7 @@ export class FrigateCardImage extends LitElement {
return false;
}
// If camera mode is enabled, reject all updates if hass is older than
// HASS_REJECTION_CUTOFF_MS or if HASS is not currently connected. By using
// an older hass (even if it is not the property being updated), we run the
// risk that the JS has an old access token for the camera, and that results
// in a notification on the HA UI about a failed login. See
// https://github.com/dermotduffy/frigate-hass-card/issues/398 .
const cameraEntity = this._getCameraEntity();
const state = cameraEntity ? this.hass.states[cameraEntity] : undefined;
if (
this.imageConfig?.mode === 'camera' &&
(!this.hass.connected ||
!state ||
Date.now() - Date.parse(state.last_updated) >= HASS_REJECTION_CUTOFF_MS)
) {
return false;
}
if (
changedProps.has('hass') &&
changedProps.size == 1 &&
@@ -113,8 +101,16 @@ export class FrigateCardImage extends LitElement {
}
}
// If the camera changed, immediately discard the old value.
if (changedProps.has('cameraConfig')) {
// If the camera or view changed, immediately discard the old value (view to
// allow pressing of the image button to fetch a fresh image). Likewise, if
// the state is not acceptable, discard the old value (to allow a stock or
// backup image to be displayed).
if (
changedProps.has('cameraConfig') ||
changedProps.has('view') ||
(this.imageConfig?.mode === 'camera' &&
!this._getAcceptableState(this._getCameraEntity()))
) {
this._cachedValueController?.clearValue();
}
@@ -123,6 +119,26 @@ export class FrigateCardImage extends LitElement {
}
}
/**
* Determine if a given entity is acceptable as the basis for an image render
* (detects old or disconnected states). Using an old state is problematic as
* it runs the risk that the JS has an old access token for the camera, and
* that results in a notification on the HA UI about a failed login. See:
* https://github.com/dermotduffy/frigate-hass-card/issues/398 .
* @param entity The entity.
* @returns The state or null if not acceptable.
*/
protected _getAcceptableState(entity: string | null): HassEntity | null {
const state = (entity ? this.hass?.states[entity] : null) ?? null;
return !!this.hass &&
this.hass.connected &&
!!state &&
Date.now() - Date.parse(state.last_updated) < HASS_REJECTION_CUTOFF_MS
? state
: null;
}
/**
* Component connected callback.
*/
@@ -155,7 +171,7 @@ export class FrigateCardImage extends LitElement {
// (401), see:
// https://github.com/dermotduffy/frigate-hass-card/issues/398
this._cachedValueController?.clearValue();
this._forceStockImage();
this._forceSafeImage();
} else {
// If the document is freshly re-visible, immediately re-render it to
// restore the image src. If the HASS object is old (i.e. browser tab was
@@ -177,26 +193,25 @@ export class FrigateCardImage extends LitElement {
}
protected _getImageSource(): string {
if (this.imageConfig?.mode === 'url' && this.imageConfig?.url) {
return this._buildImageURL(this.imageConfig.url);
} else if (this.hass && this.imageConfig?.mode === 'camera') {
const entity = this._getCameraEntity();
if (entity) {
const state = this.hass.states[entity];
if (state && state.attributes.entity_picture) {
return this._buildImageURL(state.attributes.entity_picture);
}
if (this.hass && this.imageConfig?.mode === 'camera') {
const state = this._getAcceptableState(this._getCameraEntity());
if (state?.attributes.entity_picture) {
return this._buildImageURL(state.attributes.entity_picture);
}
}
if (this.imageConfig?.mode !== 'screensaver' && this.imageConfig?.url) {
return this._buildImageURL(this.imageConfig.url);
}
return defaultImage;
}
/**
* Force the img element to the stock image.
* Force the img element to a safe image.
*/
protected _forceStockImage(): void {
protected _forceSafeImage(stockOnly?: boolean): void {
if (this._refImage.value) {
this._refImage.value.src = defaultImage;
this._refImage.value.src =
!stockOnly && this.imageConfig?.url ? this.imageConfig.url : defaultImage;
}
}
@@ -212,8 +227,10 @@ export class FrigateCardImage extends LitElement {
@error=${() => {
if (this.imageConfig?.mode === 'camera') {
// In camera mode, the user has likely not made an error, but HA
// may be unavailble, so show the stock image.
this._forceStockImage();
// may be unavailble, so show the stock image. Don't let the URL
// override the stock image in this case, as this could create an
// error loop if that URL subsequently failed to load.
this._forceSafeImage(true);
} else if (this.imageConfig?.mode === 'url') {
// In url mode, the user likely specified a URL that cannot be
// resolved. Show an error message.
+1 -1
View File
@@ -6,7 +6,7 @@
}
:host([dark]) {
filter: brightness(50%);
filter: brightness(75%);
}
div.main {