Merge pull request #298 from dermotduffy/camera-menu-choice

Allow Frigate card actions to be a list
This commit is contained in:
Dermot Duffy
2022-01-29 16:18:31 -08:00
committed by GitHub
6 changed files with 257 additions and 170 deletions
+30 -2
View File
@@ -497,6 +497,10 @@ See the [action
documentation](https://www.home-assistant.io/lovelace/actions/#hold-action) for documentation](https://www.home-assistant.io/lovelace/actions/#hold-action) for
more information on the action options available. more information on the action options available.
**Note**: The Frigate Card allows either a single action (as in stock Home
Assistant) or list of actions to be defined for each class of user interaction
(e.g. `tap`, `double_tap`, `hold`, etc). See [an example of multiple actions](#example-multiple-actions).
### Special Elements ### Special Elements
This card supports all [Picture Elements](https://www.home-assistant.io/lovelace/picture-elements/#icon-element) using the same syntax. The card also supports a handful of custom special elements to add special Frigate card functionality. This card supports all [Picture Elements](https://www.home-assistant.io/lovelace/picture-elements/#icon-element) using the same syntax. The card also supports a handful of custom special elements to add special Frigate card functionality.
@@ -551,16 +555,17 @@ Parameters for the `custom:frigate-card-conditional` element:
| Action name | Description | | Action name | Description |
| - | - | | - | - |
| `custom:frigate-card-action` | Call a Frigate Card action. Acceptable values are `frigate`, `clip`, `clips`, `image`, `live`, `snapshot`, `snapshots`, `download`, `frigate_ui`, `fullscreen`.| | `custom:frigate-card-action` | Call a Frigate Card action. Acceptable values are `default`, `clip`, `clips`, `image`, `live`, `snapshot`, `snapshots`, `download`, `frigate_ui`, `fullscreen`, `camera_select`, `menu_toggle`.|
| Value | Description | | Value | Description |
| - | - | | - | - |
| `frigate` | Show/hide the menu or trigger the default view. | | `default` | Trigger the default view. |
| `clip`, `clips`, `image`, `live`, `snapshot`, `snapshots` | Trigger the named [view](#views).| | `clip`, `clips`, `image`, `live`, `snapshot`, `snapshots` | Trigger the named [view](#views).|
|`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.|
|`menu_toggle` | Show/hide the menu (for `hidden-*` mode menus). |
<a name="views"></a> <a name="views"></a>
@@ -1111,6 +1116,29 @@ image:
``` ```
</details> </details>
<a name="example-multiple-actions"></a>
### Defining multiple actions for Elements
<details>
<summary>Expand: Changing camera and view simultaneously</summary>
This example shows how to configure multiple actions for a single Frigate card user interaction, in this case both selecting a different camera and changing the view on `tap`.
```yaml
[...]
elements:
- type: custom:frigate-card-menu-icon
icon: mdi:chair-rolling
tap_action:
- action: custom:frigate-card-action
frigate_card_action: camera_select
camera: camera.office
- action: custom:frigate-card-action
frigate_card_action: live
```
</details>
<a name="card-updates"></a> <a name="card-updates"></a>
## Card Refreshes ## Card Refreshes
+60 -84
View File
@@ -14,7 +14,6 @@ import {
HomeAssistant, HomeAssistant,
LovelaceCardEditor, LovelaceCardEditor,
getLovelace, getLovelace,
hasAction,
} from 'custom-card-helpers'; } from 'custom-card-helpers';
import screenfull from 'screenfull'; import screenfull from 'screenfull';
import { z } from 'zod'; import { z } from 'zod';
@@ -23,10 +22,10 @@ import {
Actions, Actions,
ActionType, ActionType,
CameraConfig, CameraConfig,
GetFrigateCardMenuButtonParameters,
RawFrigateCardConfig, RawFrigateCardConfig,
entitySchema, entitySchema,
frigateCardConfigSchema, frigateCardConfigSchema,
FrigateCardCustomAction,
} from './types.js'; } from './types.js';
import type { import type {
Entity, Entity,
@@ -46,6 +45,7 @@ import {
convertActionToFrigateCardCustomAction, convertActionToFrigateCardCustomAction,
createFrigateCardCustomAction, createFrigateCardCustomAction,
frigateCardHandleAction, frigateCardHandleAction,
frigateCardHasAction,
getActionConfigGivenAction, getActionConfigGivenAction,
getCameraIcon, getCameraIcon,
getCameraTitle, getCameraTitle,
@@ -256,28 +256,6 @@ export class FrigateCard extends LitElement {
}; };
} }
/**
* Get a FrigateCard MenuButton given a set of parameters.
* @param params Menu button parameters.
* @returns A MenuButton.
*/
protected _getFrigateCardMenuButton(
params: GetFrigateCardMenuButtonParameters,
): MenuButton {
return {
type: 'custom:frigate-card-menu-icon',
title: params.title,
icon: params.icon,
style: params.emphasize ? this._getEmphasizedStyle() : {},
tap_action: params.tap_action
? createFrigateCardCustomAction(params.tap_action)
: undefined,
hold_action: params.hold_action
? createFrigateCardCustomAction(params.hold_action)
: undefined,
};
}
/** /**
* Get the menu buttons to display. * Get the menu buttons to display.
* @returns An array of menu buttons. * @returns An array of menu buttons.
@@ -286,16 +264,17 @@ export class FrigateCard extends LitElement {
const buttons: MenuButton[] = []; const buttons: MenuButton[] = [];
if (this._getConfig().menu.buttons.frigate) { if (this._getConfig().menu.buttons.frigate) {
buttons.push( buttons.push({
this._getFrigateCardMenuButton({ type: 'custom:frigate-card-menu-icon',
tap_action: 'frigate', title: localize('config.menu.buttons.frigate'),
// Use a magic icon value that the menu will use to render the icon as // Use a magic icon value that the menu will use to render the icon as
// it deems appropriate (certain menu configurations change the menu // it deems appropriate (certain menu configurations change the menu
// icon for the 'Frigate' button). // icon for the 'Frigate' button).
icon: FRIGATE_BUTTON_MENU_ICON, icon: FRIGATE_BUTTON_MENU_ICON,
title: localize('config.menu.buttons.frigate'), tap_action: FrigateCardMenu.isHidingMenu(this._getConfig().menu)
}), ? (createFrigateCardCustomAction('menu_toggle') as FrigateCardCustomAction)
); : (createFrigateCardCustomAction('default') as FrigateCardCustomAction),
});
} }
if ( if (
@@ -323,56 +302,58 @@ export class FrigateCard extends LitElement {
} }
if (this._getConfig().menu.buttons.live) { if (this._getConfig().menu.buttons.live) {
buttons.push( buttons.push({
this._getFrigateCardMenuButton({ type: 'custom:frigate-card-menu-icon',
tap_action: 'live',
title: localize('config.view.views.live'), title: localize('config.view.views.live'),
icon: 'mdi:cctv', icon: 'mdi:cctv',
emphasize: this._view?.is('live'), style: this._view?.is('live') ? this._getEmphasizedStyle() : {},
}), tap_action: createFrigateCardCustomAction('live') as FrigateCardCustomAction,
); });
} }
if (this._getConfig().menu.buttons.clips) { if (this._getConfig().menu.buttons.clips) {
buttons.push( buttons.push({
this._getFrigateCardMenuButton({ type: 'custom:frigate-card-menu-icon',
tap_action: 'clips',
hold_action: 'clip',
title: localize('config.view.views.clips'), title: localize('config.view.views.clips'),
icon: 'mdi:filmstrip', icon: 'mdi:filmstrip',
emphasize: this._view?.is('clips'), style: this._view?.is('clips') ? this._getEmphasizedStyle() : {},
}), tap_action: createFrigateCardCustomAction('clips') as FrigateCardCustomAction,
); hold_action: createFrigateCardCustomAction('clip') as FrigateCardCustomAction,
});
} }
if (this._getConfig().menu.buttons.snapshots) { if (this._getConfig().menu.buttons.snapshots) {
buttons.push( buttons.push({
this._getFrigateCardMenuButton({ type: 'custom:frigate-card-menu-icon',
tap_action: 'snapshots',
hold_action: 'snapshot',
title: localize('config.view.views.snapshots'), title: localize('config.view.views.snapshots'),
icon: 'mdi:camera', icon: 'mdi:camera',
emphasize: this._view?.is('snapshots'), style: this._view?.is('snapshots') ? this._getEmphasizedStyle() : {},
}), tap_action: createFrigateCardCustomAction(
); 'snapshots',
) as FrigateCardCustomAction,
hold_action: createFrigateCardCustomAction(
'snapshot',
) as FrigateCardCustomAction,
});
} }
if (this._getConfig().menu.buttons.image) { if (this._getConfig().menu.buttons.image) {
buttons.push( buttons.push({
this._getFrigateCardMenuButton({ type: 'custom:frigate-card-menu-icon',
tap_action: 'image',
title: localize('config.view.views.image'), title: localize('config.view.views.image'),
icon: 'mdi:image', icon: 'mdi:image',
emphasize: this._view?.is('image'), style: this._view?.is('image') ? this._getEmphasizedStyle() : {},
}), tap_action: createFrigateCardCustomAction('image') as FrigateCardCustomAction,
); });
} }
if (this._getConfig().menu.buttons.download && this._view?.isViewerView()) { if (this._getConfig().menu.buttons.download && this._view?.isViewerView()) {
buttons.push( buttons.push({
this._getFrigateCardMenuButton({ type: 'custom:frigate-card-menu-icon',
tap_action: 'download',
title: localize('config.menu.buttons.download'), title: localize('config.menu.buttons.download'),
icon: 'mdi:download', icon: 'mdi:download',
}), tap_action: createFrigateCardCustomAction('download') as FrigateCardCustomAction,
); });
} }
const cameraConfig = this._getSelectedCameraConfig(); const cameraConfig = this._getSelectedCameraConfig();
@@ -381,22 +362,26 @@ export class FrigateCard extends LitElement {
cameraConfig && cameraConfig &&
cameraConfig.frigate_url cameraConfig.frigate_url
) { ) {
buttons.push( buttons.push({
this._getFrigateCardMenuButton({ type: 'custom:frigate-card-menu-icon',
tap_action: 'frigate_ui',
title: localize('config.menu.buttons.frigate_ui'), title: localize('config.menu.buttons.frigate_ui'),
icon: 'mdi:web', icon: 'mdi:web',
}), tap_action: createFrigateCardCustomAction(
); 'frigate_ui',
) as FrigateCardCustomAction,
});
} }
if (this._getConfig().menu.buttons.fullscreen && screenfull.isEnabled) { if (this._getConfig().menu.buttons.fullscreen && screenfull.isEnabled) {
buttons.push( buttons.push({
this._getFrigateCardMenuButton({ type: 'custom:frigate-card-menu-icon',
tap_action: 'fullscreen',
title: localize('config.menu.buttons.fullscreen'), title: localize('config.menu.buttons.fullscreen'),
icon: screenfull.isFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen', icon: screenfull.isFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen',
}), tap_action: createFrigateCardCustomAction(
); 'fullscreen',
) as FrigateCardCustomAction,
style: screenfull.isFullscreen ? this._getEmphasizedStyle() : {},
});
} }
return buttons.concat(this._dynamicMenuButtons); return buttons.concat(this._dynamicMenuButtons);
} }
@@ -804,7 +789,7 @@ export class FrigateCard extends LitElement {
const action = frigateCardAction.frigate_card_action; const action = frigateCardAction.frigate_card_action;
switch (action) { switch (action) {
case 'frigate': case 'default':
this._changeView(); this._changeView();
break; break;
case 'clip': case 'clip':
@@ -874,16 +859,7 @@ export class FrigateCard extends LitElement {
* Handle an action called on an element. * Handle an action called on an element.
* @param ev The actionHandler event. * @param ev The actionHandler event.
*/ */
protected _actionHandler( protected _actionHandler(ev: CustomEvent, config?: Actions): void {
ev: CustomEvent,
config?: {
hold_action?: ActionType;
tap_action?: ActionType;
double_tap_action?: ActionType;
start_tap_action?: ActionType;
end_tap_action?: ActionType;
},
): void {
const interaction = ev.detail.action; const interaction = ev.detail.action;
const node: HTMLElement | null = ev.currentTarget as HTMLElement | null; const node: HTMLElement | null = ev.currentTarget as HTMLElement | null;
if ( if (
@@ -1143,8 +1119,8 @@ export class FrigateCard extends LitElement {
return html` <ha-card return html` <ha-card
.actionHandler=${actionHandler({ .actionHandler=${actionHandler({
hasHold: hasAction(actions.hold_action), hasHold: frigateCardHasAction(actions.hold_action),
hasDoubleClick: hasAction(actions.double_tap_action), hasDoubleClick: frigateCardHasAction(actions.double_tap_action),
})} })}
@action=${(ev: CustomEvent) => this._actionHandler(ev, actions)} @action=${(ev: CustomEvent) => this._actionHandler(ev, actions)}
@ll-custom=${this._cardActionHandler.bind(this)} @ll-custom=${this._cardActionHandler.bind(this)}
+52 -21
View File
@@ -1,15 +1,13 @@
import { HassEntity, MessageBase } from 'home-assistant-js-websocket'; import { HassEntity, MessageBase } from 'home-assistant-js-websocket';
import { import { HomeAssistant, handleActionConfig, hasAction, stateIcon } from 'custom-card-helpers';
HomeAssistant,
handleActionConfig,
stateIcon,
} from 'custom-card-helpers';
import { StyleInfo } from 'lit/directives/style-map'; import { StyleInfo } from 'lit/directives/style-map';
import { ZodSchema, z } from 'zod'; import { ZodSchema, z } from 'zod';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { localize } from './localize/localize.js'; import { localize } from './localize/localize.js';
import { import {
Actions,
ActionsConfig,
ActionType, ActionType,
CameraConfig, CameraConfig,
ExtendedHomeAssistant, ExtendedHomeAssistant,
@@ -302,14 +300,8 @@ export function createFrigateCardCustomAction(
*/ */
export function getActionConfigGivenAction( export function getActionConfigGivenAction(
interaction?: string, interaction?: string,
config?: { config?: Actions,
hold_action?: ActionType; ): ActionType | ActionType[] | undefined {
tap_action?: ActionType;
double_tap_action?: ActionType;
start_tap_action?: ActionType;
end_tap_action?: ActionType;
},
): ActionType | undefined {
if (!interaction || !config) { if (!interaction || !config) {
return undefined; return undefined;
} }
@@ -499,24 +491,63 @@ export function contentsChanged(n: unknown, o: unknown): boolean {
* @param hass The Home Assistant object. * @param hass The Home Assistant object.
* @param config The multi-action configuration. * @param config The multi-action configuration.
* @param action The action string (e.g. 'hold') * @param action The action string (e.g. 'hold')
* @returns Whether or not an action was executed.
*/ */
export const frigateCardHandleAction = ( export const frigateCardHandleAction = (
node: HTMLElement,
hass: HomeAssistant,
config: ActionsConfig,
action: string,
): boolean => {
return frigateCardHandleActionConfig(
node,
hass,
config,
action,
getActionConfigGivenAction(action, config),
);
};
/**
* Handle an ActionConfig or array of ActionConfigs.
* @param node The node that fired the event.
* @param hass The Home Assistant object.
* @param actionConfig A single action config, array of action configs or
* undefined for the default action config for 'tap'.
* @param action The action string (e.g. 'hold')
* @returns Whether or not an action was executed.
*/
export const frigateCardHandleActionConfig = (
node: HTMLElement, node: HTMLElement,
hass: HomeAssistant, hass: HomeAssistant,
config: { config: {
entity?: string;
camera_image?: string; camera_image?: string;
hold_action?: ActionType; entity?: string;
tap_action?: ActionType;
double_tap_action?: ActionType;
start_tap_action?: ActionType;
end_tap_action?: ActionType;
}, },
action: string, action: string,
): void => { actionConfig: ActionType | ActionType[] | undefined,
const actionConfig = getActionConfigGivenAction(action, config); ): boolean => {
if (actionConfig || action == 'tap') { if (actionConfig || action == 'tap') {
// Only allow a tap action to use a default non-config (the more-info config). // Only allow a tap action to use a default non-config (the more-info config).
if (Array.isArray(actionConfig)) {
actionConfig.forEach((action) => handleActionConfig(node, hass, config, action));
} else {
handleActionConfig(node, hass, config, actionConfig); handleActionConfig(node, hass, config, actionConfig);
} }
return true;
}
return false;
}; };
/**
* Determine if an action config has a real action. A modified version of
* custom-card-helpers hasAction to also work with arrays of action configs.
* @param config The action config in question.
* @returns `true` if there's a real action defined, `false` otherwise.
*/
export const frigateCardHasAction = (config?: ActionType | ActionType[] | undefined): boolean => {
if (Array.isArray(config)) {
return !!config.find((item) => hasAction(item));
}
return hasAction(config);
}
+78 -23
View File
@@ -1,4 +1,4 @@
import { HASSDomEvent, HomeAssistant, hasAction } from 'custom-card-helpers'; import { HASSDomEvent, HomeAssistant } from 'custom-card-helpers';
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
import { customElement, property, state } 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';
@@ -9,7 +9,7 @@ import { actionHandler } from '../action-handler-directive.js';
import './submenu.js'; import './submenu.js';
import type { import type {
Actions, ActionsConfig,
ActionType, ActionType,
ExtendedHomeAssistant, ExtendedHomeAssistant,
MenuButton, MenuButton,
@@ -18,7 +18,8 @@ import type {
} from '../types.js'; } from '../types.js';
import { import {
convertActionToFrigateCardCustomAction, convertActionToFrigateCardCustomAction,
frigateCardHandleAction, frigateCardHandleActionConfig,
frigateCardHasAction,
getActionConfigGivenAction, getActionConfigGivenAction,
refreshDynamicStateParameters, refreshDynamicStateParameters,
} from '../common.js'; } from '../common.js';
@@ -37,8 +38,6 @@ export class FrigateCardMenu extends LitElement {
public hass?: HomeAssistant & ExtendedHomeAssistant; public hass?: HomeAssistant & ExtendedHomeAssistant;
set menuConfig(menuConfig: MenuConfig) { set menuConfig(menuConfig: MenuConfig) {
this.expanded = !menuConfig?.mode.startsWith('hidden-');
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);
@@ -53,11 +52,36 @@ export class FrigateCardMenu extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public buttons: MenuButton[] = []; public buttons: MenuButton[] = [];
protected _isFrigateCardAction(action: ActionType): boolean { /**
* Determine if a given menu configuration is a hiding menu.
* @param menuConfig The menu configuration.
* @returns `true` if the menu is hiding, `false` otherwise.
*/
static isHidingMenu(menuConfig: MenuConfig | undefined): boolean {
return menuConfig?.mode.startsWith('hidden-') ?? false;
}
/**
* Determine if a given menu configuration is a hiding menu (internal version).
* @returns `true` if the menu is hiding, `false` otherwise.
*/
protected _isHidingMenu(): boolean {
return FrigateCardMenu.isHidingMenu(this._menuConfig);
}
/**
* Determine if a given action is intended to toggle the menu.
* @param action The action to check.
* @returns `true` if the action toggles the menu, `false` otherwise.
*/
protected _isMenuToggleAction(action: ActionType | undefined): boolean {
// Determine if this action is a Frigate card action, if so handle it // Determine if this action is a Frigate card action, if so handle it
// internally. // internally.
if (!action) {
return false;
}
const frigateCardAction = convertActionToFrigateCardCustomAction(action); const frigateCardAction = convertActionToFrigateCardCustomAction(action);
return !!frigateCardAction && frigateCardAction.frigate_card_action == 'frigate'; return !!frigateCardAction && frigateCardAction.frigate_card_action == 'menu_toggle';
} }
/** /**
@@ -66,8 +90,8 @@ export class FrigateCardMenu extends LitElement {
* @param button The button configuration. * @param button The button configuration.
*/ */
protected _actionHandler( protected _actionHandler(
ev: HASSDomEvent<{ action: string; config?: Actions }>, ev: HASSDomEvent<{ action: string; config?: ActionsConfig }>,
config?: Actions, config?: ActionsConfig,
): void { ): void {
if (!ev) { if (!ev) {
return; return;
@@ -86,25 +110,56 @@ export class FrigateCardMenu extends LitElement {
ev.stopPropagation(); ev.stopPropagation();
const interaction: string = ev.detail.action; const interaction: string = ev.detail.action;
const action = getActionConfigGivenAction(interaction, config); let action = getActionConfigGivenAction(interaction, config);
if (!config || !interaction) { if (!config || !interaction) {
return; return;
} }
if ( let tookAction = false;
action && let menuToggle = false;
this._isFrigateCardAction(action) &&
this._menuConfig?.mode.startsWith('hidden-') if (Array.isArray(action)) {
) { // Case 1: An array of actions.
// If the user presses the frigate button and it's a hide-away menu, // Strip out actions that toggle the menu.
// then expand the menu and return. const actionCount = action.length;
this.expanded = !this.expanded; action = action.filter((item) => !this._isMenuToggleAction(item));
return; if (action.length != actionCount) {
menuToggle = true;
} }
// Collapse menu after the user clicks on something. // If there are still actions left, handle them as usual.
if (action.length) {
tookAction = frigateCardHandleActionConfig(
this,
this.hass as HomeAssistant,
config,
interaction,
action,
);
}
} else {
// Case 2: Either a specific action, or no action at all (i.e. default
// action for `tap`).
if (this._isMenuToggleAction(action)) {
menuToggle = true;
} else {
tookAction = frigateCardHandleActionConfig(
this,
this.hass as HomeAssistant,
config,
interaction,
action,
);
}
}
if (this._isHidingMenu()) {
if (menuToggle) {
this.expanded = !this.expanded;
} else if (tookAction) {
this.expanded = false; this.expanded = false;
frigateCardHandleAction(this, this.hass as HomeAssistant, config, interaction); }
}
} }
/** /**
@@ -146,8 +201,8 @@ export class FrigateCardMenu extends LitElement {
stateParameters = refreshDynamicStateParameters(this.hass, stateParameters); stateParameters = refreshDynamicStateParameters(this.hass, stateParameters);
} }
const hasHold = hasAction(button.hold_action); const hasHold = frigateCardHasAction(button.hold_action);
const hasDoubleClick = hasAction(button.double_tap_action); const hasDoubleClick = frigateCardHasAction(button.double_tap_action);
const classes = { const classes = {
button: true, button: true,
+6 -6
View File
@@ -1,11 +1,11 @@
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators'; import { customElement, property } from 'lit/decorators';
import { hasAction, HomeAssistant } from 'custom-card-helpers'; import { HomeAssistant } from 'custom-card-helpers';
import { styleMap } from 'lit/directives/style-map'; import { styleMap } from 'lit/directives/style-map';
import { ExtendedHomeAssistant, MenuSubmenu, MenuSubmenuItem } from '../types.js'; import { ExtendedHomeAssistant, MenuSubmenu, MenuSubmenuItem } from '../types.js';
import { actionHandler } from '../action-handler-directive.js'; import { actionHandler } from '../action-handler-directive.js';
import { refreshDynamicStateParameters } from '../common.js'; import { frigateCardHasAction, refreshDynamicStateParameters } from '../common.js';
import submenuStyle from '../scss/submenu.scss'; import submenuStyle from '../scss/submenu.scss';
import type { Corner } from "@material/mwc-menu"; import type { Corner } from "@material/mwc-menu";
@@ -39,8 +39,8 @@ export class FrigateCardSubmenu extends LitElement {
ev.detail.config = item; ev.detail.config = item;
}} }}
.actionHandler=${actionHandler({ .actionHandler=${actionHandler({
hasHold: hasAction(item.hold_action), hasHold: frigateCardHasAction(item.hold_action),
hasDoubleClick: hasAction(item.double_tap_action), hasDoubleClick: frigateCardHasAction(item.double_tap_action),
})} })}
> >
${stateParameters.title || ''} ${stateParameters.title || ''}
@@ -71,8 +71,8 @@ export class FrigateCardSubmenu extends LitElement {
slot="trigger" slot="trigger"
.label=${this.submenu.title || ''} .label=${this.submenu.title || ''}
.actionHandler=${actionHandler({ .actionHandler=${actionHandler({
hasHold: hasAction(this.submenu.hold_action), hasHold: frigateCardHasAction(this.submenu.hold_action),
hasDoubleClick: hasAction(this.submenu.double_tap_action), hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action),
})} })}
> >
<ha-icon icon="${this.submenu.icon}"></ha-icon> <ha-icon icon="${this.submenu.icon}"></ha-icon>
+11 -14
View File
@@ -119,7 +119,7 @@ const frigateCardCustomActionBaseSchema = customActionSchema.extend({
}); });
const FRIGATE_CARD_GENERAL_ACTIONS = [ const FRIGATE_CARD_GENERAL_ACTIONS = [
'frigate', 'default',
'clip', 'clip',
'clips', 'clips',
'image', 'image',
@@ -129,6 +129,7 @@ const FRIGATE_CARD_GENERAL_ACTIONS = [
'download', 'download',
'frigate_ui', 'frigate_ui',
'fullscreen', 'fullscreen',
'menu_toggle',
] as const; ] as const;
const FRIGATE_CARD_ACTIONS = [...FRIGATE_CARD_GENERAL_ACTIONS, 'camera_select'] as const; const FRIGATE_CARD_ACTIONS = [...FRIGATE_CARD_GENERAL_ACTIONS, 'camera_select'] as const;
export type FrigateCardAction = typeof FRIGATE_CARD_ACTIONS[number]; export type FrigateCardAction = typeof FRIGATE_CARD_ACTIONS[number];
@@ -159,17 +160,21 @@ export type ActionType = z.infer<typeof actionSchema>;
const actionBaseSchema = z const actionBaseSchema = z
.object({ .object({
tap_action: actionSchema.optional(), tap_action: actionSchema.or(actionSchema.array()).optional(),
hold_action: actionSchema.optional(), hold_action: actionSchema.or(actionSchema.array()).optional(),
double_tap_action: actionSchema.optional(), double_tap_action: actionSchema.or(actionSchema.array()).optional(),
start_tap_action: actionSchema.optional(), start_tap_action: actionSchema.or(actionSchema.array()).optional(),
end_tap_action: actionSchema.optional(), end_tap_action: actionSchema.or(actionSchema.array()).optional(),
}) })
// Passthrough to allow (at least) entity/camera_image to go through. This // Passthrough to allow (at least) entity/camera_image to go through. This
// card doesn't need these attributes, but handleAction() in // card doesn't need these attributes, but handleAction() in
// custom_card_helpers may depending on how the action is configured. // custom_card_helpers may depending on how the action is configured.
.passthrough(); .passthrough();
export type Actions = z.infer<typeof actionBaseSchema>; export type Actions = z.infer<typeof actionBaseSchema>;
export type ActionsConfig = Actions & {
camera_image?: string;
entity?: string;
};
const actionsSchema = z.object({ const actionsSchema = z.object({
actions: actionBaseSchema.optional(), actions: actionBaseSchema.optional(),
@@ -746,14 +751,6 @@ export interface BrowseMediaQueryParameters {
after?: number; after?: number;
} }
export interface GetFrigateCardMenuButtonParameters {
icon: string;
title: string;
tap_action: FrigateCardAction;
hold_action?: FrigateCardAction;
emphasize?: boolean;
}
export interface BrowseMediaNeighbors { export interface BrowseMediaNeighbors {
previous: BrowseMediaSource | null; previous: BrowseMediaSource | null;
previousIndex: number | null; previousIndex: number | null;