Skip to content

Zoom and Pan UX Improvements on Mobile in History Viewer#22638

Open
nrlcode wants to merge 2 commits intoblakeblackshear:devfrom
nrlcode:history-pr-clean
Open

Zoom and Pan UX Improvements on Mobile in History Viewer#22638
nrlcode wants to merge 2 commits intoblakeblackshear:devfrom
nrlcode:history-pr-clean

Conversation

@nrlcode
Copy link
Copy Markdown

@nrlcode nrlcode commented Mar 25, 2026

Please read the contributing guidelines before submitting a PR.

Proposed change

This PR improves History/Recording viewing UX on mobile (portrait), especially for wide-aspect cameras.

  • Adds a mobile portrait split control so users can drag the divider between video and timeline.
  • Updates zoom behavior so zoom first consumes newly available layout space before normal pan/zoom behavior.
  • Keeps changes scoped to History player behavior and UI.

This makes timeline review and zoomed inspection more practical on phones without requiring fullscreen-only workflows.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New feature
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code
  • Documentation Update

Additional information

For new features

AI disclosure

  • No AI tools were used in this PR.
  • AI tools were used in this PR. Details below:

AI tool(s) used (e.g., Claude, Copilot, ChatGPT, Cursor):
ChatGPT/Codex

How AI was used (e.g., code generation, code review, debugging, documentation):
Used for implementation support, refactoring cleanup, and debugging targeted History UI behavior.

Extent of AI involvement (e.g., generated entire implementation, assisted with specific functions, suggested fixes):
AI assisted with specific components and targeted fixes; final scope decisions, validation, and merge selection were directed by me.

Human oversight: Describe what manual review, testing, and validation you performed on the AI-generated portions.
I manually reviewed changed files, validated branch scope before merge selection, and ran local frontend checks (eslint, tsc --noEmit, and test/build commands used during iteration). I also created docker containers from the changes and validated that the new features exhibit the proper behavior on both mobile and desktop.

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I can explain every line of code in this PR if asked.
  • UI changes including text have used i18n keys and have been added to the en locale.
  • The code has been formatted using Ruff (ruff format frigate) [N/A]

Comment on lines +398 to +401
"size-full rounded-lg bg-black md:rounded-2xl",
loadedMetadata ? "" : "invisible",
"cursor-pointer",
videoClassName,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cursor pointer should just be joined here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. cursor-pointer is now joined into the base class string as requested.

videoClassName,
containerRef,
visible,
showControls = visible,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a good pattern, I would prefer this have a default value and the places where it is needed it can just be included in the existing visible logic.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the default coupling here.

  • showControls now defaults to true, and visibility composition is explicit:
    • show={visible && showControls && (controls || controlsOpen)}

videoClassName={videoClassName}
containerRef={containerRef}
visible={!(isScrubbing || isLoading)}
visible={true}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we changing this to always be visible?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept visible={true} intentionally in the this update to preserve transform state while scrubbing/loading. Toggling visible to false hides the transformed wrapper (display:none), which can reflow/reclamp pan at high zoom and cause jumpy position after scrubbing. It also makes the scrubbing behavior show the full camera and not the zoomed in portion, if the user is zoomed in.

  • Controls are still hidden during scrub/loading via:
    • showControls={!isScrubbing && !isLoading}

const previewPlayer = (
<PreviewPlayer
className={cn(
source ? "pointer-events-none absolute inset-0 z-20" : className,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this className change with source? This seems like an odd change. You also have !source below so this is incorrect logic. I don't think this should be changing in this way.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed preview rendering to use explicit class paths:

  • transformed overlay path when source exists
  • simple visible/hidden path when source is missing
  • This keeps class logic clear and avoids contradictory conditions, while preserving scrub preview inside the transformed layer so zoom/pan framing remains stable.

@NickM-27
Copy link
Copy Markdown
Collaborator

NickM-27 commented Mar 25, 2026

I think we need to be more specific and only make changes that actually pertain to this PR. Upon loading this code on desktop I see multiple problems:

  1. when the video first loads it is very zoomed out (small video player about 100 pixels wide with a loading indicator).
  2. once the video starts playing the control bar is showing on top of the preview which should not be possible. I can enable audio so I know the video is playing behind the preview.

These are the types of changes that are fairly complicated, and why only the minimal changes for the PR goal should be implemented

@nrlcode
Copy link
Copy Markdown
Author

nrlcode commented Mar 25, 2026

I think we need to be more specific and only make changes that actually pertain to this PR. Upon loading this code on desktop I see multiple problems:

  1. when the video first loads it is very zoomed out (small video player about 100 pixels wide with a loading indicator).
  2. once the video starts playing the control bar is showing on top of the preview which should not be possible. I can enable audio so I know the video is playing behind the preview.

These are the types of changes that are fairly complicated, and why only the minimal changes for the PR goal should be implemented

Fair enough! I have been trying to scope the changes to only this PR, which is why I broke out the snapshot behavior from the previous one.

  1. I am not seeing the small video player of 100 pixels on desktop. What OS/Browser are you testing on? I do see on desktop that the initial camera loads very fast before the preview, then the preview flashes and you get the loading indicator, and then it goes back to camera. So something I need to look at.
  2. Can you clarify? I'm not seeing any sort of control bar loading on top of the preview?

Implementing some of the suggestions you provided seems to have broken things further. I'll step back and re-evaluate.

From a functionality standpoint, on mobile I was targeting:

  1. You can zoom in / out on mobile easily
  2. There is an adjustable slider between the video and timeline that allows you to increase the size of either.
  3. When scrubbing, the preview maintains the current zoom/position of the video, and when done scrubbing, the zoom/pan of where the video previously was is maintained.

If you think it should function differently I am open to suggestions. But keeping those layers in the same zoom/position/pan was difficult to lock down.

@NickM-27
Copy link
Copy Markdown
Collaborator

When scrubbing, the preview maintains the current zoom/position of the video, and when done scrubbing, the zoom/pan of where the video previously was is maintained.

this is not something that should be done in this PR, it is very complicated given the way these views different and have sizing constraints especially around hour transitions. This seems like something overlay complicated for AI to figure out and this PR should be considerably simpler without this

@nrlcode
Copy link
Copy Markdown
Author

nrlcode commented Mar 25, 2026

So I feel like it is relevant to this PR, because otherwise scrubbing brings up the entire preview. Would we expect that it just goes back to the original view once scrubbing is done, and the user will need to zoom/pan again?

I was trying to replicate the experience when viewing individual cameras live on mobile: how they move around, zoom, pan, etc...

@NickM-27
Copy link
Copy Markdown
Collaborator

This is exactly the way it works today, adding the ability to resize the mobile timeline / preview sections is not directly related to that behavior. It is in the same area but that is separate from the new functionality.

@nrlcode
Copy link
Copy Markdown
Author

nrlcode commented Mar 25, 2026

Ok I see that. I figured it was better to have the timeline itself respect the same zoom/panning that was chosen by the user when scrubbing, but I do see that the current functionality just zooms back out. The only issue I had there was that the camera was then not returning to where the user had zoomed/panned to. If I strip out a bunch of this other functionality I should be able to get back to replicating the current behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants