Commit Graph

216 Commits

Author SHA1 Message Date
Claude
29e2003a84
Fix fit-to-screen drag types to match EventCallback signature
All LayoutItem args are nullable (LayoutItem | null) and event is Event
not MouseEvent — matching the actual react-grid-layout EventCallback type.
fitDragRef simplified to string | null. Cast event to MouseEvent inside
the handler to access clientX/Y for pixel-accurate slot detection.

https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
2026-03-22 00:04:21 +00:00
ibs0d
b277b0bd98
Revert "Fix fit-to-screen horizontal swap using real mouse coordinates" 2026-03-22 10:48:32 +11:00
Claude
051f680b04
Fix fit-to-screen horizontal swap using real mouse coordinates
rgl with compactType=vertical doesn't move items horizontally, so
layoutItem.x in onDragStop stays near its origin, making horizontal
slot detection wrong.

Switch to tracking event.clientX/Y from onDrag (fitDragRef), then in
onDragStop translate the final mouse position against the .grid-layout
element's bounding rect to get pixel-accurate targetCol/targetRow.
This makes horizontal, vertical, and long-distance swaps all reliable.

https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
2026-03-21 13:12:41 +00:00
Claude
3d1de5bf69
Fix fit-to-screen drag: support non-adjacent and horizontal swaps
Previous approach sorted react-grid-layout's post-drag positions to infer
order, which broke for non-adjacent and horizontal moves because rgl pushes
items down instead of swapping them.

New approach:
- onDrag records which item is being dragged (draggedItemRef)
- onDragStop uses the dragged item's final x/y to compute the target slot
  in our own grid, then performs a clean swap in the ordered name array
- Layout is always fully regenerated from our order array, ignoring rgl's
  position arithmetic entirely

https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
2026-03-21 12:49:02 +00:00
Claude
46334ec8be
Fix fit-to-screen drag card disappearing and swap icon
handleFitDragStop now sorts dragged items by position to determine new
order, then recalculates all x/y coords into a strict dense grid instead
of spreading react-grid-layout's arbitrary y values — prevents cards from
being pushed off-screen after a drag.

Also replaces LuMaximize with LuScanBarcode for the fit-to-screen button.

https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
2026-03-21 12:26:03 +00:00
Claude
853978ed4d
Allow drag reordering in Fit to Screen mode
In fitToScreen mode, drag is now enabled so users can reorder cameras
while in edit mode. A fitLayoutOverride state captures the new order
after each drag, normalizing w/h back to gridUnitsPerCam to prevent
size changes. The override resets automatically when the camera list or
grid parameters change.

https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
2026-03-21 11:53:49 +00:00
Claude
be1a19bccd
Add Fit to Screen mode to Live view grid
Adds a toggle button to the Live view toolbar that automatically arranges
all cameras to fit within the viewport without scrolling. Uses a brute-force
algorithm to find the optimal number of columns that maximizes camera size
while keeping all cameras visible. State persists via IndexedDB.

https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
2026-03-21 10:17:13 +00:00
Claude
25a869eb43
Fix ui.rotate: broken CSS height chain causing blank video
Root cause: LivePlayer's outer div has no explicit height (only w-full),
so when MsePlayer reads containerSize.height via ResizeObserver it gets 0.
With isRotatedGrid=true, MsePlayer sets the inner div width:
containerSize.height → width: 0 → video invisible.

Fix:
- Add size-full to LivePlayer className when camera.ui?.rotate, ensuring
  height: 100% propagates through the chain so MsePlayer gets real dims
- Re-add cameraAspectRatio inversion (1/ratio) for portrait container
  layout; now that the height chain is intact this works correctly:
  portrait container → LivePlayer size-full → MsePlayer real dims → swap+rotate

https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
2026-03-21 08:17:40 +00:00
Claude
54b3717a60
Fix: revert cameraAspectRatio inversion for ui.rotate cameras
The previous commit caused a double dimension swap for rotated cameras:
- LiveCameraView was inverting the aspect ratio (1/ratio) → portrait container
- MsePlayer was then swapping width/height again internally when
  isRotatedGrid=true → video got zero/invalid dimensions, nothing visible

The MsePlayer already handles the full rotation internally via CSS variables
(transform + width/height swap). The container in LiveCameraView should
keep the original (landscape) aspect ratio, matching the grid cell behavior
in DraggableGridLayout where this works correctly.

https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
2026-03-21 07:55:26 +00:00
Claude
ea9ca2f8d1
Add ui.rotate support to LiveCameraView single-camera view
- Invert cameraAspectRatio when camera.ui?.rotate is true so the
  container dimensions match the rotated video (width↔height swap)
- Pass CSS variables --frigate-mse-grid-rotated and
  --frigate-mse-grid-rotation to LivePlayer, enabling the existing
  MsePlayer rotation/swap logic for single-camera view
- Fullscreen orientation lock works automatically: an inverted ratio
  < 1 causes portrait lock for a normally-landscape camera

https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
2026-03-21 07:32:14 +00:00
Claude
ee7f0c6717
fix: pass camera name to Review page when navigating from Live Camera View
When clicking the History button on a specific camera's Live view,
append `?cameras=<camera_name>` to the review URL so the camera
filter is pre-set to that camera instead of showing "All Cameras".

The Events (Review) page already supports reading the `cameras` URL
parameter via useSearchEffect - no changes needed there.

Fixes: #12776, #16987

https://claude.ai/code/session_01PnMA1HcuKsEXcvVLaXRgF1
2026-03-21 06:42:43 +00:00
ibs0d
9cb7902a9d
Merge pull request #73 from ibs0d/claude/fix-zoom-statistics-WFvOm
Claude/fix zoom statistics w fv om
2026-03-19 14:57:22 +11:00
Claude
7ee24b7518
fix: motion dot outside zoom transform in LiveCameraView
Same pattern as DraggableGridLayout: render the dot outside
TransformComponent so it doesn't scale with pinch/zoom.
LivePlayer gets showMotionDot={false} to avoid duplicate.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-19 03:51:29 +00:00
ibs0d
e4e0ccc27c
Merge branch 'blakeblackshear:dev' into dev 2026-03-19 14:49:40 +11:00
ryzendigo
2ace8d3670
fix: preserve other cameras' volume when adjusting one (#22508)
setVolumeStates was replacing the entire state object instead of
merging, so changing one camera's volume reset all others to default.

Uses the functional update pattern to preserve existing state, matching
how toggleAudio already works.
2026-03-18 09:40:37 -05:00
Claude
f07636e7ec
fix: remove duplicate MdCircle and useCameraActivity imports
Rebase onto dev introduced duplicate imports since dev already had them.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-18 09:11:49 +00:00
ibs0d
301dfc185b
Revert "fix: motion dot outside zoom transform, fix activeMotion logic" 2026-03-18 20:08:04 +11:00
Claude
83be2800f9
fix: motion dot outside zoom transform, fix activeMotion logic
Two fixes:
1. useCameraActivity: replace broken ternary priority with OR — "OFF"
   (truthy string) was silently blocking camera_activity.motion fallback.
   Now: motion === true (from camera_activity) OR detectingMotion === "ON".
2. DraggableGridLayout: render CameraMotionDot outside the zoom transform
   div so the dot doesn't scale with camera zoom. LivePlayer gets
   showMotionDot={false} to avoid duplicate rendering.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-18 08:32:56 +00:00
ibs0d
fa05857976
Revert "fix: revert CameraMotionDot, restore built-in LivePlayer motion dot" 2026-03-18 19:12:01 +11:00
Claude
76652861b5
fix: revert CameraMotionDot, restore built-in LivePlayer motion dot
Remove the external CameraMotionDot component and showMotionDot={false}
override. The ws.ts fix (camera_activity -> motion topic sync) ensures
useCameraActivity gets fresh data, so the built-in dot in LivePlayer works.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-18 07:39:00 +00:00
Claude
621f484b92
fix: accept boolean | undefined for CameraMotionDot autoLive prop
autoLive ?? globalAutoLive can be undefined when useUserPersistence
hasn't hydrated yet. Change the prop type to optional boolean and
treat undefined as the default-true value (show dot unless explicitly
set to false via no-streaming mode).

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-17 07:39:59 +00:00
Claude
f9885df0e4
fix: replace callback motion dot with direct WS subscription component
The previous approach (useEffect → onActiveMotionChange callback →
parent state update) was unreliable: the dot only appeared if motion
was active at the moment of initial mount but did not react to
subsequent WS motion events.

Root cause: the intermediate state chain breaks because React's
useEffect batching and component re-render timing can cause the
parent state to lag behind or miss updates when motion changes after
mount.

Fix: replace the mechanism entirely with a dedicated CameraMotionDot
component that calls useCameraActivity directly. Being a proper React
component it subscribes to the {camera}/motion WS topic via
useSyncExternalStore and re-renders immediately and reliably whenever
motion state changes — no intermediate callbacks or parent state needed.

- Remove onActiveMotionChange prop from LivePlayer; add showMotionDot
  boolean prop (default true) to suppress the internal dot in grid view
- Remove cameraMotionStates state and setCameraMotionStates from
  DraggableGridLayout
- Add CameraMotionDot component with direct useCameraActivity hook

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-17 03:10:46 +00:00
Claude
9307272007
fix: exclude stats, spinner and motion dot from camera zoom transform
Move PlayerStats, ActivityIndicator and motion dot rendering outside the
zoom transform div in DraggableGridLayout so they are not scaled when
the user zooms with Shift+Wheel.

- Add onStatsUpdate, onLoadingChange, onActiveMotionChange callback props
  to LivePlayer; when provided, suppress the internal overlay elements
  and bubble state up to the parent instead
- In DraggableGridLayout, maintain per-camera overlay states and render
  the three overlays as siblings to the zoom div (inside the clipping
  viewport) so they remain at natural size regardless of zoom level

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-16 09:08:43 +00:00
Claude
61d399db05
Remove rounded corners from grid camera cards
https://claude.ai/code/session_01THf2SuS7hLt9NgstxvKdg8
2026-03-16 08:05:51 +00:00
Claude
b22dc4c946
Fix unused marginValue variable after removing grid spacing
Removed marginValue state and useLayoutEffect that calculated font size,
and removed unused useLayoutEffect import.

https://claude.ai/code/session_01THf2SuS7hLt9NgstxvKdg8
2026-03-16 07:36:46 +00:00
Claude
b7b5b08d53
Remove grid spacing between cards and from edges
Set margin and containerPadding to [0,0] in ResponsiveGridLayout,
removed px-2/my-2/pb-8 from the wrapper div, and updated cellHeight
formula to not account for margins.

https://claude.ai/code/session_01THf2SuS7hLt9NgstxvKdg8
2026-03-16 07:15:11 +00:00
Claude
710dec00fe
fix: use callback ref to measure container width after skeleton unmounts
useLayoutEffect with [] deps only ran on the initial render when
gridContainerRef was null (grid div was hidden behind skeleton).
After skeleton disappeared the div mounted but useLayoutEffect never
re-ran, leaving containerWidth=0 and Responsive invisible (blank screen).

A callback ref fires every time the element mounts, so containerWidth
is always set immediately when the grid div first appears.
2026-03-16 04:12:48 +00:00
Claude
067fdb50e1
fix: replace useResizeObserver with useLayoutEffect for reliable container width
useResizeObserver reads ref.current during render (before commit), so on
first render ref.current is null, no observation starts, and containerWidth
stays 0 if no subsequent re-render happens (e.g. page refresh with cached
SWR data). useLayoutEffect runs after refs are committed, so ref.current
is always the real DOM element.

This fixes both the right-column overflow (no window.innerWidth fallback
needed — width is always the actual container width) and the black screen
on refresh (containerWidth is reliable before the first paint).

https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd
2026-03-15 12:03:54 +00:00
Claude
5e40dbbcd2
fix: reliably init grid width on page refresh using useLayoutEffect
useResizeObserver reads ref.current at render time; on page refresh with
fast SWR cache, no re-render occurs after mount so ref.current remains null
in the effect, observation never starts, and containerWidth stays 0 forever.

Add a useLayoutEffect that measures offsetWidth synchronously before paint
as a seed value (effectiveWidth = containerWidth || initialWidth). Once
ResizeObserver fires normally, containerWidth takes over. The Responsive
grid is gated on effectiveWidth > 0 so it always renders correctly on both
first load and refresh.

https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd
2026-03-15 11:32:13 +00:00
Claude
d39590604f
fix: prevent grid right-edge overflow by gating Responsive on containerWidth
Gate <Responsive> rendering on containerWidth > 0 so it only mounts after
ResizeObserver has measured the container. Use availableWidth directly as
the width prop (no window.innerWidth fallback) since the component now only
renders when containerWidth is known. This prevents the grid from rendering
wider than its container (which caused the rightmost column to overflow the
right edge).

https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd
2026-03-15 11:03:26 +00:00
Claude
84f3b16461
Fix DraggableGridLayout initial height collapse due to nullish coalescing bug
availableWidth starts at 0 (not null/undefined) before ResizeObserver fires.
The ?? operator passes 0 through instead of falling back to window.innerWidth,
making cellHeight negative and causing react-grid-layout to render a ~10px
container. The overflow-x-hidden div then becomes an implicit scroll container,
producing the 'cards squeezed in a small rectangle' symptom.

Changing ?? to || makes 0 trigger the window.innerWidth fallback, giving a
reasonable initial rowHeight until the real container width is measured.

https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd
2026-03-15 10:15:52 +00:00
ibs0d
86de033f74
Merge branch 'blakeblackshear:dev' into dev 2026-03-15 13:55:05 +11:00
ibs0d
0c78103e56
Revert "Claude/fix video stretch history ans qf" 2026-03-15 13:38:11 +11:00
Claude
93a5f2691c
Use fill object-fit in draggable grid, contain elsewhere
Grid tiles explicitly set --frigate-mse-object-fit:fill so video stretches
to fill the card without preserving aspect ratio. The MsePlayer default
is contain, so History preview and all other contexts keep correct proportions.

https://claude.ai/code/session_01EwdaKGsrRLZ74smmCQ1MgW
2026-03-13 11:24:08 +00:00
ibs0d
1e238d2fe2
Revert "Expose mediaContentStyle on LivePlayer and pass through to MSEPlayer and still image" 2026-03-09 15:34:41 +11:00
ibs0d
078b6f62a7 Revert non-MSE style props from live players 2026-03-09 15:07:43 +11:00
ibs0d
626c05179e Restore live grid card zoom at grid layout layer 2026-03-09 14:15:46 +11:00
ibs0d
171688445d Revert LivePlayer transform wrapper regression 2026-03-09 13:57:16 +11:00
ibs0d
534b6ced19 Fix passive wheel listener for live grid card zoom 2026-03-09 12:47:39 +11:00
ibs0d
6940713054 Fix grid live zoom so overlays stay anchored 2026-03-09 12:14:05 +11:00
ibs0d
7969930d21 Persist per-camera live grid zoom state 2026-03-09 11:37:28 +11:00
ibs0d
bf622d5ff7 Add independent shift+wheel zoom for draggable live cards 2026-03-09 10:48:58 +11:00
ibs0d
1820f222ef
Revert "Add live zoom config and helpers for player/debug modes" 2026-03-09 10:16:46 +11:00
ibs0d
6ddfbf3d46
Revert "refactor(live): use shared live-zoom helpers in LiveCameraView" 2026-03-09 10:16:17 +11:00
ibs0d
71327cd94a
Revert "Add cursor-relative shift-wheel zoom and refactor live zoom utilities" 2026-03-09 10:15:43 +11:00
ibs0d
de9878f5fd Clamp live zoom scale and align wheel coordinates to transform viewport 2026-03-08 23:48:37 +11:00
ibs0d
c9283f5990 refactor: use shared live zoom helpers in camera view 2026-03-08 23:31:00 +11:00
ibs0d
36e9c63e91 refactor(web): trim live zoom module to shared primitives only 2026-03-08 23:21:42 +11:00
ibs0d
e398ec5f03 Fix rotated MSE sizing in draggable live grid 2026-03-08 22:18:25 +11:00
ibs0d
3a0d71c47b
Update DraggableGridLayout.tsx 2026-03-08 21:25:46 +11:00