Add option to choose view for newly selected cameras.

This commit is contained in:
Dermot Duffy
2022-01-29 17:17:56 -08:00
parent 65249fc82f
commit df93e572a2
6 changed files with 105 additions and 95 deletions
+2 -1
View File
@@ -138,6 +138,7 @@ view:
| Option | Default | Overridable | Description | | Option | Default | Overridable | Description |
| - | - | - | - | | - | - | - | - |
| `default` | `live` | :heavy_multiplication_x: | The view to show in the card by default. See [views](#views) below.| | `default` | `live` | :heavy_multiplication_x: | The view to show in the card by default. 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.|
| `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. | | `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_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_force` | `false` | :heavy_multiplication_x: | Whether automated card updates/refreshes should ignore user interaction. See [card updates](#card-updates) below for behavior and usecases.|
@@ -564,7 +565,7 @@ Parameters for the `custom:frigate-card-conditional` element:
|`download`|Download the displayed media.| |`download`|Download the displayed media.|
|`frigate_ui`|Open the Frigate UI at the configured URL.| |`frigate_ui`|Open the Frigate UI at the configured URL.|
|`fullscreen`|Toggle fullscreen.| |`fullscreen`|Toggle fullscreen.|
|`camera_select`|Select a given camera. Takes a single additional `camera` parameter with the [camera ID](#camera-ids) of the camera to select.| |`camera_select`|Select a given camera. Takes a single additional `camera` parameter with the [camera ID](#camera-ids) of the camera to select. Respects the value of `view.camera_select` to choose the appropriate view on the new camera.|
|`menu_toggle` | Show/hide the menu (for `hidden-*` mode menus). | |`menu_toggle` | Show/hide the menu (for `hidden-*` mode menus). |
<a name="views"></a> <a name="views"></a>
+5 -6
View File
@@ -10,11 +10,7 @@ import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
import { StyleInfo, styleMap } from 'lit/directives/style-map.js'; import { StyleInfo, styleMap } from 'lit/directives/style-map.js';
import { until } from 'lit/directives/until.js'; import { until } from 'lit/directives/until.js';
import { import { HomeAssistant, LovelaceCardEditor, getLovelace } from 'custom-card-helpers';
HomeAssistant,
LovelaceCardEditor,
getLovelace,
} from 'custom-card-helpers';
import screenfull from 'screenfull'; import screenfull from 'screenfull';
import { z } from 'zod'; import { z } from 'zod';
@@ -22,6 +18,7 @@ import {
Actions, Actions,
ActionType, ActionType,
CameraConfig, CameraConfig,
FrigateCardView,
RawFrigateCardConfig, RawFrigateCardConfig,
entitySchema, entitySchema,
frigateCardConfigSchema, frigateCardConfigSchema,
@@ -826,7 +823,9 @@ export class FrigateCard extends LitElement {
if (this._cameras?.has(camera) && this._view) { if (this._cameras?.has(camera) && this._view) {
this._changeView({ this._changeView({
view: new View({ view: new View({
view: this._view.view, view: this._getConfig().view.camera_select === 'current'
? this._view.view
: this._getConfig().view.camera_select as FrigateCardView,
camera: camera, camera: camera,
}), }),
}); });
+1
View File
@@ -19,6 +19,7 @@ export const CONF_CAMERAS_ARRAY_WEBRTC_URL = `${CONF_CAMERAS}.#.webrtc.url` as c
export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER = `${CONF_CAMERAS}.#.live_provider` as const; export const CONF_CAMERAS_ARRAY_LIVE_PROVIDER = `${CONF_CAMERAS}.#.live_provider` 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_DEFAULT = `${CONF_VIEW}.default` 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_TIMEOUT_SECONDS = `${CONF_VIEW}.timeout_seconds` as const;
export const CONF_VIEW_UPDATE_CYCLE_CAMERA = `${CONF_VIEW}.update_cycle_camera` as const; export const CONF_VIEW_UPDATE_CYCLE_CAMERA = `${CONF_VIEW}.update_cycle_camera` as const;
+88 -84
View File
@@ -56,6 +56,7 @@ import {
CONF_MENU_BUTTONS_SNAPSHOTS, CONF_MENU_BUTTONS_SNAPSHOTS,
CONF_MENU_BUTTON_SIZE, CONF_MENU_BUTTON_SIZE,
CONF_MENU_MODE, CONF_MENU_MODE,
CONF_VIEW_CAMERA_SELECT,
CONF_VIEW_DEFAULT, CONF_VIEW_DEFAULT,
CONF_VIEW_TIMEOUT_SECONDS, CONF_VIEW_TIMEOUT_SECONDS,
CONF_VIEW_UPDATE_CYCLE_CAMERA, CONF_VIEW_UPDATE_CYCLE_CAMERA,
@@ -238,12 +239,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
*/ */
protected _renderDropdown( protected _renderDropdown(
configPath: string, configPath: string,
dropdown: string[] | Record<string, string>, dropdown: string[] | Map<string, string>,
): TemplateResult | void { ): TemplateResult | void {
if (!this._config) { if (!this._config) {
return; return;
} }
const keys = Array.isArray(dropdown) ? dropdown : Object.keys(dropdown); const keys = Array.isArray(dropdown) ? dropdown : [...dropdown.keys()];
return html` return html`
<paper-dropdown-menu <paper-dropdown-menu
@@ -257,7 +258,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
> >
${keys.map( ${keys.map(
(key) => html` <paper-item .label="${key}"> (key) => html` <paper-item .label="${key}">
${Array.isArray(dropdown) ? key : dropdown[key]} ${Array.isArray(dropdown) ? key : dropdown.get(key)}
</paper-item>`, </paper-item>`,
)} )}
</paper-listbox> </paper-listbox>
@@ -357,13 +358,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
cameraEntities: string[], cameraEntities: string[],
addNewCamera?: boolean, addNewCamera?: boolean,
): TemplateResult | void { ): TemplateResult | void {
const liveProviders = { const liveProviders = new Map([
'': '', ['', ''],
auto: localize('config.cameras.live_providers.auto'), ['auto', localize('config.cameras.live_providers.auto')],
frigate: localize('config.cameras.live_providers.frigate'), ['frigate', localize('config.cameras.live_providers.frigate')],
'frigate-jsmpeg': localize('config.cameras.live_providers.frigate-jsmpeg'), ['frigate-jsmpeg', localize('config.cameras.live_providers.frigate-jsmpeg')],
webrtc: localize('config.cameras.live_providers.webrtc'), ['webrtc', localize('config.cameras.live_providers.webrtc')],
} as const; ]);
// 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 => {
@@ -450,7 +451,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
)} )}
${this._renderDropdown( ${this._renderDropdown(
getArrayConfigPath(CONF_CAMERAS_ARRAY_LIVE_PROVIDER, cameraIndex), getArrayConfigPath(CONF_CAMERAS_ARRAY_LIVE_PROVIDER, cameraIndex),
liveProviders)} liveProviders,
)}
${this._renderStringInput( ${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_URL, cameraIndex), getArrayConfigPath(CONF_CAMERAS_ARRAY_URL, cameraIndex),
)} )}
@@ -497,7 +499,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
if (!this._config) { if (!this._config) {
return; return;
} }
const allowedPattern = type == 'number' ? '[0-9]' : undefined const allowedPattern = type == 'number' ? '[0-9]' : undefined;
return html` <paper-input return html` <paper-input
label=${this._getLabel(configPath)} label=${this._getLabel(configPath)}
.value=${getConfigValue(this._config, configPath, '')} .value=${getConfigValue(this._config, configPath, '')}
@@ -551,70 +553,77 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
const cameraEntities = this._getEntities('camera'); const cameraEntities = this._getEntities('camera');
const viewModes = { const viewModes = new Map([
'': '', ['', ''],
live: localize('config.view.views.live'), ['live', localize('config.view.views.live')],
clips: localize('config.view.views.clips'), ['clips', localize('config.view.views.clips')],
snapshots: localize('config.view.views.snapshots'), ['snapshots', localize('config.view.views.snapshots')],
clip: localize('config.view.views.clip'), ['clip', localize('config.view.views.clip')],
snapshot: localize('config.view.views.snapshot'), ['snapshot', localize('config.view.views.snapshot')],
image: localize('config.view.views.image'), ['image', localize('config.view.views.image')],
}; ]);
const menuModes = { const cameraSelectViewModes = new Map(viewModes);
'': '', cameraSelectViewModes.set('current', localize('config.view.views.current'));
none: localize('config.menu.modes.none'),
'hidden-top': localize('config.menu.modes.hidden-top'),
'hidden-left': localize('config.menu.modes.hidden-left'),
'hidden-bottom': localize('config.menu.modes.hidden-bottom'),
'hidden-right': localize('config.menu.modes.hidden-right'),
'overlay-top': localize('config.menu.modes.overlay-top'),
'overlay-left': localize('config.menu.modes.overlay-left'),
'overlay-bottom': localize('config.menu.modes.overlay-bottom'),
'overlay-right': localize('config.menu.modes.overlay-right'),
'hover-top': localize('config.menu.modes.hover-top'),
'hover-left': localize('config.menu.modes.hover-left'),
'hover-bottom': localize('config.menu.modes.hover-bottom'),
'hover-right': localize('config.menu.modes.hover-right'),
above: localize('config.menu.modes.above'),
below: localize('config.menu.modes.below'),
};
const eventViewerNextPreviousControlStyles = { const menuModes = new Map([
'': '', ['', ''],
thumbnails: localize( ['none', localize('config.menu.modes.none')],
'config.event_viewer.controls.next_previous.styles.thumbnails', ['hidden-top', localize('config.menu.modes.hidden-top')],
), ['hidden-left', localize('config.menu.modes.hidden-left')],
chevrons: localize('config.event_viewer.controls.next_previous.styles.chevrons'), ['hidden-bottom', localize('config.menu.modes.hidden-bottom')],
none: localize('config.event_viewer.controls.next_previous.styles.none'), ['hidden-right', localize('config.menu.modes.hidden-right')],
}; ['overlay-top', localize('config.menu.modes.overlay-top')],
['overlay-left', localize('config.menu.modes.overlay-left')],
['overlay-bottom', localize('config.menu.modes.overlay-bottom')],
['overlay-right', localize('config.menu.modes.overlay-right')],
['hover-top', localize('config.menu.modes.hover-top')],
['hover-left', localize('config.menu.modes.hover-left')],
['hover-bottom', localize('config.menu.modes.hover-bottom')],
['hover-right', localize('config.menu.modes.hover-right')],
['above', localize('config.menu.modes.above')],
['below', localize('config.menu.modes.below')],
]);
const liveNextPreviousControlStyles = { const eventViewerNextPreviousControlStyles = new Map([
'': '', ['', ''],
chevrons: localize('config.live.controls.next_previous.styles.chevrons'), [
icons: localize('config.live.controls.next_previous.styles.icons'), 'thumbnails',
none: localize('config.live.controls.next_previous.styles.none'), localize('config.event_viewer.controls.next_previous.styles.thumbnails'),
}; ],
[
'chevrons',
localize('config.event_viewer.controls.next_previous.styles.chevrons'),
],
['none', localize('config.event_viewer.controls.next_previous.styles.none')],
]);
const aspectRatioModes = { const liveNextPreviousControlStyles = new Map([
'': '', ['', ''],
dynamic: localize('config.dimensions.aspect_ratio_modes.dynamic'), ['chevrons', localize('config.live.controls.next_previous.styles.chevrons')],
static: localize('config.dimensions.aspect_ratio_modes.static'), ['icons', localize('config.live.controls.next_previous.styles.icons')],
unconstrained: localize('config.dimensions.aspect_ratio_modes.unconstrained'), ['none', localize('config.live.controls.next_previous.styles.none')],
}; ]);
const thumbnailModes = { const aspectRatioModes = new Map([
'': '', ['', ''],
none: localize('config.event_viewer.controls.thumbnails.modes.none'), ['dynamic', localize('config.dimensions.aspect_ratio_modes.dynamic')],
above: localize('config.event_viewer.controls.thumbnails.modes.above'), ['static', localize('config.dimensions.aspect_ratio_modes.static')],
below: localize('config.event_viewer.controls.thumbnails.modes.below'), ['unconstrained', localize('config.dimensions.aspect_ratio_modes.unconstrained')],
}; ]);
const thumbnailMedias = { const thumbnailModes = new Map([
'': '', ['', ''],
clips: localize('config.live.controls.thumbnails.medias.clips'), ['none', localize('config.event_viewer.controls.thumbnails.modes.none')],
snapshots: localize('config.live.controls.thumbnails.medias.snapshots'), ['above', localize('config.event_viewer.controls.thumbnails.modes.above')],
}; ['below', localize('config.event_viewer.controls.thumbnails.modes.below')],
]);
const thumbnailMedias = new Map([
['', ''],
['clips', localize('config.live.controls.thumbnails.medias.clips')],
['snapshots', localize('config.live.controls.thumbnails.medias.snapshots')],
]);
const defaults = frigateCardConfigDefaults; const defaults = frigateCardConfigDefaults;
@@ -660,10 +669,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
? html` ? html`
<div class="values"> <div class="values">
${this._renderDropdown(CONF_VIEW_DEFAULT, viewModes)} ${this._renderDropdown(CONF_VIEW_DEFAULT, viewModes)}
${this._renderDropdown(CONF_VIEW_CAMERA_SELECT, cameraSelectViewModes)}
${this._renderStringInput(CONF_VIEW_TIMEOUT_SECONDS, 'number')} ${this._renderStringInput(CONF_VIEW_TIMEOUT_SECONDS, 'number')}
${this._renderStringInput(CONF_VIEW_UPDATE_SECONDS, 'number')} ${this._renderStringInput(CONF_VIEW_UPDATE_SECONDS, 'number')}
${this._renderSwitch(CONF_VIEW_UPDATE_FORCE, defaults.view.update_force)} ${this._renderSwitch(CONF_VIEW_UPDATE_FORCE, defaults.view.update_force)}
${this._renderSwitch(CONF_VIEW_UPDATE_CYCLE_CAMERA, defaults.view.update_cycle_camera)} ${this._renderSwitch(
CONF_VIEW_UPDATE_CYCLE_CAMERA,
defaults.view.update_cycle_camera,
)}
</div> </div>
` `
: ''} : ''}
@@ -721,18 +734,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
? html` ? html`
<div class="values"> <div class="values">
${this._renderSwitch(CONF_LIVE_PRELOAD, defaults.live.preload)} ${this._renderSwitch(CONF_LIVE_PRELOAD, defaults.live.preload)}
${this._renderSwitch( ${this._renderSwitch(CONF_LIVE_DRAGGABLE, defaults.live.draggable)}
CONF_LIVE_DRAGGABLE, ${this._renderSwitch(CONF_LIVE_LAZY_LOAD, defaults.live.lazy_load)}
defaults.live.draggable, ${this._renderSwitch(CONF_LIVE_LAZY_UNLOAD, defaults.live.lazy_unload)}
)}
${this._renderSwitch(
CONF_LIVE_LAZY_LOAD,
defaults.live.lazy_load,
)}
${this._renderSwitch(
CONF_LIVE_LAZY_UNLOAD,
defaults.live.lazy_unload,
)}
${this._renderDropdown( ${this._renderDropdown(
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE, CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
liveNextPreviousControlStyles, liveNextPreviousControlStyles,
@@ -756,7 +760,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderSlider( ${this._renderSlider(
CONF_EVENT_GALLERY_MIN_COLUMNS, CONF_EVENT_GALLERY_MIN_COLUMNS,
defaults.event_gallery.min_columns, defaults.event_gallery.min_columns,
"mdi:view-column", 'mdi:view-column',
1, 1,
10, 10,
)} )}
+3 -1
View File
@@ -32,6 +32,7 @@
"zone": "Frigate zone" "zone": "Frigate zone"
}, },
"view": { "view": {
"camera_select": "View for newly selected cameras",
"default": "Default view", "default": "Default view",
"views": { "views": {
"live": "Live view", "live": "Live view",
@@ -39,7 +40,8 @@
"clips": "Clips gallery", "clips": "Clips gallery",
"snapshot": "Most recent snapshot", "snapshot": "Most recent snapshot",
"snapshots": "Snapshots gallery", "snapshots": "Snapshots gallery",
"image": "Static image" "image": "Static image",
"current": "Current view"
}, },
"timeout_seconds": "Reset to default view X seconds after user action (0=never)", "timeout_seconds": "Reset to default view X seconds after user action (0=never)",
"update_cycle_camera": "Cycle through cameras when default view updates", "update_cycle_camera": "Cycle through cameras when default view updates",
+5 -2
View File
@@ -377,6 +377,7 @@ export type PictureElements = z.infer<typeof pictureElementsSchema>;
*/ */
const viewConfigDefault = { const viewConfigDefault = {
default: 'live' as const, default: 'live' as const,
camera_select: 'current' as const,
timeout_seconds: 300, timeout_seconds: 300,
update_seconds: 0, update_seconds: 0,
update_force: false, update_force: false,
@@ -386,8 +387,10 @@ const viewConfigSchema = z
.object({ .object({
default: z default: z
.enum(FRIGATE_CARD_VIEWS_USER_SPECIFIED) .enum(FRIGATE_CARD_VIEWS_USER_SPECIFIED)
.optional()
.default(viewConfigDefault.default), .default(viewConfigDefault.default),
camera_select: z
.enum([...FRIGATE_CARD_VIEWS_USER_SPECIFIED, 'current'])
.default(viewConfigDefault.camera_select),
timeout_seconds: z.number().default(viewConfigDefault.timeout_seconds), timeout_seconds: z.number().default(viewConfigDefault.timeout_seconds),
update_seconds: z.number().default(viewConfigDefault.update_seconds), update_seconds: z.number().default(viewConfigDefault.update_seconds),
update_force: z.boolean().default(viewConfigDefault.update_force), update_force: z.boolean().default(viewConfigDefault.update_force),
@@ -407,7 +410,7 @@ const imageConfigDefault = {
const imageConfigSchema = z const imageConfigSchema = z
.object({ .object({
src: z.string().optional(), src: z.string().optional(),
refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds) refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds),
}) })
.merge(actionsSchema) .merge(actionsSchema)
.default(imageConfigDefault); .default(imageConfigDefault);