diff --git a/README.md b/README.md index 0b56faa6..e19d9c3a 100644 --- a/README.md +++ b/README.md @@ -152,12 +152,13 @@ view: | - | - | - | - | | `default` | `live` | :heavy_multiplication_x: | The view to show in the card by default. The default camera is the first one listed. See [views](#views) below.| | `camera_select` | `current` | :heavy_multiplication_x: | 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` | :heavy_multiplication_x: | 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%`.| | `timeout_seconds` | `300` | :heavy_multiplication_x: | 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` | :heavy_multiplication_x: | 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` | :heavy_multiplication_x: | Whether automated card updates/refreshes should ignore user interaction. See [card updates](#card-updates) below for behavior and usecases.| | `update_entities` | | :heavy_multiplication_x: | **YAML only**: A list of entity ids that should cause the view to reset to the default. See [card updates](#card-updates) below for behavior and usecases.| | `update_cycle_camera` | `false` | :heavy_multiplication_x: | When set to `true` the selected camera is cycled on each default view change. | -| `render_entities` | | :heavy_multiplication_x: | **YAML only**: A list of entity ids that should cause the card to re-render 'in-place'. The view/camera is not changed. `update_*` flags to not pertain/relate to the behavior of this flag. This should **very** rarely be needed, but could be useful if the card is both setting and changing HA state of the same object as could be the case for some complex `card_mod` scenarios ([example](https://github.com/dermotduffy/frigate-hass-card/issues/343)). | +| `render_entities` | | :heavy_multiplication_x: | **YAML only**: A list of entity ids that should cause the card to re-render 'in-place'. The view/camera is not changed. `update_*` flags do not pertain/relate to the behavior of this flag. This should **very** rarely be needed, but could be useful if the card is both setting and changing HA state of the same object as could be the case for some complex `card_mod` scenarios ([example](https://github.com/dermotduffy/frigate-hass-card/issues/343)). | | `actions` | | :heavy_multiplication_x: | Actions to use for all views, individual actions may be overriden by view-specific actions. See [actions](#actions) below.| ### Menu Options diff --git a/src/card.ts b/src/card.ts index f73a3937..6c589271 100644 --- a/src/card.ts +++ b/src/card.ts @@ -193,7 +193,7 @@ export class FrigateCard extends LitElement { // Manually set hass in the menu, elements and image. This is to allow these // to update, without necessarily re-rendering the entire card (re-rendering - // interrupts clip playing). + // is expensive). if (this._hass) { if (this._menu) { this._menu.hass = this._hass; @@ -205,6 +205,9 @@ export class FrigateCard extends LitElement { this._image.hass = this._hass; } } + + // Dark mode may depend on HASS. + this._setLightOrDarkMode(); } /** @@ -648,6 +651,7 @@ export class FrigateCard extends LitElement { this._view = undefined; this._message = null; this._generateConditionState(); + this._setLightOrDarkMode(); } /** @@ -685,10 +689,6 @@ export class FrigateCard extends LitElement { }); this._generateConditionState(); - // The default view has been loaded, so can abandon any running - // 'screensaver' timer. - this._clearInteractionTimer(); - // Restart the update timer, so the default view is refreshed at a fixed // interval from now (if so configured). this._startUpdateTimer(); @@ -699,6 +699,22 @@ export class FrigateCard extends LitElement { } } + /** + * Set the light or dark mode. + */ + protected _setLightOrDarkMode(): void { + const needDarkMode = + this._config.view.dark_mode === 'on' || + (this._config.view.dark_mode === 'auto' && + (!this._interactionTimerID || this._hass?.themes.darkMode)); + + if (needDarkMode) { + this.setAttribute('dark', ''); + } else { + this.removeAttribute('dark'); + } + } + /** * Handle a change view event. * @param e The change view event. @@ -965,8 +981,11 @@ export class FrigateCard extends LitElement { if (this._getConfig().view.timeout_seconds) { this._interactionTimerID = window.setTimeout(() => { this._changeView(); + this._clearInteractionTimer(); + this._setLightOrDarkMode(); }, this._getConfig().view.timeout_seconds * 1000); } + this._setLightOrDarkMode(); } /** diff --git a/src/const.ts b/src/const.ts index cd300e66..193b9661 100644 --- a/src/const.ts +++ b/src/const.ts @@ -21,6 +21,7 @@ export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER = export const CONF_VIEW = 'view' as const; export const CONF_VIEW_CAMERA_SELECT = `${CONF_VIEW}.camera_select` as const; +export const CONF_VIEW_DARK_MODE = `${CONF_VIEW}.dark_mode` as const; export const CONF_VIEW_DEFAULT = `${CONF_VIEW}.default` as const; export const CONF_VIEW_TIMEOUT_SECONDS = `${CONF_VIEW}.timeout_seconds` as const; export const CONF_VIEW_UPDATE_CYCLE_CAMERA = `${CONF_VIEW}.update_cycle_camera` as const; diff --git a/src/editor.ts b/src/editor.ts index af83bfaa..851c2f21 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -81,6 +81,7 @@ import { CONF_TIMELINE_MEDIA, CONF_TIMELINE_WINDOW_SECONDS, CONF_VIEW_CAMERA_SELECT, + CONF_VIEW_DARK_MODE, CONF_VIEW_DEFAULT, CONF_VIEW_TIMEOUT_SECONDS, CONF_VIEW_UPDATE_CYCLE_CAMERA, @@ -343,6 +344,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { value: 'snapshots', label: localize('config.timeline.medias.snapshots') }, ]; + protected _darkModes = [ + { value: '', label: '' }, + { value: 'on', label: localize('config.view.dark_modes.on') }, + { value: 'off', label: localize('config.view.dark_modes.off') }, + { value: 'auto', label: localize('config.view.dark_modes.auto') }, + ]; + public setConfig(config: RawFrigateCardConfig): void { // Note: This does not use Zod to parse the configuration, so it may be // partially or completely invalid. It's more useful to have a partially @@ -833,6 +841,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_VIEW_CAMERA_SELECT, this._cameraSelectViewModes, )} + ${this._renderOptionSelector( + CONF_VIEW_DARK_MODE, + this._darkModes, + )} ${this._renderNumberInput(CONF_VIEW_TIMEOUT_SECONDS)} ${this._renderNumberInput(CONF_VIEW_UPDATE_SECONDS)} ${this._renderSwitch(CONF_VIEW_UPDATE_FORCE, defaults.view.update_force)} diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 71fc11fb..1a1a0969 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -34,6 +34,12 @@ }, "view": { "camera_select": "View for newly selected cameras", + "dark_mode": "Dark mode", + "dark_modes": { + "on": "On", + "off": "Off", + "auto": "Auto" + }, "default": "Default view", "views": { "live": "Live view", diff --git a/src/scss/card.scss b/src/scss/card.scss index 34f29ce7..f74a82a0 100644 --- a/src/scss/card.scss +++ b/src/scss/card.scss @@ -5,6 +5,10 @@ position: relative; } +:host([dark]) { + filter: brightness(50%); +} + .container { position: relative; overflow: auto; diff --git a/src/types.ts b/src/types.ts index df87f2cf..ad4a12e4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,6 +7,7 @@ import { MoreInfoActionConfig, NavigateActionConfig, NoActionConfig, + Themes, ToggleActionConfig, UrlActionConfig, } from 'custom-card-helpers'; @@ -402,6 +403,7 @@ const viewConfigDefault = { update_seconds: 0, update_force: false, update_cycle_camera: false, + dark_mode: 'off' as const, }; const viewConfigSchema = z .object({ @@ -417,6 +419,7 @@ const viewConfigSchema = z update_cycle_camera: z.boolean().default(viewConfigDefault.update_cycle_camera), update_entities: z.string().array().optional(), render_entities: z.string().array().optional(), + dark_mode: z.enum(['on', 'off', 'auto']).optional(), }) .merge(actionsSchema) .default(viewConfigDefault); @@ -931,6 +934,9 @@ const menuButtonSchema = z.discriminatedUnion('type', [ export type MenuButton = z.infer; export interface ExtendedHomeAssistant { hassUrl(path?): string; + themes: Themes & { + darkMode?: boolean + }; } export interface BrowseMediaQueryParametersBase {