fix: Confirmations should apply to all actions (#1959)
This also refactors how generic HA actions are handled, and improves typing of actions. [skip ci]
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
import { ActionContext } from 'action';
|
||||
import { z } from 'zod';
|
||||
import { ConditionsTriggerData } from '../../conditions/types.js';
|
||||
import { Actions, ActionsConfig, ActionType } from '../../config/types.js';
|
||||
import {
|
||||
ActionConfig,
|
||||
Actions,
|
||||
ActionsConfig,
|
||||
AuxillaryActionConfig,
|
||||
} from '../../config/types.js';
|
||||
import { forwardHaptic } from '../../ha/haptic.js';
|
||||
import { getActionConfigGivenAction } from '../../utils/action.js';
|
||||
import { allPromises } from '../../utils/basic.js';
|
||||
import { TemplateRenderer } from '../templates/index.js';
|
||||
import { CardActionsManagerAPI } from '../types.js';
|
||||
import { ActionSet } from './actions/set.js';
|
||||
import { ActionExecutionRequest, AuxillaryActionConfig } from './types.js';
|
||||
import { ActionExecutionRequest } from './types.js';
|
||||
|
||||
const INTERACTIONS = ['tap', 'double_tap', 'hold', 'start_tap', 'end_tap'] as const;
|
||||
export type InteractionName = (typeof INTERACTIONS)[number];
|
||||
@@ -60,7 +67,7 @@ export class ActionsManager {
|
||||
/**
|
||||
* Handle an human interaction called on an element (e.g. 'tap').
|
||||
*/
|
||||
public handleInteractionEvent = (ev: Event): void => {
|
||||
public handleInteractionEvent = async (ev: Event): Promise<void> => {
|
||||
const result = interactionEventSchema.safeParse(ev);
|
||||
if (!result.success) {
|
||||
return;
|
||||
@@ -76,7 +83,7 @@ export class ActionsManager {
|
||||
// actions).
|
||||
actionConfig
|
||||
) {
|
||||
this.executeActions(actionConfig, { config });
|
||||
await this.executeActions(actionConfig, { config });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,14 +92,16 @@ export class ActionsManager {
|
||||
* cards to fire custom actions. This card itself should not call this, but
|
||||
* embedded elements may.
|
||||
*/
|
||||
public handleCustomActionEvent = (ev: Event): void => {
|
||||
public handleCustomActionEvent = async (
|
||||
ev: Event | CustomEvent<ActionConfig>,
|
||||
): Promise<void> => {
|
||||
if (!('detail' in ev)) {
|
||||
// The event may or may not be a CustomEvent object. For example, whilst
|
||||
// this card doesn't use custom-card-helpers, embedded elements may:
|
||||
// https://github.com/custom-cards/custom-card-helpers/blob/master/src/fire-event.ts#L70
|
||||
return;
|
||||
}
|
||||
this.executeActions(ev.detail as ActionType);
|
||||
await this.executeActions(ev.detail as ActionConfig);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -107,25 +116,25 @@ export class ActionsManager {
|
||||
});
|
||||
};
|
||||
|
||||
public uninitialize(): void {
|
||||
public async uninitialize(): Promise<void> {
|
||||
// If there are any long-running actions, ensure they are stopped.
|
||||
this._actionsInFlight.forEach((actionSet) => actionSet.stop());
|
||||
await allPromises(this._actionsInFlight, (actionSet) => actionSet.stop());
|
||||
}
|
||||
|
||||
public async executeActions(
|
||||
action: ActionType | ActionType[],
|
||||
action: ActionConfig | ActionConfig[],
|
||||
options?: {
|
||||
config?: AuxillaryActionConfig;
|
||||
triggerData?: ConditionsTriggerData;
|
||||
},
|
||||
): Promise<void> {
|
||||
const hass = this._api.getHASSManager().getHASS();
|
||||
const renderedAction =
|
||||
const renderedAction: ActionConfig | ActionConfig[] =
|
||||
hass && this._templateRenderer
|
||||
? this._templateRenderer.renderRecursively(hass, action, {
|
||||
? (this._templateRenderer.renderRecursively(hass, action, {
|
||||
conditionState: this._api.getConditionStateManager().getState(),
|
||||
triggerData: options?.triggerData,
|
||||
})
|
||||
}) as ActionConfig | ActionConfig[])
|
||||
: action;
|
||||
|
||||
const actionSet = new ActionSet(this._actionContext, renderedAction, {
|
||||
@@ -134,7 +143,13 @@ export class ActionsManager {
|
||||
});
|
||||
|
||||
this._actionsInFlight.push(actionSet);
|
||||
await actionSet.execute(this._api);
|
||||
|
||||
try {
|
||||
await actionSet.execute(this._api);
|
||||
forwardHaptic('success');
|
||||
} catch (e) {
|
||||
forwardHaptic('warning');
|
||||
}
|
||||
this._actionsInFlight = this._actionsInFlight.filter((a) => a !== actionSet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ActionContext } from 'action';
|
||||
import { AdvancedCameraCardCustomAction } from '../../../config/types';
|
||||
import { ActionConfig, AuxillaryActionConfig } from '../../../config/types';
|
||||
import { localize } from '../../../localize/localize.js';
|
||||
import { isAdvancedCameraCardCustomAction } from '../../../utils/action';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { Action, AuxillaryActionConfig } from '../types';
|
||||
import { Action, ActionAbortError } from '../types';
|
||||
|
||||
export class BaseAction<T> implements Action {
|
||||
export class BaseAction<T extends ActionConfig> implements Action {
|
||||
protected _context: ActionContext;
|
||||
protected _action: T;
|
||||
protected _config?: AuxillaryActionConfig;
|
||||
@@ -14,9 +16,32 @@ export class BaseAction<T> implements Action {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public async execute(_api: CardActionsAPI): Promise<void> {
|
||||
// Pass.
|
||||
protected _shouldSeekConfirmation(api: CardActionsAPI): boolean {
|
||||
const hass = api.getHASSManager().getHASS();
|
||||
|
||||
return (
|
||||
(typeof this._action.confirmation === 'boolean' && this._action.confirmation) ||
|
||||
(typeof this._action.confirmation === 'object' &&
|
||||
(!this._action.confirmation.exemptions ||
|
||||
!this._action.confirmation.exemptions.some(
|
||||
(entry) => entry.user === hass?.user.id,
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
if (this._shouldSeekConfirmation(api)) {
|
||||
const actionName = isAdvancedCameraCardCustomAction(this._action)
|
||||
? this._action.advanced_camera_card_action
|
||||
: this._action.action;
|
||||
const text =
|
||||
(typeof this._action.confirmation === 'object'
|
||||
? this._action.confirmation.text
|
||||
: null) ?? `${localize('actions.confirmation')}: ${actionName}`;
|
||||
if (!confirm(text)) {
|
||||
throw new ActionAbortError(localize('actions.abort'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
@@ -24,6 +49,4 @@ export class BaseAction<T> implements Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class AdvancedCameraCardAction<
|
||||
T extends AdvancedCameraCardCustomAction,
|
||||
> extends BaseAction<T> {}
|
||||
export class AdvancedCameraCardAction<T extends ActionConfig> extends BaseAction<T> {}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { CallServiceActionConfig } from '../../../config/types';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class CallServiceAction extends AdvancedCameraCardAction<CallServiceActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const hass = api.getHASSManager().getHASS();
|
||||
if (!hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [domain, service] = this._action.service.split('.', 2);
|
||||
await hass.callService(domain, service, this._action.data, this._action.target);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class CameraSelectAction extends AdvancedCameraCardAction<CameraSelectActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const selectCameraID =
|
||||
this._action.camera ??
|
||||
(this._action.triggered
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class CameraUIAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getCameraURLManager().openURL();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { CustomActionConfig } from '../../../config/types';
|
||||
import { fireHASSEvent } from '../../../ha/fire-hass-event';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class CustomAction extends AdvancedCameraCardAction<CustomActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
fireHASSEvent(api.getCardElementManager().getElement(), 'll-custom', this._action);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class DefaultAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getViewManager().setViewDefaultWithNewQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class DisplayModeSelectAction extends AdvancedCameraCardAction<DisplayModeActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getViewManager().setViewByParametersWithNewQuery({
|
||||
params: {
|
||||
displayMode: this._action.display_mode,
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class DownloadAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getDownloadManager().downloadViewerMedia();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class ExpandAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getExpandManager().toggleExpanded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class FullscreenAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getFullscreenManager().toggleFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { handleActionConfig } from '../../../ha/handle-action';
|
||||
import { ActionConfig } from '../../../ha/types';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { BaseAction } from './base';
|
||||
|
||||
/**
|
||||
* Handles generic HA actions (e.g. 'more-info')
|
||||
*/
|
||||
export class GenericAction extends BaseAction<ActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
const hass = api.getHASSManager().getHASS();
|
||||
if (hass) {
|
||||
handleActionConfig(
|
||||
api.getCardElementManager().getElement(),
|
||||
hass,
|
||||
this._config ?? {},
|
||||
this._action,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class InternalCallbackAction extends AdvancedCameraCardAction<InternalCallbackActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await this._action.callback(api);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class LogAction extends AdvancedCameraCardAction<LogActionConfig> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public async execute(_api: CardActionsAPI): Promise<void> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
console[this._action.level](this._action.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class MediaPlayerAction extends AdvancedCameraCardAction<MediaPlayerActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const mediaPlayer = this._action.media_player;
|
||||
const mediaPlayerController = api.getMediaPlayerManager();
|
||||
const view = api.getViewManager().getView();
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class MenuToggleAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getCardElementManager().toggleMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class MicrophoneConnectAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getMicrophoneManager().connect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class MicrophoneDisconnectAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getMicrophoneManager().disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class MicrophoneMuteAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getMicrophoneManager().mute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class MicrophoneUnmuteAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getMicrophoneManager().unmute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MoreInfoActionConfig } from '../../../config/types';
|
||||
import { fireHASSEvent } from '../../../ha/fire-hass-event';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class MoreInfoAction extends AdvancedCameraCardAction<MoreInfoActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const entityID = this._action.entity ?? this._config?.entity ?? null;
|
||||
if (!entityID) {
|
||||
return;
|
||||
}
|
||||
|
||||
fireHASSEvent(api.getCardElementManager().getElement(), 'hass-more-info', {
|
||||
entityId: entityID,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class MuteAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getMediaLoadedInfoManager().get()?.mediaPlayerController?.mute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NavigateActionConfig } from '../../../config/types';
|
||||
import { fireHASSEvent } from '../../../ha/fire-hass-event';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class NavigateAction extends AdvancedCameraCardAction<NavigateActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
if (!!this._action.navigation_replace) {
|
||||
history.replaceState(null, '', this._action.navigation_path);
|
||||
} else {
|
||||
history.pushState(null, '', this._action.navigation_path);
|
||||
}
|
||||
fireHASSEvent(window, 'location-changed', {
|
||||
replace: !!this._action.navigation_replace,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NoneActionConfig } from '../../../config/types';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class NoneAction extends AdvancedCameraCardAction<NoneActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class PauseAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getMediaLoadedInfoManager().get()?.mediaPlayerController?.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { PerformActionActionConfig } from '../../../config/types';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class PerformActionAction extends AdvancedCameraCardAction<PerformActionActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const hass = api.getHASSManager().getHASS();
|
||||
if (!hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [domain, service] = this._action.perform_action.split('.', 2);
|
||||
await hass.callService(domain, service, this._action.data, this._action.target);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class PlayAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getMediaLoadedInfoManager().get()?.mediaPlayerController?.play();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class PTZControlsAction extends AdvancedCameraCardAction<PTZControlsActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getViewManager().setViewWithMergedContext({
|
||||
ptzControls: { enabled: this._action.enabled },
|
||||
});
|
||||
|
||||
@@ -45,6 +45,8 @@ export class PTZDigitalAction extends AdvancedCameraCardAction<PTZDigitialAction
|
||||
}
|
||||
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const view = api.getViewManager().getView();
|
||||
if (!view) {
|
||||
return;
|
||||
|
||||
@@ -8,6 +8,8 @@ import { PTZDigitalAction } from './ptz-digital';
|
||||
|
||||
export class PTZMultiAction extends AdvancedCameraCardAction<PTZMultiActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const view = api.getViewManager().getView();
|
||||
let targetID: string | null = null;
|
||||
let type: PTZType | null = null;
|
||||
|
||||
@@ -28,6 +28,8 @@ export class PTZAction extends AdvancedCameraCardAction<PTZActionConfig> {
|
||||
}
|
||||
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const view = api.getViewManager().getView();
|
||||
if (!view) {
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class ScreenshotAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getDownloadManager().downloadScreenshot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ActionContext } from 'action';
|
||||
import { ActionType } from '../../../config/types';
|
||||
import { ActionConfig, AuxillaryActionConfig } from '../../../config/types';
|
||||
import { arrayify } from '../../../utils/basic';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { ActionFactory } from '../factory';
|
||||
import { Action, AuxillaryActionConfig } from '../types';
|
||||
import { Action } from '../types';
|
||||
|
||||
export class ActionSet implements Action {
|
||||
protected _context: ActionContext;
|
||||
@@ -13,7 +13,7 @@ export class ActionSet implements Action {
|
||||
|
||||
constructor(
|
||||
context: ActionContext,
|
||||
actions: ActionType | ActionType[],
|
||||
actions: ActionConfig | ActionConfig[],
|
||||
options?: {
|
||||
config?: AuxillaryActionConfig;
|
||||
cardID?: string;
|
||||
|
||||
@@ -5,8 +5,9 @@ import { timeDeltaToSeconds } from '../utils/time-delta';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class SleepAction extends AdvancedCameraCardAction<SleepActionConfig> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public async execute(_api: CardActionsAPI): Promise<void> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await sleep(timeDeltaToSeconds(this._action.duration));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
export class StatusBarAction extends AdvancedCameraCardAction<StatusBarActionConfig> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
switch (this._action.status_bar_action) {
|
||||
case 'reset':
|
||||
api.getStatusBarItemManager().removeAllDynamicStatusBarItems();
|
||||
|
||||
@@ -5,6 +5,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class SubstreamOffAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getViewManager().setViewByParameters({
|
||||
modifiers: [new SubstreamOffViewModifier()],
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class SubstreamOnAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getViewManager().setViewByParameters({
|
||||
modifiers: [new SubstreamOnViewModifier(api)],
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class SubstreamSelectAction extends AdvancedCameraCardAction<SubstreamSelectActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
api.getViewManager().setViewByParameters({
|
||||
modifiers: [new SubstreamSelectViewModifier(this._action.camera)],
|
||||
});
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ToggleActionConfig } from '../../../config/types';
|
||||
import { computeDomain } from '../../../ha/compute-domain';
|
||||
import { STATES_OFF } from '../../../ha/const';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class ToggleAction extends AdvancedCameraCardAction<ToggleActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
const hass = api.getHASSManager().getHASS();
|
||||
const entityID = this._config?.entity;
|
||||
if (!hass || !entityID) {
|
||||
return;
|
||||
}
|
||||
const entityState = hass.states[entityID]?.state;
|
||||
if (!entityState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const turnOn = STATES_OFF.includes(entityState);
|
||||
const stateDomain = computeDomain(entityID);
|
||||
const serviceDomain = stateDomain === 'group' ? 'homeassistant' : stateDomain;
|
||||
const service =
|
||||
stateDomain === 'lock'
|
||||
? turnOn
|
||||
? 'unlock'
|
||||
: 'lock'
|
||||
: stateDomain === 'cover'
|
||||
? turnOn
|
||||
? 'open_cover'
|
||||
: 'close_cover'
|
||||
: turnOn
|
||||
? 'turn_on'
|
||||
: 'turn_off';
|
||||
await hass.callService(serviceDomain, service, { entity_id: entityID });
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class UnmuteAction extends AdvancedCameraCardAction<GeneralActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getMediaLoadedInfoManager().get()?.mediaPlayerController?.unmute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { URLActionConfig } from '../../../config/types';
|
||||
import { CardActionsAPI } from '../../types';
|
||||
import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class URLAction extends AdvancedCameraCardAction<URLActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
window.open(this._action.url_path);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { AdvancedCameraCardAction } from './base';
|
||||
|
||||
export class ViewAction extends AdvancedCameraCardAction<ViewActionConfig> {
|
||||
public async execute(api: CardActionsAPI): Promise<void> {
|
||||
await super.execute(api);
|
||||
|
||||
await api.getViewManager().setViewByParametersWithNewQuery({
|
||||
params: {
|
||||
view: this._action.advanced_camera_card_action,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { ActionContext } from 'action';
|
||||
import { ActionType, INTERNAL_CALLBACK_ACTION } from '../../config/types';
|
||||
import { ActionConfig } from '../../ha/types';
|
||||
import { convertActionToCardCustomAction } from '../../utils/action';
|
||||
import {
|
||||
ActionConfig,
|
||||
AuxillaryActionConfig,
|
||||
INTERNAL_CALLBACK_ACTION,
|
||||
} from '../../config/types';
|
||||
import { isAdvancedCameraCardCustomAction } from '../../utils/action';
|
||||
import { CallServiceAction } from './actions/call-service';
|
||||
import { CameraSelectAction } from './actions/camera-select';
|
||||
import { CameraUIAction } from './actions/camera-ui';
|
||||
import { CustomAction } from './actions/custom';
|
||||
import { DefaultAction } from './actions/default';
|
||||
import { DisplayModeSelectAction } from './actions/display-mode-select';
|
||||
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';
|
||||
@@ -18,8 +22,12 @@ import { MicrophoneConnectAction } from './actions/microphone-connect';
|
||||
import { MicrophoneDisconnectAction } from './actions/microphone-disconnect';
|
||||
import { MicrophoneMuteAction } from './actions/microphone-mute';
|
||||
import { MicrophoneUnmuteAction } from './actions/microphone-unmute';
|
||||
import { MoreInfoAction } from './actions/more-info';
|
||||
import { MuteAction } from './actions/mute';
|
||||
import { NavigateAction } from './actions/navigate';
|
||||
import { NoneAction } from './actions/none';
|
||||
import { PauseAction } from './actions/pause';
|
||||
import { PerformActionAction } from './actions/perform-action';
|
||||
import { PlayAction } from './actions/play';
|
||||
import { PTZAction } from './actions/ptz';
|
||||
import { PTZControlsAction } from './actions/ptz-controls';
|
||||
@@ -31,39 +39,53 @@ import { StatusBarAction } from './actions/status-bar';
|
||||
import { SubstreamOffAction } from './actions/substream-off';
|
||||
import { SubstreamOnAction } from './actions/substream-on';
|
||||
import { SubstreamSelectAction } from './actions/substream-select';
|
||||
import { ToggleAction } from './actions/toggle';
|
||||
import { UnmuteAction } from './actions/unmute';
|
||||
import { URLAction } from './actions/url';
|
||||
import { ViewAction } from './actions/view';
|
||||
import { Action, AuxillaryActionConfig } from './types';
|
||||
import { Action } from './types';
|
||||
|
||||
export class ActionFactory {
|
||||
public createAction(
|
||||
context: ActionContext,
|
||||
action: ActionType,
|
||||
action: ActionConfig,
|
||||
options?: {
|
||||
config?: AuxillaryActionConfig;
|
||||
cardID?: string;
|
||||
},
|
||||
): Action | null {
|
||||
const cardCustomAction = convertActionToCardCustomAction(action);
|
||||
if (action.action !== 'fire-dom-event' || !cardCustomAction) {
|
||||
// * There is a slight typing (but not functional) difference between
|
||||
// ActionType in this card and ActionConfig in `custom-card-helpers`. See
|
||||
// `ExtendedConfirmationRestrictionConfig` in `types.ts` for the source and
|
||||
// reason behind this difference.
|
||||
return new GenericAction(context, action as ActionConfig, options?.config);
|
||||
}
|
||||
|
||||
if (
|
||||
// Command not intended for this card (e.g. query string command).
|
||||
cardCustomAction.card_id &&
|
||||
cardCustomAction.card_id !== options?.cardID
|
||||
action.card_id &&
|
||||
action.card_id !== options?.cardID
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (cardCustomAction.advanced_camera_card_action) {
|
||||
switch (action.action) {
|
||||
case 'more-info':
|
||||
return new MoreInfoAction(context, action, options?.config);
|
||||
case 'toggle':
|
||||
return new ToggleAction(context, action, options?.config);
|
||||
case 'navigate':
|
||||
return new NavigateAction(context, action, options?.config);
|
||||
case 'url':
|
||||
return new URLAction(context, action, options?.config);
|
||||
case 'perform-action':
|
||||
return new PerformActionAction(context, action, options?.config);
|
||||
case 'call-service':
|
||||
return new CallServiceAction(context, action, options?.config);
|
||||
case 'none':
|
||||
return new NoneAction(context, action, options?.config);
|
||||
}
|
||||
|
||||
if (!isAdvancedCameraCardCustomAction(action)) {
|
||||
return new CustomAction(context, action, options?.config);
|
||||
}
|
||||
|
||||
switch (action.advanced_camera_card_action) {
|
||||
case 'default':
|
||||
return new DefaultAction(context, cardCustomAction, options?.config);
|
||||
return new DefaultAction(context, action, options?.config);
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
case 'image':
|
||||
@@ -74,72 +96,68 @@ export class ActionFactory {
|
||||
case 'snapshots':
|
||||
case 'timeline':
|
||||
case 'diagnostics':
|
||||
return new ViewAction(context, cardCustomAction, options?.config);
|
||||
return new ViewAction(context, action, options?.config);
|
||||
case 'sleep':
|
||||
return new SleepAction(context, cardCustomAction, options?.config);
|
||||
return new SleepAction(context, action, options?.config);
|
||||
case 'download':
|
||||
return new DownloadAction(context, cardCustomAction, options?.config);
|
||||
return new DownloadAction(context, action, options?.config);
|
||||
case 'camera_ui':
|
||||
return new CameraUIAction(context, cardCustomAction, options?.config);
|
||||
return new CameraUIAction(context, action, options?.config);
|
||||
case 'expand':
|
||||
return new ExpandAction(context, cardCustomAction, options?.config);
|
||||
return new ExpandAction(context, action, options?.config);
|
||||
case 'fullscreen':
|
||||
return new FullscreenAction(context, cardCustomAction, options?.config);
|
||||
return new FullscreenAction(context, action, options?.config);
|
||||
case 'menu_toggle':
|
||||
return new MenuToggleAction(context, cardCustomAction, options?.config);
|
||||
return new MenuToggleAction(context, action, options?.config);
|
||||
case 'camera_select':
|
||||
return new CameraSelectAction(context, cardCustomAction, options?.config);
|
||||
return new CameraSelectAction(context, action, options?.config);
|
||||
case 'live_substream_select':
|
||||
return new SubstreamSelectAction(context, cardCustomAction, options?.config);
|
||||
return new SubstreamSelectAction(context, action, options?.config);
|
||||
case 'live_substream_off':
|
||||
return new SubstreamOffAction(context, cardCustomAction, options?.config);
|
||||
return new SubstreamOffAction(context, action, options?.config);
|
||||
case 'live_substream_on':
|
||||
return new SubstreamOnAction(context, cardCustomAction, options?.config);
|
||||
return new SubstreamOnAction(context, action, options?.config);
|
||||
case 'media_player':
|
||||
return new MediaPlayerAction(context, cardCustomAction, options?.config);
|
||||
return new MediaPlayerAction(context, action, options?.config);
|
||||
case 'microphone_connect':
|
||||
return new MicrophoneConnectAction(context, cardCustomAction, options?.config);
|
||||
return new MicrophoneConnectAction(context, action, options?.config);
|
||||
case 'microphone_disconnect':
|
||||
return new MicrophoneDisconnectAction(
|
||||
context,
|
||||
cardCustomAction,
|
||||
options?.config,
|
||||
);
|
||||
return new MicrophoneDisconnectAction(context, action, options?.config);
|
||||
case 'microphone_mute':
|
||||
return new MicrophoneMuteAction(context, cardCustomAction, options?.config);
|
||||
return new MicrophoneMuteAction(context, action, options?.config);
|
||||
case 'microphone_unmute':
|
||||
return new MicrophoneUnmuteAction(context, cardCustomAction, options?.config);
|
||||
return new MicrophoneUnmuteAction(context, action, options?.config);
|
||||
case 'mute':
|
||||
return new MuteAction(context, cardCustomAction, options?.config);
|
||||
return new MuteAction(context, action, options?.config);
|
||||
case 'unmute':
|
||||
return new UnmuteAction(context, cardCustomAction, options?.config);
|
||||
return new UnmuteAction(context, action, options?.config);
|
||||
case 'play':
|
||||
return new PlayAction(context, cardCustomAction, options?.config);
|
||||
return new PlayAction(context, action, options?.config);
|
||||
case 'pause':
|
||||
return new PauseAction(context, cardCustomAction, options?.config);
|
||||
return new PauseAction(context, action, options?.config);
|
||||
case 'screenshot':
|
||||
return new ScreenshotAction(context, cardCustomAction, options?.config);
|
||||
return new ScreenshotAction(context, action, options?.config);
|
||||
case 'display_mode_select':
|
||||
return new DisplayModeSelectAction(context, cardCustomAction, options?.config);
|
||||
return new DisplayModeSelectAction(context, action, options?.config);
|
||||
case 'ptz':
|
||||
return new PTZAction(context, cardCustomAction, options?.config);
|
||||
return new PTZAction(context, action, options?.config);
|
||||
case 'ptz_digital':
|
||||
return new PTZDigitalAction(context, cardCustomAction, options?.config);
|
||||
return new PTZDigitalAction(context, action, options?.config);
|
||||
case 'ptz_multi':
|
||||
return new PTZMultiAction(context, cardCustomAction, options?.config);
|
||||
return new PTZMultiAction(context, action, options?.config);
|
||||
case 'ptz_controls':
|
||||
return new PTZControlsAction(context, cardCustomAction, options?.config);
|
||||
return new PTZControlsAction(context, action, options?.config);
|
||||
case 'log':
|
||||
return new LogAction(context, cardCustomAction, options?.config);
|
||||
return new LogAction(context, action, options?.config);
|
||||
case 'status_bar':
|
||||
return new StatusBarAction(context, cardCustomAction, options?.config);
|
||||
return new StatusBarAction(context, action, options?.config);
|
||||
case INTERNAL_CALLBACK_ACTION:
|
||||
return new InternalCallbackAction(context, cardCustomAction, options?.config);
|
||||
return new InternalCallbackAction(context, action, options?.config);
|
||||
}
|
||||
|
||||
/* istanbul ignore next: this path cannot be reached -- @preserve */
|
||||
console.warn(
|
||||
`Advanced Camera Card received unknown card action: ${cardCustomAction['advanced_camera_card_action']}`,
|
||||
`Advanced Camera Card received unknown card action: ${action['advanced_camera_card_action']}`,
|
||||
);
|
||||
/* istanbul ignore next: this path cannot be reached -- @preserve */
|
||||
return null;
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { ActionType } from '../../config/types';
|
||||
import { AdvancedCameraCardError } from '../../types.js';
|
||||
import { ActionConfig, AuxillaryActionConfig } from '../../config/types';
|
||||
import { CardActionsAPI } from '../types';
|
||||
|
||||
export interface AuxillaryActionConfig {
|
||||
camera_image?: string;
|
||||
entity?: string;
|
||||
}
|
||||
|
||||
export interface Action {
|
||||
execute(api: CardActionsAPI): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ActionExecutionRequest {
|
||||
action: ActionType[] | ActionType;
|
||||
action: ActionConfig[] | ActionConfig;
|
||||
config?: AuxillaryActionConfig;
|
||||
}
|
||||
|
||||
@@ -21,3 +17,5 @@ export interface TargetedActionContext {
|
||||
inProgressAction?: Action;
|
||||
};
|
||||
}
|
||||
|
||||
export class ActionAbortError extends AdvancedCameraCardError {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AdvancedCameraCardCustomAction, ViewActionConfig } from '../config/types';
|
||||
import { AdvancedCameraCardCustomActionConfig, ViewActionConfig } from '../config/types';
|
||||
import {
|
||||
createCameraAction,
|
||||
createGeneralAction,
|
||||
@@ -13,7 +13,7 @@ interface QueryStringViewIntent {
|
||||
default?: boolean;
|
||||
substream?: string;
|
||||
};
|
||||
other?: AdvancedCameraCardCustomAction[];
|
||||
other?: AdvancedCameraCardCustomActionConfig[];
|
||||
}
|
||||
|
||||
export class QueryStringManager {
|
||||
@@ -92,9 +92,9 @@ export class QueryStringManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected _getActions(): AdvancedCameraCardCustomAction[] {
|
||||
protected _getActions(): AdvancedCameraCardCustomActionConfig[] {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const actions: AdvancedCameraCardCustomAction[] = [];
|
||||
const actions: AdvancedCameraCardCustomActionConfig[] = [];
|
||||
const actionRE = new RegExp(
|
||||
/^(advanced-camera-card|frigate-card)-action([.:](?<cardID>\w+))?[.:](?<action>\w+)/,
|
||||
);
|
||||
@@ -104,14 +104,14 @@ export class QueryStringManager {
|
||||
continue;
|
||||
}
|
||||
const cardID: string | undefined = match.groups['cardID'];
|
||||
const action = match.groups['action'];
|
||||
const actionName = match.groups['action'];
|
||||
|
||||
let customAction: AdvancedCameraCardCustomAction | null = null;
|
||||
switch (action) {
|
||||
let action: AdvancedCameraCardCustomActionConfig | null = null;
|
||||
switch (actionName) {
|
||||
case 'camera_select':
|
||||
case 'live_substream_select':
|
||||
if (value) {
|
||||
customAction = createCameraAction(action, value, {
|
||||
action = createCameraAction(actionName, value, {
|
||||
cardID: cardID,
|
||||
});
|
||||
}
|
||||
@@ -121,7 +121,7 @@ export class QueryStringManager {
|
||||
case 'download':
|
||||
case 'expand':
|
||||
case 'menu_toggle':
|
||||
customAction = createGeneralAction(action, {
|
||||
action = createGeneralAction(actionName, {
|
||||
cardID: cardID,
|
||||
});
|
||||
break;
|
||||
@@ -135,24 +135,24 @@ export class QueryStringManager {
|
||||
case 'snapshot':
|
||||
case 'snapshots':
|
||||
case 'timeline':
|
||||
customAction = createViewAction(action, {
|
||||
action = createViewAction(actionName, {
|
||||
cardID: cardID,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
`Advanced Camera Card received unknown card action in query string: ${action}`,
|
||||
`Advanced Camera Card received unknown card action in query string: ${actionName}`,
|
||||
);
|
||||
}
|
||||
if (customAction) {
|
||||
actions.push(customAction);
|
||||
if (action) {
|
||||
actions.push(action);
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected _isViewAction = (
|
||||
action: AdvancedCameraCardCustomAction,
|
||||
action: AdvancedCameraCardCustomActionConfig,
|
||||
): action is ViewActionConfig => {
|
||||
switch (action.advanced_camera_card_action) {
|
||||
case 'clip':
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { HASS, renderTemplate } from 'ha-nunjucks/dist';
|
||||
import { ConditionState, ConditionsTriggerData } from '../../conditions/types';
|
||||
import { ActionType } from '../../config/types';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
|
||||
interface TemplateContextInternal {
|
||||
@@ -24,7 +23,7 @@ export class TemplateRenderer {
|
||||
conditionState?: ConditionState;
|
||||
triggerData?: ConditionsTriggerData;
|
||||
},
|
||||
): ActionType => {
|
||||
): unknown => {
|
||||
return this._renderTemplateRecursively(
|
||||
hass,
|
||||
data,
|
||||
@@ -59,11 +58,15 @@ export class TemplateRenderer {
|
||||
hass: HomeAssistant,
|
||||
data: unknown,
|
||||
templateContext?: TemplateContext,
|
||||
): ActionType {
|
||||
): unknown {
|
||||
if (typeof data === 'string') {
|
||||
// ha-nunjucks has a more complete model of the Home Assistant object, but
|
||||
// does not export it as a type.
|
||||
return renderTemplate(hass as unknown as typeof HASS, data, templateContext);
|
||||
return renderTemplate(
|
||||
// ha-nunjucks has a more complete model of the Home Assistant object, but
|
||||
// does not export it as a type.
|
||||
hass as unknown as typeof HASS,
|
||||
data,
|
||||
templateContext,
|
||||
);
|
||||
} else if (Array.isArray(data)) {
|
||||
return data.map((item) =>
|
||||
this._renderTemplateRecursively(hass, item, templateContext),
|
||||
|
||||
Reference in New Issue
Block a user