fix: Ensure a bare ptz_digital action resets the zoom (#2635)

- Closes: #2632
This commit is contained in:
Dermot Duffy
2026-07-29 23:18:06 -07:00
committed by GitHub
parent e9e6d9f833
commit a736f3dfaf
14 changed files with 258 additions and 113 deletions
+10 -2
View File
@@ -9,13 +9,21 @@ export class EffectAction extends AdvancedCameraCardAction<EffectActionConfig> {
const action = this._getAction();
switch (action.effect_action) {
case 'start':
void api.getEffectsManager().startEffect(action.effect);
// An effect runs for its own duration, so it is deliberately not
// awaited: doing so would stall the rest of the action set.
api
.getEffectsManager()
.startEffect(action.effect)
.catch(() => {});
break;
case 'stop':
api.getEffectsManager().stopEffect(action.effect);
break;
case 'toggle':
void api.getEffectsManager().toggleEffect(action.effect);
api
.getEffectsManager()
.toggleEffect(action.effect)
.catch(() => {});
break;
}
}
@@ -6,12 +6,12 @@ import {
ZOOM_DEFAULT_SCALE,
type PartialZoomSettings,
} from '../../../components-lib/zoom/types';
import { generateViewContextForZoom } from '../../../components-lib/zoom/zoom-view-context';
import type { PTZDigitialActionConfig } from '../../../config/schema/actions/custom/ptz-digital';
import { ZOOM_MAX, ZOOM_MIN } from '../../../config/schema/common/zoom';
import { getPTZTarget } from '../../../utils/ptz';
import { Timer } from '../../../utils/timer';
import type { CardActionsAPI } from '../../types';
import { ZoomRequestViewModifier } from '../../view/modifiers/zoom-request';
import type { TargetedActionContext } from '../types';
import {
setInProgressForThisTarget,
@@ -33,13 +33,16 @@ export class PTZDigitalAction extends AdvancedCameraCardAction<PTZDigitialAction
private _timer = new Timer();
private async _stepChange(api: CardActionsAPI, targetID: string): Promise<void> {
api.getViewManager().setViewWithMergedContext(
generateViewContextForZoom(targetID, {
requested: this._convertActionToZoomSettings(
api.getViewManager().getView()?.context?.zoom?.[targetID]?.observed,
api
.getViewManager()
.setViewWithModifiers([
new ZoomRequestViewModifier(
targetID,
this._convertActionToZoomSettings(
api.getViewManager().getView()?.context?.zoom?.[targetID]?.observed,
),
),
}),
);
]);
}
public async stop(): Promise<void> {
@@ -28,7 +28,7 @@ export class PTZMultiAction extends AdvancedCameraCardAction<PTZMultiActionConfi
return;
}
void (
await (
type === 'ptz' ? this._toPTZAction(targetID) : this._toPTZDigitalAction(targetID)
).execute(api);
}
+2 -1
View File
@@ -4,6 +4,7 @@ import type { ViewModifier } from '../types';
export const applyViewModifiers = (
view: View,
modifiers?: ViewModifier[] | null,
): void => {
): View => {
modifiers?.forEach((modifier) => modifier.modify(view));
return view;
};
@@ -0,0 +1,30 @@
import type { PartialZoomSettings } from '../../../components-lib/zoom/types';
import { generateViewContextForZoom } from '../../../components-lib/zoom/zoom-view-context';
import type { View } from '../../../view/view';
import type { ViewModifier } from '../types';
// The single write path for a zoom request. The settings are *assigned* rather than
// *merged*, so a new request always replaces the one before it. This is
// necessary as a request to reset to default is expressed as an empty object,
// and a deep merge cannot use that to *erase* the values of an earlier request.
export class ZoomRequestViewModifier implements ViewModifier {
private _targetID: string;
private _requested: PartialZoomSettings;
constructor(targetID: string, requested: PartialZoomSettings) {
this._targetID = targetID;
this._requested = requested;
}
public modify(view: View): void {
const target = view.context?.zoom?.[this._targetID];
if (target) {
target.requested = this._requested;
return;
}
view.mergeInContext(
generateViewContextForZoom(this._targetID, { requested: this._requested }),
);
}
}
+1
View File
@@ -82,6 +82,7 @@ export interface ViewManagerInterface {
setViewByParametersWithExistingQuery(options?: ViewFactoryOptions): Promise<void>;
setViewWithMergedContext(context: ViewContext | null): void;
setViewWithModifiers(modifiers: ViewModifier[]): void;
hasMajorMediaChange(oldView?: View | null, newView?: View | null): boolean;
}
+6
View File
@@ -334,6 +334,12 @@ export class ViewManager implements ViewManagerInterface {
}
}
public setViewWithModifiers(modifiers: ViewModifier[]): void {
if (this._view) {
return this._setView(applyViewModifiers(this._view.clone(), modifiers));
}
}
/**
* Detect if the current view has a major "media change" for the given previous view.
* @param oldView The previous view.
+3 -1
View File
@@ -8,7 +8,9 @@ interface ZoomViewContext {
observed?: ZoomSettingsObserved;
// Populate this to request zoom to a particular scale/x/y. An empty object
// will reset to default, null will make no change.
// will reset to default, null will make no change. Requests must be written
// by `ZoomRequestViewModifier` since an empty object needs to mean "reset to
// default" (and a merged empty object != an assigned empty object).
requested?: PartialZoomSettings | null;
}