Files
ohif-viewer/platform/ui-next/src/components/CinePlayer/CinePlayer.tsx
T

201 lines
6.3 KiB
TypeScript

import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import { Icons } from '@ohif/ui-next';
import { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover';
import { Button } from '../Button/Button';
import { Numeric } from '../Numeric/Numeric';
export type CinePlayerProps = {
className: string;
isPlaying: boolean;
minFrameRate?: number;
maxFrameRate?: number;
stepFrameRate?: number;
frameRate?: number;
onFrameRateChange: (value: number) => void;
onPlayPauseChange: (value: boolean) => void;
onClose: () => void;
updateDynamicInfo?: (info: any) => void;
dynamicInfo?: {
dimensionGroupNumber: number;
numDimensionGroups: number;
label?: string;
};
};
const CinePlayer: React.FC<CinePlayerProps> = ({
className,
isPlaying = false,
minFrameRate = 1,
maxFrameRate = 90,
stepFrameRate = 1,
frameRate: defaultFrameRate = 24,
onFrameRateChange = () => {},
onPlayPauseChange = () => {},
onClose = () => {},
dynamicInfo = {},
updateDynamicInfo,
}) => {
const isDynamic = !!dynamicInfo?.numDimensionGroups;
const [frameRate, setFrameRate] = useState(defaultFrameRate);
const [popoverOpen, setPopoverOpen] = useState(false);
const debouncedSetFrameRate = useCallback(debounce(onFrameRateChange, 100), [onFrameRateChange]);
const getPlayPauseIconName = () => (isPlaying ? 'icon-pause' : 'icon-play');
const handleSetFrameRate = (frameRate: number) => {
if (frameRate < minFrameRate || frameRate > maxFrameRate) {
return;
}
setFrameRate(frameRate);
debouncedSetFrameRate(frameRate);
};
useEffect(() => {
setFrameRate(defaultFrameRate);
}, [defaultFrameRate]);
const handleDimensionGroupNumberChange = useCallback(
(newGroupNumber: number) => {
if (isDynamic && dynamicInfo) {
updateDynamicInfo?.({
...dynamicInfo,
dimensionGroupNumber: newGroupNumber,
});
}
},
[isDynamic, dynamicInfo, updateDynamicInfo]
);
return (
<div className={`absolute top-10 left-1/2 z-50 -translate-x-1/2 ${className}`}>
<div className={'bg-muted inline-flex select-none items-center gap-2 rounded-md px-2 py-2'}>
<Button
variant="ghost"
size="icon"
onClick={() => onPlayPauseChange(!isPlaying)}
data-cy={'cine-player-play-pause'}
>
<Icons.ByName name={getPlayPauseIconName()} />
</Button>
{isDynamic && dynamicInfo && (
<div className="min-w-16 max-w-44 text-foreground flex flex-col">
<div className="text-xs">
<span className="text-foreground w-2">{dynamicInfo.dimensionGroupNumber}</span>{' '}
<span className="text-muted-foreground">{`/${dynamicInfo.numDimensionGroups}`}</span>
</div>
<div className="text-muted-foreground text-xs">{dynamicInfo.label}</div>
</div>
)}
<div>
<Popover
open={popoverOpen}
onOpenChange={setPopoverOpen}
>
<PopoverTrigger asChild>
<Button
variant="ghost"
className="h-full border-none bg-transparent p-0 hover:bg-transparent"
>
<Numeric.Container
mode="stepper"
min={minFrameRate}
max={maxFrameRate}
step={stepFrameRate}
value={frameRate}
onChange={val => handleSetFrameRate(val as number)}
className="border-0 bg-transparent"
>
<Numeric.NumberStepper
direction="horizontal"
inputWidth="w-7 max-w-7"
>
<div className="flex items-center justify-center gap-1">
<div className="text-foreground flex-shrink-0 text-center text-sm leading-[22px]">
<span className="text-muted-foreground whitespace-nowrap text-xs">
{' FPS'}
</span>
</div>
</div>
</Numeric.NumberStepper>
</Numeric.Container>
</Button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="center"
className="cine-fps-range-popover z-50 w-auto p-2"
sideOffset={8}
>
<Numeric.Container
mode="singleRange"
min={minFrameRate}
max={maxFrameRate}
step={stepFrameRate}
value={frameRate}
onChange={val => handleSetFrameRate(val as number)}
className="h-6 px-2"
>
<Numeric.SingleRange
showNumberInput={false}
sliderClassName="w-40"
/>
</Numeric.Container>
</PopoverContent>
</Popover>
</div>
<Button
variant="ghost"
size="icon"
onClick={onClose}
data-cy={'cine-player-close'}
>
<Icons.Close />
</Button>
</div>
{isDynamic && dynamicInfo && (
<Numeric.Container
mode="singleRange"
min={1}
max={dynamicInfo.numDimensionGroups}
step={1}
value={dynamicInfo.dimensionGroupNumber}
onChange={val => handleDimensionGroupNumberChange(val as number)}
className="mt-3 w-full"
>
<Numeric.SingleRange showNumberInput={false} />
</Numeric.Container>
)}
</div>
);
};
CinePlayer.propTypes = {
/** Minimum value for range slider */
minFrameRate: PropTypes.number,
/** Maximum value for range slider */
maxFrameRate: PropTypes.number,
/** Increment range slider can "step" in either direction */
stepFrameRate: PropTypes.number,
frameRate: PropTypes.number,
/** 'true' if playing, 'false' if paused */
isPlaying: PropTypes.bool.isRequired,
onPlayPauseChange: PropTypes.func,
onFrameRateChange: PropTypes.func,
onClose: PropTypes.func,
isDynamic: PropTypes.bool,
dynamicInfo: PropTypes.shape({
dimensionGroupNumber: PropTypes.number,
numDimensionGroups: PropTypes.number,
label: PropTypes.string,
}),
};
export default CinePlayer;