fix random issues (tooltip, assist, button) (#596)

* fix(ui) - live player loader

* change(ui) - removed unused

* change(ui) - button changes

* fix(ui) - tooltip changes
This commit is contained in:
Shekar Siri 2022-07-12 17:40:24 +02:00
parent d1806b08ef
commit de9a11a7da
10 changed files with 287 additions and 298 deletions

View file

@ -1,153 +1,166 @@
import React, { useState, useEffect } from 'react'
import { Popup, Icon, IconButton } from 'UI'
import { connect } from 'react-redux'
import cn from 'classnames'
import React, { useState, useEffect } from 'react';
import { Popup, Icon, Button, IconButton } from 'UI';
import { connect } from 'react-redux';
import cn from 'classnames';
import { toggleChatWindow } from 'Duck/sessions';
import { connectPlayer } from 'Player/store';
import ChatWindow from '../../ChatWindow';
import { callPeer, requestReleaseRemoteControl, toggleAnnotation } from 'Player'
import { callPeer, requestReleaseRemoteControl, toggleAnnotation } from 'Player';
import { CallingState, ConnectionStatus, RemoteControlStatus } from 'Player/MessageDistributor/managers/AssistManager';
import RequestLocalStream from 'Player/MessageDistributor/managers/LocalStream';
import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream';
import { toast } from 'react-toastify';
import { confirm } from 'UI';
import stl from './AassistActions.module.css'
import stl from './AassistActions.module.css';
function onClose(stream) {
stream.getTracks().forEach(t=>t.stop());
stream.getTracks().forEach((t) => t.stop());
}
function onReject() {
toast.info(`Call was rejected.`);
toast.info(`Call was rejected.`);
}
function onError(e) {
toast.error(typeof e === 'string' ? e : e.message);
toast.error(typeof e === 'string' ? e : e.message);
}
interface Props {
userId: String,
toggleChatWindow: (state) => void,
calling: CallingState,
annotating: boolean,
peerConnectionStatus: ConnectionStatus,
remoteControlStatus: RemoteControlStatus,
hasPermission: boolean,
isEnterprise: boolean,
userId: String;
toggleChatWindow: (state) => void;
calling: CallingState;
annotating: boolean;
peerConnectionStatus: ConnectionStatus;
remoteControlStatus: RemoteControlStatus;
hasPermission: boolean;
isEnterprise: boolean;
}
function AssistActions({ toggleChatWindow, userId, calling, annotating, peerConnectionStatus, remoteControlStatus, hasPermission, isEnterprise }: Props) {
const [ incomeStream, setIncomeStream ] = useState<MediaStream | null>(null);
const [ localStream, setLocalStream ] = useState<LocalStream | null>(null);
const [ callObject, setCallObject ] = useState<{ end: ()=>void } | null >(null);
function AssistActions({
toggleChatWindow,
userId,
calling,
annotating,
peerConnectionStatus,
remoteControlStatus,
hasPermission,
isEnterprise,
}: Props) {
const [incomeStream, setIncomeStream] = useState<MediaStream | null>(null);
const [localStream, setLocalStream] = useState<LocalStream | null>(null);
const [callObject, setCallObject] = useState<{ end: () => void } | null>(null);
useEffect(() => {
return callObject?.end()
}, [])
useEffect(() => {
return callObject?.end();
}, []);
useEffect(() => {
if (peerConnectionStatus == ConnectionStatus.Disconnected) {
toast.info(`Live session was closed.`);
}
}, [peerConnectionStatus])
function call() {
RequestLocalStream().then(lStream => {
setLocalStream(lStream);
setCallObject(callPeer(
lStream,
setIncomeStream,
lStream.stop.bind(lStream),
onReject,
onError
));
}).catch(onError)
}
const confirmCall = async () => {
if (await confirm({
header: 'Start Call',
confirmButton: 'Call',
confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?`
})) {
call()
}
}
const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting
const cannotCall = (peerConnectionStatus !== ConnectionStatus.Connected) || (isEnterprise && !hasPermission)
const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled
return (
<div className="flex items-center">
{(onCall || remoteActive) && (
<>
<div
className={
cn(
'cursor-pointer p-2 flex items-center',
{[stl.disabled]: cannotCall}
)
}
onClick={ () => toggleAnnotation(!annotating) }
role="button"
>
<IconButton label={`Annotate`} icon={ annotating ? "pencil-stop" : "pencil"} primaryText redText={annotating} />
</div>
<div className={ stl.divider } />
</>
)}
<div
className={
cn(
'cursor-pointer p-2 flex items-center',
{[stl.disabled]: cannotCall}
)
useEffect(() => {
if (peerConnectionStatus == ConnectionStatus.Disconnected) {
toast.info(`Live session was closed.`);
}
onClick={ requestReleaseRemoteControl }
role="button"
>
<IconButton label={`Remote Control`} icon={ remoteActive ? "window-x" : "remote-control"} primaryText redText={remoteActive} />
</div>
<div className={ stl.divider } />
<Popup
content={ cannotCall ? "You dont have the permissions to perform this action." : `Call ${userId ? userId : 'User'}` }
>
<div
className={
cn(
'cursor-pointer p-2 flex items-center',
{[stl.disabled]: cannotCall}
)
}
onClick={ onCall ? callObject?.end : confirmCall}
role="button"
>
<IconButton size="small" primary={!onCall} red={onCall} label={onCall ? 'End' : 'Call'} icon="headset" />
</div>
</Popup>
}, [peerConnectionStatus]);
<div className="fixed ml-3 left-0 top-0" style={{ zIndex: 999 }}>
{ onCall && callObject && <ChatWindow endCall={callObject.end} userId={userId} incomeStream={incomeStream} localStream={localStream} /> }
</div>
</div>
)
function call() {
RequestLocalStream()
.then((lStream) => {
setLocalStream(lStream);
setCallObject(callPeer(lStream, setIncomeStream, lStream.stop.bind(lStream), onReject, onError));
})
.catch(onError);
}
const confirmCall = async () => {
if (
await confirm({
header: 'Start Call',
confirmButton: 'Call',
confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?`,
})
) {
call();
}
};
const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting;
const cannotCall = peerConnectionStatus !== ConnectionStatus.Connected || (isEnterprise && !hasPermission);
const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled;
return (
<div className="flex items-center">
{(onCall || remoteActive) && (
<>
<div
className={cn('cursor-pointer p-2 flex items-center', { [stl.disabled]: cannotCall })}
onClick={() => toggleAnnotation(!annotating)}
role="button"
>
<Button
icon={annotating ? 'pencil-stop' : 'pencil'}
variant={annotating ? 'text-red' : 'text-primary'}
style={{ height: '28px' }}
>
Annotate
</Button>
{/* <IconButton label={`Annotate`} icon={annotating ? 'pencil-stop' : 'pencil'} primaryText redText={annotating} /> */}
</div>
<div className={stl.divider} />
</>
)}
<div
className={cn('cursor-pointer p-2 flex items-center', { [stl.disabled]: cannotCall })}
onClick={requestReleaseRemoteControl}
role="button"
>
<Button
icon={remoteActive ? 'window-x' : 'remote-control'}
variant={remoteActive ? 'text-red' : 'text-primary'}
style={{ height: '28px' }}
>
Remote Control
</Button>
{/* <IconButton label={`Remote Control`} icon={remoteActive ? 'window-x' : 'remote-control'} primaryText redText={remoteActive} /> */}
</div>
<div className={stl.divider} />
<Popup content={cannotCall ? 'You dont have the permissions to perform this action.' : `Call ${userId ? userId : 'User'}`}>
<div
className={cn('cursor-pointer p-2 flex items-center', { [stl.disabled]: cannotCall })}
onClick={onCall ? callObject?.end : confirmCall}
role="button"
>
<Button icon="headset" variant={onCall ? 'text-red' : 'primary'} style={{ height: '28px' }}>
{onCall ? 'End' : 'Call'}
</Button>
{/* <IconButton size="small" primary={!onCall} red={onCall} label={onCall ? 'End' : 'Call'} icon="headset" /> */}
</div>
</Popup>
<div className="fixed ml-3 left-0 top-0" style={{ zIndex: 999 }}>
{onCall && callObject && (
<ChatWindow endCall={callObject.end} userId={userId} incomeStream={incomeStream} localStream={localStream} />
)}
</div>
</div>
);
}
const con = connect(state => {
const permissions = state.getIn([ 'user', 'account', 'permissions' ]) || []
return {
hasPermission: permissions.includes('ASSIST_CALL'),
isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee',
}
}, { toggleChatWindow })
const con = connect(
(state) => {
const permissions = state.getIn(['user', 'account', 'permissions']) || [];
return {
hasPermission: permissions.includes('ASSIST_CALL'),
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
};
},
{ toggleChatWindow }
);
export default con(connectPlayer(state => ({
calling: state.calling,
annotating: state.annotating,
remoteControlStatus: state.remoteControl,
peerConnectionStatus: state.peerConnectionStatus,
}))(AssistActions))
export default con(
connectPlayer((state) => ({
calling: state.calling,
annotating: state.annotating,
remoteControlStatus: state.remoteControl,
peerConnectionStatus: state.peerConnectionStatus,
}))(AssistActions)
);

View file

@ -1,7 +0,0 @@
.wrapper {
background-color: rgba(255, 255, 255, 1);
border-top-left-radius: 20px;
border-bottom-left-radius: 20px;
padding: 5px;
box-shadow: -1px 1px 1px rgba(0, 0, 0, 0.5);
}

View file

@ -1,35 +0,0 @@
import React from 'react'
import { Icon, Popup } from 'UI'
import { connectPlayer, toggleEvents, scale } from 'Player';
import cn from 'classnames'
import stl from './EventsToggleButton.module.css'
function EventsToggleButton({ showEvents, toggleEvents }: any) {
const toggle = () => {
toggleEvents()
scale()
}
return (
<Popup
content={ showEvents ? 'Hide Events' : 'Show Events' }
size="tiny"
inverted
position="bottom right"
>
<button
className={cn("absolute right-0 z-50", stl.wrapper)}
onClick={toggle}
>
<Icon
name={ showEvents ? 'chevron-double-right' : 'chevron-double-left' }
size="12"
/>
</button>
</Popup>
)
}
export default connectPlayer(state => ({
showEvents: !state.showEvents
}), { toggleEvents })(EventsToggleButton)

View file

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

View file

@ -4,17 +4,17 @@ 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 { 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();
// const SESSIONS_ROUTE = sessionsRoute();
function LiveSession({
sessionId,
loading,
hasErrors,
// loading,
// hasErrors,
session,
fetchSession,
fetchSlackList,
@ -38,9 +38,9 @@ function LiveSession({
},[ sessionId, hasSessionsPath ]);
return (
<Loader className="flex-1" loading={ loading }>
// <Loader className="flex-1" loading={ loading }>
<LivePlayer />
</Loader>
// </Loader>
);
}
@ -50,8 +50,8 @@ export default withPermissions(['ASSIST_LIVE'], '', true)(connect((state, props)
const hasSessiosPath = state.getIn([ 'sessions', 'sessionPath' ]).pathname.includes('/sessions');
return {
sessionId,
loading: state.getIn([ 'sessions', 'loading' ]),
hasErrors: !!state.getIn([ 'sessions', 'errors' ]),
// loading: state.getIn([ 'sessions', 'loading' ]),
// hasErrors: !!state.getIn([ 'sessions', 'errors' ]),
session: state.getIn([ 'sessions', 'current' ]),
hasSessionsPath: hasSessiosPath && !isAssist,
};

View file

@ -4,7 +4,7 @@ import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/Stated
import cn from 'classnames';
import stl from './Marker.module.css';
import { activeTarget } from 'Player';
import { Popup } from 'UI';
import { Tooltip } from 'react-tippy';
interface Props {
target: MarkedTarget;
@ -21,17 +21,17 @@ export default function Marker({ target, active }: Props) {
return (
<div className={ cn(stl.marker, { [stl.active] : active }) } style={ style } onClick={() => activeTarget(target.index)}>
<div className={stl.index}>{target.index + 1}</div>
<Popup
<Tooltip
open={active}
arrow
sticky
distance={15}
content={(
html={(
<div>{target.count} Clicks</div>
)}
>
<div className="absolute inset-0"></div>
</Popup>
</Tooltip>
</div>
)
}

View file

@ -116,67 +116,67 @@ function LiveSessionList(props: Props) {
<div className="flex items-center">
<div className="flex items-center ml-6 mr-4">
<span className="mr-2 color-gray-medium">Sort By</span>
<Popup
content="No metadata available to sort"
disabled={sortOptions.length > 0}
>
<div className={ cn("flex items-center", { 'disabled': sortOptions.length === 0})} >
<Select
plain
right
options={sortOptions}
// defaultValue={sort.field}
onChange={onSortChange}
value={sortOptions.find((i: any) => i.value === filter.sort) || sortOptions[0]}
/>
<div className="mx-2" />
<SortOrderButton onChange={(state: any) => props.applyFilter({ order: state })} sortOrder={filter.order} />
</div>
<Popup content="No metadata available to sort" disabled={sortOptions.length > 0}>
<div className={cn('flex items-center', { disabled: sortOptions.length === 0 })}>
<Select
plain
right
options={sortOptions}
// defaultValue={sort.field}
onChange={onSortChange}
value={sortOptions.find((i: any) => i.value === filter.sort) || sortOptions[0]}
/>
<div className="mx-2" />
<SortOrderButton onChange={(state: any) => props.applyFilter({ order: state })} sortOrder={filter.order} />
</div>
</Popup>
</div>
</div>
</div>
<Loader loading={loading}>
<NoContent
title={'No live sessions.'}
subtext={
<span>
See how to setup the{' '}
<a target="_blank" className="link" href="https://docs.openreplay.com/plugins/assist">
{'Assist'}
</a>{' '}
plugin, if you havent done that already.
</span>
}
image={<img src="/assets/img/live-sessions.png" style={{ width: '70%', marginBottom: '30px' }} />}
show={!loading && list.size === 0}
>
<div className="bg-white p-3 rounded border">
{list.map((session) => (
<>
<SessionItem
key={session.sessionId}
session={session}
live
hasUserFilter={hasUserFilter}
onUserClick={onUserClick}
metaList={metaList}
/>
<div className="border-b" />
</>
))}
<div className="w-full flex items-center justify-center py-6">
<Pagination
page={currentPage}
totalPages={Math.ceil(total / PER_PAGE)}
onPageChange={(page: any) => props.updateCurrentPage(page)}
limit={PER_PAGE}
/>
<div className="bg-white p-3 rounded border">
<Loader loading={loading}>
<NoContent
title={'No live sessions.'}
subtext={
<span>
See how to setup the{' '}
<a target="_blank" className="link" href="https://docs.openreplay.com/plugins/assist">
{'Assist'}
</a>{' '}
plugin, if you havent done that already.
</span>
}
image={<img src="/assets/img/live-sessions.png" style={{ width: '70%', marginBottom: '30px' }} />}
show={!loading && list.size === 0}
>
<div>
{list.map((session) => (
<>
<SessionItem
key={session.sessionId}
session={session}
live
hasUserFilter={hasUserFilter}
onUserClick={onUserClick}
metaList={metaList}
/>
<div className="border-b" />
</>
))}
</div>
</div>
</NoContent>
</Loader>
</NoContent>
</Loader>
<div className={cn("w-full flex items-center justify-center py-6", { 'disabled' : loading})}>
<Pagination
page={currentPage}
totalPages={Math.ceil(total / PER_PAGE)}
onPageChange={(page: any) => props.updateCurrentPage(page)}
limit={PER_PAGE}
debounceRequest={500}
/>
</div>
</div>
</div>
);
}

View file

@ -3,67 +3,74 @@ import cn from 'classnames';
import { CircularLoader, Icon } from 'UI';
interface Props {
className?: string;
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
type?: 'button' | 'submit' | 'reset';
loading?: boolean;
icon?: string;
[x: string]: any
className?: string;
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
type?: 'button' | 'submit' | 'reset';
loading?: boolean;
icon?: string;
[x: string]: any;
}
export default (props: Props) => {
const {
icon = '',
className = '',
variant = "default",
type = "button",
size = '',
disabled = false,
children,
loading = false,
...rest
} = props;
const {
icon = '',
className = '',
variant = 'default',
type = 'button',
size = '',
disabled = false,
children,
loading = false,
...rest
} = props;
const classes = ['relative flex items-center h-10 px-3 rounded tracking-wide whitespace-nowrap'];
if (variant === 'default') {
classes.push('bg-white hover:bg-gray-lightest border border-gray-light')
}
const classes = ['relative flex items-center h-10 px-3 rounded tracking-wide whitespace-nowrap'];
if (variant === 'default') {
classes.push('bg-white hover:bg-gray-lightest border border-gray-light');
}
if (variant === 'primary') {
classes.push('bg-teal color-white hover:bg-teal-dark')
}
if (variant === 'primary') {
classes.push('bg-teal color-white hover:bg-teal-dark');
}
if (variant === 'text') {
classes.push('bg-transparent color-gray-dark hover:bg-gray-lightest hover:color-gray-dark')
}
if (variant === 'text') {
classes.push('bg-transparent color-gray-dark hover:bg-gray-lightest hover:color-gray-dark');
}
if (variant === 'text-primary') {
classes.push('bg-transparent color-teal hover:bg-teal-light hover:color-teal-dark')
}
if (variant === 'text-primary') {
classes.push('bg-transparent color-teal hover:bg-teal-light hover:color-teal-dark');
}
if (variant === 'outline') {
classes.push('bg-white color-teal border border-teal hover:bg-teal-light')
}
if (variant === 'text-red') {
classes.push('bg-transparent color-red hover:bg-teal-light');
}
if (disabled) {
classes.push('opacity-40 pointer-events-none')
}
if (variant === 'outline') {
classes.push('bg-white color-teal border border-teal hover:bg-teal-light');
}
const iconColor = variant === 'text' || variant === 'default' ? 'gray-dark' : 'teal';
// console.log('children', children)
if (disabled) {
classes.push('opacity-40 pointer-events-none');
}
return (
<button
{ ...rest }
type={type}
className={ cn(classes, className ) }
>
{ icon && <Icon className={cn({ "mr-2" : children })} name={icon} color={iconColor} size="16" /> }
{ loading && <div className="absolute flex items-center justify-center inset-0 z-1 rounded">
<CircularLoader />
</div> }
<div className={cn({ 'opacity-0' : loading }, 'flex items-center')}>{children}</div>
</button>
);
}
let iconColor = variant === 'text' || variant === 'default' ? 'gray-dark' : 'teal';
if (variant === 'primary') {
iconColor = 'white';
}
if (variant === 'text-red') {
iconColor = 'red';
}
return (
<button {...rest} type={type} className={cn(classes, className)}>
{icon && <Icon className={cn({ 'mr-2': children })} name={icon} color={iconColor} size="16" />}
{loading && (
<div className="absolute flex items-center justify-center inset-0 z-1 rounded">
<CircularLoader />
</div>
)}
<div className={cn({ 'opacity-0': loading }, 'flex items-center')}>{children}</div>
</button>
);
};

View file

@ -33,9 +33,10 @@ export default function Pagination(props: Props) {
return (
<div className="flex items-center">
<Popup
sticky
content="Previous Page"
hideOnClick={true}
// hideOnClick={true}
animation="none"
delay={1500}
>
<button
className={cn("py-2 px-3", { "opacity-50 cursor-default": isFirstPage })}
@ -51,16 +52,16 @@ export default function Pagination(props: Props) {
className={cn("py-1 px-2 bg-white border border-gray-light rounded w-16", { "opacity-50 cursor-default": totalPages === 1 })}
value={currentPage}
min={1}
max={totalPages}
max={totalPages ? totalPages : 1}
onChange={(e) => changePage(parseInt(e.target.value))}
/>
<span className="mx-3 color-gray-medium">of</span>
<span >{numberWithCommas(totalPages)}</span>
<Popup
arrow
sticky
content="Next Page"
hideOnClick={true}
// hideOnClick={true}
animation="none"
delay={1500}
>
<button
className={cn("py-2 px-3", { "opacity-50 cursor-default": isLastPage })}

View file

@ -7,6 +7,10 @@ interface Props {
trigger?: any
position?: any
className?: string
delay?: number
disabled?: boolean
arrow?: boolean
open?: boolean
[x:string]: any;
}
export default ({
@ -14,14 +18,21 @@ export default ({
title='',
className='',
trigger = 'mouseenter',
delay = 1000,
disabled = false,
arrow = true,
...props
}: Props) => (
<Tooltip
{ ...props }
// {...props}
className={className}
trigger={trigger}
html={props.content || props.title}
arrow
disabled={disabled}
arrow={arrow}
delay={delay}
hideOnClick={true}
hideOnScroll={true}
>
{ props.children }
</Tooltip>