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.

Merge pull request #26 from makebrainwaves/customDesign

Custom design

authored by

Dano Morrison and committed by
GitHub
3a346a81 19be8d0a

+1096 -328
+4
app/actions/experimentActions.js
··· 8 8 export const SET_SUBJECT = "SET_SUBJECT"; 9 9 export const CREATE_NEW_WORKSPACE = "CREATE_NEW_WORKSPACE"; 10 10 export const SET_SESSION = "SET_SESSION"; 11 + export const SET_PARAMS = "SET_PARAMS"; 12 + export const SET_DESCRIPTION = "SET_DESCRIPTION"; 11 13 export const LOAD_DEFAULT_TIMELINE = "LOAD_DEFAULT_TIMELINE"; 12 14 export const SET_TITLE = "SET_TITLE"; 13 15 export const SAVE_WORKSPACE = "SAVE_WORKSPACE"; ··· 22 24 export const setType = payload => ({ payload, type: SET_TYPE }); 23 25 export const setSubject = payload => ({ payload, type: SET_SUBJECT }); 24 26 export const setSession = payload => ({ payload, type: SET_SESSION }); 27 + export const setParams = payload => ({ payload, type: SET_PARAMS }) 28 + export const setDescription = payload => ({payload, type: SET_DESCRIPTION }); 25 29 export const createNewWorkspace = payload => ({ payload, type: CREATE_NEW_WORKSPACE}); 26 30 export const loadDefaultTimeline = () => ({ type: LOAD_DEFAULT_TIMELINE }); 27 31 export const setTitle = payload => ({ payload, type: SET_TITLE });
+52 -7
app/app.global.css
··· 5 5 @import "~font-awesome/css/font-awesome.css"; 6 6 @import "~semantic-ui-css/semantic.min.css"; 7 7 @import "~jspsych-react/css/jspsych.css"; 8 + @import "~rc-slider/assets/index.css"; 8 9 9 10 body { 10 11 position: relative; ··· 12 13 background: rgba(255, 255, 255, 0.2); 13 14 font-family: Lato, Helvetica Neue, sans-serif; 14 15 overflow-y: hidden; 15 - } 16 - 17 - #root { 18 - margin-top: 15px; 19 16 } 20 17 21 18 h1 { ··· 58 55 max-width: 100% !important; 59 56 } 60 57 58 + button:active { 59 + transform: scale(0.95) !important; 60 + } 61 + 61 62 .jspsych-display-element { 62 63 width: 100% !important; 64 + } 65 + 66 + /* Semantic UI OVerrides */ 67 + 68 + .ui.form .field > label { 69 + font-size: 14px; 63 70 } 64 71 65 72 .ui.card { ··· 70 77 width: auto; 71 78 } 72 79 73 - /* Semantic UI OVerrides */ 74 - 75 80 .ui.modal > .image.content { 76 81 display: block; 77 82 } ··· 132 137 133 138 .ui.dimmer.inverted { 134 139 top: 0px !important; 135 - background-image: none !important; 136 140 } 137 141 138 142 .ui.modal > .header { ··· 159 163 width: calc(100% - 350px); 160 164 } 161 165 166 + .ui.selection.dropdown { 167 + min-width: 50px !important; 168 + } 169 + 162 170 /* Webview-related styles */ 163 171 164 172 #eegView { ··· 168 176 #eegView.hide { 169 177 visibility: hidden; 170 178 } 179 + 180 + /* rc-slider-related styles */ 181 + 182 + .rc-slider-with-marks { 183 + width: 150px; 184 + } 185 + 186 + .rc-slider-rail { 187 + background-color: #cccccc !important; 188 + } 189 + 190 + .rc-slider-track { 191 + background-color: #cccccc !important; 192 + } 193 + 194 + .rc-slider-dot { 195 + width: 10px; 196 + height: 10px; 197 + bottom: -3px; 198 + border-color: #ccc; 199 + background-color: #ccc; 200 + } 201 + 202 + .rc-slider-handle { 203 + margin-left: -16px; 204 + margin-top: -16px; 205 + box-sizing: border-box; 206 + height: 32px; 207 + width: 32px; 208 + border: 2px solid #007c70; 209 + background-color: #007c70; 210 + } 211 + 212 + .rc-slider-mark-text { 213 + font-size: 14px; 214 + margin-top: 10px; 215 + }
+37 -16
app/components/CollectComponent/ConnectModal.js
··· 12 12 import { 13 13 DEVICES, 14 14 DEVICE_AVAILABILITY, 15 - CONNECTION_STATUS 15 + CONNECTION_STATUS, 16 + SCREENS 16 17 } from '../../constants/constants'; 17 18 import styles from '../styles/collect.css'; 18 19 import blake from '../../assets/face_house/faces/Blake_3.jpg'; 19 20 20 21 interface Props { 22 + history: Object; 21 23 open: boolean; 22 24 connectedDevice: Object; 23 25 signalQualityObservable: ?any; ··· 30 32 31 33 interface State { 32 34 selectedDevice: ?any; 33 - tutorialProgress: number; 35 + instructionProgress: number; 34 36 } 35 37 38 + const INSTRUCTION_PROGRESS = { 39 + SEARCHING: 0, 40 + TURN_ON: 1, 41 + COMPUTER_CONNECTABILITY: 2 42 + }; 43 + 36 44 export default class ConnectModal extends Component<Props, State> { 37 45 handleConnect: () => void; 38 46 handleSearch: () => void; ··· 46 54 super(props); 47 55 this.state = { 48 56 selectedDevice: null, 49 - tutorialProgress: 0 57 + instructionProgress: INSTRUCTION_PROGRESS.SEARCHING 50 58 }; 51 59 this.handleSearch = debounce(this.handleSearch.bind(this), 300, { 52 60 leading: true, ··· 56 64 leading: true, 57 65 trailing: false 58 66 }); 59 - this.handleTutorialProgress = this.handleTutorialProgress.bind(this); 67 + this.handleinstructionProgress = this.handleinstructionProgress.bind(this); 60 68 } 61 69 62 70 componentWillUpdate(nextProps: Props) { ··· 64 72 nextProps.deviceAvailability === DEVICE_AVAILABILITY.NONE && 65 73 this.props.deviceAvailability === DEVICE_AVAILABILITY.SEARCHING 66 74 ) { 67 - this.setState({ tutorialProgress: 1 }); 75 + this.setState({ instructionProgress: 1 }); 68 76 } 69 77 if ( 70 78 nextProps.deviceAvailability === DEVICE_AVAILABILITY.AVAILABLE && 71 79 this.props.deviceAvailability === DEVICE_AVAILABILITY.NONE 72 80 ) { 73 - this.setState({ tutorialProgress: 0 }); 81 + this.setState({ instructionProgress: 0 }); 74 82 } 75 83 } 76 84 77 85 handleSearch() { 78 - this.setState({ tutorialProgress: 0 }); 86 + this.setState({ instructionProgress: 0 }); 79 87 this.props.deviceActions.setDeviceAvailability( 80 88 DEVICE_AVAILABILITY.SEARCHING 81 89 ); ··· 85 93 this.props.deviceActions.connectToDevice(this.state.selectedDevice); 86 94 } 87 95 88 - handleTutorialProgress(step: number) { 89 - this.setState({ tutorialProgress: step }); 96 + handleinstructionProgress(step: INSTRUCTION_PROGRESS) { 97 + if (step === 0) { 98 + this.props.history.push(SCREENS.DESIGN.route); 99 + } else { 100 + this.setState({ instructionProgress: step }); 101 + } 90 102 } 91 103 92 104 renderAvailableDeviceList() { ··· 143 155 </React.Fragment> 144 156 ); 145 157 } 146 - if (this.state.tutorialProgress === 1) { 158 + if (this.state.instructionProgress === INSTRUCTION_PROGRESS.TURN_ON) { 147 159 return ( 148 160 <React.Fragment> 149 161 <Modal.Header className={styles.connectHeader}> ··· 161 173 <Button 162 174 fluid 163 175 className={styles.secondaryButton} 164 - onClick={() => this.handleTutorialProgress(0)} 176 + onClick={() => this.handleinstructionProgress(0)} 165 177 > 166 178 Back 167 179 </Button> ··· 170 182 <Button 171 183 fluid 172 184 className={styles.primaryButton} 173 - onClick={() => this.handleTutorialProgress(2)} 185 + onClick={() => 186 + this.handleinstructionProgress( 187 + INSTRUCTION_PROGRESS.COMPUTER_CONNECTABILITY 188 + ) 189 + } 174 190 > 175 191 Next 176 192 </Button> ··· 180 196 </React.Fragment> 181 197 ); 182 198 } 183 - if (this.state.tutorialProgress === 2) { 199 + if ( 200 + this.state.instructionProgress === 201 + INSTRUCTION_PROGRESS.COMPUTER_CONNECTABILITY 202 + ) { 184 203 return ( 185 204 <React.Fragment> 186 205 <Modal.Header className={styles.connectHeader}> ··· 198 217 <Button 199 218 fluid 200 219 className={styles.secondaryButton} 201 - onClick={() => this.handleTutorialProgress(1)} 220 + onClick={() => 221 + this.handleinstructionProgress(INSTRUCTION_PROGRESS.TURN_ON) 222 + } 202 223 > 203 224 Back 204 225 </Button> ··· 234 255 <Button 235 256 fluid 236 257 className={styles.secondaryButton} 237 - onClick={() => this.handleTutorialProgress(1)} 258 + onClick={() => this.handleinstructionProgress(1)} 238 259 > 239 260 Back 240 261 </Button> ··· 255 276 <a 256 277 role="link" 257 278 tabIndex={0} 258 - onClick={() => this.handleTutorialProgress(1)} 279 + onClick={() => this.handleinstructionProgress(1)} 259 280 > 260 281 Don&#39;t see your device? 261 282 </a>
+12 -27
app/components/CollectComponent/PreTestComponent.js
··· 1 1 import React, { Component } from 'react'; 2 - import { isNil } from 'lodash'; 3 2 import { 4 3 Grid, 5 4 Segment, ··· 11 10 import ViewerComponent from '../ViewerComponent'; 12 11 import SignalQualityIndicatorComponent from '../SignalQualityIndicatorComponent'; 13 12 import PreviewExperimentComponent from '../PreviewExperimentComponent'; 13 + import PreviewButton from '../PreviewButtonComponent'; 14 14 import HelpSidebar from './HelpSidebar'; 15 15 import styles from '../styles/collect.css'; 16 16 import { ··· 28 28 deviceActions: Object; 29 29 experimentActions: Object; 30 30 availableDevices: Array<any>; 31 - type: ?EXPERIMENTS; 31 + type: EXPERIMENTS; 32 32 isRunning: boolean; 33 - params: ?ExperimentParameters; 34 - mainTimeline: ?MainTimeline; 35 - trials: ?{ [string]: Trial }; 36 - timelines: ?{}; 33 + params: ExperimentParameters; 34 + mainTimeline: MainTimeline; 35 + trials: { [string]: Trial }; 36 + timelines: {}; 37 37 subject: string; 38 38 session: number; 39 39 openRunComponent: () => void; ··· 60 60 } 61 61 62 62 handlePreview() { 63 - if (isNil(this.props.mainTimeline)) { 64 - this.props.experimentActions.loadDefaultTimeline(); 65 - } 66 63 this.setState({ isPreviewing: !this.state.isPreviewing }); 67 64 } 68 65 ··· 74 71 if (this.state.isPreviewing) { 75 72 return ( 76 73 <PreviewExperimentComponent 77 - isPreviewing={this.state.isPreviewing} 78 74 params={this.props.params} 79 75 mainTimeline={this.props.mainTimeline} 80 76 trials={this.props.trials} 81 77 timelines={this.props.timelines} 78 + isPreviewing={this.state.isPreviewing} 82 79 /> 83 80 ); 84 81 } ··· 112 109 ); 113 110 } 114 111 115 - renderPreviewButton() { 116 - if (!this.state.isPreviewing) { 117 - return ( 118 - <Button secondary onClick={this.handlePreview}> 119 - Preview Experiment 120 - </Button> 121 - ); 122 - } 123 - return ( 124 - <Button negative onClick={this.handlePreview}> 125 - Stop Preview 126 - </Button> 127 - ); 128 - } 129 - 130 112 renderHelpButton() { 131 113 if (!this.state.isSidebarVisible) { 132 114 return ( ··· 166 148 </Header> 167 149 </Grid.Column> 168 150 <Grid.Column floated="right"> 169 - {this.renderPreviewButton()} 151 + <PreviewButton 152 + isPreviewing={this.state.isPreviewing} 153 + onClick={this.handlePreview} 154 + /> 170 155 <Button 171 156 primary 172 157 disabled={ ··· 179 164 </Grid.Column> 180 165 </Grid.Row> 181 166 <Grid.Row> 182 - <Grid.Column stretched width={6}> 167 + <Grid.Column stretched width={6} className={styles.previewColumn}> 183 168 {this.renderSignalQualityOrPreview()} 184 169 </Grid.Column> 185 170 <Grid.Column width={10}>
+2
app/components/CollectComponent/index.js
··· 17 17 import RunComponent from './RunComponent'; 18 18 19 19 interface Props { 20 + history: Object; 20 21 experimentActions: Object; 21 22 connectedDevice: Object; 22 23 signalQualityObservable: ?any; ··· 109 110 return ( 110 111 <div> 111 112 <ConnectModal 113 + history={this.props.history} 112 114 open={this.state.isConnectModalOpen} 113 115 onClose={this.handleConnectModalClose} 114 116 connectedDevice={this.props.connectedDevice}
+331
app/components/DesignComponent/CustomDesignComponent.js
··· 1 + // @flow 2 + import React, { Component } from 'react'; 3 + import { Grid, Button, Segment, Header, Form } from 'semantic-ui-react'; 4 + import { isNil } from 'lodash'; 5 + import styles from '../styles/common.css'; 6 + import { EXPERIMENTS, SCREENS } from '../../constants/constants'; 7 + import { 8 + MainTimeline, 9 + Trial, 10 + ExperimentParameters, 11 + ExperimentDescription 12 + } from '../../constants/interfaces'; 13 + import SecondaryNavComponent from '../SecondaryNavComponent'; 14 + import PreviewExperimentComponent from '../PreviewExperimentComponent'; 15 + import StimuliDesignColumn from './StimuliDesignColumn'; 16 + import ParamSlider from './ParamSlider'; 17 + import PreviewButton from '../PreviewButtonComponent'; 18 + 19 + const CUSTOM_STEPS = { 20 + OVERVIEW: 'OVERVIEW', 21 + STIMULI: 'STIMULI', 22 + PARAMETERS: 'PARAMETERS', 23 + PREVIEW: 'PREVIEW' 24 + }; 25 + 26 + const FIELDS = { 27 + QUESTION: 'Research Question', 28 + HYPOTHESIS: 'Hypothesis', 29 + METHODS: 'Methods', 30 + INTRO: 'Experiment Instructions' 31 + }; 32 + 33 + interface Props { 34 + history: Object; 35 + type: ?EXPERIMENTS; 36 + title: string; 37 + params: ExperimentParameters; 38 + mainTimeline: MainTimeline; 39 + trials: { [string]: Trial }; 40 + timelines: {}; 41 + experimentActions: Object; 42 + description: ExperimentDescription; 43 + } 44 + 45 + interface State { 46 + activeStep: string; 47 + isPreviewing: boolean; 48 + description: ExperimentDescription; 49 + params: ExperimentParameters; 50 + } 51 + 52 + export default class CustomDesign extends Component<Props, State> { 53 + props: Props; 54 + state: State; 55 + handleStepClick: (Object, Object) => void; 56 + handleStartExperiment: Object => void; 57 + handlePreview: () => void; 58 + handleSaveParams: () => void; 59 + 60 + constructor(props: Props) { 61 + super(props); 62 + this.state = { 63 + activeStep: CUSTOM_STEPS.OVERVIEW, 64 + isPreviewing: false, 65 + description: props.description, 66 + params: props.params 67 + }; 68 + this.handleStepClick = this.handleStepClick.bind(this); 69 + this.handleStartExperiment = this.handleStartExperiment.bind(this); 70 + this.handlePreview = this.handlePreview.bind(this); 71 + this.handleSaveParams = this.handleSaveParams.bind(this); 72 + if (isNil(props.params)) { 73 + props.experimentActions.loadDefaultTimeline(); 74 + } 75 + } 76 + 77 + handleStepClick(step: string) { 78 + this.setState({ activeStep: step }); 79 + } 80 + 81 + handleStartExperiment() { 82 + this.props.history.push(SCREENS.COLLECT.route); 83 + } 84 + 85 + handlePreview() { 86 + this.setState({ isPreviewing: !this.state.isPreviewing }); 87 + } 88 + 89 + handleSaveParams() { 90 + this.props.experimentActions.setParams(this.state.params); 91 + this.props.experimentActions.setDescription(this.state.description); 92 + } 93 + 94 + renderSectionContent() { 95 + switch (this.state.activeStep) { 96 + case CUSTOM_STEPS.STIMULI: 97 + return ( 98 + <Grid 99 + stretched 100 + padded 101 + relaxed="very" 102 + columns="equal" 103 + className={styles.contentGrid} 104 + > 105 + <StimuliDesignColumn 106 + num={1} 107 + {...this.state.params.stimulus1} 108 + onChange={(key, data) => 109 + this.setState({ 110 + params: { 111 + ...this.state.params, 112 + stimulus1: { ...this.state.params.stimulus1, [key]: data } 113 + } 114 + }) 115 + } 116 + /> 117 + <StimuliDesignColumn 118 + num={2} 119 + {...this.state.params.stimulus2} 120 + onChange={(key, data) => 121 + this.setState({ 122 + params: { 123 + ...this.state.params, 124 + stimulus2: { ...this.state.params.stimulus2, [key]: data } 125 + } 126 + }) 127 + } 128 + /> 129 + </Grid> 130 + ); 131 + case CUSTOM_STEPS.PARAMETERS: 132 + return ( 133 + <Grid 134 + stretched 135 + padded 136 + relaxed="very" 137 + columns="equal" 138 + className={styles.contentGrid} 139 + > 140 + <Grid.Column stretched verticalAlign="middle"> 141 + <Segment basic> 142 + <Header as="h1">Trial Duration</Header> 143 + <p> 144 + Select the trial duration. This determines the amount of time 145 + each image will be displayed during the experiment. 146 + </p> 147 + </Segment> 148 + <Segment basic> 149 + <ParamSlider 150 + label="Trial Duration (seconds)" 151 + value={this.state.params.trialDuration} 152 + onChange={value => 153 + this.setState({ 154 + params: { ...this.state.params, trialDuration: value } 155 + }) 156 + } 157 + /> 158 + </Segment> 159 + </Grid.Column> 160 + <Grid.Column stretched verticalAlign="middle"> 161 + <Segment basic> 162 + <Header as="h1">ITI Duration</Header> 163 + <p> 164 + Select the intertrial interval duration. This is the amount of 165 + time between trials measured from the end of one trial to the 166 + start of the next one. 167 + </p> 168 + </Segment> 169 + <Segment basic> 170 + <ParamSlider 171 + label="ITI Duration (seconds)" 172 + value={this.state.params.iti} 173 + onChange={value => 174 + this.setState({ 175 + params: { ...this.state.params, iti: value } 176 + }) 177 + } 178 + /> 179 + </Segment> 180 + </Grid.Column> 181 + </Grid> 182 + ); 183 + case CUSTOM_STEPS.PREVIEW: 184 + return ( 185 + <Grid relaxed padded className={styles.contentGrid}> 186 + <Grid.Column 187 + stretched 188 + width={6} 189 + textAlign="right" 190 + verticalAlign="top" 191 + className={styles.jsPsychColumn} 192 + > 193 + <PreviewExperimentComponent 194 + params={this.state.params} 195 + mainTimeline={this.props.mainTimeline} 196 + trials={this.props.trials} 197 + timelines={this.props.timelines} 198 + isPreviewing={this.state.isPreviewing} 199 + /> 200 + </Grid.Column> 201 + <Grid.Column width={6} verticalAlign="middle"> 202 + <Segment basic> 203 + <Form> 204 + <Form.TextArea 205 + autoHeight 206 + style={{ minHeight: 100 }} 207 + label={FIELDS.INTRO} 208 + value={this.state.params.intro} 209 + placeholder="You will view a series of images..." 210 + onChange={(event, data) => 211 + this.setState({ 212 + params: { ...this.state.params, intro: data.value } 213 + }) 214 + } 215 + /> 216 + </Form> 217 + </Segment> 218 + <PreviewButton 219 + isPreviewing={this.state.isPreviewing} 220 + onClick={this.handlePreview} 221 + /> 222 + </Grid.Column> 223 + </Grid> 224 + ); 225 + 226 + case CUSTOM_STEPS.OVERVIEW: 227 + default: 228 + return ( 229 + <Grid 230 + stretched 231 + relaxed 232 + padded 233 + columns="equal" 234 + className={styles.contentGrid} 235 + > 236 + <Grid.Column stretched verticalAlign="middle"> 237 + <Form> 238 + <Form.TextArea 239 + autoHeight 240 + style={{ minHeight: 100 }} 241 + label={FIELDS.QUESTION} 242 + value={this.state.description.question} 243 + placeholder="Explain your research question here." 244 + onChange={(event, data) => 245 + this.setState({ 246 + description: { 247 + ...this.state.description, 248 + question: data.value 249 + } 250 + }) 251 + } 252 + /> 253 + </Form> 254 + </Grid.Column> 255 + <Grid.Column stretched verticalAlign="middle"> 256 + <Form> 257 + <Form.TextArea 258 + autoHeight 259 + style={{ minHeight: 100 }} 260 + label={FIELDS.HYPOTHESIS} 261 + value={this.state.description.hypothesis} 262 + placeholder="Describe your hypothesis here." 263 + onChange={(event, data) => 264 + this.setState({ 265 + description: { 266 + ...this.state.description, 267 + hypothesis: data.value 268 + } 269 + }) 270 + } 271 + /> 272 + </Form> 273 + </Grid.Column> 274 + <Grid.Column stretched verticalAlign="middle"> 275 + <Form> 276 + <Form.TextArea 277 + autoHeight 278 + style={{ minHeight: 100 }} 279 + label={FIELDS.METHODS} 280 + value={this.state.description.methods} 281 + placeholder="Explain how you will design your experiment to answer the question here." 282 + onChange={(event, data) => 283 + this.setState({ 284 + description: { 285 + ...this.state.description, 286 + methods: data.value 287 + } 288 + }) 289 + } 290 + /> 291 + </Form> 292 + </Grid.Column> 293 + </Grid> 294 + ); 295 + } 296 + } 297 + 298 + render() { 299 + return ( 300 + <div className={styles.mainContainer}> 301 + <SecondaryNavComponent 302 + title="Experiment Design" 303 + steps={CUSTOM_STEPS} 304 + activeStep={this.state.activeStep} 305 + onStepClick={this.handleStepClick} 306 + button={ 307 + <div> 308 + <Button 309 + compact 310 + size="small" 311 + secondary 312 + onClick={this.handleSaveParams} 313 + > 314 + Save Experiment 315 + </Button> 316 + <Button 317 + compact 318 + size="small" 319 + primary 320 + onClick={this.handleStartExperiment} 321 + > 322 + Collect Data 323 + </Button> 324 + </div> 325 + } 326 + /> 327 + {this.renderSectionContent()} 328 + </div> 329 + ); 330 + } 331 + }
+41
app/components/DesignComponent/ParamSlider.js
··· 1 + import { Segment } from "semantic-ui-react"; 2 + import React, { PureComponent } from "react"; 3 + import Slider from "rc-slider"; 4 + import styles from "../styles/common.css"; 5 + 6 + interface Props { 7 + value: number; 8 + label: string; 9 + onChange: number => void; 10 + } 11 + 12 + const marks = { 13 + 1: "0.5", 14 + 2: "1", 15 + 3: "1.5", 16 + 4: "2" 17 + }; 18 + 19 + // Converts from a 1-4 scale to a range from 500 to 2000 ms 20 + const MS_CONVERSION = 500; 21 + 22 + export default class ParamSlider extends PureComponent<Props> { 23 + render() { 24 + return ( 25 + <div> 26 + <p className={styles.label}>{this.props.label}</p> 27 + <Segment basic> 28 + <Slider 29 + dots 30 + marks={marks} 31 + min={1} 32 + max={4} 33 + value={this.props.value / MS_CONVERSION} 34 + onChange={value => this.props.onChange(value * MS_CONVERSION)} 35 + defaultValue={1} 36 + /> 37 + </Segment> 38 + </div> 39 + ); 40 + } 41 + }
+97
app/components/DesignComponent/StimuliDesignColumn.js
··· 1 + /* Breaking this component on its own is done mainly to increase performance. Text input is slow otherwise */ 2 + 3 + import React, { Component } from 'react'; 4 + import { Grid, Segment, Header, Form } from 'semantic-ui-react'; 5 + import { loadFromSystemDialog } from '../../utils/filesystem/select'; 6 + import { FILE_TYPES } from '../../constants/constants'; 7 + 8 + interface Props { 9 + num: number; 10 + title: string; 11 + response: number; 12 + dir: string; 13 + onChange: (string, string) => void; 14 + } 15 + 16 + const RESPONSE_OPTIONS = new Array(10) 17 + .fill(0) 18 + .map((_, i) => ({ key: i, text: i, value: i })); 19 + 20 + export default class StimuliDesignColumn extends Component<Props> { 21 + constructor(props: Props) { 22 + super(props); 23 + this.handleSelectFolder = this.handleSelectFolder.bind(this); 24 + } 25 + 26 + shouldComponentUpdate(nextProps) { 27 + if ( 28 + nextProps.title !== this.props.title || 29 + nextProps.response !== this.props.response || 30 + nextProps.dir !== this.props.dir 31 + ) { 32 + return true; 33 + } 34 + return false; 35 + } 36 + 37 + async handleSelectFolder() { 38 + const dir = await loadFromSystemDialog(FILE_TYPES.STIMULUS_DIR); 39 + this.props.onChange('dir', dir); 40 + } 41 + 42 + render() { 43 + return ( 44 + <Grid.Column stretched verticalAlign="middle"> 45 + <Segment basic> 46 + <Header as="h1">Stimuli {this.props.num}</Header> 47 + <p> 48 + Give your stimuli group a title, select the location of your images, 49 + and choose the correct key response 50 + </p> 51 + </Segment> 52 + <Segment basic> 53 + <Form> 54 + <Form.Group> 55 + <Form.Input 56 + width={10} 57 + label="Title" 58 + value={this.props.title} 59 + onChange={(event, data) => 60 + this.props.onChange('title', data.value) 61 + } 62 + placeholder="e.g. Faces" 63 + /> 64 + <Form.Dropdown 65 + selection 66 + width={4} 67 + label="Correct Response" 68 + value={this.props.response} 69 + onChange={(event, data) => 70 + this.props.onChange('response', data.value) 71 + } 72 + placeholder="Response" 73 + options={RESPONSE_OPTIONS} 74 + /> 75 + </Form.Group> 76 + <Grid> 77 + <Grid.Column width={6}> 78 + <Form.Button 79 + secondary 80 + label="Location" 81 + onClick={this.handleSelectFolder} 82 + > 83 + Select Folder 84 + </Form.Button> 85 + </Grid.Column> 86 + <Grid.Column verticalAlign="bottom" floated="left" width={4}> 87 + <Segment basic compact> 88 + <em>{this.props.dir}</em> 89 + </Segment> 90 + </Grid.Column> 91 + </Grid> 92 + </Form> 93 + </Segment> 94 + </Grid.Column> 95 + ); 96 + } 97 + }
+18 -25
app/components/DesignComponent/index.js
··· 7 7 import { 8 8 MainTimeline, 9 9 Trial, 10 - ExperimentParameters 10 + ExperimentParameters, 11 + ExperimentDescription 11 12 } from '../../constants/interfaces'; 12 13 import SecondaryNavComponent from '../SecondaryNavComponent'; 13 14 import PreviewExperimentComponent from '../PreviewExperimentComponent'; 15 + import CustomDesign from './CustomDesignComponent'; 16 + import PreviewButton from '../PreviewButtonComponent'; 14 17 15 18 const DESIGN_STEPS = { 16 19 OVERVIEW: 'OVERVIEW', ··· 20 23 21 24 interface Props { 22 25 history: Object; 23 - type: ?EXPERIMENTS; 26 + type: EXPERIMENTS; 24 27 title: string; 25 - params: ?ExperimentParameters; 26 - mainTimeline: ?MainTimeline; 27 - trials: ?{ [string]: Trial }; 28 - timelines: ?{}; 28 + params: ExperimentParameters; 29 + mainTimeline: MainTimeline; 30 + trials: { [string]: Trial }; 31 + timelines: {}; 29 32 experimentActions: Object; 33 + description: ExperimentDescription; 30 34 } 31 35 32 36 interface State { ··· 64 68 } 65 69 66 70 handlePreview() { 67 - if (isNil(this.props.mainTimeline)) { 68 - this.props.experimentActions.loadDefaultTimeline(); 69 - } 70 71 this.setState({ isPreviewing: !this.state.isPreviewing }); 71 72 } 72 73 73 - renderPreviewButton() { 74 - if (!this.state.isPreviewing) { 75 - return ( 76 - <Button secondary onClick={this.handlePreview}> 77 - Preview Experiment 78 - </Button> 79 - ); 80 - } 81 - return ( 82 - <Button negative onClick={this.handlePreview}> 83 - Stop Preview 84 - </Button> 85 - ); 86 - } 87 - 88 74 renderSectionContent() { 89 75 switch (this.state.activeStep) { 90 76 case DESIGN_STEPS.BACKGROUND: ··· 125 111 width={6} 126 112 textAlign="right" 127 113 verticalAlign="middle" 114 + className={styles.jsPsychColumn} 128 115 > 129 116 <PreviewExperimentComponent 130 117 params={this.props.params} ··· 142 129 <Segment as="p" basic> 143 130 Subjects will mentally note which stimulus they are perceiving 144 131 </Segment> 145 - <Segment basic>{this.renderPreviewButton()}</Segment> 132 + <PreviewButton 133 + isPreviewing={this.state.isPreviewing} 134 + onClick={this.handlePreview} 135 + /> 146 136 </Grid.Column> 147 137 </Grid> 148 138 ); ··· 172 162 } 173 163 174 164 render() { 165 + if (this.props.type === EXPERIMENTS.CUSTOM) { 166 + return <CustomDesign {...this.props} />; 167 + } 175 168 return ( 176 169 <div className={styles.mainContainer}> 177 170 <SecondaryNavComponent
+11 -19
app/components/HomeComponent/OverviewComponent.js
··· 4 4 import { EXPERIMENTS } from '../../constants/constants'; 5 5 import SecondaryNavComponent from '../SecondaryNavComponent'; 6 6 import PreviewExperimentComponent from '../PreviewExperimentComponent'; 7 + import PreviewButton from '../PreviewButtonComponent'; 7 8 import { loadTimeline } from '../../utils/jspsych/functions'; 8 9 9 10 const OVERVIEW_STEPS = { ··· 44 45 this.setState({ isPreviewing: !this.state.isPreviewing }); 45 46 } 46 47 47 - renderPreviewButton() { 48 - if (!this.state.isPreviewing) { 49 - return ( 50 - <Button secondary onClick={this.handlePreview}> 51 - Preview Experiment 52 - </Button> 53 - ); 54 - } 55 - return ( 56 - <Button negative onClick={this.handlePreview}> 57 - Stop Preview 58 - </Button> 59 - ); 60 - } 61 - 62 48 renderSectionContent() { 63 49 switch (this.state.activeStep) { 64 50 case OVERVIEW_STEPS.PROTOCOL: ··· 69 55 width={6} 70 56 textAlign="right" 71 57 verticalAlign="middle" 58 + className={styles.jsPsychColumn} 72 59 > 73 60 <PreviewExperimentComponent 74 61 {...loadTimeline(this.props.type)} ··· 83 70 <Segment as="p" basic> 84 71 Subjects will mentally note which stimulus they are perceiving 85 72 </Segment> 86 - <Segment basic>{this.renderPreviewButton()}</Segment> 73 + <PreviewButton 74 + isPreviewing={this.state.isPreviewing} 75 + onClick={this.handlePreview} 76 + /> 87 77 </Grid.Column> 88 78 </Grid> 89 79 ); ··· 144 134 145 135 render() { 146 136 return ( 147 - <div> 137 + <React.Fragment> 148 138 <Button 149 139 basic 150 140 circular ··· 168 158 </Button> 169 159 } 170 160 /> 171 - {this.renderSectionContent()} 172 - </div> 161 + <div className={styles.homeContentContainer}> 162 + {this.renderSectionContent()} 163 + </div> 164 + </React.Fragment> 173 165 ); 174 166 } 175 167 }
+3 -2
app/components/HomeComponent/index.js
··· 186 186 Review 187 187 </Button> 188 188 <Button 189 - disabled 190 189 primary 191 190 onClick={() => this.handleNewExperiment(EXPERIMENTS.CUSTOM)} 192 191 > ··· 219 218 activeStep={this.state.activeStep} 220 219 onStepClick={this.handleStepClick} 221 220 /> 222 - {this.renderSectionContent()} 221 + <div className={styles.homeContentContainer}> 222 + {this.renderSectionContent()} 223 + </div> 223 224 </React.Fragment> 224 225 ); 225 226 }
+26
app/components/PreviewButtonComponent.js
··· 1 + import React, { PureComponent } from 'react'; 2 + import { Button, Segment } from 'semantic-ui-react'; 3 + 4 + interface Props { 5 + isPreviewing: boolean; 6 + onClick: () => void; 7 + } 8 + 9 + export default class PreviewButton extends PureComponent<Props> { 10 + render() { 11 + if (!this.props.isPreviewing) { 12 + return ( 13 + <Button secondary onClick={this.props.onClick}> 14 + Preview Experiment 15 + </Button> 16 + ); 17 + } 18 + return ( 19 + <Segment basic> 20 + <Button negative onClick={this.props.onClick}> 21 + Stop Preview 22 + </Button> 23 + </Segment> 24 + ); 25 + } 26 + }
+1 -1
app/components/PreviewExperimentComponent.js
··· 52 52 } 53 53 54 54 // async handleCustomExperimentLoad() { 55 - // const timelinePath = await loadFileFromSystemDialog(FILE_TYPES.TIMELINE); 55 + // const timelinePath = await loadFromSystemDialog(FILE_TYPES.TIMELINE); 56 56 // } 57 57 58 58 render() {
+5 -1
app/components/SecondaryNavComponent/index.js
··· 12 12 } 13 13 14 14 export default class SecondaryNavComponent extends Component<Props> { 15 + shouldComponentUpdate(nextProps) { 16 + return nextProps.activeStep !== this.props.activeStep; 17 + } 18 + 15 19 renderTitle() { 16 20 if (typeof this.props.title === 'string') { 17 21 return <Header as="h2">{this.props.title}</Header>; ··· 35 39 /> 36 40 ))} 37 41 {this.props.button ? ( 38 - <Grid.Column width={4} floated="right"> 42 + <Grid.Column width="5" floated="right"> 39 43 {this.props.button} 40 44 </Grid.Column> 41 45 ) : null}
+3 -1
app/components/TopNavComponent/index.js
··· 46 46 verticalAlign="middle" 47 47 > 48 48 <Grid.Column width="3" className={styles.experimentTitleSegment}> 49 - <NavLink to={SCREENS.HOME.route}>{this.props.title}</NavLink> 49 + <NavLink to={SCREENS.HOME.route}> 50 + {this.props.title ? this.props.title : 'Untitled'} 51 + </NavLink> 50 52 </Grid.Column> 51 53 <PrimaryNavSegment 52 54 {...SCREENS.DESIGN}
+4
app/components/styles/collect.css
··· 30 30 padding: 3% !important; 31 31 } 32 32 33 + .previewColumn { 34 + height: 100%; 35 + } 36 + 33 37 .primaryButton { 34 38 font-size: 18px !important; 35 39 font-weight: normal !important;
+22 -3
app/components/styles/common.css
··· 1 + .homeContentContainer { 2 + padding-top: 50px !important; 3 + height: 100%; 4 + } 5 + 1 6 .mainContainer { 2 7 padding: 3%; 3 8 height: 100vh; ··· 17 22 } 18 23 19 24 .contentGrid { 20 - height: 50%; 25 + height: 75%; 21 26 } 22 27 23 28 .inputDiv { 24 29 margin: 15px; 30 + } 31 + 32 + .label { 33 + font-size: 14px !important; 34 + font-weight: bold; 35 + line-height: 17px; 25 36 } 26 37 27 38 .brainwavesLogo { ··· 31 42 } 32 43 33 44 .inputModal { 34 - color: #1a1a1a !important; 45 + color: #fff !important; 35 46 font-size: 18px !important; 36 47 width: 25% !important; 37 48 text-align: center !important; ··· 40 51 .descriptionContainer p { 41 52 margin-top: 50px; 42 53 margin-bottom: 50px; 43 - min-height: 125px; 54 + min-height: 150px; 44 55 } 45 56 46 57 .descriptionContainer button { ··· 51 62 border: none !important; 52 63 box-shadow: none !important; 53 64 } 65 + 66 + .jsPsychColumn { 67 + height: 100%; 68 + } 69 + 70 + .paramSlider { 71 + width: 200px; 72 + }
-4
app/components/styles/secondarynav.css
··· 1 1 /* Secondary Nav related styles */ 2 2 3 - .secondaryNavContainer { 4 - margin-bottom: 50px !important; 5 - } 6 - 7 3 .secondaryNavSegment { 8 4 display: flex !important; 9 5 justify-content: center;
+1
app/components/styles/topnavbar.css
··· 1 1 .navContainer { 2 + margin-top: 0px !important; 2 3 position: relative; 3 4 z-index: 999; 4 5 height: 60px;
+1
app/constants/constants.js
··· 123 123 }; 124 124 125 125 export const FILE_TYPES = { 126 + STIMULUS_DIR: 'STIMULUS_DIR', 126 127 TIMELINE: 'TIMELINE' 127 128 };
+24 -17
app/constants/interfaces.js
··· 11 11 12 12 // TODO: Write interface for jsPsych plugins 13 13 14 - export interface ExperimentParameters { 15 - trialDuration?: number; 16 - experimentDuration?: number; 17 - iti?: number; 18 - jitter?: number; 19 - sampleType?: string; 20 - pluginName?: string; 21 - stimulus1?: { dir: string, type: EVENTS }; 22 - stimulus2?: { dir: string, type: EVENTS }; 23 - } 14 + export type ExperimentParameters = { 15 + trialDuration: number, 16 + experimentDuration: number, 17 + iti: number, 18 + jitter: number, 19 + sampleType: string, 20 + pluginName: string, 21 + intro: string, 22 + stimulus1: { dir: string, type: EVENTS, title: string, response: number }, 23 + stimulus2: { dir: string, type: EVENTS, title: string, response: number } 24 + }; 25 + 26 + export type ExperimentDescription = { 27 + question: string, 28 + hypothesis: string, 29 + methods: string 30 + }; 24 31 25 32 // Array of timeline and trial ids that will be presented in experiment 26 33 export type MainTimeline = Array<string>; ··· 29 36 export interface Trial { 30 37 id: string; 31 38 type: string; 32 - stimulus: string | StimulusVariable; 39 + stimulus?: string | StimulusVariable; 33 40 trial_duration?: number | (() => number); 34 41 post_trial_gap?: number; 35 42 on_load?: string => void | StimulusVariable; ··· 37 44 } 38 45 39 46 // Timeline of jsPsych trials 40 - export interface Timeline { 41 - id: string; 42 - timeline: Array<Trial>; 43 - sample?: SampleParameter; 44 - timeline_variables?: Array<Object>; 45 - } 47 + export type Timeline = { 48 + id: string, 49 + timeline: Array<Trial>, 50 + sample?: SampleParameter, 51 + timeline_variables?: Array<Object> 52 + }; 46 53 47 54 export interface SampleParameter { 48 55 type: string;
+2 -2
app/main.dev.js
··· 11 11 * @flow 12 12 */ 13 13 import { app, BrowserWindow, ipcMain } from "electron"; 14 - import { loadFile } from "./utils/filesystem/dialog"; 14 + import { loadDialog } from "./utils/filesystem/dialog"; 15 15 import MenuBuilder from "./menu"; 16 16 17 17 app.commandLine.appendSwitch("enable-experimental-web-platform-features", true); ··· 71 71 }); 72 72 73 73 // IPC Listener for file dialog events 74 - ipcMain.on("loadFile", loadFile); 74 + ipcMain.on("loadDialog", loadDialog); 75 75 76 76 mainWindow.loadURL(`file://${__dirname}/app.html`); 77 77
+157 -157
app/package-lock.json
··· 9 9 "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.0.0.tgz", 10 10 "integrity": "sha512-Yww0jXgolNtkhcK+Txo5JN+DjBpNmmAtD7G99HOebhEjBzjnACG09Tip9C8lSOF6PrhA56OeJWeOZduNJaKxBA==", 11 11 "requires": { 12 - "core-js": "^2.5.7", 13 - "regenerator-runtime": "^0.12.0" 12 + "core-js": "2.5.7", 13 + "regenerator-runtime": "0.12.1" 14 14 }, 15 15 "dependencies": { 16 16 "regenerator-runtime": { ··· 25 25 "resolved": "https://registry.npmjs.org/@neurosity/pipes/-/pipes-3.2.3.tgz", 26 26 "integrity": "sha512-aDa/iTe7OUbwgFFnRgGcB/hTgBdkr21E5vQbpOXZpByQijB4czGDgXJfFtI3zuLKST3bZuiQ7/ZPBA3+P8GENA==", 27 27 "requires": { 28 - "dsp.js": "^1.0.1", 29 - "fili": "^2.0.1", 30 - "rxjs": "^6.3.1" 28 + "dsp.js": "1.0.1", 29 + "fili": "2.0.1", 30 + "rxjs": "6.3.2" 31 31 } 32 32 }, 33 33 "abbrev": { ··· 98 98 "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 99 99 "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 100 100 "requires": { 101 - "delegates": "^1.0.0", 102 - "readable-stream": "^2.0.6" 101 + "delegates": "1.0.0", 102 + "readable-stream": "2.3.6" 103 103 } 104 104 }, 105 105 "async": { ··· 112 112 "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 113 113 "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 114 114 "requires": { 115 - "core-js": "^2.4.0", 116 - "regenerator-runtime": "^0.11.0" 115 + "core-js": "2.5.7", 116 + "regenerator-runtime": "0.11.1" 117 117 } 118 118 }, 119 119 "balanced-match": { ··· 126 126 "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", 127 127 "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", 128 128 "requires": { 129 - "readable-stream": "^2.3.5", 130 - "safe-buffer": "^5.1.1" 129 + "readable-stream": "2.3.6", 130 + "safe-buffer": "5.1.2" 131 131 } 132 132 }, 133 133 "bleat": { ··· 135 135 "resolved": "https://registry.npmjs.org/bleat/-/bleat-0.1.8.tgz", 136 136 "integrity": "sha1-ANZuUGZiXna/mBR9/mPHWZX4L1A=", 137 137 "requires": { 138 - "noble": "^1.3.0" 138 + "noble": "1.9.1" 139 139 } 140 140 }, 141 141 "boxen": { ··· 226 226 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 227 227 "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 228 228 "requires": { 229 - "balanced-match": "^1.0.0", 229 + "balanced-match": "1.0.0", 230 230 "concat-map": "0.0.1" 231 231 } 232 232 }, ··· 235 235 "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", 236 236 "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", 237 237 "requires": { 238 - "buffer-alloc-unsafe": "^1.1.0", 239 - "buffer-fill": "^1.0.0" 238 + "buffer-alloc-unsafe": "1.1.0", 239 + "buffer-fill": "1.0.0" 240 240 } 241 241 }, 242 242 "buffer-alloc-unsafe": { ··· 389 389 "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", 390 390 "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", 391 391 "requires": { 392 - "mimic-response": "^1.0.0" 392 + "mimic-response": "1.0.1" 393 393 } 394 394 }, 395 395 "deep-extend": { ··· 432 432 "resolved": "https://registry.npmjs.org/enchannel-zmq-backend/-/enchannel-zmq-backend-6.3.2.tgz", 433 433 "integrity": "sha512-E11vfIfeApmCOr4FW+ozLxP8VhxfLaCT2h0AUWFONrYECJ4f+X8UfOo+VuTlkrIxlrEoilpwtKXCT4tBh/KXRA==", 434 434 "requires": { 435 - "babel-runtime": "^6.26.0", 436 - "jmp": "^1.0.0", 437 - "rxjs": "^5.5.6", 438 - "uuid": "^3.1.0" 435 + "babel-runtime": "6.26.0", 436 + "jmp": "1.0.0", 437 + "rxjs": "5.5.12", 438 + "uuid": "3.3.2" 439 439 }, 440 440 "dependencies": { 441 441 "rxjs": { ··· 453 453 "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", 454 454 "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", 455 455 "requires": { 456 - "once": "^1.4.0" 456 + "once": "1.4.0" 457 457 } 458 458 }, 459 459 "escape-string-regexp": { ··· 519 519 "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", 520 520 "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", 521 521 "requires": { 522 - "minipass": "^2.2.1" 522 + "minipass": "2.3.4" 523 523 } 524 524 }, 525 525 "fs.realpath": { ··· 532 532 "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 533 533 "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 534 534 "requires": { 535 - "aproba": "^1.0.3", 536 - "console-control-strings": "^1.0.0", 537 - "has-unicode": "^2.0.0", 538 - "object-assign": "^4.1.0", 539 - "signal-exit": "^3.0.0", 540 - "string-width": "^1.0.1", 541 - "strip-ansi": "^3.0.1", 542 - "wide-align": "^1.1.0" 535 + "aproba": "1.2.0", 536 + "console-control-strings": "1.1.0", 537 + "has-unicode": "2.0.1", 538 + "object-assign": "4.1.1", 539 + "signal-exit": "3.0.2", 540 + "string-width": "1.0.2", 541 + "strip-ansi": "3.0.1", 542 + "wide-align": "1.1.3" 543 543 } 544 544 }, 545 545 "get-stream": { ··· 558 558 "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 559 559 "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 560 560 "requires": { 561 - "fs.realpath": "^1.0.0", 562 - "inflight": "^1.0.4", 563 - "inherits": "2", 564 - "minimatch": "^3.0.4", 565 - "once": "^1.3.0", 566 - "path-is-absolute": "^1.0.0" 561 + "fs.realpath": "1.0.0", 562 + "inflight": "1.0.6", 563 + "inherits": "2.0.3", 564 + "minimatch": "3.0.4", 565 + "once": "1.4.0", 566 + "path-is-absolute": "1.0.1" 567 567 } 568 568 }, 569 569 "global-dirs": { ··· 629 629 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 630 630 "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 631 631 "requires": { 632 - "safer-buffer": ">= 2.1.2 < 3" 632 + "safer-buffer": "2.1.2" 633 633 } 634 634 }, 635 635 "ignore-walk": { ··· 637 637 "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", 638 638 "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", 639 639 "requires": { 640 - "minimatch": "^3.0.4" 640 + "minimatch": "3.0.4" 641 641 } 642 642 }, 643 643 "import-lazy": { ··· 657 657 "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 658 658 "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 659 659 "requires": { 660 - "once": "^1.3.0", 661 - "wrappy": "1" 660 + "once": "1.4.0", 661 + "wrappy": "1.0.2" 662 662 } 663 663 }, 664 664 "inherits": { ··· 685 685 "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 686 686 "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 687 687 "requires": { 688 - "number-is-nan": "^1.0.0" 688 + "number-is-nan": "1.0.1" 689 689 } 690 690 }, 691 691 "is-installed-globally": { ··· 753 753 "resolved": "https://registry.npmjs.org/jmp/-/jmp-1.0.0.tgz", 754 754 "integrity": "sha1-3khvrkVCJr1WEVqgIArqPCK3Amk=", 755 755 "requires": { 756 - "uuid": "^3.0.1", 757 - "zeromq": "^4.0.0" 756 + "uuid": "3.3.2", 757 + "zeromq": "4.6.0" 758 758 } 759 759 }, 760 760 "jsonfile": { ··· 762 762 "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", 763 763 "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", 764 764 "requires": { 765 - "graceful-fs": "^4.1.6" 765 + "graceful-fs": "4.1.11" 766 766 } 767 767 }, 768 768 "jupyter-paths": { ··· 770 770 "resolved": "https://registry.npmjs.org/jupyter-paths/-/jupyter-paths-2.0.0.tgz", 771 771 "integrity": "sha512-ZbilXKaJbzGmMDYY/ShC3JNC+/iqFhKaFAqN2VlzrYL17ZjBAM8nTAP2hsi3K5aR5MeQZ28sL+rFmVbDHtbXew==", 772 772 "requires": { 773 - "home-dir": "^1.0.0" 773 + "home-dir": "1.0.0" 774 774 } 775 775 }, 776 776 "kernelspecs": { ··· 778 778 "resolved": "https://registry.npmjs.org/kernelspecs/-/kernelspecs-2.0.0.tgz", 779 779 "integrity": "sha512-lce4pPDrs4VdxKYTEBnGLT81A3yNP8syyMAq5AejE+CKAkiXQXrHZaHO1F4c/RmgkKKF1Otis1XrpBxOOQsdnw==", 780 780 "requires": { 781 - "jupyter-paths": "^2.0.0" 781 + "jupyter-paths": "2.0.0" 782 782 } 783 783 }, 784 784 "latest-version": { ··· 833 833 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 834 834 "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 835 835 "requires": { 836 - "brace-expansion": "^1.1.7" 836 + "brace-expansion": "1.1.11" 837 837 } 838 838 }, 839 839 "minimist": { ··· 846 846 "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz", 847 847 "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==", 848 848 "requires": { 849 - "safe-buffer": "^5.1.2", 850 - "yallist": "^3.0.0" 849 + "safe-buffer": "5.1.2", 850 + "yallist": "3.0.2" 851 851 } 852 852 }, 853 853 "minizlib": { ··· 855 855 "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", 856 856 "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", 857 857 "requires": { 858 - "minipass": "^2.2.1" 858 + "minipass": "2.3.4" 859 859 } 860 860 }, 861 861 "mkdirp": { ··· 881 881 "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.3.tgz", 882 882 "integrity": "sha512-GPL22d/U9cai87FcCPO6e+MT3vyHS2j+zwotakDc7kE2DtUAqFKMXLJCTtRp+5S75vXIwQPvIxkvlctxf9q4gQ==", 883 883 "requires": { 884 - "debug": "^2.1.2", 885 - "iconv-lite": "^0.4.4", 886 - "sax": "^1.2.4" 884 + "debug": "2.2.0", 885 + "iconv-lite": "0.4.24", 886 + "sax": "1.2.4" 887 887 } 888 888 }, 889 889 "noble": { ··· 891 891 "resolved": "https://registry.npmjs.org/noble/-/noble-1.9.1.tgz", 892 892 "integrity": "sha1-LM0x6tjsktv/bxmkLkILJYvNzdA=", 893 893 "requires": { 894 - "bluetooth-hci-socket": "^0.5.1", 894 + "bluetooth-hci-socket": "0.5.1", 895 895 "bplist-parser": "0.0.6", 896 - "debug": "~2.2.0", 897 - "xpc-connection": "~0.1.4" 896 + "debug": "2.2.0", 897 + "xpc-connection": "0.1.4" 898 898 }, 899 899 "dependencies": { 900 900 "bluetooth-hci-socket": { ··· 903 903 "integrity": "sha1-774hUk/Bz10/rl1RNl1WHUq77Qs=", 904 904 "optional": true, 905 905 "requires": { 906 - "debug": "^2.2.0", 907 - "nan": "^2.0.5", 908 - "usb": "^1.1.0" 906 + "debug": "2.2.0", 907 + "nan": "2.11.0", 908 + "usb": "1.3.3" 909 909 } 910 910 } 911 911 } ··· 915 915 "resolved": "https://registry.npmjs.org/noble-winrt/-/noble-winrt-0.1.1.tgz", 916 916 "integrity": "sha512-IUlWQfaHJMuiYOQTGvW4YdWeQR7E6tBVXut8KDMkIFGAKU+LNvTciWYTwHUlzqZKtuP3jc9VFMBvR9UDFNq7Sw==", 917 917 "requires": { 918 - "chrome-native-messaging": "^0.2.0", 919 - "debug": "^2.6.8", 920 - "noble": "^1.7.0" 918 + "chrome-native-messaging": "0.2.0", 919 + "debug": "2.6.9", 920 + "noble": "1.9.1" 921 921 }, 922 922 "dependencies": { 923 923 "debug": { ··· 940 940 "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.3.tgz", 941 941 "integrity": "sha512-b656V5C0628gOOA2kwcpNA/bxdlqYF9FvxJ+qqVX0ctdXNVZpS8J6xEUYir3WAKc7U0BH/NRlSpNbGsy+azjeg==", 942 942 "requires": { 943 - "semver": "^5.4.1" 943 + "semver": "5.5.1" 944 944 } 945 945 }, 946 946 "node-pre-gyp": { ··· 948 948 "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", 949 949 "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", 950 950 "requires": { 951 - "detect-libc": "^1.0.2", 952 - "mkdirp": "^0.5.1", 953 - "needle": "^2.2.1", 954 - "nopt": "^4.0.1", 955 - "npm-packlist": "^1.1.6", 956 - "npmlog": "^4.0.2", 957 - "rc": "^1.2.7", 958 - "rimraf": "^2.6.1", 959 - "semver": "^5.3.0", 960 - "tar": "^4" 951 + "detect-libc": "1.0.3", 952 + "mkdirp": "0.5.1", 953 + "needle": "2.2.3", 954 + "nopt": "4.0.1", 955 + "npm-packlist": "1.1.11", 956 + "npmlog": "4.1.2", 957 + "rc": "1.2.8", 958 + "rimraf": "2.6.2", 959 + "semver": "5.5.1", 960 + "tar": "4.4.6" 961 961 } 962 962 }, 963 963 "noop-logger": { ··· 970 970 "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", 971 971 "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", 972 972 "requires": { 973 - "abbrev": "1", 974 - "osenv": "^0.1.4" 973 + "abbrev": "1.1.1", 974 + "osenv": "0.1.5" 975 975 } 976 976 }, 977 977 "npm-bundled": { ··· 984 984 "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.11.tgz", 985 985 "integrity": "sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA==", 986 986 "requires": { 987 - "ignore-walk": "^3.0.1", 988 - "npm-bundled": "^1.0.1" 987 + "ignore-walk": "3.0.1", 988 + "npm-bundled": "1.0.5" 989 989 } 990 990 }, 991 991 "npm-run-path": { ··· 1002 1002 "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 1003 1003 "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 1004 1004 "requires": { 1005 - "are-we-there-yet": "~1.1.2", 1006 - "console-control-strings": "~1.1.0", 1007 - "gauge": "~2.7.3", 1008 - "set-blocking": "~2.0.0" 1005 + "are-we-there-yet": "1.1.5", 1006 + "console-control-strings": "1.1.0", 1007 + "gauge": "2.7.4", 1008 + "set-blocking": "2.0.0" 1009 1009 } 1010 1010 }, 1011 1011 "number-is-nan": { ··· 1023 1023 "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1024 1024 "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1025 1025 "requires": { 1026 - "wrappy": "1" 1026 + "wrappy": "1.0.2" 1027 1027 } 1028 1028 }, 1029 1029 "os-homedir": { ··· 1041 1041 "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 1042 1042 "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 1043 1043 "requires": { 1044 - "os-homedir": "^1.0.0", 1045 - "os-tmpdir": "^1.0.0" 1044 + "os-homedir": "1.0.2", 1045 + "os-tmpdir": "1.0.2" 1046 1046 } 1047 1047 }, 1048 1048 "p-finally": { ··· 1115 1115 "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.17.tgz", 1116 1116 "integrity": "sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ==", 1117 1117 "requires": { 1118 - "async": "^1.5.2", 1119 - "debug": "^2.2.0", 1120 - "mkdirp": "0.5.x" 1118 + "async": "1.5.2", 1119 + "debug": "2.2.0", 1120 + "mkdirp": "0.5.1" 1121 1121 } 1122 1122 }, 1123 1123 "prebuild-install": { ··· 1125 1125 "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.3.tgz", 1126 1126 "integrity": "sha512-/rI36cN2g7vDQnKWN8Uzupi++KjyqS9iS+/fpwG4Ea8d0Pip0PQ5bshUNzVwt+/D2MRfhVAplYMMvWLqWrCF/g==", 1127 1127 "requires": { 1128 - "detect-libc": "^1.0.3", 1129 - "expand-template": "^1.0.2", 1128 + "detect-libc": "1.0.3", 1129 + "expand-template": "1.1.1", 1130 1130 "github-from-package": "0.0.0", 1131 - "minimist": "^1.2.0", 1132 - "mkdirp": "^0.5.1", 1133 - "node-abi": "^2.2.0", 1134 - "noop-logger": "^0.1.1", 1135 - "npmlog": "^4.0.1", 1136 - "os-homedir": "^1.0.1", 1137 - "pump": "^2.0.1", 1138 - "rc": "^1.1.6", 1139 - "simple-get": "^2.7.0", 1140 - "tar-fs": "^1.13.0", 1141 - "tunnel-agent": "^0.6.0", 1142 - "which-pm-runs": "^1.0.0" 1131 + "minimist": "1.2.0", 1132 + "mkdirp": "0.5.1", 1133 + "node-abi": "2.4.3", 1134 + "noop-logger": "0.1.1", 1135 + "npmlog": "4.1.2", 1136 + "os-homedir": "1.0.2", 1137 + "pump": "2.0.1", 1138 + "rc": "1.2.8", 1139 + "simple-get": "2.8.1", 1140 + "tar-fs": "1.16.3", 1141 + "tunnel-agent": "0.6.0", 1142 + "which-pm-runs": "1.0.0" 1143 1143 }, 1144 1144 "dependencies": { 1145 1145 "minimist": { ··· 1171 1171 "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", 1172 1172 "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", 1173 1173 "requires": { 1174 - "end-of-stream": "^1.1.0", 1175 - "once": "^1.3.1" 1174 + "end-of-stream": "1.4.1", 1175 + "once": "1.4.0" 1176 1176 } 1177 1177 }, 1178 1178 "rc": { ··· 1180 1180 "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1181 1181 "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1182 1182 "requires": { 1183 - "deep-extend": "^0.6.0", 1184 - "ini": "~1.3.0", 1185 - "minimist": "^1.2.0", 1186 - "strip-json-comments": "~2.0.1" 1183 + "deep-extend": "0.6.0", 1184 + "ini": "1.3.5", 1185 + "minimist": "1.2.0", 1186 + "strip-json-comments": "2.0.1" 1187 1187 }, 1188 1188 "dependencies": { 1189 1189 "minimist": { ··· 1198 1198 "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 1199 1199 "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 1200 1200 "requires": { 1201 - "core-util-is": "~1.0.0", 1202 - "inherits": "~2.0.3", 1203 - "isarray": "~1.0.0", 1204 - "process-nextick-args": "~2.0.0", 1205 - "safe-buffer": "~5.1.1", 1206 - "string_decoder": "~1.1.1", 1207 - "util-deprecate": "~1.0.1" 1201 + "core-util-is": "1.0.2", 1202 + "inherits": "2.0.3", 1203 + "isarray": "1.0.0", 1204 + "process-nextick-args": "2.0.0", 1205 + "safe-buffer": "5.1.2", 1206 + "string_decoder": "1.1.1", 1207 + "util-deprecate": "1.0.2" 1208 1208 } 1209 1209 }, 1210 1210 "regenerator-runtime": { ··· 1236 1236 "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 1237 1237 "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 1238 1238 "requires": { 1239 - "glob": "^7.0.5" 1239 + "glob": "7.1.3" 1240 1240 } 1241 1241 }, 1242 1242 "rxjs": { ··· 1244 1244 "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz", 1245 1245 "integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==", 1246 1246 "requires": { 1247 - "tslib": "^1.9.0" 1247 + "tslib": "1.9.3" 1248 1248 } 1249 1249 }, 1250 1250 "safe-buffer": { ··· 1311 1311 "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", 1312 1312 "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", 1313 1313 "requires": { 1314 - "decompress-response": "^3.3.0", 1315 - "once": "^1.3.1", 1316 - "simple-concat": "^1.0.0" 1314 + "decompress-response": "3.3.0", 1315 + "once": "1.4.0", 1316 + "simple-concat": "1.0.0" 1317 1317 } 1318 1318 }, 1319 1319 "slash": { ··· 1327 1327 "resolved": "https://registry.npmjs.org/spawnteract/-/spawnteract-4.0.0.tgz", 1328 1328 "integrity": "sha512-+FozhawsjyfnLjKx/CVZ8IbX7fUPl/pG4oKiULfHlxg9S7YOM5Z5YQoJLm83Z5SAm+VKZFHax2L/tdkDXO3fuQ==", 1329 1329 "requires": { 1330 - "jsonfile": "^3.0.0", 1331 - "jupyter-paths": "^2.0.0", 1332 - "kernelspecs": "^2.0.0", 1333 - "mkdirp": "^0.5.1", 1334 - "portfinder": "^1.0.13", 1335 - "uuid": "^3.0.1" 1330 + "jsonfile": "3.0.1", 1331 + "jupyter-paths": "2.0.0", 1332 + "kernelspecs": "2.0.0", 1333 + "mkdirp": "0.5.1", 1334 + "portfinder": "1.0.17", 1335 + "uuid": "3.3.2" 1336 1336 } 1337 1337 }, 1338 1338 "string-width": { ··· 1340 1340 "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1341 1341 "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1342 1342 "requires": { 1343 - "code-point-at": "^1.0.0", 1344 - "is-fullwidth-code-point": "^1.0.0", 1345 - "strip-ansi": "^3.0.0" 1343 + "code-point-at": "1.1.0", 1344 + "is-fullwidth-code-point": "1.0.0", 1345 + "strip-ansi": "3.0.1" 1346 1346 } 1347 1347 }, 1348 1348 "string_decoder": { ··· 1350 1350 "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1351 1351 "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1352 1352 "requires": { 1353 - "safe-buffer": "~5.1.0" 1353 + "safe-buffer": "5.1.2" 1354 1354 } 1355 1355 }, 1356 1356 "strip-ansi": { ··· 1358 1358 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1359 1359 "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1360 1360 "requires": { 1361 - "ansi-regex": "^2.0.0" 1361 + "ansi-regex": "2.1.1" 1362 1362 } 1363 1363 }, 1364 1364 "strip-eof": { ··· 1388 1388 "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz", 1389 1389 "integrity": "sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg==", 1390 1390 "requires": { 1391 - "chownr": "^1.0.1", 1392 - "fs-minipass": "^1.2.5", 1393 - "minipass": "^2.3.3", 1394 - "minizlib": "^1.1.0", 1395 - "mkdirp": "^0.5.0", 1396 - "safe-buffer": "^5.1.2", 1397 - "yallist": "^3.0.2" 1391 + "chownr": "1.0.1", 1392 + "fs-minipass": "1.2.5", 1393 + "minipass": "2.3.4", 1394 + "minizlib": "1.1.0", 1395 + "mkdirp": "0.5.1", 1396 + "safe-buffer": "5.1.2", 1397 + "yallist": "3.0.2" 1398 1398 } 1399 1399 }, 1400 1400 "tar-fs": { ··· 1402 1402 "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", 1403 1403 "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", 1404 1404 "requires": { 1405 - "chownr": "^1.0.1", 1406 - "mkdirp": "^0.5.1", 1407 - "pump": "^1.0.0", 1408 - "tar-stream": "^1.1.2" 1405 + "chownr": "1.0.1", 1406 + "mkdirp": "0.5.1", 1407 + "pump": "1.0.3", 1408 + "tar-stream": "1.6.1" 1409 1409 }, 1410 1410 "dependencies": { 1411 1411 "pump": { ··· 1413 1413 "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", 1414 1414 "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", 1415 1415 "requires": { 1416 - "end-of-stream": "^1.1.0", 1417 - "once": "^1.3.1" 1416 + "end-of-stream": "1.4.1", 1417 + "once": "1.4.0" 1418 1418 } 1419 1419 } 1420 1420 } ··· 1424 1424 "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", 1425 1425 "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", 1426 1426 "requires": { 1427 - "bl": "^1.0.0", 1428 - "buffer-alloc": "^1.1.0", 1429 - "end-of-stream": "^1.0.0", 1430 - "fs-constants": "^1.0.0", 1431 - "readable-stream": "^2.3.0", 1432 - "to-buffer": "^1.1.0", 1433 - "xtend": "^4.0.0" 1427 + "bl": "1.2.2", 1428 + "buffer-alloc": "1.2.0", 1429 + "end-of-stream": "1.4.1", 1430 + "fs-constants": "1.0.0", 1431 + "readable-stream": "2.3.6", 1432 + "to-buffer": "1.1.1", 1433 + "xtend": "4.0.1" 1434 1434 } 1435 1435 }, 1436 1436 "term-size": { ··· 1472 1472 "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1473 1473 "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1474 1474 "requires": { 1475 - "safe-buffer": "^5.0.1" 1475 + "safe-buffer": "5.1.2" 1476 1476 } 1477 1477 }, 1478 1478 "unique-string": { ··· 1560 1560 "integrity": "sha512-WRBxI54yEs2QPj28G6kITI3Wu7VxrtHbqiDvDRUDKdg97lcK1pTP8y9LoDWF22OiCCrEvrdeq0lNcr84QOzjXQ==", 1561 1561 "optional": true, 1562 1562 "requires": { 1563 - "nan": "^2.8.0", 1564 - "node-pre-gyp": "^0.10.0" 1563 + "nan": "2.11.0", 1564 + "node-pre-gyp": "0.10.3" 1565 1565 } 1566 1566 }, 1567 1567 "util-deprecate": { ··· 1593 1593 "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1594 1594 "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1595 1595 "requires": { 1596 - "string-width": "^1.0.2 || 2" 1596 + "string-width": "1.0.2" 1597 1597 } 1598 1598 }, 1599 1599 "widest-line": { ··· 1666 1666 "integrity": "sha1-3Nf6oq7Gt6bhjMXdrQQvejTHcVY=", 1667 1667 "optional": true, 1668 1668 "requires": { 1669 - "nan": "^2.0.5" 1669 + "nan": "2.11.0" 1670 1670 } 1671 1671 }, 1672 1672 "xtend": { ··· 1684 1684 "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-4.6.0.tgz", 1685 1685 "integrity": "sha512-sU7pQqQj7f/C6orJZAXls+NEKaVMZZtnZqpMPTq5d5dP78CmdC0g15XIviFAN6poPuKl9qlGt74vipOUUuNeWg==", 1686 1686 "requires": { 1687 - "nan": "^2.6.2", 1688 - "prebuild-install": "^2.2.2" 1687 + "nan": "2.11.0", 1688 + "prebuild-install": "2.5.3" 1689 1689 } 1690 1690 } 1691 1691 }
+1 -1
app/package.json
··· 1 1 { 2 2 "name": "BrainWaves", 3 3 "productName": "BrainWaves", 4 - "version": "0.3.0", 4 + "version": "0.4.0", 5 5 "description": "EEG Desktop Application", 6 6 "main": "./main.prod.js", 7 7 "author": {
+26 -4
app/reducers/experimentReducer.js
··· 9 9 SET_TYPE, 10 10 SET_SUBJECT, 11 11 SET_TITLE, 12 - SET_EXPERIMENT_STATE 12 + SET_EXPERIMENT_STATE, 13 + SET_PARAMS, 14 + SET_DESCRIPTION 13 15 } from '../actions/experimentActions'; 14 16 import { EXPERIMENTS } from '../constants/constants'; 15 - import { MainTimeline, Trial, ActionType } from '../constants/interfaces'; 17 + import { 18 + MainTimeline, 19 + Trial, 20 + ActionType, 21 + ExperimentDescription, 22 + ExperimentParameters 23 + } from '../constants/interfaces'; 16 24 17 25 export interface ExperimentStateType { 18 26 +type: ?EXPERIMENTS; ··· 25 33 +subject: string; 26 34 +session: number; 27 35 +isRunning: boolean; 36 + +description: ExperimentDescription; 28 37 } 29 38 30 39 const initialState = { 31 40 type: EXPERIMENTS.NONE, 32 41 title: '', 33 - params: {}, 42 + params: null, 34 43 mainTimeline: [], 35 44 trials: {}, 36 45 timelines: {}, 37 46 plugins: {}, 38 47 subject: '', 39 48 session: 1, 40 - isRunning: false 49 + isRunning: false, 50 + description: { question: '', hypothesis: '', methods: '' } 41 51 }; 42 52 43 53 export default function experiment( ··· 63 73 session: action.payload 64 74 }; 65 75 76 + case SET_PARAMS: 77 + return { 78 + ...state, 79 + params: { ...state.params, ...action.payload } 80 + }; 81 + 66 82 case SET_TIMELINE: 67 83 return { 68 84 ...state, ··· 73 89 return { 74 90 ...state, 75 91 title: action.payload 92 + }; 93 + 94 + case SET_DESCRIPTION: 95 + return { 96 + ...state, 97 + description: action.payload 76 98 }; 77 99 78 100 case SET_IS_RUNNING:
+18 -3
app/utils/filesystem/dialog.js
··· 6 6 import { dialog } from "electron"; 7 7 import { FILE_TYPES } from "../../constants/constants"; 8 8 9 - export const loadFile = (event, arg) => { 9 + export const loadDialog = (event, arg) => { 10 10 switch (arg) { 11 + case FILE_TYPES.STIMULUS_DIR: 12 + return selectStimulusFolder(event); 13 + 11 14 case FILE_TYPES.TIMELINE: 12 15 default: 13 - selectTimeline(event); 16 + return selectTimeline(event); 14 17 } 15 18 }; 16 19 ··· 22 25 }, 23 26 filePaths => { 24 27 if (filePaths) { 25 - event.sender.send("loadFileReply", filePaths[0]); 28 + event.sender.send("loadDialogReply", filePaths[0]); 26 29 } 27 30 } 28 31 ); 29 32 }; 33 + 34 + const selectStimulusFolder = event => { 35 + dialog.showOpenDialog( 36 + { 37 + title: "Select a folder of images", 38 + properties: ["openDirectory"] 39 + }, 40 + dir => { 41 + event.sender.send("loadDialogReply", dir[0]); 42 + } 43 + ); 44 + };
+5 -4
app/utils/filesystem/select.js
··· 4 4 * Functions for selecting files and directories from disk 5 5 */ 6 6 7 - import { ipcRenderer } from "electron"; 7 + import { ipcRenderer } from 'electron'; 8 + import { FILE_TYPES } from '../../constants/constants'; 8 9 9 - export const loadFileFromSystemDialog = (fileType: string) => 10 + export const loadFromSystemDialog = (fileType: FILE_TYPES) => 10 11 new Promise(resolve => { 11 - ipcRenderer.send("loadFile", fileType); 12 - ipcRenderer.on("loadFileReply", (event, result) => { 12 + ipcRenderer.send('loadDialog', fileType); 13 + ipcRenderer.on('loadDialogReply', (event, result) => { 13 14 resolve(result); 14 15 }); 15 16 });
+15 -4
app/utils/jspsych/functions.js
··· 55 55 ...timeline.timeline[1], 56 56 stimulus: jsPsych.timelineVariable('stimulusVar'), 57 57 type: params.pluginName, 58 - trial_duration: params.trialDuration 58 + trial_duration: params.trialDuration, 59 + choices: [params.stimulus1.response, params.stimulus2.response] 59 60 } 60 61 ], 61 62 sample: { ··· 80 81 })) 81 82 ); 82 83 84 + // Inserts param.intro as stimulus property of first trial 85 + const parsedTrials = Object.assign( 86 + ...Object.entries(trials).map(([id, trial]) => { 87 + if (id === mainTimeline[0]) { 88 + return { [id]: { ...trial, stimulus: params.intro } }; 89 + } 90 + return { [id]: trial }; 91 + }) 92 + ); 93 + 83 94 // Combine trials and timelines into one object 84 - const jsPsychObject = { ...trials, ...parsedTimelines }; 95 + const jsPsychObject = { ...parsedTrials, ...parsedTimelines }; 85 96 // Map through the mainTimeline, returning the appropriate trial or timeline based on id 86 97 return mainTimeline.map(id => jsPsychObject[id]); 87 98 }; ··· 96 107 ) => 97 108 timeline.map((jspsychObject, index) => { 98 109 if (index === 0) { 99 - // start 110 + // intro 100 111 return { ...jspsychObject, on_finish: startCallback }; 101 112 } 102 113 if (index === timeline.length - 1) { 103 - // stop 114 + // end 104 115 return { ...jspsychObject, on_load: stopCallback }; 105 116 } 106 117 if (!isNil(jspsychObject.timeline)) {
+31 -24
app/utils/jspsych/timelines/n170.js
··· 1 - import * as path from "path"; 2 - import { EVENTS } from "../../../constants/constants"; 1 + import * as path from 'path'; 2 + import { EVENTS } from '../../../constants/constants'; 3 3 4 4 // Default directories containing stimuli 5 - const rootFolder = __dirname;// Note: there's a weird issue where the fs readdir function reads from BrainWaves dir 5 + const rootFolder = __dirname; // Note: there's a weird issue where the fs readdir function reads from BrainWaves dir 6 6 7 - const facesDir = path.join(rootFolder, "assets", "face_house", "faces"); 8 - const housesDir = path.join(rootFolder, "assets", "face_house", "houses"); 9 - const fixation = path.join(rootFolder, "assets", "face_house", "fixation.jpg"); 7 + const facesDir = path.join(rootFolder, 'assets', 'face_house', 'faces'); 8 + const housesDir = path.join(rootFolder, 'assets', 'face_house', 'houses'); 9 + const fixation = path.join(rootFolder, 'assets', 'face_house', 'fixation.jpg'); 10 10 11 11 export const buildN170Timeline = () => ({ 12 12 params: { ··· 14 14 experimentDuration: 120000, 15 15 iti: 800, 16 16 jitter: 200, 17 - sampleType: "with-replacement", 18 - pluginName: "callback-image-display", 19 - stimulus1: { dir: facesDir, title: "Face", type: EVENTS.FACE }, 20 - stimulus2: { dir: housesDir, title: "House", type: EVENTS.HOUSE } 17 + sampleType: 'with-replacement', 18 + pluginName: 'callback-image-display', 19 + intro: 20 + 'You will view a series of faces and houses for two minutes. Please mentally note which stimulus you are viewing. Press any key to continue', 21 + stimulus1: { dir: facesDir, title: 'Face', type: EVENTS.FACE, response: 1 }, 22 + stimulus2: { 23 + dir: housesDir, 24 + title: 'House', 25 + type: EVENTS.HOUSE, 26 + response: 9 27 + } 21 28 }, 22 - mainTimeline: ["welcome", "faceHouseTimeline", "end"], // array of trial and timeline ids 29 + mainTimeline: ['intro', 'faceHouseTimeline', 'end'], // array of trial and timeline ids 23 30 trials: { 24 - welcome: { 25 - type: "callback-html-display", 26 - id: "welcome", 27 - stimulus: "Welcome to the experiment. Press any key to begin.", 31 + intro: { 32 + type: 'callback-html-display', 33 + id: 'intro', 28 34 post_trial_gap: 1000 29 35 }, 30 36 end: { 31 - id: "end", 32 - type: "callback-html-display", 33 - stimulus: "Thanks for participating", 37 + id: 'end', 38 + type: 'callback-html-display', 39 + stimulus: 'Thanks for participating', 34 40 post_trial_gap: 500 35 41 } 36 42 }, 37 43 timelines: { 38 44 faceHouseTimeline: { 39 - id: "faceHouseTimeline", 45 + id: 'faceHouseTimeline', 40 46 timeline: [ 41 47 { 42 - id: "interTrial", 43 - type: "callback-image-display", 44 - stimulus: fixation 48 + id: 'interTrial', 49 + type: 'callback-image-display', 50 + stimulus: fixation, 51 + response_ends_trial: false 45 52 }, 46 53 { 47 - id: "trial", 48 - choices: ["f", "j"] 54 + id: 'trial', 55 + response_ends_trial: false 49 56 } 50 57 ] 51 58 }
+1 -2
keys.js
··· 3 3 4 4 var USERNAME = process.env.EMOTIV_USERNAME; 5 5 var PASSWORD = process.env.EMOTIV_PASSWORD; 6 - var CLIENT_ID = process.env.EMOTIV_CLIENT_ID;// Created through Cortex Apps page on Emotiv.com 6 + var CLIENT_ID = process.env.EMOTIV_CLIENT_ID; // Created through Cortex Apps page on Emotiv.com 7 7 var CLIENT_SECRET = process.env.EMOTIV_CLIENT_SECRET; // Created through Cortex Apps page on Emotiv.com 8 8 var LICENSE_ID = process.env.EMOTIV_LICENSE_ID; // Visible on My Account page of Emotiv.com 9 - 10 9 11 10 module.exports = { USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET, LICENSE_ID };
+143 -3
package-lock.json
··· 997 997 } 998 998 } 999 999 }, 1000 + "add-dom-event-listener": { 1001 + "version": "1.0.2", 1002 + "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.0.2.tgz", 1003 + "integrity": "sha1-j67SxBAIchzxEdodMNmVuFvkK+0=", 1004 + "requires": { 1005 + "object-assign": "4.1.1" 1006 + } 1007 + }, 1000 1008 "address": { 1001 1009 "version": "1.0.3", 1002 1010 "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", ··· 4398 4406 "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=", 4399 4407 "dev": true 4400 4408 }, 4409 + "component-classes": { 4410 + "version": "1.2.6", 4411 + "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", 4412 + "integrity": "sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE=", 4413 + "requires": { 4414 + "component-indexof": "0.0.3" 4415 + } 4416 + }, 4401 4417 "component-emitter": { 4402 4418 "version": "1.2.1", 4403 4419 "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 4404 4420 "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", 4405 4421 "dev": true 4422 + }, 4423 + "component-indexof": { 4424 + "version": "0.0.3", 4425 + "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", 4426 + "integrity": "sha1-EdCRMSI5648yyPJa6csAL/6NPCQ=" 4406 4427 }, 4407 4428 "compress-commons": { 4408 4429 "version": "1.2.2", ··· 4856 4877 "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 4857 4878 "dev": true 4858 4879 } 4880 + } 4881 + }, 4882 + "css-animation": { 4883 + "version": "1.4.1", 4884 + "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.4.1.tgz", 4885 + "integrity": "sha1-W4gTEl3g+7uwu+G0cq6EIhRpt6g=", 4886 + "requires": { 4887 + "babel-runtime": "6.26.0", 4888 + "component-classes": "1.2.6" 4859 4889 } 4860 4890 }, 4861 4891 "css-color-list": { ··· 6047 6077 } 6048 6078 } 6049 6079 } 6080 + }, 6081 + "dom-align": { 6082 + "version": "1.8.0", 6083 + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.8.0.tgz", 6084 + "integrity": "sha512-B85D4ef2Gj5lw0rK0KM2+D5/pH7yqNxg2mB+E8uzFaolpm7RQmsxEfjyEuNiF8UBBkffumYDeKRzTzc3LePP+w==" 6050 6085 }, 6051 6086 "dom-serializer": { 6052 6087 "version": "0.1.0", ··· 13068 13103 "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz", 13069 13104 "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==" 13070 13105 }, 13106 + "lodash._getnative": { 13107 + "version": "3.9.1", 13108 + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 13109 + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" 13110 + }, 13071 13111 "lodash.assign": { 13072 13112 "version": "4.2.0", 13073 13113 "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", ··· 13120 13160 "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", 13121 13161 "dev": true 13122 13162 }, 13163 + "lodash.isarguments": { 13164 + "version": "3.1.0", 13165 + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 13166 + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" 13167 + }, 13168 + "lodash.isarray": { 13169 + "version": "3.0.4", 13170 + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 13171 + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" 13172 + }, 13123 13173 "lodash.isequal": { 13124 13174 "version": "4.5.0", 13125 13175 "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", 13126 13176 "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", 13127 13177 "dev": true 13178 + }, 13179 + "lodash.keys": { 13180 + "version": "3.1.2", 13181 + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 13182 + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 13183 + "requires": { 13184 + "lodash._getnative": "3.9.1", 13185 + "lodash.isarguments": "3.1.0", 13186 + "lodash.isarray": "3.0.4" 13187 + } 13128 13188 }, 13129 13189 "lodash.memoize": { 13130 13190 "version": "4.1.2", ··· 15102 15162 "performance-now": { 15103 15163 "version": "2.1.0", 15104 15164 "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 15105 - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", 15106 - "dev": true 15165 + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 15107 15166 }, 15108 15167 "pify": { 15109 15168 "version": "2.3.0", ··· 16231 16290 "version": "3.4.0", 16232 16291 "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", 16233 16292 "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", 16234 - "dev": true, 16235 16293 "requires": { 16236 16294 "performance-now": "2.1.0" 16237 16295 } ··· 16356 16414 "ini": "1.3.5", 16357 16415 "minimist": "1.2.0", 16358 16416 "strip-json-comments": "2.0.1" 16417 + } 16418 + }, 16419 + "rc-align": { 16420 + "version": "2.4.3", 16421 + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.3.tgz", 16422 + "integrity": "sha512-h5KgyB5IXYR7iKpYFcMr54cuQ2eozPCZ11kbXPG5+6CWvmyJ+c0R/yjndVndiNk2G3MKcTMbJNdDv5DIckLAxQ==", 16423 + "requires": { 16424 + "babel-runtime": "6.26.0", 16425 + "dom-align": "1.8.0", 16426 + "prop-types": "15.6.2", 16427 + "rc-util": "4.5.1" 16428 + } 16429 + }, 16430 + "rc-animate": { 16431 + "version": "2.5.4", 16432 + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.5.4.tgz", 16433 + "integrity": "sha512-aYisNYRuOvH+r5/nxEHHUzk1celxPg4nMUOuBGHd77c6NZbZ5fcjHFgWF3/nPdRPbLaqY4Z7Q317EcLO41fOvQ==", 16434 + "requires": { 16435 + "babel-runtime": "6.26.0", 16436 + "classnames": "2.2.6", 16437 + "css-animation": "1.4.1", 16438 + "prop-types": "15.6.2", 16439 + "raf": "3.4.0", 16440 + "react-lifecycles-compat": "3.0.4" 16441 + } 16442 + }, 16443 + "rc-slider": { 16444 + "version": "8.6.3", 16445 + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-8.6.3.tgz", 16446 + "integrity": "sha512-kQaq3SJdb5EifYZxvMDotgClOJKIuAiidFr67hxNEeBv0GwUcwAToYmSWrP0B2ZCb5IYlr2csyiH0+xNomLDoA==", 16447 + "requires": { 16448 + "babel-runtime": "6.26.0", 16449 + "classnames": "2.2.6", 16450 + "prop-types": "15.6.2", 16451 + "rc-tooltip": "3.7.3", 16452 + "rc-util": "4.5.1", 16453 + "shallowequal": "1.1.0", 16454 + "warning": "3.0.0" 16455 + } 16456 + }, 16457 + "rc-tooltip": { 16458 + "version": "3.7.3", 16459 + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-3.7.3.tgz", 16460 + "integrity": "sha512-dE2ibukxxkrde7wH9W8ozHKUO4aQnPZ6qBHtrTH9LoO836PjDdiaWO73fgPB05VfJs9FbZdmGPVEbXCeOP99Ww==", 16461 + "requires": { 16462 + "babel-runtime": "6.26.0", 16463 + "prop-types": "15.6.2", 16464 + "rc-trigger": "2.6.2" 16465 + } 16466 + }, 16467 + "rc-trigger": { 16468 + "version": "2.6.2", 16469 + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.2.tgz", 16470 + "integrity": "sha512-op4xCu95/gdHVaysyxxiYxbY+Z+UcIBSUY9nQfLqm1FlitdtnAN+owD5iMPfnnsRXntgcQ5+RdYKNUFQT5DjzA==", 16471 + "requires": { 16472 + "babel-runtime": "6.26.0", 16473 + "classnames": "2.2.6", 16474 + "prop-types": "15.6.2", 16475 + "rc-align": "2.4.3", 16476 + "rc-animate": "2.5.4", 16477 + "rc-util": "4.5.1" 16478 + } 16479 + }, 16480 + "rc-util": { 16481 + "version": "4.5.1", 16482 + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.5.1.tgz", 16483 + "integrity": "sha512-PdCmHyBBodZdw6Oaikt0l+/R79IcRXpYkTrqD/Rbl4ZdoOi61t5TtEe40Q+A7rkWG5U1xjcN+h8j9H6GdtnICw==", 16484 + "requires": { 16485 + "add-dom-event-listener": "1.0.2", 16486 + "babel-runtime": "6.26.0", 16487 + "prop-types": "15.6.2", 16488 + "shallowequal": "0.2.2" 16489 + }, 16490 + "dependencies": { 16491 + "shallowequal": { 16492 + "version": "0.2.2", 16493 + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz", 16494 + "integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=", 16495 + "requires": { 16496 + "lodash.keys": "3.1.2" 16497 + } 16498 + } 16359 16499 } 16360 16500 }, 16361 16501 "react": {
+2 -1
package.json
··· 1 1 { 2 2 "name": "BrainWaves", 3 3 "productName": "BrainWaves", 4 - "version": "0.3.0", 4 + "version": "0.4.0", 5 5 "description": "EEG Experiment Desktop Application", 6 6 "scripts": { 7 7 "build": "concurrently \"npm run build-main\" \"npm run build-renderer\"", ··· 222 222 "mkdirp": "^0.5.1", 223 223 "mousetrap": "^1.6.2", 224 224 "muse-js": "^3.1.0", 225 + "rc-slider": "^8.6.3", 225 226 "react": "^16.5.0", 226 227 "react-dom": "^16.5.0", 227 228 "react-hot-loader": "^4.3.6",