An easy-to-use platform for EEG experimentation in the classroom
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

large overhaul to the design, style for better ux

Co-Authored-By: Yury-Shevchenko <shevchenko_yury@mail.ru>

this adds an option to enable/disable eeg, adds tiles for the four main experiment types

+355 -179
+4
app/actions/experimentActions.js
··· 5 5 export const PAUSE = 'PAUSE'; 6 6 export const STOP = 'STOP'; 7 7 export const SET_TYPE = 'SET_TYPE'; 8 + export const SET_PARADIGM = 'SET_PARADIGM'; 8 9 export const SET_SUBJECT = 'SET_SUBJECT'; 10 + export const SET_GROUP = 'SET_GROUP'; 9 11 export const CREATE_NEW_WORKSPACE = 'CREATE_NEW_WORKSPACE'; 10 12 export const SET_SESSION = 'SET_SESSION'; 11 13 export const SET_PARAMS = 'SET_PARAMS'; ··· 24 26 // export const stop = () => ({ type: STOP }); 25 27 export const stop = payload => ({ payload, type: STOP }); 26 28 export const setType = payload => ({ payload, type: SET_TYPE }); 29 + export const setParadigm = payload => ({ payload, type: SET_PARADIGM }); 27 30 export const setSubject = payload => ({ payload, type: SET_SUBJECT }); 31 + export const setGroup = payload => ({ payload, type: SET_GROUP }); 28 32 export const setSession = payload => ({ payload, type: SET_SESSION }); 29 33 export const setParams = payload => ({ payload, type: SET_PARAMS }); 30 34 export const setDescription = payload => ({ payload, type: SET_DESCRIPTION });
+1
app/components/CollectComponent/PreTestComponent.js
··· 36 36 trials: { [string]: Trial }; 37 37 timelines: {}; 38 38 subject: string; 39 + group: string; 39 40 session: number; 40 41 openRunComponent: () => void; 41 42 }
+32 -2
app/components/CollectComponent/RunComponent.js
··· 34 34 trials: { [string]: Trial }; 35 35 timelines: {}; 36 36 subject: string; 37 + group: string; 37 38 session: number; 38 39 deviceType: DEVICES; 39 40 isEEGEnabled: boolean; ··· 42 43 43 44 interface State { 44 45 isInputModalOpen: boolean; 46 + isGroupInputModalOpen: boolean; 45 47 } 46 48 47 49 export default class Run extends Component<Props, State> { ··· 52 54 handleStartExperiment: () => void; 53 55 handleTimeline: () => void; 54 56 handleCloseInputModal: () => void; 57 + handleCloseGroupInputModal: () => void; 55 58 handleClean: () => void; 56 59 57 60 constructor(props: Props) { 58 61 super(props); 59 62 this.state = { 60 - isInputModalOpen: props.subject.length === 0 63 + isInputModalOpen: props.subject.length === 0, 64 + isGroupInputModalOpen: false 61 65 }; 62 66 this.handleSubjectEntry = debounce(this.handleSubjectEntry, 500).bind(this); 63 67 this.handleSessionEntry = debounce(this.handleSessionEntry, 500).bind(this); 64 68 this.handleStartExperiment = this.handleStartExperiment.bind(this); 65 69 this.handleTimeline = this.handleTimeline.bind(this); 66 70 this.handleCloseInputModal = this.handleCloseInputModal.bind(this); 71 + this.handleCloseGroupInputModal = this.handleCloseGroupInputModal.bind(this); 67 72 } 68 73 69 74 componentDidMount() { ··· 94 99 this.setState({ isInputModalOpen: false }); 95 100 } 96 101 102 + handleCloseGroupInputModal(name: string) { 103 + this.props.experimentActions.setGroup(name); 104 + this.setState({ isGroupInputModalOpen: false }); 105 + } 106 + 97 107 handleTimeline() { 98 108 let injectionFunction = () => null; 99 109 if (this.props.isEEGEnabled) { ··· 144 154 basic 145 155 textAlign="left" 146 156 className={styles.descriptionContainer} 157 + vertical 147 158 > 148 159 <Header as="h1">{this.props.type}</Header> 149 160 <Segment basic className={styles.infoSegment}> ··· 157 168 onClick={() => this.setState({ isInputModalOpen: true })} 158 169 /> 159 170 </Segment> 171 + 172 + <Segment basic className={styles.infoSegment}> 173 + Group Name: <b>{this.props.group}</b> 174 + <Button 175 + basic 176 + circular 177 + size="huge" 178 + icon="edit" 179 + className={styles.closeButton} 180 + onClick={() => this.setState({ isGroupInputModalOpen: true })} 181 + /> 182 + </Segment> 183 + 160 184 <Segment basic className={styles.infoSegment}> 161 185 Session Number: <b>{this.props.session}</b> 162 186 </Segment> ··· 175 199 } 176 200 return ( 177 201 <ExperimentWindow settings={{ 178 - script: this.props.type, 202 + script: this.props.paradigm, 179 203 on_finish: (csv) => { 180 204 this.props.experimentActions.stop({data: csv}); 181 205 } ··· 200 224 onClose={this.handleCloseInputModal} 201 225 onExit={() => this.setState({ isInputModalOpen: false })} 202 226 header="Enter Subject Name" 227 + /> 228 + <InputModal 229 + open={this.state.isGroupInputModalOpen} 230 + onClose={this.handleCloseGroupInputModal} 231 + onExit={() => this.setState({ isGroupInputModalOpen: false })} 232 + header="Enter Group Name" 203 233 /> 204 234 </div> 205 235 );
+2
app/components/CollectComponent/index.js
··· 33 33 trials: ?{ [string]: Trial }; 34 34 timelines: ?{}; 35 35 subject: string; 36 + group: string; 36 37 session: number; 37 38 isEEGEnabled: boolean; 38 39 } ··· 141 142 trials={this.props.trials} 142 143 timelines={this.props.timelines} 143 144 subject={this.props.subject} 145 + group={this.props.group} 144 146 session={this.props.session} 145 147 openRunComponent={this.handleRunComponentOpen} 146 148 />
+109 -33
app/components/DesignComponent/index.js
··· 5 5 import styles from '../styles/common.css'; 6 6 import { EXPERIMENTS, SCREENS } from '../../constants/constants'; 7 7 import { 8 + readWorkspaces 9 + } from '../../utils/filesystem/storage'; 10 + import { 8 11 MainTimeline, 9 12 Trial, 10 13 ExperimentParameters, ··· 14 17 import PreviewExperimentComponent from '../PreviewExperimentComponent'; 15 18 import CustomDesign from './CustomDesignComponent'; 16 19 import PreviewButton from '../PreviewButtonComponent'; 20 + 17 21 import facesHousesOverview from '../../assets/common/FacesHouses_Overview.png'; 22 + import stroopOverview from '../../assets/common/Stroop.png'; 23 + import multitaskingOverview from '../../assets/common/Multitasking.png'; 24 + import searchOverview from '../../assets/common/VisualSearch.png'; 25 + import customOverview from '../../assets/common/Custom.png'; 26 + 27 + import { loadTimeline } from '../../utils/jspsych/functions'; 28 + import { toast } from 'react-toastify'; 29 + import InputModal from '../InputModal'; 18 30 19 31 const DESIGN_STEPS = { 20 32 OVERVIEW: 'OVERVIEW', ··· 25 37 interface Props { 26 38 history: Object; 27 39 type: EXPERIMENTS; 40 + paradigm: EXPERIMENTS; 28 41 title: string; 29 42 params: ExperimentParameters; 30 43 mainTimeline: MainTimeline; ··· 37 50 interface State { 38 51 activeStep: string; 39 52 isPreviewing: boolean; 53 + isNewExperimentModalOpen: boolean; 54 + recentWorkspaces: Array<string>; 40 55 } 41 56 42 57 export default class Design extends Component<Props, State> { ··· 44 59 state: State; 45 60 handleStepClick: (Object, Object) => void; 46 61 handleStartExperiment: Object => void; 47 - handlePreview: () => void; 62 + handleCustomizeExperiment: Object => void; 63 + handlePreview: (Object) => void; 64 + handleLoadCustomExperiment: string => void; 48 65 49 66 constructor(props: Props) { 50 67 super(props); 51 68 this.state = { 52 69 activeStep: DESIGN_STEPS.OVERVIEW, 53 - isPreviewing: false 70 + isPreviewing: false, 71 + isNewExperimentModalOpen: false, 72 + recentWorkspaces: [], 54 73 }; 55 74 this.handleStepClick = this.handleStepClick.bind(this); 56 75 this.handleStartExperiment = this.handleStartExperiment.bind(this); 76 + this.handleCustomizeExperiment = this.handleCustomizeExperiment.bind(this); 77 + this.handleLoadCustomExperiment = this.handleLoadCustomExperiment.bind(this); 57 78 this.handlePreview = this.handlePreview.bind(this); 79 + this.endPreview = this.endPreview.bind(this); 58 80 if (isNil(props.params)) { 59 81 props.experimentActions.loadDefaultTimeline(); 60 82 } 61 83 } 62 84 85 + componentDidMount() { 86 + this.setState({ recentWorkspaces: readWorkspaces() }); 87 + } 88 + 63 89 handleStepClick(step: string) { 64 90 this.setState({ activeStep: step }); 65 91 } ··· 68 94 this.props.history.push(SCREENS.COLLECT.route); 69 95 } 70 96 71 - handlePreview() { 97 + handleCustomizeExperiment(){ 98 + this.setState({ 99 + isNewExperimentModalOpen: true 100 + }); 101 + } 102 + 103 + handleLoadCustomExperiment(title: string) { 104 + this.setState({ isNewExperimentModalOpen: false }); 105 + // Don't create new workspace if it already exists or title is too short 106 + if (this.state.recentWorkspaces.includes(title)) { 107 + toast.error(`Experiment already exists`); 108 + return; 109 + } 110 + if (title.length <= 3) { 111 + toast.error(`Experiment name is too short`); 112 + return; 113 + } 114 + console.log('paradigm', this.props.paradigm) 115 + this.props.experimentActions.createNewWorkspace({ 116 + title, 117 + type: EXPERIMENTS.CUSTOM, 118 + paradigm: this.props.paradigm 119 + }); 120 + } 121 + 122 + handlePreview(e) { 123 + e.target.blur(); 72 124 this.setState({ isPreviewing: !this.state.isPreviewing }); 73 125 } 74 126 127 + endPreview() { 128 + this.setState({ isPreviewing: false }); 129 + } 130 + 131 + renderOverviewIcon(type) { 132 + switch (type) { 133 + case EXPERIMENTS.N170: 134 + return facesHousesOverview; 135 + break; 136 + 137 + case EXPERIMENTS.STROOP: 138 + return stroopOverview; 139 + break; 140 + 141 + case EXPERIMENTS.MULTI: 142 + return multitaskingOverview; 143 + break; 144 + 145 + case EXPERIMENTS.SEARCH: 146 + return searchOverview; 147 + break; 148 + 149 + case EXPERIMENTS.CUSTOM: 150 + default: 151 + return customOverview; 152 + break; 153 + } 154 + } 155 + 75 156 renderSectionContent() { 76 157 switch (this.state.activeStep) { 77 158 case DESIGN_STEPS.BACKGROUND: ··· 83 164 textAlign="right" 84 165 verticalAlign="middle" 85 166 > 86 - <Header as="h1">The N170 ERP</Header> 167 + <Header as="h1">{this.props.background_title}</Header> 87 168 </Grid.Column> 88 169 <Grid.Column stretched width={6} verticalAlign="middle"> 89 170 <Segment basic> 90 - <p>The N170 is a large negative event-related potential (ERP) 91 - component that occurs around 170ms after the detection of faces, but not 92 - objects, scrambled faces, or other body parts such as hands. The 93 - The N170 is most easily detected at lateral posterior electrodes.</p> 94 - <p>Although there is no consensus on the specific source of the N170, researchers 95 - believe it is related to activity in the fusiform face area, an 96 - area of the brain that shows a similar response pattern and is 97 - involved in encoding the holistic representation of a face (i.e 98 - eyes, nose mouth all arranged in the appropriate way).</p> 99 - 171 + {this.props.background} 100 172 </Segment> 101 173 102 174 </Grid.Column> ··· 107 179 <Grid relaxed padded className={styles.contentGrid}> 108 180 <Grid.Column 109 181 stretched 110 - width={6} 182 + width={12} 111 183 textAlign="right" 112 184 verticalAlign="middle" 113 185 className={styles.jsPsychColumn} 114 186 > 115 187 <PreviewExperimentComponent 116 - params={this.props.params} 117 - mainTimeline={this.props.mainTimeline} 118 - trials={this.props.trials} 119 - timelines={this.props.timelines} 188 + {...loadTimeline(this.props.paradigm)} 120 189 isPreviewing={this.state.isPreviewing} 190 + onEnd={this.endPreview} 191 + type={this.props.type} 192 + paradigm={this.props.paradigm} 121 193 /> 122 194 </Grid.Column> 123 - <Grid.Column width={6} verticalAlign="middle"> 124 - <p> 125 - Subjects will view a series of images of{' '} 126 - <b> faces and houses</b> for <b>120 seconds</b> 127 - </p> 128 - <p> 129 - Subjects will mentally note which stimulus they are perceiving 130 - </p> 195 + <Grid.Column width={4} verticalAlign="middle"> 196 + <Segment as="p" basic> 197 + {this.props.protocol} 198 + </Segment> 131 199 <PreviewButton 132 200 isPreviewing={this.state.isPreviewing} 133 - onClick={this.handlePreview} 201 + onClick={(e) => this.handlePreview(e)} 134 202 /> 135 203 </Grid.Column> 136 204 </Grid> ··· 141 209 <Grid stretched relaxed padded className={styles.contentGrid}> 142 210 <Grid.Column width={3}> 143 211 <Segment basic padded> 144 - <Image src={facesHousesOverview} /> 212 + <Image src={this.renderOverviewIcon(this.props.type)} /> 145 213 </Segment> 146 214 </Grid.Column> 147 215 <Grid.Column ··· 154 222 </Grid.Column> 155 223 <Grid.Column stretched width={6} verticalAlign="middle"> 156 224 <Segment as="p" basic> 157 - Faces contain a lot of information that is relevant to our 158 - survival. It 159 - {"'"}s important to be able to quickly recognize people you can 160 - trust and read emotions in both strangers and people you know 225 + {this.props.overview} 161 226 </Segment> 162 227 </Grid.Column> 163 228 </Grid> ··· 176 241 steps={DESIGN_STEPS} 177 242 activeStep={this.state.activeStep} 178 243 onStepClick={this.handleStepClick} 244 + customizeButton={ 245 + <Button secondary onClick={this.handleCustomizeExperiment}> 246 + Customize 247 + </Button> 248 + } 179 249 button={ 180 250 <Button primary onClick={this.handleStartExperiment}> 181 251 Collect Data ··· 183 253 } 184 254 /> 185 255 {this.renderSectionContent()} 256 + <InputModal 257 + open={this.state.isNewExperimentModalOpen} 258 + onClose={this.handleLoadCustomExperiment} 259 + onExit={() => this.setState({ isNewExperimentModalOpen: false })} 260 + header="Enter a title for this experiment" 261 + /> 186 262 </div> 187 263 ); 188 264 }
+7 -5
app/components/HomeComponent/OverviewComponent.js
··· 43 43 this.setState({ activeStep: step }); 44 44 } 45 45 46 - handlePreview() { 46 + handlePreview(e) { 47 + e.target.blur(); 47 48 this.setState({ isPreviewing: !this.state.isPreviewing }); 48 49 } 49 50 ··· 58 59 <Grid relaxed padded className={styles.contentGrid}> 59 60 <Grid.Column 60 61 stretched 61 - width={10} 62 + width={12} 62 63 textAlign="right" 63 64 verticalAlign="middle" 64 65 className={styles.jsPsychColumn} 65 66 > 66 67 <PreviewExperimentComponent 67 - {...loadTimeline(this.props.type)} 68 + {...loadTimeline(this.props.paradigm)} 68 69 isPreviewing={this.state.isPreviewing} 69 70 onEnd={this.endPreview} 70 71 type={this.props.type} 72 + paradigm={this.props.paradigm} 71 73 /> 72 74 </Grid.Column> 73 - <Grid.Column stretched width={6} verticalAlign="middle"> 75 + <Grid.Column stretched width={4} verticalAlign="middle"> 74 76 <Segment as="p" basic> 75 77 {this.props.protocol} 76 78 </Segment> 77 79 <PreviewButton 78 80 isPreviewing={this.state.isPreviewing} 79 - onClick={this.handlePreview} 81 + onClick={(e) => this.handlePreview(e)} 80 82 /> 81 83 </Grid.Column> 82 84 </Grid>
+107 -111
app/components/HomeComponent/index.js
··· 6 6 import styles from '../styles/common.css'; 7 7 import { EXPERIMENTS, SCREENS, KERNEL_STATUS } from '../../constants/constants'; 8 8 import faceHouseIcon from '../../assets/common/FacesHouses.png'; 9 + import stroopIcon from '../../assets/common/Stroop.png'; 10 + import multitaskingIcon from '../../assets/common/Multitasking.png'; 11 + import searchIcon from '../../assets/common/VisualSearch.png'; 9 12 import customIcon from '../../assets/common/Custom.png'; 10 13 import appLogo from '../../assets/common/app_logo.png'; 11 14 import { ··· 89 92 } else { 90 93 this.props.experimentActions.createNewWorkspace({ 91 94 title: experimentType, 92 - type: experimentType 95 + type: experimentType, 96 + paradigm: experimentType 93 97 }); 94 98 this.props.history.push(SCREENS.DESIGN.route); 95 99 } ··· 166 170 case HOME_STEPS.NEW: 167 171 default: 168 172 return ( 169 - <Grid columns="equal" relaxed padded> 170 - <Grid.Column> 171 - <Segment basic className={styles.descriptionContainer}> 172 - <Image src={faceHouseIcon} /> 173 - <Header as="h1">Faces and Houses</Header> 174 - <p> 175 - Explore the N170 event-related potential that is produced in 176 - response to viewing faces. It is called the N170 because it is 177 - a negative, downwards-facing wave that occurs around 170 178 - milliseconds after perceiving a face. 179 - </p> 180 - <Button 181 - secondary 182 - onClick={() => this.handleOpenOverview(EXPERIMENTS.N170)} 183 - > 184 - Review 185 - </Button> 186 - <Button 187 - primary 188 - onClick={() => this.handleNewExperiment(EXPERIMENTS.N170)} 189 - > 190 - Start Experiment 191 - </Button> 192 - </Segment> 193 - </Grid.Column> 194 - <Grid.Column> 195 - <Segment basic className={styles.descriptionContainer}> 196 - <Image src={faceHouseIcon} /> 197 - <Header as="h1">Stroop</Header> 198 - <p> 199 - Investigate the cognitive process of selective attention with 200 - the Stroop Task, a challenging experiment requiring the 201 - subject to name the color of a word instead of reading the 202 - word itself. 203 - </p> 204 - <Button 205 - secondary 206 - onClick={() => this.handleOpenOverview(EXPERIMENTS.STROOP)} 207 - > 208 - Review 209 - </Button> 210 - <Button secondary> 211 - Customize 212 - </Button> 213 - <Button 214 - primary 215 - onClick={() => this.handleNewExperiment(EXPERIMENTS.STROOP)} 216 - > 217 - Start Experiment 218 - </Button> 219 - </Segment> 220 - </Grid.Column> 221 - <Grid.Column> 222 - <Segment basic className={styles.descriptionContainer}> 223 - <Image src={faceHouseIcon} /> 224 - <Header as="h1">Multi-tasking</Header> 225 - <p> 226 - The multi-tasking test 227 - </p> 228 - <Button 229 - secondary 230 - onClick={() => this.handleOpenOverview(EXPERIMENTS.MULTI)} 231 - > 232 - Review 233 - </Button> 234 - <Button 235 - primary 236 - onClick={() => this.handleNewExperiment(EXPERIMENTS.MULTI)} 237 - > 238 - Start Experiment 239 - </Button> 240 - </Segment> 241 - </Grid.Column> 242 - <Grid.Column> 243 - <Segment basic className={styles.descriptionContainer}> 244 - <Image src={faceHouseIcon} /> 245 - <Header as="h1">Attention</Header> 246 - <p> 247 - The visual search task 248 - </p> 249 - <Button 250 - secondary 251 - onClick={() => this.handleOpenOverview(EXPERIMENTS.SEARCH)} 252 - > 253 - Review 254 - </Button> 255 - <Button 256 - primary 257 - onClick={() => this.handleNewExperiment(EXPERIMENTS.SEARCH)} 258 - > 259 - Start Experiment 260 - </Button> 261 - </Segment> 262 - </Grid.Column> 263 - <Grid.Column> 264 - <Segment basic className={styles.descriptionContainer}> 265 - <Image src={customIcon} /> 266 - <Header as="h1">Custom</Header> 267 - <p>Design your own EEG experiment!</p> 268 - <Button secondary disabled> 269 - Review 270 - </Button> 271 - <Button 272 - primary 273 - onClick={() => this.handleNewExperiment(EXPERIMENTS.CUSTOM)} 274 - > 275 - Start Experiment 276 - </Button> 277 - </Segment> 278 - </Grid.Column> 173 + <Grid columns="two" relaxed padded> 174 + <Grid.Row> 175 + <Grid.Column> 176 + <Segment> 177 + <Grid 178 + columns="two" 179 + className={styles.experimentCard} 180 + onClick={() => this.handleNewExperiment(EXPERIMENTS.N170)} 181 + > 182 + <Grid.Row> 183 + <Grid.Column width={4} className={styles.experimentCardImage}> 184 + <Image src={faceHouseIcon} /> 185 + </Grid.Column> 186 + <Grid.Column width={12} className={styles.descriptionContainer}> 187 + <Header as="h1" className={styles.experimentCardHeader}>Faces/Houses</Header> 188 + <div className={styles.experimentCardDescription}> 189 + <p> 190 + Explore how people react to different kinds of images. 191 + </p> 192 + </div> 193 + </Grid.Column> 194 + </Grid.Row> 195 + </Grid> 196 + </Segment> 197 + 198 + </Grid.Column> 199 + 200 + <Grid.Column> 201 + <Segment> 202 + <Grid 203 + columns="two" 204 + className={styles.experimentCard} 205 + onClick={() => this.handleNewExperiment(EXPERIMENTS.STROOP)} 206 + > 207 + <Grid.Row> 208 + <Grid.Column width={4} className={styles.experimentCardImage}> 209 + <Image src={stroopIcon} /> 210 + </Grid.Column> 211 + <Grid.Column width={12} className={styles.descriptionContainer}> 212 + <Header as="h1" className={styles.experimentCardHeader}>Stroop</Header> 213 + <div className={styles.experimentCardDescription}> 214 + <p> 215 + Investigate why it is hard to name the color of the word "RED" when it is printed in blue. 216 + </p> 217 + </div> 218 + </Grid.Column> 219 + </Grid.Row> 220 + </Grid> 221 + </Segment> 222 + </Grid.Column> 223 + 224 + </Grid.Row> 225 + 226 + <Grid.Row> 227 + <Grid.Column> 228 + <Segment> 229 + <Grid 230 + columns="two" 231 + className={styles.experimentCard} 232 + onClick={() => this.handleNewExperiment(EXPERIMENTS.MULTI)} 233 + > 234 + <Grid.Row> 235 + <Grid.Column width={4} className={styles.experimentCardImage}> 236 + <Image src={multitaskingIcon} /> 237 + </Grid.Column> 238 + <Grid.Column width={12} className={styles.descriptionContainer}> 239 + <Header as="h1" className={styles.experimentCardHeader}>Multi-tasking</Header> 240 + <div className={styles.experimentCardDescription}> 241 + <p> 242 + Explore why it is challenging to carry out multiple tasks at the same time. 243 + </p> 244 + </div> 245 + </Grid.Column> 246 + </Grid.Row> 247 + </Grid> 248 + </Segment> 249 + </Grid.Column> 250 + 251 + <Grid.Column> 252 + <Segment> 253 + <Grid 254 + columns="two" 255 + className={styles.experimentCard} 256 + onClick={() => this.handleNewExperiment(EXPERIMENTS.SEARCH)} 257 + > 258 + <Grid.Row> 259 + <Grid.Column width={4} className={styles.experimentCardImage}> 260 + <Image src={searchIcon} /> 261 + </Grid.Column> 262 + <Grid.Column width={12} className={styles.descriptionContainer}> 263 + <Header as="h1" className={styles.experimentCardHeader}>Visual Search</Header> 264 + <div className={styles.experimentCardDescription}> 265 + <p> 266 + Examine why it is hard to find your keys in a messy room. 267 + </p> 268 + </div> 269 + </Grid.Column> 270 + </Grid.Row> 271 + </Grid> 272 + </Segment> 273 + </Grid.Column> 274 + </Grid.Row> 279 275 </Grid> 280 276 ); 281 277 }
+1 -13
app/components/PreviewExperimentComponent.js
··· 66 66 return ( 67 67 <ExperimentWindow 68 68 settings={{ 69 - script: this.props.type, 69 + script: this.props.paradigm, 70 70 on_finish: (csv) => { 71 71 this.props.onEnd() 72 72 } 73 73 }} /> 74 - // <Experiment 75 - // settings={{ 76 - // timeline: this.handleTimeline(), 77 - // show_progress_bar: this.props.params.showProgessBar, 78 - // auto_update_progress_bar: false, 79 - // preload_images: this.handleImages() 80 - // }} 81 - // plugins={{ 82 - // 'callback-image-display': callbackImageDisplay, 83 - // 'callback-html-display': callbackHTMLDisplay 84 - // }} 85 - // /> 86 74 ); 87 75 } 88 76 }
+1
app/components/SecondaryNavComponent/index.js
··· 40 40 ))} 41 41 {this.props.button ? ( 42 42 <Grid.Column width="5" floated="right" textAlign="right"> 43 + {this.props.customizeButton} 43 44 {this.props.button} 44 45 </Grid.Column> 45 46 ) : null}
+17 -8
app/components/TopNavComponent/index.js
··· 68 68 {...SCREENS.COLLECT} 69 69 style={this.getStyleForScreen(SCREENS.COLLECT)} 70 70 /> 71 - <PrimaryNavSegment 72 - {...SCREENS.CLEAN} 73 - style={this.getStyleForScreen(SCREENS.CLEAN)} 74 - /> 75 - <PrimaryNavSegment 76 - {...SCREENS.ANALYZE} 77 - style={this.getStyleForScreen(SCREENS.ANALYZE)} 78 - /> 71 + {this.props.isEEGEnabled ? 72 + <PrimaryNavSegment 73 + {...SCREENS.CLEAN} 74 + style={this.getStyleForScreen(SCREENS.CLEAN)} 75 + /> : null 76 + } 77 + {this.props.isEEGEnabled ? 78 + <PrimaryNavSegment 79 + {...SCREENS.ANALYZE} 80 + style={this.getStyleForScreen(SCREENS.ANALYZE)} 81 + /> 82 + : 83 + <PrimaryNavSegment 84 + {...SCREENS.ANALYZEBEHAVIOR} 85 + style={this.getStyleForScreen(SCREENS.ANALYZE)} 86 + /> 87 + } 79 88 <Grid.Column width="3"> 80 89 <NavLink to={SCREENS.HOME.route}> 81 90 <Button secondary size="medium">
+37 -3
app/components/styles/common.css
··· 1 1 .homeContentContainer { 2 - padding-top: 50px !important; 2 + padding-top: 20px !important; 3 3 height: 100%; 4 4 overflow-y: auto; 5 5 } ··· 23 23 } 24 24 25 25 .contentGrid { 26 - height: 75%; 26 + height: 95%; 27 27 } 28 28 29 29 .overviewImage { ··· 55 55 .descriptionContainer p { 56 56 margin-top: 50px; 57 57 margin-bottom: 50px; 58 - min-height: 150px; 58 + min-height: 130px; 59 59 } 60 60 61 61 .descriptionContainer button { ··· 118 118 font-size: 18px !important; 119 119 height: 80%; 120 120 } 121 + 122 + .experimentCard { 123 + border-radius: 5px; 124 + background-color: #FFFFFF; 125 + box-shadow: 0 0 8px 0 rgba(0,0,0,0.1); 126 + border: 4px solid transparent; 127 + } 128 + 129 + .experimentCard:hover { 130 + border: 4px solid #007C70; 131 + cursor: pointer; 132 + } 133 + 134 + .experimentCardImage { 135 + align-self: center; 136 + } 137 + 138 + /* Header, Faces/Houses, Stroop, Mult-tasking, Visual Search */ 139 + .experimentCardHeader { 140 + color: #1A1A1A; 141 + font-family: Lato; 142 + font-size: 24px; 143 + letter-spacing: 0.86px; 144 + line-height: 29px; 145 + } 146 + 147 + /* Description copy */ 148 + .experimentCardDescription { 149 + color: #4A4A4A; 150 + font-family: Lato; 151 + font-size: 16px; 152 + letter-spacing: 0.57px; 153 + line-height: 24px; 154 + }
+4
app/components/styles/secondarynav.css
··· 1 1 /* Secondary Nav related styles */ 2 2 3 + .secondaryNavContainer { 4 + margin-bottom: 0px !important; 5 + } 6 + 3 7 .secondaryNavSegment { 4 8 display: flex !important; 5 9 justify-content: center;
+2 -1
app/constants/constants.js
··· 15 15 COLLECT: { route: '/collect', title: 'COLLECT', order: 2 }, 16 16 RUN: { route: '/run', title: 'RUN', order: 5 }, 17 17 CLEAN: { route: '/clean', title: 'CLEAN', order: 3 }, 18 - ANALYZE: { route: '/analyze', title: 'ANALYZE', order: 4 } 18 + ANALYZE: { route: '/analyze', title: 'ANALYZE', order: 4 }, 19 + ANALYZEBEHAVIOR: { route: '/analyze', title: 'ANALYZE', order: 3 } 19 20 }; 20 21 21 22 export const DEVICES = {
+1
app/containers/AnalyzeContainer.js
··· 10 10 title: state.experiment.title, 11 11 type: state.experiment.type, 12 12 deviceType: state.device.deviceType, 13 + isEEGEnabled: state.experiment.isEEGEnabled, 13 14 ...state.jupyter 14 15 }; 15 16 }
+1
app/containers/CleanContainer.js
··· 10 10 type: state.experiment.type, 11 11 title: state.experiment.title, 12 12 subject: state.experiment.subject, 13 + group: state.experiment.group, 13 14 session: state.experiment.session, 14 15 deviceType: state.device.deviceType, 15 16 ...state.jupyter
+2 -1
app/containers/TopNavBarContainer.js
··· 9 9 title: state.experiment.title, 10 10 location: state.router.location, 11 11 isRunning: state.experiment.isRunning, 12 - type: state.experiment.type 12 + type: state.experiment.type, 13 + isEEGEnabled: state.experiment.isEEGEnabled 13 14 }; 14 15 } 15 16
+11 -2
app/epics/experimentEpics.js
··· 13 13 } from "rxjs/operators"; 14 14 import { 15 15 setType, 16 + setParadigm, 16 17 setTitle, 17 18 saveWorkspace, 18 19 loadDefaultTimeline, ··· 21 22 STOP, 22 23 SAVE_WORKSPACE, 23 24 CREATE_NEW_WORKSPACE, 24 - SET_SUBJECT 25 + SET_SUBJECT, 26 + SET_GROUP 25 27 } from "../actions/experimentActions"; 26 28 import { 27 29 DEVICES, ··· 83 85 mergeMap(workspaceInfo => 84 86 of( 85 87 setType(workspaceInfo.type), 88 + setParadigm(workspaceInfo.paradigm), 86 89 setTitle(workspaceInfo.title), 87 90 loadDefaultTimeline() 88 91 ) ··· 91 94 92 95 const loadDefaultTimelineEpic = (action$, state$) => 93 96 action$.ofType(LOAD_DEFAULT_TIMELINE).pipe( 94 - map(() => state$.value.experiment.type), 97 + map(() => (state$.value.experiment.paradigm)), 95 98 map(loadTimeline), 96 99 map(setTimeline) 97 100 ); ··· 106 109 const writeStream = createEEGWriteStream( 107 110 state$.value.experiment.title, 108 111 state$.value.experiment.subject, 112 + state$.value.experiment.group, 109 113 state$.value.experiment.session 110 114 ); 111 115 ··· 131 135 payload.data, 132 136 state$.value.experiment.title, 133 137 state$.value.experiment.subject, 138 + state$.value.experiment.group, 134 139 state$.value.experiment.session 135 140 ) 136 141 ), ··· 157 162 const setSubjectEpic = action$ => 158 163 action$.ofType(SET_SUBJECT).pipe(map(updateSession)); 159 164 165 + const setGroupEpic = action$ => 166 + action$.ofType(SET_GROUP).pipe(map(updateSession)); 167 + 160 168 const updateSessionEpic = (action$, state$) => 161 169 action$.ofType(UPDATE_SESSION).pipe( 162 170 mergeMap(() => ··· 205 213 startEpic, 206 214 experimentStopEpic, 207 215 setSubjectEpic, 216 + setGroupEpic, 208 217 updateSessionEpic, 209 218 autoSaveEpic, 210 219 saveWorkspaceEpic,
+16
app/reducers/experimentReducer.js
··· 7 7 } from '../epics/experimentEpics'; 8 8 import { 9 9 SET_TYPE, 10 + SET_PARADIGM, 10 11 SET_SUBJECT, 12 + SET_GROUP, 11 13 SET_TITLE, 12 14 SET_EXPERIMENT_STATE, 13 15 SET_PARAMS, ··· 32 34 +timelines: {}; 33 35 +plugins: Object; 34 36 +subject: string; 37 + +group: string; 35 38 +session: number; 36 39 +isRunning: boolean; 37 40 +isEEGEnabled: boolean; ··· 47 50 timelines: {}, 48 51 plugins: {}, 49 52 subject: '', 53 + group: '', 50 54 session: 1, 51 55 isRunning: false, 52 56 isEEGEnabled: false, ··· 64 68 type: action.payload 65 69 }; 66 70 71 + case SET_PARADIGM: 72 + return { 73 + ...state, 74 + paradigm: action.payload 75 + }; 76 + 67 77 case SET_SUBJECT: 68 78 return { 69 79 ...state, 70 80 subject: action.payload 81 + }; 82 + 83 + case SET_GROUP: 84 + return { 85 + ...state, 86 + group: action.payload 71 87 }; 72 88 73 89 case SET_SESSION: