var jsPsychModule = (function (exports) { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; // Gets all non-builtin properties up the prototype chain const getAllProperties = object => { const properties = new Set(); do { for (const key of Reflect.ownKeys(object)) { properties.add([object, key]); } } while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype); return properties; }; var autoBind = (self, {include, exclude} = {}) => { const filter = key => { const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); if (include) { return include.some(match); } if (exclude) { return !exclude.some(match); } return true; }; for (const [object, key] of getAllProperties(self.constructor.prototype)) { if (key === 'constructor' || !filter(key)) { continue; } const descriptor = Reflect.getOwnPropertyDescriptor(object, key); if (descriptor && typeof descriptor.value === 'function') { self[key] = self[key].bind(self); } } return self; }; var version = "7.2.3"; 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.'); }, }; /** * 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; } } var utils = /*#__PURE__*/Object.freeze({ __proto__: null, unique: unique, deepCopy: deepCopy }); class DataColumn { constructor(values = []) { this.values = values; } sum() { let s = 0; for (const v of this.values) { s += v; } return s; } mean() { return this.sum() / this.count(); } 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]; } min() { return Math.min.apply(null, this.values); } max() { return Math.max.apply(null, this.values); } 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; } subset(eval_fn) { const out = []; for (const x of this.values) { if (eval_fn(x)) { out.push(x); } } return new DataColumn(out); } } // 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); } else { blobURL = window.URL.createObjectURL(blobToSave); } 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; } 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); } } 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 = {}; } } 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; }); } /** * 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. } } 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; } } /** * 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, }, }; 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; } } 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; } } } } } 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 = []; } } 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 generateRandomWord(); } } function generateWordWithMaxLength() { var rightSize = false; var wordUsed; while (!rightSize) { wordUsed = generateRandomWord(); if(wordUsed.length <= options.maxLength) { rightSize = true; } } return wordUsed; } function generateRandomWord() { return wordList[randInt(wordList.length)]; } function randInt(lessThan) { return Math.floor(Math.random() * lessThan); } // No arguments = generate one word if (typeof(options) === 'undefined') { return word(); } // Just a number = return that many words if (typeof(options) === 'number') { options = { exactly: options }; } // 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; } //not a function = returns the raw word if (typeof(options.formatter) !== 'function') { options.formatter = (word) => word; } //not a string = separator is a space if (typeof(options.separator) !== 'string') { options.separator = ' '; } var total = options.min + randInt(options.max + 1 - options.min); var results = []; var token = ''; var relativeIndex = 0; 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); } 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øeThe 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 = '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", '