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;
@state()
public config!: FrigateCardConfig;
public _baseConfig!: FrigateCardConfig;
@state()
public _overriddenConfig?: FrigateCardConfig;
protected _interactionTimerID: number | null = null;
@property({ attribute: false })
protected _view?: View;
@@ -152,6 +150,9 @@ export class FrigateCard extends LitElement {
@query('frigate-card-elements')
_elements?: FrigateCardElements;
// Human interaction timer ID.
protected _interactionTimerID: number | null = null;
// Whether or not media is actively playing (live or clip).
protected _mediaPlaying = false;
@@ -231,9 +232,10 @@ export class FrigateCard extends LitElement {
};
this._overriddenConfig = getOverriddenConfig(
this.config,
this.config.overrides,
this._conditionState) as FrigateCardConfig;
this._baseConfig,
this._baseConfig.overrides,
this._conditionState,
) as FrigateCardConfig;
}
/**
@@ -275,7 +277,7 @@ export class FrigateCard extends LitElement {
protected _getMenuButtons(): MenuButton[] {
const buttons: MenuButton[] = [];
if (this.config.menu.buttons.frigate) {
if (this._getConfig().menu.buttons.frigate) {
buttons.push(
this._getFrigateCardMenuButton({
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]) => {
return {
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(
this._getFrigateCardMenuButton({
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(
this._getFrigateCardMenuButton({
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(
this._getFrigateCardMenuButton({
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(
this._getFrigateCardMenuButton({
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(
this._getFrigateCardMenuButton({
tap_action: 'download',
@@ -362,7 +368,7 @@ export class FrigateCard extends LitElement {
const cameraConfig = this._getSelectedCameraConfig();
if (
this.config.menu.buttons.frigate_ui &&
this._getConfig().menu.buttons.frigate_ui &&
cameraConfig &&
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(
this._getFrigateCardMenuButton({
tap_action: 'fullscreen',
@@ -439,8 +445,8 @@ export class FrigateCard extends LitElement {
}
};
if (this.config.cameras && Array.isArray(this.config.cameras)) {
await Promise.all(this.config.cameras.map(addCameraConfig.bind(this)));
if (this._getConfig().cameras && Array.isArray(this._getConfig().cameras)) {
await Promise.all(this._getConfig().cameras.map(addCameraConfig.bind(this)));
}
if (!cameras.size) {
@@ -600,17 +606,25 @@ export class FrigateCard extends LitElement {
getLovelace().setEditMode(true);
}
this.config = config;
this._baseConfig = config;
this._cameras = 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.
this._resetInteractionTimer();
}
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 {
console.info(`Request to change view: ${JSON.stringify(args?.view?.view)}`);
@@ -626,7 +640,7 @@ export class FrigateCard extends LitElement {
if (camera) {
this._view = new View({
view: this.config.view.default,
view: this._getConfig().view.default,
camera: camera,
});
this._generateConditionState();
@@ -651,10 +665,6 @@ export class FrigateCard extends LitElement {
* @returns True if the card should be updated.
*/
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (!this.config) {
return false;
}
if (changedProps.size > 1) {
return true;
}
@@ -668,12 +678,12 @@ export class FrigateCard extends LitElement {
// Assistant update if there's been recent interaction (e.g. clicks on the
// card) or if there is media active playing.
if (
(this.config.view.update_force ||
(this._getConfig().view.update_force ||
!(this._interactionTimerID && this._mediaPlaying)) &&
shouldUpdateBasedOnHass(
this._hass,
oldHass,
this.config.view.update_entities || [],
this._getConfig().view.update_entities || [],
)
) {
// If entities being monitored have changed then reset the view to the
@@ -868,18 +878,18 @@ export class FrigateCard extends LitElement {
}
protected _resetInteractionTimer(): void {
if (this.config.view.timeout) {
if (this._getConfig().view.timeout) {
if (this._interactionTimerID) {
window.clearTimeout(this._interactionTimerID);
}
this._interactionTimerID = window.setTimeout(() => {
this._interactionTimerID = null;
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.
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 {
const classes = {
'hover-menu': this.config.menu.mode.startsWith('hover-'),
'hover-menu': this._getConfig().menu.mode.startsWith('hover-'),
};
return html`
<frigate-card-menu
.hass=${this._hass}
.menuConfig=${this.config.menu}
.menuConfig=${this._getConfig().menu}
.buttons=${this._getMenuButtons()}
.conditionState=${this._conditionState}
class="${classMap(classes)}"
@@ -1003,7 +1013,7 @@ export class FrigateCard extends LitElement {
* context.
*/
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:
// - It's fullscreen.
@@ -1027,12 +1037,12 @@ export class FrigateCard extends LitElement {
return null;
}
const aspectRatioMode = this.config.dimensions.aspect_ratio_mode;
const aspectRatioMode = this._getConfig().dimensions.aspect_ratio_mode;
if (aspectRatioMode == 'dynamic' && this._mediaShowInfo) {
return (this._mediaShowInfo.height / this._mediaShowInfo.width) * 100;
}
const defaultAspectRatio = this.config.dimensions.aspect_ratio;
const defaultAspectRatio = this._getConfig().dimensions.aspect_ratio;
if (defaultAspectRatio) {
return (defaultAspectRatio[1] / defaultAspectRatio[0]) * 100;
} else {
@@ -1048,15 +1058,15 @@ export class FrigateCard extends LitElement {
let specificActions: Actions | undefined = undefined;
if (this._view?.is('live')) {
specificActions = this._overriddenConfig?.live.actions;
specificActions = this._getConfig().live.actions;
} else if (this._view?.isGalleryView()) {
specificActions = this._overriddenConfig?.event_gallery?.actions;
specificActions = this._getConfig().event_gallery?.actions;
} else if (this._view?.isViewerView()) {
specificActions = this._overriddenConfig?.event_viewer.actions;
specificActions = this._getConfig().event_viewer.actions;
} 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: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="${classMap(contentClasses)}">
<div class="${classMap(pictureElementsClasses)}">
${this.config.elements
${this._getConfig().elements
? // Always show elements to allow for custom menu items (etc.) to
// be present even if a particular view has an error.
html` <frigate-card-elements
.hass=${this._hass}
.elements=${this.config.elements}
.elements=${this._getConfig().elements}
.conditionState=${this._conditionState}
@frigate-card:menu-add=${(e) => {
this._addDynamicMenuButton(e.detail);
@@ -1141,7 +1151,7 @@ export class FrigateCard extends LitElement {
</div>
</div>
</div>
${this.config.menu.mode != 'above' ? this._renderMenu() : ''}
${this._getConfig().menu.mode != 'above' ? this._renderMenu() : ''}
</ha-card>`;
}
@@ -1156,22 +1166,22 @@ export class FrigateCard extends LitElement {
}
const galleryClasses = {
hidden: this.config.live.preload && !this._view.isGalleryView(),
hidden: this._getConfig().live.preload && !this._view.isGalleryView(),
};
const viewerClasses = {
hidden: this.config.live.preload && !this._view.isViewerView(),
hidden: this._getConfig().live.preload && !this._view.isViewerView(),
};
const liveClasses = {
hidden: this.config.live.preload && this._view.view != 'live',
hidden: this._getConfig().live.preload && this._view.view != 'live',
};
const imageClasses = {
hidden: this.config.live.preload && this._view.view != 'image',
hidden: this._getConfig().live.preload && this._view.view != 'image',
};
return html`
${!this._message && this._view.is('image')
? html` <frigate-card-image
.imageConfig=${this.config.image}
.imageConfig=${this._getConfig().image}
class="${classMap(imageClasses)}"
>
</frigate-card-image>`
@@ -1196,25 +1206,29 @@ export class FrigateCard extends LitElement {
this._view,
cameraConfig,
)}
.viewerConfig=${this.config.event_viewer}
.viewerConfig=${this._getConfig().event_viewer}
.resolvedMediaCache=${this._resolvedMediaCache}
class="${classMap(viewerClasses)}"
>
</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.
(!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`
<frigate-card-live
.hass=${this._hass}
.view=${this._view}
.liveConfig=${this.config.live}
.liveConfig=${this._baseConfig.live}
.conditionState=${this._conditionState}
.liveOverrides=${getOverridesByKey(this.config.overrides, 'live')}
.liveOverrides=${getOverridesByKey(this._getConfig().overrides, 'live')}
.cameras=${this._cameras}
.preload=${this.config.live.preload && !this._view.is('live')}
.preload=${this._getConfig().live.preload && !this._view.is('live')}
class="${classMap(liveClasses)}"
>
</frigate-card-live>
+3 -10
View File
@@ -6,7 +6,7 @@ import {
html,
unsafeCSS,
} 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 { styleMap } from 'lit/directives/style-map.js';
@@ -28,7 +28,6 @@ import {
} from '../common.js';
import menuStyle from '../scss/menu.scss';
import { ConditionState, evaluateCondition } from '../card-condition.js';
import { Corner } from '@material/mwc-menu';
export const FRIGATE_BUTTON_MENU_ICON = 'frigate';
@@ -41,13 +40,13 @@ export class FrigateCardMenu extends LitElement {
@property({ attribute: false })
public hass?: HomeAssistant & ExtendedHomeAssistant;
@property({ attribute: false })
set menuConfig(menuConfig: MenuConfig) {
this._menuConfig = menuConfig;
if (menuConfig) {
this.style.setProperty('--frigate-card-menu-button-size', menuConfig.button_size);
}
}
@state()
protected _menuConfig?: MenuConfig;
@property({ attribute: false })
@@ -56,9 +55,6 @@ export class FrigateCardMenu extends LitElement {
@property({ attribute: false })
public buttons: MenuButton[] = [];
@property({ attribute: false })
protected conditionState?: ConditionState;
/**
* Handle an action on a menu button.
* @param ev The action event.
@@ -185,10 +181,7 @@ export class FrigateCardMenu extends LitElement {
}
const mode = this._menuConfig.mode;
if (
mode == 'none' ||
!evaluateCondition(this._menuConfig.conditions, this.conditionState)
) {
if (mode == 'none') {
return;
}
+26 -5
View File
@@ -572,9 +572,28 @@ const menuConfigDefault = {
},
button_size: '40px',
};
const menuConfigSchema = z
.object({
mode: z.enum(FRIGATE_MENU_MODES).optional().default(menuConfigDefault.mode),
const menuOverridableConfigSchema = z.object({
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
.object({
frigate: z.boolean().default(menuConfigDefault.buttons.frigate),
@@ -588,8 +607,9 @@ const menuConfigSchema = z
fullscreen: z.boolean().default(menuConfigDefault.buttons.fullscreen),
})
.default(menuConfigDefault.buttons),
button_size: z.string().default(menuConfigDefault.button_size),
conditions: frigateCardConditionSchema.optional(),
button_size: menuOverridableConfigSchema.shape.button_size.default(
menuConfigDefault.button_size,
)
})
.default(menuConfigDefault);
export type MenuConfig = z.infer<typeof menuConfigSchema>;
@@ -688,6 +708,7 @@ const dimensionsConfigSchema = z
*/
const overrideConfigurationSchema = z.object({
live: liveOverridableConfigSchema.optional(),
menu: menuOverridableConfigSchema.optional(),
});
export type OverrideConfigurationKey = keyof z.infer<typeof overrideConfigurationSchema>;