From 3d0e2efa584926307a3aa89c5e94f5ba583edcbc Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Thu, 18 Apr 2019 01:23:13 +0530 Subject: [PATCH] feat(sessions): add expected emotions capturing feature :sparkles: --- .../sessions/create-questionnaire/submit.js | 2 +- ...ionnaire-actions.js => session-actions.js} | 16 +- src/redux/reducers/index.js | 4 +- ...ionnaire-reducer.js => session-reducer.js} | 19 +- src/redux/types.js | 5 +- src/routes/dashboard.js | 6 +- src/views/sessions/emotions.jsx | 193 ++++++++++++++++++ src/views/sessions/index.js | 1 + src/views/sessions/new-session.jsx | 164 ++++++++------- src/views/sessions/questionnaire.jsx | 79 ++++--- 10 files changed, 352 insertions(+), 137 deletions(-) rename src/redux/actions/{questionnaire-actions.js => session-actions.js} (85%) rename src/redux/reducers/{questionnaire-reducer.js => session-reducer.js} (72%) create mode 100644 src/views/sessions/emotions.jsx diff --git a/src/forms/sessions/create-questionnaire/submit.js b/src/forms/sessions/create-questionnaire/submit.js index 3dfa520..2f87316 100644 --- a/src/forms/sessions/create-questionnaire/submit.js +++ b/src/forms/sessions/create-questionnaire/submit.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import validate from './validate'; import store from '../../../redux/store'; import {addNotification} from '../../../redux/actions/notification-actions'; -import {createQuestionnaire} from '../../../redux/actions/questionnaire-actions'; +import {createQuestionnaire} from '../../../redux/actions/session-actions'; function submit(values, dispatch, props) { // dirty trick to get around the redux form validation issue. diff --git a/src/redux/actions/questionnaire-actions.js b/src/redux/actions/session-actions.js similarity index 85% rename from src/redux/actions/questionnaire-actions.js rename to src/redux/actions/session-actions.js index 1bcecb7..645024a 100644 --- a/src/redux/actions/questionnaire-actions.js +++ b/src/redux/actions/session-actions.js @@ -2,9 +2,10 @@ import _ from 'lodash'; import { ADD_QUESTIONNAIRE, FETCH_QUESTIONNAIRES, - SET_QUESTIONNAIRE_VIEW_CONFIG, + SET_SESSION_VIEW_CONFIG, SET_SELECTED_QUESTIONNAIRE, - UPDATE_QUESTIONNAIRE + UPDATE_QUESTIONNAIRE, + SET_EXPECTED_EMOTIONS } from '../types'; import {API_ENDPOINTS} from '../../api'; import {HttpInterceptor} from '../../services'; @@ -65,7 +66,14 @@ export const setSelectedQuestionnaire = data => (dispatch) => { }); }; -export const setQuestionnaireViewConfig = data => dispatch => dispatch({ - type: SET_QUESTIONNAIRE_VIEW_CONFIG, +export const setExpectedEmotions = data => (dispatch) => { + return dispatch({ + type: SET_EXPECTED_EMOTIONS, + payload: data, + }); +}; + +export const setSessionViewConfig = data => dispatch => dispatch({ + type: SET_SESSION_VIEW_CONFIG, payload: data, }); diff --git a/src/redux/reducers/index.js b/src/redux/reducers/index.js index eb61831..8a0a948 100644 --- a/src/redux/reducers/index.js +++ b/src/redux/reducers/index.js @@ -7,7 +7,7 @@ import { loaderReducer } from './loader-reducer'; import { notificationReducer } from './notification-reducer'; import { userReducer } from './user-reducer'; import { applicationReducer } from './application-reducer'; -import { questionnaireReducer } from './questionnaire-reducer'; +import { sessionReducer } from './session-reducer'; const appReducer = combineReducers({ form: reduxFormReducer, @@ -18,7 +18,7 @@ const appReducer = combineReducers({ notifications: notificationReducer, users: userReducer, applications: applicationReducer, - questionnaires: questionnaireReducer, + sessions: sessionReducer, }); const rootReducer = (state, action) => { diff --git a/src/redux/reducers/questionnaire-reducer.js b/src/redux/reducers/session-reducer.js similarity index 72% rename from src/redux/reducers/questionnaire-reducer.js rename to src/redux/reducers/session-reducer.js index 00d8fc4..6ca75a0 100644 --- a/src/redux/reducers/questionnaire-reducer.js +++ b/src/redux/reducers/session-reducer.js @@ -2,9 +2,10 @@ import _ from 'lodash'; import { ADD_QUESTIONNAIRE, FETCH_QUESTIONNAIRES, - SET_QUESTIONNAIRE_VIEW_CONFIG, + SET_SESSION_VIEW_CONFIG, SET_SELECTED_QUESTIONNAIRE, - UPDATE_QUESTIONNAIRE + UPDATE_QUESTIONNAIRE, + SET_EXPECTED_EMOTIONS } from '../types'; const initialState = { @@ -12,10 +13,11 @@ const initialState = { newQuestionnaire: {}, selectedQuestionnaire: {}, editedQuestionnaire: {}, - questionnaireViewConfig: {} + sessionViewConfig: {}, + expectedEmotions: [] }; -export function questionnaireReducer(state = initialState, action) { +export function sessionReducer(state = initialState, action) { switch (action.type) { case FETCH_QUESTIONNAIRES: return { @@ -39,10 +41,15 @@ export function questionnaireReducer(state = initialState, action) { ...state, selectedQuestionnaire: action.payload, }; - case SET_QUESTIONNAIRE_VIEW_CONFIG: + case SET_EXPECTED_EMOTIONS: return { ...state, - questionnaireViewConfig: action.payload, + expectedEmotions: action.payload, + }; + case SET_SESSION_VIEW_CONFIG: + return { + ...state, + sessionViewConfig: action.payload, }; default: return state; diff --git a/src/redux/types.js b/src/redux/types.js index ce3c990..6507762 100644 --- a/src/redux/types.js +++ b/src/redux/types.js @@ -44,10 +44,11 @@ export const SET_SELECTED_APPLICATION = 'SET_SELECTED_APPLICATION'; export const SET_APPLICATION_VIEW_CONFIG = 'SET_APPLICATION_VIEW_CONFIG'; export const UPDATE_APPLICATION_SHARING_STATUS = 'UPDATE_APPLICATION_SHARING_STATUS'; -// Questionnaires +// Sessions +export const SET_SESSION_VIEW_CONFIG = 'SET_SESSION_VIEW_CONFIG'; export const FETCH_QUESTIONNAIRES = 'FETCH_QUESTIONNAIRES'; export const ADD_QUESTIONNAIRE = 'ADD_QUESTIONNAIRE'; export const UPDATE_QUESTIONNAIRE = 'UPDATE_QUESTIONNAIRE'; export const SET_SELECTED_QUESTIONNAIRE = 'SET_SELECTED_QUESTIONNAIRE'; -export const SET_QUESTIONNAIRE_VIEW_CONFIG = 'SET_QUESTIONNAIRE_VIEW_CONFIG'; +export const SET_EXPECTED_EMOTIONS = 'SET_EXPECTED_EMOTIONS'; diff --git a/src/routes/dashboard.js b/src/routes/dashboard.js index ee2af25..f621ca1 100644 --- a/src/routes/dashboard.js +++ b/src/routes/dashboard.js @@ -2,7 +2,8 @@ import { DashboardView, ApplicationsView, NewSessionView, - QuestionnaireView + QuestionnaireView, + EmotionsView } from '../views'; import testIcon from '../assets/img/icons/test.svg'; import dashboardIcon from '../assets/img/icons/dashboard.svg'; @@ -21,6 +22,9 @@ const dashboard = [ { path: '/questionnaire', hide: true, name: 'Questionnaire', icon: testIcon, component: QuestionnaireView, restrictionLevel: 0, }, + { + path: '/emotions', hide: true, name: 'Expected Emotions', icon: testIcon, component: EmotionsView, restrictionLevel: 0, + }, { redirect: true, path: '/', pathTo: '/dashboard', name: 'Dashboard', restrictionLevel: 0, }, diff --git a/src/views/sessions/emotions.jsx b/src/views/sessions/emotions.jsx new file mode 100644 index 0000000..cfaddff --- /dev/null +++ b/src/views/sessions/emotions.jsx @@ -0,0 +1,193 @@ +import React, {Component} from 'react'; +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import { + Grid, + Row, + Col, +} from 'react-bootstrap'; +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; +import 'react-sliding-pane/dist/react-sliding-pane.css'; +import * as applicationActionCreators from '../../redux/actions/application-actions'; +import * as sessionActionCreators from '../../redux/actions/session-actions'; +import {CustomButton as Button} from '../../elements'; +import happinessIcon from '../../assets/img/emotions/happiness.svg'; +import sadnessIcon from '../../assets/img/emotions/sadness.svg'; +import neutralIcon from '../../assets/img/emotions/neutral.svg'; +import disgustIcon from '../../assets/img/emotions/disgust.svg'; +import surpriseIcon from '../../assets/img/emotions/surprise.svg'; +import fearIcon from '../../assets/img/emotions/fear.svg'; +import angerIcon from '../../assets/img/emotions/anger.svg'; + +class Emotions extends Component { + + constructor(props) { + super(props); + this.state = { + emotions: [ + { + displayName: 'Happiness', + name: 'happiness', + icon: happinessIcon, + selected: false + }, + { + displayName: 'Neutral', + name: 'neutral', + icon: neutralIcon, + selected: false + }, + { + displayName: 'Sadness', + name: 'sadness', + icon: sadnessIcon, + selected: false + }, + { + displayName: 'Anger', + name: 'anger', + icon: angerIcon, + selected: false + }, + { + displayName: 'Disgust', + name: 'disgust', + icon: disgustIcon, + selected: false + }, + { + displayName: 'Fear', + name: 'fear', + icon: fearIcon, + selected: false + }, + { + displayName: 'Surprise', + name: 'surprise', + icon: surpriseIcon, + selected: false + } + ], + selectedEmotions: [] + }; + } + + handleEmotionSelect = (emotion) => { + const {emotions, selectedEmotions} = this.state; + // clone object to avoid mutating + const _emotion = _.cloneDeep(emotion); + _emotion.selected = !_emotion.selected; + + // clone array to avoid mutations + const _emotions = _.cloneDeep(emotions); + + // Find index and replace item + const index = _.findIndex(_emotions, emotion); + _emotions.splice(index, 1, _emotion); + + this.setState({emotions: _emotions}); + + const _selectedEmotions = _.cloneDeep(selectedEmotions); + + if (_emotion.selected) { + _selectedEmotions.push(_emotion.name); + } else { + const index_selected = _.findIndex(_selectedEmotions, _emotion.name); + _selectedEmotions.splice(index_selected, 1, _emotion.name); + } + + this.setState({selectedEmotions: _selectedEmotions}); + }; + + handleEmotionSubmit = () => { + const { actions } = this.props; + const { selectedEmotions } = this.state; + actions.sessions.setExpectedEmotions(selectedEmotions); + }; + + render() { + const { + emotions, selectedEmotions + } = this.state; + + return ( +
+ + +
+
Application
+
+
+
+ + +
+

Expected Emotions

+
+ Expected emotions may vary from application to application. In order for the system to provide an + accurate Cybersickness score, Please select the expected emotions from bellow. +
+
+ +
+ +
+ { + emotions.map(emotion => ( +
this.handleEmotionSelect(emotion)}> +
+
+ +
+
+
+
{emotion.displayName}
+
+
+ )) + } +
+
+
+ + +
+ +
+ +
+
+
+ ); + } +} + +const injectedPropTypes = { + actions: PropTypes.shape({}), +}; + +Emotions.propTypes = { + ...injectedPropTypes, +}; + +function mapStateToProps(state) { + return { + selectedApplication: state.applications.selectedApplication, + viewConfig: state.sessions.sessionViewConfig, + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: { + sessions: bindActionCreators(sessionActionCreators, dispatch), + applications: bindActionCreators(applicationActionCreators, dispatch), + }, + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(Emotions); diff --git a/src/views/sessions/index.js b/src/views/sessions/index.js index 210666e..1951dc9 100644 --- a/src/views/sessions/index.js +++ b/src/views/sessions/index.js @@ -1,2 +1,3 @@ export { default as NewSessionView } from './new-session'; export { default as QuestionnaireView } from './questionnaire'; +export { default as EmotionsView } from './emotions'; \ No newline at end of file diff --git a/src/views/sessions/new-session.jsx b/src/views/sessions/new-session.jsx index 98dd4dc..1af50f7 100644 --- a/src/views/sessions/new-session.jsx +++ b/src/views/sessions/new-session.jsx @@ -1,12 +1,12 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; import { Grid, Row, Col, } from 'react-bootstrap'; -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; import 'react-sliding-pane/dist/react-sliding-pane.css'; import createAppIcon from '../../assets/img/illustrations/create-app.svg' import selectAppIcon from '../../assets/img/illustrations/select-app.svg' @@ -33,7 +33,7 @@ class NewSession extends Component { } componentWillReceiveProps(nextProps) { - const { selectedApplication } = this.props; + const {selectedApplication} = this.props; if (selectedApplication && nextProps.selectedApplication) { if (!_.isEqual(selectedApplication, nextProps.selectedApplication)) { let path = '/questionnaire'; @@ -81,7 +81,7 @@ class NewSession extends Component { if (applications) { applicationOptions = applications .map(app => ( - { value: app, label: app.name } + {value: app, label: app.name} )); } @@ -90,7 +90,7 @@ class NewSession extends Component { if (applicationTypes) { applicationTypesOptions = applicationTypes .map(type => ( - { value: type, label: type.display_name_full } + {value: type, label: type.display_name_full} )); } @@ -99,99 +99,109 @@ class NewSession extends Component { if (applicationGenres) { applicationGenreOptions = applicationGenres .map(genre => ( - { value: genre, label: genre.display_name } + {value: genre, label: genre.display_name} )); } return ( -
+
- -
-
-

Create a New Testing Session

-
Please select one of the options from bellow and proceed with the session
-
-
-
-
-
- +
+ + +
+

Create a New Testing Session

+
Please select one of the options from bellow and proceed + with the session
+
+ +
+ + +
+
+
+
+ +
-
-
-
Create Application
-
-
Create a new application with custom metadata.
+
+
Create Application
+
+
Create a new application with custom metadata.
+
-
-
- -
-
-
-
-
- +
+
-
-
Select Existing
-
-
Select an application which is already being created.
+
+
+
+ +
+
+
+
Select Existing
+
+
Select an application which is already being created.
+
+
+
+
-
-
-
-
- - - - - - - - - - - - - - -
- + + + + + + + + + + + + + + + + +
); } } + const injectedPropTypes = { actions: PropTypes.shape({}), }; diff --git a/src/views/sessions/questionnaire.jsx b/src/views/sessions/questionnaire.jsx index bf29fe3..608db4f 100644 --- a/src/views/sessions/questionnaire.jsx +++ b/src/views/sessions/questionnaire.jsx @@ -1,15 +1,15 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; import { Grid, Row, Col, } from 'react-bootstrap'; -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; import 'react-sliding-pane/dist/react-sliding-pane.css'; import * as applicationActionCreators from '../../redux/actions/application-actions'; -import * as questionnaireActionCreators from '../../redux/actions/questionnaire-actions'; +import * as sessionActionCreators from '../../redux/actions/session-actions'; import _ from 'lodash'; import * as qs from 'query-string'; import { @@ -21,12 +21,12 @@ import {CreateQuestionnaireForm} from '../../forms'; class Questionnaire extends Component { componentDidMount() { - const {actions, location} = this.props; + const {actions, location, viewConfig} = this.props; const route = qs.parse(location.search); let title = null; - if(route.type) { + if (route.type) { if (route.type === 'pre') { title = 'Pre Exposure Questionnaire'; } else if (route.type === 'post') { @@ -34,57 +34,48 @@ class Questionnaire extends Component { } } - const config = { + const questionnaireViewConfig = { title: title, type: route.type, }; - actions.questionnaires.setQuestionnaireViewConfig(config); + actions.sessions.setSessionViewConfig(_.assign(viewConfig, {questionnaire: questionnaireViewConfig})); } - handleQuestionnaireSubmit = (e) => { - const {actions} = this.props; - const config = { - title: 'Create New Application', - button: 'Create Application', - mode: 'create', - }; - actions.questionnaires.setQuestionnaireViewConfig(config); - this.openModal(e); - }; - render() { const { - actions, modal, viewConfig, editingApplication, selectedApplication + viewConfig, selectedApplication } = this.props; return ( -
+
-
Application: { selectedApplication.name }
+
Application: {selectedApplication.name}
- - -
-

{ viewConfig.title }

-
Please complete the following questionnaire
-
- -
- - - - -
- )} - /> - - +
+ + +
+

{viewConfig.questionnaire ? viewConfig.questionnaire.title : 'Questionnaire'}

+
Please complete the following questionnaire
+
+ +
+ + + + +
+ )} + /> + + +
); @@ -102,14 +93,14 @@ Questionnaire.propTypes = { function mapStateToProps(state) { return { selectedApplication: state.applications.selectedApplication, - viewConfig: state.questionnaires.questionnaireViewConfig, + viewConfig: state.sessions.sessionViewConfig, }; } function mapDispatchToProps(dispatch) { return { actions: { - questionnaires: bindActionCreators(questionnaireActionCreators, dispatch), + sessions: bindActionCreators(sessionActionCreators, dispatch), applications: bindActionCreators(applicationActionCreators, dispatch), }, };