diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 702e9cc..6812521 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -17,7 +17,7 @@ module.exports = { "block-spacing": 2, "brace-style": [2, "allman", { allowSingleLine: true }], "camelcase": 1, - "capitalized-comments": [1, "always", { ignoreConsecutiveComments: true }], + "capitalized-comments": 0, "comma-spacing": 2, "comma-style": 2, "consistent-return": 1, @@ -47,7 +47,7 @@ module.exports = { "no-console": 1, "no-div-regex": 2, "no-duplicate-imports": 2, - "no-else-return": 2, + "no-else-return": 1, "no-eval": 2, "no-extend-native": 2, "no-extra-bind": 2, @@ -65,7 +65,7 @@ module.exports = { "no-mixed-requires": 2, "no-multi-spaces": 2, "no-multi-str": 2, - "no-multiple-empty-lines": [2, { max: 1, maxEOF: 0 }], + "no-multiple-empty-lines": [1, { max: 2, maxEOF: 0 }], "no-new": 2, "no-new-func": 2, "no-new-object": 2, @@ -74,7 +74,7 @@ module.exports = { "no-octal-escape": 2, "no-param-reassign": 1, "no-path-concat": 2, - "no-plusplus": 2, + "no-plusplus": 0, "no-proto": 2, "no-restricted-properties": 2, "no-return-assign": [2, "except-parens"], @@ -85,14 +85,15 @@ module.exports = { "no-shadow-restricted-names": 2, "no-tabs": [1, { allowIndentationTabs: true }], "no-template-curly-in-string": 2, - "no-throw-literal": 2, + "no-throw-literal": 0, "no-trailing-spaces": 2, "no-undef-init": 2, // https://eslint.org/docs/rules/no-underscore-dangle#disallow-dangling-underscores-in-identifiers-no-underscore-dangle - "no-underscore-dangle": 1, + "no-underscore-dangle": 0, "no-unmodified-loop-condition": 2, "no-unneeded-ternary": 2, - "no-unused-expressions": 2, + "no-unused-expressions": 1, + "no-unused-vars": 1, "no-use-before-define": [2, { functions: false }], "no-useless-call": 2, "no-useless-computed-key": 2, @@ -106,7 +107,7 @@ module.exports = { "object-property-newline": [2, { allowMultiplePropertiesPerLine: true }], "one-var": [2, "never"], "one-var-declaration-per-line": 2, - "operator-linebreak": [2, "before"], + "operator-linebreak": [1, "before"], "padded-blocks": [2, "never"], "padding-line-between-statements": 2, "prefer-const": 2, diff --git a/package.json b/package.json index 01eca0e..470fceb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psychojs", - "version": "2021.2.x", + "version": "2021.3.0", "private": true, "description": "Helps run in-browser neuroscience, psychology, and psychophysics experiments", "license": "MIT", diff --git a/src/core/ServerManager.js b/src/core/ServerManager.js index a06140a..68965df 100644 --- a/src/core/ServerManager.js +++ b/src/core/ServerManager.js @@ -453,7 +453,7 @@ export class ServerManager extends PsychObject path, data: undefined, }); - this._psychoJS.logger.debug("registered resource:", name, path); + this._psychoJS.logger.debug(`registered resource: name= ${name}, path= ${path}`); resourcesToDownload.add(name); } } @@ -476,12 +476,10 @@ export class ServerManager extends PsychObject { // to deal with potential CORS issues, we use the pavlovia.org proxy for resources // not hosted on pavlovia.org: - if ( - (path.toLowerCase().indexOf("www.") === 0 - || path.toLowerCase().indexOf("http:") === 0 - || path.toLowerCase().indexOf("https:") === 0) - && (path.indexOf("pavlovia.org") === -1) - ) + if ( (path.toLowerCase().indexOf("www.") === 0 || + path.toLowerCase().indexOf("http:") === 0 || + path.toLowerCase().indexOf("https:") === 0) && + (path.indexOf("pavlovia.org") === -1) ) { path = "https://pavlovia.org/api/v2/proxy/" + path; } @@ -491,7 +489,7 @@ export class ServerManager extends PsychObject path, data: undefined, }); - this._psychoJS.logger.debug("registered resource:", name, path); + this._psychoJS.logger.debug(`registered resource: name= ${name}, path= ${path}`); // download resources by default: if (typeof download === "undefined" || download) diff --git a/src/data/MultiStairHandler.js b/src/data/MultiStairHandler.js new file mode 100644 index 0000000..fb51572 --- /dev/null +++ b/src/data/MultiStairHandler.js @@ -0,0 +1,382 @@ +/** @module data */ +/** + * Multiple Staircase Trial Handler + * + * @author Alain Pitiot + * @version 2021.2.1 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. + * (https://opensciencetools.org) + * @license Distributed under the terms of the MIT License + */ + + +import {TrialHandler} from "./TrialHandler.js"; +import {QuestHandler} from "./QuestHandler.js"; +import * as util from "../util/Util.js"; +import seedrandom from "seedrandom"; + + +/** + *

A handler dealing with multiple staircases, simultaneously.

+ * + *

Note that, at the moment, using the MultiStairHandler requires the jsQuest.js + * library to be loaded as a resource, at the start of the experiment.

+ * + * @class module.data.MultiStairHandler + * @extends TrialHandler + * @param {Object} options - the handler options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {string} options.varName - the name of the variable / intensity / contrast + * / threshold manipulated by the staircases + * @param {module:data.MultiStairHandler.StaircaseType} [options.stairType="simple"] - the + * handler type + * @param {Array. | String} [options.conditions= [undefined] ] - if it is a string, + * we treat it as the name of a conditions resource + * @param {module:data.TrialHandler.Method} options.method - the trial method + * @param {number} [options.nTrials=50] - maximum number of trials + * @param {number} options.randomSeed - seed for the random number generator + * @param {string} options.name - name of the handler + * @param {boolean} [options.autoLog= false] - whether or not to log + */ +export class MultiStairHandler extends TrialHandler +{ + /** + * @constructor + * @public + */ + constructor({ + psychoJS, + varName, + stairType, + conditions, + method = TrialHandler.Method.RANDOM, + nTrials = 50, + randomSeed, + name, + autoLog + } = {}) + { + super({ + psychoJS, + name, + autoLog, + seed: randomSeed, + // note: multiStairHandler is a sequential TrialHandler, we deal with randomness + // in _nextTrial + method: TrialHandler.Method.SEQUENTIAL, + trialList: Array(nTrials), + nReps: 1 + }); + + // now that we have initialised a sequential TrialHandler, we update method: + this._multiMethod = method; + this._addAttribute("varName", varName); + this._addAttribute("stairType", stairType, MultiStairHandler.StaircaseType.SIMPLE); + this._addAttribute("conditions", conditions, [undefined]); + this._addAttribute("nTrials", nTrials); + + if (typeof randomSeed !== "undefined") + { + this._randomNumberGenerator = seedrandom(randomSeed); + } + else + { + this._randomNumberGenerator = seedrandom(); + } + + this._prepareStaircases(); + this._nextTrial(); + } + + /** + * Add a response to the current staircase. + * + * @name module:data.MultiStairHandler#addResponse + * @function + * @public + * @param{number} response - the response to the trial, must be either 0 (incorrect or + * non-detected) or 1 (correct or detected). + * @returns {void} + */ + addResponse(response) + { + // check that response is either 0 or 1: + if (response !== 0 && response !== 1) + { + throw { + origin: "MultiStairHandler.addResponse", + context: "when adding a trial response", + error: `the response must be either 0 or 1, got: ${JSON.stringify(response)}` + }; + } + + if (!this._finished) + { + // update the current staircase: + this._currentStaircase.addResponse(response); + + // move onto the next trial: + this._nextTrial(); + } + } + + /** + * Validate the conditions. + * + * @name module:data.MultiStairHandler#_validateConditions + * @function + * @protected + * @returns {void} + */ + _validateConditions() + { + try + { + // conditions must be a non empty array: + if (!Array.isArray(this._conditions) || this._conditions.length === 0) + { + throw "conditions should be a non empty array of objects"; + } + + // TODO this is temporary until we have implemented StairHandler: + if (this._stairType === MultiStairHandler.StaircaseType.SIMPLE) + { + throw "'simple' staircases are currently not supported"; + } + + for (const condition of this._conditions) + { + // each condition must be an object: + if (typeof condition !== "object") + { + throw "one of the conditions is not an object"; + } + + // each condition must include certain fields, such as startVal and label: + if (!("startVal" in condition)) + { + throw "each condition should include a startVal field"; + } + if (!("label" in condition)) + { + throw "each condition should include a label field"; + } + + // for QUEST, we also need startValSd: + if (this._stairType === MultiStairHandler.StaircaseType.QUEST && !("startValSd" in condition)) + { + throw "QUEST conditions must include a startValSd field"; + } + } + } + catch (error) + { + throw { + origin: "MultiStairHandler._validateConditions", + context: "when validating the conditions", + error + }; + } + } + + /** + * Setup the staircases, according to the conditions. + * + * @name module:data.MultiStairHandler#_prepareStaircases + * @function + * @protected + * @returns {void} + */ + _prepareStaircases() + { + try + { + this._validateConditions(); + + this._staircases = []; + + for (const condition of this._conditions) + { + let handler; + + // QUEST handler: + if (this._stairType === MultiStairHandler.StaircaseType.QUEST) + { + const args = Object.assign({}, condition); + args.psychoJS = this._psychoJS; + args.varName = this._varName; + args.name = condition.label; + args.autoLog = this._autoLog; + if (typeof condition.nTrials === "undefined") + { + args.nTrials = this._nTrials; + } + + handler = new QuestHandler(args); + } + + // simple StairCase handler: + if (this._stairType === MultiStairHandler.StaircaseType.SIMPLE) + { + // TODO not supported just yet, an exception is raised in _validateConditions + continue; + } + + this._staircases.push(handler); + } + + this._currentPass = []; + this._currentStaircase = null; + } + catch (error) + { + throw { + origin: "MultiStairHandler._prepareStaircases", + context: "when preparing the staircases", + error + }; + } + } + + /** + * Move onto the next trial. + * + * @name module:data.MultiStairHandler#_nextTrial + * @function + * @protected + * @returns {void} + */ + _nextTrial() + { + try + { + // if the current pass is empty, get a new one: + if (this._currentPass.length === 0) + { + this._currentPass = this._staircases.filter(handler => !handler.finished); + + if (this._multiMethod === TrialHandler.Method.SEQUENTIAL) + { + // nothing to do + } + else if (this._multiMethod === TrialHandler.Method.RANDOM) + { + this._currentPass = util.shuffle(this._currentPass, this._randomNumberGenerator); + } + else if (this._multiMethod === TrialHandler.Method.FULL_RANDOM) + { + if (this._currentPass.length > 0) + { + // select a handler at random: + const index = Math.floor(this._randomNumberGenerator() * this._currentPass.length); + const handler = this._currentPass[index]; + this._currentPass = [handler]; + } + } + } + + + // pick the next staircase in the pass: + this._currentStaircase = this._currentPass.shift(); + + + // test for termination: + if (typeof this._currentStaircase === "undefined") + { + this._finished = true; + + // update the snapshots associated with the current trial in the trial list: + for (let t = 0; t < this._snapshots.length - 1; ++t) + { + // the current trial is the last defined one: + if (typeof this._trialList[t + 1] === "undefined") + { + this._snapshots[t].finished = true; + break; + } + } + + return; + } + + + // get the value, based on the type of the trial handler: + let value = Number.MIN_VALUE; + if (this._currentStaircase instanceof QuestHandler) + { + value = this._currentStaircase.getQuestValue(); + } + // TODO add a test for simple staircase: + // if (this._currentStaircase instanceof StaircaseHandler) + // { + // value = this._currentStaircase.getStairValue(); + // } + + + this._psychoJS.logger.debug(`selected staircase: ${this._currentStaircase.name}, estimated value for variable ${this._varName}: ${value}`); + + + // update the next undefined trial in the trial list, and the associated snapshot: + for (let t = 0; t < this._trialList.length; ++t) + { + if (typeof this._trialList[t] === "undefined") + { + this._trialList[t] = {[this._varName]: value}; + + if (typeof this._snapshots[t] !== "undefined") + { + this._snapshots[t][this._varName] = value; + this._snapshots[t].trialAttributes.push(this._varName); + } + break; + } + } + } + catch (error) + { + throw { + origin: "MultiStairHandler._nextTrial", + context: "when moving onto the next trial", + error + }; + } + } +} + +/** + * MultiStairHandler staircase type. + * + * @enum {Symbol} + * @readonly + * @public + */ +MultiStairHandler.StaircaseType = { + /** + * Simple staircase handler. + */ + SIMPLE: Symbol.for("SIMPLE"), + + /** + * QUEST handler. + */ + QUEST: Symbol.for("QUEST") +}; + +/** + * Staircase status. + * + * @enum {Symbol} + * @readonly + * @public + */ +MultiStairHandler.StaircaseStatus = { + /** + * The staircase is currently running. + */ + RUNNING: Symbol.for("RUNNING"), + + /** + * The staircase is now finished. + */ + FINISHED: Symbol.for("FINISHED") +}; diff --git a/src/data/QuestHandler.js b/src/data/QuestHandler.js index 993d0a6..57def2b 100644 --- a/src/data/QuestHandler.js +++ b/src/data/QuestHandler.js @@ -9,7 +9,6 @@ */ - import {TrialHandler} from "./TrialHandler.js"; /** @@ -18,7 +17,7 @@ import {TrialHandler} from "./TrialHandler.js"; * * @class module.data.QuestHandler * @extends TrialHandler - * @param {Object} options + * @param {Object} options - the handler options * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance * @param {string} options.varName - the name of the variable / intensity / contrast / threshold manipulated by QUEST * @param {number} options.startVal - initial guess for the threshold @@ -43,23 +42,23 @@ export class QuestHandler extends TrialHandler * @public */ constructor({ - psychoJS, - varName, - startVal, - startValSd, - minVal, - maxVal, - pThreshold, - nTrials, - stopInterval, - method, - beta, - delta, - gamma, - grain, - name, - autoLog - } = {}) + psychoJS, + varName, + startVal, + startValSd, + minVal, + maxVal, + pThreshold, + nTrials, + stopInterval, + method, + beta, + delta, + gamma, + grain, + name, + autoLog + } = {}) { super({ psychoJS, @@ -70,25 +69,25 @@ export class QuestHandler extends TrialHandler nReps: 1 }); - this._addAttribute('varName', varName); - this._addAttribute('startVal', startVal); - this._addAttribute('minVal', minVal, Number.MIN_VALUE); - this._addAttribute('maxVal', maxVal, Number.MAX_VALUE); - this._addAttribute('startValSd', startValSd); - this._addAttribute('pThreshold', pThreshold, 0.82); - this._addAttribute('nTrials', nTrials); - this._addAttribute('stopInterval', stopInterval, Number.MIN_VALUE); - this._addAttribute('beta', beta, 3.5); - this._addAttribute('delta', delta, 0.01); - this._addAttribute('gamma', gamma, 0.5); - this._addAttribute('grain', grain, 0.01); - this._addAttribute('method', method, QuestHandler.Method.QUANTILE); + this._addAttribute("varName", varName); + this._addAttribute("startVal", startVal); + this._addAttribute("minVal", minVal, Number.MIN_VALUE); + this._addAttribute("maxVal", maxVal, Number.MAX_VALUE); + this._addAttribute("startValSd", startValSd); + this._addAttribute("pThreshold", pThreshold, 0.82); + this._addAttribute("nTrials", nTrials); + this._addAttribute("stopInterval", stopInterval, Number.MIN_VALUE); + this._addAttribute("beta", beta, 3.5); + this._addAttribute("delta", delta, 0.01); + this._addAttribute("gamma", gamma, 0.5); + this._addAttribute("grain", grain, 0.01); + this._addAttribute("method", method, QuestHandler.Method.QUANTILE); // setup jsQuest: this._setupJsQuest(); + this._estimateQuestValue(); } - /** * Add a response and update the PDF. * @@ -97,6 +96,7 @@ export class QuestHandler extends TrialHandler * @public * @param{number} response - the response to the trial, must be either 0 (incorrect or * non-detected) or 1 (correct or detected). + * @returns {void} */ addResponse(response) { @@ -104,8 +104,8 @@ export class QuestHandler extends TrialHandler if (response !== 0 && response !== 1) { throw { - origin: 'QuestHandler.addResponse', - context: 'when adding a trial response', + origin: "QuestHandler.addResponse", + context: "when adding a trial response", error: `the response must be either 0 or 1, got: ${JSON.stringify(response)}` }; } @@ -120,7 +120,6 @@ export class QuestHandler extends TrialHandler } } - /** * Simulate a response. * @@ -128,6 +127,7 @@ export class QuestHandler extends TrialHandler * @function * @public * @param{number} trueValue - the true, known value of the threshold / contrast / intensity + * @returns{number} the simulated response, 0 or 1 */ simulate(trueValue) { @@ -141,7 +141,6 @@ export class QuestHandler extends TrialHandler return response; } - /** * Get the mean of the Quest posterior PDF. * @@ -155,7 +154,6 @@ export class QuestHandler extends TrialHandler return jsQUEST.QuestMean(this._jsQuest); } - /** * Get the standard deviation of the Quest posterior PDF. * @@ -169,7 +167,6 @@ export class QuestHandler extends TrialHandler return jsQUEST.QuestSd(this._jsQuest); } - /** * Get the mode of the Quest posterior PDF. * @@ -184,7 +181,6 @@ export class QuestHandler extends TrialHandler return mode; } - /** * Get the standard deviation of the Quest posterior PDF. * @@ -199,6 +195,18 @@ export class QuestHandler extends TrialHandler return jsQUEST.QuestQuantile(this._jsQuest, quantileOrder); } + /** + * Get the current value of the variable / contrast / threshold. + * + * @name module:data.QuestHandler#getQuestValue + * @function + * @public + * @returns {number} the current QUEST value for the variable / contrast / threshold + */ + getQuestValue() + { + return this._questValue; + } /** * Get an estimate of the 5%-95% confidence interval (CI). @@ -206,7 +214,8 @@ export class QuestHandler extends TrialHandler * @name module:data.QuestHandler#confInterval * @function * @public - * @param{boolean} [getDifference=false] if true, return the width of the CI instead of the CI + * @param{boolean} [getDifference=false] - if true, return the width of the CI instead of the CI + * @returns{number[] | number} the 5%-95% CI or the width of the CI */ confInterval(getDifference = false) { @@ -225,13 +234,13 @@ export class QuestHandler extends TrialHandler } } - /** * Setup the JS Quest object. * * @name module:data.QuestHandler#_setupJsQuest * @function * @protected + * @returns {void} */ _setupJsQuest() { @@ -243,11 +252,8 @@ export class QuestHandler extends TrialHandler this._delta, this._gamma, this._grain); - - this._estimateQuestValue(); } - /** * Estimate the next value of the QUEST variable, based on the current value * and on the selected QUEST method. @@ -255,6 +261,7 @@ export class QuestHandler extends TrialHandler * @name module:data.QuestHandler#_estimateQuestValue * @function * @protected + * @returns {void} */ _estimateQuestValue() { @@ -275,8 +282,8 @@ export class QuestHandler extends TrialHandler else { throw { - origin: 'QuestHandler._estimateQuestValue', - context: 'when estimating the next value of the QUEST variable', + origin: "QuestHandler._estimateQuestValue", + context: "when estimating the next value of the QUEST variable", error: `unknown method: ${this._method}, please use: mean, mode, or quantile` }; } @@ -284,16 +291,15 @@ export class QuestHandler extends TrialHandler this._psychoJS.logger.debug(`estimated value for QUEST variable ${this._varName}: ${this._questValue}`); // check whether we should finish the trial: - if (this.thisN > 0 && - (this.nRemaining === 0 || this.confInterval(true) < this._stopInterval)) + if (this.thisN > 0 && (this.nRemaining === 0 || this.confInterval(true) < this._stopInterval)) { this._finished = true; // update the snapshots associated with the current trial in the trial list: - for (let t = 0; t < this._trialList.length-1; ++t) + for (let t = 0; t < this._snapshots.length - 1; ++t) { // the current trial is the last defined one: - if (typeof this._trialList[t+1] === 'undefined') + if (typeof this._trialList[t + 1] === "undefined") { this._snapshots[t].finished = true; break; @@ -306,11 +312,11 @@ export class QuestHandler extends TrialHandler // update the next undefined trial in the trial list, and the associated snapshot: for (let t = 0; t < this._trialList.length; ++t) { - if (typeof this._trialList[t] === 'undefined') + if (typeof this._trialList[t] === "undefined") { this._trialList[t] = { [this._varName]: this._questValue }; - if (typeof this._snapshots[t] !== 'undefined') + if (typeof this._snapshots[t] !== "undefined") { this._snapshots[t][this._varName] = this._questValue; this._snapshots[t].trialAttributes.push(this._varName); @@ -319,10 +325,8 @@ export class QuestHandler extends TrialHandler } } } - } - /** * QuestHandler method * @@ -334,15 +338,15 @@ QuestHandler.Method = { /** * Quantile threshold estimate. */ - QUANTILE: Symbol.for('QUANTILE'), + QUANTILE: Symbol.for("QUANTILE"), /** * Mean threshold estimate. */ - MEAN: Symbol.for('MEAN'), + MEAN: Symbol.for("MEAN"), /** * Mode threshold estimate. */ - MODE: Symbol.for('MODE') + MODE: Symbol.for("MODE") }; diff --git a/src/data/TrialHandler.js b/src/data/TrialHandler.js index 098afa7..06272ca 100644 --- a/src/data/TrialHandler.js +++ b/src/data/TrialHandler.js @@ -19,7 +19,7 @@ import * as util from "../util/Util.js"; * * @class * @extends PsychObject - * @param {Object} options + * @param {Object} options - the handler options * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance * @param {Array. | String} [options.trialList= [undefined] ] - if it is a string, we treat it as the name of a condition resource * @param {number} options.nReps - number of repetitions @@ -80,7 +80,7 @@ export class TrialHandler extends PsychObject this._addAttribute("name", name); this._addAttribute("autoLog", autoLog); this._addAttribute("seed", seed); - this._prepareTrialList(trialList); + this._prepareTrialList(); // number of stimuli this.nStim = this.trialList.length; @@ -520,7 +520,7 @@ export class TrialHandler extends PsychObject { try { - let resourceExtension = resourceName.split(".").pop(); + const resourceExtension = resourceName.split(".").pop(); if (["csv", "odp", "xls", "xlsx"].indexOf(resourceExtension) > -1) { // (*) read conditions from resource: @@ -551,9 +551,9 @@ export class TrialHandler extends PsychObject // (*) return the selected conditions as an array of 'object as map': // [ - // {field0: value0-0, field1: value0-1, ...} - // {field0: value1-0, field1: value1-1, ...} - // ... + // {field0: value0-0, field1: value0-1, ...} + // {field0: value1-0, field1: value1-1, ...} + // ... // ] let trialList = new Array(selectedRows.length - 1); for (let r = 0; r < selectedRows.length; ++r) @@ -617,10 +617,11 @@ export class TrialHandler extends PsychObject /** * Prepare the trial list. * + * @function * @protected - * @param {Array. | String} trialList - a list of trials, or the name of a condition resource + * @returns {void} */ - _prepareTrialList(trialList) + _prepareTrialList() { const response = { origin: "TrialHandler._prepareTrialList", @@ -628,28 +629,28 @@ export class TrialHandler extends PsychObject }; // we treat undefined trialList as a list with a single empty entry: - if (typeof trialList === "undefined") + if (typeof this._trialList === "undefined") { this.trialList = [undefined]; } // if trialList is an array, we make sure it is not empty: - else if (Array.isArray(trialList)) + else if (Array.isArray(this._trialList)) { - if (trialList.length === 0) + if (this._trialList.length === 0) { this.trialList = [undefined]; } } // if trialList is a string, we treat it as the name of the condition resource: - else if (typeof trialList === "string") + else if (typeof this._trialList === "string") { - this.trialList = TrialHandler.importConditions(this.psychoJS.serverManager, trialList); + this.trialList = TrialHandler.importConditions(this.psychoJS.serverManager, this._trialList); } // unknown type: else { throw Object.assign(response, { - error: "unable to prepare trial list: unknown type: " + (typeof trialList), + error: `unable to prepare trial list: unknown type: ${(typeof this._trialList)}` }); } } @@ -690,13 +691,13 @@ export class TrialHandler extends PsychObject // get an array of the indices of the elements of trialList : const indices = Array.from(this.trialList.keys()); - if (this.method === TrialHandler.Method.SEQUENTIAL) + if (this._method === TrialHandler.Method.SEQUENTIAL) { this._trialSequence = Array(this.nReps).fill(indices); // transposed version: // this._trialSequence = indices.reduce( (seq, e) => { seq.push( Array(this.nReps).fill(e) ); return seq; }, [] ); } - else if (this.method === TrialHandler.Method.RANDOM) + else if (this._method === TrialHandler.Method.RANDOM) { this._trialSequence = []; for (let i = 0; i < this.nReps; ++i) @@ -704,10 +705,10 @@ export class TrialHandler extends PsychObject this._trialSequence.push(util.shuffle(indices.slice(), this._randomNumberGenerator)); } } - else if (this.method === TrialHandler.Method.FULL_RANDOM) + else if (this._method === TrialHandler.Method.FULL_RANDOM) { // create a flat sequence with nReps repeats of indices: - let flatSequence = []; + const flatSequence = []; for (let i = 0; i < this.nReps; ++i) { flatSequence.push.apply(flatSequence, indices); diff --git a/src/data/index.js b/src/data/index.js index f001b32..5598fa3 100644 --- a/src/data/index.js +++ b/src/data/index.js @@ -1,4 +1,5 @@ -export * from './ExperimentHandler.js'; -export * from './TrialHandler.js'; -export * from './QuestHandler'; -//export * from './Shelf.js'; +export * from "./ExperimentHandler.js"; +export * from "./TrialHandler.js"; +export * from "./QuestHandler.js"; +export * from "./MultiStairHandler.js"; +//export * from "./Shelf.js";