jspsych 7.2.3 → 7.3.0
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.browser.js +82 -43
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +1 -1
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +82 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +82 -43
- package/dist/index.js.map +1 -1
- package/dist/modules/extensions.d.ts +1 -1
- package/dist/modules/plugin-api/MediaAPI.d.ts +6 -1
- package/package.json +1 -1
- package/src/JsPsych.ts +65 -47
- package/src/TimelineNode.ts +10 -2
- package/src/modules/extensions.ts +1 -1
- package/src/modules/plugin-api/MediaAPI.ts +21 -1
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.
|
|
70
|
+
var version = "7.3.0";
|
|
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() {
|
|
@@ -1082,6 +1087,17 @@ class MediaAPI {
|
|
|
1082
1087
|
getMicrophoneRecorder() {
|
|
1083
1088
|
return this.microphone_recorder;
|
|
1084
1089
|
}
|
|
1090
|
+
initializeCameraRecorder(stream, opts) {
|
|
1091
|
+
this.camera_stream = stream;
|
|
1092
|
+
const recorder = new MediaRecorder(stream, opts);
|
|
1093
|
+
this.camera_recorder = recorder;
|
|
1094
|
+
}
|
|
1095
|
+
getCameraStream() {
|
|
1096
|
+
return this.camera_stream;
|
|
1097
|
+
}
|
|
1098
|
+
getCameraRecorder() {
|
|
1099
|
+
return this.camera_recorder;
|
|
1100
|
+
}
|
|
1085
1101
|
}
|
|
1086
1102
|
|
|
1087
1103
|
class SimulationAPI {
|
|
@@ -2406,7 +2422,11 @@ class TimelineNode {
|
|
|
2406
2422
|
// recursive downward search for active trial to extract timeline variable
|
|
2407
2423
|
timelineVariable(variable_name) {
|
|
2408
2424
|
if (typeof this.timeline_parameters == "undefined") {
|
|
2409
|
-
|
|
2425
|
+
const val = this.findTimelineVariable(variable_name);
|
|
2426
|
+
if (typeof val === "undefined") {
|
|
2427
|
+
console.warn("Timeline variable " + variable_name + " not found.");
|
|
2428
|
+
}
|
|
2429
|
+
return val;
|
|
2410
2430
|
}
|
|
2411
2431
|
else {
|
|
2412
2432
|
// if progress.current_location is -1, then the timeline variable is being evaluated
|
|
@@ -2421,7 +2441,11 @@ class TimelineNode {
|
|
|
2421
2441
|
loc = loc - 1;
|
|
2422
2442
|
}
|
|
2423
2443
|
// now find the variable
|
|
2424
|
-
|
|
2444
|
+
const val = this.timeline_parameters.timeline[loc].timelineVariable(variable_name);
|
|
2445
|
+
if (typeof val === "undefined") {
|
|
2446
|
+
console.warn("Timeline variable " + variable_name + " not found.");
|
|
2447
|
+
}
|
|
2448
|
+
return val;
|
|
2425
2449
|
}
|
|
2426
2450
|
}
|
|
2427
2451
|
// recursively get all the timeline variables for this trial
|
|
@@ -2699,6 +2723,7 @@ class JsPsych {
|
|
|
2699
2723
|
return this.DOM_container;
|
|
2700
2724
|
}
|
|
2701
2725
|
finishTrial(data = {}) {
|
|
2726
|
+
var _a;
|
|
2702
2727
|
if (this.current_trial_finished) {
|
|
2703
2728
|
return;
|
|
2704
2729
|
}
|
|
@@ -2739,46 +2764,58 @@ class JsPsych {
|
|
|
2739
2764
|
}
|
|
2740
2765
|
}
|
|
2741
2766
|
// handle extension callbacks
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2767
|
+
const extensionCallbackResults = ((_a = current_trial.extensions) !== null && _a !== void 0 ? _a : []).map((extension) => this.extensions[extension.type.info.name].on_finish(extension.params));
|
|
2768
|
+
const onExtensionCallbacksFinished = () => {
|
|
2769
|
+
// about to execute lots of callbacks, so switch context.
|
|
2770
|
+
this.internal.call_immediate = true;
|
|
2771
|
+
// handle callback at plugin level
|
|
2772
|
+
if (typeof current_trial.on_finish === "function") {
|
|
2773
|
+
current_trial.on_finish(trial_data_values);
|
|
2774
|
+
}
|
|
2775
|
+
// handle callback at whole-experiment level
|
|
2776
|
+
this.opts.on_trial_finish(trial_data_values);
|
|
2777
|
+
// after the above callbacks are complete, then the data should be finalized
|
|
2778
|
+
// for this trial. call the on_data_update handler, passing in the same
|
|
2779
|
+
// data object that just went through the trial's finish handlers.
|
|
2780
|
+
this.opts.on_data_update(trial_data_values);
|
|
2781
|
+
// done with callbacks
|
|
2782
|
+
this.internal.call_immediate = false;
|
|
2783
|
+
// wait for iti
|
|
2784
|
+
if (this.simulation_mode === "data-only") {
|
|
2785
|
+
this.nextTrial();
|
|
2746
2786
|
}
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
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);
|
|
2787
|
+
else if (typeof current_trial.post_trial_gap === null ||
|
|
2788
|
+
typeof current_trial.post_trial_gap === "undefined") {
|
|
2789
|
+
if (this.opts.default_iti > 0) {
|
|
2790
|
+
setTimeout(this.nextTrial, this.opts.default_iti);
|
|
2791
|
+
}
|
|
2792
|
+
else {
|
|
2793
|
+
this.nextTrial();
|
|
2794
|
+
}
|
|
2770
2795
|
}
|
|
2771
2796
|
else {
|
|
2772
|
-
|
|
2797
|
+
if (current_trial.post_trial_gap > 0) {
|
|
2798
|
+
setTimeout(this.nextTrial, current_trial.post_trial_gap);
|
|
2799
|
+
}
|
|
2800
|
+
else {
|
|
2801
|
+
this.nextTrial();
|
|
2802
|
+
}
|
|
2773
2803
|
}
|
|
2804
|
+
};
|
|
2805
|
+
// Strictly using Promise.resolve to turn all values into promises would be cleaner here, but it
|
|
2806
|
+
// would require user test code to make the event loop tick after every simulated key press even
|
|
2807
|
+
// if there are no async `on_finish` methods. Hence, in order to avoid a breaking change, we
|
|
2808
|
+
// only rely on the event loop if at least one `on_finish` method returns a promise.
|
|
2809
|
+
if (extensionCallbackResults.some((result) => typeof result.then === "function")) {
|
|
2810
|
+
Promise.all(extensionCallbackResults.map((result) => Promise.resolve(result).then((ext_data_values) => {
|
|
2811
|
+
Object.assign(trial_data_values, ext_data_values);
|
|
2812
|
+
}))).then(onExtensionCallbacksFinished);
|
|
2774
2813
|
}
|
|
2775
2814
|
else {
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
}
|
|
2779
|
-
else {
|
|
2780
|
-
this.nextTrial();
|
|
2815
|
+
for (const values of extensionCallbackResults) {
|
|
2816
|
+
Object.assign(trial_data_values, values);
|
|
2781
2817
|
}
|
|
2818
|
+
onExtensionCallbacksFinished();
|
|
2782
2819
|
}
|
|
2783
2820
|
}
|
|
2784
2821
|
endExperiment(end_message = "", data = {}) {
|
|
@@ -3066,16 +3103,16 @@ class JsPsych {
|
|
|
3066
3103
|
}
|
|
3067
3104
|
evaluateTimelineVariables(trial) {
|
|
3068
3105
|
for (const key of Object.keys(trial)) {
|
|
3069
|
-
// timeline variables on the root level
|
|
3070
3106
|
if (typeof trial[key] === "object" &&
|
|
3071
3107
|
trial[key] !== null &&
|
|
3072
3108
|
typeof trial[key].timelineVariablePlaceholder !== "undefined") {
|
|
3073
|
-
|
|
3074
|
-
"function(){returntimeline.timelineVariable(varname);}"
|
|
3075
|
-
)*/ trial[key] = trial[key].timelineVariableFunction();
|
|
3109
|
+
trial[key] = trial[key].timelineVariableFunction();
|
|
3076
3110
|
}
|
|
3077
3111
|
// timeline variables that are nested in objects
|
|
3078
|
-
if (typeof trial[key] === "object" &&
|
|
3112
|
+
if (typeof trial[key] === "object" &&
|
|
3113
|
+
trial[key] !== null &&
|
|
3114
|
+
key !== "timeline" &&
|
|
3115
|
+
key !== "timeline_variables") {
|
|
3079
3116
|
this.evaluateTimelineVariables(trial[key]);
|
|
3080
3117
|
}
|
|
3081
3118
|
}
|
|
@@ -3118,9 +3155,11 @@ class JsPsych {
|
|
|
3118
3155
|
else if (typeof obj === "object") {
|
|
3119
3156
|
if (info === null || !info.nested) {
|
|
3120
3157
|
for (const key of Object.keys(obj)) {
|
|
3121
|
-
if (key === "type") {
|
|
3158
|
+
if (key === "type" || key === "timeline" || key === "timeline_variables") {
|
|
3122
3159
|
// Ignore the object's `type` field because it contains a plugin and we do not want to
|
|
3123
|
-
// call plugin functions
|
|
3160
|
+
// call plugin functions. Also ignore `timeline` and `timeline_variables` because they
|
|
3161
|
+
// are used in the `trials` parameter of the preload plugin and we don't want to actually
|
|
3162
|
+
// evaluate those in that context.
|
|
3124
3163
|
continue;
|
|
3125
3164
|
}
|
|
3126
3165
|
obj[key] = this.replaceFunctionsWithValues(obj[key], null);
|