diff --git a/README.md b/README.md index ed316a12..b4095efd 100644 --- a/README.md +++ b/README.md @@ -882,6 +882,21 @@ If multiple cameras are configured in the card, use [overrides](#overrides) to c See [media layout examples](#media-layout-examples). + + +### Other Options + +All listed configuration options are under the top level, e.g.: + +```yaml +type: custom:frigate-card +... +``` + +| Option | Default | Overridable | Description | +| - | - | - | - | +| `card_id` | | :heavy_multiplication_x: | **Advanced users only**: An optional ID to uniquely identify this card. For use when actions are being sent to card(s) via the [query string](#query-string-actions).| + ### Using AlexxIT's WebRTC Card @@ -1100,7 +1115,9 @@ Parameters for the `custom:frigate-card-ptz` element: | `action` | Must be `custom:frigate-card-action`. | | `frigate_card_action` | Call a Frigate Card action. Acceptable values are `default`, `clip`, `clips`, `image`, `live`, `recording`, `recordings`, `snapshot`, `snapshots`, `download`, `timeline`, `camera_ui`, `fullscreen`, `camera_select`, `menu_toggle`, `media_player`, `live_substream_select`, `expand_toggle`.| -##### Command descriptions + + +##### Action descriptions | Value | Description | | - | - | @@ -2441,6 +2458,16 @@ performance: ``` +
+ Expand: Other options + +Reference: [Other Options](#other-options). + +```yaml +card_id: main +``` +
+ ### Basic cameras configuration
@@ -3377,6 +3404,41 @@ mode: single ```
+ + +### Passing the card actions from the URL + +The card can respond to actions in the query string (see [below](#query-string-actions)). + +
+ Expand: Selecting the kitchen camera and opening the expanded view + +This example assumes the dashboard URL is `https://ha.mydomain.org/lovelace-test/0`. + +``` +https://ha.mydomain.org/lovelace-test/0?frigate-card-action/camera_select=kitchen&frigate-card-action/expand_toggle +``` +
+ +
+ Expand: Choosing the clips view on a named card + +This example assumes the dashboard URL is `https://ha.mydomain.org/lovelace-test/0`. + +It assumes that one card (of potentially multiple Frigate Cards on the dashboard) is configured with a `card_id` parameter: + +```yaml +type: custom:frigate-card +card_id: main +cameras: +[...] +``` + +``` +https://ha.mydomain.org/lovelace-test/0?frigate-card-action/main/clips +``` +
+ ## Card Refreshes @@ -3437,6 +3499,58 @@ view: timeout_seconds: 30 ``` + + +### Passing the card actions from the URL + +It is possible to pass the Frigate card one or more actions from the URL (e.g. select a particular camera, open the live view in expanded mode, etc). + +To send an action to *all* Frigate cards on a dashboard: + +``` +[PATH_TO_YOUR_HA_DASHBOARD]?frigate-card-action/[ACTION]=[VALUE] +``` + +To send an action to a named Frigate card on the dashboard: + +``` +[PATH_TO_YOUR_HA_DASHBOARD]?frigate-card-action/[CARD_ID]/[ACTION]=[VALUE] +``` + +| Parameter | Description | +| - | - | +| `ACTION` | One of the supported Frigate Card custom actions (see below). | +| `CARD_ID` | When specified only cards that have a `card_id` parameter will act. | +| `VALUE` | An optional value to use with the `camera_select` and `live_substream_select` actions. | + +#### Actions + +| Action | Supported in query string | Explanation | +| - | - | - | +| `camera_select` | :white_check_mark: | | +| `camera_ui`| :white_check_mark: | | +| `clip` | :white_check_mark: | | +| `clips` | :white_check_mark: | | +| `default` | :white_check_mark: | | +| `download`| :heavy_multiplication_x: | Latest media information is not available on initial render. | +| `expand_toggle` | :white_check_mark: | | +| `fullscreen` | :heavy_multiplication_x: | Javascript does not support activating fullscreen without direct human interaction. Use `expand_toggle` as an alternative. | +| `image` | :white_check_mark: | | +| `live_substream_select` | :white_check_mark: | | +| `live` | :white_check_mark: | | +| `media_player`| :heavy_multiplication_x: | Please [request](https://github.com/dermotduffy/frigate-hass-card/issues) if you need this. | +| `menu_toggle` | :white_check_mark: | | +| `recording` | :white_check_mark: | | +| `recordings` | :white_check_mark: | | +| `snapshot` | :white_check_mark: | | +| `snapshots` | :white_check_mark: | | + +See [custom actions](#custom-actions) for a description of what the actions do. + +#### Examples + +See [query string examples](#query-string-examples) for examples of usage. + ### Casting the Card This card can be (Chrome) casted to a device (such as a [Nest Hub](https://store.google.com/us/product/nest_hub_2nd_gen)) through the use of [Home Assistant Cast](https://cast.home-assistant.io/). diff --git a/src/card.ts b/src/card.ts index 3b971c30..0567edef 100644 --- a/src/card.ts +++ b/src/card.ts @@ -92,6 +92,7 @@ import merge from 'lodash-es/merge'; import { FrigateCardInitializer } from './utils/initializer.js'; import 'web-dialog'; import { downloadMedia } from './utils/download.js'; +import { getActionsFromQueryString } from './utils/querystring.js'; /** A note on media callbacks: * @@ -1398,15 +1399,26 @@ class FrigateCard extends LitElement { } } - /** - * Handle a request for a card action. - * @param ev The action requested. - */ - protected _cardActionHandler(ev: CustomEvent): void { + protected _cardActionEventHandler(ev: CustomEvent): void { const frigateCardAction = convertActionToFrigateCardCustomAction(ev.detail); - if (!this._view || !frigateCardAction) { + if (frigateCardAction) { + this._cardActionHandler(frigateCardAction); + } + } + + protected _cardActionHandler(frigateCardAction: FrigateCardCustomAction): void { + if (!this._view) { return; } + + if ( + frigateCardAction.card_id && + this._getConfig().card_id !== frigateCardAction.card_id + ) { + // Command not intended for this card (e.g. query string command). + return; + } + const action = frigateCardAction.frigate_card_action; switch (action) { @@ -1985,7 +1997,7 @@ class FrigateCard extends LitElement { class="${classMap(cardClasses)}" style="${styleMap(cardStyle)}" @action=${(ev: CustomEvent) => this._actionHandler(ev, actions)} - @ll-custom=${this._cardActionHandler.bind(this)} + @ll-custom=${this._cardActionEventHandler.bind(this)} @frigate-card:message=${this._messageHandler.bind(this)} @frigate-card:view:change=${this._changeViewHandler.bind(this)} @frigate-card:view:change-context=${this._addViewContextHandler.bind(this)} @@ -2116,6 +2128,11 @@ class FrigateCard extends LitElement { `; } + protected firstUpdated(): void { + // Execute query string actions after first render is complete. + getActionsFromQueryString().forEach((action) => this._cardActionHandler(action)); + } + /** * Return compiled CSS styles (thus safe to use with unsafeCSS). */ diff --git a/src/localize/localize.ts b/src/localize/localize.ts index af74e995..c082917a 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -52,9 +52,9 @@ export function getLanguage(hass?: HomeAssistant): string { export const loadLanguages = async (hass: HomeAssistant): Promise => { const lang = getLanguage(hass); if (lang === 'it') { - languages['it'] = await import('./languages/it.json'); + languages[lang] = await import('./languages/it.json'); } else if (lang === 'pt_BR') { - languages['pt_BR'] = await import('./languages/pt-BR.json'); + languages[lang] = await import('./languages/pt-BR.json'); } if (lang) { diff --git a/src/types.ts b/src/types.ts index 6592ed4e..29ef0a04 100644 --- a/src/types.ts +++ b/src/types.ts @@ -205,6 +205,9 @@ const frigateCardCustomActionsBaseSchema = customActionSchema.extend({ // Syntactic sugar to avoid 'fire-dom-event' as part of an external API. .transform((): 'fire-dom-event' => 'fire-dom-event') .or(z.literal('fire-dom-event')), + + // Card this command is intended for. + card_id: z.string().optional(), }); const FRIGATE_CARD_GENERAL_ACTIONS = [ @@ -1316,6 +1319,9 @@ export const frigateCardConfigSchema = z.object({ // Support for card_mod (https://github.com/thomasloven/lovelace-card-mod). card_mod: z.unknown(), + // Card ID (used for query string commands). + card_id: z.string().optional(), + // Stock lovelace card config. type: z.string(), test_gui: z.boolean().optional(), diff --git a/src/utils/action.ts b/src/utils/action.ts index e6449d45..e64c0c37 100644 --- a/src/utils/action.ts +++ b/src/utils/action.ts @@ -19,7 +19,7 @@ import { * @returns A FrigateCardCustomAction or null if it cannot be converted. */ export function convertActionToFrigateCardCustomAction( - action: ActionType | null, + action: unknown, ): FrigateCardCustomAction | null { if (!action) { return null; @@ -38,6 +38,7 @@ export function convertActionToFrigateCardCustomAction( export function createFrigateCardCustomAction( action: FrigateCardAction, args?: { + cardID?: string; camera?: string; media_player?: string; media_player_action?: 'play' | 'stop'; @@ -51,6 +52,7 @@ export function createFrigateCardCustomAction( action: 'fire-dom-event', frigate_card_action: action, camera: args.camera as string, + ...(args.cardID && { card_id: args.cardID}) }; } if (action === 'media_player') { @@ -62,11 +64,13 @@ export function createFrigateCardCustomAction( frigate_card_action: action, media_player: args.media_player, media_player_action: args.media_player_action, + ...(args.cardID && { card_id: args.cardID}) }; } return { action: 'fire-dom-event', frigate_card_action: action, + ...(args?.cardID && { card_id: args.cardID}) }; } diff --git a/src/utils/querystring.ts b/src/utils/querystring.ts new file mode 100644 index 00000000..ccf916e3 --- /dev/null +++ b/src/utils/querystring.ts @@ -0,0 +1,57 @@ +import { FrigateCardCustomAction } from '../types'; +import { createFrigateCardCustomAction } from './action.js'; + +export const getActionsFromQueryString = (): FrigateCardCustomAction[] => { + const params = new URLSearchParams(window.location.search); + const actions: FrigateCardCustomAction[] = []; + const actionRE = new RegExp(/^frigate-card-action(\/(?\w+))?\/(?\w+)/); + + for (const [key, value] of params.entries()) { + const match = key.match(actionRE); + if (!match || !match.groups) { + continue; + } + const cardID: string | undefined = match.groups['cardID']; + const action = match.groups['action']; + + let customAction: FrigateCardCustomAction | null = null; + switch (action) { + case 'camera_select': + case 'live_substream_select': + if (value) { + customAction = createFrigateCardCustomAction(action, { + camera: value, + cardID: cardID, + }); + } + break; + case 'camera_ui': + case 'clip': + case 'clips': + case 'default': + case 'diagnostics': + case 'download': + case 'expand_toggle': + case 'image': + case 'live': + case 'menu_toggle': + case 'recording': + case 'recordings': + case 'snapshot': + case 'snapshots': + case 'timeline': + customAction = createFrigateCardCustomAction(action, { + cardID: cardID, + }); + break; + default: + console.warn( + `Frigate card received unknown card action in query string: ${action}`, + ); + } + if (customAction) { + actions.push(customAction); + } + } + return actions; +};