feat(ui): redesign player header; move user data to header
This commit is contained in:
parent
ea658316a2
commit
3aac6cf130
5 changed files with 148 additions and 118 deletions
|
|
@ -11,20 +11,20 @@ import EventGroupWrapper from './EventGroupWrapper';
|
|||
import styles from './eventsBlock.module.css';
|
||||
import EventSearch from './EventSearch/EventSearch';
|
||||
|
||||
@connect(state => ({
|
||||
@connect(state => ({
|
||||
session: state.getIn([ 'sessions', 'current' ]),
|
||||
filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]),
|
||||
eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]),
|
||||
selectedEvents: state.getIn([ 'events', 'selected' ]),
|
||||
targetDefinerDisplayed: state.getIn([ 'components', 'targetDefiner', 'isDisplayed' ]),
|
||||
testsAvaliable: false,
|
||||
testsAvaliable: false,
|
||||
}), {
|
||||
showTargetDefiner,
|
||||
setSelected,
|
||||
setEventFilter
|
||||
})
|
||||
export default class EventsBlock extends React.PureComponent {
|
||||
state = {
|
||||
state = {
|
||||
editingEvent: null,
|
||||
mouseOver: false,
|
||||
query: ''
|
||||
|
|
@ -36,14 +36,14 @@ export default class EventsBlock extends React.PureComponent {
|
|||
defaultHeight: 300
|
||||
});
|
||||
|
||||
write = ({ target: { value, name } }) => {
|
||||
write = ({ target: { value, name } }) => {
|
||||
const { filter } = this.state;
|
||||
this.setState({ query: value })
|
||||
this.props.setEventFilter({ query: value, filter })
|
||||
|
||||
setTimeout(() => {
|
||||
this.props.setEventFilter({ query: value, filter })
|
||||
|
||||
setTimeout(() => {
|
||||
if (!this.scroller.current) return;
|
||||
|
||||
|
||||
this.scroller.current.scrollToRow(0);
|
||||
}, 100)
|
||||
}
|
||||
|
|
@ -55,11 +55,11 @@ export default class EventsBlock extends React.PureComponent {
|
|||
|
||||
this.scroller.current.forceUpdateGrid();
|
||||
|
||||
setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
if (!this.scroller.current) return;
|
||||
|
||||
|
||||
this.scroller.current.scrollToRow(0);
|
||||
}, 100)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
onSetEventFilter = (e, { name, value }) => {
|
||||
|
|
@ -84,7 +84,7 @@ export default class EventsBlock extends React.PureComponent {
|
|||
if (!this.state.mouseOver) {
|
||||
this.scroller.current.scrollToRow(this.props.currentTimeEventIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCheckboxClick(e, event) {
|
||||
|
|
@ -124,7 +124,7 @@ export default class EventsBlock extends React.PureComponent {
|
|||
onMouseLeave = () => this.setState({ mouseOver: false })
|
||||
|
||||
renderGroup = ({ index, key, style, parent }) => {
|
||||
const {
|
||||
const {
|
||||
session: { events },
|
||||
selectedEvents,
|
||||
currentTimeEventIndex,
|
||||
|
|
@ -142,16 +142,16 @@ export default class EventsBlock extends React.PureComponent {
|
|||
const isCurrent = index === currentTimeEventIndex;
|
||||
const isEditing = this.state.editingEvent === event;
|
||||
return (
|
||||
<CellMeasurer
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={this.cache}
|
||||
parent={parent}
|
||||
parent={parent}
|
||||
rowIndex={index}
|
||||
>
|
||||
{({measure, registerChild}) => (
|
||||
<div style={style} ref={registerChild}>
|
||||
<EventGroupWrapper
|
||||
query={query}
|
||||
query={query}
|
||||
presentInSearch={eventsIndex.includes(index)}
|
||||
isFirst={index==0}
|
||||
mesureHeight={measure}
|
||||
|
|
@ -173,12 +173,12 @@ export default class EventsBlock extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const { query } = this.state;
|
||||
const {
|
||||
const {
|
||||
testsAvaliable,
|
||||
session: {
|
||||
events,
|
||||
userNumericHash,
|
||||
userDisplayName,
|
||||
userDisplayName,
|
||||
userId,
|
||||
revId,
|
||||
userAnonymousId
|
||||
|
|
@ -191,16 +191,7 @@ export default class EventsBlock extends React.PureComponent {
|
|||
return (
|
||||
<>
|
||||
<div className={ cn(styles.header, 'p-3') }>
|
||||
<UserCard
|
||||
className=""
|
||||
userNumericHash={userNumericHash}
|
||||
userDisplayName={userDisplayName}
|
||||
userId={userId}
|
||||
revId={revId}
|
||||
userAnonymousId={userAnonymousId}
|
||||
/>
|
||||
|
||||
<div className={ cn(styles.hAndProgress, 'mt-3') }>
|
||||
<div className={ cn(styles.hAndProgress, 'mt-3') }>
|
||||
<EventSearch
|
||||
onChange={this.write}
|
||||
clearSearch={this.clearSearch}
|
||||
|
|
@ -209,18 +200,18 @@ export default class EventsBlock extends React.PureComponent {
|
|||
<div className="text-lg">{ `User Events (${ events.size })` }</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={ cn("flex-1 px-3 pb-3", styles.eventsList) }
|
||||
id="eventList"
|
||||
<div
|
||||
className={ cn("flex-1 px-3 pb-3", styles.eventsList) }
|
||||
id="eventList"
|
||||
data-openreplay-masked
|
||||
onMouseOver={ this.onMouseOver }
|
||||
onMouseLeave={ this.onMouseLeave }
|
||||
>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<List
|
||||
<List
|
||||
ref={this.scroller}
|
||||
className={ styles.eventsList }
|
||||
height={height}
|
||||
|
|
@ -231,7 +222,7 @@ export default class EventsBlock extends React.PureComponent {
|
|||
deferredMeasurementCache={this.cache}
|
||||
rowHeight={this.cache.rowHeight}
|
||||
rowRenderer={this.renderGroup}
|
||||
scrollToAlignment="start"
|
||||
scrollToAlignment="start"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
|
|
|||
|
|
@ -1,55 +1,113 @@
|
|||
import React, { useState } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { List } from 'immutable'
|
||||
import { Avatar, TextEllipsis, SlideModal } from 'UI'
|
||||
import { countries } from 'App/constants';
|
||||
import { browserIcon, osIcon, deviceTypeIcon } from 'App/iconNames';
|
||||
import { formatTimeOrDate, formatDateTimeDefault } from 'App/date';
|
||||
import { Avatar, TextEllipsis, SlideModal, Popup, CountryFlag, Icon } from 'UI'
|
||||
import cn from 'classnames'
|
||||
import Metadata from '../Metadata'
|
||||
import { withRequest } from 'HOCs'
|
||||
import SessionList from '../Metadata/SessionList'
|
||||
import SessionInfoItem from '../../SessionInfoItem'
|
||||
|
||||
function UserCard({ className, userNumericHash, userDisplayName, similarSessions, userId, userAnonymousId, request, loading, revId }) {
|
||||
function UserCard({
|
||||
className,
|
||||
request,
|
||||
session,
|
||||
width,
|
||||
height,
|
||||
}) {
|
||||
const [showUserSessions, setShowUserSessions] = useState(false)
|
||||
const hasUserDetails = !!userId || !!userAnonymousId;
|
||||
const {
|
||||
userBrowser,
|
||||
userDevice,
|
||||
userCountry,
|
||||
userBrowserVersion,
|
||||
userOs,
|
||||
userOsVersion,
|
||||
startedAt,
|
||||
userId,
|
||||
userAnonymousId,
|
||||
userNumericHash,
|
||||
userDisplayName,
|
||||
userDeviceType,
|
||||
revId,
|
||||
} = session;
|
||||
|
||||
const hasUserDetails = !!userId || !!userAnonymousId;
|
||||
const showSimilarSessions = () => {
|
||||
setShowUserSessions(true);
|
||||
request({ key: !userId ? 'USERANONYMOUSID' : 'USERID', value: userId || userAnonymousId });
|
||||
}
|
||||
|
||||
const getDimension = (width, height) => {
|
||||
return width && height ? (
|
||||
<div className="flex items-center">
|
||||
{ width || 'x' } <Icon name="close" size="12" className="mx-1" /> { height || 'x' }
|
||||
</div>
|
||||
) : <span className="">Resolution N/A</span>;
|
||||
}
|
||||
|
||||
const avatarbgSize = '38px'
|
||||
return (
|
||||
<div className={cn("bg-white rounded border", className)}>
|
||||
<div className={ cn("flex items-center p-3")}>
|
||||
<Avatar iconSize="36" width="50px" height="50px" seed={ userNumericHash } />
|
||||
<div className={cn("bg-white flex items-center w-full", className)}>
|
||||
<div className="flex items-center">
|
||||
<Avatar iconSize="23" width={avatarbgSize} height={avatarbgSize} seed={ userNumericHash } />
|
||||
<div className="ml-3 overflow-hidden leading-tight">
|
||||
<TextEllipsis
|
||||
noHint
|
||||
className={ cn("text-xl", { 'color-teal cursor-pointer' : hasUserDetails })}
|
||||
className={ cn("font-medium", { 'color-teal cursor-pointer' : hasUserDetails })}
|
||||
onClick={hasUserDetails && showSimilarSessions}
|
||||
>
|
||||
{ userDisplayName }
|
||||
</TextEllipsis>
|
||||
|
||||
<div className="text-sm color-gray-medium">
|
||||
<span>{formatDateTimeDefault(startedAt)}</span>
|
||||
<span className="mx-1">·</span>
|
||||
<span>{userBrowser}, {userDevice}</span>
|
||||
<span className="mx-1">·</span>
|
||||
<span>{countries[userCountry]}</span>
|
||||
<span className="mx-1">·</span>
|
||||
<Popup
|
||||
trigger={<span className="color-teal cursor-pointer">More</span>}
|
||||
|
||||
content={(
|
||||
<div className=''>
|
||||
<SessionInfoItem comp={<CountryFlag country={ userCountry } />} label={countries[userCountry]} value={ formatTimeOrDate(startedAt) } />
|
||||
<SessionInfoItem icon={browserIcon(userBrowser)} label={userBrowser} value={ `v${ userBrowserVersion }` } />
|
||||
<SessionInfoItem icon={osIcon(userOs)} label={userOs} value={ userOsVersion } />
|
||||
<SessionInfoItem icon={deviceTypeIcon(userDeviceType)} label={userDeviceType} value={ getDimension(width, height) } isLast />
|
||||
</div>
|
||||
)}
|
||||
position="bottom center"
|
||||
hoverable
|
||||
disabled={false}
|
||||
on="hover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{revId && (
|
||||
<div className="border-t py-2 px-3">
|
||||
<div className="border-l py-2 px-3">
|
||||
<span className="font-medium">Rev ID:</span> {revId}
|
||||
</div>
|
||||
)}
|
||||
<div className="border-t">
|
||||
<Metadata />
|
||||
</div>
|
||||
<SlideModal
|
||||
|
||||
{/* <SlideModal
|
||||
title={ <div>User Sessions</div> }
|
||||
isDisplayed={ showUserSessions }
|
||||
content={ showUserSessions && <SessionList similarSessions={ similarSessions } loading={ loading } /> }
|
||||
onClose={ () => showUserSessions ? setShowUserSessions(false) : null }
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const component = React.memo(connect(state => ({ session: state.getIn([ 'sessions', 'current' ]) }))(UserCard))
|
||||
|
||||
export default withRequest({
|
||||
initialData: List(),
|
||||
endpoint: '/metadata/session_search',
|
||||
dataWrapper: data => Object.values(data),
|
||||
dataName: 'similarSessions',
|
||||
})(UserCard)
|
||||
})(component)
|
||||
|
|
|
|||
|
|
@ -1,28 +1,25 @@
|
|||
import React from 'react';
|
||||
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, assist as assistRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes';
|
||||
import { Button, Icon, CountryFlag, IconButton, BackLink, Popup, Link } from 'UI';
|
||||
import { Button, Icon, BackLink, Link } from 'UI';
|
||||
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
|
||||
import cn from 'classnames';
|
||||
import { connectPlayer } from 'Player';
|
||||
// import HeaderInfo from './HeaderInfo';
|
||||
import SharePopup from '../shared/SharePopup/SharePopup';
|
||||
import { countries } from 'App/constants';
|
||||
import SessionMetaList from 'Shared/SessionItem/SessionMetaList';
|
||||
import Bookmark from 'Shared/Bookmark'
|
||||
import UserCard from './EventsBlock/UserCard';
|
||||
|
||||
import stl from './playerBlockHeader.module.css';
|
||||
import Issues from './Issues/Issues';
|
||||
import Autoplay from './Autoplay';
|
||||
import AssistActions from '../Assist/components/AssistActions';
|
||||
import AssistTabs from '../Assist/components/AssistTabs';
|
||||
import SessionInfoItem from './SessionInfoItem'
|
||||
|
||||
const SESSIONS_ROUTE = sessionsRoute();
|
||||
const ASSIST_ROUTE = assistRoute();
|
||||
|
||||
@connectPlayer(state => ({
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
|
|
@ -75,23 +72,7 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
const {
|
||||
width,
|
||||
height,
|
||||
session: {
|
||||
sessionId,
|
||||
userCountry,
|
||||
userId,
|
||||
userNumericHash,
|
||||
favorite,
|
||||
startedAt,
|
||||
userBrowser,
|
||||
userOs,
|
||||
userOsVersion,
|
||||
userDevice,
|
||||
userBrowserVersion,
|
||||
userDeviceType,
|
||||
live,
|
||||
metadata,
|
||||
},
|
||||
loading,
|
||||
session,
|
||||
disabled,
|
||||
jiraConfig,
|
||||
fullscreen,
|
||||
|
|
@ -102,17 +83,31 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
} = this.props;
|
||||
// const _live = isAssist;
|
||||
|
||||
const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => {
|
||||
const {
|
||||
sessionId,
|
||||
userId,
|
||||
userNumericHash,
|
||||
favorite,
|
||||
live,
|
||||
metadata,
|
||||
} = session;
|
||||
let _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => {
|
||||
const value = metadata[key];
|
||||
return { label: key, value };
|
||||
});
|
||||
console.log(session.toJS())
|
||||
|
||||
return (
|
||||
<div className={ cn(stl.header, "flex justify-between", { "hidden" : fullscreen}) }>
|
||||
<div className="flex w-full items-center">
|
||||
|
||||
<BackLink onClick={this.backHandler} label="Back" />
|
||||
|
||||
<div className={ stl.divider } />
|
||||
<UserCard
|
||||
className=""
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
{ isAssist && <AssistTabs userId={userId} userNumericHash={userNumericHash} />}
|
||||
|
||||
<div className={cn("ml-auto flex items-center", { 'hidden' : closedLive })}>
|
||||
|
|
@ -126,31 +121,9 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
<div className={ stl.divider } />
|
||||
</>
|
||||
)}
|
||||
|
||||
{ isAssist && (
|
||||
<>
|
||||
<SessionMetaList className="" metaList={_metaList} maxLength={2} />
|
||||
<div className={ stl.divider } />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Popup
|
||||
theme="tippy-light"
|
||||
multiple={false}
|
||||
unmountHTMLWhenHide={true}
|
||||
content={(
|
||||
<div className=''>
|
||||
<SessionInfoItem comp={<CountryFlag country={ userCountry } />} label={countries[userCountry]} value={ formatTimeOrDate(startedAt) } />
|
||||
<SessionInfoItem icon={browserIcon(userBrowser)} label={userBrowser} value={ `v${ userBrowserVersion }` } />
|
||||
<SessionInfoItem icon={osIcon(userOs)} label={userOs} value={ userOsVersion } />
|
||||
<SessionInfoItem icon={deviceTypeIcon(userDeviceType)} label={userDeviceType} value={ this.getDimension(width, height) } isLast />
|
||||
</div>
|
||||
)}
|
||||
// trigger="click"
|
||||
hideOnClick={true}
|
||||
>
|
||||
<IconButton icon="info-circle" primaryText label="More Info" disabled={disabled} />
|
||||
</Popup>
|
||||
|
||||
<SessionMetaList className="" metaList={_metaList} maxLength={2} />
|
||||
|
||||
<div className={ stl.divider } />
|
||||
{ isAssist && <AssistActions userId={userId} /> }
|
||||
{ !isAssist && (
|
||||
|
|
@ -184,4 +157,3 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ interface Props {
|
|||
export default function SessionMetaList(props: Props) {
|
||||
const { className = '', metaList, maxLength = 4 } = props
|
||||
return (
|
||||
<div className={cn("text-sm flex items-start", className)}>
|
||||
<div className={cn("text-sm flex items-center", className)}>
|
||||
{metaList.slice(0, maxLength).map(({ label, value }, index) => (
|
||||
<MetaItem key={index} label={label} value={''+value} className="mr-3" />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -119,23 +119,32 @@ module.exports = {
|
|||
'border-gray': '0 0 0 1px #999',
|
||||
},
|
||||
keyframes: {
|
||||
'fade-in': {
|
||||
'0%': {
|
||||
opacity: '0',
|
||||
// transform: 'translateY(-10px)'
|
||||
},
|
||||
'100%': {
|
||||
opacity: '1',
|
||||
// transform: 'translateY(0)'
|
||||
},
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fade-in 0.2s ease-out'
|
||||
}
|
||||
'fade-in': {
|
||||
'0%': {
|
||||
opacity: '0',
|
||||
// transform: 'translateY(-10px)'
|
||||
},
|
||||
'100%': {
|
||||
opacity: '1',
|
||||
// transform: 'translateY(0)'
|
||||
},
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fade-in 0.2s ease-out'
|
||||
},
|
||||
colors: {
|
||||
'disabled-text': 'rgba(0,0,0, 0.38)',
|
||||
},
|
||||
boxShadow: {
|
||||
'border-blue': `0 0 0 1px ${colors['active-blue-border']}`,
|
||||
'border-main': `0 0 0 1px ${colors['main']}`,
|
||||
'border-gray': '0 0 0 1px #999',
|
||||
}
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
},
|
||||
content: [],
|
||||
variants: {
|
||||
visibility: ['responsive', 'hover', 'focus', 'group-hover']
|
||||
},
|
||||
plugins: [],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue