feature(ui) - assist wip

This commit is contained in:
Shekar Siri 2021-06-29 16:45:30 +05:30
parent 3190924c1d
commit 5a9fec1f25
13 changed files with 135 additions and 58 deletions

View file

@ -20,7 +20,8 @@ const siteIdRequiredPaths = [
'/rehydrations',
'/sourcemaps',
'/errors',
'/funnels'
'/funnels',
'/assist'
];
const noStoringFetchPathStarts = [

View file

@ -18,32 +18,32 @@ function Assist({ session, jwt }) {
initPlayer(session, jwt);
return () => cleanPlayer()
}, [ session.sessionId ]);
useEffect(() => {
if (screeRef.current) {
attachPlayer(findDOMNode(screeRef.current));
}
}, [ ])
return (
<div className="h-screen w-screen">
<div ref={screeRef}
<div className="absolute">
{/* <div ref={screeRef}
// Just for testing TODO: flexible layout.
// It should consider itself as empty but take maximum of the space available
// Screen will adapt automatically.
style={{margin: "100px", height: "300px", width:"600px" }}
style={{height: "300px", width:"600px" }}
className="relative overflow-hidden bg-gray-lightest"
/>
/> */}
<ChatWindow call={ callPeer } />
{/* <ScreenSharing /> */}
</div>
)
}
export default connect(state => ({
session: { // Testing mock. Should be retrieved from redux
startedAt: 1624314191394,
live: true,
sessionId: "4870254843916045",
},
// session: { // Testing mock. Should be retrieved from redux
// // startedAt: 1624314191394,
// live: true,
// // sessionId: "4870254843916045",
// },
jwt: state.get('jwt'),
}))(Assist);

View file

@ -1,8 +1,8 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { IconButton } from 'UI';
import VideoContainer from '../components/VideoContainer';
// import stl from './chatWindow.css';
import stl from './chatWindow.css';
import { callPeer } from 'App/player';
interface Props {
call: (oStream: MediaStream, cb: (iStream: MediaStream)=>void)=>void
@ -12,7 +12,12 @@ function ChatWindow({ call }: Props) {
const [ inputStream, setInputStream ] = useState<MediaStream | null>(null);
const [ outputStream, setOutputStream ] = useState<MediaStream | null>(null);
const onCallClick = () => {
useEffect(() => {
startOutputStream()
callPeer()
}, [])
const startOutputStream = () => {
navigator.mediaDevices.getUserMedia({video:true, audio:true})
.then(oStream => {
setOutputStream(oStream);
@ -21,15 +26,25 @@ function ChatWindow({ call }: Props) {
})
.catch(console.log) // TODO: handle error in ui
}
// const onCallClick = () => {
// navigator.mediaDevices.getUserMedia({video:true, audio:true})
// .then(oStream => {
// setOutputStream(oStream);
// call(oStream, setInputStream); // Returns false when unable to connect.
// // TODO: handle calling state
// })
// .catch(console.log) // TODO: handle error in ui
// }
return (
<div className="fixed border radius bg-white z-50 shadow-xl mt-16">
<div className="p-2">
<VideoContainer stream={ inputStream } />
<div className="py-1" />
<VideoContainer stream={ outputStream } muted/>
<div className="cursor-pointer p-2 mr-2">
{/* <div className="cursor-pointer p-2 mr-2">
<IconButton icon="telephone" size="20" onClick={ onCallClick }/>
</div>
</div> */}
</div>
</div>
)

View file

@ -1,24 +1,31 @@
import React from 'react'
import { Button, Icon } from 'UI'
import { connect } from 'react-redux'
import cn from 'classnames'
import { callPeer } from 'App/player';
import { toggleChatWindow } from 'Duck/sessions';
// import stl from './AassistActions.css'
interface Props {
isLive: false;
isLive: false;
toggleChatWindow: (state) => void
}
function AssistActions({ }: Props) {
function AssistActions({ toggleChatWindow }: Props) {
return (
<div className="flex items-center">
<div className={cn('cursor-pointer p-2 mr-2')}>
<div
className={cn('cursor-pointer p-2 mr-2')}
onClick={() => toggleChatWindow(true)}
>
<Icon name="telephone" size="20" />
</div>
<div className="flex items-center p-2 cursor-pointer">
<Icon name="controller" size="20" />
<span className="ml-2">Request Control</span>
</div>
<Icon name="controller" size="20" />
<span className="ml-2">Request Control</span>
</div>
</div>
)
}
export default AssistActions
export default connect(null, { toggleChatWindow })(AssistActions)

View file

@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react'
import React, { useState, useEffect, useRef } from 'react'
import { Button, Icon } from 'UI'
interface Props {
@ -6,24 +6,40 @@ interface Props {
muted?: boolean
}
function VideoContainer({ stream, muted = false }: Props) {
function VideoContainer({ stream, muted = false }: Props) {
const [muteAudio, setMuteAudio] = useState(false)
const [muteVideo, setMuteVideo] = useState(false)
const ref = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (ref.current) {
ref.current.srcObject = stream;
}
}, [ ref.current, stream ])
return (
<div className="relative h-20 bg-gray-light-shade border p-1" style={{ height: '160px', width: '200px' }}>
<div className="absolute left-0 right-0 bottom-0 flex justify-center border border-gray-300 p-1 bg-white radius bg-opacity-25">
<video autoPlay ref={ ref } muted={ muted } />
<Button plain size="small">
<Icon name="mic" size="16" />
</Button>
<Button plain size="small">
<Icon name="camera-video" size="16" />
</Button>
const toggleAudio = () => {
// stream.getAudioTracks().forEach(track => track.enabled = !track.enabled);
setMuteAudio(!muteAudio)
}
const toggleVideo = () => {
// stream.getVideoTracks().forEach(track => track.enabled = !track.enabled);
setMuteVideo(!muteVideo)
}
return (
<div className="relative bg-gray-light-shade border p-1" style={{ height: '160px', width: '200px' }}>
<div className="absolute inset-0 flex justify-center border border-gray-300 p-1 bg-white radius bg-opacity-25">
<video autoPlay ref={ ref } muted={ muted } />
<div className="flex items-center absolute w-full justify-center bottom-0 bg-gray-lightest">
<Button plain size="small" onClick={toggleAudio}>
<Icon name={muteAudio ? 'mic-mute' : 'mic'} size="16" />
</Button>
<Button plain size="small" onClick={toggleVideo}>
<Icon name={ muteVideo ? 'camera-video-off' : 'camera-video' } size="16" />
</Button>
</div>
</div>
</div>
)

View file

@ -1,15 +1,23 @@
import React from 'react'
// import { fetchLiveList } from 'Duck/sessions'
import React, { useEffect } from 'react';
import { fetchLiveList } from 'Duck/sessions';
import { connect } from 'react-redux';
import { NoContent } from 'UI';
import { List } from 'immutable';
import SessionItem from 'Shared/SessionItem';
interface Props {
loading: Boolean,
list?: List<any>
list?: List<any>,
fetchLiveList: () => void
}
function LiveSessionList({ loading, list }: Props ) {
function LiveSessionList(props: Props) {
const { loading, list } = props;
useEffect(() => {
props.fetchLiveList();
}, [])
return (
<div>
<NoContent
@ -18,12 +26,19 @@ function LiveSessionList({ loading, list }: Props ) {
icon="exclamation-circle"
show={ !loading && list && list.size === 0}
>
{list?.map(session => (
<SessionItem
key={ session.sessionId }
session={ session }
// hasUserFilter={hasUserFilter}
// onUserClick={this.onUserClick}
/>
))}
</NoContent>
</div>
)
}
export default connect(state => ({
}), { })(LiveSessionList)
list: state.getIn(['sessions', 'liveSessions'])
}), { fetchLiveList })(LiveSessionList)

View file

@ -30,7 +30,7 @@ const InitLoader = connectPlayer(state => ({
}))(Loader);
function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt }) {
function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt }) {
useEffect(() => {
initPlayer(session, jwt);
return () => cleanPlayer()
@ -43,11 +43,11 @@ function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscr
}, [])
return (
<PlayerProvider>
<InitLoader className="flex-1">
<Assist />
<InitLoader className="flex-1 p-3">
{ showAssist && <Assist session={session} /> }
<PlayerBlockHeader fullscreen={fullscreen}/>
<div className={ styles.session } data-fullscreen={fullscreen}>
<PlayerBlock />
<PlayerBlock />
</div>
</InitLoader>
</PlayerProvider>
@ -57,6 +57,7 @@ function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscr
export default connect(state => ({
session: state.getIn([ 'sessions', 'current' ]),
showAssist: state.getIn([ 'sessions', 'showChatWindow' ]),
jwt: state.get('jwt'),
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
}), {

View file

@ -113,8 +113,8 @@ export default class PlayerBlockHeader extends React.PureComponent {
<HeaderInfo icon={ osIcon(userOs) } label={ userOs } />
<div className='ml-auto flex items-center'>
{ !live && <AssistActions isLive /> }
{ live && (
{ live && <AssistActions isLive /> }
{ !live && (
<>
<Autoplay />
<div className={ cls.divider } />

View file

@ -20,6 +20,7 @@ const REDEFINE_TARGET = 'sessions/REDEFINE_TARGET';
const SET_TIMEZONE = 'sessions/SET_TIMEZONE';
const SET_EVENT_QUERY = 'sessions/SET_EVENT_QUERY';
const SET_AUTOPLAY_VALUES = 'sessions/SET_AUTOPLAY_VALUES';
const TOGGLE_CHAT_WINDOW = 'sessions/TOGGLE_CHAT_WINDOW';
const SET_ACTIVE_TAB = 'sessions/SET_ACTIVE_TAB';
@ -36,7 +37,9 @@ const initialState = Map({
errorStack: List(),
eventsIndex: [],
sourcemapUploaded: true,
filteredEvents: null
filteredEvents: null,
showChatWindow: false,
liveSessions: List()
});
const reducer = (state = initialState, action = {}) => {
@ -50,6 +53,11 @@ const reducer = (state = initialState, action = {}) => {
: state;
case FETCH_ERROR_STACK.SUCCESS:
return state.set('errorStack', List(action.data.trace).map(ErrorStack)).set('sourcemapUploaded', action.data.sourcemapUploaded)
case FETCH_LIVE_LIST.SUCCESS:
// const { sessions, total } = action.data;
const liveList = List(action.data).map(Session);
return state
.set('liveSessions', liveList)
case FETCH_LIST.SUCCESS:
const { sessions, total } = action.data;
const list = List(sessions).map(Session);
@ -99,8 +107,7 @@ const reducer = (state = initialState, action = {}) => {
.set('sessionIds', list.map(({ sessionId }) => sessionId ).toJS())
.set('total', total)
.set('keyMap', keyMap)
.set('wdTypeCount', wdTypeCount);
.set('wdTypeCount', wdTypeCount);
case SET_AUTOPLAY_VALUES: {
const sessionIds = state.get('sessionIds')
const currentSessionId = state.get('current').sessionId
@ -195,6 +202,9 @@ const reducer = (state = initialState, action = {}) => {
.set('sessionIds', allList.map(({ sessionId }) => sessionId ).toJS())
case SET_TIMEZONE:
return state.set('timezone', action.timezone)
case TOGGLE_CHAT_WINDOW:
console.log(action)
return state.set('showChatWindow', action.state)
default:
return state;
}
@ -256,7 +266,14 @@ export function fetchFavoriteList() {
export function fetchLiveList() {
return {
types: FETCH_LIVE_LIST.toArray(),
call: client => client.get('/sessions2/favorite'),
call: client => client.get('/assist/sessions'),
};
}
export function toggleChatWindow(state) {
return {
type: TOGGLE_CHAT_WINDOW,
state
};
}

View file

@ -147,7 +147,7 @@ export default class MessageDistributor extends StatedScreen {
this.peer = peer;
peer.on("open", me => {
console.log("peer opened", me);
const id = `Amva-sf98-234fd-OR-test-${this.session.sessionId}`;
const id = `3sWXSsqHgSKnEO5YkNJK-${this.session.sessionId}`;
console.log("trying to connect to", id)
const conn = peer.connect(id);
conn.on('open', () => {
@ -179,7 +179,7 @@ export default class MessageDistributor extends StatedScreen {
callPeer(localStream: MediaStream, cb: (s: MediaStream)=>void): boolean {
if (!this.peer) { return false; }
const conn = this.peer.connections[`Amva-sf98-234fd-OR-test-${this.session.sessionId}`]?.[0];
const conn = this.peer.connections[`3sWXSsqHgSKnEO5YkNJK-${this.session.sessionId}`]?.[0];
if (!conn || !conn.open) { return false; } // Conn not established
const call = this.peer.call(conn.peer, localStream);
console.log('calling...')

View file

@ -26,7 +26,7 @@ function hashString(s: string): number {
export default Record({
sessionId: '',
siteId: '',
live: false,
live: true,
startedAt: 0,
duration: 0,
events: List(),

View file

@ -1,6 +1,7 @@
const colors = require('./app/theme/colors');
module.exports = {
important: true,
purge: [],
corePlugins: [
'preflight',

View file

@ -16,7 +16,11 @@
"Appo": ["./app"],
"Types": ["./app/types" ],
"Types/*": ["./app/types/*"], // Sublime hack
"UI": ["./app/components/ui"]
"UI": ["./app/components/ui"],
"Duck": ["./app/duck"],
"Duck/*": ["./app/duck/*"],
"Shared": ["./app/components/shared"],
"Shared/*": ["./app/components/shared/*"],
}
},
"exclude": [