Accept . as a query string delimiter.
This commit is contained in:
@@ -3807,7 +3807,7 @@ The card can respond to actions in the query string (see [below](#query-string-a
|
|||||||
This example assumes the dashboard URL is `https://ha.mydomain.org/lovelace-test/0`.
|
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
|
https://ha.mydomain.org/lovelace-test/0?frigate-card-action.camera_select=kitchen&frigate-card-action.expand
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -3826,7 +3826,7 @@ cameras:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
https://ha.mydomain.org/lovelace-test/0?frigate-card-action:main:clips
|
https://ha.mydomain.org/lovelace-test/0?frigate-card-action.main.clips
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -3859,7 +3859,7 @@ elements:
|
|||||||
left: 30%
|
left: 30%
|
||||||
tap_action:
|
tap_action:
|
||||||
action: navigate
|
action: navigate
|
||||||
navigation_path: /lovelace-frigate/map?frigate-card-action:camera_select=camera.living_room
|
navigation_path: /lovelace-frigate/map?frigate-card-action.camera_select=camera.living_room
|
||||||
- type: icon
|
- type: icon
|
||||||
icon: mdi:cctv
|
icon: mdi:cctv
|
||||||
style:
|
style:
|
||||||
@@ -3867,7 +3867,7 @@ elements:
|
|||||||
left: 42%
|
left: 42%
|
||||||
tap_action:
|
tap_action:
|
||||||
action: navigate
|
action: navigate
|
||||||
navigation_path: /lovelace-frigate/map?frigate-card-action:camera_select=camera.landing
|
navigation_path: /lovelace-frigate/map?frigate-card-action.camera_select=camera.landing
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@@ -3998,13 +3998,13 @@ The Frigate card will execute these actions in the following circumstances:
|
|||||||
To send an action to *all* Frigate cards:
|
To send an action to *all* Frigate cards:
|
||||||
|
|
||||||
```
|
```
|
||||||
[PATH_TO_YOUR_HA_DASHBOARD]?frigate-card-action:[ACTION]=[VALUE]
|
[PATH_TO_YOUR_HA_DASHBOARD]?frigate-card-action.[ACTION]=[VALUE]
|
||||||
```
|
```
|
||||||
|
|
||||||
To send an action to a named Frigate card:
|
To send an action to a named Frigate card:
|
||||||
|
|
||||||
```
|
```
|
||||||
[PATH_TO_YOUR_HA_DASHBOARD]?frigate-card-action:[CARD_ID]:[ACTION]=[VALUE]
|
[PATH_TO_YOUR_HA_DASHBOARD]?frigate-card-action.[CARD_ID].[ACTION]=[VALUE]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
@@ -4013,6 +4013,8 @@ To send an action to a named Frigate card:
|
|||||||
| `CARD_ID` | When specified only cards that have a `card_id` parameter will act. |
|
| `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. |
|
| `VALUE` | An optional value to use with the `camera_select` and `live_substream_select` actions. |
|
||||||
|
|
||||||
|
**Note**: Both `.` and `:` may be used as the delimiter. If you use `:` some browsers may require it be escaped to `%3A`.
|
||||||
|
|
||||||
**Note**: If a dashboard has multiple Frigate cards on it, even if they are on
|
**Note**: If a dashboard has multiple Frigate cards on it, even if they are on
|
||||||
different 'tabs' within that dashboard, they will all respond to the actions
|
different 'tabs' within that dashboard, they will all respond to the actions
|
||||||
unless the action is targeted with a `CARD_ID` as shown above.
|
unless the action is targeted with a `CARD_ID` as shown above.
|
||||||
|
|||||||
+4
-2
@@ -749,7 +749,7 @@ class FrigateCard extends LitElement {
|
|||||||
// the default view and the querystring view, see:
|
// the default view and the querystring view, see:
|
||||||
// https://github.com/dermotduffy/frigate-hass-card/issues/1200
|
// https://github.com/dermotduffy/frigate-hass-card/issues/1200
|
||||||
if (!this._view) {
|
if (!this._view) {
|
||||||
const querystringActions = getActionsFromQueryString();
|
const querystringActions = getActionsFromQueryString(window.location.search);
|
||||||
if (
|
if (
|
||||||
!querystringActions.find(
|
!querystringActions.find(
|
||||||
(action) =>
|
(action) =>
|
||||||
@@ -1594,7 +1594,9 @@ class FrigateCard extends LitElement {
|
|||||||
protected _locationChangeHandler = (): void => {
|
protected _locationChangeHandler = (): void => {
|
||||||
// Only execute actions when the card has rendered at least once.
|
// Only execute actions when the card has rendered at least once.
|
||||||
if (this.hasUpdated) {
|
if (this.hasUpdated) {
|
||||||
getActionsFromQueryString().forEach((action) => this._cardActionHandler(action));
|
getActionsFromQueryString(window.location.search).forEach((action) =>
|
||||||
|
this._cardActionHandler(action),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -217,7 +217,10 @@ const frigateCardCustomActionsBaseSchema = customActionSchema.extend({
|
|||||||
.or(z.literal('fire-dom-event')),
|
.or(z.literal('fire-dom-event')),
|
||||||
|
|
||||||
// Card this command is intended for.
|
// Card this command is intended for.
|
||||||
card_id: z.string().optional(),
|
card_id: z
|
||||||
|
.string()
|
||||||
|
.regex(/^\w+$/, 'card_id parameter can only contain [a-z][A-Z][0-9_]')
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const FRIGATE_CARD_GENERAL_ACTIONS = [
|
const FRIGATE_CARD_GENERAL_ACTIONS = [
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { FrigateCardCustomAction } from '../types';
|
import { FrigateCardCustomAction } from '../types';
|
||||||
import { createFrigateCardCustomAction } from './action.js';
|
import { createFrigateCardCustomAction } from './action.js';
|
||||||
|
|
||||||
export const getActionsFromQueryString = (): FrigateCardCustomAction[] => {
|
export const getActionsFromQueryString = (
|
||||||
const params = new URLSearchParams(window.location.search);
|
queryString: string,
|
||||||
|
): FrigateCardCustomAction[] => {
|
||||||
|
const params = new URLSearchParams(queryString);
|
||||||
const actions: FrigateCardCustomAction[] = [];
|
const actions: FrigateCardCustomAction[] = [];
|
||||||
const actionRE = new RegExp(/^frigate-card-action(:(?<cardID>\w+))?:(?<action>\w+)/);
|
const actionRE = new RegExp(
|
||||||
|
/^frigate-card-action([.:](?<cardID>\w+))?[.:](?<action>\w+)/,
|
||||||
|
);
|
||||||
for (const [key, value] of params.entries()) {
|
for (const [key, value] of params.entries()) {
|
||||||
const match = key.match(actionRE);
|
const match = key.match(actionRE);
|
||||||
if (!match || !match.groups) {
|
if (!match || !match.groups) {
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { getActionsFromQueryString } from '../../src/utils/querystring';
|
||||||
|
|
||||||
|
// @vitest-environment jsdom
|
||||||
|
describe('getActionsFromQueryString', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject malformed query string', () => {
|
||||||
|
expect(getActionsFromQueryString(`?BOGUS_KEY=BOGUS_VALUE`)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept colon as delimiter', () => {
|
||||||
|
expect(getActionsFromQueryString(`?frigate-card-action:id:clips=`)).toEqual([
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
card_id: 'id',
|
||||||
|
frigate_card_action: 'clips',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('should get simple action from query string', () => {
|
||||||
|
it.each([
|
||||||
|
['camera_ui' as const],
|
||||||
|
['clip' as const],
|
||||||
|
['clips' as const],
|
||||||
|
['default' as const],
|
||||||
|
['diagnostics' as const],
|
||||||
|
['download' as const],
|
||||||
|
['expand' as const],
|
||||||
|
['image' as const],
|
||||||
|
['live' as const],
|
||||||
|
['menu_toggle' as const],
|
||||||
|
['recording' as const],
|
||||||
|
['recordings' as const],
|
||||||
|
['snapshot' as const],
|
||||||
|
['snapshots' as const],
|
||||||
|
['timeline' as const],
|
||||||
|
])('%s', (action: string) => {
|
||||||
|
expect(getActionsFromQueryString(`?frigate-card-action.id.${action}=`)).toEqual([
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
card_id: 'id',
|
||||||
|
frigate_card_action: action,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get camera_select action', () => {
|
||||||
|
expect(
|
||||||
|
getActionsFromQueryString(`?frigate-card-action.id.camera_select=camera.foo`),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
card_id: 'id',
|
||||||
|
frigate_card_action: 'camera_select',
|
||||||
|
camera: 'camera.foo',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get live_substream_select action', () => {
|
||||||
|
expect(
|
||||||
|
getActionsFromQueryString(
|
||||||
|
`?frigate-card-action.id.live_substream_select=camera.bar`,
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
card_id: 'id',
|
||||||
|
frigate_card_action: 'live_substream_select',
|
||||||
|
camera: 'camera.bar',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('should reject value-based actions without value', () => {
|
||||||
|
it.each([['camera_select' as const], ['live_substream_select' as const]])(
|
||||||
|
'%s',
|
||||||
|
(action: string) => {
|
||||||
|
expect(getActionsFromQueryString(`?frigate-card-action.id.${action}=`)).toEqual(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log unknown but correctly formed action', () => {
|
||||||
|
const spy = vi.spyOn(global.console, 'warn').mockImplementation(() => true);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getActionsFromQueryString(`?frigate-card-action.id.not_a_real_action}=`),
|
||||||
|
).toEqual([]);
|
||||||
|
|
||||||
|
expect(spy).toBeCalledWith(
|
||||||
|
'Frigate card received unknown card action in query string: not_a_real_action',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user