mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-07 06:19:30 +00:00
feat(player): add transitions to menus
This commit is contained in:
parent
eb23d3e4db
commit
3f5dedd072
9 changed files with 78 additions and 87 deletions
|
|
@ -88,7 +88,7 @@
|
|||
|
||||
.fade-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
transition: opacity 0.1s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
}
|
||||
|
||||
.fade-exit {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ type Props = {
|
|||
children: JSX.Element,
|
||||
when: boolean,
|
||||
name: string,
|
||||
duration?: number,
|
||||
};
|
||||
|
||||
const Transition = ({ children, when, name }: Props) => {
|
||||
const Transition = ({ children, when, name, duration }: Props) => {
|
||||
const [element, setElement] = useState<HTMLElement | null>(null);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
|
|
@ -29,6 +30,10 @@ const Transition = ({ children, when, name }: Props) => {
|
|||
);
|
||||
}, [name, state, active, children]);
|
||||
|
||||
const style = useMemo(() => {
|
||||
if (duration) return { transitionDuration: `${duration}ms` };
|
||||
}, [duration]);
|
||||
|
||||
const onTransitionEnd = useCallback(() => {
|
||||
state === 'exit' && setMounted(false);
|
||||
}, [state]);
|
||||
|
|
@ -53,6 +58,7 @@ const Transition = ({ children, when, name }: Props) => {
|
|||
mounted && cloneElement(children, {
|
||||
ref: callbackRef,
|
||||
className,
|
||||
style,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { MouseEvent, useCallback } from 'react';
|
||||
import React, { forwardRef, MouseEvent, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import classNames from 'classnames';
|
||||
import { languages } from 'stremio/common';
|
||||
|
|
@ -12,7 +12,7 @@ type Props = {
|
|||
onAudioTrackSelected: (id: string) => void,
|
||||
};
|
||||
|
||||
const AudioMenu = ({ className, selectedAudioTrackId, audioTracks, onAudioTrackSelected }: Props) => {
|
||||
const AudioMenu = forwardRef<HTMLDivElement, Props>(({ className, selectedAudioTrackId, audioTracks, onAudioTrackSelected }: Props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onAudioTrackClick = useCallback(({ currentTarget }: MouseEvent) => {
|
||||
|
|
@ -26,7 +26,7 @@ const AudioMenu = ({ className, selectedAudioTrackId, audioTracks, onAudioTrackS
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(className, styles['audio-menu'])} onMouseDown={onMouseDown}>
|
||||
<div ref={ref} className={classNames(className, styles['audio-menu'])} onMouseDown={onMouseDown}>
|
||||
<div className={styles['container']}>
|
||||
<div className={styles['header']}>
|
||||
{ t('AUDIO_TRACKS') }
|
||||
|
|
@ -62,6 +62,6 @@ const AudioMenu = ({ className, selectedAudioTrackId, audioTracks, onAudioTrackS
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default AudioMenu;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ const Indicator = ({ className, videoState, disabled }: Props) => {
|
|||
}, [videoState]);
|
||||
|
||||
return (
|
||||
<Transition when={shown && !disabled} name={'fade'}>
|
||||
<Transition when={shown && !disabled} name={'fade'} duration={300}>
|
||||
<div className={classNames(className, styles['indicator-container'])}>
|
||||
<div className={styles['indicator']}>
|
||||
<div>{label} {value}</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const { useServices } = require('stremio/services');
|
|||
const Option = require('./Option');
|
||||
const styles = require('./styles');
|
||||
|
||||
const OptionsMenu = ({ className, stream, playbackDevices, extraSubtitlesTracks, selectedExtraSubtitlesTrackId }) => {
|
||||
const OptionsMenu = React.forwardRef(({ className, stream, playbackDevices, extraSubtitlesTracks, selectedExtraSubtitlesTrackId }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const platform = usePlatform();
|
||||
|
|
@ -108,7 +108,7 @@ const OptionsMenu = ({ className, stream, playbackDevices, extraSubtitlesTracks,
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classnames(className, styles['options-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<div ref={ref} className={classnames(className, styles['options-menu-container'])} onMouseDown={onMouseDown}>
|
||||
{
|
||||
streamingUrl || downloadUrl ?
|
||||
<Option
|
||||
|
|
@ -167,7 +167,7 @@ const OptionsMenu = ({ className, stream, playbackDevices, extraSubtitlesTracks,
|
|||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
OptionsMenu.propTypes = {
|
||||
className: PropTypes.string,
|
||||
|
|
|
|||
|
|
@ -1000,15 +1000,12 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
statisticsMenuOpen ?
|
||||
<StatisticsMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
{...statistics}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
<Transition when={statisticsMenuOpen} name={'fade'}>
|
||||
<StatisticsMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
{...statistics}
|
||||
/>
|
||||
</Transition>
|
||||
<Transition when={sideDrawerOpen} name={'slide-left'}>
|
||||
<SideDrawer
|
||||
className={classnames(styles['layer'], styles['side-drawer-layer'])}
|
||||
|
|
@ -1018,65 +1015,53 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
selected={player.selected?.streamRequest?.path.id}
|
||||
/>
|
||||
</Transition>
|
||||
{
|
||||
subtitlesMenuOpen ?
|
||||
<SubtitlesMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
subtitlesLanguage={settings.subtitlesLanguage}
|
||||
interfaceLanguage={settings.interfaceLanguage}
|
||||
subtitlesTracks={video.state.subtitlesTracks}
|
||||
selectedSubtitlesTrackId={video.state.selectedSubtitlesTrackId}
|
||||
subtitlesOffset={video.state.subtitlesOffset}
|
||||
subtitlesSize={video.state.subtitlesSize}
|
||||
extraSubtitlesTracks={video.state.extraSubtitlesTracks}
|
||||
selectedExtraSubtitlesTrackId={video.state.selectedExtraSubtitlesTrackId}
|
||||
extraSubtitlesOffset={video.state.extraSubtitlesOffset}
|
||||
extraSubtitlesDelay={video.state.extraSubtitlesDelay}
|
||||
extraSubtitlesSize={video.state.extraSubtitlesSize}
|
||||
onSubtitlesTrackSelected={onSubtitlesTrackSelected}
|
||||
onExtraSubtitlesTrackSelected={onExtraSubtitlesTrackSelected}
|
||||
onSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
||||
onSubtitlesSizeChanged={onSubtitlesSizeChanged}
|
||||
onExtraSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
||||
onExtraSubtitlesDelayChanged={onExtraSubtitlesDelayChanged}
|
||||
onExtraSubtitlesSizeChanged={onSubtitlesSizeChanged}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
audioMenuOpen ?
|
||||
<AudioMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
audioTracks={video.state.audioTracks}
|
||||
selectedAudioTrackId={video.state.selectedAudioTrackId}
|
||||
onAudioTrackSelected={onAudioTrackSelected}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
speedMenuOpen ?
|
||||
<SpeedMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
playbackSpeed={video.state.playbackSpeed}
|
||||
onPlaybackSpeedChanged={onPlaybackSpeedChanged}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
optionsMenuOpen ?
|
||||
<OptionsMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
stream={player.selected.stream}
|
||||
playbackDevices={playbackDevices}
|
||||
extraSubtitlesTracks={video.state.extraSubtitlesTracks}
|
||||
selectedExtraSubtitlesTrackId={video.state.selectedExtraSubtitlesTrackId}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
<Transition when={subtitlesMenuOpen} name={'fade'}>
|
||||
<SubtitlesMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
subtitlesLanguage={settings.subtitlesLanguage}
|
||||
interfaceLanguage={settings.interfaceLanguage}
|
||||
subtitlesTracks={video.state.subtitlesTracks}
|
||||
selectedSubtitlesTrackId={video.state.selectedSubtitlesTrackId}
|
||||
subtitlesOffset={video.state.subtitlesOffset}
|
||||
subtitlesSize={video.state.subtitlesSize}
|
||||
extraSubtitlesTracks={video.state.extraSubtitlesTracks}
|
||||
selectedExtraSubtitlesTrackId={video.state.selectedExtraSubtitlesTrackId}
|
||||
extraSubtitlesOffset={video.state.extraSubtitlesOffset}
|
||||
extraSubtitlesDelay={video.state.extraSubtitlesDelay}
|
||||
extraSubtitlesSize={video.state.extraSubtitlesSize}
|
||||
onSubtitlesTrackSelected={onSubtitlesTrackSelected}
|
||||
onExtraSubtitlesTrackSelected={onExtraSubtitlesTrackSelected}
|
||||
onSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
||||
onSubtitlesSizeChanged={onSubtitlesSizeChanged}
|
||||
onExtraSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
||||
onExtraSubtitlesDelayChanged={onExtraSubtitlesDelayChanged}
|
||||
onExtraSubtitlesSizeChanged={onSubtitlesSizeChanged}
|
||||
/>
|
||||
</Transition>
|
||||
<Transition when={audioMenuOpen} name={'fade'}>
|
||||
<AudioMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
audioTracks={video.state.audioTracks}
|
||||
selectedAudioTrackId={video.state.selectedAudioTrackId}
|
||||
onAudioTrackSelected={onAudioTrackSelected}
|
||||
/>
|
||||
</Transition>
|
||||
<Transition when={speedMenuOpen} name={'fade'}>
|
||||
<SpeedMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
playbackSpeed={video.state.playbackSpeed}
|
||||
onPlaybackSpeedChanged={onPlaybackSpeedChanged}
|
||||
/>
|
||||
</Transition>
|
||||
<Transition when={optionsMenuOpen} name={'fade'}>
|
||||
<OptionsMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
stream={player.selected?.stream}
|
||||
playbackDevices={playbackDevices}
|
||||
extraSubtitlesTracks={video.state.extraSubtitlesTracks}
|
||||
selectedExtraSubtitlesTrackId={video.state.selectedExtraSubtitlesTrackId}
|
||||
/>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const styles = require('./styles');
|
|||
|
||||
const RATES = Array.from(Array(8).keys(), (n) => n * 0.25 + 0.25).reverse();
|
||||
|
||||
const SpeedMenu = ({ className, playbackSpeed, onPlaybackSpeedChanged }) => {
|
||||
const SpeedMenu = React.forwardRef(({ className, playbackSpeed, onPlaybackSpeedChanged }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.speedMenuClosePrevented = true;
|
||||
|
|
@ -20,7 +20,7 @@ const SpeedMenu = ({ className, playbackSpeed, onPlaybackSpeedChanged }) => {
|
|||
}
|
||||
}, [onPlaybackSpeedChanged]);
|
||||
return (
|
||||
<div className={classnames(className, styles['speed-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<div ref={ref} className={classnames(className, styles['speed-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<div className={styles['title']}>
|
||||
{ t('PLAYBACK_SPEED') }
|
||||
</div>
|
||||
|
|
@ -39,7 +39,7 @@ const SpeedMenu = ({ className, playbackSpeed, onPlaybackSpeedChanged }) => {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
SpeedMenu.propTypes = {
|
||||
className: PropTypes.string,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ const classNames = require('classnames');
|
|||
const PropTypes = require('prop-types');
|
||||
const styles = require('./styles.less');
|
||||
|
||||
const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => {
|
||||
const StatisticsMenu = React.forwardRef(({ className, peers, speed, completed, infoHash }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={classNames(className, styles['statistics-menu-container'])}>
|
||||
<div ref={ref} className={classNames(className, styles['statistics-menu-container'])}>
|
||||
<div className={styles['title']}>
|
||||
{t('PLAYER_STATISTICS')}
|
||||
</div>
|
||||
|
|
@ -49,7 +49,7 @@ const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
StatisticsMenu.propTypes = {
|
||||
className: PropTypes.string,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const sortByValues = (items, values) => items.sort((a, b) => {
|
|||
return left - right;
|
||||
});
|
||||
|
||||
const SubtitlesMenu = React.memo((props) => {
|
||||
const SubtitlesMenu = React.memo(React.forwardRef((props, ref) => {
|
||||
const subtitlesTracks = React.useMemo(() => {
|
||||
return normalizeTracksLang(Array.isArray(props.subtitlesTracks) ? props.subtitlesTracks : []);
|
||||
}, [props.subtitlesTracks]);
|
||||
|
|
@ -153,7 +153,7 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
}
|
||||
}, [props.selectedSubtitlesTrackId, props.selectedExtraSubtitlesTrackId, props.subtitlesOffset, props.extraSubtitlesOffset, props.onSubtitlesOffsetChanged, props.onExtraSubtitlesOffsetChanged]);
|
||||
return (
|
||||
<div className={classnames(props.className, styles['subtitles-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<div ref={ref} className={classnames(props.className, styles['subtitles-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<div className={styles['languages-container']}>
|
||||
<div className={styles['languages-header']}>{ t('PLAYER_SUBTITLES_LANGUAGES') }</div>
|
||||
<div className={styles['languages-list']}>
|
||||
|
|
@ -255,7 +255,7 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
SubtitlesMenu.displayName = 'MainNavBars';
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue