Skip to main content

The Control Panel

Hover the 60° camera image and its cockpit fades in: close, the pan/zoom tool toggle, a "…" menu, Home, Peak Finder, playback, and a zoom slider down the right edge. That's CameraControlButtons — and the way to read its ~one file of &&-guarded JSX is to know its two organizing ideas: it has three personalities, and its buttons answer to gates.

Three personalities, one at a time

movementSliderMode in cameraMovementStore selects one of three mutually exclusive layouts:

  • zoom — the full panel, wrapped in FadeAfterInactivityContainer so it melts away while the operator watches the frame.
  • focus — only FocusModeButtons.
  • brightness — only BrightnessModeButtons.

The focus and brightness modes have an entry requirement: the camera must have reported the capability. foc and br arrive via SignalR into realTimeCameraStore; if a camera has never reported one, its menu item renders disabled at 50% opacity and the mode won't mount even if something sets it. A camera that can't focus simply never shows you a focus panel.

And remember the request board: neither mode panel calls an API. Applying focus sets newFocus + shouldApplyFocus on the store; the overlay notices and dispatches. Exiting either mode returns you to zoom.

The gates

Most of the panel answers to one derived boolean:

const canAdjustCameraSettings =
isLoggedIn && !!cameraSelected?.cId && canControlCamera(cameraSelected.cId);

The tool toggle, the zoom slider, Home, and the focus/brightness menu entries all require it (the last two additionally require the capability field). Playback hides during playback itself, while loading, and when listed in btnsToHide.

But two controls are deliberately ungated, and both trip up reviewers:

  • The "…" menu and the download action inside it. An anonymous or view-only user can open the menu and download the current frame — they just find the focus/brightness entries missing. Watching and saving evidence requires no control rights.
  • Peak Finder. It's a view aid — an overlay naming the ridgelines — not a camera control, so it sits outside the RBAC guards entirely. (It also renders inside the left button column rather than positioning itself over the image, so it stays flush with whichever buttons this user's permissions actually produced.)

If you're ever tempted to "fix" either gate, know that both are choices, not omissions.

Wrapping up

One panel, three mutually exclusive personalities selected by movementSliderMode; one permission boolean guarding most controls, with the menu and Peak Finder ungated on purpose; and every "apply" routed through the store to the overlay, never straight to an API.

Next: the component all those requests funnel into — the overlay, where gestures become degrees.