feat: Add control/output of selected camera via an entity (#1895)
- Closes #1871
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
- [`overrides`](overrides.md)
|
- [`overrides`](overrides.md)
|
||||||
- [`performance`](performance.md)
|
- [`performance`](performance.md)
|
||||||
- [`profiles`](profiles.md)
|
- [`profiles`](profiles.md)
|
||||||
|
- [`remote_control`](remote-control.md)
|
||||||
- [`status_bar`](status-bar.md)
|
- [`status_bar`](status-bar.md)
|
||||||
- [`timeline`](timeline.md)
|
- [`timeline`](timeline.md)
|
||||||
- [`view`](view.md)
|
- [`view`](view.md)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
- [`overrides`](../overrides.md)
|
- [`overrides`](../overrides.md)
|
||||||
- [`performance`](../performance.md)
|
- [`performance`](../performance.md)
|
||||||
- [`profiles`](../profiles.md)
|
- [`profiles`](../profiles.md)
|
||||||
|
- [`remote_control`](../remote-control.md)
|
||||||
- [`status_bar`](../status-bar.md)
|
- [`status_bar`](../status-bar.md)
|
||||||
- [`timeline`](../timeline.md)
|
- [`timeline`](../timeline.md)
|
||||||
- [`view`](../view.md)
|
- [`view`](../view.md)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
- [`overrides`](../../overrides.md)
|
- [`overrides`](../../overrides.md)
|
||||||
- [`performance`](../../performance.md)
|
- [`performance`](../../performance.md)
|
||||||
- [`profiles`](../../profiles.md)
|
- [`profiles`](../../profiles.md)
|
||||||
|
- [`remote_control`](../../remote-control.md)
|
||||||
- [`status_bar`](../../status-bar.md)
|
- [`status_bar`](../../status-bar.md)
|
||||||
- [`timeline`](../../timeline.md)
|
- [`timeline`](../../timeline.md)
|
||||||
- [`view`](../../view.md)
|
- [`view`](../../view.md)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
- [`overrides`](../../overrides.md)
|
- [`overrides`](../../overrides.md)
|
||||||
- [`performance`](../../performance.md)
|
- [`performance`](../../performance.md)
|
||||||
- [`profiles`](../../profiles.md)
|
- [`profiles`](../../profiles.md)
|
||||||
|
- [`remote_control`](../../remote-control.md)
|
||||||
- [`status_bar`](../../status-bar.md)
|
- [`status_bar`](../../status-bar.md)
|
||||||
- [`timeline`](../../timeline.md)
|
- [`timeline`](../../timeline.md)
|
||||||
- [`view`](../../view.md)
|
- [`view`](../../view.md)
|
||||||
|
|||||||
@@ -53,8 +53,9 @@ cameras:
|
|||||||
### Capabilities
|
### Capabilities
|
||||||
|
|
||||||
| Capability | Purpose |
|
| Capability | Purpose |
|
||||||
| --------------------- | -------------------------------------------------------- |
|
| ----------------------- | ------------------------------------------------------------------------------------------------- |
|
||||||
| `clips` | Clips can be fetched from the camera. |
|
| `clips` | Clips can be fetched from the camera. |
|
||||||
|
| `remote-control-entity` | The camera can be selected by a [Camera Remote Control Entity](../remote-control.md?id=entities). |
|
||||||
| `favorite-events` | Events can be favorited. |
|
| `favorite-events` | Events can be favorited. |
|
||||||
| `favorite-recordings` | Recordings can be favorited. |
|
| `favorite-recordings` | Recordings can be favorited. |
|
||||||
| `live` | Live video can be received from the camera. |
|
| `live` | Live video can be received from the camera. |
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
- [`overrides`](../overrides.md)
|
- [`overrides`](../overrides.md)
|
||||||
- [`performance`](../performance.md)
|
- [`performance`](../performance.md)
|
||||||
- [`profiles`](../profiles.md)
|
- [`profiles`](../profiles.md)
|
||||||
|
- [`remote_control`](../remote-control.md)
|
||||||
- [`status_bar`](../status-bar.md)
|
- [`status_bar`](../status-bar.md)
|
||||||
- [`timeline`](../timeline.md)
|
- [`timeline`](../timeline.md)
|
||||||
- [`view`](../view.md)
|
- [`view`](../view.md)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
- [`overrides`](../overrides.md)
|
- [`overrides`](../overrides.md)
|
||||||
- [`performance`](../performance.md)
|
- [`performance`](../performance.md)
|
||||||
- [`profiles`](../profiles.md)
|
- [`profiles`](../profiles.md)
|
||||||
|
- [`remote_control`](../remote-control.md)
|
||||||
- [`status_bar`](../status-bar.md)
|
- [`status_bar`](../status-bar.md)
|
||||||
- [`timeline`](../timeline.md)
|
- [`timeline`](../timeline.md)
|
||||||
- [`view`](../view.md)
|
- [`view`](../view.md)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
- [`overrides`](../../overrides.md)
|
- [`overrides`](../../overrides.md)
|
||||||
- [`performance`](../../performance.md)
|
- [`performance`](../../performance.md)
|
||||||
- [`profiles`](../../profiles.md)
|
- [`profiles`](../../profiles.md)
|
||||||
|
- [`remote_control`](../../remote-control.md)
|
||||||
- [`status_bar`](../../status-bar.md)
|
- [`status_bar`](../../status-bar.md)
|
||||||
- [`timeline`](../../timeline.md)
|
- [`timeline`](../../timeline.md)
|
||||||
- [`view`](../../view.md)
|
- [`view`](../../view.md)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
- [`overrides`](../../overrides.md)
|
- [`overrides`](../../overrides.md)
|
||||||
- [`performance`](../../performance.md)
|
- [`performance`](../../performance.md)
|
||||||
- [`profiles`](../../profiles.md)
|
- [`profiles`](../../profiles.md)
|
||||||
|
- [`remote_control`](../../remote-control.md)
|
||||||
- [`status_bar`](../../status-bar.md)
|
- [`status_bar`](../../status-bar.md)
|
||||||
- [`timeline`](../../timeline.md)
|
- [`timeline`](../../timeline.md)
|
||||||
- [`view`](../../view.md)
|
- [`view`](../../view.md)
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# `remote_control`
|
||||||
|
|
||||||
|
The `remote_control` configuration options control how a card instance can be remotely controlled.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
remote_control:
|
||||||
|
# [...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## `entities`
|
||||||
|
|
||||||
|
Sets entities that are used to control different aspects of the card.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
remote_control:
|
||||||
|
entities:
|
||||||
|
# [...]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
| -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `camera` | | An `input_select` entity that the card will use for bidirectional control. When the selected camera on the card changes the entity will be updated to match. Likewise, when the entity state changes, the selected camera on the card will be updated to match. When the card is first started, the `input_select` entity will be updated to only have valid camera IDs from this card. Values must start with `input_select.`. |
|
||||||
|
|
||||||
|
?> To create an `input_select` entity to use in this manner, in the visual card
|
||||||
|
editor, under `Remote Control -> Remote Control Entities`, choose `Create a new
|
||||||
|
Dropdown helper`. Give the new entity an entity name (e.g. `my_selected_camera`)
|
||||||
|
and an optional icon. You must specify at least one option -- you can use any
|
||||||
|
placeholder value (e.g. `camera`) then choose `Add` (the card will automatically
|
||||||
|
reset the allowable options on start). Finally, click `Create`.
|
||||||
|
|
||||||
|
## Related Topics
|
||||||
|
|
||||||
|
- [Passing actions to the card in the URL](../usage/url-actions.md).
|
||||||
|
|
||||||
|
## Fully expanded reference
|
||||||
|
|
||||||
|
[](common/expanded-warning.md ':include')
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
remote_control:
|
||||||
|
entities:
|
||||||
|
camera: input_select.my_selected_camera
|
||||||
|
```
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.2 MiB |
@@ -14,6 +14,12 @@ A dashboard with the card can be cast onto a suitable device (such as the Nest H
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Control via Entities
|
||||||
|
|
||||||
|
Bidirectional control of camera selection via an entity.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Dark mode
|
## Dark mode
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export class FrigateCamera extends Camera {
|
|||||||
{
|
{
|
||||||
'favorite-events': !birdseye,
|
'favorite-events': !birdseye,
|
||||||
'favorite-recordings': false,
|
'favorite-recordings': false,
|
||||||
|
'remote-control-entity': true,
|
||||||
seek: !birdseye,
|
seek: !birdseye,
|
||||||
clips: !birdseye,
|
clips: !birdseye,
|
||||||
snapshots: !birdseye,
|
snapshots: !birdseye,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
|
|||||||
{
|
{
|
||||||
'favorite-events': false,
|
'favorite-events': false,
|
||||||
'favorite-recordings': false,
|
'favorite-recordings': false,
|
||||||
|
'remote-control-entity': true,
|
||||||
clips: false,
|
clips: false,
|
||||||
live: true,
|
live: true,
|
||||||
menu: true,
|
menu: true,
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export class MotionEyeCameraManagerEngine extends BrowseMediaCameraManagerEngine
|
|||||||
{
|
{
|
||||||
'favorite-events': false,
|
'favorite-events': false,
|
||||||
'favorite-recordings': false,
|
'favorite-recordings': false,
|
||||||
|
'remote-control-entity': true,
|
||||||
clips: true,
|
clips: true,
|
||||||
live: true,
|
live: true,
|
||||||
menu: true,
|
menu: true,
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ export class ReolinkCameraManagerEngine extends BrowseMediaCameraManagerEngine {
|
|||||||
{
|
{
|
||||||
'favorite-events': false,
|
'favorite-events': false,
|
||||||
'favorite-recordings': false,
|
'favorite-recordings': false,
|
||||||
|
'remote-control-entity': true,
|
||||||
clips: true,
|
clips: true,
|
||||||
live: true,
|
live: true,
|
||||||
menu: true,
|
menu: true,
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ export class CameraSelectAction extends AdvancedCameraCardAction<CameraSelectAct
|
|||||||
const view = api.getViewManager().getView();
|
const view = api.getViewManager().getView();
|
||||||
const config = api.getConfigManager().getConfig();
|
const config = api.getConfigManager().getConfig();
|
||||||
|
|
||||||
if (selectCameraID && view) {
|
// Don't do anything if the camera is already selected (especially important
|
||||||
|
// for control entities, as otherwise every camera change will generate a
|
||||||
|
// double request for events, once when the camera changes and another when
|
||||||
|
// the observed state of the control entity changes to match).
|
||||||
|
if (selectCameraID && view && selectCameraID !== view.camera) {
|
||||||
const viewOnCameraSelect = config?.view.camera_select ?? 'current';
|
const viewOnCameraSelect = config?.view.camera_select ?? 'current';
|
||||||
const targetViewName =
|
const targetViewName =
|
||||||
viewOnCameraSelect === 'current' ? view.view : viewOnCameraSelect;
|
viewOnCameraSelect === 'current' ? view.view : viewOnCameraSelect;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { InternalCallbackActionConfig } from '../../../config/types';
|
||||||
|
import { CardActionsAPI } from '../../types';
|
||||||
|
import { AdvancedCameraCardAction } from './base';
|
||||||
|
|
||||||
|
export class InternalCallbackAction extends AdvancedCameraCardAction<InternalCallbackActionConfig> {
|
||||||
|
public async execute(api: CardActionsAPI): Promise<void> {
|
||||||
|
await this._action.callback(api);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ActionConfig } from '@dermotduffy/custom-card-helpers';
|
import { ActionConfig } from '@dermotduffy/custom-card-helpers';
|
||||||
import { ActionContext } from 'action';
|
import { ActionContext } from 'action';
|
||||||
import { ActionType } from '../../config/types';
|
import { ActionType, INTERNAL_CALLBACK_ACTION } from '../../config/types';
|
||||||
import { convertActionToCardCustomAction } from '../../utils/action';
|
import { convertActionToCardCustomAction } from '../../utils/action';
|
||||||
import { CameraSelectAction } from './actions/camera-select';
|
import { CameraSelectAction } from './actions/camera-select';
|
||||||
import { CameraUIAction } from './actions/camera-ui';
|
import { CameraUIAction } from './actions/camera-ui';
|
||||||
@@ -10,6 +10,7 @@ import { DownloadAction } from './actions/download';
|
|||||||
import { ExpandAction } from './actions/expand';
|
import { ExpandAction } from './actions/expand';
|
||||||
import { FullscreenAction } from './actions/fullscreen';
|
import { FullscreenAction } from './actions/fullscreen';
|
||||||
import { GenericAction } from './actions/generic';
|
import { GenericAction } from './actions/generic';
|
||||||
|
import { InternalCallbackAction } from './actions/internal-callback';
|
||||||
import { LogAction } from './actions/log';
|
import { LogAction } from './actions/log';
|
||||||
import { MediaPlayerAction } from './actions/media-player';
|
import { MediaPlayerAction } from './actions/media-player';
|
||||||
import { MenuToggleAction } from './actions/menu-toggle';
|
import { MenuToggleAction } from './actions/menu-toggle';
|
||||||
@@ -132,6 +133,8 @@ export class ActionFactory {
|
|||||||
return new LogAction(context, cardCustomAction, options?.config);
|
return new LogAction(context, cardCustomAction, options?.config);
|
||||||
case 'status_bar':
|
case 'status_bar':
|
||||||
return new StatusBarAction(context, cardCustomAction, options?.config);
|
return new StatusBarAction(context, cardCustomAction, options?.config);
|
||||||
|
case INTERNAL_CALLBACK_ACTION:
|
||||||
|
return new InternalCallbackAction(context, cardCustomAction, options?.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* istanbul ignore next: this path cannot be reached -- @preserve */
|
/* istanbul ignore next: this path cannot be reached -- @preserve */
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { InitializationAspect } from '../initialization-manager.js';
|
|||||||
import { CardConfigAPI } from '../types.js';
|
import { CardConfigAPI } from '../types.js';
|
||||||
import { getOverriddenConfig } from './get-overridden-config.js';
|
import { getOverriddenConfig } from './get-overridden-config.js';
|
||||||
import { setAutomationsFromConfig } from './load-automations.js';
|
import { setAutomationsFromConfig } from './load-automations.js';
|
||||||
|
import { setRemoteControlEntityFromConfig } from './load-control-entities.js';
|
||||||
import { setKeyboardShortcutsFromConfig } from './load-keyboard-shortcuts.js';
|
import { setKeyboardShortcutsFromConfig } from './load-keyboard-shortcuts.js';
|
||||||
|
|
||||||
export class ConfigManager {
|
export class ConfigManager {
|
||||||
@@ -109,7 +110,8 @@ export class ConfigManager {
|
|||||||
this._api.getMessageManager().reset();
|
this._api.getMessageManager().reset();
|
||||||
this._api.getStatusBarItemManager().removeAllDynamicStatusBarItems();
|
this._api.getStatusBarItemManager().removeAllDynamicStatusBarItems();
|
||||||
|
|
||||||
setKeyboardShortcutsFromConfig(this._api, this);
|
setKeyboardShortcutsFromConfig(this._api);
|
||||||
|
setRemoteControlEntityFromConfig(this._api);
|
||||||
setAutomationsFromConfig(this._api);
|
setAutomationsFromConfig(this._api);
|
||||||
|
|
||||||
this._processOverrideConfig();
|
this._processOverrideConfig();
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import {
|
||||||
|
createCameraAction,
|
||||||
|
createInternalCallbackAction,
|
||||||
|
createPerformAction,
|
||||||
|
} from '../../utils/action';
|
||||||
|
import { CardActionsAPI, CardConfigLoaderAPI, TaggedAutomation } from '../types';
|
||||||
|
|
||||||
|
export const setRemoteControlEntityFromConfig = (api: CardConfigLoaderAPI) => {
|
||||||
|
const automationTag = setRemoteControlEntityFromConfig;
|
||||||
|
|
||||||
|
api.getAutomationsManager().deleteAutomations(automationTag);
|
||||||
|
|
||||||
|
const cameraControlEntity = api.getConfigManager().getConfig()?.remote_control
|
||||||
|
?.entities?.camera;
|
||||||
|
if (!cameraControlEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSelectOptionAction = (option: string) =>
|
||||||
|
createPerformAction('input_select.select_option', {
|
||||||
|
target: {
|
||||||
|
entity_id: cameraControlEntity,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
option: option,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Control entities functionality is implemented entirely by populating
|
||||||
|
// automations.
|
||||||
|
|
||||||
|
const automations: TaggedAutomation[] = [
|
||||||
|
{
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
condition: 'config' as const,
|
||||||
|
paths: ['remote_control.entities.camera'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
// Set the possible options on the entity to the camera IDs via a
|
||||||
|
// callback to `setCameraOptionsOnEntity` (below).
|
||||||
|
createInternalCallbackAction((api: CardActionsAPI) =>
|
||||||
|
setCameraOptionsOnEntity(cameraControlEntity, api),
|
||||||
|
),
|
||||||
|
// Set the selected option to the current camera ID.
|
||||||
|
createSelectOptionAction('{{ advanced_camera_card.camera }}'),
|
||||||
|
],
|
||||||
|
tag: automationTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
condition: 'camera' as const,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
// When the camera changes, update the entity to match.
|
||||||
|
createSelectOptionAction('{{ advanced_camera_card.trigger.camera.to }}'),
|
||||||
|
],
|
||||||
|
tag: automationTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
condition: 'state' as const,
|
||||||
|
entity: cameraControlEntity,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
// When the entity state changes, updated the selected option.
|
||||||
|
createCameraAction(
|
||||||
|
'camera_select',
|
||||||
|
'{{ advanced_camera_card.trigger.state.to }}',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tag: automationTag,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
api.getAutomationsManager().addAutomations(automations);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCameraOptionsOnEntity = async (entity: string, api: CardActionsAPI) => {
|
||||||
|
const hass = api.getHASSManager().getHASS();
|
||||||
|
const cameraIDs = api.getCameraManager().getStore().getCameraIDs();
|
||||||
|
|
||||||
|
await hass?.callService(
|
||||||
|
'input_select',
|
||||||
|
'set_options',
|
||||||
|
{
|
||||||
|
options: [...cameraIDs],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity_id: entity,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,18 +6,16 @@ import { PTZAction } from '../../config/ptz';
|
|||||||
import { createPTZMultiAction } from '../../utils/action';
|
import { createPTZMultiAction } from '../../utils/action';
|
||||||
import { CardConfigLoaderAPI, TaggedAutomation } from '../types';
|
import { CardConfigLoaderAPI, TaggedAutomation } from '../types';
|
||||||
|
|
||||||
export const setKeyboardShortcutsFromConfig = (
|
export const setKeyboardShortcutsFromConfig = (api: CardConfigLoaderAPI) => {
|
||||||
api: CardConfigLoaderAPI,
|
const automationTag = setKeyboardShortcutsFromConfig;
|
||||||
tag: unknown,
|
api.getAutomationsManager().deleteAutomations(automationTag);
|
||||||
) => {
|
|
||||||
api.getAutomationsManager().deleteAutomations(tag);
|
|
||||||
|
|
||||||
const shortcuts = api.getConfigManager().getConfig()?.view.keyboard_shortcuts;
|
const shortcuts = api.getConfigManager().getConfig()?.view.keyboard_shortcuts;
|
||||||
if (!shortcuts) {
|
if (!shortcuts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const automations = convertKeyboardShortcutsToAutomations(tag, shortcuts);
|
const automations = convertKeyboardShortcutsToAutomations(automationTag, shortcuts);
|
||||||
if (automations.length) {
|
if (automations.length) {
|
||||||
api.getAutomationsManager().addAutomations(automations);
|
api.getAutomationsManager().addAutomations(automations);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { AdvancedCameraCardCustomAction, ViewActionConfig } from '../config/types';
|
import { AdvancedCameraCardCustomAction, ViewActionConfig } from '../config/types';
|
||||||
import { createCameraAction, createGeneralAction } from '../utils/action.js';
|
import {
|
||||||
|
createCameraAction,
|
||||||
|
createGeneralAction,
|
||||||
|
createViewAction,
|
||||||
|
} from '../utils/action.js';
|
||||||
import { ViewParameters } from '../view/view';
|
import { ViewParameters } from '../view/view';
|
||||||
import { CardQueryStringAPI } from './types';
|
import { CardQueryStringAPI } from './types';
|
||||||
import { SubstreamSelectViewModifier } from './view/modifiers/substream-select';
|
import { SubstreamSelectViewModifier } from './view/modifiers/substream-select';
|
||||||
@@ -113,21 +117,25 @@ export class QueryStringManager {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'camera_ui':
|
case 'camera_ui':
|
||||||
case 'clip':
|
|
||||||
case 'clips':
|
|
||||||
case 'default':
|
case 'default':
|
||||||
case 'diagnostics':
|
|
||||||
case 'download':
|
case 'download':
|
||||||
case 'expand':
|
case 'expand':
|
||||||
|
case 'menu_toggle':
|
||||||
|
customAction = createGeneralAction(action, {
|
||||||
|
cardID: cardID,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'clip':
|
||||||
|
case 'clips':
|
||||||
|
case 'diagnostics':
|
||||||
case 'image':
|
case 'image':
|
||||||
case 'live':
|
case 'live':
|
||||||
case 'menu_toggle':
|
|
||||||
case 'recording':
|
case 'recording':
|
||||||
case 'recordings':
|
case 'recordings':
|
||||||
case 'snapshot':
|
case 'snapshot':
|
||||||
case 'snapshots':
|
case 'snapshots':
|
||||||
case 'timeline':
|
case 'timeline':
|
||||||
customAction = createGeneralAction(action, {
|
customAction = createViewAction(action, {
|
||||||
cardID: cardID,
|
cardID: cardID,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export interface CardConfigAPI {
|
|||||||
getConditionStateManager(): ConditionStateManager;
|
getConditionStateManager(): ConditionStateManager;
|
||||||
getConfigManager(): ConfigManager;
|
getConfigManager(): ConfigManager;
|
||||||
getDefaultManager(): DefaultManager;
|
getDefaultManager(): DefaultManager;
|
||||||
|
getHASSManager(): HASSManager;
|
||||||
getInitializationManager(): InitializationManager;
|
getInitializationManager(): InitializationManager;
|
||||||
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
||||||
getMediaPlayerManager(): MediaPlayerManager;
|
getMediaPlayerManager(): MediaPlayerManager;
|
||||||
@@ -101,8 +102,9 @@ export interface CardConfigAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CardConfigLoaderAPI {
|
export interface CardConfigLoaderAPI {
|
||||||
getConfigManager(): ConfigManager;
|
|
||||||
getAutomationsManager(): AutomationsManager;
|
getAutomationsManager(): AutomationsManager;
|
||||||
|
getConfigManager(): ConfigManager;
|
||||||
|
getHASSManager(): HASSManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardDefaultManagerAPI {
|
export interface CardDefaultManagerAPI {
|
||||||
@@ -178,6 +180,7 @@ export interface CardInitializerAPI {
|
|||||||
getCardElementManager(): CardElementManager;
|
getCardElementManager(): CardElementManager;
|
||||||
getConditionStateManager(): ConditionStateManager;
|
getConditionStateManager(): ConditionStateManager;
|
||||||
getConfigManager(): ConfigManager;
|
getConfigManager(): ConfigManager;
|
||||||
|
getConditionStateManager(): ConditionStateManager;
|
||||||
getDefaultManager(): DefaultManager;
|
getDefaultManager(): DefaultManager;
|
||||||
getEntityRegistryManager(): EntityRegistryManager;
|
getEntityRegistryManager(): EntityRegistryManager;
|
||||||
getHASSManager(): HASSManager;
|
getHASSManager(): HASSManager;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
createMediaPlayerAction,
|
createMediaPlayerAction,
|
||||||
createPTZControlsAction,
|
createPTZControlsAction,
|
||||||
createPTZMultiAction,
|
createPTZMultiAction,
|
||||||
|
createViewAction,
|
||||||
} from '../utils/action';
|
} from '../utils/action';
|
||||||
import { isTruthy } from '../utils/basic';
|
import { isTruthy } from '../utils/basic';
|
||||||
import { isBeingCasted } from '../utils/casting';
|
import { isBeingCasted } from '../utils/casting';
|
||||||
@@ -119,7 +120,7 @@ export class MenuButtonController {
|
|||||||
config.menu?.style === 'hidden'
|
config.menu?.style === 'hidden'
|
||||||
? (createGeneralAction('menu_toggle') as AdvancedCameraCardCustomAction)
|
? (createGeneralAction('menu_toggle') as AdvancedCameraCardCustomAction)
|
||||||
: (createGeneralAction('default') as AdvancedCameraCardCustomAction),
|
: (createGeneralAction('default') as AdvancedCameraCardCustomAction),
|
||||||
hold_action: createGeneralAction('diagnostics') as AdvancedCameraCardCustomAction,
|
hold_action: createViewAction('diagnostics') as AdvancedCameraCardCustomAction,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +234,7 @@ export class MenuButtonController {
|
|||||||
type: 'custom:advanced-camera-card-menu-icon',
|
type: 'custom:advanced-camera-card-menu-icon',
|
||||||
title: localize('config.view.views.live'),
|
title: localize('config.view.views.live'),
|
||||||
style: view.is('live') ? this._getEmphasizedStyle() : {},
|
style: view.is('live') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createGeneralAction('live') as AdvancedCameraCardCustomAction,
|
tap_action: createViewAction('live') as AdvancedCameraCardCustomAction,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
@@ -250,8 +251,8 @@ export class MenuButtonController {
|
|||||||
type: 'custom:advanced-camera-card-menu-icon',
|
type: 'custom:advanced-camera-card-menu-icon',
|
||||||
title: localize('config.view.views.clips'),
|
title: localize('config.view.views.clips'),
|
||||||
style: view?.is('clips') ? this._getEmphasizedStyle() : {},
|
style: view?.is('clips') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createGeneralAction('clips') as AdvancedCameraCardCustomAction,
|
tap_action: createViewAction('clips') as AdvancedCameraCardCustomAction,
|
||||||
hold_action: createGeneralAction('clip') as AdvancedCameraCardCustomAction,
|
hold_action: createViewAction('clip') as AdvancedCameraCardCustomAction,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
@@ -268,8 +269,8 @@ export class MenuButtonController {
|
|||||||
type: 'custom:advanced-camera-card-menu-icon',
|
type: 'custom:advanced-camera-card-menu-icon',
|
||||||
title: localize('config.view.views.snapshots'),
|
title: localize('config.view.views.snapshots'),
|
||||||
style: view?.is('snapshots') ? this._getEmphasizedStyle() : {},
|
style: view?.is('snapshots') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createGeneralAction('snapshots') as AdvancedCameraCardCustomAction,
|
tap_action: createViewAction('snapshots') as AdvancedCameraCardCustomAction,
|
||||||
hold_action: createGeneralAction('snapshot') as AdvancedCameraCardCustomAction,
|
hold_action: createViewAction('snapshot') as AdvancedCameraCardCustomAction,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
@@ -286,12 +287,8 @@ export class MenuButtonController {
|
|||||||
type: 'custom:advanced-camera-card-menu-icon',
|
type: 'custom:advanced-camera-card-menu-icon',
|
||||||
title: localize('config.view.views.recordings'),
|
title: localize('config.view.views.recordings'),
|
||||||
style: view.is('recordings') ? this._getEmphasizedStyle() : {},
|
style: view.is('recordings') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createGeneralAction(
|
tap_action: createViewAction('recordings') as AdvancedCameraCardCustomAction,
|
||||||
'recordings',
|
hold_action: createViewAction('recording') as AdvancedCameraCardCustomAction,
|
||||||
) as AdvancedCameraCardCustomAction,
|
|
||||||
hold_action: createGeneralAction(
|
|
||||||
'recording',
|
|
||||||
) as AdvancedCameraCardCustomAction,
|
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
@@ -308,7 +305,7 @@ export class MenuButtonController {
|
|||||||
type: 'custom:advanced-camera-card-menu-icon',
|
type: 'custom:advanced-camera-card-menu-icon',
|
||||||
title: localize('config.view.views.image'),
|
title: localize('config.view.views.image'),
|
||||||
style: view?.is('image') ? this._getEmphasizedStyle() : {},
|
style: view?.is('image') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createGeneralAction('image') as AdvancedCameraCardCustomAction,
|
tap_action: createViewAction('image') as AdvancedCameraCardCustomAction,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
@@ -325,7 +322,7 @@ export class MenuButtonController {
|
|||||||
type: 'custom:advanced-camera-card-menu-icon',
|
type: 'custom:advanced-camera-card-menu-icon',
|
||||||
title: localize('config.view.views.timeline'),
|
title: localize('config.view.views.timeline'),
|
||||||
style: view.is('timeline') ? this._getEmphasizedStyle() : {},
|
style: view.is('timeline') ? this._getEmphasizedStyle() : {},
|
||||||
tap_action: createGeneralAction('timeline') as AdvancedCameraCardCustomAction,
|
tap_action: createViewAction('timeline') as AdvancedCameraCardCustomAction,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-2
@@ -397,7 +397,7 @@ const logActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({
|
|||||||
});
|
});
|
||||||
export type LogActionConfig = z.infer<typeof logActionConfigSchema>;
|
export type LogActionConfig = z.infer<typeof logActionConfigSchema>;
|
||||||
|
|
||||||
export const advancedCameraCardCustomActionSchema = z.union([
|
const advancedCameraCardCustomActionSchema = z.union([
|
||||||
cameraSelectActionConfigSchema,
|
cameraSelectActionConfigSchema,
|
||||||
generalActionConfigSchema,
|
generalActionConfigSchema,
|
||||||
substreamSelectActionConfigSchema,
|
substreamSelectActionConfigSchema,
|
||||||
@@ -416,6 +416,26 @@ export type AdvancedCameraCardCustomAction = z.infer<
|
|||||||
typeof advancedCameraCardCustomActionSchema
|
typeof advancedCameraCardCustomActionSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
// An action that can be used internally to call a callback.
|
||||||
|
// Note: The internal callback action is kept out of schemas that can be user-specified.
|
||||||
|
export const INTERNAL_CALLBACK_ACTION = '__INTERNAL_CALLBACK_ACTION__';
|
||||||
|
const internalCallbackActionConfigSchema =
|
||||||
|
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||||
|
advanced_camera_card_action: z.literal(INTERNAL_CALLBACK_ACTION),
|
||||||
|
|
||||||
|
// The callback is expected to be called with a CardController API object.
|
||||||
|
callback: z.function().args(z.any()).returns(z.promise(z.void())),
|
||||||
|
});
|
||||||
|
export type InternalCallbackActionConfig = z.infer<
|
||||||
|
typeof internalCallbackActionConfigSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const internalAdvancedCameraCardCustomActionSchema =
|
||||||
|
advancedCameraCardCustomActionSchema.or(internalCallbackActionConfigSchema);
|
||||||
|
export type InternalAdvancedCameraCardCustomAction = z.infer<
|
||||||
|
typeof internalAdvancedCameraCardCustomActionSchema
|
||||||
|
>;
|
||||||
|
|
||||||
// Cannot use discriminatedUnion since advancedCameraCardCustomActionSchema uses
|
// Cannot use discriminatedUnion since advancedCameraCardCustomActionSchema uses
|
||||||
// a transform on the discriminated union key.
|
// a transform on the discriminated union key.
|
||||||
export const actionSchema = z.union([
|
export const actionSchema = z.union([
|
||||||
@@ -429,7 +449,9 @@ export const actionSchema = z.union([
|
|||||||
customActionSchema,
|
customActionSchema,
|
||||||
advancedCameraCardCustomActionSchema,
|
advancedCameraCardCustomActionSchema,
|
||||||
]);
|
]);
|
||||||
export type ActionType = z.infer<typeof actionSchema>;
|
|
||||||
|
const internalActionSchema = actionSchema.or(internalCallbackActionConfigSchema);
|
||||||
|
export type ActionType = z.infer<typeof internalActionSchema>;
|
||||||
|
|
||||||
const actionsBaseSchema = z
|
const actionsBaseSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -2075,6 +2097,20 @@ const PROFILES = ['casting', 'low-performance', 'scrubbing'] as const;
|
|||||||
export type ProfileType = (typeof PROFILES)[number];
|
export type ProfileType = (typeof PROFILES)[number];
|
||||||
export const profilesSchema = z.enum(PROFILES).array().optional();
|
export const profilesSchema = z.enum(PROFILES).array().optional();
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
// *** Remote Control Configuration ***
|
||||||
|
// *************************************************************************
|
||||||
|
|
||||||
|
const remoteControlConfigSchema = z
|
||||||
|
.object({
|
||||||
|
entities: z
|
||||||
|
.object({
|
||||||
|
camera: z.string().startsWith('input_select.').optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.optional();
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
// *** Card Configuration ***
|
// *** Card Configuration ***
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
@@ -2114,6 +2150,8 @@ export const advancedCameraCardConfigSchema = z.object({
|
|||||||
// that be easily used in a URL.
|
// that be easily used in a URL.
|
||||||
card_id: z.string().regex(cardIDRegex).optional(),
|
card_id: z.string().regex(cardIDRegex).optional(),
|
||||||
|
|
||||||
|
remote_control: remoteControlConfigSchema,
|
||||||
|
|
||||||
// Stock lovelace card config.
|
// Stock lovelace card config.
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -382,6 +382,10 @@ export const CONF_PERFORMANCE_STYLE_BORDER_RADIUS = `${CONF_PERFORMANCE}.style.b
|
|||||||
|
|
||||||
export const CONF_PROFILES = 'profiles' as const;
|
export const CONF_PROFILES = 'profiles' as const;
|
||||||
|
|
||||||
|
export const CONF_REMOTE_CONTROL = 'remote_control' as const;
|
||||||
|
export const CONF_REMOTE_CONTROL_ENTITIES_CAMERA =
|
||||||
|
`${CONF_REMOTE_CONTROL}.entities.camera` as const;
|
||||||
|
|
||||||
// Taken from https://github.com/home-assistant/frontend/blob/a759767d794f02527d127802831e68d3caf0cb7a/src/data/media-player.ts#L82
|
// Taken from https://github.com/home-assistant/frontend/blob/a759767d794f02527d127802831e68d3caf0cb7a/src/data/media-player.ts#L82
|
||||||
export const MEDIA_PLAYER_SUPPORT_TURN_OFF = 256;
|
export const MEDIA_PLAYER_SUPPORT_TURN_OFF = 256;
|
||||||
export const MEDIA_PLAYER_SUPPORT_STOP = 4096;
|
export const MEDIA_PLAYER_SUPPORT_STOP = 4096;
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ import {
|
|||||||
CONF_PERFORMANCE_STYLE_BORDER_RADIUS,
|
CONF_PERFORMANCE_STYLE_BORDER_RADIUS,
|
||||||
CONF_PERFORMANCE_STYLE_BOX_SHADOW,
|
CONF_PERFORMANCE_STYLE_BOX_SHADOW,
|
||||||
CONF_PROFILES,
|
CONF_PROFILES,
|
||||||
|
CONF_REMOTE_CONTROL_ENTITIES_CAMERA,
|
||||||
CONF_STATUS_BAR_HEIGHT,
|
CONF_STATUS_BAR_HEIGHT,
|
||||||
CONF_STATUS_BAR_ITEMS,
|
CONF_STATUS_BAR_ITEMS,
|
||||||
CONF_STATUS_BAR_POPUP_SECONDS,
|
CONF_STATUS_BAR_POPUP_SECONDS,
|
||||||
@@ -281,6 +282,7 @@ const MENU_MENU_BUTTONS = 'menu.buttons';
|
|||||||
const MENU_OPTIONS = 'options';
|
const MENU_OPTIONS = 'options';
|
||||||
const MENU_PERFORMANCE_FEATURES = 'performance.features';
|
const MENU_PERFORMANCE_FEATURES = 'performance.features';
|
||||||
const MENU_PERFORMANCE_STYLE = 'performance.style';
|
const MENU_PERFORMANCE_STYLE = 'performance.style';
|
||||||
|
const MENU_REMOTE_CONTROL_ENTITIES = 'remote_control.entities';
|
||||||
const MENU_STATUS_BAR_ITEMS = 'status_bar.items';
|
const MENU_STATUS_BAR_ITEMS = 'status_bar.items';
|
||||||
const MENU_TIMELINE_FORMAT = 'timeline.format';
|
const MENU_TIMELINE_FORMAT = 'timeline.format';
|
||||||
const MENU_TIMELINE_CONTROLS_THUMBNAILS = 'timeline.controls.thumbnails';
|
const MENU_TIMELINE_CONTROLS_THUMBNAILS = 'timeline.controls.thumbnails';
|
||||||
@@ -369,6 +371,11 @@ const options: EditorOptions = {
|
|||||||
name: localize('editor.profiles'),
|
name: localize('editor.profiles'),
|
||||||
secondary: localize('editor.profiles_secondary'),
|
secondary: localize('editor.profiles_secondary'),
|
||||||
},
|
},
|
||||||
|
remote_control: {
|
||||||
|
icon: 'remote',
|
||||||
|
name: localize('editor.remote_control'),
|
||||||
|
secondary: localize('editor.remote_control_secondary'),
|
||||||
|
},
|
||||||
overrides: {
|
overrides: {
|
||||||
icon: 'file-replace',
|
icon: 'file-replace',
|
||||||
name: localize('editor.overrides'),
|
name: localize('editor.overrides'),
|
||||||
@@ -3051,6 +3058,23 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
|
|||||||
)}
|
)}
|
||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
|
${this._renderOptionSetHeader('remote_control')}
|
||||||
|
${this._expandedMenus[MENU_OPTIONS] === 'remote_control'
|
||||||
|
? html` <div class="values">
|
||||||
|
${this._putInSubmenu(
|
||||||
|
MENU_REMOTE_CONTROL_ENTITIES,
|
||||||
|
true,
|
||||||
|
'config.remote_control.entities.editor_label',
|
||||||
|
'mdi:devices',
|
||||||
|
html`
|
||||||
|
${this._renderEntitySelector(
|
||||||
|
CONF_REMOTE_CONTROL_ENTITIES_CAMERA,
|
||||||
|
'input_select',
|
||||||
|
)}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</div>`
|
||||||
|
: ''}
|
||||||
${this._config['overrides'] !== undefined
|
${this._config['overrides'] !== undefined
|
||||||
? html` ${this._renderOptionSetHeader('overrides')}
|
? html` ${this._renderOptionSetHeader('overrides')}
|
||||||
${this._expandedMenus[MENU_OPTIONS] === 'overrides'
|
${this._expandedMenus[MENU_OPTIONS] === 'overrides'
|
||||||
|
|||||||
@@ -459,6 +459,12 @@
|
|||||||
"low-performance": "",
|
"low-performance": "",
|
||||||
"scrubbing": ""
|
"scrubbing": ""
|
||||||
},
|
},
|
||||||
|
"remote_control": {
|
||||||
|
"entities": {
|
||||||
|
"camera": "",
|
||||||
|
"editor_label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"status_bar": {
|
"status_bar": {
|
||||||
"height": "",
|
"height": "",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -596,6 +602,8 @@
|
|||||||
"performance_secondary": "Opcions de rendiment de la targeta",
|
"performance_secondary": "Opcions de rendiment de la targeta",
|
||||||
"profiles": "",
|
"profiles": "",
|
||||||
"profiles_secondary": "",
|
"profiles_secondary": "",
|
||||||
|
"remote_control": "",
|
||||||
|
"remote_control_secondary": "",
|
||||||
"status_bar": "",
|
"status_bar": "",
|
||||||
"status_bar_secondary": "",
|
"status_bar_secondary": "",
|
||||||
"timeline": "Cronologia",
|
"timeline": "Cronologia",
|
||||||
|
|||||||
@@ -459,6 +459,12 @@
|
|||||||
"low-performance": "Low performance",
|
"low-performance": "Low performance",
|
||||||
"scrubbing": "Video scrubbing"
|
"scrubbing": "Video scrubbing"
|
||||||
},
|
},
|
||||||
|
"remote_control": {
|
||||||
|
"entities": {
|
||||||
|
"editor_label": "Remote Control Entities",
|
||||||
|
"camera": "Input Select entity to control camera"
|
||||||
|
}
|
||||||
|
},
|
||||||
"status_bar": {
|
"status_bar": {
|
||||||
"height": "Status bar height in pixels",
|
"height": "Status bar height in pixels",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -596,6 +602,8 @@
|
|||||||
"performance_secondary": "Card performance options",
|
"performance_secondary": "Card performance options",
|
||||||
"profiles": "Configuration profiles",
|
"profiles": "Configuration profiles",
|
||||||
"profiles_secondary": "Choose pre-configured sets of defaults",
|
"profiles_secondary": "Choose pre-configured sets of defaults",
|
||||||
|
"remote_control": "Remote Control",
|
||||||
|
"remote_control_secondary": "Options for remote controlling the card",
|
||||||
"status_bar": "Status bar",
|
"status_bar": "Status bar",
|
||||||
"status_bar_secondary": "Status bar look & feel options",
|
"status_bar_secondary": "Status bar look & feel options",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
|
|||||||
@@ -459,6 +459,12 @@
|
|||||||
"low-performance": "Basse performance",
|
"low-performance": "Basse performance",
|
||||||
"scrubbing": "Balayage vidéo"
|
"scrubbing": "Balayage vidéo"
|
||||||
},
|
},
|
||||||
|
"remote_control": {
|
||||||
|
"entities": {
|
||||||
|
"camera": "",
|
||||||
|
"editor_label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"status_bar": {
|
"status_bar": {
|
||||||
"height": "Hauteur de la barre d'état en pixels",
|
"height": "Hauteur de la barre d'état en pixels",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -596,6 +602,8 @@
|
|||||||
"performance_secondary": "Options de performances de la carte",
|
"performance_secondary": "Options de performances de la carte",
|
||||||
"profiles": "Profils de configuration",
|
"profiles": "Profils de configuration",
|
||||||
"profiles_secondary": "Choisir des ensembles de paramètres par défaut pré-configurés",
|
"profiles_secondary": "Choisir des ensembles de paramètres par défaut pré-configurés",
|
||||||
|
"remote_control": "",
|
||||||
|
"remote_control_secondary": "",
|
||||||
"status_bar": "Barre d'état",
|
"status_bar": "Barre d'état",
|
||||||
"status_bar_secondary": "Options d'apparence et de comportement de la barre d'état",
|
"status_bar_secondary": "Options d'apparence et de comportement de la barre d'état",
|
||||||
"timeline": "Chronologie",
|
"timeline": "Chronologie",
|
||||||
|
|||||||
@@ -459,6 +459,12 @@
|
|||||||
"low-performance": "",
|
"low-performance": "",
|
||||||
"scrubbing": ""
|
"scrubbing": ""
|
||||||
},
|
},
|
||||||
|
"remote_control": {
|
||||||
|
"entities": {
|
||||||
|
"camera": "",
|
||||||
|
"editor_label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"status_bar": {
|
"status_bar": {
|
||||||
"height": "",
|
"height": "",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -596,6 +602,8 @@
|
|||||||
"performance_secondary": "",
|
"performance_secondary": "",
|
||||||
"profiles": "",
|
"profiles": "",
|
||||||
"profiles_secondary": "",
|
"profiles_secondary": "",
|
||||||
|
"remote_control": "",
|
||||||
|
"remote_control_secondary": "",
|
||||||
"status_bar": "",
|
"status_bar": "",
|
||||||
"status_bar_secondary": "",
|
"status_bar_secondary": "",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
|
|||||||
@@ -459,6 +459,12 @@
|
|||||||
"low-performance": "",
|
"low-performance": "",
|
||||||
"scrubbing": ""
|
"scrubbing": ""
|
||||||
},
|
},
|
||||||
|
"remote_control": {
|
||||||
|
"entities": {
|
||||||
|
"camera": "",
|
||||||
|
"editor_label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"status_bar": {
|
"status_bar": {
|
||||||
"height": "",
|
"height": "",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -596,6 +602,8 @@
|
|||||||
"performance_secondary": "Opções de desempenho do cartão",
|
"performance_secondary": "Opções de desempenho do cartão",
|
||||||
"profiles": "",
|
"profiles": "",
|
||||||
"profiles_secondary": "",
|
"profiles_secondary": "",
|
||||||
|
"remote_control": "",
|
||||||
|
"remote_control_secondary": "",
|
||||||
"status_bar": "",
|
"status_bar": "",
|
||||||
"status_bar_secondary": "",
|
"status_bar_secondary": "",
|
||||||
"timeline": "Linha do tempo",
|
"timeline": "Linha do tempo",
|
||||||
|
|||||||
@@ -459,6 +459,12 @@
|
|||||||
"low-performance": "",
|
"low-performance": "",
|
||||||
"scrubbing": ""
|
"scrubbing": ""
|
||||||
},
|
},
|
||||||
|
"remote_control": {
|
||||||
|
"entities": {
|
||||||
|
"camera": "",
|
||||||
|
"editor_label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"status_bar": {
|
"status_bar": {
|
||||||
"height": "",
|
"height": "",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -596,6 +602,8 @@
|
|||||||
"performance_secondary": "",
|
"performance_secondary": "",
|
||||||
"profiles": "",
|
"profiles": "",
|
||||||
"profiles_secondary": "",
|
"profiles_secondary": "",
|
||||||
|
"remote_control": "",
|
||||||
|
"remote_control_secondary": "",
|
||||||
"status_bar": "",
|
"status_bar": "",
|
||||||
"status_bar_secondary": "",
|
"status_bar_secondary": "",
|
||||||
"timeline": "Linha do tempo",
|
"timeline": "Linha do tempo",
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ export interface CapabilitiesRaw {
|
|||||||
'favorite-events'?: boolean;
|
'favorite-events'?: boolean;
|
||||||
'favorite-recordings'?: boolean;
|
'favorite-recordings'?: boolean;
|
||||||
|
|
||||||
|
'remote-control-entity'?: boolean;
|
||||||
|
|
||||||
seek?: boolean;
|
seek?: boolean;
|
||||||
|
|
||||||
ptz?: PTZCapabilities;
|
ptz?: PTZCapabilities;
|
||||||
@@ -123,6 +125,7 @@ export interface CapabilitiesRaw {
|
|||||||
export type CapabilityKey = keyof CapabilitiesRaw;
|
export type CapabilityKey = keyof CapabilitiesRaw;
|
||||||
export const capabilityKeys: readonly [CapabilityKey, ...CapabilityKey[]] = [
|
export const capabilityKeys: readonly [CapabilityKey, ...CapabilityKey[]] = [
|
||||||
'clips',
|
'clips',
|
||||||
|
'remote-control-entity',
|
||||||
'favorite-events',
|
'favorite-events',
|
||||||
'favorite-recordings',
|
'favorite-recordings',
|
||||||
'live',
|
'live',
|
||||||
|
|||||||
+63
-10
@@ -1,22 +1,33 @@
|
|||||||
import {
|
import {
|
||||||
ActionConfig,
|
ActionConfig,
|
||||||
|
ServiceCallRequest,
|
||||||
hasAction as customCardHasAction,
|
hasAction as customCardHasAction,
|
||||||
} from '@dermotduffy/custom-card-helpers';
|
} from '@dermotduffy/custom-card-helpers';
|
||||||
|
import { CardActionsAPI } from '../card-controller/types.js';
|
||||||
import { ZoomSettingsBase } from '../components-lib/zoom/types.js';
|
import { ZoomSettingsBase } from '../components-lib/zoom/types.js';
|
||||||
import { PTZAction } from '../config/ptz.js';
|
import { PTZAction } from '../config/ptz.js';
|
||||||
import {
|
import {
|
||||||
ActionPhase,
|
ActionPhase,
|
||||||
ActionType,
|
ActionType,
|
||||||
Actions,
|
Actions,
|
||||||
AdvancedCameraCardCustomAction,
|
|
||||||
AdvancedCameraCardGeneralAction,
|
AdvancedCameraCardGeneralAction,
|
||||||
AdvancedCameraCardUserSpecifiedView,
|
AdvancedCameraCardUserSpecifiedView,
|
||||||
|
CameraSelectActionConfig,
|
||||||
|
DisplayModeActionConfig,
|
||||||
|
GeneralActionConfig,
|
||||||
|
INTERNAL_CALLBACK_ACTION,
|
||||||
|
InternalAdvancedCameraCardCustomAction,
|
||||||
|
InternalCallbackActionConfig,
|
||||||
LogActionConfig,
|
LogActionConfig,
|
||||||
LogActionLevel,
|
LogActionLevel,
|
||||||
|
MediaPlayerActionConfig,
|
||||||
PTZActionConfig,
|
PTZActionConfig,
|
||||||
|
PTZControlsActionConfig,
|
||||||
PTZDigitialActionConfig,
|
PTZDigitialActionConfig,
|
||||||
PTZMultiActionConfig,
|
PTZMultiActionConfig,
|
||||||
advancedCameraCardCustomActionSchema,
|
SubstreamSelectActionConfig,
|
||||||
|
ViewActionConfig,
|
||||||
|
internalAdvancedCameraCardCustomActionSchema,
|
||||||
} from '../config/types.js';
|
} from '../config/types.js';
|
||||||
import { arrayify } from './basic.js';
|
import { arrayify } from './basic.js';
|
||||||
|
|
||||||
@@ -27,22 +38,35 @@ import { arrayify } from './basic.js';
|
|||||||
*/
|
*/
|
||||||
export function convertActionToCardCustomAction(
|
export function convertActionToCardCustomAction(
|
||||||
action: unknown,
|
action: unknown,
|
||||||
): AdvancedCameraCardCustomAction | null {
|
): InternalAdvancedCameraCardCustomAction | null {
|
||||||
if (!action) {
|
if (!action) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Parse a custom event as other things could generate ll-custom events that
|
// Parse a custom event as other things could generate ll-custom events that
|
||||||
// are not related to Advanced Camera Card.
|
// are not related to Advanced Camera Card.
|
||||||
const parseResult = advancedCameraCardCustomActionSchema.safeParse(action);
|
const parseResult = internalAdvancedCameraCardCustomActionSchema.safeParse(action);
|
||||||
return parseResult.success ? parseResult.data : null;
|
return parseResult.success ? parseResult.data : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createGeneralAction(
|
export function createGeneralAction(
|
||||||
action: AdvancedCameraCardGeneralAction | AdvancedCameraCardUserSpecifiedView,
|
action: AdvancedCameraCardGeneralAction,
|
||||||
options?: {
|
options?: {
|
||||||
cardID?: string;
|
cardID?: string;
|
||||||
},
|
},
|
||||||
): AdvancedCameraCardCustomAction {
|
): GeneralActionConfig {
|
||||||
|
return {
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
advanced_camera_card_action: action,
|
||||||
|
...(options?.cardID && { card_id: options.cardID }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createViewAction(
|
||||||
|
action: AdvancedCameraCardUserSpecifiedView,
|
||||||
|
options?: {
|
||||||
|
cardID?: string;
|
||||||
|
},
|
||||||
|
): ViewActionConfig {
|
||||||
return {
|
return {
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
advanced_camera_card_action: action,
|
advanced_camera_card_action: action,
|
||||||
@@ -56,7 +80,7 @@ export function createCameraAction(
|
|||||||
options?: {
|
options?: {
|
||||||
cardID?: string;
|
cardID?: string;
|
||||||
},
|
},
|
||||||
): AdvancedCameraCardCustomAction {
|
): CameraSelectActionConfig | SubstreamSelectActionConfig {
|
||||||
return {
|
return {
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
advanced_camera_card_action: action,
|
advanced_camera_card_action: action,
|
||||||
@@ -71,7 +95,7 @@ export function createMediaPlayerAction(
|
|||||||
options?: {
|
options?: {
|
||||||
cardID?: string;
|
cardID?: string;
|
||||||
},
|
},
|
||||||
): AdvancedCameraCardCustomAction {
|
): MediaPlayerActionConfig {
|
||||||
return {
|
return {
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
advanced_camera_card_action: 'media_player',
|
advanced_camera_card_action: 'media_player',
|
||||||
@@ -86,7 +110,7 @@ export function createDisplayModeAction(
|
|||||||
options?: {
|
options?: {
|
||||||
cardID?: string;
|
cardID?: string;
|
||||||
},
|
},
|
||||||
): AdvancedCameraCardCustomAction {
|
): DisplayModeActionConfig {
|
||||||
return {
|
return {
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
advanced_camera_card_action: 'display_mode_select',
|
advanced_camera_card_action: 'display_mode_select',
|
||||||
@@ -100,7 +124,7 @@ export function createPTZControlsAction(
|
|||||||
options?: {
|
options?: {
|
||||||
cardID?: string;
|
cardID?: string;
|
||||||
},
|
},
|
||||||
): AdvancedCameraCardCustomAction {
|
): PTZControlsActionConfig {
|
||||||
return {
|
return {
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
advanced_camera_card_action: 'ptz_controls',
|
advanced_camera_card_action: 'ptz_controls',
|
||||||
@@ -179,6 +203,35 @@ export function createLogAction(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createInternalCallbackAction(
|
||||||
|
callback: (api: CardActionsAPI) => Promise<void>,
|
||||||
|
options?: {
|
||||||
|
cardID?: string;
|
||||||
|
},
|
||||||
|
): InternalCallbackActionConfig {
|
||||||
|
return {
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
advanced_camera_card_action: INTERNAL_CALLBACK_ACTION,
|
||||||
|
callback: callback,
|
||||||
|
...(options?.cardID && { card_id: options.cardID }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPerformAction(
|
||||||
|
perform_action: string,
|
||||||
|
options?: {
|
||||||
|
data?: ServiceCallRequest['serviceData'];
|
||||||
|
target?: ServiceCallRequest['target'];
|
||||||
|
},
|
||||||
|
): ActionType {
|
||||||
|
return {
|
||||||
|
action: 'perform-action' as const,
|
||||||
|
perform_action: perform_action,
|
||||||
|
...(options?.target && { target: options.target }),
|
||||||
|
...(options?.data && { data: options.data }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an action configuration given a config and an interaction (e.g. 'tap').
|
* Get an action configuration given a config and an interaction (e.g. 'tap').
|
||||||
* @param interaction The interaction: `tap`, `hold` or `double_tap`
|
* @param interaction The interaction: `tap`, `hold` or `double_tap`
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ describe('ReolinkCameraManagerEngine', () => {
|
|||||||
'favorite-events': false,
|
'favorite-events': false,
|
||||||
'favorite-recordings': false,
|
'favorite-recordings': false,
|
||||||
clips: true,
|
clips: true,
|
||||||
|
'remote-control-entity': true,
|
||||||
live: true,
|
live: true,
|
||||||
menu: true,
|
menu: true,
|
||||||
recordings: false,
|
recordings: false,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('should handle camera_select action', () => {
|
|||||||
{
|
{
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
advanced_camera_card_action: 'camera_select',
|
advanced_camera_card_action: 'camera_select',
|
||||||
camera: 'camera',
|
camera: 'camera.office',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -23,13 +23,36 @@ describe('should handle camera_select action', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
params: {
|
params: {
|
||||||
view: 'live',
|
view: 'live',
|
||||||
camera: 'camera',
|
camera: 'camera.office',
|
||||||
},
|
},
|
||||||
failSafe: true,
|
failSafe: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should ignore requests to change to the current camera', async () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||||
|
createView({
|
||||||
|
camera: 'camera.office',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||||
|
|
||||||
|
const action = new CameraSelectAction(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
advanced_camera_card_action: 'camera_select',
|
||||||
|
camera: 'camera.office',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await action.execute(api);
|
||||||
|
|
||||||
|
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('without config', async () => {
|
it('without config', async () => {
|
||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(null);
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(null);
|
||||||
@@ -45,7 +68,7 @@ describe('should handle camera_select action', () => {
|
|||||||
{
|
{
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
advanced_camera_card_action: 'camera_select',
|
advanced_camera_card_action: 'camera_select',
|
||||||
camera: 'camera',
|
camera: 'camera.office',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -55,7 +78,7 @@ describe('should handle camera_select action', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
params: {
|
params: {
|
||||||
view: 'timeline',
|
view: 'timeline',
|
||||||
camera: 'camera',
|
camera: 'camera.office',
|
||||||
},
|
},
|
||||||
failSafe: true,
|
failSafe: true,
|
||||||
}),
|
}),
|
||||||
@@ -84,7 +107,7 @@ describe('should handle camera_select action', () => {
|
|||||||
{
|
{
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
advanced_camera_card_action: 'camera_select',
|
advanced_camera_card_action: 'camera_select',
|
||||||
camera: 'camera',
|
camera: 'camera.office',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -94,7 +117,7 @@ describe('should handle camera_select action', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
params: {
|
params: {
|
||||||
view: 'clips',
|
view: 'clips',
|
||||||
camera: 'camera',
|
camera: 'camera.office',
|
||||||
},
|
},
|
||||||
failSafe: true,
|
failSafe: true,
|
||||||
}),
|
}),
|
||||||
@@ -106,7 +129,7 @@ describe('should handle camera_select action', () => {
|
|||||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||||
vi.mocked(api.getTriggersManager().getMostRecentlyTriggeredCameraID).mockReturnValue(
|
vi.mocked(api.getTriggersManager().getMostRecentlyTriggeredCameraID).mockReturnValue(
|
||||||
'camera',
|
'camera.office',
|
||||||
);
|
);
|
||||||
|
|
||||||
const action = new CameraSelectAction(
|
const action = new CameraSelectAction(
|
||||||
@@ -124,7 +147,7 @@ describe('should handle camera_select action', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
params: {
|
params: {
|
||||||
view: 'live',
|
view: 'live',
|
||||||
camera: 'camera',
|
camera: 'camera.office',
|
||||||
},
|
},
|
||||||
failSafe: true,
|
failSafe: true,
|
||||||
}),
|
}),
|
||||||
@@ -136,7 +159,7 @@ describe('should handle camera_select action', () => {
|
|||||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||||
vi.mocked(api.getTriggersManager().getMostRecentlyTriggeredCameraID).mockReturnValue(
|
vi.mocked(api.getTriggersManager().getMostRecentlyTriggeredCameraID).mockReturnValue(
|
||||||
'camera',
|
'camera.office',
|
||||||
);
|
);
|
||||||
|
|
||||||
const action = new CameraSelectAction(
|
const action = new CameraSelectAction(
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { expect, it, vi } from 'vitest';
|
||||||
|
import { InternalCallbackAction } from '../../../../src/card-controller/actions/actions/internal-callback';
|
||||||
|
import { INTERNAL_CALLBACK_ACTION } from '../../../../src/config/types';
|
||||||
|
import { createCardAPI } from '../../../test-utils';
|
||||||
|
|
||||||
|
it('should handle internal callback action', async () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
const callback = vi.fn();
|
||||||
|
const action = new InternalCallbackAction(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
advanced_camera_card_action: INTERNAL_CALLBACK_ACTION,
|
||||||
|
callback: callback,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await action.execute(api);
|
||||||
|
|
||||||
|
expect(callback).toBeCalledWith(api);
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { CameraSelectAction } from '../../../src/card-controller/actions/actions/camera-select';
|
import { CameraSelectAction } from '../../../src/card-controller/actions/actions/camera-select';
|
||||||
import { CameraUIAction } from '../../../src/card-controller/actions/actions/camera-ui';
|
import { CameraUIAction } from '../../../src/card-controller/actions/actions/camera-ui';
|
||||||
import { DefaultAction } from '../../../src/card-controller/actions/actions/default';
|
import { DefaultAction } from '../../../src/card-controller/actions/actions/default';
|
||||||
@@ -7,6 +7,7 @@ import { DownloadAction } from '../../../src/card-controller/actions/actions/dow
|
|||||||
import { ExpandAction } from '../../../src/card-controller/actions/actions/expand';
|
import { ExpandAction } from '../../../src/card-controller/actions/actions/expand';
|
||||||
import { FullscreenAction } from '../../../src/card-controller/actions/actions/fullscreen';
|
import { FullscreenAction } from '../../../src/card-controller/actions/actions/fullscreen';
|
||||||
import { GenericAction } from '../../../src/card-controller/actions/actions/generic';
|
import { GenericAction } from '../../../src/card-controller/actions/actions/generic';
|
||||||
|
import { InternalCallbackAction } from '../../../src/card-controller/actions/actions/internal-callback';
|
||||||
import { LogAction } from '../../../src/card-controller/actions/actions/log';
|
import { LogAction } from '../../../src/card-controller/actions/actions/log';
|
||||||
import { MediaPlayerAction } from '../../../src/card-controller/actions/actions/media-player';
|
import { MediaPlayerAction } from '../../../src/card-controller/actions/actions/media-player';
|
||||||
import { MenuToggleAction } from '../../../src/card-controller/actions/actions/menu-toggle';
|
import { MenuToggleAction } from '../../../src/card-controller/actions/actions/menu-toggle';
|
||||||
@@ -30,7 +31,10 @@ import { SubstreamSelectAction } from '../../../src/card-controller/actions/acti
|
|||||||
import { UnmuteAction } from '../../../src/card-controller/actions/actions/unmute';
|
import { UnmuteAction } from '../../../src/card-controller/actions/actions/unmute';
|
||||||
import { ViewAction } from '../../../src/card-controller/actions/actions/view';
|
import { ViewAction } from '../../../src/card-controller/actions/actions/view';
|
||||||
import { ActionFactory } from '../../../src/card-controller/actions/factory';
|
import { ActionFactory } from '../../../src/card-controller/actions/factory';
|
||||||
import { AdvancedCameraCardCustomAction } from '../../../src/config/types';
|
import {
|
||||||
|
AdvancedCameraCardCustomAction,
|
||||||
|
INTERNAL_CALLBACK_ACTION,
|
||||||
|
} from '../../../src/config/types';
|
||||||
|
|
||||||
// @vitest-environment jsdom
|
// @vitest-environment jsdom
|
||||||
describe('ActionFactory', () => {
|
describe('ActionFactory', () => {
|
||||||
@@ -165,6 +169,13 @@ describe('ActionFactory', () => {
|
|||||||
},
|
},
|
||||||
StatusBarAction,
|
StatusBarAction,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
advanced_camera_card_action: INTERNAL_CALLBACK_ACTION,
|
||||||
|
callback: vi.fn(),
|
||||||
|
},
|
||||||
|
InternalCallbackAction,
|
||||||
|
],
|
||||||
])(
|
])(
|
||||||
'advanced_camera_card_action: $advanced_camera_card_action',
|
'advanced_camera_card_action: $advanced_camera_card_action',
|
||||||
(action: Partial<AdvancedCameraCardCustomAction>, classObject: object) => {
|
(action: Partial<AdvancedCameraCardCustomAction>, classObject: object) => {
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { setRemoteControlEntityFromConfig } from '../../../src/card-controller/config/load-control-entities';
|
||||||
|
import { createCardAPI, createConfig, createHASS, createStore } from '../../test-utils';
|
||||||
|
import { INTERNAL_CALLBACK_ACTION } from '../../../src/config/types';
|
||||||
|
|
||||||
|
describe('setRemoteControlEntityFromConfig', () => {
|
||||||
|
it('without control entity', () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
setRemoteControlEntityFromConfig(api);
|
||||||
|
|
||||||
|
expect(api.getAutomationsManager().deleteAutomations).toBeCalled();
|
||||||
|
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with control entity', () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||||
|
createConfig({
|
||||||
|
remote_control: {
|
||||||
|
entities: {
|
||||||
|
camera: 'input_select.camera',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
setRemoteControlEntityFromConfig(api);
|
||||||
|
|
||||||
|
expect(api.getAutomationsManager().deleteAutomations).toBeCalled();
|
||||||
|
expect(api.getAutomationsManager().addAutomations).toBeCalledWith([
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
advanced_camera_card_action: '__INTERNAL_CALLBACK_ACTION__',
|
||||||
|
callback: expect.any(Function),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'perform-action',
|
||||||
|
data: {
|
||||||
|
option: '{{ advanced_camera_card.camera }}',
|
||||||
|
},
|
||||||
|
perform_action: 'input_select.select_option',
|
||||||
|
target: {
|
||||||
|
entity_id: 'input_select.camera',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
condition: 'config',
|
||||||
|
paths: ['remote_control.entities.camera'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tag: setRemoteControlEntityFromConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'perform-action',
|
||||||
|
data: {
|
||||||
|
option: '{{ advanced_camera_card.trigger.camera.to }}',
|
||||||
|
},
|
||||||
|
perform_action: 'input_select.select_option',
|
||||||
|
target: {
|
||||||
|
entity_id: 'input_select.camera',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
condition: 'camera',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tag: setRemoteControlEntityFromConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
advanced_camera_card_action: 'camera_select',
|
||||||
|
camera: '{{ advanced_camera_card.trigger.state.to }}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
condition: 'state',
|
||||||
|
entity: 'input_select.camera',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tag: setRemoteControlEntityFromConfig,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('internal action should set options', () => {
|
||||||
|
const hass = createHASS();
|
||||||
|
const api = createCardAPI();
|
||||||
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||||
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||||
|
createConfig({
|
||||||
|
remote_control: {
|
||||||
|
entities: {
|
||||||
|
camera: 'input_select.camera',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const store = createStore([
|
||||||
|
{
|
||||||
|
cameraID: 'camera.one',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cameraID: 'camera.two',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(store);
|
||||||
|
|
||||||
|
setRemoteControlEntityFromConfig(api);
|
||||||
|
|
||||||
|
const addOptionsAction = vi.mocked(api.getAutomationsManager().addAutomations).mock
|
||||||
|
.calls[0][0][0].actions[0];
|
||||||
|
expect(addOptionsAction.advanced_camera_card_action).toBe(INTERNAL_CALLBACK_ACTION);
|
||||||
|
|
||||||
|
addOptionsAction.callback(api);
|
||||||
|
expect(hass.callService).toBeCalledWith(
|
||||||
|
'input_select',
|
||||||
|
'set_options',
|
||||||
|
{
|
||||||
|
options: ['camera.one', 'camera.two'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity_id: 'input_select.camera',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,9 +7,11 @@ import { PTZAction } from '../../../src/config/ptz';
|
|||||||
describe('setKeyboardShortcutsFromConfig', () => {
|
describe('setKeyboardShortcutsFromConfig', () => {
|
||||||
it('without shortcuts', () => {
|
it('without shortcuts', () => {
|
||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
setKeyboardShortcutsFromConfig(api, 'tag');
|
setKeyboardShortcutsFromConfig(api);
|
||||||
|
|
||||||
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith('tag');
|
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(
|
||||||
|
setKeyboardShortcutsFromConfig,
|
||||||
|
);
|
||||||
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
|
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -24,9 +26,11 @@ describe('setKeyboardShortcutsFromConfig', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
setKeyboardShortcutsFromConfig(api, 'tag');
|
setKeyboardShortcutsFromConfig(api);
|
||||||
|
|
||||||
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith('tag');
|
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(
|
||||||
|
setKeyboardShortcutsFromConfig,
|
||||||
|
);
|
||||||
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
|
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,9 +63,11 @@ describe('setKeyboardShortcutsFromConfig', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
setKeyboardShortcutsFromConfig(api, 'tag');
|
setKeyboardShortcutsFromConfig(api);
|
||||||
|
|
||||||
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith('tag');
|
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(
|
||||||
|
setKeyboardShortcutsFromConfig,
|
||||||
|
);
|
||||||
expect(api.getAutomationsManager().addAutomations).toBeCalledWith([
|
expect(api.getAutomationsManager().addAutomations).toBeCalledWith([
|
||||||
{
|
{
|
||||||
actions: [
|
actions: [
|
||||||
@@ -83,7 +89,7 @@ describe('setKeyboardShortcutsFromConfig', () => {
|
|||||||
state: 'down',
|
state: 'down',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tag: 'tag',
|
tag: setKeyboardShortcutsFromConfig,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
actions: [
|
actions: [
|
||||||
@@ -101,7 +107,7 @@ describe('setKeyboardShortcutsFromConfig', () => {
|
|||||||
state: 'up',
|
state: 'up',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tag: 'tag',
|
tag: setKeyboardShortcutsFromConfig,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -110,9 +116,11 @@ describe('setKeyboardShortcutsFromConfig', () => {
|
|||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||||
|
|
||||||
setKeyboardShortcutsFromConfig(api, 'tag');
|
setKeyboardShortcutsFromConfig(api);
|
||||||
|
|
||||||
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith('tag');
|
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(
|
||||||
|
setKeyboardShortcutsFromConfig,
|
||||||
|
);
|
||||||
expect(api.getAutomationsManager().addAutomations).toBeCalledWith(
|
expect(api.getAutomationsManager().addAutomations).toBeCalledWith(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
{
|
{
|
||||||
@@ -133,7 +141,7 @@ describe('setKeyboardShortcutsFromConfig', () => {
|
|||||||
state: 'down',
|
state: 'down',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tag: 'tag',
|
tag: setKeyboardShortcutsFromConfig,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
ViewDisplayMode,
|
ViewDisplayMode,
|
||||||
} from '../../src/config/types';
|
} from '../../src/config/types';
|
||||||
import { AdvancedCameraCardMediaPlayer } from '../../src/types';
|
import { AdvancedCameraCardMediaPlayer } from '../../src/types';
|
||||||
import { createGeneralAction } from '../../src/utils/action';
|
import { createGeneralAction, createViewAction } from '../../src/utils/action';
|
||||||
import { ViewMedia } from '../../src/view/media';
|
import { ViewMedia } from '../../src/view/media';
|
||||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||||
import { View } from '../../src/view/view';
|
import { View } from '../../src/view/view';
|
||||||
@@ -94,7 +94,7 @@ describe('MenuButtonController', () => {
|
|||||||
type: 'custom:advanced-camera-card-menu-icon',
|
type: 'custom:advanced-camera-card-menu-icon',
|
||||||
title: 'Iris / Default View / Unhide menu',
|
title: 'Iris / Default View / Unhide menu',
|
||||||
tap_action: createGeneralAction('menu_toggle'),
|
tap_action: createGeneralAction('menu_toggle'),
|
||||||
hold_action: createGeneralAction('diagnostics'),
|
hold_action: createViewAction('diagnostics'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ describe('MenuButtonController', () => {
|
|||||||
type: 'custom:advanced-camera-card-menu-icon',
|
type: 'custom:advanced-camera-card-menu-icon',
|
||||||
title: 'Iris / Default View / Unhide menu',
|
title: 'Iris / Default View / Unhide menu',
|
||||||
tap_action: createGeneralAction('default'),
|
tap_action: createGeneralAction('default'),
|
||||||
hold_action: createGeneralAction('diagnostics'),
|
hold_action: createViewAction('diagnostics'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
dimensionsConfigSchema,
|
dimensionsConfigSchema,
|
||||||
advancedCameraCardConditionSchema,
|
advancedCameraCardConditionSchema,
|
||||||
advancedCameraCardCustomActionsBaseSchema,
|
advancedCameraCardCustomActionsBaseSchema,
|
||||||
advancedCameraCardCustomActionSchema,
|
internalAdvancedCameraCardCustomActionSchema,
|
||||||
} from '../../src/config/types';
|
} from '../../src/config/types';
|
||||||
import { createConfig } from '../test-utils';
|
import { createConfig } from '../test-utils';
|
||||||
|
|
||||||
@@ -641,7 +641,7 @@ describe('should lazy evaluate schemas', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
expect(advancedCameraCardCustomActionSchema.parse(input)).toEqual(input);
|
expect(internalAdvancedCameraCardCustomActionSchema.parse(input)).toEqual(input);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -19,7 +19,6 @@ import { ActionsManager } from '../src/card-controller/actions/actions-manager';
|
|||||||
import { AutomationsManager } from '../src/card-controller/automations-manager';
|
import { AutomationsManager } from '../src/card-controller/automations-manager';
|
||||||
import { CameraURLManager } from '../src/card-controller/camera-url-manager';
|
import { CameraURLManager } from '../src/card-controller/camera-url-manager';
|
||||||
import { CardElementManager } from '../src/card-controller/card-element-manager';
|
import { CardElementManager } from '../src/card-controller/card-element-manager';
|
||||||
import { ConditionStateManager } from '../src/conditions/state-manager';
|
|
||||||
import { ConfigManager } from '../src/card-controller/config/config-manager';
|
import { ConfigManager } from '../src/card-controller/config/config-manager';
|
||||||
import { CardController } from '../src/card-controller/controller';
|
import { CardController } from '../src/card-controller/controller';
|
||||||
import { DefaultManager } from '../src/card-controller/default-manager';
|
import { DefaultManager } from '../src/card-controller/default-manager';
|
||||||
@@ -40,18 +39,17 @@ import { StatusBarItemManager } from '../src/card-controller/status-bar-item-man
|
|||||||
import { StyleManager } from '../src/card-controller/style-manager';
|
import { StyleManager } from '../src/card-controller/style-manager';
|
||||||
import { TriggersManager } from '../src/card-controller/triggers-manager';
|
import { TriggersManager } from '../src/card-controller/triggers-manager';
|
||||||
import { ViewManager } from '../src/card-controller/view/view-manager';
|
import { ViewManager } from '../src/card-controller/view/view-manager';
|
||||||
|
import { ConditionStateManager } from '../src/conditions/state-manager';
|
||||||
import {
|
import {
|
||||||
ActionsConfig,
|
ActionsConfig,
|
||||||
AdvancedCameraCardCondition,
|
|
||||||
AdvancedCameraCardConfig,
|
AdvancedCameraCardConfig,
|
||||||
AdvancedCameraCardCustomAction,
|
|
||||||
CameraConfig,
|
CameraConfig,
|
||||||
|
InternalAdvancedCameraCardCustomAction,
|
||||||
PerformanceConfig,
|
PerformanceConfig,
|
||||||
RawAdvancedCameraCardConfig,
|
RawAdvancedCameraCardConfig,
|
||||||
advancedCameraCardConditionSchema,
|
|
||||||
advancedCameraCardConfigSchema,
|
advancedCameraCardConfigSchema,
|
||||||
advancedCameraCardCustomActionSchema,
|
|
||||||
cameraConfigSchema,
|
cameraConfigSchema,
|
||||||
|
internalAdvancedCameraCardCustomActionSchema,
|
||||||
performanceConfigSchema,
|
performanceConfigSchema,
|
||||||
} from '../src/config/types';
|
} from '../src/config/types';
|
||||||
import { CapabilitiesRaw, ExtendedHomeAssistant, MediaLoadedInfo } from '../src/types';
|
import { CapabilitiesRaw, ExtendedHomeAssistant, MediaLoadedInfo } from '../src/types';
|
||||||
@@ -65,8 +63,8 @@ import { View, ViewParameters } from '../src/view/view';
|
|||||||
|
|
||||||
export const createAction = (
|
export const createAction = (
|
||||||
action: Record<string, unknown>,
|
action: Record<string, unknown>,
|
||||||
): AdvancedCameraCardCustomAction | null => {
|
): InternalAdvancedCameraCardCustomAction | null => {
|
||||||
const result = advancedCameraCardCustomActionSchema.safeParse({
|
const result = internalAdvancedCameraCardCustomActionSchema.safeParse({
|
||||||
action: 'custom:advanced-camera-card-action',
|
action: 'custom:advanced-camera-card-action',
|
||||||
...action,
|
...action,
|
||||||
});
|
});
|
||||||
@@ -260,6 +258,7 @@ export const createCapabilities = (capabilities?: CapabilitiesRaw): Capabilities
|
|||||||
return new Capabilities({
|
return new Capabilities({
|
||||||
'favorite-events': false,
|
'favorite-events': false,
|
||||||
'favorite-recordings': false,
|
'favorite-recordings': false,
|
||||||
|
'remote-control-entity': true,
|
||||||
clips: false,
|
clips: false,
|
||||||
live: false,
|
live: false,
|
||||||
recordings: false,
|
recordings: false,
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import { hasAction as customCardHasAction } from '@dermotduffy/custom-card-helpers';
|
import { hasAction as customCardHasAction } from '@dermotduffy/custom-card-helpers';
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { mock } from 'vitest-mock-extended';
|
import { mock } from 'vitest-mock-extended';
|
||||||
import { actionSchema } from '../../src/config/types';
|
import { actionSchema, INTERNAL_CALLBACK_ACTION } from '../../src/config/types';
|
||||||
import {
|
import {
|
||||||
convertActionToCardCustomAction,
|
convertActionToCardCustomAction,
|
||||||
createCameraAction,
|
createCameraAction,
|
||||||
createDisplayModeAction,
|
createDisplayModeAction,
|
||||||
createGeneralAction,
|
createGeneralAction,
|
||||||
|
createInternalCallbackAction,
|
||||||
createLogAction,
|
createLogAction,
|
||||||
createMediaPlayerAction,
|
createMediaPlayerAction,
|
||||||
createPTZAction,
|
createPTZAction,
|
||||||
createPTZControlsAction,
|
createPTZControlsAction,
|
||||||
createPTZDigitalAction,
|
createPTZDigitalAction,
|
||||||
createPTZMultiAction,
|
createPTZMultiAction,
|
||||||
|
createViewAction,
|
||||||
getActionConfigGivenAction,
|
getActionConfigGivenAction,
|
||||||
hasAction,
|
hasAction,
|
||||||
stopEventFromActivatingCardWideActions,
|
stopEventFromActivatingCardWideActions,
|
||||||
@@ -45,7 +47,21 @@ describe('convertActionToAdvancedCameraCardCustomAction', () => {
|
|||||||
describe('createGeneralAction', () => {
|
describe('createGeneralAction', () => {
|
||||||
it('should create general action', () => {
|
it('should create general action', () => {
|
||||||
expect(
|
expect(
|
||||||
createGeneralAction('clips', {
|
createGeneralAction('camera_ui', {
|
||||||
|
cardID: 'card_id',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
advanced_camera_card_action: 'camera_ui',
|
||||||
|
card_id: 'card_id',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createViewAction', () => {
|
||||||
|
it('should create view action', () => {
|
||||||
|
expect(
|
||||||
|
createViewAction('clips', {
|
||||||
cardID: 'card_id',
|
cardID: 'card_id',
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
@@ -241,6 +257,22 @@ describe('createLogAction', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('createInternalCallbackAction', () => {
|
||||||
|
it('should create internal callback action', () => {
|
||||||
|
const callback = vi.fn();
|
||||||
|
expect(
|
||||||
|
createInternalCallbackAction(callback, {
|
||||||
|
cardID: 'card_id',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
action: 'fire-dom-event',
|
||||||
|
advanced_camera_card_action: INTERNAL_CALLBACK_ACTION,
|
||||||
|
callback: callback,
|
||||||
|
card_id: 'card_id',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getActionConfigGivenAction', () => {
|
describe('getActionConfigGivenAction', () => {
|
||||||
const action = actionSchema.parse({
|
const action = actionSchema.parse({
|
||||||
action: 'fire-dom-event',
|
action: 'fire-dom-event',
|
||||||
|
|||||||
Reference in New Issue
Block a user