import React, { useEffect, useMemo, useContext, useState, useRef } from 'react'; import { connect } from 'react-redux'; import { Icon } from 'UI' import TimeTracker from './TimeTracker'; import stl from './timeline.module.css'; import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions'; import DraggableCircle from './components/DraggableCircle'; import CustomDragLayer, { OnDragCallback } from './components/CustomDragLayer'; import { debounce } from 'App/utils'; import TooltipContainer from './components/TooltipContainer'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; import { DateTime, Duration } from 'luxon'; import Issue from "Types/session/issue"; import { toJS } from 'mobx' function getTimelinePosition(value: number, scale: number) { const pos = value * scale; return pos > 100 ? 99 : pos; } interface IProps { issues: Issue[] setTimelineHoverTime: (t: number) => void startedAt: number tooltipVisible: boolean } function Timeline(props: IProps) { const { player, store } = useContext(PlayerContext) const [wasPlaying, setWasPlaying] = useState(false) const { notesStore, settingsStore } = useStore(); const { playing, time, skipIntervals, skip, skipToIssue, ready, endTime, devtoolsLoading, domLoading, tabStates, } = store.get() const { issues } = props; const notes = notesStore.sessionNotes const progressRef = useRef(null) const timelineRef = useRef(null) const events = Object.keys(tabStates).length > 0 ? Object.keys(tabStates).reduce((acc, tabId) => { return acc.concat(tabStates[tabId].eventList) }, []) : [] const scale = 100 / endTime; useEffect(() => { const firstIssue = issues[0]; if (firstIssue && skipToIssue) { player.jump(firstIssue.time); } }, []) const debouncedJump = useMemo(() => debounce(player.jump, 500), []) const debouncedTooltipChange = useMemo(() => debounce(props.setTimelineHoverTime, 50), []) const onDragEnd = () => { if (wasPlaying) { player.togglePlay(); } }; const onDrag: OnDragCallback = (offset) => { // @ts-ignore react mismatch const p = (offset.x) / progressRef.current.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); debouncedJump(time); hideTimeTooltip(); if (playing) { setWasPlaying(true) player.pause(); } }; const showTimeTooltip = (e: React.MouseEvent) => { if ( e.target !== progressRef.current && e.target !== timelineRef.current // @ts-ignore black magic && !progressRef.current.contains(e.target) ) { return props.tooltipVisible && hideTimeTooltip(); } const time = getTime(e); if (!time) return; const tz = settingsStore.sessionSettings.timezone.value const timeStr = DateTime.fromMillis(props.startedAt + time).setZone(tz).toFormat(`hh:mm:ss a`) const timeLineTooltip = { time: Duration.fromMillis(time).toFormat(`mm:ss`), timeStr, offset: e.nativeEvent.pageX, isVisible: true, }; debouncedTooltipChange(timeLineTooltip); } const hideTimeTooltip = () => { const timeLineTooltip = { isVisible: false }; debouncedTooltipChange(timeLineTooltip); }; const seekProgress = (e: React.MouseEvent) => { const time = getTime(e); player.jump(time); hideTimeTooltip(); }; const jumpToTime = (e: React.MouseEvent) => { seekProgress(e); }; const getTime = (e: React.MouseEvent, customEndTime?: number) => { // @ts-ignore react mismatch const p = e.nativeEvent.offsetX / e.target.offsetWidth; const targetTime = customEndTime || endTime; return Math.max(Math.round(p * targetTime), 0); }; return (
{skip ? skipIntervals.map((interval) => (
)) : null}
{devtoolsLoading || domLoading || !ready ?
: null}
{events.map((e) => (
))} {/* TODO: refactor and make any sense out of this */} {/* {issues.map((i: Issue) => (*/} {/* */} {/*))}*/} {notes.map((note) => note.timestamp > 0 ? (
) : null)}
) } export default connect( (state: any) => ({ issues: state.getIn(['sessions', 'current']).issues || [], startedAt: state.getIn(['sessions', 'current']).startedAt || 0, tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), }), { setTimelinePointer, setTimelineHoverTime } )(observer(Timeline))