diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 1b5b97c22..7212c35c3 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -122,6 +122,7 @@ const Header = (props) => { diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js index c142167f0..0c07b134b 100644 --- a/frontend/app/components/Session/LivePlayer.js +++ b/frontend/app/components/Session/LivePlayer.js @@ -49,7 +49,7 @@ function LivePlayer ({ }, []) const TABS = { - EVENTS: 'Events', + EVENTS: 'User Actions', HEATMAPS: 'Click Map', } const [activeTab, setActiveTab] = useState(''); diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js index 1285ac9b1..cef4d4bae 100644 --- a/frontend/app/components/Session/WebPlayer.js +++ b/frontend/app/components/Session/WebPlayer.js @@ -14,7 +14,7 @@ import styles from '../Session_/session.module.css'; import { countDaysFrom } from 'App/date'; const TABS = { - EVENTS: 'Events', + EVENTS: 'User Actions', HEATMAPS: 'Click Map', }; diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.js b/frontend/app/components/Session_/EventsBlock/EventsBlock.js index 1bc4419d4..e690ce3cc 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.js +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.js @@ -197,7 +197,7 @@ export default class EventsBlock extends React.PureComponent { setActiveTab={setActiveTab} value={query} header={ -
User Events { events.size }
+
User Actions { events.size }
} /> diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 26fda0a30..4cadd1fad 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -1,6 +1,6 @@ import { connectPlayer } from 'App/player'; import { toggleBottomBlock } from 'Duck/components/player'; -import React from 'react'; +import React, { useEffect } from 'react'; import BottomBlock from '../BottomBlock'; import EventRow from './components/EventRow'; import { TYPES } from 'Types/session/event'; @@ -10,7 +10,7 @@ import FeatureSelection from './components/FeatureSelection/FeatureSelection'; import TimelinePointer from './components/TimelinePointer'; import VerticalPointerLine from './components/VerticalPointerLine'; import cn from 'classnames'; -import VerticalLine from './components/VerticalLine'; +// import VerticalLine from './components/VerticalLine'; import OverviewPanelContainer from './components/OverviewPanelContainer'; interface Props { @@ -20,52 +20,67 @@ interface Props { toggleBottomBlock: any; stackEventList: any[]; issuesList: any[]; + performanceChartData: any; + endTime: number; } function OverviewPanel(props: Props) { - const { resourceList, exceptionsList, eventsList, stackEventList, issuesList } = props; - - const clickRageList = React.useMemo(() => { - return eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE); - }, [eventsList]); + const [dataLoaded, setDataLoaded] = React.useState(false); const [selectedFeatures, setSelectedFeatures] = React.useState(['PERFORMANCE', 'ERRORS', 'EVENTS']); const resources: any = React.useMemo(() => { + const { resourceList, exceptionsList, eventsList, stackEventList, issuesList, performanceChartData } = props; return { NETWORK: resourceList, ERRORS: exceptionsList, EVENTS: stackEventList, - CLICKRAGE: clickRageList, - PERFORMANCE: issuesList, + CLICKRAGE: eventsList.filter((item: any) => item.type === TYPES.CLICKRAGE), + PERFORMANCE: performanceChartData, }; - }, [resourceList, exceptionsList, stackEventList, clickRageList, issuesList]); + }, [dataLoaded]); + + useEffect(() => { + if (dataLoaded) { + return; + } + + if (props.resourceList.length > 0) { + setDataLoaded(true); + } + }, [props.resourceList]); return ( - - - Overview -
- -
-
- - - -
- - {selectedFeatures.map((feature: any, index: number) => ( -
- } - /> + dataLoaded && ( + + + + X-RAY +
+ +
+
+ + + +
+ + {selectedFeatures.map((feature: any, index: number) => ( +
+ } + endTime={props.endTime} + /> +
+ ))}
- ))} -
- - - + + + + + ) ); } @@ -82,5 +97,12 @@ export default connect( exceptionsList: state.exceptionsList, eventsList: state.eventList, stackEventList: state.stackList, + performanceChartData: state.performanceChartData, + endTime: state.endTime, + // endTime: 30000000, }))(OverviewPanel) ); + +const Wrapper = React.memo((props: any) => { + return
{props.children}
; +}); diff --git a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx index cff0c6182..ee7bcb857 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/EventRow/EventRow.tsx @@ -1,43 +1,48 @@ import React from 'react'; -import cn from 'classnames' +import cn from 'classnames'; import { getTimelinePosition } from 'App/utils'; import { connectPlayer } from 'App/player'; - +import PerformanceGraph from '../PerformanceGraph'; interface Props { list?: any[]; title: string; className?: string; endTime?: number; renderElement?: (item: any) => React.ReactNode; + isGraph?: boolean; } const EventRow = React.memo((props: Props) => { - const { title, className, list = [], endTime = 0 } = props; + const { title, className, list = [], endTime = 0, isGraph = false } = props; const scale = 100 / endTime; - const _list = React.useMemo(() => { - return list.map((item: any, _index: number) => { - return { - ...item.toJS(), - left: getTimelinePosition(item.time, scale), - } - }) - }, [list]); + const _list = + !isGraph && + React.useMemo(() => { + return list.map((item: any, _index: number) => { + return { + ...item.toJS(), + left: getTimelinePosition(item.time, scale), + }; + }); + }, [list]); + return ( -
-
{title}
-
- {_list.map((item: any, index: number) => { - return ( -
- {props.renderElement ? props.renderElement(item) : null} -
- ) - } - )} +
+
{title}
+
+ {isGraph ? ( + + ) : ( + _list.map((item: any, index: number) => { + return ( +
+ {props.renderElement ? props.renderElement(item) : null} +
+ ); + }) + )}
); }); -export default connectPlayer((state: any) => ({ - endTime: state.endTime, -}))(EventRow); \ No newline at end of file +export default EventRow; diff --git a/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx index 979e8bb53..5a898c67e 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx @@ -7,29 +7,31 @@ interface Props { endTime: number; } -function OverviewPanelContainer(props: Props) { +const OverviewPanelContainer = React.memo((props: Props) => { const { endTime } = props; const [mouseX, setMouseX] = React.useState(0); const [mouseIn, setMouseIn] = React.useState(false); const onClickTrack = (e: any) => { const p = e.nativeEvent.offsetX / e.target.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); - Controls.jump(time); + if (time) { + Controls.jump(time); + } }; - const onMouseMoveCapture = (e: any) => { - if (!mouseIn) { - return; - } - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - setMouseX(p * 100); - }; + // const onMouseMoveCapture = (e: any) => { + // if (!mouseIn) { + // return; + // } + // const p = e.nativeEvent.offsetX / e.target.offsetWidth; + // setMouseX(p * 100); + // }; return (
setMouseIn(true)} // onMouseOut={() => setMouseIn(false)} > @@ -37,8 +39,10 @@ function OverviewPanelContainer(props: Props) {
{props.children}
); -} +}); -export default connectPlayer((state: any) => ({ - endTime: state.endTime, -}))(OverviewPanelContainer); +export default OverviewPanelContainer; + +// export default connectPlayer((state: any) => ({ +// endTime: state.endTime, +// }))(OverviewPanelContainer); diff --git a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx new file mode 100644 index 000000000..28193cd10 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { connectPlayer } from 'App/player'; +import { AreaChart, Area, Tooltip, ResponsiveContainer } from 'recharts'; + +interface Props { + list: any; +} +const PerformanceGraph = React.memo((props: Props) => { + const { list } = props; + + const finalValues = React.useMemo(() => { + const cpuMax = list.reduce((acc: number, item: any) => { + return Math.max(acc, item.cpu); + }, 0); + const cpuMin = list.reduce((acc: number, item: any) => { + return Math.min(acc, item.cpu); + }, Infinity); + + const memoryMin = list.reduce((acc: number, item: any) => { + return Math.min(acc, item.usedHeap); + }, Infinity); + const memoryMax = list.reduce((acc: number, item: any) => { + return Math.max(acc, item.usedHeap); + }, 0); + + const convertToPercentage = (val: number, max: number, min: number) => { + return ((val - min) / (max - min)) * 100; + }; + const cpuValues = list.map((item: any) => convertToPercentage(item.cpu, cpuMax, cpuMin)); + const memoryValues = list.map((item: any) => convertToPercentage(item.usedHeap, memoryMax, memoryMin)); + const mergeArraysWithMaxNumber = (arr1: any[], arr2: any[]) => { + const maxLength = Math.max(arr1.length, arr2.length); + const result = []; + for (let i = 0; i < maxLength; i++) { + const num = Math.round(Math.max(arr1[i] || 0, arr2[i] || 0)); + result.push(num > 60 ? num : 1); + } + return result; + }; + const finalValues = mergeArraysWithMaxNumber(cpuValues, memoryValues); + return finalValues; + }, []); + + const data = list.map((item: any, index: number) => { + return { + time: item.time, + cpu: finalValues[index], + }; + }); + + return ( + + + + + + + + + {/* */} + + + + ); +}); + +export default PerformanceGraph; diff --git a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/index.ts b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/index.ts new file mode 100644 index 000000000..2c5c88675 --- /dev/null +++ b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/index.ts @@ -0,0 +1 @@ +export { default } from './PerformanceGraph'; \ No newline at end of file diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx index c3cf175dd..6e45b5e99 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx @@ -45,7 +45,7 @@ const TimelinePointer = React.memo((props: Props) => { position="top" >
-
+
); @@ -92,7 +92,7 @@ const TimelinePointer = React.memo((props: Props) => { - {item.name} + {item.type}
} delay={0} diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx index e0b4be0b7..3b7fc453e 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx @@ -1,14 +1,60 @@ import React from 'react'; +import { connectPlayer } from 'App/player'; +import { millisToMinutesAndSeconds } from 'App/utils'; interface Props { - + endTime: number; } function TimelineScale(props: Props) { - return ( -
- -
- ); + const { endTime } = props; + const scaleRef = React.useRef(null); + const gap = 60; + + const drawScale = (container: any) => { + const width = container.offsetWidth; + const part = Math.round(width / gap); + container.replaceChildren(); + for (var i = 0; i < part; i++) { + const txt = millisToMinutesAndSeconds(i * (endTime / part)); + const el = document.createElement('div'); + // el.style.height = '10px'; + // el.style.width = '1px'; + // el.style.backgroundColor = '#ccc'; + el.style.position = 'absolute'; + el.style.left = `${i * gap}px`; + el.style.paddingTop = '1px'; + el.style.opacity = '0.8'; + el.innerHTML = txt + ''; + el.style.fontSize = '12px'; + el.style.color = 'white'; + + container.appendChild(el); + } + }; + + React.useEffect(() => { + if (!scaleRef.current) { + return; + } + + drawScale(scaleRef.current); + + // const resize = () => drawScale(scaleRef.current); + + // window.addEventListener('resize', resize); + // return () => { + // window.removeEventListener('resize', resize); + // }; + }, [scaleRef]); + return ( +
+ {/*
*/} +
+ ); } -export default TimelineScale; \ No newline at end of file +export default TimelineScale; + +// export default connectPlayer((state: any) => ({ +// endTime: state.endTime, +// }))(TimelineScale); diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index e2197e6b3..ec0fa4ab3 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -1,344 +1,342 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; -import { - connectPlayer, - STORAGE_TYPES, - selectStorageType, - selectStorageListNow, -} from 'Player/store'; +import { connectPlayer, STORAGE_TYPES, selectStorageType, selectStorageListNow } from 'Player/store'; import LiveTag from 'Shared/LiveTag'; -import { session as sessionRoute, withSiteId } from 'App/routes'; -import { - toggleTimetravel, - jumpToLive, -} from 'Player'; +import { toggleTimetravel, jumpToLive } from 'Player'; -import { Icon } from 'UI'; +import { Icon, Button } from 'UI'; import { toggleInspectorMode } from 'Player'; import { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - OVERVIEW, - CONSOLE, - NETWORK, - STACKEVENTS, - STORAGE, - PROFILER, - PERFORMANCE, - GRAPHQL, - FETCH, - EXCEPTIONS, - INSPECTOR, + fullscreenOn, + fullscreenOff, + toggleBottomBlock, + OVERVIEW, + CONSOLE, + NETWORK, + STACKEVENTS, + STORAGE, + PROFILER, + PERFORMANCE, + GRAPHQL, + FETCH, + EXCEPTIONS, + INSPECTOR, } from 'Duck/components/player'; import { ReduxTime, AssistDuration } from './Time'; import Timeline from './Timeline'; import ControlButton from './ControlButton'; -import PlayerControls from './components/PlayerControls' +import PlayerControls from './components/PlayerControls'; import styles from './controls.module.css'; import { Tooltip } from 'react-tippy'; - +import XRayButton from 'Shared/XRayButton'; function getStorageIconName(type) { - switch(type) { - case STORAGE_TYPES.REDUX: - return "vendors/redux"; - case STORAGE_TYPES.MOBX: - return "vendors/mobx" - case STORAGE_TYPES.VUEX: - return "vendors/vuex"; - case STORAGE_TYPES.NGRX: - return "vendors/ngrx"; - case STORAGE_TYPES.NONE: - return "store" - } + switch (type) { + case STORAGE_TYPES.REDUX: + return 'vendors/redux'; + case STORAGE_TYPES.MOBX: + return 'vendors/mobx'; + case STORAGE_TYPES.VUEX: + return 'vendors/vuex'; + case STORAGE_TYPES.NGRX: + return 'vendors/ngrx'; + case STORAGE_TYPES.NONE: + return 'store'; + } } function getStorageName(type) { - switch(type) { - case STORAGE_TYPES.REDUX: - return "REDUX"; - case STORAGE_TYPES.MOBX: - return "MOBX"; - case STORAGE_TYPES.VUEX: - return "VUEX"; - case STORAGE_TYPES.NGRX: - return "NGRX"; - case STORAGE_TYPES.NONE: - return "STATE"; - } + switch (type) { + case STORAGE_TYPES.REDUX: + return 'REDUX'; + case STORAGE_TYPES.MOBX: + return 'MOBX'; + case STORAGE_TYPES.VUEX: + return 'VUEX'; + case STORAGE_TYPES.NGRX: + return 'NGRX'; + case STORAGE_TYPES.NONE: + return 'STATE'; + } } -@connectPlayer(state => ({ - time: state.time, - endTime: state.endTime, - live: state.live, - livePlay: state.livePlay, - playing: state.playing, - completed: state.completed, - skip: state.skip, - skipToIssue: state.skipToIssue, - speed: state.speed, - disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, - inspectorMode: state.inspectorMode, - fullscreenDisabled: state.messagesLoading, - logCount: state.logListNow.length, - logRedCount: state.logRedCountNow, - resourceRedCount: state.resourceRedCountNow, - fetchRedCount: state.fetchRedCountNow, - showStack: state.stackList.length > 0, - stackCount: state.stackListNow.length, - stackRedCount: state.stackRedCountNow, - profilesCount: state.profilesListNow.length, - storageCount: selectStorageListNow(state).length, - storageType: selectStorageType(state), - showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE, - showProfiler: state.profilesList.length > 0, - showGraphql: state.graphqlList.length > 0, - showFetch: state.fetchCount > 0, - fetchCount: state.fetchCountNow, - graphqlCount: state.graphqlListNow.length, - exceptionsCount: state.exceptionsListNow.length, - showExceptions: state.exceptionsList.length > 0, - showLongtasks: state.longtasksList.length > 0, - liveTimeTravel: state.liveTimeTravel, +@connectPlayer((state) => ({ + time: state.time, + endTime: state.endTime, + live: state.live, + livePlay: state.livePlay, + playing: state.playing, + completed: state.completed, + skip: state.skip, + skipToIssue: state.skipToIssue, + speed: state.speed, + disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, + inspectorMode: state.inspectorMode, + fullscreenDisabled: state.messagesLoading, + logCount: state.logListNow.length, + logRedCount: state.logRedCountNow, + resourceRedCount: state.resourceRedCountNow, + fetchRedCount: state.fetchRedCountNow, + showStack: state.stackList.length > 0, + stackCount: state.stackListNow.length, + stackRedCount: state.stackRedCountNow, + profilesCount: state.profilesListNow.length, + storageCount: selectStorageListNow(state).length, + storageType: selectStorageType(state), + showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE, + showProfiler: state.profilesList.length > 0, + showGraphql: state.graphqlList.length > 0, + showFetch: state.fetchCount > 0, + fetchCount: state.fetchCountNow, + graphqlCount: state.graphqlListNow.length, + exceptionsCount: state.exceptionsListNow.length, + showExceptions: state.exceptionsList.length > 0, + showLongtasks: state.longtasksList.length > 0, + liveTimeTravel: state.liveTimeTravel, })) -@connect((state, props) => { - const permissions = state.getIn([ 'user', 'account', 'permissions' ]) || []; - const isEnterprise = state.getIn([ 'user', 'account', 'edition' ]) === 'ee'; - return { - disabled: props.disabled || (isEnterprise && !permissions.includes('DEV_TOOLS')), - fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]), - bottomBlock: state.getIn([ 'components', 'player', 'bottomBlock' ]), - showStorage: props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']), - showStack: props.showStack || !state.getIn(['components', 'player', 'hiddenHints', 'stack']), - closedLive: !!state.getIn([ 'sessions', 'errors' ]) || !state.getIn([ 'sessions', 'current', 'live' ]), - } -}, { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, -}) +@connect( + (state, props) => { + const permissions = state.getIn(['user', 'account', 'permissions']) || []; + const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee'; + return { + disabled: props.disabled || (isEnterprise && !permissions.includes('DEV_TOOLS')), + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), + showStorage: props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']), + showStack: props.showStack || !state.getIn(['components', 'player', 'hiddenHints', 'stack']), + closedLive: !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']), + }; + }, + { + fullscreenOn, + fullscreenOff, + toggleBottomBlock, + } +) export default class Controls extends React.Component { - - componentDidMount() { - document.addEventListener('keydown', this.onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeyDown); - //this.props.toggleInspectorMode(false); - } - - shouldComponentUpdate(nextProps) { - if ( - nextProps.fullscreen !== this.props.fullscreen || - nextProps.bottomBlock !== this.props.bottomBlock || - nextProps.live !== this.props.live || - nextProps.livePlay !== this.props.livePlay || - nextProps.playing !== this.props.playing || - nextProps.completed !== this.props.completed || - nextProps.skip !== this.props.skip || - nextProps.skipToIssue !== this.props.skipToIssue || - nextProps.speed !== this.props.speed || - nextProps.disabled !== this.props.disabled || - nextProps.fullscreenDisabled !== this.props.fullscreenDisabled || - // nextProps.inspectorMode !== this.props.inspectorMode || - nextProps.logCount !== this.props.logCount || - nextProps.logRedCount !== this.props.logRedCount || - nextProps.resourceRedCount !== this.props.resourceRedCount || - nextProps.fetchRedCount !== this.props.fetchRedCount || - nextProps.showStack !== this.props.showStack || - nextProps.stackCount !== this.props.stackCount || - nextProps.stackRedCount !== this.props.stackRedCount || - nextProps.profilesCount !== this.props.profilesCount || - nextProps.storageCount !== this.props.storageCount || - nextProps.storageType !== this.props.storageType || - nextProps.showStorage !== this.props.showStorage || - nextProps.showProfiler !== this.props.showProfiler || - nextProps.showGraphql !== this.props.showGraphql || - nextProps.showFetch !== this.props.showFetch || - nextProps.fetchCount !== this.props.fetchCount || - nextProps.graphqlCount !== this.props.graphqlCount || - nextProps.showExceptions !== this.props.showExceptions || - nextProps.exceptionsCount !== this.props.exceptionsCount || - nextProps.showLongtasks !== this.props.showLongtasks || - nextProps.liveTimeTravel !== this.props.liveTimeTravel - ) return true; - return false; - } - - onKeyDown = (e) => { - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { - return; + componentDidMount() { + document.addEventListener('keydown', this.onKeyDown); } - if (this.props.inspectorMode) { - if (e.key === 'Esc' || e.key === 'Escape') { - toggleInspectorMode(false); - } + + componentWillUnmount() { + document.removeEventListener('keydown', this.onKeyDown); + //this.props.toggleInspectorMode(false); + } + + shouldComponentUpdate(nextProps) { + if ( + nextProps.fullscreen !== this.props.fullscreen || + nextProps.bottomBlock !== this.props.bottomBlock || + nextProps.live !== this.props.live || + nextProps.livePlay !== this.props.livePlay || + nextProps.playing !== this.props.playing || + nextProps.completed !== this.props.completed || + nextProps.skip !== this.props.skip || + nextProps.skipToIssue !== this.props.skipToIssue || + nextProps.speed !== this.props.speed || + nextProps.disabled !== this.props.disabled || + nextProps.fullscreenDisabled !== this.props.fullscreenDisabled || + // nextProps.inspectorMode !== this.props.inspectorMode || + nextProps.logCount !== this.props.logCount || + nextProps.logRedCount !== this.props.logRedCount || + nextProps.resourceRedCount !== this.props.resourceRedCount || + nextProps.fetchRedCount !== this.props.fetchRedCount || + nextProps.showStack !== this.props.showStack || + nextProps.stackCount !== this.props.stackCount || + nextProps.stackRedCount !== this.props.stackRedCount || + nextProps.profilesCount !== this.props.profilesCount || + nextProps.storageCount !== this.props.storageCount || + nextProps.storageType !== this.props.storageType || + nextProps.showStorage !== this.props.showStorage || + nextProps.showProfiler !== this.props.showProfiler || + nextProps.showGraphql !== this.props.showGraphql || + nextProps.showFetch !== this.props.showFetch || + nextProps.fetchCount !== this.props.fetchCount || + nextProps.graphqlCount !== this.props.graphqlCount || + nextProps.showExceptions !== this.props.showExceptions || + nextProps.exceptionsCount !== this.props.exceptionsCount || + nextProps.showLongtasks !== this.props.showLongtasks || + nextProps.liveTimeTravel !== this.props.liveTimeTravel + ) + return true; + return false; + } + + onKeyDown = (e) => { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + return; + } + if (this.props.inspectorMode) { + if (e.key === 'Esc' || e.key === 'Escape') { + toggleInspectorMode(false); + } + } + // if (e.key === ' ') { + // document.activeElement.blur(); + // this.props.togglePlay(); + // } + if (e.key === 'Esc' || e.key === 'Escape') { + this.props.fullscreenOff(); + } + if (e.key === 'ArrowRight') { + this.forthTenSeconds(); + } + if (e.key === 'ArrowLeft') { + this.backTenSeconds(); + } + if (e.key === 'ArrowDown') { + this.props.speedDown(); + } + if (e.key === 'ArrowUp') { + this.props.speedUp(); + } }; - // if (e.key === ' ') { - // document.activeElement.blur(); - // this.props.togglePlay(); - // } - if (e.key === 'Esc' || e.key === 'Escape') { - this.props.fullscreenOff(); - } - if (e.key === "ArrowRight") { - this.forthTenSeconds(); - } - if (e.key === "ArrowLeft") { - this.backTenSeconds(); - } - if (e.key === "ArrowDown") { - this.props.speedDown(); - } - if (e.key === "ArrowUp") { - this.props.speedUp(); - } - } - forthTenSeconds = () => { - const { time, endTime, jump } = this.props; - jump(Math.min(endTime, time + 1e4)) - } + forthTenSeconds = () => { + const { time, endTime, jump } = this.props; + jump(Math.min(endTime, time + 1e4)); + }; - backTenSeconds = () => { //shouldComponentUpdate - const { time, jump } = this.props; - jump(Math.max(0, time - 1e4)); - } + backTenSeconds = () => { + //shouldComponentUpdate + const { time, jump } = this.props; + jump(Math.max(0, time - 1e4)); + }; - goLive =() => this.props.jump(this.props.endTime) + goLive = () => this.props.jump(this.props.endTime); - renderPlayBtn = () => { - const { completed, playing } = this.props; - let label; - let icon; - if (completed) { - icon = 'arrow-clockwise'; - label = 'Replay this session' - } else if (playing) { - icon = 'pause-fill'; - label = 'Pause'; - } else { - icon = 'play-fill-new'; - label = 'Pause'; - label = 'Play' - } + renderPlayBtn = () => { + const { completed, playing } = this.props; + let label; + let icon; + if (completed) { + icon = 'arrow-clockwise'; + label = 'Replay this session'; + } else if (playing) { + icon = 'pause-fill'; + label = 'Pause'; + } else { + icon = 'play-fill-new'; + label = 'Pause'; + label = 'Play'; + } - return ( - -
- -
-
- ) - } - - controlIcon = (icon, size, action, isBackwards, additionalClasses) => -
- -
- - render() { - const { - bottomBlock, - toggleBottomBlock, - live, - livePlay, - skip, - speed, - disabled, - logCount, - logRedCount, - resourceRedCount, - fetchRedCount, - showStack, - stackCount, - stackRedCount, - profilesCount, - storageCount, - showStorage, - storageType, - showProfiler, - showGraphql, - showFetch, - fetchCount, - graphqlCount, - exceptionsCount, - showExceptions, - fullscreen, - inspectorMode, - closedLive, - toggleSpeed, - toggleSkip, - liveTimeTravel, - } = this.props; - - const toggleBottomTools = (blockName) => { - if (blockName === INSPECTOR) { - toggleInspectorMode(); - bottomBlock && toggleBottomBlock(); - } else { - toggleInspectorMode(false); - toggleBottomBlock(blockName); - } - } - - return ( -
- { !live || liveTimeTravel ? : null} - { !fullscreen && -
-
- {!live && ( - - )} - - { live && !closedLive && ( -
- livePlay ? null : jumpToLive()} /> -
- - {!liveTimeTravel && ( -
- See Past Activity -
- )} + return ( + +
+
- )} -
+ + ); + }; -
- { !live &&
} - {/* ! TEMP DISABLED ! + controlIcon = (icon, size, action, isBackwards, additionalClasses) => ( +
+ +
+ ); + + render() { + const { + bottomBlock, + toggleBottomBlock, + live, + livePlay, + skip, + speed, + disabled, + logCount, + logRedCount, + resourceRedCount, + fetchRedCount, + showStack, + stackCount, + stackRedCount, + profilesCount, + storageCount, + showStorage, + storageType, + showProfiler, + showGraphql, + showFetch, + fetchCount, + graphqlCount, + exceptionsCount, + showExceptions, + fullscreen, + inspectorMode, + closedLive, + toggleSpeed, + toggleSkip, + liveTimeTravel, + } = this.props; + + const toggleBottomTools = (blockName) => { + if (blockName === INSPECTOR) { + toggleInspectorMode(); + bottomBlock && toggleBottomBlock(); + } else { + toggleInspectorMode(false); + toggleBottomBlock(blockName); + } + }; + + return ( +
+ {!live || liveTimeTravel ? ( + + ) : null} + {!fullscreen && ( +
+
+ {!live && ( + <> + + {/* */} +
+ toggleBottomTools(OVERVIEW)} /> + + )} + + {live && !closedLive && ( +
+ (livePlay ? null : jumpToLive())} /> +
+ +
+ + {!liveTimeTravel && ( +
+ See Past Activity +
+ )} +
+ )} +
+ +
+ {/* { !live &&
} */} + {/* ! TEMP DISABLED ! {!live && ( )} */} - toggleBottomTools(OVERVIEW) } active={ bottomBlock === OVERVIEW && !inspectorMode} @@ -360,132 +358,132 @@ export default class Controls extends React.Component { // count={ logCount } // hasErrors={ logRedCount > 0 } containerClassName="mx-2" - /> - toggleBottomTools(CONSOLE) } - active={ bottomBlock === CONSOLE && !inspectorMode} - label="CONSOLE" - noIcon - labelClassName="!text-base font-semibold" - count={ logCount } - hasErrors={ logRedCount > 0 } - containerClassName="mx-2" - /> - { !live && - toggleBottomTools(NETWORK) } - active={ bottomBlock === NETWORK && !inspectorMode } - label="NETWORK" - hasErrors={ resourceRedCount > 0 } - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - {!live && - toggleBottomTools(PERFORMANCE) } - active={ bottomBlock === PERFORMANCE && !inspectorMode } - label="PERFORMANCE" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - {showFetch && - toggleBottomTools(FETCH) } - active={ bottomBlock === FETCH && !inspectorMode } - hasErrors={ fetchRedCount > 0 } - count={ fetchCount } - label="FETCH" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - { !live && showGraphql && - toggleBottomTools(GRAPHQL) } - active={ bottomBlock === GRAPHQL && !inspectorMode } - count={ graphqlCount } - label="GRAPHQL" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - { !live && showStorage && - toggleBottomTools(STORAGE) } - active={ bottomBlock === STORAGE && !inspectorMode } - count={ storageCount } - label={ getStorageName(storageType) } - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - { showExceptions && - toggleBottomTools(EXCEPTIONS) } - active={ bottomBlock === EXCEPTIONS && !inspectorMode } - label="EXCEPTIONS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - count={ exceptionsCount } - hasErrors={ exceptionsCount > 0 } - /> - } - { !live && showStack && - toggleBottomTools(STACKEVENTS) } - active={ bottomBlock === STACKEVENTS && !inspectorMode } - label="EVENTS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - count={ stackCount } - hasErrors={ stackRedCount > 0 } - /> - } - { !live && showProfiler && - toggleBottomTools(PROFILER) } - active={ bottomBlock === PROFILER && !inspectorMode } - count={ profilesCount } - label="PROFILER" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - } - { !live &&
} - { !live && ( - - {this.controlIcon("arrows-angle-extend", 18, this.props.fullscreenOn, false, "rounded hover:bg-gray-light-shade color-gray-medium")} - - ) - } + /> */} + toggleBottomTools(CONSOLE)} + active={bottomBlock === CONSOLE && !inspectorMode} + label="CONSOLE" + noIcon + labelClassName="!text-base font-semibold" + count={logCount} + hasErrors={logRedCount > 0} + containerClassName="mx-2" + /> + {!live && ( + toggleBottomTools(NETWORK)} + active={bottomBlock === NETWORK && !inspectorMode} + label="NETWORK" + hasErrors={resourceRedCount > 0} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && ( + toggleBottomTools(PERFORMANCE)} + active={bottomBlock === PERFORMANCE && !inspectorMode} + label="PERFORMANCE" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {showFetch && ( + toggleBottomTools(FETCH)} + active={bottomBlock === FETCH && !inspectorMode} + hasErrors={fetchRedCount > 0} + count={fetchCount} + label="FETCH" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && showGraphql && ( + toggleBottomTools(GRAPHQL)} + active={bottomBlock === GRAPHQL && !inspectorMode} + count={graphqlCount} + label="GRAPHQL" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && showStorage && ( + toggleBottomTools(STORAGE)} + active={bottomBlock === STORAGE && !inspectorMode} + count={storageCount} + label={getStorageName(storageType)} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {showExceptions && ( + toggleBottomTools(EXCEPTIONS)} + active={bottomBlock === EXCEPTIONS && !inspectorMode} + label="EXCEPTIONS" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + count={exceptionsCount} + hasErrors={exceptionsCount > 0} + /> + )} + {!live && showStack && ( + toggleBottomTools(STACKEVENTS)} + active={bottomBlock === STACKEVENTS && !inspectorMode} + label="EVENTS" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + count={stackCount} + hasErrors={stackRedCount > 0} + /> + )} + {!live && showProfiler && ( + toggleBottomTools(PROFILER)} + active={bottomBlock === PROFILER && !inspectorMode} + count={profilesCount} + label="PROFILER" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live &&
} + {!live && ( + + {this.controlIcon( + 'arrows-angle-extend', + 18, + this.props.fullscreenOn, + false, + 'rounded hover:bg-gray-light-shade color-gray-medium' + )} + + )} +
+
+ )}
-
- } -
- ); - } + ); + } } diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index c177c14e0..5ae8d67d2 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -13,393 +13,392 @@ import { debounce } from 'App/utils'; import { Tooltip } from 'react-tippy'; import TooltipContainer from './components/TooltipContainer'; -const BOUNDRY = 15 +const BOUNDRY = 0; function getTimelinePosition(value, scale) { - const pos = value * scale; + const pos = value * scale; - return pos > 100 ? 100 : pos; + return pos > 100 ? 100 : pos; } const getPointerIcon = (type) => { - // exception, - switch(type) { - case 'fetch': - return 'funnel/file-earmark-minus-fill'; - case 'exception': - return 'funnel/exclamation-circle-fill'; - case 'log': - return 'funnel/exclamation-circle-fill'; - case 'stack': - return 'funnel/patch-exclamation-fill'; - case 'resource': - return 'funnel/file-earmark-minus-fill'; + // exception, + switch (type) { + case 'fetch': + return 'funnel/file-earmark-minus-fill'; + case 'exception': + return 'funnel/exclamation-circle-fill'; + case 'log': + return 'funnel/exclamation-circle-fill'; + case 'stack': + return 'funnel/patch-exclamation-fill'; + case 'resource': + return 'funnel/file-earmark-minus-fill'; - case 'dead_click': - return 'funnel/dizzy'; - case 'click_rage': - return 'funnel/dizzy'; - case 'excessive_scrolling': - return 'funnel/mouse'; - case 'bad_request': - return 'funnel/file-medical-alt'; - case 'missing_resource': - return 'funnel/file-earmark-minus-fill'; - case 'memory': - return 'funnel/sd-card'; - case 'cpu': - return 'funnel/microchip'; - case 'slow_resource': - return 'funnel/hourglass-top'; - case 'slow_page_load': - return 'funnel/hourglass-top'; - case 'crash': - return 'funnel/file-exclamation'; - case 'js_exception': - return 'funnel/exclamation-circle-fill'; - } - - return 'info'; -} + case 'dead_click': + return 'funnel/dizzy'; + case 'click_rage': + return 'funnel/dizzy'; + case 'excessive_scrolling': + return 'funnel/mouse'; + case 'bad_request': + return 'funnel/file-medical-alt'; + case 'missing_resource': + return 'funnel/file-earmark-minus-fill'; + case 'memory': + return 'funnel/sd-card'; + case 'cpu': + return 'funnel/microchip'; + case 'slow_resource': + return 'funnel/hourglass-top'; + case 'slow_page_load': + return 'funnel/hourglass-top'; + case 'crash': + return 'funnel/file-exclamation'; + case 'js_exception': + return 'funnel/exclamation-circle-fill'; + } + return 'info'; +}; let deboucneJump = () => null; let debounceTooltipChange = () => null; -@connectPlayer(state => ({ - playing: state.playing, - time: state.time, - skipIntervals: state.skipIntervals, - events: state.eventList, - skip: state.skip, - // not updating properly rn - // skipToIssue: state.skipToIssue, - disabled: state.cssLoading || state.messagesLoading || state.markedTargets, - endTime: state.endTime, - live: state.live, - logList: state.logList, - exceptionsList: state.exceptionsList, - resourceList: state.resourceList, - stackList: state.stackList, - fetchList: state.fetchList, +@connectPlayer((state) => ({ + playing: state.playing, + time: state.time, + skipIntervals: state.skipIntervals, + events: state.eventList, + skip: state.skip, + // not updating properly rn + // skipToIssue: state.skipToIssue, + disabled: state.cssLoading || state.messagesLoading || state.markedTargets, + endTime: state.endTime, + live: state.live, + logList: state.logList, + exceptionsList: state.exceptionsList, + resourceList: state.resourceList, + stackList: state.stackList, + fetchList: state.fetchList, })) -@connect(state => ({ - issues: state.getIn([ 'sessions', 'current', 'issues' ]), - clickRageTime: state.getIn([ 'sessions', 'current', 'clickRage' ]) && - state.getIn([ 'sessions', 'current', 'clickRageTime' ]), - returningLocationTime: state.getIn([ 'sessions', 'current', 'returningLocation' ]) && - state.getIn([ 'sessions', 'current', 'returningLocationTime' ]), - tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']) -}), { setTimelinePointer, setTimelineHoverTime }) +@connect( + (state) => ({ + issues: state.getIn(['sessions', 'current', 'issues']), + clickRageTime: state.getIn(['sessions', 'current', 'clickRage']) && state.getIn(['sessions', 'current', 'clickRageTime']), + returningLocationTime: + state.getIn(['sessions', 'current', 'returningLocation']) && state.getIn(['sessions', 'current', 'returningLocationTime']), + tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), + }), + { setTimelinePointer, setTimelineHoverTime } +) export default class Timeline extends React.PureComponent { - progressRef = React.createRef() - timelineRef = React.createRef() - wasPlaying = false + progressRef = React.createRef(); + timelineRef = React.createRef(); + wasPlaying = false; - seekProgress = (e) => { - const time = this.getTime(e) - this.props.jump(time); - this.hideTimeTooltip() - } + seekProgress = (e) => { + const time = this.getTime(e); + this.props.jump(time); + this.hideTimeTooltip(); + }; - getTime = (e) => { - const { endTime } = this.props; - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - const time = Math.max(Math.round(p * endTime), 0); + getTime = (e) => { + const { endTime } = this.props; + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); - return time - } + return time; + }; - createEventClickHandler = pointer => (e) => { - e.stopPropagation(); - this.props.jump(pointer.time); - this.props.setTimelinePointer(pointer); - } + createEventClickHandler = (pointer) => (e) => { + e.stopPropagation(); + this.props.jump(pointer.time); + this.props.setTimelinePointer(pointer); + }; - componentDidMount() { - const { issues } = this.props; - const skipToIssue = Controls.updateSkipToIssue(); - const firstIssue = issues.get(0); - deboucneJump = debounce(this.props.jump, 500); - debounceTooltipChange = debounce(this.props.setTimelineHoverTime, 50); + componentDidMount() { + const { issues } = this.props; + const skipToIssue = Controls.updateSkipToIssue(); + const firstIssue = issues.get(0); + deboucneJump = debounce(this.props.jump, 500); + debounceTooltipChange = debounce(this.props.setTimelineHoverTime, 50); - if (firstIssue && skipToIssue) { - this.props.jump(firstIssue.time); + if (firstIssue && skipToIssue) { + this.props.jump(firstIssue.time); + } } - } - onDragEnd = () => { - if (this.wasPlaying) { - this.props.togglePlay(); + onDragEnd = () => { + if (this.wasPlaying) { + this.props.togglePlay(); + } + }; + + onDrag = (offset) => { + const { endTime } = this.props; + + const p = (offset.x - BOUNDRY) / this.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(); + } + }; + + showTimeTooltip = (e) => { + if (e.target !== this.progressRef.current && e.target !== this.timelineRef.current) { + return this.props.tooltipVisible && this.hideTimeTooltip(); + } + const time = this.getTime(e); + const { endTime, liveTimeTravel } = this.props; + + const timeLineTooltip = { + time: liveTimeTravel ? endTime - time : time, + offset: e.nativeEvent.offsetX, + isVisible: true, + }; + debounceTooltipChange(timeLineTooltip); + }; + + hideTimeTooltip = () => { + const timeLineTooltip = { isVisible: false }; + debounceTooltipChange(timeLineTooltip); + }; + + render() { + const { + events, + skip, + skipIntervals, + disabled, + endTime, + exceptionsList, + resourceList, + clickRageTime, + stackList, + fetchList, + issues, + liveTimeTravel, + } = this.props; + + const scale = 100 / endTime; + + return ( +
+
+ + {/* custo color is live */} + + + + + {skip && + skipIntervals.map((interval) => ( +
+ ))} +
+ + {events.map((e) => ( +
+ ))} + {/* {issues.map((iss) => ( +
+ + {iss.name} +
+ } + > + + +
+ ))} + {events + .filter((e) => e.type === TYPES.CLICKRAGE) + .map((e) => ( +
+ + {'Click Rage'} +
+ } + > + + +
+ ))} + {typeof clickRageTime === 'number' && ( +
+ + {'Click Rage'} +
+ } + > + + +
+ )} + {exceptionsList.map((e) => ( +
+ + {'Exception'} +
+ {e.message} +
+ } + > + + +
+ ))} + {resourceList + .filter((r) => r.isRed() || r.isYellow()) + .map((r) => ( +
+ + {r.success ? 'Slow resource: ' : 'Missing resource:'} +
+ {r.name} +
+ } + > + + +
+ ))} + {fetchList + .filter((e) => e.isRed()) + .map((e) => ( +
+ + Failed Fetch +
+ {e.name} +
+ } + > + + +
+ ))} + {stackList + .filter((e) => e.isRed()) + .map((e) => ( +
+ + Stack Event +
+ {e.name} +
+ } + > + + +
+ ))} */} +
+
+ ); } - } - - onDrag = (offset) => { - const { endTime } = this.props; - - const p = (offset.x - BOUNDRY) / this.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(); - } - } - - showTimeTooltip = (e) => { - if (e.target !== this.progressRef.current && e.target !== this.timelineRef.current) { - return this.props.tooltipVisible && this.hideTimeTooltip() - } - const time = this.getTime(e); - const { endTime, liveTimeTravel } = this.props; - - const timeLineTooltip = { - time: liveTimeTravel ? endTime - time : time, - offset: e.nativeEvent.offsetX, - isVisible: true - } - debounceTooltipChange(timeLineTooltip) - } - - hideTimeTooltip = () => { - const timeLineTooltip = { isVisible: false } - debounceTooltipChange(timeLineTooltip) - } - - render() { - const { - events, - skip, - skipIntervals, - disabled, - endTime, - exceptionsList, - resourceList, - clickRageTime, - stackList, - fetchList, - issues, - liveTimeTravel, - } = this.props; - - const scale = 100 / endTime; - - return ( -
-
- - {/* custo color is live */} - - - - - { skip && skipIntervals.map(interval => - (
)) - } -
- - { events.map(e => ( -
- )) - } - { - issues.map(iss => ( -
- - { iss.name } -
- } - > - - -
- )) - } - { events.filter(e => e.type === TYPES.CLICKRAGE).map(e => ( -
- - { "Click Rage" } -
- } - > - - -
- ))} - {typeof clickRageTime === 'number' && -
- - { "Click Rage" } -
- } - > - - -
- } - { exceptionsList - .map(e => ( -
- - { "Exception" } -
- { e.message } -
- } - > - - -
- )) - } - { resourceList - .filter(r => r.isRed() || r.isYellow()) - .map(r => ( -
- - { r.success ? "Slow resource: " : "Missing resource:" } -
- { r.name } -
- } - > - - -
- )) - } - { fetchList - .filter(e => e.isRed()) - .map(e => ( -
- - Failed Fetch -
- { e.name } -
- } - > - - -
- )) - } - { stackList - .filter(e => e.isRed()) - .map(e => ( -
- - Stack Event -
- { e.name } -
- } - > - - -
- )) - } -
-
- ); - } } diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index be3ac24b3..5b07012df 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -1,111 +1,72 @@ -import React from 'react' +import React from 'react'; import { Tooltip } from 'react-tippy'; import { ReduxTime } from '../Time'; import { Icon } from 'UI'; import cn from 'classnames'; // @ts-ignore -import styles from '../controls.module.css' +import styles from '../controls.module.css'; interface Props { - live: boolean; - skip: boolean; - speed: number; - disabled: boolean; - playButton: JSX.Element; - backTenSeconds: () => void; - forthTenSeconds: () => void; - toggleSpeed: () => void; - toggleSkip: () => void; - controlIcon: (icon: string, size: number, action: () => void, isBackwards: boolean, additionalClasses: string) => JSX.Element; + live: boolean; + skip: boolean; + speed: number; + disabled: boolean; + playButton: JSX.Element; + backTenSeconds: () => void; + forthTenSeconds: () => void; + toggleSpeed: () => void; + toggleSkip: () => void; + controlIcon: (icon: string, size: number, action: () => void, isBackwards: boolean, additionalClasses: string) => JSX.Element; } function PlayerControls(props: Props) { - const { - live, - skip, - speed, - disabled, - playButton, - backTenSeconds, - forthTenSeconds, - toggleSpeed, - toggleSkip, - controlIcon - } = props; - return ( -
- {playButton} - {!live && ( -
- {/* @ts-ignore */} - - / - {/* @ts-ignore */} - + const { live, skip, speed, disabled, playButton, backTenSeconds, forthTenSeconds, toggleSpeed, toggleSkip, controlIcon } = props; + return ( +
+ {playButton} + {!live && ( +
+ {/* @ts-ignore */} + + / + {/* @ts-ignore */} + +
+ )} + +
+ {/* @ts-ignore */} + + {controlIcon('skip-forward-fill', 18, backTenSeconds, true, 'hover:bg-active-blue-border color-main h-full flex items-center')} + +
10s
+ {/* @ts-ignore */} + + {controlIcon('skip-forward-fill', 18, forthTenSeconds, false, 'hover:bg-active-blue-border color-main h-full flex items-center')} + +
+ + {!live && ( +
+ {/* @ts-ignore */} + + + + + +
+ )}
- )} - -
- {/* @ts-ignore */} - - {controlIcon( - "skip-forward-fill", - 18, - backTenSeconds, - true, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} - -
10s
- {/* @ts-ignore */} - - {controlIcon( - "skip-forward-fill", - 18, - forthTenSeconds, - false, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} - -
- - {!live && -
- {/* @ts-ignore */} - - - - - -
- } -
- ) + ); } export default PlayerControls; diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index ef69d5c2d..9a95121ac 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -104,9 +104,9 @@ export default class Player extends React.PureComponent { ref={ this.screenWrapper } />
- { // !fullscreen && !!bottomBlock && + { !fullscreen && !!bottomBlock &&
- { // bottomBlock === OVERVIEW && + { bottomBlock === OVERVIEW && } { bottomBlock === CONSOLE && diff --git a/frontend/app/components/shared/XRayButton/XRayButton.tsx b/frontend/app/components/shared/XRayButton/XRayButton.tsx new file mode 100644 index 000000000..2d66ed283 --- /dev/null +++ b/frontend/app/components/shared/XRayButton/XRayButton.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import stl from './xrayButton.module.css'; +import cn from 'classnames'; + +interface Props { + onClick?: () => void; + isActive?: boolean; +} +function XRayButton(props: Props) { + const { isActive } = props; + return ( + + ); +} + +export default XRayButton; diff --git a/frontend/app/components/shared/XRayButton/index.ts b/frontend/app/components/shared/XRayButton/index.ts new file mode 100644 index 000000000..45f067067 --- /dev/null +++ b/frontend/app/components/shared/XRayButton/index.ts @@ -0,0 +1 @@ +export { default } from './XRayButton'; \ No newline at end of file diff --git a/frontend/app/components/shared/XRayButton/xrayButton.module.css b/frontend/app/components/shared/XRayButton/xrayButton.module.css new file mode 100644 index 000000000..3b3f76ca4 --- /dev/null +++ b/frontend/app/components/shared/XRayButton/xrayButton.module.css @@ -0,0 +1,17 @@ +.wrapper { + text-align: center; + padding: 4px 14px; + border: none; + border-radius: 6px; + font-weight: 500; + + &.default { + color: white; + background: linear-gradient(90deg, rgba(57, 78, 255, 0.87) 0%, rgba(62, 170, 175, 0.87) 100%); + } + + &.active { + background: rgba(63, 81, 181, 0.08); + color: $gray-darkest; + } +} diff --git a/frontend/app/utils.ts b/frontend/app/utils.ts index 517ab5e72..1e738024c 100644 --- a/frontend/app/utils.ts +++ b/frontend/app/utils.ts @@ -356,3 +356,9 @@ export function getTimelinePosition(value: any, scale: any) { const pos = value * scale; return pos > 100 ? 100 : pos; } + +export function millisToMinutesAndSeconds(millis: any) { + const minutes = Math.floor(millis / 60000); + const seconds: any = ((millis % 60000) / 1000).toFixed(0); + return minutes + 'm' + (seconds < 10 ? '0' : '') + seconds + 's'; +} \ No newline at end of file