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.

Updated store

+175 -248
+45 -64
app/epics/deviceEpics.ts
··· 10 10 connectToEmotiv, 11 11 createRawEmotivObservable, 12 12 createEmotivSignalQualityObservable, 13 - disconnectFromEmotiv 13 + disconnectFromEmotiv, 14 14 } from '../utils/eeg/emotiv'; 15 15 import { 16 16 getMuse, 17 17 connectToMuse, 18 18 createRawMuseObservable, 19 19 createMuseSignalQualityObservable, 20 - disconnectFromMuse 20 + disconnectFromMuse, 21 21 } from '../utils/eeg/muse'; 22 22 import { 23 23 CONNECTION_STATUS, 24 24 DEVICES, 25 25 DEVICE_AVAILABILITY, 26 - SEARCH_TIMER 26 + SEARCH_TIMER, 27 27 } from '../constants/constants'; 28 28 import { Device, DeviceInfo } from '../constants/interfaces'; 29 29 import { DeviceStateType } from '../reducers/deviceReducer'; ··· 33 33 // Epics 34 34 35 35 // NOTE: Uses a Promise "then" inside b/c Observable.from leads to loss of user gesture propagation for web bluetooth 36 - const searchMuseEpic: Epic< 37 - DeviceActionType, 38 - DeviceActionType, 39 - RootState 40 - > = action$ => 36 + const searchMuseEpic: Epic<DeviceActionType, DeviceActionType, RootState> = ( 37 + action$ 38 + ) => 41 39 action$.pipe( 42 40 filter(isActionOf(DeviceActions.SetDeviceAvailability)), 43 41 pluck('payload'), 44 - filter(status => status === DEVICE_AVAILABILITY.SEARCHING), 42 + filter((status) => status === DEVICE_AVAILABILITY.SEARCHING), 45 43 map(getMuse), 46 - mergeMap(promise => 44 + mergeMap((promise) => 47 45 promise.then( 48 - devices => devices, 49 - error => { 46 + (devices) => devices, 47 + (error) => { 50 48 // This error will fire a bit too promiscuously until we fix windows web bluetooth 51 49 // toast.error(`"Device Error: " ${error.toString()}`); 52 50 return []; 53 51 } 54 52 ) 55 53 ), 56 - filter(devices => !isNil(devices) && devices.length >= 1), 54 + filter((devices) => !isNil(devices) && devices.length >= 1), 57 55 map(DeviceActions.DeviceFound) 58 56 ); 59 57 60 - const searchEmotivEpic: Epic< 61 - DeviceActionType, 62 - DeviceActionType, 63 - RootState 64 - > = action$ => 58 + const searchEmotivEpic: Epic<DeviceActionType, DeviceActionType, RootState> = ( 59 + action$ 60 + ) => 65 61 action$.pipe( 66 62 filter(isActionOf(DeviceActions.SetDeviceAvailability)), 67 63 pluck('payload'), 68 - filter(status => status === DEVICE_AVAILABILITY.SEARCHING), 64 + filter((status) => status === DEVICE_AVAILABILITY.SEARCHING), 69 65 filter(() => process.platform === 'darwin' || process.platform === 'win32'), 70 66 map(getEmotiv), 71 - mergeMap(promise => 67 + mergeMap((promise) => 72 68 promise.then( 73 - devices => devices, 74 - error => { 69 + (devices) => devices, 70 + (error) => { 75 71 if (error.message.includes('client.queryHeadsets')) { 76 72 toast.error( 77 73 'Could not connect to Cortex Service. Please connect to the internet and install Cortex to use Emotiv EEG', ··· 85 81 } 86 82 ) 87 83 ), 88 - filter(devices => devices.length >= 1), 84 + filter((devices) => devices.length >= 1), 89 85 map(DeviceActions.DeviceFound) 90 86 ); 91 87 ··· 96 92 action$.pipe( 97 93 filter(isActionOf(DeviceActions.DeviceFound)), 98 94 pluck('payload'), 99 - map(foundDevices => 95 + map((foundDevices) => 100 96 foundDevices.reduce((acc, curr) => { 101 - if (acc.find(device => device.id === curr.id)) { 97 + if (acc.find((device) => device.id === curr.id)) { 102 98 return acc; 103 99 } 104 100 return acc.concat(curr); 105 101 }, state$.value.device.availableDevices) 106 102 ), 107 - mergeMap(devices => 103 + mergeMap((devices) => 108 104 of( 109 105 DeviceActions.SetAvailableDevices(devices), 110 106 DeviceActions.SetDeviceAvailability(DEVICE_AVAILABILITY.AVAILABLE) ··· 119 115 action$.pipe( 120 116 filter(isActionOf(DeviceActions.SetDeviceAvailability)), 121 117 pluck('payload'), 122 - filter(status => status === DEVICE_AVAILABILITY.SEARCHING), 118 + filter((status) => status === DEVICE_AVAILABILITY.SEARCHING), 123 119 mergeMap(() => timer(SEARCH_TIMER)), 124 120 filter( 125 121 () => ··· 128 124 map(() => DeviceActions.SetDeviceAvailability(DEVICE_AVAILABILITY.NONE)) 129 125 ); 130 126 131 - const connectEpic: Epic< 132 - DeviceActionType, 133 - DeviceActionType, 134 - RootState 135 - > = action$ => 127 + const connectEpic: Epic<DeviceActionType, DeviceActionType, RootState> = ( 128 + action$ 129 + ) => 136 130 action$.pipe( 137 131 filter(isActionOf(DeviceActions.ConnectToDevice)), 138 132 pluck('payload'), 139 - map<Device, Promise<any>>(device => 133 + map<Device, Promise<any>>((device) => 140 134 isNil(device.name) ? connectToEmotiv(device) : connectToMuse(device) 141 135 ), 142 - mergeMap<Promise<any>, ObservableInput<DeviceInfo>>(promise => 143 - promise.then(deviceInfo => deviceInfo) 136 + mergeMap<Promise<any>, ObservableInput<DeviceInfo>>((promise) => 137 + promise.then((deviceInfo) => deviceInfo) 144 138 ), 145 - mergeMap<DeviceInfo, ObservableInput<any>>(deviceInfo => { 139 + mergeMap<DeviceInfo, ObservableInput<any>>((deviceInfo) => { 146 140 if (!isNil(deviceInfo) && !isNil(deviceInfo.samplingRate)) { 147 141 return of( 148 142 DeviceActions.SetDeviceType( ··· 158 152 }) 159 153 ); 160 154 161 - const isConnectingEpic: Epic< 162 - DeviceActionType, 163 - DeviceActionType, 164 - RootState 165 - > = action$ => 155 + const isConnectingEpic: Epic<DeviceActionType, DeviceActionType, RootState> = ( 156 + action$ 157 + ) => 166 158 action$.pipe( 167 159 filter(isActionOf(DeviceActions.ConnectToDevice)), 168 160 map(() => DeviceActions.SetConnectionStatus(CONNECTION_STATUS.CONNECTING)) ··· 192 184 action$.pipe( 193 185 filter(isActionOf(DeviceActions.SetRawObservable)), 194 186 pluck('payload'), 195 - map(rawObservable => { 187 + map((rawObservable) => { 196 188 if (state$.value.device.deviceType === DEVICES.EMOTIV) { 197 189 return createEmotivSignalQualityObservable(rawObservable); 198 190 } ··· 224 216 map(DeviceActions.Cleanup) 225 217 ); 226 218 227 - // TODO: Fix this error handling so epics can refire once they error out 228 - const rootEpic = (action$, state$) => 229 - combineEpics( 230 - searchMuseEpic, 231 - searchEmotivEpic, 232 - deviceFoundEpic, 233 - searchTimerEpic, 234 - connectEpic, 235 - isConnectingEpic, 236 - setRawObservableEpic, 237 - setSignalQualityObservableEpic, 238 - deviceCleanupEpic 239 - )(action$, state$, null).pipe( 240 - catchError(error => 241 - of(error).pipe( 242 - tap(err => toast.error(`"Device Error: " ${err.toString()}`)), 243 - map(DeviceActions.Cleanup) 244 - ) 245 - ) 246 - ); 247 - 248 - export default rootEpic; 219 + export default combineEpics( 220 + searchMuseEpic, 221 + searchEmotivEpic, 222 + deviceFoundEpic, 223 + searchTimerEpic, 224 + connectEpic, 225 + isConnectingEpic, 226 + setRawObservableEpic, 227 + setSignalQualityObservableEpic, 228 + deviceCleanupEpic 229 + );
+15 -14
app/epics/experimentEpics.ts
··· 10 10 takeUntil, 11 11 throttleTime, 12 12 ignoreElements, 13 - tap 13 + tap, 14 14 } from 'rxjs/operators'; 15 15 import { ExperimentActions, ExperimentActionType } from '../actions'; 16 16 import { 17 17 DEVICES, 18 18 MUSE_CHANNELS, 19 19 EMOTIV_CHANNELS, 20 - CONNECTION_STATUS 20 + CONNECTION_STATUS, 21 21 } from '../constants/constants'; 22 22 import { loadProtocol } from '../utils/labjs/functions'; 23 23 import { 24 24 createEEGWriteStream, 25 25 writeHeader, 26 - writeEEGData 26 + writeEEGData, 27 27 } from '../utils/filesystem/write'; 28 28 import { 29 29 getWorkspaceDir, ··· 31 31 restoreExperimentState, 32 32 createWorkspaceDir, 33 33 storeBehaviouralData, 34 - readWorkspaceBehaviorData 34 + readWorkspaceBehaviorData, 35 35 } from '../utils/filesystem/storage'; 36 36 import { createEmotivRecord, stopEmotivRecord } from '../utils/eeg/emotiv'; 37 37 import { RootState } from '../reducers'; ··· 43 43 ExperimentActionType, 44 44 ExperimentActionType, 45 45 RootState 46 - > = action$ => 46 + > = (action$) => 47 47 action$.pipe( 48 48 filter(isActionOf(ExperimentActions.CreateNewWorkspace)), 49 49 pluck('payload'), 50 - tap(workspaceInfo => createWorkspaceDir(workspaceInfo.title)), 51 - mergeMap(workspaceInfo => 50 + tap((workspaceInfo) => createWorkspaceDir(workspaceInfo.title)), 51 + mergeMap((workspaceInfo) => 52 52 of( 53 53 ExperimentActions.SetType(workspaceInfo.type), 54 54 ExperimentActions.SetParadigm(workspaceInfo.paradigm), ··· 107 107 ) 108 108 ) 109 109 ) 110 - .subscribe(eegData => writeEEGData(writeStream, eegData)); 110 + .subscribe((eegData) => writeEEGData(writeStream, eegData)); 111 111 } 112 112 }), 113 113 mapTo(true), ··· 159 159 mergeMap(() => 160 160 from(readWorkspaceBehaviorData(state$.value.experiment.title!)) 161 161 ), 162 - map(behaviorFiles => { 162 + map((behaviorFiles) => { 163 163 if (behaviorFiles.length > 0) { 164 - const subjectFiles = behaviorFiles.filter(filepath => 164 + const subjectFiles = behaviorFiles.filter((filepath) => 165 165 filepath.name.startsWith(state$.value.experiment.subject) 166 166 ); 167 167 return subjectFiles.length + 1; ··· 171 171 map(ExperimentActions.SetSession) 172 172 ); 173 173 174 - const autoSaveEpic: Epic<any, ExperimentActionType, RootState> = action$ => 174 + const autoSaveEpic: Epic<any, ExperimentActionType, RootState> = (action$) => 175 175 action$.ofType('@@router/LOCATION_CHANGE').pipe( 176 176 pluck('payload', 'pathname'), 177 - filter(pathname => pathname !== '/' && pathname !== '/home'), 177 + filter((pathname) => pathname !== '/' && pathname !== '/home'), 178 178 map(ExperimentActions.SaveWorkspace) 179 179 ); 180 180 ··· 202 202 ) => 203 203 action$.ofType('@@router/LOCATION_CHANGE').pipe( 204 204 pluck('payload', 'pathname'), 205 - filter(pathname => pathname === '/' || pathname === '/home'), 205 + filter((pathname) => pathname === '/' || pathname === '/home'), 206 206 tap(() => restoreExperimentState(state$.value.experiment)), 207 207 map(ExperimentActions.ExperimentCleanup) 208 208 ); ··· 211 211 loadDefaultTimelineEpic, 212 212 createNewWorkspaceEpic, 213 213 startEpic, 214 - experimentStopEpic, // setSubjectEpic, 214 + experimentStopEpic, 215 + // setSubjectEpic, 215 216 // setGroupEpic, 216 217 updateSessionEpic, 217 218 autoSaveEpic,
+2 -1
app/epics/index.ts
··· 3 3 import device from './deviceEpics'; 4 4 import experiment from './experimentEpics'; 5 5 6 - export default combineEpics(device, jupyter, experiment); 6 + // TODO: Fix issue: https://github.com/piotrwitek/typesafe-actions/issues/174 7 + export default combineEpics(jupyter, device, experiment);
+42 -45
app/epics/jupyterEpics.ts
··· 7 7 pluck, 8 8 ignoreElements, 9 9 filter, 10 - take 10 + take, 11 11 } from 'rxjs/operators'; 12 12 import { find } from 'kernelspecs'; 13 13 import { launchSpec } from 'spawnteract'; ··· 33 33 plotPSD, 34 34 plotERP, 35 35 plotTopoMap, 36 - saveEpochs 36 + saveEpochs, 37 37 } from '../utils/jupyter/cells'; 38 38 import { 39 39 EMOTIV_CHANNELS, 40 40 EVENTS, 41 41 DEVICES, 42 42 MUSE_CHANNELS, 43 - JUPYTER_VARIABLE_NAMES 43 + JUPYTER_VARIABLE_NAMES, 44 44 } from '../constants/constants'; 45 45 import { 46 46 parseSingleQuoteJSON, 47 47 parseKernelStatus, 48 - debugParseMessage 48 + debugParseMessage, 49 49 } from '../utils/jupyter/functions'; 50 50 51 51 // ------------------------------------------------------------------------- 52 52 // Epics 53 53 54 - const launchEpic: Epic< 55 - JupyterActionType, 56 - JupyterActionType, 57 - RootState 58 - > = action$ => 54 + const launchEpic: Epic<JupyterActionType, JupyterActionType, RootState> = ( 55 + action$ 56 + ) => 59 57 action$.pipe( 60 58 filter(isActionOf(JupyterActions.LaunchKernel)), 61 59 mergeMap(() => from(find('brainwaves'))), 62 - tap(kernelInfo => { 60 + tap((kernelInfo) => { 63 61 if (isNil(kernelInfo)) { 64 62 toast.error( 65 63 "Could not find 'brainwaves' jupyter kernel. Have you installed Python?" 66 64 ); 67 65 } 68 66 }), 69 - filter(kernelInfo => !isNil(kernelInfo)), 70 - mergeMap<any, ObservableInput<any>>(kernelInfo => 67 + filter((kernelInfo) => !isNil(kernelInfo)), 68 + mergeMap<any, ObservableInput<any>>((kernelInfo) => 71 69 from( 72 70 launchSpec(kernelInfo.spec, { 73 71 // No STDIN, opt in to STDOUT and STDERR as node streams 74 - stdio: ['ignore', 'pipe', 'pipe'] 72 + stdio: ['ignore', 'pipe', 'pipe'], 75 73 }) 76 74 ) 77 75 ), 78 - tap(kernel => { 76 + tap((kernel) => { 79 77 // Route everything that we won't get in messages to our own stdout 80 - kernel.spawn.stdout.on('data', data => { 78 + kernel.spawn.stdout.on('data', (data) => { 81 79 const text = data.toString(); 82 80 console.log('KERNEL STDOUT: ', text); 83 81 }); 84 - kernel.spawn.stderr.on('data', data => { 82 + kernel.spawn.stderr.on('data', (data) => { 85 83 const text = data.toString(); 86 84 console.log('KERNEL STDERR: ', text); 87 85 toast.error('Jupyter: ', text); ··· 98 96 JupyterActionType, 99 97 JupyterActionType, 100 98 RootState 101 - > = action$ => 99 + > = (action$) => 102 100 action$.pipe( 103 101 filter(isActionOf(JupyterActions.SetKernel)), 104 102 pluck('payload'), 105 - mergeMap(kernel => from(createMainChannel(kernel.config))), 106 - tap(mainChannel => mainChannel.next(executeRequest(imports()))), 107 - tap(mainChannel => mainChannel.next(executeRequest(utils()))), 103 + mergeMap((kernel) => from(createMainChannel(kernel.config))), 104 + tap((mainChannel) => mainChannel.next(executeRequest(imports()))), 105 + tap((mainChannel) => mainChannel.next(executeRequest(utils()))), 108 106 map(JupyterActions.SetMainChannel) 109 107 ); 110 108 ··· 117 115 filter(isActionOf(JupyterActions.SetMainChannel)), 118 116 mergeMap<{}, ObservableInput<JupyterActionType>>(() => 119 117 state$.value.jupyter.mainChannel.pipe( 120 - map<object, JupyterActionType>(msg => { 118 + map<object, JupyterActionType>((msg) => { 121 119 console.log(debugParseMessage(msg)); 122 120 switch (msg['header']['msg_type']) { 123 121 case 'kernel_info_reply': ··· 134 132 default: 135 133 return JupyterActions.ReceiveDisplayData(msg); 136 134 } 137 - }), 138 - filter(action => !isNil(action)) 135 + }) 139 136 ) 140 137 ) 141 138 ); ··· 160 157 action$.pipe( 161 158 filter(isActionOf(JupyterActions.LoadEpochs)), 162 159 pluck('payload'), 163 - filter(filePathsArray => filePathsArray.length >= 1), 164 - map(filePathsArray => 160 + filter((filePathsArray) => filePathsArray.length >= 1), 161 + map((filePathsArray) => 165 162 state$.value.jupyter.mainChannel.next( 166 163 executeRequest(loadCSV(filePathsArray)) 167 164 ) ··· 175 172 [state$.value.experiment.params!.stimulus1!.title]: EVENTS.STIMULUS_1, 176 173 [state$.value.experiment.params!.stimulus2!.title]: EVENTS.STIMULUS_2, 177 174 [state$.value.experiment.params!.stimulus3!.title]: EVENTS.STIMULUS_3, 178 - [state$.value.experiment.params!.stimulus4!.title]: EVENTS.STIMULUS_4 175 + [state$.value.experiment.params!.stimulus4!.title]: EVENTS.STIMULUS_4, 179 176 }, 180 177 -0.1, 181 178 0.8 182 179 ) 183 180 ), 184 - tap(e => { 181 + tap((e) => { 185 182 console.log('e', e); 186 183 }), 187 - map(epochEventsCommand => 184 + map((epochEventsCommand) => 188 185 state$.value.jupyter.mainChannel.next(executeRequest(epochEventsCommand)) 189 186 ), 190 187 awaitOkMessage(action$), ··· 199 196 action$.pipe( 200 197 filter(isActionOf(JupyterActions.LoadCleanedEpochs)), 201 198 pluck('payload'), 202 - filter(filePathsArray => filePathsArray.length >= 1), 203 - map(filePathsArray => 199 + filter((filePathsArray) => filePathsArray.length >= 1), 200 + map((filePathsArray) => 204 201 state$.value.jupyter.mainChannel.next( 205 202 executeRequest(loadCleanedEpochs(filePathsArray)) 206 203 ) ··· 226 223 action$.ofType(JupyterActions.ReceiveStream.type).pipe( 227 224 pluck('payload'), 228 225 filter( 229 - msg => 226 + (msg) => 230 227 msg.channel === 'iopub' && 231 228 msg.content.text.includes('Channels marked as bad') 232 229 ), ··· 255 252 action$.pipe( 256 253 filter(isActionOf(JupyterActions.GetEpochsInfo)), 257 254 pluck('payload'), 258 - map(variableName => 255 + map((variableName) => 259 256 state$.value.jupyter.mainChannel.next( 260 257 executeRequest(requestEpochsInfo(variableName)) 261 258 ) ··· 263 260 mergeMap(() => 264 261 action$.ofType(JupyterActions.ReceiveExecuteReply.type).pipe( 265 262 pluck('payload'), 266 - filter(msg => msg.channel === 'iopub' && !isNil(msg.content.data)), 263 + filter((msg) => msg.channel === 'iopub' && !isNil(msg.content.data)), 267 264 pluck('content', 'data', 'text/plain'), 268 - filter(msg => msg.includes('Drop Percentage')), 265 + filter((msg) => msg.includes('Drop Percentage')), 269 266 take(1) 270 267 ) 271 268 ), 272 - map(epochInfoString => 273 - parseSingleQuoteJSON(epochInfoString).map(infoObj => ({ 269 + map((epochInfoString) => 270 + parseSingleQuoteJSON(epochInfoString).map((infoObj) => ({ 274 271 name: Object.keys(infoObj)[0], 275 - value: infoObj[Object.keys(infoObj)[0]] 272 + value: infoObj[Object.keys(infoObj)[0]], 276 273 })) 277 274 ), 278 275 map(JupyterActions.SetEpochInfo) ··· 289 286 mergeMap(() => 290 287 action$.ofType(JupyterActions.ReceiveExecuteResult.type).pipe( 291 288 pluck('payload'), 292 - filter(msg => msg.channel === 'iopub' && !isNil(msg.content.data)), 289 + filter((msg) => msg.channel === 'iopub' && !isNil(msg.content.data)), 293 290 pluck('content', 'data', 'text/plain'), // Filter to prevent this from reading requestEpochsInfo returns 294 - filter(msg => !msg.includes('Drop Percentage')), 291 + filter((msg) => !msg.includes('Drop Percentage')), 295 292 take(1) 296 293 ) 297 294 ), 298 - map(channelInfoString => 295 + map((channelInfoString) => 299 296 JupyterActions.SetChannelInfo(parseSingleQuoteJSON(channelInfoString)) 300 297 ) 301 298 ); ··· 310 307 mergeMap(() => 311 308 action$.ofType(JupyterActions.ReceiveDisplayData.type).pipe( 312 309 pluck('payload'), // PSD graphs should have two axes 313 - filter(msg => msg.content.data['text/plain'].includes('2 Axes')), 310 + filter((msg) => msg.content.data['text/plain'].includes('2 Axes')), 314 311 pluck('content', 'data'), 315 312 take(1) 316 313 ) ··· 330 327 .ofType(JupyterActions.ReceiveDisplayData.type) 331 328 .pipe(pluck('payload'), pluck('content', 'data'), take(1)) 332 329 ), 333 - mergeMap(topoPlot => 330 + mergeMap((topoPlot) => 334 331 of( 335 332 JupyterActions.SetTopoPlot(topoPlot), 336 333 JupyterActions.LoadERP( ··· 349 346 action$.pipe( 350 347 filter(isActionOf(JupyterActions.LoadERP)), 351 348 pluck('payload'), 352 - map(channelName => { 349 + map((channelName) => { 353 350 if (MUSE_CHANNELS.includes(channelName)) { 354 351 return MUSE_CHANNELS.indexOf(channelName); 355 352 } ··· 361 358 ); 362 359 return EMOTIV_CHANNELS[0]; 363 360 }), 364 - map(channelIndex => 361 + map((channelIndex) => 365 362 state$.value.jupyter.mainChannel.next( 366 363 executeRequest(plotERP(channelIndex)) 367 364 ) ··· 369 366 mergeMap(() => 370 367 action$.ofType(JupyterActions.ReceiveDisplayData.type).pipe( 371 368 pluck('payload'), // ERP graphs should have 1 axis according to MNE 372 - filter(msg => msg.content.data['text/plain'].includes('1 Axes')), 369 + filter((msg) => msg.content.data['text/plain'].includes('1 Axes')), 373 370 pluck('content', 'data'), 374 371 take(1) 375 372 )
+2 -2
app/index.tsx
··· 2 2 import { render } from 'react-dom'; 3 3 import { AppContainer as ReactHotAppContainer } from 'react-hot-loader'; 4 4 import Root from './containers/Root'; 5 - import { configureStore, history } from './store/configureStore'; 5 + import { configuredStore, history } from './store'; 6 6 import './app.global.css'; 7 7 8 - const store = configureStore(); 8 + const store = configuredStore(); 9 9 10 10 const AppContainer = process.env.PLAIN_HMR ? Fragment : ReactHotAppContainer; 11 11
+1 -1
app/main.dev.ts
··· 114 114 // eslint-disable-next-line 115 115 new AppUpdater(); 116 116 117 - mainWindow.setMenu(null); 117 + // mainWindow.setMenu(null); 118 118 // if (process.env.NODE_ENV === 'development') { 119 119 // mainWindow.webContents.openDevTools(); 120 120 // }
+11 -10
app/reducers/index.ts
··· 1 1 import { combineReducers } from 'redux'; 2 - import { routerReducer as router } from 'react-router-redux'; 2 + import { connectRouter } from 'connected-react-router'; 3 + import { History } from 'history'; 3 4 import jupyter, { JupyterStateType } from './jupyterReducer'; 4 5 import device, { DeviceStateType } from './deviceReducer'; 5 6 import experiment, { ExperimentStateType } from './experimentReducer'; ··· 8 9 jupyter: JupyterStateType; 9 10 device: DeviceStateType; 10 11 experiment: ExperimentStateType; 11 - router: unknown; 12 + router: any; 12 13 } 13 14 14 - const rootReducer = combineReducers({ 15 - jupyter, 16 - device, 17 - experiment, 18 - router 19 - }); 20 - 21 - export default rootReducer; 15 + export default function createRootReducer(history: History) { 16 + return combineReducers({ 17 + router: connectRouter(history), 18 + jupyter, 19 + device, 20 + experiment, 21 + }); 22 + }
+56
app/store.ts
··· 1 + import { configureStore, getDefaultMiddleware, Action } from '@reduxjs/toolkit'; 2 + import { createHashHistory } from 'history'; 3 + import { routerMiddleware } from 'connected-react-router'; 4 + import { createEpicMiddleware } from 'redux-observable'; 5 + import { createLogger } from 'redux-logger'; 6 + import { ThunkAction } from 'redux-thunk'; 7 + import createRootReducer from './reducers'; 8 + import rootEpic from './epics'; 9 + 10 + export const history = createHashHistory(); 11 + const rootReducer = createRootReducer(history); 12 + 13 + export type RootState = ReturnType<typeof rootReducer>; 14 + 15 + const router = routerMiddleware(history); 16 + const middleware = [...getDefaultMiddleware(), router]; 17 + 18 + // Redux Observable (Epic) Middleware 19 + const epicMiddleware = createEpicMiddleware(); 20 + middleware.push(epicMiddleware); 21 + 22 + const excludeLoggerEnvs = ['test', 'production']; 23 + const shouldIncludeLogger = !excludeLoggerEnvs.includes( 24 + process.env.NODE_ENV || '' 25 + ); 26 + 27 + if (shouldIncludeLogger) { 28 + const logger = createLogger({ 29 + level: 'info', 30 + collapsed: true, 31 + }); 32 + middleware.push(logger); 33 + } 34 + 35 + export const configuredStore = (initialState?: RootState) => { 36 + // Create Store 37 + const store = configureStore({ 38 + reducer: rootReducer, 39 + middleware, 40 + preloadedState: initialState, 41 + }); 42 + 43 + if (process.env.NODE_ENV === 'development' && module.hot) { 44 + module.hot.accept( 45 + './reducers', 46 + // eslint-disable-next-line global-require 47 + () => store.replaceReducer(require('./reducers').default) 48 + ); 49 + } 50 + 51 + epicMiddleware.run(rootEpic); 52 + return store; 53 + }; 54 + 55 + export type Store = ReturnType<typeof configuredStore>; 56 + export type AppThunk = ThunkAction<void, RootState, unknown, Action<string>>;
-78
app/store/configureStore.dev.ts
··· 1 - import { createStore, applyMiddleware, compose } from 'redux'; 2 - import thunk from 'redux-thunk'; 3 - import { createEpicMiddleware } from 'redux-observable'; 4 - import { createHashHistory } from 'history'; 5 - import { routerMiddleware, routerActions } from 'react-router-redux'; 6 - import { createLogger } from 'redux-logger'; 7 - import rootReducer, { RootState } from '../reducers'; 8 - import rootEpic from '../epics'; 9 - import { JupyterActions, DeviceActions } from '../actions'; 10 - 11 - const history = createHashHistory(); 12 - 13 - const configureStore = (initialState?: RootState) => { 14 - // Redux Configuration 15 - const middleware = []; 16 - const enhancers = []; 17 - 18 - // Thunk Middleware 19 - middleware.push(thunk); 20 - 21 - // Redux Observable (Epic) Middleware 22 - const epicMiddleware = createEpicMiddleware(); 23 - middleware.push(epicMiddleware); 24 - 25 - // Logging Middleware 26 - const logger = createLogger({ 27 - level: 'info', 28 - collapsed: true 29 - }); 30 - 31 - // Skip redux logs in console during the tests 32 - if (process.env.NODE_ENV !== 'test') { 33 - middleware.push(logger); 34 - } 35 - 36 - // Router Middleware 37 - const router = routerMiddleware(history); 38 - middleware.push(router); 39 - 40 - // Redux DevTools Configuration 41 - const actionCreators = { 42 - ...DeviceActions, 43 - ...JupyterActions, 44 - ...routerActions 45 - }; 46 - // If Redux DevTools Extension is installed use it, otherwise use Redux compose 47 - 48 - /* eslint-disable no-underscore-dangle */ 49 - const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 50 - ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 51 - // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html 52 - actionCreators 53 - }) 54 - : compose; 55 - 56 - /* eslint-enable no-underscore-dangle */ 57 - 58 - // Apply Middleware & Compose Enhancers 59 - enhancers.push(applyMiddleware(...middleware)); 60 - const enhancer = composeEnhancers(...enhancers); 61 - 62 - // Create Store 63 - const store = createStore(rootReducer, initialState, enhancer); 64 - 65 - if (module.hot) { 66 - module.hot.accept( 67 - '../reducers', 68 - () => store.replaceReducer(require('../reducers')) // eslint-disable-line global-require 69 - ); 70 - } 71 - 72 - // Redux Observable 73 - epicMiddleware.run(rootEpic); 74 - 75 - return store; 76 - }; 77 - 78 - export default { configureStore, history };
-21
app/store/configureStore.prod.ts
··· 1 - import { createStore, applyMiddleware } from 'redux'; 2 - import thunk from 'redux-thunk'; 3 - import { createHashHistory } from 'history'; 4 - import { createEpicMiddleware } from 'redux-observable'; 5 - import { routerMiddleware } from 'react-router-redux'; 6 - import rootEpic from '../epics'; 7 - 8 - import rootReducer from '../reducers'; 9 - 10 - const history = createHashHistory(); 11 - const epicMiddleware = createEpicMiddleware(); 12 - const router = routerMiddleware(history); 13 - const enhancer = applyMiddleware(thunk, router, epicMiddleware); 14 - 15 - function configureStore(initialState?: any) { 16 - const store = createStore(rootReducer, initialState, enhancer); 17 - epicMiddleware.run(rootEpic); 18 - return store; 19 - } 20 - 21 - export default { configureStore, history };
-11
app/store/configureStore.ts
··· 1 - import configureStoreDev from './configureStore.dev'; 2 - import configureStoreProd from './configureStore.prod'; 3 - 4 - const selectedConfigureStore = 5 - process.env.NODE_ENV === 'production' 6 - ? configureStoreProd 7 - : configureStoreDev; 8 - 9 - export const { configureStore } = selectedConfigureStore; 10 - 11 - export const { history } = selectedConfigureStore;
+1 -1
app/utils/eeg/cortex.ts
··· 18 18 * just pass information back and forth without doing much with it, with the 19 19 * exception of the login/auth flow, which we expose as the init() method. 20 20 */ 21 - const WebSocket = require('ws'); 21 + // const WebSocket = require('ws'); 22 22 const EventEmitter = require('events'); 23 23 24 24 const CORTEX_URL = 'wss://localhost:6868';