feat: Add control/output of selected camera via an entity (#1895)
- Closes #1871
This commit is contained in:
@@ -139,6 +139,7 @@ export class FrigateCamera extends Camera {
|
||||
{
|
||||
'favorite-events': !birdseye,
|
||||
'favorite-recordings': false,
|
||||
'remote-control-entity': true,
|
||||
seek: !birdseye,
|
||||
clips: !birdseye,
|
||||
snapshots: !birdseye,
|
||||
|
||||
@@ -63,6 +63,7 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
|
||||
{
|
||||
'favorite-events': false,
|
||||
'favorite-recordings': false,
|
||||
'remote-control-entity': true,
|
||||
clips: false,
|
||||
live: true,
|
||||
menu: true,
|
||||
|
||||
@@ -83,6 +83,7 @@ export class MotionEyeCameraManagerEngine extends BrowseMediaCameraManagerEngine
|
||||
{
|
||||
'favorite-events': false,
|
||||
'favorite-recordings': false,
|
||||
'remote-control-entity': true,
|
||||
clips: true,
|
||||
live: true,
|
||||
menu: true,
|
||||
|
||||
@@ -137,6 +137,7 @@ export class ReolinkCameraManagerEngine extends BrowseMediaCameraManagerEngine {
|
||||
{
|
||||
'favorite-events': false,
|
||||
'favorite-recordings': false,
|
||||
'remote-control-entity': true,
|
||||
clips: true,
|
||||
live: true,
|
||||
menu: true,
|
||||
|
||||
@@ -12,7 +12,11 @@ export class CameraSelectAction extends AdvancedCameraCardAction<CameraSelectAct
|
||||
const view = api.getViewManager().getView();
|
||||
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 targetViewName =
|
||||
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 { ActionContext } from 'action';
|
||||
import { ActionType } from '../../config/types';
|
||||
import { ActionType, INTERNAL_CALLBACK_ACTION } from '../../config/types';
|
||||
import { convertActionToCardCustomAction } from '../../utils/action';
|
||||
import { CameraSelectAction } from './actions/camera-select';
|
||||
import { CameraUIAction } from './actions/camera-ui';
|
||||
@@ -10,6 +10,7 @@ import { DownloadAction } from './actions/download';
|
||||
import { ExpandAction } from './actions/expand';
|
||||
import { FullscreenAction } from './actions/fullscreen';
|
||||
import { GenericAction } from './actions/generic';
|
||||
import { InternalCallbackAction } from './actions/internal-callback';
|
||||
import { LogAction } from './actions/log';
|
||||
import { MediaPlayerAction } from './actions/media-player';
|
||||
import { MenuToggleAction } from './actions/menu-toggle';
|
||||
@@ -132,6 +133,8 @@ export class ActionFactory {
|
||||
return new LogAction(context, cardCustomAction, options?.config);
|
||||
case 'status_bar':
|
||||
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 */
|
||||
|
||||
@@ -14,6 +14,7 @@ import { InitializationAspect } from '../initialization-manager.js';
|
||||
import { CardConfigAPI } from '../types.js';
|
||||
import { getOverriddenConfig } from './get-overridden-config.js';
|
||||
import { setAutomationsFromConfig } from './load-automations.js';
|
||||
import { setRemoteControlEntityFromConfig } from './load-control-entities.js';
|
||||
import { setKeyboardShortcutsFromConfig } from './load-keyboard-shortcuts.js';
|
||||
|
||||
export class ConfigManager {
|
||||
@@ -109,7 +110,8 @@ export class ConfigManager {
|
||||
this._api.getMessageManager().reset();
|
||||
this._api.getStatusBarItemManager().removeAllDynamicStatusBarItems();
|
||||
|
||||
setKeyboardShortcutsFromConfig(this._api, this);
|
||||
setKeyboardShortcutsFromConfig(this._api);
|
||||
setRemoteControlEntityFromConfig(this._api);
|
||||
setAutomationsFromConfig(this._api);
|
||||
|
||||
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 { CardConfigLoaderAPI, TaggedAutomation } from '../types';
|
||||
|
||||
export const setKeyboardShortcutsFromConfig = (
|
||||
api: CardConfigLoaderAPI,
|
||||
tag: unknown,
|
||||
) => {
|
||||
api.getAutomationsManager().deleteAutomations(tag);
|
||||
export const setKeyboardShortcutsFromConfig = (api: CardConfigLoaderAPI) => {
|
||||
const automationTag = setKeyboardShortcutsFromConfig;
|
||||
api.getAutomationsManager().deleteAutomations(automationTag);
|
||||
|
||||
const shortcuts = api.getConfigManager().getConfig()?.view.keyboard_shortcuts;
|
||||
if (!shortcuts) {
|
||||
return;
|
||||
}
|
||||
|
||||
const automations = convertKeyboardShortcutsToAutomations(tag, shortcuts);
|
||||
const automations = convertKeyboardShortcutsToAutomations(automationTag, shortcuts);
|
||||
if (automations.length) {
|
||||
api.getAutomationsManager().addAutomations(automations);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
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 { CardQueryStringAPI } from './types';
|
||||
import { SubstreamSelectViewModifier } from './view/modifiers/substream-select';
|
||||
@@ -113,21 +117,25 @@ export class QueryStringManager {
|
||||
}
|
||||
break;
|
||||
case 'camera_ui':
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
case 'default':
|
||||
case 'diagnostics':
|
||||
case 'download':
|
||||
case 'expand':
|
||||
case 'menu_toggle':
|
||||
customAction = createGeneralAction(action, {
|
||||
cardID: cardID,
|
||||
});
|
||||
break;
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
case 'diagnostics':
|
||||
case 'image':
|
||||
case 'live':
|
||||
case 'menu_toggle':
|
||||
case 'recording':
|
||||
case 'recordings':
|
||||
case 'snapshot':
|
||||
case 'snapshots':
|
||||
case 'timeline':
|
||||
customAction = createGeneralAction(action, {
|
||||
customAction = createViewAction(action, {
|
||||
cardID: cardID,
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -90,6 +90,7 @@ export interface CardConfigAPI {
|
||||
getConditionStateManager(): ConditionStateManager;
|
||||
getConfigManager(): ConfigManager;
|
||||
getDefaultManager(): DefaultManager;
|
||||
getHASSManager(): HASSManager;
|
||||
getInitializationManager(): InitializationManager;
|
||||
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
||||
getMediaPlayerManager(): MediaPlayerManager;
|
||||
@@ -101,8 +102,9 @@ export interface CardConfigAPI {
|
||||
}
|
||||
|
||||
export interface CardConfigLoaderAPI {
|
||||
getConfigManager(): ConfigManager;
|
||||
getAutomationsManager(): AutomationsManager;
|
||||
getConfigManager(): ConfigManager;
|
||||
getHASSManager(): HASSManager;
|
||||
}
|
||||
|
||||
export interface CardDefaultManagerAPI {
|
||||
@@ -178,6 +180,7 @@ export interface CardInitializerAPI {
|
||||
getCardElementManager(): CardElementManager;
|
||||
getConditionStateManager(): ConditionStateManager;
|
||||
getConfigManager(): ConfigManager;
|
||||
getConditionStateManager(): ConditionStateManager;
|
||||
getDefaultManager(): DefaultManager;
|
||||
getEntityRegistryManager(): EntityRegistryManager;
|
||||
getHASSManager(): HASSManager;
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
createMediaPlayerAction,
|
||||
createPTZControlsAction,
|
||||
createPTZMultiAction,
|
||||
createViewAction,
|
||||
} from '../utils/action';
|
||||
import { isTruthy } from '../utils/basic';
|
||||
import { isBeingCasted } from '../utils/casting';
|
||||
@@ -119,7 +120,7 @@ export class MenuButtonController {
|
||||
config.menu?.style === 'hidden'
|
||||
? (createGeneralAction('menu_toggle') 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',
|
||||
title: localize('config.view.views.live'),
|
||||
style: view.is('live') ? this._getEmphasizedStyle() : {},
|
||||
tap_action: createGeneralAction('live') as AdvancedCameraCardCustomAction,
|
||||
tap_action: createViewAction('live') as AdvancedCameraCardCustomAction,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
@@ -250,8 +251,8 @@ export class MenuButtonController {
|
||||
type: 'custom:advanced-camera-card-menu-icon',
|
||||
title: localize('config.view.views.clips'),
|
||||
style: view?.is('clips') ? this._getEmphasizedStyle() : {},
|
||||
tap_action: createGeneralAction('clips') as AdvancedCameraCardCustomAction,
|
||||
hold_action: createGeneralAction('clip') as AdvancedCameraCardCustomAction,
|
||||
tap_action: createViewAction('clips') as AdvancedCameraCardCustomAction,
|
||||
hold_action: createViewAction('clip') as AdvancedCameraCardCustomAction,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
@@ -268,8 +269,8 @@ export class MenuButtonController {
|
||||
type: 'custom:advanced-camera-card-menu-icon',
|
||||
title: localize('config.view.views.snapshots'),
|
||||
style: view?.is('snapshots') ? this._getEmphasizedStyle() : {},
|
||||
tap_action: createGeneralAction('snapshots') as AdvancedCameraCardCustomAction,
|
||||
hold_action: createGeneralAction('snapshot') as AdvancedCameraCardCustomAction,
|
||||
tap_action: createViewAction('snapshots') as AdvancedCameraCardCustomAction,
|
||||
hold_action: createViewAction('snapshot') as AdvancedCameraCardCustomAction,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
@@ -286,12 +287,8 @@ export class MenuButtonController {
|
||||
type: 'custom:advanced-camera-card-menu-icon',
|
||||
title: localize('config.view.views.recordings'),
|
||||
style: view.is('recordings') ? this._getEmphasizedStyle() : {},
|
||||
tap_action: createGeneralAction(
|
||||
'recordings',
|
||||
) as AdvancedCameraCardCustomAction,
|
||||
hold_action: createGeneralAction(
|
||||
'recording',
|
||||
) as AdvancedCameraCardCustomAction,
|
||||
tap_action: createViewAction('recordings') as AdvancedCameraCardCustomAction,
|
||||
hold_action: createViewAction('recording') as AdvancedCameraCardCustomAction,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
@@ -308,7 +305,7 @@ export class MenuButtonController {
|
||||
type: 'custom:advanced-camera-card-menu-icon',
|
||||
title: localize('config.view.views.image'),
|
||||
style: view?.is('image') ? this._getEmphasizedStyle() : {},
|
||||
tap_action: createGeneralAction('image') as AdvancedCameraCardCustomAction,
|
||||
tap_action: createViewAction('image') as AdvancedCameraCardCustomAction,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
@@ -325,7 +322,7 @@ export class MenuButtonController {
|
||||
type: 'custom:advanced-camera-card-menu-icon',
|
||||
title: localize('config.view.views.timeline'),
|
||||
style: view.is('timeline') ? this._getEmphasizedStyle() : {},
|
||||
tap_action: createGeneralAction('timeline') as AdvancedCameraCardCustomAction,
|
||||
tap_action: createViewAction('timeline') as AdvancedCameraCardCustomAction,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
+40
-2
@@ -397,7 +397,7 @@ const logActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
});
|
||||
export type LogActionConfig = z.infer<typeof logActionConfigSchema>;
|
||||
|
||||
export const advancedCameraCardCustomActionSchema = z.union([
|
||||
const advancedCameraCardCustomActionSchema = z.union([
|
||||
cameraSelectActionConfigSchema,
|
||||
generalActionConfigSchema,
|
||||
substreamSelectActionConfigSchema,
|
||||
@@ -416,6 +416,26 @@ export type AdvancedCameraCardCustomAction = z.infer<
|
||||
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
|
||||
// a transform on the discriminated union key.
|
||||
export const actionSchema = z.union([
|
||||
@@ -429,7 +449,9 @@ export const actionSchema = z.union([
|
||||
customActionSchema,
|
||||
advancedCameraCardCustomActionSchema,
|
||||
]);
|
||||
export type ActionType = z.infer<typeof actionSchema>;
|
||||
|
||||
const internalActionSchema = actionSchema.or(internalCallbackActionConfigSchema);
|
||||
export type ActionType = z.infer<typeof internalActionSchema>;
|
||||
|
||||
const actionsBaseSchema = z
|
||||
.object({
|
||||
@@ -2075,6 +2097,20 @@ const PROFILES = ['casting', 'low-performance', 'scrubbing'] as const;
|
||||
export type ProfileType = (typeof PROFILES)[number];
|
||||
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 ***
|
||||
// *************************************************************************
|
||||
@@ -2114,6 +2150,8 @@ export const advancedCameraCardConfigSchema = z.object({
|
||||
// that be easily used in a URL.
|
||||
card_id: z.string().regex(cardIDRegex).optional(),
|
||||
|
||||
remote_control: remoteControlConfigSchema,
|
||||
|
||||
// Stock lovelace card config.
|
||||
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_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
|
||||
export const MEDIA_PLAYER_SUPPORT_TURN_OFF = 256;
|
||||
export const MEDIA_PLAYER_SUPPORT_STOP = 4096;
|
||||
|
||||
@@ -190,6 +190,7 @@ import {
|
||||
CONF_PERFORMANCE_STYLE_BORDER_RADIUS,
|
||||
CONF_PERFORMANCE_STYLE_BOX_SHADOW,
|
||||
CONF_PROFILES,
|
||||
CONF_REMOTE_CONTROL_ENTITIES_CAMERA,
|
||||
CONF_STATUS_BAR_HEIGHT,
|
||||
CONF_STATUS_BAR_ITEMS,
|
||||
CONF_STATUS_BAR_POPUP_SECONDS,
|
||||
@@ -281,6 +282,7 @@ const MENU_MENU_BUTTONS = 'menu.buttons';
|
||||
const MENU_OPTIONS = 'options';
|
||||
const MENU_PERFORMANCE_FEATURES = 'performance.features';
|
||||
const MENU_PERFORMANCE_STYLE = 'performance.style';
|
||||
const MENU_REMOTE_CONTROL_ENTITIES = 'remote_control.entities';
|
||||
const MENU_STATUS_BAR_ITEMS = 'status_bar.items';
|
||||
const MENU_TIMELINE_FORMAT = 'timeline.format';
|
||||
const MENU_TIMELINE_CONTROLS_THUMBNAILS = 'timeline.controls.thumbnails';
|
||||
@@ -369,6 +371,11 @@ const options: EditorOptions = {
|
||||
name: localize('editor.profiles'),
|
||||
secondary: localize('editor.profiles_secondary'),
|
||||
},
|
||||
remote_control: {
|
||||
icon: 'remote',
|
||||
name: localize('editor.remote_control'),
|
||||
secondary: localize('editor.remote_control_secondary'),
|
||||
},
|
||||
overrides: {
|
||||
icon: 'file-replace',
|
||||
name: localize('editor.overrides'),
|
||||
@@ -3051,6 +3058,23 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
|
||||
)}
|
||||
</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
|
||||
? html` ${this._renderOptionSetHeader('overrides')}
|
||||
${this._expandedMenus[MENU_OPTIONS] === 'overrides'
|
||||
|
||||
@@ -459,6 +459,12 @@
|
||||
"low-performance": "",
|
||||
"scrubbing": ""
|
||||
},
|
||||
"remote_control": {
|
||||
"entities": {
|
||||
"camera": "",
|
||||
"editor_label": ""
|
||||
}
|
||||
},
|
||||
"status_bar": {
|
||||
"height": "",
|
||||
"items": {
|
||||
@@ -596,6 +602,8 @@
|
||||
"performance_secondary": "Opcions de rendiment de la targeta",
|
||||
"profiles": "",
|
||||
"profiles_secondary": "",
|
||||
"remote_control": "",
|
||||
"remote_control_secondary": "",
|
||||
"status_bar": "",
|
||||
"status_bar_secondary": "",
|
||||
"timeline": "Cronologia",
|
||||
|
||||
@@ -459,6 +459,12 @@
|
||||
"low-performance": "Low performance",
|
||||
"scrubbing": "Video scrubbing"
|
||||
},
|
||||
"remote_control": {
|
||||
"entities": {
|
||||
"editor_label": "Remote Control Entities",
|
||||
"camera": "Input Select entity to control camera"
|
||||
}
|
||||
},
|
||||
"status_bar": {
|
||||
"height": "Status bar height in pixels",
|
||||
"items": {
|
||||
@@ -596,6 +602,8 @@
|
||||
"performance_secondary": "Card performance options",
|
||||
"profiles": "Configuration profiles",
|
||||
"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_secondary": "Status bar look & feel options",
|
||||
"timeline": "Timeline",
|
||||
|
||||
@@ -459,6 +459,12 @@
|
||||
"low-performance": "Basse performance",
|
||||
"scrubbing": "Balayage vidéo"
|
||||
},
|
||||
"remote_control": {
|
||||
"entities": {
|
||||
"camera": "",
|
||||
"editor_label": ""
|
||||
}
|
||||
},
|
||||
"status_bar": {
|
||||
"height": "Hauteur de la barre d'état en pixels",
|
||||
"items": {
|
||||
@@ -596,6 +602,8 @@
|
||||
"performance_secondary": "Options de performances de la carte",
|
||||
"profiles": "Profils de configuration",
|
||||
"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_secondary": "Options d'apparence et de comportement de la barre d'état",
|
||||
"timeline": "Chronologie",
|
||||
|
||||
@@ -459,6 +459,12 @@
|
||||
"low-performance": "",
|
||||
"scrubbing": ""
|
||||
},
|
||||
"remote_control": {
|
||||
"entities": {
|
||||
"camera": "",
|
||||
"editor_label": ""
|
||||
}
|
||||
},
|
||||
"status_bar": {
|
||||
"height": "",
|
||||
"items": {
|
||||
@@ -596,6 +602,8 @@
|
||||
"performance_secondary": "",
|
||||
"profiles": "",
|
||||
"profiles_secondary": "",
|
||||
"remote_control": "",
|
||||
"remote_control_secondary": "",
|
||||
"status_bar": "",
|
||||
"status_bar_secondary": "",
|
||||
"timeline": "Timeline",
|
||||
|
||||
@@ -459,6 +459,12 @@
|
||||
"low-performance": "",
|
||||
"scrubbing": ""
|
||||
},
|
||||
"remote_control": {
|
||||
"entities": {
|
||||
"camera": "",
|
||||
"editor_label": ""
|
||||
}
|
||||
},
|
||||
"status_bar": {
|
||||
"height": "",
|
||||
"items": {
|
||||
@@ -596,6 +602,8 @@
|
||||
"performance_secondary": "Opções de desempenho do cartão",
|
||||
"profiles": "",
|
||||
"profiles_secondary": "",
|
||||
"remote_control": "",
|
||||
"remote_control_secondary": "",
|
||||
"status_bar": "",
|
||||
"status_bar_secondary": "",
|
||||
"timeline": "Linha do tempo",
|
||||
|
||||
@@ -459,6 +459,12 @@
|
||||
"low-performance": "",
|
||||
"scrubbing": ""
|
||||
},
|
||||
"remote_control": {
|
||||
"entities": {
|
||||
"camera": "",
|
||||
"editor_label": ""
|
||||
}
|
||||
},
|
||||
"status_bar": {
|
||||
"height": "",
|
||||
"items": {
|
||||
@@ -596,6 +602,8 @@
|
||||
"performance_secondary": "",
|
||||
"profiles": "",
|
||||
"profiles_secondary": "",
|
||||
"remote_control": "",
|
||||
"remote_control_secondary": "",
|
||||
"status_bar": "",
|
||||
"status_bar_secondary": "",
|
||||
"timeline": "Linha do tempo",
|
||||
|
||||
@@ -111,6 +111,8 @@ export interface CapabilitiesRaw {
|
||||
'favorite-events'?: boolean;
|
||||
'favorite-recordings'?: boolean;
|
||||
|
||||
'remote-control-entity'?: boolean;
|
||||
|
||||
seek?: boolean;
|
||||
|
||||
ptz?: PTZCapabilities;
|
||||
@@ -123,6 +125,7 @@ export interface CapabilitiesRaw {
|
||||
export type CapabilityKey = keyof CapabilitiesRaw;
|
||||
export const capabilityKeys: readonly [CapabilityKey, ...CapabilityKey[]] = [
|
||||
'clips',
|
||||
'remote-control-entity',
|
||||
'favorite-events',
|
||||
'favorite-recordings',
|
||||
'live',
|
||||
|
||||
+63
-10
@@ -1,22 +1,33 @@
|
||||
import {
|
||||
ActionConfig,
|
||||
ServiceCallRequest,
|
||||
hasAction as customCardHasAction,
|
||||
} from '@dermotduffy/custom-card-helpers';
|
||||
import { CardActionsAPI } from '../card-controller/types.js';
|
||||
import { ZoomSettingsBase } from '../components-lib/zoom/types.js';
|
||||
import { PTZAction } from '../config/ptz.js';
|
||||
import {
|
||||
ActionPhase,
|
||||
ActionType,
|
||||
Actions,
|
||||
AdvancedCameraCardCustomAction,
|
||||
AdvancedCameraCardGeneralAction,
|
||||
AdvancedCameraCardUserSpecifiedView,
|
||||
CameraSelectActionConfig,
|
||||
DisplayModeActionConfig,
|
||||
GeneralActionConfig,
|
||||
INTERNAL_CALLBACK_ACTION,
|
||||
InternalAdvancedCameraCardCustomAction,
|
||||
InternalCallbackActionConfig,
|
||||
LogActionConfig,
|
||||
LogActionLevel,
|
||||
MediaPlayerActionConfig,
|
||||
PTZActionConfig,
|
||||
PTZControlsActionConfig,
|
||||
PTZDigitialActionConfig,
|
||||
PTZMultiActionConfig,
|
||||
advancedCameraCardCustomActionSchema,
|
||||
SubstreamSelectActionConfig,
|
||||
ViewActionConfig,
|
||||
internalAdvancedCameraCardCustomActionSchema,
|
||||
} from '../config/types.js';
|
||||
import { arrayify } from './basic.js';
|
||||
|
||||
@@ -27,22 +38,35 @@ import { arrayify } from './basic.js';
|
||||
*/
|
||||
export function convertActionToCardCustomAction(
|
||||
action: unknown,
|
||||
): AdvancedCameraCardCustomAction | null {
|
||||
): InternalAdvancedCameraCardCustomAction | null {
|
||||
if (!action) {
|
||||
return null;
|
||||
}
|
||||
// Parse a custom event as other things could generate ll-custom events that
|
||||
// are not related to Advanced Camera Card.
|
||||
const parseResult = advancedCameraCardCustomActionSchema.safeParse(action);
|
||||
const parseResult = internalAdvancedCameraCardCustomActionSchema.safeParse(action);
|
||||
return parseResult.success ? parseResult.data : null;
|
||||
}
|
||||
|
||||
export function createGeneralAction(
|
||||
action: AdvancedCameraCardGeneralAction | AdvancedCameraCardUserSpecifiedView,
|
||||
action: AdvancedCameraCardGeneralAction,
|
||||
options?: {
|
||||
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 {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: action,
|
||||
@@ -56,7 +80,7 @@ export function createCameraAction(
|
||||
options?: {
|
||||
cardID?: string;
|
||||
},
|
||||
): AdvancedCameraCardCustomAction {
|
||||
): CameraSelectActionConfig | SubstreamSelectActionConfig {
|
||||
return {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: action,
|
||||
@@ -71,7 +95,7 @@ export function createMediaPlayerAction(
|
||||
options?: {
|
||||
cardID?: string;
|
||||
},
|
||||
): AdvancedCameraCardCustomAction {
|
||||
): MediaPlayerActionConfig {
|
||||
return {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'media_player',
|
||||
@@ -86,7 +110,7 @@ export function createDisplayModeAction(
|
||||
options?: {
|
||||
cardID?: string;
|
||||
},
|
||||
): AdvancedCameraCardCustomAction {
|
||||
): DisplayModeActionConfig {
|
||||
return {
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'display_mode_select',
|
||||
@@ -100,7 +124,7 @@ export function createPTZControlsAction(
|
||||
options?: {
|
||||
cardID?: string;
|
||||
},
|
||||
): AdvancedCameraCardCustomAction {
|
||||
): PTZControlsActionConfig {
|
||||
return {
|
||||
action: 'fire-dom-event',
|
||||
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').
|
||||
* @param interaction The interaction: `tap`, `hold` or `double_tap`
|
||||
|
||||
Reference in New Issue
Block a user