jspsych 7.2.3 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -67,7 +67,7 @@ var autoBind = (self, {include, exclude} = {}) => {
67
67
  return self;
68
68
  };
69
69
 
70
- var version = "7.2.3";
70
+ var version = "7.3.1";
71
71
 
72
72
  class MigrationError extends Error {
73
73
  constructor(message = "The global `jsPsych` variable is no longer available in jsPsych v7.") {
@@ -839,8 +839,13 @@ class MediaAPI {
839
839
  this.img_cache = {};
840
840
  this.preloadMap = new Map();
841
841
  this.microphone_recorder = null;
842
+ this.camera_stream = null;
843
+ this.camera_recorder = null;
842
844
  }
843
845
  getVideoBuffer(videoID) {
846
+ if (videoID.startsWith("blob:")) {
847
+ this.video_buffers[videoID] = videoID;
848
+ }
844
849
  return this.video_buffers[videoID];
845
850
  }
846
851
  initAudio() {
@@ -895,15 +900,15 @@ class MediaAPI {
895
900
  callback_error({ source: source, error: e });
896
901
  });
897
902
  };
898
- request.onerror = function (e) {
903
+ request.onerror = (e) => {
899
904
  let err = e;
900
- if (this.status == 404) {
905
+ if (request.status == 404) {
901
906
  err = "404";
902
907
  }
903
908
  callback_error({ source: source, error: err });
904
909
  };
905
- request.onloadend = function (e) {
906
- if (this.status == 404) {
910
+ request.onloadend = (e) => {
911
+ if (request.status == 404) {
907
912
  callback_error({ source: source, error: "404" });
908
913
  }
909
914
  };
@@ -960,20 +965,21 @@ class MediaAPI {
960
965
  callback_complete();
961
966
  return;
962
967
  }
963
- for (var i = 0; i < images.length; i++) {
964
- var img = new Image();
965
- img.onload = function () {
968
+ for (let i = 0; i < images.length; i++) {
969
+ const img = new Image();
970
+ const src = images[i];
971
+ img.onload = () => {
966
972
  n_loaded++;
967
- callback_load(img.src);
973
+ callback_load(src);
968
974
  if (n_loaded === images.length) {
969
975
  callback_complete();
970
976
  }
971
977
  };
972
- img.onerror = function (e) {
973
- callback_error({ source: img.src, error: e });
978
+ img.onerror = (e) => {
979
+ callback_error({ source: src, error: e });
974
980
  };
975
- img.src = images[i];
976
- this.img_cache[images[i]] = img;
981
+ img.src = src;
982
+ this.img_cache[src] = img;
977
983
  this.preload_requests.push(img);
978
984
  }
979
985
  }
@@ -991,9 +997,9 @@ class MediaAPI {
991
997
  const request = new XMLHttpRequest();
992
998
  request.open("GET", video, true);
993
999
  request.responseType = "blob";
994
- request.onload = function () {
995
- if (this.status === 200 || this.status === 0) {
996
- const videoBlob = this.response;
1000
+ request.onload = () => {
1001
+ if (request.status === 200 || request.status === 0) {
1002
+ const videoBlob = request.response;
997
1003
  video_buffers[video] = URL.createObjectURL(videoBlob); // IE10+
998
1004
  n_loaded++;
999
1005
  callback_load(video);
@@ -1002,15 +1008,15 @@ class MediaAPI {
1002
1008
  }
1003
1009
  }
1004
1010
  };
1005
- request.onerror = function (e) {
1011
+ request.onerror = (e) => {
1006
1012
  let err = e;
1007
- if (this.status == 404) {
1013
+ if (request.status == 404) {
1008
1014
  err = "404";
1009
1015
  }
1010
1016
  callback_error({ source: video, error: err });
1011
1017
  };
1012
- request.onloadend = function (e) {
1013
- if (this.status == 404) {
1018
+ request.onloadend = (e) => {
1019
+ if (request.status == 404) {
1014
1020
  callback_error({ source: video, error: "404" });
1015
1021
  }
1016
1022
  };
@@ -1082,6 +1088,17 @@ class MediaAPI {
1082
1088
  getMicrophoneRecorder() {
1083
1089
  return this.microphone_recorder;
1084
1090
  }
1091
+ initializeCameraRecorder(stream, opts) {
1092
+ this.camera_stream = stream;
1093
+ const recorder = new MediaRecorder(stream, opts);
1094
+ this.camera_recorder = recorder;
1095
+ }
1096
+ getCameraStream() {
1097
+ return this.camera_stream;
1098
+ }
1099
+ getCameraRecorder() {
1100
+ return this.camera_recorder;
1101
+ }
1085
1102
  }
1086
1103
 
1087
1104
  class SimulationAPI {
@@ -1856,7 +1873,8 @@ function shuffleNoRepeats(arr, equalityTest) {
1856
1873
  // test to make sure the new neighbor isn't equal to the old one
1857
1874
  while (equalityTest(random_shuffle[i + 1], random_shuffle[random_pick]) ||
1858
1875
  equalityTest(random_shuffle[i + 1], random_shuffle[random_pick + 1]) ||
1859
- equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1])) {
1876
+ equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1]) ||
1877
+ equalityTest(random_shuffle[i], random_shuffle[random_pick])) {
1860
1878
  random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
1861
1879
  }
1862
1880
  const new_neighbor = random_shuffle[random_pick];
@@ -2406,7 +2424,11 @@ class TimelineNode {
2406
2424
  // recursive downward search for active trial to extract timeline variable
2407
2425
  timelineVariable(variable_name) {
2408
2426
  if (typeof this.timeline_parameters == "undefined") {
2409
- return this.findTimelineVariable(variable_name);
2427
+ const val = this.findTimelineVariable(variable_name);
2428
+ if (typeof val === "undefined") {
2429
+ console.warn("Timeline variable " + variable_name + " not found.");
2430
+ }
2431
+ return val;
2410
2432
  }
2411
2433
  else {
2412
2434
  // if progress.current_location is -1, then the timeline variable is being evaluated
@@ -2421,7 +2443,11 @@ class TimelineNode {
2421
2443
  loc = loc - 1;
2422
2444
  }
2423
2445
  // now find the variable
2424
- return this.timeline_parameters.timeline[loc].timelineVariable(variable_name);
2446
+ const val = this.timeline_parameters.timeline[loc].timelineVariable(variable_name);
2447
+ if (typeof val === "undefined") {
2448
+ console.warn("Timeline variable " + variable_name + " not found.");
2449
+ }
2450
+ return val;
2425
2451
  }
2426
2452
  }
2427
2453
  // recursively get all the timeline variables for this trial
@@ -2699,6 +2725,7 @@ class JsPsych {
2699
2725
  return this.DOM_container;
2700
2726
  }
2701
2727
  finishTrial(data = {}) {
2728
+ var _a;
2702
2729
  if (this.current_trial_finished) {
2703
2730
  return;
2704
2731
  }
@@ -2739,46 +2766,58 @@ class JsPsych {
2739
2766
  }
2740
2767
  }
2741
2768
  // handle extension callbacks
2742
- if (Array.isArray(current_trial.extensions)) {
2743
- for (const extension of current_trial.extensions) {
2744
- const ext_data_values = this.extensions[extension.type.info.name].on_finish(extension.params);
2745
- Object.assign(trial_data_values, ext_data_values);
2769
+ const extensionCallbackResults = ((_a = current_trial.extensions) !== null && _a !== void 0 ? _a : []).map((extension) => this.extensions[extension.type.info.name].on_finish(extension.params));
2770
+ const onExtensionCallbacksFinished = () => {
2771
+ // about to execute lots of callbacks, so switch context.
2772
+ this.internal.call_immediate = true;
2773
+ // handle callback at plugin level
2774
+ if (typeof current_trial.on_finish === "function") {
2775
+ current_trial.on_finish(trial_data_values);
2776
+ }
2777
+ // handle callback at whole-experiment level
2778
+ this.opts.on_trial_finish(trial_data_values);
2779
+ // after the above callbacks are complete, then the data should be finalized
2780
+ // for this trial. call the on_data_update handler, passing in the same
2781
+ // data object that just went through the trial's finish handlers.
2782
+ this.opts.on_data_update(trial_data_values);
2783
+ // done with callbacks
2784
+ this.internal.call_immediate = false;
2785
+ // wait for iti
2786
+ if (this.simulation_mode === "data-only") {
2787
+ this.nextTrial();
2746
2788
  }
2747
- }
2748
- // about to execute lots of callbacks, so switch context.
2749
- this.internal.call_immediate = true;
2750
- // handle callback at plugin level
2751
- if (typeof current_trial.on_finish === "function") {
2752
- current_trial.on_finish(trial_data_values);
2753
- }
2754
- // handle callback at whole-experiment level
2755
- this.opts.on_trial_finish(trial_data_values);
2756
- // after the above callbacks are complete, then the data should be finalized
2757
- // for this trial. call the on_data_update handler, passing in the same
2758
- // data object that just went through the trial's finish handlers.
2759
- this.opts.on_data_update(trial_data_values);
2760
- // done with callbacks
2761
- this.internal.call_immediate = false;
2762
- // wait for iti
2763
- if (this.simulation_mode === "data-only") {
2764
- this.nextTrial();
2765
- }
2766
- else if (typeof current_trial.post_trial_gap === null ||
2767
- typeof current_trial.post_trial_gap === "undefined") {
2768
- if (this.opts.default_iti > 0) {
2769
- setTimeout(this.nextTrial, this.opts.default_iti);
2789
+ else if (typeof current_trial.post_trial_gap === null ||
2790
+ typeof current_trial.post_trial_gap === "undefined") {
2791
+ if (this.opts.default_iti > 0) {
2792
+ setTimeout(this.nextTrial, this.opts.default_iti);
2793
+ }
2794
+ else {
2795
+ this.nextTrial();
2796
+ }
2770
2797
  }
2771
2798
  else {
2772
- this.nextTrial();
2799
+ if (current_trial.post_trial_gap > 0) {
2800
+ setTimeout(this.nextTrial, current_trial.post_trial_gap);
2801
+ }
2802
+ else {
2803
+ this.nextTrial();
2804
+ }
2773
2805
  }
2806
+ };
2807
+ // Strictly using Promise.resolve to turn all values into promises would be cleaner here, but it
2808
+ // would require user test code to make the event loop tick after every simulated key press even
2809
+ // if there are no async `on_finish` methods. Hence, in order to avoid a breaking change, we
2810
+ // only rely on the event loop if at least one `on_finish` method returns a promise.
2811
+ if (extensionCallbackResults.some((result) => typeof result.then === "function")) {
2812
+ Promise.all(extensionCallbackResults.map((result) => Promise.resolve(result).then((ext_data_values) => {
2813
+ Object.assign(trial_data_values, ext_data_values);
2814
+ }))).then(onExtensionCallbacksFinished);
2774
2815
  }
2775
2816
  else {
2776
- if (current_trial.post_trial_gap > 0) {
2777
- setTimeout(this.nextTrial, current_trial.post_trial_gap);
2778
- }
2779
- else {
2780
- this.nextTrial();
2817
+ for (const values of extensionCallbackResults) {
2818
+ Object.assign(trial_data_values, values);
2781
2819
  }
2820
+ onExtensionCallbacksFinished();
2782
2821
  }
2783
2822
  }
2784
2823
  endExperiment(end_message = "", data = {}) {
@@ -3066,16 +3105,16 @@ class JsPsych {
3066
3105
  }
3067
3106
  evaluateTimelineVariables(trial) {
3068
3107
  for (const key of Object.keys(trial)) {
3069
- // timeline variables on the root level
3070
3108
  if (typeof trial[key] === "object" &&
3071
3109
  trial[key] !== null &&
3072
3110
  typeof trial[key].timelineVariablePlaceholder !== "undefined") {
3073
- /*trial[key].toString().replace(/\s/g, "") ==
3074
- "function(){returntimeline.timelineVariable(varname);}"
3075
- )*/ trial[key] = trial[key].timelineVariableFunction();
3111
+ trial[key] = trial[key].timelineVariableFunction();
3076
3112
  }
3077
3113
  // timeline variables that are nested in objects
3078
- if (typeof trial[key] === "object" && trial[key] !== null) {
3114
+ if (typeof trial[key] === "object" &&
3115
+ trial[key] !== null &&
3116
+ key !== "timeline" &&
3117
+ key !== "timeline_variables") {
3079
3118
  this.evaluateTimelineVariables(trial[key]);
3080
3119
  }
3081
3120
  }
@@ -3118,9 +3157,11 @@ class JsPsych {
3118
3157
  else if (typeof obj === "object") {
3119
3158
  if (info === null || !info.nested) {
3120
3159
  for (const key of Object.keys(obj)) {
3121
- if (key === "type") {
3160
+ if (key === "type" || key === "timeline" || key === "timeline_variables") {
3122
3161
  // Ignore the object's `type` field because it contains a plugin and we do not want to
3123
- // call plugin functions
3162
+ // call plugin functions. Also ignore `timeline` and `timeline_variables` because they
3163
+ // are used in the `trials` parameter of the preload plugin and we don't want to actually
3164
+ // evaluate those in that context.
3124
3165
  continue;
3125
3166
  }
3126
3167
  obj[key] = this.replaceFunctionsWithValues(obj[key], null);