Add menu overrides and remove menu conditions.

This commit is contained in:
Dermot Duffy
2022-01-14 21:31:16 -08:00
parent ee9f22f9f7
commit cfcfd07467
3 changed files with 97 additions and 69 deletions
+68 -54
View File
@@ -133,13 +133,11 @@ export class FrigateCard extends LitElement {
protected _hass?: HomeAssistant & ExtendedHomeAssistant; protected _hass?: HomeAssistant & ExtendedHomeAssistant;
@state() @state()
public config!: FrigateCardConfig; public _baseConfig!: FrigateCardConfig;
@state() @state()
public _overriddenConfig?: FrigateCardConfig; public _overriddenConfig?: FrigateCardConfig;
protected _interactionTimerID: number | null = null;
@property({ attribute: false }) @property({ attribute: false })
protected _view?: View; protected _view?: View;
@@ -152,6 +150,9 @@ export class FrigateCard extends LitElement {
@query('frigate-card-elements') @query('frigate-card-elements')
_elements?: FrigateCardElements; _elements?: FrigateCardElements;
// Human interaction timer ID.
protected _interactionTimerID: number | null = null;
// Whether or not media is actively playing (live or clip). // Whether or not media is actively playing (live or clip).
protected _mediaPlaying = false; protected _mediaPlaying = false;
@@ -231,9 +232,10 @@ export class FrigateCard extends LitElement {
}; };
this._overriddenConfig = getOverriddenConfig( this._overriddenConfig = getOverriddenConfig(
this.config, this._baseConfig,
this.config.overrides, this._baseConfig.overrides,
this._conditionState) as FrigateCardConfig; this._conditionState,
) as FrigateCardConfig;
} }
/** /**
@@ -275,7 +277,7 @@ export class FrigateCard extends LitElement {
protected _getMenuButtons(): MenuButton[] { protected _getMenuButtons(): MenuButton[] {
const buttons: MenuButton[] = []; const buttons: MenuButton[] = [];
if (this.config.menu.buttons.frigate) { if (this._getConfig().menu.buttons.frigate) {
buttons.push( buttons.push(
this._getFrigateCardMenuButton({ this._getFrigateCardMenuButton({
tap_action: 'frigate', tap_action: 'frigate',
@@ -288,7 +290,11 @@ export class FrigateCard extends LitElement {
); );
} }
if (this.config.menu.buttons.cameras && this._cameras && this._cameras.size > 1) { if (
this._getConfig().menu.buttons.cameras &&
this._cameras &&
this._cameras.size > 1
) {
const menuItems = Array.from(this._cameras, ([camera, config]) => { const menuItems = Array.from(this._cameras, ([camera, config]) => {
return { return {
icon: getCameraIcon(this._hass, config), icon: getCameraIcon(this._hass, config),
@@ -307,7 +313,7 @@ export class FrigateCard extends LitElement {
}); });
} }
if (this.config.menu.buttons.live) { if (this._getConfig().menu.buttons.live) {
buttons.push( buttons.push(
this._getFrigateCardMenuButton({ this._getFrigateCardMenuButton({
tap_action: 'live', tap_action: 'live',
@@ -318,7 +324,7 @@ export class FrigateCard extends LitElement {
); );
} }
if (this.config.menu.buttons.clips) { if (this._getConfig().menu.buttons.clips) {
buttons.push( buttons.push(
this._getFrigateCardMenuButton({ this._getFrigateCardMenuButton({
tap_action: 'clips', tap_action: 'clips',
@@ -329,7 +335,7 @@ export class FrigateCard extends LitElement {
}), }),
); );
} }
if (this.config.menu.buttons.snapshots) { if (this._getConfig().menu.buttons.snapshots) {
buttons.push( buttons.push(
this._getFrigateCardMenuButton({ this._getFrigateCardMenuButton({
tap_action: 'snapshots', tap_action: 'snapshots',
@@ -340,7 +346,7 @@ export class FrigateCard extends LitElement {
}), }),
); );
} }
if (this.config.menu.buttons.image) { if (this._getConfig().menu.buttons.image) {
buttons.push( buttons.push(
this._getFrigateCardMenuButton({ this._getFrigateCardMenuButton({
tap_action: 'image', tap_action: 'image',
@@ -350,7 +356,7 @@ export class FrigateCard extends LitElement {
}), }),
); );
} }
if (this.config.menu.buttons.download && this._view?.isViewerView()) { if (this._getConfig().menu.buttons.download && this._view?.isViewerView()) {
buttons.push( buttons.push(
this._getFrigateCardMenuButton({ this._getFrigateCardMenuButton({
tap_action: 'download', tap_action: 'download',
@@ -362,7 +368,7 @@ export class FrigateCard extends LitElement {
const cameraConfig = this._getSelectedCameraConfig(); const cameraConfig = this._getSelectedCameraConfig();
if ( if (
this.config.menu.buttons.frigate_ui && this._getConfig().menu.buttons.frigate_ui &&
cameraConfig && cameraConfig &&
cameraConfig.frigate_url cameraConfig.frigate_url
) { ) {
@@ -374,7 +380,7 @@ export class FrigateCard extends LitElement {
}), }),
); );
} }
if (this.config.menu.buttons.fullscreen && screenfull.isEnabled) { if (this._getConfig().menu.buttons.fullscreen && screenfull.isEnabled) {
buttons.push( buttons.push(
this._getFrigateCardMenuButton({ this._getFrigateCardMenuButton({
tap_action: 'fullscreen', tap_action: 'fullscreen',
@@ -439,8 +445,8 @@ export class FrigateCard extends LitElement {
} }
}; };
if (this.config.cameras && Array.isArray(this.config.cameras)) { if (this._getConfig().cameras && Array.isArray(this._getConfig().cameras)) {
await Promise.all(this.config.cameras.map(addCameraConfig.bind(this))); await Promise.all(this._getConfig().cameras.map(addCameraConfig.bind(this)));
} }
if (!cameras.size) { if (!cameras.size) {
@@ -600,17 +606,25 @@ export class FrigateCard extends LitElement {
getLovelace().setEditMode(true); getLovelace().setEditMode(true);
} }
this.config = config; this._baseConfig = config;
this._cameras = undefined; this._cameras = undefined;
this._view = undefined; this._view = undefined;
if (this.config.view.update_force) { if (this._getConfig().view.update_force) {
// If update force is enabled, start a timer right away. // If update force is enabled, start a timer right away.
this._resetInteractionTimer(); this._resetInteractionTimer();
} }
this._changeView(); this._changeView();
} }
/**
* Card the card config, prioritizing the overriden config if present.
* @returns A FrigateCardConfig.
*/
protected _getConfig(): FrigateCardConfig {
return this._overriddenConfig || this._baseConfig;
}
protected _changeView(args?: { view?: View; resetMessage?: boolean }): void { protected _changeView(args?: { view?: View; resetMessage?: boolean }): void {
console.info(`Request to change view: ${JSON.stringify(args?.view?.view)}`); console.info(`Request to change view: ${JSON.stringify(args?.view?.view)}`);
@@ -626,7 +640,7 @@ export class FrigateCard extends LitElement {
if (camera) { if (camera) {
this._view = new View({ this._view = new View({
view: this.config.view.default, view: this._getConfig().view.default,
camera: camera, camera: camera,
}); });
this._generateConditionState(); this._generateConditionState();
@@ -651,10 +665,6 @@ export class FrigateCard extends LitElement {
* @returns True if the card should be updated. * @returns True if the card should be updated.
*/ */
protected shouldUpdate(changedProps: PropertyValues): boolean { protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.config) {
return false;
}
if (changedProps.size > 1) { if (changedProps.size > 1) {
return true; return true;
} }
@@ -668,12 +678,12 @@ export class FrigateCard extends LitElement {
// Assistant update if there's been recent interaction (e.g. clicks on the // Assistant update if there's been recent interaction (e.g. clicks on the
// card) or if there is media active playing. // card) or if there is media active playing.
if ( if (
(this.config.view.update_force || (this._getConfig().view.update_force ||
!(this._interactionTimerID && this._mediaPlaying)) && !(this._interactionTimerID && this._mediaPlaying)) &&
shouldUpdateBasedOnHass( shouldUpdateBasedOnHass(
this._hass, this._hass,
oldHass, oldHass,
this.config.view.update_entities || [], this._getConfig().view.update_entities || [],
) )
) { ) {
// If entities being monitored have changed then reset the view to the // If entities being monitored have changed then reset the view to the
@@ -868,18 +878,18 @@ export class FrigateCard extends LitElement {
} }
protected _resetInteractionTimer(): void { protected _resetInteractionTimer(): void {
if (this.config.view.timeout) { if (this._getConfig().view.timeout) {
if (this._interactionTimerID) { if (this._interactionTimerID) {
window.clearTimeout(this._interactionTimerID); window.clearTimeout(this._interactionTimerID);
} }
this._interactionTimerID = window.setTimeout(() => { this._interactionTimerID = window.setTimeout(() => {
this._interactionTimerID = null; this._interactionTimerID = null;
this._changeView(); this._changeView();
if (this.config.view.update_force) { if (this._getConfig().view.update_force) {
// If force is enabled, the timer just resets and starts over. // If force is enabled, the timer just resets and starts over.
this._resetInteractionTimer(); this._resetInteractionTimer();
} }
}, this.config.view.timeout * 1000); }, this._getConfig().view.timeout * 1000);
} }
} }
@@ -889,12 +899,12 @@ export class FrigateCard extends LitElement {
*/ */
protected _renderMenu(): TemplateResult | void { protected _renderMenu(): TemplateResult | void {
const classes = { const classes = {
'hover-menu': this.config.menu.mode.startsWith('hover-'), 'hover-menu': this._getConfig().menu.mode.startsWith('hover-'),
}; };
return html` return html`
<frigate-card-menu <frigate-card-menu
.hass=${this._hass} .hass=${this._hass}
.menuConfig=${this.config.menu} .menuConfig=${this._getConfig().menu}
.buttons=${this._getMenuButtons()} .buttons=${this._getMenuButtons()}
.conditionState=${this._conditionState} .conditionState=${this._conditionState}
class="${classMap(classes)}" class="${classMap(classes)}"
@@ -1003,7 +1013,7 @@ export class FrigateCard extends LitElement {
* context. * context.
*/ */
protected _isAspectRatioEnforced(): boolean { protected _isAspectRatioEnforced(): boolean {
const aspectRatioMode = this.config.dimensions.aspect_ratio_mode; const aspectRatioMode = this._getConfig().dimensions.aspect_ratio_mode;
// Do not artifically constrain aspect ratio if: // Do not artifically constrain aspect ratio if:
// - It's fullscreen. // - It's fullscreen.
@@ -1027,12 +1037,12 @@ export class FrigateCard extends LitElement {
return null; return null;
} }
const aspectRatioMode = this.config.dimensions.aspect_ratio_mode; const aspectRatioMode = this._getConfig().dimensions.aspect_ratio_mode;
if (aspectRatioMode == 'dynamic' && this._mediaShowInfo) { if (aspectRatioMode == 'dynamic' && this._mediaShowInfo) {
return (this._mediaShowInfo.height / this._mediaShowInfo.width) * 100; return (this._mediaShowInfo.height / this._mediaShowInfo.width) * 100;
} }
const defaultAspectRatio = this.config.dimensions.aspect_ratio; const defaultAspectRatio = this._getConfig().dimensions.aspect_ratio;
if (defaultAspectRatio) { if (defaultAspectRatio) {
return (defaultAspectRatio[1] / defaultAspectRatio[0]) * 100; return (defaultAspectRatio[1] / defaultAspectRatio[0]) * 100;
} else { } else {
@@ -1048,15 +1058,15 @@ export class FrigateCard extends LitElement {
let specificActions: Actions | undefined = undefined; let specificActions: Actions | undefined = undefined;
if (this._view?.is('live')) { if (this._view?.is('live')) {
specificActions = this._overriddenConfig?.live.actions; specificActions = this._getConfig().live.actions;
} else if (this._view?.isGalleryView()) { } else if (this._view?.isGalleryView()) {
specificActions = this._overriddenConfig?.event_gallery?.actions; specificActions = this._getConfig().event_gallery?.actions;
} else if (this._view?.isViewerView()) { } else if (this._view?.isViewerView()) {
specificActions = this._overriddenConfig?.event_viewer.actions; specificActions = this._getConfig().event_viewer.actions;
} else if (this._view?.is('image')) { } else if (this._view?.is('image')) {
specificActions = this._overriddenConfig?.image?.actions; specificActions = this._getConfig().image?.actions;
} }
return { ...this.config.view.actions, ...specificActions }; return { ...this._getConfig().view.actions, ...specificActions };
} }
/** /**
@@ -1096,16 +1106,16 @@ export class FrigateCard extends LitElement {
@frigate-card:pause=${this._pauseHandler} @frigate-card:pause=${this._pauseHandler}
@frigate-card:play=${this._playHandler} @frigate-card:play=${this._playHandler}
> >
${this.config.menu.mode == 'above' ? this._renderMenu() : ''} ${this._getConfig().menu.mode == 'above' ? this._renderMenu() : ''}
<div class="container outer" style="${styleMap(outerStyle)}"> <div class="container outer" style="${styleMap(outerStyle)}">
<div class="${classMap(contentClasses)}"> <div class="${classMap(contentClasses)}">
<div class="${classMap(pictureElementsClasses)}"> <div class="${classMap(pictureElementsClasses)}">
${this.config.elements ${this._getConfig().elements
? // Always show elements to allow for custom menu items (etc.) to ? // Always show elements to allow for custom menu items (etc.) to
// be present even if a particular view has an error. // be present even if a particular view has an error.
html` <frigate-card-elements html` <frigate-card-elements
.hass=${this._hass} .hass=${this._hass}
.elements=${this.config.elements} .elements=${this._getConfig().elements}
.conditionState=${this._conditionState} .conditionState=${this._conditionState}
@frigate-card:menu-add=${(e) => { @frigate-card:menu-add=${(e) => {
this._addDynamicMenuButton(e.detail); this._addDynamicMenuButton(e.detail);
@@ -1141,7 +1151,7 @@ export class FrigateCard extends LitElement {
</div> </div>
</div> </div>
</div> </div>
${this.config.menu.mode != 'above' ? this._renderMenu() : ''} ${this._getConfig().menu.mode != 'above' ? this._renderMenu() : ''}
</ha-card>`; </ha-card>`;
} }
@@ -1156,22 +1166,22 @@ export class FrigateCard extends LitElement {
} }
const galleryClasses = { const galleryClasses = {
hidden: this.config.live.preload && !this._view.isGalleryView(), hidden: this._getConfig().live.preload && !this._view.isGalleryView(),
}; };
const viewerClasses = { const viewerClasses = {
hidden: this.config.live.preload && !this._view.isViewerView(), hidden: this._getConfig().live.preload && !this._view.isViewerView(),
}; };
const liveClasses = { const liveClasses = {
hidden: this.config.live.preload && this._view.view != 'live', hidden: this._getConfig().live.preload && this._view.view != 'live',
}; };
const imageClasses = { const imageClasses = {
hidden: this.config.live.preload && this._view.view != 'image', hidden: this._getConfig().live.preload && this._view.view != 'image',
}; };
return html` return html`
${!this._message && this._view.is('image') ${!this._message && this._view.is('image')
? html` <frigate-card-image ? html` <frigate-card-image
.imageConfig=${this.config.image} .imageConfig=${this._getConfig().image}
class="${classMap(imageClasses)}" class="${classMap(imageClasses)}"
> >
</frigate-card-image>` </frigate-card-image>`
@@ -1196,25 +1206,29 @@ export class FrigateCard extends LitElement {
this._view, this._view,
cameraConfig, cameraConfig,
)} )}
.viewerConfig=${this.config.event_viewer} .viewerConfig=${this._getConfig().event_viewer}
.resolvedMediaCache=${this._resolvedMediaCache} .resolvedMediaCache=${this._resolvedMediaCache}
class="${classMap(viewerClasses)}" class="${classMap(viewerClasses)}"
> >
</frigate-card-viewer>` </frigate-card-viewer>`
: ``} : ``}
${ ${
// Note the subtle difference in condition below vs the other views in order // Note: Subtle difference in condition below vs the other views in order
// to always render the live view for live.preload mode. // to always render the live view for live.preload mode.
(!this._message && this._view.is('live')) || this.config.live.preload
// Note: <frigate-card-live> uses the baseConfig rather than the
// overriden config, as it does it's own overriding as part of the
// camera carousel.
(!this._message && this._view.is('live')) || this._getConfig().live.preload
? html` ? html`
<frigate-card-live <frigate-card-live
.hass=${this._hass} .hass=${this._hass}
.view=${this._view} .view=${this._view}
.liveConfig=${this.config.live} .liveConfig=${this._baseConfig.live}
.conditionState=${this._conditionState} .conditionState=${this._conditionState}
.liveOverrides=${getOverridesByKey(this.config.overrides, 'live')} .liveOverrides=${getOverridesByKey(this._getConfig().overrides, 'live')}
.cameras=${this._cameras} .cameras=${this._cameras}
.preload=${this.config.live.preload && !this._view.is('live')} .preload=${this._getConfig().live.preload && !this._view.is('live')}
class="${classMap(liveClasses)}" class="${classMap(liveClasses)}"
> >
</frigate-card-live> </frigate-card-live>
+3 -10
View File
@@ -6,7 +6,7 @@ import {
html, html,
unsafeCSS, unsafeCSS,
} from 'lit'; } from 'lit';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
@@ -28,7 +28,6 @@ import {
} from '../common.js'; } from '../common.js';
import menuStyle from '../scss/menu.scss'; import menuStyle from '../scss/menu.scss';
import { ConditionState, evaluateCondition } from '../card-condition.js';
import { Corner } from '@material/mwc-menu'; import { Corner } from '@material/mwc-menu';
export const FRIGATE_BUTTON_MENU_ICON = 'frigate'; export const FRIGATE_BUTTON_MENU_ICON = 'frigate';
@@ -41,13 +40,13 @@ export class FrigateCardMenu extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public hass?: HomeAssistant & ExtendedHomeAssistant; public hass?: HomeAssistant & ExtendedHomeAssistant;
@property({ attribute: false })
set menuConfig(menuConfig: MenuConfig) { set menuConfig(menuConfig: MenuConfig) {
this._menuConfig = menuConfig; this._menuConfig = menuConfig;
if (menuConfig) { if (menuConfig) {
this.style.setProperty('--frigate-card-menu-button-size', menuConfig.button_size); this.style.setProperty('--frigate-card-menu-button-size', menuConfig.button_size);
} }
} }
@state()
protected _menuConfig?: MenuConfig; protected _menuConfig?: MenuConfig;
@property({ attribute: false }) @property({ attribute: false })
@@ -56,9 +55,6 @@ export class FrigateCardMenu extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public buttons: MenuButton[] = []; public buttons: MenuButton[] = [];
@property({ attribute: false })
protected conditionState?: ConditionState;
/** /**
* Handle an action on a menu button. * Handle an action on a menu button.
* @param ev The action event. * @param ev The action event.
@@ -185,10 +181,7 @@ export class FrigateCardMenu extends LitElement {
} }
const mode = this._menuConfig.mode; const mode = this._menuConfig.mode;
if ( if (mode == 'none') {
mode == 'none' ||
!evaluateCondition(this._menuConfig.conditions, this.conditionState)
) {
return; return;
} }
+26 -5
View File
@@ -572,9 +572,28 @@ const menuConfigDefault = {
}, },
button_size: '40px', button_size: '40px',
}; };
const menuConfigSchema = z
.object({ const menuOverridableConfigSchema = z.object({
mode: z.enum(FRIGATE_MENU_MODES).optional().default(menuConfigDefault.mode), mode: z.enum(FRIGATE_MENU_MODES).optional(),
buttons: z
.object({
frigate: z.boolean().optional(),
cameras: z.boolean().optional(),
live: z.boolean().optional(),
clips: z.boolean().optional(),
snapshots: z.boolean().optional(),
image: z.boolean().optional(),
download: z.boolean().optional(),
frigate_ui: z.boolean().optional(),
fullscreen: z.boolean().optional(),
})
.optional(),
button_size: z.string().optional(),
});
const menuConfigSchema = menuOverridableConfigSchema
.extend({
mode: menuOverridableConfigSchema.shape.mode.default(menuConfigDefault.mode),
buttons: z buttons: z
.object({ .object({
frigate: z.boolean().default(menuConfigDefault.buttons.frigate), frigate: z.boolean().default(menuConfigDefault.buttons.frigate),
@@ -588,8 +607,9 @@ const menuConfigSchema = z
fullscreen: z.boolean().default(menuConfigDefault.buttons.fullscreen), fullscreen: z.boolean().default(menuConfigDefault.buttons.fullscreen),
}) })
.default(menuConfigDefault.buttons), .default(menuConfigDefault.buttons),
button_size: z.string().default(menuConfigDefault.button_size), button_size: menuOverridableConfigSchema.shape.button_size.default(
conditions: frigateCardConditionSchema.optional(), menuConfigDefault.button_size,
)
}) })
.default(menuConfigDefault); .default(menuConfigDefault);
export type MenuConfig = z.infer<typeof menuConfigSchema>; export type MenuConfig = z.infer<typeof menuConfigSchema>;
@@ -688,6 +708,7 @@ const dimensionsConfigSchema = z
*/ */
const overrideConfigurationSchema = z.object({ const overrideConfigurationSchema = z.object({
live: liveOverridableConfigSchema.optional(), live: liveOverridableConfigSchema.optional(),
menu: menuOverridableConfigSchema.optional(),
}); });
export type OverrideConfigurationKey = keyof z.infer<typeof overrideConfigurationSchema>; export type OverrideConfigurationKey = keyof z.infer<typeof overrideConfigurationSchema>;