refactor(ui/player): refactor timeline and overlay

This commit is contained in:
sylenien 2022-11-22 15:20:22 +01:00
parent 2634d6cbd0
commit 0d57722fec
4 changed files with 133 additions and 154 deletions

View file

@ -7,7 +7,8 @@ import {
PlayerProvider,
createWebPlayer,
} from 'Player';
import { makeAutoObservable } from 'mobx'
import { observer } from "mobx-react-lite"
import withLocationHandlers from 'HOCs/withLocationHandlers';
import { useStore } from 'App/mstore';
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
@ -35,7 +36,10 @@ function WebPlayer(props: any) {
useEffect(() => {
fetchList('issues');
const [WebPlayerInst, PlayerStore] = createWebPlayer(session, jwt);
const [WebPlayerInst, PlayerStore] = createWebPlayer(
session,
(state) => makeAutoObservable(state)
);
setContextValue({ player: WebPlayerInst, store: PlayerStore })
// initPlayer(session, jwt); TODOPlayer

View file

@ -1,7 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { Icon } from 'UI'
import { connectPlayer, Controls, toggleTimetravel } from 'Player';
import TimeTracker from './TimeTracker';
import stl from './timeline.module.css';
import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions';
@ -9,6 +8,8 @@ import DraggableCircle from './DraggableCircle';
import CustomDragLayer from './CustomDragLayer';
import { debounce } from 'App/utils';
import TooltipContainer from './components/TooltipContainer';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
const BOUNDRY = 0;
@ -20,105 +21,63 @@ function getTimelinePosition(value, scale) {
let deboucneJump = () => null;
let debounceTooltipChange = () => null;
@connectPlayer((state) => ({
playing: state.playing,
time: state.time,
skipIntervals: state.skipIntervals,
events: state.eventList,
skip: state.skip,
skipToIssue: state.skipToIssue,
disabled: state.cssLoading || state.messagesLoading || state.markedTargets,
endTime: state.endTime,
live: state.live,
notes: state.notes || [], // TODO: implement notes without interaction with Player state
}))
@connect(
(state) => ({
issues: state.getIn(['sessions', 'current', 'issues']),
startedAt: state.getIn(['sessions', 'current', 'startedAt']),
tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']),
}),
{ setTimelinePointer, setTimelineHoverTime }
)
export default class Timeline extends React.PureComponent {
progressRef = React.createRef();
timelineRef = React.createRef();
wasPlaying = false;
seekProgress = (e) => {
const time = this.getTime(e);
this.props.jump(time);
this.hideTimeTooltip();
};
loadAndSeek = async (e) => {
e.persist();
await toggleTimetravel();
function Timeline(props) {
const { player, store } = React.useContext(PlayerContext)
const [wasPlaying, setWasPlaying] = React.useState(false);
const {
playing,
time,
skipIntervals,
eventList: events,
skip,
skipToIssue,
disabled,
endTime,
live,
notes = [],
} = store.get()
setTimeout(() => {
this.seekProgress(e);
});
};
const progressRef = React.useRef();
const timelineRef = React.useRef();
jumpToTime = (e) => {
if (this.props.live && !this.props.liveTimeTravel) {
this.loadAndSeek(e);
} else {
this.seekProgress(e);
}
};
getTime = (e, customEndTime) => {
const { endTime } = this.props;
const p = e.nativeEvent.offsetX / e.target.offsetWidth;
const targetTime = customEndTime || endTime;
const time = Math.max(Math.round(p * targetTime), 0);
const scale = 100 / endTime;
return time;
};
createEventClickHandler = (pointer) => (e) => {
e.stopPropagation();
this.props.jump(pointer.time);
this.props.setTimelinePointer(pointer);
};
componentDidMount() {
const { issues, skipToIssue } = this.props;
React.useEffect(() => {
const { issues } = props;
const firstIssue = issues.get(0);
deboucneJump = debounce(this.props.jump, 500);
debounceTooltipChange = debounce(this.props.setTimelineHoverTime, 50);
deboucneJump = debounce(player.jump, 500);
debounceTooltipChange = debounce(props.setTimelineHoverTime, 50);
if (firstIssue && skipToIssue) {
this.props.jump(firstIssue.time);
player.jump(firstIssue.time);
}
}
}, [])
onDragEnd = () => {
const { live, liveTimeTravel } = this.props;
const onDragEnd = () => {
if (live && !liveTimeTravel) return;
if (this.wasPlaying) {
this.props.togglePlay();
if (wasPlaying) {
player.togglePlay();
}
};
onDrag = (offset) => {
const { endTime, live, liveTimeTravel } = this.props;
const onDrag = (offset) => {
if (live && !liveTimeTravel) return;
const p = (offset.x - BOUNDRY) / this.progressRef.current.offsetWidth;
const p = (offset.x - BOUNDRY) / progressRef.current.offsetWidth;
const time = Math.max(Math.round(p * endTime), 0);
deboucneJump(time);
this.hideTimeTooltip();
if (this.props.playing) {
this.wasPlaying = true;
this.props.pause();
hideTimeTooltip();
if (playing) {
setWasPlaying(true)
player.pause();
}
};
getLiveTime = (e) => {
const { startedAt } = this.props;
const getLiveTime = (e) => {
const duration = new Date().getTime() - startedAt;
const p = e.nativeEvent.offsetX / e.target.offsetWidth;
const time = Math.max(Math.round(p * duration), 0);
@ -126,23 +85,23 @@ export default class Timeline extends React.PureComponent {
return [time, duration];
};
showTimeTooltip = (e) => {
if (e.target !== this.progressRef.current && e.target !== this.timelineRef.current) {
return this.props.tooltipVisible && this.hideTimeTooltip();
const showTimeTooltip = (e) => {
if (e.target !== progressRef.current && e.target !== timelineRef.current) {
return props.tooltipVisible && hideTimeTooltip();
}
const { live } = this.props;
let timeLineTooltip;
if (live) {
const [time, duration] = this.getLiveTime(e);
const [time, duration] = getLiveTime(e);
timeLineTooltip = {
time: duration - time,
offset: e.nativeEvent.offsetX,
isVisible: true,
};
} else {
const time = this.getTime(e);
const time = getTime(e);
timeLineTooltip = {
time: time,
offset: e.nativeEvent.offsetX,
@ -153,18 +112,44 @@ export default class Timeline extends React.PureComponent {
debounceTooltipChange(timeLineTooltip);
};
hideTimeTooltip = () => {
const hideTimeTooltip = () => {
const timeLineTooltip = { isVisible: false };
debounceTooltipChange(timeLineTooltip);
};
render() {
const { events, skip, skipIntervals, disabled, endTime, live, notes } = this.props;
const seekProgress = (e) => {
const time = getTime(e);
player.jump(time);
hideTimeTooltip();
};
const scale = 100 / endTime;
const loadAndSeek = async (e) => {
e.persist();
await toggleTimetravel();
return (
<div
setTimeout(() => {
seekProgress(e);
});
};
const jumpToTime = (e) => {
if (live && !liveTimeTravel) {
loadAndSeek(e);
} else {
seekProgress(e);
}
};
const getTime = (e, customEndTime) => {
const p = e.nativeEvent.offsetX / e.target.offsetWidth;
const targetTime = customEndTime || endTime;
const time = Math.max(Math.round(p * targetTime), 0);
return time;
};
return (
<div
className="flex items-center absolute w-full"
style={{
top: '-4px',
@ -176,26 +161,26 @@ export default class Timeline extends React.PureComponent {
>
<div
className={stl.progress}
onClick={disabled ? null : this.jumpToTime}
ref={this.progressRef}
onClick={disabled ? null : jumpToTime}
ref={progressRef}
role="button"
onMouseMoveCapture={this.showTimeTooltip}
onMouseEnter={this.showTimeTooltip}
onMouseLeave={this.hideTimeTooltip}
onMouseMoveCapture={showTimeTooltip}
onMouseEnter={showTimeTooltip}
onMouseLeave={hideTimeTooltip}
>
<TooltipContainer live={live} />
{/* custo color is live */}
<DraggableCircle
left={this.props.time * scale}
onDrop={this.onDragEnd}
live={this.props.live}
left={time * scale}
onDrop={onDragEnd}
live={live}
/>
<CustomDragLayer
onDrag={this.onDrag}
onDrag={onDrag}
minX={BOUNDRY}
maxX={this.progressRef.current && this.progressRef.current.offsetWidth + BOUNDRY}
maxX={progressRef.current && progressRef.current.offsetWidth + BOUNDRY}
/>
<TimeTracker scale={scale} live={this.props.live} left={this.props.time * scale} />
<TimeTracker scale={scale} live={live} left={time * scale} />
{!live && skip ?
skipIntervals.map((interval) => (
@ -208,7 +193,7 @@ export default class Timeline extends React.PureComponent {
}}
/>
)) : null}
<div className={stl.timeline} ref={this.timelineRef} />
<div className={stl.timeline} ref={timelineRef} />
{events.map((e) => (
<div
@ -235,6 +220,14 @@ export default class Timeline extends React.PureComponent {
) : null)}
</div>
</div>
);
}
)
}
export default connect(
(state) => ({
issues: state.getIn(['sessions', 'current', 'issues']),
startedAt: state.getIn(['sessions', 'current', 'startedAt']),
tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']),
}),
{ setTimelinePointer, setTimelineHoverTime }
)(observer(Timeline))

View file

@ -1,7 +1,5 @@
import React from 'react';
import { connectPlayer } from 'Player';
import { getStatusText } from 'Player';
import type { MarkedTarget } from 'Player';
import { CallingState, ConnectionStatus, RemoteControlStatus } from 'Player';
import AutoplayTimer from './Overlay/AutoplayTimer';
@ -10,45 +8,39 @@ import LiveStatusText from './Overlay/LiveStatusText';
import Loader from './Overlay/Loader';
import ElementsMarker from './Overlay/ElementsMarker';
import RequestingWindow, { WindowType } from 'App/components/Assist/RequestingWindow';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
interface Props {
playing: boolean,
completed: boolean,
inspectorMode: boolean,
loading: boolean,
live: boolean,
liveStatusText: string,
concetionStatus: ConnectionStatus,
autoplay: boolean,
markedTargets: MarkedTarget[] | null,
activeTargetIndex: number,
calling: CallingState,
remoteControl: RemoteControlStatus
nextId: string,
togglePlay: () => void,
closedLive?: boolean,
livePlay?: boolean,
}
function Overlay({
playing,
completed,
inspectorMode,
loading,
live,
liveStatusText,
concetionStatus,
autoplay,
markedTargets,
activeTargetIndex,
nextId,
togglePlay,
closedLive,
livePlay,
calling,
remoteControl,
}: Props) {
const { player, store } = React.useContext(PlayerContext)
const {
playing,
messagesLoading,
cssLoading,
completed,
autoplay,
inspectorMode,
live,
peerConnectionStatus,
markedTargets,
activeTargetIndex,
livePlay,
calling,
remoteControl,
} = store.get()
const loading = messagesLoading || cssLoading
const liveStatusText = getStatusText(peerConnectionStatus)
const concetionStatus = peerConnectionStatus
const showAutoplayTimer = !live && completed && autoplay && nextId
const showPlayIconLayer = !live && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer;
const showLiveStatusText = live && livePlay && liveStatusText && !loading;
@ -65,7 +57,7 @@ function Overlay({
}
{ loading ? <Loader /> : null }
{ showPlayIconLayer &&
<PlayIconLayer playing={playing} togglePlay={togglePlay} />
<PlayIconLayer playing={playing} togglePlay={player.togglePlay} />
}
{ markedTargets && <ElementsMarker targets={ markedTargets } activeIndex={activeTargetIndex}/>
}
@ -74,18 +66,4 @@ function Overlay({
}
export default connectPlayer(state => ({
playing: state.playing,
loading: state.messagesLoading || state.cssLoading,
completed: state.completed,
autoplay: state.autoplay,
inspectorMode: state.inspectorMode,
live: state.live,
liveStatusText: getStatusText(state.peerConnectionStatus),
concetionStatus: state.peerConnectionStatus,
markedTargets: state.markedTargets,
activeTargetIndex: state.activeTargetIndex,
livePlay: state.livePlay,
calling: state.calling,
remoteControl: state.remoteControl,
}))(Overlay);
export default observer(Overlay);

View file

@ -73,7 +73,7 @@ function Player(props) {
>
{fullscreen && <EscapeButton onClose={fullscreenOff} />}
<div className="relative flex-1 overflow-hidden">
<Overlay nextId={nextId} togglePlay={playerContext.player.togglePlay} closedLive={closedLive} />
<Overlay nextId={nextId} closedLive={closedLive} />
<div className={stl.screenWrapper} ref={screenWrapper} />
</div>
{!fullscreen && !!bottomBlock && (
@ -94,7 +94,11 @@ function Player(props) {
{bottomBlock === INSPECTOR && <Inspector />}
</div>
)}
<Controls {...playerContext.player} />
<Controls
speedDown={playerContext.player.speedDown}
speedUp={playerContext.player.speedUp}
jump={playerContext.player.jump}
/>
</div>
)
}