From 4aa9ab012b193391587e52be6f679366a8758c85 Mon Sep 17 00:00:00 2001 From: Valkyrie Felso Date: Mon, 2 May 2022 15:48:52 +0200 Subject: [PATCH 01/21] remove .psiturk_history --- .gitignore | 1 + experiment/.psiturk_history | 91 ------------------------------------- 2 files changed, 1 insertion(+), 91 deletions(-) delete mode 100644 experiment/.psiturk_history diff --git a/.gitignore b/.gitignore index c18dd8d..398c163 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ __pycache__/ +experiment/.psiturk_history \ No newline at end of file diff --git a/experiment/.psiturk_history b/experiment/.psiturk_history deleted file mode 100644 index 39d450b..0000000 --- a/experiment/.psiturk_history +++ /dev/null @@ -1,91 +0,0 @@ -server off -server on -debug -exit -y -server off -server on -debug -server off -exit -y -server on -debug -exit -y -server on -debug -exit -y -serveron -server on -debug -exit -y -server on -debug -server off -exit -n -psiturk -help -help create -hit creat help -hit create help -hit create --help -hit help -hit --help -hit create -exit -serve on -debug -exit -server on -debug -exit -y -server on -exit -y -server on -debug -exit -y -server on -debug -hit create --help -hit help -hit create -hit -hit create -debug -exit -server on -exit -server on -debug -server off -server on -s -debug -server off -server on -debug -worker bonus --auto --hit 3B6F54KMR2C7FW0BQO0988WPVHX1S7 -exit -server on -debug -server off -server on -exit -server on -debug -exit -y -debug -server on -debug -debg -debug -exit -y From a7cdc699db699765178e375250e5f92530e56f5e Mon Sep 17 00:00:00 2001 From: Valkyrie Felso Date: Mon, 2 May 2022 15:52:54 +0200 Subject: [PATCH 02/21] add server log to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 398c163..bd3bb79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ -experiment/.psiturk_history \ No newline at end of file +experiment/.psiturk_history +experiment/server.log \ No newline at end of file From 067dcb5492f95b89b94613e53f9d7329714ab5b7 Mon Sep 17 00:00:00 2001 From: Valkyrie Felso Date: Tue, 10 May 2022 09:16:49 +0200 Subject: [PATCH 03/21] add local database to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bd3bb79..226b7a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__/ experiment/.psiturk_history -experiment/server.log \ No newline at end of file +experiment/server.log +experiment/participants.db \ No newline at end of file From 80c22235eb76fe06de60a90e400c86d14ba89410 Mon Sep 17 00:00:00 2001 From: Valkyrie Felso Date: Tue, 10 May 2022 09:19:05 +0200 Subject: [PATCH 04/21] update jsPsych to 7.2.1 --- experiment/static/js/jsPsych/README.md | 30 - experiment/static/js/jsPsych/contributors.md | 9 - experiment/static/js/jsPsych/css/jspsych.css | 372 -- .../static/js/jsPsych/jspsych-logo-readme.jpg | Bin 91551 -> 0 bytes experiment/static/js/jsPsych/jspsych.css | 516 ++ experiment/static/js/jsPsych/jspsych.js | 4684 +++++++++++------ experiment/static/js/jsPsych/license.txt | 21 - .../jsPsych/plugin-html-keyboard-response.js | 173 + .../plugins/jspsych-button-response.js | 152 - .../static/js/jsPsych/plugins/jspsych-html.js | 52 - .../jsPsych/plugins/jspsych-instructions.js | 160 - .../plugins/jspsych-survey-multi-choice.js | 166 - .../js/jsPsych/plugins/jspsych-survey-text.js | 97 - .../static/js/jsPsych/plugins/jspsych-text.js | 71 - .../template/jspsych-plugin-template.js | 32 - 15 files changed, 3892 insertions(+), 2643 deletions(-) delete mode 100644 experiment/static/js/jsPsych/README.md delete mode 100644 experiment/static/js/jsPsych/contributors.md delete mode 100644 experiment/static/js/jsPsych/css/jspsych.css delete mode 100644 experiment/static/js/jsPsych/jspsych-logo-readme.jpg create mode 100644 experiment/static/js/jsPsych/jspsych.css mode change 100755 => 100644 experiment/static/js/jsPsych/jspsych.js delete mode 100644 experiment/static/js/jsPsych/license.txt create mode 100644 experiment/static/js/jsPsych/plugin-html-keyboard-response.js delete mode 100644 experiment/static/js/jsPsych/plugins/jspsych-button-response.js delete mode 100644 experiment/static/js/jsPsych/plugins/jspsych-html.js delete mode 100644 experiment/static/js/jsPsych/plugins/jspsych-instructions.js delete mode 100644 experiment/static/js/jsPsych/plugins/jspsych-survey-multi-choice.js delete mode 100644 experiment/static/js/jsPsych/plugins/jspsych-survey-text.js delete mode 100644 experiment/static/js/jsPsych/plugins/jspsych-text.js delete mode 100644 experiment/static/js/jsPsych/plugins/template/jspsych-plugin-template.js diff --git a/experiment/static/js/jsPsych/README.md b/experiment/static/js/jsPsych/README.md deleted file mode 100644 index da2f3f2..0000000 --- a/experiment/static/js/jsPsych/README.md +++ /dev/null @@ -1,30 +0,0 @@ -![logo](jspsych-logo-readme.jpg) - -jsPsych is a JavaScript library for creating and running behavioral experiments in a web browser. jsPsych simplifies the process of coding browser-based experiments by providing a set of flexibile plugins that define different kinds of tasks a subject could complete during an experiment. By assembling different plugins together and customizing the parameters of each, it is possible to create many different types of experiments. - -Documentation -------------- - -Documentation is available at [docs.jspsych.org](http://docs.jspsych.org). - -Contributing ------------- - -Contributions to the code are welcome. Please use the [Issue tracker system](https://github.com/jodeleeuw/jsPsych/issues) to report bugs or discuss suggestions for new features and improvements. If you would like to contribute code, [submit a Pull request](https://help.github.com/articles/using-pull-requests). - -Need help? ----------- - -For questions about using the library, please post to the [jsPsych e-mail list](https://groups.google.com/forum/#!forum/jspsych). This creates a publically available archive of questions and solutions. - -Contact -------- - -jsPsych was created by Josh de Leeuw ([@jodeleeuw](https://github.com/jodeleeuw)) at Indiana University. - -Citation --------- - -If you use this library in academic work, please cite the [paper that describes jsPsych](http://link.springer.com/article/10.3758%2Fs13428-014-0458-y) - -de Leeuw, J.R. (2015). jsPsych: A JavaScript library for creating behavioral experiments in a Web browser. *Behavior Research Methods*, _47_(1), 1-12. doi:10.3758/s13428-014-0458-y diff --git a/experiment/static/js/jsPsych/contributors.md b/experiment/static/js/jsPsych/contributors.md deleted file mode 100644 index af9f922..0000000 --- a/experiment/static/js/jsPsych/contributors.md +++ /dev/null @@ -1,9 +0,0 @@ -The following people have contributed to the development of jsPsych by writing code, documentation, and/or suggesting major improvements (in alphabetical order): -* Jason Carpenter -* Josh de Leeuw - https://github.com/jodeleeuw -* Jonas Lambers -* Shane Martin - https://github.com/shamrt -* Adrian Oesch - https://github.com/adrianoesch -* Marian Sauter - https://github.com/mariansauter -* Tim Vergenz - https://github.com/vergenzt -* Erik Weitnauer - https://github.com/eweitnauer diff --git a/experiment/static/js/jsPsych/css/jspsych.css b/experiment/static/js/jsPsych/css/jspsych.css deleted file mode 100644 index 614b651..0000000 --- a/experiment/static/js/jsPsych/css/jspsych.css +++ /dev/null @@ -1,372 +0,0 @@ -/* - * CSS for jsPsych experiments. - * - * This stylesheet provides minimal styling to make jsPsych - * experiments look polished without any additional styles. - */ - - -/* fonts and type */ - -@import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); -html { - font-family: 'Open Sans', 'Arial', sans-serif; - font-size: 18px; - line-height: 1.6em; -} -body { - margin: 0; - padding: 0; -} -p { - clear: both; -} -.very-small { - font-size: 50%; -} -.small { - font-size: 75%; -} -.large { - font-size: 125%; -} -.very-large { - font-size: 150%; -} - -/* Classes for changing location of things */ - -.left { - float: left; -} -.right { - float: right; -} -.center-content { - text-align: center; -} - -.block-center { - margin: 1em auto; - display: block; -} -/* Form elements like input fields and buttons */ - -input[type="text"] { - font-family: 'Open Sans', 'Arial', sans-sefif; - font-size: 14px; -} - -/* borrowing Bootstrap style for btn elements, but combining styles a bit */ -.jspsych-btn { - display: inline-block; - padding: 6px 12px; - margin: 0px 8px; - font-size: 14px; - font-weight: 400; - font-family: 'Open Sans', 'Arial', sans-serif; - cursor: pointer; - line-height: 1.4; - text-align: center; - white-space: nowrap; - vertical-align: middle; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; - color: #333; - background-color: #fff; - border-color: #ccc; -} - -.jspsych-btn:hover { - background-color: #ddd; - border-color: #aaa; -} - -/* Container holding jsPsych content */ - -.jspsych-display-element { - width: 1000px; - margin: 50px auto 50px auto; -} - -/* jsPsych progress bar */ - -#jspsych-progressbar-container { - color: #777; - border-bottom: 2px solid #dedede; - background-color: #f3f3f3; - margin-bottom: 1em; - text-align: center; - padding: 10px 0px; -} -#jspsych-progressbar-container s {} -#jspsych-progressbar-outer { - background-color: #dedede; - border-radius: 5px; - padding: 1px; - width: 800px; - margin: auto; -} -#jspsych-progressbar-inner { - background-color: #aaa; - /* #3EB3D7; */ - width: 0%; - height: 1em; - border-radius: 5px; -} - -/* PLUGIN: jspsych-mdp */ - -#jspsych-mdp-reward { - font-size: 80px; - font-weight: 400; - font-family: 'Open Sans', 'Arial', sans-serif; - text-align: center; - vertical-align: middle; - border: 1px solid transparent; - border-radius: 4px; - /*color: #333;*/ - /*background-color: #fff;*/ - /*border-color: #ccc;*/ -} - -#jspsych-distributed-imgs { - margin-top:90px; - margin-left: auto; - margin-right: auto; - - text-align: justify; - -ms-text-justify: distribute-all-lines; - text-justify: distribute-all-lines; -} -#jspsych-distributed-imgs img { - width: 30%; - vertical-align: top; - display: inline-block; - *display: inline; - zoom: 1; -} -.stretch { - width: 100%; - display: inline-block; - font-size: 0; - line-height: 0 -} - -.big-text { - margin: auto; - padding: 200px; - font-size: 60px; - vertical-align: middle; - text-align: center; -} - - - -/* PLUGIN: jspsych-animation */ - -#jspsych-animation-image { - display: block; - margin-left: auto; - margin-right: auto; -} - -/* PLUGIN: jspsych-categorize-animation */ - -#jspsych-categorize-animation-stimulus { - display: block; - margin-left: auto; - margin-right: auto; -} - -/* PLUGIN: jspsych-categorize */ - -#jspsych-categorize-stimulus { - display: block; - margin-left: auto; - margin-right: auto; -} -.feedback { - display: block; - margin-left: auto; - margin-right: auto; -} - -/* PLUGIN: jspsych-free-sort */ - -#jspsych-free-sort-arena { - margin-left: auto; - margin-right: auto; - border: 2px solid #444; -} -.jspsych-free-sort-draggable { - cursor: move; -} -#jspsych-free-sort-done-btn { - display: block; - margin: auto; - margin-top: 25px; -} - -/* PLUGIN: jspsych-instructions */ - -.jspsych-instructions-nav { - text-align: center; - margin-top: 2em; -} -.jspsych-instructions-nav button { - margin: 20px; -} - -/* PLUGIN: jspsych-multi-stim-multi-response */ - -#jspsych-multi-stim-multi-response-stimulus { - display: block; - margin: auto; -} - -/* PLUGIN: jspsych-palmer */ - -#jspsych-palmer-snapCanvas { - margin-left: auto; - margin-right: auto; -} - -/* PLUGIN: jspsych-same-different */ - -.jspsych-same-different-stimulus { - display: block; - margin-left: auto; - margin-right: auto; -} - -/* PLUGIN: jspsych-single-stim */ - -#jspsych-single-stim-stimulus { - display: block; - margin-left: auto; - margin-right: auto; -} - -/* PLUGIN: jspsych-survey-text */ - -.jspsych-survey-text { - margin: 0.25em 0em; -} -.jspsych-survey-text-question { - margin: 2em 0em; -} - -/* PLUGIN: jspsych-survey-likert */ - -.jspsych-survey-likert-statement { - display:block; - font-size: 18px; - padding-top: 30px; - margin-bottom:10px; -} -.jspsych-survey-likert-opts { - list-style:none; - width:100%; - margin:0; - padding:0 0 35px; - display:block; - font-size: 14px; - line-height:1.1em; -} - -.jspsych-survey-likert-opt-label { - line-height: 1.1em; -} - -.jspsych-survey-likert-opts:before { - content: ''; - position:relative; - top:11px; - /*left:9.5%;*/ - display:block; - background-color:#efefef; - height:4px; - width:100%; -} - -.jspsych-survey-likert-opts:last-of-type { - border-bottom: 0; -} - -.jspsych-survey-likert-opts li { - display:inline-block; - /*width:19%;*/ - text-align:center; - vertical-align: top; -} - -.jspsych-survey-likert-opts li input[type=radio] { - display:block; - position:relative; - top:0; - left:50%; - margin-left:-6px; -} - -/* - * - * PLUGIN: jspsych-survey-multi-choice - * - */ - -.jspsych-survey-multi-choice-question { - margin-top: 2em; - margin-bottom: 2em; - } - -.jspsych-survey-multi-choice-text span.required { - color: darkred; -} - -.jspsych-survey-multi-choice-horizontal .jspsych-survey-multi-choice-text { - text-align: center; -} - -.jspsych-survey-multi-choice-option { - line-height: 2; -} -.jspsych-survey-multi-choice-horizontal .jspsych-survey-multi-choice-option { - display: inline-block; - margin-left: 1em; - margin-right: 1em; - vertical-align: top; -} - -label.jspsych-survey-multi-choice-text input[type="radio"] { - margin-right: 1em; -} - -#jspsych-survey-multi-choice-next { - display: block; - margin: auto; -} - -/** PLUGIN: jspsych-visual-search-circle **/ - -#jspsych-visual-search-circle-svg { - display: block; - margin-left: auto; - margin-right: auto; -} - -/* PLUGIN: jspsych-vsl-animate-occlusion */ - -#jspsych-vsl-animate-occlusion-canvas { - display: block; - margin: auto; -} - -/* PLUGIN: jspsych-xab */ - -.jspsych-xab-stimulus { - display: block; - margin-left: auto; - margin-right: auto; -} diff --git a/experiment/static/js/jsPsych/jspsych-logo-readme.jpg b/experiment/static/js/jsPsych/jspsych-logo-readme.jpg deleted file mode 100644 index b2f8313eb9b76902dda99412ac99de75f41a7e13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91551 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 z 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 { + const properties = new Set(); - var percent_complete = timeline.percentComplete() + do { + for (const key of Reflect.ownKeys(object)) { + properties.add([object, key]); + } + } while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype); - var obj = { - "total_trials": timeline.length(), - "current_trial_global": global_trial_index, - "percent_complete": percent_complete + return properties; }; - return obj; - }; + var autoBind = (self, {include, exclude} = {}) => { + const filter = key => { + const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); - core.startTime = function() { - return exp_start_time; - }; + if (include) { + return include.some(match); + } - core.totalTime = function() { - return (new Date()).getTime() - exp_start_time.getTime(); - }; + if (exclude) { + return !exclude.some(match); + } - core.getDisplayElement = function() { - return DOM_target; - }; + return true; + }; - core.finishTrial = function(data) { - // write the data from the trial - data = typeof data == 'undefined' ? {} : data; - jsPsych.data.write(data); + for (const [object, key] of getAllProperties(self.constructor.prototype)) { + if (key === 'constructor' || !filter(key)) { + continue; + } - // get back the data with all of the defaults in - var trial_data = jsPsych.data.getDataByTrialIndex(global_trial_index); + const descriptor = Reflect.getOwnPropertyDescriptor(object, key); + if (descriptor && typeof descriptor.value === 'function') { + self[key] = self[key].bind(self); + } + } - // handle callback at plugin level - if (typeof current_trial.on_finish === 'function') { - current_trial.on_finish(trial_data); - } + return self; + }; - // handle callback at whole-experiment level - opts.on_trial_finish(trial_data); + var version = "7.2.1"; - // wait for iti - if (typeof current_trial.timing_post_trial == 'undefined') { - if (opts.default_iti > 0) { - setTimeout(next_trial, opts.default_iti); - } else { - next_trial(); - } - } else { - if (current_trial.timing_post_trial > 0) { - setTimeout(next_trial, current_trial.timing_post_trial); - } else { - next_trial(); - } + class MigrationError extends Error { + constructor(message = "The global `jsPsych` variable is no longer available in jsPsych v7.") { + super(`${message} Please follow the migration guide at https://www.jspsych.org/7.0/support/migration-v7/ to update your experiment.`); + this.name = "MigrationError"; + } } + // Define a global jsPsych object to handle invocations on it with migration errors + window.jsPsych = { + get init() { + throw new MigrationError("`jsPsych.init()` was replaced by `initJsPsych()` in jsPsych v7."); + }, + get data() { + throw new MigrationError(); + }, + get randomization() { + throw new MigrationError(); + }, + get turk() { + throw new MigrationError(); + }, + get pluginAPI() { + throw new MigrationError(); + }, + get ALL_KEYS() { + throw new MigrationError('jsPsych.ALL_KEYS was replaced by the "ALL_KEYS" string in jsPsych v7.'); + }, + get NO_KEYS() { + throw new MigrationError('jsPsych.NO_KEYS was replaced by the "NO_KEYS" string in jsPsych v7.'); + }, + }; - function next_trial() { - global_trial_index++; - - // advance timeline - var complete = timeline.advance(); - - // update progress bar if shown - if (opts.show_progress_bar === true) { - updateProgressBar(); - } - - // check if experiment is over - if (complete) { - finishExperiment(); - return; - } - - doTrial(timeline.trial()); + /** + * Finds all of the unique items in an array. + * @param arr The array to extract unique values from + * @returns An array with one copy of each unique item in `arr` + */ + function unique(arr) { + return [...new Set(arr)]; + } + function deepCopy(obj) { + if (!obj) + return obj; + let out; + if (Array.isArray(obj)) { + out = []; + for (const x of obj) { + out.push(deepCopy(x)); + } + return out; + } + else if (typeof obj === "object" && obj !== null) { + out = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + out[key] = deepCopy(obj[key]); + } + } + return out; + } + else { + return obj; + } } - }; - - core.endExperiment = function(end_message) { - timeline.end_message = end_message; - timeline.end(); - } - - core.endCurrentTimeline = function() { - timeline.endActiveNode(); - } - - core.currentTrial = function() { - return current_trial; - }; - - core.initSettings = function() { - return opts; - }; - core.currentTimelineNodeID = function() { - return timeline.activeID(); - }; - - function TimelineNode(parameters, parent, relativeID) { - - // a unique ID for this node, relative to the parent - var relative_id; - - // store the timeline for this node - var timeline = []; - - // store the parent for this node - var parent_node; - - // if there is a loop function, store it - var loop_function; - - // if there is a conditional function, store it - var conditional_function; - - // data for the trial if this node is a trial - var trial_data; - - // flag to randomize the order of the trials - var randomize_order = false; - - // keep track of progress - var current_location = 0; - var current_iteration = 0; - - // flag to force the node to be finished - var done_flag = false; - - // reference to self - var self = this; - - // constructor - var _construct = function() { - // store a link to the parent of this node - parent_node = parent; - - // create the ID for this node - if (typeof parent == 'undefined') { - relative_id = 0; - } - relative_id = relativeID; - - // check if there is a timeline parameter - // if there is, then this is not a trial node - if (typeof parameters.timeline !== 'undefined') { - // extract all of the node level data and parameters - var node_data = $.extend(true, {}, parameters); - delete node_data.timeline; - delete node_data.conditional_function; - delete node_data.loop_function; - delete node_data.randomize_order; + var utils = /*#__PURE__*/Object.freeze({ + __proto__: null, + unique: unique, + deepCopy: deepCopy + }); - // create a TimelineNode for each element in the timeline - for (var i = 0; i < parameters.timeline.length; i++) { - timeline.push(new TimelineNode($.extend(true, {}, node_data, parameters.timeline[i]), self, i)); + class DataColumn { + constructor(values = []) { + this.values = values; } - // store the loop function if it exists - if (typeof parameters.loop_function !== 'undefined') { - loop_function = parameters.loop_function; + sum() { + let s = 0; + for (const v of this.values) { + s += v; + } + return s; } - // store the conditional function if it exists - if (typeof parameters.conditional_function !== 'undefined') { - conditional_function = parameters.conditional_function; + mean() { + return this.sum() / this.count(); } - // flag to randomize the order of trials - if (typeof parameters.randomize_order !== 'undefined') { - randomize_order = parameters.randomize_order; + median() { + if (this.values.length === 0) { + return undefined; + } + const numbers = this.values.slice(0).sort(function (a, b) { + return a - b; + }); + const middle = Math.floor(numbers.length / 2); + const isEven = numbers.length % 2 === 0; + return isEven ? (numbers[middle] + numbers[middle - 1]) / 2 : numbers[middle]; } - if (randomize_order === true) { - timeline = jsPsych.randomization.shuffle(timeline); + min() { + return Math.min.apply(null, this.values); } - } - // if there is no timeline parameter, then this node is a trial node - else { - // check to see if a valid trial type is defined - var trial_type = parameters.type; - if (typeof trial_type == 'undefined') { - console.error('Trial level node is missing the "type" parameter. The parameters for the node are: ' + JSON.stringify(parameters)); - } else if (typeof jsPsych.plugins[trial_type] == 'undefined') { - console.error('No plugin loaded for trials of type "' + trial_type + '"'); - } - // create a deep copy of the parameters for the trial - trial_data = $.extend(true, {}, parameters); - } - }(); - - // recursively get the number of **trials** contained in the timeline - // assuming that while loops execute exactly once and if conditionals - // always run - this.length = function() { - var length = 0; - if (timeline.length > 0) { - for (var i = 0; i < timeline.length; i++) { - length += timeline[i].length(); - } - } else { - return 1; - } - return length; - } - - // recursively get the next trial to run. - // if this node is a leaf (trial), then return the trial. - // otherwise, recursively find the next trial in the child timeline. - this.trial = function() { - if (timeline.length == 0) { - return trial_data; - } else { - if (current_location >= timeline.length) { - return null; - } else { - return timeline[current_location].trial(); + max() { + return Math.max.apply(null, this.values); } - } - } - - // update the current trial node to be completed - // returns true if the node is complete after advance - // returns false otherwise - this.advance = function() { - // first check to see if this node is done - if(done_flag){ - return true; - } - // propogate down to the current trial, and update the current_location - // of that node (effectively ending that node) - if (timeline.length !== 0) { - if (timeline[current_location].advance()) { - // if this returns true, then the node below is complete, and we need to - // advance this node. - current_location++; - if (this.checkCompletion()) { + count() { + return this.values.length; + } + variance() { + const mean = this.mean(); + let sum_square_error = 0; + for (const x of this.values) { + sum_square_error += Math.pow(x - mean, 2); + } + const mse = sum_square_error / (this.values.length - 1); + return mse; + } + sd() { + const mse = this.variance(); + const rmse = Math.sqrt(mse); + return rmse; + } + frequencies() { + const unique = {}; + for (const x of this.values) { + if (typeof unique[x] === "undefined") { + unique[x] = 1; + } + else { + unique[x]++; + } + } + return unique; + } + all(eval_fn) { + for (const x of this.values) { + if (!eval_fn(x)) { + return false; + } + } return true; - } else { - // we advanced the node, now we need to check if the node we advanced - // to is also complete, and keep advancing until we find a node that - // is not complete, or until this node is complete. - while (!this.checkCompletion() && timeline[current_location].checkCompletion()) { - current_location++; - } - if (this.checkCompletion()) { - return true; - } else { - return false; + } + subset(eval_fn) { + const out = []; + for (const x of this.values) { + if (eval_fn(x)) { + out.push(x); + } } - } - } else { - // if this returns false, then the node below is not complete, and we - // don't need to do anything else here - return false; - } - } else { - // if we get here, then this is a trial node, and the node is complete - current_location++; - done_flag = true; - return true; - } + return new DataColumn(out); + } } - // return true if the node is completely done (no more possible trials) - // otherwise, return false - this.checkCompletion = function() { - // if the done_flag is true, the node is complete no matter what. - if (done_flag) { - return true; - } - - // check for trial nodes - if (timeline.length == 0 && current_location > 0) { - done_flag = true; - return true; - } - - // check for non-trial nodes - if (timeline.length > 0) { - // checking nodes that have reached the end of the timeline. - // if there is a loop function, evaluate it. - // otherwise, the node is done. - if (current_location >= timeline.length) { - // check if there is a loop function - if (typeof loop_function !== 'undefined') { - if (loop_function(this.generatedData())) { - this.reset(); - } else { - done_flag = true; - return true; - } - } else { - done_flag = true; - return true; - } + // private function to save text file on local drive + function saveTextToFile(textstr, filename) { + const blobToSave = new Blob([textstr], { + type: "text/plain", + }); + let blobURL = ""; + if (typeof window.webkitURL !== "undefined") { + blobURL = window.webkitURL.createObjectURL(blobToSave); } - // checking nodes with conditional functions - if (typeof conditional_function !== 'undefined' && current_location == 0) { - if (conditional_function()) { - // run the timeline - return false; - } else { - // skip the timeline - done_flag = true; - return true; - } + else { + blobURL = window.URL.createObjectURL(blobToSave); } - } - - return false; + const link = document.createElement("a"); + link.id = "jspsych-download-as-text-link"; + link.style.display = "none"; + link.download = filename; + link.href = blobURL; + link.click(); + } + // this function based on code suggested by StackOverflow users: + // http://stackoverflow.com/users/64741/zachary + // http://stackoverflow.com/users/317/joseph-sturtevant + function JSON2CSV(objArray) { + const array = typeof objArray != "object" ? JSON.parse(objArray) : objArray; + let line = ""; + let result = ""; + const columns = []; + for (const row of array) { + for (const key in row) { + let keyString = key + ""; + keyString = '"' + keyString.replace(/"/g, '""') + '",'; + if (!columns.includes(key)) { + columns.push(key); + line += keyString; + } + } + } + line = line.slice(0, -1); // removes last comma + result += line + "\r\n"; + for (const row of array) { + line = ""; + for (const col of columns) { + let value = typeof row[col] === "undefined" ? "" : row[col]; + if (typeof value == "object") { + value = JSON.stringify(value); + } + const valueString = value + ""; + line += '"' + valueString.replace(/"/g, '""') + '",'; + } + line = line.slice(0, -1); + result += line + "\r\n"; + } + return result; + } + // this function is modified from StackOverflow: + // http://stackoverflow.com/posts/3855394 + function getQueryString() { + const a = window.location.search.substr(1).split("&"); + const b = {}; + for (let i = 0; i < a.length; ++i) { + const p = a[i].split("=", 2); + if (p.length == 1) + b[p[0]] = ""; + else + b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); + } + return b; } - // check the status of the done flag - this.isComplete = function() { - return done_flag; + class DataCollection { + constructor(data = []) { + this.trials = data; + } + push(new_data) { + this.trials.push(new_data); + return this; + } + join(other_data_collection) { + this.trials = this.trials.concat(other_data_collection.values()); + return this; + } + top() { + if (this.trials.length <= 1) { + return this; + } + else { + return new DataCollection([this.trials[this.trials.length - 1]]); + } + } + /** + * Queries the first n elements in a collection of trials. + * + * @param n A positive integer of elements to return. A value of + * n that is less than 1 will throw an error. + * + * @return First n objects of a collection of trials. If fewer than + * n trials are available, the trials.length elements will + * be returned. + * + */ + first(n = 1) { + if (n < 1) { + throw `You must query with a positive nonzero integer. Please use a + different value for n.`; + } + if (this.trials.length === 0) + return new DataCollection(); + if (n > this.trials.length) + n = this.trials.length; + return new DataCollection(this.trials.slice(0, n)); + } + /** + * Queries the last n elements in a collection of trials. + * + * @param n A positive integer of elements to return. A value of + * n that is less than 1 will throw an error. + * + * @return Last n objects of a collection of trials. If fewer than + * n trials are available, the trials.length elements will + * be returned. + * + */ + last(n = 1) { + if (n < 1) { + throw `You must query with a positive nonzero integer. Please use a + different value for n.`; + } + if (this.trials.length === 0) + return new DataCollection(); + if (n > this.trials.length) + n = this.trials.length; + return new DataCollection(this.trials.slice(this.trials.length - n, this.trials.length)); + } + values() { + return this.trials; + } + count() { + return this.trials.length; + } + readOnly() { + return new DataCollection(deepCopy(this.trials)); + } + addToAll(properties) { + for (const trial of this.trials) { + Object.assign(trial, properties); + } + return this; + } + addToLast(properties) { + if (this.trials.length != 0) { + Object.assign(this.trials[this.trials.length - 1], properties); + } + return this; + } + filter(filters) { + // [{p1: v1, p2:v2}, {p1:v2}] + // {p1: v1} + let f; + if (!Array.isArray(filters)) { + f = deepCopy([filters]); + } + else { + f = deepCopy(filters); + } + const filtered_data = []; + for (const trial of this.trials) { + let keep = false; + for (const filter of f) { + let match = true; + for (const key of Object.keys(filter)) { + if (typeof trial[key] !== "undefined" && trial[key] === filter[key]) ; + else { + match = false; + } + } + if (match) { + keep = true; + break; + } // can break because each filter is OR. + } + if (keep) { + filtered_data.push(trial); + } + } + return new DataCollection(filtered_data); + } + filterCustom(fn) { + return new DataCollection(this.trials.filter(fn)); + } + filterColumns(columns) { + return new DataCollection(this.trials.map((trial) => Object.fromEntries(columns.filter((key) => key in trial).map((key) => [key, trial[key]])))); + } + select(column) { + const values = []; + for (const trial of this.trials) { + if (typeof trial[column] !== "undefined") { + values.push(trial[column]); + } + } + return new DataColumn(values); + } + ignore(columns) { + if (!Array.isArray(columns)) { + columns = [columns]; + } + const o = deepCopy(this.trials); + for (const trial of o) { + for (const delete_key of columns) { + delete trial[delete_key]; + } + } + return new DataCollection(o); + } + uniqueNames() { + const names = []; + for (const trial of this.trials) { + for (const key of Object.keys(trial)) { + if (!names.includes(key)) { + names.push(key); + } + } + } + return names; + } + csv() { + return JSON2CSV(this.trials); + } + json(pretty = false) { + if (pretty) { + return JSON.stringify(this.trials, null, "\t"); + } + return JSON.stringify(this.trials); + } + localSave(format, filename) { + format = format.toLowerCase(); + let data_string; + if (format === "json") { + data_string = this.json(); + } + else if (format === "csv") { + data_string = this.csv(); + } + else { + throw new Error('Invalid format specified for localSave. Must be "json" or "csv".'); + } + saveTextToFile(data_string, filename); + } } - // return the percentage of trials completed, grouped at the first child level - // counts a set of trials as complete when the child node is done - this.percentComplete = function() { - var total_trials = this.length(); - var completed_trials = 0; - for (var i = 0; i < timeline.length; i++) { - if (timeline[i].isComplete()) { - completed_trials += timeline[i].length(); + class JsPsychData { + constructor(jsPsych) { + this.jsPsych = jsPsych; + // data properties for all trials + this.dataProperties = {}; + this.reset(); + } + reset() { + this.allData = new DataCollection(); + this.interactionData = new DataCollection(); + } + get() { + return this.allData; + } + getInteractionData() { + return this.interactionData; + } + write(data_object) { + const progress = this.jsPsych.getProgress(); + const trial = this.jsPsych.getCurrentTrial(); + //var trial_opt_data = typeof trial.data == 'function' ? trial.data() : trial.data; + const default_data = { + trial_type: trial.type.info.name, + trial_index: progress.current_trial_global, + time_elapsed: this.jsPsych.getTotalTime(), + internal_node_id: this.jsPsych.getCurrentTimelineNodeID(), + }; + this.allData.push(Object.assign(Object.assign(Object.assign(Object.assign({}, data_object), trial.data), default_data), this.dataProperties)); + } + addProperties(properties) { + // first, add the properties to all data that's already stored + this.allData.addToAll(properties); + // now add to list so that it gets appended to all future data + this.dataProperties = Object.assign({}, this.dataProperties, properties); + } + addDataToLastTrial(data) { + this.allData.addToLast(data); + } + getDataByTimelineNode(node_id) { + return this.allData.filterCustom((x) => x.internal_node_id.slice(0, node_id.length) === node_id); + } + getLastTrialData() { + return this.allData.top(); + } + getLastTimelineData() { + const lasttrial = this.getLastTrialData(); + const node_id = lasttrial.select("internal_node_id").values[0]; + if (typeof node_id === "undefined") { + return new DataCollection(); + } + else { + const parent_node_id = node_id.substr(0, node_id.lastIndexOf("-")); + const lastnodedata = this.getDataByTimelineNode(parent_node_id); + return lastnodedata; + } + } + displayData(format = "json") { + format = format.toLowerCase(); + if (format != "json" && format != "csv") { + console.log("Invalid format declared for displayData function. Using json as default."); + format = "json"; + } + const data_string = format === "json" ? this.allData.json(true) : this.allData.csv(); + const display_element = this.jsPsych.getDisplayElement(); + display_element.innerHTML = '
';
+            document.getElementById("jspsych-data-display").textContent = data_string;
+        }
+        urlVariables() {
+            if (typeof this.query_string == "undefined") {
+                this.query_string = getQueryString();
+            }
+            return this.query_string;
+        }
+        getURLVariable(whichvar) {
+            return this.urlVariables()[whichvar];
+        }
+        createInteractionListeners() {
+            // blur event capture
+            window.addEventListener("blur", () => {
+                const data = {
+                    event: "blur",
+                    trial: this.jsPsych.getProgress().current_trial_global,
+                    time: this.jsPsych.getTotalTime(),
+                };
+                this.interactionData.push(data);
+                this.jsPsych.getInitSettings().on_interaction_data_update(data);
+            });
+            // focus event capture
+            window.addEventListener("focus", () => {
+                const data = {
+                    event: "focus",
+                    trial: this.jsPsych.getProgress().current_trial_global,
+                    time: this.jsPsych.getTotalTime(),
+                };
+                this.interactionData.push(data);
+                this.jsPsych.getInitSettings().on_interaction_data_update(data);
+            });
+            // fullscreen change capture
+            const fullscreenchange = () => {
+                const data = {
+                    event: 
+                    // @ts-expect-error
+                    document.isFullScreen ||
+                        // @ts-expect-error
+                        document.webkitIsFullScreen ||
+                        // @ts-expect-error
+                        document.mozIsFullScreen ||
+                        document.fullscreenElement
+                        ? "fullscreenenter"
+                        : "fullscreenexit",
+                    trial: this.jsPsych.getProgress().current_trial_global,
+                    time: this.jsPsych.getTotalTime(),
+                };
+                this.interactionData.push(data);
+                this.jsPsych.getInitSettings().on_interaction_data_update(data);
+            };
+            document.addEventListener("fullscreenchange", fullscreenchange);
+            document.addEventListener("mozfullscreenchange", fullscreenchange);
+            document.addEventListener("webkitfullscreenchange", fullscreenchange);
+        }
+        // public methods for testing purposes. not recommended for use.
+        _customInsert(data) {
+            this.allData = new DataCollection(data);
+        }
+        _fullreset() {
+            this.reset();
+            this.dataProperties = {};
         }
-      }
-      return (completed_trials / total_trials * 100)
     }
 
-    // reset the location pointer to the start of the timeline, and reset all the
-    // child nodes on the timeline.
-    this.reset = function() {
-      current_location = 0;
-      done_flag = false;
-      if (timeline.length > 0) {
-        for (var i = 0; i < timeline.length; i++) {
-          timeline[i].reset();
+    class HardwareAPI {
+        constructor() {
+            /**
+             * Indicates whether this instance of jspsych has opened a hardware connection through our browser
+             * extension
+             **/
+            this.hardwareConnected = false;
+            //it might be useful to open up a line of communication from the extension back to this page
+            //script, again, this will have to pass through DOM events. For now speed is of no concern so I
+            //will use jQuery
+            document.addEventListener("jspsych-activate", (evt) => {
+                this.hardwareConnected = true;
+            });
         }
-
-        if (randomize_order === true) {
-          timeline = jsPsych.randomization.shuffle(timeline);
+        /**
+         * Allows communication with user hardware through our custom Google Chrome extension + native C++ program
+         * @param		mess	The message to be passed to our extension, see its documentation for the expected members of this object.
+         * @author	Daniel Rivas
+         *
+         */
+        hardware(mess) {
+            //since Chrome extension content-scripts do not share the javascript environment with the page
+            //script that loaded jspsych, we will need to use hacky methods like communicating through DOM
+            //events.
+            const jspsychEvt = new CustomEvent("jspsych", { detail: mess });
+            document.dispatchEvent(jspsychEvt);
+            //And voila! it will be the job of the content script injected by the extension to listen for
+            //the event and do the appropriate actions.
         }
-      } else {
-        // reset the parameters of this trial to the original parameters, which
-        // will reset any functions-as-parameters to the function.
-        trial_data = $.extend(true, {}, parameters);
-      }
-      current_iteration++;
     }
 
-    // mark this node as finished
-    this.end = function() {
-      done_flag = true;
+    class KeyboardListenerAPI {
+        constructor(getRootElement, areResponsesCaseSensitive = false, minimumValidRt = 0) {
+            this.getRootElement = getRootElement;
+            this.areResponsesCaseSensitive = areResponsesCaseSensitive;
+            this.minimumValidRt = minimumValidRt;
+            this.listeners = new Set();
+            this.heldKeys = new Set();
+            this.areRootListenersRegistered = false;
+            autoBind(this);
+            this.registerRootListeners();
+        }
+        /**
+         * If not previously done and `this.getRootElement()` returns an element, adds the root key
+         * listeners to that element.
+         */
+        registerRootListeners() {
+            if (!this.areRootListenersRegistered) {
+                const rootElement = this.getRootElement();
+                if (rootElement) {
+                    rootElement.addEventListener("keydown", this.rootKeydownListener);
+                    rootElement.addEventListener("keyup", this.rootKeyupListener);
+                    this.areRootListenersRegistered = true;
+                }
+            }
+        }
+        rootKeydownListener(e) {
+            // Iterate over a static copy of the listeners set because listeners might add other listeners
+            // that we do not want to be included in the loop
+            for (const listener of Array.from(this.listeners)) {
+                listener(e);
+            }
+            this.heldKeys.add(this.toLowerCaseIfInsensitive(e.key));
+        }
+        toLowerCaseIfInsensitive(string) {
+            return this.areResponsesCaseSensitive ? string : string.toLowerCase();
+        }
+        rootKeyupListener(e) {
+            this.heldKeys.delete(this.toLowerCaseIfInsensitive(e.key));
+        }
+        isResponseValid(validResponses, allowHeldKey, key) {
+            // check if key was already held down
+            if (!allowHeldKey && this.heldKeys.has(key)) {
+                return false;
+            }
+            if (validResponses === "ALL_KEYS") {
+                return true;
+            }
+            if (validResponses === "NO_KEYS") {
+                return false;
+            }
+            return validResponses.includes(key);
+        }
+        getKeyboardResponse({ callback_function, valid_responses = "ALL_KEYS", rt_method = "performance", persist, audio_context, audio_context_start_time, allow_held_key = false, minimum_valid_rt = this.minimumValidRt, }) {
+            if (rt_method !== "performance" && rt_method !== "audio") {
+                console.log('Invalid RT method specified in getKeyboardResponse. Defaulting to "performance" method.');
+                rt_method = "performance";
+            }
+            const usePerformanceRt = rt_method === "performance";
+            const startTime = usePerformanceRt ? performance.now() : audio_context_start_time * 1000;
+            this.registerRootListeners();
+            if (!this.areResponsesCaseSensitive && typeof valid_responses !== "string") {
+                valid_responses = valid_responses.map((r) => r.toLowerCase());
+            }
+            const listener = (e) => {
+                const rt = Math.round((rt_method == "performance" ? performance.now() : audio_context.currentTime * 1000) -
+                    startTime);
+                if (rt < minimum_valid_rt) {
+                    return;
+                }
+                const key = this.toLowerCaseIfInsensitive(e.key);
+                if (this.isResponseValid(valid_responses, allow_held_key, key)) {
+                    // if this is a valid response, then we don't want the key event to trigger other actions
+                    // like scrolling via the spacebar.
+                    e.preventDefault();
+                    if (!persist) {
+                        // remove keyboard listener if it exists
+                        this.cancelKeyboardResponse(listener);
+                    }
+                    callback_function({ key, rt });
+                }
+            };
+            this.listeners.add(listener);
+            return listener;
+        }
+        cancelKeyboardResponse(listener) {
+            // remove the listener from the set of listeners if it is contained
+            this.listeners.delete(listener);
+        }
+        cancelAllKeyboardResponses() {
+            this.listeners.clear();
+        }
+        compareKeys(key1, key2) {
+            if ((typeof key1 !== "string" && key1 !== null) ||
+                (typeof key2 !== "string" && key2 !== null)) {
+                console.error("Error in jsPsych.pluginAPI.compareKeys: arguments must be key strings or null.");
+                return undefined;
+            }
+            if (typeof key1 === "string" && typeof key2 === "string") {
+                // if both values are strings, then check whether or not letter case should be converted before comparing (case_sensitive_responses in initJsPsych)
+                return this.areResponsesCaseSensitive
+                    ? key1 === key2
+                    : key1.toLowerCase() === key2.toLowerCase();
+            }
+            return key1 === null && key2 === null;
+        }
     }
 
-    // recursively end whatever sub-node is running the current trial
-    this.endActiveNode = function() {
-      if (timeline.length == 0) {
-        this.end();
-        parent_node.end();
-      } else {
-        timeline[current_location].endActiveNode();
-      }
-    }
+    /**
+     * Parameter types for plugins
+     */
+    exports.ParameterType = void 0;
+    (function (ParameterType) {
+        ParameterType[ParameterType["BOOL"] = 0] = "BOOL";
+        ParameterType[ParameterType["STRING"] = 1] = "STRING";
+        ParameterType[ParameterType["INT"] = 2] = "INT";
+        ParameterType[ParameterType["FLOAT"] = 3] = "FLOAT";
+        ParameterType[ParameterType["FUNCTION"] = 4] = "FUNCTION";
+        ParameterType[ParameterType["KEY"] = 5] = "KEY";
+        ParameterType[ParameterType["KEYS"] = 6] = "KEYS";
+        ParameterType[ParameterType["SELECT"] = 7] = "SELECT";
+        ParameterType[ParameterType["HTML_STRING"] = 8] = "HTML_STRING";
+        ParameterType[ParameterType["IMAGE"] = 9] = "IMAGE";
+        ParameterType[ParameterType["AUDIO"] = 10] = "AUDIO";
+        ParameterType[ParameterType["VIDEO"] = 11] = "VIDEO";
+        ParameterType[ParameterType["OBJECT"] = 12] = "OBJECT";
+        ParameterType[ParameterType["COMPLEX"] = 13] = "COMPLEX";
+        ParameterType[ParameterType["TIMELINE"] = 14] = "TIMELINE";
+    })(exports.ParameterType || (exports.ParameterType = {}));
+    const universalPluginParameters = {
+        /**
+         * Data to add to this trial (key-value pairs)
+         */
+        data: {
+            type: exports.ParameterType.OBJECT,
+            pretty_name: "Data",
+            default: {},
+        },
+        /**
+         * Function to execute when trial begins
+         */
+        on_start: {
+            type: exports.ParameterType.FUNCTION,
+            pretty_name: "On start",
+            default: function () {
+                return;
+            },
+        },
+        /**
+         * Function to execute when trial is finished
+         */
+        on_finish: {
+            type: exports.ParameterType.FUNCTION,
+            pretty_name: "On finish",
+            default: function () {
+                return;
+            },
+        },
+        /**
+         * Function to execute after the trial has loaded
+         */
+        on_load: {
+            type: exports.ParameterType.FUNCTION,
+            pretty_name: "On load",
+            default: function () {
+                return;
+            },
+        },
+        /**
+         * Length of gap between the end of this trial and the start of the next trial
+         */
+        post_trial_gap: {
+            type: exports.ParameterType.INT,
+            pretty_name: "Post trial gap",
+            default: null,
+        },
+        /**
+         * A list of CSS classes to add to the jsPsych display element for the duration of this trial
+         */
+        css_classes: {
+            type: exports.ParameterType.STRING,
+            pretty_name: "Custom CSS classes",
+            default: null,
+        },
+        /**
+         * Options to control simulation mode for the trial.
+         */
+        simulation_options: {
+            type: exports.ParameterType.COMPLEX,
+            default: null,
+        },
+    };
 
-    // get a unique ID associated with this node
-    // the ID reflects the current iteration through this node.
-    this.ID = function() {
-      var id = "";
-      if (typeof parent_node == 'undefined') {
-        return "0." + current_iteration;
-      } else {
-        id += parent_node.ID() + "-";
-        id += relative_id + "." + current_iteration;
-        return id;
-      }
+    const preloadParameterTypes = [
+        exports.ParameterType.AUDIO,
+        exports.ParameterType.IMAGE,
+        exports.ParameterType.VIDEO,
+    ];
+    class MediaAPI {
+        constructor(useWebaudio, webaudioContext) {
+            this.useWebaudio = useWebaudio;
+            this.webaudioContext = webaudioContext;
+            // video //
+            this.video_buffers = {};
+            // audio //
+            this.context = null;
+            this.audio_buffers = [];
+            // preloading stimuli //
+            this.preload_requests = [];
+            this.img_cache = {};
+            this.preloadMap = new Map();
+            this.microphone_recorder = null;
+        }
+        getVideoBuffer(videoID) {
+            return this.video_buffers[videoID];
+        }
+        initAudio() {
+            this.context = this.useWebaudio ? this.webaudioContext : null;
+        }
+        audioContext() {
+            if (this.context !== null) {
+                if (this.context.state !== "running") {
+                    this.context.resume();
+                }
+            }
+            return this.context;
+        }
+        getAudioBuffer(audioID) {
+            return new Promise((resolve, reject) => {
+                // check whether audio file already preloaded
+                if (typeof this.audio_buffers[audioID] == "undefined" ||
+                    this.audio_buffers[audioID] == "tmp") {
+                    // if audio is not already loaded, try to load it
+                    this.preloadAudio([audioID], () => {
+                        resolve(this.audio_buffers[audioID]);
+                    }, () => { }, (e) => {
+                        reject(e.error);
+                    });
+                }
+                else {
+                    // audio is already loaded
+                    resolve(this.audio_buffers[audioID]);
+                }
+            });
+        }
+        preloadAudio(files, callback_complete = () => { }, callback_load = (filepath) => { }, callback_error = (error_msg) => { }) {
+            files = unique(files.flat());
+            let n_loaded = 0;
+            if (files.length == 0) {
+                callback_complete();
+                return;
+            }
+            const load_audio_file_webaudio = (source, count = 1) => {
+                const request = new XMLHttpRequest();
+                request.open("GET", source, true);
+                request.responseType = "arraybuffer";
+                request.onload = () => {
+                    this.context.decodeAudioData(request.response, (buffer) => {
+                        this.audio_buffers[source] = buffer;
+                        n_loaded++;
+                        callback_load(source);
+                        if (n_loaded == files.length) {
+                            callback_complete();
+                        }
+                    }, (e) => {
+                        callback_error({ source: source, error: e });
+                    });
+                };
+                request.onerror = function (e) {
+                    let err = e;
+                    if (this.status == 404) {
+                        err = "404";
+                    }
+                    callback_error({ source: source, error: err });
+                };
+                request.onloadend = function (e) {
+                    if (this.status == 404) {
+                        callback_error({ source: source, error: "404" });
+                    }
+                };
+                request.send();
+                this.preload_requests.push(request);
+            };
+            const load_audio_file_html5audio = (source, count = 1) => {
+                const audio = new Audio();
+                const handleCanPlayThrough = () => {
+                    this.audio_buffers[source] = audio;
+                    n_loaded++;
+                    callback_load(source);
+                    if (n_loaded == files.length) {
+                        callback_complete();
+                    }
+                    audio.removeEventListener("canplaythrough", handleCanPlayThrough);
+                };
+                audio.addEventListener("canplaythrough", handleCanPlayThrough);
+                audio.addEventListener("error", function handleError(e) {
+                    callback_error({ source: audio.src, error: e });
+                    audio.removeEventListener("error", handleError);
+                });
+                audio.addEventListener("abort", function handleAbort(e) {
+                    callback_error({ source: audio.src, error: e });
+                    audio.removeEventListener("abort", handleAbort);
+                });
+                audio.src = source;
+                this.preload_requests.push(audio);
+            };
+            for (const file of files) {
+                if (typeof this.audio_buffers[file] !== "undefined") {
+                    n_loaded++;
+                    callback_load(file);
+                    if (n_loaded == files.length) {
+                        callback_complete();
+                    }
+                }
+                else {
+                    this.audio_buffers[file] = "tmp";
+                    if (this.audioContext() !== null) {
+                        load_audio_file_webaudio(file);
+                    }
+                    else {
+                        load_audio_file_html5audio(file);
+                    }
+                }
+            }
+        }
+        preloadImages(images, callback_complete = () => { }, callback_load = (filepath) => { }, callback_error = (error_msg) => { }) {
+            // flatten the images array
+            images = unique(images.flat());
+            var n_loaded = 0;
+            if (images.length === 0) {
+                callback_complete();
+                return;
+            }
+            for (var i = 0; i < images.length; i++) {
+                var img = new Image();
+                img.onload = function () {
+                    n_loaded++;
+                    callback_load(img.src);
+                    if (n_loaded === images.length) {
+                        callback_complete();
+                    }
+                };
+                img.onerror = function (e) {
+                    callback_error({ source: img.src, error: e });
+                };
+                img.src = images[i];
+                this.img_cache[images[i]] = img;
+                this.preload_requests.push(img);
+            }
+        }
+        preloadVideo(videos, callback_complete = () => { }, callback_load = (filepath) => { }, callback_error = (error_msg) => { }) {
+            // flatten the video array
+            videos = unique(videos.flat());
+            let n_loaded = 0;
+            if (videos.length === 0) {
+                callback_complete();
+                return;
+            }
+            for (const video of videos) {
+                const video_buffers = this.video_buffers;
+                //based on option 4 here: http://dinbror.dk/blog/how-to-preload-entire-html5-video-before-play-solved/
+                const request = new XMLHttpRequest();
+                request.open("GET", video, true);
+                request.responseType = "blob";
+                request.onload = function () {
+                    if (this.status === 200 || this.status === 0) {
+                        const videoBlob = this.response;
+                        video_buffers[video] = URL.createObjectURL(videoBlob); // IE10+
+                        n_loaded++;
+                        callback_load(video);
+                        if (n_loaded === videos.length) {
+                            callback_complete();
+                        }
+                    }
+                };
+                request.onerror = function (e) {
+                    let err = e;
+                    if (this.status == 404) {
+                        err = "404";
+                    }
+                    callback_error({ source: video, error: err });
+                };
+                request.onloadend = function (e) {
+                    if (this.status == 404) {
+                        callback_error({ source: video, error: "404" });
+                    }
+                };
+                request.send();
+                this.preload_requests.push(request);
+            }
+        }
+        getAutoPreloadList(timeline_description) {
+            /** Map each preload parameter type to a set of paths to be preloaded */
+            const preloadPaths = Object.fromEntries(preloadParameterTypes.map((type) => [type, new Set()]));
+            const traverseTimeline = (node, inheritedTrialType) => {
+                var _a, _b, _c, _d;
+                const isTimeline = typeof node.timeline !== "undefined";
+                if (isTimeline) {
+                    for (const childNode of node.timeline) {
+                        traverseTimeline(childNode, (_a = node.type) !== null && _a !== void 0 ? _a : inheritedTrialType);
+                    }
+                }
+                else if ((_c = ((_b = node.type) !== null && _b !== void 0 ? _b : inheritedTrialType)) === null || _c === void 0 ? void 0 : _c.info) {
+                    // node is a trial with type.info set
+                    // Get the plugin name and parameters object from the info object
+                    const { name: pluginName, parameters } = ((_d = node.type) !== null && _d !== void 0 ? _d : inheritedTrialType).info;
+                    // Extract parameters to be preloaded and their types from parameter info if this has not
+                    // yet been done for `pluginName`
+                    if (!this.preloadMap.has(pluginName)) {
+                        this.preloadMap.set(pluginName, Object.fromEntries(Object.entries(parameters)
+                            // Filter out parameter entries with media types and a non-false `preload` option
+                            .filter(([_name, { type, preload }]) => preloadParameterTypes.includes(type) && (preload !== null && preload !== void 0 ? preload : true))
+                            // Map each entry's value to its parameter type
+                            .map(([name, { type }]) => [name, type])));
+                    }
+                    // Add preload paths from this trial
+                    for (const [parameterName, parameterType] of Object.entries(this.preloadMap.get(pluginName))) {
+                        const parameterValue = node[parameterName];
+                        const elements = preloadPaths[parameterType];
+                        if (typeof parameterValue === "string") {
+                            elements.add(parameterValue);
+                        }
+                        else if (Array.isArray(parameterValue)) {
+                            for (const element of parameterValue.flat()) {
+                                if (typeof element === "string") {
+                                    elements.add(element);
+                                }
+                            }
+                        }
+                    }
+                }
+            };
+            traverseTimeline({ timeline: timeline_description });
+            return {
+                images: [...preloadPaths[exports.ParameterType.IMAGE]],
+                audio: [...preloadPaths[exports.ParameterType.AUDIO]],
+                video: [...preloadPaths[exports.ParameterType.VIDEO]],
+            };
+        }
+        cancelPreloads() {
+            for (const request of this.preload_requests) {
+                request.onload = () => { };
+                request.onerror = () => { };
+                request.oncanplaythrough = () => { };
+                request.onabort = () => { };
+            }
+            this.preload_requests = [];
+        }
+        initializeMicrophoneRecorder(stream) {
+            const recorder = new MediaRecorder(stream);
+            this.microphone_recorder = recorder;
+        }
+        getMicrophoneRecorder() {
+            return this.microphone_recorder;
+        }
     }
 
-    // get the ID of the active trial
-    this.activeID = function() {
-      if (timeline.length == 0) {
-        return this.ID();
-      } else {
-        return timeline[current_location].activeID();
-      }
+    class SimulationAPI {
+        dispatchEvent(event) {
+            document.body.dispatchEvent(event);
+        }
+        /**
+         * Dispatches a `keydown` event for the specified key
+         * @param key Character code (`.key` property) for the key to press.
+         */
+        keyDown(key) {
+            this.dispatchEvent(new KeyboardEvent("keydown", { key }));
+        }
+        /**
+         * Dispatches a `keyup` event for the specified key
+         * @param key Character code (`.key` property) for the key to press.
+         */
+        keyUp(key) {
+            this.dispatchEvent(new KeyboardEvent("keyup", { key }));
+        }
+        /**
+         * Dispatches a `keydown` and `keyup` event in sequence to simulate pressing a key.
+         * @param key Character code (`.key` property) for the key to press.
+         * @param delay Length of time to wait (ms) before executing action
+         */
+        pressKey(key, delay = 0) {
+            if (delay > 0) {
+                setTimeout(() => {
+                    this.keyDown(key);
+                    this.keyUp(key);
+                }, delay);
+            }
+            else {
+                this.keyDown(key);
+                this.keyUp(key);
+            }
+        }
+        /**
+         * Dispatches `mousedown`, `mouseup`, and `click` events on the target element
+         * @param target The element to click
+         * @param delay Length of time to wait (ms) before executing action
+         */
+        clickTarget(target, delay = 0) {
+            if (delay > 0) {
+                setTimeout(() => {
+                    target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
+                    target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
+                    target.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+                }, delay);
+            }
+            else {
+                target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
+                target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
+                target.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+            }
+        }
+        /**
+         * Sets the value of a target text input
+         * @param target A text input element to fill in
+         * @param text Text to input
+         * @param delay Length of time to wait (ms) before executing action
+         */
+        fillTextInput(target, text, delay = 0) {
+            if (delay > 0) {
+                setTimeout(() => {
+                    target.value = text;
+                }, delay);
+            }
+            else {
+                target.value = text;
+            }
+        }
+        /**
+         * Picks a valid key from `choices`, taking into account jsPsych-specific
+         * identifiers like "NO_KEYS" and "ALL_KEYS".
+         * @param choices Which keys are valid.
+         * @returns A key selected at random from the valid keys.
+         */
+        getValidKey(choices) {
+            const possible_keys = [
+                "a",
+                "b",
+                "c",
+                "d",
+                "e",
+                "f",
+                "g",
+                "h",
+                "i",
+                "j",
+                "k",
+                "l",
+                "m",
+                "n",
+                "o",
+                "p",
+                "q",
+                "r",
+                "s",
+                "t",
+                "u",
+                "v",
+                "w",
+                "x",
+                "y",
+                "z",
+                "0",
+                "1",
+                "2",
+                "3",
+                "4",
+                "5",
+                "6",
+                "7",
+                "8",
+                "9",
+                " ",
+            ];
+            let key;
+            if (choices == "NO_KEYS") {
+                key = null;
+            }
+            else if (choices == "ALL_KEYS") {
+                key = possible_keys[Math.floor(Math.random() * possible_keys.length)];
+            }
+            else {
+                const flat_choices = choices.flat();
+                key = flat_choices[Math.floor(Math.random() * flat_choices.length)];
+            }
+            return key;
+        }
+        mergeSimulationData(default_data, simulation_options) {
+            // override any data with data from simulation object
+            return Object.assign(Object.assign({}, default_data), simulation_options === null || simulation_options === void 0 ? void 0 : simulation_options.data);
+        }
+        ensureSimulationDataConsistency(trial, data) {
+            // All RTs must be rounded
+            if (data.rt) {
+                data.rt = Math.round(data.rt);
+            }
+            // If a trial_duration and rt exist, make sure that the RT is not longer than the trial.
+            if (trial.trial_duration && data.rt && data.rt > trial.trial_duration) {
+                data.rt = null;
+                if (data.response) {
+                    data.response = null;
+                }
+                if (data.correct) {
+                    data.correct = false;
+                }
+            }
+            // If trial.choices is NO_KEYS make sure that response and RT are null
+            if (trial.choices && trial.choices == "NO_KEYS") {
+                if (data.rt) {
+                    data.rt = null;
+                }
+                if (data.response) {
+                    data.response = null;
+                }
+            }
+            // If response is not allowed before stimulus display complete, ensure RT
+            // is longer than display time.
+            if (trial.allow_response_before_complete) {
+                if (trial.sequence_reps && trial.frame_time) {
+                    const min_time = trial.sequence_reps * trial.frame_time * trial.stimuli.length;
+                    if (data.rt < min_time) {
+                        data.rt = null;
+                        data.response = null;
+                    }
+                }
+            }
+        }
     }
 
-    // get all the data generated within this node
-    this.generatedData = function() {
-      return jsPsych.data.getDataByTimelineNode(this.ID());
+    class TimeoutAPI {
+        constructor() {
+            this.timeout_handlers = [];
+        }
+        setTimeout(callback, delay) {
+            const handle = window.setTimeout(callback, delay);
+            this.timeout_handlers.push(handle);
+            return handle;
+        }
+        clearAllTimeouts() {
+            for (const handler of this.timeout_handlers) {
+                clearTimeout(handler);
+            }
+            this.timeout_handlers = [];
+        }
     }
 
-    // get all the trials of a particular type
-    this.trialsOfType = function(type) {
-      if (timeline.length == 0) {
-        if (trial_data.type == type) {
-          return trial_data;
+    function createJointPluginAPIObject(jsPsych) {
+        const settings = jsPsych.getInitSettings();
+        return Object.assign({}, ...[
+            new KeyboardListenerAPI(jsPsych.getDisplayContainerElement, settings.case_sensitive_responses, settings.minimum_valid_rt),
+            new TimeoutAPI(),
+            new MediaAPI(settings.use_webaudio, jsPsych.webaudio_context),
+            new HardwareAPI(),
+            new SimulationAPI(),
+        ].map((object) => autoBind(object)));
+    }
+
+    var wordList = [
+      // Borrowed from xkcd password generator which borrowed it from wherever
+      "ability","able","aboard","about","above","accept","accident","according",
+      "account","accurate","acres","across","act","action","active","activity",
+      "actual","actually","add","addition","additional","adjective","adult","adventure",
+      "advice","affect","afraid","after","afternoon","again","against","age",
+      "ago","agree","ahead","aid","air","airplane","alike","alive",
+      "all","allow","almost","alone","along","aloud","alphabet","already",
+      "also","although","am","among","amount","ancient","angle","angry",
+      "animal","announced","another","answer","ants","any","anybody","anyone",
+      "anything","anyway","anywhere","apart","apartment","appearance","apple","applied",
+      "appropriate","are","area","arm","army","around","arrange","arrangement",
+      "arrive","arrow","art","article","as","aside","ask","asleep",
+      "at","ate","atmosphere","atom","atomic","attached","attack","attempt",
+      "attention","audience","author","automobile","available","average","avoid","aware",
+      "away","baby","back","bad","badly","bag","balance","ball",
+      "balloon","band","bank","bar","bare","bark","barn","base",
+      "baseball","basic","basis","basket","bat","battle","be","bean",
+      "bear","beat","beautiful","beauty","became","because","become","becoming",
+      "bee","been","before","began","beginning","begun","behavior","behind",
+      "being","believed","bell","belong","below","belt","bend","beneath",
+      "bent","beside","best","bet","better","between","beyond","bicycle",
+      "bigger","biggest","bill","birds","birth","birthday","bit","bite",
+      "black","blank","blanket","blew","blind","block","blood","blow",
+      "blue","board","boat","body","bone","book","border","born",
+      "both","bottle","bottom","bound","bow","bowl","box","boy",
+      "brain","branch","brass","brave","bread","break","breakfast","breath",
+      "breathe","breathing","breeze","brick","bridge","brief","bright","bring",
+      "broad","broke","broken","brother","brought","brown","brush","buffalo",
+      "build","building","built","buried","burn","burst","bus","bush",
+      "business","busy","but","butter","buy","by","cabin","cage",
+      "cake","call","calm","came","camera","camp","can","canal",
+      "cannot","cap","capital","captain","captured","car","carbon","card",
+      "care","careful","carefully","carried","carry","case","cast","castle",
+      "cat","catch","cattle","caught","cause","cave","cell","cent",
+      "center","central","century","certain","certainly","chain","chair","chamber",
+      "chance","change","changing","chapter","character","characteristic","charge","chart",
+      "check","cheese","chemical","chest","chicken","chief","child","children",
+      "choice","choose","chose","chosen","church","circle","circus","citizen",
+      "city","class","classroom","claws","clay","clean","clear","clearly",
+      "climate","climb","clock","close","closely","closer","cloth","clothes",
+      "clothing","cloud","club","coach","coal","coast","coat","coffee",
+      "cold","collect","college","colony","color","column","combination","combine",
+      "come","comfortable","coming","command","common","community","company","compare",
+      "compass","complete","completely","complex","composed","composition","compound","concerned",
+      "condition","congress","connected","consider","consist","consonant","constantly","construction",
+      "contain","continent","continued","contrast","control","conversation","cook","cookies",
+      "cool","copper","copy","corn","corner","correct","correctly","cost",
+      "cotton","could","count","country","couple","courage","course","court",
+      "cover","cow","cowboy","crack","cream","create","creature","crew",
+      "crop","cross","crowd","cry","cup","curious","current","curve",
+      "customs","cut","cutting","daily","damage","dance","danger","dangerous",
+      "dark","darkness","date","daughter","dawn","day","dead","deal",
+      "dear","death","decide","declared","deep","deeply","deer","definition",
+      "degree","depend","depth","describe","desert","design","desk","detail",
+      "determine","develop","development","diagram","diameter","did","die","differ",
+      "difference","different","difficult","difficulty","dig","dinner","direct","direction",
+      "directly","dirt","dirty","disappear","discover","discovery","discuss","discussion",
+      "disease","dish","distance","distant","divide","division","do","doctor",
+      "does","dog","doing","doll","dollar","done","donkey","door",
+      "dot","double","doubt","down","dozen","draw","drawn","dream",
+      "dress","drew","dried","drink","drive","driven","driver","driving",
+      "drop","dropped","drove","dry","duck","due","dug","dull",
+      "during","dust","duty","each","eager","ear","earlier","early",
+      "earn","earth","easier","easily","east","easy","eat","eaten",
+      "edge","education","effect","effort","egg","eight","either","electric",
+      "electricity","element","elephant","eleven","else","empty","end","enemy",
+      "energy","engine","engineer","enjoy","enough","enter","entire","entirely",
+      "environment","equal","equally","equator","equipment","escape","especially","essential",
+      "establish","even","evening","event","eventually","ever","every","everybody",
+      "everyone","everything","everywhere","evidence","exact","exactly","examine","example",
+      "excellent","except","exchange","excited","excitement","exciting","exclaimed","exercise",
+      "exist","expect","experience","experiment","explain","explanation","explore","express",
+      "expression","extra","eye","face","facing","fact","factor","factory",
+      "failed","fair","fairly","fall","fallen","familiar","family","famous",
+      "far","farm","farmer","farther","fast","fastened","faster","fat",
+      "father","favorite","fear","feathers","feature","fed","feed","feel",
+      "feet","fell","fellow","felt","fence","few","fewer","field",
+      "fierce","fifteen","fifth","fifty","fight","fighting","figure","fill",
+      "film","final","finally","find","fine","finest","finger","finish",
+      "fire","fireplace","firm","first","fish","five","fix","flag",
+      "flame","flat","flew","flies","flight","floating","floor","flow",
+      "flower","fly","fog","folks","follow","food","foot","football",
+      "for","force","foreign","forest","forget","forgot","forgotten","form",
+      "former","fort","forth","forty","forward","fought","found","four",
+      "fourth","fox","frame","free","freedom","frequently","fresh","friend",
+      "friendly","frighten","frog","from","front","frozen","fruit","fuel",
+      "full","fully","fun","function","funny","fur","furniture","further",
+      "future","gain","game","garage","garden","gas","gasoline","gate",
+      "gather","gave","general","generally","gentle","gently","get","getting",
+      "giant","gift","girl","give","given","giving","glad","glass",
+      "globe","go","goes","gold","golden","gone","good","goose",
+      "got","government","grabbed","grade","gradually","grain","grandfather","grandmother",
+      "graph","grass","gravity","gray","great","greater","greatest","greatly",
+      "green","grew","ground","group","grow","grown","growth","guard",
+      "guess","guide","gulf","gun","habit","had","hair","half",
+      "halfway","hall","hand","handle","handsome","hang","happen","happened",
+      "happily","happy","harbor","hard","harder","hardly","has","hat",
+      "have","having","hay","he","headed","heading","health","heard",
+      "hearing","heart","heat","heavy","height","held","hello","help",
+      "helpful","her","herd","here","herself","hidden","hide","high",
+      "higher","highest","highway","hill","him","himself","his","history",
+      "hit","hold","hole","hollow","home","honor","hope","horn",
+      "horse","hospital","hot","hour","house","how","however","huge",
+      "human","hundred","hung","hungry","hunt","hunter","hurried","hurry",
+      "hurt","husband","ice","idea","identity","if","ill","image",
+      "imagine","immediately","importance","important","impossible","improve","in","inch",
+      "include","including","income","increase","indeed","independent","indicate","individual",
+      "industrial","industry","influence","information","inside","instance","instant","instead",
+      "instrument","interest","interior","into","introduced","invented","involved","iron",
+      "is","island","it","its","itself","jack","jar","jet",
+      "job","join","joined","journey","joy","judge","jump","jungle",
+      "just","keep","kept","key","kids","kill","kind","kitchen",
+      "knew","knife","know","knowledge","known","label","labor","lack",
+      "lady","laid","lake","lamp","land","language","large","larger",
+      "largest","last","late","later","laugh","law","lay","layers",
+      "lead","leader","leaf","learn","least","leather","leave","leaving",
+      "led","left","leg","length","lesson","let","letter","level",
+      "library","lie","life","lift","light","like","likely","limited",
+      "line","lion","lips","liquid","list","listen","little","live",
+      "living","load","local","locate","location","log","lonely","long",
+      "longer","look","loose","lose","loss","lost","lot","loud",
+      "love","lovely","low","lower","luck","lucky","lunch","lungs",
+      "lying","machine","machinery","mad","made","magic","magnet","mail",
+      "main","mainly","major","make","making","man","managed","manner",
+      "manufacturing","many","map","mark","market","married","mass","massage",
+      "master","material","mathematics","matter","may","maybe","me","meal",
+      "mean","means","meant","measure","meat","medicine","meet","melted",
+      "member","memory","men","mental","merely","met","metal","method",
+      "mice","middle","might","mighty","mile","military","milk","mill",
+      "mind","mine","minerals","minute","mirror","missing","mission","mistake",
+      "mix","mixture","model","modern","molecular","moment","money","monkey",
+      "month","mood","moon","more","morning","most","mostly","mother",
+      "motion","motor","mountain","mouse","mouth","move","movement","movie",
+      "moving","mud","muscle","music","musical","must","my","myself",
+      "mysterious","nails","name","nation","national","native","natural","naturally",
+      "nature","near","nearby","nearer","nearest","nearly","necessary","neck",
+      "needed","needle","needs","negative","neighbor","neighborhood","nervous","nest",
+      "never","new","news","newspaper","next","nice","night","nine",
+      "no","nobody","nodded","noise","none","noon","nor","north",
+      "nose","not","note","noted","nothing","notice","noun","now",
+      "number","numeral","nuts","object","observe","obtain","occasionally","occur",
+      "ocean","of","off","offer","office","officer","official","oil",
+      "old","older","oldest","on","once","one","only","onto",
+      "open","operation","opinion","opportunity","opposite","or","orange","orbit",
+      "order","ordinary","organization","organized","origin","original","other","ought",
+      "our","ourselves","out","outer","outline","outside","over","own",
+      "owner","oxygen","pack","package","page","paid","pain","paint",
+      "pair","palace","pale","pan","paper","paragraph","parallel","parent",
+      "park","part","particles","particular","particularly","partly","parts","party",
+      "pass","passage","past","path","pattern","pay","peace","pen",
+      "pencil","people","per","percent","perfect","perfectly","perhaps","period",
+      "person","personal","pet","phrase","physical","piano","pick","picture",
+      "pictured","pie","piece","pig","pile","pilot","pine","pink",
+      "pipe","pitch","place","plain","plan","plane","planet","planned",
+      "planning","plant","plastic","plate","plates","play","pleasant","please",
+      "pleasure","plenty","plural","plus","pocket","poem","poet","poetry",
+      "point","pole","police","policeman","political","pond","pony","pool",
+      "poor","popular","population","porch","port","position","positive","possible",
+      "possibly","post","pot","potatoes","pound","pour","powder","power",
+      "powerful","practical","practice","prepare","present","president","press","pressure",
+      "pretty","prevent","previous","price","pride","primitive","principal","principle",
+      "printed","private","prize","probably","problem","process","produce","product",
+      "production","program","progress","promised","proper","properly","property","protection",
+      "proud","prove","provide","public","pull","pupil","pure","purple",
+      "purpose","push","put","putting","quarter","queen","question","quick",
+      "quickly","quiet","quietly","quite","rabbit","race","radio","railroad",
+      "rain","raise","ran","ranch","range","rapidly","rate","rather",
+      "raw","rays","reach","read","reader","ready","real","realize",
+      "rear","reason","recall","receive","recent","recently","recognize","record",
+      "red","refer","refused","region","regular","related","relationship","religious",
+      "remain","remarkable","remember","remove","repeat","replace","replied","report",
+      "represent","require","research","respect","rest","result","return","review",
+      "rhyme","rhythm","rice","rich","ride","riding","right","ring",
+      "rise","rising","river","road","roar","rock","rocket","rocky",
+      "rod","roll","roof","room","root","rope","rose","rough",
+      "round","route","row","rubbed","rubber","rule","ruler","run",
+      "running","rush","sad","saddle","safe","safety","said","sail",
+      "sale","salmon","salt","same","sand","sang","sat","satellites",
+      "satisfied","save","saved","saw","say","scale","scared","scene",
+      "school","science","scientific","scientist","score","screen","sea","search",
+      "season","seat","second","secret","section","see","seed","seeing",
+      "seems","seen","seldom","select","selection","sell","send","sense",
+      "sent","sentence","separate","series","serious","serve","service","sets",
+      "setting","settle","settlers","seven","several","shade","shadow","shake",
+      "shaking","shall","shallow","shape","share","sharp","she","sheep",
+      "sheet","shelf","shells","shelter","shine","shinning","ship","shirt",
+      "shoe","shoot","shop","shore","short","shorter","shot","should",
+      "shoulder","shout","show","shown","shut","sick","sides","sight",
+      "sign","signal","silence","silent","silk","silly","silver","similar",
+      "simple","simplest","simply","since","sing","single","sink","sister",
+      "sit","sitting","situation","six","size","skill","skin","sky",
+      "slabs","slave","sleep","slept","slide","slight","slightly","slip",
+      "slipped","slope","slow","slowly","small","smaller","smallest","smell",
+      "smile","smoke","smooth","snake","snow","so","soap","social",
+      "society","soft","softly","soil","solar","sold","soldier","solid",
+      "solution","solve","some","somebody","somehow","someone","something","sometime",
+      "somewhere","son","song","soon","sort","sound","source","south",
+      "southern","space","speak","special","species","specific","speech","speed",
+      "spell","spend","spent","spider","spin","spirit","spite","split",
+      "spoken","sport","spread","spring","square","stage","stairs","stand",
+      "standard","star","stared","start","state","statement","station","stay",
+      "steady","steam","steel","steep","stems","step","stepped","stick",
+      "stiff","still","stock","stomach","stone","stood","stop","stopped",
+      "store","storm","story","stove","straight","strange","stranger","straw",
+      "stream","street","strength","stretch","strike","string","strip","strong",
+      "stronger","struck","structure","struggle","stuck","student","studied","studying",
+      "subject","substance","success","successful","such","sudden","suddenly","sugar",
+      "suggest","suit","sum","summer","sun","sunlight","supper","supply",
+      "support","suppose","sure","surface","surprise","surrounded","swam","sweet",
+      "swept","swim","swimming","swing","swung","syllable","symbol","system",
+      "table","tail","take","taken","tales","talk","tall","tank",
+      "tape","task","taste","taught","tax","tea","teach","teacher",
+      "team","tears","teeth","telephone","television","tell","temperature","ten",
+      "tent","term","terrible","test","than","thank","that","thee",
+      "them","themselves","then","theory","there","therefore","these","they",
+      "thick","thin","thing","think","third","thirty","this","those",
+      "thou","though","thought","thousand","thread","three","threw","throat",
+      "through","throughout","throw","thrown","thumb","thus","thy","tide",
+      "tie","tight","tightly","till","time","tin","tiny","tip",
+      "tired","title","to","tobacco","today","together","told","tomorrow",
+      "tone","tongue","tonight","too","took","tool","top","topic",
+      "torn","total","touch","toward","tower","town","toy","trace",
+      "track","trade","traffic","trail","train","transportation","trap","travel",
+      "treated","tree","triangle","tribe","trick","tried","trip","troops",
+      "tropical","trouble","truck","trunk","truth","try","tube","tune",
+      "turn","twelve","twenty","twice","two","type","typical","uncle",
+      "under","underline","understanding","unhappy","union","unit","universe","unknown",
+      "unless","until","unusual","up","upon","upper","upward","us",
+      "use","useful","using","usual","usually","valley","valuable","value",
+      "vapor","variety","various","vast","vegetable","verb","vertical","very",
+      "vessels","victory","view","village","visit","visitor","voice","volume",
+      "vote","vowel","voyage","wagon","wait","walk","wall","want",
+      "war","warm","warn","was","wash","waste","watch","water",
+      "wave","way","we","weak","wealth","wear","weather","week",
+      "weigh","weight","welcome","well","went","were","west","western",
+      "wet","whale","what","whatever","wheat","wheel","when","whenever",
+      "where","wherever","whether","which","while","whispered","whistle","white",
+      "who","whole","whom","whose","why","wide","widely","wife",
+      "wild","will","willing","win","wind","window","wing","winter",
+      "wire","wise","wish","with","within","without","wolf","women",
+      "won","wonder","wonderful","wood","wooden","wool","word","wore",
+      "work","worker","world","worried","worry","worse","worth","would",
+      "wrapped","write","writer","writing","written","wrong","wrote","yard",
+      "year","yellow","yes","yesterday","yet","you","young","younger",
+      "your","yourself","youth","zero","zebra","zipper","zoo","zulu"
+    ];
+
+    function words(options) {
+
+      function word() {
+        if (options && options.maxLength > 1) {
+          return generateWordWithMaxLength();
         } else {
-          return [];
-        }
-      } else {
-        var trials = [];
-        for (var i = 0; i < timeline.length; i++) {
-          var t = timeline[i].trialsOfType(type);
-          trials = trials.concat(t);
+          return generateRandomWord();
         }
-        return trials;
       }
-    }
-  }
-
-  function startExperiment() {
-
-    var fullscreen = opts.fullscreen;
-
-    // fullscreen setup
-    if (fullscreen) {
-      // check if keys are allowed in fullscreen mode
-      var keyboardNotAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
-      if (keyboardNotAllowed) {
-        go();
-      } else {
-        DOM_target.append('

The experiment will launch in fullscreen mode when you click the button below.

'); - $('#jspsych-fullscreen-btn').on('click', function() { - var element = document.documentElement; - if (element.requestFullscreen) { - element.requestFullscreen(); - } else if (element.mozRequestFullScreen) { - element.mozRequestFullScreen(); - } else if (element.webkitRequestFullscreen) { - element.webkitRequestFullscreen(); - } else if (element.msRequestFullscreen) { - element.msRequestFullscreen(); + + function generateWordWithMaxLength() { + var rightSize = false; + var wordUsed; + while (!rightSize) { + wordUsed = generateRandomWord(); + if(wordUsed.length <= options.maxLength) { + rightSize = true; } - $('#jspsych-fullscreen-btn').off('click'); - DOM_target.html(''); - go(); - }); - } - } else { - go(); - } - function go() { - // show progress bar if requested - if (opts.show_progress_bar === true) { - drawProgressBar(); + } + return wordUsed; } - // record the start time - exp_start_time = new Date(); - - // begin! - doTrial(timeline.trial()); - } - } - - function finishExperiment() { - opts.on_finish(jsPsych.data.getData()); - - if(typeof timeline.end_message !== 'undefined'){ - DOM_target.html(timeline.end_message); - } - - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - document.webkitExitFullscreen(); - } - - } - - function doTrial(trial) { - - current_trial = trial; - - // call experiment wide callback - opts.on_trial_start(); + function generateRandomWord() { + return wordList[randInt(wordList.length)]; + } - // check if trial has it's own display element - var display_element = DOM_target; - if(typeof trial.display_element !== 'undefined'){ - display_element = trial.display_element; - } + function randInt(lessThan) { + return Math.floor(Math.random() * lessThan); + } - // execute trial method - jsPsych.plugins[trial.type].trial(display_element, trial); - } + // No arguments = generate one word + if (typeof(options) === 'undefined') { + return word(); + } - function drawProgressBar() { - $('body').prepend($('
Completion Progress
')); - } + // Just a number = return that many words + if (typeof(options) === 'number') { + options = { exactly: options }; + } - function updateProgressBar() { - var progress = jsPsych.progress(); + // options supported: exactly, min, max, join + if (options.exactly) { + options.min = options.exactly; + options.max = options.exactly; + } + + // not a number = one word par string + if (typeof(options.wordsPerString) !== 'number') { + options.wordsPerString = 1; + } - $('#jspsych-progressbar-inner').css('width', progress.percent_complete + "%"); - } + //not a function = returns the raw word + if (typeof(options.formatter) !== 'function') { + options.formatter = (word) => word; + } - return core; -})(); + //not a string = separator is a space + if (typeof(options.separator) !== 'string') { + options.separator = ' '; + } -jsPsych.plugins = {}; + var total = options.min + randInt(options.max + 1 - options.min); + var results = []; + var token = ''; + var relativeIndex = 0; -jsPsych.data = (function() { + for (var i = 0; (i < total * options.wordsPerString); i++) { + if (relativeIndex === options.wordsPerString - 1) { + token += options.formatter(word(), relativeIndex); + } + else { + token += options.formatter(word(), relativeIndex) + options.separator; + } + relativeIndex++; + if ((i + 1) % options.wordsPerString === 0) { + results.push(token); + token = ''; + relativeIndex = 0; + } + + } + if (typeof options.join === 'string') { + results = results.join(options.join); + } - var module = {}; + return results; + } + + var randomWords$1 = words; + // Export the word list as it is often useful + words.wordList = wordList; + + var alea = {exports: {}}; + + (function (module) { + // A port of an algorithm by Johannes Baagøe , 2010 + // http://baagoe.com/en/RandomMusings/javascript/ + // https://github.com/nquinlan/better-random-numbers-for-javascript-mirror + // Original work is under MIT license - + + // Copyright (C) 2010 by Johannes Baagøe + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + // THE SOFTWARE. + + + + (function(global, module, define) { + + function Alea(seed) { + var me = this, mash = Mash(); + + me.next = function() { + var t = 2091639 * me.s0 + me.c * 2.3283064365386963e-10; // 2^-32 + me.s0 = me.s1; + me.s1 = me.s2; + return me.s2 = t - (me.c = t | 0); + }; - // data storage object - var allData = []; + // Apply the seeding algorithm from Baagoe. + me.c = 1; + me.s0 = mash(' '); + me.s1 = mash(' '); + me.s2 = mash(' '); + me.s0 -= mash(seed); + if (me.s0 < 0) { me.s0 += 1; } + me.s1 -= mash(seed); + if (me.s1 < 0) { me.s1 += 1; } + me.s2 -= mash(seed); + if (me.s2 < 0) { me.s2 += 1; } + mash = null; + } + + function copy(f, t) { + t.c = f.c; + t.s0 = f.s0; + t.s1 = f.s1; + t.s2 = f.s2; + return t; + } + + function impl(seed, opts) { + var xg = new Alea(seed), + state = opts && opts.state, + prng = xg.next; + prng.int32 = function() { return (xg.next() * 0x100000000) | 0; }; + prng.double = function() { + return prng() + (prng() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53 + }; + prng.quick = prng; + if (state) { + if (typeof(state) == 'object') copy(state, xg); + prng.state = function() { return copy(xg, {}); }; + } + return prng; + } + + function Mash() { + var n = 0xefc8249d; + + var mash = function(data) { + data = String(data); + for (var i = 0; i < data.length; i++) { + n += data.charCodeAt(i); + var h = 0.02519603282416938 * n; + n = h >>> 0; + h -= n; + h *= n; + n = h >>> 0; + h -= n; + n += h * 0x100000000; // 2^32 + } + return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 + }; - // data properties for all trials - var dataProperties = {}; + return mash; + } - module.getData = function() { - return $.extend(true, [], allData); // deep clone - }; - module.write = function(data_object) { - - var progress = jsPsych.progress(); - var trial = jsPsych.currentTrial(); - - //var trial_opt_data = typeof trial.data == 'function' ? trial.data() : trial.data; - - var default_data = { - 'trial_type': trial.type, - 'trial_index': progress.current_trial_global, - 'time_elapsed': jsPsych.totalTime(), - 'internal_node_id': jsPsych.currentTimelineNodeID() - }; - - var ext_data_object = $.extend({}, data_object, trial.data, default_data, dataProperties); - - allData.push(ext_data_object); - - var initSettings = jsPsych.initSettings(); - initSettings.on_data_update(ext_data_object); - }; - - module.addProperties = function(properties) { - - // first, add the properties to all data that's already stored - for (var i = 0; i < allData.length; i++) { - for (var key in properties) { - allData[i][key] = properties[key]; - } - } - - // now add to list so that it gets appended to all future data - dataProperties = $.extend({}, dataProperties, properties); - }; - - module.addDataToLastTrial = function(data) { - if (allData.length == 0) { - throw new Error("Cannot add data to last trial - no data recorded so far"); - } - allData[allData.length - 1] = $.extend({}, allData[allData.length - 1], data); - } - - module.dataAsCSV = function() { - var dataObj = module.getData(); - return JSON2CSV(dataObj); - }; - - module.dataAsJSON = function() { - var dataObj = module.getData(); - return JSON.stringify(dataObj); - }; - - module.localSave = function(filename, format) { - - var data_string; - - if (format == 'JSON' || format == 'json') { - data_string = JSON.stringify(module.getData()); - } else if (format == 'CSV' || format == 'csv') { - data_string = module.dataAsCSV(); - } else { - throw new Error('invalid format specified for jsPsych.data.localSave'); - } - - saveTextToFile(data_string, filename); - }; - - module.getTrialsOfType = function(trial_type) { - var data = module.getData(); - - data = flatten(data); - - var trials = []; - for (var i = 0; i < data.length; i++) { - if (data[i].trial_type == trial_type) { - trials.push(data[i]); - } - } - - return trials; - }; - - module.getDataByTimelineNode = function(node_id) { - var data = module.getData(); - - data = flatten(data); - - var trials = []; - for (var i = 0; i < data.length; i++) { - if (data[i].internal_node_id.slice(0, node_id.length) === node_id) { - trials.push(data[i]); - } - } - - return trials; - }; - - module.getLastTrialData = function() { - if (allData.length == 0) { - return {}; - } - return allData[allData.length - 1]; - }; - - module.getDataByTrialIndex = function(trial_index) { - for (var i = 0; i < allData.length; i++) { - if (allData[i].trial_index == trial_index) { - return allData[i]; - } - } - return undefined; - } - - module.getLastTimelineData = function() { - var lasttrial = module.getLastTrialData(); - var node_id = lasttrial.internal_node_id; - if (typeof node_id === 'undefined') { - return []; - } else { - var parent_node_id = node_id.substr(0,node_id.lastIndexOf('-')); - var lastnodedata = module.getDataByTimelineNode(parent_node_id); - return lastnodedata; - } - } - - module.displayData = function(format) { - format = (typeof format === 'undefined') ? "json" : format.toLowerCase(); - if (format != "json" && format != "csv") { - console.log('Invalid format declared for displayData function. Using json as default.'); - format = "json"; - } - - var data_string; - - if (format == 'json') { - data_string = JSON.stringify(module.getData(), undefined, 1); - } else { - data_string = module.dataAsCSV(); - } - - var display_element = jsPsych.getDisplayElement(); - - display_element.append($('
'));
-
-    $('#jspsych-data-display').text(data_string);
-  };
-
-  module.urlVariables = function() {
-    return query_string;
-  }
-
-  module.getURLVariable = function(whichvar){
-    return query_string[whichvar];
-  }
-  // private function to save text file on local drive
-
-  function saveTextToFile(textstr, filename) {
-    var blobToSave = new Blob([textstr], {
-      type: 'text/plain'
-    });
-    var blobURL = "";
-    if (typeof window.webkitURL !== 'undefined') {
-      blobURL = window.webkitURL.createObjectURL(blobToSave);
+    if (module && module.exports) {
+      module.exports = impl;
+    } else if (define && define.amd) {
+      define(function() { return impl; });
     } else {
-      blobURL = window.URL.createObjectURL(blobToSave);
-    }
-
-    var display_element = jsPsych.getDisplayElement();
-
-    display_element.append($('', {
-      id: 'jspsych-download-as-text-link',
-      href: blobURL,
-      css: {
-        display: 'none'
-      },
-      download: filename,
-      html: 'download file'
-    }));
-    $('#jspsych-download-as-text-link')[0].click();
-  }
-
-  //
-  // A few helper functions to handle data format conversion
-  //
-
-  // this function based on code suggested by StackOverflow users:
-  // http://stackoverflow.com/users/64741/zachary
-  // http://stackoverflow.com/users/317/joseph-sturtevant
-
-  function JSON2CSV(objArray) {
-    var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
-    var line = '';
-    var result = '';
-    var columns = [];
-
-    var i = 0;
-    for (var j = 0; j < array.length; j++) {
-      for (var key in array[j]) {
-        var keyString = key + "";
-        keyString = '"' + keyString.replace(/"/g, '""') + '",';
-        if ($.inArray(key, columns) == -1) {
-          columns[i] = key;
-          line += keyString;
-          i++;
+      this.alea = impl;
+    }
+
+    })(
+      commonjsGlobal,
+      module,    // present in node.js
+      (typeof undefined) == 'function'    // present with an AMD loader
+    );
+    }(alea));
+
+    var seedrandom = alea.exports;
+
+    /**
+     * Uses the `seedrandom` package to replace Math.random() with a seedable PRNG.
+     *
+     * @param seed An optional seed. If none is given, a random seed will be generated.
+     * @returns The seed value.
+     */
+    function setSeed(seed = Math.random().toString()) {
+        Math.random = seedrandom(seed);
+        return seed;
+    }
+    function repeat(array, repetitions, unpack = false) {
+        const arr_isArray = Array.isArray(array);
+        const rep_isArray = Array.isArray(repetitions);
+        // if array is not an array, then we just repeat the item
+        if (!arr_isArray) {
+            if (!rep_isArray) {
+                array = [array];
+                repetitions = [repetitions];
+            }
+            else {
+                repetitions = [repetitions[0]];
+                console.log("Unclear parameters given to randomization.repeat. Multiple set sizes specified, but only one item exists to sample. Proceeding using the first set size.");
+            }
         }
-      }
-    }
-
-    line = line.slice(0, -1);
-    result += line + '\r\n';
-
-    for (var i = 0; i < array.length; i++) {
-      var line = '';
-      for (var j = 0; j < columns.length; j++) {
-        var value = (typeof array[i][columns[j]] === 'undefined') ? '' : array[i][columns[j]];
-        var valueString = value + "";
-        line += '"' + valueString.replace(/"/g, '""') + '",';
-      }
-
-      line = line.slice(0, -1);
-      result += line + '\r\n';
-    }
-
-    return result;
-  }
-
-  // this function is from StackOverflow:
-  // http://stackoverflow.com/posts/3855394
-
-  var query_string = (function(a) {
-    if (a == "") return {};
-    var b = {};
-    for (var i = 0; i < a.length; ++i)
-    {
-        var p=a[i].split('=', 2);
-        if (p.length == 1)
-            b[p[0]] = "";
-        else
-            b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
-    }
-    return b;
-})(window.location.search.substr(1).split('&'));
-
-  return module;
-
-})();
-
-jsPsych.turk = (function() {
-
-  var module = {};
-
-  // core.turkInfo gets information relevant to mechanical turk experiments. returns an object
-  // containing the workerID, assignmentID, and hitID, and whether or not the HIT is in
-  // preview mode, meaning that they haven't accepted the HIT yet.
-  module.turkInfo = function() {
-
-    var turk = {};
-
-    var param = function(url, name) {
-      name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
-      var regexS = "[\\?&]" + name + "=([^&#]*)";
-      var regex = new RegExp(regexS);
-      var results = regex.exec(url);
-      return (results == null) ? "" : results[1];
-    };
-
-    var src = param(window.location.href, "assignmentId") ? window.location.href : document.referrer;
-
-    var keys = ["assignmentId", "hitId", "workerId", "turkSubmitTo"];
-    keys.map(
-
-      function(key) {
-        turk[key] = unescape(param(src, key));
-      });
-
-    turk.previewMode = (turk.assignmentId == "ASSIGNMENT_ID_NOT_AVAILABLE");
-
-    turk.outsideTurk = (!turk.previewMode && turk.hitId === "" && turk.assignmentId == "" && turk.workerId == "")
-
-    turk_info = turk;
-
-    return turk;
-
-  };
-
-  // core.submitToTurk will submit a MechanicalTurk ExternalHIT type
-  module.submitToTurk = function(data) {
-
-    var turkInfo = jsPsych.turk.turkInfo();
-    var assignmentId = turkInfo.assignmentId;
-    var turkSubmitTo = turkInfo.turkSubmitTo;
-
-    if (!assignmentId || !turkSubmitTo) return;
-
-    var dataString = [];
-
-    for (var key in data) {
-
-      if (data.hasOwnProperty(key)) {
-        dataString.push(key + "=" + escape(data[key]));
-      }
-    }
-
-    dataString.push("assignmentId=" + assignmentId);
-
-    var url = turkSubmitTo + "/mturk/externalSubmit?" + dataString.join("&");
-
-    window.location.href = url;
-  };
-
-  return module;
-
-})();
-
-jsPsych.randomization = (function() {
-
-  var module = {};
-
-  module.repeat = function(array, repetitions, unpack) {
-
-    var arr_isArray = Array.isArray(array);
-    var rep_isArray = Array.isArray(repetitions);
-
-    // if array is not an array, then we just repeat the item
-    if (!arr_isArray) {
-      if (!rep_isArray) {
-        array = [array];
-        repetitions = [repetitions];
-      } else {
-        repetitions = [repetitions[0]];
-        console.log('Unclear parameters given to randomization.repeat. Multiple set sizes specified, but only one item exists to sample. Proceeding using the first set size.');
-      }
-    } else {
-      if (!rep_isArray) {
-        var reps = [];
-        for (var i = 0; i < array.length; i++) {
-          reps.push(repetitions);
-        }
-        repetitions = reps;
-      } else {
-        if (array.length != repetitions.length) {
-          console.warning('Unclear parameters given to randomization.repeat. Items and repetitions are unequal lengths. Behavior may not be as expected.');
-          // throw warning if repetitions is too short, use first rep ONLY.
-          if (repetitions.length < array.length) {
-            var reps = [];
-            for (var i = 0; i < array.length; i++) {
-              reps.push(repetitions);
-            }
-            repetitions = reps;
-          } else {
-            // throw warning if too long, and then use the first N
-            repetitions = repetions.slice(0, array.length);
-          }
+        else {
+            // if repetitions is not an array, but array is, then we
+            // repeat repetitions for each entry in array
+            if (!rep_isArray) {
+                let reps = [];
+                for (let i = 0; i < array.length; i++) {
+                    reps.push(repetitions);
+                }
+                repetitions = reps;
+            }
+            else {
+                if (array.length != repetitions.length) {
+                    console.warn("Unclear parameters given to randomization.repeat. Items and repetitions are unequal lengths. Behavior may not be as expected.");
+                    // throw warning if repetitions is too short, use first rep ONLY.
+                    if (repetitions.length < array.length) {
+                        let reps = [];
+                        for (let i = 0; i < array.length; i++) {
+                            reps.push(repetitions);
+                        }
+                        repetitions = reps;
+                    }
+                    else {
+                        // throw warning if too long, and then use the first N
+                        repetitions = repetitions.slice(0, array.length);
+                    }
+                }
+            }
         }
-      }
-    }
-
-    // should be clear at this point to assume that array and repetitions are arrays with == length
-    var allsamples = [];
-    for (var i = 0; i < array.length; i++) {
-      for (var j = 0; j < repetitions[i]; j++) {
-        allsamples.push(array[i]);
-      }
-    }
-
-    var out = shuffle(allsamples);
-
-    if (unpack) {
-      out = unpackArray(out);
-    }
-
-    return out;
-  }
-
-  module.shuffle = function(arr) {
-    return shuffle(arr);
-  }
-
-  module.shuffleNoRepeats = function(arr, equalityTest) {
-    // define a default equalityTest
-    if (typeof equalityTest == 'undefined') {
-      equalityTest = function(a, b) {
-        if (a === b) {
-          return true;
-        } else {
-          return false;
+        // should be clear at this point to assume that array and repetitions are arrays with == length
+        let allsamples = [];
+        for (let i = 0; i < array.length; i++) {
+            for (let j = 0; j < repetitions[i]; j++) {
+                if (array[i] == null || typeof array[i] != "object") {
+                    allsamples.push(array[i]);
+                }
+                else {
+                    allsamples.push(Object.assign({}, array[i]));
+                }
+            }
         }
-      }
-    }
-
-    var random_shuffle = shuffle(arr);
-    for (var i = 0; i < random_shuffle.length - 2; i++) {
-      if (equalityTest(random_shuffle[i], random_shuffle[i + 1])) {
-        // neighbors are equal, pick a new random neighbor to swap (not the first or last element, to avoid edge cases)
-        var random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
-        // test to make sure the new neighbor isn't equal to the old one
-        while (
-          equalityTest(random_shuffle[i + 1], random_shuffle[random_pick]) ||
-          (equalityTest(random_shuffle[i + 1], random_shuffle[random_pick + 1]) || equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1]))
-        ) {
-          random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
-        }
-        var new_neighbor = random_shuffle[random_pick];
-        random_shuffle[random_pick] = random_shuffle[i + 1];
-        random_shuffle[i + 1] = new_neighbor;
-      }
-    }
-
-    return random_shuffle;
-  }
-
-  module.sample = function(arr, size, withReplacement) {
-    if (withReplacement == false) {
-      if (size > arr.length) {
-        console.error("jsPsych.randomization.sample cannot take a sample " +
-          "larger than the size of the set of items to sample from when " +
-          "sampling without replacement.");
-      }
-    }
-    var samp = [];
-    var shuff_arr = shuffle(arr);
-    for (var i = 0; i < size; i++) {
-      if (!withReplacement) {
-        samp.push(shuff_arr.pop());
-      } else {
-        samp.push(shuff_arr[Math.floor(Math.random() * shuff_arr.length)]);
-      }
-    }
-    return samp;
-  }
-
-  module.factorial = function(factors, repetitions, unpack) {
-
-    var factorNames = Object.keys(factors);
-
-    var factor_combinations = [];
-
-    for (var i = 0; i < factors[factorNames[0]].length; i++) {
-      factor_combinations.push({});
-      factor_combinations[i][factorNames[0]] = factors[factorNames[0]][i];
-    }
-
-    for (var i = 1; i < factorNames.length; i++) {
-      var toAdd = factors[factorNames[i]];
-      var n = factor_combinations.length;
-      for (var j = 0; j < n; j++) {
-        var base = factor_combinations[j];
-        for (var k = 0; k < toAdd.length; k++) {
-          var newpiece = {};
-          newpiece[factorNames[i]] = toAdd[k];
-          factor_combinations.push($.extend({}, base, newpiece));
+        let out = shuffle(allsamples);
+        if (unpack) {
+            out = unpackArray(out);
         }
-      }
-      factor_combinations.splice(0, n);
-    }
-
-    repetitions = (typeof repetitions === 'undefined') ? 1 : repetitions;
-    var with_repetitions = module.repeat(factor_combinations, repetitions, unpack);
-
-    return with_repetitions;
-  }
-
-  module.randomID = function(length){
-    var result = '';
-    var length = (typeof length == 'undefined') ? 32 : length;
-    var chars = '0123456789abcdefghjklmnopqrstuvwxyz';
-    for(var i = 0; i -1) {
-
-          if (!parameters.persist) {
-            // remove keyboard listener
-            module.cancelKeyboardResponse(listener_id);
-          }
+        // define a default equalityTest
+        if (typeof equalityTest == "undefined") {
+            equalityTest = function (a, b) {
+                if (a === b) {
+                    return true;
+                }
+                else {
+                    return false;
+                }
+            };
         }
-
-        var after_up = function(up) {
-
-          if (up.which == e.which) {
-            $(document).off('keyup', after_up);
-
-            // mark key as released
-            held_keys.splice($.inArray(e.which, held_keys), 1);
-          }
-        };
-
-        $(document).keyup(after_up);
-      }
-    };
-
-    $(document).keydown(listener_function);
-
-    // create listener id object
-    listener_id = {
-      type: 'keydown',
-      fn: listener_function
-    };
-
-    // add this keyboard listener to the list of listeners
-    keyboard_listeners.push(listener_id);
-
-    return listener_id;
-
-  };
-
-  module.cancelKeyboardResponse = function(listener) {
-    // remove the listener from the doc
-    $(document).off(listener.type, listener.fn);
-
-    // remove the listener from the list of listeners
-    if ($.inArray(listener, keyboard_listeners) > -1) {
-      keyboard_listeners.splice($.inArray(listener, keyboard_listeners), 1);
-    }
-  };
-
-  module.cancelAllKeyboardResponses = function() {
-    for (var i = 0; i < keyboard_listeners.length; i++) {
-      $(document).off(keyboard_listeners[i].type, keyboard_listeners[i].fn);
-    }
-    keyboard_listeners = [];
-  };
-
-  module.convertKeyCharacterToKeyCode = function(character) {
-    var code;
-    if (typeof keylookup[character] !== 'undefined') {
-      code = keylookup[character];
+        const random_shuffle = shuffle(arr);
+        for (let i = 0; i < random_shuffle.length - 1; i++) {
+            if (equalityTest(random_shuffle[i], random_shuffle[i + 1])) {
+                // neighbors are equal, pick a new random neighbor to swap (not the first or last element, to avoid edge cases)
+                let random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
+                // test to make sure the new neighbor isn't equal to the old one
+                while (equalityTest(random_shuffle[i + 1], random_shuffle[random_pick]) ||
+                    equalityTest(random_shuffle[i + 1], random_shuffle[random_pick + 1]) ||
+                    equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1])) {
+                    random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
+                }
+                const new_neighbor = random_shuffle[random_pick];
+                random_shuffle[random_pick] = random_shuffle[i + 1];
+                random_shuffle[i + 1] = new_neighbor;
+            }
+        }
+        return random_shuffle;
     }
-    return code;
-  }
-
-  var keylookup = {
-    'backspace': 8,
-    'tab': 9,
-    'enter': 13,
-    'shift': 16,
-    'ctrl': 17,
-    'alt': 18,
-    'pause': 19,
-    'capslock': 20,
-    'esc': 27,
-    'space': 32,
-    'spacebar': 32,
-    ' ': 32,
-    'pageup': 33,
-    'pagedown': 34,
-    'end': 35,
-    'home': 36,
-    'leftarrow': 37,
-    'uparrow': 38,
-    'rightarrow': 39,
-    'downarrow': 40,
-    'insert': 45,
-    'delete': 46,
-    '0': 48,
-    '1': 49,
-    '2': 50,
-    '3': 51,
-    '4': 52,
-    '5': 53,
-    '6': 54,
-    '7': 55,
-    '8': 56,
-    '9': 57,
-    'a': 65,
-    'b': 66,
-    'c': 67,
-    'd': 68,
-    'e': 69,
-    'f': 70,
-    'g': 71,
-    'h': 72,
-    'i': 73,
-    'j': 74,
-    'k': 75,
-    'l': 76,
-    'm': 77,
-    'n': 78,
-    'o': 79,
-    'p': 80,
-    'q': 81,
-    'r': 82,
-    's': 83,
-    't': 84,
-    'u': 85,
-    'v': 86,
-    'w': 87,
-    'x': 88,
-    'y': 89,
-    'z': 90,
-    'A': 65,
-    'B': 66,
-    'C': 67,
-    'D': 68,
-    'E': 69,
-    'F': 70,
-    'G': 71,
-    'H': 72,
-    'I': 73,
-    'J': 74,
-    'K': 75,
-    'L': 76,
-    'M': 77,
-    'N': 78,
-    'O': 79,
-    'P': 80,
-    'Q': 81,
-    'R': 82,
-    'S': 83,
-    'T': 84,
-    'U': 85,
-    'V': 86,
-    'W': 87,
-    'X': 88,
-    'Y': 89,
-    'Z': 90,
-    '0numpad': 96,
-    '1numpad': 97,
-    '2numpad': 98,
-    '3numpad': 99,
-    '4numpad': 100,
-    '5numpad': 101,
-    '6numpad': 102,
-    '7numpad': 103,
-    '8numpad': 104,
-    '9numpad': 105,
-    'multiply': 106,
-    'plus': 107,
-    'minus': 109,
-    'decimal': 110,
-    'divide': 111,
-    'F1': 112,
-    'F2': 113,
-    'F3': 114,
-    'F4': 115,
-    'F5': 116,
-    'F6': 117,
-    'F7': 118,
-    'F8': 119,
-    'F9': 120,
-    'F10': 121,
-    'F11': 122,
-    'F12': 123,
-    '=': 187,
-    ',': 188,
-    '.': 190,
-    '/': 191,
-    '`': 192,
-    '[': 219,
-    '\\': 220,
-    ']': 221
-  };
-
-  // plugin parameter evaluation //
-
-  module.evaluateFunctionParameters = function(trial, protect) {
-
-    // keys that are always protected
-    var always_protected = ['on_finish'];
-
-    protect = (typeof protect === 'undefined') ? [] : protect;
-
-    protect = protect.concat(always_protected);
-
-    var keys = Object.keys(trial);
-
-    for (var i = 0; i < keys.length; i++) {
-
-      var process = true;
-      for (var j = 0; j < protect.length; j++) {
-        if (protect[j] == keys[i]) {
-          process = false;
-          break;
+    function shuffleAlternateGroups(arr_groups, random_group_order = false) {
+        const n_groups = arr_groups.length;
+        if (n_groups == 1) {
+            console.warn("shuffleAlternateGroups() was called with only one group. Defaulting to simple shuffle.");
+            return shuffle(arr_groups[0]);
         }
-      }
-
-      if (typeof trial[keys[i]] == "function" && process) {
-        trial[keys[i]] = trial[keys[i]].call();
-      }
-
+        let group_order = [];
+        for (let i = 0; i < n_groups; i++) {
+            group_order.push(i);
+        }
+        if (random_group_order) {
+            group_order = shuffle(group_order);
+        }
+        const randomized_groups = [];
+        let min_length = null;
+        for (let i = 0; i < n_groups; i++) {
+            min_length =
+                min_length === null ? arr_groups[i].length : Math.min(min_length, arr_groups[i].length);
+            randomized_groups.push(shuffle(arr_groups[i]));
+        }
+        const out = [];
+        for (let i = 0; i < min_length; i++) {
+            for (let j = 0; j < group_order.length; j++) {
+                out.push(randomized_groups[group_order[j]][i]);
+            }
+        }
+        return out;
     }
-
-    return trial;
-
-  };
-
-  // audio //
-
-  // temporary patch for Safari
-  if (window.hasOwnProperty('webkitAudioContext') && !window.hasOwnProperty('AudioContext')) {
-    window.AudioContext = webkitAudioContext;
-  }
-  // end patch
-
-  var context = (typeof window.AudioContext !== 'undefined') ? new AudioContext() : null;
-  var audio_buffers = [];
-
-  module.getAudioBuffer = function(audioID) {
-
-    if (audio_buffers[audioID] == 'tmp') {
-      console.error('Audio file failed to load in the time alloted.')
-      return;
+    function sampleWithoutReplacement(arr, size) {
+        if (!Array.isArray(arr)) {
+            console.error("First argument to sampleWithoutReplacement() must be an array");
+        }
+        if (size > arr.length) {
+            console.error("Cannot take a sample larger than the size of the set of items to sample.");
+        }
+        return shuffle(arr).slice(0, size);
     }
-
-    return audio_buffers[audioID];
-
-  }
-
-  // preloading stimuli //
-
-  var preloads = [];
-
-  module.preloadAudioFiles = function(files, callback_complete, callback_load) {
-
-    files = flatten(files);
-
-    var n_loaded = 0;
-    var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load;
-    var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete;
-
-    if(files.length==0){
-      finishfn();
-      return;
+    function sampleWithReplacement(arr, size, weights) {
+        if (!Array.isArray(arr)) {
+            console.error("First argument to sampleWithReplacement() must be an array");
+        }
+        const normalized_weights = [];
+        if (typeof weights !== "undefined") {
+            if (weights.length !== arr.length) {
+                console.error("The length of the weights array must equal the length of the array " +
+                    "to be sampled from.");
+            }
+            let weight_sum = 0;
+            for (const weight of weights) {
+                weight_sum += weight;
+            }
+            for (const weight of weights) {
+                normalized_weights.push(weight / weight_sum);
+            }
+        }
+        else {
+            for (let i = 0; i < arr.length; i++) {
+                normalized_weights.push(1 / arr.length);
+            }
+        }
+        const cumulative_weights = [normalized_weights[0]];
+        for (let i = 1; i < normalized_weights.length; i++) {
+            cumulative_weights.push(normalized_weights[i] + cumulative_weights[i - 1]);
+        }
+        const samp = [];
+        for (let i = 0; i < size; i++) {
+            const rnd = Math.random();
+            let index = 0;
+            while (rnd > cumulative_weights[index]) {
+                index++;
+            }
+            samp.push(arr[index]);
+        }
+        return samp;
+    }
+    function factorial(factors, repetitions = 1, unpack = false) {
+        let design = [{}];
+        for (const [factorName, factor] of Object.entries(factors)) {
+            const new_design = [];
+            for (const level of factor) {
+                for (const cell of design) {
+                    new_design.push(Object.assign(Object.assign({}, cell), { [factorName]: level }));
+                }
+            }
+            design = new_design;
+        }
+        return repeat(design, repetitions, unpack);
     }
+    function randomID(length = 32) {
+        let result = "";
+        const chars = "0123456789abcdefghjklmnopqrstuvwxyz";
+        for (let i = 0; i < length; i++) {
+            result += chars[Math.floor(Math.random() * chars.length)];
+        }
+        return result;
+    }
+    /**
+     * Generate a random integer from `lower` to `upper`, inclusive of both end points.
+     * @param lower The lowest value it is possible to generate
+     * @param upper The highest value it is possible to generate
+     * @returns A random integer
+     */
+    function randomInt(lower, upper) {
+        if (upper < lower) {
+            throw new Error("Upper boundary must be less than or equal to lower boundary");
+        }
+        return lower + Math.floor(Math.random() * (upper - lower + 1));
+    }
+    /**
+     * Generates a random sample from a Bernoulli distribution.
+     * @param p The probability of sampling 1.
+     * @returns 0, with probability 1-p, or 1, with probability p.
+     */
+    function sampleBernoulli(p) {
+        return Math.random() <= p ? 1 : 0;
+    }
+    function sampleNormal(mean, standard_deviation) {
+        return randn_bm() * standard_deviation + mean;
+    }
+    function sampleExponential(rate) {
+        return -Math.log(Math.random()) / rate;
+    }
+    function sampleExGaussian(mean, standard_deviation, rate, positive = false) {
+        let s = sampleNormal(mean, standard_deviation) + sampleExponential(rate);
+        if (positive) {
+            while (s <= 0) {
+                s = sampleNormal(mean, standard_deviation) + sampleExponential(rate);
+            }
+        }
+        return s;
+    }
+    /**
+     * Generate one or more random words.
+     *
+     * This is a wrapper function for the {@link https://www.npmjs.com/package/random-words `random-words` npm package}.
+     *
+     * @param opts An object with optional properties `min`, `max`, `exactly`,
+     * `join`, `maxLength`, `wordsPerString`, `separator`, and `formatter`.
+     *
+     * @returns An array of words or a single string, depending on parameter choices.
+     */
+    function randomWords(opts) {
+        return randomWords$1(opts);
+    }
+    // Box-Muller transformation for a random sample from normal distribution with mean = 0, std = 1
+    // https://stackoverflow.com/a/36481059/3726673
+    function randn_bm() {
+        var u = 0, v = 0;
+        while (u === 0)
+            u = Math.random(); //Converting [0,1) to (0,1)
+        while (v === 0)
+            v = Math.random();
+        return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
+    }
+    function unpackArray(array) {
+        const out = {};
+        for (const x of array) {
+            for (const key of Object.keys(x)) {
+                if (typeof out[key] === "undefined") {
+                    out[key] = [];
+                }
+                out[key].push(x[key]);
+            }
+        }
+        return out;
+    }
+
+    var randomization = /*#__PURE__*/Object.freeze({
+        __proto__: null,
+        setSeed: setSeed,
+        repeat: repeat,
+        shuffle: shuffle,
+        shuffleNoRepeats: shuffleNoRepeats,
+        shuffleAlternateGroups: shuffleAlternateGroups,
+        sampleWithoutReplacement: sampleWithoutReplacement,
+        sampleWithReplacement: sampleWithReplacement,
+        factorial: factorial,
+        randomID: randomID,
+        randomInt: randomInt,
+        sampleBernoulli: sampleBernoulli,
+        sampleNormal: sampleNormal,
+        sampleExponential: sampleExponential,
+        sampleExGaussian: sampleExGaussian,
+        randomWords: randomWords
+    });
 
-    function load_audio_file(source){
-      var request = new XMLHttpRequest();
-      request.open('GET', source, true);
-      request.responseType = 'arraybuffer';
-      request.onload = function() {
-        context.decodeAudioData(request.response, function(buffer) {
-          audio_buffers[source] = buffer;
-          n_loaded++;
-          loadfn(n_loaded);
-          if(n_loaded == files.length) {
-            finishfn();
-          }
-        }, function() {
-          console.error('Error loading audio file: ' + bufferID);
+    /**
+     * Gets information about the Mechanical Turk Environment, HIT, Assignment, and Worker
+     * by parsing the URL variables that Mechanical Turk generates.
+     * @returns An object containing information about the Mechanical Turk Environment, HIT, Assignment, and Worker.
+     */
+    function turkInfo() {
+        const turk = {
+            previewMode: false,
+            outsideTurk: false,
+            hitId: "INVALID_URL_PARAMETER",
+            assignmentId: "INVALID_URL_PARAMETER",
+            workerId: "INVALID_URL_PARAMETER",
+            turkSubmitTo: "INVALID_URL_PARAMETER",
+        };
+        const param = function (url, name) {
+            name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+            const regexS = "[\\?&]" + name + "=([^&#]*)";
+            const regex = new RegExp(regexS);
+            const results = regex.exec(url);
+            return results == null ? "" : results[1];
+        };
+        const src = param(window.location.href, "assignmentId")
+            ? window.location.href
+            : document.referrer;
+        const keys = ["assignmentId", "hitId", "workerId", "turkSubmitTo"];
+        keys.map(function (key) {
+            turk[key] = unescape(param(src, key));
         });
-      }
-      request.send();
-    }
-
-    for (var i = 0; i < files.length; i++) {
-      var bufferID = files[i];
-      if (typeof audio_buffers[bufferID] !== 'undefined') {
-        n_loaded++;
-        loadfn(n_loaded);
-        if(n_loaded == files.length) {
-          finishfn();
+        turk.previewMode = turk.assignmentId == "ASSIGNMENT_ID_NOT_AVAILABLE";
+        turk.outsideTurk =
+            !turk.previewMode && turk.hitId === "" && turk.assignmentId == "" && turk.workerId == "";
+        return turk;
+    }
+    /**
+     * Send data to Mechnical Turk for storage.
+     * @param data An object containing `key:value` pairs to send to Mechanical Turk. Values
+     * cannot contain nested objects, arrays, or functions.
+     * @returns Nothing
+     */
+    function submitToTurk(data) {
+        const turk = turkInfo();
+        const assignmentId = turk.assignmentId;
+        const turkSubmitTo = turk.turkSubmitTo;
+        if (!assignmentId || !turkSubmitTo)
+            return;
+        const form = document.createElement("form");
+        form.method = "POST";
+        form.action = turkSubmitTo + "/mturk/externalSubmit?assignmentId=" + assignmentId;
+        for (const key in data) {
+            if (data.hasOwnProperty(key)) {
+                const hiddenField = document.createElement("input");
+                hiddenField.type = "hidden";
+                hiddenField.name = key;
+                hiddenField.id = key;
+                hiddenField.value = data[key];
+                form.appendChild(hiddenField);
+            }
         }
-      }
-      audio_buffers[bufferID] = 'tmp';
-      load_audio_file(bufferID);
+        document.body.appendChild(form);
+        form.submit();
     }
 
-  }
-
-  module.preloadImages = function(images, callback_complete, callback_load) {
-
-    // flatten the images array
-    images = flatten(images);
-
-    var n_loaded = 0;
-    var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load;
-    var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete;
-
-    if(images.length==0){
-      finishfn();
-      return;
-    }
-
-    for (var i = 0; i < images.length; i++) {
-      var img = new Image();
+    var turk = /*#__PURE__*/Object.freeze({
+        __proto__: null,
+        turkInfo: turkInfo,
+        submitToTurk: submitToTurk
+    });
 
-      img.onload = function() {
-        n_loaded++;
-        loadfn(n_loaded);
-        if (n_loaded == images.length) {
-          finishfn();
+    class TimelineNode {
+        // constructor
+        constructor(jsPsych, parameters, parent, relativeID) {
+            this.jsPsych = jsPsych;
+            // track progress through the node
+            this.progress = {
+                current_location: -1,
+                current_variable_set: 0,
+                current_repetition: 0,
+                current_iteration: 0,
+                done: false,
+            };
+            // store a link to the parent of this node
+            this.parent_node = parent;
+            // create the ID for this node
+            this.relative_id = typeof parent === "undefined" ? 0 : relativeID;
+            // check if there is a timeline parameter
+            // if there is, then this node has its own timeline
+            if (typeof parameters.timeline !== "undefined") {
+                // create timeline properties
+                this.timeline_parameters = {
+                    timeline: [],
+                    loop_function: parameters.loop_function,
+                    conditional_function: parameters.conditional_function,
+                    sample: parameters.sample,
+                    randomize_order: typeof parameters.randomize_order == "undefined" ? false : parameters.randomize_order,
+                    repetitions: typeof parameters.repetitions == "undefined" ? 1 : parameters.repetitions,
+                    timeline_variables: typeof parameters.timeline_variables == "undefined"
+                        ? [{}]
+                        : parameters.timeline_variables,
+                    on_timeline_finish: parameters.on_timeline_finish,
+                    on_timeline_start: parameters.on_timeline_start,
+                };
+                this.setTimelineVariablesOrder();
+                // extract all of the node level data and parameters
+                // but remove all of the timeline-level specific information
+                // since this will be used to copy things down hierarchically
+                var node_data = Object.assign({}, parameters);
+                delete node_data.timeline;
+                delete node_data.conditional_function;
+                delete node_data.loop_function;
+                delete node_data.randomize_order;
+                delete node_data.repetitions;
+                delete node_data.timeline_variables;
+                delete node_data.sample;
+                delete node_data.on_timeline_start;
+                delete node_data.on_timeline_finish;
+                this.node_trial_data = node_data; // store for later...
+                // create a TimelineNode for each element in the timeline
+                for (var i = 0; i < parameters.timeline.length; i++) {
+                    // merge parameters
+                    var merged_parameters = Object.assign({}, node_data, parameters.timeline[i]);
+                    // merge any data from the parent node into child nodes
+                    if (typeof node_data.data == "object" && typeof parameters.timeline[i].data == "object") {
+                        var merged_data = Object.assign({}, node_data.data, parameters.timeline[i].data);
+                        merged_parameters.data = merged_data;
+                    }
+                    this.timeline_parameters.timeline.push(new TimelineNode(this.jsPsych, merged_parameters, this, i));
+                }
+            }
+            // if there is no timeline parameter, then this node is a trial node
+            else {
+                // check to see if a valid trial type is defined
+                if (typeof parameters.type === "undefined") {
+                    console.error('Trial level node is missing the "type" parameter. The parameters for the node are: ' +
+                        JSON.stringify(parameters));
+                }
+                // create a deep copy of the parameters for the trial
+                this.trial_parameters = Object.assign({}, parameters);
+            }
         }
-      };
-
-      img.onerror = function() {
-        n_loaded++;
-        loadfn(n_loaded);
-        if (n_loaded == images.length) {
-          finishfn();
+        // recursively get the next trial to run.
+        // if this node is a leaf (trial), then return the trial.
+        // otherwise, recursively find the next trial in the child timeline.
+        trial() {
+            if (typeof this.timeline_parameters == "undefined") {
+                // returns a clone of the trial_parameters to
+                // protect functions.
+                return deepCopy(this.trial_parameters);
+            }
+            else {
+                if (this.progress.current_location >= this.timeline_parameters.timeline.length) {
+                    return null;
+                }
+                else {
+                    return this.timeline_parameters.timeline[this.progress.current_location].trial();
+                }
+            }
+        }
+        markCurrentTrialComplete() {
+            if (typeof this.timeline_parameters === "undefined") {
+                this.progress.done = true;
+            }
+            else {
+                this.timeline_parameters.timeline[this.progress.current_location].markCurrentTrialComplete();
+            }
+        }
+        nextRepetiton() {
+            this.setTimelineVariablesOrder();
+            this.progress.current_location = -1;
+            this.progress.current_variable_set = 0;
+            this.progress.current_repetition++;
+            for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
+                this.timeline_parameters.timeline[i].reset();
+            }
+        }
+        // set the order for going through the timeline variables array
+        setTimelineVariablesOrder() {
+            const timeline_parameters = this.timeline_parameters;
+            // check to make sure this node has variables
+            if (typeof timeline_parameters === "undefined" ||
+                typeof timeline_parameters.timeline_variables === "undefined") {
+                return;
+            }
+            var order = [];
+            for (var i = 0; i < timeline_parameters.timeline_variables.length; i++) {
+                order.push(i);
+            }
+            if (typeof timeline_parameters.sample !== "undefined") {
+                if (timeline_parameters.sample.type == "custom") {
+                    order = timeline_parameters.sample.fn(order);
+                }
+                else if (timeline_parameters.sample.type == "with-replacement") {
+                    order = sampleWithReplacement(order, timeline_parameters.sample.size, timeline_parameters.sample.weights);
+                }
+                else if (timeline_parameters.sample.type == "without-replacement") {
+                    order = sampleWithoutReplacement(order, timeline_parameters.sample.size);
+                }
+                else if (timeline_parameters.sample.type == "fixed-repetitions") {
+                    order = repeat(order, timeline_parameters.sample.size, false);
+                }
+                else if (timeline_parameters.sample.type == "alternate-groups") {
+                    order = shuffleAlternateGroups(timeline_parameters.sample.groups, timeline_parameters.sample.randomize_group_order);
+                }
+                else {
+                    console.error('Invalid type in timeline sample parameters. Valid options for type are "custom", "with-replacement", "without-replacement", "fixed-repetitions", and "alternate-groups"');
+                }
+            }
+            if (timeline_parameters.randomize_order) {
+                order = shuffle(order);
+            }
+            this.progress.order = order;
+        }
+        // next variable set
+        nextSet() {
+            this.progress.current_location = -1;
+            this.progress.current_variable_set++;
+            for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
+                this.timeline_parameters.timeline[i].reset();
+            }
+        }
+        // update the current trial node to be completed
+        // returns true if the node is complete after advance (all subnodes are also complete)
+        // returns false otherwise
+        advance() {
+            const progress = this.progress;
+            const timeline_parameters = this.timeline_parameters;
+            const internal = this.jsPsych.internal;
+            // first check to see if done
+            if (progress.done) {
+                return true;
+            }
+            // if node has not started yet (progress.current_location == -1),
+            // then try to start the node.
+            if (progress.current_location == -1) {
+                // check for on_timeline_start and conditonal function on nodes with timelines
+                if (typeof timeline_parameters !== "undefined") {
+                    // only run the conditional function if this is the first repetition of the timeline when
+                    // repetitions > 1, and only when on the first variable set
+                    if (typeof timeline_parameters.conditional_function !== "undefined" &&
+                        progress.current_repetition == 0 &&
+                        progress.current_variable_set == 0) {
+                        internal.call_immediate = true;
+                        var conditional_result = timeline_parameters.conditional_function();
+                        internal.call_immediate = false;
+                        // if the conditional_function() returns false, then the timeline
+                        // doesn't run and is marked as complete.
+                        if (conditional_result == false) {
+                            progress.done = true;
+                            return true;
+                        }
+                    }
+                    // if we reach this point then the node has its own timeline and will start
+                    // so we need to check if there is an on_timeline_start function if we are on the first variable set
+                    if (typeof timeline_parameters.on_timeline_start !== "undefined" &&
+                        progress.current_variable_set == 0) {
+                        timeline_parameters.on_timeline_start();
+                    }
+                }
+                // if we reach this point, then either the node doesn't have a timeline of the
+                // conditional function returned true and it can start
+                progress.current_location = 0;
+                // call advance again on this node now that it is pointing to a new location
+                return this.advance();
+            }
+            // if this node has a timeline, propogate down to the current trial.
+            if (typeof timeline_parameters !== "undefined") {
+                var have_node_to_run = false;
+                // keep incrementing the location in the timeline until one of the nodes reached is incomplete
+                while (progress.current_location < timeline_parameters.timeline.length &&
+                    have_node_to_run == false) {
+                    // check to see if the node currently pointed at is done
+                    var target_complete = timeline_parameters.timeline[progress.current_location].advance();
+                    if (!target_complete) {
+                        have_node_to_run = true;
+                        return false;
+                    }
+                    else {
+                        progress.current_location++;
+                    }
+                }
+                // if we've reached the end of the timeline (which, if the code is here, we have)
+                // there are a few steps to see what to do next...
+                // first, check the timeline_variables to see if we need to loop through again
+                // with a new set of variables
+                if (progress.current_variable_set < progress.order.length - 1) {
+                    // reset the progress of the node to be with the new set
+                    this.nextSet();
+                    // then try to advance this node again.
+                    return this.advance();
+                }
+                // if we're all done with the timeline_variables, then check to see if there are more repetitions
+                else if (progress.current_repetition < timeline_parameters.repetitions - 1) {
+                    this.nextRepetiton();
+                    // check to see if there is an on_timeline_finish function
+                    if (typeof timeline_parameters.on_timeline_finish !== "undefined") {
+                        timeline_parameters.on_timeline_finish();
+                    }
+                    return this.advance();
+                }
+                // if we're all done with the repetitions...
+                else {
+                    // check to see if there is an on_timeline_finish function
+                    if (typeof timeline_parameters.on_timeline_finish !== "undefined") {
+                        timeline_parameters.on_timeline_finish();
+                    }
+                    // if we're all done with the repetitions, check if there is a loop function.
+                    if (typeof timeline_parameters.loop_function !== "undefined") {
+                        internal.call_immediate = true;
+                        if (timeline_parameters.loop_function(this.generatedData())) {
+                            this.reset();
+                            internal.call_immediate = false;
+                            return this.parent_node.advance();
+                        }
+                        else {
+                            progress.done = true;
+                            internal.call_immediate = false;
+                            return true;
+                        }
+                    }
+                }
+                // no more loops on this timeline, we're done!
+                progress.done = true;
+                return true;
+            }
+        }
+        // check the status of the done flag
+        isComplete() {
+            return this.progress.done;
+        }
+        // getter method for timeline variables
+        getTimelineVariableValue(variable_name) {
+            if (typeof this.timeline_parameters == "undefined") {
+                return undefined;
+            }
+            var v = this.timeline_parameters.timeline_variables[this.progress.order[this.progress.current_variable_set]][variable_name];
+            return v;
+        }
+        // recursive upward search for timeline variables
+        findTimelineVariable(variable_name) {
+            var v = this.getTimelineVariableValue(variable_name);
+            if (typeof v == "undefined") {
+                if (typeof this.parent_node !== "undefined") {
+                    return this.parent_node.findTimelineVariable(variable_name);
+                }
+                else {
+                    return undefined;
+                }
+            }
+            else {
+                return v;
+            }
+        }
+        // recursive downward search for active trial to extract timeline variable
+        timelineVariable(variable_name) {
+            if (typeof this.timeline_parameters == "undefined") {
+                return this.findTimelineVariable(variable_name);
+            }
+            else {
+                // if progress.current_location is -1, then the timeline variable is being evaluated
+                // in a function that runs prior to the trial starting, so we should treat that trial
+                // as being the active trial for purposes of finding the value of the timeline variable
+                var loc = Math.max(0, this.progress.current_location);
+                // if loc is greater than the number of elements on this timeline, then the timeline
+                // variable is being evaluated in a function that runs after the trial on the timeline
+                // are complete but before advancing to the next (like a loop_function).
+                // treat the last active trial as the active trial for this purpose.
+                if (loc == this.timeline_parameters.timeline.length) {
+                    loc = loc - 1;
+                }
+                // now find the variable
+                return this.timeline_parameters.timeline[loc].timelineVariable(variable_name);
+            }
+        }
+        // recursively get all the timeline variables for this trial
+        allTimelineVariables() {
+            var all_tvs = this.allTimelineVariablesNames();
+            var all_tvs_vals = {};
+            for (var i = 0; i < all_tvs.length; i++) {
+                all_tvs_vals[all_tvs[i]] = this.timelineVariable(all_tvs[i]);
+            }
+            return all_tvs_vals;
+        }
+        // helper to get all the names at this stage.
+        allTimelineVariablesNames(so_far = []) {
+            if (typeof this.timeline_parameters !== "undefined") {
+                so_far = so_far.concat(Object.keys(this.timeline_parameters.timeline_variables[this.progress.order[this.progress.current_variable_set]]));
+                // if progress.current_location is -1, then the timeline variable is being evaluated
+                // in a function that runs prior to the trial starting, so we should treat that trial
+                // as being the active trial for purposes of finding the value of the timeline variable
+                var loc = Math.max(0, this.progress.current_location);
+                // if loc is greater than the number of elements on this timeline, then the timeline
+                // variable is being evaluated in a function that runs after the trial on the timeline
+                // are complete but before advancing to the next (like a loop_function).
+                // treat the last active trial as the active trial for this purpose.
+                if (loc == this.timeline_parameters.timeline.length) {
+                    loc = loc - 1;
+                }
+                // now find the variable
+                return this.timeline_parameters.timeline[loc].allTimelineVariablesNames(so_far);
+            }
+            if (typeof this.timeline_parameters == "undefined") {
+                return so_far;
+            }
+        }
+        // recursively get the number of **trials** contained in the timeline
+        // assuming that while loops execute exactly once and if conditionals
+        // always run
+        length() {
+            var length = 0;
+            if (typeof this.timeline_parameters !== "undefined") {
+                for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
+                    length += this.timeline_parameters.timeline[i].length();
+                }
+            }
+            else {
+                return 1;
+            }
+            return length;
+        }
+        // return the percentage of trials completed, grouped at the first child level
+        // counts a set of trials as complete when the child node is done
+        percentComplete() {
+            var total_trials = this.length();
+            var completed_trials = 0;
+            for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
+                if (this.timeline_parameters.timeline[i].isComplete()) {
+                    completed_trials += this.timeline_parameters.timeline[i].length();
+                }
+            }
+            return (completed_trials / total_trials) * 100;
+        }
+        // resets the node and all subnodes to original state
+        // but increments the current_iteration counter
+        reset() {
+            this.progress.current_location = -1;
+            this.progress.current_repetition = 0;
+            this.progress.current_variable_set = 0;
+            this.progress.current_iteration++;
+            this.progress.done = false;
+            this.setTimelineVariablesOrder();
+            if (typeof this.timeline_parameters != "undefined") {
+                for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
+                    this.timeline_parameters.timeline[i].reset();
+                }
+            }
+        }
+        // mark this node as finished
+        end() {
+            this.progress.done = true;
+        }
+        // recursively end whatever sub-node is running the current trial
+        endActiveNode() {
+            if (typeof this.timeline_parameters == "undefined") {
+                this.end();
+                this.parent_node.end();
+            }
+            else {
+                this.timeline_parameters.timeline[this.progress.current_location].endActiveNode();
+            }
+        }
+        // get a unique ID associated with this node
+        // the ID reflects the current iteration through this node.
+        ID() {
+            var id = "";
+            if (typeof this.parent_node == "undefined") {
+                return "0." + this.progress.current_iteration;
+            }
+            else {
+                id += this.parent_node.ID() + "-";
+                id += this.relative_id + "." + this.progress.current_iteration;
+                return id;
+            }
+        }
+        // get the ID of the active trial
+        activeID() {
+            if (typeof this.timeline_parameters == "undefined") {
+                return this.ID();
+            }
+            else {
+                return this.timeline_parameters.timeline[this.progress.current_location].activeID();
+            }
+        }
+        // get all the data generated within this node
+        generatedData() {
+            return this.jsPsych.data.getDataByTimelineNode(this.ID());
+        }
+        // get all the trials of a particular type
+        trialsOfType(type) {
+            if (typeof this.timeline_parameters == "undefined") {
+                if (this.trial_parameters.type == type) {
+                    return this.trial_parameters;
+                }
+                else {
+                    return [];
+                }
+            }
+            else {
+                var trials = [];
+                for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
+                    var t = this.timeline_parameters.timeline[i].trialsOfType(type);
+                    trials = trials.concat(t);
+                }
+                return trials;
+            }
+        }
+        // add new trials to end of this timeline
+        insert(parameters) {
+            if (typeof this.timeline_parameters === "undefined") {
+                console.error("Cannot add new trials to a trial-level node.");
+            }
+            else {
+                this.timeline_parameters.timeline.push(new TimelineNode(this.jsPsych, Object.assign(Object.assign({}, this.node_trial_data), parameters), this, this.timeline_parameters.timeline.length));
+            }
         }
-      }
-
-      img.src = images[i];
     }
-  };
 
-  module.registerPreload = function(plugin_name, parameter, media_type) {
-    if (!(media_type == 'audio' || media_type == 'image')) {
-      console.error('Invalid media_type parameter for jsPsych.pluginAPI.registerPreload. Please check the plugin file.');
+    function delay(ms) {
+        return new Promise((resolve) => setTimeout(resolve, ms));
+    }
+    class JsPsych {
+        constructor(options) {
+            this.extensions = {};
+            this.turk = turk;
+            this.randomization = randomization;
+            this.utils = utils;
+            //
+            // private variables
+            //
+            /**
+             * options
+             */
+            this.opts = {};
+            // flow control
+            this.global_trial_index = 0;
+            this.current_trial = {};
+            this.current_trial_finished = false;
+            /**
+             * is the experiment paused?
+             */
+            this.paused = false;
+            this.waiting = false;
+            /**
+             * is the page retrieved directly via file:// protocol (true) or hosted on a server (false)?
+             */
+            this.file_protocol = false;
+            /**
+             * is the experiment running in `simulate()` mode
+             */
+            this.simulation_mode = null;
+            // storing a single webaudio context to prevent problems with multiple inits
+            // of jsPsych
+            this.webaudio_context = null;
+            this.internal = {
+                /**
+                 * this flag is used to determine whether we are in a scope where
+                 * jsPsych.timelineVariable() should be executed immediately or
+                 * whether it should return a function to access the variable later.
+                 *
+                 **/
+                call_immediate: false,
+            };
+            this.progress_bar_amount = 0;
+            // override default options if user specifies an option
+            options = Object.assign({ display_element: undefined, on_finish: () => { }, on_trial_start: () => { }, on_trial_finish: () => { }, on_data_update: () => { }, on_interaction_data_update: () => { }, on_close: () => { }, use_webaudio: true, exclusions: {}, show_progress_bar: false, message_progress_bar: "Completion Progress", auto_update_progress_bar: true, default_iti: 0, minimum_valid_rt: 0, experiment_width: null, override_safe_mode: false, case_sensitive_responses: false, extensions: [] }, options);
+            this.opts = options;
+            autoBind(this); // so we can pass JsPsych methods as callbacks and `this` remains the JsPsych instance
+            this.webaudio_context =
+                typeof window !== "undefined" && typeof window.AudioContext !== "undefined"
+                    ? new AudioContext()
+                    : null;
+            // detect whether page is running in browser as a local file, and if so, disable web audio and video preloading to prevent CORS issues
+            if (window.location.protocol == "file:" &&
+                (options.override_safe_mode === false || typeof options.override_safe_mode === "undefined")) {
+                options.use_webaudio = false;
+                this.file_protocol = true;
+                console.warn("jsPsych detected that it is running via the file:// protocol and not on a web server. " +
+                    "To prevent issues with cross-origin requests, Web Audio and video preloading have been disabled. " +
+                    "If you would like to override this setting, you can set 'override_safe_mode' to 'true' in initJsPsych. " +
+                    "For more information, see: https://www.jspsych.org/overview/running-experiments");
+            }
+            // initialize modules
+            this.data = new JsPsychData(this);
+            this.pluginAPI = createJointPluginAPIObject(this);
+            // create instances of extensions
+            for (const extension of options.extensions) {
+                this.extensions[extension.type.info.name] = new extension.type(this);
+            }
+            // initialize audio context based on options and browser capabilities
+            this.pluginAPI.initAudio();
+        }
+        version() {
+            return version;
+        }
+        /**
+         * Starts an experiment using the provided timeline and returns a promise that is resolved when
+         * the experiment is finished.
+         *
+         * @param timeline The timeline to be run
+         */
+        run(timeline) {
+            return __awaiter(this, void 0, void 0, function* () {
+                if (typeof timeline === "undefined") {
+                    console.error("No timeline declared in jsPsych.run. Cannot start experiment.");
+                }
+                if (timeline.length === 0) {
+                    console.error("No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment.");
+                }
+                // create experiment timeline
+                this.timelineDescription = timeline;
+                this.timeline = new TimelineNode(this, { timeline });
+                yield this.prepareDom();
+                yield this.checkExclusions(this.opts.exclusions);
+                yield this.loadExtensions(this.opts.extensions);
+                document.documentElement.setAttribute("jspsych", "present");
+                this.startExperiment();
+                yield this.finished;
+            });
+        }
+        simulate(timeline, simulation_mode = "data-only", simulation_options = {}) {
+            return __awaiter(this, void 0, void 0, function* () {
+                this.simulation_mode = simulation_mode;
+                this.simulation_options = simulation_options;
+                yield this.run(timeline);
+            });
+        }
+        getProgress() {
+            return {
+                total_trials: typeof this.timeline === "undefined" ? undefined : this.timeline.length(),
+                current_trial_global: this.global_trial_index,
+                percent_complete: typeof this.timeline === "undefined" ? 0 : this.timeline.percentComplete(),
+            };
+        }
+        getStartTime() {
+            return this.exp_start_time;
+        }
+        getTotalTime() {
+            if (typeof this.exp_start_time === "undefined") {
+                return 0;
+            }
+            return new Date().getTime() - this.exp_start_time.getTime();
+        }
+        getDisplayElement() {
+            return this.DOM_target;
+        }
+        getDisplayContainerElement() {
+            return this.DOM_container;
+        }
+        finishTrial(data = {}) {
+            if (this.current_trial_finished) {
+                return;
+            }
+            this.current_trial_finished = true;
+            // remove any CSS classes that were added to the DOM via css_classes parameter
+            if (typeof this.current_trial.css_classes !== "undefined" &&
+                Array.isArray(this.current_trial.css_classes)) {
+                this.DOM_target.classList.remove(...this.current_trial.css_classes);
+            }
+            // write the data from the trial
+            this.data.write(data);
+            // get back the data with all of the defaults in
+            const trial_data = this.data.get().filter({ trial_index: this.global_trial_index });
+            // for trial-level callbacks, we just want to pass in a reference to the values
+            // of the DataCollection, for easy access and editing.
+            const trial_data_values = trial_data.values()[0];
+            const current_trial = this.current_trial;
+            if (typeof current_trial.save_trial_parameters === "object") {
+                for (const key of Object.keys(current_trial.save_trial_parameters)) {
+                    const key_val = current_trial.save_trial_parameters[key];
+                    if (key_val === true) {
+                        if (typeof current_trial[key] === "undefined") {
+                            console.warn(`Invalid parameter specified in save_trial_parameters. Trial has no property called "${key}".`);
+                        }
+                        else if (typeof current_trial[key] === "function") {
+                            trial_data_values[key] = current_trial[key].toString();
+                        }
+                        else {
+                            trial_data_values[key] = current_trial[key];
+                        }
+                    }
+                    if (key_val === false) {
+                        // we don't allow internal_node_id or trial_index to be deleted because it would break other things
+                        if (key !== "internal_node_id" && key !== "trial_index") {
+                            delete trial_data_values[key];
+                        }
+                    }
+                }
+            }
+            // handle extension callbacks
+            if (Array.isArray(current_trial.extensions)) {
+                for (const extension of current_trial.extensions) {
+                    const ext_data_values = this.extensions[extension.type.info.name].on_finish(extension.params);
+                    Object.assign(trial_data_values, ext_data_values);
+                }
+            }
+            // about to execute lots of callbacks, so switch context.
+            this.internal.call_immediate = true;
+            // handle callback at plugin level
+            if (typeof current_trial.on_finish === "function") {
+                current_trial.on_finish(trial_data_values);
+            }
+            // handle callback at whole-experiment level
+            this.opts.on_trial_finish(trial_data_values);
+            // after the above callbacks are complete, then the data should be finalized
+            // for this trial. call the on_data_update handler, passing in the same
+            // data object that just went through the trial's finish handlers.
+            this.opts.on_data_update(trial_data_values);
+            // done with callbacks
+            this.internal.call_immediate = false;
+            // wait for iti
+            if (this.simulation_mode === "data-only") {
+                this.nextTrial();
+            }
+            else if (typeof current_trial.post_trial_gap === null ||
+                typeof current_trial.post_trial_gap === "undefined") {
+                if (this.opts.default_iti > 0) {
+                    setTimeout(this.nextTrial, this.opts.default_iti);
+                }
+                else {
+                    this.nextTrial();
+                }
+            }
+            else {
+                if (current_trial.post_trial_gap > 0) {
+                    setTimeout(this.nextTrial, current_trial.post_trial_gap);
+                }
+                else {
+                    this.nextTrial();
+                }
+            }
+        }
+        endExperiment(end_message = "", data = {}) {
+            this.timeline.end_message = end_message;
+            this.timeline.end();
+            this.pluginAPI.cancelAllKeyboardResponses();
+            this.pluginAPI.clearAllTimeouts();
+            this.finishTrial(data);
+        }
+        endCurrentTimeline() {
+            this.timeline.endActiveNode();
+        }
+        getCurrentTrial() {
+            return this.current_trial;
+        }
+        getInitSettings() {
+            return this.opts;
+        }
+        getCurrentTimelineNodeID() {
+            return this.timeline.activeID();
+        }
+        timelineVariable(varname, immediate = false) {
+            if (this.internal.call_immediate || immediate === true) {
+                return this.timeline.timelineVariable(varname);
+            }
+            else {
+                return {
+                    timelineVariablePlaceholder: true,
+                    timelineVariableFunction: () => this.timeline.timelineVariable(varname),
+                };
+            }
+        }
+        getAllTimelineVariables() {
+            return this.timeline.allTimelineVariables();
+        }
+        addNodeToEndOfTimeline(new_timeline, preload_callback) {
+            this.timeline.insert(new_timeline);
+        }
+        pauseExperiment() {
+            this.paused = true;
+        }
+        resumeExperiment() {
+            this.paused = false;
+            if (this.waiting) {
+                this.waiting = false;
+                this.nextTrial();
+            }
+        }
+        loadFail(message) {
+            message = message || "

The experiment failed to load.

"; + this.DOM_target.innerHTML = message; + } + getSafeModeStatus() { + return this.file_protocol; + } + getTimeline() { + return this.timelineDescription; + } + prepareDom() { + return __awaiter(this, void 0, void 0, function* () { + // Wait until the document is ready + if (document.readyState !== "complete") { + yield new Promise((resolve) => { + window.addEventListener("load", resolve); + }); + } + const options = this.opts; + // set DOM element where jsPsych will render content + // if undefined, then jsPsych will use the tag and the entire page + if (typeof options.display_element === "undefined") { + // check if there is a body element on the page + const body = document.querySelector("body"); + if (body === null) { + document.documentElement.appendChild(document.createElement("body")); + } + // using the full page, so we need the HTML element to + // have 100% height, and body to be full width and height with + // no margin + document.querySelector("html").style.height = "100%"; + document.querySelector("body").style.margin = "0px"; + document.querySelector("body").style.height = "100%"; + document.querySelector("body").style.width = "100%"; + options.display_element = document.querySelector("body"); + } + else { + // make sure that the display element exists on the page + const display = options.display_element instanceof Element + ? options.display_element + : document.querySelector("#" + options.display_element); + if (display === null) { + console.error("The display_element specified in initJsPsych() does not exist in the DOM."); + } + else { + options.display_element = display; + } + } + options.display_element.innerHTML = + '
'; + this.DOM_container = options.display_element; + this.DOM_target = document.querySelector("#jspsych-content"); + // set experiment_width if not null + if (options.experiment_width !== null) { + this.DOM_target.style.width = options.experiment_width + "px"; + } + // add tabIndex attribute to scope event listeners + options.display_element.tabIndex = 0; + // add CSS class to DOM_target + if (options.display_element.className.indexOf("jspsych-display-element") === -1) { + options.display_element.className += " jspsych-display-element"; + } + this.DOM_target.className += "jspsych-content"; + // create listeners for user browser interaction + this.data.createInteractionListeners(); + // add event for closing window + window.addEventListener("beforeunload", options.on_close); + }); + } + loadExtensions(extensions) { + return __awaiter(this, void 0, void 0, function* () { + // run the .initialize method of any extensions that are in use + // these should return a Promise to indicate when loading is complete + try { + yield Promise.all(extensions.map((extension) => this.extensions[extension.type.info.name].initialize(extension.params || {}))); + } + catch (error_message) { + console.error(error_message); + throw new Error(error_message); + } + }); + } + startExperiment() { + this.finished = new Promise((resolve) => { + this.resolveFinishedPromise = resolve; + }); + // show progress bar if requested + if (this.opts.show_progress_bar === true) { + this.drawProgressBar(this.opts.message_progress_bar); + } + // record the start time + this.exp_start_time = new Date(); + // begin! + this.timeline.advance(); + this.doTrial(this.timeline.trial()); + } + finishExperiment() { + const finish_result = this.opts.on_finish(this.data.get()); + const done_handler = () => { + if (typeof this.timeline.end_message !== "undefined") { + this.DOM_target.innerHTML = this.timeline.end_message; + } + this.resolveFinishedPromise(); + }; + if (finish_result) { + Promise.resolve(finish_result).then(done_handler); + } + else { + done_handler(); + } + } + nextTrial() { + // if experiment is paused, don't do anything. + if (this.paused) { + this.waiting = true; + return; + } + this.global_trial_index++; + // advance timeline + this.timeline.markCurrentTrialComplete(); + const complete = this.timeline.advance(); + // update progress bar if shown + if (this.opts.show_progress_bar === true && this.opts.auto_update_progress_bar === true) { + this.updateProgressBar(); + } + // check if experiment is over + if (complete) { + this.finishExperiment(); + return; + } + this.doTrial(this.timeline.trial()); + } + doTrial(trial) { + this.current_trial = trial; + this.current_trial_finished = false; + // process all timeline variables for this trial + this.evaluateTimelineVariables(trial); + if (typeof trial.type === "string") { + throw new MigrationError("A string was provided as the trial's `type` parameter. Since jsPsych v7, the `type` parameter needs to be a plugin object."); + } + // instantiate the plugin for this trial + trial.type = Object.assign(Object.assign({}, autoBind(new trial.type(this))), { info: trial.type.info }); + // evaluate variables that are functions + this.evaluateFunctionParameters(trial); + // get default values for parameters + this.setDefaultValues(trial); + // about to execute callbacks + this.internal.call_immediate = true; + // call experiment wide callback + this.opts.on_trial_start(trial); + // call trial specific callback if it exists + if (typeof trial.on_start === "function") { + trial.on_start(trial); + } + // call any on_start functions for extensions + if (Array.isArray(trial.extensions)) { + for (const extension of trial.extensions) { + this.extensions[extension.type.info.name].on_start(extension.params); + } + } + // apply the focus to the element containing the experiment. + this.DOM_container.focus(); + // reset the scroll on the DOM target + this.DOM_target.scrollTop = 0; + // add CSS classes to the DOM_target if they exist in trial.css_classes + if (typeof trial.css_classes !== "undefined") { + if (!Array.isArray(trial.css_classes) && typeof trial.css_classes === "string") { + trial.css_classes = [trial.css_classes]; + } + if (Array.isArray(trial.css_classes)) { + this.DOM_target.classList.add(...trial.css_classes); + } + } + // setup on_load event callback + const load_callback = () => { + if (typeof trial.on_load === "function") { + trial.on_load(); + } + // call any on_load functions for extensions + if (Array.isArray(trial.extensions)) { + for (const extension of trial.extensions) { + this.extensions[extension.type.info.name].on_load(extension.params); + } + } + }; + let trial_complete; + if (!this.simulation_mode) { + trial_complete = trial.type.trial(this.DOM_target, trial, load_callback); + } + if (this.simulation_mode) { + // check if the trial supports simulation + if (trial.type.simulate) { + let trial_sim_opts; + if (!trial.simulation_options) { + trial_sim_opts = this.simulation_options.default; + } + if (trial.simulation_options) { + if (typeof trial.simulation_options == "string") { + if (this.simulation_options[trial.simulation_options]) { + trial_sim_opts = this.simulation_options[trial.simulation_options]; + } + else if (this.simulation_options.default) { + console.log(`No matching simulation options found for "${trial.simulation_options}". Using "default" options.`); + trial_sim_opts = this.simulation_options.default; + } + else { + console.log(`No matching simulation options found for "${trial.simulation_options}" and no "default" options provided. Using the default values provided by the plugin.`); + trial_sim_opts = {}; + } + } + else { + trial_sim_opts = trial.simulation_options; + } + } + trial_sim_opts = this.utils.deepCopy(trial_sim_opts); + trial_sim_opts = this.replaceFunctionsWithValues(trial_sim_opts, null); + if ((trial_sim_opts === null || trial_sim_opts === void 0 ? void 0 : trial_sim_opts.simulate) === false) { + trial_complete = trial.type.trial(this.DOM_target, trial, load_callback); + } + else { + trial_complete = trial.type.simulate(trial, (trial_sim_opts === null || trial_sim_opts === void 0 ? void 0 : trial_sim_opts.mode) || this.simulation_mode, trial_sim_opts, load_callback); + } + } + else { + // trial doesn't have a simulate method, so just run as usual + trial_complete = trial.type.trial(this.DOM_target, trial, load_callback); + } + } + // see if trial_complete is a Promise by looking for .then() function + const is_promise = trial_complete && typeof trial_complete.then == "function"; + // in simulation mode we let the simulate function call the load_callback always. + if (!is_promise && !this.simulation_mode) { + load_callback(); + } + // done with callbacks + this.internal.call_immediate = false; + } + evaluateTimelineVariables(trial) { + for (const key of Object.keys(trial)) { + // timeline variables on the root level + if (typeof trial[key] === "object" && + trial[key] !== null && + typeof trial[key].timelineVariablePlaceholder !== "undefined") { + /*trial[key].toString().replace(/\s/g, "") == + "function(){returntimeline.timelineVariable(varname);}" + )*/ trial[key] = trial[key].timelineVariableFunction(); + } + // timeline variables that are nested in objects + if (typeof trial[key] === "object" && trial[key] !== null) { + this.evaluateTimelineVariables(trial[key]); + } + } + } + evaluateFunctionParameters(trial) { + // set a flag so that jsPsych.timelineVariable() is immediately executed in this context + this.internal.call_immediate = true; + // iterate over each parameter + for (const key of Object.keys(trial)) { + // check to make sure parameter is not "type", since that was eval'd above. + if (key !== "type") { + // this if statement is checking to see if the parameter type is expected to be a function, in which case we should NOT evaluate it. + // the first line checks if the parameter is defined in the universalPluginParameters set + // the second line checks the plugin-specific parameters + if (typeof universalPluginParameters[key] !== "undefined" && + universalPluginParameters[key].type !== exports.ParameterType.FUNCTION) { + trial[key] = this.replaceFunctionsWithValues(trial[key], null); + } + if (typeof trial.type.info.parameters[key] !== "undefined" && + trial.type.info.parameters[key].type !== exports.ParameterType.FUNCTION) { + trial[key] = this.replaceFunctionsWithValues(trial[key], trial.type.info.parameters[key]); + } + } + } + // reset so jsPsych.timelineVariable() is no longer immediately executed + this.internal.call_immediate = false; + } + replaceFunctionsWithValues(obj, info) { + // null typeof is 'object' (?!?!), so need to run this first! + if (obj === null) { + return obj; + } + // arrays + else if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + obj[i] = this.replaceFunctionsWithValues(obj[i], info); + } + } + // objects + else if (typeof obj === "object") { + if (info === null || !info.nested) { + for (const key of Object.keys(obj)) { + if (key === "type") { + // Ignore the object's `type` field because it contains a plugin and we do not want to + // call plugin functions + continue; + } + obj[key] = this.replaceFunctionsWithValues(obj[key], null); + } + } + else { + for (const key of Object.keys(obj)) { + if (typeof info.nested[key] === "object" && + info.nested[key].type !== exports.ParameterType.FUNCTION) { + obj[key] = this.replaceFunctionsWithValues(obj[key], info.nested[key]); + } + } + } + } + else if (typeof obj === "function") { + return obj(); + } + return obj; + } + setDefaultValues(trial) { + for (const param in trial.type.info.parameters) { + // check if parameter is complex with nested defaults + if (trial.type.info.parameters[param].type === exports.ParameterType.COMPLEX) { + if (trial.type.info.parameters[param].array === true) { + // iterate over each entry in the array + trial[param].forEach(function (ip, i) { + // check each parameter in the plugin description + for (const p in trial.type.info.parameters[param].nested) { + if (typeof trial[param][i][p] === "undefined" || trial[param][i][p] === null) { + if (typeof trial.type.info.parameters[param].nested[p].default === "undefined") { + console.error("You must specify a value for the " + + p + + " parameter (nested in the " + + param + + " parameter) in the " + + trial.type + + " plugin."); + } + else { + trial[param][i][p] = trial.type.info.parameters[param].nested[p].default; + } + } + } + }); + } + } + // if it's not nested, checking is much easier and do that here: + else if (typeof trial[param] === "undefined" || trial[param] === null) { + if (typeof trial.type.info.parameters[param].default === "undefined") { + console.error("You must specify a value for the " + + param + + " parameter in the " + + trial.type.info.name + + " plugin."); + } + else { + trial[param] = trial.type.info.parameters[param].default; + } + } + } + } + checkExclusions(exclusions) { + return __awaiter(this, void 0, void 0, function* () { + if (exclusions.min_width || exclusions.min_height || exclusions.audio) { + console.warn("The exclusions option in `initJsPsych()` is deprecated and will be removed in a future version. We recommend using the browser-check plugin instead. See https://www.jspsych.org/latest/plugins/browser-check/."); + } + // MINIMUM SIZE + if (exclusions.min_width || exclusions.min_height) { + const mw = exclusions.min_width || 0; + const mh = exclusions.min_height || 0; + if (window.innerWidth < mw || window.innerHeight < mh) { + this.getDisplayElement().innerHTML = + "

Your browser window is too small to complete this experiment. " + + "Please maximize the size of your browser window. If your browser window is already maximized, " + + "you will not be able to complete this experiment.

" + + "

The minimum width is " + + mw + + "px. Your current width is " + + window.innerWidth + + "px.

" + + "

The minimum height is " + + mh + + "px. Your current height is " + + window.innerHeight + + "px.

"; + // Wait for window size to increase + while (window.innerWidth < mw || window.innerHeight < mh) { + yield delay(100); + } + this.getDisplayElement().innerHTML = ""; + } + } + // WEB AUDIO API + if (typeof exclusions.audio !== "undefined" && exclusions.audio) { + if (!window.hasOwnProperty("AudioContext") && !window.hasOwnProperty("webkitAudioContext")) { + this.getDisplayElement().innerHTML = + "

Your browser does not support the WebAudio API, which means that you will not " + + "be able to complete the experiment.

Browsers that support the WebAudio API include " + + "Chrome, Firefox, Safari, and Edge.

"; + throw new Error(); + } + } + }); + } + drawProgressBar(msg) { + document + .querySelector(".jspsych-display-element") + .insertAdjacentHTML("afterbegin", '
' + + "" + + msg + + "" + + '
' + + '
' + + "
"); + } + updateProgressBar() { + this.setProgressBar(this.getProgress().percent_complete / 100); + } + setProgressBar(proportion_complete) { + proportion_complete = Math.max(Math.min(1, proportion_complete), 0); + document.querySelector("#jspsych-progressbar-inner").style.width = + proportion_complete * 100 + "%"; + this.progress_bar_amount = proportion_complete; + } + getProgressBarCompleted() { + return this.progress_bar_amount; + } } - var preload = { - plugin: plugin_name, - parameter: parameter, - media_type: media_type + // temporary patch for Safari + if (typeof window !== "undefined" && + window.hasOwnProperty("webkitAudioContext") && + !window.hasOwnProperty("AudioContext")) { + // @ts-expect-error + window.AudioContext = webkitAudioContext; + } + // end patch + // The following function provides a uniform interface to initialize jsPsych, no matter whether a + // browser supports ES6 classes or not (and whether the ES6 build or the Babel build is used). + /** + * Creates a new JsPsych instance using the provided options. + * + * @param options The options to pass to the JsPsych constructor + * @returns A new JsPsych instance + */ + function initJsPsych(options) { + const jsPsych = new JsPsych(options); + // Handle invocations of non-existent v6 methods with migration errors + const migrationMessages = { + init: "`jsPsych.init()` was replaced by `initJsPsych()` in jsPsych v7.", + ALL_KEYS: 'jsPsych.ALL_KEYS was replaced by the "ALL_KEYS" string in jsPsych v7.', + NO_KEYS: 'jsPsych.NO_KEYS was replaced by the "NO_KEYS" string in jsPsych v7.', + // Getter functions that were renamed + currentTimelineNodeID: "`currentTimelineNodeID()` was renamed to `getCurrentTimelineNodeID()` in jsPsych v7.", + progress: "`progress()` was renamed to `getProgress()` in jsPsych v7.", + startTime: "`startTime()` was renamed to `getStartTime()` in jsPsych v7.", + totalTime: "`totalTime()` was renamed to `getTotalTime()` in jsPsych v7.", + currentTrial: "`currentTrial()` was renamed to `getCurrentTrial()` in jsPsych v7.", + initSettings: "`initSettings()` was renamed to `getInitSettings()` in jsPsych v7.", + allTimelineVariables: "`allTimelineVariables()` was renamed to `getAllTimelineVariables()` in jsPsych v7.", + }; + Object.defineProperties(jsPsych, Object.fromEntries(Object.entries(migrationMessages).map(([key, message]) => [ + key, + { + get() { + throw new MigrationError(message); + }, + }, + ]))); + return jsPsych; } - preloads.push(preload); - } - - module.autoPreload = function(timeline, callback) { - // list of items to preload - var images = []; - var audio = []; - - // construct list - for (var i = 0; i < preloads.length; i++) { - var type = preloads[i].plugin; - var param = preloads[i].parameter; - var media = preloads[i].media_type; - var trials = timeline.trialsOfType(type); - for (var j = 0; j < trials.length; j++) { - if (typeof trials[j][param] !== 'undefined') { - if (media == 'image') { - images = images.concat(flatten([trials[j][param]])); - } else if (media == 'audio') { - audio = audio.concat(flatten([trials[j][param]])); - } - } - } - } + exports.JsPsych = JsPsych; + exports.initJsPsych = initJsPsych; + exports.universalPluginParameters = universalPluginParameters; - // do the preloading - // first the images, then when the images are complete - // wait for the audio files to finish - module.preloadImages(images, function() { - module.preloadAudioFiles(audio, function() { - callback(); - }); - }); - } + Object.defineProperty(exports, '__esModule', { value: true }); - return module; -})(); + return exports; -// methods used in multiple modules -function flatten(arr, out) { - out = (typeof out === 'undefined') ? [] : out; - for (var i = 0; i < arr.length; i++) { - if (Array.isArray(arr[i])) { - flatten(arr[i], out); - } else { - out.push(arr[i]); - } - } - return out; -} +})({}); +var initJsPsych = jsPsychModule.initJsPsych; diff --git a/experiment/static/js/jsPsych/license.txt b/experiment/static/js/jsPsych/license.txt deleted file mode 100644 index 55a24e9..0000000 --- a/experiment/static/js/jsPsych/license.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Joshua R. de Leeuw - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/experiment/static/js/jsPsych/plugin-html-keyboard-response.js b/experiment/static/js/jsPsych/plugin-html-keyboard-response.js new file mode 100644 index 0000000..d488219 --- /dev/null +++ b/experiment/static/js/jsPsych/plugin-html-keyboard-response.js @@ -0,0 +1,173 @@ +var jsPsychHtmlKeyboardResponse = (function (jspsych) { + 'use strict'; + + const info = { + name: "html-keyboard-response", + parameters: { + /** + * The HTML string to be displayed. + */ + stimulus: { + type: jspsych.ParameterType.HTML_STRING, + pretty_name: "Stimulus", + default: undefined, + }, + /** + * Array containing the key(s) the subject is allowed to press to respond to the stimulus. + */ + choices: { + type: jspsych.ParameterType.KEYS, + pretty_name: "Choices", + default: "ALL_KEYS", + }, + /** + * Any content here will be displayed below the stimulus. + */ + prompt: { + type: jspsych.ParameterType.HTML_STRING, + pretty_name: "Prompt", + default: null, + }, + /** + * How long to show the stimulus. + */ + stimulus_duration: { + type: jspsych.ParameterType.INT, + pretty_name: "Stimulus duration", + default: null, + }, + /** + * How long to show trial before it ends. + */ + trial_duration: { + type: jspsych.ParameterType.INT, + pretty_name: "Trial duration", + default: null, + }, + /** + * If true, trial will end when subject makes a response. + */ + response_ends_trial: { + type: jspsych.ParameterType.BOOL, + pretty_name: "Response ends trial", + default: true, + }, + }, + }; + /** + * **html-keyboard-response** + * + * jsPsych plugin for displaying a stimulus and getting a keyboard response + * + * @author Josh de Leeuw + * @see {@link https://www.jspsych.org/plugins/jspsych-html-keyboard-response/ html-keyboard-response plugin documentation on jspsych.org} + */ + class HtmlKeyboardResponsePlugin { + constructor(jsPsych) { + this.jsPsych = jsPsych; + } + trial(display_element, trial) { + var new_html = '
' + trial.stimulus + "
"; + // add prompt + if (trial.prompt !== null) { + new_html += trial.prompt; + } + // draw + display_element.innerHTML = new_html; + // store response + var response = { + rt: null, + key: null, + }; + // function to end trial when it is time + const end_trial = () => { + // kill any remaining setTimeout handlers + this.jsPsych.pluginAPI.clearAllTimeouts(); + // kill keyboard listeners + if (typeof keyboardListener !== "undefined") { + this.jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener); + } + // gather the data to store for the trial + var trial_data = { + rt: response.rt, + stimulus: trial.stimulus, + response: response.key, + }; + // clear the display + display_element.innerHTML = ""; + // move on to the next trial + this.jsPsych.finishTrial(trial_data); + }; + // function to handle responses by the subject + var after_response = (info) => { + // after a valid response, the stimulus will have the CSS class 'responded' + // which can be used to provide visual feedback that a response was recorded + display_element.querySelector("#jspsych-html-keyboard-response-stimulus").className += + " responded"; + // only record the first response + if (response.key == null) { + response = info; + } + if (trial.response_ends_trial) { + end_trial(); + } + }; + // start the response listener + if (trial.choices != "NO_KEYS") { + var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({ + callback_function: after_response, + valid_responses: trial.choices, + rt_method: "performance", + persist: false, + allow_held_key: false, + }); + } + // hide stimulus if stimulus_duration is set + if (trial.stimulus_duration !== null) { + this.jsPsych.pluginAPI.setTimeout(() => { + display_element.querySelector("#jspsych-html-keyboard-response-stimulus").style.visibility = "hidden"; + }, trial.stimulus_duration); + } + // end trial if trial_duration is set + if (trial.trial_duration !== null) { + this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration); + } + } + simulate(trial, simulation_mode, simulation_options, load_callback) { + if (simulation_mode == "data-only") { + load_callback(); + this.simulate_data_only(trial, simulation_options); + } + if (simulation_mode == "visual") { + this.simulate_visual(trial, simulation_options, load_callback); + } + } + create_simulation_data(trial, simulation_options) { + const default_data = { + stimulus: trial.stimulus, + rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true), + response: this.jsPsych.pluginAPI.getValidKey(trial.choices), + }; + const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options); + this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data); + return data; + } + simulate_data_only(trial, simulation_options) { + const data = this.create_simulation_data(trial, simulation_options); + this.jsPsych.finishTrial(data); + } + simulate_visual(trial, simulation_options, load_callback) { + const data = this.create_simulation_data(trial, simulation_options); + const display_element = this.jsPsych.getDisplayElement(); + this.trial(display_element, trial); + load_callback(); + if (data.rt !== null) { + this.jsPsych.pluginAPI.pressKey(data.response, data.rt); + } + } + } + HtmlKeyboardResponsePlugin.info = info; + + return HtmlKeyboardResponsePlugin; + +})(jsPsychModule); diff --git a/experiment/static/js/jsPsych/plugins/jspsych-button-response.js b/experiment/static/js/jsPsych/plugins/jspsych-button-response.js deleted file mode 100644 index 5861272..0000000 --- a/experiment/static/js/jsPsych/plugins/jspsych-button-response.js +++ /dev/null @@ -1,152 +0,0 @@ -/** - * jspsych-button-response - * Josh de Leeuw - * - * plugin for displaying a stimulus and getting a keyboard response - * - * documentation: docs.jspsych.org - * - **/ - -jsPsych.plugins["button-response"] = (function() { - - var plugin = {}; - - plugin.trial = function(display_element, trial) { - - // default trial parameters - trial.button_html = trial.button_html || ''; - trial.response_ends_trial = (typeof trial.response_ends_trial === 'undefined') ? true : trial.response_ends_trial; - trial.timing_stim = trial.timing_stim || -1; // if -1, then show indefinitely - trial.timing_response = trial.timing_response || -1; // if -1, then wait for response forever - trial.is_html = (typeof trial.is_html === 'undefined') ? false : trial.is_html; - trial.prompt = (typeof trial.prompt === 'undefined') ? "" : trial.prompt; - - // if any trial variables are functions - // this evaluates the function and replaces - // it with the output of the function - trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial); - - // this array holds handlers from setTimeout calls - // that need to be cleared if the trial ends early - var setTimeoutHandlers = []; - - // display stimulus - if (!trial.is_html) { - display_element.append($('', { - src: trial.stimulus, - id: 'jspsych-button-response-stimulus', - class: 'block-center' - })); - } else { - display_element.append($('
', { - html: trial.stimulus, - id: 'jspsych-button-response-stimulus', - class: 'block-center' - })); - } - - //display buttons - var buttons = []; - if (Array.isArray(trial.button_html)) { - if (trial.button_html.length == trial.choices.length) { - buttons = trial.button_html; - } else { - console.error('Error in button-response plugin. The length of the button_html array does not equal the length of the choices array'); - } - } else { - for (var i = 0; i < trial.choices.length; i++) { - buttons.push(trial.button_html); - } - } - display_element.append('
') - for (var i = 0; i < trial.choices.length; i++) { - var str = buttons[i].replace(/%choice%/g, trial.choices[i]); - $('#jspsych-button-response-btngroup').append( - $(str).attr('id', 'jspsych-button-response-button-' + i).data('choice', i).addClass('jspsych-button-response-button').on('click', function(e) { - var choice = $('#' + this.id).data('choice'); - after_response(choice); - }) - ); - } - - //show prompt if there is one - if (trial.prompt !== "") { - display_element.append(trial.prompt); - } - - // store response - var response = { - rt: -1, - button: -1 - }; - - // start time - var start_time = 0; - - // function to handle responses by the subject - function after_response(choice) { - - // measure rt - var end_time = Date.now(); - var rt = end_time - start_time; - response.button = choice; - response.rt = rt; - - // after a valid response, the stimulus will have the CSS class 'responded' - // which can be used to provide visual feedback that a response was recorded - $("#jspsych-button-response-stimulus").addClass('responded'); - - // disable all the buttons after a response - $('.jspsych-button-response-button').off('click').attr('disabled', 'disabled'); - - if (trial.response_ends_trial) { - end_trial(); - } - }; - - // function to end trial when it is time - function end_trial() { - - // kill any remaining setTimeout handlers - for (var i = 0; i < setTimeoutHandlers.length; i++) { - clearTimeout(setTimeoutHandlers[i]); - } - - // gather the data to store for the trial - var trial_data = { - "rt": response.rt, - "stimulus": trial.stimulus, - "button_pressed": response.button - }; - - // clear the display - display_element.html(''); - - // move on to the next trial - jsPsych.finishTrial(trial_data); - }; - - // start timing - start_time = Date.now(); - - // hide image if timing is set - if (trial.timing_stim > 0) { - var t1 = setTimeout(function() { - $('#jspsych-button-response-stimulus').css('visibility', 'hidden'); - }, trial.timing_stim); - setTimeoutHandlers.push(t1); - } - - // end trial if time limit is set - if (trial.timing_response > 0) { - var t2 = setTimeout(function() { - end_trial(); - }, trial.timing_response); - setTimeoutHandlers.push(t2); - } - - }; - - return plugin; -})(); diff --git a/experiment/static/js/jsPsych/plugins/jspsych-html.js b/experiment/static/js/jsPsych/plugins/jspsych-html.js deleted file mode 100644 index e8decb4..0000000 --- a/experiment/static/js/jsPsych/plugins/jspsych-html.js +++ /dev/null @@ -1,52 +0,0 @@ -/** (July 2012, Erik Weitnauer) -The html-plugin will load and display an arbitrary number of html pages. To proceed to the next, the -user might either press a button on the page or a specific key. Afterwards, the page get hidden and -the plugin will wait of a specified time before it proceeds. - -documentation: docs.jspsych.org -*/ - -jsPsych.plugins.html = (function() { - - var plugin = {}; - - plugin.trial = function(display_element, trial) { - - // default parameters - trial.check_fn = trial.check_fn || function() { return true; } - trial.force_refresh = (typeof trial.force_refresh === 'undefined') ? false : trial.force_refresh - - // if any trial variables are functions - // this evaluates the function and replaces - // it with the output of the function - trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial, ["check_fn"]); - - var url = trial.url; - if (trial.force_refresh) { - url = trial.url + "?time=" + (new Date().getTime()); - } - - display_element.load(trial.url, function() { - var t0 = (new Date()).getTime(); - var finish = function() { - if (trial.check_fn && !trial.check_fn(display_element)) return; - if (trial.cont_key) $(document).unbind('keydown', key_listener); - var trial_data = { - rt: (new Date()).getTime() - t0, - url: trial.url - }; - display_element.empty(); - jsPsych.finishTrial(trial_data); - }; - if (trial.cont_btn) $('#' + trial.cont_btn).click(finish); - if (trial.cont_key) { - var key_listener = function(e) { - if (e.which == trial.cont_key) finish(); - }; - $(document).keydown(key_listener); - } - }); - }; - - return plugin; -})(); diff --git a/experiment/static/js/jsPsych/plugins/jspsych-instructions.js b/experiment/static/js/jsPsych/plugins/jspsych-instructions.js deleted file mode 100644 index 48af66f..0000000 --- a/experiment/static/js/jsPsych/plugins/jspsych-instructions.js +++ /dev/null @@ -1,160 +0,0 @@ -/* jspsych-text.js - * Josh de Leeuw - * - * This plugin displays text (including HTML formatted strings) during the experiment. - * Use it to show instructions, provide performance feedback, etc... - * - * documentation: docs.jspsych.org - * - * - */ - -jsPsych.plugins.instructions = (function() { - - var plugin = {}; - - plugin.trial = function(display_element, trial) { - - trial.key_forward = trial.key_forward || 'rightarrow'; - trial.key_backward = trial.key_backward || 'leftarrow'; - trial.allow_backward = (typeof trial.allow_backward === 'undefined') ? true : trial.allow_backward; - trial.allow_keys = (typeof trial.allow_keys === 'undefined') ? true : trial.allow_keys; - trial.show_clickable_nav = (typeof trial.show_clickable_nav === 'undefined') ? false : trial.show_clickable_nav; - - // if any trial variables are functions - // this evaluates the function and replaces - // it with the output of the function - trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial); - - var current_page = 0; - - var view_history = []; - - var start_time = (new Date()).getTime(); - - var last_page_update_time = start_time; - - function show_current_page() { - display_element.html(trial.pages[current_page]); - - if (trial.show_clickable_nav) { - - var nav_html = "
"; - if (current_page != 0 && trial.allow_backward) { - nav_html += ""; - } - nav_html += "
" - - display_element.append(nav_html); - - if (current_page != 0 && trial.allow_backward) { - $('#jspsych-instructions-back').on('click', function() { - clear_button_handlers(); - back(); - }); - } - - $('#jspsych-instructions-next').on('click', function() { - clear_button_handlers(); - next(); - }); - - } - } - - function clear_button_handlers() { - $('#jspsych-instructions-next').off('click'); - $('#jspsych-instructions-back').off('click'); - } - - function next() { - - add_current_page_to_view_history() - - current_page++; - - // if done, finish up... - if (current_page >= trial.pages.length) { - endTrial(); - } else { - show_current_page(); - } - - } - - function back() { - - add_current_page_to_view_history() - - current_page--; - - show_current_page(); - } - - function add_current_page_to_view_history() { - - var current_time = (new Date()).getTime(); - - var page_view_time = current_time - last_page_update_time; - - view_history.push({ - page_index: current_page, - viewing_time: page_view_time - }); - - last_page_update_time = current_time; - } - - function endTrial() { - - if (trial.allow_keys) { - jsPsych.pluginAPI.cancelKeyboardResponse(keyboard_listener); - } - - display_element.html(''); - - var trial_data = { - "view_history": JSON.stringify(view_history), - "rt": (new Date()).getTime() - start_time - }; - - jsPsych.finishTrial(trial_data); - } - - var after_response = function(info) { - - // have to reinitialize this instead of letting it persist to prevent accidental skips of pages by holding down keys too long - keyboard_listener = jsPsych.pluginAPI.getKeyboardResponse({ - callback_function: after_response, - valid_responses: [trial.key_forward, trial.key_backward], - rt_method: 'date', - persist: false, - allow_held_key: false - }); - // check if key is forwards or backwards and update page - if (info.key === trial.key_backward || info.key === jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_backward)) { - if (current_page !== 0 && trial.allow_backward) { - back(); - } - } - - if (info.key === trial.key_forward || info.key === jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_forward)) { - next(); - } - - }; - - show_current_page(); - - if (trial.allow_keys) { - var keyboard_listener = jsPsych.pluginAPI.getKeyboardResponse({ - callback_function: after_response, - valid_responses: [trial.key_forward, trial.key_backward], - rt_method: 'date', - persist: false - }); - } - }; - - return plugin; -})(); diff --git a/experiment/static/js/jsPsych/plugins/jspsych-survey-multi-choice.js b/experiment/static/js/jsPsych/plugins/jspsych-survey-multi-choice.js deleted file mode 100644 index 5ebec99..0000000 --- a/experiment/static/js/jsPsych/plugins/jspsych-survey-multi-choice.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * jspsych-survey-multi-choice - * a jspsych plugin for multiple choice survey questions - * - * Shane Martin - * - * documentation: docs.jspsych.org - * - */ - - -jsPsych.plugins['survey-multi-choice'] = (function() { - - var plugin = {}; - - plugin.trial = function(display_element, trial) { - display_element.html('') - - var plugin_id_name = "jspsych-survey-multi-choice"; - var plugin_id_selector = '#' + plugin_id_name; - var _join = function( /*args*/ ) { - var arr = Array.prototype.slice.call(arguments, _join.length); - return arr.join(separator = '-'); - } - - // trial defaults - trial.preamble = typeof trial.preamble == 'undefined' ? "" : trial.preamble; - trial.required = typeof trial.required == 'undefined' ? null : trial.required; - trial.horizontal = typeof trial.required == 'undefined' ? false : trial.horizontal; - - // if any trial variables are functions - // this evaluates the function and replaces - // it with the output of the function - trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial, protect=['on_mistake']); - - // form element - var trial_form_id = _join(plugin_id_name, "form"); - display_element.append($('
', { - "id": trial_form_id - })); - var $trial_form = $("#" + trial_form_id); - - // show preamble text - var preamble_id_name = _join(plugin_id_name, 'preamble'); - $trial_form.append($('
', { - "id": preamble_id_name, - "class": preamble_id_name - })); - $('#' + preamble_id_name).html(trial.preamble); - - // add multiple-choice questions - for (var i = 0; i < trial.questions.length; i++) { - // create question container - var question_classes = [_join(plugin_id_name, 'question')]; - if (trial.horizontal) { - question_classes.push(_join(plugin_id_name, 'horizontal')); - } - - $trial_form.append($('
', { - "id": _join(plugin_id_name, i), - "class": question_classes.join(' ') - })); - - var question_selector = _join(plugin_id_selector, i); - - // add question text - $(question_selector).append( - '

' + trial.questions[i] + '

' - ); - - // create option radio buttons - for (var j = 0; j < trial.options[i].length; j++) { - var option_id_name = _join(plugin_id_name, "option", i, j), - option_id_selector = '#' + option_id_name; - - // add radio button container - $(question_selector).append($('
', { - "id": option_id_name, - "class": _join(plugin_id_name, 'option') - })); - - // add label and question text - var option_label = ''; - $(option_id_selector).append(option_label); - - // create radio button - var input_id_name = _join(plugin_id_name, 'response', i); - $(option_id_selector + " label").prepend(''); - } - - if (trial.required && trial.required[i]) { - // add "question required" asterisk - $(question_selector + " p").append("*") - - // add required property - $(question_selector + " input:radio").prop("required", true); - } - } - - // add submit button - $trial_form.append($('', { - 'type': 'submit', - 'id': plugin_id_name + '-next', - 'class': plugin_id_name + ' jspsych-btn', - 'value': 'Submit Answers' - })); - - // trial_form.noValidate = true; - $trial_form.submit(function(event) { - - event.preventDefault(); - - if (!event.target.checkValidity()) { - event.preventDefault(); // dismiss the default functionality - alert('Please answer all required questions.'); // error message - return - } - - // measure response time - var endTime = (new Date()).getTime(); - var response_time = endTime - startTime; - - // create object to hold responses - var question_data = []; - $("div." + plugin_id_name + "-question").each(function(index) { - // var id = "Q" + index; - var val = $(this).find("input:radio:checked").val(); - // var obje = {}; - // obje[id] = val; - // $.extend(question_data, obje); - question_data.push(val) - }); - - var correct = null; - var mistake = false; - console.log(trial.correct) - if (trial.correct) { - correct = []; - for (var i = 0; i < question_data.length; i++) { - var c = trial.correct[i] == question_data[i]; - if (!c) mistake = true; - correct.push(c) - } - } - - - // save data - var trial_data = { - "rt": response_time, - "questions": JSON.stringify(trial.questions), - "responses": JSON.stringify(question_data), - "correct": correct - }; - if (trial.on_mistake && mistake) trial.on_mistake(trial_data); - - display_element.html(''); - - // next trial - jsPsych.finishTrial(trial_data); - }); - - var startTime = (new Date()).getTime(); - }; - - return plugin; -})(); diff --git a/experiment/static/js/jsPsych/plugins/jspsych-survey-text.js b/experiment/static/js/jsPsych/plugins/jspsych-survey-text.js deleted file mode 100644 index e7fd5d4..0000000 --- a/experiment/static/js/jsPsych/plugins/jspsych-survey-text.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * jspsych-survey-text - * a jspsych plugin for free response survey questions - * - * Josh de Leeuw - * - * documentation: docs.jspsych.org - * - */ - - -jsPsych.plugins['survey-text'] = (function() { - - var plugin = {}; - - plugin.trial = function(display_element, trial) { - - trial.preamble = typeof trial.preamble == 'undefined' ? "" : trial.preamble; - if (typeof trial.rows == 'undefined') { - trial.rows = []; - for (var i = 0; i < trial.questions.length; i++) { - trial.rows.push(1); - } - } - if (typeof trial.columns == 'undefined') { - trial.columns = []; - for (var i = 0; i < trial.questions.length; i++) { - trial.columns.push(40); - } - } - - // if any trial variables are functions - // this evaluates the function and replaces - // it with the output of the function - trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial); - - // show preamble text - display_element.append($('
', { - "id": 'jspsych-survey-text-preamble', - "class": 'jspsych-survey-text-preamble' - })); - - $('#jspsych-survey-text-preamble').html(trial.preamble); - - // add questions - for (var i = 0; i < trial.questions.length; i++) { - // create div - display_element.append($('
', { - "id": 'jspsych-survey-text-' + i, - "class": 'jspsych-survey-text-question' - })); - - // add question text - $("#jspsych-survey-text-" + i).append('

' + trial.questions[i] + '

'); - - // add text box - $("#jspsych-survey-text-" + i).append(''); - } - - // add submit button - display_element.append($('