Skip to content

[7418] VideoPicker control#4817

Draft
jvega190 wants to merge 8 commits into
craftercms:developfrom
jvega190:feature/7418-video-picker
Draft

[7418] VideoPicker control#4817
jvega190 wants to merge 8 commits into
craftercms:developfrom
jvega190:feature/7418-video-picker

Conversation

@jvega190

@jvega190 jvega190 commented Feb 24, 2026

Copy link
Copy Markdown
Member

craftercms/craftercms#7418

Summary by CodeRabbit

  • New Features
    • Added a video picker control for forms with a preview card showing video metadata (dimensions, duration, file size, content type).
    • Actions: Replace (Browse/Upload menu), Download, and Delete; Replace/Delete are disabled in read-only mode.
    • Displays loading skeletons, distinct errors for metadata/dimensions, and localized UI strings; shows placeholder/options when no video is selected.

Review Change Stack

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new VideoPicker form control (lazy-registered) and a useVideoInfo hook that loads video dimensions/duration via a video element and fetches metadata (Content-Type, size) via HEAD requests.

Changes

Cohort / File(s) Summary
VideoPicker Component
ui/app/src/components/FormsEngine/controls/VideoPicker.tsx
New React control exposing VideoPickerProps and VideoPicker. Renders preview or placeholder, shows metadata (type, size, dimensions), provides Replace/Download/Delete actions and a datasource menu; handles loading/error states.
Control Map Registration
ui/app/src/components/FormsEngine/lib/controlMap.ts
Replaces null entry with lazy import: 'video-picker': lazy(() => import('../controls/VideoPicker')).
Video Metadata Hook
ui/app/src/hooks/useVideoInfo.ts
New useVideoInfo(url) hook and VideoInfo type. Loads dimensions/duration via a created video element and fetches Content-Type/Content-Length via HEAD; exposes separate loading/error flags and cleans up on change/unmount.

Sequence Diagram

sequenceDiagram
    participant User
    participant VideoPicker
    participant useVideoInfo
    participant VideoElement
    participant Server

    User->>VideoPicker: Mount with video URL
    VideoPicker->>useVideoInfo: request info(url)
    
    par fetch dimensions
        useVideoInfo->>VideoElement: create & set src
        VideoElement-->>useVideoInfo: onloadedmetadata (width,height,duration)
    and fetch metadata
        useVideoInfo->>Server: HEAD request for headers
        Server-->>useVideoInfo: Content-Type, Content-Length
    end

    useVideoInfo-->>VideoPicker: return videoInfo + loading/error states
    VideoPicker->>User: render preview/controls

    User->>VideoPicker: Click Replace
    VideoPicker->>User: show datasource menu

    User->>VideoPicker: Click Delete
    VideoPicker->>VideoPicker: clear value, reset state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description only contains a link to the GitHub issue without additional context or explanation matching the repository template. Expand the description to include a ticket reference section explaining what the PR accomplishes, beyond just the issue link.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly references the issue number and accurately describes the primary change: adding a VideoPicker control component.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
ui/app/src/components/FormsEngine/controls/VideoPicker.tsx (3)

65-67: anchorEl retains a stale DOM reference after the menu closes.

setAddMenuOpen(false) on line 138 hides the menu, but anchorEl still holds the previous button element. The standard MUI Menu pattern is to drive open state from anchorEl alone (i.e. open={Boolean(anchorEl)}), or at minimum clear anchorEl on close. Also, addMenuButtonRef (line 66) is declared but only assigned — it's never read.

Simplify by driving menu state from anchorEl only and drop unused ref
- const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
- const addMenuButtonRef = useRef<HTMLButtonElement>(undefined);
- const [addMenuOpen, setAddMenuOpen] = useState(false);
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);

  ...

  <Menu
    anchorEl={anchorEl}
-   open={addMenuOpen}
-   onClose={() => setAddMenuOpen(false)}
+   open={Boolean(anchorEl)}
+   onClose={() => setAnchorEl(null)}
    ...
  >

  ...

  <IconButton
    ...
-   ref={addMenuButtonRef}
    onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
      setAnchorEl(event.currentTarget);
-     setAddMenuOpen(true);
    }}
  >

Also applies to: 135-144

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx` around lines 65 -
67, The menu open state is kept separately and anchorEl is never cleared,
leaving a stale DOM reference; remove the unused addMenuButtonRef, stop using
addMenuOpen, and drive the MUI Menu open state from anchorEl (i.e.
open={Boolean(anchorEl)}), setting anchorEl via setAnchorEl when opening and
calling setAnchorEl(null) on close (replace uses of setAddMenuOpen(false) with
setAnchorEl(null)); update any handlers that referenced addMenuOpen or
addMenuButtonRef to use anchorEl instead.

73-127: Storing JSX elements (with event handlers) inside useMemo can cause subtle issues.

The menuOptions array bakes JSX with a no-op handleDataSourceOptionClick into the memoized value. When the actual handler is implemented, it will likely need to reference state or callbacks that aren't in the dependency array, causing stale closures. Consider memoizing only the data (paths, types) and rendering the JSX in the return block instead. This also avoids the children prop lint issue on the <Box> at line 227.

Also, allowedBrowsePaths and allowedUploadPaths are returned from the memo but never consumed outside of this memo — they can be dropped from the return value unless they're planned for future use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx` around lines 73 -
127, The useMemo in VideoPicker currently returns JSX (menuOptions) that closes
over a noop handleDataSourceOptionClick and may produce stale closures; change
useMemo to only compute and return plain data: dataSourceIds,
allowedBrowsePaths, allowedUploadPaths (based on field, contentType,
contentTypes) and remove menuOptions from the memo return; create the actual
handleDataSourceOptionClick and build the MenuItem JSX in the component
render/return block (not inside useMemo) so the handler can reference current
state/props and be included in its dependency list; also drop
allowedBrowsePaths/allowedUploadPaths from the memo return if they are unused
elsewhere, or keep them as plain data-only values used by the render logic.

107-107: Avoid passing children as a prop (flagged by Biome).

Lines 107, 117, and 227 use children={...} as a prop attribute. This is flagged by Biome's noChildrenProp rule. Consider nesting the children instead.

Proposed fix (example for lines 107 and 117)
-  <ListItemText children={<FormattedMessage defaultMessage="Browse" />} />
+  <ListItemText><FormattedMessage defaultMessage="Browse" /></ListItemText>
-  <ListItemText children={<FormattedMessage defaultMessage="Upload" />} />
+  <ListItemText><FormattedMessage defaultMessage="Upload" /></ListItemText>

For line 227, similarly nest menuOptions as a child of <Box>:

-  <Box
-    children={menuOptions}
-    sx={{...}}
-  />
+  <Box sx={{...}}>
+    {menuOptions}
+  </Box>

Also applies to: 117-117, 227-227

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx` at line 107, In
VideoPicker.tsx replace any usage of the children prop (e.g., ListItemText
children={<FormattedMessage .../>} at the ListItemText occurrences and Box
children={menuOptions}) with nested JSX: put the <FormattedMessage .../> as the
inner content of <ListItemText> and place the menuOptions value as the child
inside <Box>, removing the children={...} prop; update all three flagged
occurrences (ListItemText instances and the Box with menuOptions) so the
components receive their children via nesting rather than the children prop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx`:
- Line 168: The display suffix uses "Kb" which denotes kilobits; update the
string in VideoPicker.tsx where videoInfo?.size is rendered (the expression
using videoInfo.size) to use "KB" instead of "Kb" so it correctly shows
kilobytes; locate the rendering expression referencing videoInfo.size in the
VideoPicker component and replace the suffix accordingly.
- Around line 162-163: The error message in the VideoPicker control is a
copy-paste typo: change the FormattedMessage defaultMessage that currently reads
"Error loading image metadata" to "Error loading video metadata" in the
VideoPicker component (look for the Typography/FormattedMessage block inside
VideoPicker.tsx) so the copy reflects the control's media type.

In `@ui/app/src/hooks/useVideoInfo.ts`:
- Around line 53-95: The effect in useVideoInfo creates a video element and
starts a HEAD fetch but lacks cleanup and doesn't clear previous videoInfo when
url changes; fix by: when starting a new url, immediately reset merged state via
setVideoInfo(videoInfoInitialState) and
setIsFetchingDimensions/Metadata(true)/errors(null); create an AbortController
for the HEAD fetch and use it in fetch, and store both the video element and
controller in the effect scope so the cleanup function can abort the fetch,
remove/clear video event handlers (video.onloadedmetadata = null; video.onerror
= null), pause and remove the video (video.pause(); video.src = '';) to stop
loading, and ensure no stale callbacks call set* after cleanup; apply these
changes inside the effect that currently references video, fetch, setVideoInfo,
setIsFetchingDimensions, setIsFetchingMetadata, setErrorDimensions,
setErrorMetadata.

---

Nitpick comments:
In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx`:
- Around line 65-67: The menu open state is kept separately and anchorEl is
never cleared, leaving a stale DOM reference; remove the unused
addMenuButtonRef, stop using addMenuOpen, and drive the MUI Menu open state from
anchorEl (i.e. open={Boolean(anchorEl)}), setting anchorEl via setAnchorEl when
opening and calling setAnchorEl(null) on close (replace uses of
setAddMenuOpen(false) with setAnchorEl(null)); update any handlers that
referenced addMenuOpen or addMenuButtonRef to use anchorEl instead.
- Around line 73-127: The useMemo in VideoPicker currently returns JSX
(menuOptions) that closes over a noop handleDataSourceOptionClick and may
produce stale closures; change useMemo to only compute and return plain data:
dataSourceIds, allowedBrowsePaths, allowedUploadPaths (based on field,
contentType, contentTypes) and remove menuOptions from the memo return; create
the actual handleDataSourceOptionClick and build the MenuItem JSX in the
component render/return block (not inside useMemo) so the handler can reference
current state/props and be included in its dependency list; also drop
allowedBrowsePaths/allowedUploadPaths from the memo return if they are unused
elsewhere, or keep them as plain data-only values used by the render logic.
- Line 107: In VideoPicker.tsx replace any usage of the children prop (e.g.,
ListItemText children={<FormattedMessage .../>} at the ListItemText occurrences
and Box children={menuOptions}) with nested JSX: put the <FormattedMessage .../>
as the inner content of <ListItemText> and place the menuOptions value as the
child inside <Box>, removing the children={...} prop; update all three flagged
occurrences (ListItemText instances and the Box with menuOptions) so the
components receive their children via nesting rather than the children prop.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d569411 and cb68c60.

📒 Files selected for processing (3)
  • ui/app/src/components/FormsEngine/controls/VideoPicker.tsx
  • ui/app/src/components/FormsEngine/lib/controlMap.ts
  • ui/app/src/hooks/useVideoInfo.ts

Comment thread ui/app/src/components/FormsEngine/controls/VideoPicker.tsx Outdated
Comment thread ui/app/src/components/FormsEngine/controls/VideoPicker.tsx Outdated
Comment thread ui/app/src/hooks/useVideoInfo.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
ui/app/src/components/FormsEngine/controls/VideoPicker.tsx (1)

103-118: Avoid passing children via prop (Biome noChildrenProp).
This is flagged by the linter and is less idiomatic in React.

♻️ Suggested refactor
-					<ListItemText children={<FormattedMessage defaultMessage="Browse" />} />
+					<ListItemText>
+						<FormattedMessage defaultMessage="Browse" />
+					</ListItemText>
...
-					<ListItemText children={<FormattedMessage defaultMessage="Upload" />} />
+					<ListItemText>
+						<FormattedMessage defaultMessage="Upload" />
+					</ListItemText>
...
-					<Box
-						children={menuOptions}
-						sx={{
+					<Box
+						sx={{
 							p: 1,
 							gap: 1,
 							py: 0.5,
 							display: 'flex',
 							flexDirection: 'row',
 							flexWrap: 'wrap',
 							color: 'primary.main',
 							justifyContent: 'center',
 							[`.${svgIconClasses.root}`]: {
 								color: 'primary.main'
 							},
 							[`.${menuItemClasses.root}`]: {
 								flexDirection: 'column',
 								justifyContent: 'center',
 								borderRadius: 1
 							},
 							[`.${listItemIconClasses.root}`]: {
 								justifyContent: 'center'
 							}
 						}}
-					/>
+					>
+						{menuOptions}
+					</Box>

Also applies to: 225-249

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx` around lines 103
- 118, The linter flags passing children via the children prop on ListItemText
in VideoPicker.tsx; change usages (e.g., the ListItemText inside the MenuItem
blocks that call handleDataSourceOptionClick and the other occurrences around
lines 225-249) to use normal JSX children instead of the children prop (move
<FormattedMessage defaultMessage="Browse" /> and <FormattedMessage
defaultMessage="Upload" /> between the ListItemText opening and closing tags);
ensure all ListItemText components in this file are updated consistently so no
component receives a children prop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx`:
- Around line 73-95: The code in useMemo assumes contentTypes[contentType.id]
exists and will crash if it's undefined; update the useMemo callback (the block
that computes menuOptions, allowedBrowsePaths, allowedUploadPaths) to first
check whether contentTypes and contentTypes[contentType.id] are defined and
return default empty arrays/values when not ready, e.g., bail out early before
iterating over contentTypes[contentType.id].dataSources; adjust references to
contentTypes[contentType.id].dataSources inside that function (the same useMemo)
so they only run after the guard.
- Around line 98-114: The Browse/Upload menu items call
handleDataSourceOptionClick but that function is empty, leaving clickable
options that do nothing; implement handleDataSourceOptionClick to perform the
intended action (e.g., dispatch the data-source flow by invoking the existing
picker/open handler in this component such as openPicker, setPickerType, or
props.onOpenDataSource with the PickerType argument), or if that flow isn't
ready, remove the onClick and mark the MenuItem disabled or hide the options by
gating them off (use allowedBrowsePaths/allowedUploadPaths plus a
"datasourceReady" flag) so users can't click inert items; update the handler
referenced by handleDataSourceOptionClick and the MenuItem onClick usages
accordingly to restore expected behavior.
- Line 148: In VideoPicker, the preview URL is built by raw concatenation
(CardMedia component using image={`${guestBase}${value}`}) which can create
double slashes; update the preview construction to use the same normalization
helper as metadata (call ensureSingleSlash(guestBase, value) or equivalent) and
pass the normalized URL to CardMedia so both metadata and preview use the same
single-slash-normalized path.

In `@ui/app/src/hooks/useVideoInfo.ts`:
- Around line 83-94: In useVideoInfo, the HEAD fetch currently treats any HTTP
status as success; update the logic after awaiting fetch(url) to check
response.ok and handle non-OK responses by calling setErrorMetadata (or
throwing) and setting setIsFetchingMetadata(false) to avoid silent success;
additionally guard Content-Length parsing by verifying contentLength is a
numeric string before Number(...) and only compute sizeKb when valid, then call
setVideoInfo({ contentType, size: sizeKb }) for valid responses and ensure error
state is set for non-ok or invalid-length cases (refer to response, contentType,
contentLength, sizeKb, setVideoInfo, setErrorMetadata, setIsFetchingMetadata in
useVideoInfo).

---

Nitpick comments:
In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx`:
- Around line 103-118: The linter flags passing children via the children prop
on ListItemText in VideoPicker.tsx; change usages (e.g., the ListItemText inside
the MenuItem blocks that call handleDataSourceOptionClick and the other
occurrences around lines 225-249) to use normal JSX children instead of the
children prop (move <FormattedMessage defaultMessage="Browse" /> and
<FormattedMessage defaultMessage="Upload" /> between the ListItemText opening
and closing tags); ensure all ListItemText components in this file are updated
consistently so no component receives a children prop.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb68c60 and 1bee622.

📒 Files selected for processing (2)
  • ui/app/src/components/FormsEngine/controls/VideoPicker.tsx
  • ui/app/src/hooks/useVideoInfo.ts

Comment thread ui/app/src/components/FormsEngine/controls/VideoPicker.tsx
Comment on lines +98 to +114
const menuOptions = [];
// TODO: pending when datasources implementation is ready.
const handleDataSourceOptionClick = (event: ReactMouseEvent<HTMLLIElement, MouseEvent>, option: PickerType) => {};
if (allowedBrowsePaths.length > 0) {
menuOptions.push(
<MenuItem key="search" onClick={(event) => handleDataSourceOptionClick(event, 'browse')}>
<ListItemIcon sx={{ mr: 0 }}>
<TravelExploreOutlined fontSize="small" />
</ListItemIcon>
<ListItemText children={<FormattedMessage defaultMessage="Browse" />} />
</MenuItem>
);
}
if (allowedUploadPaths.length > 0) {
menuOptions.push(
<MenuItem key="upload" onClick={(event) => handleDataSourceOptionClick(event, 'upload')}>
<ListItemIcon sx={{ mr: 0 }}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Browse/Upload menu actions are no-ops.
handleDataSourceOptionClick is empty, so menu items appear clickable but do nothing. Either implement the handlers or temporarily disable/hide the options until datasource support is ready.

🧰 Tools
🪛 Biome (2.4.4)

[error] 107-107: Avoid passing children using a prop

(lint/correctness/noChildrenProp)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/FormsEngine/controls/VideoPicker.tsx` around lines 98 -
114, The Browse/Upload menu items call handleDataSourceOptionClick but that
function is empty, leaving clickable options that do nothing; implement
handleDataSourceOptionClick to perform the intended action (e.g., dispatch the
data-source flow by invoking the existing picker/open handler in this component
such as openPicker, setPickerType, or props.onOpenDataSource with the PickerType
argument), or if that flow isn't ready, remove the onClick and mark the MenuItem
disabled or hide the options by gating them off (use
allowedBrowsePaths/allowedUploadPaths plus a "datasourceReady" flag) so users
can't click inert items; update the handler referenced by
handleDataSourceOptionClick and the MenuItem onClick usages accordingly to
restore expected behavior.

Comment thread ui/app/src/components/FormsEngine/controls/VideoPicker.tsx Outdated
Comment thread ui/app/src/hooks/useVideoInfo.ts
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":401,"request":{"method":"PATCH","url":"https://api.github.com/repos/craftercms/studio-ui/issues/comments/3953570895","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\nNo actionable comments were generated in the recent review. 🎉\n\n<details>\n<summary>ℹ️ Recent review info</summary>\n\n<details>\n<summary>⚙️ Run configuration</summary>\n\n**Configuration used**: Path: .coderabbit.yaml\n\n**Review profile**: CHILL\n\n**Plan**: Pro\n\n**Run ID**: `6ca3daae-9ac7-42f0-a842-183536325f1a`\n\n</details>\n\n<details>\n<summary>📥 Commits</summary>\n\nReviewing files that changed from the base of the PR and between 1bee6222f53bce9255d5f8d96d1ef7a3eb7faf24 and c523845ac3cc2b1f1703805a7c5b2197d30801b3.\n\n</details>\n\n<details>\n<summary>📒 Files selected for processing (3)</summary>\n\n* `ui/app/src/components/FormsEngine/controls/VideoPicker.tsx`\n* `ui/app/src/components/FormsEngine/lib/controlMap.ts`\n* `ui/app/src/hooks/useVideoInfo.ts`\n\n</details>\n\n<details>\n<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>\n\n* ui/app/src/components/FormsEngine/lib/controlMap.ts\n* ui/app/src/components/FormsEngine/controls/VideoPicker.tsx\n\n</details>\n\n</details>\n\n---\n<!-- walkthrough_start -->\n\n## Walkthrough\n\nAdds a new VideoPicker form control (lazy-registered) and a useVideoInfo hook that loads video dimensions/duration via a video element and fetches metadata (Content-Type, size) via HEAD requests.\n\n## Changes\n\n**VideoPicker Feature**\n\n|Layer / File(s)|Summary|\n|---|---|\n|**useVideoInfo hook** <br> `ui/app/src/hooks/useVideoInfo.ts`|Adds `VideoInfo` type, `videoInfoInitialState`, and `useVideoInfo(url)` which loads dimensions via an in-memory video element and metadata via HEAD; exposes separate loading/error flags and cleans up on change/unmount.|\n|**VideoPicker component UI** <br> `ui/app/src/components/FormsEngine/controls/VideoPicker.tsx`|New `VideoPickerProps` and `VideoPicker` component. Uses `useVideoInfo` to show video preview card (type, size, width×height), Replace/Download/Delete actions (Replace shows a datasource menu; menu handlers are currently stubs), and placeholder state when no value present.|\n|**Control Map Registration** <br> `ui/app/src/components/FormsEngine/lib/controlMap.ts`|Replaces `null` with `lazy(() => import('../controls/VideoPicker'))` for `controlMap['video-picker']`.|\n\n## Sequence Diagram\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant VideoPicker\n    participant useVideoInfo\n    participant VideoElement\n    participant Server\n\n    User->>VideoPicker: Mount with video URL\n    VideoPicker->>useVideoInfo: request info(url)\n    \n    par fetch dimensions\n        useVideoInfo->>VideoElement: create & set src\n        VideoElement-->>useVideoInfo: onloadedmetadata (width,height,duration)\n    and fetch metadata\n        useVideoInfo->>Server: HEAD request for headers\n        Server-->>useVideoInfo: Content-Type, Content-Length\n    end\n\n    useVideoInfo-->>VideoPicker: return videoInfo + loading/error states\n    VideoPicker->>User: render preview/controls\n\n    User->>VideoPicker: Click Replace\n    VideoPicker->>User: show datasource menu\n\n    User->>VideoPicker: Click Delete\n    VideoPicker->>VideoPicker: clear value, reset state\n```\n\n## Estimated code review effort\n\n🎯 3 (Moderate) | ⏱️ ~25 minutes\n\n## Suggested reviewers\n\n- rart\n\n<!-- walkthrough_end -->\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 3 | ❌ 2</summary>\n\n### ❌ Failed checks (2 warnings)\n\n|     Check name     | Status     | Explanation                                                                                                                                    | Resolution                                                                                                                                       |\n| :----------------: | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |\n|  Description check | ⚠️ Warning | The description only provides an issue link without following the required template structure, offering insufficient detail about the changes. | Expand the description to follow the template: add a ticket reference section and provide a brief explanation of what the PR implements and why. |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.                                                          | Write docstrings for the functions missing them to satisfy the coverage threshold.                                                               |\n\n<details>\n<summary>✅ Passed checks (3 passed)</summary>\n\n|         Check name         | Status   | Explanation                                                                                                                                    |\n| :------------------------: | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |\n|         Title check        | ✅ Passed | The title '[7418] VideoPicker control' clearly describes the main change—introducing a new VideoPicker control component for the Forms Engine. |\n|     Linked Issues check    | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                       |\n| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                       |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=craftercms/studio-ui&utm_content=4817)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"signal":{}}},"response":{"url":"https://api.github.com/repos/craftercms/studio-ui/issues/comments/3953570895","status":401,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","connection":"close","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Wed, 27 May 2026 15:05:04 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","vary":"Accept-Encoding, Accept, X-Requested-With","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"3C65:2D1525:A4715FB:27EBF8B6:6A17081F","x-xss-protection":"0"},"data":{"message":"Requires authentication","documentation_url":"https://docs.github.com/rest","status":"401"}}}

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.

1 participant