change(ui): change url with added multi sessions to allow sharing, add refresh button and memo to sub comps

This commit is contained in:
sylenien 2022-12-05 13:46:59 +01:00 committed by Delirium
parent 80811f4c49
commit c8ef071774
9 changed files with 183 additions and 73 deletions

View file

@ -83,6 +83,7 @@ const CLIENT_PATH = routes.client();
const ONBOARDING_PATH = routes.onboarding();
const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB);
const MULTIVIEW_PATH = routes.multiview();
const MULTIVIEW_INDEX_PATH = routes.multiviewIndex();
@withStore
@withRouter
@ -173,8 +174,14 @@ class Router extends React.Component {
render() {
const { isLoggedIn, jwt, siteId, sites, loading, changePassword, location, existingTenant, onboarding, isEnterprise } = this.props;
const siteIdList = sites.map(({ id }) => id).toJS();
const hideHeader = (location.pathname && location.pathname.includes('/session/')) || location.pathname.includes('/assist/');
const isPlayer = isRoute(SESSION_PATH, location.pathname) || isRoute(LIVE_SESSION_PATH, location.pathname) || isRoute(MULTIVIEW_PATH, location.pathname);
const hideHeader = (location.pathname && location.pathname.includes('/session/'))
|| location.pathname.includes('/assist/')
|| location.pathname.includes('multiview');
const isPlayer = isRoute(SESSION_PATH, location.pathname)
|| isRoute(LIVE_SESSION_PATH, location.pathname)
|| isRoute(MULTIVIEW_PATH, location.pathname)
|| isRoute(MULTIVIEW_INDEX_PATH, location.pathname);
const redirectToOnboarding = !onboarding && localStorage.getItem(GLOBAL_HAS_NO_RECORDINGS) === 'true'
return isLoggedIn ? (
@ -221,7 +228,8 @@ class Router extends React.Component {
<Route exact strict path={withSiteId(DASHBOARD_METRIC_CREATE_PATH, siteIdList)} component={Dashboard} />
<Route exact strict path={withSiteId(DASHBOARD_METRIC_DETAILS_PATH, siteIdList)} component={Dashboard} />
<Route exact strict path={withSiteId(MULTIVIEW_PATH, siteIdList)} component={Multiview} />
<Route exact path={withSiteId(MULTIVIEW_INDEX_PATH, siteIdList)} component={Multiview} />
<Route path={withSiteId(MULTIVIEW_PATH, siteIdList)} component={Multiview} />
<Route exact strict path={withSiteId(ASSIST_PATH, siteIdList)} component={Assist} />
<Route exact strict path={withSiteId(RECORDINGS_PATH, siteIdList)} component={Assist} />
<Route exact strict path={withSiteId(ERRORS_PATH, siteIdList)} component={Errors} />

View file

@ -1,73 +1,121 @@
import React from 'react';
import { useStore } from 'App/mstore';
import { BackLink, Icon } from 'UI'
import { BackLink, Icon } from 'UI';
import { observer } from 'mobx-react-lite';
import { connect } from 'react-redux';
import { fetchSessions } from 'Duck/liveSearch';
import { useHistory } from 'react-router-dom';
import { liveSession, assist, withSiteId } from 'App/routes';
import { fetchSessions, customSetSessions } from 'Duck/liveSearch';
import { useHistory, useParams } from 'react-router-dom';
import { liveSession, assist, withSiteId, multiview } from 'App/routes';
import AssistSessionsModal from 'App/components/Session_/Player/Controls/AssistSessionsModal';
import { useModal } from 'App/components/Modal';
import LivePlayer from 'App/components/Session/LivePlayer';
import { InactiveTab } from 'App/components/Session_/Player/Controls/AssistSessionsTabs'
import { InactiveTab } from 'App/components/Session_/Player/Controls/AssistSessionsTabs';
function Multiview({ total, fetchSessions, siteId, assistCredendials }: { total: number; fetchSessions: (filter: any) => void, siteId: string, assistCredendials: any }) {
function Multiview({
total,
fetchSessions,
siteId,
assistCredendials,
customSetSessions,
}: {
total: number;
customSetSessions: (data: any) => void;
fetchSessions: (filter: any) => void;
siteId: string;
assistCredendials: any;
list: Record<string, any>[];
}) {
const { showModal, hideModal } = useModal();
const { assistMultiviewStore } = useStore();
const history = useHistory();
// @ts-ignore
const { sessionsquery } = useParams();
const onSessionsChange = (sessions: Record<string, any>[]) => {
const sessionIdQuery = encodeURIComponent(sessions.map((s) => s.sessionId).join(','));
return history.replace(withSiteId(multiview(sessionIdQuery), siteId));
};
React.useEffect(() => {
if (total === 0) {
assistMultiviewStore.setOnChange(onSessionsChange);
if (sessionsquery) {
const sessionIds = decodeURIComponent(sessionsquery).split(',');
// preset
assistMultiviewStore.presetSessions(sessionIds).then((data) => {
customSetSessions(data);
});
} else {
fetchSessions({});
}
}, []);
const openLiveSession = (e: React.MouseEvent, sessionId: string) => {
e.stopPropagation()
e.stopPropagation();
assistMultiviewStore.setActiveSession(sessionId);
history.push(withSiteId(liveSession(sessionId), siteId));
};
const openList = () => {
history.push(withSiteId(assist(), siteId))
}
history.push(withSiteId(assist(), siteId));
};
const openListModal = () => {
showModal(<AssistSessionsModal onAdd={hideModal} />, { right: true });
}
};
const replaceSession = (e: React.MouseEvent, sessionId: string) => {
e.stopPropagation()
e.stopPropagation();
showModal(<AssistSessionsModal onAdd={hideModal} replaceTarget={sessionId} />, { right: true });
}
};
const deleteSession = (e: React.MouseEvent, sessionId: string) => {
e.stopPropagation()
assistMultiviewStore.removeSession(sessionId)
}
e.stopPropagation();
assistMultiviewStore.removeSession(sessionId);
};
const placeholder = new Array(4 - assistMultiviewStore.sessions.length).fill(0);
return (
<div className="w-screen h-screen flex flex-col">
<div className="w-full p-4 flex justify-between items-center">
{/* @ts-ignore */}
<div><BackLink label="Back to sessions list" onClick={openList}/> </div>
<div>
{/* @ts-ignore */}
<BackLink label="Back to sessions list" onClick={openList} />
</div>
<div>{`Watching ${assistMultiviewStore.sessions.length} of ${total} Live Sessions`}</div>
</div>
<div className="w-full h-full grid grid-cols-2 grid-rows-2">
{assistMultiviewStore.sortedSessions.map((session) => (
{assistMultiviewStore.sortedSessions.map((session: Record<string, any>) => (
<div
key={session.key}
className="border hover:border-active-blue-border relative group cursor-pointer"
>
<div onClick={(e) => openLiveSession(e, session.sessionId)} className="w-full h-full">
{session.agentToken ? <LivePlayer isMultiview customSession={session} customAssistCredendials={assistCredendials} /> : <div>Loading session</div>}
{session.agentToken ? (
<LivePlayer
isMultiview
customSession={session}
customAssistCredendials={assistCredendials}
/>
) : (
<div>Loading session</div>
)}
</div>
<div className="absolute z-10 bottom-0 w-full left-0 p-2 opacity-70 bg-gray-darkest text-white flex justify-between">
<div>{session.userDisplayName}</div>
<div className="hidden group-hover:flex items-center gap-2">
<div className="cursor-pointer hover:font-semibold" onClick={(e) => replaceSession(e, session.sessionId)}>
Replace Session
<div
className="cursor-pointer hover:font-semibold"
onClick={(e) => replaceSession(e, session.sessionId)}
>
Replace Session
</div>
<div className="cursor-pointer hover:font-semibold" onClick={(e) => deleteSession(e, session.sessionId)}>
<div
className="cursor-pointer hover:font-semibold"
onClick={(e) => deleteSession(e, session.sessionId)}
>
<Icon name="trash" size={18} color="white" />
</div>
</div>
@ -75,9 +123,13 @@ function Multiview({ total, fetchSessions, siteId, assistCredendials }: { total:
</div>
))}
{placeholder.map((_, i) => (
<div key={i} className="border hover:border-active-blue-border flex flex-col gap-2 items-center justify-center cursor-pointer" onClick={openListModal}>
<InactiveTab classNames='!bg-gray-bg w-12' />
Add Session
<div
key={i}
className="border hover:border-active-blue-border flex flex-col gap-2 items-center justify-center cursor-pointer"
onClick={openListModal}
>
<InactiveTab classNames="!bg-gray-bg w-12" />
Add Session
</div>
))}
</div>
@ -88,9 +140,10 @@ function Multiview({ total, fetchSessions, siteId, assistCredendials }: { total:
export default connect(
(state: any) => ({
total: state.getIn(['liveSearch', 'total']),
siteId: state.getIn([ 'site', 'siteId' ])
siteId: state.getIn(['site', 'siteId']),
}),
{
fetchSessions,
customSetSessions,
}
)(observer(Multiview))
)(observer(Multiview));

View file

@ -1,5 +1,5 @@
import React from 'react';
import { Loader, Pagination, Tooltip } from 'UI';
import { Loader, Pagination, Tooltip, Button } from 'UI';
import { connect } from 'react-redux';
import SessionItem from 'Shared/SessionItem';
import { addFilterByKeyAndValue, updateCurrentPage, applyFilter } from 'Duck/liveSearch';
@ -51,8 +51,11 @@ function AssistSessionsModal(props: Props) {
.toJS();
React.useEffect(() => {
props.applyFilter({ ...filter });
if (total === 0) {
reloadSessions()
}
}, []);
const reloadSessions = () => props.applyFilter({ ...filter });
const onSortChange = ({ value }: any) => {
props.applyFilter({ sort: value.value });
};
@ -68,8 +71,9 @@ function AssistSessionsModal(props: Props) {
return (
<div className="bg-gray-lightest box-shadow h-screen p-4" style={{ width: '840px' }}>
<div className="flex items-center my-2">
<div className="flex items-center">
<span className="mr-2 color-gray-medium">Sort By</span>
<div className="flex items-center gap-2" w-full>
<Button className="mr-2" variant="text" onClick={reloadSessions}>Refresh</Button>
<span className="color-gray-medium">Sort By</span>
<Tooltip title="No metadata available to sort" disabled={sortOptions.length > 0}>
<div className={cn('flex items-center', { disabled: sortOptions.length === 0 })}>
<Select
@ -79,7 +83,6 @@ function AssistSessionsModal(props: Props) {
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}
@ -95,7 +98,7 @@ function AssistSessionsModal(props: Props) {
className={cn(
'rounded bg-white mb-2 overflow-hidden border',
assistMultiviewStore.sessions.findIndex(
(s) => s.sessionId === session.sessionId
(s: Record<string, any>) => s.sessionId === session.sessionId
) !== -1
? 'cursor-not-allowed opacity-60'
: ''
@ -110,7 +113,7 @@ function AssistSessionsModal(props: Props) {
metaList={metaList}
isDisabled={
assistMultiviewStore.sessions.findIndex(
(s) => s.sessionId === session.sessionId
(s: Record<string, any>) => s.sessionId === session.sessionId
) !== -1
}
isAdd

View file

@ -22,23 +22,23 @@ const Tab = (props: ITab) => (
</div>
);
export const InactiveTab = (props: Omit<ITab, 'children'>) => (
export const InactiveTab = React.memo((props: Omit<ITab, 'children'>) => (
<Tab onClick={props.onClick} classNames={cn("hover:bg-gray-bg bg-gray-light", props.classNames)}>
<Icon name="plus" size="22" color="white" />
</Tab>
);
));
const ActiveTab = (props: Omit<ITab, 'children'>) => (
const ActiveTab = React.memo((props: Omit<ITab, 'children'>) => (
<Tab onClick={props.onClick} classNames="hover:bg-teal bg-borderColor-primary">
<Icon name="play-fill-new" size="22" color="white" />
</Tab>
);
));
const CurrentTab = () => (
const CurrentTab = React.memo(() => (
<Tab classNames="bg-teal color-white">
<span style={{ fontSize: '0.65rem' }}>PLAYING</span>
</Tab>
);
));
function AssistTabs({ session, siteId }: { session: Record<string, any>; siteId: string }) {
const history = useHistory();
@ -53,17 +53,17 @@ function AssistTabs({ session, siteId }: { session: Record<string, any>; siteId:
}, []);
const openGrid = () => {
history.push(withSiteId(multiview(), siteId));
const sessionIdQuery = encodeURIComponent(assistMultiviewStore.sessions.map((s) => s.sessionId).join(','));
return history.push(withSiteId(multiview(sessionIdQuery), siteId));
};
const openLiveSession = (sessionId: string) => {
assistMultiviewStore.setActiveSession(sessionId);
history.push(withSiteId(liveSession(sessionId), siteId));
};
console.log(assistMultiviewStore.activeSessionId)
return (
<div className="grid grid-cols-2 w-28 h-full" style={{ gap: '4px' }}>
{assistMultiviewStore.sortedSessions.map((session) => (
{assistMultiviewStore.sortedSessions.map((session: { key: number, sessionId: string }) => (
<React.Fragment key={session.key}>
{assistMultiviewStore.isActive(session.sessionId) ? (
<CurrentTab />

View file

@ -234,14 +234,12 @@ function SessionItem(props: RouteComponentProps & Props) {
</div>
)}
{props.isAdd ? (
<div className="rounded-full border-tealx p-2 border">
<div
className="rounded-full border-tealx p-2 border"
onClick={() => (props.isDisabled ? null : props.onClick())}
>
<div className="bg-tealx rounded-full p-2">
<Icon
name="plus"
size={16}
color="white"
onClick={() => (props.isDisabled ? null : props.onClick())}
/>
<Icon name="plus" size={16} color="white" />
</div>
</div>
) : (

View file

@ -64,6 +64,11 @@ export default mergeReducers(
}),
);
export const customSetSessions = (data) => ({
type: success(FETCH_SESSION_LIST),
data
})
const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => {
dispatch(actionCreator(...args));
const filter = getState().getIn([ 'liveSearch', 'instance']).toData();

View file

@ -1,5 +1,8 @@
import { makeAutoObservable } from 'mobx';
import { sessionService } from 'App/services'
import { sessionService } from 'App/services';
import Filter from 'Types/filter';
import Session from 'Types/session';
import { List, Map } from 'immutable';
type MultiSessions = [
LiveSessionListItem?,
@ -14,16 +17,20 @@ export interface LiveSessionListItem extends Record<string, any> {
export default class AssistMultiviewStore {
sessions: MultiSessions = [];
activeSession: LiveSessionListItem = null;
onChangeCb: (sessions: MultiSessions) => void;
constructor() {
makeAutoObservable(this);
}
isActive(sessionId: string): boolean {
console.log(sessionId, this.activeSessionId)
return this.activeSessionId === sessionId;
}
setOnChange(cb: (sessions: MultiSessions) => void) {
this.onChangeCb = cb;
}
get sortedSessions() {
// @ts-ignore ???
return this.sessions.slice().sort((a, b) => a.key - b.key);
@ -34,20 +41,30 @@ export default class AssistMultiviewStore {
}
addSession(session: Record<string, any>) {
if (this.sessions.length < 4) {
this.sessions.push({ ...session.toJS(), key: this.sessions.length });
if (
this.sessions.length < 4 &&
this.sessions.findIndex((s) => s.sessionId === session.sessionId) === -1
) {
const plainSession = session.toJS ? session.toJS() : session;
this.sessions.push({ ...plainSession, key: this.sessions.length });
return this.onChangeCb(this.sessions);
}
}
replaceSession(targetId: string, session: Record<string, any>) {
const targetIndex = this.sessions.findIndex(s => s.sessionId === targetId);
const targetIndex = this.sessions.findIndex((s) => s.sessionId === targetId);
if (targetIndex !== -1) {
this.sessions[targetIndex] = { ...session.toJS(), key: targetIndex }
const plainSession = session.toJS ? session.toJS() : session;
this.sessions[targetIndex] = { ...plainSession, key: targetIndex };
return this.onChangeCb(this.sessions);
}
}
removeSession(sessionId: string) {
return this.sessions = this.sessions.filter(session => session.sessionId !== sessionId) as MultiSessions;
this.sessions = this.sessions.filter(
(session) => session.sessionId !== sessionId
) as MultiSessions;
return this.onChangeCb(this.sessions);
}
setActiveSession(sessionId: string) {
@ -56,24 +73,43 @@ export default class AssistMultiviewStore {
setDefault(session: Record<string, any>) {
if (this.sessions.length === 0) {
const firstItem = {...session.toJS?.(), key: 0}
this.sessions = [firstItem]
this.activeSession = firstItem
const plainSession = session.toJS ? session.toJS() : session;
const firstItem = { ...plainSession, key: 0 };
this.sessions = [firstItem];
this.activeSession = firstItem;
return this.onChangeCb?.(this.sessions);
}
}
async fetchAgentTokenInfo(sessionId: string) {
const info = await sessionService.getSessionInfo(sessionId, true)
const info = await sessionService.getSessionInfo(sessionId, true);
return this.setToken(sessionId, info.agentToken);
}
return this.setToken(sessionId, info.agentToken)
async presetSessions(ids: string[]) {
// @ts-ignore
const filter = new Filter({ filters: [], sort: '' }).toData();
const data = await sessionService.getLiveSessions(filter);
const matchingSessions = data.sessions.filter(
(s: Record<string, any>) => ids.includes(s.sessionID) || ids.includes(s.sessionId)
);
const immutMatchingSessions = List(matchingSessions).map(Session);
immutMatchingSessions.forEach((session: Record<string, any>) => {
this.addSession(session);
this.fetchAgentTokenInfo(session.sessionId);
});
return data;
}
setToken(sessionId: string, token: string) {
const sessions = this.sessions
const targetIndex = this.sessions.findIndex(s => s.sessionId === sessionId)
sessions[targetIndex].agentToken = token
const sessions = this.sessions;
const targetIndex = sessions.findIndex((s) => s.sessionId === sessionId);
sessions[targetIndex].agentToken = token;
return this.sessions = sessions
return (this.sessions = sessions);
}
reset() {

View file

@ -85,11 +85,10 @@ export const onboarding = (tab = routerOBTabString) => `/onboarding/${ tab }`;
export const sessions = params => queried('/sessions', params);
export const assist = params => queried('/assist', params);
export const recordings = params => queried("/recordings", params);
export const multiview = params => queried("/assist/multiview", params);
export const multiviewIndex = params => queried('/multiview', params);
export const multiview = (sessionsQuery = ':sessionsquery', hash) => hashed(`/multiview/${sessionsQuery}`, hash);
export const session = (sessionId = ':sessionId', hash) => hashed(`/session/${ sessionId }`, hash);
export const liveSession = (sessionId = ':sessionId', params, hash) => hashed(queried(`/assist/${ sessionId }`, params), 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);
@ -127,6 +126,7 @@ const REQUIRED_SITE_ID_ROUTES = [
assist(),
recordings(),
multiview(),
multiviewIndex(),
metrics(),
metricDetails(''),

View file

@ -37,4 +37,11 @@ export default class SettingsService {
.then((j) => j.data || {})
.catch(console.error);
}
getLiveSessions(filter: any) {
return this.client
.post('/assist/sessions', filter)
.then(fetchErrorCheck)
.then((response) => response.data || []);
}
}