Add support for card and view actions.

This commit is contained in:
Dermot Duffy
2021-11-13 20:50:11 -08:00
parent 155e62dcfd
commit fc3033a0fe
6 changed files with 211 additions and 83 deletions
+81 -13
View File
@@ -12,19 +12,23 @@ import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { until } from 'lit/directives/until.js';
import {
ActionConfig,
HomeAssistant,
LovelaceCardEditor,
getLovelace,
handleAction,
hasAction,
} from 'custom-card-helpers';
import screenfull from 'screenfull';
import { z } from 'zod';
import {
CardAction,
entitySchema,
frigateCardConfigSchema,
ActionType,
GetFrigateCardMenuButtonParameters,
RawFrigateCardConfig,
entitySchema,
frigateCardConfigSchema,
Actions,
} from './types.js';
import type {
BrowseMediaQueryParameters,
@@ -38,10 +42,16 @@ import type {
import { CARD_VERSION, REPO_URL } from './const.js';
import { FrigateCardElements } from './components/elements.js';
import { FrigateCardMenu, FRIGATE_BUTTON_MENU_ICON, MENU_HEIGHT } from './components/menu.js';
import {
FRIGATE_BUTTON_MENU_ICON,
MENU_HEIGHT,
FrigateCardMenu,
} from './components/menu.js';
import { View } from './view.js';
import {
convertActionToFrigateCardCustomAction,
createFrigateCardCustomAction,
getActionConfigGivenAction,
homeAssistantSignPath,
homeAssistantWSRequest,
isValidMediaShowInfo,
@@ -65,14 +75,15 @@ import cardStyle from './scss/card.scss';
import { ResolvedMediaCache } from './resolved-media.js';
import { BrowseMediaUtil } from './browse-media-util.js';
import { isConfigUpgradeable } from './config-mgmt.js';
import { actionHandler } from './action-handler-directive.js';
/** A note on media callbacks:
*
* We need media elements (e.g. <video>, <img> or <canvas>) to callback when:
* Media elements (e.g. <video>, <img> or <canvas>) need to callback when:
* - Metadata is loaded / dimensions are known (for aspect-ratio)
* - Media is playing / paused (to avoid reloading)
*
* There are a number of different approaches used to attach event handlers to
* A number of different approaches used to attach event handlers to
* get these callbacks (which need to be attached directly to the media
* elements, which may be 'buried' down the DOM):
* - Extend the `ha-hls-player` and `ha-camera-stream` to specify the required
@@ -83,6 +94,15 @@ import { isConfigUpgradeable } from './config-mgmt.js';
* - Directly specifying hooks (e.g. for snapshot viewing with simple <img> tags)
*/
/** A note on action/menu/ll-custom events:
*
* The card supports actions being configured in a number of places (e.g. tap on an
* element, double_tap on a menu item, hold on the live view). These actions are
* handled by handleAction() from custom-card-helpers. For Frigate-card specific
* actions, handleAction() call will result in an ll-custom DOM event being
* fired, which needs to be caught at the card level to handle.
*/
/* eslint no-console: 0 */
console.info(
`%c FRIGATE-HASS-CARD \n%c ${localize('common.version')} ${CARD_VERSION} `,
@@ -106,7 +126,7 @@ console.info(
@customElement('frigate-card')
export class FrigateCard extends LitElement {
@property({ attribute: false })
protected _hass: (HomeAssistant & ExtendedHomeAssistant) | null = null;
protected _hass?: HomeAssistant & ExtendedHomeAssistant;
@state()
public config!: FrigateCardConfig;
@@ -580,11 +600,12 @@ export class FrigateCard extends LitElement {
* Handle a request for a card action.
* @param action The action requested (e.g. clips, fullscreen)
*/
protected _cardActionHandler(event: CustomEvent<CardAction>): void {
const action = event.detail.action;
if (!action) {
protected _cardActionHandler(event: CustomEvent<ActionType>): void {
const frigateCardAction = convertActionToFrigateCardCustomAction(event.detail);
if (!frigateCardAction) {
return;
}
const action = frigateCardAction.frigate_card_action;
switch (action) {
case 'frigate':
@@ -649,6 +670,29 @@ export class FrigateCard extends LitElement {
}, this.config.view.timeout * 1000);
}
protected _actionHandler(
ev: CustomEvent,
config?: {
hold_action?: ActionType;
tap_action?: ActionType;
double_tap_action?: ActionType;
},
): void {
const interaction = ev.detail.action;
const node: HTMLElement | null = ev.currentTarget as HTMLElement | null;
if (
config &&
node &&
interaction &&
// Don't call handleAction() unless there is explicitly an action defined
// (as it uses a default that is unhelpful for views that have default
// tap/click actions).
getActionConfigGivenAction(interaction, config)
) {
handleAction(node, this._hass as HomeAssistant, config, ev.detail.action);
}
}
/**
* Render the card menu.
* @returns A rendered template.
@@ -663,7 +707,6 @@ export class FrigateCard extends LitElement {
.menuConfig=${this.config.menu}
.buttons=${this._getMenuButtons()}
class="${classMap(classes)}"
@frigate-card:card-action=${this._cardActionHandler.bind(this)}
></frigate-card-menu>
`;
}
@@ -823,6 +866,21 @@ export class FrigateCard extends LitElement {
}
}
protected _getMergedActions(): Actions {
let specificActions: Actions | undefined = undefined;
if (this._view.is('live')) {
specificActions = this.config.live.actions;
} else if (this._view.isGalleryView()) {
specificActions = this.config.event_gallery?.actions;
} else if (this._view.isViewerView()) {
specificActions = this.config.event_viewer.actions;
} else if (this._view.is('image')) {
specificActions = this.config.image?.actions;
}
return { ...this.config.view.actions, ...specificActions };
}
/**
* Master render method for the card.
*/
@@ -868,7 +926,18 @@ export class FrigateCard extends LitElement {
absolute: padding != null,
};
return html` <ha-card @click=${this._interactionHandler}>
const actions = this._getMergedActions();
return html` <ha-card
@click=${this._interactionHandler}
.actionHandler=${actionHandler({
hasHold: hasAction(actions.hold_action),
hasDoubleClick: hasAction(actions.double_tap_action),
})}
@action=${(ev: CustomEvent) =>
this._actionHandler(ev, actions)}
@ll-custom=${this._cardActionHandler.bind(this)}
>
${this.config.menu.mode == 'above' ? this._renderMenu() : ''}
<div class="container outer" style="${styleMap(outerStyle)}">
<div class="${classMap(contentClasses)}" style="${styleMap(innerStyle)}">
@@ -999,7 +1068,6 @@ export class FrigateCard extends LitElement {
// 'frigate-card-elements' to re-render (by being a property).
e.view = this._view;
}}
@frigate-card:card-action=${this._cardActionHandler.bind(this)}
>
</frigate-card-elements>
`