feat(ui): change events tab design, move action buttons to subheader

This commit is contained in:
sylenien 2022-05-23 15:55:00 +02:00 committed by Delirium
parent aff6f54397
commit c584b0f653
17 changed files with 280 additions and 155 deletions

View file

@ -2,7 +2,7 @@ import React, { useState } from 'react'
import EventsBlock from '../Session_/EventsBlock';
import PageInsightsPanel from '../Session_/PageInsightsPanel/PageInsightsPanel'
import { Controls as PlayerControls } from 'Player';
import { Tabs } from 'UI';
import Tabs from './Tabs';
import { connectPlayer } from 'Player';
import NewBadge from 'Shared/NewBadge';
@ -29,7 +29,7 @@ export default function RightBlock() {
}
}
return (
<div style={{ width: '270px', height: 'calc(100vh- 50px)'}} className="flex flex-col">
<div style={{ width: '270px', height: 'calc(100vh- 50px)'}} className="flex flex-col bg-white border-l">
<div className="relative">
<Tabs
tabs={ TABS }
@ -40,8 +40,8 @@ export default function RightBlock() {
<div className="absolute" style={{ left: '160px', top: '13px' }}>{ <NewBadge />}</div>
</div>
{
renderActiveTab(activeTab)
}
renderActiveTab(activeTab)
}
</div>
)
)
}

View file

@ -0,0 +1,32 @@
import React from 'react';
import cn from 'classnames';
import stl from './tabs.css';
interface Props {
tabs: Array<any>;
active: string;
onClick: (key: any) => void;
border?: boolean;
className?: string;
}
const Tabs = ({ tabs, active, onClick, border = true, className }: Props) => (
<div className={ cn(stl.tabs, className, { [ stl.bordered ]: border }) } role="tablist" >
{ tabs.map(({ key, text, hidden = false, disabled = false }) => (
<div
key={ key }
className={ cn(stl.tab, { [ stl.active ]: active === key, [ stl.disabled ]: disabled }) }
data-hidden={ hidden }
onClick={ onClick && (() => onClick(key)) }
role="tab"
data-openreplay-label={text}
>
{ text }
</div>
))}
</div>
);
Tabs.displayName = 'Tabs';
export default Tabs;

View file

@ -0,0 +1 @@
export { default } from './Tabs'

View file

@ -0,0 +1,32 @@
.tabs {
display: flex;
justify-content: flex-start;
align-items: center;
&.bordered {
border-bottom: solid thin $gray-light;
}
}
.tab {
padding: 14px 15px;
cursor: pointer;
transition: all 0.2s;
color: $gray-darkest;
border-bottom: solid thin transparent;
font-weight: 500;
white-space: nowrap;
&:hover {
color: $teal;
}
&.active {
color: $teal;
border-bottom: solid thin $teal;
}
}
.disabled {
pointer-events: none;
opacity: 0.5;
}

View file

@ -3,7 +3,7 @@ import { useEffect } from 'react';
import { connect } from 'react-redux';
import { Loader } from 'UI';
import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
import {
import {
PlayerProvider,
connectPlayer,
init as initPlayer,
@ -20,26 +20,33 @@ import PlayerBlock from '../Session_/PlayerBlock';
import styles from '../Session_/session.module.css';
const InitLoader = connectPlayer(state => ({
const InitLoader = connectPlayer(state => ({
loading: !state.initialized
}))(Loader);
const PlayerContentConnected = connectPlayer(state => ({
const PlayerContentConnected = connectPlayer(state => ({
showEvents: !state.showEvents
}))(PlayerContent);
function PlayerContent({ live, fullscreen, showEvents }) {
function PlayerContent({ live, fullscreen }) {
return (
<div className={ cn(styles.session, 'relative') } data-fullscreen={fullscreen}>
<PlayerBlock />
{ showEvents && !live && !fullscreen && <RightBlock /> }
<PlayerBlock />
</div>
)
}
function RightMenu({ showEvents, live, fullscreen }) {
console.log(!live, !fullscreen, showEvents)
return showEvents && !live && !fullscreen && <RightBlock />
}
const ConnectedMenu = connectPlayer(state => ({
showEvents: !state.showEvents}))(RightMenu)
function WebPlayer (props) {
const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt} = props;
const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, config, showEvents } = props;
useEffect(() => {
initPlayer(session, jwt);
@ -60,8 +67,13 @@ function WebPlayer (props) {
return (
<PlayerProvider>
<InitLoader className="flex-1">
<PlayerBlockHeader fullscreen={fullscreen}/>
<PlayerContentConnected fullscreen={fullscreen} live={live} />
<div className="flex">
<div className="w-full">
<PlayerBlockHeader fullscreen={fullscreen}/>
<PlayerContentConnected fullscreen={fullscreen} live={live} />
</div>
<ConnectedMenu fullscreen={fullscreen} live={live} />
</div>
</InitLoader>
</PlayerProvider>
);
@ -72,6 +84,7 @@ export default connect(state => ({
jwt: state.get('jwt'),
// config: state.getIn([ 'user', 'account', 'iceServers' ]),
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
showEvents: state.get('showEvents'),
}), {
toggleFullscreen,
closeBottomBlock,

View file

@ -2,7 +2,7 @@ import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { setAutoplayValues } from 'Duck/sessions'
import { session as sessionRoute } from 'App/routes';
import { Link, Icon, Toggler, Popup } from 'UI';
import { Link, Icon, Slider, Toggler } from 'UI';
import { connectPlayer } from 'Player/store';
import { Controls as PlayerControls } from 'Player';
@ -15,20 +15,21 @@ function Autoplay(props) {
return (
<div className="flex items-center">
<Link to={ sessionRoute(previousId) } disabled={!previousId}>
<Icon name="prev1" size="20" color="teal" />
</Link>
<Popup content={'Autoplay'} distance={22} >
<div onClick={props.toggleAutoplay} className="cursor-pointer flex items-center mr-2">
<Toggler
name="sessionsLive"
onChange={ props.toggleAutoplay }
checked={ autoplay }
style={{ margin: '0px 10px 0px 12px'}}
name="sessionsLive"
onChange={ props.toggleAutoplay }
checked={ autoplay }
plain
/>
</Popup>
<span className="ml-2">Auto-Play</span>
</div>
<Link to={ sessionRoute(previousId) } disabled={!previousId}>
<Icon name="prev1" size="16" color="teal" />
</Link>
<Link to={ sessionRoute(nextId) } disabled={!nextId}>
<Icon name="next1" size="20" color="teal" />
<Icon name="next1" size="16" color="teal" />
</Link>
</div>
)
@ -41,6 +42,6 @@ const connectAutoplay = connect(state => ({
export default connectAutoplay(connectPlayer(state => ({
autoplay: state.autoplay,
}), {
toggleAutoplay: PlayerControls.toggleAutoplay
}), {
toggleAutoplay: PlayerControls.toggleAutoplay
})(Autoplay))

View file

@ -5,48 +5,44 @@ export default function EventSearch(props) {
const { onChange, clearSearch, value, header } = props;
const [showSearch, setShowSearch] = useState(false)
useEffect(() => {
useEffect(() => {
return () => {
clearSearch()
}
}, [])
const toggleSearch = () => {
setShowSearch(!showSearch)
clearSearch();
}
return (
<div className="flex items-center w-full">
<div className="flex flex-1 relative items-center" style={{ height: '32px' }}>
{ showSearch ?
<div className="flex items-center">
<div className="flex items-center w-full relative">
<div className="flex flex-1 flex-col">
<div className='flex flex-center justify-between'>
<span>{header}</span>
<div
onClick={() => toggleSearch()}
className=" flex items-center justify-center bg-white cursor-pointer"
>
<Icon name={ showSearch ? 'close' : 'search'} size="18" />
</div>
</div>
{showSearch && (
<div className="flex items-center mt-2">
<Input
autoFocus
type="text"
placeholder="Filter Events"
className="absolute inset-0 w-full"
className="inset-0 w-full"
name="query"
value={value}
onChange={onChange}
style={{ height: '32px' }}
autocomplete="off"
/>
<div
onClick={() => { setShowSearch(!showSearch); clearSearch() }}
className="flex items-center justify-center cursor-pointer absolute right-0"
style={{ height: '30px', width: '32px' }}
>
<Icon name={'close'} size="16" color="teal" />
</div>
</div>
:
header
}
)}
</div>
{ !showSearch &&
<div
onClick={() => setShowSearch(!showSearch)}
className="border rounded flex items-center justify-center bg-white cursor-pointer"
style={{ height: '32px', width: '32px' }}
>
<Icon name={ showSearch ? 'close' : 'search'} size="12" color="teal" />
</div>
}
</div>
)
}

View file

@ -190,20 +190,20 @@ export default class EventsBlock extends React.PureComponent {
return (
<>
<div className={ cn(styles.header, 'p-3') }>
<div className={ cn(styles.header, 'p-4') }>
<div className={ cn(styles.hAndProgress, 'mt-3') }>
<EventSearch
onChange={this.write}
clearSearch={this.clearSearch}
value={query}
header={
<div className="text-lg">{ `User Events (${ events.size })` }</div>
<div className="text-xl">User Events <span className="color-gray-medium">{ events.size }</span></div>
}
/>
</div>
</div>
<div
className={ cn("flex-1 px-3 pb-3", styles.eventsList) }
className={ cn("flex-1 px-4 pb-4", styles.eventsList) }
id="eventList"
data-openreplay-masked
onMouseOver={ this.onMouseOver }

View file

@ -1,55 +1,45 @@
import React, { useCallback, useState } from 'react';
import React from 'react';
import { connect } from 'react-redux';
import { NoContent, IconButton, Popup } from 'UI';
import withToggle from 'HOCs/withToggle';
import MetadataItem from './MetadataItem';
import stl from './metadata.module.css';
import cn from 'classnames';
export default connect(state => ({
metadata: state.getIn([ 'sessions', 'current', 'metadata' ]),
}))(function Metadata ({ metadata }) {
const [ visible, setVisible ] = useState(false);
metadata = {
test: 'a',
dealership: 'very test wow',
"bklajsdlkas-123": 123123,
test: 'a',
dealership: 'very test wow',
"bklajsdlkas-123": 123123,
test: 'a',
dealership: 'very test wow',
"bklajsdlkas-123": 123123,
test: 'a',
dealership: 'very test wow',
"bklajsdlkas-123": 123123,
test: 'a',
dealership: 'very test wow',
"bklajsdlkas-123": 123123,
test: 'a',
dealership: 'very test wow',
"bklajsdlkas-123": 123123,
}
const metaLenth = Object.keys(metadata).length;
const toggle = useCallback(() => metaLenth > 0 && setVisible(v => !v), []);
if (metaLenth === 0) {
return (
(<span className="text-sm color-gray-medium">Check <a href="https://docs.openreplay.com/installation/metadata" target="_blank" className="link">how to use Metadata</a> if you havent yet done so.</span>)
)
}
return (
<>
<Popup
content={
<div className="p-2">
Check <a href="https://docs.openreplay.com/installation/metadata" target="_blank" className="link">how to use Metadata</a> if you havent yet done so.
</div>
}
on="click"
disabled={metaLenth > 0}
size="tiny"
inverted
position="top center"
>
<IconButton
className={cn("w-full", { 'opacity-25' : metaLenth === 0 })}
onClick={ toggle }
icon="id-card"
plain
label="Metadata"
primaryText
active={ visible }
id="metadata-button"
// disabled={ metadata.length === 0 }
/>
</Popup>
{ visible &&
<div className={ stl.modal } >
<NoContent show={ metaLenth === 0 } size="small">
{ Object.keys(metadata).map((key) => {
// const key = Object.keys(i)[0]
const value = metadata[key]
return <MetadataItem item={ { value, key } } key={ key } />
}) }
</NoContent>
</div>
}
</>
<div>
{ Object.keys(metadata).map((key) => {
// const key = Object.keys(i)[0]
const value = metadata[key]
return <MetadataItem item={ { value, key } } key={ key } />
}) }
</div>
);
});

View file

@ -1,15 +1,11 @@
.eventsBlock {
width: 270px;
/* padding: 0 10px; */
margin-bottom: 5px;
}
.header {
/* height: 40px; */
/* margin-bottom: 15px; */
padding-left: 2px;
/* padding-right: 0px; */
& .hAndProgress {
display:flex;
justify-content: space-between;
@ -23,7 +19,7 @@
background: #ffcc99;
}
& :global(.progress) {
font-size: 9px;
font-size: 9px;
}
}
}
@ -70,5 +66,3 @@
color: $gray-medium;
justify-content: space-between;
}

View file

@ -59,9 +59,9 @@ export default class Player extends React.PureComponent {
{!live && !fullscreen && <EventsToggleButton /> }
<div className="relative flex-1 overflow-hidden">
<Overlay nextId={nextId} togglePlay={PlayerControls.togglePlay} closedLive={closedLive} />
<div
<div
className={ stl.screenWrapper }
ref={ this.screenWrapper }
ref={ this.screenWrapper }
/>
</div>
<Controls

View file

@ -1,8 +1,8 @@
import React from 'react';
import cn from "classnames";
import { connect } from 'react-redux';
import { scale as scalePlayerScreen } from 'Player';
import {
import { scale as scalePlayerScreen } from 'Player';
import {
NONE,
CONSOLE,
NETWORK,
@ -28,26 +28,32 @@ import Fetch from './Fetch';
import Exceptions from './Exceptions/Exceptions';
import LongTasks from './LongTasks';
import Inspector from './Inspector';
import styles from './playerBlock.module.css';
import styles from './playerBlock.css';
import SubHeader from "./SubHeader";
@connect(state => ({
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
bottomBlock: state.getIn([ 'components', 'player', 'bottomBlock' ]),
sessionId: state.getIn([ 'sessions', 'current', 'sessionId' ]),
disabled: state.getIn([ 'components', 'targetDefiner', 'inspectorMode' ]),
}))
export default class PlayerBlock extends React.PureComponent {
componentDidUpdate(prevProps) {
if ([ prevProps.bottomBlock, this.props.bottomBlock ].includes(NONE) ||
if ([ prevProps.bottomBlock, this.props.bottomBlock ].includes(NONE) ||
prevProps.fullscreen !== this.props.fullscreen) {
scalePlayerScreen();
}
}
render() {
const { fullscreen, bottomBlock } = this.props;
const { fullscreen, bottomBlock, sessionId, disabled } = this.props;
return (
<div className={ cn(styles.playerBlock, "flex flex-col") }>
<SubHeader
sessionId={sessionId}
disabled={disabled}
/>
<Player
className="flex-1"
bottomBlockIsActive={ !fullscreen && bottomBlock !== NONE }
@ -60,7 +66,7 @@ export default class PlayerBlock extends React.PureComponent {
{ bottomBlock === NETWORK &&
<Network />
}
{ bottomBlock === STACKEVENTS &&
{ bottomBlock === STACKEVENTS &&
<StackEvents />
}
{ bottomBlock === STORAGE &&
@ -72,10 +78,10 @@ export default class PlayerBlock extends React.PureComponent {
{ bottomBlock === PERFORMANCE &&
<ConnectedPerformance />
}
{ bottomBlock === GRAPHQL &&
{ bottomBlock === GRAPHQL &&
<GraphQL />
}
{ bottomBlock === FETCH &&
{ bottomBlock === FETCH &&
<Fetch />
}
{ bottomBlock === EXCEPTIONS &&

View file

@ -124,33 +124,8 @@ export default class PlayerBlockHeader extends React.PureComponent {
<SessionMetaList className="" metaList={_metaList} maxLength={2} />
<div className={ stl.divider } />
{ isAssist && <AssistActions userId={userId} /> }
{ !isAssist && (
<>
<Autoplay />
<div className={ stl.divider } />
<Bookmark sessionId={sessionId} favorite={favorite} />
<div className={ stl.divider } />
<SharePopup
entity="sessions"
id={ sessionId }
showCopyLink={true}
trigger={
<Button
// className="mr-2"
// tooltip="Share Session"
// tooltipPosition="top right"
disabled={ disabled }
icon={ 'share-alt' }
variant="text-primary"
/>
}
/>
</>
)}
{/* { !isAssist && jiraConfig && jiraConfig.token && <Issues sessionId={ sessionId } /> } */}
{ <Issues sessionId={ sessionId } /> }
{ !isAssist && jiraConfig && jiraConfig.token && <Issues sessionId={ sessionId } /> }
</div>
</div>
</div>

View file

@ -0,0 +1,46 @@
import React from 'react';
import { Icon } from 'UI';
import Autoplay from './Autoplay';
import Bookmark from 'Shared/Bookmark'
import SharePopup from '../shared/SharePopup/SharePopup';
function SubHeader(props) {
const isAssist = window.location.pathname.includes('/assist/');
if (isAssist) return null;
return (
<div className="w-full p-4">
<div className="ml-auto flex items-center color-gray-medium" style={{ width: 'max-content' }}>
<div className="cursor-pointer">
<SharePopup
entity="sessions"
id={ props.sessionId }
showCopyLink={true}
trigger={
<div className="flex items-center">
<Icon
className="mr-2"
disabled={ props.disabled }
name="share-alt"
size="16"
plain
/>
<span>Share</span>
</div>
}
/>
</div>
<div className="mx-4">
<Bookmark />
</div>
<div>
<Autoplay />
</div>
<div>
</div>
</div>
</div>
)
}
export default React.memo(SubHeader)

View file

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'
import { Popup, Button } from 'UI'
import { Popup, Button, Icon } from 'UI'
import { toggleFavorite } from 'Duck/sessions'
import { connect } from 'react-redux'
import { toast } from 'react-toastify';
@ -10,11 +10,11 @@ interface Props {
sessionId: any,
isEnterprise: Boolean
}
function Bookmark(props : Props ) {
function Bookmark(props : Props ) {
const { sessionId, favorite, isEnterprise } = props;
const [isFavorite, setIsFavorite] = useState(favorite);
const ADDED_MESSAGE = isEnterprise ? 'Session added to vault' : 'Session added to your favorites';
const REMOVED_MESSAGE = isEnterprise ? 'Session removed from vault' : 'Session removed from your favorites';
const REMOVED_MESSAGE = isEnterprise ? 'Session removed from vault' : 'Session removed from your favorites';
const TOOLTIP_TEXT_ADD = isEnterprise ? 'Add to vault' : 'Add to favorites';
const TOOLTIP_TEXT_REMOVE = isEnterprise ? 'Remove from vault' : 'Remove from favorites';
@ -34,7 +34,7 @@ function Bookmark(props : Props ) {
}
return (
<Popup
<Popup
delay={500}
content={isFavorite ? TOOLTIP_TEXT_REMOVE : TOOLTIP_TEXT_ADD}
hideOnClick={true}
@ -42,9 +42,11 @@ function Bookmark(props : Props ) {
>
<Button
onClick={ toggleFavorite }
variant="text-primary"
icon={isFavorite ? ACTIVE_ICON : INACTIVE_ICON}
/>
data-favourite={ isFavorite }
>
<Icon name={ isFavorite ? ACTIVE_ICON : INACTIVE_ICON } color={isFavorite ? "teal" : undefined} size="16" />
<span className="ml-2">{isEnterprise ? 'Vault' : 'Bookmark'}</span>
</Button>
</Popup>
)
}

View file

@ -7,17 +7,18 @@ export default ({
className = '',
checked,
label = '',
plain,
}) => (
<div className={ className }>
<label className={styles.label}>
<div className={ styles.switch }>
<div className={ plain ? styles.switchPlain : styles.switch }>
<input
type={ styles.checkbox }
onClick={ onChange }
name={ name }
checked={ checked }
/>
<span className={ `${ styles.slider } ${ checked ? styles.checked : '' }` } />
<span className={ `${ plain ? styles.sliderPlain : styles.slider } ${ checked ? styles.checked : '' }` } />
</div>
{ label && <span>{ label }</span> }
</label>

View file

@ -59,3 +59,39 @@
.slider.checked:before {
/* transform: translateX(15px); */
}
.switchPlain {
position: relative;
display: inline-block;
width: 25px;
height: 12px;
}
.switchPlain input {
display:none;
}
.sliderPlain {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc !important; /* postss reset is bad */
transition: .4s;
border-radius: 34px !important;
}
.sliderPlain:before {
position: absolute;
content: "";
height: 10px;
width: 10px;
left: 2px;
bottom: 1px;
background: white;
transition: .4s;
border-radius: 50%;
}
.sliderPlain.checked:before {
transform: translateX(11px);
background: $teal;
}