mirror of
https://github.com/zoriya/v10.git
synced 2026-08-15 10:23:32 +00:00
feat: implement current time display components
Adds current time display functionality across HTML and React platforms: - Core state definition for current time with duration support - HTML component with shadow DOM and span rendering - React component following hook-style architecture - Integration into both HTML and React default skins - Proper exports and component registration 🤖 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
6db4c3c4b8
commit
5bd0a154db
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @fileoverview Current time display component state definition
|
||||
*
|
||||
* This module provides the component state definition for current time display
|
||||
* components across HTML, React, and React Native platforms. The current time
|
||||
* display is a read-only component that shows the current playback time of media.
|
||||
*/
|
||||
|
||||
/**
|
||||
* State interface for current time display components
|
||||
*/
|
||||
export interface CurrentTimeDisplayState {
|
||||
/** The current time value in seconds */
|
||||
currentTime: number | undefined;
|
||||
|
||||
/** The total duration in seconds (for future functionality) */
|
||||
duration: number | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current time display component state definition following VJS-10 patterns.
|
||||
* This provides a read-only display component that shows the current playback time.
|
||||
*/
|
||||
export const currentTimeDisplayStateDefinition = {
|
||||
/**
|
||||
* Keys from the media store that this component depends on
|
||||
*/
|
||||
keys: ['currentTime', 'duration'] as const,
|
||||
|
||||
/**
|
||||
* Transform raw media store state into current time display component state
|
||||
* @param rawState - Raw state from media store
|
||||
* @returns Transformed state for current time display component
|
||||
*/
|
||||
stateTransform: (rawState: Record<string, any>): CurrentTimeDisplayState => {
|
||||
const { currentTime, duration } = rawState;
|
||||
|
||||
return {
|
||||
currentTime,
|
||||
duration,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Current time display is read-only, so no request methods are needed
|
||||
* @param _dispatch - Dispatch function (unused)
|
||||
* @returns Empty object (no request methods)
|
||||
*/
|
||||
createRequestMethods: (_dispatch: (action: { type: string; detail?: any }) => void) => ({}),
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Type helper to extract the state type from the current time display state definition
|
||||
*/
|
||||
export type CurrentTimeDisplayStateDefinition = typeof currentTimeDisplayStateDefinition;
|
||||
|
||||
/**
|
||||
* Type helper to extract the transformed state type
|
||||
*/
|
||||
export type CurrentTimeDisplayComponentState = ReturnType<typeof currentTimeDisplayStateDefinition.stateTransform>;
|
||||
|
||||
/**
|
||||
* Type helper to extract the request methods type (empty for read-only component)
|
||||
*/
|
||||
export type CurrentTimeDisplayRequestMethods = ReturnType<typeof currentTimeDisplayStateDefinition.createRequestMethods>;
|
||||
@@ -9,4 +9,5 @@ export * from './component-state-definitions/volume-range';
|
||||
export * from './component-state-definitions/time-range';
|
||||
export * from './component-state-definitions/fullscreen-button';
|
||||
export * from './component-state-definitions/duration-display';
|
||||
export * from './component-state-definitions/current-time-display';
|
||||
export * from './utils/time';
|
||||
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
toConnectedHTMLComponent,
|
||||
StateHook,
|
||||
PropsHook,
|
||||
} from '../utils/component-factory';
|
||||
import { currentTimeDisplayStateDefinition, formatDisplayTime } from '@vjs-10/media-store';
|
||||
import { namedNodeMapToObject } from '../utils/element-utils.js';
|
||||
|
||||
export function getTemplateHTML(
|
||||
this: typeof CurrentTimeDisplayBase,
|
||||
_attrs: Record<string, string>,
|
||||
_props: Record<string, any> = {},
|
||||
) {
|
||||
return /* html */ `
|
||||
<span></span>
|
||||
`;
|
||||
}
|
||||
|
||||
export class CurrentTimeDisplayBase extends HTMLElement {
|
||||
static shadowRootOptions = {
|
||||
mode: 'open' as ShadowRootMode,
|
||||
};
|
||||
static getTemplateHTML = getTemplateHTML;
|
||||
|
||||
_state:
|
||||
| {
|
||||
currentTime: number | undefined;
|
||||
duration: number | undefined;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!this.shadowRoot) {
|
||||
this.attachShadow(
|
||||
(this.constructor as typeof CurrentTimeDisplayBase).shadowRootOptions,
|
||||
);
|
||||
|
||||
const attrs = namedNodeMapToObject(this.attributes);
|
||||
const html = (
|
||||
this.constructor as typeof CurrentTimeDisplayBase
|
||||
).getTemplateHTML(attrs);
|
||||
const shadowRoot = this.shadowRoot as unknown as ShadowRoot;
|
||||
shadowRoot.setHTMLUnsafe
|
||||
? shadowRoot.setHTMLUnsafe(html)
|
||||
: (shadowRoot.innerHTML = html);
|
||||
}
|
||||
}
|
||||
|
||||
get currentTime() {
|
||||
return this._state?.currentTime;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this._state?.duration;
|
||||
}
|
||||
|
||||
_update(_props: any, state: any) {
|
||||
this._state = state;
|
||||
|
||||
// Update the span content with formatted current time
|
||||
const spanElement = this.shadowRoot?.querySelector('span') as HTMLElement;
|
||||
if (spanElement) {
|
||||
spanElement.textContent = formatDisplayTime(state.currentTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CurrentTimeDisplay state hook - equivalent to React's useCurrentTimeDisplayState
|
||||
* Handles media store state subscription and transformation
|
||||
*/
|
||||
export const useCurrentTimeDisplayState: StateHook<{
|
||||
currentTime: number | undefined;
|
||||
duration: number | undefined;
|
||||
}> = {
|
||||
keys: [...currentTimeDisplayStateDefinition.keys],
|
||||
transform: (rawState, _mediaStore) => ({
|
||||
...currentTimeDisplayStateDefinition.stateTransform(rawState),
|
||||
// Current time display is read-only, so no request methods needed
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* CurrentTimeDisplay props hook - equivalent to React's useCurrentTimeDisplayProps
|
||||
* Handles element attributes and properties based on state
|
||||
*/
|
||||
export const useCurrentTimeDisplayProps: PropsHook<{
|
||||
currentTime: number | undefined;
|
||||
duration: number | undefined;
|
||||
}> = (_state, _element) => {
|
||||
const baseProps: Record<string, any> = {};
|
||||
return baseProps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connected CurrentTimeDisplay component using hook-style architecture
|
||||
* Equivalent to React's CurrentTimeDisplay = toConnectedComponent(...)
|
||||
*/
|
||||
export const CurrentTimeDisplay = toConnectedHTMLComponent(
|
||||
CurrentTimeDisplayBase,
|
||||
useCurrentTimeDisplayState,
|
||||
useCurrentTimeDisplayProps,
|
||||
'CurrentTimeDisplay',
|
||||
);
|
||||
|
||||
// Register the custom element
|
||||
if (!globalThis.customElements.get('media-current-time-display')) {
|
||||
// @ts-ignore - Custom element constructor compatibility
|
||||
globalThis.customElements.define('media-current-time-display', CurrentTimeDisplay);
|
||||
}
|
||||
|
||||
export default CurrentTimeDisplay;
|
||||
@@ -7,6 +7,7 @@ export { MuteButton } from './components/media-mute-button.js';
|
||||
export { VolumeRange } from './components/media-volume-range.js';
|
||||
export { FullscreenButton } from './components/media-fullscreen-button.js';
|
||||
export { DurationDisplay } from './components/media-duration-display.js';
|
||||
export { CurrentTimeDisplay } from './components/media-current-time-display.js';
|
||||
|
||||
export function defineVjsPlayer() {
|
||||
/** @TODO - Reimplement me (at least as a POC) (CJP) */
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../components/media-volume-range';
|
||||
import '../components/media-time-range';
|
||||
import '../components/media-fullscreen-button';
|
||||
import '../components/media-duration-display';
|
||||
import '../components/media-current-time-display';
|
||||
import '@vjs-10/html-icons';
|
||||
|
||||
export function getTemplateHTML() {
|
||||
@@ -99,6 +100,7 @@ export function getTemplateHTML() {
|
||||
<media-play-icon class="icon play-icon"></media-play-icon>
|
||||
<media-pause-icon class="icon pause-icon"></media-pause-icon>
|
||||
</media-play-button>
|
||||
<media-current-time-display></media-current-time-display>
|
||||
<media-time-range></media-time-range>
|
||||
<media-duration-display></media-duration-display>
|
||||
<media-mute-button class="button">
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
shallowEqual,
|
||||
useMediaSelector,
|
||||
useMediaStore,
|
||||
} from '@vjs-10/react-media-store';
|
||||
import * as React from 'react';
|
||||
import { toConnectedComponent } from '../utils/component-factory';
|
||||
import { currentTimeDisplayStateDefinition, formatDisplayTime } from '@vjs-10/media-store';
|
||||
|
||||
export const useCurrentTimeDisplayState = (_props: any) => {
|
||||
const mediaStore = useMediaStore();
|
||||
/** @TODO Fix type issues with hooks (CJP) */
|
||||
const mediaState = useMediaSelector(
|
||||
currentTimeDisplayStateDefinition.stateTransform,
|
||||
shallowEqual,
|
||||
);
|
||||
|
||||
// Current time display is read-only, no request methods needed
|
||||
return {
|
||||
currentTime: mediaState.currentTime,
|
||||
duration: mediaState.duration,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export type useCurrentTimeDisplayState = typeof useCurrentTimeDisplayState;
|
||||
export type CurrentTimeDisplayState = ReturnType<useCurrentTimeDisplayState>;
|
||||
|
||||
export const useCurrentTimeDisplayProps = (
|
||||
props: React.PropsWithChildren<{ [k: string]: any }>,
|
||||
state: ReturnType<typeof useCurrentTimeDisplayState>,
|
||||
) => {
|
||||
const baseProps: Record<string, any> = {
|
||||
/** external props spread last to allow for overriding */
|
||||
...props,
|
||||
};
|
||||
|
||||
return baseProps;
|
||||
};
|
||||
|
||||
export type useCurrentTimeDisplayProps = typeof useCurrentTimeDisplayProps;
|
||||
type CurrentTimeDisplayProps = ReturnType<useCurrentTimeDisplayProps>;
|
||||
|
||||
export const renderCurrentTimeDisplay = (
|
||||
props: CurrentTimeDisplayProps,
|
||||
state: CurrentTimeDisplayState,
|
||||
) => {
|
||||
return (
|
||||
<span {...props}>
|
||||
{formatDisplayTime(state.currentTime)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export type renderCurrentTimeDisplay = typeof renderCurrentTimeDisplay;
|
||||
|
||||
export const CurrentTimeDisplay = toConnectedComponent(
|
||||
useCurrentTimeDisplayState,
|
||||
useCurrentTimeDisplayProps,
|
||||
renderCurrentTimeDisplay,
|
||||
'CurrentTimeDisplay',
|
||||
);
|
||||
export default CurrentTimeDisplay;
|
||||
@@ -10,4 +10,5 @@ export { PlayButton } from './components/PlayButton';
|
||||
export { MuteButton } from './components/MuteButton';
|
||||
export { VolumeRange } from './components/VolumeRange';
|
||||
export { FullscreenButton } from './components/FullscreenButton';
|
||||
export { DurationDisplay } from './components/DurationDisplay';
|
||||
export { DurationDisplay } from './components/DurationDisplay';
|
||||
export { CurrentTimeDisplay } from './components/CurrentTimeDisplay';
|
||||
@@ -6,6 +6,7 @@ import { VolumeRange } from '../components/VolumeRange';
|
||||
import { TimeRange } from '../components/TimeRange';
|
||||
import { FullscreenButton } from '../components/FullscreenButton';
|
||||
import { DurationDisplay } from '../components/DurationDisplay';
|
||||
import { CurrentTimeDisplay } from '../components/CurrentTimeDisplay';
|
||||
import { MediaContainer } from '../components/MediaContainer';
|
||||
import {
|
||||
VolumeHighIcon,
|
||||
@@ -30,6 +31,7 @@ export const MediaSkinDefault: React.FC<{ children: React.ReactNode }> = ({
|
||||
<PlayIcon className={styles.PlayIcon}></PlayIcon>
|
||||
<PauseIcon className={styles.PauseIcon}></PauseIcon>
|
||||
</PlayButton>
|
||||
<CurrentTimeDisplay />
|
||||
<TimeRange className={styles.TimeRange} />
|
||||
<DurationDisplay />
|
||||
{/* @ts-ignore */}
|
||||
|
||||
Reference in New Issue
Block a user