refactor(ui/player): create player context for webplayer components
This commit is contained in:
parent
c5209efd87
commit
1621f73c69
14 changed files with 271 additions and 441 deletions
70
frontend/app/components/Session/PlayerContent.js
Normal file
70
frontend/app/components/Session/PlayerContent.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
connectPlayer,
|
||||
} from 'Player';
|
||||
import PlayerBlock from '../Session_/PlayerBlock';
|
||||
import styles from '../Session_/session.module.css';
|
||||
import { countDaysFrom } from 'App/date';
|
||||
import cn from 'classnames';
|
||||
import RightBlock from './RightBlock';
|
||||
|
||||
function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) {
|
||||
const sessionDays = countDaysFrom(session.startedAt);
|
||||
return (
|
||||
<div className="relative">
|
||||
{hasError ? (
|
||||
<div
|
||||
className="inset-0 flex items-center justify-center absolute"
|
||||
style={{
|
||||
// background: '#f6f6f6',
|
||||
height: 'calc(100vh - 50px)',
|
||||
zIndex: '999',
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="text-lg -mt-8">
|
||||
{sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'}
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
{sessionDays > 2
|
||||
? 'Please check your data retention policy.'
|
||||
: 'Please check it again in a few minutes.'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={cn('flex', { 'pointer-events-none': hasError })}>
|
||||
<div
|
||||
className="w-full"
|
||||
style={activeTab && !fullscreen ? { maxWidth: 'calc(100% - 270px)' } : undefined}
|
||||
>
|
||||
<div className={cn(styles.session, 'relative')} data-fullscreen={fullscreen}>
|
||||
<PlayerBlock activeTab={activeTab} />
|
||||
</div>
|
||||
</div>
|
||||
{activeTab !== '' && (
|
||||
<RightMenu
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
fullscreen={fullscreen}
|
||||
tabs={TABS}
|
||||
live={live}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) {
|
||||
return (
|
||||
!live &&
|
||||
!fullscreen && <RightBlock tabs={tabs} setActiveTab={setActiveTab} activeTab={activeTab} />
|
||||
);
|
||||
}
|
||||
|
||||
export default connectPlayer((state) => ({
|
||||
showEvents: !state.showEvents,
|
||||
hasError: state.error,
|
||||
}))(PlayerContent);
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Loader, Modal } from 'UI';
|
||||
import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
|
||||
import { fetchList } from 'Duck/integrations';
|
||||
import { PlayerProvider, injectNotes, connectPlayer, init as initPlayer, clean as cleanPlayer, Controls } from 'Player';
|
||||
import cn from 'classnames';
|
||||
import RightBlock from './RightBlock';
|
||||
import withLocationHandlers from 'HOCs/withLocationHandlers';
|
||||
import { useStore } from 'App/mstore'
|
||||
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
|
||||
import PlayerBlock from '../Session_/PlayerBlock';
|
||||
import styles from '../Session_/session.module.css';
|
||||
import { countDaysFrom } from 'App/date';
|
||||
import ReadNote from '../Session_/Player/Controls/components/ReadNote';
|
||||
import { fetchList as fetchMembers } from 'Duck/member';
|
||||
|
||||
const TABS = {
|
||||
EVENTS: 'User Steps',
|
||||
HEATMAPS: 'Click Map',
|
||||
};
|
||||
|
||||
const InitLoader = connectPlayer((state) => ({
|
||||
loading: !state.initialized,
|
||||
}))(Loader);
|
||||
|
||||
const PlayerContentConnected = connectPlayer((state) => ({
|
||||
showEvents: !state.showEvents,
|
||||
hasError: state.error,
|
||||
}))(PlayerContent);
|
||||
|
||||
function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) {
|
||||
const sessionDays = countDaysFrom(session.startedAt);
|
||||
return (
|
||||
<div className="relative">
|
||||
{hasError ? (
|
||||
<div className="inset-0 flex items-center justify-center absolute" style={{
|
||||
// background: '#f6f6f6',
|
||||
height: 'calc(100vh - 50px)',
|
||||
zIndex: '999',
|
||||
}}>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="text-lg -mt-8">{sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'}</div>
|
||||
<div className="text-sm">{sessionDays > 2 ? 'Please check your data retention policy.' : 'Please check it again in a few minutes.'}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={cn('flex', { 'pointer-events-none': hasError })}>
|
||||
<div className="w-full" style={activeTab && !fullscreen ? { maxWidth: 'calc(100% - 270px)'} : undefined}>
|
||||
<div className={cn(styles.session, 'relative')} data-fullscreen={fullscreen}>
|
||||
<PlayerBlock activeTab={activeTab} />
|
||||
</div>
|
||||
</div>
|
||||
{activeTab !== '' && <RightMenu activeTab={activeTab} setActiveTab={setActiveTab} fullscreen={fullscreen} tabs={TABS} live={live} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) {
|
||||
return !live && !fullscreen && <RightBlock tabs={tabs} setActiveTab={setActiveTab} activeTab={activeTab} />;
|
||||
}
|
||||
|
||||
function WebPlayer(props) {
|
||||
const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props;
|
||||
const { notesStore } = useStore()
|
||||
const [activeTab, setActiveTab] = useState('');
|
||||
const [showNoteModal, setShowNote] = useState(false)
|
||||
const [noteItem, setNoteItem] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetchList('issues');
|
||||
initPlayer(session, jwt);
|
||||
props.fetchMembers()
|
||||
|
||||
notesStore.fetchSessionNotes(session.sessionId).then(r => {
|
||||
injectNotes(r)
|
||||
const note = props.query.get('note');
|
||||
if (note) {
|
||||
Controls.pause()
|
||||
setNoteItem(notesStore.getNoteById(parseInt(note, 10), r))
|
||||
setShowNote(true)
|
||||
}
|
||||
})
|
||||
|
||||
const jumptTime = props.query.get('jumpto');
|
||||
if (jumptTime) {
|
||||
Controls.jump(parseInt(jumptTime));
|
||||
}
|
||||
|
||||
return () => cleanPlayer();
|
||||
}, [session.sessionId]);
|
||||
|
||||
// LAYOUT (TODO: local layout state - useContext or something..)
|
||||
useEffect(
|
||||
() => () => {
|
||||
toggleFullscreen(false);
|
||||
closeBottomBlock();
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onNoteClose = () => {setShowNote(false); Controls.togglePlay()}
|
||||
return (
|
||||
<PlayerProvider>
|
||||
<InitLoader className="flex-1">
|
||||
<PlayerBlockHeader activeTab={activeTab} setActiveTab={setActiveTab} tabs={TABS} fullscreen={fullscreen} />
|
||||
<PlayerContentConnected activeTab={activeTab} fullscreen={fullscreen} live={live} setActiveTab={setActiveTab} session={session} />
|
||||
<Modal open={showNoteModal} onClose={onNoteClose}>
|
||||
{showNoteModal ? (
|
||||
<ReadNote
|
||||
userEmail={props.members.find(m => m.id === noteItem?.userId)?.email || ''}
|
||||
note={noteItem}
|
||||
onClose={onNoteClose}
|
||||
notFound={!noteItem}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
</InitLoader>
|
||||
</PlayerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => ({
|
||||
session: state.getIn(['sessions', 'current']),
|
||||
jwt: state.get('jwt'),
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
showEvents: state.get('showEvents'),
|
||||
members: state.getIn(['members', 'list']),
|
||||
}),
|
||||
{
|
||||
toggleFullscreen,
|
||||
closeBottomBlock,
|
||||
fetchList,
|
||||
fetchMembers,
|
||||
}
|
||||
)(withLocationHandlers()(WebPlayer));
|
||||
127
frontend/app/components/Session/WebPlayer.tsx
Normal file
127
frontend/app/components/Session/WebPlayer.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Modal } from 'UI';
|
||||
import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
|
||||
import { fetchList } from 'Duck/integrations';
|
||||
import {
|
||||
PlayerProvider,
|
||||
createWebPlayer,
|
||||
} from 'Player';
|
||||
|
||||
import withLocationHandlers from 'HOCs/withLocationHandlers';
|
||||
import { useStore } from 'App/mstore';
|
||||
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
|
||||
import ReadNote from '../Session_/Player/Controls/components/ReadNote';
|
||||
import { fetchList as fetchMembers } from 'Duck/member';
|
||||
import PlayerContent from './PlayerContent'
|
||||
import {
|
||||
IPlayerContext,
|
||||
PlayerContext,
|
||||
defaultContextValue
|
||||
} from './playerContext'
|
||||
|
||||
const TABS = {
|
||||
EVENTS: 'User Steps',
|
||||
HEATMAPS: 'Click Map',
|
||||
};
|
||||
|
||||
function WebPlayer(props: any) {
|
||||
const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props;
|
||||
const { notesStore } = useStore();
|
||||
const [activeTab, setActiveTab] = useState('');
|
||||
const [showNoteModal, setShowNote] = useState(false);
|
||||
const [noteItem, setNoteItem] = useState(null);
|
||||
const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue)
|
||||
|
||||
useEffect(() => {
|
||||
fetchList('issues');
|
||||
const [WebPlayerInst, PlayerStore] = createWebPlayer(session, jwt);
|
||||
setContextValue({ player: WebPlayerInst, store: PlayerStore })
|
||||
|
||||
// initPlayer(session, jwt); TODOPlayer
|
||||
props.fetchMembers();
|
||||
|
||||
notesStore.fetchSessionNotes(session.sessionId).then((r) => {
|
||||
// WebPlayerInst.injectNotes(r);
|
||||
// PlayerStore.update({ notes: r })
|
||||
const note = props.query.get('note');
|
||||
if (note) {
|
||||
WebPlayerInst.pause();
|
||||
setNoteItem(notesStore.getNoteById(parseInt(note, 10), r));
|
||||
setShowNote(true);
|
||||
}
|
||||
});
|
||||
|
||||
const jumptTime = props.query.get('jumpto');
|
||||
if (jumptTime) {
|
||||
WebPlayerInst.jump(parseInt(jumptTime));
|
||||
}
|
||||
|
||||
return () => WebPlayerInst.clean();
|
||||
}, [session.sessionId]);
|
||||
|
||||
// LAYOUT (TODO: local layout state - useContext or something..)
|
||||
useEffect(
|
||||
() => () => {
|
||||
toggleFullscreen(false);
|
||||
closeBottomBlock();
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onNoteClose = () => {
|
||||
setShowNote(false);
|
||||
contextValue.player.togglePlay();
|
||||
};
|
||||
|
||||
if (!contextValue.player) return null;
|
||||
|
||||
return (
|
||||
<PlayerContext.Provider value={contextValue}>
|
||||
<PlayerProvider>
|
||||
<>
|
||||
<PlayerBlockHeader
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
tabs={TABS}
|
||||
fullscreen={fullscreen}
|
||||
/>
|
||||
{/* @ts-ignore */}
|
||||
<PlayerContent
|
||||
activeTab={activeTab}
|
||||
fullscreen={fullscreen}
|
||||
live={live}
|
||||
setActiveTab={setActiveTab}
|
||||
session={session}
|
||||
/>
|
||||
<Modal open={showNoteModal} onClose={onNoteClose}>
|
||||
{showNoteModal ? (
|
||||
<ReadNote
|
||||
userEmail={props.members.find((m: Record<string, any>) => m.id === noteItem?.userId)?.email || ''}
|
||||
note={noteItem}
|
||||
onClose={onNoteClose}
|
||||
notFound={!noteItem}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
</>
|
||||
</PlayerProvider>
|
||||
</PlayerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
session: state.getIn(['sessions', 'current']),
|
||||
jwt: state.get('jwt'),
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
showEvents: state.get('showEvents'),
|
||||
members: state.getIn(['members', 'list']),
|
||||
}),
|
||||
{
|
||||
toggleFullscreen,
|
||||
closeBottomBlock,
|
||||
fetchList,
|
||||
fetchMembers,
|
||||
}
|
||||
)(withLocationHandlers()(WebPlayer));
|
||||
12
frontend/app/components/Session/playerContext.ts
Normal file
12
frontend/app/components/Session/playerContext.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { createContext } from 'react';
|
||||
import {
|
||||
IWebPlayer,
|
||||
IStore
|
||||
} from 'Player'
|
||||
|
||||
export interface IPlayerContext {
|
||||
player: IWebPlayer
|
||||
store: IStore,
|
||||
}
|
||||
export const defaultContextValue: IPlayerContext = { player: undefined, store: undefined}
|
||||
export const PlayerContext = createContext<IPlayerContext>(defaultContextValue);
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import React from 'react'
|
||||
import { connectPlayer, jump } from 'Player';
|
||||
import ConsoleContent from './ConsoleContent';
|
||||
|
||||
@connectPlayer(state => ({
|
||||
logs: state.logList,
|
||||
// time: state.time,
|
||||
livePlay: state.livePlay,
|
||||
listNow: state.logListNow,
|
||||
}))
|
||||
export default class Console extends React.PureComponent {
|
||||
render() {
|
||||
const { logs, time, listNow } = this.props;
|
||||
return (
|
||||
<ConsoleContent jump={!this.props.livePlay && jump} logs={logs} lastIndex={listNow.length - 1} logsNow={listNow} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { getRE } from 'App/utils';
|
||||
import { Icon, NoContent, Tabs, Input } from 'UI';
|
||||
import { jump } from 'Player';
|
||||
import { LEVEL } from 'Types/session/log';
|
||||
import Autoscroll from '../Autoscroll';
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import stl from './console.module.css';
|
||||
import ConsoleRow from './ConsoleRow';
|
||||
// import { Duration } from 'luxon';
|
||||
|
||||
const ALL = 'ALL';
|
||||
const INFO = 'INFO';
|
||||
const WARNINGS = 'WARNINGS';
|
||||
const ERRORS = 'ERRORS';
|
||||
|
||||
const LEVEL_TAB = {
|
||||
[LEVEL.INFO]: INFO,
|
||||
[LEVEL.LOG]: INFO,
|
||||
[LEVEL.WARNING]: WARNINGS,
|
||||
[LEVEL.ERROR]: ERRORS,
|
||||
[LEVEL.EXCEPTION]: ERRORS,
|
||||
};
|
||||
|
||||
const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab }));
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
const getIconProps = (level) => {
|
||||
switch (level) {
|
||||
case LEVEL.INFO:
|
||||
case LEVEL.LOG:
|
||||
return {
|
||||
name: 'console/info',
|
||||
color: 'blue2',
|
||||
};
|
||||
case LEVEL.WARN:
|
||||
case LEVEL.WARNING:
|
||||
return {
|
||||
name: 'console/warning',
|
||||
color: 'red2',
|
||||
};
|
||||
case LEVEL.ERROR:
|
||||
return {
|
||||
name: 'console/error',
|
||||
color: 'red',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
function renderWithNL(s = '') {
|
||||
if (typeof s !== 'string') return '';
|
||||
return s.split('\n').map((line, i) => <div className={cn({ 'ml-20': i !== 0 })}>{line}</div>);
|
||||
}
|
||||
|
||||
export default class ConsoleContent extends React.PureComponent {
|
||||
state = {
|
||||
filter: '',
|
||||
activeTab: ALL,
|
||||
};
|
||||
onTabClick = (activeTab) => this.setState({ activeTab });
|
||||
onFilterChange = ({ target: { value } }) => this.setState({ filter: value });
|
||||
|
||||
render() {
|
||||
const { logs, isResult, additionalHeight, logsNow } = this.props;
|
||||
const time = logsNow.length > 0 ? logsNow[logsNow.length - 1].time : undefined;
|
||||
const { filter, activeTab, currentError } = this.state;
|
||||
const filterRE = getRE(filter, 'i');
|
||||
const filtered = logs.filter(({ level, value }) =>
|
||||
activeTab === ALL
|
||||
? filterRE.test(value)
|
||||
: filterRE.test(value) && LEVEL_TAB[level] === activeTab
|
||||
);
|
||||
|
||||
const lastIndex = filtered.filter((item) => item.time <= time).length - 1;
|
||||
|
||||
return (
|
||||
<>
|
||||
<BottomBlock style={{ height: 300 + additionalHeight + 'px' }}>
|
||||
<BottomBlock.Header showClose={!isResult}>
|
||||
<div className="flex items-center">
|
||||
<span className="font-semibold color-gray-medium mr-4">Console</span>
|
||||
<Tabs tabs={TABS} active={activeTab} onClick={this.onTabClick} border={false} />
|
||||
</div>
|
||||
<Input
|
||||
className="input-small h-8"
|
||||
placeholder="Filter by keyword"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
height={28}
|
||||
onChange={this.onFilterChange}
|
||||
/>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<NoContent
|
||||
title={
|
||||
<div className="capitalize flex items-center mt-16">
|
||||
<Icon name="info-circle" className="mr-2" size="18" />
|
||||
No Data
|
||||
</div>
|
||||
}
|
||||
size="small"
|
||||
show={filtered.length === 0}
|
||||
>
|
||||
<Autoscroll autoScrollTo={Math.max(lastIndex, 0)}>
|
||||
{filtered.map((l) => (
|
||||
<ConsoleRow
|
||||
log={l}
|
||||
jump={jump}
|
||||
iconProps={getIconProps(l.level)}
|
||||
renderWithNL={renderWithNL}
|
||||
/>
|
||||
))}
|
||||
</Autoscroll>
|
||||
</NoContent>
|
||||
</BottomBlock.Content>
|
||||
</BottomBlock>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import cn from 'classnames';
|
||||
import stl from '../console.module.css';
|
||||
import { Icon } from 'UI';
|
||||
import JumpButton from 'Shared/DevTools/JumpButton';
|
||||
|
||||
interface Props {
|
||||
log: any;
|
||||
iconProps: any;
|
||||
jump?: any;
|
||||
renderWithNL?: any;
|
||||
}
|
||||
function ConsoleRow(props: Props) {
|
||||
const { log, iconProps, jump, renderWithNL } = props;
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const lines = log.value.split('\n').filter((l: any) => !!l);
|
||||
const canExpand = lines.length > 1;
|
||||
return (
|
||||
<div
|
||||
className={cn(stl.line, 'flex py-2 px-4 overflow-hidden group relative select-none', {
|
||||
info: !log.isYellow() && !log.isRed(),
|
||||
warn: log.isYellow(),
|
||||
error: log.isRed(),
|
||||
'cursor-pointer': canExpand,
|
||||
})}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
<div className={cn(stl.timestamp)}>
|
||||
<Icon size="14" className={stl.icon} {...iconProps} />
|
||||
</div>
|
||||
{/* <div className={cn(stl.timestamp, {})}>
|
||||
{Duration.fromMillis(log.time).toFormat('mm:ss.SSS')}
|
||||
</div> */}
|
||||
<div key={log.key} className={cn('')} data-scroll-item={log.isRed()}>
|
||||
<div className={cn(stl.message, 'flex items-center')}>
|
||||
{canExpand && (
|
||||
<Icon name={expanded ? 'caret-down-fill' : 'caret-right-fill'} className="mr-2" />
|
||||
)}
|
||||
<span>{renderWithNL(lines.pop())}</span>
|
||||
</div>
|
||||
{canExpand && expanded && lines.map((l: any) => <div className="ml-4 mb-1">{l}</div>)}
|
||||
</div>
|
||||
<JumpButton onClick={() => jump(log.time)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConsoleRow;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ConsoleRow';
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
|
||||
.message {
|
||||
overflow-x: auto;
|
||||
margin-left: 10px;
|
||||
font-size: 13px;
|
||||
overflow-x: auto;
|
||||
&::-webkit-scrollbar {
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
font-family: 'Menlo', 'monaco', 'consolas', monospace;
|
||||
/* margin-top: -1px; ??? */
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
border-bottom: solid thin $gray-light-shade;
|
||||
&:hover {
|
||||
background-coor: $active-blue !important;
|
||||
}
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
|
||||
}
|
||||
|
||||
.activeRow {
|
||||
background-color: $teal-light !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding-top: 4px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.inactiveRow {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
@ -53,10 +53,10 @@ export default function Inspector () {
|
|||
|
||||
if (!doc) return null;
|
||||
return (
|
||||
<BottomBlock>
|
||||
<BottomBlock>
|
||||
<BottomBlock.Content>
|
||||
<div onMouseLeave={ () => markElement(null) } className={stl.wrapper}>
|
||||
<ElementView
|
||||
<ElementView
|
||||
element={ doc.documentElement }
|
||||
level={0}
|
||||
context={doc.defaultView}
|
||||
|
|
@ -69,4 +69,4 @@ export default function Inspector () {
|
|||
</BottomBlock.Content>
|
||||
</BottomBlock>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@ export default class GraphQL extends React.PureComponent {
|
|||
const { filter, current } = this.state;
|
||||
const filterRE = getRE(filter, 'i');
|
||||
const filtered = list
|
||||
.filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) =>
|
||||
filterRE.test(containerName) ||
|
||||
.filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) =>
|
||||
filterRE.test(containerName) ||
|
||||
filterRE.test(containerId) ||
|
||||
filterRE.test(containerSrc) ||
|
||||
filterRE.test(CONTEXTS[ context ]) ||
|
||||
filterRE.test(CONTAINER_TYPES[ containerType ]));
|
||||
filterRE.test(CONTAINER_TYPES[ containerType ]));
|
||||
const lastIndex = filtered.filter(item => item.time <= time).length - 1;
|
||||
return (
|
||||
<BottomBlock>
|
||||
|
|
@ -64,7 +64,7 @@ export default class GraphQL extends React.PureComponent {
|
|||
<QuestionMarkHint
|
||||
content={
|
||||
<>
|
||||
<a className="color-teal underline mr-2" target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Long_Tasks_API">Learn more </a>
|
||||
<a className="color-teal underline mr-2" target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Long_Tasks_API">Learn more </a>
|
||||
about Long Tasks API
|
||||
</>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,21 +20,13 @@ import {
|
|||
OVERVIEW,
|
||||
} from 'Duck/components/player';
|
||||
import NetworkPanel from 'Shared/DevTools/NetworkPanel';
|
||||
import Console from '../Console/Console';
|
||||
import StackEvents from '../StackEvents/StackEvents';
|
||||
import Storage from '../Storage';
|
||||
import Profiler from '../Profiler';
|
||||
import { ConnectedPerformance } from '../Performance';
|
||||
import GraphQL from '../GraphQL';
|
||||
import Exceptions from '../Exceptions/Exceptions';
|
||||
import LongTasks from '../LongTasks';
|
||||
import Inspector from '../Inspector';
|
||||
import {
|
||||
attach as attachPlayer,
|
||||
Controls as PlayerControls,
|
||||
scale as scalePlayerScreen,
|
||||
connectPlayer,
|
||||
} from 'Player';
|
||||
import Controls from './Controls';
|
||||
import Overlay from './Overlay';
|
||||
import stl from './player.module.css';
|
||||
|
|
@ -42,70 +34,47 @@ import { updateLastPlayedSession } from 'Duck/sessions';
|
|||
import OverviewPanel from '../OverviewPanel';
|
||||
import ConsolePanel from 'Shared/DevTools/ConsolePanel';
|
||||
import ProfilerPanel from 'Shared/DevTools/ProfilerPanel';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
|
||||
@connectPlayer((state) => ({
|
||||
live: state.live,
|
||||
}))
|
||||
@connect(
|
||||
(state) => {
|
||||
const isAssist = window.location.pathname.includes('/assist/');
|
||||
return {
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
nextId: state.getIn(['sessions', 'nextId']),
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
closedLive:
|
||||
!!state.getIn(['sessions', 'errors']) ||
|
||||
(isAssist && !state.getIn(['sessions', 'current', 'live'])),
|
||||
};
|
||||
},
|
||||
{
|
||||
hideTargetDefiner,
|
||||
function Player(props) {
|
||||
const {
|
||||
className,
|
||||
bottomBlockIsActive,
|
||||
fullscreen,
|
||||
fullscreenOff,
|
||||
updateLastPlayedSession,
|
||||
}
|
||||
)
|
||||
export default class Player extends React.PureComponent {
|
||||
screenWrapper = React.createRef();
|
||||
nextId,
|
||||
closedLive,
|
||||
bottomBlock,
|
||||
activeTab,
|
||||
} = props;
|
||||
const playerContext = React.useContext(PlayerContext)
|
||||
const screenWrapper = React.useRef();
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
[prevProps.bottomBlock, this.props.bottomBlock].includes(NONE) ||
|
||||
prevProps.fullscreen !== this.props.fullscreen
|
||||
) {
|
||||
scalePlayerScreen();
|
||||
React.useEffect(() => {
|
||||
props.updateLastPlayedSession(props.sessionId);
|
||||
if (!props.closedLive) {
|
||||
const parentElement = findDOMNode(screenWrapper.current); //TODO: good architecture
|
||||
playerContext.player.attach(parentElement);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.updateLastPlayedSession(this.props.sessionId);
|
||||
if (this.props.closedLive) return;
|
||||
}, [])
|
||||
|
||||
const parentElement = findDOMNode(this.screenWrapper.current); //TODO: good architecture
|
||||
attachPlayer(parentElement);
|
||||
}
|
||||
React.useEffect(() => {
|
||||
playerContext.player.scale();
|
||||
}, [props.bottomBlock, props.fullscreen, playerContext.player])
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
bottomBlockIsActive,
|
||||
fullscreen,
|
||||
fullscreenOff,
|
||||
nextId,
|
||||
closedLive,
|
||||
bottomBlock,
|
||||
activeTab,
|
||||
} = this.props;
|
||||
if (!playerContext.player) return null;
|
||||
|
||||
const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw';
|
||||
return (
|
||||
<div
|
||||
const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw';
|
||||
return (
|
||||
<div
|
||||
className={cn(className, stl.playerBody, 'flex flex-col relative', fullscreen && 'pb-2')}
|
||||
data-bottom-block={bottomBlockIsActive}
|
||||
>
|
||||
{fullscreen && <EscapeButton onClose={fullscreenOff} />}
|
||||
<div className="relative flex-1 overflow-hidden">
|
||||
<Overlay nextId={nextId} togglePlay={PlayerControls.togglePlay} closedLive={closedLive} />
|
||||
<div className={stl.screenWrapper} ref={this.screenWrapper} />
|
||||
<Overlay nextId={nextId} togglePlay={playerContext.player.togglePlay} closedLive={closedLive} />
|
||||
<div className={stl.screenWrapper} ref={screenWrapper} />
|
||||
</div>
|
||||
{!fullscreen && !!bottomBlock && (
|
||||
<div style={{ maxWidth, width: '100%' }}>
|
||||
|
|
@ -125,8 +94,25 @@ export default class Player extends React.PureComponent {
|
|||
{bottomBlock === INSPECTOR && <Inspector />}
|
||||
</div>
|
||||
)}
|
||||
<Controls {...PlayerControls} />
|
||||
<Controls {...playerContext.player} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default connect((state) => {
|
||||
const isAssist = window.location.pathname.includes('/assist/');
|
||||
return {
|
||||
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
|
||||
nextId: state.getIn(['sessions', 'nextId']),
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
closedLive:
|
||||
!!state.getIn(['sessions', 'errors']) ||
|
||||
(isAssist && !state.getIn(['sessions', 'current', 'live'])),
|
||||
};
|
||||
},
|
||||
{
|
||||
hideTargetDefiner,
|
||||
fullscreenOff,
|
||||
updateLastPlayedSession,
|
||||
}
|
||||
)(Player)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default class Profiler extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SlideModal
|
||||
<SlideModal
|
||||
title={ modalProfile && modalProfile.name }
|
||||
isDisplayed={ modalProfile !== null }
|
||||
content={ modalProfile && <ProfileInfo profile={ modalProfile } />}
|
||||
|
|
@ -55,7 +55,7 @@ export default class Profiler extends React.PureComponent {
|
|||
/>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<TimeTable
|
||||
<TimeTable
|
||||
rows={ filteredProfiles }
|
||||
onRowClick={ this.onProfileClick }
|
||||
hoverable
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import Player, { State as PState } from './player/Player'
|
|||
import WebPlayer from './_web/WebPlayer'
|
||||
|
||||
type WebPlayerStore = Store<PState & MMState>
|
||||
export type IWebPlayer = WebPlayer
|
||||
export type IWebPlayerStore = Store<PState & MMState>
|
||||
|
||||
export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] {
|
||||
export function createWebPlayer(session: Record<string, any>, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] {
|
||||
let store: WebPlayerStore = new SimpleStore<PState & MMState>({
|
||||
...Player.INITIAL_STATE,
|
||||
...MM_INITIAL_STATE,
|
||||
|
|
@ -20,7 +22,7 @@ export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPl
|
|||
}
|
||||
|
||||
|
||||
export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] {
|
||||
export function createLiveWebPlayer(session: Record<string, any>, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] {
|
||||
let store: WebPlayerStore = new SimpleStore<PState & MMState>({
|
||||
...Player.INITIAL_STATE,
|
||||
...MM_INITIAL_STATE,
|
||||
|
|
@ -30,4 +32,4 @@ export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?:
|
|||
}
|
||||
const player = new WebPlayer(store, session, config, true)
|
||||
return [player, store]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue