diff --git a/.gitignore b/.gitignore index 360a787b..8e4e0753 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,5 @@ main.js.map .idea npm-debug.log.* keys.js + +app/utils/pyodide/src diff --git a/app/actions/jupyterActions.js b/app/actions/pyodideActions.js similarity index 53% rename from app/actions/jupyterActions.js rename to app/actions/pyodideActions.js index f3234fd3..f4c2efdb 100644 --- a/app/actions/jupyterActions.js +++ b/app/actions/pyodideActions.js @@ -1,23 +1,23 @@ // ------------------------------------------------------------------------- // Action Types -export const LAUNCH_KERNEL = 'LAUNCH_KERNEL'; -export const REQUEST_KERNEL_INFO = 'REQUEST_KERNEL_INFO'; -export const SEND_EXECUTE_REQUEST = 'SEND_EXECUTE_REQUEST'; -export const LOAD_EPOCHS = 'LOAD_EPOCHS'; -export const LOAD_CLEANED_EPOCHS = 'LOAD_CLEANED_EPOCHS'; -export const LOAD_PSD = 'LOAD_PSD'; -export const LOAD_ERP = 'LOAD_ERP'; -export const LOAD_TOPO = 'LOAD_TOPO'; -export const CLEAN_EPOCHS = 'CLEAN_EPOCHS'; -export const CLOSE_KERNEL = 'CLOSE_KERNEL'; +export const LAUNCH = "LAUNCH"; +export const SEND_EXECUTE_REQUEST = "SEND_EXECUTE_REQUEST"; +export const LOAD_EPOCHS = "LOAD_EPOCHS"; +export const LOAD_CLEANED_EPOCHS = "LOAD_CLEANED_EPOCHS"; +export const LOAD_PSD = "LOAD_PSD"; +export const LOAD_ERP = "LOAD_ERP"; +export const LOAD_TOPO = "LOAD_TOPO"; +export const CLEAN_EPOCHS = "CLEAN_EPOCHS"; // ------------------------------------------------------------------------- // Actions -export const launchKernel = () => ({ type: LAUNCH_KERNEL }); -export const requestKernelInfo = () => ({ type: REQUEST_KERNEL_INFO }); +export const launch = () => ({ + type: LAUNCH +}); + export const sendExecuteRequest = (payload: string) => ({ payload, @@ -48,5 +48,3 @@ export const loadTopo = () => ({ }); export const cleanEpochs = () => ({ type: CLEAN_EPOCHS }); - -export const closeKernel = () => ({ type: CLOSE_KERNEL }); diff --git a/app/components/CleanComponent/index.js b/app/components/CleanComponent/index.js index 3890089a..7eeca11d 100644 --- a/app/components/CleanComponent/index.js +++ b/app/components/CleanComponent/index.js @@ -14,8 +14,7 @@ import { import { Link } from 'react-router-dom'; import { isNil } from 'lodash'; import styles from './../styles/collect.css'; -import { EXPERIMENTS, DEVICES, KERNEL_STATUS } from '../../constants/constants'; -import { Kernel } from '../../constants/interfaces'; +import { EXPERIMENTS, DEVICES } from '../../constants/constants'; import { readWorkspaceRawEEGData } from '../../utils/filesystem/storage'; import CleanSidebar from './CleanSidebar'; import * as path from 'path'; @@ -25,10 +24,8 @@ interface Props { title: string; deviceType: DEVICES; mainChannel: ?any; - kernel: ?Kernel; - kernelStatus: KERNEL_STATUS; epochsInfo: ?Array<{ [string]: number | string }>; - jupyterActions: Object; + pyodideActions: Object; experimentActions: Object; subject: string; session: number; @@ -107,7 +104,7 @@ export default class Clean extends Component { handleLoadData() { this.props.experimentActions.setSubject(this.state.selectedSubject); - this.props.jupyterActions.loadEpochs(this.state.selectedFilePaths); + this.props.pyodideActions.loadEpochs(this.state.selectedFilePaths); } handleSidebarToggle() { @@ -200,11 +197,6 @@ export default class Clean extends Component { diff --git a/app/components/HomeComponent/index.js b/app/components/HomeComponent/index.js index debbc88f..3362a1a9 100644 --- a/app/components/HomeComponent/index.js +++ b/app/components/HomeComponent/index.js @@ -6,13 +6,7 @@ import { toast } from 'react-toastify'; import * as moment from 'moment'; import styles from '../styles/common.css'; -import { - EXPERIMENTS, - SCREENS, - KERNEL_STATUS, - CONNECTION_STATUS, - DEVICE_AVAILABILITY, -} from '../../constants/constants'; +import { EXPERIMENTS, SCREENS } from '../../constants/constants'; import faceHouseIcon from '../../assets/common/FacesHouses.png'; import stroopIcon from '../../assets/common/Stroop.png'; import multitaskingIcon from '../../assets/common/Multitasking.png'; @@ -30,12 +24,6 @@ import { import InputModal from '../InputModal'; import SecondaryNavComponent from '../SecondaryNavComponent'; import OverviewComponent from './OverviewComponent'; -import { loadProtocol } from '../../utils/labjs/functions'; -import EEGExplorationComponent from '../EEGExplorationComponent'; - -import { remote } from 'electron'; - -const { dialog } = remote; const HOME_STEPS = { // TODO: maybe change the recent and new labels, but not necessary right now @@ -45,17 +33,11 @@ const HOME_STEPS = { }; interface Props { - kernelStatus: KERNEL_STATUS; history: Object; - jupyterActions: Object; - connectedDevice: Object; - signalQualityObservable: ?any; - deviceType: DEVICES; - deviceAvailability: DEVICE_AVAILABILITY; - connectionStatus: CONNECTION_STATUS; deviceActions: Object; availableDevices: Array; experimentActions: Object; + pyodideActions: Object; } interface State { @@ -94,6 +76,7 @@ export default class Home extends Component { } componentDidMount() { + this.props.pyodideActions.launch(); this.setState({ recentWorkspaces: readWorkspaces() }); } diff --git a/app/components/JupyterPlotWidget.js b/app/components/JupyterPlotWidget.js index 6f092705..66bb2a2f 100644 --- a/app/components/JupyterPlotWidget.js +++ b/app/components/JupyterPlotWidget.js @@ -1,9 +1,8 @@ // @flow -import React, { Component } from 'react'; -import { Segment, Button } from 'semantic-ui-react'; -import { richestMimetype, standardDisplayOrder, standardTransforms } from '@nteract/transforms'; -import { isNil } from 'lodash'; -import { storeJupyterImage } from '../utils/filesystem/storage'; +import React, { Component } from "react"; +import { Segment, Button } from "semantic-ui-react"; +import { isNil } from "lodash"; +import { storePyodideImage } from "../utils/filesystem/storage"; interface Props { title: string; @@ -16,7 +15,7 @@ interface State { mimeType: string; } -export default class JupyterPlotWidget extends Component { +export default class PyodidePlotWidget extends Component { props: Props; state: State; constructor(props: Props) { @@ -42,8 +41,8 @@ export default class JupyterPlotWidget extends Component { } handleSave() { - const buf = Buffer.from(this.state.rawData, 'base64'); - storeJupyterImage(this.props.title, this.props.imageTitle, buf); + const buf = Buffer.from(this.state.rawData, "base64"); + storePyodideImage(this.props.title, this.props.imageTitle, buf); } renderResults() { diff --git a/app/constants/constants.js b/app/constants/constants.js index e0fefd22..4c68cf65 100644 --- a/app/constants/constants.js +++ b/app/constants/constants.js @@ -20,39 +20,32 @@ export const SCREENS = { }; export const DEVICES = { - NONE: 'NONE', - MUSE: 'MUSE', - EMOTIV: 'EMOTIV', - GANGLION: 'GANGLION', // One day ;) + NONE: "NONE", + MUSE: "MUSE", + EMOTIV: "EMOTIV", + GANGLION: "GANGLION" // One day ;) }; export const CONNECTION_STATUS = { - CONNECTED: 'CONNECTED', - CONNECTING: 'CONNECTING', - DISCONNECTED: 'DISCONNECTED', - NO_DEVICES: 'NO_DEVICES', - NOT_YET_CONNECTED: 'NOT_YET_CONNECTED', - SEARCHING: 'SEARCHING', - BLUETOOTH_DISABLED: 'BLUETOOTH_DISABLED', -}; - -export const KERNEL_STATUS = { - OFFLINE: 'Offline', - BUSY: 'Busy', - IDLE: 'Idle', - STARTING: 'Starting', + CONNECTED: "CONNECTED", + CONNECTING: "CONNECTING", + DISCONNECTED: "DISCONNECTED", + NO_DEVICES: "NO_DEVICES", + NOT_YET_CONNECTED: "NOT_YET_CONNECTED", + SEARCHING: "SEARCHING", + BLUETOOTH_DISABLED: "BLUETOOTH_DISABLED" }; export const DEVICE_AVAILABILITY = { - NONE: 'NONE', - SEARCHING: 'SEARCHING', - AVAILABLE: 'AVAILABLE', + NONE: "NONE", + SEARCHING: "SEARCHING", + AVAILABLE: "AVAILABLE" }; -// Names of variables in the jupyter kernel -export const JUPYTER_VARIABLE_NAMES = { - RAW_EPOCHS: 'raw_epochs', - CLEAN_EPOCHS: 'clean_epochs', +// Names of variables in pyodide +export const PYODIDE_VARIABLE_NAMES = { + RAW_EPOCHS: "raw_epochs", + CLEAN_EPOCHS: "clean_epochs" }; export const SEARCH_TIMER = 3000; @@ -67,46 +60,46 @@ export const EVENTS = { export const CHANNELS = { // Epoc channels - AF3: { index: 0, color: '#9B6ABC' }, - F7: { index: 1, color: '#7EA0C5' }, - F3: { index: 2, color: '#8BD6E9' }, - FC5: { index: 3, color: '#66B0A9' }, - T7: { index: 4, color: '#E7789E' }, - P7: { index: 5, color: '#F1A766' }, - O1: { index: 6, color: '#FFDA6A' }, - O2: { index: 7, color: '#F8F8F8' }, - P8: { index: 8, color: '#F8F8F8' }, - T8: { index: 9, color: '#F8F8F8' }, - FC6: { index: 10, color: '#F8F8F8' }, - F4: { index: 11, color: '#F8F8F8' }, - F8: { index: 12, color: '#F8F8F8' }, - AF4: { index: 13, color: '#F8F8F8' }, + AF3: { index: 0, color: "#9B6ABC" }, + F7: { index: 1, color: "#7EA0C5" }, + F3: { index: 2, color: "#8BD6E9" }, + FC5: { index: 3, color: "#66B0A9" }, + T7: { index: 4, color: "#E7789E" }, + P7: { index: 5, color: "#F1A766" }, + O1: { index: 6, color: "#FFDA6A" }, + O2: { index: 7, color: "#F8F8F8" }, + P8: { index: 8, color: "#F8F8F8" }, + T8: { index: 9, color: "#F8F8F8" }, + FC6: { index: 10, color: "#F8F8F8" }, + F4: { index: 11, color: "#F8F8F8" }, + F8: { index: 12, color: "#F8F8F8" }, + AF4: { index: 13, color: "#F8F8F8" }, // Muse channels - TP9: { index: 0, color: '#9B6ABC' }, - AF7: { index: 1, color: '#7EA0C5' }, - AF8: { index: 2, color: '#8BD6E9' }, - TP10: { index: 3, color: '#66B0A9' }, - AUX: { index: 4, color: '#E7789E' }, + TP9: { index: 0, color: "#9B6ABC" }, + AF7: { index: 1, color: "#7EA0C5" }, + AF8: { index: 2, color: "#8BD6E9" }, + TP10: { index: 3, color: "#66B0A9" }, + AUX: { index: 4, color: "#E7789E" } }; export const EMOTIV_CHANNELS = [ - 'AF3', - 'F7', - 'F3', - 'FC5', - 'T7', - 'P7', - 'O1', - 'O2', - 'P8', - 'T8', - 'FC6', - 'F4', - 'F8', - 'AF4', + "AF3", + "F7", + "F3", + "FC5", + "T7", + "P7", + "O1", + "O2", + "P8", + "T8", + "FC6", + "F4", + "F8", + "AF4" ]; -export const MUSE_CHANNELS = ['TP9', 'AF7', 'AF8', 'TP10', 'AUX']; +export const MUSE_CHANNELS = ["TP9", "AF7", "AF8", "TP10", "AUX"]; export const ZOOM_SCALAR = 1.5; export const MUSE_SAMPLING_RATE = 256; @@ -120,10 +113,10 @@ export const VIEWER_DEFAULTS = { }; export const SIGNAL_QUALITY = { - BAD: '#ed5a5a', - OK: '#FFCD39', - GREAT: '#66B0A9', - DISCONNECTED: '#BFBFBF', + BAD: "#ed5a5a", + OK: "#FFCD39", + GREAT: "#66B0A9", + DISCONNECTED: "#BFBFBF" }; export const SIGNAL_QUALITY_THRESHOLDS = { @@ -133,6 +126,14 @@ export const SIGNAL_QUALITY_THRESHOLDS = { }; export const FILE_TYPES = { - STIMULUS_DIR: 'STIMULUS_DIR', - TIMELINE: 'TIMELINE', + STIMULUS_DIR: "STIMULUS_DIR", + TIMELINE: "TIMELINE" +}; + +// ----------------------------------------------- +// Pyodide + +export const PYODIDE_STATUS = { + LOADED: "LOADED", + NOT_LOADED: "NOT_LOADED" }; diff --git a/app/constants/interfaces.js b/app/constants/interfaces.js index 6400ad0c..480ba058 100644 --- a/app/constants/interfaces.js +++ b/app/constants/interfaces.js @@ -61,16 +61,6 @@ export interface SampleParameter { export type StimulusVariable = () => any; -// -------------------------------------------------------------------- -// Jupyter - -export interface Kernel { - config: Object; - connectionFile: string; - kernelSpec: Object; - spawn: ChildProcess; -} - // -------------------------------------------------------------------- // Device diff --git a/app/containers/AnalyzeContainer.js b/app/containers/AnalyzeContainer.js index 81010f30..72fe9d52 100644 --- a/app/containers/AnalyzeContainer.js +++ b/app/containers/AnalyzeContainer.js @@ -3,7 +3,7 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import Analyze from '../components/AnalyzeComponent'; import * as experimentActions from '../actions/experimentActions'; -import * as jupyterActions from '../actions/jupyterActions'; +import * as pyodideActions from '../actions/pyodideActions'; function mapStateToProps(state) { return { @@ -11,14 +11,14 @@ function mapStateToProps(state) { type: state.experiment.type, deviceType: state.device.deviceType, isEEGEnabled: state.experiment.isEEGEnabled, - ...state.jupyter, + ...state.pyodide }; } function mapDispatchToProps(dispatch) { return { experimentActions: bindActionCreators(experimentActions, dispatch), - jupyterActions: bindActionCreators(jupyterActions, dispatch), + pyodideActions: bindActionCreators(pyodideActions, dispatch) }; } diff --git a/app/containers/CleanContainer.js b/app/containers/CleanContainer.js index db8b3660..12858081 100644 --- a/app/containers/CleanContainer.js +++ b/app/containers/CleanContainer.js @@ -3,7 +3,7 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import CleanComponent from '../components/CleanComponent'; import * as experimentActions from '../actions/experimentActions'; -import * as jupyterActions from '../actions/jupyterActions'; +import * as pyodideActions from '../actions/pyodideActions'; function mapStateToProps(state) { return { @@ -13,14 +13,14 @@ function mapStateToProps(state) { group: state.experiment.group, session: state.experiment.session, deviceType: state.device.deviceType, - ...state.jupyter, + ...state.pyodide }; } function mapDispatchToProps(dispatch) { return { experimentActions: bindActionCreators(experimentActions, dispatch), - jupyterActions: bindActionCreators(jupyterActions, dispatch), + pyodideActions: bindActionCreators(pyodideActions, dispatch) }; } diff --git a/app/containers/HomeContainer.js b/app/containers/HomeContainer.js index 48ddbe74..aa57af8f 100644 --- a/app/containers/HomeContainer.js +++ b/app/containers/HomeContainer.js @@ -3,20 +3,19 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import Home from '../components/HomeComponent'; import * as deviceActions from '../actions/deviceActions'; -import * as jupyterActions from '../actions/jupyterActions'; +import * as pyodideActions from '../actions/pyodideActions'; import * as experimentActions from '../actions/experimentActions'; function mapStateToProps(state) { return { - ...state.device, - kernelStatus: state.jupyter.kernelStatus, + availableDevices: state.device.availableDevices, }; } function mapDispatchToProps(dispatch) { return { deviceActions: bindActionCreators(deviceActions, dispatch), - jupyterActions: bindActionCreators(jupyterActions, dispatch), + pyodideActions: bindActionCreators(pyodideActions, dispatch), experimentActions: bindActionCreators(experimentActions, dispatch), }; } diff --git a/app/epics/index.js b/app/epics/index.js index 076d6cbe..6d240a4b 100644 --- a/app/epics/index.js +++ b/app/epics/index.js @@ -1,6 +1,6 @@ import { combineEpics } from 'redux-observable'; -import jupyter from './jupyterEpics'; +import pyodide from './pyodideEpics'; import device from './deviceEpics'; import experiment from './experimentEpics'; -export default combineEpics(device, jupyter, experiment); +export default combineEpics(device, pyodide, experiment); diff --git a/app/epics/jupyterEpics.js b/app/epics/jupyterEpics.js deleted file mode 100644 index c6b9b6b4..00000000 --- a/app/epics/jupyterEpics.js +++ /dev/null @@ -1,410 +0,0 @@ -import { combineEpics } from 'redux-observable'; -import { from, of } from 'rxjs'; -import { map, mergeMap, tap, pluck, ignoreElements, filter, take } from 'rxjs/operators'; -import { find } from 'kernelspecs'; -import { launchSpec } from 'spawnteract'; -import { createMainChannel } from 'enchannel-zmq-backend'; -import { isNil } from 'lodash'; -import { kernelInfoRequest, executeRequest } from '@nteract/messaging'; -import { toast } from 'react-toastify'; -import { execute, awaitOkMessage } from '../utils/jupyter/pipes'; -import { getWorkspaceDir } from '../utils/filesystem/storage'; -import { - LAUNCH_KERNEL, - REQUEST_KERNEL_INFO, - LOAD_EPOCHS, - LOAD_CLEANED_EPOCHS, - LOAD_PSD, - LOAD_ERP, - LOAD_TOPO, - CLEAN_EPOCHS, - CLOSE_KERNEL, - loadTopo, - loadERP, -} from '../actions/jupyterActions'; -import { - imports, - utils, - loadCSV, - loadCleanedEpochs, - filterIIR, - epochEvents, - requestEpochsInfo, - requestChannelInfo, - cleanEpochsPlot, - plotPSD, - plotERP, - plotTopoMap, - saveEpochs, -} from '../utils/jupyter/cells'; -import { - EMOTIV_CHANNELS, - EVENTS, - DEVICES, - MUSE_CHANNELS, - JUPYTER_VARIABLE_NAMES, -} from '../constants/constants'; -import { - parseSingleQuoteJSON, - parseKernelStatus, - debugParseMessage, -} from '../utils/jupyter/functions'; - -export const GET_EPOCHS_INFO = 'GET_EPOCHS_INFO'; -export const GET_CHANNEL_INFO = 'GET_CHANNEL_INFO'; -export const SET_KERNEL = 'SET_KERNEL'; -export const SET_KERNEL_STATUS = 'SET_KERNEL_STATUS'; -export const SET_KERNEL_INFO = 'SET_KERNEL_INFO'; -export const SET_MAIN_CHANNEL = 'SET_MAIN_CHANNEL'; -export const SET_EPOCH_INFO = 'SET_EPOCH_INFO'; -export const SET_CHANNEL_INFO = 'SET_CHANNEL_INFO'; -export const SET_PSD_PLOT = 'SET_PSD_PLOT'; -export const SET_ERP_PLOT = 'SET_ERP_PLOT'; -export const SET_TOPO_PLOT = 'SET_TOPO_PLOT'; -export const RECEIVE_EXECUTE_REPLY = 'RECEIVE_EXECUTE_REPLY'; -export const RECEIVE_EXECUTE_RESULT = 'RECEIVE_EXECUTE_RESULT'; -export const RECEIVE_STREAM = 'RECEIVE_STREAM'; -export const RECEIVE_DISPLAY_DATA = 'RECEIVE_DISPLAY_DATA'; - -// ------------------------------------------------------------------------- -// Action Creators - -const getEpochsInfo = (payload) => ({ payload, type: GET_EPOCHS_INFO }); - -const getChannelInfo = () => ({ type: GET_CHANNEL_INFO }); - -const setKernel = (payload) => ({ - payload, - type: SET_KERNEL, -}); - -const setKernelStatus = (payload) => ({ - payload, - type: SET_KERNEL_STATUS, -}); - -const setKernelInfo = (payload) => ({ - payload, - type: SET_KERNEL_INFO, -}); - -const setMainChannel = (payload) => ({ - payload, - type: SET_MAIN_CHANNEL, -}); - -const setEpochInfo = (payload) => ({ - payload, - type: SET_EPOCH_INFO, -}); - -const setChannelInfo = (payload) => ({ - payload, - type: SET_CHANNEL_INFO, -}); - -const setPSDPlot = (payload) => ({ - payload, - type: SET_PSD_PLOT, -}); - -const setTopoPlot = (payload) => ({ - payload, - type: SET_TOPO_PLOT, -}); - -const setERPPlot = (payload) => ({ - payload, - type: SET_ERP_PLOT, -}); - -const receiveExecuteReply = (payload) => ({ - payload, - type: RECEIVE_EXECUTE_REPLY, -}); - -const receiveExecuteResult = (payload) => ({ - payload, - type: RECEIVE_EXECUTE_RESULT, -}); - -const receiveDisplayData = (payload) => ({ - payload, - type: RECEIVE_DISPLAY_DATA, -}); - -const receiveStream = (payload) => ({ - payload, - type: RECEIVE_STREAM, -}); - -// ------------------------------------------------------------------------- -// Epics - -const launchEpic = (action$) => - action$.ofType(LAUNCH_KERNEL).pipe( - mergeMap(() => from(find('brainwaves'))), - tap((kernelInfo) => { - if (isNil(kernelInfo)) { - toast.error("Could not find 'brainwaves' jupyter kernel. Have you installed Python?"); - } - }), - filter((kernelInfo) => !isNil(kernelInfo)), - mergeMap((kernelInfo) => - from( - launchSpec(kernelInfo.spec, { - // No STDIN, opt in to STDOUT and STDERR as node streams - stdio: ['ignore', 'pipe', 'pipe'], - }) - ) - ), - tap((kernel) => { - // Route everything that we won't get in messages to our own stdout - kernel.spawn.stdout.on('data', (data) => { - const text = data.toString(); - console.log('KERNEL STDOUT: ', text); - }); - kernel.spawn.stderr.on('data', (data) => { - const text = data.toString(); - console.log('KERNEL STDERR: ', text); - toast.error('Jupyter: ', text); - }); - - kernel.spawn.on('close', () => { - console.log('Kernel closed'); - }); - }), - map(setKernel) - ); - -const setUpChannelEpic = (action$) => - action$.ofType(SET_KERNEL).pipe( - pluck('payload'), - mergeMap((kernel) => from(createMainChannel(kernel.config))), - tap((mainChannel) => mainChannel.next(executeRequest(imports()))), - tap((mainChannel) => mainChannel.next(executeRequest(utils()))), - map(setMainChannel) - ); - -const receiveChannelMessageEpic = (action$, state$) => - action$.ofType(SET_MAIN_CHANNEL).pipe( - mergeMap(() => - state$.value.jupyter.mainChannel.pipe( - map((msg) => { - console.log(debugParseMessage(msg)); - switch (msg['header']['msg_type']) { - case 'kernel_info_reply': - return setKernelInfo(msg); - case 'status': - return setKernelStatus(parseKernelStatus(msg)); - case 'stream': - return receiveStream(msg); - case 'execute_reply': - return receiveExecuteReply(msg); - case 'execute_result': - return receiveExecuteResult(msg); - case 'display_data': - return receiveDisplayData(msg); - default: - } - }), - filter((action) => !isNil(action)) - ) - ) - ); - -const requestKernelInfoEpic = (action$, state$) => - action$.ofType(REQUEST_KERNEL_INFO).pipe( - filter(() => state$.value.jupyter.mainChannel), - map(() => state$.value.jupyter.mainChannel.next(kernelInfoRequest())), - ignoreElements() - ); - -const loadEpochsEpic = (action$, state$) => - action$.ofType(LOAD_EPOCHS).pipe( - pluck('payload'), - filter((filePathsArray) => filePathsArray.length >= 1), - map((filePathsArray) => - state$.value.jupyter.mainChannel.next(executeRequest(loadCSV(filePathsArray))) - ), - awaitOkMessage(action$), - execute(filterIIR(1, 30), state$), - awaitOkMessage(action$), - map(() => - epochEvents( - { - [state$.value.experiment.params.stimulus1.title]: EVENTS.STIMULUS_1, - [state$.value.experiment.params.stimulus2.title]: EVENTS.STIMULUS_2, - }, - -0.1, - 0.8 - ) - ), - map((epochEventsCommand) => - state$.value.jupyter.mainChannel.next(executeRequest(epochEventsCommand)) - ), - awaitOkMessage(action$), - map(() => getEpochsInfo(JUPYTER_VARIABLE_NAMES.RAW_EPOCHS)) - ); - -const loadCleanedEpochsEpic = (action$, state$) => - action$.ofType(LOAD_CLEANED_EPOCHS).pipe( - pluck('payload'), - filter((filePathsArray) => filePathsArray.length >= 1), - map((filePathsArray) => - state$.value.jupyter.mainChannel.next(executeRequest(loadCleanedEpochs(filePathsArray))) - ), - awaitOkMessage(action$), - mergeMap(() => - of(getEpochsInfo(JUPYTER_VARIABLE_NAMES.CLEAN_EPOCHS), getChannelInfo(), loadTopo()) - ) - ); - -const cleanEpochsEpic = (action$, state$) => - action$.ofType(CLEAN_EPOCHS).pipe( - execute(cleanEpochsPlot(), state$), - mergeMap(() => - action$.ofType(RECEIVE_STREAM).pipe( - pluck('payload'), - filter( - (msg) => msg.channel === 'iopub' && msg.content.text.includes('Channels marked as bad') - ), - take(1) - ) - ), - map(() => - state$.value.jupyter.mainChannel.next( - executeRequest( - saveEpochs( - getWorkspaceDir(state$.value.experiment.title), - state$.value.experiment.subject - ) - ) - ) - ), - awaitOkMessage(action$), - map(() => getEpochsInfo(JUPYTER_VARIABLE_NAMES.RAW_EPOCHS)) - ); - -const getEpochsInfoEpic = (action$, state$) => - action$.ofType(GET_EPOCHS_INFO).pipe( - pluck('payload'), - map((variableName) => - state$.value.jupyter.mainChannel.next(executeRequest(requestEpochsInfo(variableName))) - ), - mergeMap(() => - action$.ofType(RECEIVE_EXECUTE_RESULT).pipe( - pluck('payload'), - filter((msg) => msg.channel === 'iopub' && !isNil(msg.content.data)), - pluck('content', 'data', 'text/plain'), - filter((msg) => msg.includes('Drop Percentage')), - take(1) - ) - ), - map((epochInfoString) => - parseSingleQuoteJSON(epochInfoString).map((infoObj) => ({ - name: Object.keys(infoObj)[0], - value: infoObj[Object.keys(infoObj)[0]], - })) - ), - map(setEpochInfo) - ); - -const getChannelInfoEpic = (action$, state$) => - action$.ofType(GET_CHANNEL_INFO).pipe( - execute(requestChannelInfo(), state$), - mergeMap(() => - action$.ofType(RECEIVE_EXECUTE_RESULT).pipe( - pluck('payload'), - filter((msg) => msg.channel === 'iopub' && !isNil(msg.content.data)), - pluck('content', 'data', 'text/plain'), - // Filter to prevent this from reading requestEpochsInfo returns - filter((msg) => !msg.includes('Drop Percentage')), - take(1) - ) - ), - map((channelInfoString) => setChannelInfo(parseSingleQuoteJSON(channelInfoString))) - ); - -const loadPSDEpic = (action$, state$) => - action$.ofType(LOAD_PSD).pipe( - execute(plotPSD(), state$), - mergeMap(() => - action$.ofType(RECEIVE_DISPLAY_DATA).pipe( - pluck('payload'), - // PSD graphs should have two axes - filter((msg) => msg.content.data['text/plain'].includes('2 Axes')), - pluck('content', 'data'), - take(1) - ) - ), - map(setPSDPlot) - ); - -const loadTopoEpic = (action$, state$) => - action$.ofType(LOAD_TOPO).pipe( - execute(plotTopoMap(), state$), - mergeMap(() => - action$.ofType(RECEIVE_DISPLAY_DATA).pipe(pluck('payload'), pluck('content', 'data'), take(1)) - ), - mergeMap((topoPlot) => - of( - setTopoPlot(topoPlot), - loadERP( - state$.value.device.deviceType === DEVICES.EMOTIV ? EMOTIV_CHANNELS[0] : MUSE_CHANNELS[0] - ) - ) - ) - ); - -const loadERPEpic = (action$, state$) => - action$.ofType(LOAD_ERP).pipe( - pluck('payload'), - map((channelName) => { - if (MUSE_CHANNELS.includes(channelName)) { - return MUSE_CHANNELS.indexOf(channelName); - } else if (EMOTIV_CHANNELS.includes(channelName)) { - return EMOTIV_CHANNELS.indexOf(channelName); - } - console.warn('channel name supplied to loadERPEpic does not belong to either device'); - return EMOTIV_CHANNELS[0]; - }), - map((channelIndex) => - state$.value.jupyter.mainChannel.next(executeRequest(plotERP(channelIndex))) - ), - mergeMap(() => - action$.ofType(RECEIVE_DISPLAY_DATA).pipe( - pluck('payload'), - // ERP graphs should have 1 axis according to MNE - filter((msg) => msg.content.data['text/plain'].includes('1 Axes')), - pluck('content', 'data'), - take(1) - ) - ), - map(setERPPlot) - ); - -const closeKernelEpic = (action$, state$) => - action$.ofType(CLOSE_KERNEL).pipe( - map(() => { - state$.value.jupyter.kernel.spawn.kill(); - state$.value.jupyter.mainChannel.complete(); - }), - ignoreElements() - ); - -export default combineEpics( - launchEpic, - setUpChannelEpic, - requestKernelInfoEpic, - receiveChannelMessageEpic, - loadEpochsEpic, - loadCleanedEpochsEpic, - cleanEpochsEpic, - getEpochsInfoEpic, - getChannelInfoEpic, - loadPSDEpic, - loadTopoEpic, - loadERPEpic, - closeKernelEpic -); diff --git a/app/epics/pyodideEpics.js b/app/epics/pyodideEpics.js new file mode 100644 index 00000000..d31aaf89 --- /dev/null +++ b/app/epics/pyodideEpics.js @@ -0,0 +1,206 @@ +import { combineEpics } from 'redux-observable'; +import { of } from 'rxjs'; +import { map, mergeMap, tap, pluck, filter } from 'rxjs/operators'; +import { getWorkspaceDir } from '../utils/filesystem/storage'; +import { parseSingleQuoteJSON } from '../utils/pyodide/functions'; +import { readFiles } from '../utils/filesystem/read'; +import { + LAUNCH, + LOAD_EPOCHS, + LOAD_CLEANED_EPOCHS, + LOAD_PSD, + LOAD_ERP, + LOAD_TOPO, + CLEAN_EPOCHS, + loadTopo, + loadERP, +} from '../actions/pyodideActions'; +import { + loadPackages, + utils, + loadCSV, + loadCleanedEpochs, + filterIIR, + epochEvents, + requestEpochsInfo, + requestChannelInfo, + cleanEpochsPlot, + plotPSD, + plotERP, + plotTopoMap, + saveEpochs, +} from '../utils/pyodide'; +import { + EMOTIV_CHANNELS, + EVENTS, + DEVICES, + MUSE_CHANNELS, + PYODIDE_VARIABLE_NAMES, + PYODIDE_STATUS, +} from '../constants/constants'; + +export const GET_EPOCHS_INFO = 'GET_EPOCHS_INFO'; +export const GET_CHANNEL_INFO = 'GET_CHANNEL_INFO'; +export const SET_MAIN_CHANNEL = 'SET_MAIN_CHANNEL'; +export const SET_EPOCH_INFO = 'SET_EPOCH_INFO'; +export const SET_CHANNEL_INFO = 'SET_CHANNEL_INFO'; +export const SET_PSD_PLOT = 'SET_PSD_PLOT'; +export const SET_ERP_PLOT = 'SET_ERP_PLOT'; +export const SET_TOPO_PLOT = 'SET_TOPO_PLOT'; +export const SET_PYODIDE_STATUS = 'SET_PYODIDE_STATUS'; +export const RECEIVE_EXECUTE_REPLY = 'RECEIVE_EXECUTE_REPLY'; +export const RECEIVE_EXECUTE_RESULT = 'RECEIVE_EXECUTE_RESULT'; +export const RECEIVE_STREAM = 'RECEIVE_STREAM'; +export const RECEIVE_DISPLAY_DATA = 'RECEIVE_DISPLAY_DATA'; + +// ------------------------------------------------------------------------- +// Action Creators + +const getEpochsInfo = (payload) => ({ payload, type: GET_EPOCHS_INFO }); + +const getChannelInfo = () => ({ type: GET_CHANNEL_INFO }); + +const setEpochInfo = (payload) => ({ + payload, + type: SET_EPOCH_INFO, +}); + +const setChannelInfo = (payload) => ({ + payload, + type: SET_CHANNEL_INFO, +}); + +const setPSDPlot = (payload) => ({ + payload, + type: SET_PSD_PLOT, +}); + +const setTopoPlot = (payload) => ({ + payload, + type: SET_TOPO_PLOT, +}); + +const setERPPlot = (payload) => ({ + payload, + type: SET_ERP_PLOT, +}); + +const setPyodideStatus = (payload) => ({ + payload, + type: SET_PYODIDE_STATUS, +}); + +// ------------------------------------------------------------------------- +// Epics + +const launchEpic = (action$) => + action$.ofType(LAUNCH).pipe( + tap(() => console.log('launching')), + mergeMap(loadPackages), + mergeMap(utils), + map(() => setPyodideStatus(PYODIDE_STATUS.LOADED)) + ); + +const loadEpochsEpic = (action$, state$) => + action$.ofType(LOAD_EPOCHS).pipe( + pluck('payload'), + filter((filePathsArray) => filePathsArray.length >= 1), + tap((files) => console.log('files:', files)), + map((filePathsArray) => readFiles(filePathsArray)), + tap((csvArray) => console.log('csvs:', csvArray)), + mergeMap((csvArray) => loadCSV(csvArray)), + mergeMap(() => filterIIR(1, 30)), + mergeMap(() => + epochEvents( + { + [state$.value.experiment.params.stimulus1.title]: EVENTS.STIMULUS_1, + [state$.value.experiment.params.stimulus2.title]: EVENTS.STIMULUS_2, + }, + -0.1, + 0.8 + ) + ), + map(() => getEpochsInfo(PYODIDE_VARIABLE_NAMES.RAW_EPOCHS)) + ); + +const loadCleanedEpochsEpic = (action$) => + action$.ofType(LOAD_CLEANED_EPOCHS).pipe( + pluck('payload'), + filter((filePathsArray) => filePathsArray.length >= 1), + map((filePathsArray) => loadCleanedEpochs(filePathsArray)), + mergeMap(() => + of(getEpochsInfo(PYODIDE_VARIABLE_NAMES.CLEAN_EPOCHS), getChannelInfo(), loadTopo()) + ) + ); + +const cleanEpochsEpic = (action$, state$) => + action$.ofType(CLEAN_EPOCHS).pipe( + map(cleanEpochsPlot), + map(() => + saveEpochs(getWorkspaceDir(state$.value.experiment.title), state$.value.experiment.subject) + ), + map(() => getEpochsInfo(PYODIDE_VARIABLE_NAMES.RAW_EPOCHS)) + ); + +const getEpochsInfoEpic = (action$) => + action$.ofType(GET_EPOCHS_INFO).pipe( + pluck('payload'), + tap((payload) => console.log('payload: ', payload)), + mergeMap(requestEpochsInfo), + map((epochInfoArray) => + epochInfoArray.map((infoObj) => ({ + name: Object.keys(infoObj)[0], + value: infoObj[Object.keys(infoObj)[0]], + })) + ), + map(setEpochInfo) + ); + +const getChannelInfoEpic = (action$) => + action$.ofType(GET_CHANNEL_INFO).pipe( + map(requestChannelInfo), + map((channelInfoString) => setChannelInfo(parseSingleQuoteJSON(channelInfoString))) + ); + +const loadPSDEpic = (action$) => action$.ofType(LOAD_PSD).pipe(map(plotPSD), map(setPSDPlot)); + +const loadTopoEpic = (action$, state$) => + action$.ofType(LOAD_TOPO).pipe( + map(plotTopoMap), + mergeMap((topoPlot) => + of( + setTopoPlot(topoPlot), + loadERP( + state$.value.device.deviceType === DEVICES.EMOTIV ? EMOTIV_CHANNELS[0] : MUSE_CHANNELS[0] + ) + ) + ) + ); + +const loadERPEpic = (action$) => + action$.ofType(LOAD_ERP).pipe( + pluck('payload'), + map((channelName) => { + if (MUSE_CHANNELS.includes(channelName)) { + return MUSE_CHANNELS.indexOf(channelName); + } else if (EMOTIV_CHANNELS.includes(channelName)) { + return EMOTIV_CHANNELS.indexOf(channelName); + } + console.warn('channel name supplied to loadERPEpic does not belong to either device'); + return EMOTIV_CHANNELS[0]; + }), + map((channelIndex) => plotERP(channelIndex)), + map(setERPPlot) + ); + +export default combineEpics( + launchEpic, + loadEpochsEpic, + loadCleanedEpochsEpic, + cleanEpochsEpic, + getEpochsInfoEpic, + getChannelInfoEpic, + loadPSDEpic, + loadTopoEpic, + loadERPEpic +); diff --git a/app/package-lock.json b/app/package-lock.json index 63843828..b53ac9e5 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -95,14 +95,6 @@ "readable-stream": "^2.0.6" } }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -266,25 +258,6 @@ "concat-map": "0.0.1" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" - }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -464,32 +437,11 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, - "enchannel-zmq-backend": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/enchannel-zmq-backend/-/enchannel-zmq-backend-6.3.3.tgz", - "integrity": "sha512-k6He6quqWqGa+Sr2lUZhlbQuMgwmvs4RfFNUXzuGH4R5PUd9HyJLf8hZdG4MnUJumLQYl6y91pCTsft4HBYcAA==", - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0", - "jmp": "^1.0.0", - "rxjs": "^5.5.6", - "uuid": "^3.1.0" - }, - "dependencies": { - "rxjs": { - "version": "5.5.12", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", - "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", - "requires": { - "symbol-observable": "1.0.1" - } - } - } - }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, "requires": { "once": "^1.4.0" } @@ -535,7 +487,8 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "optional": true }, "fs-extra": { "version": "4.0.3", @@ -596,7 +549,8 @@ "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", + "optional": true }, "glob": { "version": "7.1.5", @@ -642,7 +596,8 @@ "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true }, "has-ansi": { "version": "2.0.0", @@ -664,11 +619,6 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, - "home-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/home-dir/-/home-dir-1.0.0.tgz", - "integrity": "sha1-KRfrRL3JByztqUJXlUOEfjAX/k4=" - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -793,39 +743,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "jmp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jmp/-/jmp-1.1.0.tgz", - "integrity": "sha512-hbS+RJ8JweNU/72Dr6deIqHb6cir4DtQ0bovpHoV5LSrB9rln7GREz438UQ40LJKxl+icIJbnCGURicxsdcRig==", - "requires": { - "uuid": "3", - "zeromq": "4" - } - }, - "jsonfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", - "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jupyter-paths": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jupyter-paths/-/jupyter-paths-2.0.0.tgz", - "integrity": "sha512-ZbilXKaJbzGmMDYY/ShC3JNC+/iqFhKaFAqN2VlzrYL17ZjBAM8nTAP2hsi3K5aR5MeQZ28sL+rFmVbDHtbXew==", - "requires": { - "home-dir": "^1.0.0" - } - }, - "kernelspecs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kernelspecs/-/kernelspecs-2.0.0.tgz", - "integrity": "sha512-lce4pPDrs4VdxKYTEBnGLT81A3yNP8syyMAq5AejE+CKAkiXQXrHZaHO1F4c/RmgkKKF1Otis1XrpBxOOQsdnw==", - "requires": { - "jupyter-paths": "^2.0.0" - } - }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -835,11 +752,6 @@ "package-json": "^4.0.0" } }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -932,7 +844,8 @@ "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "optional": true }, "napi-build-utils": { "version": "1.0.1", @@ -1005,6 +918,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.12.0.tgz", "integrity": "sha512-VhPBXCIcvmo/5K8HPmnWJyyhvgKxnHTUMXR/XwGHV68+wrgkzST4UmQrY/XszSWA5dtnXpNp528zkcyJ/pzVcw==", + "optional": true, "requires": { "semver": "^5.4.1" } @@ -1029,7 +943,8 @@ "noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", + "optional": true }, "nopt": { "version": "4.0.1", @@ -1168,31 +1083,6 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "prebuild-install": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.2.tgz", @@ -1365,7 +1255,8 @@ "simple-concat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", + "optional": true }, "simple-get": { "version": "3.1.0", @@ -1384,19 +1275,6 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, - "spawnteract": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spawnteract/-/spawnteract-4.0.0.tgz", - "integrity": "sha512-+FozhawsjyfnLjKx/CVZ8IbX7fUPl/pG4oKiULfHlxg9S7YOM5Z5YQoJLm83Z5SAm+VKZFHax2L/tdkDXO3fuQ==", - "requires": { - "jsonfile": "^3.0.0", - "jupyter-paths": "^2.0.0", - "kernelspecs": "^2.0.0", - "mkdirp": "^0.5.1", - "portfinder": "^1.0.13", - "uuid": "^3.0.1" - } - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -1440,11 +1318,6 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" - }, "tar": { "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", @@ -1521,11 +1394,6 @@ "os-tmpdir": "~1.0.1" } }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" - }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -1535,6 +1403,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "optional": true, "requires": { "safe-buffer": "^5.0.1" } @@ -1642,11 +1511,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -1659,7 +1523,8 @@ "which-pm-runs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "optional": true }, "wide-align": { "version": "1.1.3", @@ -1742,130 +1607,10 @@ "nan": "^2.0.5" } }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "zeromq": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-4.6.0.tgz", - "integrity": "sha512-sU7pQqQj7f/C6orJZAXls+NEKaVMZZtnZqpMPTq5d5dP78CmdC0g15XIviFAN6poPuKl9qlGt74vipOUUuNeWg==", - "requires": { - "nan": "^2.6.2", - "prebuild-install": "^2.2.2" - }, - "dependencies": { - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "expand-template": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", - "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==" - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, - "prebuild-install": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.3.tgz", - "integrity": "sha512-/rI36cN2g7vDQnKWN8Uzupi++KjyqS9iS+/fpwG4Ea8d0Pip0PQ5bshUNzVwt+/D2MRfhVAplYMMvWLqWrCF/g==", - "requires": { - "detect-libc": "^1.0.3", - "expand-template": "^1.0.2", - "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "node-abi": "^2.2.0", - "noop-logger": "^0.1.1", - "npmlog": "^4.0.1", - "os-homedir": "^1.0.1", - "pump": "^2.0.1", - "rc": "^1.1.6", - "simple-get": "^2.7.0", - "tar-fs": "^1.13.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" - } - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "simple-get": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", - "requires": { - "decompress-response": "^3.3.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", - "requires": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" - }, - "dependencies": { - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - } - } - } } } } diff --git a/app/package.json b/app/package.json index 91844a6b..eb6cc800 100644 --- a/app/package.json +++ b/app/package.json @@ -19,11 +19,8 @@ "@neurosity/pipes": "^3.2.3", "babel-runtime": "^6.26.0", "bleat": "^0.1.8", - "enchannel-zmq-backend": "^6.0.11", - "kernelspecs": "^2.0.0", "noble-winrt": "^0.1.0", - "node-pre-gyp": "^0.10.0", - "spawnteract": "^4.0.0" + "node-pre-gyp": "^0.10.0" }, "devDependencies": { "patch-package": "^5.1.1" diff --git a/app/reducers/index.js b/app/reducers/index.js index 71494b4f..6c7920aa 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -1,12 +1,12 @@ // @flow import { combineReducers } from 'redux'; import { routerReducer as router } from 'react-router-redux'; -import jupyter from './jupyterReducer'; +import pyodide from './pyodideReducer'; import device from './deviceReducer'; import experiment from './experimentReducer'; const rootReducer = combineReducers({ - jupyter, + pyodide, device, experiment, router, diff --git a/app/reducers/jupyterReducer.js b/app/reducers/pyodideReducer.js similarity index 66% rename from app/reducers/jupyterReducer.js rename to app/reducers/pyodideReducer.js index 1a423107..7c1e993c 100644 --- a/app/reducers/jupyterReducer.js +++ b/app/reducers/pyodideReducer.js @@ -1,23 +1,19 @@ // @flow import { - SET_KERNEL, - SET_KERNEL_STATUS, SET_MAIN_CHANNEL, - SET_KERNEL_INFO, SET_EPOCH_INFO, SET_CHANNEL_INFO, SET_PSD_PLOT, SET_TOPO_PLOT, SET_ERP_PLOT, RECEIVE_EXECUTE_RETURN, -} from '../epics/jupyterEpics'; -import { ActionType, Kernel } from '../constants/interfaces'; -import { KERNEL_STATUS } from '../constants/constants'; -import { EXPERIMENT_CLEANUP } from '../epics/experimentEpics'; + SET_PYODIDE_STATUS +} from "../epics/pyodideEpics"; +import { ActionType } from "../constants/interfaces"; +import { PYODIDE_STATUS } from "../constants/constants"; +import { EXPERIMENT_CLEANUP } from "../epics/experimentEpics"; -export interface JupyterStateType { - +kernel: ?Kernel; - +kernelStatus: KERNEL_STATUS; +export interface PyodideStateType { +mainChannel: ?any; +epochsInfo: ?Array<{ [string]: number | string }>; +channelInfo: ?Array; @@ -27,39 +23,26 @@ export interface JupyterStateType { } const initialState = { - kernel: null, - kernelStatus: KERNEL_STATUS.OFFLINE, mainChannel: null, epochsInfo: null, channelInfo: [], psdPlot: null, topoPlot: null, erpPlot: null, + status: PYODIDE_STATUS.NOT_LOADED }; -export default function jupyter(state: JupyterStateType = initialState, action: ActionType) { +export default function pyodide( + state: PyodideStateType = initialState, + action: ActionType +) { switch (action.type) { - case SET_KERNEL: - return { - ...state, - kernel: action.payload, - }; - - case SET_KERNEL_STATUS: - return { - ...state, - kernelStatus: action.payload, - }; - case SET_MAIN_CHANNEL: return { ...state, mainChannel: action.payload, }; - case SET_KERNEL_INFO: - return state; - case SET_EPOCH_INFO: return { ...state, @@ -101,6 +84,12 @@ export default function jupyter(state: JupyterStateType = initialState, action: case RECEIVE_EXECUTE_RETURN: return state; + case SET_PYODIDE_STATUS: + return { + ...state, + status: action.payload + }; + default: return state; } diff --git a/app/store/configureStore.dev.js b/app/store/configureStore.dev.js index ed220453..7b3cd13a 100644 --- a/app/store/configureStore.dev.js +++ b/app/store/configureStore.dev.js @@ -6,7 +6,7 @@ import { routerMiddleware, routerActions } from 'react-router-redux'; import { createLogger } from 'redux-logger'; import rootReducer from '../reducers'; import rootEpic from '../epics'; -import * as jupyterActions from '../actions/jupyterActions'; +import * as pyodideActions from '../actions/pyodideActions'; import * as deviceActions from '../actions/deviceActions'; const history = createHashHistory(); @@ -41,8 +41,8 @@ const configureStore = (initialState?: AppState) => { // Redux DevTools Configuration const actionCreators = { ...deviceActions, - ...jupyterActions, - ...routerActions, + ...pyodideActions, + ...routerActions }; // If Redux DevTools Extension is installed use it, otherwise use Redux compose /* eslint-disable no-underscore-dangle */ diff --git a/app/utils/filesystem/read.js b/app/utils/filesystem/read.js new file mode 100644 index 00000000..23bc63c4 --- /dev/null +++ b/app/utils/filesystem/read.js @@ -0,0 +1,18 @@ +const fs = require("fs"); + +export const readFiles = (filePathsArray) => { + return filePathsArray.map(path => { + console.log('about to read file') + const file = fs.readFileSync(path, 'utf8') + console.log('read file') + return file + }) +} + + + +// ------------------------------------------- +// Helper methods + +const formatFilePath = (filePath: string) => + `"${filePath.replace(/\\/g, "/")}"`; diff --git a/app/utils/filesystem/storage.js b/app/utils/filesystem/storage.js index 5344df2c..c4abf4e5 100755 --- a/app/utils/filesystem/storage.js +++ b/app/utils/filesystem/storage.js @@ -59,7 +59,11 @@ export const storeBehaviouralData = ( }; // Stores an image to workspace dir -export const storeJupyterImage = (title: string, imageTitle: string, rawData: Buffer) => { +export const storePyodideImage = ( + title: string, + imageTitle: string, + rawData: Buffer +) => { const dir = path.join(getWorkspaceDir(title), 'Results', 'Images'); const filename = `${imageTitle}.png`; mkdirPathSync(dir); diff --git a/app/utils/jupyter/cells.js b/app/utils/jupyter/cells.js deleted file mode 100644 index a40ce1c9..00000000 --- a/app/utils/jupyter/cells.js +++ /dev/null @@ -1,94 +0,0 @@ -import * as path from 'path'; -import { readFileSync } from 'fs'; - -export const imports = () => - [ - 'from mne import Epochs, find_events, set_eeg_reference, read_epochs, concatenate_epochs', - 'from time import time, strftime, gmtime', - 'import os', - 'from collections import OrderedDict', - 'from glob import glob', - 'from mne import create_info, concatenate_raws', - 'from mne.io import RawArray', - 'from mne.io import RawArray', - 'from mne.channels import read_montage', - 'import pandas as pd', - 'import numpy as np', - 'import seaborn as sns', - 'from matplotlib import pyplot as plt', - "plt.style.use('fivethirtyeight')", - ].join('\n'); - -export const utils = () => readFileSync(path.join(__dirname, '/utils/jupyter/utils.py'), 'utf8'); - -export const loadCSV = (filePathArray: Array) => - [ - `files = [${filePathArray.map((filePath) => formatFilePath(filePath))}]`, - `replace_ch_names = None`, - `raw = load_data(files, replace_ch_names)`, - ].join('\n'); - -export const loadCleanedEpochs = (filePathArray: Array) => - [ - `files = [${filePathArray.map((filePath) => formatFilePath(filePath))}]`, - `clean_epochs = concatenate_epochs([read_epochs(file) for file in files])`, - `conditions = OrderedDict({key: [value] for (key, value) in clean_epochs.event_id.items()})`, - ].join('\n'); - -// NOTE: this command includes a ';' to prevent returning data -export const filterIIR = (lowCutoff: number, highCutoff: number) => - `raw.filter(${lowCutoff}, ${highCutoff}, method='iir');`; - -export const plotPSD = () => [`%matplotlib inline`, `raw.plot_psd(fmin=1, fmax=30)`].join('\n'); - -export const epochEvents = ( - eventIDs: { [string]: number }, - tmin: number, - tmax: number, - reject?: Array | string = 'None' -) => { - const command = [ - `event_id = ${JSON.stringify(eventIDs)}`, - `tmin=${tmin}`, - `tmax=${tmax}`, - `baseline= (tmin, tmax)`, - `picks = None`, - `reject = ${reject}`, - 'events = find_events(raw)', - `raw_epochs = Epochs(raw, events=events, event_id=event_id, - tmin=tmin, tmax=tmax, baseline=baseline, reject=reject, preload=True, - verbose=False, picks=picks)`, - `conditions = OrderedDict({key: [value] for (key, value) in raw_epochs.event_id.items()})`, - ].join('\n'); - return command; -}; - -export const requestEpochsInfo = (variableName: string) => `get_epochs_info(${variableName})`; - -export const requestChannelInfo = () => `[ch for ch in clean_epochs.ch_names if ch != 'Marker']`; - -export const cleanEpochsPlot = () => - [ - `%matplotlib`, - `raw_epochs.plot(scalings='auto', n_epochs=6, title="Clean Data", events=None)`, - ].join('\n'); - -export const plotTopoMap = () => - [`%matplotlib inline`, `plot_topo(clean_epochs, conditions)`].join('\n'); - -export const plotERP = (channelIndex: number) => - [ - `%matplotlib inline`, - `X, y = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions, - ci=97.5, n_boot=1000, title='', diff_waveform=None)`, - ].join('\n'); - -export const saveEpochs = (workspaceDir: string, subject: string) => - `raw_epochs.save(${formatFilePath( - path.join(workspaceDir, 'Data', subject, 'EEG', `${subject}-cleaned-epo.fif`) - )})`; - -// ------------------------------------------- -// Helper methods - -const formatFilePath = (filePath: string) => `"${filePath.replace(/\\/g, '/')}"`; diff --git a/app/utils/jupyter/functions.js b/app/utils/pyodide/functions.js similarity index 60% rename from app/utils/jupyter/functions.js rename to app/utils/pyodide/functions.js index 7688f024..a58cc35a 100644 --- a/app/utils/jupyter/functions.js +++ b/app/utils/pyodide/functions.js @@ -1,18 +1,8 @@ -import { KERNEL_STATUS } from '../../constants/constants'; +// ------------------------------------------- +// Helper & utility functions -export const parseSingleQuoteJSON = (string: string) => JSON.parse(string.replace(/'/g, '"')); - -export const parseKernelStatus = (msg: Object) => { - switch (msg['content']['execution_state']) { - case 'busy': - return KERNEL_STATUS.BUSY; - case 'idle': - return KERNEL_STATUS.IDLE; - case 'starting': - default: - return KERNEL_STATUS.STARTING; - } -}; +export const parseSingleQuoteJSON = (string: string) => + JSON.parse(string.replace(/'/g, '"')); export const debugParseMessage = (msg: Object) => { let content = ''; @@ -40,3 +30,6 @@ export const debugParseMessage = (msg: Object) => { } return `${msg.channel} ${content}`; }; + +export const formatFilePath = (filePath: string) => + `"${filePath.replace(/\\/g, '/')}"`; diff --git a/app/utils/pyodide/index.js b/app/utils/pyodide/index.js new file mode 100644 index 00000000..424fec39 --- /dev/null +++ b/app/utils/pyodide/index.js @@ -0,0 +1,118 @@ +import * as path from 'path'; +import { readFileSync } from 'fs'; +import { languagePluginLoader } from './pyodide'; +import { formatFilePath } from './functions'; + +// --------------------------------- +// This file contains the JS functions that allow the app to access python-wasm through pyodide +// These functions wrap the python strings defined in the + +// ----------------------------- +// Imports and Utility functions + +// Note: this takes an incredibly long time +export const loadPackages = async () => { + await languagePluginLoader; + console.log('loaded language plugin'); + // using window.pyodide instead of pyodide to get linter to stop yelling ;) + await window.pyodide.loadPackage(['matplotlib', 'mne', 'pandas']); + await window.pyodide.runPython('import js'); + console.log('loaded mne package'); +}; + +export const loadUtils = async () => + window.pyodide.runPython( + readFileSync(path.join(__dirname, '/utils/pyodide/utils.py'), 'utf8') + ); + +export const loadCSV = async (csvArray: Array) => { + window.csvArray = csvArray; + // TODO: Pass attached variable name as parameter to load_data + await window.pyodide.runPython(`raw = load_data()`); +}; + +// --------------------------- +// MNE-Related Data Processing + +// export const loadCleanedEpochs = (epocsArray: Array) => +// [ +// `clean_epochs = concatenate_epochs([read_epochs(file) for file in files])`, +// `conditions = OrderedDict({key: [value] for (key, value) in clean_epochs.event_id.items()})` +// ].join("\n"); + +// NOTE: this command includes a ';' to prevent returning data +export const filterIIR = async (lowCutoff: number, highCutoff: number) => + window.pyodide.runPython( + `raw.filter(${lowCutoff}, ${highCutoff}, method='iir');` + ); + +export const epochEvents = async ( + eventIDs: { [string]: number }, + tmin: number, + tmax: number, + reject?: Array | string = 'None' +) => + window.pyodide.runPython( + [ + `event_id = ${JSON.stringify(eventIDs)}`, + `tmin=${tmin}`, + `tmax=${tmax}`, + `baseline= (tmin, tmax)`, + `picks = None`, + `reject = ${reject}`, + 'events = find_events(raw)', + `raw_epochs = Epochs(raw, events=events, event_id=event_id, + tmin=tmin, tmax=tmax, baseline=baseline, reject=reject, preload=True, + verbose=False, picks=picks)`, + `conditions = OrderedDict({key: [value] for (key, value) in raw_epochs.event_id.items()})` + ].join('\n') + ); + +export const requestEpochsInfo = async (variableName: string) => { + const pyodideReturn = await window.pyodide.runPython( + `get_epochs_info(${variableName})` + ); + return pyodideReturn; +}; + +export const requestChannelInfo = async () => + window.pyodide.runPython( + `[ch for ch in clean_epochs.ch_names if ch != 'Marker']` + ); + +// ----------------------------- +// Plot functions + +export const cleanEpochsPlot = async () => { + // TODO: Figure out how to get image results from pyodide + window.pyodide.runPython( + `raw_epochs.plot(scalings='auto', n_epochs=6, title="Clean Data", events=None)` + ); +}; + +export const plotPSD = async () => { + // TODO: Figure out how to get image results from pyodide + window.pyodide.runPython(`raw.plot_psd(fmin=1, fmax=30)`); +}; + +export const plotTopoMap = async () => { + // TODO: Figure out how to get image results from pyodide + window.pyodide.runPython(`plot_topo(clean_epochs, conditions)`); +}; + +export const plotERP = (channelIndex: number) => + `X, y = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions, + ci=97.5, n_boot=1000, title='', diff_waveform=None)`; + +export const saveEpochs = (workspaceDir: string, subject: string) => + window.pyodide.runPython( + `raw_epochs.save(${formatFilePath( + path.join( + workspaceDir, + 'Data', + subject, + 'EEG', + `${subject}-cleaned-epo.fif` + ) + )}` + ); diff --git a/app/utils/jupyter/pipes.js b/app/utils/pyodide/pipes.js similarity index 77% rename from app/utils/jupyter/pipes.js rename to app/utils/pyodide/pipes.js index b5ffe1bf..6ad0587f 100644 --- a/app/utils/jupyter/pipes.js +++ b/app/utils/pyodide/pipes.js @@ -1,11 +1,13 @@ import { pipe } from 'rxjs'; import { map, pluck, filter, take, mergeMap } from 'rxjs/operators'; import { executeRequest } from '@nteract/messaging'; -import { RECEIVE_EXECUTE_REPLY } from '../../epics/jupyterEpics'; +import { RECEIVE_EXECUTE_REPLY } from '../../epics/pyodideEpics'; // Refactor this so command can be calculated either up stream or inside pipe export const execute = (command, state$) => - pipe(map(() => state$.value.jupyter.mainChannel.next(executeRequest(command)))); + pipe( + map(() => state$.value.pyodide.mainChannel.next(executeRequest(command))) + ); export const awaitOkMessage = (action$) => pipe( diff --git a/app/utils/pyodide/pyimport.py b/app/utils/pyodide/pyimport.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/app/utils/pyodide/pyimport.py @@ -0,0 +1 @@ + diff --git a/app/utils/pyodide/pyodide.js b/app/utils/pyodide/pyodide.js new file mode 100644 index 00000000..d1601285 --- /dev/null +++ b/app/utils/pyodide/pyodide.js @@ -0,0 +1,360 @@ +/** + * The main bootstrap script for loading pyodide. + */ +const port = process.env.PORT || 1212; + +export const languagePluginLoader = new Promise((resolve, reject) => { + + var baseURL = 'http://localhost:' + port + '/src/'; + // var baseURL = self.languagePluginUrl || 'https://iodide.io/pyodide-demo/'; + baseURL = baseURL.substr(0, baseURL.lastIndexOf('/')) + '/'; + + //////////////////////////////////////////////////////////// + // Package loading + let loadedPackages = new Array(); + var loadPackagePromise = new Promise((resolve) => resolve()); + // Regexp for validating package name and URI + var package_name_regexp = '[a-z0-9_][a-z0-9_\-]*' + var package_uri_regexp = + new RegExp('^https?://.*?(' + package_name_regexp + ').js$', 'i'); + var package_name_regexp = new RegExp('^' + package_name_regexp + '$', 'i'); + + let _uri_to_package_name = (package_uri) => { + // Generate a unique package name from URI + + if (package_name_regexp.test(package_uri)) { + return package_uri; + } else if (package_uri_regexp.test(package_uri)) { + let match = package_uri_regexp.exec(package_uri); + // Get the regexp group corresponding to the package name + return match[1]; + } else { + return null; + } + }; + + // clang-format off + let preloadWasm = () => { + // On Chrome, we have to instantiate wasm asynchronously. Since that + // can't be done synchronously within the call to dlopen, we instantiate + // every .so that comes our way up front, caching it in the + // `preloadedWasm` dictionary. + + let promise = new Promise((resolve) => resolve()); + let FS = pyodide._module.FS; + + function recurseDir(rootpath) { + let dirs; + try { + dirs = FS.readdir(rootpath); + } catch (err) { + return; + } + for (let entry of dirs) { + if (entry.startsWith('.')) { + continue; + } + const path = rootpath + entry; + if (entry.endsWith('.so')) { + if (Module['preloadedWasm'][path] === undefined) { + promise = promise + .then(() => Module['loadWebAssemblyModule']( + FS.readFile(path), {loadAsync: true})) + .then((module) => { + Module['preloadedWasm'][path] = module; + }); + } + } else if (FS.isDir(FS.lookupPath(path).node.mode)) { + recurseDir(path + '/'); + } + } + } + + recurseDir('/'); + + return promise; + } + // clang-format on + + function loadScript(url, onload, onerror) { + if (self.document) { // browser + const script = self.document.createElement('script'); + script.src = url; + script.onload = (e) => { onload(); }; + script.onerror = (e) => { onerror(); }; + self.document.head.appendChild(script); + } else if (self.importScripts) { // webworker + try { + self.importScripts(url); + onload(); + } catch (err) { + onerror(); + } + } + } + + let _loadPackage = (names, messageCallback) => { + // DFS to find all dependencies of the requested packages + let packages = self.pyodide._module.packages.dependencies; + let loadedPackages = self.pyodide.loadedPackages; + let queue = [].concat(names || []); + let toLoad = new Array(); + while (queue.length) { + let package_uri = queue.pop(); + + const packageName = _uri_to_package_name(package_uri); + + if (packageName == null) { + console.error(`Invalid package name or URI '${package_uri}'`); + return; + } else if (packageName == package_uri) { + package_uri = 'default channel'; + } + + if (packageName in loadedPackages) { + if (package_uri != loadedPackages[packageName]) { + console.error(`URI mismatch, attempting to load package ` + + `${packageName} from ${package_uri} while it is already ` + + `loaded from ${loadedPackages[packageName]}!`); + return; + } + } else if (packageName in toLoad) { + if (package_uri != toLoad[packageName]) { + console.error(`URI mismatch, attempting to load package ` + + `${packageName} from ${package_uri} while it is already ` + + `being loaded from ${toLoad[packageName]}!`); + return; + } + } else { + console.log(`Loading ${packageName} from ${package_uri}`); + + toLoad[packageName] = package_uri; + if (packages.hasOwnProperty(packageName)) { + packages[packageName].forEach((subpackage) => { + if (!(subpackage in loadedPackages) && !(subpackage in toLoad)) { + queue.push(subpackage); + } + }); + } else { + console.error(`Unknown package '${packageName}'`); + } + } + } + + self.pyodide._module.locateFile = (path) => { + // handle packages loaded from custom URLs + let packageName = path.replace(/\.data$/, ""); + if (packageName in toLoad) { + let package_uri = toLoad[packageName]; + if (package_uri != 'default channel') { + return package_uri.replace(/\.js$/, ".data"); + }; + }; + return baseURL + path; + }; + + let promise = new Promise((resolve, reject) => { + if (Object.keys(toLoad).length === 0) { + resolve('No new packages to load'); + return; + } + + const packageList = Array.from(Object.keys(toLoad)).join(', '); + if (messageCallback !== undefined) { + messageCallback(`Loading ${packageList}`); + } + + // monitorRunDependencies is called at the beginning and the end of each + // package being loaded. We know we are done when it has been called + // exactly "toLoad * 2" times. + var packageCounter = Object.keys(toLoad).length * 2; + + self.pyodide._module.monitorRunDependencies = () => { + packageCounter--; + if (packageCounter === 0) { + for (let packageName in toLoad) { + self.pyodide.loadedPackages[packageName] = toLoad[packageName]; + } + delete self.pyodide._module.monitorRunDependencies; + self.removeEventListener('error', windowErrorHandler); + if (!isFirefox) { + preloadWasm().then(() => {resolve(`Loaded ${packageList}`)}); + } else { + resolve(`Loaded ${packageList}`); + } + } + }; + + // Add a handler for any exceptions that are thrown in the process of + // loading a package + var windowErrorHandler = (err) => { + delete self.pyodide._module.monitorRunDependencies; + self.removeEventListener('error', windowErrorHandler); + // Set up a new Promise chain, since this one failed + loadPackagePromise = new Promise((resolve) => resolve()); + reject(err.message); + }; + self.addEventListener('error', windowErrorHandler); + + for (let packageName in toLoad) { + let scriptSrc; + let package_uri = toLoad[packageName]; + if (package_uri == 'default channel') { + scriptSrc = `${baseURL}${packageName}.js`; + } else { + scriptSrc = `${package_uri}`; + } + loadScript(scriptSrc, () => {}, () => { + // If the package_uri fails to load, call monitorRunDependencies twice + // (so packageCounter will still hit 0 and finish loading), and remove + // the package from toLoad so we don't mark it as loaded. + console.error(`Couldn't load package from URL ${scriptSrc}`) + let index = toLoad.indexOf(packageName); + if (index !== -1) { + toLoad.splice(index, 1); + } + for (let i = 0; i < 2; i++) { + self.pyodide._module.monitorRunDependencies(); + } + }); + } + + // We have to invalidate Python's import caches, or it won't + // see the new files. This is done here so it happens in parallel + // with the fetching over the network. + self.pyodide.runPython('import importlib as _importlib\n' + + '_importlib.invalidate_caches()\n'); + }); + + return promise; + }; + + let loadPackage = (names, messageCallback) => { + /* We want to make sure that only one loadPackage invocation runs at any + * given time, so this creates a "chain" of promises. */ + loadPackagePromise = + loadPackagePromise.then(() => _loadPackage(names, messageCallback)); + return loadPackagePromise; + }; + + //////////////////////////////////////////////////////////// + // Fix Python recursion limit + function fixRecursionLimit(pyodide) { + // The Javascript/Wasm call stack may be too small to handle the default + // Python call stack limit of 1000 frames. This is generally the case on + // Chrom(ium), but not on Firefox. Here, we determine the Javascript call + // stack depth available, and then divide by 50 (determined heuristically) + // to set the maximum Python call stack depth. + + let depth = 0; + function recurse() { + depth += 1; + recurse(); + } + try { + recurse(); + } catch (err) { + ; + } + + let recursionLimit = depth / 50; + if (recursionLimit > 1000) { + recursionLimit = 1000; + } + pyodide.runPython( + `import sys; sys.setrecursionlimit(int(${recursionLimit}))`); + }; + + //////////////////////////////////////////////////////////// + // Rearrange namespace for public API + let PUBLIC_API = [ + 'globals', + 'loadPackage', + 'loadedPackages', + 'pyimport', + 'repr', + 'runPython', + 'runPythonAsync', + 'checkABI', + 'version', + ]; + + function makePublicAPI(module, public_api) { + var namespace = {_module : module}; + for (let name of public_api) { + namespace[name] = module[name]; + } + return namespace; + } + + //////////////////////////////////////////////////////////// + // Loading Pyodide + let wasmURL = `${baseURL}pyodide.asm.wasm`; + let Module = {}; + self.Module = Module; + + Module.noImageDecoding = true; + Module.noAudioDecoding = true; + Module.noWasmDecoding = true; + Module.preloadedWasm = {}; + let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + + let wasm_promise = WebAssembly.compileStreaming(fetch(wasmURL)); + Module.instantiateWasm = (info, receiveInstance) => { + wasm_promise.then(module => WebAssembly.instantiate(module, info)) + .then(instance => receiveInstance(instance)); + return {}; + }; + + Module.checkABI = function(ABI_number) { + if (ABI_number !== parseInt('1')) { + var ABI_mismatch_exception = + `ABI numbers differ. Expected 1, got ${ABI_number}`; + console.error(ABI_mismatch_exception); + throw ABI_mismatch_exception; + } + return true; + }; + + Module.locateFile = (path) => baseURL + path; + var postRunPromise = new Promise((resolve, reject) => { + Module.postRun = () => { + delete self.Module; + fetch(`${baseURL}packages.json`) + .then((response) => response.json()) + .then((json) => { + fixRecursionLimit(self.pyodide); + self.pyodide.globals = + self.pyodide.runPython('import sys\nsys.modules["__main__"]'); + self.pyodide = makePublicAPI(self.pyodide, PUBLIC_API); + self.pyodide._module.packages = json; + resolve(); + }); + }; + }); + + var dataLoadPromise = new Promise((resolve, reject) => { + Module.monitorRunDependencies = + (n) => { + if (n === 0) { + delete Module.monitorRunDependencies; + resolve(); + } + } + }); + + Promise.all([ postRunPromise, dataLoadPromise ]).then(() => resolve()); + + const data_script_src = `${baseURL}pyodide.asm.data.js`; + loadScript(data_script_src, () => { + const scriptSrc = `${baseURL}pyodide.asm.js`; + loadScript(scriptSrc, () => { + // The emscripten module needs to be at this location for the core + // filesystem to install itself. Once that's complete, it will be replaced + // by the call to `makePublicAPI` with a more limited public API. + self.pyodide = pyodide(Module); + self.pyodide.loadedPackages = new Array(); + self.pyodide.loadPackage = loadPackage; + }, () => {}); + }, () => {}); +}); diff --git a/app/utils/pyodide/pythonStrings.js b/app/utils/pyodide/pythonStrings.js new file mode 100644 index 00000000..03cc3694 --- /dev/null +++ b/app/utils/pyodide/pythonStrings.js @@ -0,0 +1,84 @@ +// DEPRECATED + +// import * as path from "path"; +// import { readFileSync } from "fs"; + +// // The output of the functions contained in this file are python commands encoded as strings +// // that would be run in a notebook environment in order to perform the experimental analyses underlying BrainWaves + +// export const utils = () => +// readFileSync(path.join(__dirname, "/utils/pyodide/utils.py"), "utf8"); + +// // export const loadCSV = (filePathArray: Array) => +// // [ +// // `files = [${filePathArray.map(filePath => formatFilePath(filePath))}]`, +// // `replace_ch_names = None`, +// // `raw = load_data(files, replace_ch_names)` +// // ].join("\n"); + +// // export const loadCleanedEpochs = (filePathArray: Array) => +// // [ +// // `files = [${filePathArray.map(filePath => formatFilePath(filePath))}]`, +// // `clean_epochs = concatenate_epochs([read_epochs(file) for file in files])`, +// // `conditions = OrderedDict({key: [value] for (key, value) in clean_epochs.event_id.items()})` +// // ].join("\n"); + +// // NOTE: this command includes a ';' to prevent returning data +// export const filterIIR = (lowCutoff: number, highCutoff: number) => +// `raw.filter(${lowCutoff}, ${highCutoff}, method='iir');`; + +// export const plotPSD = () => +// [`%matplotlib inline`, `raw.plot_psd(fmin=1, fmax=30)`].join("\n"); + +// export const epochEvents = ( +// eventIDs: { [string]: number }, +// tmin: number, +// tmax: number, +// reject?: Array | string = "None" +// ) => +// [ +// `event_id = ${JSON.stringify(eventIDs)}`, +// `tmin=${tmin}`, +// `tmax=${tmax}`, +// `baseline= (tmin, tmax)`, +// `picks = None`, +// `reject = ${reject}`, +// "events = find_events(raw)", +// `raw_epochs = Epochs(raw, events=events, event_id=event_id, +// tmin=tmin, tmax=tmax, baseline=baseline, reject=reject, preload=True, +// verbose=False, picks=picks)`, +// `conditions = OrderedDict({key: [value] for (key, value) in raw_epochs.event_id.items()})` +// ].join("\n"); + +// export const requestEpochsInfo = (variableName: string) => +// `get_epochs_info(${variableName})`; + +// export const requestChannelInfo = () => +// `[ch for ch in clean_epochs.ch_names if ch != 'Marker']`; + +// export const cleanEpochsPlot = () => +// [ +// `%matplotlib`, +// `raw_epochs.plot(scalings='auto', n_epochs=6, title="Clean Data", events=None)` +// ].join("\n"); + +// export const plotTopoMap = () => +// [`%matplotlib inline`, `plot_topo(clean_epochs, conditions)`].join("\n"); + +// export const plotERP = (channelIndex: number) => +// [ +// `%matplotlib inline`, +// `X, y = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions, +// ci=97.5, n_boot=1000, title='', diff_waveform=None)` +// ].join("\n"); + +// export const saveEpochs = (workspaceDir: string, subject: string) => +// `raw_epochs.save(${formatFilePath( +// path.join( +// workspaceDir, +// "Data", +// subject, +// "EEG", +// `${subject}-cleaned-epo.fif` +// ) +// )})`; diff --git a/app/utils/jupyter/utils.py b/app/utils/pyodide/utils.py similarity index 55% rename from app/utils/jupyter/utils.py rename to app/utils/pyodide/utils.py index c09aa583..ce736458 100644 --- a/app/utils/jupyter/utils.py +++ b/app/utils/pyodide/utils.py @@ -1,38 +1,50 @@ from glob import glob import os +from time import time, strftime, gmtime from collections import OrderedDict -from mne import create_info, concatenate_raws, viz -from mne.io import RawArray -from mne.channels import read_montage -import pandas as pd + import numpy as np -import seaborn as sns from matplotlib import pyplot as plt +import pandas as pd # maybe we can remove this dependency +# import seaborn as sns -sns.set_context('talk') -sns.set_style('white') +from mne import (Epochs, concatenate_raws, concatenate_epochs, create_info, + find_events, read_epochs, set_eeg_reference, viz) +from mne.io import RawArray +from io import StringIO +from mne.channels import read_montage -def load_data(fnames, sfreq=128., replace_ch_names=None): - """Load CSV files from the /data directory into a Raw object. +# plt.style.use(fivethirtyeight) - Args: - fnames (array): CSV filepaths from which to load data +# sns.set_context('talk') +# sns.set_style('white') - Keyword Args: - sfreq (float): EEG sampling frequency - replace_ch_names (dict or None): dictionary containing a mapping to - rename channels. Useful when an external electrode was used. - Returns: - (mne.io.array.array.RawArray): loaded EEG - """ +def load_data(sfreq=128., replace_ch_names=None): + """Load CSV files from the /data directory into a RawArray object. + + Parameters + ---------- + sfreq : float + EEG sampling frequency + replace_ch_names : dict | None + A dict containing a mapping to rename channels. + Useful when an external electrode was used during recording. + + Returns + ------- + raw : an instance of mne.io.RawArray + The loaded data. + """ + ## js is loaded in loadPackages + ## TODO: Received attached variable name raw = [] - print(fnames) - for fname in fnames: + for csv in js.csvArray: + string_io = StringIO(csv) # read the file - data = pd.read_csv(fname, index_col=0) + data = pd.read_csv(string_io, index_col=0) data = data.dropna() @@ -73,7 +85,11 @@ def load_data(fnames, sfreq=128., replace_ch_names=None): def plot_topo(epochs, conditions=OrderedDict()): - palette = sns.color_palette("hls", len(conditions) + 1) + # palette = sns.color_palette("hls", len(conditions) + 1) + # temp hack, just pull in the color palette from seaborn + palette = [(0.85999999999999999, 0.37119999999999997, 0.33999999999999997), + (0.33999999999999997, 0.85999999999999999, 0.37119999999999997), + (0.37119999999999997, 0.33999999999999997, 0.85999999999999999)] evokeds = [epochs[name].average() for name in (conditions)] evoked_topo = viz.plot_evoked_topo( @@ -94,35 +110,48 @@ def plot_topo(epochs, conditions=OrderedDict()): return evoked_topo -def plot_conditions(epochs, ch_ind=0, conditions=OrderedDict(), ci=97.5, n_boot=1000, - title='', palette=None, - diff_waveform=(4, 3)): +def plot_conditions(epochs, ch_ind=0, conditions=OrderedDict(), ci=97.5, + n_boot=1000, title='', palette=None, diff_waveform=(4, 3)): """Plot Averaged Epochs with ERP conditions. - Args: - epochs (mne.epochs): EEG epochs - - Keyword Args: - conditions (OrderedDict): dictionary that contains the names of the - conditions to plot as keys, and the list of corresponding marker - numbers as value. E.g., - - conditions = {'Non-target': [0, 1], - 'Target': [2, 3, 4]} - - ch_ind (int): index of channel to plot data from - ci (float): confidence interval in range [0, 100] - n_boot (int): number of bootstrap samples - title (str): title of the figure - palette (list): color palette to use for conditions - ylim (tuple): (ymin, ymax) - diff_waveform (tuple or None): tuple of ints indicating which - conditions to subtract for producing the difference waveform. + Parameters + ---------- + epochs : an instance of mne.epochs + EEG epochs + conditions : an instance of OrderedDict + An ordered dictionary that contains the names of the + conditions to plot as keys, and the list of corresponding marker + numbers as value. + + E.g., + + conditions = {'Non-target': [0, 1], + 'Target': [2, 3, 4]} + + ch_ind : int + An index of channel to plot data from. + ci : float + The confidence interval of the measurement within + the range [0, 100]. + n_boot : int + Number of bootstrap samples. + title : str + Title of the figure. + palette : list + Color palette to use for conditions. + ylim : tuple + (ymin, ymax) + diff_waveform : tuple | None + tuple of ints indicating which conditions to subtract for + producing the difference waveform. If None, do not plot a difference waveform - Returns: - (matplotlib.figure.Figure): figure object - (list of matplotlib.axes._subplots.AxesSubplot): list of axes + Returns + ------- + fig : an instance of matplotlib.figure.Figure + A figure object. + ax : list of matplotlib.axes._subplots.AxesSubplot + A list of axes """ if isinstance(conditions, dict): conditions = OrderedDict(conditions) @@ -172,4 +201,8 @@ def plot_conditions(epochs, ch_ind=0, conditions=OrderedDict(), ci=97.5, n_boot= return fig, ax def get_epochs_info(epochs): - return [*[{x: len(epochs[x])} for x in epochs.event_id], {"Drop Percentage": round((1 - len(epochs.events)/len(epochs.drop_log)) * 100, 2)}, {"Total Epochs": len(epochs.events)}] + print('Get Epochs Info:') + return [*[{x: len(epochs[x])} for x in epochs.event_id], + {"Drop Percentage": round((1 - len(epochs.events) / + len(epochs.drop_log)) * 100, 2)}, + {"Total Epochs": len(epochs.events)}] diff --git a/internals/scripts/InstallPyodide.js b/internals/scripts/InstallPyodide.js new file mode 100644 index 00000000..90461ec8 --- /dev/null +++ b/internals/scripts/InstallPyodide.js @@ -0,0 +1,72 @@ +// mkdir app/utils/pyodide/src +// && cd app/utils/pyodide/src +// curl -LJO https://github.com/iodide-project/pyodide/releases/download/0.12.0/pyodide-build-0.12.0.tar.bz2 +// tar xjf pyodide-build-0.12.0.tar.bz2 +// rm pyodide-build-0.12.0.tar.bz2", + +import chalk from 'chalk'; +import fs from 'fs'; +import https from 'https'; +import mkdirp from 'mkdirp'; +import tar from 'tar-fs'; +import url from 'url'; +import bz2 from 'unbzip2-stream'; + +const PYODIDE_VERSION = '0.14.3'; +const TAR_NAME = `pyodide-build-${PYODIDE_VERSION}.tar.bz2`; +const TAR_URL = `https://github.com/iodide-project/pyodide/releases/download/${PYODIDE_VERSION}/pyodide-build-${PYODIDE_VERSION}.tar.bz2`; +const PYODIDE_DIR = 'app/utils/pyodide/src/'; + +const writeAndUnzipFile = response => { + const filePath = `${PYODIDE_DIR}${TAR_NAME}`; + const writeStream = fs.createWriteStream(filePath); + response.pipe(writeStream); + + writeStream.on('finish', () => { + console.log(`${chalk.green.bold(`Unzipping pyodide`)}`); + + const readStream = fs.createReadStream(filePath); + try { + readStream.pipe(bz2()).pipe(tar.extract(PYODIDE_DIR)); + } catch (e) { + throw new Error('Error in unzip:', e); + } + + readStream.on('end', () => { + console.log(`${chalk.green.bold(`Unzip successful`)}`); + }); + }); +}; + +const downloadFile = response => { + if ( + response.statusCode > 300 && + response.statusCode < 400 && + response.headers.location + ) { + if (url.parse(response.headers.location).hostname) { + https.get(response.headers.location, writeAndUnzipFile); + } else { + https.get( + url.resolve(url.parse(TAR_URL).hostname, response.headers.location), + writeAndUnzipFile + ); + } + } else { + writeAndUnzipFile(response); + } +}; + +(() => { + if (fs.existsSync(`${PYODIDE_DIR}${TAR_NAME}`)) { + console.log( + `${chalk.green.bold(`Pyodide is already present: ${PYODIDE_VERSION}...`)}` + ); + return; + } + console.log( + `${chalk.green.bold(`Downloading pyodide ${PYODIDE_VERSION}...`)}` + ); + mkdirp.sync(`app/utils/pyodide/src`); + https.get(TAR_URL, downloadFile); +})(); diff --git a/package-lock.json b/package-lock.json index 6cc56114..76c359d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -249,6 +249,12 @@ "@babel/types": "7.0.0-beta.44" } }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", + "dev": true + }, "@babel/helpers": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz", @@ -674,30 +680,11 @@ } } }, - "@nteract/markdown": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nteract/markdown/-/markdown-3.0.1.tgz", - "integrity": "sha512-Dr79niruytzbcDsLLS5NoPIzvVJ3YkJX18O0hmZAmrVn2Ni8L5VW+ZymWsSgRBogDgrYQzhV4L1MmIC/X6rbUg==", - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "@nteract/mathjax": "^3.0.1", - "babel-runtime": "^6.26.0", - "react-markdown": "^4.0.0" - } - }, - "@nteract/mathjax": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nteract/mathjax/-/mathjax-3.0.1.tgz", - "integrity": "sha512-jL4a2KjY9wzcg7dG3HvufKMXOk+FxWQ6BFH6p3fwJUerbf3SF6H/RlaLxuMgFP+Tv+sHVXo7FHTTTIIsRyuv8g==", - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0" - } - }, "@nteract/messaging": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@nteract/messaging/-/messaging-4.2.2.tgz", "integrity": "sha512-jFk2GoqMevGd+VD5cbQJtQ2ShuZXV9AAqRjWtt2PJqxYbbTPLs4aFf/w+SNekmajHPithJ/dq00j3qp4sWyu0A==", + "dev": true, "requires": { "@babel/runtime-corejs2": "^7.0.0", "babel-runtime": "^6.26.0", @@ -705,30 +692,6 @@ "uuid": "^3.1.0" } }, - "@nteract/transform-vdom": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@nteract/transform-vdom/-/transform-vdom-2.2.5.tgz", - "integrity": "sha512-q6FbWlrSEWUmQpDV1DBPcw5FZpUcQbKOQ2a59vY/qcQ/Qjh1KUCC+gortso+WIE4P36eHZRxKz5ptCu5i47OLg==", - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "@nteract/transforms": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@nteract/transforms/-/transforms-4.4.7.tgz", - "integrity": "sha512-G84BeBfzfdAg3cAV78ES9/yxe2ZNitg7j8MCKKx4iNS9vqeQbO92vyBsBW2D6qlddefU/RB5/HUvvtNUpd0ZRw==", - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "@nteract/markdown": "^3.0.1", - "@nteract/mathjax": "^3.0.1", - "@nteract/transform-vdom": "^2.2.5", - "ansi-to-react": "^3.3.5", - "babel-runtime": "^6.26.0", - "react-json-tree": "^0.11.0" - } - }, "@octokit/auth-token": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.0.tgz", @@ -1579,11 +1542,6 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, - "anser": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.9.tgz", - "integrity": "sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA==" - }, "ansi-align": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", @@ -1689,17 +1647,6 @@ "color-convert": "^1.9.0" } }, - "ansi-to-react": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/ansi-to-react/-/ansi-to-react-3.3.5.tgz", - "integrity": "sha512-uAI8NOh+/5PC1poTnLwhuO7whaxPst1lZCeq+7P7hlP0A6GRXjXu1f5qprTwT3NHtjIWyMcFJAL0Im0HyB2XeQ==", - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "anser": "^1.4.1", - "babel-runtime": "^6.26.0", - "escape-carriage": "^1.2.0" - } - }, "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", @@ -2254,6 +2201,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, "requires": { "lodash": "^4.17.14" } @@ -3681,9 +3629,10 @@ "dev": true }, "bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", + "integrity": "sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==", + "dev": true }, "balanced-match": { "version": "1.0.0", @@ -3765,11 +3714,6 @@ } } }, - "base16": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" - }, "base62": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/base62/-/base62-1.2.8.tgz", @@ -3861,10 +3805,7 @@ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } + "optional": true }, "bit-twiddle": { "version": "1.0.2", @@ -4691,9 +4632,10 @@ } }, "character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", + "integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==", + "dev": true }, "character-entities-html4": { "version": "1.1.4", @@ -4702,14 +4644,16 @@ "dev": true }, "character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz", + "integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==", + "dev": true }, "character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz", + "integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==", + "dev": true }, "chardet": { "version": "0.4.2", @@ -5108,9 +5052,10 @@ "dev": true }, "collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.5.tgz", + "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==", + "dev": true }, "collection-visit": { "version": "1.0.0", @@ -7266,9 +7211,10 @@ } }, "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", + "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", + "dev": true, "requires": { "domelementtype": "^2.0.1", "entities": "^2.0.0" @@ -7288,7 +7234,8 @@ "domelementtype": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true }, "domexception": { "version": "1.0.1", @@ -7299,24 +7246,6 @@ "webidl-conversions": "^4.0.2" } }, - "domhandler": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", - "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", - "requires": { - "domelementtype": "^2.0.1" - } - }, - "domutils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", - "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", - "requires": { - "dom-serializer": "^0.2.1", - "domelementtype": "^2.0.1", - "domhandler": "^3.0.0" - } - }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -8132,7 +8061,8 @@ "entities": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true }, "env-paths": { "version": "2.2.0", @@ -8194,6 +8124,14 @@ "react-is": "^16.12.0", "react-test-renderer": "^16.0.0-0", "semver": "^5.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + } } }, "enzyme-adapter-utils": { @@ -8228,6 +8166,14 @@ "requires": { "lodash": "^4.17.15", "react-is": "^16.12.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + } } }, "errno": { @@ -8324,7 +8270,8 @@ "escape-carriage": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.0.tgz", - "integrity": "sha512-ATWi5MD8QlAGQOeMgI8zTp671BG8aKvAC0M7yenlxU4CRLGO/sKthxVUyjiOFKjHdIo+6dZZUNFgHFeVEaKfGQ==" + "integrity": "sha512-ATWi5MD8QlAGQOeMgI8zTp671BG8aKvAC0M7yenlxU4CRLGO/sKthxVUyjiOFKjHdIo+6dZZUNFgHFeVEaKfGQ==", + "dev": true }, "escape-html": { "version": "1.0.3", @@ -8904,26 +8851,26 @@ } }, "@babel/generator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.7.tgz", - "integrity": "sha512-DQwjiKJqH4C3qGiyQCAExJHoZssn49JTMJgZ8SANGgVFdkupcUhLOdkAeoC6kmHZCPfoDG5M0b6cFlSN5wW7Ew==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", + "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", "dev": true, "requires": { - "@babel/types": "^7.8.7", + "@babel/types": "^7.9.5", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.8.3", "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.9.5" } }, "@babel/helper-get-function-arity": { @@ -8945,13 +8892,13 @@ } }, "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", "dev": true, "requires": { + "@babel/helper-validator-identifier": "^7.9.0", "chalk": "^2.0.0", - "esutils": "^2.0.2", "js-tokens": "^4.0.0" } }, @@ -8967,29 +8914,37 @@ } }, "@babel/traverse": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", - "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", + "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.6", - "@babel/helper-function-name": "^7.8.3", + "@babel/generator": "^7.9.5", + "@babel/helper-function-name": "^7.9.5", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.5", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/parser": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", + "dev": true + } } }, "@babel/types": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", - "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", + "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", "dev": true, "requires": { - "esutils": "^2.0.2", + "@babel/helper-validator-identifier": "^7.9.5", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } @@ -9664,7 +9619,8 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extend-shallow": { "version": "3.0.2", @@ -10073,13 +10029,6 @@ "integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg==", "dev": true }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -12926,24 +12875,6 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true }, - "gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dev": true, - "requires": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } - }, "handle-thing": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", @@ -13204,11 +13135,6 @@ "react-is": "^16.7.0" } }, - "home-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/home-dir/-/home-dir-1.0.0.tgz", - "integrity": "sha1-KRfrRL3JByztqUJXlUOEfjAX/k4=" - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -13345,28 +13271,6 @@ "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=", "dev": true }, - "html-to-react": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.2.tgz", - "integrity": "sha512-TdTfxd95sRCo6QL8admCkE7mvNNrXtGoVr1dyS+7uvc8XCqAymnf/6ckclvnVbQNUo2Nh21VPwtfEHd0khiV7g==", - "requires": { - "domhandler": "^3.0", - "htmlparser2": "^4.0", - "lodash.camelcase": "^4.3.0", - "ramda": "^0.26" - } - }, - "htmlparser2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", - "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^3.0.0", - "domutils": "^2.0.0", - "entities": "^2.0.0" - } - }, "http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -14157,9 +14061,10 @@ } }, "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", + "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==", + "dev": true }, "is-alphanumeric": { "version": "1.0.0", @@ -14168,9 +14073,10 @@ "dev": true }, "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz", + "integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==", + "dev": true, "requires": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" @@ -14251,9 +14157,10 @@ "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", + "integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==", + "dev": true }, "is-descriptor": { "version": "0.1.6", @@ -14347,9 +14254,10 @@ } }, "is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", + "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==", + "dev": true }, "is-iexplorer": { "version": "1.0.0", @@ -14577,9 +14485,10 @@ "dev": true }, "is-whitespace-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", - "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz", + "integrity": "sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==", + "dev": true }, "is-windows": { "version": "1.0.2", @@ -14588,9 +14497,10 @@ "dev": true }, "is-word-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", - "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.3.tgz", + "integrity": "sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==", + "dev": true }, "is-wsl": { "version": "1.1.0", @@ -15662,14 +15572,6 @@ "minimist": "^1.2.0" } }, - "jsonfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", - "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "jsonfilter": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/jsonfilter/-/jsonfilter-1.1.2.tgz", @@ -15700,10 +15602,6 @@ "verror": "1.10.0" } }, - "jspsych": { - "version": "git://github.com/makebrainwaves/jspsych.git#031dbdc8ce857ea13d9363e732a1ed039810367c", - "from": "git://github.com/makebrainwaves/jspsych.git#modular" - }, "jspsych-react": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/jspsych-react/-/jspsych-react-0.3.0.tgz", @@ -15713,6 +15611,10 @@ "react": "^0.14.3" }, "dependencies": { + "jspsych": { + "version": "git://github.com/makebrainwaves/jspsych.git#031dbdc8ce857ea13d9363e732a1ed039810367c", + "from": "git://github.com/makebrainwaves/jspsych.git#modular" + }, "react": { "version": "0.14.9", "resolved": "https://registry.npmjs.org/react/-/react-0.14.9.tgz", @@ -15753,14 +15655,6 @@ "object.assign": "^4.1.0" } }, - "jupyter-paths": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jupyter-paths/-/jupyter-paths-2.0.0.tgz", - "integrity": "sha512-ZbilXKaJbzGmMDYY/ShC3JNC+/iqFhKaFAqN2VlzrYL17ZjBAM8nTAP2hsi3K5aR5MeQZ28sL+rFmVbDHtbXew==", - "requires": { - "home-dir": "^1.0.0" - } - }, "just-extend": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", @@ -15772,14 +15666,6 @@ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" }, - "kernelspecs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kernelspecs/-/kernelspecs-2.0.0.tgz", - "integrity": "sha512-lce4pPDrs4VdxKYTEBnGLT81A3yNP8syyMAq5AejE+CKAkiXQXrHZaHO1F4c/RmgkKKF1Otis1XrpBxOOQsdnw==", - "requires": { - "jupyter-paths": "^2.0.0" - } - }, "keyboard-key": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz", @@ -16503,18 +16389,14 @@ "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, - "lodash.curry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" - }, "lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", @@ -16527,11 +16409,6 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, - "lodash.flow": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -16828,9 +16705,10 @@ } }, "markdown-escapes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", - "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", + "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==", + "dev": true }, "markdown-table": { "version": "1.1.3", @@ -16930,14 +16808,6 @@ "safe-buffer": "^5.1.2" } }, - "mdast-add-list-metadata": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz", - "integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==", - "requires": { - "unist-util-visit-parents": "1.1.2" - } - }, "mdast-util-compact": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz", @@ -18795,6 +18665,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "dev": true, "requires": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", @@ -19606,6 +19477,7 @@ "version": "1.0.25", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "dev": true, "requires": { "async": "^2.6.2", "debug": "^3.1.1", @@ -19616,9 +19488,16 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, "requires": { "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -20648,11 +20527,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "pure-color": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", - "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" - }, "pxls": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/pxls/-/pxls-2.3.2.tgz", @@ -20788,11 +20662,6 @@ "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", "dev": true }, - "ramda": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", - "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" - }, "randexp": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", @@ -20960,6 +20829,13 @@ "react-is": "^16.12.0", "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.1.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "react": { @@ -20972,17 +20848,6 @@ "prop-types": "^15.6.2" } }, - "react-base16-styling": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", - "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=", - "requires": { - "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" - } - }, "react-dom": { "version": "16.12.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz", @@ -21017,40 +20882,15 @@ } }, "react-is": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", - "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" - }, - "react-json-tree": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.11.2.tgz", - "integrity": "sha512-aYhUPj1y5jR3ZQ+G3N7aL8FbTyO03iLwnVvvEikLcNFqNTyabdljo9xDftZndUBFyyyL0aK3qGO9+8EilILHUw==", - "requires": { - "babel-runtime": "^6.6.1", - "prop-types": "^15.5.8", - "react-base16-styling": "^0.5.1" - } + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.11.0.tgz", + "integrity": "sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==" }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, - "react-markdown": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.3.1.tgz", - "integrity": "sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw==", - "requires": { - "html-to-react": "^1.3.4", - "mdast-add-list-metadata": "1.0.1", - "prop-types": "^15.7.2", - "react-is": "^16.8.6", - "remark-parse": "^5.0.0", - "unified": "^6.1.5", - "unist-util-visit": "^1.3.0", - "xtend": "^4.0.1" - } - }, "react-plotly.js": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/react-plotly.js/-/react-plotly.js-2.4.0.tgz", @@ -22039,28 +21879,6 @@ } } }, - "remark-parse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", - "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", - "requires": { - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^1.1.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^1.0.0", - "vfile-location": "^2.0.0", - "xtend": "^4.0.1" - } - }, "remark-stringify": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", @@ -22112,7 +21930,8 @@ "replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true }, "request": { "version": "2.88.2", @@ -23880,19 +23699,6 @@ } } }, - "spawnteract": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spawnteract/-/spawnteract-4.0.0.tgz", - "integrity": "sha512-+FozhawsjyfnLjKx/CVZ8IbX7fUPl/pG4oKiULfHlxg9S7YOM5Z5YQoJLm83Z5SAm+VKZFHax2L/tdkDXO3fuQ==", - "requires": { - "jsonfile": "^3.0.0", - "jupyter-paths": "^2.0.0", - "kernelspecs": "^2.0.0", - "mkdirp": "^0.5.1", - "portfinder": "^1.0.13", - "uuid": "^3.0.1" - } - }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -24100,9 +23906,10 @@ "dev": true }, "state-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", - "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", + "integrity": "sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==", + "dev": true }, "static-eval": { "version": "2.0.3", @@ -27043,7 +26850,8 @@ "trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true }, "trim-newlines": { "version": "1.0.0", @@ -27058,14 +26866,16 @@ "dev": true }, "trim-trailing-lines": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", - "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz", + "integrity": "sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==", + "dev": true }, "trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.4.tgz", + "integrity": "sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==", + "dev": true }, "true-case-path": { "version": "1.0.3", @@ -27306,12 +27116,13 @@ } }, "unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.2.tgz", + "integrity": "sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==", + "dev": true, "requires": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" + "inherits": "^2.0.1", + "xtend": "^4.0.1" } }, "unicode-canonical-property-names-ecmascript": { @@ -27338,19 +27149,6 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==" }, - "unified": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", - "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^1.1.0", - "trough": "^1.0.0", - "vfile": "^2.0.0", - "x-is-string": "^0.1.0" - } - }, "union-find": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/union-find/-/union-find-1.0.2.tgz", @@ -27418,12 +27216,14 @@ "unist-util-is": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", - "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==" + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true }, "unist-util-remove-position": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", - "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.3.tgz", + "integrity": "sha512-CtszTlOjP2sBGYc2zcKA/CvNdTdEs3ozbiJ63IPBxh8iZg42SCCb8m04f8z2+V1aSk5a7BxbZKEdoDjadmBkWA==", + "dev": true, "requires": { "unist-util-visit": "^1.1.0" } @@ -27431,12 +27231,14 @@ "unist-util-stringify-position": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==" + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", + "dev": true }, "unist-util-visit": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, "requires": { "unist-util-visit-parents": "^2.0.0" }, @@ -27445,17 +27247,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, "requires": { "unist-util-is": "^3.0.0" } } } }, - "unist-util-visit-parents": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz", - "integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q==" - }, "universal-user-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", @@ -27844,9 +27642,10 @@ } }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "dev": true }, "v8-compile-cache": { "version": "2.0.3", @@ -27991,26 +27790,17 @@ "extsprintf": "^1.2.0" } }, - "vfile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", - "requires": { - "is-buffer": "^1.1.4", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^1.0.0", - "vfile-message": "^1.0.0" - } - }, "vfile-location": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", - "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.5.tgz", + "integrity": "sha512-Pa1ey0OzYBkLPxPZI3d9E+S4BmvfVwNAAXrrqGbwTVXWaX2p9kM1zZ+n35UtVM06shmWKH4RPRN8KI80qE3wNQ==", + "dev": true }, "vfile-message": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, "requires": { "unist-util-stringify-position": "^1.1.1" } @@ -29024,6 +28814,22 @@ "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", "dev": true }, + "gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, "ws": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", @@ -30313,7 +30119,8 @@ "x-is-string": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", - "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=" + "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", + "dev": true }, "xdg-basedir": { "version": "3.0.0", diff --git a/package.json b/package.json index 6aa82d20..3301b4b0 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "dist/", "node_modules/", "assets/", - "utils/jupyter/utils.py", + "utils/pyodide/utils.py", + "utils/pyodide/pyimport.py", "app.html", "viewer.html", "main.prod.js", @@ -233,8 +234,6 @@ }, "dependencies": { "@babel/runtime-corejs2": "^7.0.0", - "@nteract/messaging": "^4.0.1", - "@nteract/transforms": "^4.3.0", "ajv": "^6.5.3", "babel-runtime": "^6.26.0", "d3": "^5.5.0", @@ -244,7 +243,6 @@ "hazardous": "^0.3.0", "history": "^4.7.2", "jspsych-react": "^0.3.0", - "kernelspecs": "^2.0.0", "lodash": "^4.17.15", "lodash.clonedeep": "^4.5.0", "mkdirp": "^0.5.1", @@ -274,7 +272,6 @@ "simple-statistics": "^7.0.2", "simplify-js": "^1.2.3", "source-map-support": "^0.5.3", - "spawnteract": "^4.0.0", "ws": "^5.2.0" }, "devEngines": { diff --git a/webpack.config.renderer.dev.js b/webpack.config.renderer.dev.js index aabb88cd..0927d49c 100644 --- a/webpack.config.renderer.dev.js +++ b/webpack.config.renderer.dev.js @@ -262,8 +262,8 @@ export default merge.smart(baseConfig, { inline: true, lazy: false, hot: true, - headers: { 'Access-Control-Allow-Origin': '*' }, - contentBase: path.join(__dirname, 'dist'), + headers: { "Access-Control-Allow-Origin": "*" }, + contentBase: [path.join(__dirname, "app", "dist"), path.join(__dirname, "app", "utils", "pyodide")], watchOptions: { aggregateTimeout: 300, ignored: /node_modules/, @@ -273,7 +273,7 @@ export default merge.smart(baseConfig, { verbose: true, disableDotRule: false, }, - before() { + before(app) { if (process.env.START_HOT) { console.log('Starting Main Process...'); spawn('npm', ['run', 'start-main-dev'], {