From f155192fd5a9ddcff6dc3bcc835ea0ad89515b26 Mon Sep 17 00:00:00 2001 From: Alain Pitiot Date: Mon, 31 May 2021 08:32:35 +0200 Subject: [PATCH] _ --- docs/TextInput.html | 4 +- docs/core_EventManager.js.html | 4 +- docs/core_GUI.js.html | 33 +- docs/core_Keyboard.js.html | 6 +- docs/core_Logger.js.html | 12 +- docs/core_MinimalStim.js.html | 4 +- docs/core_Mouse.js.html | 4 +- docs/core_PsychoJS.js.html | 77 +- docs/core_ServerManager.js.html | 584 +++++++++-- docs/core_Window.js.html | 10 +- docs/core_WindowMixin.js.html | 4 +- docs/data_ExperimentHandler.js.html | 4 +- docs/data_TrialHandler.js.html | 7 +- docs/index.html | 67 +- docs/module-core.BuilderKeyResponse.html | 4 +- docs/module-core.EventManager.html | 4 +- docs/module-core.GUI.html | 8 +- docs/module-core.KeyPress.html | 4 +- docs/module-core.Keyboard.html | 4 +- docs/module-core.Logger.html | 4 +- docs/module-core.MinimalStim.html | 4 +- docs/module-core.Mouse.html | 4 +- docs/module-core.PsychoJS.html | 420 ++++---- docs/module-core.ServerManager.html | 1166 ++++++++++++++++++---- docs/module-core.Window.html | 4 +- docs/module-core.WindowMixin.html | 4 +- docs/module-core.html | 4 +- docs/module-data.ExperimentHandler.html | 4 +- docs/module-data.TrialHandler.html | 30 +- docs/module-data.html | 7 +- docs/module-sound.Sound.html | 4 +- docs/module-sound.SoundPlayer.html | 4 +- docs/module-sound.TonePlayer.html | 4 +- docs/module-sound.TrackPlayer.html | 4 +- docs/module-sound.html | 16 +- docs/module-util.Clock.html | 4 +- docs/module-util.Color.html | 4 +- docs/module-util.ColorMixin.html | 4 +- docs/module-util.CountdownTimer.html | 4 +- docs/module-util.EventEmitter.html | 4 +- docs/module-util.MixinBuilder.html | 4 +- docs/module-util.MonotonicClock.html | 4 +- docs/module-util.PsychObject.html | 44 +- docs/module-util.Scheduler.html | 4 +- docs/module-util.html | 165 ++- docs/module-visual.Form.html | 4 +- docs/module-visual.ImageStim.html | 4 +- docs/module-visual.MovieStim.html | 4 +- docs/module-visual.Polygon.html | 4 +- docs/module-visual.Rect.html | 4 +- docs/module-visual.ShapeStim.html | 4 +- docs/module-visual.Slider.html | 4 +- docs/module-visual.TextBox.html | 4 +- docs/module-visual.TextStim.html | 4 +- docs/module-visual.VisualStim.html | 4 +- docs/module-visual.html | 4 +- docs/sound_Sound.js.html | 4 +- docs/sound_SoundPlayer.js.html | 4 +- docs/sound_TonePlayer.js.html | 4 +- docs/sound_TrackPlayer.js.html | 4 +- docs/util_Clock.js.html | 4 +- docs/util_Color.js.html | 4 +- docs/util_ColorMixin.js.html | 4 +- docs/util_EventEmitter.js.html | 4 +- docs/util_PsychObject.js.html | 10 +- docs/util_Scheduler.js.html | 16 +- docs/util_Util.js.html | 53 +- docs/visual_Form.js.html | 4 +- docs/visual_ImageStim.js.html | 4 +- docs/visual_MovieStim.js.html | 4 +- docs/visual_Polygon.js.html | 4 +- docs/visual_Rect.js.html | 4 +- docs/visual_ShapeStim.js.html | 4 +- docs/visual_Slider.js.html | 4 +- docs/visual_TextBox.js.html | 4 +- docs/visual_TextInput.js.html | 4 +- docs/visual_TextStim.js.html | 4 +- docs/visual_VisualStim.js.html | 4 +- index.css | 149 +++ index.js | 5 + package.json | 3 +- src/core/GUI.js | 29 +- src/core/Keyboard.js | 2 +- src/core/Logger.js | 8 +- src/core/PsychoJS.js | 73 +- src/core/ServerManager.js | 580 +++++++++-- src/data/TrialHandler.js | 3 +- src/data/index.js | 1 + src/sound/AudioClip.js | 368 +++++++ src/sound/Microphone.js | 531 ++++++++++ src/sound/index.js | 4 + src/util/PsychObject.js | 6 +- src/util/Scheduler.js | 12 +- src/util/Util.js | 392 +++++++- 94 files changed, 4192 insertions(+), 941 deletions(-) create mode 100644 index.css create mode 100644 index.js create mode 100644 src/sound/AudioClip.js create mode 100644 src/sound/Microphone.js diff --git a/docs/TextInput.html b/docs/TextInput.html index 2d6c4f2..37764dc 100644 --- a/docs/TextInput.html +++ b/docs/TextInput.html @@ -175,13 +175,13 @@
diff --git a/docs/core_EventManager.js.html b/docs/core_EventManager.js.html index 971c491..9b5ee72 100644 --- a/docs/core_EventManager.js.html +++ b/docs/core_EventManager.js.html @@ -699,13 +699,13 @@ export class BuilderKeyResponse
diff --git a/docs/core_GUI.js.html b/docs/core_GUI.js.html index 01f44d9..eef7a8a 100644 --- a/docs/core_GUI.js.html +++ b/docs/core_GUI.js.html @@ -341,7 +341,7 @@ export class GUI //$.blockUI({ message: "", baseZ: 1}); // show dialog box: - $("#progressbar").progressbar({value: self._progressBarCurrentIncrement}); + $("#progressbar").progressbar({value: self._progressBarCurrentValue}); $("#progressbar").progressbar("option", "max", self._progressBarMax); } @@ -615,15 +615,14 @@ export class GUI { this._psychoJS.logger.debug('signal: ' + util.toString(signal)); - // all resources have been registered: - if (signal.message === ServerManager.Event.RESOURCES_REGISTERED) + // the download of the specified resources has started: + if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCES) { // for each resource, we have a 'downloading resource' and a 'resource downloaded' message: this._progressBarMax = signal.count * 2; $("#progressbar").progressbar("option", "max", this._progressBarMax); - this._progressBarCurrentIncrement = 0; - $("#progressMsg").text('all resources registered.'); + this._progressBarCurrentValue = 0; } // all the resources have been downloaded: show the ok button @@ -635,23 +634,25 @@ export class GUI } // update progress bar: - else if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCE || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) + else if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCE + || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) { - if (typeof this._progressBarCurrentIncrement === 'undefined') + if (typeof this._progressBarCurrentValue === 'undefined') { - this._progressBarCurrentIncrement = 0; + this._progressBarCurrentValue = 0; } - ++this._progressBarCurrentIncrement; + ++this._progressBarCurrentValue; if (signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) { - $("#progressMsg").text('downloaded ' + this._progressBarCurrentIncrement / 2 + ' / ' + this._progressBarMax / 2); + $("#progressMsg").text('downloaded ' + (this._progressBarCurrentValue / 2) + ' / ' + (this._progressBarMax / 2)); + } + else + { + $("#progressMsg").text('downloading ' + (this._progressBarCurrentValue / 2) + ' / ' + (this._progressBarMax / 2)); } // $("#progressMsg").text(signal.resource + ': downloaded.'); - // else - // $("#progressMsg").text(signal.resource + ': downloading...'); - - $("#progressbar").progressbar("option", "value", this._progressBarCurrentIncrement); + $("#progressbar").progressbar("option", "value", this._progressBarCurrentValue); } // unknown message: we just display it @@ -908,13 +909,13 @@ GUI.dialogMargin = [50, 50];
diff --git a/docs/core_Keyboard.js.html b/docs/core_Keyboard.js.html index b693e8a..ec87b44 100644 --- a/docs/core_Keyboard.js.html +++ b/docs/core_Keyboard.js.html @@ -226,7 +226,7 @@ export class Keyboard extends PsychObject const keyEvent = this._circularBuffer[i]; if (keyEvent && keyEvent.status === Keyboard.KeyStatus.KEY_UP) { - // check that the key is in the keyList: + // if the keylist is empty of the key is in the keyList: if (keyList.length === 0 || keyList.includes(keyEvent.pigletKey)) { // look for a corresponding, preceding keydown event: @@ -526,13 +526,13 @@ Keyboard.KeyStatus = {
diff --git a/docs/core_Logger.js.html b/docs/core_Logger.js.html index a7808c5..36a525c 100644 --- a/docs/core_Logger.js.html +++ b/docs/core_Logger.js.html @@ -340,9 +340,9 @@ export class Logger */ _customConsoleLayout() { - const detectedBrowser = this._psychoJS.browser; + const detectedBrowser = util.detectBrowser(); - const customLayout = new log4javascript.PatternLayout("%p %f{1} | %m"); + const customLayout = new log4javascript.PatternLayout("%p %d{HH:mm:ss.SSS} %f{1} | %m"); customLayout.setCustomField('location', function (layout, loggingReference) { // we throw a fake exception to retrieve the stack trace @@ -373,7 +373,7 @@ export class Logger const file = buf[buf.length - 3].split('/').pop(); const method = relevantEntry.split('@')[0]; - return method + ' ' + file + ' ' + line; + return method + ' ' + file + ':' + line; } else if (detectedBrowser === 'Safari') { @@ -391,7 +391,7 @@ export class Logger const line = buf.pop(); const file = buf.pop().split('/').pop(); - return method + ' ' + file + ' ' + line; + return method + ' ' + file + ':' + line; } else { @@ -474,13 +474,13 @@ Logger._ServerLevelValue = {
diff --git a/docs/core_MinimalStim.js.html b/docs/core_MinimalStim.js.html index 72d37c5..3ff925f 100644 --- a/docs/core_MinimalStim.js.html +++ b/docs/core_MinimalStim.js.html @@ -260,13 +260,13 @@ export class MinimalStim extends PsychObject
diff --git a/docs/core_Mouse.js.html b/docs/core_Mouse.js.html index 1500d2e..0b7b6c7 100644 --- a/docs/core_Mouse.js.html +++ b/docs/core_Mouse.js.html @@ -397,13 +397,13 @@ export class Mouse extends PsychObject
diff --git a/docs/core_PsychoJS.js.html b/docs/core_PsychoJS.js.html index 3604fd4..0f421dd 100644 --- a/docs/core_PsychoJS.js.html +++ b/docs/core_PsychoJS.js.html @@ -46,6 +46,7 @@ import {GUI} from './GUI'; import {MonotonicClock} from '../util/Clock'; import {Logger} from './Logger'; import * as util from '../util/Util'; +// import {Shelf} from "../data/Shelf"; /** @@ -58,6 +59,7 @@ import * as util from '../util/Util'; */ export class PsychoJS { + /** * Properties */ @@ -137,6 +139,11 @@ export class PsychoJS return this._browser; } + // get shelf() + // { + // return this._shelf; + // } + /** * @constructor @@ -182,6 +189,9 @@ export class PsychoJS // Window: this._window = undefined; + // // Shelf: + // this._shelf = new Shelf(this); + // redirection URLs: this._cancellationUrl = undefined; this._completionUrl = undefined; @@ -320,6 +330,17 @@ export class PsychoJS /** * Start the experiment. * + * <p>The resources are specified in the following fashion: + * <ul> + * <li>For an experiment running locally: the root directory for the specified resources is that of index.html + * unless they are prepended with a protocol, such as http:// or https://.</li> + * <li>For an experiment running on the server: if no resources are specified, all files in the resources directory + * of the experiment are downloaded, otherwise we only download the specified resources. All resources are assumed + * local to index.html unless they are prepended with a protocol.</li> + * <li>If resources is null: we do not download any resources.</li> + * </ul> + * </p> + * * @param {Object} options * @param {string} [options.configURL=config.json] - the URL of the configuration file * @param {string} [options.expName=UNKNOWN] - the name of the experiment @@ -327,8 +348,6 @@ export class PsychoJS * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources * @async * @public - * - * @todo: close session on window or tab close */ async start({configURL = 'config.json', expName = 'UNKNOWN', expInfo = {}, resources = []} = {}) { @@ -414,11 +433,11 @@ export class PsychoJS // start the asynchronous download of resources: - this._serverManager.downloadResources(resources); + await this._serverManager.prepareResources(resources); // start the experiment: this.logger.info('[PsychoJS] Start Experiment.'); - this._scheduler.start(); + await this._scheduler.start(); } catch (error) { @@ -428,8 +447,12 @@ export class PsychoJS } + /** - * Synchronously download resources for the experiment. + * Block the experiment until the specified resources have been downloaded. + * + * <p>Note: only those resources that have not already been downloaded at that point are + * considered.</p> * * <ul> * <li>For an experiment running locally: the root directory for the specified resources is that of index.html @@ -439,14 +462,18 @@ export class PsychoJS * local to index.html unless they are prepended with a protocol.</li> * * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources - * @async * @public */ - async downloadResources(resources = []) + waitForResources(resources = []) { + const response = { + origin: 'PsychoJS.waitForResources', + context: 'while waiting for resources to be downloaded' + }; + try { - await this.serverManager.downloadResources(resources); + return this.serverManager.waitForResources(resources); } catch (error) { @@ -456,6 +483,7 @@ export class PsychoJS } + /** * Make the attributes of the given object those of PsychoJS and those of * the top level variable (e.g. window) as well. @@ -631,6 +659,14 @@ export class PsychoJS { throw 'missing URL in pavlovia block in configuration'; } + if (!('gitlab' in this._config)) + { + throw 'missing gitlab block in configuration'; + } + if (!('projectId' in this._config.gitlab)) + { + throw 'missing projectId in gitlab block in configuration'; + } this._config.environment = ExperimentHandler.Environment.SERVER; @@ -713,23 +749,21 @@ export class PsychoJS */ _captureErrors() { - this.logger.debug('capturing all errors using window.onerror'); + this.logger.debug('capturing all errors and showing them in a pop up window'); const self = this; window.onerror = function (message, source, lineno, colno, error) { console.error(error); - self._gui.dialog({"error": error}); + self._gui.dialog({error}); return true; }; - - /* NOT UNIVERSALLY SUPPORTED YET - window.addEventListener('unhandledrejection', event => { - console.error(error); - self._gui.dialog({"error" : error}); + window.onunhandledrejection = function (error) + { + console.error(error.reason); + self._gui.dialog({error: error.reason}); return true; - });*/ - + }; } @@ -765,9 +799,10 @@ PsychoJS.Status = { CONFIGURED: Symbol.for('CONFIGURED'), NOT_STARTED: Symbol.for('NOT_STARTED'), STARTED: Symbol.for('STARTED'), + PAUSED: Symbol.for('PAUSED'), FINISHED: Symbol.for('FINISHED'), - - STOPPED: Symbol.for('FINISHED') //Symbol.for('STOPPED') + STOPPED: Symbol.for('FINISHED'), //Symbol.for('STOPPED') + ERROR: Symbol.for('ERROR') }; @@ -780,13 +815,13 @@ PsychoJS.Status = {
diff --git a/docs/core_ServerManager.js.html b/docs/core_ServerManager.js.html index 59ea9dc..94f3e94 100644 --- a/docs/core_ServerManager.js.html +++ b/docs/core_ServerManager.js.html @@ -27,7 +27,7 @@
/**
- * Manager responsible for the communication between the experiment running in the participant's browser and the remote PsychoJS manager running on the remote https://pavlovia.org server.
+ * Manager responsible for the communication between the experiment running in the participant's browser and the pavlovia.org server.
  *
  * @author Alain Pitiot
  * @version 2021.1.4
@@ -46,9 +46,8 @@ import {MonotonicClock} from "../util/Clock";
 
 
 /**
- * <p>This manager handles all communications between the experiment running in the participant's browser and the remote PsychoJS manager running on the [pavlovia.org]{@link http://pavlovia.org} server, <em>in an asynchronous manner</em>.</p>
- * <p>It is responsible for reading the configuration file of an experiment, for opening and closing a session, for listing and downloading resources, and for uploading results and log.</p>
- * <p>Note: The Server Manager uses [Promises]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise} to deal with asynchronicity, is mostly called by {@link PsychoJS}, and is not exposed to the experiment code.</p>
+ * <p>This manager handles all communications between the experiment running in the participant's browser and the [pavlovia.org]{@link http://pavlovia.org} server, <em>in an asynchronous manner</em>.</p>
+ * <p>It is responsible for reading the configuration file of an experiment, for opening and closing a session, for listing and downloading resources, and for uploading results, logs, and audio recordings.</p>
  *
  * @name module:core.ServerManager
  * @class
@@ -59,6 +58,16 @@ import {MonotonicClock} from "../util/Clock";
  */
 export class ServerManager extends PsychObject
 {
+	/**
+	 * Used to indicate to the ServerManager that all resources must be registered (and
+	 * subsequently downloaded)
+	 *
+	 * @type {symbol}
+	 * @readonly
+	 * @public
+	 */
+	static ALL_RESOURCES = Symbol.for('ALL_RESOURCES');
+
 
 	constructor({
 								psychoJS,
@@ -70,9 +79,8 @@ export class ServerManager extends PsychObject
 		// session:
 		this._session = {};
 
-		// resources is a map of {name: string, path: string} -> data: any
+		// resources is a map of <name: string, { path: string, status: ResourceStatus, data: any }>
 		this._resources = new Map();
-		this._nbResources = -1;
 
 		this._addAttribute('autoLog', autoLog);
 		this._addAttribute('status', ServerManager.Status.READY);
@@ -300,25 +308,65 @@ export class ServerManager extends PsychObject
 	 * @name module:core.ServerManager#getResource
 	 * @function
 	 * @public
-	 * @param {string} name of the requested resource
-	 * @return {Object} value of the resource
+	 * @param {string} name - name of the requested resource
+	 * @param {boolean} [errorIfNotDownloaded = false] whether or not to throw an exception if the
+	 * resource status is not DOWNLOADED
+	 * @return {Object} value of the resource, or undefined if the resource has been registered
+	 * but not downloaded yet.
 	 * @throws {Object.<string, *>} exception if no resource with that name has previously been registered
 	 */
-	getResource(name)
+	getResource(name, errorIfNotDownloaded = false)
 	{
 		const response = {
 			origin: 'ServerManager.getResource',
 			context: 'when getting the value of resource: ' + name
 		};
 
-		const path_data = this._resources.get(name);
-		if (typeof path_data === 'undefined')
-		// throw { ...response, error: 'unknown resource' };
+		const pathStatusData = this._resources.get(name);
+
+		if (typeof pathStatusData === 'undefined')
 		{
+			// throw { ...response, error: 'unknown resource' };
 			throw Object.assign(response, {error: 'unknown resource'});
 		}
 
-		return path_data.data;
+		if (errorIfNotDownloaded && pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED)
+		{
+			throw Object.assign(response, {
+				error: name + ' is not available for use (yet), its current status is: ' +
+					util.toString(pathStatusData.status)
+			});
+		}
+
+		return pathStatusData.data;
+	}
+
+
+	/**
+	 * Get the status of a resource.
+	 *
+	 * @name module:core.ServerManager#getResourceStatus
+	 * @function
+	 * @public
+	 * @param {string} name of the requested resource
+	 * @return {core.ServerManager.ResourceStatus} status of the resource
+	 * @throws {Object.<string, *>} exception if no resource with that name has previously been registered
+	 */
+	getResourceStatus(name)
+	{
+		const response = {
+			origin: 'ServerManager.getResourceStatus',
+			context: 'when getting the status of resource: ' + name
+		};
+
+		const pathStatusData = this._resources.get(name);
+		if (typeof pathStatusData === 'undefined')
+		{
+			// throw { ...response, error: 'unknown resource' };
+			throw Object.assign(response, {error: 'unknown resource'});
+		}
+
+		return pathStatusData.status;
 	}
 
 
@@ -373,7 +421,8 @@ export class ServerManager extends PsychObject
 
 
 	/**
-	 * Asynchronously download resources for the experiment and register them with the server manager.
+	 * Prepare resources for the experiment: register them with the server manager and possibly
+	 * start downloading them right away.
 	 *
 	 * <ul>
 	 *   <li>For an experiment running locally: the root directory for the specified resources is that of index.html
@@ -381,85 +430,218 @@ export class ServerManager extends PsychObject
 	 *   <li>For an experiment running on the server: if no resources are specified, all files in the resources directory
 	 *   of the experiment are downloaded, otherwise we only download the specified resources. All resources are assumed
 	 *   local to index.html unless they are prepended with a protocol.</li>
+	 *   <li>If resources is null, then we do not download any resources</li>
 	 * </ul>
 	 *
-	 * @name module:core.ServerManager#downloadResources
+	 * @name module:core.ServerManager#prepareResources
+	 * @param {Array.<{name: string, path: string, download: boolean} | Symbol>} [resources=[]] - the list of resources
+	 * @function
+	 * @public
+	 */
+	async prepareResources(resources = [])
+	{
+		const response = {
+			origin: 'ServerManager.prepareResources',
+			context: 'when preparing resources for experiment: ' + this._psychoJS.config.experiment.name
+		};
+
+		this._psychoJS.logger.debug('preparing resources for experiment: ' + this._psychoJS.config.experiment.name);
+
+		try
+		{
+			const resourcesToDownload = new Set();
+
+			// register the resources:
+			if (resources !== null)
+			{
+				if (!Array.isArray(resources))
+				{
+					throw "resources should be an array of objects";
+				}
+
+				// whether all resources have been requested:
+
+				const allResources = (resources.length === 1 && resources[0] === ServerManager.ALL_RESOURCES);
+
+				// if the experiment is hosted on the pavlovia.org server and
+				// resources is [ServerManager.ALL_RESOURCES], then we register all the resources
+				// in the "resources" sub-directory
+				if (this._psychoJS.config.environment === ExperimentHandler.Environment.SERVER
+					&& allResources)
+				{
+					// list the resources from the resources directory of the experiment on the server:
+					const serverResponse = await this._listResources();
+
+					// register and mark for download those resources that have not been
+					// registered already:
+					for (const name of serverResponse.resources)
+					{
+						if (!this._resources.has(name))
+						{
+							const path = serverResponse.resourceDirectory + '/' + name;
+							this._resources.set(name, {
+								status: ServerManager.ResourceStatus.REGISTERED,
+								path,
+								data: undefined
+							});
+							this._psychoJS.logger.debug('registered resource:', name, path);
+							resourcesToDownload.add(name);
+						}
+					}
+				}
+
+				// if the experiment is hosted locally (localhost) or if specific resources were given
+				// then we register those specific resources, if they have not been registered already
+				else
+				{
+					// we cannot ask for all resources to be registered locally, since we cannot list
+					// them:
+					if (this._psychoJS.config.environment === ExperimentHandler.Environment.LOCAL
+						&& allResources)
+					{
+						throw "resources must be manually specified when the experiment is running locally: ALL_RESOURCES cannot be used";
+					}
+
+					for (let {name, path, download} of resources)
+					{
+						if (!this._resources.has(name))
+						{
+							// to deal with potential CORS issues, we use the pavlovia.org proxy for resources
+							// not hosted on pavlovia.org:
+							if ((path.toLowerCase().indexOf('www.') === 0 ||
+								path.toLowerCase().indexOf('http:') === 0 ||
+								path.toLowerCase().indexOf('https:') === 0) &&
+								(path.indexOf('pavlovia.org') === -1))
+							{
+								path = 'https://pavlovia.org/api/v2/proxy/' + path;
+							}
+
+							this._resources.set(name, {
+								status: ServerManager.ResourceStatus.REGISTERED,
+								path,
+								data: undefined
+							});
+							this._psychoJS.logger.debug('registered resource:', name, path);
+
+							// download resources by default:
+							if (typeof download === 'undefined' || download)
+							{
+								resourcesToDownload.add(name);
+							}
+						}
+					}
+				}
+			}
+
+			// download those registered resources for which download = true:
+			/*await*/ this._downloadResources(resourcesToDownload);
+		}
+		catch (error)
+		{
+			console.log('error', error);
+			throw Object.assign(response, {error});
+			// throw { ...response, error: error };
+		}
+	}
+
+
+	/**
+	 * Block the experiment until the specified resources have been downloaded.
+	 *
+	 * @name module:core.ServerManager#waitForResources
 	 * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources
 	 * @function
 	 * @public
 	 */
-	downloadResources(resources = [])
+	waitForResources(resources = [])
 	{
-		const response = {
-			origin: 'ServerManager.downloadResources',
-			context: 'when downloading the resources for experiment: ' + this._psychoJS.config.experiment.name
+		// prepare a PsychoJS component:
+		this._waitForDownloadComponent = {
+			status: PsychoJS.Status.NOT_STARTED,
+			clock: new Clock(),
+			resources: new Set()
 		};
 
-		this._psychoJS.logger.debug('downloading resources for experiment: ' + this._psychoJS.config.experiment.name);
-
-		// we use an anonymous async function here since downloadResources is non-blocking (asynchronous)
-		// but we want to run the asynchronous _listResources and _downloadResources in sequence
 		const self = this;
-		const newResources = new Map();
-		let download = async () =>
+		return () =>
 		{
-			try
+			const t = self._waitForDownloadComponent.clock.getTime();
+
+			// start the component:
+			if (t >= 0.0 && self._waitForDownloadComponent.status === PsychoJS.Status.NOT_STARTED)
 			{
-				if (self._psychoJS.config.environment === ExperimentHandler.Environment.SERVER)
+				self._waitForDownloadComponent.tStart = t;
+				self._waitForDownloadComponent.status = PsychoJS.Status.STARTED;
+
+				// if resources is an empty array, we consider all registered resources:
+				if (resources.length === 0)
 				{
-					// no resources specified, we register them all:
-					if (resources.length === 0)
+					for (const [name, {status, path, data}] of this._resources)
 					{
-						// list the resources from the resources directory of the experiment on the server:
-						const serverResponse = await self._listResources();
-						for (const name of serverResponse.resources)
-						{
-							self._resources.set(name, {path: serverResponse.resourceDirectory + '/' + name});
-						}
-					}
-					else
-					{
-						// only registered the specified resources:
-						for (const {name, path} of resources)
-						{
-							self._resources.set(name, {path});
-							newResources.set(name, {path});
-						}
-					}
-				}
-				else
-				{
-					// register the specified resources:
-					for (const {name, path} of resources)
-					{
-						self._resources.set(name, {path});
-						newResources.set(name, {path});
+						resources.append({ name, path });
 					}
 				}
 
-				self._nbResources = self._resources.size;
-				for (const name of self._resources.keys())
+				// only download those resources not already downloaded or downloading:
+				const resourcesToDownload = new Set();
+				for (let {name, path} of resources)
 				{
-					this._psychoJS.logger.debug('resource:', name, self._resources.get(name).path);
+					// to deal with potential CORS issues, we use the pavlovia.org proxy for resources
+					// not hosted on pavlovia.org:
+					if ( (path.toLowerCase().indexOf('www.') === 0 ||
+						path.toLowerCase().indexOf('http:') === 0 ||
+						path.toLowerCase().indexOf('https:') === 0) &&
+						(path.indexOf('pavlovia.org') === -1) )
+					{
+						path = 'https://devlovia.org/api/v2/proxy/' + path;
+					}
+
+					const pathStatusData = this._resources.get(name);
+
+					// the resource has not been registered yet:
+					if (typeof pathStatusData === 'undefined')
+					{
+						self._resources.set(name, {
+							status: ServerManager.ResourceStatus.REGISTERED,
+							path,
+							data: undefined
+						});
+						self._waitForDownloadComponent.resources.add(name);
+						resourcesToDownload.add(name);
+						self._psychoJS.logger.debug('registered resource:', name, path);
+					}
+
+					// the resource has been registered but is not downloaded yet:
+					else if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED)
+						// else if (typeof pathStatusData.data === 'undefined')
+					{
+						self._waitForDownloadComponent.resources.add(name);
+					}
+
 				}
 
-				self.emit(ServerManager.Event.RESOURCE, {
-					message: ServerManager.Event.RESOURCES_REGISTERED,
-					count: self._nbResources
-				});
-
-				// download the registered resources:
-				await self._downloadRegisteredResources(newResources);
+				// start the download:
+				self._downloadResources(resourcesToDownload);
 			}
-			catch (error)
+
+			// check whether all resources have been downloaded:
+			for (const name of self._waitForDownloadComponent.resources)
 			{
-				console.log('error', error);
-				// throw { ...response, error: error };
-				throw Object.assign(response, {error});
+				const pathStatusData = this._resources.get(name);
+
+				// the resource has not been downloaded yet: loop this component
+				if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED)
+					// if (typeof pathStatusData.data === 'undefined')
+				{
+					return Scheduler.Event.FLIP_REPEAT;
+				}
 			}
+
+			// all resources have been downloaded: move to the next component:
+			self._waitForDownloadComponent.status = PsychoJS.Status.FINISHED;
+			return Scheduler.Event.NEXT;
 		};
 
-		download();
 	}
 
 
@@ -470,7 +652,7 @@ export class ServerManager extends PsychObject
 	 * @property {Object.<string, *>} [error] an error message if we could not upload the data
 	 */
 	/**
-	 * Asynchronously upload experiment data to the remote PsychoJS manager.
+	 * Asynchronously upload experiment data to the pavlovia server.
 	 *
 	 * @name module:core.ServerManager#uploadData
 	 * @function
@@ -535,8 +717,9 @@ export class ServerManager extends PsychObject
 	}
 
 
+
 	/**
-	 * Asynchronously upload experiment logs to the remote PsychoJS manager.
+	 * Asynchronously upload experiment logs to the pavlovia server.
 	 *
 	 * @name module:core.ServerManager#uploadLog
 	 * @function
@@ -595,6 +778,84 @@ export class ServerManager extends PsychObject
 	}
 
 
+
+	/**
+	 * Asynchronously upload audio data to the pavlovia server.
+	 *
+	 * @name module:core.ServerManager#uploadAudio
+	 * @function
+	 * @public
+	 * @param {Blob} audioBlob - the audio blob to be uploaded
+	 * @param {string} tag - additional tag
+	 * @returns {Promise<ServerManager.UploadDataPromise>} the response
+	 */
+	async uploadAudio(audioBlob, tag)
+	{
+		const response = {
+			origin: 'ServerManager.uploadAudio',
+			context: 'when uploading audio data for experiment: ' + this._psychoJS.config.experiment.fullpath
+		};
+
+		try
+		{
+			if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER ||
+				this._psychoJS.config.experiment.status !== 'RUNNING' ||
+				this._psychoJS._serverMsg.has('__pilotToken'))
+			{
+				throw 'audio recordings can only be uploaded to the server for experiments running on the server';
+			}
+
+			this._psychoJS.logger.debug('uploading audio data for experiment: ' + this._psychoJS.config.experiment.fullpath);
+			this.setStatus(ServerManager.Status.BUSY);
+
+			// prepare the request:
+			const info = this.psychoJS.experiment.extraInfo;
+			const participant = ((typeof info.participant === 'string' && info.participant.length > 0) ? info.participant : 'PARTICIPANT');
+			const experimentName = (typeof info.expName !== 'undefined') ? info.expName : this.psychoJS.config.experiment.name;
+			const datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr());
+			const filename = participant + '_' + experimentName + '_' + datetime + '_' + tag;
+
+			const formData = new FormData();
+			formData.append('audio', audioBlob, filename);
+
+			const url = this._psychoJS.config.pavlovia.URL +
+				'/api/v2/experiments/' + this._psychoJS.config.gitlab.projectId +
+				'/sessions/' + this._psychoJS.config.session.token +
+				'/audio';
+
+			// query the pavlovia server:
+			const response = await fetch(url, {
+				method: 'POST',
+				mode: 'cors', // no-cors, *cors, same-origin
+				cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
+				credentials: 'same-origin', // include, *same-origin, omit
+				redirect: 'follow', // manual, *follow, error
+				referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
+				body: formData
+			});
+			const jsonResponse = await response.json();
+
+			// deal with server errors:
+			if (!response.ok)
+			{
+				throw jsonResponse;
+			}
+
+			this.setStatus(ServerManager.Status.READY);
+			return jsonResponse;
+		}
+		catch (error)
+		{
+			this.setStatus(ServerManager.Status.ERROR);
+			console.error(error);
+
+			throw {...response, error};
+		}
+
+	}
+
+
+
 	/**
 	 * List the resources available to the experiment.
 
@@ -664,62 +925,76 @@ export class ServerManager extends PsychObject
 	}
 
 
+
 	/**
-	 * Download the resources previously registered.
+	 * Download the specified resources.
 	 *
 	 * <p>Note: we use the [preloadjs library]{@link https://www.createjs.com/preloadjs}.</p>
 	 *
-	 * @name module:core.ServerManager#_downloadRegisteredResources
+	 * @name module:core.ServerManager#_downloadResources
 	 * @function
-	 * @private
+	 * @protected
+	 * @param {Set} resources - a set of names of previously registered resources
 	 */
-	_downloadRegisteredResources(resources = new Map())
+	_downloadResources(resources)
 	{
 		const response = {
 			origin: 'ServerManager._downloadResources',
-			context: 'when downloading the resources for experiment: ' + this._psychoJS.config.experiment.name
+			context: 'when downloading resources for experiment: ' + this._psychoJS.config.experiment.name
 		};
 
-		this._psychoJS.logger.debug('downloading the registered resources for experiment: ' + this._psychoJS.config.experiment.name);
+		this._psychoJS.logger.debug('downloading resources for experiment: ' + this._psychoJS.config.experiment.name);
 
 		this.setStatus(ServerManager.Status.BUSY);
+		this.emit(ServerManager.Event.RESOURCE, {
+			message: ServerManager.Event.DOWNLOADING_RESOURCES,
+			count: resources.size
+		});
+
 		this._nbLoadedResources = 0;
 
 
 		// (*) set-up preload.js:
-		this._resourceQueue = new createjs.LoadQueue(true); //, this._psychoJS.config.experiment.resourceDirectory);
+		this._resourceQueue = new createjs.LoadQueue(true, '', true);
 
 		const self = this;
 
-		const filesToDownload = resources.size ? resources : this._resources;
-
+		// the loading of a specific resource has started:
 		this._resourceQueue.addEventListener("filestart", event =>
 		{
+			const pathStatusData = self._resources.get(event.item.id);
+			pathStatusData.status =  ServerManager.ResourceStatus.DOWNLOADING;
+
 			self.emit(ServerManager.Event.RESOURCE, {
 				message: ServerManager.Event.DOWNLOADING_RESOURCE,
 				resource: event.item.id
 			});
 		});
 
+		// the loading of a specific resource has completed:
 		this._resourceQueue.addEventListener("fileload", event =>
 		{
-			++self._nbLoadedResources;
-			let path_data = self._resources.get(event.item.id);
-			path_data.data = event.result;
+			const pathStatusData = self._resources.get(event.item.id);
+			pathStatusData.data = event.result;
+			pathStatusData.status =  ServerManager.ResourceStatus.DOWNLOADED;
+
+			++ self._nbLoadedResources;
 			self.emit(ServerManager.Event.RESOURCE, {
 				message: ServerManager.Event.RESOURCE_DOWNLOADED,
 				resource: event.item.id
 			});
 		});
 
-		// loading completed:
+		// the loading of all given resources completed:
 		this._resourceQueue.addEventListener("complete", event =>
 		{
 			self._resourceQueue.close();
-			if (self._nbLoadedResources === filesToDownload.size)
+			if (self._nbLoadedResources === resources.size)
 			{
 				self.setStatus(ServerManager.Status.READY);
-				self.emit(ServerManager.Event.RESOURCE, {message: ServerManager.Event.DOWNLOAD_COMPLETED});
+				self.emit(ServerManager.Event.RESOURCE, {
+					message: ServerManager.Event.DOWNLOAD_COMPLETED
+				});
 			}
 		});
 
@@ -727,16 +1002,43 @@ export class ServerManager extends PsychObject
 		this._resourceQueue.addEventListener("error", event =>
 		{
 			self.setStatus(ServerManager.Status.ERROR);
-			const resourceId = (typeof event.data !== 'undefined') ? event.data.id : 'UNKNOWN RESOURCE';
-			// throw { ...response, error: 'unable to download resource: ' + resourceId + ' (' + event.title + ')' };
-			throw Object.assign(response, {error: 'unable to download resource: ' + resourceId + ' (' + event.title + ')'});
+			if (typeof event.item !== 'undefined')
+			{
+				const pathStatusData = self._resources.get(event.item.id);
+				pathStatusData.status =  ServerManager.ResourceStatus.ERROR;
+				throw Object.assign(response, {
+					error: 'unable to download resource: ' + event.item.id + ' (' + event.title + ')'
+				});
+			}
+			else
+			{
+				console.error(event);
+
+				if (event.title === 'FILE_LOAD_ERROR' && typeof event.data !== 'undefined')
+				{
+					const id = event.data.id;
+					const title = event.data.src;
+
+					throw Object.assign(response, {
+						error: 'unable to download resource: ' + id + ' (' + title + ')'
+					});
+				}
+
+				else
+				{
+					throw Object.assign(response, {
+						error: 'unspecified download error'
+					});
+				}
+
+			}
 		});
 
 
 		// (*) dispatch resources to preload.js or howler.js based on extension:
 		let manifest = [];
-		let soundResources = [];
-		for (const [name, path_data] of filesToDownload)
+		const soundResources = new Set();
+		for (const name of resources)
 		{
 			const nameParts = name.toLowerCase().split('.');
 			const extension = (nameParts.length > 1) ? nameParts.pop() : undefined;
@@ -747,10 +1049,25 @@ export class ServerManager extends PsychObject
 				this.psychoJS.logger.warn(`"${name}" does not appear to have an extension, which may negatively impact its loading. We highly recommend you add an extension.`);
 			}
 
+			const pathStatusData = this._resources.get(name);
+			if (typeof pathStatusData === 'undefined')
+			{
+				throw Object.assign(response, {error: name + ' has not been previously registered'});
+			}
+			if (pathStatusData.status !== ServerManager.ResourceStatus.REGISTERED)
+			{
+				throw Object.assign(response, {error: name + ' is already downloaded or is currently already downloading'});
+			}
+
 			// preload.js with forced binary for xls and xlsx:
 			if (['csv', 'odp', 'xls', 'xlsx'].indexOf(extension) > -1)
 			{
-				manifest.push({id: name, src: path_data.path, type: createjs.Types.BINARY});
+				manifest.push(new createjs.LoadItem().set({
+					id: name,
+					src: pathStatusData.path,
+					type: createjs.Types.BINARY,
+					crossOrigin: 'Anonymous'
+				}));
 			}/* ascii .csv are adequately handled in binary format
 			// forced text for .csv:
 			else if (['csv'].indexOf(resourceExtension) > -1)
@@ -760,7 +1077,7 @@ export class ServerManager extends PsychObject
 			// sound files are loaded through howler.js:
 			else if (['mp3', 'mpeg', 'opus', 'ogg', 'oga', 'wav', 'aac', 'caf', 'm4a', 'weba', 'dolby', 'flac'].indexOf(extension) > -1)
 			{
-				soundResources.push(name);
+				soundResources.add(name);
 
 				if (extension === 'wav')
 				{
@@ -771,7 +1088,11 @@ export class ServerManager extends PsychObject
 			// preload.js for the other extensions (download type decided by preload.js):
 			else
 			{
-				manifest.push({id: name, src: path_data.path});
+				manifest.push(new createjs.LoadItem().set({
+					id: name,
+					src: pathStatusData.path,
+					crossOrigin: 'Anonymous'
+				}));
 			}
 		}
 
@@ -783,10 +1104,11 @@ export class ServerManager extends PsychObject
 		}
 		else
 		{
-			if (this._nbLoadedResources === filesToDownload.size)
+			if (this._nbLoadedResources === resources.size)
 			{
 				this.setStatus(ServerManager.Status.READY);
-				this.emit(ServerManager.Event.RESOURCE, {message: ServerManager.Event.DOWNLOAD_COMPLETED});
+				this.emit(ServerManager.Event.RESOURCE, {
+					message: ServerManager.Event.DOWNLOAD_COMPLETED});
 			}
 		}
 
@@ -794,33 +1116,37 @@ export class ServerManager extends PsychObject
 		// (*) prepare and start loading sound resources:
 		for (const name of soundResources)
 		{
-			self.emit(ServerManager.Event.RESOURCE, {
+			const pathStatusData = this._resources.get(name);
+			pathStatusData.status =  ServerManager.ResourceStatus.DOWNLOADING;
+			this.emit(ServerManager.Event.RESOURCE, {
 				message: ServerManager.Event.DOWNLOADING_RESOURCE,
 				resource: name
 			});
-			const path_data = self._resources.get(name);
 			const howl = new Howl({
-				src: path_data.path,
+				src: pathStatusData.path,
 				preload: false,
 				autoplay: false
 			});
 
 			howl.on('load', (event) =>
 			{
-				++self._nbLoadedResources;
-				path_data.data = howl;
-				// self._resources.set(resource.name, howl);
+				++ self._nbLoadedResources;
+				pathStatusData.data = howl;
+
+				pathStatusData.status =  ServerManager.ResourceStatus.DOWNLOADED;
 				self.emit(ServerManager.Event.RESOURCE, {
 					message: ServerManager.Event.RESOURCE_DOWNLOADED,
 					resource: name
 				});
 
-				if (self._nbLoadedResources === filesToDownload.size)
+				if (self._nbLoadedResources === resources.size)
 				{
 					self.setStatus(ServerManager.Status.READY);
-					self.emit(ServerManager.Event.RESOURCE, {message: ServerManager.Event.DOWNLOAD_COMPLETED});
+					self.emit(ServerManager.Event.RESOURCE, {
+						message: ServerManager.Event.DOWNLOAD_COMPLETED});
 				}
 			});
+
 			howl.on('loaderror', (id, error) =>
 			{
 				// throw { ...response, error: 'unable to download resource: ' + name + ' (' + util.toString(error) + ')' };
@@ -829,6 +1155,7 @@ export class ServerManager extends PsychObject
 
 			howl.load();
 		}
+
 	}
 
 }
@@ -849,22 +1176,26 @@ ServerManager.Event = {
 	 * Event type: resource event
 	 */
 	RESOURCE: Symbol.for('RESOURCE'),
+
 	/**
-	 * Event: resources all registered
+	 * Event: resources have started to download
 	 */
-	RESOURCES_REGISTERED: Symbol.for('RESOURCES_REGISTERED'),
+	DOWNLOADING_RESOURCES: Symbol.for('DOWNLOADING_RESOURCES'),
+
 	/**
-	 * Event: resource download has started
+	 * Event: a specific resource download has started
 	 */
 	DOWNLOADING_RESOURCE: Symbol.for('DOWNLOADING_RESOURCE'),
+
 	/**
-	 * Event: resource has been downloaded
+	 * Event: a specific resource has been downloaded
 	 */
 	RESOURCE_DOWNLOADED: Symbol.for('RESOURCE_DOWNLOADED'),
+
 	/**
-	 * Event: resources all downloaded
+	 * Event: resources have all downloaded
 	 */
-	DOWNLOAD_COMPLETED: Symbol.for('DOWNLOAD_COMPLETED'),
+	DOWNLOADS_COMPLETED: Symbol.for('DOWNLOAD_COMPLETED'),
 
 	/**
 	 * Event type: status event
@@ -897,6 +1228,37 @@ ServerManager.Status = {
 	 */
 	ERROR: Symbol.for('ERROR')
 };
+
+
+/**
+ * Resource status
+ *
+ * @name module:core.ServerManager#ResourceStatus
+ * @enum {Symbol}
+ * @readonly
+ * @public
+ */
+ServerManager.ResourceStatus = {
+	/**
+	 * The resource has been registered.
+	 */
+	REGISTERED: Symbol.for('REGISTERED'),
+
+	/**
+	 * The resource is currently downloading.
+	 */
+	DOWNLOADING: Symbol.for('DOWNLOADING'),
+
+	/**
+	 * The resource has been downloaded.
+	 */
+	DOWNLOADED: Symbol.for('DOWNLOADED'),
+
+	/**
+	 * There was an error during downloading, or the resource is in an unknown state.
+	 */
+	ERROR: Symbol.for('ERROR'),
+};
 
@@ -907,13 +1269,13 @@ ServerManager.Status = {
diff --git a/docs/core_Window.js.html b/docs/core_Window.js.html index 4a4553e..aeff689 100644 --- a/docs/core_Window.js.html +++ b/docs/core_Window.js.html @@ -35,9 +35,9 @@ * @license Distributed under the terms of the MIT License */ -import {Color} from '../util'; -import {PsychObject} from '../util'; -import {MonotonicClock} from '../util'; +import {Color} from '../util/Color'; +import {PsychObject} from '../util/PsychObject'; +import {MonotonicClock} from '../util/Clock'; import {Logger} from "./Logger"; /** @@ -551,13 +551,13 @@ export class Window extends PsychObject
diff --git a/docs/core_WindowMixin.js.html b/docs/core_WindowMixin.js.html index a77b2d9..a7935d5 100644 --- a/docs/core_WindowMixin.js.html +++ b/docs/core_WindowMixin.js.html @@ -229,13 +229,13 @@ export let WindowMixin = (superclass) => class extends superclass
diff --git a/docs/data_ExperimentHandler.js.html b/docs/data_ExperimentHandler.js.html index 9b47e06..ff125da 100644 --- a/docs/data_ExperimentHandler.js.html +++ b/docs/data_ExperimentHandler.js.html @@ -487,13 +487,13 @@ ExperimentHandler.Environment = {
diff --git a/docs/data_TrialHandler.js.html b/docs/data_TrialHandler.js.html index 3747849..00431b7 100644 --- a/docs/data_TrialHandler.js.html +++ b/docs/data_TrialHandler.js.html @@ -239,7 +239,6 @@ export class TrialHandler extends PsychObject * @property {number} ran - whether or not the trial ran * @property {number} finished - whether or not the trials finished */ - /** * Get a snapshot of the current internal state of the trial handler (e.g. current trial number, * number of trial remaining). @@ -461,7 +460,7 @@ export class TrialHandler extends PsychObject if (['csv', 'odp', 'xls', 'xlsx'].indexOf(resourceExtension) > -1) { // (*) read conditions from resource: - const resourceValue = serverManager.getResource(resourceName); + const resourceValue = serverManager.getResource(resourceName, true); // Conditionally use a `TextDecoder` to reprocess .csv type input, // which is then read in as a string @@ -727,13 +726,13 @@ TrialHandler.Method = {
diff --git a/docs/index.html b/docs/index.html index e1a94ed..815d98e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -42,69 +42,6 @@ -
-

PsychoJS

-

PsychoJS is a JavaScript library that makes it possible to run neuroscience, psychology, and psychophysics experiments in a browser. It is the online counterpart of the PsychoPy Python library. -It is also a git submodule: psychopy/psychojs

-

Motivation

-

Many studies in behavioural sciences (e.g. psychology, neuroscience, linguistics or mental health) use computers to present stimuli and record responses in a precise manner. These studies are still typically conducted on small numbers of people in laboratory environments equipped with dedicated hardware.

-

With high-speed broadband, improved web technologies and smart devices everywhere, studies can now go online without sacrificing too much temporal precision. This is a “game changer”. Data can be collected on larger, more varied, international populations. We can study people in environments they do not find intimidating. Experiments can be run multiple times per day, without data collection becoming impractical.

-

The idea behind PsychoJS is to make PsychoPy experiments available online, from a web page, so participants can run them on any device equipped with a web browser such as desktops, laptops, or tablets. In some circumstance, they can even use their phone!

-

Getting Started

-

Running PsychoPy experiments online requires the generation of an index.html file and of a javascript file that contains the code describing the experiment. Those files need to be hosted on a web server to which participants will point their browser in order to run the experiment. The server will also need to host the PsychoJS library.

-

PsychoPy Builder

-

Starting with PsychoPy version 3.0, PsychoPy Builder can automatically generate the javascript and html files. Many of the existing Builder experiments should "just work", subject to the Components being currently supported by PsychoJS (see below).

-

JavaScript Code

-

We built the PsychoJS library to make the JavaScript experiment files look and behave in very much the same way as to the Builder-generated Python files. PsychoJS offers classes such as Window and ImageStim, with very similar attributes to their Python equivalents. Experiment designers familiar with the PsychoPy library should feel at home with PsychoJS, and can expect the same level of control they have with PsychoPy, from the structure of the trials/loops all the way down to frame-by-frame updates.

-

There are however notable differences between the PsychoJS and PsychoPy libraries, most of which have to do with the way a web browser interprets and runs JavaScript, deals with resources (such as images, sound or videos), or render stimuli. To manage those web-specific aspect, PsychoJS introduces the concept of Scheduler. As the name indicate, Scheduler's offer a way to organise various PsychoJS along a timeline, such as downloading resources, running a loop, checking for keyboard input, saving experiment results, etc. As an illustration, a Flow in PsychoPy can be conceptualised as a Schedule, with various tasks on it. Some of those tasks, such as trial loops, can also schedule further events (i.e. the individual trials to be run).

-

Under the hood PsychoJS relies on PixiJs to present stimuli and collect responses. PixiJs is a multi-platform, accelerated, 2-D renderer, that runs in most modern browsers. It uses WebGL wherever possible and silently falls back to HTML5 canvas where not. WebGL directly addresses the graphic card, thereby considerably improving the rendering performance.

-

Hosting Experiments

-

A convenient way to make experiment available to participants is to host them on pavlovia.org, an open-science server under active development. PsychoPy Builder offers the possibility of uploading the experiment directly to pavlovia.org.

-

Which PsychoPy Components are supported by PsychoJS?

-

PsychoJS currently supports the following Components:

-

Stimuli:

-
    -
  • Form
  • -
  • Image
  • -
  • Rect
  • -
  • Shape (Polygon)
  • -
  • Slider
  • -
  • Sound (tones and tracks)
  • -
  • Text
  • -
  • TextBox
  • -
  • Video
  • -
-

Events:

-
    -
  • Keyboard
  • -
  • Mouse
  • -
-

We are constantly adding new Components and are regularly updating this list.

-

API

-

The full documentation of the PsychoJS API is here.

-

Maintainers

-

Alain Pitiot - @apitiot

-

Contributors

-

The PsychoJS library was initially written by Ilixa with support from the Wellcome Trust. -It is now a collaborative effort, supported by the Chan Zuckerberg Initiative (2020-2021) and Open Science Tools (2020-):

- -

The PsychoPy Builder's javascript code generator is built and maintained by the creators of PsychoPy at the University of Nottingham, with support from the Wellcome Trust (2017-2019), from the Chan Zuckerberg Initiative (2020-2021), and from Open Science Tools (2020-):

- -

License

-

This project is licensed under the MIT License - see the LICENSE.md file for details.

-
- @@ -113,13 +50,13 @@ It is now a collaborative effort, supported by the Home

Modules

Classes

Interfaces

Mixins

+

Home

Modules

Classes

Interfaces

Mixins


diff --git a/docs/module-core.BuilderKeyResponse.html b/docs/module-core.BuilderKeyResponse.html index b9f382e..1eafc6f 100644 --- a/docs/module-core.BuilderKeyResponse.html +++ b/docs/module-core.BuilderKeyResponse.html @@ -254,13 +254,13 @@
diff --git a/docs/module-core.EventManager.html b/docs/module-core.EventManager.html index bf1ac8a..55e4227 100644 --- a/docs/module-core.EventManager.html +++ b/docs/module-core.EventManager.html @@ -1721,13 +1721,13 @@
diff --git a/docs/module-core.GUI.html b/docs/module-core.GUI.html index a7b340c..8ea2548 100644 --- a/docs/module-core.GUI.html +++ b/docs/module-core.GUI.html @@ -247,7 +247,7 @@
Source:
@@ -319,7 +319,7 @@
Source:
@@ -1011,13 +1011,13 @@ the dictionary remains unchanged.


diff --git a/docs/module-core.KeyPress.html b/docs/module-core.KeyPress.html index ffa5f63..ef8a904 100644 --- a/docs/module-core.KeyPress.html +++ b/docs/module-core.KeyPress.html @@ -250,13 +250,13 @@
diff --git a/docs/module-core.Keyboard.html b/docs/module-core.Keyboard.html index fbe87dd..81fc27d 100644 --- a/docs/module-core.Keyboard.html +++ b/docs/module-core.Keyboard.html @@ -1371,13 +1371,13 @@ waitRelease = false, key presses without a corresponding key release will have a
diff --git a/docs/module-core.Logger.html b/docs/module-core.Logger.html index 738d29e..8d9aef5 100644 --- a/docs/module-core.Logger.html +++ b/docs/module-core.Logger.html @@ -795,13 +795,13 @@ See https://github.com/nodeca/pako for details.


diff --git a/docs/module-core.MinimalStim.html b/docs/module-core.MinimalStim.html index 06bc3d4..283de5c 100644 --- a/docs/module-core.MinimalStim.html +++ b/docs/module-core.MinimalStim.html @@ -1083,13 +1083,13 @@
diff --git a/docs/module-core.Mouse.html b/docs/module-core.Mouse.html index 257efc7..cc243be 100644 --- a/docs/module-core.Mouse.html +++ b/docs/module-core.Mouse.html @@ -1733,13 +1733,13 @@ call to getPos
diff --git a/docs/module-core.PsychoJS.html b/docs/module-core.PsychoJS.html index d2ac380..270532d 100644 --- a/docs/module-core.PsychoJS.html +++ b/docs/module-core.PsychoJS.html @@ -251,7 +251,7 @@
Source:
@@ -460,6 +460,29 @@ + + + + + + + + PAUSED + + + + + +Symbol + + + + + + + + + @@ -506,6 +529,29 @@ + + + + + + + + ERROR + + + + + +Symbol + + + + + + + + + @@ -545,7 +591,7 @@
Source:
@@ -607,7 +653,7 @@
Source:
@@ -687,7 +733,7 @@
Source:
@@ -847,7 +893,7 @@
Source:
@@ -937,171 +983,7 @@
Source:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

(async) downloadResources(resourcesopt)

- - - - - - -
- Synchronously download resources for the experiment. - -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
resources - - -Array.<{name: string, path: string}> - - - - - - <optional>
- - - - - -
- - [] - - the list of resources
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
@@ -1189,7 +1071,7 @@
Source:
@@ -1352,7 +1234,7 @@ the top level variable (e.g. window) as well.
Source:
@@ -1719,7 +1601,7 @@ before flipping
Source:
@@ -1995,7 +1877,7 @@ that he or she needs to wait for a bit.

Source:
@@ -2145,7 +2027,7 @@ that he or she needs to wait for a bit.

Source:
@@ -2328,7 +2210,7 @@ that he or she needs to wait for a bit.

Source:
@@ -2488,7 +2370,7 @@ that he or she needs to wait for a bit.

Source:
@@ -2533,6 +2415,17 @@ that he or she needs to wait for a bit.

Start the experiment. + +

The resources are specified in the following fashion: +

    +
  • For an experiment running locally: the root directory for the specified resources is that of index.html + unless they are prepended with a protocol, such as http:// or https://.
  • +
  • For an experiment running on the server: if no resources are specified, all files in the resources directory + of the experiment are downloaded, otherwise we only download the specified resources. All resources are assumed + local to index.html unless they are prepended with a protocol.
  • +
  • If resources is null: we do not download any resources.
  • +
+

@@ -2825,7 +2718,174 @@ that he or she needs to wait for a bit.

Source:
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

waitForResources(resourcesopt)

+ + + + + + +
+ Block the experiment until the specified resources have been downloaded. + +

Note: only those resources that have not already been downloaded at that point are +considered.

+ +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
resources + + +Array.<{name: string, path: string}> + + + + + + <optional>
+ + + + + +
+ + [] + + the list of resources
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -2871,13 +2931,13 @@ that he or she needs to wait for a bit.


- Documentation generated by JSDoc 3.6.6 on Fri Apr 09 2021 14:03:40 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.6 on Fri May 28 2021 10:49:17 GMT+0200 (Central European Summer Time)
diff --git a/docs/module-core.ServerManager.html b/docs/module-core.ServerManager.html index 920c180..37f7bf5 100644 --- a/docs/module-core.ServerManager.html +++ b/docs/module-core.ServerManager.html @@ -50,9 +50,8 @@
-

This manager handles all communications between the experiment running in the participant's browser and the remote PsychoJS manager running on the pavlovia.org server, in an asynchronous manner.

-

It is responsible for reading the configuration file of an experiment, for opening and closing a session, for listing and downloading resources, and for uploading results and log.

-

Note: The Server Manager uses Promises to deal with asynchronicity, is mostly called by PsychoJS, and is not exposed to the experiment code.

+

This manager handles all communications between the experiment running in the participant's browser and the pavlovia.org server, in an asynchronous manner.

+

It is responsible for reading the configuration file of an experiment, for opening and closing a session, for listing and downloading resources, and for uploading results, logs, and audio recordings.

@@ -366,7 +365,79 @@
Source:
+ + + + + + + +
+ + + + + + + + +

(readonly) ResourceStatus :Symbol

+ + + + +
+ Resource status +
+ + + +
Type:
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -510,7 +581,7 @@
Source:
@@ -532,6 +603,145 @@

Methods

+ + + + + + +

(protected) _downloadResources(resources)

+ + + + + + +
+ Download the specified resources. + +

Note: we use the preloadjs library.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
resources + + +Set + + + + a set of names of previously registered resources
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -698,7 +908,7 @@
Source:
@@ -753,171 +963,6 @@ - - - - - - -

downloadResources(resourcesopt)

- - - - - - -
- Asynchronously download resources for the experiment and register them with the server manager. - -
    -
  • For an experiment running locally: the root directory for the specified resources is that of index.html - unless they are prepended with a protocol, such as http:// or https://.
  • -
  • For an experiment running on the server: if no resources are specified, all files in the resources directory - of the experiment are downloaded, otherwise we only download the specified resources. All resources are assumed - local to index.html unless they are prepended with a protocol.
  • -
-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
resources - - -Array.<{name: string, path: string}> - - - - - - <optional>
- - - - - -
- - [] - - the list of resources
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - @@ -1025,7 +1070,7 @@
Source:
@@ -1083,7 +1128,7 @@ -

getResource(name) → {Object}

+

getResource(name, errorIfNotDownloadedopt) → {Object}

@@ -1102,6 +1147,251 @@ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +string + + + + + + + + + + + + name of the requested resource
errorIfNotDownloaded + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to throw an exception if the +resource status is not DOWNLOADED
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+ exception if no resource with that name has previously been registered +
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+ value of the resource, or undefined if the resource has been registered +but not downloaded yet. +
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + + + + +

getResourceStatus(name) → {core.ServerManager.ResourceStatus}

+ + + + + + +
+ Get the status of a resource. +
+ + + + + + + + +
Parameters:
@@ -1184,7 +1474,7 @@
Source:
@@ -1242,7 +1532,7 @@
- value of the resource + status of the resource
@@ -1253,7 +1543,7 @@
-Object +core.ServerManager.ResourceStatus
@@ -1323,7 +1613,7 @@
Source:
@@ -1375,6 +1665,173 @@ + + + + + + +

prepareResources(resourcesopt)

+ + + + + + +
+ Prepare resources for the experiment: register them with the server manager and possibly +start downloading them right away. + +
    +
  • For an experiment running locally: the root directory for the specified resources is that of index.html + unless they are prepended with a protocol, such as http:// or https://.
  • +
  • For an experiment running on the server: if no resources are specified, all files in the resources directory + of the experiment are downloaded, otherwise we only download the specified resources. All resources are assumed + local to index.html unless they are prepended with a protocol.
  • +
  • If resources is null, then we do not download any resources
  • +
+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
resources + + +Array.<({name: string, path: string, download: boolean}|Symbol)> + + + + + + <optional>
+ + + + + +
+ + [] + + the list of resources
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -1433,7 +1890,7 @@
Source:
@@ -1543,7 +2000,7 @@
Source:
@@ -1573,6 +2030,188 @@ + + + + + + +

uploadAudio(audioBlob, tag) → {Promise.<ServerManager.UploadDataPromise>}

+ + + + + + +
+ Asynchronously upload audio data to the pavlovia server. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
audioBlob + + +Blob + + + + the audio blob to be uploaded
tag + + +string + + + + additional tag
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ the response +
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.UploadDataPromise> + + +
+
+ + + + + + + @@ -1587,7 +2226,7 @@
- Asynchronously upload experiment data to the remote PsychoJS manager. + Asynchronously upload experiment data to the pavlovia server.
@@ -1770,7 +2409,7 @@
Source:
@@ -1836,7 +2475,7 @@
- Asynchronously upload experiment logs to the remote PsychoJS manager. + Asynchronously upload experiment logs to the pavlovia server.
@@ -1984,7 +2623,7 @@
Source:
@@ -2036,6 +2675,163 @@ + + + + + + +

waitForResources(resourcesopt)

+ + + + + + +
+ Block the experiment until the specified resources have been downloaded. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
resources + + +Array.<{name: string, path: string}> + + + + + + <optional>
+ + + + + +
+ + [] + + the list of resources
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -2052,13 +2848,13 @@
- Documentation generated by JSDoc 3.6.6 on Fri Apr 09 2021 14:03:40 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.6 on Fri May 28 2021 10:49:17 GMT+0200 (Central European Summer Time)
diff --git a/docs/module-core.Window.html b/docs/module-core.Window.html index 6ab9362..8643cd1 100644 --- a/docs/module-core.Window.html +++ b/docs/module-core.Window.html @@ -2039,13 +2039,13 @@ Window.
- Documentation generated by JSDoc 3.6.6 on Fri Apr 09 2021 14:03:40 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.6 on Fri May 28 2021 10:49:17 GMT+0200 (Central European Summer Time)
diff --git a/docs/module-core.WindowMixin.html b/docs/module-core.WindowMixin.html index f09c938..7549aff 100644 --- a/docs/module-core.WindowMixin.html +++ b/docs/module-core.WindowMixin.html @@ -817,13 +817,13 @@
- Documentation generated by JSDoc 3.6.6 on Fri Apr 09 2021 14:03:40 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.6 on Fri May 28 2021 10:49:17 GMT+0200 (Central European Summer Time)
diff --git a/docs/module-core.html b/docs/module-core.html index e1b951b..d0deb76 100644 --- a/docs/module-core.html +++ b/docs/module-core.html @@ -114,13 +114,13 @@
- Documentation generated by JSDoc 3.6.6 on Fri Apr 09 2021 14:03:40 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.6 on Fri May 28 2021 10:49:17 GMT+0200 (Central European Summer Time)
diff --git a/docs/module-data.ExperimentHandler.html b/docs/module-data.ExperimentHandler.html index 7d60665..b4aa38f 100644 --- a/docs/module-data.ExperimentHandler.html +++ b/docs/module-data.ExperimentHandler.html @@ -1381,13 +1381,13 @@ will be associated with the next trial.
- Documentation generated by JSDoc 3.6.6 on Fri Apr 09 2021 14:03:40 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.6 on Fri May 28 2021 10:49:17 GMT+0200 (Central European Summer Time)
diff --git a/docs/module-data.TrialHandler.html b/docs/module-data.TrialHandler.html index 5f6f866..c486d7a 100644 --- a/docs/module-data.TrialHandler.html +++ b/docs/module-data.TrialHandler.html @@ -672,7 +672,7 @@
Source:
@@ -734,7 +734,7 @@
Source:
@@ -977,7 +977,7 @@ It can be a single integer, an array of indices, or a string to be parsed, e.g.:
Source:
@@ -1168,7 +1168,7 @@ It can be a single integer, an array of indices, or a string to be parsed, e.g.:
Source:
@@ -1328,7 +1328,7 @@ It can be a single integer, an array of indices, or a string to be parsed, e.g.:
Source:
@@ -1551,7 +1551,7 @@ and consequently consider only the attributes of the first trial.

Source:
@@ -1661,7 +1661,7 @@ and consequently consider only the attributes of the first trial.

Source:
@@ -1841,7 +1841,7 @@ and consequently consider only the attributes of the first trial.

Source:
@@ -2023,7 +2023,7 @@ and consequently consider only the attributes of the first trial.

Source:
@@ -2140,7 +2140,7 @@ number of trial remaining).
Source:
@@ -2307,7 +2307,7 @@ number of trial remaining).
Source:
@@ -2420,7 +2420,7 @@ number of trial remaining).
Source:
@@ -2579,7 +2579,7 @@ number of trial remaining).
Source:
@@ -2721,13 +2721,13 @@ for (const thisTrial of handler) { console.log(thisTrial); }
- Documentation generated by JSDoc 3.6.6 on Fri Apr 09 2021 14:03:40 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.6 on Fri May 28 2021 10:49:17 GMT+0200 (Central European Summer Time)
diff --git a/docs/module-data.html b/docs/module-data.html index 21c1d18..2d5a69c 100644 --- a/docs/module-data.html +++ b/docs/module-data.html @@ -52,6 +52,9 @@
ExperimentHandler
+
Shelf
+
+
TrialHandler
@@ -411,13 +414,13 @@
diff --git a/docs/module-sound.Sound.html b/docs/module-sound.Sound.html index b6b4aef..e1446dd 100644 --- a/docs/module-sound.Sound.html +++ b/docs/module-sound.Sound.html @@ -2159,13 +2159,13 @@ Repeat calls to play may results in the sounds being played on top of each other
diff --git a/docs/module-sound.SoundPlayer.html b/docs/module-sound.SoundPlayer.html index 47d9d0c..5a9197f 100644 --- a/docs/module-sound.SoundPlayer.html +++ b/docs/module-sound.SoundPlayer.html @@ -1033,13 +1033,13 @@
diff --git a/docs/module-sound.TonePlayer.html b/docs/module-sound.TonePlayer.html index 3f00d6a..2ab04c2 100644 --- a/docs/module-sound.TonePlayer.html +++ b/docs/module-sound.TonePlayer.html @@ -1513,13 +1513,13 @@ we throw an exception.


diff --git a/docs/module-sound.TrackPlayer.html b/docs/module-sound.TrackPlayer.html index 87eb0d4..b452f0c 100644 --- a/docs/module-sound.TrackPlayer.html +++ b/docs/module-sound.TrackPlayer.html @@ -1607,13 +1607,13 @@
diff --git a/docs/module-sound.html b/docs/module-sound.html index 57395bc..b425b7d 100644 --- a/docs/module-sound.html +++ b/docs/module-sound.html @@ -49,6 +49,12 @@

Classes

+
AudioClip
+
+ +
Microphone
+
+
Sound
@@ -57,6 +63,12 @@
TrackPlayer
+ +
Transcriber
+
+ +
Transcript
+
@@ -90,13 +102,13 @@
diff --git a/docs/module-util.Clock.html b/docs/module-util.Clock.html index cf98427..e9dd0ee 100644 --- a/docs/module-util.Clock.html +++ b/docs/module-util.Clock.html @@ -480,13 +480,13 @@ smaller). As a consequence, getTime() may return a negative number.


diff --git a/docs/module-util.Color.html b/docs/module-util.Color.html index 7b44e52..8ba164e 100644 --- a/docs/module-util.Color.html +++ b/docs/module-util.Color.html @@ -1951,13 +1951,13 @@ static methods for converting colors from one space to another.


diff --git a/docs/module-util.ColorMixin.html b/docs/module-util.ColorMixin.html index ef7523b..e6883f1 100644 --- a/docs/module-util.ColorMixin.html +++ b/docs/module-util.ColorMixin.html @@ -672,13 +672,13 @@
diff --git a/docs/module-util.CountdownTimer.html b/docs/module-util.CountdownTimer.html index ffc1ace..a21b5ce 100644 --- a/docs/module-util.CountdownTimer.html +++ b/docs/module-util.CountdownTimer.html @@ -652,13 +652,13 @@ to newTime
diff --git a/docs/module-util.EventEmitter.html b/docs/module-util.EventEmitter.html index 9f701b7..e104681 100644 --- a/docs/module-util.EventEmitter.html +++ b/docs/module-util.EventEmitter.html @@ -994,13 +994,13 @@ observable.emit("change", { a: 1 });
diff --git a/docs/module-util.MixinBuilder.html b/docs/module-util.MixinBuilder.html index c3bfc53..1d72f66 100644 --- a/docs/module-util.MixinBuilder.html +++ b/docs/module-util.MixinBuilder.html @@ -215,13 +215,13 @@ class NewClass extends mix(BaseClass).with(Mixin1, Mixin2) { ... }
diff --git a/docs/module-util.MonotonicClock.html b/docs/module-util.MonotonicClock.html index 5024e0e..7257aa1 100644 --- a/docs/module-util.MonotonicClock.html +++ b/docs/module-util.MonotonicClock.html @@ -740,13 +740,13 @@
diff --git a/docs/module-util.PsychObject.html b/docs/module-util.PsychObject.html index 0e4dd5f..5bb8094 100644 --- a/docs/module-util.PsychObject.html +++ b/docs/module-util.PsychObject.html @@ -362,7 +362,7 @@ It is responsible for handling attributes.

-

(protected) _addAttribute(name, value, defaultValue, onChange)

+

(protected) _addAttribute(name, value, defaultValueopt, onChangeopt)

@@ -394,6 +394,8 @@ It is responsible for handling attributes.

Type + Attributes + @@ -419,6 +421,14 @@ It is responsible for handling attributes.

+ + + + + + + + @@ -442,6 +452,14 @@ It is responsible for handling attributes.

+ + + + + + + + @@ -465,6 +483,16 @@ It is responsible for handling attributes.

+ + + <optional>
+ + + + + + + @@ -488,6 +516,16 @@ It is responsible for handling attributes.

+ + + <optional>
+ + + + + + + @@ -1016,13 +1054,13 @@ was not previously set)
diff --git a/docs/module-util.Scheduler.html b/docs/module-util.Scheduler.html index 59395f4..43b88b4 100644 --- a/docs/module-util.Scheduler.html +++ b/docs/module-util.Scheduler.html @@ -945,13 +945,13 @@ task would be by calling scheduler.add(
diff --git a/docs/module-util.html b/docs/module-util.html index 78a6ec9..0aa7d11 100644 --- a/docs/module-util.html +++ b/docs/module-util.html @@ -353,6 +353,167 @@ https://stackoverflow.com/a/9851769

+

(static) extensionFromMimeType(mimeType) → {string}

+ + + + + + +
+ Return the file extension corresponding to an audio mime type. +If the provided mimeType is not a string (e.g. null, undefined, an array) +or unknown, then '.dat' is returned, instead of throwing an exception. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
mimeType + + +string + + + + the MIME type, e.g. 'audio/webm;codecs=opus'
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ the corresponding file extension, e.g. '.webm' +
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + +

(static) flattenArray(array) → {Array.<Object>}

@@ -5197,13 +5358,13 @@ missing the naught prefix, and is able to process several arrays, e.g. "[1,2][3,
diff --git a/docs/module-visual.Form.html b/docs/module-visual.Form.html index bccc6d5..d6000e4 100644 --- a/docs/module-visual.Form.html +++ b/docs/module-visual.Form.html @@ -2192,13 +2192,13 @@ This is typically called in the constructor of a stimulus, when attributes are a
diff --git a/docs/module-visual.ImageStim.html b/docs/module-visual.ImageStim.html index 7bed7b5..14dd2c4 100644 --- a/docs/module-visual.ImageStim.html +++ b/docs/module-visual.ImageStim.html @@ -1291,13 +1291,13 @@
diff --git a/docs/module-visual.MovieStim.html b/docs/module-visual.MovieStim.html index b79d365..f373872 100644 --- a/docs/module-visual.MovieStim.html +++ b/docs/module-visual.MovieStim.html @@ -1088,13 +1088,13 @@
diff --git a/docs/module-visual.Polygon.html b/docs/module-visual.Polygon.html index c5af66e..5f0286f 100644 --- a/docs/module-visual.Polygon.html +++ b/docs/module-visual.Polygon.html @@ -1021,13 +1021,13 @@
diff --git a/docs/module-visual.Rect.html b/docs/module-visual.Rect.html index 28d187e..e6e174a 100644 --- a/docs/module-visual.Rect.html +++ b/docs/module-visual.Rect.html @@ -1027,13 +1027,13 @@
diff --git a/docs/module-visual.ShapeStim.html b/docs/module-visual.ShapeStim.html index 275a18e..479b243 100644 --- a/docs/module-visual.ShapeStim.html +++ b/docs/module-visual.ShapeStim.html @@ -1243,13 +1243,13 @@ This is overridden in order to provide a finer inclusion test.
diff --git a/docs/module-visual.Slider.html b/docs/module-visual.Slider.html index 918ad7f..ade7872 100644 --- a/docs/module-visual.Slider.html +++ b/docs/module-visual.Slider.html @@ -2720,13 +2720,13 @@ This is typically called in the constructor of a stimulus, when attributes are a
diff --git a/docs/module-visual.TextBox.html b/docs/module-visual.TextBox.html index 1d1d52e..1f3c67c 100644 --- a/docs/module-visual.TextBox.html +++ b/docs/module-visual.TextBox.html @@ -1616,13 +1616,13 @@
diff --git a/docs/module-visual.TextStim.html b/docs/module-visual.TextStim.html index f2332e7..5c97d71 100644 --- a/docs/module-visual.TextStim.html +++ b/docs/module-visual.TextStim.html @@ -1384,13 +1384,13 @@ unlike getSize().
diff --git a/docs/module-visual.VisualStim.html b/docs/module-visual.VisualStim.html index ad61c18..882eec4 100644 --- a/docs/module-visual.VisualStim.html +++ b/docs/module-visual.VisualStim.html @@ -1408,13 +1408,13 @@ This is typically called in the constructor of a stimulus, when attributes are a
diff --git a/docs/module-visual.html b/docs/module-visual.html index ae95217..180dc6d 100644 --- a/docs/module-visual.html +++ b/docs/module-visual.html @@ -104,13 +104,13 @@
diff --git a/docs/sound_Sound.js.html b/docs/sound_Sound.js.html index d312151..577f51c 100644 --- a/docs/sound_Sound.js.html +++ b/docs/sound_Sound.js.html @@ -290,13 +290,13 @@ export class Sound extends PsychObject
diff --git a/docs/sound_SoundPlayer.js.html b/docs/sound_SoundPlayer.js.html index a54ebc0..e71a1e9 100644 --- a/docs/sound_SoundPlayer.js.html +++ b/docs/sound_SoundPlayer.js.html @@ -197,13 +197,13 @@ export class SoundPlayer extends PsychObject
diff --git a/docs/sound_TonePlayer.js.html b/docs/sound_TonePlayer.js.html index ae36bd7..e8815c7 100644 --- a/docs/sound_TonePlayer.js.html +++ b/docs/sound_TonePlayer.js.html @@ -422,13 +422,13 @@ TonePlayer.SoundLibrary = {
diff --git a/docs/sound_TrackPlayer.js.html b/docs/sound_TrackPlayer.js.html index 9516e69..27d3cab 100644 --- a/docs/sound_TrackPlayer.js.html +++ b/docs/sound_TrackPlayer.js.html @@ -264,13 +264,13 @@ export class TrackPlayer extends SoundPlayer
diff --git a/docs/util_Clock.js.html b/docs/util_Clock.js.html index 7f772a0..10f0548 100644 --- a/docs/util_Clock.js.html +++ b/docs/util_Clock.js.html @@ -260,13 +260,13 @@ export class CountdownTimer extends Clock
diff --git a/docs/util_Color.js.html b/docs/util_Color.js.html index 0ac1330..55a6bb2 100644 --- a/docs/util_Color.js.html +++ b/docs/util_Color.js.html @@ -753,13 +753,13 @@ Color.NAMED_COLORS = {
diff --git a/docs/util_ColorMixin.js.html b/docs/util_ColorMixin.js.html index ca25efd..5e7ba73 100644 --- a/docs/util_ColorMixin.js.html +++ b/docs/util_ColorMixin.js.html @@ -115,13 +115,13 @@ export let ColorMixin = (superclass) => class extends superclass
diff --git a/docs/util_EventEmitter.js.html b/docs/util_EventEmitter.js.html index e14ae5b..183d319 100644 --- a/docs/util_EventEmitter.js.html +++ b/docs/util_EventEmitter.js.html @@ -193,13 +193,13 @@ export class EventEmitter
diff --git a/docs/util_PsychObject.js.html b/docs/util_PsychObject.js.html index 53fb58e..0dfe197 100644 --- a/docs/util_PsychObject.js.html +++ b/docs/util_PsychObject.js.html @@ -377,10 +377,10 @@ export class PsychObject extends EventEmitter * @protected * @param {string} name - the name of the attribute * @param {object} value - the value of the attribute - * @param {object} defaultValue - the default value for the attribute - * @param {function} onChange - function called upon changes to the attribute value + * @param {object} [defaultValue] - the default value for the attribute + * @param {function} [onChange] - function called upon changes to the attribute value */ - _addAttribute(name, value, defaultValue, onChange = () => {}) + _addAttribute(name, value, defaultValue = undefined, onChange = () => {}) { const getPropertyName = 'get' + name[0].toUpperCase() + name.substr(1); if (typeof this[getPropertyName] === 'undefined') @@ -445,13 +445,13 @@ export class PsychObject extends EventEmitter
diff --git a/docs/util_Scheduler.js.html b/docs/util_Scheduler.js.html index a353d18..17f7792 100644 --- a/docs/util_Scheduler.js.html +++ b/docs/util_Scheduler.js.html @@ -162,10 +162,10 @@ export class Scheduler * @name module:util.Scheduler#start * @public */ - start() + async start() { const self = this; - let update = (timestamp) => + const update = async (timestamp) => { // stop the animation if need be: if (self._stopAtNextUpdate) @@ -177,7 +177,7 @@ export class Scheduler // self._psychoJS.window._writeLogOnFlip(); // run the next scheduled tasks until a scene render is requested: - const state = self._runNextTasks(); + const state = await self._runNextTasks(); if (state === Scheduler.Event.QUIT) { self._status = Scheduler.Status.STOPPED; @@ -223,7 +223,7 @@ export class Scheduler * @private * @return {module:util.Scheduler#Event} the state of the scheduler after the last task ran */ - _runNextTasks() + async _runNextTasks() { this._status = Scheduler.Status.RUNNING; @@ -262,14 +262,14 @@ export class Scheduler // if the current task is a function, we run it: if (this._currentTask instanceof Function) { - state = this._currentTask(...this._currentArgs); + state = await this._currentTask(...this._currentArgs); } // otherwise, we assume that the current task is a scheduler and we run its tasks until a rendering // of the scene is required. // note: "if (this._currentTask instanceof Scheduler)" does not work because of CORS... else { - state = this._currentTask._runNextTasks(); + state = await this._currentTask._runNextTasks(); if (state === Scheduler.Event.QUIT) { // if the experiment has not ended, we move onto the next task: @@ -356,13 +356,13 @@ Scheduler.Status = {
diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index bbecf01..6aa6b39 100644 --- a/docs/util_Util.js.html +++ b/docs/util_Util.js.html @@ -986,12 +986,12 @@ export function offerDataForDownload(filename, data, type) } else { - let elem = window.document.createElement('a'); - elem.href = window.URL.createObjectURL(blob); - elem.download = filename; - document.body.appendChild(elem); - elem.click(); - document.body.removeChild(elem); + const anchor = document.createElement('a'); + anchor.href = window.URL.createObjectURL(blob); + anchor.download = filename; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); } } @@ -1122,6 +1122,43 @@ export function sum(inputMaybe = []) // Add up each successive entry starting from naught .reduce(add, 0); } + + +/** + * Return the file extension corresponding to an audio mime type. + * If the provided mimeType is not a string (e.g. null, undefined, an array) + * or unknown, then '.dat' is returned, instead of throwing an exception. + * + * @name module:util.extensionFromMimeType + * @function + * @public + * @param {string} mimeType the MIME type, e.g. 'audio/webm;codecs=opus' + * @return {string} the corresponding file extension, e.g. '.webm' + */ +export function extensionFromMimeType(mimeType) +{ + if (typeof mimeType !== 'string') + { + return '.dat'; + } + + if (mimeType.indexOf('audio/webm') === 0) + { + return '.webm'; + } + + if (mimeType.indexOf('audio/ogg') === 0) + { + return '.ogg'; + } + + if (mimeType.indexOf('audio/wav') === 0) + { + return '.wav'; + } + + return '.dat'; +} @@ -1132,13 +1169,13 @@ export function sum(inputMaybe = [])
diff --git a/docs/visual_Form.js.html b/docs/visual_Form.js.html index 7521c8f..10cf284 100644 --- a/docs/visual_Form.js.html +++ b/docs/visual_Form.js.html @@ -1176,13 +1176,13 @@ Form._defaultItems = {
diff --git a/docs/visual_ImageStim.js.html b/docs/visual_ImageStim.js.html index 2c75bc3..9dda506 100644 --- a/docs/visual_ImageStim.js.html +++ b/docs/visual_ImageStim.js.html @@ -395,13 +395,13 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
diff --git a/docs/visual_MovieStim.js.html b/docs/visual_MovieStim.js.html index 0983d3e..5f4473c 100644 --- a/docs/visual_MovieStim.js.html +++ b/docs/visual_MovieStim.js.html @@ -475,13 +475,13 @@ export class MovieStim extends VisualStim
diff --git a/docs/visual_Polygon.js.html b/docs/visual_Polygon.js.html index 326c415..3a74b10 100644 --- a/docs/visual_Polygon.js.html +++ b/docs/visual_Polygon.js.html @@ -181,13 +181,13 @@ export class Polygon extends ShapeStim
diff --git a/docs/visual_Rect.js.html b/docs/visual_Rect.js.html index 6feb07f..2b6c526 100644 --- a/docs/visual_Rect.js.html +++ b/docs/visual_Rect.js.html @@ -186,13 +186,13 @@ export class Rect extends ShapeStim
diff --git a/docs/visual_ShapeStim.js.html b/docs/visual_ShapeStim.js.html index 27ee47a..2f5627b 100644 --- a/docs/visual_ShapeStim.js.html +++ b/docs/visual_ShapeStim.js.html @@ -423,13 +423,13 @@ ShapeStim.KnownShapes = {
diff --git a/docs/visual_Slider.js.html b/docs/visual_Slider.js.html index 3bfe483..e047f73 100644 --- a/docs/visual_Slider.js.html +++ b/docs/visual_Slider.js.html @@ -1399,13 +1399,13 @@ Slider.Skin = {
diff --git a/docs/visual_TextBox.js.html b/docs/visual_TextBox.js.html index 9c161d7..8eb3a56 100644 --- a/docs/visual_TextBox.js.html +++ b/docs/visual_TextBox.js.html @@ -557,13 +557,13 @@ TextBox._defaultSizeMap = new Map([
diff --git a/docs/visual_TextInput.js.html b/docs/visual_TextInput.js.html index 01ae60b..f5c9373 100644 --- a/docs/visual_TextInput.js.html +++ b/docs/visual_TextInput.js.html @@ -889,13 +889,13 @@ function DefaultBoxGenerator(styles)
diff --git a/docs/visual_TextStim.js.html b/docs/visual_TextStim.js.html index bd95a09..cb7a375 100644 --- a/docs/visual_TextStim.js.html +++ b/docs/visual_TextStim.js.html @@ -459,13 +459,13 @@ TextStim._defaultWrapWidthMap = new Map([
diff --git a/docs/visual_VisualStim.js.html b/docs/visual_VisualStim.js.html index 077a327..31e7caf 100644 --- a/docs/visual_VisualStim.js.html +++ b/docs/visual_VisualStim.js.html @@ -341,13 +341,13 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
diff --git a/index.css b/index.css new file mode 100644 index 0000000..2208889 --- /dev/null +++ b/index.css @@ -0,0 +1,149 @@ + +/* project and resource dialogs */ +label, input, select { + display: block; + padding-bottom: .5em; +} + +input.text, select.text { + margin-bottom: 1em; + width: 95%; + padding: .5em; +} + +fieldset { + padding: 0; + border: 0; + margin-top: 1em; +} + +a, a:active, a:focus, a:visited { + outline: 0; + color: #007EB7; +} + +a:hover { + color: #000000; +} + +.progress { + padding: .5em 0 .5em 0; +} + +.logo { + display: block; + margin-left: auto; + margin-right: auto; + max-width: 100%; + margin-bottom: 1em; +} + + +/* don't display close button in the top right corner of the box */ +.no-close .ui-dialog-titlebar-close { + display: none; +} + +.ui-dialog-content { + margin-top: 1em; +} + + +/* for mobile phones only */ +@media only screen and (max-width: 1080px) { + + .ui-widget { + -ms-transform: scale(2); + -webkit-transform: scale(2); + transform: scale(2); + } + + .ui-widget .ui-progressbar { + -ms-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); + } + + .ui-dialog .ui-dialog-buttonpane { + padding-top: 1em; + } + + .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { + -ms-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); + } + + .ui-dialog .ui-dialog-titlebar { + padding: 1em 2em; + } + + .ui-dialog-titlebar .ui-button { + margin-right: 1em; + } + + .ui-dialog-titlebar .ui-dialog-titlebar-close { + -ms-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@media only screen and (max-width: 1080px) and (orientation:landscape) { + + .ui-widget { + -ms-transform: scale(1.5); + -webkit-transform: scale(1.5); + transform: scale(1.5); + } + + .ui-widget .ui-progressbar { + -ms-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); + } + + .ui-dialog .ui-dialog-buttonpane { + padding-top: 1em; + } + + .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { + -ms-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); + } + + .ui-dialog .ui-dialog-titlebar { + padding: 1em 2em; + } + + .ui-dialog-titlebar .ui-button { + margin-right: 1em; + } + + .ui-dialog-titlebar .ui-dialog-titlebar-close { + -ms-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); + } +} + + +/* Initialisation message (which will disappear behind the canvas) */ +#root:after { + content: "initialising the experiment..."; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +/* Initialisation message for IE11 */ +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + #root:after { + content: "initialising the experiment... | Internet Explorer / Edge [beta]"; + + color: #A05000; + font-weight: bold; + } +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..d484753 --- /dev/null +++ b/index.js @@ -0,0 +1,5 @@ +export * as util from './js/util/index.js'; +export * as core from './js/core/index.js'; +export * as data from './js/data/index.js'; +export * as visual from './js/visual/index.js'; +export * as sound from './js/sound/index.js'; diff --git a/package.json b/package.json index 7a982a4..fc77762 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psychojs", - "version": "2021.1.4", + "version": "2021.x", "private": true, "description": "Helps run in-browser neuroscience, psychology, and psychophysics experiments", "license": "MIT", @@ -49,7 +49,6 @@ ] } }, - "dependencies": {}, "devDependencies": { "@babel/core": "^7.12.3", "@babel/preset-env": "^7.12.1", diff --git a/src/core/GUI.js b/src/core/GUI.js index 9964c1a..7bdb57b 100644 --- a/src/core/GUI.js +++ b/src/core/GUI.js @@ -313,7 +313,7 @@ export class GUI //$.blockUI({ message: "", baseZ: 1}); // show dialog box: - $("#progressbar").progressbar({value: self._progressBarCurrentIncrement}); + $("#progressbar").progressbar({value: self._progressBarCurrentValue}); $("#progressbar").progressbar("option", "max", self._progressBarMax); } @@ -587,15 +587,14 @@ export class GUI { this._psychoJS.logger.debug('signal: ' + util.toString(signal)); - // all resources have been registered: - if (signal.message === ServerManager.Event.RESOURCES_REGISTERED) + // the download of the specified resources has started: + if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCES) { // for each resource, we have a 'downloading resource' and a 'resource downloaded' message: this._progressBarMax = signal.count * 2; $("#progressbar").progressbar("option", "max", this._progressBarMax); - this._progressBarCurrentIncrement = 0; - $("#progressMsg").text('all resources registered.'); + this._progressBarCurrentValue = 0; } // all the resources have been downloaded: show the ok button @@ -607,23 +606,25 @@ export class GUI } // update progress bar: - else if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCE || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) + else if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCE + || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) { - if (typeof this._progressBarCurrentIncrement === 'undefined') + if (typeof this._progressBarCurrentValue === 'undefined') { - this._progressBarCurrentIncrement = 0; + this._progressBarCurrentValue = 0; } - ++this._progressBarCurrentIncrement; + ++this._progressBarCurrentValue; if (signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) { - $("#progressMsg").text('downloaded ' + this._progressBarCurrentIncrement / 2 + ' / ' + this._progressBarMax / 2); + $("#progressMsg").text('downloaded ' + (this._progressBarCurrentValue / 2) + ' / ' + (this._progressBarMax / 2)); + } + else + { + $("#progressMsg").text('downloading ' + (this._progressBarCurrentValue / 2) + ' / ' + (this._progressBarMax / 2)); } // $("#progressMsg").text(signal.resource + ': downloaded.'); - // else - // $("#progressMsg").text(signal.resource + ': downloading...'); - - $("#progressbar").progressbar("option", "value", this._progressBarCurrentIncrement); + $("#progressbar").progressbar("option", "value", this._progressBarCurrentValue); } // unknown message: we just display it diff --git a/src/core/Keyboard.js b/src/core/Keyboard.js index 338fc8b..170854a 100644 --- a/src/core/Keyboard.js +++ b/src/core/Keyboard.js @@ -198,7 +198,7 @@ export class Keyboard extends PsychObject const keyEvent = this._circularBuffer[i]; if (keyEvent && keyEvent.status === Keyboard.KeyStatus.KEY_UP) { - // check that the key is in the keyList: + // if the keylist is empty of the key is in the keyList: if (keyList.length === 0 || keyList.includes(keyEvent.pigletKey)) { // look for a corresponding, preceding keydown event: diff --git a/src/core/Logger.js b/src/core/Logger.js index 880a789..7de2bfb 100644 --- a/src/core/Logger.js +++ b/src/core/Logger.js @@ -312,9 +312,9 @@ export class Logger */ _customConsoleLayout() { - const detectedBrowser = this._psychoJS.browser; + const detectedBrowser = util.detectBrowser(); - const customLayout = new log4javascript.PatternLayout("%p %f{1} | %m"); + const customLayout = new log4javascript.PatternLayout("%p %d{HH:mm:ss.SSS} %f{1} | %m"); customLayout.setCustomField('location', function (layout, loggingReference) { // we throw a fake exception to retrieve the stack trace @@ -345,7 +345,7 @@ export class Logger const file = buf[buf.length - 3].split('/').pop(); const method = relevantEntry.split('@')[0]; - return method + ' ' + file + ' ' + line; + return method + ' ' + file + ':' + line; } else if (detectedBrowser === 'Safari') { @@ -363,7 +363,7 @@ export class Logger const line = buf.pop(); const file = buf.pop().split('/').pop(); - return method + ' ' + file + ' ' + line; + return method + ' ' + file + ':' + line; } else { diff --git a/src/core/PsychoJS.js b/src/core/PsychoJS.js index cb72574..96183f7 100644 --- a/src/core/PsychoJS.js +++ b/src/core/PsychoJS.js @@ -18,6 +18,7 @@ import {GUI} from './GUI'; import {MonotonicClock} from '../util/Clock'; import {Logger} from './Logger'; import * as util from '../util/Util'; +// import {Shelf} from "../data/Shelf"; /** @@ -30,6 +31,7 @@ import * as util from '../util/Util'; */ export class PsychoJS { + /** * Properties */ @@ -109,6 +111,11 @@ export class PsychoJS return this._browser; } + // get shelf() + // { + // return this._shelf; + // } + /** * @constructor @@ -154,6 +161,9 @@ export class PsychoJS // Window: this._window = undefined; + // // Shelf: + // this._shelf = new Shelf(this); + // redirection URLs: this._cancellationUrl = undefined; this._completionUrl = undefined; @@ -292,6 +302,17 @@ export class PsychoJS /** * Start the experiment. * + *

The resources are specified in the following fashion: + *

+ *

+ * * @param {Object} options * @param {string} [options.configURL=config.json] - the URL of the configuration file * @param {string} [options.expName=UNKNOWN] - the name of the experiment @@ -299,8 +320,6 @@ export class PsychoJS * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources * @async * @public - * - * @todo: close session on window or tab close */ async start({configURL = 'config.json', expName = 'UNKNOWN', expInfo = {}, resources = []} = {}) { @@ -386,11 +405,11 @@ export class PsychoJS // start the asynchronous download of resources: - this._serverManager.downloadResources(resources); + await this._serverManager.prepareResources(resources); // start the experiment: this.logger.info('[PsychoJS] Start Experiment.'); - this._scheduler.start(); + await this._scheduler.start(); } catch (error) { @@ -400,8 +419,12 @@ export class PsychoJS } + /** - * Synchronously download resources for the experiment. + * Block the experiment until the specified resources have been downloaded. + * + *

Note: only those resources that have not already been downloaded at that point are + * considered.

* *