feat(ui) - live player separate the route

This commit is contained in:
Shekar Siri 2022-02-23 14:17:56 +01:00
parent c55916c3d4
commit f6668ebc97
14 changed files with 140 additions and 79 deletions

View file

@ -11,6 +11,7 @@ import UpdatePassword from 'Components/UpdatePassword/UpdatePassword';
import ClientPure from 'Components/Client/Client';
import OnboardingPure from 'Components/Onboarding/Onboarding';
import SessionPure from 'Components/Session/Session';
import LiveSessionPure from 'Components/Session/LiveSession';
import AssistPure from 'Components/Assist';
import BugFinderPure from 'Components/BugFinder/BugFinder';
import DashboardPure from 'Components/Dashboard/Dashboard';
@ -31,6 +32,7 @@ import { setSessionPath } from 'Duck/sessions';
const BugFinder = withSiteIdUpdater(BugFinderPure);
const Dashboard = withSiteIdUpdater(DashboardPure);
const Session = withSiteIdUpdater(SessionPure);
const LiveSession = withSiteIdUpdater(LiveSessionPure);
const Assist = withSiteIdUpdater(AssistPure);
const Client = withSiteIdUpdater(ClientPure);
const Onboarding = withSiteIdUpdater(OnboardingPure);
@ -119,7 +121,7 @@ class Router extends React.Component {
render() {
const { isLoggedIn, jwt, siteId, sites, loading, changePassword, location, existingTenant, onboarding } = this.props;
const siteIdList = sites.map(({ id }) => id).toJS();
const hideHeader = location.pathname && location.pathname.includes('/session/');
const hideHeader = location.pathname && location.pathname.includes('/session/') || location.pathname.includes('/assist/');
return isLoggedIn ?
<Loader loading={ loading } className="flex-1" >
@ -160,6 +162,7 @@ class Router extends React.Component {
<Route exact strict path={ withSiteId(FUNNEL_ISSUE_PATH, siteIdList) } component={ FunnelIssue } />
<Route exact strict path={ withSiteId(SESSIONS_PATH, siteIdList) } component={ BugFinder } />
<Route exact strict path={ withSiteId(SESSION_PATH, siteIdList) } component={ Session } />
<Route exact strict path={ withSiteId(LIVE_SESSION_PATH, siteIdList) } component={ LiveSession } />
<Route exact strict path={ withSiteId(LIVE_SESSION_PATH, siteIdList) } render={ (props) => <Session { ...props } live /> } />
{ routes.redirects.map(([ fr, to ]) => (
<Redirect key={ fr } exact strict from={ fr } to={ to } />

View file

@ -5,13 +5,10 @@ import cn from 'classnames'
import withPageTitle from 'HOCs/withPageTitle';
import withPermissions from 'HOCs/withPermissions'
// @withPageTitle("Assist - OpenReplay")
function Assist() {
return (
<div className="page-margin container-90 flex relative">
<div className="flex-1 flex">
{/* <div className="side-menu">
</div> */}
<div className={cn("w-full mx-auto")} style={{ maxWidth: '1300px'}}>
<LiveSessionSearch />
<div className="my-4" />
@ -22,4 +19,4 @@ function Assist() {
)
}
export default withPageTitle("Assist - OpenReplay")(withPermissions(['ASSIST_LIVE', 'SESSION_REPLAY'])(Assist));
export default withPageTitle("Assist - OpenReplay")(withPermissions(['ASSIST_LIVE'])(Assist));

View file

@ -12,27 +12,19 @@ import {
import withPermissions from 'HOCs/withPermissions';
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
import EventsBlock from '../Session_/EventsBlock';
import PlayerBlock from '../Session_/PlayerBlock';
import styles from '../Session_/session.css';
const EventsBlockConnected = connectPlayer(state => ({
currentTimeEventIndex: state.eventListNow.length > 0 ? state.eventListNow.length - 1 : 0,
playing: state.playing,
}))(EventsBlock)
const InitLoader = connectPlayer(state => ({
loading: !state.initialized
}))(Loader);
function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, loadingCredentials, assistCredendials, request, isEnterprise, hasSessionsPath }) {
function LivePlayer ({ session, toggleFullscreen, closeBottomBlock, fullscreen, jwt, loadingCredentials, assistCredendials, request, isEnterprise, hasErrors }) {
useEffect(() => {
if (!loadingCredentials) {
initPlayer(session, jwt, assistCredendials, !hasSessionsPath && session.live);
initPlayer(session, jwt, assistCredendials, true);
}
return () => cleanPlayer()
}, [ session.sessionId, loadingCredentials, assistCredendials ]);
@ -48,11 +40,10 @@ function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, l
}
}, [])
return (
<PlayerProvider>
<InitLoader className="flex-1 p-3">
<PlayerBlockHeader fullscreen={fullscreen}/>
<PlayerBlockHeader fullscreen={fullscreen} />
<div className={ styles.session } data-fullscreen={fullscreen}>
<PlayerBlock />
</div>
@ -78,7 +69,8 @@ export default withRequest({
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
hasSessionsPath: hasSessioPath && !isAssist,
isEnterprise: state.getIn([ 'user', 'client', 'edition' ]) === 'ee',
hasErrors: !!state.getIn([ 'sessions', 'errors' ]),
}
},
{ toggleFullscreen, closeBottomBlock },
)(WebPlayer)));
)(LivePlayer)));

View file

@ -0,0 +1,60 @@
import { useEffect } from 'react';
import { connect } from 'react-redux';
import usePageTitle from 'App/hooks/usePageTitle';
import { fetch as fetchSession } from 'Duck/sessions';
import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
import { Link, NoContent, Loader } from 'UI';
import { sessions as sessionsRoute } from 'App/routes';
import withPermissions from 'HOCs/withPermissions'
import LivePlayer from './LivePlayer';
const SESSIONS_ROUTE = sessionsRoute();
function LiveSession({
sessionId,
loading,
hasErrors,
session,
fetchSession,
fetchSlackList,
hasSessionsPath
}) {
usePageTitle("OpenReplay Assist");
useEffect(() => {
fetchSlackList()
}, []);
useEffect(() => {
if (sessionId != null) {
fetchSession(sessionId)
} else {
console.error("No sessionID in route.")
}
return () => {
if (!session.exists()) return;
}
},[ sessionId, hasSessionsPath ]);
return (
<Loader className="flex-1" loading={ loading }>
<LivePlayer />
</Loader>
);
}
export default withPermissions(['ASSIST_LIVE'], '', true)(connect((state, props) => {
const { match: { params: { sessionId } } } = props;
const isAssist = state.getIn(['sessions', 'activeTab']).type === 'live';
const hasSessiosPath = state.getIn([ 'sessions', 'sessionPath' ]).includes('/sessions');
return {
sessionId,
loading: state.getIn([ 'sessions', 'loading' ]),
hasErrors: !!state.getIn([ 'sessions', 'errors' ]),
session: state.getIn([ 'sessions', 'current' ]),
hasSessionsPath: hasSessiosPath && !isAssist,
};
}, {
fetchSession,
fetchSlackList,
})(LiveSession));

View file

@ -106,6 +106,7 @@ function getStorageName(type) {
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' ]),
}
}, {
fullscreenOn,
@ -253,7 +254,8 @@ export default class Controls extends React.Component {
showExceptions,
fullscreen,
skipToIssue,
inspectorMode
inspectorMode,
closedLive,
} = this.props;
// const inspectorMode = bottomBlock === INSPECTOR;
@ -263,30 +265,35 @@ export default class Controls extends React.Component {
{ !live && <Timeline jump={ this.props.jump } /> }
{ !fullscreen &&
<div className={ styles.buttons } data-is-live={ live }>
{ !live ?
<div className={ styles.buttonsLeft }>
{ this.renderPlayBtn() }
<ControlButton
onClick={ this.backTenSeconds }
disabled={ disabled }
label="Back"
icon="replay-10"
/>
<ControlButton
disabled={ disabled }
onClick={ this.props.toggleSkipToIssue }
active={ skipToIssue }
label="Skip to Issue"
icon={skipToIssue ? 'skip-forward-fill' : 'skip-forward'}
/>
</div>
:
<div className={ styles.buttonsLeft }>
<LiveTag isLive={livePlay} />
{'Elapsed'}
<ReduxTime name="time" />
</div>
}
<div>
{ !live && (
<div className={ styles.buttonsLeft }>
{ this.renderPlayBtn() }
<ControlButton
onClick={ this.backTenSeconds }
disabled={ disabled }
label="Back"
icon="replay-10"
/>
<ControlButton
disabled={ disabled }
onClick={ this.props.toggleSkipToIssue }
active={ skipToIssue }
label="Skip to Issue"
icon={skipToIssue ? 'skip-forward-fill' : 'skip-forward'}
/>
</div>
)}
{ live && !closedLive && (
<div className={ styles.buttonsLeft }>
<LiveTag isLive={livePlay} />
{'Elapsed'}
<ReduxTime name="time" />
</div>
)}
</div>
<div className={ styles.butonsRight }>
{!live &&
<React.Fragment>

View file

@ -25,6 +25,7 @@ interface Props {
nextId: string,
togglePlay: () => void,
closedLive?: boolean
}
function Overlay({
@ -41,6 +42,7 @@ function Overlay({
activeTargetIndex,
nextId,
togglePlay,
closedLive
}: Props) {
// useEffect(() =>{
@ -56,7 +58,7 @@ function Overlay({
<>
{ showAutoplayTimer && <AutoplayTimer /> }
{ showLiveStatusText &&
<LiveStatusText text={liveStatusText} concetionStatus={concetionStatus} />
<LiveStatusText text={liveStatusText} concetionStatus={closedLive ? ConnectionStatus.Closed : concetionStatus} />
}
{ messagesLoading && <Loader/> }
{ showPlayIconLayer &&

View file

@ -12,6 +12,14 @@ interface Props {
export default function LiveStatusText({ text, concetionStatus }: Props) {
const renderView = () => {
switch (concetionStatus) {
case ConnectionStatus.Closed:
return (
<div className="flex flex-col items-center text-center">
<div className="text-lg -mt-8">Session not found</div>
<div className="text-sm">The remote session doesnt exist anymore. <br/> The user may have closed the tab/browser while you were trying to establish a connection.</div>
</div>
)
case ConnectionStatus.Connecting:
return (
<div className="flex flex-col items-center">

View file

@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import { findDOMNode } from 'react-dom';
import cn from 'classnames';
import { Loader, IconButton, EscapeButton } from 'UI';
import { EscapeButton } from 'UI';
import { hide as hideTargetDefiner } from 'Duck/components/targetDefiner';
import { fullscreenOff } from 'Duck/components/player';
import { attach as attachPlayer, Controls as PlayerControls, connectPlayer } from 'Player';
@ -10,14 +10,13 @@ import Overlay from './Overlay';
import stl from './player.css';
import EventsToggleButton from '../../Session/EventsToggleButton';
@connectPlayer(state => ({
live: state.live,
}))
@connect(state => ({
//session: state.getIn([ 'sessions', 'current' ]),
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
nextId: state.getIn([ 'sessions', 'nextId' ]),
closedLive: !!state.getIn([ 'sessions', 'errors' ]),
}), {
hideTargetDefiner,
fullscreenOff,
@ -26,6 +25,8 @@ export default class Player extends React.PureComponent {
screenWrapper = React.createRef();
componentDidMount() {
if (this.props.closedLive) return;
const parentElement = findDOMNode(this.screenWrapper.current); //TODO: good architecture
attachPlayer(parentElement);
}
@ -38,6 +39,7 @@ export default class Player extends React.PureComponent {
fullscreenOff,
nextId,
live,
closedLive,
} = this.props;
return (
@ -45,12 +47,10 @@ export default class Player extends React.PureComponent {
className={ cn(className, stl.playerBody, "flex flex-col relative") }
data-bottom-block={ bottomBlockIsActive }
>
{ fullscreen &&
<EscapeButton onClose={ fullscreenOff } />
}
{fullscreen && <EscapeButton onClose={ fullscreenOff } />}
{!live && !fullscreen && <EventsToggleButton /> }
<div className="relative flex-1 overflow-hidden">
<Overlay nextId={nextId} togglePlay={PlayerControls.togglePlay} />
<Overlay nextId={nextId} togglePlay={PlayerControls.togglePlay} closedLive={closedLive} />
<div
className={ stl.screenWrapper }
ref={ this.screenWrapper }

View file

@ -22,7 +22,6 @@ import StackEvents from './StackEvents/StackEvents';
import Storage from './Storage';
import Profiler from './Profiler';
import { ConnectedPerformance } from './Performance';
import PlayerBlockHeader from './PlayerBlockHeader';
import GraphQL from './GraphQL';
import Fetch from './Fetch';
import Exceptions from './Exceptions/Exceptions';
@ -34,6 +33,7 @@ import styles from './playerBlock.css';
@connect(state => ({
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
bottomBlock: state.getIn([ 'components', 'player', 'bottomBlock' ]),
closedLive: !!state.getIn([ 'sessions', 'errors' ]),
}))
export default class PlayerBlock extends React.PureComponent {
componentDidUpdate(prevProps) {
@ -44,13 +44,14 @@ export default class PlayerBlock extends React.PureComponent {
}
render() {
const { fullscreen, bottomBlock } = this.props;
const { fullscreen, bottomBlock, closedLive } = this.props;
return (
<div className={ cn(styles.playerBlock, "flex flex-col") }>
<Player
className="flex-1"
bottomBlockIsActive={ !fullscreen && bottomBlock !== NONE }
closedLive={closedLive}
/>
{ !fullscreen && !!bottomBlock &&
<div className="">

View file

@ -2,12 +2,12 @@ import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { browserIcon, osIcon, deviceTypeIcon } from 'App/iconNames';
import { formatTimeOrDate } from 'App/date';
import { sessions as sessionsRoute, withSiteId } from 'App/routes';
import { sessions as sessionsRoute, assist as assistRoute, withSiteId } from 'App/routes';
import { Icon, CountryFlag, IconButton, BackLink, Popup } from 'UI';
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
import cn from 'classnames';
import { connectPlayer } from 'Player';
import HeaderInfo from './HeaderInfo';
// import HeaderInfo from './HeaderInfo';
import SharePopup from '../shared/SharePopup/SharePopup';
import { fetchList as fetchListIntegration } from 'Duck/integrations/actions';
import { countries } from 'App/constants';
@ -21,10 +21,7 @@ import AssistTabs from '../Assist/components/AssistTabs';
import SessionInfoItem from './SessionInfoItem'
const SESSIONS_ROUTE = sessionsRoute();
function capitalise(str) {
return str[0].toUpperCase() + str.slice(1);
}
const ASSIST_ROUTE = assistRoute();
@connectPlayer(state => ({
width: state.width,
height: state.height,
@ -46,6 +43,7 @@ function capitalise(str) {
siteId: state.getIn([ 'user', 'siteId' ]),
hasSessionsPath: hasSessioPath && !isAssist,
metaList: state.getIn(['customFields', 'list']).map(i => i.key),
closedLive: !!state.getIn([ 'sessions', 'errors' ]),
}
}, {
toggleFavorite, fetchListIntegration, setSessionPath
@ -67,8 +65,9 @@ export default class PlayerBlockHeader extends React.PureComponent {
backHandler = () => {
const { history, siteId, sessionPath } = this.props;
if (sessionPath === history.location.pathname || sessionPath.includes("/session/")) {
history.push(withSiteId(SESSIONS_ROUTE), siteId);
const isLiveSession = sessionPath.includes("/assist");
if (sessionPath === history.location.pathname || sessionPath.includes("/session/") || isLiveSession) {
history.push(withSiteId(isLiveSession ? ASSIST_ROUTE: SESSIONS_ROUTE, siteId));
} else {
history.push(sessionPath ? sessionPath : withSiteId(SESSIONS_ROUTE, siteId));
}
@ -107,6 +106,7 @@ export default class PlayerBlockHeader extends React.PureComponent {
hasSessionsPath,
sessionPath,
metaList,
closedLive = false,
} = this.props;
const _live = live && !hasSessionsPath;
@ -122,20 +122,8 @@ export default class PlayerBlockHeader extends React.PureComponent {
<div className={ stl.divider } />
{ _live && <AssistTabs userId={userId} userNumericHash={userNumericHash} />}
{/* <div className="mx-4 flex items-center">
<CountryFlag country={ userCountry } />
<div className="ml-2 font-normal color-gray-dark mt-1 text-sm">
{ formatTimeOrDate(startedAt) } <span>{ this.props.local === 'UTC' ? 'UTC' : ''}</span>
</div>
</div>
<HeaderInfo icon={ browserIcon(userBrowser) } label={ `v${ userBrowserVersion }` } />
<HeaderInfo icon={ deviceTypeIcon(userDeviceType) } label={ capitalise(userDevice) } />
<HeaderInfo icon="expand-wide" label={ this.getDimension(width, height) } />
<HeaderInfo icon={ osIcon(userOs) } label={ userOs } /> */}
<div className='ml-auto flex items-center'>
<div className={cn("ml-auto flex items-center", { 'hidden' : closedLive })}>
{ live && hasSessionsPath && (
<>
<div className={stl.liveSwitchButton} onClick={() => this.props.setSessionPath('')}>

View file

@ -11,7 +11,7 @@ import {
} from 'UI';
import { deviceTypeIcon } from 'App/iconNames';
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
import { session as sessionRoute, withSiteId } from 'App/routes';
import { session as sessionRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes';
import { durationFormatted, formatTimeOrDate } from 'App/date';
import stl from './sessionItem.css';
import LiveTag from 'Shared/LiveTag';
@ -136,7 +136,7 @@ export default class SessionItem extends React.PureComponent {
<div className="flex items-center">
<div className={ stl.playLink } id="play-button" data-viewed={ viewed }>
<Link to={ sessionRoute(sessionId) }>
<Link to={ isAssist ? liveSessionRoute(sessionId) : sessionRoute(sessionId) }>
<Icon name={ !viewed && !isAssist ? 'play-fill' : 'play-circle-light' } size="42" color={isAssist ? "tealx" : "teal"} />
</Link>
</div>

View file

@ -26,6 +26,7 @@ export enum ConnectionStatus {
Inactive,
Disconnected,
Error,
Closed,
}
export enum RemoteControlStatus {
@ -37,6 +38,8 @@ export enum RemoteControlStatus {
export function getStatusText(status: ConnectionStatus): string {
switch(status) {
case ConnectionStatus.Closed:
return 'Closed...';
case ConnectionStatus.Connecting:
return "Connecting...";
case ConnectionStatus.Connected:

View file

@ -85,7 +85,8 @@ export const sessions = params => queried('/sessions', params);
export const assist = params => queried('/assist', params);
export const session = (sessionId = ':sessionId', hash) => hashed(`/session/${ sessionId }`, hash);
export const liveSession = (sessionId = ':sessionId', hash) => hashed(`/live/session/${ sessionId }`, hash);
export const liveSession = (sessionId = ':sessionId', hash) => hashed(`/assist/${ sessionId }`, hash);
// export const liveSession = (sessionId = ':sessionId', hash) => hashed(`/live/session/${ sessionId }`, hash);
export const errors = params => queried('/errors', params);
export const error = (id = ':errorId', hash) => hashed(`/errors/${ id }`, hash);
@ -132,7 +133,6 @@ export function isRoute(route, path){
const SITE_CHANGE_AVALIABLE_ROUTES = [ sessions(), assist(), dashboard(), errors(), onboarding('')];
export const siteChangeAvaliable = path => SITE_CHANGE_AVALIABLE_ROUTES.some(r => isRoute(r, path));
export const redirects = Object.entries({
[ client('custom-fields') ]: client(CLIENT_TABS.CUSTOM_FIELDS),
});

View file

@ -13,7 +13,7 @@ module.exports = {
//'appearance',
// 'backgroundAttachment',
'backgroundColor',
// 'backgroundOpacity',
'backgroundOpacity',
// 'backgroundPosition',
// 'backgroundRepeat',
// 'backgroundSize',