Skip to content

Commit

Permalink
feat(sessions): implement evaluation view ✨
Browse files Browse the repository at this point in the history
  • Loading branch information
brionmario committed Apr 18, 2019
1 parent 0002cf4 commit c811723
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 24 deletions.
6 changes: 6 additions & 0 deletions src/assets/sass/partials/_misc.scss
Original file line number Diff line number Diff line change
Expand Up @@ -459,8 +459,14 @@ legend {
}
.main-title {
margin: 5px 0 10px 0;
font-weight: $font-weight-normal;
}
.sub-title {
margin: 5px 0 10px 0;
font-weight: $font-weight-light;
}
.extra-context {
font-size: $font-size-small;
font-weight: $font-weight-light;
}
}
10 changes: 9 additions & 1 deletion src/assets/sass/partials/_pages.scss
Original file line number Diff line number Diff line change
Expand Up @@ -282,14 +282,22 @@
}
.feed-wrapper {
padding: 2% 8%;
.phone-feed-wrapper {
.phone-feed-wrapper,
.camera-feed-wrapper {
padding: 10px 20px;
.phone-feed-container {
img {
max-height: $phone-feed-max-height;
margin-bottom: 20px;
}
}
.camera-feed-container {
video {
max-height: $camera-feed-max-height;
width: 100%;
margin-bottom: 20px;
}
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/assets/sass/partials/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -385,4 +385,5 @@ $screen-sm-max: ($screen-md-min - 1) !default;
$screen-md-max: ($screen-lg-min - 1) !default;


$phone-feed-max-height: calc(100vh - 60vh);
$phone-feed-max-height: calc(100vh - 60vh);
$camera-feed-max-height: calc(100vh - 60vh);
27 changes: 26 additions & 1 deletion src/redux/actions/session-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import {
UPDATE_QUESTIONNAIRE,
SET_EXPECTED_EMOTIONS,
SET_RAW_PHONE_FEED_WS_URL,
SET_RAW_PHONE_FEED_WS_CONNECTION_STATUS, SET_RAW_PHONE_FEED_WS_DATA
SET_RAW_PHONE_FEED_WS_CONNECTION_STATUS,
SET_RAW_PHONE_FEED_WS_DATA,
SET_LIST_OF_AVAILABLE_CAMERAS,
SET_SELECTED_CAMERA,
SET_CAMERA_CONNECTION_STATUS
} from '../types';
import {API_ENDPOINTS} from '../../api';
import {HttpInterceptor} from '../../services';
Expand Down Expand Up @@ -103,6 +107,27 @@ export const setRawPhoneFeedWSConnectionStatus = data => (dispatch) => {
});
};

export const setCameraConnectionStatus = data => (dispatch) => {
return dispatch({
type: SET_CAMERA_CONNECTION_STATUS,
payload: data,
});
};

export const setListOfAvailableCameras = data => (dispatch) => {
return dispatch({
type: SET_LIST_OF_AVAILABLE_CAMERAS,
payload: data,
});
};

export const setSelectedCamera = data => (dispatch) => {
return dispatch({
type: SET_SELECTED_CAMERA,
payload: data,
});
};

export const setSessionViewConfig = data => dispatch => dispatch({
type: SET_SESSION_VIEW_CONFIG,
payload: data,
Expand Down
25 changes: 23 additions & 2 deletions src/redux/reducers/session-reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
SET_EXPECTED_EMOTIONS,
SET_RAW_PHONE_FEED_WS_URL,
SET_RAW_PHONE_FEED_WS_CONNECTION_STATUS,
SET_RAW_PHONE_FEED_WS_DATA
SET_RAW_PHONE_FEED_WS_DATA,
SET_LIST_OF_AVAILABLE_CAMERAS,
SET_SELECTED_CAMERA,
SET_CAMERA_CONNECTION_STATUS
} from '../types';

const initialState = {
Expand All @@ -20,7 +23,10 @@ const initialState = {
expectedEmotions: [],
rawPhoneFeedWSURL: 'default',
rawPhoneFeedWSData: 'default',
isRawPhoneFeedWSConnected: false
isRawPhoneFeedWSConnected: false,
isCameraConnected: false,
availableCameras: [],
selectedCamera: null
};

export function sessionReducer(state = initialState, action) {
Expand Down Expand Up @@ -67,11 +73,26 @@ export function sessionReducer(state = initialState, action) {
...state,
isRawPhoneFeedWSConnected: action.payload,
};
case SET_CAMERA_CONNECTION_STATUS:
return {
...state,
isCameraConnected: action.payload,
};
case SET_RAW_PHONE_FEED_WS_DATA:
return {
...state,
rawPhoneFeedWSData: action.payload,
};
case SET_LIST_OF_AVAILABLE_CAMERAS:
return {
...state,
availableCameras: action.payload,
};
case SET_SELECTED_CAMERA:
return {
...state,
selectedCamera: action.payload,
};
default:
return state;
}
Expand Down
4 changes: 3 additions & 1 deletion src/redux/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ export const SET_SELECTED_QUESTIONNAIRE = 'SET_SELECTED_QUESTIONNAIRE';
export const SET_EXPECTED_EMOTIONS = 'SET_EXPECTED_EMOTIONS';
export const SET_RAW_PHONE_FEED_WS_URL = 'SET_RAW_PHONE_FEED_WS_URL';
export const SET_RAW_PHONE_FEED_WS_CONNECTION_STATUS = 'SET_RAW_PHONE_FEED_WS_CONNECTION_STATUS';
export const SET_CAMERA_CONNECTION_STATUS = 'SET_CAMERA_CONNECTION_STATUS';
export const SET_RAW_PHONE_FEED_WS_DATA = 'SET_RAW_PHONE_FEED_WS_DATA';

export const SET_LIST_OF_AVAILABLE_CAMERAS = 'SET_LIST_OF_AVAILABLE_CAMERAS';
export const SET_SELECTED_CAMERA = 'SET_SELECTED_CAMERA';
127 changes: 109 additions & 18 deletions src/views/sessions/evaluation.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, {Component} from 'react';
import _ from 'lodash';
import PropTypes from 'prop-types';
import {
Grid,
Expand All @@ -13,49 +12,121 @@ import * as sessionActionCreators from '../../redux/actions/session-actions';
import {Card} from '../../components';
import {CustomButton as Button} from '../../elements';
import {PhoneFeedConnectionForm} from '../../forms'
import {CameraFeedConnectionForm} from '../../forms/sessions';
import {EmptyPlaceholder} from '../../components';
import videCameraIcon from '../../assets/img/icons/video-camera.svg';
import phoneIcon from '../../assets/img/icons/smartphone.svg';

class Evaluation extends Component {

componentDidMount() {
const {actions} = this.props;
actions.sessions.setCameraConnectionStatus(false);
actions.sessions.setRawPhoneFeedWSConnectionStatus(false);
navigator.mediaDevices.enumerateDevices().then(this.extractCameras).catch(this.handleCameraError);
}

componentWillReceiveProps(nextProps) {
const {rawPhoneFeedWSURL} = this.props;
const {rawPhoneFeedWSURL, selectedCamera, actions} = this.props;
if (rawPhoneFeedWSURL && nextProps.rawPhoneFeedWSURL) {
if (rawPhoneFeedWSURL !== nextProps.rawPhoneFeedWSURL) {
this.runWebSocket(nextProps.rawPhoneFeedWSURL);
}
}
if (nextProps.selectedCamera) {
if (selectedCamera !== nextProps.selectedCamera) {
actions.sessions.setCameraConnectionStatus(true);
this.startCameraStream(nextProps.selectedCamera);
}
}
}

runWebSocket = (address) => {
const {actions, rawPhoneFeedWSData} = this.props;
const {actions} = this.props;
let ws;

if ('WebSocket' in window) {
console.log('WebSocket is supported by your Browser!');
console.log('CSSI_DEBUG: WebSocket is supported by your Browser!');

ws = new WebSocket('ws://' + address);

ws.onopen = function () {
// Web Socket is connected, send data using send()
ws.send('Message to send\n');
console.log('Message is sent...');
actions.sessions.setRawPhoneFeedWSConnectionStatus(true);
};

ws.onmessage = function (evt) {
console.log('ws message received', evt.data);
actions.sessions.setRawPhoneFeedWSData(evt.data);
};
ws.onclose = function () {
// websocket is closed.
console.log('Connection is closed...');
actions.sessions.setRawPhoneFeedWSConnectionStatus(false);
};
}
else {
// The browser doesn't support WebSocket
alert('WebSocket NOT supported by your Browser!');
console.log('CSSI_DEBUG: WebSocket NOT supported by your Browser!');
}
}
};

extractCameras = (devices) => {
const {actions} = this.props;
let videoCaptureDevices = [];

for (let i = 0; i !== devices.length; ++i) {
let device = devices[i];
let option = {
label: '',
value: null
};
option.value = device.deviceId;
if (device.kind === 'videoinput') {
option.label = device.label || 'Camera ' +
(videoCaptureDevices.length + 1);
videoCaptureDevices.push(option);
}
}
actions.sessions.setListOfAvailableCameras(videoCaptureDevices);
};

startCameraStream = (source) => {
if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop();
});
}
const constraints = {
video: {deviceId: source ? {exact: source} : undefined}
};
navigator.mediaDevices.getUserMedia(constraints).then(this.attachStreamToCamera).then(this.extractCameras).catch(this.handleCameraError);
};

attachStreamToCamera = (stream) => {
const {actions} = this.props;

const videoElement = document.querySelector('video');
window.stream = stream;
videoElement.srcObject = stream;
// Refresh button list
return navigator.mediaDevices.enumerateDevices();
};

handleCameraError = (error) => {
const {actions} = this.props;
actions.sessions.setCameraConnectionStatus(false);
console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
};

render() {
const {isRawPhoneFeedWSConnected, rawPhoneFeedWSData} = this.props;
const {isRawPhoneFeedWSConnected, rawPhoneFeedWSData, availableCameras, isCameraConnected} = this.props;

console.log(isRawPhoneFeedWSConnected)

let cameraTypeOptions = null;

if (availableCameras) {
cameraTypeOptions = availableCameras
.map(cam => (
{value: cam.value, label: cam.label}
));
}

return (
<div className="main-content no-padding session-page">
<Grid fluid>
Expand Down Expand Up @@ -84,7 +155,14 @@ class Evaluation extends Component {
content={(
<div className="phone-feed-wrapper">
<div className="phone-feed-container text-center">
<img src={'data:image/jpg;base64,' + rawPhoneFeedWSData}/>
{
isRawPhoneFeedWSConnected ?
<img src={'data:image/jpg;base64,' + rawPhoneFeedWSData}/>
:
<EmptyPlaceholder title={'Mobile Phone Feed'} subTitle={'Phone feed is not connected'}
extraContext={'Please enter the websocket address bellow. ex: http://192.168.8.103:8123'}
icon={phoneIcon}/>
}
</div>
<PhoneFeedConnectionForm/>
</div>
Expand All @@ -95,8 +173,18 @@ class Evaluation extends Component {
<Card
title={''}
content={(
<div className="questionnaire-wrapper">

<div className="camera-feed-wrapper">
<div className="camera-feed-container text-center">
{
isCameraConnected ?
<video id="video" playsinline autoPlay></video>
:
<EmptyPlaceholder title={'Camera Feed'} subTitle={'Camera feed is not connected'}
extraContext={'Please select the preferred video source from the select box bellow'}
icon={videCameraIcon}/>
}
<CameraFeedConnectionForm cameraTypes={cameraTypeOptions}/>
</div>
</div>
)}
/>
Expand Down Expand Up @@ -129,9 +217,12 @@ Evaluation.propTypes = {

function mapStateToProps(state) {
return {
isCameraConnected: state.sessions.isCameraConnected,
isRawPhoneFeedWSConnected: state.sessions.isRawPhoneFeedWSConnected,
rawPhoneFeedWSURL: state.sessions.rawPhoneFeedWSURL,
rawPhoneFeedWSData: state.sessions.rawPhoneFeedWSData,
availableCameras: state.sessions.availableCameras,
selectedCamera: state.sessions.selectedCamera,
viewConfig: state.sessions.sessionViewConfig,
};
}
Expand Down

0 comments on commit c811723

Please sign in to comment.