mirror of
https://github.com/zoriya/v10.git
synced 2026-08-15 10:23:32 +00:00
refactor(react): consolidate Video component into single module
- Merge connected, connected-with-defaults, and media-elements Video components - Create unified Video.tsx in main components directory alongside PlayButton/MuteButton - Preserve all existing functionality: media store connection, custom components, defaults - Maintain backwards compatibility with both Video and MediaElementVideo exports - Remove duplicate component directories and reduce architectural complexity - Simplify component structure while keeping TypeScript types and 'use client' directive This consolidation reduces the Video component from 3 separate files across 3 directories to a single, comprehensive module that's easier to understand and maintain. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
Christian Pillsbury
co-authored by
Claude
parent
e1d326ffaf
commit
a1de5ebae3
@@ -0,0 +1,125 @@
|
||||
'use client';
|
||||
import * as React from 'react';
|
||||
import { useMediaRef } from '@vjs-10/react-media-store';
|
||||
import { createMediaStateOwner } from '@vjs-10/media';
|
||||
import {
|
||||
ElementType,
|
||||
VideoHTMLAttributes,
|
||||
DetailedHTMLProps,
|
||||
PropsWithChildren,
|
||||
CSSProperties,
|
||||
Ref,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
export type MuxVideoProps = {
|
||||
'playback-id'?: string;
|
||||
};
|
||||
|
||||
type MediaStateOwner = NonNullable<Parameters<ReturnType<typeof useMediaRef>>[0]>;
|
||||
|
||||
/** @TODO Improve type inference and narrowing/widening for different use cases (CJP) */
|
||||
type ComponentType = ElementType<
|
||||
Omit<
|
||||
DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>,
|
||||
'ref'
|
||||
> & { ref: Ref<MediaStateOwner> }
|
||||
>;
|
||||
|
||||
// These are the first steps/WIP POC of decoupling the Media State Owner from the DOM.
|
||||
// Note that everything will still work if you use:
|
||||
// 1. an audio/video element directly
|
||||
// 2. a custom element a la media-elements
|
||||
type CreateMediaStateOwner = typeof createMediaStateOwner;
|
||||
const useMediaStateOwner = (
|
||||
ref: Ref<any>,
|
||||
createMediaStateOwner: CreateMediaStateOwner /*, props? */
|
||||
) => {
|
||||
const mediaStateOwnerRef = useRef(createMediaStateOwner(/* props? */));
|
||||
useImperativeHandle(ref, () => mediaStateOwnerRef.current, []);
|
||||
/** @TODO Parameterize this (CJP) */
|
||||
type ComponentProps = DetailedHTMLProps<
|
||||
VideoHTMLAttributes<HTMLVideoElement>,
|
||||
HTMLVideoElement
|
||||
>;
|
||||
return {
|
||||
updateMediaElement(
|
||||
mediaEl: HTMLMediaElement | null,
|
||||
props: ComponentProps
|
||||
) {
|
||||
// NOTE: The details here will almost definitely change for a less "bare bones"/"POC" implementation of Media State Owner impl. (CJP)
|
||||
mediaStateOwnerRef.current.mediaElement = mediaEl ?? undefined;
|
||||
mediaStateOwnerRef.current.src = props.src as string;
|
||||
if (props.muted) {
|
||||
mediaStateOwnerRef.current.muted = props.muted;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const DefaultVideoComponent: ElementType<
|
||||
Omit<
|
||||
DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>,
|
||||
'ref'
|
||||
> & { ref: Ref<any> }
|
||||
> = ({ children, ref, ...props }) => {
|
||||
const { updateMediaElement } = useMediaStateOwner(ref, createMediaStateOwner);
|
||||
return (
|
||||
<video {...props} ref={(mediaEl) => {
|
||||
/** @TODO In later iterations/non-POC, we should be able to have a function that can be used directly for the `ref` prop (CJP) */
|
||||
updateMediaElement(mediaEl, props);
|
||||
}}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description This is a "thin wrapper" around the media component whose primary responsibility is to wire up the element
|
||||
* to the <MediaProvider/>'s MediaStore for the media state.
|
||||
* @param props - Identical to both a <video/>'s props and the <Player/> props, with one addition that may be familiar to
|
||||
* MUI users: a `component` prop that allows you to use something other than the <video/> element under the hood.
|
||||
* @returns A media react component (e.g. <video/>), wired up as the media element.
|
||||
*/
|
||||
const ConnectedVideo = ({
|
||||
component,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<{
|
||||
component: ComponentType;
|
||||
className?: string | undefined;
|
||||
style?: CSSProperties | undefined;
|
||||
}>) => {
|
||||
const Component = component;
|
||||
const mediaRefCallback = useMediaRef();
|
||||
// NOTE: While this may feel like magic to folks, in the "default" use case, you can think of it as:
|
||||
// return (<video ref={mediaRefCallback} {...restProps} >{children}</video>);
|
||||
return (
|
||||
<Component {...props} ref={mediaRefCallback}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
// Main Video component with default component
|
||||
export const Video = ({
|
||||
component = DefaultVideoComponent,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<{
|
||||
component?: ComponentType;
|
||||
className?: string | undefined;
|
||||
style?: CSSProperties | undefined;
|
||||
}>) => {
|
||||
return (
|
||||
<ConnectedVideo {...props} component={component}>
|
||||
{children}
|
||||
</ConnectedVideo>
|
||||
);
|
||||
};
|
||||
|
||||
// MediaElementVideo export (same as Video but for backwards compatibility)
|
||||
export const MediaElementVideo = Video;
|
||||
|
||||
export default Video;
|
||||
@@ -1,87 +0,0 @@
|
||||
// NOTE: This is just a thin wrapper around the "skeletal" connected component that also applies a default (but overridable) definition of the
|
||||
// "BaseComponent" that defines the actual UI.
|
||||
// NOTE: Definitions like this should be able to be autogenerated via codegen, defined via a factory function (HoC or higher order component), or both.
|
||||
// import BaseComponent from '../ui/PlayerUI';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
DetailedHTMLProps,
|
||||
ElementType,
|
||||
Ref,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
VideoHTMLAttributes,
|
||||
} from 'react';
|
||||
/** @TODO Improve types crud (CJP) */
|
||||
import ConnectedComponent from '../connected/Video';
|
||||
import { createMediaStateOwner } from '@vjs-10/media';
|
||||
|
||||
// These are the first steps/WIP POC of decoupling the Media State Owner from the DOM.
|
||||
// Note that everything will still work if you use:
|
||||
// 1. an audio/video element directly
|
||||
// 2. a custom element a la media-elements
|
||||
type CreateMediaStateOwner = typeof createMediaStateOwner;
|
||||
const useMediaStateOwner = (
|
||||
ref: Ref<any>,
|
||||
createMediaStateOwner: CreateMediaStateOwner /*, props? */
|
||||
) => {
|
||||
const mediaStateOwnerRef = useRef(createMediaStateOwner(/* props? */));
|
||||
useImperativeHandle(ref, () => mediaStateOwnerRef.current, []);
|
||||
/** @TODO Parameterize this (CJP) */
|
||||
type ComponentProps = DetailedHTMLProps<
|
||||
VideoHTMLAttributes<HTMLVideoElement>,
|
||||
HTMLVideoElement
|
||||
>;
|
||||
return {
|
||||
updateMediaElement(
|
||||
mediaEl: HTMLMediaElement | null,
|
||||
props: ComponentProps
|
||||
) {
|
||||
// NOTE: The details here will almost definitely change for a less "bare bones"/"POC" implementation of Media State Owner impl. (CJP)
|
||||
mediaStateOwnerRef.current.mediaElement = mediaEl ?? undefined;
|
||||
mediaStateOwnerRef.current.src = props.src as string;
|
||||
if (props.muted) {
|
||||
mediaStateOwnerRef.current.muted = props.muted;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const BaseComponent: ElementType<
|
||||
Omit<
|
||||
DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>,
|
||||
'ref'
|
||||
> & { ref: Ref<any> }
|
||||
> = ({ children, ref, ...props }) => {
|
||||
const { updateMediaElement } = useMediaStateOwner(ref, createMediaStateOwner);
|
||||
return (
|
||||
<video {...props} ref={(mediaEl) => {
|
||||
/** @TODO In later iterations/non-POC, we should be able to have a function that can be used directly for the `ref` prop (CJP) */
|
||||
updateMediaElement(mediaEl, props);
|
||||
}}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
};
|
||||
|
||||
// NOTE: Assuming FC or "functional component" here for type expediency (though may be fine for our use cases).
|
||||
type ConnectedComponentProps = Parameters<typeof ConnectedComponent>[0];
|
||||
type ConnectedComponentReturnType = ReturnType<typeof ConnectedComponent>;
|
||||
type DefaultedProps = 'component';
|
||||
type ConnectedComponentWithDefaults = (
|
||||
props: Omit<ConnectedComponentProps, DefaultedProps> &
|
||||
Partial<Pick<ConnectedComponentProps, DefaultedProps>>,
|
||||
) => ConnectedComponentReturnType;
|
||||
|
||||
const Component: ConnectedComponentWithDefaults = ({
|
||||
component = BaseComponent,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<ConnectedComponent {...props} component={component}>
|
||||
{children}
|
||||
</ConnectedComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
@@ -1,54 +0,0 @@
|
||||
'use client';
|
||||
import * as React from 'react';
|
||||
import { useMediaRef } from '@vjs-10/react-media-store';
|
||||
import {
|
||||
ElementType,
|
||||
VideoHTMLAttributes,
|
||||
DetailedHTMLProps,
|
||||
PropsWithChildren,
|
||||
CSSProperties,
|
||||
Ref,
|
||||
} from 'react';
|
||||
|
||||
export type MuxVideoProps = {
|
||||
'playback-id'?: string;
|
||||
};
|
||||
|
||||
type MediaStateOwner = NonNullable<Parameters<ReturnType<typeof useMediaRef>>[0]>;
|
||||
|
||||
/** @TODO Improve type inference and narrowing/widening for different use cases (CJP) */
|
||||
type ComponentType = ElementType<
|
||||
Omit<
|
||||
DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>,
|
||||
'ref'
|
||||
> & { ref: Ref<MediaStateOwner> }
|
||||
>;
|
||||
|
||||
/**
|
||||
* @description This is a "thin wrapper" around the media component whose primary responsibility is to wire up the element
|
||||
* to the <MediaProvider/>'s MediaStore for the media state.
|
||||
* @param props - Identical to both a <video/>'s props and the <Player/> props, with one addition that may be familiar to
|
||||
* MUI users: a `component` prop that allows you to use something other than the <video/> element under the hood.
|
||||
* @returns A media react component (e.g. <video/>), wired up as the media element.
|
||||
*/
|
||||
const Video = ({
|
||||
component,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<{
|
||||
component: ComponentType;
|
||||
className?: string | undefined;
|
||||
style?: CSSProperties | undefined;
|
||||
}>) => {
|
||||
const Component = component;
|
||||
const mediaRefCallback = useMediaRef();
|
||||
// NOTE: While this may feel like magic to folks, in the "default" use case, you can think of it as:
|
||||
// return (<video ref={mediaRefCallback} {...restProps} >{children}</video>);
|
||||
return (
|
||||
<Component {...props} ref={mediaRefCallback}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
export default Video;
|
||||
@@ -1,87 +0,0 @@
|
||||
// NOTE: This is just a thin wrapper around the "skeletal" connected component that also applies a default (but overridable) definition of the
|
||||
// "BaseComponent" that defines the actual UI.
|
||||
// NOTE: Definitions like this should be able to be autogenerated via codegen, defined via a factory function (HoC or higher order component), or both.
|
||||
// import BaseComponent from '../ui/PlayerUI';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
DetailedHTMLProps,
|
||||
ElementType,
|
||||
Ref,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
VideoHTMLAttributes,
|
||||
} from 'react';
|
||||
/** @TODO Improve types crud (CJP) */
|
||||
import ConnectedComponent from '../connected/Video';
|
||||
import { createMediaStateOwner } from '@vjs-10/media';
|
||||
|
||||
// These are the first steps/WIP POC of decoupling the Media State Owner from the DOM.
|
||||
// Note that everything will still work if you use:
|
||||
// 1. an audio/video element directly
|
||||
// 2. a custom element a la media-elements
|
||||
type CreateMediaStateOwner = typeof createMediaStateOwner;
|
||||
const useMediaStateOwner = (
|
||||
ref: Ref<any>,
|
||||
createMediaStateOwner: CreateMediaStateOwner /*, props? */
|
||||
) => {
|
||||
const mediaStateOwnerRef = useRef(createMediaStateOwner(/* props? */));
|
||||
useImperativeHandle(ref, () => mediaStateOwnerRef.current, []);
|
||||
/** @TODO Parameterize this (CJP) */
|
||||
type ComponentProps = DetailedHTMLProps<
|
||||
VideoHTMLAttributes<HTMLVideoElement>,
|
||||
HTMLVideoElement
|
||||
>;
|
||||
return {
|
||||
updateMediaElement(
|
||||
mediaEl: HTMLMediaElement | null,
|
||||
props: ComponentProps
|
||||
) {
|
||||
// NOTE: The details here will almost definitely change for a less "bare bones"/"POC" implementation of Media State Owner impl. (CJP)
|
||||
mediaStateOwnerRef.current.mediaElement = mediaEl ?? undefined;
|
||||
mediaStateOwnerRef.current.src = props.src as string;
|
||||
if (props.muted) {
|
||||
mediaStateOwnerRef.current.muted = props.muted;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const BaseComponent: ElementType<
|
||||
Omit<
|
||||
DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>,
|
||||
'ref'
|
||||
> & { ref: Ref<any> }
|
||||
> = ({ children, ref, ...props }) => {
|
||||
const { updateMediaElement } = useMediaStateOwner(ref, createMediaStateOwner);
|
||||
return (
|
||||
<video {...props} ref={(mediaEl) => {
|
||||
/** @TODO In later iterations/non-POC, we should be able to have a function that can be used directly for the `ref` prop (CJP) */
|
||||
updateMediaElement(mediaEl, props);
|
||||
}}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
};
|
||||
|
||||
// NOTE: Assuming FC or "functional component" here for type expediency (though may be fine for our use cases).
|
||||
type ConnectedComponentProps = Parameters<typeof ConnectedComponent>[0];
|
||||
type ConnectedComponentReturnType = ReturnType<typeof ConnectedComponent>;
|
||||
type DefaultedProps = 'component';
|
||||
type ConnectedComponentWithDefaults = (
|
||||
props: Omit<ConnectedComponentProps, DefaultedProps> &
|
||||
Partial<Pick<ConnectedComponentProps, DefaultedProps>>,
|
||||
) => ConnectedComponentReturnType;
|
||||
|
||||
const Component: ConnectedComponentWithDefaults = ({
|
||||
component = BaseComponent,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<ConnectedComponent {...props} component={component}>
|
||||
{children}
|
||||
</ConnectedComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
@@ -2,6 +2,4 @@
|
||||
|
||||
export * from '@vjs-10/react-media-store';
|
||||
export * from './skins/MediaSkinDefault';
|
||||
import Video from './components/connected-with-defaults/Video';
|
||||
import MediaElementVideo from './components/media-elements/Video';
|
||||
export { Video, MediaElementVideo };
|
||||
export { Video, MediaElementVideo } from './components/Video';
|
||||
Reference in New Issue
Block a user