diff --git a/README.md b/README.md
index 43333eb..acddc00 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
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](http://www.psychopy.org/) Python library.
-You can create online experiments from the [PsychoPy Builder](http://www.psychopy.org/builder/builder.html), you can find and adapt existing experiments on [pavlovia.org](https://www.pavlovia.org), or create them from scratch: the PsychoJS API is available [here](https://psychopy.github.io/psychojs/).
+You can create online experiments from the [PsychoPy Builder](http://www.psychopy.org/builder/builder.html), you can find and adapt existing experiments on [pavlovia.org](https://www.pavlovia.org), or create them from scratch.
PsychoJS is an open-source project. You can contribute by submitting pull requests to the [PsychoJS GitHub repository](https://github.com/psychopy/psychojs), and discuss issues and current and future features on the [Online category of the PsychoPy Forum](https://discourse.psychopy.org/c/online).
@@ -55,8 +55,8 @@ Alain Pitiot - [@apitiot](https://github.com/apitiot)
The PsychoJS library was initially written by [Ilixa](http://www.ilixa.com) with support from the [Wellcome Trust](https://wellcome.ac.uk).
It is now a collaborative effort, supported by the [Chan Zuckerberg Initiative](https://chanzuckerberg.com/) (2020-2021) and [Open Science Tools](https://opensciencetools.org/) (2020-):
- Alain Pitiot - [@apitiot](https://github.com/apitiot)
-- Sotiri Bakagiannis - [@thewhodidthis](https://github.com/thewhodidthis)
- Nikita Agafonov - [@lightest](https://github.com/lightest)
+- Sotiri Bakagiannis - [@thewhodidthis](https://github.com/thewhodidthis)
- Jonathan Peirce - [@peircej](https://github.com/peircej)
- Thomas Pronk - [@tpronk](https://github.com/tpronk)
- Hiroyuki Sogo - [@hsogo](https://github.com/hsogo)
diff --git a/docs/AudioClip.html b/docs/AudioClip.html
new file mode 100644
index 0000000..0ed740c
--- /dev/null
+++ b/docs/AudioClip.html
@@ -0,0 +1,2132 @@
+
+
+
Utility class used by the experiment scripts to keep track of a clock and of the current status (whether or not we are currently checking the keyboard)
This map provides support for browsers that have not yet
+adopted the W3C KeyboardEvent.code standard for detecting key presses.
+It maps the deprecated KeyboardEvent.keycode values to the W3C UI event codes.
+
Unfortunately, it is not very fine-grained: for instance, there is no difference between Alt Left and Alt
+Right, or between Enter and Numpad Enter. Use at your own risk (or upgrade your browser...).
Note: The w3c key-event viewer can be used to see possible values for the items in the keyList given the user's keyboard and chosen layout. The "key" and "code" columns in the UI Events fields are the relevant values for the keyList argument.
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
+
+
+
Description
+
+
+
+
+
+
+
+
+
options
+
+
+
+
+
+Object
+
+
+
+
+
+
+
+
+
+
+
Properties
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
Attributes
+
+
+
+
Default
+
+
+
Description
+
+
+
+
+
+
+
+
+
keyList
+
+
+
+
+
+Array.<string>
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ null
+
+
+
+
+
keyList allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If no keyList is given, all keys will be checked and the key buffer will be cleared completely.
+
+
+
+
+
+
+
timeStamped
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
If true will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time).
An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful
+for generating a single data file from an experiment with many different loops (e.g. interleaved
+staircases or loops within loops.
Multiple key/value pairs can be added to any given entry of the data file. There are
+considered part of the same entry until a call to nextEntry is made.
For an experiment running locally, the results are offered for immediate download.
+
For an experiment running on the server, the results are uploaded to the server.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
+
+
+
Description
+
+
+
+
+
+
+
+
+
options
+
+
+
+
+
+Object
+
+
+
+
+
+
+
+
+
+
+
Properties
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
Attributes
+
+
+
+
Default
+
+
+
Description
+
+
+
+
+
+
+
+
+
attributes
+
+
+
+
+
+Array.<Object>
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
the attributes to be saved
+
+
+
+
+
+
+
sync
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not to communicate with the server in a synchronous manner
+
+
+
+
+
+
+
tag
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ ''
+
+
+
+
+
an optional tag to add to the filename to which the data is saved (for CSV and XLSX saving options)
+
+
+
+
+
+
+
clear
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not to clear all experiment results immediately after they are saved (this is useful when saving data in separate chunks, throughout an experiment)
Generate a callback that prepares updates to the stimulus.
+This is typically called in the constructor of a stimulus, when attributes are added
+with _addAttribute.
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
Attributes
+
+
+
+
Default
+
+
+
Description
+
+
+
+
+
+
+
+
+
withPixi
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not the PIXI representation must
+also be updated
+
+
+
+
+
+
+
withBoundingBox
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not to immediately estimate
+the bounding box
GUI manages the various pop-up dialog boxes that guide the participant, throughout the
+lifecycle of the experiment, e.g. at the start while the resources are downloading, or at the
+end when the data is uploading to the server
Create a dialog box that (a) enables the participant to set some
+experimental values (e.g. the session name), (b) shows progress of resource
+download, and (c) enables the participant to cancel the experiment.
+
Setting experiment values
+
DlgFromDict displays an input field for all values in the dictionary.
+It is possible to specify default values e.g.:
Create a dialog box with a progress bar, to inform the participant of
+the last stages of the experiment: upload of results, of log, and closing
+of session.
Get the list of keys pressed or pushed by the participant.
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
+
+
+
Description
+
+
+
+
+
+
+
+
+
options
+
+
+
+
+
+Object
+
+
+
+
+
+
+
+
+
+
+
Properties
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
Attributes
+
+
+
+
Default
+
+
+
Description
+
+
+
+
+
+
+
+
+
keyList
+
+
+
+
+
+Array.<string>
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ []
+
+
+
+
+
the list of keys to consider. If keyList is empty, we consider all keys.
+Note that we use pyglet keys here, to make the PsychoJs code more homogeneous with PsychoPy.
+
+
+
+
+
+
+
waitRelease
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
whether or not to include those keys pressed but not released. If
+waitRelease = false, key presses without a corresponding key release will have an undefined duration.
+
+
+
+
+
+
+
clear
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not to keep in the buffer the key presses or pushes for a subsequent call to getKeys. If a keyList has been given and clear = true, we only remove from the buffer those keys in keyList
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
+
+
+
+
the list of keys that were pressed (keydown followed by keyup) or pushed
+(keydown with no subsequent keyup at the time getKeys is called).
MonotonicClock offers a convenient way to keep track of time during experiments. An experiment can have as many independent clocks as needed, e.g. one to time responses, another one to keep track of stimuli, etc.
Get the status of each button (pressed or released) and, optionally, the time elapsed between the last call to clickReset and the pressing or releasing of the buttons.
+
Note: clickReset is typically called at stimulus onset. When the participant presses a button, the time elapsed since the clickReset is stored internally and can be accessed any time afterwards with getPressed.
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
Attributes
+
+
+
+
Default
+
+
+
Description
+
+
+
+
+
+
+
+
+
getTime
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not to also return timestamps
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
+
+
+
+
either an array of size 3 with the status (1 for pressed, 0 for released) of each mouse button [left, center, right], or a tuple with that array and another array of size 3 with the timestamps.
Determine whether the mouse has moved beyond a certain distance.
+
distance
+
+
mouseMoved() or mouseMoved(undefined, false): determine whether the mouse has moved at all since the last
+call to getPos
+
mouseMoved(distance: number, false): determine whether the mouse has travelled further than distance, in terms of line of sight
+
mouseMoved(distance: [number,number], false): determine whether the mouse has travelled horizontally or vertically further then the given horizontal and vertical distances
+
+
reset
+
+
mouseMoved(distance, true): reset the mouse move clock, return false
+
mouseMoved(distance, 'here'): return false
+
mouseMoved(distance, [x: number, y: number]: artifically set the previous mouse position to the given coordinates and determine whether the mouse moved further than the given distance
A Trial Handler that implements the Quest algorithm for quick measurement of
+ psychophysical thresholds. QuestHandler relies on the jsQuest library, a port of Prof Dennis Pelli's QUEST algorithm by Daiichiro Kuroki.
A scheduler helps run the main loop by managing scheduled functions,
+called tasks, after each frame is displayed.
+
+Tasks are either another Scheduler, or a
+JavaScript functions returning one of the following codes:
+
+
Scheduler.Event.NEXT: Move onto the next task *without* rendering the scene first.
+
Scheduler.Event.FLIP_REPEAT: Render the scene and repeat the task.
+
Scheduler.Event.FLIP_NEXT: Render the scene and move onto the next task.
+
Scheduler.Event.QUIT: Quit the scheduler.
+
+
+
It is possible to create sub-schedulers, e.g. to handle loops.
+Sub-schedulers are added to a parent scheduler as a normal
+task would be by calling scheduler.add(subScheduler).
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.
A server event is emitted by the manager to inform its listeners of either a change of status, or of a resource related event (e.g. download started, download is completed).
Get the status of a single resource or the reduced status of an array of resources.
+
If an array of resources is given, getResourceStatus returns a single, reduced status
+that is the status furthest away from DOWNLOADED, with the status ordered as follow:
+ERROR (furthest from DOWNLOADED), REGISTERED, DOWNLOADING, and DOWNLOADED
+
For example, given three resources:
+
+
if at least one of the resource status is ERROR, the reduced status is ERROR
+
if at least one of the resource status is DOWNLOADING, the reduced status is DOWNLOADING
+
if the status of all three resources is REGISTERED, the reduced status is REGISTERED
+
if the status of all three resources is DOWNLOADED, the reduced status is DOWNLOADED
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
+
+
+
Description
+
+
+
+
+
+
+
+
+
names
+
+
+
+
+
+string
+|
+
+Array.<string>
+
+
+
+
+
+
+
+
+
+
names of the resources whose statuses are requested
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Throws:
+
+
+
+
+
+
+
if at least one of the names is not that of a previously
+registered resource
+
+
+
+
+
+
+ Type
+
+
+
+Object.<string, *>
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
+
+
+
+
status of the resource if there is only one, or reduced status otherwise
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
Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the
+server, and can be accessed and manipulated in a concurrent fashion.
-
+
+
-
+
Constructor
+
new Shelf(options)
-
-
-
Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the
-server, and be accessed and manipulated in a concurrent fashion.
@@ -299,10 +325,10 @@ server, and be accessed and manipulated in a concurrent fashion.
-
+
-
+
@@ -311,26 +337,7 @@ server, and be accessed and manipulated in a concurrent fashion.
-
(static) MAX_KEY_LENGTH :number
-
-
-
-
-
- Maximum number of components in a key
-
-
-
-
-
Type:
-
-
-
-number
-
-
-
-
+
(static, readonly) Status :Symbol
@@ -339,33 +346,9 @@ server, and be accessed and manipulated in a concurrent fashion.
-
-
-
-
-
@@ -1740,14 +1788,14 @@ given key exists on the shelf
-
- exception if delta is not an integer, or if there is no record with the given
- key, or if there is a record but it is locked or it is not of type INTEGER
+
+
exception if delta is not an integer, or if there is no record with the given
+key, or if there is a record but it is locked or it is not of type INTEGER
-
+
Type
@@ -1770,12 +1818,12 @@ given key exists on the shelf
- the new value
+
the new value
-
+
Type
@@ -1791,23 +1839,64 @@ given key exists on the shelf
-
-
-
appendListValue(options) → {Promise.<Array.<*>>}
-
-
- Append an element, or a list of elements, to the value of a record of type LIST associated with the given key.
+
+
Get the value of a given field in the dictionary record associated with the given key.
@@ -3256,6 +3393,8 @@ given key exists on the shelf
+
+
Parameters:
@@ -3339,7 +3478,7 @@ given key exists on the shelf
-
key as an array of key components
+
key as an array of key components
@@ -3362,7 +3501,7 @@ given key exists on the shelf
-
the name of the field
+
the name of the field
@@ -3385,8 +3524,8 @@ given key exists on the shelf
-
the default value returned if no record with the given key exists on
- the shelf, or if is a record of type DICTIONARY with the given key but it has no such field
+
the default value returned if no record with the given key exists on
+the shelf, or if is a record of type DICTIONARY with the given key but it has no such field
@@ -3405,50 +3544,6 @@ given key exists on the shelf
-
-
-
-
-
-
@@ -5264,14 +5368,14 @@ with the given key.
-
- exception if value is not a boolean, or if there is no record with the given
- key, or if there is a record but it is locked or it is not of type BOOLEAN
+
+
exception if value is not a boolean, or if there is no record with the given
+key, or if there is a record but it is locked or it is not of type BOOLEAN
-
+
Type
@@ -5294,12 +5398,12 @@ with the given key.
- the new value
+
the new value
-
+
Type
@@ -5315,23 +5419,64 @@ with the given key.
-
-
-
-
-
-
-
-
-
@@ -5809,14 +5911,14 @@ with the given key.
-
- exception if value is not an object, or or if there is no record
- with the given key, or if there is a record but it is locked or it is not of type DICTIONARY
+
+
exception if value is not an object, or or if there is no record
+with the given key, or if there is a record but it is locked or it is not of type DICTIONARY
-
+
Type
@@ -5839,12 +5941,12 @@ with the given key.
- the new value
+
the new value
-
+
Type
@@ -5860,23 +5962,64 @@ with the given key.
-
-
-
setIntegerValue(options) → {Promise.<number>}
-
-
- Set the value of a record of type INTEGER associated with the given key.
+
+
-
-
-
-
-
@@ -6070,14 +6171,14 @@ with the given key.
-
- exception if value is not an integer, or or if there is no record
- with the given key, or if there is a record but it is locked or it is not of type INTEGER
+
+
exception if value is not an integer, or or if there is no record
+with the given key, or if there is a record but it is locked or it is not of type INTEGER
-
+
Type
@@ -6100,12 +6201,12 @@ with the given key.
- the new value
+
the new value
-
+
Type
@@ -6121,23 +6222,64 @@ with the given key.
-
-
-
setListValue(options) → {Promise.<Array.<*>>}
-
-
- Set the value of a record of type LIST associated with the given key.
+
+
-
-
-
-
-
@@ -6331,14 +6431,14 @@ with the given key.
-
- exception if value is not an array or if there is no record with the given key,
- or if there is a record but it is locked or it is not of type LIST
+
+
exception if value is not an array or if there is no record with the given key,
+or if there is a record but it is locked or it is not of type LIST
-
+
Type
@@ -6361,12 +6461,12 @@ with the given key.
- the new value
+
the new value
-
+
Type
@@ -6382,23 +6482,64 @@ with the given key.
-
-
-
setTextValue(options) → {Promise.<string>}
-
-
- Set the value of a record of type TEXT associated with the given key.
+
+
Generate a callback that prepares updates to the stimulus.
+This is typically called in the constructor of a stimulus, when attributes are added
+with _addAttribute.
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
Attributes
+
+
+
+
Default
+
+
+
Description
+
+
+
+
+
+
+
+
+
withPixi
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not the PIXI representation must
+also be updated
+
+
+
+
+
+
+
withBoundingBox
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not to immediately estimate
+the bounding box
Get the list of transcripts still in the buffer, i.e. those that have not been
+previously cleared by calls to getTranscripts with clear = true.
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
+
+
+
Description
+
+
+
+
+
+
+
+
+
options
+
+
+
+
+
+Object
+
+
+
+
+
+
+
+
+
+
+
Properties
+
+
+
+
+
+
+
Name
+
+
+
Type
+
+
+
Attributes
+
+
+
+
Default
+
+
+
Description
+
+
+
+
+
+
+
+
+
transcriptList
+
+
+
+
+
+Array.<string>
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ []
+
+
+
+
+
the list of transcripts texts to consider. If transcriptList is empty, we consider all transcripts.
+
+
+
+
+
+
+
clear
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+ <optional>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
whether or not to keep in the buffer the transcripts for a subsequent call to getTranscripts. If a keyList has been given and clear = true, we only remove from the buffer those keys in keyList
+
@@ -31,8 +55,8 @@
*
* @author Alain Pitiot
* @author Sijia Zhao - fine-grained resource loading
- * @version 2021.2.0
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @version 2021.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -43,22 +67,37 @@ import { Scheduler } from "../util/Scheduler.js";
import * as util from "../util/Util.js";
import { PsychoJS } from "./PsychoJS.js";
import { ServerManager } from "./ServerManager.js";
+import A11yDialog from "a11y-dialog";
/**
- * @class
- * Graphic User Interface
- *
- * @name module:core.GUI
- * @class
- * @param {module:core.PsychoJS} psychoJS the PsychoJS instance
+ * <p>GUI manages the various pop-up dialog boxes that guide the participant, throughout the
+ * lifecycle of the experiment, e.g. at the start while the resources are downloading, or at the
+ * end when the data is uploading to the server</p>
*/
export class GUI
{
+ /**
+ * Default settings for GUI.
+ *
+ * @type {Object}
+ */
+ static DEFAULT_SETTINGS = {
+ DlgFromDict: {
+ // The dialog box shows an OK button. The button becomes enable when all registered resources
+ // have been downloaded. Participants must click on the OK button to move on with the experiment.
+ requireParticipantClick: true
+ }
+ };
+
get dialogComponent()
{
return this._dialogComponent;
}
+ /**
+ * @memberof module:core
+ * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance
+ */
constructor(psychoJS)
{
this._psychoJS = psychoJS;
@@ -68,8 +107,6 @@ export class GUI
{
this._onResourceEvents(signal);
});
-
- this._dialogScalingFactor = 0;
}
/**
@@ -86,37 +123,39 @@ export class GUI
* <p>If the participant cancels (by pressing Cancel or by closing the dialog box), then
* the dictionary remains unchanged.</p>
*
- * @name module:core.GUI#DlgFromDict
- * @function
- * @public
* @param {Object} options
* @param {String} [options.logoUrl] - Url of the experiment logo
* @param {String} [options.text] - information text
* @param {Object} options.dictionary - associative array of values for the participant to set
* @param {String} options.title - name of the project
+ * @param {boolean} [options.requireParticipantClick=true] - whether the participant must click on the OK
+ * button, when it becomes enabled, to move on with the experiment
*/
DlgFromDict({
logoUrl,
text,
dictionary,
title,
+ requireParticipantClick = GUI.DEFAULT_SETTINGS.DlgFromDict.requireParticipantClick
})
{
// get info from URL:
const infoFromUrl = util.getUrlParameters();
- this._progressMsg = " ";
this._progressBarMax = 0;
this._allResourcesDownloaded = false;
this._requiredKeys = [];
this._setRequiredKeys = new Map();
+ this._progressMessage = " ";
+ this._requireParticipantClick = requireParticipantClick;
+ this._dictionary = dictionary;
- // prepare PsychoJS component:
+ // prepare a PsychoJS component:
this._dialogComponent = {};
this._dialogComponent.status = PsychoJS.Status.NOT_STARTED;
const dialogClock = new Clock();
- const self = this;
+ const self = this;
return () =>
{
const t = dialogClock.getTime();
@@ -128,38 +167,30 @@ export class GUI
// if the experiment is licensed, and running on the license rather than on credit,
// we use the license logo:
- if (
- self._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
+ if (self._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
&& typeof self._psychoJS.config.experiment.license !== "undefined"
&& self._psychoJS.config.experiment.runMode === "LICENSE"
- && typeof self._psychoJS.config.experiment.license.institutionLogo !== "undefined"
- )
+ && typeof self._psychoJS.config.experiment.license.institutionLogo !== "undefined")
{
logoUrl = self._psychoJS.config.experiment.license.institutionLogo;
}
- // prepare jquery UI dialog box:
- let htmlCode = '<div id="expDialog" title="' + title + '">';
+ // prepare the markup for the a11y-dialog:
+ let markup = "<div class='dialog-container' id='experiment-dialog' aria-hidden='true' role='alertdialog'>";
+ markup += "<div class='dialog-overlay'></div>";
+ // markup += "<div class='dialog-overlay' data-a11y-dialog-hide></div>";
+ markup += "<div class='dialog-content'>";
- // uncomment for older version of the library:
- // htmlCode += '<p style="font-size: 0.8em; padding: 0.5em; margin-bottom: 0.5em; color: #FFAA00; border: 1px solid #FFAA00;">⚠ This experiment uses a deprecated version of the PsychoJS library. Consider updating to a newer version (e.g. by updating PsychoPy and re-exporting the experiment).</p>'+
+ // alert title and close button:
+ markup += `<div id='experiment-dialog-title' class='dialog-title'><p>${title}</p><button id='dialogClose' class='dialog-close' data-a11y-dialog-hide aria-label='Cancel Experiment'>×</button></div>`;
- // logo:
+ // logo, if need be:
if (typeof logoUrl === "string")
{
- htmlCode += '<img id="dialog-logo" class="logo" alt="logo" src="' + logoUrl + '">';
- }
-
- // information text:
- if (typeof text === "string" && text.length > 0)
- {
- htmlCode += "<p>" + text + "</p>";
+ markup += '<img id="dialog-logo" class="logo" alt="logo" src="' + logoUrl + '">';
}
// add a combobox or text areas for each entry in the dictionary:
-
- // These may include Symbols as opposed to when using a for...in loop,
- // but only strings are allowed in PsychoPy
Object.keys(dictionary).forEach((key, keyIdx) =>
{
const value = dictionary[key];
@@ -180,7 +211,7 @@ export class GUI
if (!inUrl)
{
- htmlCode += '<label for="' + keyId + '">' + key + "</label>";
+ markup += `<label for='${keyId}'> ${key} </label>`;
// if the field is required:
if (key.slice(-1) === "*")
@@ -191,45 +222,76 @@ export class GUI
// if value is an array, we create a select drop-down menu:
if (Array.isArray(value))
{
- htmlCode += '<select name="' + key + '" id="' + keyId + '" class="text ui-widget-content'
- + ' ui-corner-all">';
+ markup += `<select name='${key}' id='${keyId}' class='text'>`;
// if the field is required, we add an empty option and select it:
if (key.slice(-1) === "*")
{
- htmlCode += "<option disabled selected>...</option>";
+ markup += "<option disabled selected>...</option>";
}
for (const option of value)
{
- htmlCode += "<option>" + option + "</option>";
+ markup += `<option> ${option} </option>`;
}
- htmlCode += "</select>";
- jQuery("#" + keyId).selectmenu({ classes: {} });
+ markup += "</select>";
}
- // otherwise we use a single string input:
- /*if (typeof value === 'string')*/
+ // otherwise we use a single string input:
+ //if (typeof value === 'string')
else
{
- htmlCode += '<input type="text" name="' + key + '" id="' + keyId;
- htmlCode += '" value="' + value + '" class="text ui-widget-content ui-corner-all">';
+ markup += `<input type='text' name='${key}' id='${keyId}' value='${value}' class='text'>`;
}
}
});
- if (this._requiredKeys.length > 0)
+ if (self._requiredKeys.length > 0)
{
- htmlCode += '<p class="validateTips">Fields marked with an asterisk (*) are required.</p>';
+ markup += "<p class='validateTips'>Fields marked with an asterisk (*) are required.</p>";
}
- // add a progress bar:
- htmlCode += '<hr><div id="progressMsg" class="progress">' + self._progressMsg + "</div>";
- htmlCode += '<div id="progressbar"></div></div>';
+ // progress bar:
+ markup += `<hr><div id='progressMsg' class='progress-msg'>${self._progressMessage}</div>`;
+ markup += "<div class='progress-container'><div id='progressBar' class='progress-bar'></div></div>";
- // replace root by the html code:
+ // buttons:
+ markup += "<hr>";
+ markup += "<button id='dialogCancel' class='dialog-button' aria-label='Cancel Experiment'>Cancel</button>";
+ if (self._requireParticipantClick)
+ {
+ markup += "<button id='dialogOK' class='dialog-button disabled' aria-label='Start Experiment'>Ok</button>";
+ }
+
+ markup += "</div></div>";
+
+ // replace root by the markup code:
const dialogElement = document.getElementById("root");
- dialogElement.innerHTML = htmlCode;
+ dialogElement.innerHTML = markup;
+
+ // init and open the dialog box:
+ const dialogDiv = document.getElementById("experiment-dialog");
+ self._dialog = new A11yDialog(dialogDiv);
+ self._dialog.show();
+
+ // button callbacks:
+ self._dialogComponent.button = "Cancel";
+ self._cancelButton = document.getElementById("dialogCancel");
+ self._cancelButton.onclick = self._onCancelExperiment.bind(self);
+ if (self._requireParticipantClick)
+ {
+ self._okButton = document.getElementById("dialogOK");
+ self._okButton.onclick = self._onStartExperiment.bind(self);
+ }
+ self._closeButton = document.getElementById("dialogClose");
+ self._closeButton.onclick = self._onCancelExperiment.bind(self);
+
+ // update the OK button status:
+ self._updateDialog();
+
+ self._progressMsg = document.getElementById("progressMsg");
+ self._progressBar = document.getElementById("progressBar");
+ self._updateProgressBar();
// setup change event handlers for all required keys:
this._requiredKeys.forEach((keyId) =>
@@ -240,79 +302,6 @@ export class GUI
input.oninput = (event) => GUI._onKeyChange(self, event);
}
});
-
- // init and open the dialog box:
- self._dialogComponent.button = "Cancel";
- jQuery("#expDialog").dialog({
- width: "500",
-
- autoOpen: true,
- modal: false,
- closeOnEscape: false,
- resizable: false,
- draggable: false,
-
- buttons: [
- {
- id: "buttonCancel",
- text: "Cancel",
- click: function()
- {
- self._dialogComponent.button = "Cancel";
- jQuery("#expDialog").dialog("close");
- },
- },
- {
- id: "buttonOk",
- text: "Ok",
- click: function()
- {
- // update dictionary:
- Object.keys(dictionary).forEach((key, keyIdx) =>
- {
- const input = document.getElementById("form-input-" + keyIdx);
- if (input)
- {
- dictionary[key] = input.value;
- }
- });
-
- self._dialogComponent.button = "OK";
- jQuery("#expDialog").dialog("close");
-
- // Tackle browser demands on having user action initiate audio context
- Tone.start();
-
- // switch to full screen if requested:
- self._psychoJS.window.adjustScreenSize();
-
- // Clear events (and keypresses) accumulated during the dialog
- self._psychoJS.eventManager.clearEvents();
- },
- },
- ],
-
- // close is called by both buttons and when the user clicks on the cross:
- close: function()
- {
- // jQuery.unblockUI();
- jQuery(this).dialog("destroy").remove();
- self._dialogComponent.status = PsychoJS.Status.FINISHED;
- },
- })
- // change colour of title bar
- .prev(".ui-dialog-titlebar").css("background", "green");
-
- // update the OK button status:
- self._updateOkButtonStatus();
-
- // block UI until user has pressed dialog button:
- // note: block UI does not allow for text to be entered in the dialog form boxes, alas!
- // jQuery.blockUI({ message: "", baseZ: 1});
-
- // show dialog box:
- jQuery("#progressbar").progressbar({ value: self._progressBarCurrentValue });
- jQuery("#progressbar").progressbar("option", "max", self._progressBarMax);
}
if (self._dialogComponent.status === PsychoJS.Status.FINISHED)
@@ -334,9 +323,6 @@ export class GUI
*
* <p>This function can be used to display both warning and error messages.</p>
*
- * @name module:core.GUI#dialog
- * @function
- * @public
* @param {Object} options
* @param {string} options.message - the message to be displayed
* @param {Object.<string, *>} options.error - an exception
@@ -349,14 +335,16 @@ export class GUI
warning,
error,
showOK = true,
- onOK,
+ onOK
} = {})
{
// close the previously opened dialog box, if there is one:
this.closeDialog();
- let htmlCode;
- let titleColour;
+ // prepare the markup for the a11y-dialog:
+ let markup = "<div class='dialog-container' id='experiment-dialog' aria-hidden='true' role='alertdialog'>";
+ markup += "<div class='dialog-overlay'></div>";
+ markup += "<div class='dialog-content'>";
// we are displaying an error:
if (typeof error !== "undefined")
@@ -369,9 +357,8 @@ export class GUI
error = "Unspecified JavaScript error";
}
- let errorCode = null;
-
// go through the error stack and look for errorCode if there is one:
+ let errorCode = null;
let stackCode = "<ul>";
while (true)
{
@@ -403,101 +390,206 @@ export class GUI
if (errorCode)
{
const error = this._userFriendlyError(errorCode);
- htmlCode = error.htmlCode;
- titleColour = error.titleColour;
+ markup += `<div id='experiment-dialog-title' class='dialog-title ${error.class}'><p>${error.title}</p></div>`;
+ markup += `<p>${error.text}</p>`;
}
else
{
- htmlCode = '<div id="msgDialog" title="Error">';
- htmlCode += '<p class="validateTips">Unfortunately we encountered the following error:</p>';
- htmlCode += stackCode;
- htmlCode += "<p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>";
- htmlCode += "</div>";
-
- titleColour = "red";
+ markup += `<div id='experiment-dialog-title' class='dialog-title dialog-error'><p>Error</p></div>`;
+ markup += `<p>Unfortunately we encountered the following error:</p>`;
+ markup += stackCode;
+ markup += "<p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>";
}
}
- // we are displaying a message:
- else if (typeof message !== "undefined")
- {
- htmlCode = '<div id="msgDialog" title="Message">'
- + '<p class="validateTips">' + message + "</p>"
- + "</div>";
- titleColour = "green";
- }
+
// we are displaying a warning:
else if (typeof warning !== "undefined")
{
- htmlCode = '<div id="msgDialog" title="Warning">'
- + '<p class="validateTips">' + warning + "</p>"
- + "</div>";
- titleColour = "orange";
+ markup += `<div id='experiment-dialog-title' class='dialog-title dialog-warning'><p>Warning</p></div>`;
+ markup += `<p>${warning}</p>`;
}
- // replace root by the html code:
+ // we are displaying a message:
+ else if (typeof message !== "undefined")
+ {
+ markup += `<div id='experiment-dialog-title' class='dialog-title'><p>Message</p></div>`;
+ markup += `<p>${message}</p>`;
+ }
+
+ if (showOK)
+ {
+ markup += "<hr><button id='dialogOK' class='dialog-button' aria-label='Close dialog'>Ok</button>";
+ }
+ markup += "</div></div>";
+
+ // replace root by the markup code:
const dialogElement = document.getElementById("root");
- dialogElement.innerHTML = htmlCode;
+ dialogElement.innerHTML = markup;
// init and open the dialog box:
- const self = this;
- jQuery("#msgDialog").dialog({
- dialogClass: "no-close",
+ const dialogDiv = document.getElementById("experiment-dialog");
+ this._dialog = new A11yDialog(dialogDiv);
+ this._dialog.show();
- width: "500",
+ // button callbacks:
+ if (showOK)
+ {
+ this._okButton = document.getElementById("dialogOK");
+ this._okButton.onclick = () =>
+ {
+ this.closeDialog();
- autoOpen: true,
- modal: false,
- closeOnEscape: false,
- resizable: false,
- draggable: false,
-
- buttons: (!showOK) ? [] : [{
- id: "buttonOk",
- text: "Ok",
- click: function()
+ // execute callback function:
+ if (typeof onOK !== "undefined")
{
- jQuery(this).dialog("destroy").remove();
+ onOK();
+ }
+ };
+ }
+ }
- // execute callback function:
- if (typeof onOK !== "undefined")
- {
- onOK();
- }
- },
- }],
- })
- // change colour of title bar
- .prev(".ui-dialog-titlebar").css("background", titleColour);
+ /**
+ * <p>Create a dialog box with a progress bar, to inform the participant of
+ * the last stages of the experiment: upload of results, of log, and closing
+ * of session.</p>
+ *
+ * @param {Object} options
+ * @param {String} [options.text] - information text
+ */
+ finishDialog({ text = "", nbSteps = 0 })
+ {
+ this.closeDialog();
+
+ // prepare the markup for the a11y-dialog:
+ let markup = "<div class='dialog-container' id='experiment-dialog' aria-hidden='true' role='alertdialog'>";
+ markup += "<div class='dialog-overlay'></div>";
+ markup += "<div class='dialog-content'>";
+ markup += `<div id='experiment-dialog-title' class='dialog-title dialog-warning'><p>Warning</p></div>`;
+ markup += `<p>${text}</p>`;
+
+ // progress bar:
+ markup += `<hr><div id='progressMsg' class='progress-msg'> </div>`;
+ markup += "<div class='progress-container'><div id='progressBar' class='progress-bar'></div></div>";
+
+ markup += "</div></div>";
+
+ // replace root by the markup code:
+ const dialogElement = document.getElementById("root");
+ dialogElement.innerHTML = markup;
+
+ // init and open the dialog box:
+ const dialogDiv = document.getElementById("experiment-dialog");
+ this._dialog = new A11yDialog(dialogDiv);
+ this._dialog.show();
+
+ this._progressMsg = document.getElementById("progressMsg");
+ this._progressBar = document.getElementById("progressBar");
+
+ this._progressMessage = " ";
+ this._progressBarCurrentValue = 0;
+ this._progressBarMax = nbSteps;
+ this._updateProgressBar();
+ }
+
+ finishDialogNextStep(text)
+ {
+ this._setProgressMessage(text);
+ ++ this._progressBarCurrentValue;
+ this._updateProgressBar();
}
/**
* Close the previously opened dialog box, if there is one.
- *
- * @name module:core.GUI#closeDialog
- * @function
- * @public
*/
closeDialog()
{
- const expDialog = jQuery("#expDialog");
- if (expDialog.length)
+ if (this._dialog)
{
- expDialog.dialog("destroy").remove();
- }
- const msgDialog = jQuery("#msgDialog");
- if (msgDialog.length)
- {
- msgDialog.dialog("destroy").remove();
+ this._dialog.hide();
}
}
/**
- * Listener for resource event from the [Server Manager]{@link ServerManager}.
+ * Set the progress message.
*
- * @name module:core.GUI#_onResourceEvents
- * @function
- * @private
- * @param {Object.<string, string|Symbol>} signal the signal
+ * @protected
+ * @param {string} message the message
+ */
+ _setProgressMessage(message)
+ {
+ this._progressMessage = message;
+ if (typeof this._progressMsg !== "undefined")
+ {
+ this._progressMsg.innerText = message;
+ }
+ }
+
+ /**
+ * Update the progress bar.
+ *
+ * @protected
+ */
+ _updateProgressBar()
+ {
+ if (typeof this._progressBar !== "undefined")
+ {
+ this._progressBar.style.width = `${Math.round(this._progressBarCurrentValue * 100.0 / this._progressBarMax)}%`;
+ }
+ }
+
+ /**
+ * Callback triggered when the participant presses the Cancel button
+ *
+ * @protected
+ */
+ _onCancelExperiment()
+ {
+ this._dialogComponent.button = "Cancel";
+
+ this._dialog.hide();
+ this._dialog = null;
+ this._dialogComponent.status = PsychoJS.Status.FINISHED;
+ }
+
+ /**
+ * Callback triggered when the participant presses the OK button
+ *
+ * @protected
+ */
+ _onStartExperiment()
+ {
+ this._dialogComponent.button = "OK";
+
+ // update the dictionary:
+ Object.keys(this._dictionary).forEach((key, keyIdx) =>
+ {
+ const input = document.getElementById("form-input-" + keyIdx);
+ if (input)
+ {
+ this._dictionary[key] = input.value;
+ }
+ });
+
+
+ // Start Tone here, since a user action is required to initiate the audio context:
+ Tone.start();
+
+ // switch to full screen if requested:
+ this._psychoJS.window.adjustScreenSize();
+
+ // clear all events (and keypresses) accumulated until now:
+ this._psychoJS.eventManager.clearEvents();
+
+ this._dialog.hide();
+ this._dialog = null;
+ this._dialogComponent.status = PsychoJS.Status.FINISHED;
+ }
+
+ /**
+ * Callback triggered upon a resource event from the [Server Manager]{@link module:core.ServerManager}.
+ *
+ * @protected
+ * @param {Object.<string, string|Symbol>} signal - the ServerManager's signal
*/
_onResourceEvents(signal)
{
@@ -508,16 +600,19 @@ export class GUI
{
// for each resource, we have a 'downloading resource' and a 'resource downloaded' message:
this._progressBarMax = signal.count * 2;
- jQuery("#progressbar").progressbar("option", "max", this._progressBarMax);
-
this._progressBarCurrentValue = 0;
+ this._updateProgressBar();
}
// all the resources have been downloaded: show the ok button
else if (signal.message === ServerManager.Event.DOWNLOAD_COMPLETED)
{
this._allResourcesDownloaded = true;
- jQuery("#progressMsg").text("all resources downloaded.");
- this._updateOkButtonStatus();
+ this._progressBarMax = 100;
+ this._progressBarCurrentValue = 100;
+ this._updateProgressBar();
+ this._setProgressMessage("all resources downloaded.");
+
+ this._updateDialog();
}
// update progress bar:
else if (
@@ -533,68 +628,75 @@ export class GUI
if (signal.message === ServerManager.Event.RESOURCE_DOWNLOADED)
{
- jQuery("#progressMsg").text("downloaded " + (this._progressBarCurrentValue / 2) + " / " + (this._progressBarMax / 2));
+ this._setProgressMessage(`downloaded ${this._progressBarCurrentValue / 2} / ${this._progressBarMax / 2}`);
}
else
{
- jQuery("#progressMsg").text("downloading " + (this._progressBarCurrentValue / 2) + " / " + (this._progressBarMax / 2));
+ this._setProgressMessage(`downloading ${this._progressBarCurrentValue / 2} / ${this._progressBarMax / 2}`);
}
- // $("#progressMsg").text(signal.resource + ': downloaded.');
- jQuery("#progressbar").progressbar("option", "value", this._progressBarCurrentValue);
+
+ this._updateProgressBar();
}
// unknown message: we just display it
else
{
- jQuery("#progressMsg").text(signal.message);
+ this._progressMsg.innerHTML = signal.message;
}
}
/**
- * Update the status of the OK button.
+ * Update the dialog box.
*
- * @name module:core.GUI#_updateOkButtonStatus
- * @param [changeFocus = false] - whether or not to change the focus to the OK button
- * @function
- * @private
+ * @protected
+ * @param [changeOKButtonFocus = false] - whether to change the focus to the OK button
*/
- _updateOkButtonStatus(changeFocus = true)
+ _updateDialog(changeOKButtonFocus = true)
{
- if (
- (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL)
- || (this._allResourcesDownloaded && this._setRequiredKeys && this._setRequiredKeys.size >= this._requiredKeys.length)
- )
+ const allRequirementsFulfilled = this._allResourcesDownloaded
+ && (this._setRequiredKeys && this._setRequiredKeys.size >= this._requiredKeys.length);
+
+ // if the participant is required to click on the OK button:
+ if (this._requireParticipantClick)
{
- if (changeFocus)
+ if (typeof this._okButton !== "undefined")
{
- jQuery("#buttonOk").button("option", "disabled", false).focus();
+ // locally the OK button is always enabled, otherwise only if all requirements have been fulfilled:
+ if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL || allRequirementsFulfilled)
+ {
+ if (changeOKButtonFocus)
+ {
+ this._okButton.classList = ["dialog-button"];
+ this._okButton.focus();
+ }
+ else
+ {
+ this._okButton.classList = ["dialog-button"];
+ }
+ }
+ else
+ {
+ this._okButton.classList = ["dialog-button", "disabled"];
+ }
}
- else
- {
- jQuery("#buttonOk").button("option", "disabled", false);
- }
- }
- else
- {
- jQuery("#buttonOk").button("option", "disabled", true);
+
+ return;
}
- // strangely, changing the disabled option sometimes fails to update the ui,
- // so we need to hide it and show it again:
- jQuery("#buttonOk").hide(0, () =>
+
+ // if all requirements are fulfilled and the participant is not required to click on the OK button,
+ // then we close the dialog box and move on with the experiment:
+ if (allRequirementsFulfilled)
{
- jQuery("#buttonOk").show();
- });
+ this._onStartExperiment();
+ }
}
/**
- * Listener for change event for required keys.
+ * Callback triggered upon change event (for required keys).
*
- * @name module:core.GUI#_onKeyChange
- * @function
- * @static
- * @private
+ * @protected
* @param {module:core.GUI} gui - this GUI
- * @param {Event} event - event
+ * @param {Event} event - the key's event
*/
static _onKeyChange(gui, event)
{
@@ -610,15 +712,15 @@ export class GUI
gui._setRequiredKeys.delete(event.target);
}
- gui._updateOkButtonStatus(false);
+ gui._updateDialog(false);
}
/**
- * Get a more user-friendly html message.
+ * Get the user-friendly html message associated to a pavlovia.or server error code.
*
+ * @protected
* @param {number} errorCode - the pavlovia.org server error code
- * @private
- * @return {{htmlCode: string, titleColour: string}} a user-friendly error message
+ * @return {{class: string, title: string, text: string}} a user-friendly error message
*/
_userFriendlyError(errorCode)
{
@@ -627,130 +729,101 @@ export class GUI
// INTERNAL_ERROR
case 1:
return {
- htmlCode:
- '<div id="msgDialog" title="Error"><p>Oops we encountered an internal server error.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p></div>',
- titleColour: "red",
+ class: "dialog-error",
+ title: "Error",
+ text: "<p>Oops we encountered an <strong>internal server error</strong>.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>"
};
// MONGODB_ERROR
-
case 2:
return {
- htmlCode:
- '<div id="msgDialog" title="Error"><p>Oops we encountered a database error.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p></div>',
- titleColour: "red",
+ class: "dialog-error",
+ title: "Error",
+ text: "<p>Oops we encountered a <strong>database error</strong>.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>"
};
// STATUS_NONE
-
case 20:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> does not have any status and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> does not have any status and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.</p>`
};
// STATUS_INACTIVE
-
case 21:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is currently inactive and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is currently inactive and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.</p>`
};
// STATUS_DELETED
-
case 22:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> has been deleted and cannot be run.</p><p>If you are the experiment designer, either go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING, or generate a new experiment.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment has been deleted and cannot be run any longer.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> has been deleted and cannot be run.</p><p>If you are the experiment designer, either go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING, or generate a new experiment.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment has been deleted and cannot be run any longer.</p>`
};
// STATUS_ARCHIVED
-
case 23:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> has been archived and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment has been archived and cannot be run at the moment.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> has been archived and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment has been archived and cannot be run at the moment.</p>`
};
// PILOTING_NO_TOKEN
-
case 30:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is currently in PILOTING mode but the pilot token is missing from the URL.</p><p>If you are the experiment designer, you can pilot it by pressing the pilot button on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is currently in PILOTING mode but the pilot token is missing from the URL.</p><p>If you are the experiment designer, you can pilot it by pressing the pilot button on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.</p>`
};
// PILOTING_INVALID_TOKEN
-
case 31:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> cannot be run because the pilot token in the URL is invalid, possibly because it has expired.</p><p>If you are the experiment designer, you can generate a new token by pressing the pilot button on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> cannot be run because the pilot token in the URL is invalid, possibly because it has expired.</p><p>If you are the experiment designer, you can generate a new token by pressing the pilot button on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.</p>`
};
// LICENSE_EXPIRED
-
case 50:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is covered by a license that has expired. </p><p>If you are the experiment designer, you can either contact the license manager to inquire about the expiration, or you can run your experiments using credits. You will find all relevant details about the license on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>, where you will also be able to change its running mode to CREDIT.</p><p>Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license having expired.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is covered by a license that has expired. </p><p>If you are the experiment designer, you can either contact the license manager to inquire about the expiration, or you can run your experiments using credits. You will find all relevant details about the license on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>, where you will also be able to change its running mode to CREDIT.</p><p>Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license having expired.</p>`
};
// LICENSE_APPROVAL_NEEDED
-
case 51:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is covered by a license that requires one or more documents to be approved before the experiment can be run. </p><p>If you are the experiment designer, please contact the license manager and ask him or her which documents must be approved. You will find all relevant details about the license on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license requiring documents to be approved.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is covered by a license that requires one or more documents to be approved before the experiment can be run. </p><p>If you are the experiment designer, please contact the license manager and ask him or her which documents must be approved. You will find all relevant details about the license on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license requiring documents to be approved.</p>`
};
// CREDIT_NOT_ENOUGH
-
case 60:
return {
- htmlCode:
- `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> does not have any assigned credit left and cannot be run.</p><p>If you are the experiment designer, you can assign more credits to it on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment requires more assigned credits to run.</p></div>`,
- titleColour: "orange",
+ class: "dialog-warning",
+ title: "Warning",
+ text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> does not have any assigned credit left and cannot be run.</p><p>If you are the experiment designer, you can assign more credits to it on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment requires more assigned credits to run.</p>`
};
default:
return {
- htmlCode:
- `<div id="msgDialog" title="Error"><p>Unfortunately we encountered an unspecified error (error code: ${errorCode}.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p></div>`,
- titleColour: "red",
+ class: "dialog-error",
+ title: "Error",
+ text: `<p>Unfortunately we encountered an unspecified error (error code: ${errorCode}.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>`
};
}
}
}
-/**
- * Maximal dimensions of the dialog window.
- *
- * @name module:core.GUI#dialogMaxSize
- * @enum {Symbol}
- * @readonly
- * @public
- */
-GUI.dialogMaxSize = [500, 600];
-
-/**
- * Dialog window margins.
- *
- * @name module:core.GUI#dialogMargin
- * @enum {Symbol}
- * @readonly
- * @public
- */
-GUI.dialogMargin = [50, 50];
@@ -758,19 +831,23 @@ GUI.dialogMargin = [50, 50];
+
+
+
@@ -30,8 +54,8 @@
* Logger
*
* @author Alain Pitiot
- * @version 2021.2.0
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @version 2022.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -46,13 +70,14 @@ import * as util from "../util/Util.js";
* a remote one, etc.</p>
*
* <p>Note: we use log4javascript for the console logger, and our own for the server logger.</p>
- *
- * @name module:core.Logger
- * @class
- * @param {*} threshold - the logging threshold, e.g. log4javascript.Level.ERROR
*/
export class Logger
{
+ /**
+ * @memberof module:core
+ * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance
+ * @param {*} threshold - the logging threshold, e.g. log4javascript.Level.ERROR
+ */
constructor(psychoJS, threshold)
{
this._psychoJS = psychoJS;
@@ -97,8 +122,6 @@ export class Logger
/**
* Change the logging level.
*
- * @name module:core.Logger#setLevel
- * @public
* @param {module:core.Logger.ServerLevel} serverLevel - the new logging level
*/
setLevel(serverLevel)
@@ -110,8 +133,6 @@ export class Logger
/**
* Log a server message at the EXP level.
*
- * @name module:core.Logger#exp
- * @public
* @param {string} msg - the message to be logged.
* @param {number} [time] - the logging time
* @param {object} [obj] - the associated object (e.g. a Trial)
@@ -124,8 +145,6 @@ export class Logger
/**
* Log a server message at the DATA level.
*
- * @name module:core.Logger#data
- * @public
* @param {string} msg - the message to be logged.
* @param {number} [time] - the logging time
* @param {object} [obj] - the associated object (e.g. a Trial)
@@ -138,8 +157,6 @@ export class Logger
/**
* Log a server message.
*
- * @name module:core.Logger#log
- * @public
* @param {string} msg - the message to be logged.
* @param {module:core.Logger.ServerLevel} level - logging level
* @param {number} [time] - the logging time
@@ -178,9 +195,7 @@ export class Logger
/**
* Check whether or not a log messages must be throttled.
*
- * @name module:core.Logger#_throttle
* @protected
- *
* @param {number} time - the time of the latest log message
* @return {boolean} whether or not to log the message
*/
@@ -256,9 +271,6 @@ export class Logger
*
* <p>Note: the logs are compressed using Pako's zlib algorithm.
* See https://github.com/nodeca/pako for details.</p>
- *
- * @name module:core.Logger#flush
- * @public
*/
async flush()
{
@@ -324,8 +336,7 @@ export class Logger
/**
* Create a custom console layout.
*
- * @name module:core.Logger#_customConsoleLayout
- * @private
+ * @protected
* @return {*} the custom layout
*/
_customConsoleLayout()
@@ -396,7 +407,6 @@ export class Logger
/**
* Get the integer value associated with a logging level.
*
- * @name module:core.Logger#_getValue
* @protected
* @param {module:core.Logger.ServerLevel} level - the logging level
* @return {number} - the value associated with the logging level, or 30 is the logging level is unknown.
@@ -411,10 +421,8 @@ export class Logger
/**
* Server logging level.
*
- * @name module:core.Logger#ServerLevel
* @enum {Symbol}
* @readonly
- * @public
*
* @note These are similar to PsychoPy's logging levels, as defined in logging.py
*/
@@ -434,7 +442,6 @@ Logger.ServerLevel = {
*
* <p>We use those values to determine whether a log is to be sent to the server or not.</p>
*
- * @name module:core.Logger#_ServerLevelValue
* @enum {number}
* @readonly
* @protected
@@ -456,19 +463,23 @@ Logger._ServerLevelValue = {
+
+
+
@@ -31,8 +55,8 @@
*
* @author Alain Pitiot
* @author Sotiri Bakagiannis - isPressedIn
- * @version 2021.2.0
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @version 2022.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -44,18 +68,17 @@ import { PsychoJS } from "./PsychoJS.js";
* <p>This manager handles the interactions between the experiment's stimuli and the mouse.</p>
* <p>Note: the unit of Mouse is that of its associated Window.</p>
*
- * @name module:core.Mouse
- * @class
- * @extends PsychObject
- * @param {Object} options
- * @param {String} options.name - the name used when logging messages from this stimulus
- * @param {Window} options.win - the associated Window
- * @param {boolean} [options.autoLog= true] - whether or not to log
- *
* @todo visible is not handled at the moment (mouse is always visible)
*/
export class Mouse extends PsychObject
{
+ /**
+ * @memberof module:core
+ * @param {Object} options
+ * @param {String} options.name - the name used when logging messages from this stimulus
+ * @param {Window} options.win - the associated Window
+ * @param {boolean} [options.autoLog= true] - whether or not to log
+ */
constructor({
name,
win,
@@ -82,9 +105,6 @@ export class Mouse extends PsychObject
/**
* Get the current position of the mouse in mouse/Window units.
*
- * @name module:core.Mouse#getPos
- * @function
- * @public
* @return {Array.number} the position of the mouse in mouse/Window units
*/
getPos()
@@ -107,9 +127,6 @@ export class Mouse extends PsychObject
* Get the position of the mouse relative to that at the last call to getRel
* or getPos, in mouse/Window units.
*
- * @name module:core.Mouse#getRel
- * @function
- * @public
* @return {Array.number} the relation position of the mouse in mouse/Window units.
*/
getRel()
@@ -133,9 +150,6 @@ export class Mouse extends PsychObject
* <p>Note: Even though this method returns a [x, y] array, for most wheels/systems y is the only
* value that varies.</p>
*
- * @name module:core.Mouse#getWheelRel
- * @function
- * @public
* @return {Array.number} the mouse scroll wheel travel
*/
getWheelRel()
@@ -155,9 +169,6 @@ export class Mouse extends PsychObject
*
* <p>Note: clickReset is typically called at stimulus onset. When the participant presses a button, the time elapsed since the clickReset is stored internally and can be accessed any time afterwards with getPressed.</p>
*
- * @name module:core.Mouse#getPressed
- * @function
- * @public
* @param {boolean} [getTime= false] whether or not to also return timestamps
* @return {Array.number | Array.<Array.number>} either an array of size 3 with the status (1 for pressed, 0 for released) of each mouse button [left, center, right], or a tuple with that array and another array of size 3 with the timestamps.
*/
@@ -178,9 +189,6 @@ export class Mouse extends PsychObject
/**
* Helper method for checking whether a stimulus has had any button presses within bounds.
*
- * @name module:core.Mouse#isPressedIn
- * @function
- * @public
* @param {object|module:visual.VisualStim} shape A type of visual stimulus or object having a `contains()` method.
* @param {object|number} [buttons] The target button index potentially tucked inside an object.
* @param {object} [options]
@@ -250,9 +258,6 @@ export class Mouse extends PsychObject
* <li>mouseMoved(distance, [x: number, y: number]: artifically set the previous mouse position to the given coordinates and determine whether the mouse moved further than the given distance</li>
* </ul></p>
*
- * @name module:core.Mouse#mouseMoved
- * @function
- * @public
* @param {undefined|number|Array.number} [distance] - the distance to which the mouse movement is compared (see above for a full description)
* @param {boolean|String|Array.number} [reset= false] - see above for a full description
* @return {boolean} see above for a full description
@@ -347,9 +352,6 @@ export class Mouse extends PsychObject
/**
* Get the amount of time elapsed since the last mouse movement.
*
- * @name module:core.Mouse#mouseMoveTime
- * @function
- * @public
* @return {number} the time elapsed since the last mouse movement
*/
mouseMoveTime()
@@ -360,9 +362,6 @@ export class Mouse extends PsychObject
/**
* Reset the clocks associated to the given mouse buttons.
*
- * @name module:core.Mouse#clickReset
- * @function
- * @public
* @param {Array.number} [buttons= [0,1,2]] the buttons to reset (0: left, 1: center, 2: right)
*/
clickReset(buttons = [0, 1, 2])
@@ -382,19 +381,23 @@ export class Mouse extends PsychObject
+
+
+
@@ -31,8 +55,8 @@
* Main component of the PsychoJS library.
*
* @author Alain Pitiot
- * @version 2021.2.0
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @version 2022.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -49,18 +73,11 @@ import { Window } from "./Window.js";
import {Shelf} from "../data/Shelf";
/**
- * <p>PsychoJS manages the lifecycle of an experiment. It initialises the PsychoJS library and its various components (e.g. the {@link ServerManager}, the {@link EventManager}), and is used by the experiment to schedule the various tasks.</p>
- *
- * @class
- * @param {Object} options
- * @param {boolean} [options.debug= true] whether or not to log debug information in the browser console
- * @param {boolean} [options.collectIP= false] whether or not to collect the IP information of the participant
+ * <p>PsychoJS initialises the library and its various components (e.g. the [ServerManager]{@link module:core.ServerManager}, the [EventManager]{@link module:core.EventManager}), and manages
+ * the lifecycle of an experiment.</p>
*/
export class PsychoJS
{
- /**
- * Properties
- */
get status()
{
return this._status;
@@ -143,8 +160,9 @@ export class PsychoJS
}
/**
- * @constructor
- * @public
+ * @param {Object} options
+ * @param {boolean} [options.debug= true] whether to log debug information in the browser console
+ * @param {boolean} [options.collectIP= false] whether to collect the IP information of the participant
*/
constructor({
debug = true,
@@ -204,7 +222,7 @@ export class PsychoJS
}
this.logger.info("[PsychoJS] Initialised.");
- this.logger.info("[PsychoJS] @version 2022.2.0");
+ this.logger.info("[PsychoJS] @version 2022.2.1");
// hide the initialisation message:
const root = document.getElementById("root");
@@ -240,8 +258,6 @@ export class PsychoJS
* @param {boolean} [options.waitBlanking] whether or not to wait for all rendering operations to be done
* before flipping
* @throws {Object.<string, *>} exception if a window has already been opened
- *
- * @public
*/
openWindow({
name,
@@ -291,9 +307,8 @@ export class PsychoJS
/**
* Schedule a task.
*
- * @param task - the task to be scheduled
- * @param args - arguments for that task
- * @public
+ * @param {module:util.Scheduler~Task} task - the task to be scheduled
+ * @param {*} args - arguments for that task
*/
schedule(task, args)
{
@@ -310,9 +325,8 @@ export class PsychoJS
* Schedule a series of task based on a condition.
*
* @param {PsychoJS.condition} condition
- * @param {Scheduler} thenScheduler scheduler to run if the condition is true
- * @param {Scheduler} elseScheduler scheduler to run if the condition is false
- * @public
+ * @param {Scheduler} thenScheduler - scheduler to run if the condition is true
+ * @param {Scheduler} elseScheduler - scheduler to run if the condition is false
*/
scheduleCondition(condition, thenScheduler, elseScheduler)
{
@@ -340,8 +354,6 @@ export class PsychoJS
* @param {string} [options.expName=UNKNOWN] - the name of the experiment
* @param {Object.<string, *>} [options.expInfo] - additional information about the experiment
* @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources
- * @async
- * @public
*/
async start({ configURL = "config.json", expName = "UNKNOWN", expInfo = {}, resources = [], dataFileName } = {})
{
@@ -452,7 +464,6 @@ 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
- * @public
*/
waitForResources(resources = [])
{
@@ -476,7 +487,6 @@ export class PsychoJS
* Make the attributes of the given object those of window, such that they become global.
*
* @param {Object.<string, *>} obj the object whose attributes are to become global
- * @public
*/
importAttributes(obj)
{
@@ -502,9 +512,7 @@ export class PsychoJS
*
* @param {Object} options
* @param {string} [options.message] - optional message to be displayed in a dialog box before quitting
- * @param {boolean} [options.isCompleted = false] - whether or not the participant has completed the experiment
- * @async
- * @public
+ * @param {boolean} [options.isCompleted = false] - whether the participant has completed the experiment
*/
async quit({ message, isCompleted = false } = {})
{
@@ -512,6 +520,7 @@ export class PsychoJS
this._experiment.experimentEnded = true;
this._status = PsychoJS.Status.FINISHED;
+ const isServerEnv = this.getEnvironment() === ExperimentHandler.Environment.SERVER;
try
{
@@ -519,28 +528,32 @@ export class PsychoJS
this._scheduler.stop();
// remove the beforeunload listener:
- if (this.getEnvironment() === ExperimentHandler.Environment.SERVER)
+ if (isServerEnv)
{
window.removeEventListener("beforeunload", this.beforeunloadCallback);
}
// save the results and the logs of the experiment:
- this.gui.dialog({
- warning: "Closing the session. Please wait a few moments.",
- showOK: false,
+ this.gui.finishDialog({
+ text: "Terminating the experiment. Please wait a few moments...",
+ nbSteps: 2 + ((isServerEnv) ? 1 : 0)
});
+
if (isCompleted || this._config.experiment.saveIncompleteResults)
{
if (!this._serverMsg.has("__noOutput"))
{
+ this.gui.finishDialogNextStep("saving results");
await this._experiment.save();
+ this.gui.finishDialogNextStep("saving logs");
await this._logger.flush();
}
}
// close the session:
- if (this.getEnvironment() === ExperimentHandler.Environment.SERVER)
+ if (isServerEnv)
{
+ this.gui.finishDialogNextStep("closing the session");
await this._serverManager.closeSession(isCompleted);
}
@@ -575,6 +588,7 @@ export class PsychoJS
}
},
});
+
}
catch (error)
{
@@ -586,7 +600,6 @@ export class PsychoJS
/**
* Configure PsychoJS for the running experiment.
*
- * @async
* @protected
* @param {string} configURL - the URL of the configuration file
* @param {string} name - the name of the experiment
@@ -737,7 +750,6 @@ export class PsychoJS
/**
* Capture all errors and display them in a pop-up error box.
- *
* @protected
*/
_captureErrors()
@@ -784,7 +796,7 @@ export class PsychoJS
/**
* Make the various Status top level, in order to accommodate PsychoPy's Code Components.
- * @private
+ * @protected
*/
_makeStatusTopLevel()
{
@@ -800,7 +812,6 @@ export class PsychoJS
*
* @enum {Symbol}
* @readonly
- * @public
*
* @note PsychoPy is currently moving away from STOPPED and replacing STOPPED by FINISHED.
* For backward compatibility reasons, we are keeping
@@ -824,19 +835,23 @@ PsychoJS.Status = {
+
+
+
@@ -30,8 +54,8 @@
* Window responsible for displaying the experiment stimuli
*
* @author Alain Pitiot
- * @version 2021.2.0
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @version 2022.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -46,20 +70,7 @@ import { Logger } from "./Logger.js";
* <p>Window displays the various stimuli of the experiment.</p>
* <p>It sets up a [PIXI]{@link http://www.pixijs.com/} renderer, which we use to render the experiment stimuli.</p>
*
- * @name module:core.Window
- * @class
* @extends PsychObject
- * @param {Object} options
- * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
- * @param {string} [options.name] the name of the window
- * @param {boolean} [options.fullscr= false] whether or not to go fullscreen
- * @param {Color} [options.color= Color('black')] the background color of the window
- * @param {number} [options.gamma= 1] sets the divisor for gamma correction. In other words gamma correction is calculated as pow(rgb, 1/gamma)
- * @param {number} [options.contrast= 1] sets the contrast value
- * @param {string} [options.units= 'pix'] the units of the window
- * @param {boolean} [options.waitBlanking= false] whether or not to wait for all rendering operations to be done
- * before flipping
- * @param {boolean} [options.autoLog= true] whether or not to log
*/
export class Window extends PsychObject
{
@@ -75,6 +86,20 @@ export class Window extends PsychObject
return 1.0 / this.getActualFrameRate();
}
+ /**
+ * @memberof module:core
+ * @param {Object} options
+ * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
+ * @param {string} [options.name] the name of the window
+ * @param {boolean} [options.fullscr= false] whether or not to go fullscreen
+ * @param {Color} [options.color= Color('black')] the background color of the window
+ * @param {number} [options.gamma= 1] sets the divisor for gamma correction. In other words gamma correction is calculated as pow(rgb, 1/gamma)
+ * @param {number} [options.contrast= 1] sets the contrast value
+ * @param {string} [options.units= 'pix'] the units of the window
+ * @param {boolean} [options.waitBlanking= false] whether or not to wait for all rendering operations to be done
+ * before flipping
+ * @param {boolean} [options.autoLog= true] whether or not to log
+ */
constructor({
psychoJS,
name,
@@ -104,7 +129,7 @@ export class Window extends PsychObject
this._addAttribute("fullscr", fullscr);
this._addAttribute("color", color, new Color("black"), () => {
if (this._backgroundSprite) {
- this._backgroundSprite.tint = color.int;
+ this._backgroundSprite.tint = this._color.int;
}
});
this._addAttribute("gamma", gamma, 1, () => {
@@ -152,10 +177,6 @@ export class Window extends PsychObject
* Close the window.
*
* <p> Note: this actually only removes the canvas used to render the experiment stimuli.</p>
- *
- * @name module:core.Window#close
- * @function
- * @public
*/
close()
{
@@ -189,9 +210,6 @@ export class Window extends PsychObject
/**
* Estimate the frame rate.
*
- * @name module:core.Window#getActualFrameRate
- * @function
- * @public
* @return {number} rAF based delta time based approximation, 60.0 by default
*/
getActualFrameRate()
@@ -205,10 +223,6 @@ export class Window extends PsychObject
/**
* Take the browser full screen if possible.
- *
- * @name module:core.Window#adjustScreenSize
- * @function
- * @public
*/
adjustScreenSize()
{
@@ -250,10 +264,6 @@ export class Window extends PsychObject
/**
* Take the browser back from full screen if needed.
- *
- * @name module:core.Window#closeFullScreen
- * @function
- * @public
*/
closeFullScreen()
{
@@ -293,9 +303,6 @@ export class Window extends PsychObject
*
* <p> Note: the message will be time-stamped at the next call to requestAnimationFrame.</p>
*
- * @name module:core.Window#logOnFlip
- * @function
- * @public
* @param {Object} options
* @param {String} options.msg the message to be logged
* @param {module:util.Logger.ServerLevel} [level = module:util.Logger.ServerLevel.EXP] the log level
@@ -322,9 +329,6 @@ export class Window extends PsychObject
*
* <p>This is typically used to reset a timer or clock.</p>
*
- * @name module:core.Window#callOnFlip
- * @function
- * @public
* @param {module:core.Window~OnFlipCallback} flipCallback - callback function.
* @param {...*} flipCallbackArgs - arguments for the callback function.
*/
@@ -335,10 +339,6 @@ export class Window extends PsychObject
/**
* Add PIXI.DisplayObject to the container displayed on the scene (window)
- *
- * @name module:core.Window#addPixiObject
- * @function
- * @public
*/
addPixiObject(pixiObject)
{
@@ -347,10 +347,6 @@ export class Window extends PsychObject
/**
* Remove PIXI.DisplayObject from the container displayed on the scene (window)
- *
- * @name module:core.Window#removePixiObject
- * @function
- * @public
*/
removePixiObject(pixiObject)
{
@@ -359,10 +355,6 @@ export class Window extends PsychObject
/**
* Render the stimuli onto the canvas.
- *
- * @name module:core.Window#render
- * @function
- * @public
*/
render()
{
@@ -406,9 +398,7 @@ export class Window extends PsychObject
/**
* Update this window, if need be.
*
- * @name module:core.Window#_updateIfNeeded
- * @function
- * @private
+ * @protected
*/
_updateIfNeeded()
{
@@ -417,6 +407,7 @@ export class Window extends PsychObject
if (this._renderer)
{
this._renderer.backgroundColor = this._color.int;
+ this._backgroundSprite.tint = this._color.int;
}
// we also change the background color of the body since
@@ -430,9 +421,7 @@ export class Window extends PsychObject
/**
* Recompute this window's draw list and _container children for the next animation frame.
*
- * @name module:core.Window#_refresh
- * @function
- * @private
+ * @protected
*/
_refresh()
{
@@ -454,9 +443,7 @@ export class Window extends PsychObject
/**
* Force an update of all stimuli in this window's drawlist.
*
- * @name module:core.Window#_fullRefresh
- * @function
- * @private
+ * @protected
*/
_fullRefresh()
{
@@ -476,9 +463,7 @@ export class Window extends PsychObject
* <p>A new renderer is created and a container is added to it. The renderer's touch and mouse events
* are handled by the {@link EventManager}.</p>
*
- * @name module:core.Window#_setupPixi
- * @function
- * @private
+ * @protected
*/
_setupPixi()
{
@@ -538,6 +523,8 @@ export class Window extends PsychObject
this._resizeCallback = (e) =>
{
Window._resizePixiRenderer(this, e);
+ this._backgroundSprite.width = this._size[0];
+ this._backgroundSprite.height = this._size[1];
this._fullRefresh();
};
window.addEventListener("resize", this._resizeCallback);
@@ -548,9 +535,7 @@ export class Window extends PsychObject
* Adjust the size of the renderer and the position of the root container
* in response to a change in the browser's size.
*
- * @name module:core.Window#_resizePixiRenderer
- * @function
- * @private
+ * @protected
* @param {module:core.Window} pjsWindow - the PsychoJS Window
* @param event
*/
@@ -579,9 +564,7 @@ export class Window extends PsychObject
/**
* Send all logged messages to the {@link Logger}.
*
- * @name module:core.Window#_writeLogOnFlip
- * @function
- * @private
+ * @protected
*/
_writeLogOnFlip()
{
@@ -601,19 +584,23 @@ export class Window extends PsychObject
+
+
+
@@ -30,8 +54,8 @@
* Experiment Handler
*
* @author Alain Pitiot
- * @version 2021.2.0
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @version 2022.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -45,22 +69,12 @@ import * as util from "../util/Util.js";
* for generating a single data file from an experiment with many different loops (e.g. interleaved
* staircases or loops within loops.</p>
*
- * @name module:data.ExperimentHandler
- * @class
* @extends PsychObject
- * @param {Object} options
- * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
- * @param {string} options.name - name of the experiment
- * @param {Object} options.extraInfo - additional information, such as session name, participant name, etc.
*/
export class ExperimentHandler extends PsychObject
{
/**
* Getter for experimentEnded.
- *
- * @name module:data.ExperimentHandler#experimentEnded
- * @function
- * @public
*/
get experimentEnded()
{
@@ -69,10 +83,6 @@ export class ExperimentHandler extends PsychObject
/**
* Setter for experimentEnded.
- *
- * @name module:data.ExperimentHandler#experimentEnded
- * @function
- * @public
*/
set experimentEnded(ended)
{
@@ -92,6 +102,13 @@ export class ExperimentHandler extends PsychObject
return this._trialsData;
}
+ /**
+ * @memberof module:data
+ * @param {Object} options
+ * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
+ * @param {string} options.name - name of the experiment
+ * @param {Object} options.extraInfo - additional information, such as session name, participant name, etc.
+ */
constructor({
psychoJS,
name,
@@ -139,9 +156,6 @@ export class ExperimentHandler extends PsychObject
* Whether or not the current entry (i.e. trial data) is empty.
* <p>Note: this is mostly useful at the end of an experiment, in order to ensure that the last entry is saved.</p>
*
- * @name module:data.ExperimentHandler#isEntryEmpty
- * @function
- * @public
* @returns {boolean} whether or not the current entry is empty
* @todo This really should be renamed: IsCurrentEntryNotEmpty
*/
@@ -156,9 +170,6 @@ export class ExperimentHandler extends PsychObject
* <p> The loop might be a {@link TrialHandler}, for instance.</p>
* <p> Data from this loop will be included in the resulting data files.</p>
*
- * @name module:data.ExperimentHandler#addLoop
- * @function
- * @public
* @param {Object} loop - the loop, e.g. an instance of TrialHandler or StairHandler
*/
addLoop(loop)
@@ -171,9 +182,6 @@ export class ExperimentHandler extends PsychObject
/**
* Remove the given loop from the list of unfinished loops, e.g. when it has completed.
*
- * @name module:data.ExperimentHandler#removeLoop
- * @function
- * @public
* @param {Object} loop - the loop, e.g. an instance of TrialHandler or StairHandler
*/
removeLoop(loop)
@@ -191,9 +199,6 @@ export class ExperimentHandler extends PsychObject
* <p> Multiple key/value pairs can be added to any given entry of the data file. There are
* considered part of the same entry until a call to {@link nextEntry} is made. </p>
*
- * @name module:data.ExperimentHandler#addData
- * @function
- * @public
* @param {Object} key - the key
* @param {Object} value - the value
*/
@@ -217,9 +222,6 @@ export class ExperimentHandler extends PsychObject
* Inform this ExperimentHandler that the current trial has ended. Further calls to {@link addData}
* will be associated with the next trial.
*
- * @name module:data.ExperimentHandler#nextEntry
- * @function
- * @public
* @param {Object | Object[] | undefined} snapshots - array of loop snapshots
*/
nextEntry(snapshots)
@@ -284,9 +286,6 @@ export class ExperimentHandler extends PsychObject
* </ul>
* <p>
*
- * @name module:data.ExperimentHandler#save
- * @function
- * @public
* @param {Object} options
* @param {Array.<Object>} [options.attributes] - the attributes to be saved
* @param {boolean} [options.sync=false] - whether or not to communicate with the server in a synchronous manner
@@ -408,9 +407,6 @@ export class ExperimentHandler extends PsychObject
* Get the attribute names and values for the current trial of a given loop.
* <p> Only info relating to the trial execution are returned.</p>
*
- * @name module:data.ExperimentHandler#_getLoopAttributes
- * @function
- * @static
* @protected
* @param {Object} loop - the loop
*/
@@ -470,10 +466,8 @@ export class ExperimentHandler extends PsychObject
/**
* Experiment result format
*
- * @name module:core.ServerManager#SaveFormat
* @enum {Symbol}
* @readonly
- * @public
*/
ExperimentHandler.SaveFormat = {
/**
@@ -492,7 +486,6 @@ ExperimentHandler.SaveFormat = {
*
* @enum {Symbol}
* @readonly
- * @public
*/
ExperimentHandler.Environment = {
SERVER: Symbol.for("SERVER"),
@@ -505,19 +498,23 @@ ExperimentHandler.Environment = {
+
+
/**
* Multiple Staircase Trial Handler
*
* @author Alain Pitiot
- * @version 2021.2.1
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd.
+ * @version 2021.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd.
* (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -50,27 +73,25 @@ import seedrandom from "seedrandom";
* <p>Note that, at the moment, using the MultiStairHandler requires the jsQuest.js
* library to be loaded as a resource, at the start of the experiment.</p>
*
- * @class module.data.MultiStairHandler
* @extends TrialHandler
- * @param {Object} options - the handler options
- * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
- * @param {string} options.varName - the name of the variable / intensity / contrast
- * / threshold manipulated by the staircases
- * @param {module:data.MultiStairHandler.StaircaseType} [options.stairType="simple"] - the
- * handler type
- * @param {Array.<Object> | String} [options.conditions= [undefined] ] - if it is a string,
- * we treat it as the name of a conditions resource
- * @param {module:data.TrialHandler.Method} options.method - the trial method
- * @param {number} [options.nTrials=50] - maximum number of trials
- * @param {number} options.randomSeed - seed for the random number generator
- * @param {string} options.name - name of the handler
- * @param {boolean} [options.autoLog= false] - whether or not to log
*/
export class MultiStairHandler extends TrialHandler
{
/**
- * @constructor
- * @public
+ * @memberof module:data
+ * @param {Object} options - the handler options
+ * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
+ * @param {string} options.varName - the name of the variable / intensity / contrast
+ * / threshold manipulated by the staircases
+ * @param {MultiStairHandler.StaircaseType} [options.stairType="simple"] - the
+ * handler type
+ * @param {Array.<Object> | String} [options.conditions= [undefined] ] - if it is a string,
+ * we treat it as the name of a conditions resource
+ * @param {module:data.TrialHandler.Method} options.method - the trial method
+ * @param {number} [options.nTrials=50] - maximum number of trials
+ * @param {number} options.randomSeed - seed for the random number generator
+ * @param {string} options.name - name of the handler
+ * @param {boolean} [options.autoLog= false] - whether or not to log
*/
constructor({
psychoJS,
@@ -119,10 +140,7 @@ export class MultiStairHandler extends TrialHandler
/**
* Get the current staircase.
*
- * @name module:data.MultiStairHandler#currentStaircase
- * @function
- * @public
- * @returns {module.data.TrialHandler} the current staircase, or undefined if the trial has ended
+ * @returns {TrialHandler} the current staircase, or undefined if the trial has ended
*/
get currentStaircase()
{
@@ -132,9 +150,6 @@ export class MultiStairHandler extends TrialHandler
/**
* Get the current intensity.
*
- * @name module:data.MultiStairHandler#intensity
- * @function
- * @public
* @returns {number} the intensity of the current staircase, or undefined if the trial has ended
*/
get intensity()
@@ -156,13 +171,9 @@ export class MultiStairHandler extends TrialHandler
/**
* Add a response to the current staircase.
*
- * @name module:data.MultiStairHandler#addResponse
- * @function
- * @public
* @param{number} response - the response to the trial, must be either 0 (incorrect or
* non-detected) or 1 (correct or detected)
* @param{number | undefined} [value] - optional intensity / contrast / threshold
- * @returns {void}
*/
addResponse(response, value)
{
@@ -191,10 +202,7 @@ export class MultiStairHandler extends TrialHandler
/**
* Validate the conditions.
*
- * @name module:data.MultiStairHandler#_validateConditions
- * @function
* @protected
- * @returns {void}
*/
_validateConditions()
{
@@ -250,10 +258,7 @@ export class MultiStairHandler extends TrialHandler
/**
* Setup the staircases, according to the conditions.
*
- * @name module:data.MultiStairHandler#_prepareStaircases
- * @function
* @protected
- * @returns {void}
*/
_prepareStaircases()
{
@@ -310,10 +315,7 @@ export class MultiStairHandler extends TrialHandler
/**
* Move onto the next trial.
*
- * @name module:data.MultiStairHandler#_nextTrial
- * @function
* @protected
- * @returns {void}
*/
_nextTrial()
{
@@ -453,7 +455,6 @@ export class MultiStairHandler extends TrialHandler
*
* @enum {Symbol}
* @readonly
- * @public
*/
MultiStairHandler.StaircaseType = {
/**
@@ -472,7 +473,6 @@ MultiStairHandler.StaircaseType = {
*
* @enum {Symbol}
* @readonly
- * @public
*/
MultiStairHandler.StaircaseStatus = {
/**
@@ -492,19 +492,23 @@ MultiStairHandler.StaircaseStatus = {
+
+
/**
* Quest Trial Handler
*
* @author Alain Pitiot & Thomas Pronk
- * @version 2021.2.0
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @version 2022.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -43,31 +66,29 @@ import {TrialHandler} from "./TrialHandler.js";
* <p>A Trial Handler that implements the Quest algorithm for quick measurement of
psychophysical thresholds. QuestHandler relies on the [jsQuest]{@link https://github.com/kurokida/jsQUEST} library, a port of Prof Dennis Pelli's QUEST algorithm by [Daiichiro Kuroki]{@link https://github.com/kurokida}.</p>
*
- * @class module.data.QuestHandler
* @extends TrialHandler
- * @param {Object} options - the handler options
- * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
- * @param {string} options.varName - the name of the variable / intensity / contrast / threshold manipulated by QUEST
- * @param {number} options.startVal - initial guess for the threshold
- * @param {number} options.startValSd - standard deviation of the initial guess
- * @param {number} options.minVal - minimum value for the threshold
- * @param {number} options.maxVal - maximum value for the threshold
- * @param {number} [options.pThreshold=0.82] - threshold criterion expressed as probability of getting a correct response
- * @param {number} options.nTrials - maximum number of trials
- * @param {number} options.stopInterval - minimum [5%, 95%] confidence interval required for the loop to stop
- * @param {module:data.QuestHandler.Method} options.method - the QUEST method
- * @param {number} [options.beta=3.5] - steepness of the QUEST psychometric function
- * @param {number} [options.delta=0.01] - fraction of trials with blind responses
- * @param {number} [options.gamma=0.5] - fraction of trails that would generate a correct response when the threshold is infinitely small
- * @param {number} [options.grain=0.01] - quantization of the internal table
- * @param {string} options.name - name of the handler
- * @param {boolean} [options.autoLog= false] - whether or not to log
*/
export class QuestHandler extends TrialHandler
{
/**
- * @constructor
- * @public
+ * @memberof module:data
+ * @param {Object} options - the handler options
+ * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
+ * @param {string} options.varName - the name of the variable / intensity / contrast / threshold manipulated by QUEST
+ * @param {number} options.startVal - initial guess for the threshold
+ * @param {number} options.startValSd - standard deviation of the initial guess
+ * @param {number} options.minVal - minimum value for the threshold
+ * @param {number} options.maxVal - maximum value for the threshold
+ * @param {number} [options.pThreshold=0.82] - threshold criterion expressed as probability of getting a correct response
+ * @param {number} options.nTrials - maximum number of trials
+ * @param {number} options.stopInterval - minimum [5%, 95%] confidence interval required for the loop to stop
+ * @param {QuestHandler.Method} options.method - the QUEST method
+ * @param {number} [options.beta=3.5] - steepness of the QUEST psychometric function
+ * @param {number} [options.delta=0.01] - fraction of trials with blind responses
+ * @param {number} [options.gamma=0.5] - fraction of trails that would generate a correct response when the threshold is infinitely small
+ * @param {number} [options.grain=0.01] - quantization of the internal table
+ * @param {string} options.name - name of the handler
+ * @param {boolean} [options.autoLog= false] - whether or not to log
*/
constructor({
psychoJS,
@@ -141,15 +162,11 @@ export class QuestHandler extends TrialHandler
/**
* Add a response and update the PDF.
*
- * @name module:data.QuestHandler#addResponse
- * @function
- * @public
* @param{number} response - the response to the trial, must be either 0 (incorrect or
* non-detected) or 1 (correct or detected)
* @param{number | undefined} value - optional intensity / contrast / threshold
* @param{boolean} [doAddData = true] - whether or not to add the response as data to the
* experiment
- * @returns {void}
*/
addResponse(response, value, doAddData = true)
{
@@ -191,9 +208,6 @@ export class QuestHandler extends TrialHandler
/**
* Simulate a response.
*
- * @name module:data.QuestHandler#simulate
- * @function
- * @public
* @param{number} trueValue - the true, known value of the threshold / contrast / intensity
* @returns{number} the simulated response, 0 or 1
*/
@@ -212,9 +226,6 @@ export class QuestHandler extends TrialHandler
/**
* Get the mean of the Quest posterior PDF.
*
- * @name module:data.QuestHandler#mean
- * @function
- * @public
* @returns {number} the mean
*/
mean()
@@ -225,9 +236,6 @@ export class QuestHandler extends TrialHandler
/**
* Get the standard deviation of the Quest posterior PDF.
*
- * @name module:data.QuestHandler#sd
- * @function
- * @public
* @returns {number} the standard deviation
*/
sd()
@@ -238,9 +246,6 @@ export class QuestHandler extends TrialHandler
/**
* Get the mode of the Quest posterior PDF.
*
- * @name module:data.QuestHandler#mode
- * @function
- * @public
* @returns {number} the mode
*/
mode()
@@ -252,9 +257,6 @@ export class QuestHandler extends TrialHandler
/**
* Get the standard deviation of the Quest posterior PDF.
*
- * @name module:data.QuestHandler#quantile
- * @function
- * @public
* @param{number} quantileOrder the quantile order
* @returns {number} the quantile
*/
@@ -266,9 +268,6 @@ export class QuestHandler extends TrialHandler
/**
* Get the current value of the variable / contrast / threshold.
*
- * @name module:data.QuestHandler#getQuestValue
- * @function
- * @public
* @returns {number} the current QUEST value for the variable / contrast / threshold
*/
getQuestValue()
@@ -281,9 +280,6 @@ export class QuestHandler extends TrialHandler
*
* <p>This is the getter associated to getQuestValue.</p>
*
- * @name module:data.MultiStairHandler#intensity
- * @function
- * @public
* @returns {number} the intensity of the current staircase, or undefined if the trial has ended
*/
get intensity()
@@ -294,9 +290,6 @@ export class QuestHandler extends TrialHandler
/**
* Get an estimate of the 5%-95% confidence interval (CI).
*
- * @name module:data.QuestHandler#confInterval
- * @function
- * @public
* @param{boolean} [getDifference=false] - if true, return the width of the CI instead of the CI
* @returns{number[] | number} the 5%-95% CI or the width of the CI
*/
@@ -320,10 +313,7 @@ export class QuestHandler extends TrialHandler
/**
* Setup the JS Quest object.
*
- * @name module:data.QuestHandler#_setupJsQuest
- * @function
* @protected
- * @returns {void}
*/
_setupJsQuest()
{
@@ -341,10 +331,7 @@ export class QuestHandler extends TrialHandler
* Estimate the next value of the QUEST variable, based on the current value
* and on the selected QUEST method.
*
- * @name module:data.QuestHandler#_estimateQuestValue
- * @function
* @protected
- * @returns {void}
*/
_estimateQuestValue()
{
@@ -415,7 +402,6 @@ export class QuestHandler extends TrialHandler
*
* @enum {Symbol}
* @readonly
- * @public
*/
QuestHandler.Method = {
/**
@@ -440,19 +426,23 @@ QuestHandler.Method = {
+
+
/**
* Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the
- * server, and be accessed and manipulated in a concurrent fashion.
+ * server, and can be accessed and manipulated in a concurrent fashion.
*
* @author Alain Pitiot
+ * @version 2021.2.3
* @copyright (c) 2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -44,27 +68,25 @@ import {Scheduler} from "../util/Scheduler.js";
/**
* <p>Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the
- * server, and be accessed and manipulated in a concurrent fashion.</p>
+ * server, and can be accessed and manipulated in a concurrent fashion.</p>
*
- * <p></p>
- *
- * @name module:data.Shelf
- * @class
* @extends PsychObject
- * @param {Object} options
- * @param {module:core.PsychoJS} options.psychoJS the PsychoJS instance
- * @param {boolean} [options.autoLog= false] whether to log
*/
export class Shelf extends PsychObject
{
/**
* Maximum number of components in a key
- * @name module:data.Shelf.#MAX_KEY_LENGTH
* @type {number}
* @note this value should mirror that on the server, i.e. the server also checks that the key is valid
*/
static #MAX_KEY_LENGTH = 10;
+ /**
+ * @memberOf module:data
+ * @param {Object} options
+ * @param {module:core.PsychoJS} options.psychoJS the PsychoJS instance
+ * @param {boolean} [options.autoLog= false] whether to log
+ */
constructor({psychoJS, autoLog = false } = {})
{
super(psychoJS);
@@ -84,9 +106,6 @@ export class Shelf extends PsychObject
/**
* Get the value of a record of type BOOLEAN associated with the given key.
*
- * @name module:data.Shelf#getBooleanValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {boolean} options.defaultValue the default value returned if no record with the given key exists
@@ -103,9 +122,6 @@ export class Shelf extends PsychObject
/**
* Set the value of a record of type BOOLEAN associated with the given key.
*
- * @name module:data.Shelf#setBooleanValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {boolean} options.value the new value
@@ -136,9 +152,6 @@ export class Shelf extends PsychObject
/**
* Flip the value of a record of type BOOLEAN associated with the given key.
*
- * @name module:data.Shelf#flipBooleanValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @return {Promise<boolean>} the new, flipped, value
@@ -157,9 +170,6 @@ export class Shelf extends PsychObject
/**
* Get the value of a record of type INTEGER associated with the given key.
*
- * @name module:data.Shelf#getIntegerValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {number} options.defaultValue the default value returned if no record with the given key
@@ -176,9 +186,6 @@ export class Shelf extends PsychObject
/**
* Set the value of a record of type INTEGER associated with the given key.
*
- * @name module:data.Shelf#setIntegerValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {number} options.value the new value
@@ -209,9 +216,6 @@ export class Shelf extends PsychObject
/**
* Add a delta to the value of a record of type INTEGER associated with the given key.
*
- * @name module:data.Shelf#addIntegerValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {number} options.delta the delta, positive or negative, to add to the value
@@ -242,9 +246,6 @@ export class Shelf extends PsychObject
/**
* Get the value of a record of type TEXT associated with the given key.
*
- * @name module:data.Shelf#getTextValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {string} options.defaultValue the default value returned if no record with the given key exists on
@@ -261,9 +262,6 @@ export class Shelf extends PsychObject
/**
* Set the value of a record of type TEXT associated with the given key.
*
- * @name module:data.Shelf#setTextValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {string} options.value the new value
@@ -294,9 +292,6 @@ export class Shelf extends PsychObject
/**
* Get the value of a record of type LIST associated with the given key.
*
- * @name module:data.Shelf#getListValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {Array.<*>} options.defaultValue the default value returned if no record with the given key exists on
@@ -313,9 +308,6 @@ export class Shelf extends PsychObject
/**
* Set the value of a record of type LIST associated with the given key.
*
- * @name module:data.Shelf#setListValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {Array.<*>} options.value the new value
@@ -346,9 +338,6 @@ export class Shelf extends PsychObject
/**
* Append an element, or a list of elements, to the value of a record of type LIST associated with the given key.
*
- * @name module:data.Shelf#appendListValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {*} options.elements the element or list of elements to be appended
@@ -370,9 +359,6 @@ export class Shelf extends PsychObject
* Pop an element, at the given index, from the value of a record of type LIST associated
* with the given key.
*
- * @name module:data.Shelf#popListValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {number} [options.index = -1] the index of the element to be popped
@@ -393,9 +379,6 @@ export class Shelf extends PsychObject
/**
* Empty the value of a record of type LIST associated with the given key.
*
- * @name module:data.Shelf#clearListValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @return {Promise<Array.<*>>} the new, empty value, i.e. []
@@ -414,9 +397,6 @@ export class Shelf extends PsychObject
/**
* Shuffle the elements of the value of a record of type LIST associated with the given key.
*
- * @name module:data.Shelf#shuffleListValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @return {Promise<Array.<*>>} the new, shuffled value
@@ -436,9 +416,6 @@ export class Shelf extends PsychObject
/**
* Get the names of the fields in the dictionary record associated with the given key.
*
- * @name module:data.Shelf#getDictionaryFieldNames
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @return {Promise<string[]>} the list of field names
@@ -453,9 +430,6 @@ export class Shelf extends PsychObject
/**
* Get the value of a given field in the dictionary record associated with the given key.
*
- * @name module:data.Shelf#getDictionaryFieldValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {string} options.fieldName the name of the field
@@ -473,9 +447,6 @@ export class Shelf extends PsychObject
/**
* Set a field in the dictionary record associated to the given key.
*
- * @name module:data.Shelf#setDictionaryFieldValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {string} options.fieldName the name of the field
@@ -498,9 +469,6 @@ export class Shelf extends PsychObject
/**
* Get the value of a record of type DICTIONARY associated with the given key.
*
- * @name module:data.Shelf#getDictionaryValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {Object.<string, *>} options.defaultValue the default value returned if no record with the given key
@@ -517,9 +485,6 @@ export class Shelf extends PsychObject
/**
* Set the value of a record of type DICTIONARY associated with the given key.
*
- * @name module:data.Shelf#setDictionaryValue
- * @function
- * @public
* @param {Object} options
* @param {string[]} options.key key as an array of key components
* @param {Object.<string, *>} options.value the new value
@@ -551,9 +516,6 @@ export class Shelf extends PsychObject
* Schedulable component that will block the experiment until the counter associated with the given key
* has been incremented by the given amount.
*
- * @name module:data.Shelf#incrementComponent
- * @function
- * @public
* @param key
* @param increment
* @param callback
@@ -604,15 +566,14 @@ export class Shelf extends PsychObject
/**
* Get the name of a group, using a counterbalanced design.
*
- * @name module:data.Shelf#counterBalanceSelect
- * @function
- * @public
- * @param {string[]} key key as an array of key components
- * @param {string[]} groups the names of the groups
- * @param {number[]} groupSizes the size of the groups
- * @return {Promise<any>}
+ * @param {Object} options
+ * @param {string[]} options.key key as an array of key components
+ * @param {string[]} options.groups the names of the groups
+ * @param {number[]} options.groupSizes the size of the groups
+ * @return {Promise<{string, boolean}>} an object with the name of the selected group and whether all groups
+ * have been depleted
*/
- async counterBalanceSelect(key, groups, groupSizes)
+ async counterBalanceSelect({key, groups, groupSizes} = {})
{
const response = {
origin: 'Shelf.counterBalanceSelect',
@@ -625,7 +586,6 @@ export class Shelf extends PsychObject
this._checkKey(key);
// prepare the request:
- // const componentList = key.reduce((list, component) => list + '+' + component, '');
const url = `${this._psychoJS.config.pavlovia.URL}/api/v2/shelf/${this._psychoJS.config.session.token}/counterbalance`;
const data = {
key,
@@ -634,7 +594,7 @@ export class Shelf extends PsychObject
};
// query the server:
- const response = await fetch(url, {
+ const putResponse = await fetch(url, {
method: 'PUT',
mode: 'cors',
cache: 'no-cache',
@@ -648,16 +608,19 @@ export class Shelf extends PsychObject
});
// convert the response to json:
- const document = await response.json();
+ const document = await putResponse.json();
- if (response.status !== 200)
+ if (putResponse.status !== 200)
{
throw ('error' in document) ? document.error : document;
}
// return the updated value:
this._status = Shelf.Status.READY;
- return [ document.group, document.finished ];
+ return {
+ group: document.group,
+ finished: document.finished
+ };
}
catch (error)
{
@@ -672,9 +635,6 @@ export class Shelf extends PsychObject
*
* <p>This is a generic method, typically called from the Shelf helper methods, e.g. setBinaryValue.</p>
*
- * @name module:data.Shelf#_updateValue
- * @function
- * @protected
* @param {string[]} key key as an array of key components
* @param {Shelf.Type} type the type of the record associated with the given key
* @param {*} update the desired update
@@ -703,7 +663,7 @@ export class Shelf extends PsychObject
};
// query the server:
- const response = await fetch(url, {
+ const postResponse = await fetch(url, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
@@ -717,9 +677,9 @@ export class Shelf extends PsychObject
});
// convert the response to json:
- const document = await response.json();
+ const document = await postResponse.json();
- if (response.status !== 200)
+ if (postResponse.status !== 200)
{
throw ('error' in document) ? document.error : document;
}
@@ -740,9 +700,6 @@ export class Shelf extends PsychObject
*
* <p>This is a generic method, typically called from the Shelf helper methods, e.g. getBinaryValue.</p>
*
- * @name module:data.Shelf#_getValue
- * @function
- * @protected
* @param {string[]} key key as an array of key components
* @param {Shelf.Type} type the type of the record associated with the given key
* @param {Object} [options] the options, e.g. the default value returned if no record with the
@@ -782,7 +739,7 @@ export class Shelf extends PsychObject
}
// query the server:
- const response = await fetch(url, {
+ const putResponse = await fetch(url, {
method: 'PUT',
mode: 'cors',
cache: 'no-cache',
@@ -795,9 +752,9 @@ export class Shelf extends PsychObject
body: JSON.stringify(data)
});
- const document = await response.json();
+ const document = await putResponse.json();
- if (response.status !== 200)
+ if (putResponse.status !== 200)
{
throw ('error' in document) ? document.error : document;
}
@@ -818,11 +775,8 @@ export class Shelf extends PsychObject
*
* <p>Since all Shelf methods call _checkAvailability, we also use it as a means to throttle those calls.</p>
*
- * @name module:data.Shelf#_checkAvailability
- * @function
- * @public
- * @param {string} [methodName=""] name of the method requiring a check
- * @throws {Object.<string, *>} exception if it is not possible to run the given shelf command
+ * @param {string} [methodName=""] - name of the method requiring a check
+ * @throws {Object.<string, *>} exception if it is not possible to run the given shelf command
*/
_checkAvailability(methodName = "")
{
@@ -871,9 +825,6 @@ export class Shelf extends PsychObject
/**
* Check the validity of the key.
*
- * @name module:data.Shelf#_checkKey
- * @function
- * @public
* @param {object} key key whose validity is to be checked
* @throws {Object.<string, *>} exception if the key is invalid
*/
@@ -898,10 +849,8 @@ export class Shelf extends PsychObject
/**
* Shelf status
*
- * @name module:data.Shelf#Status
* @enum {Symbol}
* @readonly
- * @public
*/
Shelf.Status = {
/**
@@ -925,7 +874,6 @@ Shelf.Status = {
*
* @enum {Symbol}
* @readonly
- * @public
*/
Shelf.Type = {
INTEGER: Symbol.for('INTEGER'),
@@ -941,19 +889,23 @@ Shelf.Type = {
+
+
+
@@ -32,8 +56,8 @@
*
* @author Alain Pitiot
* @author Hiroyuki Sogo & Sotiri Bakagiannis - better support for BOM and accented characters
- * @version 2021.2.0
- * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @version 2022.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
@@ -45,25 +69,12 @@ import * as util from "../util/Util.js";
/**
* <p>A Trial Handler handles the importing and sequencing of conditions.</p>
*
- * @class
* @extends PsychObject
- * @param {Object} options - the handler options
- * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
- * @param {Array.<Object> | String} [options.trialList= [undefined] ] - if it is a string, we treat it as the name of a condition resource
- * @param {number} options.nReps - number of repetitions
- * @param {module:data.TrialHandler.Method} options.method - the trial method
- * @param {Object} options.extraInfo - additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc.
- * @param {number} options.seed - seed for the random number generator
- * @param {boolean} [options.autoLog= false] - whether or not to log
*/
export class TrialHandler extends PsychObject
{
/**
* Getter for experimentHandler.
- *
- * @name module:core.Window#experimentHandler
- * @function
- * @public
*/
get experimentHandler()
{
@@ -72,10 +83,6 @@ export class TrialHandler extends PsychObject
/**
* Setter for experimentHandler.
- *
- * @name module:core.Window#experimentHandler
- * @function
- * @public
*/
set experimentHandler(exp)
{
@@ -83,8 +90,14 @@ export class TrialHandler extends PsychObject
}
/**
- * @constructor
- * @public
+ * @param {Object} options - the handler options
+ * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
+ * @param {Array.<Object> | String} [options.trialList= [undefined] ] - if it is a string, we treat it as the name of a condition resource
+ * @param {number} options.nReps - number of repetitions
+ * @param {module:data.TrialHandler.Method} options.method - the trial method
+ * @param {Object} options.extraInfo - additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc.
+ * @param {number} options.seed - seed for the random number generator
+ * @param {boolean} [options.autoLog= false] - whether or not to log
*
* @todo extraInfo is not taken into account, we use the expInfo of the ExperimentHandler instead
*/
@@ -251,7 +264,6 @@ export class TrialHandler extends PsychObject
*
* <p>This is typically used in the LoopBegin function, in order to capture the current state of a TrialHandler</p>
*
- * @public
* @return {Snapshot} - a snapshot of the current internal state.
*/
getSnapshot()
@@ -324,8 +336,6 @@ export class TrialHandler extends PsychObject
/**
* Set the internal state of the snapshot's trial handler from the snapshot.
*
- * @public
- * @static
* @param {Snapshot} snapshot - the snapshot from which to update the current internal state of the
* snapshot's trial handler
*/
@@ -393,7 +403,6 @@ export class TrialHandler extends PsychObject
/**
* Get the trial index.
*
- * @public
* @return {number} the current trial index
*/
getTrialIndex()
@@ -417,7 +426,6 @@ export class TrialHandler extends PsychObject
* <p>Note: we assume that all trials in the trialList share the same attributes
* and consequently consider only the attributes of the first trial.</p>
*
- * @public
* @return {Array.string} the attributes
*/
getAttributes()
@@ -439,7 +447,6 @@ export class TrialHandler extends PsychObject
/**
* Get the current trial.
*
- * @public
* @return {Object} the current trial
*/
getCurrentTrial()
@@ -466,7 +473,6 @@ export class TrialHandler extends PsychObject
/**
* Get the nth future or past trial, without advancing through the trial list.
*
- * @public
* @param {number} [n = 1] - increment
* @return {Object|undefined} the future trial (if n is positive) or past trial (if n is negative)
* or undefined if attempting to go beyond the last trial.
@@ -485,7 +491,6 @@ export class TrialHandler extends PsychObject
* Get the nth previous trial.
* <p> Note: this is useful for comparisons in n-back tasks.</p>
*
- * @public
* @param {number} [n = -1] - increment
* @return {Object|undefined} the past trial or undefined if attempting to go prior to the first trial.
*/
@@ -497,7 +502,6 @@ export class TrialHandler extends PsychObject
/**
* Add a key/value pair to data about the current trial held by the experiment handler
*
- * @public
* @param {Object} key - the key
* @param {Object} value - the value
*/
@@ -536,8 +540,6 @@ export class TrialHandler extends PsychObject
* '5:'
* '-5:-2, 9, 11:5:22'
*
- * @public
- * @static
* @param {module:core.ServerManager} serverManager - the server manager
* @param {String} resourceName - the name of the resource containing the list of conditions, which must have been registered with the server manager.
* @param {Object} [selection = null] - the selection
@@ -645,7 +647,6 @@ export class TrialHandler extends PsychObject
/**
* Prepare the trial list.
*
- * @function
* @protected
* @returns {void}
*/
@@ -683,7 +684,7 @@ export class TrialHandler extends PsychObject
}
}
- /*
+ /**
* Prepare the sequence of trials.
*
* <p>The returned sequence is a matrix (an array of arrays) of trial indices
@@ -708,7 +709,7 @@ export class TrialHandler extends PsychObject
* </p>
*
* @protected
- */
+ **/
_prepareSequence()
{
const response = {
@@ -766,7 +767,6 @@ export class TrialHandler extends PsychObject
*
* @enum {Symbol}
* @readonly
- * @public
*/
TrialHandler.Method = {
/**
@@ -796,19 +796,23 @@ TrialHandler.Method = {
+
+
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/docs/fonts/Montserrat/Montserrat-Bold.eot b/docs/fonts/Montserrat/Montserrat-Bold.eot
new file mode 100644
index 0000000..f2970bb
Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Bold.eot differ
diff --git a/docs/fonts/Montserrat/Montserrat-Bold.ttf b/docs/fonts/Montserrat/Montserrat-Bold.ttf
new file mode 100644
index 0000000..3bfd79b
Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Bold.ttf differ
diff --git a/docs/fonts/Montserrat/Montserrat-Bold.woff b/docs/fonts/Montserrat/Montserrat-Bold.woff
new file mode 100644
index 0000000..9260765
Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Bold.woff differ
diff --git a/docs/fonts/Montserrat/Montserrat-Bold.woff2 b/docs/fonts/Montserrat/Montserrat-Bold.woff2
new file mode 100644
index 0000000..d9940cd
Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Bold.woff2 differ
diff --git a/docs/fonts/Montserrat/Montserrat-Regular.eot b/docs/fonts/Montserrat/Montserrat-Regular.eot
new file mode 100644
index 0000000..735d12b
Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Regular.eot differ
diff --git a/docs/fonts/Montserrat/Montserrat-Regular.ttf b/docs/fonts/Montserrat/Montserrat-Regular.ttf
new file mode 100644
index 0000000..5da852a
Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Regular.ttf differ
diff --git a/docs/fonts/Montserrat/Montserrat-Regular.woff b/docs/fonts/Montserrat/Montserrat-Regular.woff
new file mode 100644
index 0000000..bf91832
Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Regular.woff differ
diff --git a/docs/fonts/Montserrat/Montserrat-Regular.woff2 b/docs/fonts/Montserrat/Montserrat-Regular.woff2
new file mode 100644
index 0000000..72d13c6
Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Regular.woff2 differ
diff --git a/docs/fonts/OpenSans-Bold-webfont.eot b/docs/fonts/OpenSans-Bold-webfont.eot
deleted file mode 100644
index 5d20d91..0000000
Binary files a/docs/fonts/OpenSans-Bold-webfont.eot and /dev/null differ
diff --git a/docs/fonts/OpenSans-Bold-webfont.svg b/docs/fonts/OpenSans-Bold-webfont.svg
deleted file mode 100644
index 3ed7be4..0000000
--- a/docs/fonts/OpenSans-Bold-webfont.svg
+++ /dev/null
@@ -1,1830 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/fonts/OpenSans-Bold-webfont.woff b/docs/fonts/OpenSans-Bold-webfont.woff
deleted file mode 100644
index 1205787..0000000
Binary files a/docs/fonts/OpenSans-Bold-webfont.woff and /dev/null differ
diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.eot b/docs/fonts/OpenSans-BoldItalic-webfont.eot
deleted file mode 100644
index 1f639a1..0000000
Binary files a/docs/fonts/OpenSans-BoldItalic-webfont.eot and /dev/null differ
diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.svg b/docs/fonts/OpenSans-BoldItalic-webfont.svg
deleted file mode 100644
index 6a2607b..0000000
--- a/docs/fonts/OpenSans-BoldItalic-webfont.svg
+++ /dev/null
@@ -1,1830 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.woff b/docs/fonts/OpenSans-BoldItalic-webfont.woff
deleted file mode 100644
index ed760c0..0000000
Binary files a/docs/fonts/OpenSans-BoldItalic-webfont.woff and /dev/null differ
diff --git a/docs/fonts/OpenSans-Italic-webfont.eot b/docs/fonts/OpenSans-Italic-webfont.eot
deleted file mode 100644
index 0c8a0ae..0000000
Binary files a/docs/fonts/OpenSans-Italic-webfont.eot and /dev/null differ
diff --git a/docs/fonts/OpenSans-Italic-webfont.svg b/docs/fonts/OpenSans-Italic-webfont.svg
deleted file mode 100644
index e1075dc..0000000
--- a/docs/fonts/OpenSans-Italic-webfont.svg
+++ /dev/null
@@ -1,1830 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/fonts/OpenSans-Italic-webfont.woff b/docs/fonts/OpenSans-Italic-webfont.woff
deleted file mode 100644
index ff652e6..0000000
Binary files a/docs/fonts/OpenSans-Italic-webfont.woff and /dev/null differ
diff --git a/docs/fonts/OpenSans-Light-webfont.eot b/docs/fonts/OpenSans-Light-webfont.eot
deleted file mode 100644
index 1486840..0000000
Binary files a/docs/fonts/OpenSans-Light-webfont.eot and /dev/null differ
diff --git a/docs/fonts/OpenSans-Light-webfont.svg b/docs/fonts/OpenSans-Light-webfont.svg
deleted file mode 100644
index 11a472c..0000000
--- a/docs/fonts/OpenSans-Light-webfont.svg
+++ /dev/null
@@ -1,1831 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/fonts/OpenSans-Light-webfont.woff b/docs/fonts/OpenSans-Light-webfont.woff
deleted file mode 100644
index e786074..0000000
Binary files a/docs/fonts/OpenSans-Light-webfont.woff and /dev/null differ
diff --git a/docs/fonts/OpenSans-LightItalic-webfont.eot b/docs/fonts/OpenSans-LightItalic-webfont.eot
deleted file mode 100644
index 8f44592..0000000
Binary files a/docs/fonts/OpenSans-LightItalic-webfont.eot and /dev/null differ
diff --git a/docs/fonts/OpenSans-LightItalic-webfont.svg b/docs/fonts/OpenSans-LightItalic-webfont.svg
deleted file mode 100644
index 431d7e3..0000000
--- a/docs/fonts/OpenSans-LightItalic-webfont.svg
+++ /dev/null
@@ -1,1835 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/fonts/OpenSans-LightItalic-webfont.woff b/docs/fonts/OpenSans-LightItalic-webfont.woff
deleted file mode 100644
index 43e8b9e..0000000
Binary files a/docs/fonts/OpenSans-LightItalic-webfont.woff and /dev/null differ
diff --git a/docs/fonts/OpenSans-Regular-webfont.eot b/docs/fonts/OpenSans-Regular-webfont.eot
deleted file mode 100644
index 6bbc3cf..0000000
Binary files a/docs/fonts/OpenSans-Regular-webfont.eot and /dev/null differ
diff --git a/docs/fonts/OpenSans-Regular-webfont.svg b/docs/fonts/OpenSans-Regular-webfont.svg
deleted file mode 100644
index 25a3952..0000000
--- a/docs/fonts/OpenSans-Regular-webfont.svg
+++ /dev/null
@@ -1,1831 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/docs/fonts/OpenSans-Regular-webfont.woff b/docs/fonts/OpenSans-Regular-webfont.woff
deleted file mode 100644
index e231183..0000000
Binary files a/docs/fonts/OpenSans-Regular-webfont.woff and /dev/null differ
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot
new file mode 100644
index 0000000..0f24510
Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot differ
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg
new file mode 100644
index 0000000..5384f98
--- /dev/null
+++ b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg
@@ -0,0 +1,978 @@
+
+
+
\ No newline at end of file
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf
new file mode 100644
index 0000000..e6c158c
Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf differ
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff
new file mode 100644
index 0000000..d0a1c29
Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff differ
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2
new file mode 100644
index 0000000..d286974
Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 differ
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot
new file mode 100644
index 0000000..b420448
Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot differ
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg
new file mode 100644
index 0000000..dee0949
--- /dev/null
+++ b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg
@@ -0,0 +1,1049 @@
+
+
+
\ No newline at end of file
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf
new file mode 100644
index 0000000..4d56c33
Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf differ
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff
new file mode 100644
index 0000000..4681019
Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff differ
diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2
new file mode 100644
index 0000000..8ddcae3
Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 differ
diff --git a/docs/hardware_Camera.js.html b/docs/hardware_Camera.js.html
new file mode 100644
index 0000000..917b8f8
--- /dev/null
+++ b/docs/hardware_Camera.js.html
@@ -0,0 +1,724 @@
+
+
+
+
+
+ hardware/Camera.js - PsychoJS API
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
hardware/Camera.js
+
+
+
+
+
+
+
+
+
+
/** **/
+/**
+ * Manager handling the recording of video signal.
+ *
+ * @author Alain Pitiot
+ * @version 2022.2.0
+ * @copyright (c) 2022 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @license Distributed under the terms of the MIT License
+ */
+
+import {Clock} from "../util/Clock.js";
+import {PsychObject} from "../util/PsychObject.js";
+import {PsychoJS} from "../core/PsychoJS.js";
+import * as util from "../util/Util.js";
+import {ExperimentHandler} from "../data/ExperimentHandler.js";
+// import {VideoClip} from "./VideoClip";
+
+
+/**
+ * <p>This manager handles the recording of video signal.</p>
+ *
+ * @name module:hardware.Camera
+ * @class
+ * @param {Object} options
+ * @param {module:core.Window} options.win - the associated Window
+ * @param {string} [options.format='video/webm;codecs=vp9'] the video format
+ * @param {Clock} [options.clock= undefined] - an optional clock
+ * @param {boolean} [options.autoLog= false] - whether or not to log
+ *
+ * @todo add video constraints as parameter
+ */
+export class Camera extends PsychObject
+{
+ constructor({win, name, format, clock, autoLog} = {})
+ {
+ super(win._psychoJS);
+
+ this._addAttribute("win", win, undefined);
+ this._addAttribute("name", name, "camera");
+ this._addAttribute("format", format, "video/webm;codecs=vp9", this._onChange);
+ this._addAttribute("clock", clock, new Clock());
+ this._addAttribute("autoLog", autoLog, false);
+ this._addAttribute("status", PsychoJS.Status.NOT_STARTED);
+
+ this._stream = null;
+ this._recorder = null;
+
+ if (this._autoLog)
+ {
+ this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`);
+ }
+ }
+
+ /**
+ * Prompt the user for permission to use the camera on their device.
+ *
+ * @name module:hardware.Camera#authorize
+ * @function
+ * @public
+ * @param {boolean} [showDialog=false] - whether to open a dialog box to inform the
+ * participant to wait for the camera to be initialised
+ * @param {string} [dialogMsg] - the dialog message
+ * @returns {boolean} whether or not the camera is ready to record
+ */
+ async authorize(showDialog = false, dialogMsg = undefined)
+ {
+ const response = {
+ origin: "Camera.authorize",
+ context: "when authorizing access to the device's camera"
+ };
+
+ // open pop-up dialog, if required:
+ if (showDialog)
+ {
+ dialogMsg ??= "Please wait a few moments while the camera initialises. You may need to grant permission to your browser to use the camera.";
+ this.psychoJS.gui.dialog({
+ warning: dialogMsg,
+ showOK: false,
+ });
+ }
+
+ try
+ {
+ // prompt for permission and get a MediaStream:
+ // TODO use size constraints [https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia]
+ this._stream = await navigator.mediaDevices.getUserMedia({
+ video: true
+ });
+ }
+ catch (error)
+ {
+ // close the dialog, if need be:
+ if (showDialog)
+ {
+ this.psychoJS.gui.closeDialog();
+ }
+
+ this._status = PsychoJS.Status.ERROR;
+ throw {...response, error};
+ }
+
+ // close the dialog, if need be:
+ if (showDialog)
+ {
+ this.psychoJS.gui.closeDialog();
+ }
+ }
+
+ /**
+ * Query whether the camera is ready to record.
+ *
+ * @name module:hardware.Camera#isReady
+ * @function
+ * @public
+ * @returns {boolean} true if the camera is ready to record, false otherwise
+ */
+ get isReady()
+ {
+ return (this._recorder !== null);
+ }
+
+ /**
+ * Get the underlying video stream.
+ *
+ * @name module:hardware.Camera#getStream
+ * @function
+ * @public
+ * @returns {MediaStream} the video stream
+ */
+ getStream()
+ {
+ return this._stream;
+ }
+
+ /**
+ * Get a video element pointing to the Camera stream.
+ *
+ * @name module:hardware.Camera#getVideo
+ * @function
+ * @public
+ * @returns {HTMLVideoElement} a video element
+ */
+ getVideo()
+ {
+ // note: we need to return a new video each time, since the camera feed can be used by
+ // several stimuli and one of them might pause the feed
+
+ // create a video with the appropriate size:
+ const video = document.createElement("video");
+ this._videos.push(video);
+
+ video.width = this._streamSettings.width;
+ video.height = this._streamSettings.height;
+ video.autoplay = true;
+
+ // prevent clicking:
+ video.onclick = (mouseEvent) =>
+ {
+ mouseEvent.preventDefault();
+ return false;
+ };
+
+ // use the camera stream as source for the video:
+ video.srcObject = this._stream;
+
+ return video;
+ }
+
+ /**
+ * Open the video stream.
+ *
+ * @name module:hardware.Camera#open
+ * @function
+ * @public
+ */
+ open()
+ {
+ if (this._stream === null)
+ {
+ throw {
+ origin: "Camera.open",
+ context: "when opening the camera's video stream",
+ error: "access to the camera has not been authorized, or no camera could be found"
+ };
+ }
+
+ // prepare the recording:
+ this._prepareRecording();
+ }
+
+ /**
+ * Submit a request to start the recording.
+ *
+ * @name module:hardware.Camera#record
+ * @function
+ * @public
+ * @return {Promise} promise fulfilled when the recording actually starts
+ */
+ record()
+ {
+ // if the camera is currently paused, a call to start resumes it
+ // with a new recording:
+ if (this._status === PsychoJS.Status.PAUSED)
+ {
+ return this.resume({clear: true});
+ }
+
+ if (this._status !== PsychoJS.Status.STARTED)
+ {
+ this._psychoJS.logger.debug("request to start video recording");
+
+ try
+ {
+ if (!this._recorder)
+ {
+ throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record video";
+ }
+
+ this._recorder.start();
+
+ // return a promise, which will be satisfied when the recording actually starts, which
+ // is also when the reset of the clock and the change of status takes place
+ const self = this;
+ return new Promise((resolve, reject) =>
+ {
+ self._startCallback = resolve;
+ self._errorCallback = reject;
+ });
+ }
+ catch (error)
+ {
+ this._psychoJS.logger.error("unable to start the video recording: " + JSON.stringify(error));
+ this._status = PsychoJS.Status.ERROR;
+
+ throw {
+ origin: "Camera.record",
+ context: "when starting the video recording for camera: " + this._name,
+ error
+ };
+ }
+
+ }
+
+ }
+
+ /**
+ * Submit a request to stop the recording.
+ *
+ * @name module:hardware.Camera#stop
+ * @function
+ * @public
+ * @param {Object} options
+ * @return {Promise} promise fulfilled when the recording actually stopped, and the recorded
+ * data was made available
+ */
+ stop()
+ {
+ if (this._status === PsychoJS.Status.STARTED || this._status === PsychoJS.Status.PAUSED)
+ {
+ this._psychoJS.logger.debug("request to stop video recording");
+
+ // stop the videos:
+ for (const video of this._videos)
+ {
+ video.pause();
+ }
+
+ // note: calling the MediaRecorder.stop will first raise a dataavailable event, and then a stop event
+ // ref: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/stop
+ this._recorder.stop();
+
+ // return a promise, which will be satisfied when the recording actually stops and the data
+ // has been made available:
+ const self = this;
+ return new Promise((resolve, reject) =>
+ {
+ self._stopCallback = resolve;
+ self._errorCallback = reject;
+ });
+ }
+ }
+
+ /**
+ * Submit a request to pause the recording.
+ *
+ * @name module:hardware.Camera#pause
+ * @function
+ * @public
+ * @return {Promise} promise fulfilled when the recording actually paused
+ */
+ pause()
+ {
+ if (this._status === PsychoJS.Status.STARTED)
+ {
+ this._psychoJS.logger.debug("request to pause video recording");
+
+ try
+ {
+ if (!this._recorder)
+ {
+ throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record video";
+ }
+
+ // note: calling the pause method of the MediaRecorder raises a pause event
+ this._recorder.pause();
+
+ // return a promise, which will be satisfied when the recording actually pauses:
+ const self = this;
+ return new Promise((resolve, reject) =>
+ {
+ self._pauseCallback = resolve;
+ self._errorCallback = reject;
+ });
+ }
+ catch (error)
+ {
+ self._psychoJS.logger.error("unable to pause the video recording: " + JSON.stringify(error));
+ this._status = PsychoJS.Status.ERROR;
+
+ throw {
+ origin: "Camera.pause",
+ context: "when pausing the video recording for camera: " + this._name,
+ error
+ };
+ }
+
+ }
+ }
+
+ /**
+ * Submit a request to resume the recording.
+ *
+ * <p>resume has no effect if the recording was not previously paused.</p>
+ *
+ * @name module:hardware.Camera#resume
+ * @function
+ * @param {Object} options
+ * @param {boolean} [options.clear= false] whether or not to empty the video buffer before
+ * resuming the recording
+ * @return {Promise} promise fulfilled when the recording actually resumed
+ */
+ resume({clear = false } = {})
+ {
+ if (this._status === PsychoJS.Status.PAUSED)
+ {
+ this._psychoJS.logger.debug("request to resume video recording");
+
+ try
+ {
+ if (!this._recorder)
+ {
+ throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record video";
+ }
+
+ // empty the audio buffer is needed:
+ if (clear)
+ {
+ this._audioBuffer = [];
+ this._videoBuffer.length = 0;
+ }
+
+ this._recorder.resume();
+
+ // return a promise, which will be satisfied when the recording actually resumes:
+ const self = this;
+ return new Promise((resolve, reject) =>
+ {
+ self._resumeCallback = resolve;
+ self._errorCallback = reject;
+ });
+ }
+ catch (error)
+ {
+ self._psychoJS.logger.error("unable to resume the video recording: " + JSON.stringify(error));
+ this._status = PsychoJS.Status.ERROR;
+
+ throw {
+ origin: "Camera.resume",
+ context: "when resuming the video recording for camera: " + this._name,
+ error
+ };
+ }
+
+ }
+ }
+
+ /**
+ * Submit a request to flush the recording.
+ *
+ * @name module:hardware.Camera#flush
+ * @function
+ * @public
+ * @return {Promise} promise fulfilled when the data has actually been made available
+ */
+ flush()
+ {
+ if (this._status === PsychoJS.Status.STARTED || this._status === PsychoJS.Status.PAUSED)
+ {
+ this._psychoJS.logger.debug("request to flush video recording");
+
+ // note: calling the requestData method of the MediaRecorder will raise a
+ // dataavailable event
+ // ref: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/requestData
+ this._recorder.requestData();
+
+ // return a promise, which will be satisfied when the data has been made available:
+ const self = this;
+ return new Promise((resolve, reject) =>
+ {
+ self._dataAvailableCallback = resolve;
+ self._errorCallback = reject;
+ });
+ }
+ }
+
+ /**
+ * Get the current video recording as a VideoClip in the given format.
+ *
+ * @name module:hardware.Camera#getRecording
+ * @function
+ * @public
+ * @param {string} tag an optional tag for the video clip
+ * @param {boolean} [flush=false] whether or not to first flush the recording
+ */
+ async getRecording({tag, flush = false} = {})
+ {
+ // default tag: the name of this Microphone object
+ if (typeof tag === "undefined")
+ {
+ tag = this._name;
+ }
+
+ // TODO
+ }
+
+ /**
+ * Upload the video recording to the pavlovia server.
+ *
+ * @name module:hardware.Camera#_upload
+ * @function
+ * @protected
+ * @param {string} tag an optional tag for the video file
+ * @param {boolean} [waitForCompletion= false] whether to wait for completion
+ * before returning
+ * @param {boolean} [showDialog=false] - whether to open a dialog box to inform the participant to wait for the data to be uploaded to the server
+ * @param {string} [dialogMsg=""] - default message informing the participant to wait for the data to be uploaded to the server
+ */
+ save({tag, waitForCompletion = false, showDialog = false, dialogMsg = ""} = {})
+ {
+ this._psychoJS.logger.info("[PsychoJS] Save video recording.");
+
+ // default tag: the name of this Camera object
+ if (typeof tag === "undefined")
+ {
+ tag = this._name;
+ }
+
+ // add a format-dependent video extension to the tag:
+ tag += util.extensionFromMimeType(this._format);
+
+ // if the video recording cannot be uploaded, e.g. the experiment is running locally, or
+ // if it is piloting mode, then we offer the video recording as a file for download:
+ if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER ||
+ this._psychoJS.config.experiment.status !== "RUNNING" ||
+ this._psychoJS._serverMsg.has("__pilotToken"))
+ {
+ const videoBlob = new Blob(this._videoBuffer);
+
+ const anchor = document.createElement("a");
+ anchor.href = window.URL.createObjectURL(videoBlob);
+ anchor.download = tag;
+ document.body.appendChild(anchor);
+ anchor.click();
+ document.body.removeChild(anchor);
+
+ return;
+ }
+
+ // upload the blob:
+ const videoBlob = new Blob(this._videoBuffer);
+ return this._psychoJS.serverManager.uploadAudioVideo({
+ mediaBlob: videoBlob,
+ tag,
+ waitForCompletion,
+ showDialog,
+ dialogMsg});
+ }
+
+ /**
+ * Close the camera stream.
+ *
+ * @name module:hardware.Camera#close
+ * @function
+ * @public
+ * @returns {Promise<void>} promise fulfilled when the stream has stopped and is now closed
+ */
+ async close()
+ {
+ await this.stop();
+
+ this._videos = [];
+ this._stream = null;
+ this._recorder = null;
+ }
+
+ /**
+ * Callback for changes to the recording settings.
+ *
+ * <p>Changes to the settings require the recording to stop and be re-started.</p>
+ *
+ * @name module:hardware.Camera#_onChange
+ * @function
+ * @protected
+ */
+ _onChange()
+ {
+ if (this._status === PsychoJS.Status.STARTED)
+ {
+ this.stop();
+ }
+
+ this._prepareRecording();
+
+ this.start();
+ }
+
+ /**
+ * Prepare the recording.
+ *
+ * @name module:hardware.Camera#_prepareRecording
+ * @function
+ * @protected
+ */
+ _prepareRecording()
+ {
+ // empty the video buffer:
+ this._videoBuffer = [];
+ this._recorder = null;
+ this._videos = [];
+
+ // check the actual width and height:
+ this._streamSettings = this._stream.getVideoTracks()[0].getSettings();
+ this._psychoJS.logger.debug(`camera stream settings: ${JSON.stringify(this._streamSettings)}`);
+
+ // check that the specified format is supported, use default if it is not:
+ let options;
+ if (typeof this._format === "string" && MediaRecorder.isTypeSupported(this._format))
+ {
+ options = { type: this._format };
+ }
+ else
+ {
+ this._psychoJS.logger.warn(`The specified video format, ${this._format}, is not supported by this browser, using the default format instead`);
+ }
+
+ // create a video recorder:
+ this._recorder = new MediaRecorder(this._stream, options);
+
+ // setup the callbacks:
+ const self = this;
+
+ // called upon Camera.start(), at which point the audio data starts being gathered
+ // into a blob:
+ this._recorder.onstart = () =>
+ {
+ self._videoBuffer = [];
+ self._videoBuffer.length = 0;
+ self._clock.reset();
+ self._status = PsychoJS.Status.STARTED;
+ self._psychoJS.logger.debug("video recording started");
+
+ // resolve the Camera.start promise:
+ if (self._startCallback)
+ {
+ self._startCallback(self._psychoJS.monotonicClock.getTime());
+ }
+ };
+
+ // called upon Camera.pause():
+ this._recorder.onpause = () =>
+ {
+ self._status = PsychoJS.Status.PAUSED;
+ self._psychoJS.logger.debug("video recording paused");
+
+ // resolve the Camera.pause promise:
+ if (self._pauseCallback)
+ {
+ self._pauseCallback(self._psychoJS.monotonicClock.getTime());
+ }
+ };
+
+ // called upon Camera.resume():
+ this._recorder.onresume = () =>
+ {
+ self._status = PsychoJS.Status.STARTED;
+ self._psychoJS.logger.debug("video recording resumed");
+
+ // resolve the Camera.resume promise:
+ if (self._resumeCallback)
+ {
+ self._resumeCallback(self._psychoJS.monotonicClock.getTime());
+ }
+ };
+
+ // called when video data is available, typically upon Camera.stop() or Camera.flush():
+ this._recorder.ondataavailable = (event) =>
+ {
+ const data = event.data;
+
+ // add data to the buffer:
+ self._videoBuffer.push(data);
+ self._psychoJS.logger.debug("video data added to the buffer");
+
+ // resolve the data available promise, if needed:
+ if (self._dataAvailableCallback)
+ {
+ self._dataAvailableCallback(self._psychoJS.monotonicClock.getTime());
+ }
+ };
+
+ // called upon Camera.stop(), after data has been made available:
+ this._recorder.onstop = () =>
+ {
+ self._psychoJS.logger.debug("video recording stopped");
+ self._status = PsychoJS.Status.STOPPED;
+
+ // resolve the Camera.stop promise:
+ if (self._stopCallback)
+ {
+ self._stopCallback(self._psychoJS.monotonicClock.getTime());
+ }
+ };
+
+ // called upon recording errors:
+ this._recorder.onerror = (event) =>
+ {
+ // TODO
+ self._psychoJS.logger.error("video recording error: " + JSON.stringify(event));
+ self._status = PsychoJS.Status.ERROR;
+ };
+ }
+
+}
+
+
+
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.
-
You can create online experiments from the PsychoPy Builder, you can find and adapt existing experiments on pavlovia.org, or create them from scratch: the PsychoJS API is available here.
+
You can create online experiments from the PsychoPy Builder, you can find and adapt existing experiments on pavlovia.org, or create them from scratch.
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.
- Utility class used by the experiment scripts to keep track of a clock and of the current status (whether or not we are currently checking the keyboard)
-
- Get the list of keys pressed by the participant.
-
-
Note: The w3c key-event viewer can be used to see possible values for the items in the keyList given the user's keyboard and chosen layout. The "key" and "code" columns in the UI Events fields are the relevant values for the keyList argument.
-
-
-
-
-
-
-
-
-
-
-
Parameters:
-
-
-
-
-
-
-
Name
-
-
-
Type
-
-
-
-
-
-
Description
-
-
-
-
-
-
-
-
-
options
-
-
-
-
-
-Object
-
-
-
-
-
-
-
-
-
-
-
Properties
-
-
-
-
-
-
-
Name
-
-
-
Type
-
-
-
Attributes
-
-
-
-
Default
-
-
-
Description
-
-
-
-
-
-
-
-
-
keyList
-
-
-
-
-
-Array.<string>
-
-
-
-
-
-
-
-
- <optional>
-
-
-
-
-
-
-
-
-
-
-
- null
-
-
-
-
-
keyList allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If no keyList is given, all keys will be checked and the key buffer will be cleared completely.
-
-
-
-
-
-
-
timeStamped
-
-
-
-
-
-boolean
-
-
-
-
-
-
-
-
- <optional>
-
-
-
-
-
-
-
-
-
-
-
- false
-
-
-
-
-
If true will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time).
Create a dialog box that (a) enables the participant to set some
-experimental values (e.g. the session name), (b) shows progress of resource
-download, and (c) enables the participant to cancel the experiment.
-
-Setting experiment values
-
DlgFromDict displays an input field for all values in the dictionary.
-It is possible to specify default values e.g.:
- Get the list of those keyboard events still in the buffer, i.e. those that have not been
-previously cleared by calls to getKeys with clear = true.
-
- Get the list of keys pressed or pushed by the participant.
-
-
-
-
-
-
-
-
-
-
-
Parameters:
-
-
-
-
-
-
-
Name
-
-
-
Type
-
-
-
-
-
-
Description
-
-
-
-
-
-
-
-
-
options
-
-
-
-
-
-Object
-
-
-
-
-
-
-
-
-
-
-
Properties
-
-
-
-
-
-
-
Name
-
-
-
Type
-
-
-
Attributes
-
-
-
-
Default
-
-
-
Description
-
-
-
-
-
-
-
-
-
keyList
-
-
-
-
-
-Array.<string>
-
-
-
-
-
-
-
-
- <optional>
-
-
-
-
-
-
-
-
-
-
-
- []
-
-
-
-
-
the list of keys to consider. If keyList is empty, we consider all keys.
-Note that we use pyglet keys here, to make the PsychoJs code more homogeneous with PsychoPy.
-
-
-
-
-
-
-
waitRelease
-
-
-
-
-
-boolean
-
-
-
-
-
-
-
-
- <optional>
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
-
-
whether or not to include those keys pressed but not released. If
-waitRelease = false, key presses without a corresponding key release will have an undefined duration.
-
-
-
-
-
-
-
clear
-
-
-
-
-
-boolean
-
-
-
-
-
-
-
-
- <optional>
-
-
-
-
-
-
-
-
-
-
-
- false
-
-
-
-
-
whether or not to keep in the buffer the key presses or pushes for a subsequent call to getKeys. If a keyList has been given and clear = true, we only remove from the buffer those keys in keyList
- Get the status of each button (pressed or released) and, optionally, the time elapsed between the last call to clickReset and the pressing or releasing of the buttons.
-
-
Note: clickReset is typically called at stimulus onset. When the participant presses a button, the time elapsed since the clickReset is stored internally and can be accessed any time afterwards with getPressed.
- either an array of size 3 with the status (1 for pressed, 0 for released) of each mouse button [left, center, right], or a tuple with that array and another array of size 3 with the timestamps.
-
-
-
-
-
-
- Type
-
-
-
-Array.number
-|
-
-Array.<Array.number>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
getRel() → {Array.number}
-
-
-
-
-
-
-
- Get the position of the mouse relative to that at the last call to getRel
-or getPos, in mouse/Window units.
-
- Whether button pressed is contained within stimulus.
-
-
-
-
-
-
- Type
-
-
-
-boolean
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
mouseMoved(distanceopt, resetopt) → {boolean}
-
-
-
-
-
-
-
- Determine whether the mouse has moved beyond a certain distance.
-
-
distance
-
-
mouseMoved() or mouseMoved(undefined, false): determine whether the mouse has moved at all since the last
-call to getPos
-
mouseMoved(distance: number, false): determine whether the mouse has travelled further than distance, in terms of line of sight
-
mouseMoved(distance: [number,number], false): determine whether the mouse has travelled horizontally or vertically further then the given horizontal and vertical distances
-
-
-
reset
-
-
mouseMoved(distance, true): reset the mouse move clock, return false
-
mouseMoved(distance, 'here'): return false
-
mouseMoved(distance, [x: number, y: number]: artifically set the previous mouse position to the given coordinates and determine whether the mouse moved further than the given distance
PsychoJS manages the lifecycle of an experiment. It initialises the PsychoJS library and its various components (e.g. the ServerManager, the EventManager), and is used by the experiment to schedule the various tasks.
+ PsychoJS
+
+
+
PsychoJS initialises the library and its various components (e.g. the ServerManager, the EventManager), and manages
+the lifecycle of an experiment.
- Close everything and exit nicely at the end of the experiment,
-potentially redirecting to one of the URLs previously specified by setRedirectUrls.
+
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
@@ -2435,6 +2529,8 @@ that he or she needs to wait for a bit.
+
+
Parameters:
@@ -2549,12 +2645,12 @@ that he or she needs to wait for a bit.
- config.json
+ config.json
-
the URL of the configuration file
+
the URL of the configuration file
@@ -2588,12 +2684,12 @@ that he or she needs to wait for a bit.
- UNKNOWN
+ UNKNOWN
-
the name of the experiment
+
the name of the experiment
@@ -2630,7 +2726,7 @@ that he or she needs to wait for a bit.
-
additional information about the experiment
+
additional information about the experiment
@@ -2671,12 +2767,12 @@ that he or she needs to wait for a bit.
- []
+ []
-
the list of resources
+
the list of resources
@@ -2686,52 +2782,6 @@ that he or she needs to wait for a bit.
-
-
-
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.
-
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://.
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.
An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful
-for generating a single data file from an experiment with many different loops (e.g. interleaved
-staircases or loops within loops.