feat(ui): redesign player header; move user data to header

This commit is contained in:
sylenien 2022-05-23 13:52:44 +02:00 committed by Delirium
parent ea658316a2
commit 3aac6cf130
5 changed files with 148 additions and 118 deletions

View file

@ -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>

View file

@ -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)

View file

@ -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 {
);
}
}

View file

@ -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" />
))}

View file

@ -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: [],