1
0
mirror of https://github.com/psychopy/psychojs.git synced 2025-05-10 10:40:54 +00:00

Merge branch 'main' into bf#348--no-moment

This commit is contained in:
Alain Pitiot 2021-06-15 14:49:10 +02:00 committed by GitHub
commit 6ecb96b561
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 161 additions and 207 deletions

View File

@ -3,7 +3,8 @@
name: Automated Test (full)
on:
schedule:
- cron: '0 0 * * sat'
# Temporarily disabled so the stager test logs aren't cleaned up
# - cron: '0 0 * * sat'
workflow_dispatch:
inputs:
repo:

149
index.css
View File

@ -1,149 +0,0 @@
/* project and resource dialogs */
label, input, select {
display: block;
padding-bottom: .5em;
}
input.text, select.text {
margin-bottom: 1em;
width: 95%;
padding: .5em;
}
fieldset {
padding: 0;
border: 0;
margin-top: 1em;
}
a, a:active, a:focus, a:visited {
outline: 0;
color: #007EB7;
}
a:hover {
color: #000000;
}
.progress {
padding: .5em 0 .5em 0;
}
.logo {
display: block;
margin-left: auto;
margin-right: auto;
max-width: 100%;
margin-bottom: 1em;
}
/* don't display close button in the top right corner of the box */
.no-close .ui-dialog-titlebar-close {
display: none;
}
.ui-dialog-content {
margin-top: 1em;
}
/* for mobile phones only */
@media only screen and (max-width: 1080px) {
.ui-widget {
-ms-transform: scale(2);
-webkit-transform: scale(2);
transform: scale(2);
}
.ui-widget .ui-progressbar {
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
}
.ui-dialog .ui-dialog-buttonpane {
padding-top: 1em;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button {
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
}
.ui-dialog .ui-dialog-titlebar {
padding: 1em 2em;
}
.ui-dialog-titlebar .ui-button {
margin-right: 1em;
}
.ui-dialog-titlebar .ui-dialog-titlebar-close {
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
}
}
@media only screen and (max-width: 1080px) and (orientation:landscape) {
.ui-widget {
-ms-transform: scale(1.5);
-webkit-transform: scale(1.5);
transform: scale(1.5);
}
.ui-widget .ui-progressbar {
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
}
.ui-dialog .ui-dialog-buttonpane {
padding-top: 1em;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button {
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
}
.ui-dialog .ui-dialog-titlebar {
padding: 1em 2em;
}
.ui-dialog-titlebar .ui-button {
margin-right: 1em;
}
.ui-dialog-titlebar .ui-dialog-titlebar-close {
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
}
}
/* Initialisation message (which will disappear behind the canvas) */
#root:after {
content: "initialising the experiment...";
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* Initialisation message for IE11 */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
#root:after {
content: "initialising the experiment... | Internet Explorer / Edge [beta]";
color: #A05000;
font-weight: bold;
}
}

View File

@ -1,5 +0,0 @@
export * as util from './js/util/index.js';
export * as core from './js/core/index.js';
export * as data from './js/data/index.js';
export * as visual from './js/visual/index.js';
export * as sound from './js/sound/index.js';

20
package-lock.json generated
View File

@ -1,18 +1,18 @@
{
"name": "psychojs",
"version": "2021.x",
"version": "2021.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "2021.x",
"name": "psychojs",
"version": "2021.2.0",
"license": "MIT",
"dependencies": {
"howler": "^2.2.1",
"log4javascript": "github:Ritzlgrmft/log4javascript",
"pako": "^1.0.10",
"pixi.js-legacy": "^6.0.4",
"preload-js": "^0.6.3",
"seedrandom": "^3.0.5",
"tone": "^14.7.77",
"xlsx": "^0.17.0"
@ -1706,7 +1706,7 @@
},
"node_modules/log4javascript": {
"version": "1.4.16",
"resolved": "git+ssh://git@github.com/Ritzlgrmft/log4javascript.git#d27efb927c3c47ce9d747263427905d16ded2f2c"
"resolved": "git+https://git@github.com/Ritzlgrmft/log4javascript.git#d27efb927c3c47ce9d747263427905d16ded2f2c"
},
"node_modules/lru-cache": {
"version": "6.0.0",
@ -1970,11 +1970,6 @@
"url": "https://opencollective.com/pixijs"
}
},
"node_modules/preload-js": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/preload-js/-/preload-js-0.6.3.tgz",
"integrity": "sha1-3LS3wOGC27ziZ3rU8s+u4uJbTfc="
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -3805,7 +3800,7 @@
"dev": true
},
"log4javascript": {
"version": "git+ssh://git@github.com/Ritzlgrmft/log4javascript.git#d27efb927c3c47ce9d747263427905d16ded2f2c",
"version": "git+https://git@github.com/Ritzlgrmft/log4javascript.git#d27efb927c3c47ce9d747263427905d16ded2f2c",
"from": "log4javascript@github:Ritzlgrmft/log4javascript"
},
"lru-cache": {
@ -4022,11 +4017,6 @@
"pixi.js": "6.0.4"
}
},
"preload-js": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/preload-js/-/preload-js-0.6.3.tgz",
"integrity": "sha1-3LS3wOGC27ziZ3rU8s+u4uJbTfc="
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "psychojs",
"version": "2021.x",
"version": "2021.2.0",
"private": true,
"description": "Helps run in-browser neuroscience, psychology, and psychophysics experiments",
"license": "MIT",
@ -30,7 +30,6 @@
"log4javascript": "github:Ritzlgrmft/log4javascript",
"pako": "^1.0.10",
"pixi.js-legacy": "^6.0.4",
"preload-js": "^0.6.3",
"seedrandom": "^3.0.5",
"tone": "^14.7.77",
"xlsx": "^0.17.0"

View File

@ -106,7 +106,7 @@ export class MinimalStim extends PsychObject
if (typeof this._pixi === 'undefined')
{
this.psychoJS.logger.warn('the Pixi.js representation of this stimulus is undefined.');
}// throw Object.assign(response, { error: 'the PIXI representation of the stimulus is unavailable'});
}
else
{
this.win._rootContainer.addChild(this._pixi);

View File

@ -743,8 +743,15 @@ export class PsychoJS
};
window.onunhandledrejection = function (error)
{
console.error(error.reason);
self._gui.dialog({error: error.reason});
console.error(error?.reason);
if (error?.reason?.stack === undefined) {
// No stack? Error thrown by PsychoJS; stringify whole error
document.body.setAttribute('data-error', JSON.stringify(error?.reason));
} else {
// Yes stack? Error thrown by JS; stringify stack
document.body.setAttribute('data-error', JSON.stringify(error?.reason?.stack));
}
self._gui.dialog({error: error?.reason});
return true;
};
}

View File

@ -7,7 +7,6 @@
* @license Distributed under the terms of the MIT License
*/
import createjs from 'preload-js';
import { Howl } from 'howler';
import {PsychoJS} from './PsychoJS';
import {PsychObject} from '../util/PsychObject';
@ -1045,7 +1044,7 @@ export class ServerManager extends PsychObject
manifest.push(/*new createjs.LoadItem().set(*/{
id: name,
src: pathStatusData.path,
type: createjs.LoadQueue.BINARY, //createjs.Types.BINARY,
type: createjs.Types.BINARY,
crossOrigin: 'Anonymous'
}/*)*/);
}

View File

@ -359,7 +359,8 @@ export class Window extends PsychObject
this._renderer.backgroundColor = this._color.int;
}
// we also change the background color of the body since the dialog popup may be longer than the window's height:
// we also change the background color of the body since
// the dialog popup may be longer than the window's height:
document.body.style.backgroundColor = this._color.hex;
this._needUpdate = false;
@ -378,7 +379,8 @@ export class Window extends PsychObject
{
this._updateIfNeeded();
// if a stimuli needs to be updated, we remove it from the window container, update it, then put it back
// if a stimuli needs to be updated, we remove it from the window container,
// update it, then put it back
for (const stimulus of this._drawList)
{
if (stimulus._needUpdate && typeof stimulus._pixi !== 'undefined')

View File

@ -80,12 +80,11 @@ export class TrialHandler extends PsychObject
this._addAttribute('nReps', nReps);
this._addAttribute('method', method);
this._addAttribute('extraInfo', extraInfo);
this._addAttribute('seed', seed);
this._addAttribute('name', name);
this._addAttribute('autoLog', autoLog);
this._addAttribute('seed', seed);
this._prepareTrialList(trialList);
// number of stimuli
this.nStim = this.trialList.length;
@ -212,6 +211,7 @@ export class TrialHandler extends PsychObject
/**
* @typedef {Object} Snapshot
* @property {TrialHandler} handler - the trialHandler
* @property {string} name - the trialHandler name
* @property {number} nStim - the number of stimuli
* @property {number} nTotal - the total number of trials that will be run
@ -237,6 +237,7 @@ export class TrialHandler extends PsychObject
const currentIndex = this.thisIndex;
const snapshot = {
handler: this,
name: this.name,
nStim: this.nStim,
nTotal: this.nTotal,
@ -250,6 +251,8 @@ export class TrialHandler extends PsychObject
getCurrentTrial: () => this.getTrial(currentIndex),
getTrial: (index = 0) => this.getTrial(index),
addData: (key, value) => this.addData(key, value)
};
this._snapshots.push(snapshot);
@ -257,6 +260,63 @@ export class TrialHandler extends PsychObject
return snapshot;
}
/**
* Setter for the seed attribute.
*
* @param {boolean} newSeed - New value for seed
*/
setSeed(seed, log)
{
this._setAttribute('seed', seed, log);
if (typeof this.seed !== 'undefined')
{
this._randomNumberGenerator = seedrandom(this.seed);
}
else
{
this._randomNumberGenerator = seedrandom();
}
}
/**
* Set the internal state of this trial handler from the given snapshot.
*
* @public
* @static
* @param {Snapshot} snapshot - the snapshot from which to update the current internal state.
*/
static fromSnapshot(snapshot)
{
// if snapshot is undefined, do nothing:
if (typeof snapshot === 'undefined')
{
return;
}
snapshot.handler.nStim = snapshot.nStim;
snapshot.handler.nTotal = snapshot.nTotal;
snapshot.handler.nRemaining = snapshot.nRemaining;
snapshot.handler.thisRepN = snapshot.thisRepN;
snapshot.handler.thisTrialN = snapshot.thisTrialN;
snapshot.handler.thisN = snapshot.thisN;
snapshot.handler.thisIndex = snapshot.thisIndex;
snapshot.handler.ran = snapshot.ran;
snapshot.handler._finished = snapshot._finished;
snapshot.handler.thisTrial = snapshot.handler.getCurrentTrial();
}
/**
* Getter for the finished attribute.
*
* @returns {boolean} whether or not the trial has finished.
*/
get finished()
{
return this._finished;
}
/**
* Setter for the finished attribute.
@ -616,16 +676,6 @@ export class TrialHandler extends PsychObject
// get an array of the indices of the elements of trialList :
const indices = Array.from(this.trialList.keys());
// seed the random number generator:
if (typeof (this.seed) !== 'undefined')
{
seedrandom(this.seed);
}
else
{
seedrandom();
}
if (this.method === TrialHandler.Method.SEQUENTIAL)
{
this._trialSequence = Array(this.nReps).fill(indices);
@ -638,7 +688,7 @@ export class TrialHandler extends PsychObject
this._trialSequence = [];
for (let i = 0; i < this.nReps; ++i)
{
this._trialSequence.push(util.shuffle(indices.slice()));
this._trialSequence.push(util.shuffle(indices.slice(), this._randomNumberGenerator));
}
}
@ -652,7 +702,7 @@ export class TrialHandler extends PsychObject
}
// shuffle the sequence:
util.shuffle(flatSequence);
util.shuffle(flatSequence, this._randomNumberGenerator);
// reshape it into the trialSequence:
this._trialSequence = [];

View File

@ -200,7 +200,18 @@ export class AudioClip extends PsychObject
this._psychoJS.logger.debug('speech.googleapis.com response:', JSON.stringify(decodedResponse));
// TODO deal with more than one results and/or alternatives
resolve(decodedResponse.results[0].alternatives[0]);
if (('results' in decodedResponse) && (decodedResponse.results.length > 0))
{
resolve(decodedResponse.results[0].alternatives[0]);
}
else
{
// no transcription available:
resolve({
transcript: '',
confidence: -1
});
}
});
}

View File

@ -21,7 +21,7 @@ import {AudioClip} from "./AudioClip";
* @class
* @param {Object} options
* @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
* @param {String} options.name - the name used when logging messages
* @param @param {module:core.Window} options.win - the associated Window
* @param {string} [options.format='audio/webm;codecs=opus'] the format for the audio file
* @param {number} [options.sampleRateHz= 48000] - the audio sampling rate, in Hz
* @param {Clock} [options.clock= undefined] - an optional clock
@ -30,10 +30,11 @@ import {AudioClip} from "./AudioClip";
export class Microphone extends PsychObject
{
constructor({psychoJS, name, format, sampleRateHz, clock, autoLog} = {})
constructor({win, name, format, sampleRateHz, clock, autoLog} = {})
{
super(psychoJS);
super(win._psychoJS);
this._addAttribute('win', win, undefined);
this._addAttribute('name', name, 'microphone');
this._addAttribute('format', format, 'audio/webm;codecs=opus', this._onChange);
this._addAttribute('sampleRateHz', sampleRateHz, 48000, this._onChange);

View File

@ -340,13 +340,17 @@ export function IsPointInsidePolygon(point, vertices)
* @function
* @public
* @param {Object[]} array - the input 1-D array
* @param {Function} [randomNumberGenerator = undefined] - A function used to generated random numbers in the interal [0, 1). Defaults to Math.random
* @return {Object[]} the shuffled array
*/
export function shuffle(array)
export function shuffle(array, randomNumberGenerator = undefined)
{
if (randomNumberGenerator === undefined) {
randomNumberGenerator = Math.random;
}
for (let i = array.length - 1; i > 0; i--)
{
const j = Math.floor(Math.random() * (i + 1));
const j = Math.floor(randomNumberGenerator() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;

View File

@ -923,7 +923,9 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._visual.stimuliTotalHeight = stimulusOffset;
// scrollbar:
// scrollbar
// note: we add this Form as a dependent stimulus such that the Form is redrawn whenever
// the slider is updated
this._scrollbar = new Slider({
win: this._win,
name: 'scrollbar',
@ -934,6 +936,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
size: [this._scrollbarWidth, this._size[1]],
style: [Slider.Style.SLIDER],
ticks: [0, -this._visual.stimuliTotalHeight / this._size[1]],
dependentStims: [this]
});
this._prevScrollbarMarkerPos = 0;
this._scrollbar.setMarkerPos(this._prevScrollbarMarkerPos);
@ -968,7 +971,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
}
this._needUpdate = false;
// calculate the edges of the form and various other sizes, in various units:
this._leftEdge = this._pos[0] - this._size[0] / 2.0;
this._rightEdge = this._pos[0] + this._size[0] / 2.0;

View File

@ -59,6 +59,9 @@ import {PsychoJS} from "../core/PsychoJS";
* frame flip
* @param {boolean} [options.autoLog= false] - whether or not to log
*
* @param {core.MinimalStim[]} [options.dependentStims = [] ] - the list of dependent stimuli,
* which must be updated when this Slider is updated, e.g. a Form.
*
* @todo check that parameters are valid, e.g. ticks are an array of numbers, etc.
* @todo readOnly
* @todo complete setters, for instance setTicks should change this._isCategorical
@ -66,7 +69,7 @@ import {PsychoJS} from "../core/PsychoJS";
*/
export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
{
constructor({name, win, pos, size, ori, units, color, markerColor, contrast, opacity, style, ticks, labels, granularity, flip, readOnly, font, bold, italic, fontSize, compact, clipMask, autoDraw, autoLog} = {})
constructor({name, win, pos, size, ori, units, color, markerColor, contrast, opacity, style, ticks, labels, granularity, flip, readOnly, font, bold, italic, fontSize, compact, clipMask, autoDraw, autoLog, dependentStims} = {})
{
super({name, win, units, ori, opacity, pos, size, clipMask, autoDraw, autoLog});
@ -179,6 +182,14 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._onChange(true, false)
);
this._addAttribute(
'dependentStims',
dependentStims,
[],
this._onChange(false, false)
);
// slider rating (which might be different from the visible marker rating):
this._addAttribute('rating', undefined);
@ -380,6 +391,31 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
/** Let `fillColor` alias `markerColor` to parallel PsychoPy */
set fillColor(color) {
this.markerColor = color;
}
setFillColor(color) {
this.setMarkerColor(color);
}
get fillColor() {
return this.markerColor;
}
getFillColor() {
return this.getMarkerColor();
}
/**
* Estimate the bounding box.
*
@ -607,6 +643,12 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._pixi.position = this._getPosition_px();
this._pixi.alpha = this._opacity;
// make sure that the dependent Stimuli are also updated:
for (const dependentStim of this._dependentStims)
{
dependentStim.draw();
}
}

View File

@ -56,7 +56,7 @@ export class TextInput extends PIXI.Container
this._selection = [0, 0];
this._restrict_value = '';
this._createDOMInput();
this.substituteText = true;
this.substituteText = false;
this._setState('DEFAULT');
}
@ -831,9 +831,9 @@ function DefaultBoxGenerator(styles)
if (style.stroke)
{
box.lineStyle(
style.stroke.width || 1,
style.stroke.color || 0,
style.stroke.alpha || 1
style.stroke.width ?? 1,
style.stroke.color ?? 0,
style.stroke.alpha ?? 1
);
}