feature(ui) - assist wip
This commit is contained in:
parent
3190924c1d
commit
5a9fec1f25
13 changed files with 135 additions and 58 deletions
|
|
@ -20,7 +20,8 @@ const siteIdRequiredPaths = [
|
|||
'/rehydrations',
|
||||
'/sourcemaps',
|
||||
'/errors',
|
||||
'/funnels'
|
||||
'/funnels',
|
||||
'/assist'
|
||||
];
|
||||
|
||||
const noStoringFetchPathStarts = [
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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' ]),
|
||||
}), {
|
||||
|
|
|
|||
|
|
@ -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 } />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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...')
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ function hashString(s: string): number {
|
|||
export default Record({
|
||||
sessionId: '',
|
||||
siteId: '',
|
||||
live: false,
|
||||
live: true,
|
||||
startedAt: 0,
|
||||
duration: 0,
|
||||
events: List(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const colors = require('./app/theme/colors');
|
||||
|
||||
module.exports = {
|
||||
important: true,
|
||||
purge: [],
|
||||
corePlugins: [
|
||||
'preflight',
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue