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.

Fixing things

+52 -40
+18 -11
app/components/ViewerComponent.tsx
··· 1 1 import React, { Component } from 'react'; 2 2 import { Subscription, Observable } from 'rxjs'; 3 3 import { isNil } from 'lodash'; 4 + import { WebviewTag } from 'electron'; 4 5 import { 5 6 MUSE_CHANNELS, 6 7 EMOTIV_CHANNELS, 7 8 DEVICES, 8 9 VIEWER_DEFAULTS, 9 10 } from '../constants/constants'; 11 + import { PipesEpoch } from '../constants/interfaces'; 10 12 11 13 const Mousetrap = require('mousetrap'); 12 14 13 15 interface Props { 14 - signalQualityObservable: Observable | null | undefined; 16 + signalQualityObservable: Observable<PipesEpoch>; 15 17 deviceType: DEVICES; 16 18 plottingInterval: number; 17 19 } ··· 23 25 } 24 26 25 27 class ViewerComponent extends Component<Props, State> { 26 - // graphView: ?HTMLElement; 27 - // signalQualitySubscription: Subscription; 28 + graphView: WebviewTag | null; 29 + 30 + signalQualitySubscription: Subscription | null; 31 + 28 32 constructor(props: Props) { 29 33 super(props); 30 34 this.state = { ··· 38 42 39 43 componentDidMount() { 40 44 this.graphView = document.querySelector('webview'); 41 - this.graphView.addEventListener('dom-ready', () => { 42 - this.graphView.send('initGraph', { 45 + this.graphView?.addEventListener('dom-ready', () => { 46 + this.graphView?.send('initGraph', { 43 47 plottingInterval: this.props.plottingInterval, 44 48 channels: this.state.channels, 45 49 domain: this.state.domain, ··· 66 70 : EMOTIV_CHANNELS, 67 71 }); 68 72 } 73 + if (!this.graphView) { 74 + return; 75 + } 69 76 if (this.state.channels !== prevState.channels) { 70 77 this.graphView.send('updateChannels', this.state.channels); 71 78 } ··· 89 96 } 90 97 91 98 setKeyListeners() { 92 - Mousetrap.bind('up', () => this.graphView.send('zoomIn')); 93 - Mousetrap.bind('down', () => this.graphView.send('zoomOut')); 99 + Mousetrap.bind('up', () => this.graphView?.send('zoomIn')); 100 + Mousetrap.bind('down', () => this.graphView?.send('zoomOut')); 94 101 } 95 102 96 103 subscribeToObservable(observable: any) { ··· 99 106 } 100 107 this.signalQualitySubscription = observable.subscribe( 101 108 (chunk) => { 102 - this.graphView.send('newData', chunk); 109 + this.graphView?.send('newData', chunk); 103 110 }, 104 111 (error) => new Error(`Error in epochSubscription ${error}`) 105 112 ); ··· 110 117 <webview 111 118 id="eegView" 112 119 src={`file://${__dirname}/viewer.html`} 113 - autosize="true" 114 - nodeintegration="true" 115 - plugins="true" 120 + autosize 121 + nodeintegration 122 + plugins 116 123 /> 117 124 ); 118 125 }
+10
app/constants/interfaces.ts
··· 136 136 samplingRate: number; 137 137 } 138 138 139 + export interface PipesEpoch { 140 + data: number[][]; 141 + signalQuality: { [channelName: string]: number }; 142 + info: { 143 + samplingRate: number; 144 + startTime: number; 145 + channelNames?: string[]; 146 + }; 147 + } 148 + 139 149 // -------------------------------------------------------------------- 140 150 // General 141 151
app/utils/eeg/cortex.ts app/utils/eeg/cortex.js
+1 -1
app/utils/eeg/emotiv.ts
··· 17 17 const options = { verbose }; 18 18 19 19 // This global client is used in every Cortex API call 20 - const client = new Cortex(options); 20 + const client: any = new Cortex(options); 21 21 22 22 // This global session is how I'm passing data between connectToEmotiv and createRawEmotivObservable 23 23 // I'm not a fan of doing this but I don't want to refactor the Redux store based on this API change that
+3 -2
app/utils/eeg/pipes.ts
··· 4 4 SIGNAL_QUALITY, 5 5 SIGNAL_QUALITY_THRESHOLDS, 6 6 } from '../../constants/constants'; 7 + import { PipesEpoch } from '../../constants/interfaces'; 7 8 8 9 export const parseMuseSignalQuality = () => 9 10 pipe( 10 - map((epoch: Record<string, any>) => ({ 11 + map((epoch: PipesEpoch) => ({ 11 12 ...epoch, 12 13 signalQuality: Object.assign( 13 14 {}, ··· 31 32 32 33 export const parseEmotivSignalQuality = () => 33 34 pipe( 34 - map((epoch: Record<string, any>) => ({ 35 + map((epoch: PipesEpoch) => ({ 35 36 ...epoch, 36 37 signalQuality: Object.assign( 37 38 {},
+19 -25
app/utils/filesystem/dialog.ts
··· 3 3 * These functions are all executed in the main process 4 4 */ 5 5 6 - import { dialog } from 'electron'; 6 + import { remote } from 'electron'; 7 7 import { FILE_TYPES } from '../../constants/constants'; 8 + 9 + const { dialog } = remote; 8 10 9 11 export const loadDialog = (event, arg) => { 10 12 switch (arg) { ··· 18 20 }; 19 21 20 22 const selectTimeline = (event) => { 21 - dialog.showOpenDialog( 22 - { 23 - title: 'Select a jsPsych timeline file', 24 - properties: ['openFile', 'promptToCreate'], 25 - }, 26 - (filePaths) => { 27 - if (filePaths) { 28 - event.sender.send('loadDialogReply', filePaths[0]); 29 - } 30 - } 31 - ); 23 + const filePaths = dialog.showOpenDialog({ 24 + title: 'Select a jsPsych timeline file', 25 + properties: ['openFile', 'promptToCreate'], 26 + }); 27 + if (filePaths) { 28 + event.sender.send('loadDialogReply', filePaths[0]); 29 + } 32 30 }; 33 31 34 32 const selectStimulusFolder = (event) => { 35 - dialog.showOpenDialog( 36 - { 37 - title: 'Select a folder of images', 38 - properties: ['openDirectory'], 39 - }, 40 - (dir) => { 41 - if (dir) { 42 - event.sender.send('loadDialogReply', dir[0]); 43 - } else { 44 - event.sender.send('loadDialogReply', ''); 45 - } 46 - } 47 - ); 33 + const dirs = dialog.showOpenDialog({ 34 + title: 'Select a folder of images', 35 + properties: ['openDirectory'], 36 + }); 37 + if (dirs) { 38 + event.sender.send('loadDialogReply', dirs[0]); 39 + } else { 40 + event.sender.send('loadDialogReply', ''); 41 + } 48 42 };
+1 -1
app/utils/jupyter/functions.ts
··· 32 32 } 33 33 34 34 if (msg.content.data) { 35 - content = Object.keys(msg.content.data); 35 + content = JSON.stringify(Object.keys(msg.content.data)); 36 36 } 37 37 38 38 break;