From 29795025b994a6bd14d404255b6ddb9210bd09ee Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Mon, 14 Jun 2021 21:06:52 +0100 Subject: [PATCH 01/29] visual/TextInput: upgrade to PIXI.TextInput latest, with patches applied --- src/visual/TextInput.js | 222 ++++++++++++++++++++-------------------- 1 file changed, 111 insertions(+), 111 deletions(-) diff --git a/src/visual/TextInput.js b/src/visual/TextInput.js index 1826ce6..e429e09 100644 --- a/src/visual/TextInput.js +++ b/src/visual/TextInput.js @@ -9,7 +9,7 @@ * We are currently using it almost as is but will be making modification in the near future. */ -import * as PIXI from 'pixi.js-legacy'; +import * as PIXI from "pixi.js-legacy"; export class TextInput extends PIXI.Container { @@ -18,26 +18,26 @@ export class TextInput extends PIXI.Container super(); this._input_style = Object.assign( { - position: 'absolute', - background: 'none', - border: 'none', - outline: 'none', - transformOrigin: '0 0', - lineHeight: '1' + position: "absolute", + background: "none", + border: "none", + outline: "none", + transformOrigin: "0 0", + lineHeight: "1", }, - styles.input + styles.input, ); if (styles.box) { - this._box_generator = typeof styles.box === 'function' ? styles.box : new DefaultBoxGenerator(styles.box); + this._box_generator = typeof styles.box === "function" ? styles.box : new DefaultBoxGenerator(styles.box); } else { this._box_generator = null; } - if (this._input_style.hasOwnProperty('multiline')) + if (this._input_style.hasOwnProperty("multiline")) { this._multiline = !!this._input_style.multiline; delete this._input_style.multiline; @@ -51,16 +51,15 @@ export class TextInput extends PIXI.Container this._previous = {}; this._dom_added = false; this._dom_visible = true; - this._placeholder = ''; + this._placeholder = ""; this._placeholderColor = 0xa9a9a9; this._selection = [0, 0]; - this._restrict_value = ''; + this._restrict_value = ""; this._createDOMInput(); this.substituteText = false; - this._setState('DEFAULT'); + this._setState("DEFAULT"); } - // GETTERS & SETTERS get substituteText() @@ -102,7 +101,7 @@ export class TextInput extends PIXI.Container if (this._substituted) { this._updateSurrogate(); - this._dom_input.placeholder = ''; + this._dom_input.placeholder = ""; } else { @@ -119,7 +118,7 @@ export class TextInput extends PIXI.Container { this._disabled = disabled; this._dom_input.disabled = disabled; - this._setState(disabled ? 'DISABLED' : 'DEFAULT'); + this._setState(disabled ? "DISABLED" : "DEFAULT"); } get maxLength() @@ -130,7 +129,7 @@ export class TextInput extends PIXI.Container set maxLength(length) { this._max_length = length; - this._dom_input.setAttribute('maxlength', length); + this._dom_input.setAttribute("maxlength", length); } get restrict() @@ -144,21 +143,21 @@ export class TextInput extends PIXI.Container { regex = regex.toString().slice(1, -1); - if (regex.charAt(0) !== '^') + if (regex.charAt(0) !== "^") { - regex = '^' + regex; + regex = "^" + regex; } - if (regex.charAt(regex.length - 1) !== '$') + if (regex.charAt(regex.length - 1) !== "$") { - regex = regex + '$'; + regex = regex + "$"; } regex = new RegExp(regex); } else { - regex = new RegExp('^[' + regex + ']*$'); + regex = new RegExp("^[" + regex + "]*$"); } this._restrict_regex = regex; @@ -191,7 +190,6 @@ export class TextInput extends PIXI.Container } this._dom_input.focus(options); - } blur() @@ -210,7 +208,7 @@ export class TextInput extends PIXI.Container this._input_style[key] = value; this._dom_input.style[key] = value; - if (this._substituted && (key === 'fontFamily' || key === 'fontSize')) + if (this._substituted && (key === "fontFamily" || key === "fontSize")) { this._updateFontMetrics(); } @@ -227,20 +225,19 @@ export class TextInput extends PIXI.Container super.destroy(options); } - // SETUP _createDOMInput() { if (this._multiline) { - this._dom_input = document.createElement('textarea'); - this._dom_input.style.resize = 'none'; + this._dom_input = document.createElement("textarea"); + this._dom_input.style.resize = "none"; } else { - this._dom_input = document.createElement('input'); - this._dom_input.type = 'text'; + this._dom_input = document.createElement("input"); + this._dom_input.type = "text"; } for (let key in this._input_style) @@ -251,23 +248,23 @@ export class TextInput extends PIXI.Container _addListeners() { - this.on('added', this._onAdded.bind(this)); - this.on('removed', this._onRemoved.bind(this)); - this._dom_input.addEventListener('keydown', this._onInputKeyDown.bind(this)); - this._dom_input.addEventListener('input', this._onInputInput.bind(this)); - this._dom_input.addEventListener('keyup', this._onInputKeyUp.bind(this)); - this._dom_input.addEventListener('focus', this._onFocused.bind(this)); - this._dom_input.addEventListener('blur', this._onBlurred.bind(this)); + this.on("added", this._onAdded.bind(this)); + this.on("removed", this._onRemoved.bind(this)); + this._dom_input.addEventListener("keydown", this._onInputKeyDown.bind(this)); + this._dom_input.addEventListener("input", this._onInputInput.bind(this)); + this._dom_input.addEventListener("keyup", this._onInputKeyUp.bind(this)); + this._dom_input.addEventListener("focus", this._onFocused.bind(this)); + this._dom_input.addEventListener("blur", this._onBlurred.bind(this)); } _onInputKeyDown(e) { this._selection = [ this._dom_input.selectionStart, - this._dom_input.selectionEnd + this._dom_input.selectionEnd, ]; - this.emit('keydown', e.keyCode); + this.emit("keydown", e.keyCode); } _onInputInput(e) @@ -282,30 +279,30 @@ export class TextInput extends PIXI.Container this._updateSubstitution(); } - this.emit('input', this.text); + this.emit("input", this.text); } _onInputKeyUp(e) { - this.emit('keyup', e.keyCode); + this.emit("keyup", e.keyCode); } _onFocused() { - this._setState('FOCUSED'); - this.emit('focus'); + this._setState("FOCUSED"); + this.emit("focus"); } _onBlurred() { - this._setState('DEFAULT'); - this.emit('blur'); + this._setState("DEFAULT"); + this.emit("blur"); } _onAdded() { document.body.appendChild(this._dom_input); - this._dom_input.style.display = 'none'; + this._dom_input.style.display = "none"; this._dom_added = true; } @@ -325,7 +322,6 @@ export class TextInput extends PIXI.Container } } - // RENDER & UPDATE // for pixi v4 @@ -382,7 +378,10 @@ export class TextInput extends PIXI.Container this._buildBoxCache(); } - if (this.state == this._previous.state && this._box == this._box_cache[this.state]) + if ( + this.state == this._previous.state + && this._box == this._box_cache[this.state] + ) { return; } @@ -399,7 +398,7 @@ export class TextInput extends PIXI.Container _updateSubstitution() { - if (this.state === 'FOCUSED') + if (this.state === "FOCUSED") { this._dom_visible = true; this._surrogate.visible = this.text.length === 0; @@ -420,8 +419,8 @@ export class TextInput extends PIXI.Container return; } - this._dom_input.style.top = (this._canvas_bounds.top || 0) + 'px'; - this._dom_input.style.left = (this._canvas_bounds.left || 0) + 'px'; + this._dom_input.style.top = (this._canvas_bounds.top || 0) + "px"; + this._dom_input.style.left = (this._canvas_bounds.left || 0) + "px"; this._dom_input.style.transform = this._pixiMatrixToCSS(this._getDOMRelativeWorldTransform()); this._dom_input.style.opacity = this.worldAlpha; this._setDOMInputVisible(this.worldVisible && this._dom_visible); @@ -443,21 +442,20 @@ export class TextInput extends PIXI.Container this.text = this._restrict_value; this._dom_input.setSelectionRange( this._selection[0], - this._selection[1] + this._selection[1], ); } } - // STATE COMPAIRSON (FOR PERFORMANCE BENEFITS) _needsUpdate() { return ( - !this._comparePixiMatrices(this.worldTransform, this._previous.world_transform) || - !this._compareClientRects(this._canvas_bounds, this._previous.canvas_bounds) || - this.worldAlpha != this._previous.world_alpha || - this.worldVisible != this._previous.world_visible + !this._comparePixiMatrices(this.worldTransform, this._previous.world_transform) + || !this._compareClientRects(this._canvas_bounds, this._previous.canvas_bounds) + || this.worldAlpha != this._previous.world_alpha + || this.worldVisible != this._previous.world_visible ); } @@ -465,13 +463,12 @@ export class TextInput extends PIXI.Container { let input_bounds = this._getDOMInputBounds(); return ( - !this._previous.input_bounds || - input_bounds.width != this._previous.input_bounds.width || - input_bounds.height != this._previous.input_bounds.height + !this._previous.input_bounds + || input_bounds.width != this._previous.input_bounds.width + || input_bounds.height != this._previous.input_bounds.height ); } - // INPUT SUBSTITUTION _createSurrogate() @@ -479,14 +476,14 @@ export class TextInput extends PIXI.Container this._surrogate_hitbox = new PIXI.Graphics(); this._surrogate_hitbox.alpha = 0; this._surrogate_hitbox.interactive = true; - this._surrogate_hitbox.cursor = 'text'; - this._surrogate_hitbox.on('pointerdown', this._onSurrogateFocus.bind(this)); + this._surrogate_hitbox.cursor = "text"; + this._surrogate_hitbox.on("pointerdown", this._onSurrogateFocus.bind(this)); this.addChild(this._surrogate_hitbox); this._surrogate_mask = new PIXI.Graphics(); this.addChild(this._surrogate_mask); - this._surrogate = new PIXI.Text('', {}); + this._surrogate = new PIXI.Text("", {}); this.addChild(this._surrogate); this._surrogate.mask = this._surrogate_mask; @@ -508,15 +505,15 @@ export class TextInput extends PIXI.Container switch (this._surrogate.style.align) { - case 'left': + case "left": this._surrogate.x = padding[3]; break; - case 'center': + case "center": this._surrogate.x = input_bounds.width * 0.5 - this._surrogate.width * 0.5; break; - case 'right': + case "right": this._surrogate.x = input_bounds.width - padding[1] - this._surrogate.width; break; } @@ -562,7 +559,7 @@ export class TextInput extends PIXI.Container _onSurrogateFocus() { this._setDOMInputVisible(true); - //sometimes the input is not being focused by the mouseclick + // sometimes the input is not being focused by the mouseclick setTimeout(this._ensureFocus.bind(this), 10); } @@ -582,23 +579,23 @@ export class TextInput extends PIXI.Container { switch (key) { - case 'color': + case "color": style.fill = this._input_style.color; break; - case 'fontFamily': - case 'fontSize': - case 'fontWeight': - case 'fontVariant': - case 'fontStyle': + case "fontFamily": + case "fontSize": + case "fontWeight": + case "fontVariant": + case "fontStyle": style[key] = this._input_style[key]; break; - case 'letterSpacing': + case "letterSpacing": style.letterSpacing = parseFloat(this._input_style.letterSpacing); break; - case 'textAlign': + case "textAlign": style.align = this._input_style.textAlign; break; } @@ -625,7 +622,7 @@ export class TextInput extends PIXI.Container if (this._input_style.padding && this._input_style.padding.length > 0) { - let components = this._input_style.padding.trim().split(' '); + let components = this._input_style.padding.trim().split(" "); if (components.length == 1) { @@ -654,7 +651,17 @@ export class TextInput extends PIXI.Container _deriveSurrogateText() { - return this._dom_input.value.length === 0 ? this._placeholder : this._dom_input.value; + if (this._dom_input.value.length === 0) + { + return this._placeholder; + } + + if (this._dom_input.type == "password") + { + return "•".repeat(this._dom_input.value.length); + } + + return this._dom_input.value; } _updateFontMetrics() @@ -665,25 +672,23 @@ export class TextInput extends PIXI.Container this._font_metrics = PIXI.TextMetrics.measureFont(font); } - // CACHING OF INPUT BOX GRAPHICS _buildBoxCache() { this._destroyBoxCache(); - let states = ['DEFAULT', 'FOCUSED', 'DISABLED']; + let states = ["DEFAULT", "FOCUSED", "DISABLED"]; let input_bounds = this._getDOMInputBounds(); states.forEach((state) => - { - this._box_cache[state] = this._box_generator( - input_bounds.width, - input_bounds.height, - state - ); - } - ); + { + this._box_cache[state] = this._box_generator( + input_bounds.width, + input_bounds.height, + state, + ); + }); this._previous.input_bounds = input_bounds; } @@ -704,7 +709,6 @@ export class TextInput extends PIXI.Container } } - // HELPER FUNCTIONS _hasFocus() @@ -714,13 +718,13 @@ export class TextInput extends PIXI.Container _setDOMInputVisible(visible) { - this._dom_input.style.display = visible ? 'block' : 'none'; + this._dom_input.style.display = visible ? "block" : "none"; } _getCanvasBounds() { let rect = this._last_renderer.view.getBoundingClientRect(); - let bounds = {top: rect.top, left: rect.left, width: rect.width, height: rect.height}; + let bounds = { top: rect.top, left: rect.left, width: rect.width, height: rect.height }; bounds.left += window.scrollX; bounds.top += window.scrollY; return bounds; @@ -738,8 +742,8 @@ export class TextInput extends PIXI.Container let org_transform = this._dom_input.style.transform; let org_display = this._dom_input.style.display; - this._dom_input.style.transform = ''; - this._dom_input.style.display = 'block'; + this._dom_input.style.transform = ""; + this._dom_input.style.display = "block"; let bounds = this._dom_input.getBoundingClientRect(); this._dom_input.style.transform = org_transform; this._dom_input.style.display = org_display; @@ -758,14 +762,13 @@ export class TextInput extends PIXI.Container let matrix = this.worldTransform.clone(); matrix.scale(this._resolution, this._resolution); - matrix.scale(canvas_bounds.width / this._last_renderer.width, - canvas_bounds.height / this._last_renderer.height); + matrix.scale(canvas_bounds.width / this._last_renderer.width, canvas_bounds.height / this._last_renderer.height); return matrix; } _pixiMatrixToCSS(m) { - return 'matrix(' + [m.a, m.b, m.c, m.d, m.tx, m.ty].join(',') + ')'; + return "matrix(" + [m.a, m.b, m.c, m.d, m.tx, m.ty].join(",") + ")"; } _comparePixiMatrices(m1, m2) @@ -775,12 +778,12 @@ export class TextInput extends PIXI.Container return false; } return ( - m1.a == m2.a && - m1.b == m2.b && - m1.c == m2.c && - m1.d == m2.d && - m1.tx == m2.tx && - m1.ty == m2.ty + m1.a == m2.a + && m1.b == m2.b + && m1.c == m2.c + && m1.d == m2.d + && m1.tx == m2.tx + && m1.ty == m2.ty ); } @@ -791,20 +794,17 @@ export class TextInput extends PIXI.Container return false; } return ( - r1.left == r2.left && - r1.top == r2.top && - r1.width == r2.width && - r1.height == r2.height + r1.left == r2.left + && r1.top == r2.top + && r1.width == r2.width + && r1.height == r2.height ); } - - } - function DefaultBoxGenerator(styles) { - styles = styles || {fill: 0xcccccc}; + styles = styles || { fill: 0xcccccc }; if (styles.default) { @@ -818,7 +818,7 @@ function DefaultBoxGenerator(styles) styles.default = styles.focused = styles.disabled = temp_styles; } - return function (w, h, state) + return function(w, h, state) { let style = styles[state.toLowerCase()]; let box = new PIXI.Graphics(); @@ -833,7 +833,7 @@ function DefaultBoxGenerator(styles) box.lineStyle( style.stroke.width ?? 1, style.stroke.color ?? 0, - style.stroke.alpha ?? 1 + style.stroke.alpha ?? 1, ); } From ffb75f996c845d975a7ea71c3ca1ab0cd913c7dd Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Wed, 23 Jun 2021 16:11:37 +0100 Subject: [PATCH 02/29] css: drop scaling of dialog boxes, clean up --- src/index.css | 86 ++++++++++++++++----------------------------------- 1 file changed, 27 insertions(+), 59 deletions(-) diff --git a/src/index.css b/src/index.css index 70f84cd..61a4b04 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,16 @@ +body { + align-items: center; + display: flex; + height: 100vh; + justify-content: center; + margin: 0; +} + /* Project and resource dialogs */ label, input, select { + box-sizing: border-box; display: block; padding-bottom: 0.5em; } @@ -10,7 +19,7 @@ input.text, select.text { margin-bottom: 1em; padding: 0.5em; - width: 95%; + width: 100%; } fieldset { @@ -32,83 +41,42 @@ a:hover { } .progress { + box-sizing: border-box; padding: 0.5em 0; } .logo { display: block; margin: 0 auto 1em; - max-width: 100%; max-height: 20vh; + max-width: 100%; +} + +.ui-dialog { + margin: auto; + position: relative; } /* Don't display close button in the top right corner of the box */ -.no-close .ui-dialog-titlebar-close { +.ui-dialog.no-close .ui-dialog-titlebar-close { display: none; } -.ui-dialog-content { +.ui-dialog .ui-dialog-content { margin-top: 1em; + max-height: calc(100vh - 12em) !important; + overflow-y: auto; +} + +.ui-dialog .ui-dialog-buttonpane { + /* Avoid padding related overflow */ + box-sizing: border-box; } @media only screen and (max-width: 1080px) { - .ui-widget { - transform: scale(2); - } - - .ui-widget .ui-progressbar { - transform: scale(1); - } - - .ui-dialog-titlebar .ui-button { - margin-right: 1em; - } - - .ui-dialog-titlebar .ui-dialog-titlebar-close { - transform: scale(1); - } - .ui-dialog .ui-dialog-buttonpane { padding-top: 1em; } - - .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { - transform: scale(1); - } - - .ui-dialog .ui-dialog-titlebar { - padding: 1em 2em; - } -} - -@media only screen and (max-width: 1080px) and (orientation: landscape) { - .ui-widget { - transform: scale(1.5); - } - - .ui-widget .ui-progressbar { - transform: scale(1); - } - - .ui-dialog-titlebar .ui-button { - margin-right: 1em; - } - - .ui-dialog-titlebar .ui-dialog-titlebar-close { - transform: scale(1); - } - - .ui-dialog .ui-dialog-buttonpane { - padding-top: 1em; - } - - .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { - transform: scale(1); - } - - .ui-dialog .ui-dialog-titlebar { - padding: 1em 2em; - } } /* Initialisation message (which will disappear behind the canvas) */ @@ -123,8 +91,8 @@ a:hover { /* Initialisation message for IE11 */ @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { #root::after { - font-weight: bold; color: #a05000; content: "initialising the experiment... | Internet Explorer / Edge [beta]"; + font-weight: bold; } } From 9f2c16f2a9cb26f25f0414b0d24fccc91185b1df Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Wed, 23 Jun 2021 16:28:17 +0100 Subject: [PATCH 03/29] core/GUI: clean up dialog sizing extras --- src/core/GUI.js | 170 ++---------------------------------------------- 1 file changed, 4 insertions(+), 166 deletions(-) diff --git a/src/core/GUI.js b/src/core/GUI.js index 4dfa094..ee30f19 100644 --- a/src/core/GUI.js +++ b/src/core/GUI.js @@ -207,17 +207,6 @@ export class GUI dialogElement.innerHTML = htmlCode; - // when the logo is loaded, we call _onDialogOpen again to reset the dimensions and position of - // the dialog box: - if (typeof logoUrl === 'string') - { - jQuery("#dialog-logo").on('load', () => - { - self._onDialogOpen('#expDialog')(); - }); - } - - // setup change event handlers for all required keys: this._requiredKeys.forEach((keyId) => { @@ -231,14 +220,11 @@ export class GUI // init and open the dialog box: self._dialogComponent.button = 'Cancel'; - self._estimateDialogScalingFactor(); - const dialogSize = self._getDialogSize(); jQuery("#expDialog").dialog({ - width: dialogSize[0], - maxHeight: dialogSize[1], + width: "500", autoOpen: true, - modal: true, + modal: false, closeOnEscape: false, resizable: false, draggable: false, @@ -286,9 +272,6 @@ export class GUI } ], - // open the dialog in the middle of the screen: - open: self._onDialogOpen('#expDialog'), - // close is called by both buttons and when the user clicks on the cross: close: function () { @@ -306,10 +289,6 @@ export class GUI self._updateOkButtonStatus(); - // when the browser window is resize, we redimension and reposition the dialog: - self._dialogResize('#expDialog'); - - // 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}); @@ -458,17 +437,14 @@ export class GUI dialogElement.innerHTML = htmlCode; // init and open the dialog box: - this._estimateDialogScalingFactor(); - const dialogSize = this._getDialogSize(); const self = this; jQuery("#msgDialog").dialog({ dialogClass: 'no-close', - width: dialogSize[0], - maxHeight: dialogSize[1], + width: "500", autoOpen: true, - modal: true, + modal: false, closeOnEscape: false, resizable: false, draggable: false, @@ -487,93 +463,9 @@ export class GUI } } }], - - // open the dialog in the middle of the screen: - open: self._onDialogOpen('#msgDialog'), - }) // change colour of title bar .prev(".ui-dialog-titlebar").css("background", titleColour); - - - // when the browser window is resize, we redimension and reposition the dialog: - self._dialogResize('#msgDialog'); - } - - - /** - * Callback triggered when the jQuery UI dialog box is open. - * - * @name module:core.GUI#_onDialogOpen - * @function - * @param {String} dialogId - the dialog ID - * @returns {Function} function setting the dimension and position of the dialog box - * @private - */ - _onDialogOpen(dialogId) - { - const self = this; - - return () => - { - const windowSize = [jQuery(window).width(), jQuery(window).height()]; - - // note: jQuery(dialogId) is the dialog-content, jQuery(dialogId).parent() is the actual widget - const parent = jQuery(dialogId).parent(); - parent.css({ - position: 'absolute', - left: Math.max(0, (windowSize[0] - parent.outerWidth()) / 2.0), - top: Math.max(0, (windowSize[1] - parent.outerHeight()) / 2.0) - }); - - // record width and height difference between dialog content and dialog: - self._contentDelta = [ - parent.css('width').slice(0, -2) - jQuery(dialogId).css('width').slice(0, -2), - parent.css('height').slice(0, -2) - jQuery(dialogId).css('height').slice(0, -2)]; - }; - } - - - /** - * Ensure that the browser window's resize events redimension and reposition the dialog UI. - * - * @name module:core.GUI#_dialogResize - * @function - * @param {String} dialogId - the dialog ID - * @private - */ - _dialogResize(dialogId) - { - const self = this; - - jQuery(window).resize(function () - { - const parent = jQuery(dialogId).parent(); - const windowSize = [jQuery(window).width(), jQuery(window).height()]; - - // size (we need to redimension both the dialog and the dialog content): - const dialogSize = self._getDialogSize(); - parent.css({ - width: dialogSize[0], - maxHeight: dialogSize[1] - }); - - const isDifferent = self._estimateDialogScalingFactor(); - if (!isDifferent) - { - jQuery(dialogId).css({ - width: dialogSize[0] - self._contentDelta[0], - maxHeight: dialogSize[1] - self._contentDelta[1] - }); - } - - // position: - parent.css({ - position: 'absolute', - left: Math.max(0, (windowSize[0] - parent.outerWidth()) / 2.0), - top: Math.max(0, (windowSize[1] - parent.outerHeight()) / 2.0), - }); - }); } @@ -672,60 +564,6 @@ export class GUI } - /** - * Estimate the scaling factor for the dialog popup windows. - * - * @name module:core.GUI#_estimateDialogScalingFactor - * @function - * @private - * @returns {boolean} whether or not the scaling factor is different from the previously estimated one - */ - _estimateDialogScalingFactor() - { - const windowSize = [jQuery(window).width(), jQuery(window).height()]; - - // desktop: - let dialogScalingFactor = 1.0; - - // mobile or tablet: - if (windowSize[0] < 1080) - { - // landscape: - if (windowSize[0] > windowSize[1]) - { - dialogScalingFactor = 1.5; - }// portrait: - else - { - dialogScalingFactor = 2.0; - } - } - - const isDifferent = (dialogScalingFactor !== this._dialogScalingFactor); - this._dialogScalingFactor = dialogScalingFactor; - - return isDifferent; - } - - - /** - * Get the size of the dialog. - * - * @name module:core.GUI#_getDialogSize - * @private - * @returns {number[]} the size of the popup dialog window - */ - _getDialogSize() - { - const windowSize = [jQuery(window).width(), jQuery(window).height()]; - this._estimateDialogScalingFactor(); - - return [ - Math.min(GUI.dialogMaxSize[0], (windowSize[0] - GUI.dialogMargin[0]) / this._dialogScalingFactor), - Math.min(GUI.dialogMaxSize[1], (windowSize[1] - GUI.dialogMargin[1]) / this._dialogScalingFactor)]; - } - - /** * Listener for change event for required keys. * From 9038281dc97b88176a4d8b00796b771318076554 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Wed, 23 Jun 2021 16:29:22 +0100 Subject: [PATCH 04/29] css: give dialogs a max width for screens < 500 --- src/index.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.css b/src/index.css index 61a4b04..ced7904 100644 --- a/src/index.css +++ b/src/index.css @@ -54,6 +54,7 @@ a:hover { .ui-dialog { margin: auto; + max-width: 88vw; position: relative; } From 966e7a5b39db3664351cf6f585ab6749b9a07722 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Wed, 7 Jul 2021 14:42:34 +0100 Subject: [PATCH 05/29] core/ServerManager: do import Clock --- src/core/ServerManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ServerManager.js b/src/core/ServerManager.js index 6b73966..5f8bf1f 100644 --- a/src/core/ServerManager.js +++ b/src/core/ServerManager.js @@ -12,7 +12,7 @@ import {PsychoJS} from './PsychoJS'; import {PsychObject} from '../util/PsychObject'; import * as util from '../util/Util'; import {ExperimentHandler} from "../data/ExperimentHandler"; -import {MonotonicClock} from "../util/Clock"; +import {MonotonicClock, Clock} from "../util/Clock"; /** From 6211e606dd269492b0088bab185791bc595741c6 Mon Sep 17 00:00:00 2001 From: Alain Pitiot Date: Wed, 7 Jul 2021 16:11:35 +0200 Subject: [PATCH 06/29] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c38c1a6..20af5f6 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "scripts": { "build": "npm run build:js && npm run build:css && npm run build:docs", "build:css": "node ./scripts/build.css.cjs", - "build:docs": "jsdoc src -r -d docs -c jsdoc-conf.json --readme README.md", + "build:docs": "jsdoc src -r -d docs --readme README.md", "build:js": "node ./scripts/build.js.cjs", "lint": "npm run lint:js && npm run lint:css", "lint:css": "csslint src", From c2b38cb550a270a04ce5ce3f7e603096d02c6e1c Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Thu, 8 Jul 2021 17:52:12 +0100 Subject: [PATCH 07/29] util/Util: add custom isNumeric func --- src/util/Util.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/util/Util.js b/src/util/Util.js index c1dcf60..5f398a7 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -299,6 +299,19 @@ export function toNumerical(obj) } +/** + * Check whether a value looks like a number + * + * @name module:util.isNumeric + * @function + * @public + * @param {*} input - Some value + * @return {boolean} Whether or not the value can be converted into a number + */ +export function isNumeric(input) { + return Number.isNaN(Number(input)) === false; +} + /** * Check whether a point lies within a polygon From b02d907964254edad17fcf84def820328eb1715b Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Thu, 8 Jul 2021 17:52:36 +0100 Subject: [PATCH 08/29] sound/TonePlayer: replace $.isNumeric --- src/sound/TonePlayer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sound/TonePlayer.js b/src/sound/TonePlayer.js index dbac89f..dfacd9a 100644 --- a/src/sound/TonePlayer.js +++ b/src/sound/TonePlayer.js @@ -8,6 +8,7 @@ */ import * as Tone from 'tone'; +import { isNumeric } from "../util/Util.js"; import {SoundPlayer} from './SoundPlayer'; @@ -74,7 +75,7 @@ export class TonePlayer extends SoundPlayer static accept(sound) { // if the sound's value is an integer, we interpret it as a frequency: - if ($.isNumeric(sound.value)) + if (isNumeric(sound.value)) { return new TonePlayer({ psychoJS: sound.psychoJS, From 1eecdfda5d728bb56771df885582132f77a08d3a Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 07:37:53 +0100 Subject: [PATCH 09/29] readme: clean up --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f7cc03c..5911875 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ PsychoJS is an open-source project. You can contribute by submitting pull reques ## Motivation -Many studies in behavioural sciences (e.g. psychology, neuroscience, linguistics or mental health) use computers to present stimuli and record responses in a precise manner. These studies are still typically conducted on small numbers of people in laboratory environments equipped with dedicated hardware. +Many studies in behavioural sciences (e.g., psychology, neuroscience, linguistics or mental health) use computers to present stimuli and record responses in a precise manner. These studies are still typically conducted on small numbers of people in laboratory environments equipped with dedicated hardware. -With high-speed broadband, improved web technologies and smart devices everywhere, studies can now go online without sacrificing too much temporal precision. This is a “game changer”. Data can be collected on larger, more varied, international populations. We can study people in environments they do not find intimidating. Experiments can be run multiple times per day, without data collection becoming impractical. +With high-speed broadband, improved web technologies and smart devices everywhere, studies can now go online without sacrificing too much temporal precision. This is a "game changer". Data can be collected on larger, more varied, international populations. We can study people in environments they do not find intimidating. Experiments can be run multiple times per day, without data collection becoming impractical. The idea behind PsychoJS is to make PsychoPy experiments available online, from a web page, so participants can run them on any device equipped with a web browser such as desktops, laptops, or tablets. In some circumstance, they can even use their phone! @@ -31,7 +31,7 @@ We built the PsychoJS library to make the JavaScript experiment files look and b There are however notable differences between the PsychoJS and PsychoPy libraries, most of which have to do with the way a web browser interprets and runs JavaScript, deals with resources (such as images, sound or videos), or render stimuli. To manage those web-specific aspect, PsychoJS introduces the concept of Scheduler. As the name indicate, Scheduler's offer a way to organise various PsychoJS along a timeline, such as downloading resources, running a loop, checking for keyboard input, saving experiment results, etc. As an illustration, a Flow in PsychoPy can be conceptualised as a Schedule, with various tasks on it. Some of those tasks, such as trial loops, can also schedule further events (i.e. the individual trials to be run). -Under the hood PsychoJS relies on [PixiJs](http://www.pixijs.com) to present stimuli and collect responses. PixiJs is a multi-platform, accelerated, 2-D renderer, that runs in most modern browsers. It uses WebGL wherever possible and silently falls back to HTML5 canvas where not. WebGL directly addresses the graphic card, thereby considerably improving the rendering performance. +Under the hood PsychoJS relies on [PixiJS](http://www.pixijs.com) to present stimuli and collect responses. PixiJS is a multi-platform, accelerated, 2D renderer, that runs in most modern browsers. It uses WebGL wherever possible and silently falls back to HTML5 canvas where not. WebGL directly addresses the graphic card, thereby considerably improving the rendering performance. ### Hosting Experiments From 898e2b5749a0a72f88b75e4f6e99bbf004087476 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 07:47:08 +0100 Subject: [PATCH 10/29] eslint: tweak config --- .eslintrc.cjs | 273 +++++++++++++++++++++++++------------------------- 1 file changed, 134 insertions(+), 139 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 63ed411..702e9cc 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,140 +1,135 @@ module.exports = { - extends: 'eslint:recommended', - parserOptions: { - ecmaVersion: 11, - sourceType: 'module' - }, - env: { - browser: true, - node: true, - es6: true - }, - plugins: [], - rules: { - 'accessor-pairs': 1, - 'arrow-body-style': [2, 'as-needed'], - 'arrow-parens': [2, 'as-needed', { requireForBlockBody: true }], - 'arrow-spacing': 2, - 'block-spacing': 2, - 'brace-style': [2, 'allman', { allowSingleLine: true }], - 'camelcase': 1, - 'capitalized-comments': [1, 'always', { ignoreConsecutiveComments: true }], - 'comma-dangle': 2, - 'comma-spacing': 2, - 'comma-style': 2, - 'consistent-return': 1, - 'consistent-this': 2, - 'curly': 2, - 'default-case': 2, - 'dot-location': [2, 'property'], - 'dot-notation': 1, - 'eol-last': 2, - 'eqeqeq': [2, 'always'], - 'func-call-spacing': 2, - 'func-style': [2, 'declaration', { allowArrowFunctions: true }], - 'generator-star-spacing': 2, - 'global-require': 2, - 'handle-callback-err': [2, '^(err|error)$' ], - 'indent': [2, 'tab'], - 'key-spacing': 2, - 'keyword-spacing': 2, - 'line-comment-position': 2, - 'lines-around-comment': [2, { afterLineComment: false }], - 'max-len': [1, 100], - 'new-cap': [2, { capIsNew: false }], - 'new-parens': 2, - 'no-array-constructor': 2, - 'no-buffer-constructor': 2, - 'no-caller': 2, - 'no-confusing-arrow': [2, { allowParens: true }], - 'no-console': 1, - 'no-div-regex': 2, - 'no-duplicate-imports': 2, - 'no-else-return': 2, - 'no-eval': 2, - 'no-extend-native': 2, - 'no-extra-bind': 2, - 'no-extra-label': 2, - 'no-extra-parens': [2, 'functions'], - 'no-floating-decimal': 2, - 'no-implied-eval': 2, - 'no-inline-comments': 2, - 'no-invalid-this': 2, - 'no-iterator': 2, - 'no-label-var': 2, - 'no-labels': 2, - 'no-lone-blocks': 2, - 'no-mixed-operators': 2, - 'no-mixed-requires': 2, - 'no-multi-spaces': 2, - 'no-multi-str': 2, - 'no-multiple-empty-lines': [2, { max: 1, maxEOF: 0 }], - 'no-new': 2, - 'no-new-func': 2, - 'no-new-object': 2, - 'no-new-require': 2, - 'no-new-wrappers': 2, - 'no-octal-escape': 2, - 'no-param-reassign': 1, - 'no-path-concat': 2, - 'no-plusplus': 2, - 'no-proto': 2, - 'no-restricted-properties': 2, - 'no-return-assign': [2, 'except-parens'], - 'no-return-await': 2, - 'no-self-compare': 2, - 'no-sequences': 2, - 'no-shadow': 2, - 'no-shadow-restricted-names': 2, - 'no-tabs': [1, { allowIndentationTabs: true }], - 'no-template-curly-in-string': 2, - 'no-throw-literal': 2, - 'no-trailing-spaces': 2, - 'no-undef-init': 2, - 'no-underscore-dangle': 0, - 'no-unmodified-loop-condition': 2, - 'no-unneeded-ternary': 2, - 'no-unused-expressions': 2, - 'no-use-before-define': [2, { functions: false }], - 'no-useless-call': 2, - 'no-useless-computed-key': 2, - 'no-useless-constructor': 2, - 'no-useless-rename': 2, - 'no-useless-return': 2, - 'no-var': 2, - 'no-void': 2, - 'no-whitespace-before-property': 2, - 'no-with': 2, - 'object-property-newline': [2, { allowMultiplePropertiesPerLine: true }], - 'one-var': [2, 'never'], - 'one-var-declaration-per-line': 2, - 'operator-linebreak': [2, 'before'], - 'padded-blocks': [2, 'never'], - 'padding-line-between-statements': 2, - 'prefer-const': 2, - 'prefer-promise-reject-errors': 2, - 'prefer-rest-params': 1, - 'prefer-template': 2, - 'quote-props': [2, 'consistent-as-needed'], - 'quotes': [2, 'single'], - 'radix': 2, - 'require-await': 2, - 'rest-spread-spacing': 2, - // 'semi': [2, 'never'], - // 'semi-spacing': 2, - 'space-before-blocks': 2, - 'space-before-function-paren': [2, { named: 'never' }], - 'space-in-parens': 2, - 'space-infix-ops': 2, - 'space-unary-ops': 2, - 'spaced-comment': 2, - 'symbol-description': 2, - 'template-curly-spacing': 2, - 'template-tag-spacing': 2, - 'unicode-bom': 2, - 'valid-jsdoc': 2, - 'wrap-iife': [2, 'any', { functionPrototypeMethods: true }], - 'yield-star-spacing': [2, 'both'], - 'yoda': 2 - } -} + extends: "eslint:recommended", + parserOptions: { + ecmaVersion: 11, + sourceType: "module", + }, + env: { + browser: true, + node: true, + es6: true, + }, + plugins: [], + rules: { + "accessor-pairs": 1, + "arrow-body-style": [2, "as-needed"], + "arrow-spacing": 2, + "block-spacing": 2, + "brace-style": [2, "allman", { allowSingleLine: true }], + "camelcase": 1, + "capitalized-comments": [1, "always", { ignoreConsecutiveComments: true }], + "comma-spacing": 2, + "comma-style": 2, + "consistent-return": 1, + "consistent-this": 2, + "curly": 2, + "default-case": 2, + "dot-location": [2, "property"], + "dot-notation": 1, + "eol-last": 2, + "eqeqeq": [2, "always"], + "func-call-spacing": 2, + "func-style": [2, "declaration", { allowArrowFunctions: true }], + "generator-star-spacing": 2, + "global-require": 2, + "handle-callback-err": [2, "^(err|error)$"], + "indent": ["error", "tab", { "SwitchCase": 1 }], + "key-spacing": 2, + "keyword-spacing": 2, + "line-comment-position": 2, + "max-len": 0, + "new-cap": [2, { capIsNew: false }], + "new-parens": 2, + "no-array-constructor": 2, + "no-buffer-constructor": 2, + "no-caller": 2, + "no-confusing-arrow": [2, { allowParens: true }], + "no-console": 1, + "no-div-regex": 2, + "no-duplicate-imports": 2, + "no-else-return": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-label": 2, + "no-extra-parens": [2, "functions"], + "no-floating-decimal": 2, + "no-implied-eval": 2, + "no-inline-comments": 2, + "no-invalid-this": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-mixed-operators": 2, + "no-mixed-requires": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, { max: 1, maxEOF: 0 }], + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-param-reassign": 1, + "no-path-concat": 2, + "no-plusplus": 2, + "no-proto": 2, + "no-restricted-properties": 2, + "no-return-assign": [2, "except-parens"], + "no-return-await": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": 2, + "no-shadow-restricted-names": 2, + "no-tabs": [1, { allowIndentationTabs: true }], + "no-template-curly-in-string": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-undef-init": 2, + // https://eslint.org/docs/rules/no-underscore-dangle#disallow-dangling-underscores-in-identifiers-no-underscore-dangle + "no-underscore-dangle": 1, + "no-unmodified-loop-condition": 2, + "no-unneeded-ternary": 2, + "no-unused-expressions": 2, + "no-use-before-define": [2, { functions: false }], + "no-useless-call": 2, + "no-useless-computed-key": 2, + "no-useless-constructor": 2, + "no-useless-rename": 2, + "no-useless-return": 2, + "no-var": 2, + "no-void": 2, + "no-whitespace-before-property": 2, + "no-with": 2, + "object-property-newline": [2, { allowMultiplePropertiesPerLine: true }], + "one-var": [2, "never"], + "one-var-declaration-per-line": 2, + "operator-linebreak": [2, "before"], + "padded-blocks": [2, "never"], + "padding-line-between-statements": 2, + "prefer-const": 2, + "prefer-promise-reject-errors": 2, + "prefer-rest-params": 1, + "prefer-template": 2, + "quote-props": [2, "consistent-as-needed"], + "quotes": [2, "double"], + "radix": 2, + "require-await": 2, + "rest-spread-spacing": 2, + "space-before-blocks": 2, + "space-in-parens": 2, + "space-infix-ops": 2, + "space-unary-ops": 2, + "spaced-comment": 2, + "symbol-description": 2, + "template-curly-spacing": 2, + "template-tag-spacing": 2, + "unicode-bom": 2, + "valid-jsdoc": 2, + "wrap-iife": [2, "any", { functionPrototypeMethods: true }], + "yield-star-spacing": [2, "both"], + "yoda": 2, + }, +}; From 8e01f3f7e65bd2d6ecb4f1bcee56ea5e3541d2b1 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 07:48:59 +0100 Subject: [PATCH 11/29] package: add dprint --- package-lock.json | 17 +++++++++++++++++ package.json | 1 + 2 files changed, 18 insertions(+) diff --git a/package-lock.json b/package-lock.json index 699c059..82fde88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ }, "devDependencies": { "csslint": "^1.0.5", + "dprint": "^0.15.3", "esbuild": "^0.12.1", "eslint": "^7.26.0", "jsdoc": "^3.6.7" @@ -919,6 +920,16 @@ "node": ">=6.0.0" } }, + "node_modules/dprint": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/dprint/-/dprint-0.15.3.tgz", + "integrity": "sha512-x/7wc+7TMSj+gO0vzeyU6X/3RqWph1nariuYCuhaF9hFSkmzShMuAhw4aJLxZoS/V/qtDAbJtsZ4/mJeGU1qvg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "dprint": "bin.js" + } + }, "node_modules/earcut": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.2.tgz", @@ -3190,6 +3201,12 @@ "esutils": "^2.0.2" } }, + "dprint": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/dprint/-/dprint-0.15.3.tgz", + "integrity": "sha512-x/7wc+7TMSj+gO0vzeyU6X/3RqWph1nariuYCuhaF9hFSkmzShMuAhw4aJLxZoS/V/qtDAbJtsZ4/mJeGU1qvg==", + "dev": true + }, "earcut": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.2.tgz", diff --git a/package.json b/package.json index 20af5f6..7582f1d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "devDependencies": { "csslint": "^1.0.5", + "dprint": "^0.15.3", "esbuild": "^0.12.1", "eslint": "^7.26.0", "jsdoc": "^3.6.7" From f6bd9b9448d1a56854999034e990c61eebaa8526 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 07:49:31 +0100 Subject: [PATCH 12/29] package: add dprint config --- .dprint.json | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .dprint.json diff --git a/.dprint.json b/.dprint.json new file mode 100644 index 0000000..e98b17b --- /dev/null +++ b/.dprint.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://dprint.dev/schemas/v0.json", + "projectType": "openSource", + "incremental": true, + "lineWidth": 180, + "indentWidth": 2, + "useTabs": false, + "typescript": { + "useTabs": true, + "semiColons": "always", + "quoteStyle": "alwaysDouble", + "newLineKind": "lf", + "useBraces": "always", + "bracePosition": "nextLine", + "singleBodyPosition": "nextLine", + "nextControlFlowPosition": "nextLine", + "trailingCommas": "onlyMultiLine", + "operatorPosition": "nextLine", + "preferHanging": false, + "preferSingleLine": false, + "arrowFunction.useParentheses": "force", + "binaryExpression.linePerExpression": false, + "jsx.quoteStyle": "preferDouble", + "memberExpression.linePerExpression": false, + "typeLiteral.separatorKind": "semiColon", + "enumDeclaration.memberSpacing": "maintain", + "spaceSurroundingProperties": true, + "objectExpression.spaceSurroundingProperties": true, + "objectPattern.spaceSurroundingProperties": true, + "typeLiteral.spaceSurroundingProperties": true, + "binaryExpression.spaceSurroundingBitwiseAndArithmeticOperator": true, + "commentLine.forceSpaceAfterSlashes": true, + "constructor.spaceBeforeParentheses": false, + "constructorType.spaceAfterNewKeyword": false, + "constructSignature.spaceAfterNewKeyword": false, + "doWhileStatement.spaceAfterWhileKeyword": true, + "exportDeclaration.spaceSurroundingNamedExports": true, + "forInStatement.spaceAfterForKeyword": true, + "forOfStatement.spaceAfterForKeyword": true, + "forStatement.spaceAfterForKeyword": true, + "forStatement.spaceAfterSemiColons": true, + "functionDeclaration.spaceBeforeParentheses": false, + "functionExpression.spaceBeforeParentheses": false, + "functionExpression.spaceAfterFunctionKeyword": false, + "getAccessor.spaceBeforeParentheses": false, + "ifStatement.spaceAfterIfKeyword": true, + "importDeclaration.spaceSurroundingNamedImports": true, + "jsxExpressionContainer.spaceSurroundingExpression": false, + "method.spaceBeforeParentheses": false, + "setAccessor.spaceBeforeParentheses": false, + "taggedTemplate.spaceBeforeLiteral": true, + "typeAnnotation.spaceBeforeColon": false, + "typeAssertion.spaceBeforeExpression": true, + "whileStatement.spaceAfterWhileKeyword": true + }, + "json": {}, + "markdown": {}, + "includes": ["**/*.{js,cjs,mjs,json,md}"], + "excludes": [ + "dist", + "docs", + "node_modules", + "*-lock.json", + "out" + ], + "plugins": [ + "https://plugins.dprint.dev/typescript-0.48.0.wasm", + "https://plugins.dprint.dev/json-0.12.1.wasm", + "https://plugins.dprint.dev/markdown-0.9.2.wasm" + ] +} From 3d635cc35588ebac8509af456e1a620e173ce82c Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 07:50:50 +0100 Subject: [PATCH 13/29] package: add fmt script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7582f1d..7c8c2ca 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "build:css": "node ./scripts/build.css.cjs", "build:docs": "jsdoc src -r -d docs --readme README.md", "build:js": "node ./scripts/build.js.cjs", + "fmt": "dprint fmt", "lint": "npm run lint:js && npm run lint:css", "lint:css": "csslint src", "lint:js": "eslint src", From a8c4b46ae81f1aa78e14087bbc760268ebf7a516 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 07:54:03 +0100 Subject: [PATCH 14/29] scripts: fix for dprint --- scripts/build.css.cjs | 10 +++++----- scripts/build.js.cjs | 29 +++++++++++++++-------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/scripts/build.css.cjs b/scripts/build.css.cjs index e50b393..e563dd6 100644 --- a/scripts/build.css.cjs +++ b/scripts/build.css.cjs @@ -1,13 +1,13 @@ -const { buildSync } = require('esbuild'); -const pkg = require('psychojs/package.json'); +const { buildSync } = require("esbuild"); +const pkg = require("psychojs/package.json"); const versionMaybe = process.env.npm_config_outver; const dirMaybe = process.env.npm_config_outdir; -const [,,, dir = dirMaybe || 'out', version = versionMaybe || pkg.version] = process.argv; +const [, , , dir = dirMaybe || "out", version = versionMaybe || pkg.version] = process.argv; buildSync({ bundle: true, - entryPoints: ['src/index.css'], + entryPoints: ["src/index.css"], minify: true, - outfile: `./${dir}/psychojs-${version}.css` + outfile: `./${dir}/psychojs-${version}.css`, }); diff --git a/scripts/build.js.cjs b/scripts/build.js.cjs index cf311ce..825e0cd 100644 --- a/scripts/build.js.cjs +++ b/scripts/build.js.cjs @@ -1,24 +1,25 @@ -const { buildSync } = require('esbuild'); -const pkg = require('psychojs/package.json'); +const { buildSync } = require("esbuild"); +const pkg = require("psychojs/package.json"); const versionMaybe = process.env.npm_config_outver; const dirMaybe = process.env.npm_config_outdir; -const [,,, dir = dirMaybe || 'out', version = versionMaybe || pkg.version] = process.argv; +const [, , , dir = dirMaybe || "out", version = versionMaybe || pkg.version] = process.argv; [ // The ESM bundle { - format: 'esm', - legalComments: 'external', + format: "esm", + legalComments: "external", outfile: `./${dir}/psychojs-${version}.js`, }, // The IIFE { - globalName: 'PsychoJS', - legalComments: 'none', - outfile: `./${dir}/psychojs-${version}.iife.js` - } -].forEach(function(options) { + globalName: "PsychoJS", + legalComments: "none", + outfile: `./${dir}/psychojs-${version}.iife.js`, + }, +].forEach(function(options) +{ buildSync({ ...this, ...options }); }, { // Shared options @@ -27,12 +28,12 @@ const [,,, dir = dirMaybe || 'out', version = versionMaybe || pkg.version] = pro }, bundle: true, sourcemap: true, - entryPoints: ['src/index.js'], + entryPoints: ["src/index.js"], minifySyntax: true, minifyWhitespace: true, target: [ // https://github.com/evanw/esbuild/issues/121#issuecomment-646956379 - 'es2017', - 'node14', - ] + "es2017", + "node14", + ], }); From 4a94508c46a53d57b29ef572955e603ca628cd6d Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 08:47:20 +0100 Subject: [PATCH 15/29] util/Util: add toNumeric unit test --- src/util/Util.test.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/util/Util.test.js diff --git a/src/util/Util.test.js b/src/util/Util.test.js new file mode 100644 index 0000000..d7b09bc --- /dev/null +++ b/src/util/Util.test.js @@ -0,0 +1,7 @@ +import assert from "assert"; +import { isNumeric } from "./Util.js"; + +assert(isNumeric("1.2")) +assert(isNumeric(0)) +assert(!isNumeric("NaN")) +assert(!isNumeric("hey")) From a5dbe83a3d9878f610e9d1d6cb2b22e54ff3afaa Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 12:34:06 +0100 Subject: [PATCH 16/29] util/visual: separate out PIXI specific util and adjust imports --- src/util/Pixi.js | 35 +++++++++++++++++++++++++++++++++++ src/util/Util.js | 29 ----------------------------- src/util/index.js | 1 + src/visual/Form.js | 3 ++- src/visual/ImageStim.js | 3 ++- src/visual/MovieStim.js | 3 ++- src/visual/ShapeStim.js | 3 ++- src/visual/Slider.js | 5 +++-- src/visual/TextStim.js | 3 ++- 9 files changed, 49 insertions(+), 36 deletions(-) create mode 100644 src/util/Pixi.js diff --git a/src/util/Pixi.js b/src/util/Pixi.js new file mode 100644 index 0000000..fcc5cf8 --- /dev/null +++ b/src/util/Pixi.js @@ -0,0 +1,35 @@ +/** + * PIXI utilities. + * + * @authors Alain Pitiot, Sotiri Bakagiannis, 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) + * @license Distributed under the terms of the MIT License + */ + +import * as PIXI from "pixi.js-legacy"; + +/** + * Convert a position to a PIXI Point. + * + * @name module:util.to_pixiPoint + * @function + * @public + * @param {number[]} pos - the input position + * @param {string} posUnit - the position units + * @param {Window} win - the associated Window + * @param {boolean} [integerCoordinates = false] - whether or not to round the PIXI Point coordinates. + * @returns {number[]} the position as a PIXI Point + */ +export function to_pixiPoint(pos, posUnit, win, integerCoordinates = false) +{ + const pos_px = to_px(pos, posUnit, win); + if (integerCoordinates) + { + return new PIXI.Point(Math.round(pos_px[0]), Math.round(pos_px[1])); + } + else + { + return new PIXI.Point(pos_px[0], pos_px[1]); + } +} diff --git a/src/util/Util.js b/src/util/Util.js index 5f398a7..3a2291c 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -7,9 +7,6 @@ * @license Distributed under the terms of the MIT License */ -import * as PIXI from 'pixi.js-legacy'; - - /** * Syntactic sugar for Mixins * @@ -615,32 +612,6 @@ export function to_unit(pos, posUnit, win, targetUnit) } -/** - * Convert a position to a PIXI Point. - * - * @name module:util.to_pixiPoint - * @function - * @public - * @param {number[]} pos - the input position - * @param {string} posUnit - the position units - * @param {Window} win - the associated Window - * @param {boolean} [integerCoordinates = false] - whether or not to round the PIXI Point coordinates. - * @returns {number[]} the position as a PIXI Point - */ -export function to_pixiPoint(pos, posUnit, win, integerCoordinates = false) -{ - const pos_px = to_px(pos, posUnit, win); - if (integerCoordinates) - { - return new PIXI.Point(Math.round(pos_px[0]), Math.round(pos_px[1])); - } - else - { - return new PIXI.Point(pos_px[0], pos_px[1]); - } -} - - /** * Convert an object to its string representation, taking care of symbols. * diff --git a/src/util/index.js b/src/util/index.js index 031be3d..9f7c607 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -2,6 +2,7 @@ export * from './Clock.js'; export * from './Color.js'; export * from './ColorMixin.js'; export * from './EventEmitter.js'; +export * from "./Pixi.js"; export * from './PsychObject.js'; export * from './Scheduler.js'; export * from './Util.js'; diff --git a/src/visual/Form.js b/src/visual/Form.js index c9cfb21..c931c0a 100644 --- a/src/visual/Form.js +++ b/src/visual/Form.js @@ -17,6 +17,7 @@ import {TextStim} from './TextStim'; import {TextBox} from './TextBox'; import {VisualStim} from './VisualStim'; import {Slider} from './Slider'; +import { to_pixiPoint } from "../util/Pixi.js"; @@ -1100,7 +1101,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._pixi.scale.x = 1; this._pixi.scale.y = 1; this._pixi.rotation = 0; - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); this._pixi.alpha = this._opacity; this._pixi.zIndex = this._depth; diff --git a/src/visual/ImageStim.js b/src/visual/ImageStim.js index d74b3bd..62273bd 100644 --- a/src/visual/ImageStim.js +++ b/src/visual/ImageStim.js @@ -13,6 +13,7 @@ import {VisualStim} from './VisualStim'; import {Color} from '../util/Color'; import {ColorMixin} from '../util/ColorMixin'; import * as util from '../util/Util'; +import { to_pixiPoint } from "../util/Pixi.js"; /** @@ -320,7 +321,7 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) this._pixi.scale.y = this.flipVert ? scaleY : -scaleY; // set the position, rotation, and anchor (image centered on pos): - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); this._pixi.rotation = this.ori * Math.PI / 180; this._pixi.anchor.x = 0.5; this._pixi.anchor.y = 0.5; diff --git a/src/visual/MovieStim.js b/src/visual/MovieStim.js index 13d19bf..3dec9a9 100644 --- a/src/visual/MovieStim.js +++ b/src/visual/MovieStim.js @@ -14,6 +14,7 @@ import {Color} from '../util/Color'; import {ColorMixin} from '../util/ColorMixin'; import * as util from '../util/Util'; import {PsychoJS} from "../core/PsychoJS"; +import { to_pixiPoint } from "../util/Pixi.js"; /** @@ -404,7 +405,7 @@ export class MovieStim extends VisualStim this._pixi.scale.y = this.flipVert ? scaleY : -scaleY; // set the position, rotation, and anchor (movie centered on pos): - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); this._pixi.rotation = this.ori * Math.PI / 180; this._pixi.anchor.x = 0.5; this._pixi.anchor.y = 0.5; diff --git a/src/visual/ShapeStim.js b/src/visual/ShapeStim.js index 4594ec0..613dbd0 100644 --- a/src/visual/ShapeStim.js +++ b/src/visual/ShapeStim.js @@ -14,6 +14,7 @@ import {VisualStim} from './VisualStim'; import {Color} from '../util/Color'; import {ColorMixin} from '../util/ColorMixin'; import * as util from '../util/Util'; +import { to_pixiPoint } from "../util/Pixi.js"; import {WindowMixin} from "../core/WindowMixin"; @@ -268,7 +269,7 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin } // set polygon position and rotation: - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); this._pixi.rotation = this.ori * Math.PI / 180.0; } diff --git a/src/visual/Slider.js b/src/visual/Slider.js index d0979a5..ab1af75 100644 --- a/src/visual/Slider.js +++ b/src/visual/Slider.js @@ -16,6 +16,7 @@ import {WindowMixin} from '../core/WindowMixin'; import {Clock} from '../util/Clock'; import * as util from '../util/Util'; import {PsychoJS} from "../core/PsychoJS"; +import { to_pixiPoint } from "../util/Pixi.js"; /** @@ -691,7 +692,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) */ _getPosition_px() { - const position = util.to_pixiPoint(this.pos, this.units, this.win, true); + const position = to_pixiPoint(this.pos, this.units, this.win, true); if (this._compact && (this._style.indexOf(Slider.Style.RADIO) > -1 || this._style.indexOf(Slider.Style.RATING) > -1)) { @@ -729,7 +730,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) if (typeof this._markerPos !== 'undefined') { const visibleMarkerPos = this._ratingToPos([this._markerPos]); - this._marker.position = util.to_pixiPoint(visibleMarkerPos[0], this.units, this.win, true); + this._marker.position = to_pixiPoint(visibleMarkerPos[0], this.units, this.win, true); this._marker.alpha = 1; } else diff --git a/src/visual/TextStim.js b/src/visual/TextStim.js index d41e488..f02c980 100644 --- a/src/visual/TextStim.js +++ b/src/visual/TextStim.js @@ -13,6 +13,7 @@ import {VisualStim} from './VisualStim'; import {Color} from '../util/Color'; import {ColorMixin} from '../util/ColorMixin'; import * as util from '../util/Util'; +import { to_pixiPoint } from "../util/Pixi.js"; /** @@ -314,7 +315,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) this._pixi.scale.y = this._flipVert ? 1 : -1; this._pixi.rotation = this._ori * Math.PI / 180; - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); this._pixi.alpha = this._opacity; this._pixi.zIndex = this._depth; From 6b856e99c3c3c6c08fb53362c98b4e229b37464e Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 13:00:55 +0100 Subject: [PATCH 17/29] util/Util: add more unit tests --- src/util/Util.test.js | 116 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 5 deletions(-) diff --git a/src/util/Util.test.js b/src/util/Util.test.js index d7b09bc..9ffaa25 100644 --- a/src/util/Util.test.js +++ b/src/util/Util.test.js @@ -1,7 +1,113 @@ import assert from "assert"; -import { isNumeric } from "./Util.js"; +import { isNumeric, randint, round, sum, toNumerical, turnSquareBracketsIntoArrays } from "./Util.js"; -assert(isNumeric("1.2")) -assert(isNumeric(0)) -assert(!isNumeric("NaN")) -assert(!isNumeric("hey")) +assert(isNumeric("1.2")); +assert(isNumeric(0)); +assert(!isNumeric("NaN")); +assert(!isNumeric("hey")); + +// number -> number, e.g. 2 -> 2 +assert.equal(2, toNumerical(2)); + +// [number] -> [number], e.g. [1,2,3] -> [1,2,3] +assert.deepEqual([1, 2, 3], toNumerical([1, 2, 3])); +assert(Array.isArray(toNumerical([0]))); + +// numeral string -> number, e.g. "8" -> 8 +assert.deepEqual(8, toNumerical("8")); + +// [number | numeral string] -> [number], e.g. [1, 2, "3"] -> [1,2,3] +assert.deepEqual([1, 2, 3], toNumerical([1, 2, "3"])); + +// Establish what happens when fed an array-like string +assert.deepEqual([1, 2, 3], toNumerical(...turnSquareBracketsIntoArrays("[1, 2, 3][]]", 2))); + +// Throws +(async () => +{ + await assert.rejects( + async () => + { + toNumerical(turnSquareBracketsIntoArrays([1, 2])); + }, + { + origin: "util.toNumerical", + context: "when converting an object to its numerical form", + error: "unable to convert undefined to a number", + }, + ); +})(); + +// Towards a NumPy inspired bound random integer producer +for (let i = 0; i < 100; i += 1) +{ + // Calling sans arguments gives back zero no matter what + assert.equal(randint(), 0); +} + +for (let i = 0; i < 100; i += 1) +{ + // Same when calling with a min of one sans max + assert.equal(randint(1), 0); +} + +// Expect min to be zero, max to be one, result to be zero +assert(randint(1) >= 0 === randint(1) < 1); + +// Same when calling with a min of one sans max +assert.equal(randint(1), 0); + +for (let i = 0; i < 100; i += 1) +{ + // Same with null + assert.equal(randint(null), 0); +} + +for (let i = 100; i > 0; i -= 1) +{ + // Try out a few ranges in the positive + assert(randint(i) < i); +} + +for (let i = -99; i < 0; i += 1) +{ + // What happens when using negative parameters? + assert(randint(2 * i, i) <= i); +} + +try +{ + randint(0, -10); +} +catch ({ error }) +{ + assert.equal(error, "min should be <= max"); +} + +// Implement Crib Sheet math extras +// These are taken from the SO question above +// https://stackoverflow.com/questions/11832914 +const actual = [ + 10, + 1.7777777, + 9.1, +]; + +const expected = [ + 10, + 1.78, + 9.1, +]; + +const got = actual.map((input) => round(input, 2)); + +assert.deepEqual(expected, got); + +assert.equal(sum(null), 0); +assert.equal(sum(), 0); +assert(!sum([0])); +assert.equal(sum([1, NaN, null, undefined]), 1); +assert.equal(sum([1, 2, -3]), 0); + +// Careful Thomas! +assert.equal(sum(["a1", 2]), 2); From 9d37366d5d63de9e93db6020e91a3651d0257037 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 13:12:54 +0100 Subject: [PATCH 18/29] util: fix import paths --- src/util/ColorMixin.js | 2 +- src/util/EventEmitter.js | 2 +- src/util/PsychObject.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/ColorMixin.js b/src/util/ColorMixin.js index 59a31ca..2379bae 100644 --- a/src/util/ColorMixin.js +++ b/src/util/ColorMixin.js @@ -8,7 +8,7 @@ */ -import {Color} from './Color'; +import {Color} from './Color.js'; /** diff --git a/src/util/EventEmitter.js b/src/util/EventEmitter.js index 5f4ad88..3f5814a 100644 --- a/src/util/EventEmitter.js +++ b/src/util/EventEmitter.js @@ -8,7 +8,7 @@ */ -import * as util from './Util'; +import * as util from './Util.js'; /** diff --git a/src/util/PsychObject.js b/src/util/PsychObject.js index 381a6f9..fe955bd 100644 --- a/src/util/PsychObject.js +++ b/src/util/PsychObject.js @@ -9,8 +9,8 @@ */ -import {EventEmitter} from './EventEmitter'; -import * as util from './Util'; +import {EventEmitter} from './EventEmitter.js'; +import * as util from './Util.js'; /** From 904c6e216d39913a5754f989d235350fac474b92 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 13:44:22 +0100 Subject: [PATCH 19/29] core: fix import paths --- src/core/EventManager.js | 4 ++-- src/core/GUI.js | 12 ++++++------ src/core/Keyboard.js | 8 ++++---- src/core/Logger.js | 6 +++--- src/core/MinimalStim.js | 6 +++--- src/core/Mouse.js | 6 +++--- src/core/PsychoJS.js | 18 +++++++++--------- src/core/ServerManager.js | 10 +++++----- src/core/Window.js | 8 ++++---- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/core/EventManager.js b/src/core/EventManager.js index 4ec8e04..c2f7716 100644 --- a/src/core/EventManager.js +++ b/src/core/EventManager.js @@ -7,8 +7,8 @@ * @license Distributed under the terms of the MIT License */ -import {MonotonicClock, Clock} from '../util/Clock'; -import {PsychoJS} from './PsychoJS'; +import {MonotonicClock, Clock} from '../util/Clock.js'; +import {PsychoJS} from './PsychoJS.js'; /** diff --git a/src/core/GUI.js b/src/core/GUI.js index ee30f19..66e84ab 100644 --- a/src/core/GUI.js +++ b/src/core/GUI.js @@ -9,12 +9,12 @@ */ import * as Tone from 'tone'; -import {PsychoJS} from './PsychoJS'; -import {ServerManager} from './ServerManager'; -import {Scheduler} from '../util/Scheduler'; -import {Clock} from '../util/Clock'; -import {ExperimentHandler} from '../data/ExperimentHandler'; -import * as util from '../util/Util'; +import {PsychoJS} from './PsychoJS.js'; +import {ServerManager} from './ServerManager.js'; +import {Scheduler} from '../util/Scheduler.js'; +import {Clock} from '../util/Clock.js'; +import {ExperimentHandler} from '../data/ExperimentHandler.js'; +import * as util from '../util/Util.js'; /** diff --git a/src/core/Keyboard.js b/src/core/Keyboard.js index 1d8f52e..1d09b5d 100644 --- a/src/core/Keyboard.js +++ b/src/core/Keyboard.js @@ -7,10 +7,10 @@ * @license Distributed under the terms of the MIT License */ -import {Clock, MonotonicClock} from "../util/Clock"; -import {PsychObject} from "../util/PsychObject"; -import {PsychoJS} from "./PsychoJS"; -import {EventManager} from "./EventManager"; +import {Clock, MonotonicClock} from "../util/Clock.js"; +import {PsychObject} from "../util/PsychObject.js"; +import {PsychoJS} from "./PsychoJS.js"; +import {EventManager} from "./EventManager.js"; /** diff --git a/src/core/Logger.js b/src/core/Logger.js index 23ddae9..603e73d 100644 --- a/src/core/Logger.js +++ b/src/core/Logger.js @@ -10,9 +10,9 @@ import log4javascript from 'log4javascript'; import pako from 'pako'; -import * as util from '../util/Util'; -import {MonotonicClock} from '../util/Clock'; -import {ExperimentHandler} from '../data/ExperimentHandler'; +import * as util from '../util/Util.js'; +import {MonotonicClock} from '../util/Clock.js'; +import {ExperimentHandler} from '../data/ExperimentHandler.js'; /** *

This class handles a variety of loggers, e.g. a browser console one (mostly for debugging), diff --git a/src/core/MinimalStim.js b/src/core/MinimalStim.js index 60c4bd3..b459cc9 100644 --- a/src/core/MinimalStim.js +++ b/src/core/MinimalStim.js @@ -8,9 +8,9 @@ */ -import {PsychObject} from '../util/PsychObject'; -import {PsychoJS} from './PsychoJS'; -import * as util from '../util/Util'; +import {PsychObject} from '../util/PsychObject.js'; +import {PsychoJS} from './PsychoJS.js'; +import * as util from '../util/Util.js'; diff --git a/src/core/Mouse.js b/src/core/Mouse.js index 39f28ed..6effb21 100644 --- a/src/core/Mouse.js +++ b/src/core/Mouse.js @@ -8,9 +8,9 @@ * @license Distributed under the terms of the MIT License */ -import {PsychoJS} from './PsychoJS'; -import {PsychObject} from '../util/PsychObject'; -import * as util from '../util/Util'; +import {PsychoJS} from './PsychoJS.js'; +import {PsychObject} from '../util/PsychObject.js'; +import * as util from '../util/Util.js'; /** diff --git a/src/core/PsychoJS.js b/src/core/PsychoJS.js index 3b6882b..dc60025 100644 --- a/src/core/PsychoJS.js +++ b/src/core/PsychoJS.js @@ -9,15 +9,15 @@ */ import log4javascript from 'log4javascript'; -import {Scheduler} from '../util/Scheduler'; -import {ServerManager} from './ServerManager'; -import {ExperimentHandler} from '../data/ExperimentHandler'; -import {EventManager} from './EventManager'; -import {Window} from './Window'; -import {GUI} from './GUI'; -import {MonotonicClock} from '../util/Clock'; -import {Logger} from './Logger'; -import * as util from '../util/Util'; +import {Scheduler} from '../util/Scheduler.js'; +import {ServerManager} from './ServerManager.js'; +import {ExperimentHandler} from '../data/ExperimentHandler.js'; +import {EventManager} from './EventManager.js'; +import {Window} from './Window.js'; +import {GUI} from './GUI.js'; +import {MonotonicClock} from '../util/Clock.js'; +import {Logger} from './Logger.js'; +import * as util from '../util/Util.js'; // import {Shelf} from "../data/Shelf"; diff --git a/src/core/ServerManager.js b/src/core/ServerManager.js index 5f8bf1f..2142a34 100644 --- a/src/core/ServerManager.js +++ b/src/core/ServerManager.js @@ -8,11 +8,11 @@ */ import { Howl } from 'howler'; -import {PsychoJS} from './PsychoJS'; -import {PsychObject} from '../util/PsychObject'; -import * as util from '../util/Util'; -import {ExperimentHandler} from "../data/ExperimentHandler"; -import {MonotonicClock, Clock} from "../util/Clock"; +import {PsychoJS} from './PsychoJS.js'; +import {PsychObject} from '../util/PsychObject.js'; +import * as util from '../util/Util.js'; +import {ExperimentHandler} from "../data/ExperimentHandler.js"; +import {MonotonicClock, Clock} from "../util/Clock.js"; /** diff --git a/src/core/Window.js b/src/core/Window.js index 829d031..8926e68 100644 --- a/src/core/Window.js +++ b/src/core/Window.js @@ -8,10 +8,10 @@ */ import * as PIXI from 'pixi.js-legacy'; -import {Color} from '../util/Color'; -import {PsychObject} from '../util/PsychObject'; -import {MonotonicClock} from '../util/Clock'; -import {Logger} from "./Logger"; +import {Color} from '../util/Color.js'; +import {PsychObject} from '../util/PsychObject.js'; +import {MonotonicClock} from '../util/Clock.js'; +import {Logger} from "./Logger.js"; /** *

Window displays the various stimuli of the experiment.

From 24f43ccbbb7e1019347eb4e52645251b7868a2e6 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 13:45:56 +0100 Subject: [PATCH 20/29] data: fix import paths --- src/data/ExperimentHandler.js | 6 +++--- src/data/TrialHandler.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data/ExperimentHandler.js b/src/data/ExperimentHandler.js index 50a2d60..bc50cd8 100644 --- a/src/data/ExperimentHandler.js +++ b/src/data/ExperimentHandler.js @@ -9,9 +9,9 @@ import * as XLSX from 'xlsx'; -import {PsychObject} from '../util/PsychObject'; -import {MonotonicClock} from '../util/Clock'; -import * as util from '../util/Util'; +import {PsychObject} from '../util/PsychObject.js'; +import {MonotonicClock} from '../util/Clock.js'; +import * as util from '../util/Util.js'; /** diff --git a/src/data/TrialHandler.js b/src/data/TrialHandler.js index 47b75a4..e2fc982 100644 --- a/src/data/TrialHandler.js +++ b/src/data/TrialHandler.js @@ -12,8 +12,8 @@ import seedrandom from 'seedrandom'; import * as XLSX from 'xlsx'; -import {PsychObject} from '../util/PsychObject'; -import * as util from '../util/Util'; +import {PsychObject} from '../util/PsychObject.js'; +import * as util from '../util/Util.js'; /** *

A Trial Handler handles the importing and sequencing of conditions.

From 7f6b37d720a439f71d97718170862b2391aa4ee3 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 13:47:36 +0100 Subject: [PATCH 21/29] sound: fix import paths --- src/sound/AudioClip.js | 8 ++++---- src/sound/AudioClipPlayer.js | 4 ++-- src/sound/Microphone.js | 12 ++++++------ src/sound/Sound.js | 10 +++++----- src/sound/SoundPlayer.js | 2 +- src/sound/TonePlayer.js | 2 +- src/sound/TrackPlayer.js | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/sound/AudioClip.js b/src/sound/AudioClip.js index 1c358eb..72c07db 100644 --- a/src/sound/AudioClip.js +++ b/src/sound/AudioClip.js @@ -7,10 +7,10 @@ * @license Distributed under the terms of the MIT License */ -import {PsychObject} from '../util/PsychObject'; -import {PsychoJS} from '../core/PsychoJS'; -import {ExperimentHandler} from '../data/ExperimentHandler'; -import * as util from '../util/Util'; +import {PsychObject} from '../util/PsychObject.js'; +import {PsychoJS} from '../core/PsychoJS.js'; +import {ExperimentHandler} from '../data/ExperimentHandler.js'; +import * as util from '../util/Util.js'; /** diff --git a/src/sound/AudioClipPlayer.js b/src/sound/AudioClipPlayer.js index eb53947..6e819be 100644 --- a/src/sound/AudioClipPlayer.js +++ b/src/sound/AudioClipPlayer.js @@ -7,8 +7,8 @@ * @license Distributed under the terms of the MIT License */ -import {SoundPlayer} from './SoundPlayer'; -import {AudioClip} from "./AudioClip"; +import {SoundPlayer} from './SoundPlayer.js'; +import {AudioClip} from "./AudioClip.js"; /** diff --git a/src/sound/Microphone.js b/src/sound/Microphone.js index e594153..a4dbc17 100644 --- a/src/sound/Microphone.js +++ b/src/sound/Microphone.js @@ -7,12 +7,12 @@ * @license Distributed under the terms of the MIT License */ -import {Clock} from "../util/Clock"; -import {PsychObject} from "../util/PsychObject"; -import {PsychoJS} from "../core/PsychoJS"; -import * as util from '../util/Util'; -import {ExperimentHandler} from "../data/ExperimentHandler"; -import {AudioClip} from "./AudioClip"; +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 {AudioClip} from "./AudioClip.js"; /** *

This manager handles the recording of audio signal.

diff --git a/src/sound/Sound.js b/src/sound/Sound.js index 068cf6c..6d9e0c1 100644 --- a/src/sound/Sound.js +++ b/src/sound/Sound.js @@ -8,11 +8,11 @@ * @license Distributed under the terms of the MIT License */ -import {PsychoJS} from '../core/PsychoJS'; -import {PsychObject} from '../util/PsychObject'; -import {TonePlayer} from './TonePlayer'; -import {TrackPlayer} from './TrackPlayer'; -import {AudioClipPlayer} from './AudioClipPlayer'; +import {PsychoJS} from '../core/PsychoJS.js'; +import {PsychObject} from '../util/PsychObject.js'; +import {TonePlayer} from './TonePlayer.js'; +import {TrackPlayer} from './TrackPlayer.js'; +import {AudioClipPlayer} from './AudioClipPlayer.js'; /** diff --git a/src/sound/SoundPlayer.js b/src/sound/SoundPlayer.js index 8d088a5..9b55d0e 100644 --- a/src/sound/SoundPlayer.js +++ b/src/sound/SoundPlayer.js @@ -7,7 +7,7 @@ * @license Distributed under the terms of the MIT License */ -import {PsychObject} from '../util/PsychObject'; +import {PsychObject} from '../util/PsychObject.js'; /** diff --git a/src/sound/TonePlayer.js b/src/sound/TonePlayer.js index dfacd9a..a7a064e 100644 --- a/src/sound/TonePlayer.js +++ b/src/sound/TonePlayer.js @@ -9,7 +9,7 @@ import * as Tone from 'tone'; import { isNumeric } from "../util/Util.js"; -import {SoundPlayer} from './SoundPlayer'; +import {SoundPlayer} from './SoundPlayer.js'; /** diff --git a/src/sound/TrackPlayer.js b/src/sound/TrackPlayer.js index 058d481..a481a66 100644 --- a/src/sound/TrackPlayer.js +++ b/src/sound/TrackPlayer.js @@ -7,7 +7,7 @@ * @license Distributed under the terms of the MIT License */ -import {SoundPlayer} from './SoundPlayer'; +import {SoundPlayer} from './SoundPlayer.js'; /** From 67873dabd4e84325627efb35b01740193bce1c0c Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 13:50:36 +0100 Subject: [PATCH 22/29] visual: fix import paths --- src/visual/Form.js | 16 ++++++++-------- src/visual/ImageStim.js | 8 ++++---- src/visual/MovieStim.js | 10 +++++----- src/visual/Polygon.js | 4 ++-- src/visual/Rect.js | 4 ++-- src/visual/ShapeStim.js | 10 +++++----- src/visual/Slider.js | 14 +++++++------- src/visual/TextBox.js | 10 +++++----- src/visual/TextStim.js | 8 ++++---- src/visual/VisualStim.js | 6 +++--- 10 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/visual/Form.js b/src/visual/Form.js index c931c0a..a526ffd 100644 --- a/src/visual/Form.js +++ b/src/visual/Form.js @@ -9,14 +9,14 @@ import * as PIXI from 'pixi.js-legacy'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; -import {TrialHandler} from '../data/TrialHandler'; -import {TextStim} from './TextStim'; -import {TextBox} from './TextBox'; -import {VisualStim} from './VisualStim'; -import {Slider} from './Slider'; +import {Color} from '../util/Color.js'; +import {ColorMixin} from '../util/ColorMixin.js'; +import * as util from '../util/Util.js'; +import {TrialHandler} from '../data/TrialHandler.js'; +import {TextStim} from './TextStim.js'; +import {TextBox} from './TextBox.js'; +import {VisualStim} from './VisualStim.js'; +import {Slider} from './Slider.js'; import { to_pixiPoint } from "../util/Pixi.js"; diff --git a/src/visual/ImageStim.js b/src/visual/ImageStim.js index 62273bd..39479a8 100644 --- a/src/visual/ImageStim.js +++ b/src/visual/ImageStim.js @@ -9,10 +9,10 @@ import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; +import {VisualStim} from './VisualStim.js'; +import {Color} from '../util/Color.js'; +import {ColorMixin} from '../util/ColorMixin.js'; +import * as util from '../util/Util.js'; import { to_pixiPoint } from "../util/Pixi.js"; diff --git a/src/visual/MovieStim.js b/src/visual/MovieStim.js index 3dec9a9..475b05f 100644 --- a/src/visual/MovieStim.js +++ b/src/visual/MovieStim.js @@ -9,11 +9,11 @@ import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; -import {PsychoJS} from "../core/PsychoJS"; +import {VisualStim} from './VisualStim.js'; +import {Color} from '../util/Color.js'; +import {ColorMixin} from '../util/ColorMixin.js'; +import * as util from '../util/Util.js'; +import {PsychoJS} from "../core/PsychoJS.js"; import { to_pixiPoint } from "../util/Pixi.js"; diff --git a/src/visual/Polygon.js b/src/visual/Polygon.js index d861ffa..1f74e8d 100644 --- a/src/visual/Polygon.js +++ b/src/visual/Polygon.js @@ -8,8 +8,8 @@ */ -import {ShapeStim} from './ShapeStim'; -import {Color} from '../util/Color'; +import {ShapeStim} from './ShapeStim.js'; +import {Color} from '../util/Color.js'; /** diff --git a/src/visual/Rect.js b/src/visual/Rect.js index 063897f..43740bf 100644 --- a/src/visual/Rect.js +++ b/src/visual/Rect.js @@ -8,8 +8,8 @@ */ -import {ShapeStim} from './ShapeStim'; -import {Color} from '../util/Color'; +import {ShapeStim} from './ShapeStim.js'; +import {Color} from '../util/Color.js'; /** diff --git a/src/visual/ShapeStim.js b/src/visual/ShapeStim.js index 613dbd0..8aeab4b 100644 --- a/src/visual/ShapeStim.js +++ b/src/visual/ShapeStim.js @@ -10,12 +10,12 @@ import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; +import {VisualStim} from './VisualStim.js'; +import {Color} from '../util/Color.js'; +import {ColorMixin} from '../util/ColorMixin.js'; +import * as util from '../util/Util.js'; import { to_pixiPoint } from "../util/Pixi.js"; -import {WindowMixin} from "../core/WindowMixin"; +import {WindowMixin} from "../core/WindowMixin.js"; /** diff --git a/src/visual/Slider.js b/src/visual/Slider.js index ab1af75..aab8448 100644 --- a/src/visual/Slider.js +++ b/src/visual/Slider.js @@ -9,13 +9,13 @@ import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import {WindowMixin} from '../core/WindowMixin'; -import {Clock} from '../util/Clock'; -import * as util from '../util/Util'; -import {PsychoJS} from "../core/PsychoJS"; +import {VisualStim} from './VisualStim.js'; +import {Color} from '../util/Color.js'; +import {ColorMixin} from '../util/ColorMixin.js'; +import {WindowMixin} from '../core/WindowMixin.js'; +import {Clock} from '../util/Clock.js'; +import * as util from '../util/Util.js'; +import {PsychoJS} from "../core/PsychoJS.js"; import { to_pixiPoint } from "../util/Pixi.js"; diff --git a/src/visual/TextBox.js b/src/visual/TextBox.js index 01b2b7e..313f5d9 100644 --- a/src/visual/TextBox.js +++ b/src/visual/TextBox.js @@ -9,12 +9,12 @@ import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import {TextInput} from './TextInput'; +import {VisualStim} from './VisualStim.js'; +import {Color} from '../util/Color.js'; +import {ColorMixin} from '../util/ColorMixin.js'; +import {TextInput} from './TextInput.js'; import {ButtonStim} from './ButtonStim.js'; -import * as util from '../util/Util'; +import * as util from '../util/Util.js'; // TODO finish documenting all options /** diff --git a/src/visual/TextStim.js b/src/visual/TextStim.js index f02c980..c214377 100644 --- a/src/visual/TextStim.js +++ b/src/visual/TextStim.js @@ -9,10 +9,10 @@ import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; +import {VisualStim} from './VisualStim.js'; +import {Color} from '../util/Color.js'; +import {ColorMixin} from '../util/ColorMixin.js'; +import * as util from '../util/Util.js'; import { to_pixiPoint } from "../util/Pixi.js"; diff --git a/src/visual/VisualStim.js b/src/visual/VisualStim.js index 4f1b815..7c6919b 100644 --- a/src/visual/VisualStim.js +++ b/src/visual/VisualStim.js @@ -9,9 +9,9 @@ import * as PIXI from 'pixi.js-legacy'; -import {MinimalStim} from '../core/MinimalStim'; -import {WindowMixin} from '../core/WindowMixin'; -import * as util from '../util/Util'; +import {MinimalStim} from '../core/MinimalStim.js'; +import {WindowMixin} from '../core/WindowMixin.js'; +import * as util from '../util/Util.js'; /** From 61fb7744a1c2ad6b6b487f68b1466ceab2429c6e Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 14:06:22 +0100 Subject: [PATCH 23/29] dprint: prefer double quotes only if it makes sense --- .dprint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.dprint.json b/.dprint.json index e98b17b..0cea429 100644 --- a/.dprint.json +++ b/.dprint.json @@ -8,7 +8,7 @@ "typescript": { "useTabs": true, "semiColons": "always", - "quoteStyle": "alwaysDouble", + "quoteStyle": "preferDouble", "newLineKind": "lf", "useBraces": "always", "bracePosition": "nextLine", From c9cb3c84123ded3100c12e6d35da57160785f102 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 14:07:04 +0100 Subject: [PATCH 24/29] visual: enforce consistent formatting --- src/visual/ButtonStim.js | 74 ++++++-- src/visual/Form.js | 387 ++++++++++++++++++-------------------- src/visual/ImageStim.js | 118 ++++++------ src/visual/MovieStim.js | 138 ++++++-------- src/visual/Polygon.js | 33 ++-- src/visual/Rect.js | 39 ++-- src/visual/ShapeStim.js | 123 ++++++------ src/visual/Slider.js | 393 ++++++++++++++++++--------------------- src/visual/TextBox.js | 276 ++++++++++++++------------- src/visual/TextInput.js | 6 +- src/visual/TextStim.js | 207 +++++++++++---------- src/visual/VisualStim.js | 101 ++++------ src/visual/index.js | 24 +-- 13 files changed, 917 insertions(+), 1002 deletions(-) diff --git a/src/visual/ButtonStim.js b/src/visual/ButtonStim.js index 4055de4..bc422e5 100644 --- a/src/visual/ButtonStim.js +++ b/src/visual/ButtonStim.js @@ -7,10 +7,8 @@ * @license Distributed under the terms of the MIT License */ - -import {TextBox} from './TextBox.js'; -import {Mouse} from '../core/Mouse.js'; - +import { Mouse } from "../core/Mouse.js"; +import { TextBox } from "./TextBox.js"; /** *

ButtonStim visual stimulus.

@@ -39,28 +37,71 @@ import {Mouse} from '../core/Mouse.js'; */ export class ButtonStim extends TextBox { - constructor({win, name, text, font, pos, size, padding, anchor = 'center', units, color, fillColor = 'darkgrey', borderColor, borderWidth = 0, opacity, letterHeight, bold = true, italic, autoDraw, autoLog} = {}) + constructor( + { + win, + name, + text, + font, + pos, + size, + padding, + anchor = "center", + units, + color, + fillColor = "darkgrey", + borderColor, + borderWidth = 0, + opacity, + letterHeight, + bold = true, + italic, + autoDraw, + autoLog, + } = {}, + ) { - super({win, name, text, font, pos, size, padding, anchor, units, color, fillColor, borderColor, borderWidth, opacity, letterHeight, bold, italic, alignment: 'center', autoDraw, autoLog}); + super({ + win, + name, + text, + font, + pos, + size, + padding, + anchor, + units, + color, + fillColor, + borderColor, + borderWidth, + opacity, + letterHeight, + bold, + italic, + alignment: "center", + autoDraw, + autoLog, + }); - this.psychoJS.logger.debug('create a new Button with name: ', name); + this.psychoJS.logger.debug("create a new Button with name: ", name); - this.listener = new Mouse({name, win, autoLog}); + this.listener = new Mouse({ name, win, autoLog }); this._addAttribute( - 'wasClicked', - false + "wasClicked", + false, ); // Arrays to store times of clicks on and off this._addAttribute( - 'timesOn', - [] + "timesOn", + [], ); this._addAttribute( - 'timesOff', - [] + "timesOff", + [], ); if (this._autoLog) @@ -69,8 +110,6 @@ export class ButtonStim extends TextBox } } - - /** * How many times has this button been clicked on? * @@ -82,8 +121,6 @@ export class ButtonStim extends TextBox return this.timesOn.length; } - - /** * Is this button currently being clicked on? * @@ -94,5 +131,4 @@ export class ButtonStim extends TextBox { return this.listener.isPressedIn(this, [1, 0, 0]); } - } diff --git a/src/visual/Form.js b/src/visual/Form.js index a526ffd..5d7001f 100644 --- a/src/visual/Form.js +++ b/src/visual/Form.js @@ -7,19 +7,16 @@ * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {Color} from '../util/Color.js'; -import {ColorMixin} from '../util/ColorMixin.js'; -import * as util from '../util/Util.js'; -import {TrialHandler} from '../data/TrialHandler.js'; -import {TextStim} from './TextStim.js'; -import {TextBox} from './TextBox.js'; -import {VisualStim} from './VisualStim.js'; -import {Slider} from './Slider.js'; +import * as PIXI from "pixi.js-legacy"; +import { TrialHandler } from "../data/TrialHandler.js"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; import { to_pixiPoint } from "../util/Pixi.js"; - - +import * as util from "../util/Util.js"; +import { Slider } from "./Slider.js"; +import { TextBox } from "./TextBox.js"; +import { TextStim } from "./TextStim.js"; +import { VisualStim } from "./VisualStim.js"; /** * Form stimulus. @@ -58,99 +55,127 @@ import { to_pixiPoint } from "../util/Pixi.js"; */ export class Form extends util.mix(VisualStim).with(ColorMixin) { - constructor({name, win, pos, size, units, borderColor, fillColor, itemColor, markerColor, responseColor, color, contrast, opacity, depth, items, randomize, itemPadding, font, fontFamily, bold, italic, fontSize, clipMask, autoDraw, autoLog} = {}) + constructor( + { + name, + win, + pos, + size, + units, + borderColor, + fillColor, + itemColor, + markerColor, + responseColor, + color, + contrast, + opacity, + depth, + items, + randomize, + itemPadding, + font, + fontFamily, + bold, + italic, + fontSize, + clipMask, + autoDraw, + autoLog, + } = {}, + ) { - super({name, win, units, opacity, depth, pos, size, clipMask, autoDraw, autoLog}); + super({ name, win, units, opacity, depth, pos, size, clipMask, autoDraw, autoLog }); this._addAttribute( - 'itemPadding', + "itemPadding", itemPadding, - util.to_unit([20, 0], 'pix', win, this._units)[0], - this._onChange(true, false) + util.to_unit([20, 0], "pix", win, this._units)[0], + this._onChange(true, false), ); // colors: this._addAttribute( - 'color', + "color", // Same as itemColor color, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'borderColor', + "borderColor", borderColor, fillColor, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'fillColor', + "fillColor", fillColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'itemColor', + "itemColor", itemColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'markerColor', + "markerColor", markerColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'responseColor', + "responseColor", responseColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); // fonts: this._addAttribute( - 'font', + "font", font, - 'Arial', - this._onChange(true, true) + "Arial", + this._onChange(true, true), ); // Not in use at present this._addAttribute( - 'fontFamily', + "fontFamily", fontFamily, - 'Helvetica', - this._onChange(true, true) + "Helvetica", + this._onChange(true, true), ); this._addAttribute( - 'fontSize', + "fontSize", fontSize, - (this._units === 'pix') ? 14 : 0.03, - this._onChange(true, true) + (this._units === "pix") ? 14 : 0.03, + this._onChange(true, true), ); this._addAttribute( - 'bold', + "bold", bold, false, - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'italic', + "italic", italic, false, - this._onChange(true, true) + this._onChange(true, true), ); // callback to deal with changes to items: @@ -166,16 +191,17 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) }; this._addAttribute( - 'items', + "items", items, [], - onItemChange); + onItemChange, + ); this._addAttribute( - 'randomize', + "randomize", randomize, false, - onItemChange); - + onItemChange, + ); this._scrollbarWidth = 0.02; this._responseTextHeightRatio = 0.8; @@ -192,8 +218,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } } - - /** * Force a refresh of the stimulus. * @@ -218,8 +242,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } } - - /** * Overridden draw that also calls the draw method of all form elements. * @@ -260,8 +282,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._scrollbar.draw(); } - - /** * Overridden hide that also calls the hide method of all form elements. * @@ -276,7 +296,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) super.hide(); // hide the stimuli: - if (typeof this._items !== 'undefined') + if (typeof this._items !== "undefined") { for (let i = 0; i < this._items.length; ++i) { @@ -298,8 +318,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } } - - /** * Reset the form. * @@ -309,7 +327,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) */ reset() { - this.psychoJS.logger.debug('reset Form: ', this._name); + this.psychoJS.logger.debug("reset Form: ", this._name); // reset the stimuli: for (let i = 0; i < this._items.length; ++i) @@ -327,8 +345,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._needUpdate = true; } - - /** * Collate the questions and responses into a single dataset. * @@ -352,9 +368,9 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) item.response = responseStim.getRating(); item.rt = responseStim.getRT(); - if (typeof item.response === 'undefined') + if (typeof item.response === "undefined") { - ++ nbIncompleteResponse; + ++nbIncompleteResponse; } } else if (item.type === Form.Types.FREE_TEXT) @@ -364,7 +380,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) if (item.response.length === 0) { - ++ nbIncompleteResponse; + ++nbIncompleteResponse; } } } @@ -372,9 +388,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._items._complete = (nbIncompleteResponse === 0); - // return a copy of this._items: - return this._items.map(item => Object.assign({}, item)); + return this._items.map((item) => Object.assign({}, item)); } /** * Check if the form is complete. @@ -386,7 +401,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) */ formComplete() { - //same as complete but might be used by some experiments before 2020.2 + // same as complete but might be used by some experiments before 2020.2 this.getData(); return this._items._complete; } @@ -399,15 +414,21 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) * @param {module:data.ExperimentHandler} experiment - the experiment into which to insert the form data * @param {string} [format= 'rows'] - whether to insert the data as rows or as columns */ - addDataToExp(experiment, format = 'rows') + addDataToExp(experiment, format = "rows") { - const addAsColumns = ['cols', 'columns'].includes(format.toLowerCase()); + const addAsColumns = ["cols", "columns"].includes(format.toLowerCase()); const data = this.getData(); const _doNotSave = [ - 'itemCtrl', 'responseCtrl', - 'itemColor', 'options', 'ticks', 'tickLabels', - 'responseWidth', 'responseColor', 'layout' + "itemCtrl", + "responseCtrl", + "itemColor", + "options", + "ticks", + "tickLabels", + "responseWidth", + "responseColor", + "layout", ]; for (const item of this.getData()) @@ -420,7 +441,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) const columnName = (addAsColumns) ? `${this._name}[${index}]${field}` : `${this._name}${field}`; experiment.addData(columnName, item[field]); } - ++ index; + ++index; } if (!addAsColumns) @@ -435,8 +456,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } } - - /** * Import and process the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array. * @@ -447,8 +466,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) _processItems() { const response = { - origin: 'Form._processItems', - context: 'when processing the form items' + origin: "Form._processItems", + context: "when processing the form items", }; try @@ -456,7 +475,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) if (this._autoLog) { // note: we use the same log message as PsychoPy even though we called this method differently - this._psychoJS.experimentLogger.exp('Importing items...'); + this._psychoJS.experimentLogger.exp("Importing items..."); } // import the items: @@ -474,12 +493,10 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Import the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array. * @@ -490,8 +507,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) _importItems() { const response = { - origin: 'Form._importItems', - context: 'when importing the form items' + origin: "Form._importItems", + context: "when importing the form items", }; try @@ -499,17 +516,15 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) const itemsType = typeof this._items; // we treat undefined items as a list with a single default entry: - if (itemsType === 'undefined') + if (itemsType === "undefined") { this._items = [Form._defaultItems]; } - // if items is a string, we treat it as the name of a resource file and import it: - else if (itemsType === 'string') + else if (itemsType === "string") { this._items = TrialHandler.importConditions(this._psychoJS.serverManager, this._items); } - // unknown items type: else { @@ -521,17 +536,14 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) { this._items = [Form._defaultItems]; } - } catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Sanitize the form items: check that the keys are valid, and fill in default values. * @@ -542,8 +554,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) _sanitizeItems() { const response = { - origin: 'Form._sanitizeItems', - context: 'when sanitizing the form items' + origin: "Form._sanitizeItems", + context: "when sanitizing the form items", }; try @@ -552,7 +564,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) for (const item of this._items) { // old style forms have questionText instead of itemText: - if (typeof item.questionText !== 'undefined') + if (typeof item.questionText !== "undefined") { item.itemText = item.questionText; delete item.questionText; @@ -561,12 +573,11 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) delete item.questionWidth; // for items of type 'rating, the ticks are in 'options' instead of in 'ticks': - if (item.type === 'rating' || item.type === 'slider') + if (item.type === "rating" || item.type === "slider") { item.ticks = item.options; item.options = undefined; } - } } @@ -584,9 +595,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) missingKeys.add(key); item[key] = Form._defaultItems[key]; } - // undefined value: - else if (typeof item[key] === 'undefined') + else if (typeof item[key] === "undefined") { // TODO: options = '' for FREE_TEXT item[key] = Form._defaultItems[key]; @@ -596,16 +606,17 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) if (missingKeys.size > 0) { - this._psychoJS.logger.warn(`Missing headers: ${Array.from(missingKeys).join(', ')}\nNote, headers are case sensitive and must match: ${Array.from(defaultKeys).join(', ')}`); + this._psychoJS.logger.warn( + `Missing headers: ${Array.from(missingKeys).join(", ")}\nNote, headers are case sensitive and must match: ${Array.from(defaultKeys).join(", ")}`, + ); } - // check the types and options: const formTypes = Object.getOwnPropertyNames(Form.Types); for (const item of this._items) { // convert type to upper case, replace spaces by underscores - item.type = item.type.toUpperCase().replace(' ', '_'); + item.type = item.type.toUpperCase().replace(" ", "_"); // check that the type is valid: if (!formTypes.includes(item.type)) @@ -614,9 +625,9 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } // Support the 'radio' type found on older versions of PsychoPy - if (item.type === 'RADIO') + if (item.type === "RADIO") { - item.type = 'CHOICE'; + item.type = "CHOICE"; } // convert item type to symbol: @@ -625,18 +636,17 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) // turn the option into an array and check length, where applicable: if (item.type === Form.Types.CHOICE) { - item.options = item.options.split(','); + item.options = item.options.split(","); if (item.options.length < 2) { throw `at least two choices should be provided for choice item: ${item.itemText}`; } } - // turn the ticks and tickLabels into arrays, where applicable: else if (item.type === Form.Types.RATING || item.type === Form.Types.SLIDER) { - item.ticks = item.ticks.split(',').map( (_,t) => parseInt(t) ); - item.tickLabels = (item.tickLabels.length > 0) ? item.tickLabels.split(',') : []; + item.ticks = item.ticks.split(",").map((_, t) => parseInt(t)); + item.tickLabels = (item.tickLabels.length > 0) ? item.tickLabels.split(",") : []; } // TODO @@ -645,7 +655,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } // check the layout: - const formLayouts = ['HORIZ', 'VERT']; + const formLayouts = ["HORIZ", "VERT"]; for (const item of this._items) { // convert layout to upper case: @@ -658,18 +668,16 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } // convert item layout to symbol: - item.layout = (item.layout === 'HORIZ') ? Form.Layout.HORIZONTAL : Form.Layout.VERTICAL; + item.layout = (item.layout === "HORIZ") ? Form.Layout.HORIZONTAL : Form.Layout.VERTICAL; } } catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Estimate the bounding box. * @@ -685,12 +693,10 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._pos[0] - this._size[0] / 2.0, this._pos[1] - this._size[1] / 2.0, this._size[0], - this._size[1] + this._size[1], ); } - - /** * Setup the stimuli, and the scrollbar. * @@ -706,7 +712,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } // clean up the previously setup stimuli: - if (typeof this._visual !== 'undefined') + if (typeof this._visual !== "undefined") { for (const textStim of this._visual.textStims) { @@ -724,31 +730,30 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) textStims: [], responseStims: [], visibles: [], - stimuliTotalHeight: 0 + stimuliTotalHeight: 0, }; // instantiate the clip mask that will be used by all stimuli: this._stimuliClipMask = new PIXI.Graphics(); - // default stimulus options: const textStimOption = { win: this._win, - name: 'item text', + name: "item text", font: this.font, units: this._units, - alignHoriz: 'left', - alignVert: 'top', + alignHoriz: "left", + alignVert: "top", height: this._fontSize, color: this.itemColor, ori: 0, opacity: 1, depth: this._depth + 1, - clipMask: this._stimuliClipMask + clipMask: this._stimuliClipMask, }; const sliderOption = { win: this._win, - name: 'choice response', + name: "choice response", units: this._units, flip: false, // Not part of Slider options as things stand @@ -763,13 +768,13 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) opacity: 1, depth: this._depth + 1, clipMask: this._stimuliClipMask, - granularity: 1 + granularity: 1, }; const textBoxOption = { win: this._win, - name: 'free text response', + name: "free text response", units: this._units, - anchor: 'left-top', + anchor: "left-top", flip: false, opacity: 1, depth: this._depth + 1, @@ -777,7 +782,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) letterHeight: this._fontSize * this._responseTextHeightRatio, bold: false, italic: false, - alignment: 'left', + alignment: "left", color: this.responseColor, fillColor: this.fillColor, contrast: 1.0, @@ -785,17 +790,16 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) borderWidth: 0.002, padding: 0.01, editable: true, - clipMask: this._stimuliClipMask + clipMask: this._stimuliClipMask, }; // we use for the slider's tick size the height of a word: - const textStim = new TextStim(Object.assign(textStimOption, { text: 'Ag', pos: [0, 0]})); + const textStim = new TextStim(Object.assign(textStimOption, { text: "Ag", pos: [0, 0] })); const textMetrics_px = textStim.getTextMetrics(); const sliderTickSize = this._getLengthUnits(textMetrics_px.height) / 2; textStim.release(false); - - let stimulusOffset = - this._itemPadding; + let stimulusOffset = -this._itemPadding; for (const item of this._items) { // initially, all items are invisible: @@ -806,8 +810,10 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) // - description: + + + = this._size[0] // - choice with vert layout: + + + = this._size[0] let rowWidth; - if (item.type === Form.Types.HEADING || item.type === Form.Types.DESCRIPTION || - (item.type === Form.Types.CHOICE && item.layout === Form.Layout.VERTICAL)) + if ( + item.type === Form.Types.HEADING || item.type === Form.Types.DESCRIPTION + || (item.type === Form.Types.CHOICE && item.layout === Form.Layout.VERTICAL) + ) { rowWidth = (this._size[0] - this._itemPadding * 2 - this._scrollbarWidth); } @@ -818,12 +824,13 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } // item text - const itemWidth = rowWidth * item.itemWidth; + const itemWidth = rowWidth * item.itemWidth; const textStim = new TextStim( Object.assign(textStimOption, { text: item.itemText, - wrapWidth: itemWidth - })); + wrapWidth: itemWidth, + }), + ); textStim._relativePos = [this._itemPadding, stimulusOffset]; const textHeight = textStim.boundingBox.height; this._visual.textStims.push(textStim); @@ -847,7 +854,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } else { - sliderSize = [sliderTickSize, (sliderTickSize*1.5) * item.options.length]; + sliderSize = [sliderTickSize, (sliderTickSize * 1.5) * item.options.length]; compact = false; flip = true; } @@ -882,23 +889,23 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) labels, ticks, compact, - flip - }) + flip, + }), ); responseHeight = responseStim.boundingBox.height; if (item.layout === Form.Layout.HORIZONTAL) { responseStim._relativePos = [ this._itemPadding * 2 + itemWidth + responseWidth / 2, - stimulusOffset - //- Math.max(0, (textHeight - responseHeight) / 2) // (vertical centering) + stimulusOffset, + // - Math.max(0, (textHeight - responseHeight) / 2) // (vertical centering) ]; } else { responseStim._relativePos = [ - this._itemPadding * 2 + itemWidth, //this._itemPadding + sliderTickSize, - stimulusOffset - responseHeight / 2 - textHeight - this._itemPadding + this._itemPadding * 2 + itemWidth, // this._itemPadding + sliderTickSize, + stimulusOffset - responseHeight / 2 - textHeight - this._itemPadding, ]; // since rowHeight will be the max of itemHeight and responseHeight, we need to alter responseHeight @@ -906,20 +913,19 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) responseHeight += textHeight + this._itemPadding; } } - // FREE TEXT else if (item.type === Form.Types.FREE_TEXT) { responseStim = new TextBox( Object.assign(textBoxOption, { text: item.options, - size: [responseWidth, -1] - }) + size: [responseWidth, -1], + }), ); responseHeight = responseStim.boundingBox.height; responseStim._relativePos = [ this._itemPadding * 2 + itemWidth, - stimulusOffset + stimulusOffset, ]; } @@ -932,13 +938,12 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) } this._visual.stimuliTotalHeight = stimulusOffset; - // 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', + name: "scrollbar", units: this._units, color: this.itemColor, depth: this._depth + 1, @@ -946,24 +951,20 @@ 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] + dependentStims: [this], }); this._prevScrollbarMarkerPos = 0; this._scrollbar.setMarkerPos(this._prevScrollbarMarkerPos); - // estimate the bounding box: this._estimateBoundingBox(); - if (this._autoLog) { this._psychoJS.experimentLogger.exp(`Layout set for: ${this.name}`); } } - - /** * Update the form visual representation, if necessary. * @@ -991,17 +992,18 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) [this._leftEdge, this._topEdge], this.units, this.win, - true); + true, + ); [this._rightEdge_px, this._bottomEdge_px] = util.to_px( [this._rightEdge, this._bottomEdge], this.units, this.win, - true); + true, + ); this._itemPadding_px = this._getLengthPix(this._itemPadding); this._scrollbarWidth_px = this._getLengthPix(this._scrollbarWidth, true); this._size_px = util.to_px(this._size, this.units, this.win, true); - // update the stimuli clip mask // note: the clip mask is in screen coordinates this._stimuliClipMask.clear(); @@ -1010,11 +1012,10 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._win._rootContainer.position.x + this._leftEdge_px + 2, this._win._rootContainer.position.y + this._bottomEdge_px + 2, this._size_px[0] - 4, - this._size_px[1] - 6 + this._size_px[1] - 6, ); this._stimuliClipMask.endFill(); - // position the scrollbar and get the scrollbar offset, in form units: this._scrollbar.setPos([this._rightEdge - this._scrollbarWidth / 2, this._pos[1]], false); this._scrollbar.setOpacity(0.5); @@ -1025,8 +1026,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._updateDecorations(); } - - /** * Update the visible stimuli. * @@ -1042,7 +1041,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) const textStim = this._visual.textStims[i]; const textStimPos = [ this._leftEdge + textStim._relativePos[0], - this._topEdge + textStim._relativePos[1] - this._scrollbarOffset + this._topEdge + textStim._relativePos[1] - this._scrollbarOffset, ]; textStim.setPos(textStimPos); @@ -1052,7 +1051,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) { const responseStimPos = [ this._leftEdge + responseStim._relativePos[0], - this._topEdge + responseStim._relativePos[1] - this._scrollbarOffset + this._topEdge + responseStim._relativePos[1] - this._scrollbarOffset, ]; responseStim.setPos(responseStimPos); } @@ -1078,11 +1077,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._visual.visibles[i] = false; } } - } - - /** * Update the form decorations (bounding box, lines between items, etc.) * @@ -1092,7 +1088,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) */ _updateDecorations() { - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } @@ -1109,7 +1105,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) // apply the form clip mask (n.b., that is not the stimuli clip mask): this._pixi.mask = this._clipMask; - // form background: this._pixi.lineStyle(1, new Color(this.borderColor).int, this._opacity, 0.5); // this._decorations.beginFill(this._barFillColor.int, this._opacity); @@ -1122,7 +1117,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) this._decorations = new PIXI.Graphics(); this._pixi.addChild(this._decorations); this._decorations.mask = this._stimuliClipMask; - this._decorations.lineStyle(1, new Color('gray').int, this._opacity, 0.5); + this._decorations.lineStyle(1, new Color("gray").int, this._opacity, 0.5); this._decorations.alpha = 0.5; for (let i = 0; i < this._items.length; ++i) @@ -1136,27 +1131,23 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) const textStim = this._visual.textStims[i]; const textStimPos = [ this._leftEdge + textStim._relativePos[0], - this._topEdge + textStim._relativePos[1] - this._scrollbarOffset + this._topEdge + textStim._relativePos[1] - this._scrollbarOffset, ]; const textStimPos_px = util.to_px(textStimPos, this._units, this._win); - this._decorations.beginFill(new Color('darkgray').int); + this._decorations.beginFill(new Color("darkgray").int); this._decorations.drawRect( textStimPos_px[0] - this._itemPadding_px / 2, textStimPos_px[1] + this._itemPadding_px / 2, this._size_px[0] - this._itemPadding_px - this._scrollbarWidth_px, - -this._getLengthPix(this._visual.rowHeights[i]) - this._itemPadding_px + -this._getLengthPix(this._visual.rowHeights[i]) - this._itemPadding_px, ); this._decorations.endFill(); } } } - - } } - - /** * Form item types. * @@ -1165,17 +1156,15 @@ export class Form extends util.mix(VisualStim).with(ColorMixin) * @public */ Form.Types = { - HEADING: Symbol.for('HEADING'), - DESCRIPTION: Symbol.for('DESCRIPTION'), - RATING: Symbol.for('RATING'), - SLIDER: Symbol.for('SLIDER'), - FREE_TEXT: Symbol.for('FREE_TEXT'), - CHOICE: Symbol.for('CHOICE'), - RADIO: Symbol.for('RADIO') + HEADING: Symbol.for("HEADING"), + DESCRIPTION: Symbol.for("DESCRIPTION"), + RATING: Symbol.for("RATING"), + SLIDER: Symbol.for("SLIDER"), + FREE_TEXT: Symbol.for("FREE_TEXT"), + CHOICE: Symbol.for("CHOICE"), + RADIO: Symbol.for("RADIO"), }; - - /** * Form item layout. * @@ -1184,12 +1173,10 @@ Form.Types = { * @public */ Form.Layout = { - HORIZONTAL: Symbol.for('HORIZONTAL'), - VERTICAL: Symbol.for('VERTICAL') + HORIZONTAL: Symbol.for("HORIZONTAL"), + VERTICAL: Symbol.for("VERTICAL"), }; - - /** * Default form item. * @@ -1198,18 +1185,16 @@ Form.Layout = { * */ Form._defaultItems = { - 'itemText': 'Default question', - 'type': 'rating', - 'options': 'Yes, No', - 'tickLabels': '', - 'itemWidth': 0.7, - 'itemColor': 'white', + "itemText": "Default question", + "type": "rating", + "options": "Yes, No", + "tickLabels": "", + "itemWidth": 0.7, + "itemColor": "white", - 'responseWidth': 0.3, - 'responseColor': 'white', + "responseWidth": 0.3, + "responseColor": "white", - 'index': 0, - 'layout': 'horiz' + "index": 0, + "layout": "horiz", }; - - diff --git a/src/visual/ImageStim.js b/src/visual/ImageStim.js index 39479a8..29c542a 100644 --- a/src/visual/ImageStim.js +++ b/src/visual/ImageStim.js @@ -7,14 +7,12 @@ * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim.js'; -import {Color} from '../util/Color.js'; -import {ColorMixin} from '../util/ColorMixin.js'; -import * as util from '../util/Util.js'; +import * as PIXI from "pixi.js-legacy"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; import { to_pixiPoint } from "../util/Pixi.js"; - +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; /** * Image Stimulus. @@ -46,53 +44,53 @@ import { to_pixiPoint } from "../util/Pixi.js"; */ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) { - constructor({name, win, image, mask, pos, units, ori, size, color, opacity, contrast, texRes, depth, interpolate, flipHoriz, flipVert, autoDraw, autoLog} = {}) + constructor({ name, win, image, mask, pos, units, ori, size, color, opacity, contrast, texRes, depth, interpolate, flipHoriz, flipVert, autoDraw, autoLog } = {}) { - super({name, win, units, ori, opacity, depth, pos, size, autoDraw, autoLog}); + super({ name, win, units, ori, opacity, depth, pos, size, autoDraw, autoLog }); this._addAttribute( - 'image', - image + "image", + image, ); this._addAttribute( - 'mask', - mask + "mask", + mask, ); this._addAttribute( - 'color', + "color", color, - 'white', - this._onChange(true, false) + "white", + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'texRes', + "texRes", texRes, 128, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'interpolate', + "interpolate", interpolate, false, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'flipHoriz', + "flipHoriz", flipHoriz, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'flipVert', + "flipVert", flipVert, false, - this._onChange(false, false) + this._onChange(false, false), ); // estimate the bounding box: @@ -104,8 +102,6 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) } } - - /** * Setter for the image attribute. * @@ -117,22 +113,22 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) setImage(image, log = false) { const response = { - origin: 'ImageStim.setImage', - context: 'when setting the image of ImageStim: ' + this._name + origin: "ImageStim.setImage", + context: "when setting the image of ImageStim: " + this._name, }; try { // image is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem - if (typeof image === 'undefined') + if (typeof image === "undefined") { - this.psychoJS.logger.warn('setting the image of ImageStim: ' + this._name + ' with argument: undefined.'); - this.psychoJS.logger.debug('set the image of ImageStim: ' + this._name + ' as: undefined'); + this.psychoJS.logger.warn("setting the image of ImageStim: " + this._name + " with argument: undefined."); + this.psychoJS.logger.debug("set the image of ImageStim: " + this._name + " as: undefined"); } else { // image is a string: it should be the name of a resource, which we load - if (typeof image === 'string') + if (typeof image === "string") { image = this.psychoJS.serverManager.getResource(image); } @@ -140,16 +136,16 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) // image should now be an actual HTMLImageElement: we raise an error if it is not if (!(image instanceof HTMLImageElement)) { - throw 'the argument: ' + image.toString() + ' is not an image" }'; + throw "the argument: " + image.toString() + ' is not an image" }'; } - this.psychoJS.logger.debug('set the image of ImageStim: ' + this._name + ' as: src= ' + image.src + ', size= ' + image.width + 'x' + image.height); + this.psychoJS.logger.debug("set the image of ImageStim: " + this._name + " as: src= " + image.src + ", size= " + image.width + "x" + image.height); } const existingImage = this.getImage(); const hasChanged = existingImage ? existingImage.src !== image.src : true; - this._setAttribute('image', image, log); + this._setAttribute("image", image, log); if (hasChanged) { @@ -158,12 +154,10 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Setter for the mask attribute. * @@ -175,22 +169,22 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) setMask(mask, log = false) { const response = { - origin: 'ImageStim.setMask', - context: 'when setting the mask of ImageStim: ' + this._name + origin: "ImageStim.setMask", + context: "when setting the mask of ImageStim: " + this._name, }; try { // mask is undefined: that's fine but we raise a warning in case this is a sympton of an actual problem - if (typeof mask === 'undefined') + if (typeof mask === "undefined") { - this.psychoJS.logger.warn('setting the mask of ImageStim: ' + this._name + ' with argument: undefined.'); - this.psychoJS.logger.debug('set the mask of ImageStim: ' + this._name + ' as: undefined'); + this.psychoJS.logger.warn("setting the mask of ImageStim: " + this._name + " with argument: undefined."); + this.psychoJS.logger.debug("set the mask of ImageStim: " + this._name + " as: undefined"); } else { // mask is a string: it should be the name of a resource, which we load - if (typeof mask === 'string') + if (typeof mask === "string") { mask = this.psychoJS.serverManager.getResource(mask); } @@ -198,24 +192,22 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) // mask should now be an actual HTMLImageElement: we raise an error if it is not if (!(mask instanceof HTMLImageElement)) { - throw 'the argument: ' + mask.toString() + ' is not an image" }'; + throw "the argument: " + mask.toString() + ' is not an image" }'; } - this.psychoJS.logger.debug('set the mask of ImageStim: ' + this._name + ' as: src= ' + mask.src + ', size= ' + mask.width + 'x' + mask.height); + this.psychoJS.logger.debug("set the mask of ImageStim: " + this._name + " as: src= " + mask.src + ", size= " + mask.width + "x" + mask.height); } - this._setAttribute('mask', mask, log); + this._setAttribute("mask", mask, log); this._onChange(true, false)(); } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Estimate the bounding box. * @@ -227,21 +219,19 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) _estimateBoundingBox() { const size = this._getDisplaySize(); - if (typeof size !== 'undefined') + if (typeof size !== "undefined") { this._boundingBox = new PIXI.Rectangle( this._pos[0] - size[0] / 2, this._pos[1] - size[1] / 2, size[0], - size[1] + size[1], ); } // TODO take the orientation into account } - - /** * Update the stimulus, if necessary. * @@ -261,14 +251,14 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } this._pixi = undefined; // no image to draw: return immediately - if (typeof this._image === 'undefined') + if (typeof this._image === "undefined") { return; } @@ -279,7 +269,7 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) this._pixi = PIXI.Sprite.from(this._texture); // add a mask if need be: - if (typeof this._mask !== 'undefined') + if (typeof this._mask !== "undefined") { this._pixi.mask = PIXI.Sprite.from(this._mask); @@ -330,8 +320,6 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) this._estimateBoundingBox(); } - - /** * Get the size of the display image, which is either that of the ImageStim or that of the image * it contains. @@ -344,18 +332,16 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) { let displaySize = this.size; - if (typeof displaySize === 'undefined') + if (typeof displaySize === "undefined") { // use the size of the texture, if we have access to it: - if (typeof this._texture !== 'undefined' && this._texture.width > 0) + if (typeof this._texture !== "undefined" && this._texture.width > 0) { const textureSize = [this._texture.width, this._texture.height]; - displaySize = util.to_unit(textureSize, 'pix', this.win, this.units); + displaySize = util.to_unit(textureSize, "pix", this.win, this.units); } } return displaySize; } - - } diff --git a/src/visual/MovieStim.js b/src/visual/MovieStim.js index 475b05f..945e471 100644 --- a/src/visual/MovieStim.js +++ b/src/visual/MovieStim.js @@ -7,15 +7,13 @@ * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim.js'; -import {Color} from '../util/Color.js'; -import {ColorMixin} from '../util/ColorMixin.js'; -import * as util from '../util/Util.js'; -import {PsychoJS} from "../core/PsychoJS.js"; +import * as PIXI from "pixi.js-legacy"; +import { PsychoJS } from "../core/PsychoJS.js"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; import { to_pixiPoint } from "../util/Pixi.js"; - +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; /** * Movie Stimulus. @@ -49,82 +47,81 @@ import { to_pixiPoint } from "../util/Pixi.js"; */ export class MovieStim extends VisualStim { - constructor({name, win, movie, pos, units, ori, size, color, opacity, contrast, interpolate, flipHoriz, flipVert, loop, volume, noAudio, autoPlay, autoDraw, autoLog} = {}) + constructor({ name, win, movie, pos, units, ori, size, color, opacity, contrast, interpolate, flipHoriz, flipVert, loop, volume, noAudio, autoPlay, autoDraw, autoLog } = {}) { - super({name, win, units, ori, opacity, pos, size, autoDraw, autoLog}); + super({ name, win, units, ori, opacity, pos, size, autoDraw, autoLog }); - this.psychoJS.logger.debug('create a new MovieStim with name: ', name); + this.psychoJS.logger.debug("create a new MovieStim with name: ", name); // movie and movie control: this._addAttribute( - 'movie', - movie + "movie", + movie, ); this._addAttribute( - 'volume', + "volume", volume, 1.0, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'noAudio', + "noAudio", noAudio, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'autoPlay', + "autoPlay", autoPlay, true, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'flipHoriz', + "flipHoriz", flipHoriz, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'flipVert', + "flipVert", flipVert, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'interpolate', + "interpolate", interpolate, false, - this._onChange(true, false) + this._onChange(true, false), ); // colors: this._addAttribute( - 'color', + "color", color, - 'white', - this._onChange(true, false) + "white", + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'loop', + "loop", loop, false, - this._onChange(false, false) + this._onChange(false, false), ); - // estimate the bounding box: this._estimateBoundingBox(); // check whether the fastSeek method on HTMLVideoElement is implemented: - const videoElement = document.createElement('video'); - this._hasFastSeek = (typeof videoElement.fastSeek === 'function'); + const videoElement = document.createElement("video"); + this._hasFastSeek = (typeof videoElement.fastSeek === "function"); if (this._autoLog) { @@ -132,8 +129,6 @@ export class MovieStim extends VisualStim } } - - /** * Setter for the movie attribute. * @@ -146,22 +141,22 @@ export class MovieStim extends VisualStim setMovie(movie, log = false) { const response = { - origin: 'MovieStim.setMovie', - context: 'when setting the movie of MovieStim: ' + this._name + origin: "MovieStim.setMovie", + context: "when setting the movie of MovieStim: " + this._name, }; try { // movie is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem - if (typeof movie === 'undefined') + if (typeof movie === "undefined") { - this.psychoJS.logger.warn('setting the movie of MovieStim: ' + this._name + ' with argument: undefined.'); - this.psychoJS.logger.debug('set the movie of MovieStim: ' + this._name + ' as: undefined'); + this.psychoJS.logger.warn("setting the movie of MovieStim: " + this._name + " with argument: undefined."); + this.psychoJS.logger.debug("set the movie of MovieStim: " + this._name + " as: undefined"); } else { // movie is a string: it should be the name of a resource, which we load - if (typeof movie === 'string') + if (typeof movie === "string") { movie = this.psychoJS.serverManager.getResource(movie); } @@ -169,7 +164,7 @@ export class MovieStim extends VisualStim // movie should now be an actual HTMLVideoElement: we raise an error if it is not if (!(movie instanceof HTMLVideoElement)) { - throw 'the argument: ' + movie.toString() + ' is not a video" }'; + throw "the argument: " + movie.toString() + ' is not a video" }'; } this.psychoJS.logger.debug(`set the movie of MovieStim: ${this._name} as: src= ${movie.src}, size= ${movie.videoWidth}x${movie.videoHeight}, duration= ${movie.duration}s`); @@ -186,20 +181,16 @@ export class MovieStim extends VisualStim } } - - - this._setAttribute('movie', movie, log); + this._setAttribute("movie", movie, log); this._needUpdate = true; this._needPixiUpdate = true; } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Reset the stimulus. * @@ -212,8 +203,6 @@ export class MovieStim extends VisualStim this.seek(0, log); } - - /** * Start playing the movie. * @@ -228,18 +217,17 @@ export class MovieStim extends VisualStim if (playPromise !== undefined) { - playPromise.catch((error) => { + playPromise.catch((error) => + { throw { - origin: 'MovieStim.play', + origin: "MovieStim.play", context: `when attempting to play MovieStim: ${this._name}`, - error + error, }; }); } } - - /** * Pause the movie. * @@ -251,8 +239,6 @@ export class MovieStim extends VisualStim this._movie.pause(); } - - /** * Stop the movie and reset to 0s. * @@ -265,8 +251,6 @@ export class MovieStim extends VisualStim this.seek(0, log); } - - /** * Jump to a specific timepoint * @@ -280,9 +264,9 @@ export class MovieStim extends VisualStim if (timePoint < 0 || timePoint > this._movie.duration) { throw { - origin: 'MovieStim.seek', + origin: "MovieStim.seek", context: `when seeking to timepoint: ${timePoint} of MovieStim: ${this._name}`, - error: `the timepoint does not belong to [0, ${this._movie.duration}` + error: `the timepoint does not belong to [0, ${this._movie.duration}`, }; } @@ -299,16 +283,14 @@ export class MovieStim extends VisualStim catch (error) { throw { - origin: 'MovieStim.seek', + origin: "MovieStim.seek", context: `when seeking to timepoint: ${timePoint} of MovieStim: ${this._name}`, - error + error, }; } } } - - /** * Estimate the bounding box. * @@ -320,21 +302,19 @@ export class MovieStim extends VisualStim _estimateBoundingBox() { const size = this._getDisplaySize(); - if (typeof size !== 'undefined') + if (typeof size !== "undefined") { this._boundingBox = new PIXI.Rectangle( this._pos[0] - size[0] / 2, this._pos[1] - size[1] / 2, size[0], - size[1] + size[1], ); } // TODO take the orientation into account } - - /** * Update the stimulus, if necessary. * @@ -354,20 +334,20 @@ export class MovieStim extends VisualStim { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { // Leave original video in place // https://pixijs.download/dev/docs/PIXI.Sprite.html#destroy this._pixi.destroy({ children: true, texture: true, - baseTexture: false + baseTexture: false, }); } this._pixi = undefined; // no movie to draw: return immediately - if (typeof this._movie === 'undefined') + if (typeof this._movie === "undefined") { return; } @@ -414,8 +394,6 @@ export class MovieStim extends VisualStim this._estimateBoundingBox(); } - - /** * Get the size of the display image, which is either that of the ImageStim or that of the image * it contains. @@ -428,18 +406,16 @@ export class MovieStim extends VisualStim { let displaySize = this.size; - if (typeof displaySize === 'undefined') + if (typeof displaySize === "undefined") { // use the size of the texture, if we have access to it: - if (typeof this._texture !== 'undefined' && this._texture.width > 0) + if (typeof this._texture !== "undefined" && this._texture.width > 0) { const textureSize = [this._texture.width, this._texture.height]; - displaySize = util.to_unit(textureSize, 'pix', this.win, this.units); + displaySize = util.to_unit(textureSize, "pix", this.win, this.units); } } return displaySize; } - - } diff --git a/src/visual/Polygon.js b/src/visual/Polygon.js index 1f74e8d..7e8196d 100644 --- a/src/visual/Polygon.js +++ b/src/visual/Polygon.js @@ -7,10 +7,8 @@ * @license Distributed under the terms of the MIT License */ - -import {ShapeStim} from './ShapeStim.js'; -import {Color} from '../util/Color.js'; - +import { Color } from "../util/Color.js"; +import { ShapeStim } from "./ShapeStim.js"; /** *

Polygonal visual stimulus.

@@ -39,7 +37,7 @@ import {Color} from '../util/Color.js'; */ export class Polygon extends ShapeStim { - constructor({name, win, lineWidth, lineColor, fillColor, opacity, edges, radius, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {}) + constructor({ name, win, lineWidth, lineColor, fillColor, opacity, edges, radius, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {}) { super({ name, @@ -56,20 +54,20 @@ export class Polygon extends ShapeStim depth, interpolate, autoDraw, - autoLog + autoLog, }); - this._psychoJS.logger.debug('create a new Polygon with name: ', name); + this._psychoJS.logger.debug("create a new Polygon with name: ", name); this._addAttribute( - 'edges', + "edges", edges, - 3 + 3, ); this._addAttribute( - 'radius', + "radius", radius, - 0.5 + 0.5, ); this._updateVertices(); @@ -80,8 +78,6 @@ export class Polygon extends ShapeStim } } - - /** * Setter for the radius attribute. * @@ -92,7 +88,7 @@ export class Polygon extends ShapeStim */ setRadius(radius, log = false) { - const hasChanged = this._setAttribute('radius', radius, log); + const hasChanged = this._setAttribute("radius", radius, log); if (hasChanged) { @@ -100,8 +96,6 @@ export class Polygon extends ShapeStim } } - - /** * Setter for the edges attribute. * @@ -112,7 +106,7 @@ export class Polygon extends ShapeStim */ setEdges(edges, log = false) { - const hasChanged = this._setAttribute('edges', Math.round(edges), log); + const hasChanged = this._setAttribute("edges", Math.round(edges), log); if (hasChanged) { @@ -120,8 +114,6 @@ export class Polygon extends ShapeStim } } - - /** * Update the vertices. * @@ -130,7 +122,7 @@ export class Polygon extends ShapeStim */ _updateVertices() { - this._psychoJS.logger.debug('update the vertices of Polygon: ', this.name); + this._psychoJS.logger.debug("update the vertices of Polygon: ", this.name); const angle = 2.0 * Math.PI / this._edges; const vertices = []; @@ -141,5 +133,4 @@ export class Polygon extends ShapeStim this.setVertices(vertices); } - } diff --git a/src/visual/Rect.js b/src/visual/Rect.js index 43740bf..e067f84 100644 --- a/src/visual/Rect.js +++ b/src/visual/Rect.js @@ -7,10 +7,8 @@ * @license Distributed under the terms of the MIT License */ - -import {ShapeStim} from './ShapeStim.js'; -import {Color} from '../util/Color.js'; - +import { Color } from "../util/Color.js"; +import { ShapeStim } from "./ShapeStim.js"; /** *

Rectangular visual stimulus.

@@ -39,7 +37,7 @@ import {Color} from '../util/Color.js'; */ export class Rect extends ShapeStim { - constructor({name, win, lineWidth, lineColor, fillColor, opacity, width, height, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {}) + constructor({ name, win, lineWidth, lineColor, fillColor, opacity, width, height, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {}) { super({ name, @@ -56,20 +54,20 @@ export class Rect extends ShapeStim depth, interpolate, autoDraw, - autoLog + autoLog, }); - this._psychoJS.logger.debug('create a new Rect with name: ', name); + this._psychoJS.logger.debug("create a new Rect with name: ", name); this._addAttribute( - 'width', + "width", width, - 0.5 + 0.5, ); this._addAttribute( - 'height', + "height", height, - 0.5 + 0.5, ); this._updateVertices(); @@ -80,8 +78,6 @@ export class Rect extends ShapeStim } } - - /** * Setter for the width attribute. * @@ -92,9 +88,9 @@ export class Rect extends ShapeStim */ setWidth(width, log = false) { - this._psychoJS.logger.debug('set the width of Rect: ', this.name, 'to: ', width); + this._psychoJS.logger.debug("set the width of Rect: ", this.name, "to: ", width); - const hasChanged = this._setAttribute('width', width, log); + const hasChanged = this._setAttribute("width", width, log); if (hasChanged) { @@ -102,8 +98,6 @@ export class Rect extends ShapeStim } } - - /** * Setter for the height attribute. * @@ -114,9 +108,9 @@ export class Rect extends ShapeStim */ setHeight(height, log = false) { - this._psychoJS.logger.debug('set the height of Rect: ', this.name, 'to: ', height); + this._psychoJS.logger.debug("set the height of Rect: ", this.name, "to: ", height); - const hasChanged = this._setAttribute('height', height, log); + const hasChanged = this._setAttribute("height", height, log); if (hasChanged) { @@ -124,8 +118,6 @@ export class Rect extends ShapeStim } } - - /** * Update the vertices. * @@ -134,7 +126,7 @@ export class Rect extends ShapeStim */ _updateVertices() { - this._psychoJS.logger.debug('update the vertices of Rect: ', this.name); + this._psychoJS.logger.debug("update the vertices of Rect: ", this.name); const halfWidth = this._width / 2.0; const halfHeight = this._height / 2.0; @@ -143,8 +135,7 @@ export class Rect extends ShapeStim [-halfWidth, -halfHeight], [halfWidth, -halfHeight], [halfWidth, halfHeight], - [-halfWidth, halfHeight] + [-halfWidth, halfHeight], ]); } - } diff --git a/src/visual/ShapeStim.js b/src/visual/ShapeStim.js index 8aeab4b..7baea8b 100644 --- a/src/visual/ShapeStim.js +++ b/src/visual/ShapeStim.js @@ -8,15 +8,13 @@ * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim.js'; -import {Color} from '../util/Color.js'; -import {ColorMixin} from '../util/ColorMixin.js'; -import * as util from '../util/Util.js'; +import * as PIXI from "pixi.js-legacy"; +import { WindowMixin } from "../core/WindowMixin.js"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; import { to_pixiPoint } from "../util/Pixi.js"; -import {WindowMixin} from "../core/WindowMixin.js"; - +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; /** *

This class provides the basic functionality of shape stimuli.

@@ -45,9 +43,9 @@ import {WindowMixin} from "../core/WindowMixin.js"; */ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin) { - constructor({name, win, lineWidth, lineColor, fillColor, opacity, vertices, closeShape, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {}) + constructor({ name, win, lineWidth, lineColor, fillColor, opacity, vertices, closeShape, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {}) { - super({name, win, units, ori, opacity, pos, depth, size, autoDraw, autoLog}); + super({ name, win, units, ori, opacity, pos, depth, size, autoDraw, autoLog }); // the PIXI polygon corresponding to the vertices, in pixel units: this._pixiPolygon_px = undefined; @@ -55,58 +53,56 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin this._vertices_px = undefined; // shape: - if (typeof size === 'undefined' || size === null) + if (typeof size === "undefined" || size === null) { this.size = [1.0, 1.0]; } this._addAttribute( - 'vertices', + "vertices", vertices, - [[-0.5, 0], [0, 0.5], [0.5, 0]] + [[-0.5, 0], [0, 0.5], [0.5, 0]], ); this._addAttribute( - 'closeShape', + "closeShape", closeShape, true, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'interpolate', + "interpolate", interpolate, true, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'lineWidth', + "lineWidth", lineWidth, 1.5, - this._onChange(true, true) + this._onChange(true, true), ); // colors: this._addAttribute( - 'lineColor', + "lineColor", lineColor, - 'white', - this._onChange(true, false) + "white", + this._onChange(true, false), ); this._addAttribute( - 'fillColor', + "fillColor", fillColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); } - - /** * Setter for the vertices attribute. * @@ -118,16 +114,16 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin setVertices(vertices, log = false) { const response = { - origin: 'ShapeStim.setVertices', - context: 'when setting the vertices of ShapeStim: ' + this._name + origin: "ShapeStim.setVertices", + context: "when setting the vertices of ShapeStim: " + this._name, }; - this._psychoJS.logger.debug('set the vertices of ShapeStim:', this.name); + this._psychoJS.logger.debug("set the vertices of ShapeStim:", this.name); try { // if vertices is a string, we check whether it is a known shape: - if (typeof vertices === 'string') + if (typeof vertices === "string") { if (vertices in ShapeStim.KnownShapes) { @@ -139,18 +135,16 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin } } - this._setAttribute('vertices', vertices, log); + this._setAttribute("vertices", vertices, log); this._onChange(true, true)(); } catch (error) { - throw Object.assign(response, {error: error}); + throw Object.assign(response, { error: error }); } } - - /** * Determine whether an object is inside the bounding box of the ShapeStim. * @@ -168,24 +162,22 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin // get the position of the object, in pixel coordinates: const objectPos_px = util.getPositionFromObject(object, units); - if (typeof objectPos_px === 'undefined') + if (typeof objectPos_px === "undefined") { throw { - origin: 'VisualStim.contains', - context: 'when determining whether VisualStim: ' + this._name + ' contains object: ' + util.toString(object), - error: 'unable to determine the position of the object' + origin: "VisualStim.contains", + context: "when determining whether VisualStim: " + this._name + " contains object: " + util.toString(object), + error: "unable to determine the position of the object", }; } // test for inclusion: const pos_px = util.to_px(this.pos, this.units, this.win); this._getVertices_px(); - const polygon_px = this._vertices_px.map(v => [v[0] + pos_px[0], v[1] + pos_px[1]]); + const polygon_px = this._vertices_px.map((v) => [v[0] + pos_px[0], v[1] + pos_px[1]]); return util.IsPointInsidePolygon(objectPos_px, polygon_px); } - - /** * Estimate the bounding box. * @@ -203,7 +195,7 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, - Number.NEGATIVE_INFINITY + Number.NEGATIVE_INFINITY, ]; for (const vertex of this._vertices_px) { @@ -217,14 +209,12 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin this._pos[0] + this._getLengthUnits(limits_px[0]), this._pos[1] + this._getLengthUnits(limits_px[1]), this._getLengthUnits(limits_px[2] - limits_px[0]), - this._getLengthUnits(limits_px[3] - limits_px[1]) + this._getLengthUnits(limits_px[3] - limits_px[1]), ); // TODO take the orientation into account } - - /** * Update the stimulus, if necessary. * @@ -244,7 +234,7 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } @@ -256,13 +246,13 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin // prepare the polygon in the given color and opacity: this._pixi = new PIXI.Graphics(); this._pixi.lineStyle(this._lineWidth, this._lineColor.int, this._opacity, 0.5); - if (typeof this._fillColor !== 'undefined' && this._fillColor !== null) + if (typeof this._fillColor !== "undefined" && this._fillColor !== null) { const contrastedColor = this.getContrastedColor(new Color(this._fillColor), this._contrast); this._pixi.beginFill(contrastedColor.int, this._opacity); } this._pixi.drawPolygon(this._pixiPolygon_px); - if (typeof this._fillColor !== 'undefined' && this._fillColor !== null) + if (typeof this._fillColor !== "undefined" && this._fillColor !== null) { this._pixi.endFill(); } @@ -273,8 +263,6 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin this._pixi.rotation = this.ori * Math.PI / 180.0; } - - /** * Get the PIXI polygon (in pixel units) corresponding to the vertices. * @@ -312,8 +300,6 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin return this._pixiPolygon_px; } - - /** * Get the vertices in pixel units. * @@ -325,28 +311,28 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin { // handle flipping: let flip = [1.0, 1.0]; - if ('_flipHoriz' in this && this._flipHoriz) + if ("_flipHoriz" in this && this._flipHoriz) { flip[0] = -1.0; } - if ('_flipVert' in this && this._flipVert) + if ("_flipVert" in this && this._flipVert) { flip[1] = -1.0; } // handle size, flipping, and convert to pixel units: - this._vertices_px = this._vertices.map(v => util.to_px( - [v[0] * this._size[0] * flip[0], v[1] * this._size[1] * flip[1]], - this._units, - this._win) + this._vertices_px = this._vertices.map((v) => + util.to_px( + [v[0] * this._size[0] * flip[0], v[1] * this._size[1] * flip[1]], + this._units, + this._win, + ) ); return this._vertices_px; } - } - /** * Known shapes. * @@ -358,15 +344,15 @@ ShapeStim.KnownShapes = { [-0.1, +0.5], // up [+0.1, +0.5], [+0.1, +0.1], - [+0.5, +0.1], // right + [+0.5, +0.1], // right [+0.5, -0.1], [+0.1, -0.1], - [+0.1, -0.5], // down + [+0.1, -0.5], // down [-0.1, -0.5], [-0.1, -0.1], - [-0.5, -0.1], // left + [-0.5, -0.1], // left [-0.5, +0.1], - [-0.1, +0.1] + [-0.1, +0.1], ], star7: [ @@ -383,7 +369,6 @@ ShapeStim.KnownShapes = { [-0.49, -0.11], [-0.19, 0.04], [-0.39, 0.31], - [-0.09, 0.18] - ] - + [-0.09, 0.18], + ], }; diff --git a/src/visual/Slider.js b/src/visual/Slider.js index aab8448..8d6985f 100644 --- a/src/visual/Slider.js +++ b/src/visual/Slider.js @@ -7,17 +7,15 @@ * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim.js'; -import {Color} from '../util/Color.js'; -import {ColorMixin} from '../util/ColorMixin.js'; -import {WindowMixin} from '../core/WindowMixin.js'; -import {Clock} from '../util/Clock.js'; -import * as util from '../util/Util.js'; -import {PsychoJS} from "../core/PsychoJS.js"; +import * as PIXI from "pixi.js-legacy"; +import { PsychoJS } from "../core/PsychoJS.js"; +import { WindowMixin } from "../core/WindowMixin.js"; +import { Clock } from "../util/Clock.js"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; import { to_pixiPoint } from "../util/Pixi.js"; - +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; /** * Slider stimulus. @@ -70,9 +68,38 @@ import { to_pixiPoint } from "../util/Pixi.js"; */ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) { - constructor({name, win, pos, size, ori, units, color, markerColor, lineColor, contrast, opacity, style, ticks, labels, granularity, flip, readOnly, font, bold, italic, fontSize, compact, clipMask, autoDraw, autoLog, dependentStims} = {}) + constructor( + { + name, + win, + pos, + size, + ori, + units, + color, + markerColor, + lineColor, + 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}); + super({ name, win, units, ori, opacity, pos, size, clipMask, autoDraw, autoLog }); this._needMarkerUpdate = false; @@ -96,119 +123,117 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) }; this._addAttribute( - 'style', + "style", style, [Slider.Style.RATING], - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'ticks', + "ticks", ticks, [1, 2, 3, 4, 5], - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'labels', + "labels", labels, [], - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'granularity', + "granularity", granularity, 0, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'readOnly', + "readOnly", readOnly, - false + false, ); this._addAttribute( - 'compact', + "compact", compact, false, - this._onChange(true, true) + this._onChange(true, true), ); // font: this._addAttribute( - 'font', + "font", font, - 'Arial', - this._onChange(true, true) + "Arial", + this._onChange(true, true), ); this._addAttribute( - 'fontSize', + "fontSize", fontSize, - (this._units === 'pix') ? 14 : 0.03, - this._onChange(true, true) + (this._units === "pix") ? 14 : 0.03, + this._onChange(true, true), ); this._addAttribute( - 'bold', + "bold", bold, true, - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'italic', + "italic", italic, false, - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'flip', + "flip", flip, false, - this._onChange(true, true) + this._onChange(true, true), ); // color: this._addAttribute( - 'color', + "color", color, - 'lightgray', - this._onChange(true, false) + "lightgray", + this._onChange(true, false), ); this._addAttribute( - 'lineColor', + "lineColor", lineColor, - 'lightgray', - this._onChange(true, false) + "lightgray", + this._onChange(true, false), ); this._addAttribute( - 'markerColor', + "markerColor", markerColor, - 'red', - this._onChange(true, false) + "red", + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'dependentStims', + "dependentStims", dependentStims, [], - this._onChange(false, false) + this._onChange(false, false), ); - - // slider rating (which might be different from the visible marker rating): - this._addAttribute('rating', undefined); + this._addAttribute("rating", undefined); // visible marker rating (which might be different from the actual rating): - this._addAttribute('markerPos', undefined); + this._addAttribute("markerPos", undefined); // full history of ratings and response times: - this._addAttribute('history', []); + this._addAttribute("history", []); // various graphical components: - this._addAttribute('lineAspectRatio', 0.01); + this._addAttribute("lineAspectRatio", 0.01); // check for attribute conflicts, missing values, etc.: this._sanitizeAttributes(); @@ -225,8 +250,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Force a refresh of the stimulus. * @@ -240,8 +263,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._needMarkerUpdate = true; } - - /** * Reset the slider. * @@ -250,7 +271,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) */ reset() { - this.psychoJS.logger.debug('reset Slider: ', this._name); + this.psychoJS.logger.debug("reset Slider: ", this._name); this._markerPos = undefined; this._history = []; @@ -262,14 +283,12 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._needUpdate = true; // the marker should be invisible when markerPos is undefined: - if (typeof this._marker !== 'undefined') + if (typeof this._marker !== "undefined") { this._marker.alpha = 0; } } - - /** * Get the current value of the rating. * @@ -290,8 +309,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Get the response time of the most recent change to the rating. * @@ -312,8 +329,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Setter for the readOnly attribute. * @@ -327,7 +342,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) */ setReadOnly(readOnly = true, log = false) { - const hasChanged = this._setAttribute('readOnly', readOnly, log); + const hasChanged = this._setAttribute("readOnly", readOnly, log); if (hasChanged) { @@ -345,8 +360,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Setter for the markerPos attribute. * @@ -372,8 +385,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Setter for the rating attribute. * @@ -393,60 +404,51 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) rating = this._labels[Math.round(rating)]; } - this._setAttribute('rating', rating, log); + this._setAttribute("rating", rating, log); } - - /** Let `borderColor` alias `lineColor` to parallel PsychoPy */ - set borderColor(color) { + set borderColor(color) + { this.lineColor = color; } - - - setBorderColor(color) { + setBorderColor(color) + { this.setLineColor(color); } - - - get borderColor() { + get borderColor() + { return this.lineColor; } - - - getBorderColor() { + getBorderColor() + { return this.getLineColor(); } - /** Let `fillColor` alias `markerColor` to parallel PsychoPy */ - set fillColor(color) { + set fillColor(color) + { this.markerColor = color; } - - - setFillColor(color) { + setFillColor(color) + { this.setMarkerColor(color); } - - - get fillColor() { + get fillColor() + { return this.markerColor; } - - - getFillColor() { + getFillColor() + { return this.getMarkerColor(); } - - /** * Estimate the bounding box. * @@ -462,18 +464,18 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) { // setup the slider's style (taking into account the Window dimension, etc.): this._setupStyle(); - + // calculate various values in pixel units: this._tickSize_px = util.to_px(this._tickSize, this._units, this._win); this._fontSize_px = this._getLengthPix(this._fontSize); - this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map(v => Math.max(1, v)); + this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map((v) => Math.max(1, v)); this._markerSize_px = util.to_px(this._markerSize, this._units, this._win, true); const pos_px = util.to_px(this._pos, this._units, this._win); const size_px = util.to_px(this._size, this._units, this._win); // calculate the position of the ticks: const tickPositions = this._ratingToPos(this._ticks); - this._tickPositions_px = tickPositions.map(p => util.to_px(p, this._units, this._win)); + this._tickPositions_px = tickPositions.map((p) => util.to_px(p, this._units, this._win)); // left, top, right, bottom limits: const limits_px = [0, 0, size_px[0], size_px[1]]; @@ -490,7 +492,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) for (let l = 0; l < this._labels.length; ++l) { - const tickPositionIndex = Math.round( l / (this._labels.length - 1) * (this._ticks.length - 1) ); + const tickPositionIndex = Math.round(l / (this._labels.length - 1) * (this._ticks.length - 1)); this._labelPositions_px[l] = this._tickPositions_px[tickPositionIndex]; const labelBounds = PIXI.TextMetrics.measureText(this._labels[l].toString(), labelTextStyle); @@ -515,8 +517,10 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } // ensure that that labels are not overlapping: - if (prevLabelBounds && - (this._labelPositions_px[l - 1][0] + prevLabelBounds.width + tolerance >= this._labelPositions_px[l][0])) + if ( + prevLabelBounds + && (this._labelPositions_px[l - 1][0] + prevLabelBounds.width + tolerance >= this._labelPositions_px[l][0]) + ) { if (prevNonOverlapOffset === 0) { @@ -576,12 +580,10 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._getLengthUnits(position_px.x + limits_px[0]), this._getLengthUnits(position_px.y + limits_px[1]), this._getLengthUnits(limits_px[2] - limits_px[0]), - this._getLengthUnits(limits_px[3] - limits_px[1]) + this._getLengthUnits(limits_px[3] - limits_px[1]), ); } - - /** * Sanitize the slider attributes: check for attribute conflicts, missing values, etc. * @@ -592,9 +594,9 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) _sanitizeAttributes() { // convert potential string styles into Symbols: - this._style.forEach( (style, index) => + this._style.forEach((style, index) => { - if (typeof style === 'string') + if (typeof style === "string") { this._style[index] = Symbol.for(style.toUpperCase()); } @@ -606,14 +608,11 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._isCategorical = (this._ticks.length === 0); if (this._isCategorical) { - this._ticks = [...Array(this._labels.length)].map( (_, i) => i ); + this._ticks = [...Array(this._labels.length)].map((_, i) => i); this._granularity = 1.0; } - } - - /** * Set the current rating. * @@ -629,7 +628,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) recordRating(rating, responseTime = undefined, log = false) { // get response time: - if (typeof responseTime === 'undefined') + if (typeof responseTime === "undefined") { responseTime = this._responseClock.getTime(); } @@ -640,15 +639,14 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this.setRating(rating, log); // add rating and response time to history: - this._history.push({rating: this._rating, responseTime}); - this.psychoJS.logger.debug('record a new rating: ', this._rating, 'with response time: ', responseTime, 'for Slider: ', this._name); + this._history.push({ rating: this._rating, responseTime }); + this.psychoJS.logger.debug("record a new rating: ", this._rating, "with response time: ", responseTime, "for Slider: ", this._name); // update slider: this._needMarkerUpdate = true; this._needUpdate = true; } - /** * Update the stimulus, if necessary. * @@ -682,7 +680,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - /** * Estimate the position of the slider, taking the compactness into account. * @@ -693,8 +690,10 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) _getPosition_px() { const position = to_pixiPoint(this.pos, this.units, this.win, true); - if (this._compact && - (this._style.indexOf(Slider.Style.RADIO) > -1 || this._style.indexOf(Slider.Style.RATING) > -1)) + if ( + this._compact + && (this._style.indexOf(Slider.Style.RADIO) > -1 || this._style.indexOf(Slider.Style.RATING) > -1) + ) { if (this._isHorizontal()) { @@ -709,8 +708,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) return position; } - - /** * Update the position of the marker if necessary. * @@ -725,9 +722,9 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } this._needMarkerUpdate = false; - if (typeof this._marker !== 'undefined') + if (typeof this._marker !== "undefined") { - if (typeof this._markerPos !== 'undefined') + if (typeof this._markerPos !== "undefined") { const visibleMarkerPos = this._ratingToPos([this._markerPos]); this._marker.position = to_pixiPoint(visibleMarkerPos[0], this.units, this.win, true); @@ -740,8 +737,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Setup the PIXI components of the slider (bar, ticks, labels, marker, etc.). * @@ -759,17 +754,15 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._setupStyle(); - // calculate various values in pixel units: this._tickSize_px = util.to_px(this._tickSize, this._units, this._win); this._fontSize_px = this._getLengthPix(this._fontSize); - this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map(v => Math.max(1, v)); + this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map((v) => Math.max(1, v)); this._markerSize_px = util.to_px(this._markerSize, this._units, this._win, true); const tickPositions = this._ratingToPos(this._ticks); - this._tickPositions_px = tickPositions.map(p => util.to_px(p, this._units, this._win)); + this._tickPositions_px = tickPositions.map((p) => util.to_px(p, this._units, this._win)); - - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } @@ -782,7 +775,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._body.interactive = true; this._pixi.addChild(this._body); - // ensure that pointer events will be captured along the slider body, even outside of // marker and labels: if (this._tickType === Slider.Shape.DISC) @@ -792,7 +784,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) -this._barSize_px[0] / 2 - maxTickSize_px, -this._barSize_px[1] / 2 - maxTickSize_px, this._barSize_px[0] + maxTickSize_px * 2, - this._barSize_px[1] + maxTickSize_px * 2); + this._barSize_px[1] + maxTickSize_px * 2, + ); } else { @@ -800,7 +793,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) -this._barSize_px[0] / 2 - this._tickSize_px[0] / 2, -this._barSize_px[1] / 2 - this._tickSize_px[1] / 2, this._barSize_px[0] + this._tickSize_px[0], - this._barSize_px[1] + this._tickSize_px[1]); + this._barSize_px[1] + this._tickSize_px[1], + ); } // central bar: @@ -816,8 +810,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._setupMarker(); } - - /** * Setup the central bar. * @@ -830,7 +822,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) if (this._barLineWidth_px > 0) { this._body.lineStyle(this._barLineWidth_px, this._barLineColor.int, 1, 0.5); - if (typeof this._barFillColor !== 'undefined') + if (typeof this._barFillColor !== "undefined") { this._body.beginFill(this._barFillColor.int, 1); } @@ -838,17 +830,15 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) Math.round(-this._barSize_px[0] / 2), Math.round(-this._barSize_px[1] / 2), Math.round(this._barSize_px[0]), - Math.round(this._barSize_px[1]) + Math.round(this._barSize_px[1]), ); - if (typeof this._barFillColor !== 'undefined') + if (typeof this._barFillColor !== "undefined") { this._body.endFill(); } } } - - /** * Setup the marker, and the associated mouse events. * @@ -858,7 +848,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) */ _setupMarker() { -/* this is now deprecated and replaced by _body.hitArea + /* this is now deprecated and replaced by _body.hitArea // transparent rectangle necessary to capture pointer events outside of marker and labels: const eventCaptureRectangle = new PIXI.Graphics(); eventCaptureRectangle.beginFill(0, 0); @@ -870,7 +860,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) ); eventCaptureRectangle.endFill(); this._pixi.addChild(eventCaptureRectangle); -*/ + */ // marker: this._marker = new PIXI.Graphics(); @@ -927,7 +917,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) Math.round(-this._markerSize_px[0] / 2), Math.round(-this._markerSize_px[1] / 2), this._markerSize_px[0], - this._markerSize_px[1] + this._markerSize_px[1], ); this._marker.endFill(); @@ -935,7 +925,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) // this._marker.drawCircle(0, 0, this._markerSize_px[0] / 3); } - // marker mouse events: const self = this; self._markerDragging = false; @@ -1001,7 +990,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } }; - // (*) slider mouse events outside of marker // note: this only works thanks to eventCaptureRectangle /* not quite right just yet (as of May 2020) @@ -1035,8 +1023,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) }; } - - /** * Setup the ticks. * @@ -1072,8 +1058,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Get the PIXI Text Style applied to the PIXI.Text labels. * @@ -1084,19 +1068,17 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) _getTextStyle() { this._fontSize_px = this._getLengthPix(this._fontSize); - + return new PIXI.TextStyle({ fontFamily: this._font, fontSize: Math.round(this._fontSize_px), - fontWeight: (this._bold) ? 'bold' : 'normal', - fontStyle: (this._italic) ? 'italic' : 'normal', + fontWeight: (this._bold) ? "bold" : "normal", + fontStyle: (this._italic) ? "italic" : "normal", fill: this.getContrastedColor(this._labelColor, this._contrast).hex, - align: 'center', + align: "center", }); } - - /** * Setup the labels. * @@ -1121,8 +1103,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Apply a particular style to the slider. * @@ -1158,7 +1138,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._tickType = Slider.Shape.LINE; this._tickColor = (!skin.TICK_COLOR) ? new Color(this._lineColor) : skin.TICK_COLOR; - if (this.markerColor === undefined) { + if (this.markerColor === undefined) + { this.markerColor = skin.MARKER_COLOR; } @@ -1171,7 +1152,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._labelOri = 0; - // rating: if (this._style.indexOf(Slider.Style.RATING) > -1) { @@ -1184,8 +1164,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._markerType = Slider.Shape.TRIANGLE; if (!this._skin.MARKER_SIZE) { - this._markerSize = this._markerSize.map(s => s * 2); - } + this._markerSize = this._markerSize.map((s) => s * 2); + } } // slider: @@ -1194,9 +1174,9 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) this._markerType = Slider.Shape.BOX; if (!this._skin.MARKER_SIZE) { - this._markerSize = (this._isHorizontal()) ? - [this._size[0] / (this._ticks[this._ticks.length - 1] - this._ticks[0]), this._size[1]] : - [this._size[0], this._size[1] / (this._ticks[this._ticks.length - 1] - this._ticks[0])]; + this._markerSize = (this._isHorizontal()) + ? [this._size[0] / (this._ticks[this._ticks.length - 1] - this._ticks[0]), this._size[1]] + : [this._size[0], this._size[1] / (this._ticks[this._ticks.length - 1] - this._ticks[0])]; } this._barSize = [this._size[0], this._size[1]]; this._barFillColor = this.getContrastedColor(new Color(this.color), 0.5); @@ -1235,12 +1215,10 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) if (!this._skin.MARKER_SIZE) { - this._markerSize = this._markerSize.map(s => s * 0.7); + this._markerSize = this._markerSize.map((s) => s * 0.7); + } } } - } - - /** * Convert an array of ratings into an array of [x,y] positions (in Slider units, with 0 at the center of the Slider) @@ -1260,20 +1238,22 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) // in compact mode the circular markers of RADIO sliders must fit within the width: if (this._compact && this._style.indexOf(Slider.Style.RADIO) > -1) { - return ratings.map(v => [ - ((v - this._ticks[0]) / range) * (this._size[0] - this._tickSize[1]*2) - - (this._size[0] / 2) + this._tickSize[1], - 0]); + return ratings.map((v) => [ + ((v - this._ticks[0]) / range) * (this._size[0] - this._tickSize[1] * 2) + - (this._size[0] / 2) + this._tickSize[1], + 0, + ]); } else if (this._style.indexOf(Slider.Style.SLIDER) > -1) { - return ratings.map(v => [ + return ratings.map((v) => [ ((v - this._ticks[0]) / range - 0.5) * (this._size[0] - this._markerSize[0]), - 0]); + 0, + ]); } else { - return ratings.map(v => [((v - this._ticks[0]) / range - 0.5) * this._size[0], 0]); + return ratings.map((v) => [((v - this._ticks[0]) / range - 0.5) * this._size[0], 0]); } } else @@ -1281,25 +1261,26 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) // in compact mode the circular markers of RADIO sliders must fit within the height: if (this._compact && this._style.indexOf(Slider.Style.RADIO) > -1) { - return ratings.map(v => [0, - ((v - this._ticks[0]) / range) * (this._size[1] - this._tickSize[0]*2) - - (this._size[1] / 2) + this._tickSize[0]]); + return ratings.map((v) => [ + 0, + ((v - this._ticks[0]) / range) * (this._size[1] - this._tickSize[0] * 2) + - (this._size[1] / 2) + this._tickSize[0], + ]); } else if (this._style.indexOf(Slider.Style.SLIDER) > -1) { - return ratings.map(v => [ + return ratings.map((v) => [ 0, - ((v - this._ticks[0]) / range - 0.5) * (this._size[1] - this._markerSize[1])]); + ((v - this._ticks[0]) / range - 0.5) * (this._size[1] - this._markerSize[1]), + ]); } else { - return ratings.map(v => [0, (1.0 - (v - this._ticks[0]) / range - 0.5) * this._size[1]]); + return ratings.map((v) => [0, (1.0 - (v - this._ticks[0]) / range - 0.5) * this._size[1]]); } } } - - /** * Convert a [x,y] position, in pixel units, relative to the slider, into a rating. * @@ -1339,8 +1320,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) } } - - /** * Determine whether the slider is horizontal. * @@ -1356,8 +1335,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) return (this._size[0] > this._size[1]); } - - /** * Calculate the rating once granularity has been taken into account. * @@ -1369,7 +1346,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) */ _granularise(rating) { - if (typeof rating === 'undefined') + if (typeof rating === "undefined") { return undefined; } @@ -1382,10 +1359,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) return rating; } - } - /** * Shape of the marker and of the ticks. * @@ -1395,13 +1370,12 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) * @public */ Slider.Shape = { - DISC: Symbol.for('DISC'), - TRIANGLE: Symbol.for('TRIANGLE'), - LINE: Symbol.for('LINE'), - BOX: Symbol.for('BOX') + DISC: Symbol.for("DISC"), + TRIANGLE: Symbol.for("TRIANGLE"), + LINE: Symbol.for("LINE"), + BOX: Symbol.for("BOX"), }; - /** * Styles. * @@ -1411,15 +1385,14 @@ Slider.Shape = { * @public */ Slider.Style = { - RATING: Symbol.for('RATING'), - TRIANGLE_MARKER: Symbol.for('TRIANGLE_MARKER'), - SLIDER: Symbol.for('SLIDER'), - WHITE_ON_BLACK: Symbol.for('WHITE_ON_BLACK'), - LABELS_45: Symbol.for('LABELS_45'), - RADIO: Symbol.for('RADIO') + RATING: Symbol.for("RATING"), + TRIANGLE_MARKER: Symbol.for("TRIANGLE_MARKER"), + SLIDER: Symbol.for("SLIDER"), + WHITE_ON_BLACK: Symbol.for("WHITE_ON_BLACK"), + LABELS_45: Symbol.for("LABELS_45"), + RADIO: Symbol.for("RADIO"), }; - /** * Skin. * @@ -1433,15 +1406,15 @@ Slider.Style = { Slider.Skin = { MARKER_SIZE: null, STANDARD: { - MARKER_COLOR: new Color('red'), + MARKER_COLOR: new Color("red"), BAR_LINE_COLOR: null, TICK_COLOR: null, - LABEL_COLOR: null + LABEL_COLOR: null, }, WHITE_ON_BLACK: { - MARKER_COLOR: new Color('white'), - BAR_LINE_COLOR: new Color('black'), - TICK_COLOR: new Color('black'), - LABEL_COLOR: new Color('black') - } + MARKER_COLOR: new Color("white"), + BAR_LINE_COLOR: new Color("black"), + TICK_COLOR: new Color("black"), + LABEL_COLOR: new Color("black"), + }, }; diff --git a/src/visual/TextBox.js b/src/visual/TextBox.js index 313f5d9..af42324 100644 --- a/src/visual/TextBox.js +++ b/src/visual/TextBox.js @@ -7,14 +7,13 @@ * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim.js'; -import {Color} from '../util/Color.js'; -import {ColorMixin} from '../util/ColorMixin.js'; -import {TextInput} from './TextInput.js'; -import {ButtonStim} from './ButtonStim.js'; -import * as util from '../util/Util.js'; +import * as PIXI from "pixi.js-legacy"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; +import * as util from "../util/Util.js"; +import { ButtonStim } from "./ButtonStim.js"; +import { TextInput } from "./TextInput.js"; +import { VisualStim } from "./VisualStim.js"; // TODO finish documenting all options /** @@ -50,122 +49,154 @@ import * as util from '../util/Util.js'; */ export class TextBox extends util.mix(VisualStim).with(ColorMixin) { - constructor({name, win, pos, anchor, size, units, ori, opacity, depth, text, font, letterHeight, bold, italic, alignment, color, contrast, flipHoriz, flipVert, fillColor, borderColor, borderWidth, padding, editable, multiline, autofocus, clipMask, autoDraw, autoLog} = {}) + constructor( + { + name, + win, + pos, + anchor, + size, + units, + ori, + opacity, + depth, + text, + font, + letterHeight, + bold, + italic, + alignment, + color, + contrast, + flipHoriz, + flipVert, + fillColor, + borderColor, + borderWidth, + padding, + editable, + multiline, + autofocus, + clipMask, + autoDraw, + autoLog, + } = {}, + ) { - super({name, win, pos, size, units, ori, opacity, depth, clipMask, autoDraw, autoLog}); + super({ name, win, pos, size, units, ori, opacity, depth, clipMask, autoDraw, autoLog }); this._addAttribute( - 'text', + "text", text, - '', - this._onChange(true, true) + "", + this._onChange(true, true), ); this._addAttribute( - 'placeholder', + "placeholder", text, - '', - this._onChange(true, true) - ); + "", + this._onChange(true, true), + ); this._addAttribute( - 'anchor', + "anchor", anchor, - 'center', - this._onChange(false, true) + "center", + this._onChange(false, true), ); this._addAttribute( - 'flipHoriz', + "flipHoriz", flipHoriz, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'flipVert', + "flipVert", flipVert, false, - this._onChange(false, false) + this._onChange(false, false), ); // font: this._addAttribute( - 'font', + "font", font, - 'Arial', - this._onChange(true, true) + "Arial", + this._onChange(true, true), ); this._addAttribute( - 'letterHeight', + "letterHeight", letterHeight, this._getDefaultLetterHeight(), - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'bold', + "bold", bold, false, - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'italic', + "italic", italic, false, - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'alignment', + "alignment", alignment, - 'left', - this._onChange(true, true) + "left", + this._onChange(true, true), ); // colors: this._addAttribute( - 'color', + "color", color, - 'white', - this._onChange(true, false) + "white", + this._onChange(true, false), ); this._addAttribute( - 'fillColor', + "fillColor", fillColor, - 'lightgrey', - this._onChange(true, false) + "lightgrey", + this._onChange(true, false), ); this._addAttribute( - 'borderColor', + "borderColor", borderColor, this.fillColor, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); // default border width: 1px this._addAttribute( - 'borderWidth', + "borderWidth", borderWidth, - util.to_unit([1, 0], 'pix', win, this._units)[0], - this._onChange(true, true) + util.to_unit([1, 0], "pix", win, this._units)[0], + this._onChange(true, true), ); // default padding: half of the letter height this._addAttribute( - 'padding', + "padding", padding, this._letterHeight / 2.0, - this._onChange(true, true) + this._onChange(true, true), ); - this._addAttribute('multiline', multiline, false, this._onChange(true, true)); - this._addAttribute('editable', editable, false, this._onChange(true, true)); - this._addAttribute('autofocus', autofocus, true, this._onChange(true, false)); - // this._setAttribute({ - // name: 'vertices', - // value: vertices, - // assert: v => (v != null) && (typeof v !== 'undefined') && Array.isArray(v) ) - // log); + this._addAttribute("multiline", multiline, false, this._onChange(true, true)); + this._addAttribute("editable", editable, false, this._onChange(true, true)); + this._addAttribute("autofocus", autofocus, true, this._onChange(true, false)); + // this._setAttribute({ + // name: 'vertices', + // value: vertices, + // assert: v => (v != null) && (typeof v !== 'undefined') && Array.isArray(v) ) + // log); // estimate the bounding box: this._estimateBoundingBox(); @@ -176,7 +207,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) } } - /** * Clears the current text value or sets it back to match the placeholder. * @@ -185,13 +215,11 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) */ reset() { - const text = this.editable ? '' : this.placeholder; + const text = this.editable ? "" : this.placeholder; this.setText(this.placeholder); } - - /** * Clears the current text value. * @@ -203,8 +231,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) this.setText(); } - - /** * For tweaking the underlying input value. * @@ -212,9 +238,9 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) * @public * @param {string} text */ - setText(text = '') + setText(text = "") { - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.text = text; } @@ -222,7 +248,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) this._text = text; } - /** * For accessing the underlying input value. * @@ -232,7 +257,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) */ getText() { - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { return this._pixi.text; } @@ -240,7 +265,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) return this._text; } - /** * Setter for the size attribute. * @@ -253,25 +277,25 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) { // test with the size is undefined, or [undefined, undefined]: let isSizeUndefined = ( - (typeof size === 'undefined') || (size === null) || - ( Array.isArray(size) && size.every( v => typeof v === 'undefined' || v === null) ) - ); + (typeof size === "undefined") || (size === null) + || (Array.isArray(size) && size.every((v) => typeof v === "undefined" || v === null)) + ); if (isSizeUndefined) { size = TextBox._defaultSizeMap.get(this._units); - if (typeof size === 'undefined') + if (typeof size === "undefined") { throw { - origin: 'TextBox.setSize', - context: 'when setting the size of TextBox: ' + this._name, - error: 'no default size for unit: ' + this._units + origin: "TextBox.setSize", + context: "when setting the size of TextBox: " + this._name, + error: "no default size for unit: " + this._units, }; } } - const hasChanged = this._setAttribute('size', size, log); + const hasChanged = this._setAttribute("size", size, log); if (hasChanged) { @@ -283,8 +307,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) } } - - /** * Get the default letter height given the stimulus' units. * @@ -296,20 +318,18 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) { const height = TextBox._defaultLetterHeightMap.get(this._units); - if (typeof height === 'undefined') + if (typeof height === "undefined") { throw { - origin: 'TextBox._getDefaultLetterHeight', - context: 'when getting the default height of TextBox: ' + this._name, - error: 'no default letter height for unit: ' + this._units + origin: "TextBox._getDefaultLetterHeight", + context: "when getting the default height of TextBox: " + this._name, + error: "no default letter height for unit: " + this._units, }; } return height; } - - /** * Get the TextInput options applied to the PIXI.TextInput. * @@ -328,24 +348,24 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) return { input: { fontFamily: this._font, - fontSize: letterHeight_px + 'px', + fontSize: letterHeight_px + "px", color: new Color(this._color).hex, - fontWeight: (this._bold) ? 'bold' : 'normal', - fontStyle: (this._italic) ? 'italic' : 'normal', + fontWeight: (this._bold) ? "bold" : "normal", + fontStyle: (this._italic) ? "italic" : "normal", - padding: padding_px + 'px', + padding: padding_px + "px", multiline, text: this._text, - height: multiline ? (height_px - 2 * padding_px) + 'px' : undefined, - width: (width_px - 2 * padding_px) + 'px' + height: multiline ? (height_px - 2 * padding_px) + "px" : undefined, + width: (width_px - 2 * padding_px) + "px", }, box: { fill: new Color(this._fillColor).int, rounded: 5, stroke: { color: new Color(this._borderColor).int, - width: borderWidth_px - } + width: borderWidth_px, + }, /*default: { fill: new Color(this._fillColor).int, rounded: 5, @@ -370,12 +390,10 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) width: borderWidth_px } }*/ - } + }, }; } - - /** * Estimate the bounding box. * @@ -395,14 +413,12 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) this._pos[0] - anchor[0] * this._size[0], this._pos[1] - anchor[1] * boxHeight, this._size[0], - boxHeight + boxHeight, ); // TODO take the orientation into account } - - /** * Update the stimulus, if necessary. * @@ -424,13 +440,13 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } // Get the currently entered text - let enteredText = this._pixi !== undefined? this._pixi.text: ''; - // Create new TextInput + let enteredText = this._pixi !== undefined ? this._pixi.text : ""; + // Create new TextInput this._pixi = new TextInput(this._getTextInputOptions()); // listeners required for regular textboxes, but may cause problems with button stimuli @@ -441,7 +457,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) // check if other TextBox instances are already in focus const { _drawList = [] } = this.psychoJS.window; - const otherTextBoxWithFocus = _drawList.some(item => item instanceof TextBox && item._pixi && item._pixi._hasFocus()); + const otherTextBoxWithFocus = _drawList.some((item) => item instanceof TextBox && item._pixi && item._pixi._hasFocus()); if (this._autofocus && !otherTextBoxWithFocus) { this._pixi._onSurrogateFocus(); @@ -452,7 +468,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) } if (this._editable) { - this.text = enteredText; + this.text = enteredText; this._pixi.placeholder = this._placeholder; } else @@ -479,8 +495,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) this._pixi.mask = this._clipMask; } - - /** * Convert the anchor attribute into numerical values. * @@ -493,30 +507,27 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) { const anchor = [0.5, 0.5]; - if (this._anchor.indexOf('left') > -1) + if (this._anchor.indexOf("left") > -1) { anchor[0] = 0; } - else if (this._anchor.indexOf('right') > -1) + else if (this._anchor.indexOf("right") > -1) { anchor[0] = 1; } - if (this._anchor.indexOf('top') > -1) + if (this._anchor.indexOf("top") > -1) { anchor[1] = 0; } - else if (this._anchor.indexOf('bottom') > -1) + else if (this._anchor.indexOf("bottom") > -1) { anchor[1] = 1; } return anchor; } - - } - /** *

This map associates units to default letter height.

* @@ -525,18 +536,17 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin) * @private */ TextBox._defaultLetterHeightMap = new Map([ - ['cm', 1.0], - ['deg', 1.0], - ['degs', 1.0], - ['degFlatPos', 1.0], - ['degFlat', 1.0], - ['norm', 0.1], - ['height', 0.2], - ['pix', 20], - ['pixels', 20] + ["cm", 1.0], + ["deg", 1.0], + ["degs", 1.0], + ["degFlatPos", 1.0], + ["degFlat", 1.0], + ["norm", 0.1], + ["height", 0.2], + ["pix", 20], + ["pixels", 20], ]); - /** *

This map associates units to default sizes.

* @@ -545,13 +555,13 @@ TextBox._defaultLetterHeightMap = new Map([ * @private */ TextBox._defaultSizeMap = new Map([ - ['cm', [15.0, -1]], - ['deg', [15.0, -1]], - ['degs', [15.0, -1]], - ['degFlatPos', [15.0, -1]], - ['degFlat', [15.0, -1]], - ['norm', [1, -1]], - ['height', [1, -1]], - ['pix', [500, -1]], - ['pixels', [500, -1]] + ["cm", [15.0, -1]], + ["deg", [15.0, -1]], + ["degs", [15.0, -1]], + ["degFlatPos", [15.0, -1]], + ["degFlat", [15.0, -1]], + ["norm", [1, -1]], + ["height", [1, -1]], + ["pix", [500, -1]], + ["pixels", [500, -1]], ]); diff --git a/src/visual/TextInput.js b/src/visual/TextInput.js index af7292a..b343507 100644 --- a/src/visual/TextInput.js +++ b/src/visual/TextInput.js @@ -24,7 +24,7 @@ export class TextInput extends PIXI.Container outline: "none", text: "", transformOrigin: "0 0", - lineHeight: "1" + lineHeight: "1", }, styles.input, ); @@ -58,7 +58,7 @@ export class TextInput extends PIXI.Container this._restrict_value = ""; this._createDOMInput(); this.substituteText = !this._multiline; - this._setState('DEFAULT'); + this._setState("DEFAULT"); } // GETTERS & SETTERS @@ -638,7 +638,7 @@ export class TextInput extends PIXI.Container } else if (components.length == 4) { - let padding = components.map(component => + let padding = components.map((component) => { return parseFloat(component); }); diff --git a/src/visual/TextStim.js b/src/visual/TextStim.js index c214377..b48da12 100644 --- a/src/visual/TextStim.js +++ b/src/visual/TextStim.js @@ -7,14 +7,12 @@ * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim.js'; -import {Color} from '../util/Color.js'; -import {ColorMixin} from '../util/ColorMixin.js'; -import * as util from '../util/Util.js'; +import * as PIXI from "pixi.js-legacy"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; import { to_pixiPoint } from "../util/Pixi.js"; - +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; /** * @name module:visual.TextStim @@ -49,9 +47,34 @@ import { to_pixiPoint } from "../util/Pixi.js"; */ export class TextStim extends util.mix(VisualStim).with(ColorMixin) { - constructor({name, win, text, font, pos, color, opacity, depth, contrast, units, ori, height, bold, italic, alignHoriz, alignVert, wrapWidth, flipHoriz, flipVert, clipMask, autoDraw, autoLog} = {}) + constructor( + { + name, + win, + text, + font, + pos, + color, + opacity, + depth, + contrast, + units, + ori, + height, + bold, + italic, + alignHoriz, + alignVert, + wrapWidth, + flipHoriz, + flipVert, + clipMask, + autoDraw, + autoLog, + } = {}, + ) { - super({name, win, units, ori, opacity, depth, pos, clipMask, autoDraw, autoLog}); + super({ name, win, units, ori, opacity, depth, pos, clipMask, autoDraw, autoLog }); // callback to deal with text metrics invalidation: const onChange = (withPixi = false, withBoundingBox = false, withMetrics = false) => @@ -69,81 +92,80 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) // text and font: this._addAttribute( - 'text', + "text", text, - 'Hello World', - onChange(true, true, true) + "Hello World", + onChange(true, true, true), ); this._addAttribute( - 'alignHoriz', + "alignHoriz", alignHoriz, - 'center', - onChange(true, true, true) + "center", + onChange(true, true, true), ); this._addAttribute( - 'alignVert', + "alignVert", alignVert, - 'center', - onChange(true, true, true) + "center", + onChange(true, true, true), ); this._addAttribute( - 'flipHoriz', + "flipHoriz", flipHoriz, false, - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'flipVert', + "flipVert", flipVert, false, - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'font', + "font", font, - 'Arial', - this._onChange(true, true) + "Arial", + this._onChange(true, true), ); this._addAttribute( - 'height', + "height", height, this._getDefaultLetterHeight(), - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'wrapWidth', + "wrapWidth", wrapWidth, this._getDefaultWrapWidth(), - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'bold', + "bold", bold, false, - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'italic', + "italic", italic, false, - onChange(true, true, true) + onChange(true, true, true), ); // color: this._addAttribute( - 'color', + "color", color, - 'white', - this._onChange(true, false) + "white", + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); - // estimate the bounding box (using TextMetrics): this._estimateBoundingBox(); @@ -153,8 +175,6 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) } } - - /** * Get the metrics estimated for the text and style. * @@ -166,7 +186,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) */ getTextMetrics() { - if (typeof this._textMetrics === 'undefined') + if (typeof this._textMetrics === "undefined") { this._textMetrics = PIXI.TextMetrics.measureText(this._text, this._getTextStyle()); } @@ -174,8 +194,6 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) return this._textMetrics; } - - /** * Get the default letter height given the stimulus' units. * @@ -187,20 +205,18 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) { const height = TextStim._defaultLetterHeightMap.get(this._units); - if (typeof height === 'undefined') + if (typeof height === "undefined") { throw { - origin: 'TextStim._getDefaultLetterHeight', - context: 'when getting the default height of TextStim: ' + this._name, - error: 'no default letter height for unit: ' + this._units + origin: "TextStim._getDefaultLetterHeight", + context: "when getting the default height of TextStim: " + this._name, + error: "no default letter height for unit: " + this._units, }; } return height; } - - /** * Get the default wrap width given the stimulus' units. * @@ -212,20 +228,18 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) { const wrapWidth = TextStim._defaultWrapWidthMap.get(this._units); - if (typeof wrapWidth === 'undefined') + if (typeof wrapWidth === "undefined") { throw { - origin: 'TextStim._getDefaultWrapWidth', - context: 'when getting the default wrap width of TextStim: ' + this._name, - error: 'no default wrap width for unit: ' + this._units + origin: "TextStim._getDefaultWrapWidth", + context: "when getting the default wrap width of TextStim: " + this._name, + error: "no default wrap width for unit: " + this._units, }; } return wrapWidth; } - - /** * Estimate the bounding box. * @@ -238,11 +252,11 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) { // size of the text, irrespective of the orientation: const textMetrics = this.getTextMetrics(); - const textSize = util.to_unit( + const textSize = util.to_unit( [textMetrics.width, textMetrics.height], - 'pix', + "pix", this._win, - this._units + this._units, ); // take the alignment into account: @@ -251,14 +265,12 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) this._pos[0] - anchor[0] * textSize[0], this._pos[1] - anchor[1] * textSize[1], textSize[0], - textSize[1] + textSize[1], ); // TODO take the orientation into account } - - /** * Get the PIXI Text Style applied to the PIXI.Text * @@ -270,17 +282,15 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) return new PIXI.TextStyle({ fontFamily: this._font, fontSize: Math.round(this._getLengthPix(this._height)), - fontWeight: (this._bold) ? 'bold' : 'normal', - fontStyle: (this._italic) ? 'italic' : 'normal', + fontWeight: (this._bold) ? "bold" : "normal", + fontStyle: (this._italic) ? "italic" : "normal", fill: this.getContrastedColor(new Color(this._color), this._contrast).hex, align: this._alignHoriz, - wordWrap: (typeof this._wrapWidth !== 'undefined'), - wordWrapWidth: (typeof this._wrapWidth !== 'undefined') ? this._getHorLengthPix(this._wrapWidth) : 0 + wordWrap: (typeof this._wrapWidth !== "undefined"), + wordWrapWidth: (typeof this._wrapWidth !== "undefined") ? this._getHorLengthPix(this._wrapWidth) : 0, }); } - - /** * Update the stimulus, if necessary. * @@ -301,7 +311,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } @@ -326,7 +336,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) // update the size attributes: this._size = [ this._getLengthUnits(Math.abs(this._pixi.width)), - this._getLengthUnits(Math.abs(this._pixi.height)) + this._getLengthUnits(Math.abs(this._pixi.height)), ]; // refine the estimate of the bounding box: @@ -334,12 +344,10 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) this._pos[0] - anchor[0] * this._size[0], this._pos[1] - anchor[1] * this._size[1], this._size[0], - this._size[1] + this._size[1], ); } - - /** * Convert the alignment attributes into an anchor. * @@ -354,36 +362,33 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) switch (this._alignHoriz) { - case 'left': + case "left": anchor.push(0); break; - case 'right': + case "right": anchor.push(1); break; default: - case 'center': + case "center": anchor.push(0.5); } switch (this._alignVert) { - case 'top': + case "top": anchor.push(0); break; - case 'bottom': + case "bottom": anchor.push(1); break; default: - case 'center': + case "center": anchor.push(0.5); } return anchor; } - } - - /** *

This map associates units to default letter height.

* @@ -392,19 +397,17 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin) * @private */ TextStim._defaultLetterHeightMap = new Map([ - ['cm', 1.0], - ['deg', 1.0], - ['degs', 1.0], - ['degFlatPos', 1.0], - ['degFlat', 1.0], - ['norm', 0.1], - ['height', 0.2], - ['pix', 20], - ['pixels', 20] + ["cm", 1.0], + ["deg", 1.0], + ["degs", 1.0], + ["degFlatPos", 1.0], + ["degFlat", 1.0], + ["norm", 0.1], + ["height", 0.2], + ["pix", 20], + ["pixels", 20], ]); - - /** *

This map associates units to default wrap width.

* @@ -413,13 +416,13 @@ TextStim._defaultLetterHeightMap = new Map([ * @private */ TextStim._defaultWrapWidthMap = new Map([ - ['cm', 15.0], - ['deg', 15.0], - ['degs', 15.0], - ['degFlatPos', 15.0], - ['degFlat', 15.0], - ['norm', 1], - ['height', 1], - ['pix', 500], - ['pixels', 500] + ["cm", 15.0], + ["deg", 15.0], + ["degs", 15.0], + ["degFlatPos", 15.0], + ["degFlat", 15.0], + ["norm", 1], + ["height", 1], + ["pix", 500], + ["pixels", 500], ]); diff --git a/src/visual/VisualStim.js b/src/visual/VisualStim.js index 7c6919b..02ae2d9 100644 --- a/src/visual/VisualStim.js +++ b/src/visual/VisualStim.js @@ -7,12 +7,10 @@ * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {MinimalStim} from '../core/MinimalStim.js'; -import {WindowMixin} from '../core/WindowMixin.js'; -import * as util from '../util/Util.js'; - +import * as PIXI from "pixi.js-legacy"; +import { MinimalStim } from "../core/MinimalStim.js"; +import { WindowMixin } from "../core/WindowMixin.js"; +import * as util from "../util/Util.js"; /** * Base class for all visual stimuli. @@ -36,55 +34,54 @@ import * as util from '../util/Util.js'; */ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) { - constructor({name, win, units, ori, opacity, depth, pos, size, clipMask, autoDraw, autoLog} = {}) + constructor({ name, win, units, ori, opacity, depth, pos, size, clipMask, autoDraw, autoLog } = {}) { - super({win, name, autoDraw, autoLog}); + super({ win, name, autoDraw, autoLog }); this._addAttribute( - 'units', + "units", units, - (typeof win !== 'undefined' && win !== null) ? win.units : 'height', - this._onChange(true, true) + (typeof win !== "undefined" && win !== null) ? win.units : "height", + this._onChange(true, true), ); this._addAttribute( - 'pos', + "pos", pos, - [0, 0] + [0, 0], ); this._addAttribute( - 'size', + "size", size, - undefined + undefined, ); this._addAttribute( - 'ori', + "ori", ori, - 0.0 + 0.0, ); this._addAttribute( - 'opacity', + "opacity", opacity, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'depth', + "depth", depth, 0, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'clipMask', + "clipMask", clipMask, null, - this._onChange(false, false) + this._onChange(false, false), ); // bounding box of the stimulus, in stimulus units // note: boundingBox does not take the orientation into account - this._addAttribute('boundingBox', PIXI.Rectangle.EMPTY); + this._addAttribute("boundingBox", PIXI.Rectangle.EMPTY); - // the stimulus need to be updated: this._needUpdate = true; @@ -92,8 +89,6 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) this._needPixiUpdate = true; } - - /** * Force a refresh of the stimulus. * @@ -107,8 +102,6 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) this._onChange(true, true)(); } - - /** * Setter for the size attribute. * @@ -120,7 +113,7 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) setSize(size, log = false) { // size is either undefined, null, or a tuple of numbers: - if (typeof size !== 'undefined' && size !== null) + if (typeof size !== "undefined" && size !== null) { size = util.toNumerical(size); if (!Array.isArray(size)) @@ -129,7 +122,7 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) } } - const hasChanged = this._setAttribute('size', size, log); + const hasChanged = this._setAttribute("size", size, log); if (hasChanged) { @@ -137,8 +130,6 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) } } - - /** * Setter for the orientation attribute. * @@ -149,20 +140,17 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) */ setOri(ori, log = false) { - const hasChanged = this._setAttribute('ori', ori, log); + const hasChanged = this._setAttribute("ori", ori, log); if (hasChanged) { let radians = -ori * 0.017453292519943295; - this._rotationMatrix = [[Math.cos(radians), -Math.sin(radians)], - [Math.sin(radians), Math.cos(radians)]]; + this._rotationMatrix = [[Math.cos(radians), -Math.sin(radians)], [Math.sin(radians), Math.cos(radians)]]; this._onChange(true, true)(); } } - - /** * Setter for the position attribute. * @@ -174,20 +162,18 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) setPos(pos, log = false) { const prevPos = this._pos; - const hasChanged = this._setAttribute('pos', util.toNumerical(pos), log); + const hasChanged = this._setAttribute("pos", util.toNumerical(pos), log); if (hasChanged) { this._needUpdate = true; - + // update the bounding box, without calling _estimateBoundingBox: this._boundingBox.x += this._pos[0] - prevPos[0]; this._boundingBox.y += this._pos[1] - prevPos[1]; } } - - /** * Determine whether an object is inside the bounding box of the stimulus. * @@ -202,12 +188,12 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) // get the position of the object, in pixel coordinates: const objectPos_px = util.getPositionFromObject(object, units); - if (typeof objectPos_px === 'undefined') + if (typeof objectPos_px === "undefined") { throw { - origin: 'VisualStim.contains', - context: 'when determining whether VisualStim: ' + this._name + ' contains object: ' + util.toString(object), - error: 'unable to determine the position of the object' + origin: "VisualStim.contains", + context: "when determining whether VisualStim: " + this._name + " contains object: " + util.toString(object), + error: "unable to determine the position of the object", }; } @@ -215,8 +201,6 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) return this._getBoundingBox_px().contains(objectPos_px[0], objectPos_px[1]); } - - /** * Estimate the bounding box. * @@ -227,14 +211,12 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) _estimateBoundingBox() { throw { - origin: 'VisualStim._estimateBoundingBox', + origin: "VisualStim._estimateBoundingBox", context: `when estimating the bounding box of visual stimulus: ${this._name}`, - error: 'this method is abstract and should not be called.' + error: "this method is abstract and should not be called.", }; } - - /** * Get the bounding box in pixel coordinates * @@ -245,37 +227,35 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) */ _getBoundingBox_px() { - if (this._units === 'pix') + if (this._units === "pix") { return this._boundingBox.clone(); } - else if (this._units === 'norm') + else if (this._units === "norm") { return new PIXI.Rectangle( this._boundingBox.x * this._win.size[0] / 2, this._boundingBox.y * this._win.size[1] / 2, this._boundingBox.width * this._win.size[0] / 2, - this._boundingBox.height * this._win.size[1] / 2 + this._boundingBox.height * this._win.size[1] / 2, ); } - else if (this._units === 'height') + else if (this._units === "height") { const minSize = Math.min(this._win.size[0], this._win.size[1]); return new PIXI.Rectangle( this._boundingBox.x * minSize, this._boundingBox.y * minSize, this._boundingBox.width * minSize, - this._boundingBox.height * minSize + this._boundingBox.height * minSize, ); } else { - throw Object.assign(response, {error: `unknown units: ${this._units}`}); + throw Object.assign(response, { error: `unknown units: ${this._units}` }); } } - - /** * 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. @@ -302,5 +282,4 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) } }; } - } diff --git a/src/visual/index.js b/src/visual/index.js index bef7d96..834e0a8 100644 --- a/src/visual/index.js +++ b/src/visual/index.js @@ -1,12 +1,12 @@ -export * from './ButtonStim.js'; -export * from './Form.js'; -export * from './ImageStim.js'; -export * from './MovieStim.js'; -export * from './Polygon.js'; -export * from './Rect.js'; -export * from './ShapeStim.js'; -export * from './Slider.js'; -export * from './TextBox.js'; -export * from './TextInput.js'; -export * from './TextStim.js'; -export * from './VisualStim.js'; +export * from "./ButtonStim.js"; +export * from "./Form.js"; +export * from "./ImageStim.js"; +export * from "./MovieStim.js"; +export * from "./Polygon.js"; +export * from "./Rect.js"; +export * from "./ShapeStim.js"; +export * from "./Slider.js"; +export * from "./TextBox.js"; +export * from "./TextInput.js"; +export * from "./TextStim.js"; +export * from "./VisualStim.js"; From 57a590c5365168a0b370a47c798a61f2c2357d62 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 14:07:40 +0100 Subject: [PATCH 25/29] util: enforce formatting rules --- src/util/Clock.js | 37 ++-- src/util/Color.js | 393 +++++++++++++++++------------------- src/util/ColorMixin.js | 61 +++--- src/util/EventEmitter.js | 23 +-- src/util/PsychObject.js | 206 +++++++++---------- src/util/Scheduler.js | 28 +-- src/util/Util.js | 422 ++++++++++++++++++--------------------- src/util/index.js | 14 +- 8 files changed, 539 insertions(+), 645 deletions(-) diff --git a/src/util/Clock.js b/src/util/Clock.js index 4ee213b..5259a46 100644 --- a/src/util/Clock.js +++ b/src/util/Clock.js @@ -7,7 +7,6 @@ * @license Distributed under the terms of the MIT License */ - /** *

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.

* @@ -22,7 +21,6 @@ export class MonotonicClock this._timeAtLastReset = startTime; } - /** * Get the current time on this clock. * @@ -36,7 +34,6 @@ export class MonotonicClock return MonotonicClock.getReferenceTime() - this._timeAtLastReset; } - /** * Get the current offset being applied to the high resolution timebase used by this Clock. * @@ -50,7 +47,6 @@ export class MonotonicClock return this._timeAtLastReset; } - /** * Get the time elapsed since the reference point. * @@ -65,7 +61,6 @@ export class MonotonicClock // return (new Date().getTime()) / 1000.0 - MonotonicClock._referenceTime; } - /** * Get the current timestamp with language-sensitive formatting rules applied. * @@ -79,18 +74,18 @@ export class MonotonicClock * @param {object} options - An object with detailed date and time styling information. * @return {string} The current timestamp in the chosen format. */ - static getDate(locales = 'en-CA', optionsMaybe) + static getDate(locales = "en-CA", optionsMaybe) { const date = new Date(); const options = Object.assign({ hour12: false, - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - fractionalSecondDigits: 3 + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "numeric", + minute: "numeric", + second: "numeric", + fractionalSecondDigits: 3, }, optionsMaybe); const dateTimeFormat = new Intl.DateTimeFormat(locales, options); @@ -114,15 +109,14 @@ export class MonotonicClock // yyyy-mm-dd, hh:mm:ss.sss return MonotonicClock.getDate() // yyyy-mm-dd_hh:mm:ss.sss - .replace(', ', '_') + .replace(", ", "_") // yyyy-mm-dd_hh[h]mm:ss.sss - .replace(':', 'h') + .replace(":", "h") // yyyy-mm-dd_hh[h]mm.ss.sss - .replace(':', '.'); + .replace(":", "."); } } - /** * The clock's referenceTime is the time when the module was loaded (in seconds). * @@ -135,7 +129,6 @@ MonotonicClock._referenceTime = performance.now() / 1000.0; // MonotonicClock._referenceTime = new Date().getTime() / 1000.0; - /** *

Clock is a MonotonicClock that also offers the possibility of being reset.

* @@ -164,7 +157,6 @@ export class Clock extends MonotonicClock this._timeAtLastReset = MonotonicClock.getReferenceTime() + newTime; } - /** * Add more time to the clock's 'start' time (t0). * @@ -182,7 +174,6 @@ export class Clock extends MonotonicClock } } - /** *

CountdownTimer is a clock counts down from the time of last reset.This class handles multiple color spaces, and offers various * static methods for converting colors from one space to another.

@@ -32,27 +31,26 @@ */ export class Color { - - constructor(obj = 'black', colorspace = Color.COLOR_SPACE.RGB) + constructor(obj = "black", colorspace = Color.COLOR_SPACE.RGB) { const response = { - origin: 'Color', - context: 'when defining a color' + origin: "Color", + context: "when defining a color", }; // named color (e.g. 'seagreen') or string hexadecimal representation (e.g. '#FF0000'): // note: we expect the color space to be RGB - if (typeof obj == 'string') + if (typeof obj == "string") { if (colorspace !== Color.COLOR_SPACE.RGB) { throw Object.assign(response, { - error: 'the colorspace must be RGB for a named color' + error: "the colorspace must be RGB for a named color", }); } // hexademical representation: - if (obj[0] === '#') + if (obj[0] === "#") { this._hex = obj; } @@ -61,7 +59,7 @@ export class Color { if (!(obj.toLowerCase() in Color.NAMED_COLORS)) { - throw Object.assign(response, {error: 'unknown named color: ' + obj}); + throw Object.assign(response, { error: "unknown named color: " + obj }); } this._hex = Color.NAMED_COLORS[obj.toLowerCase()]; @@ -69,23 +67,21 @@ export class Color this._rgb = Color.hexToRgb(this._hex); } - // hexadecimal number representation (e.g. 0xFF0000) // note: we expect the color space to be RGB - else if (typeof obj == 'number') + else if (typeof obj == "number") { if (colorspace !== Color.COLOR_SPACE.RGB) { throw Object.assign(response, { - error: 'the colorspace must be RGB for' + - ' a' + - ' named color' + error: "the colorspace must be RGB for" + + " a" + + " named color", }); } this._rgb = Color._intToRgb(obj); } - // array of numbers: else if (Array.isArray(obj)) { @@ -124,17 +120,15 @@ export class Color break; default: - throw Object.assign(response, {error: 'unknown colorspace: ' + colorspace}); + throw Object.assign(response, { error: "unknown colorspace: " + colorspace }); } } - else if (obj instanceof Color) { this._rgb = obj._rgb.slice(); } } - /** * Get the [0,1] RGB triplet equivalent of this Color. * @@ -148,7 +142,6 @@ export class Color return this._rgb; } - /** * Get the [0,255] RGB triplet equivalent of this Color. * @@ -162,7 +155,6 @@ export class Color return [Math.round(this._rgb[0] * 255.0), Math.round(this._rgb[1] * 255.0), Math.round(this._rgb[2] * 255.0)]; } - /** * Get the hexadecimal color code equivalent of this Color. * @@ -173,7 +165,7 @@ export class Color */ get hex() { - if (typeof this._hex === 'undefined') + if (typeof this._hex === "undefined") { this._hex = Color._rgbToHex(this._rgb); } @@ -190,14 +182,13 @@ export class Color */ get int() { - if (typeof this._int === 'undefined') + if (typeof this._int === "undefined") { this._int = Color._rgbToInt(this._rgb); } return this._int; } - /* get hsv() { if (typeof this._hsv === 'undefined') @@ -216,7 +207,6 @@ export class Color } */ - /** * String representation of the color, i.e. the hexadecimal representation. * @@ -230,7 +220,6 @@ export class Color return this.hex; } - /** * Get the [0,255] RGB triplet equivalent of the hexadecimal color code. * @@ -247,16 +236,15 @@ export class Color if (result == null) { throw { - origin: 'Color.hexToRgb255', - context: 'when converting an hexadecimal color code to its 255- or [0,1]-based RGB color representation', - error: 'unable to parse the argument: wrong type or wrong code' + origin: "Color.hexToRgb255", + context: "when converting an hexadecimal color code to its 255- or [0,1]-based RGB color representation", + error: "unable to parse the argument: wrong type or wrong code", }; } return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]; } - /** * Get the [0,1] RGB triplet equivalent of the hexadecimal color code. * @@ -273,7 +261,6 @@ export class Color return [r255 / 255.0, g255 / 255.0, b255 / 255.0]; } - /** * Get the hexadecimal color code equivalent of the [0, 255] RGB triplet. * @@ -287,8 +274,8 @@ export class Color static rgb255ToHex(rgb255) { const response = { - origin: 'Color.rgb255ToHex', - context: 'when converting an rgb triplet to its hexadecimal color representation' + origin: "Color.rgb255ToHex", + context: "when converting an rgb triplet to its hexadecimal color representation", }; try @@ -298,11 +285,10 @@ export class Color } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the hexadecimal color code equivalent of the [0, 1] RGB triplet. * @@ -316,8 +302,8 @@ export class Color static rgbToHex(rgb) { const response = { - origin: 'Color.rgbToHex', - context: 'when converting an rgb triplet to its hexadecimal color representation' + origin: "Color.rgbToHex", + context: "when converting an rgb triplet to its hexadecimal color representation", }; try @@ -327,11 +313,10 @@ export class Color } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the integer equivalent of the [0, 1] RGB triplet. * @@ -345,8 +330,8 @@ export class Color static rgbToInt(rgb) { const response = { - origin: 'Color.rgbToInt', - context: 'when converting an rgb triplet to its integer representation' + origin: "Color.rgbToInt", + context: "when converting an rgb triplet to its integer representation", }; try @@ -356,11 +341,10 @@ export class Color } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the integer equivalent of the [0, 255] RGB triplet. * @@ -374,8 +358,8 @@ export class Color static rgb255ToInt(rgb255) { const response = { - origin: 'Color.rgb255ToInt', - context: 'when converting an rgb triplet to its integer representation' + origin: "Color.rgb255ToInt", + context: "when converting an rgb triplet to its integer representation", }; try { @@ -384,11 +368,10 @@ export class Color } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the hexadecimal color code equivalent of the [0, 255] RGB triplet. * @@ -406,7 +389,6 @@ export class Color return "#" + ((1 << 24) + (rgb255[0] << 16) + (rgb255[1] << 8) + rgb255[2]).toString(16).slice(1); } - /** * Get the hexadecimal color code equivalent of the [0, 1] RGB triplet. * @@ -425,7 +407,6 @@ export class Color return Color._rgb255ToHex(rgb255); } - /** * Get the integer equivalent of the [0, 1] RGB triplet. * @@ -444,7 +425,6 @@ export class Color return Color._rgb255ToInt(rgb255); } - /** * Get the integer equivalent of the [0, 255] RGB triplet. * @@ -462,7 +442,6 @@ export class Color return rgb255[0] * 0x10000 + rgb255[1] * 0x100 + rgb255[2]; } - /** * Get the [0, 255] based RGB triplet equivalent of the integer color code. * @@ -484,7 +463,6 @@ export class Color return [r255, g255, b255]; } - /** * Get the [0, 1] based RGB triplet equivalent of the integer color code. * @@ -517,20 +495,21 @@ export class Color */ static _checkTypeAndRange(arg, range = undefined) { - if (!Array.isArray(arg) || arg.length !== 3 || - typeof arg[0] !== 'number' || typeof arg[1] !== 'number' || typeof arg[2] !== 'number') + if ( + !Array.isArray(arg) || arg.length !== 3 + || typeof arg[0] !== "number" || typeof arg[1] !== "number" || typeof arg[2] !== "number" + ) { - throw 'the argument should be an array of numbers of length 3'; + throw "the argument should be an array of numbers of length 3"; } - if (typeof range !== 'undefined' && (arg[0] < range[0] || arg[0] > range[1] || arg[1] < range[0] || arg[1] > range[1] || arg[2] < range[0] || arg[2] > range[1])) + if (typeof range !== "undefined" && (arg[0] < range[0] || arg[0] > range[1] || arg[1] < range[0] || arg[1] > range[1] || arg[2] < range[0] || arg[2] > range[1])) { - throw 'the color components should all belong to [' + range[0] + ', ' + range[1] + ']'; + throw "the color components should all belong to [" + range[0] + ", " + range[1] + "]"; } } } - /** * Color spaces. * @@ -543,13 +522,12 @@ Color.COLOR_SPACE = { /** * RGB colorspace: [r,g,b] with r,g,b in [-1, 1] */ - RGB: Symbol.for('RGB'), + RGB: Symbol.for("RGB"), /** * RGB255 colorspace: [r,g,b] with r,g,b in [0, 255] */ - RGB255: Symbol.for('RGB255'), - + RGB255: Symbol.for("RGB255"), /* HSV: Symbol.for('HSV'), DKL: Symbol.for('DKL'), @@ -557,7 +535,6 @@ Color.COLOR_SPACE = { */ }; - /** * Named colors. * @@ -567,151 +544,151 @@ Color.COLOR_SPACE = { * @public */ Color.NAMED_COLORS = { - 'aliceblue': '#F0F8FF', - 'antiquewhite': '#FAEBD7', - 'aqua': '#00FFFF', - 'aquamarine': '#7FFFD4', - 'azure': '#F0FFFF', - 'beige': '#F5F5DC', - 'bisque': '#FFE4C4', - 'black': '#000000', - 'blanchedalmond': '#FFEBCD', - 'blue': '#0000FF', - 'blueviolet': '#8A2BE2', - 'brown': '#A52A2A', - 'burlywood': '#DEB887', - 'cadetblue': '#5F9EA0', - 'chartreuse': '#7FFF00', - 'chocolate': '#D2691E', - 'coral': '#FF7F50', - 'cornflowerblue': '#6495ED', - 'cornsilk': '#FFF8DC', - 'crimson': '#DC143C', - 'cyan': '#00FFFF', - 'darkblue': '#00008B', - 'darkcyan': '#008B8B', - 'darkgoldenrod': '#B8860B', - 'darkgray': '#A9A9A9', - 'darkgrey': '#A9A9A9', - 'darkgreen': '#006400', - 'darkkhaki': '#BDB76B', - 'darkmagenta': '#8B008B', - 'darkolivegreen': '#556B2F', - 'darkorange': '#FF8C00', - 'darkorchid': '#9932CC', - 'darkred': '#8B0000', - 'darksalmon': '#E9967A', - 'darkseagreen': '#8FBC8B', - 'darkslateblue': '#483D8B', - 'darkslategray': '#2F4F4F', - 'darkslategrey': '#2F4F4F', - 'darkturquoise': '#00CED1', - 'darkviolet': '#9400D3', - 'deeppink': '#FF1493', - 'deepskyblue': '#00BFFF', - 'dimgray': '#696969', - 'dimgrey': '#696969', - 'dodgerblue': '#1E90FF', - 'firebrick': '#B22222', - 'floralwhite': '#FFFAF0', - 'forestgreen': '#228B22', - 'fuchsia': '#FF00FF', - 'gainsboro': '#DCDCDC', - 'ghostwhite': '#F8F8FF', - 'gold': '#FFD700', - 'goldenrod': '#DAA520', - 'gray': '#808080', - 'grey': '#808080', - 'green': '#008000', - 'greenyellow': '#ADFF2F', - 'honeydew': '#F0FFF0', - 'hotpink': '#FF69B4', - 'indianred': '#CD5C5C', - 'indigo': '#4B0082', - 'ivory': '#FFFFF0', - 'khaki': '#F0E68C', - 'lavender': '#E6E6FA', - 'lavenderblush': '#FFF0F5', - 'lawngreen': '#7CFC00', - 'lemonchiffon': '#FFFACD', - 'lightblue': '#ADD8E6', - 'lightcoral': '#F08080', - 'lightcyan': '#E0FFFF', - 'lightgoldenrodyellow': '#FAFAD2', - 'lightgray': '#D3D3D3', - 'lightgrey': '#D3D3D3', - 'lightgreen': '#90EE90', - 'lightpink': '#FFB6C1', - 'lightsalmon': '#FFA07A', - 'lightseagreen': '#20B2AA', - 'lightskyblue': '#87CEFA', - 'lightslategray': '#778899', - 'lightslategrey': '#778899', - 'lightsteelblue': '#B0C4DE', - 'lightyellow': '#FFFFE0', - 'lime': '#00FF00', - 'limegreen': '#32CD32', - 'linen': '#FAF0E6', - 'magenta': '#FF00FF', - 'maroon': '#800000', - 'mediumaquamarine': '#66CDAA', - 'mediumblue': '#0000CD', - 'mediumorchid': '#BA55D3', - 'mediumpurple': '#9370DB', - 'mediumseagreen': '#3CB371', - 'mediumslateblue': '#7B68EE', - 'mediumspringgreen': '#00FA9A', - 'mediumturquoise': '#48D1CC', - 'mediumvioletred': '#C71585', - 'midnightblue': '#191970', - 'mintcream': '#F5FFFA', - 'mistyrose': '#FFE4E1', - 'moccasin': '#FFE4B5', - 'navajowhite': '#FFDEAD', - 'navy': '#000080', - 'oldlace': '#FDF5E6', - 'olive': '#808000', - 'olivedrab': '#6B8E23', - 'orange': '#FFA500', - 'orangered': '#FF4500', - 'orchid': '#DA70D6', - 'palegoldenrod': '#EEE8AA', - 'palegreen': '#98FB98', - 'paleturquoise': '#AFEEEE', - 'palevioletred': '#DB7093', - 'papayawhip': '#FFEFD5', - 'peachpuff': '#FFDAB9', - 'peru': '#CD853F', - 'pink': '#FFC0CB', - 'plum': '#DDA0DD', - 'powderblue': '#B0E0E6', - 'purple': '#800080', - 'red': '#FF0000', - 'rosybrown': '#BC8F8F', - 'royalblue': '#4169E1', - 'saddlebrown': '#8B4513', - 'salmon': '#FA8072', - 'sandybrown': '#F4A460', - 'seagreen': '#2E8B57', - 'seashell': '#FFF5EE', - 'sienna': '#A0522D', - 'silver': '#C0C0C0', - 'skyblue': '#87CEEB', - 'slateblue': '#6A5ACD', - 'slategray': '#708090', - 'slategrey': '#708090', - 'snow': '#FFFAFA', - 'springgreen': '#00FF7F', - 'steelblue': '#4682B4', - 'tan': '#D2B48C', - 'teal': '#008080', - 'thistle': '#D8BFD8', - 'tomato': '#FF6347', - 'turquoise': '#40E0D0', - 'violet': '#EE82EE', - 'wheat': '#F5DEB3', - 'white': '#FFFFFF', - 'whitesmoke': '#F5F5F5', - 'yellow': '#FFFF00', - 'yellowgreen': '#9ACD32' + "aliceblue": "#F0F8FF", + "antiquewhite": "#FAEBD7", + "aqua": "#00FFFF", + "aquamarine": "#7FFFD4", + "azure": "#F0FFFF", + "beige": "#F5F5DC", + "bisque": "#FFE4C4", + "black": "#000000", + "blanchedalmond": "#FFEBCD", + "blue": "#0000FF", + "blueviolet": "#8A2BE2", + "brown": "#A52A2A", + "burlywood": "#DEB887", + "cadetblue": "#5F9EA0", + "chartreuse": "#7FFF00", + "chocolate": "#D2691E", + "coral": "#FF7F50", + "cornflowerblue": "#6495ED", + "cornsilk": "#FFF8DC", + "crimson": "#DC143C", + "cyan": "#00FFFF", + "darkblue": "#00008B", + "darkcyan": "#008B8B", + "darkgoldenrod": "#B8860B", + "darkgray": "#A9A9A9", + "darkgrey": "#A9A9A9", + "darkgreen": "#006400", + "darkkhaki": "#BDB76B", + "darkmagenta": "#8B008B", + "darkolivegreen": "#556B2F", + "darkorange": "#FF8C00", + "darkorchid": "#9932CC", + "darkred": "#8B0000", + "darksalmon": "#E9967A", + "darkseagreen": "#8FBC8B", + "darkslateblue": "#483D8B", + "darkslategray": "#2F4F4F", + "darkslategrey": "#2F4F4F", + "darkturquoise": "#00CED1", + "darkviolet": "#9400D3", + "deeppink": "#FF1493", + "deepskyblue": "#00BFFF", + "dimgray": "#696969", + "dimgrey": "#696969", + "dodgerblue": "#1E90FF", + "firebrick": "#B22222", + "floralwhite": "#FFFAF0", + "forestgreen": "#228B22", + "fuchsia": "#FF00FF", + "gainsboro": "#DCDCDC", + "ghostwhite": "#F8F8FF", + "gold": "#FFD700", + "goldenrod": "#DAA520", + "gray": "#808080", + "grey": "#808080", + "green": "#008000", + "greenyellow": "#ADFF2F", + "honeydew": "#F0FFF0", + "hotpink": "#FF69B4", + "indianred": "#CD5C5C", + "indigo": "#4B0082", + "ivory": "#FFFFF0", + "khaki": "#F0E68C", + "lavender": "#E6E6FA", + "lavenderblush": "#FFF0F5", + "lawngreen": "#7CFC00", + "lemonchiffon": "#FFFACD", + "lightblue": "#ADD8E6", + "lightcoral": "#F08080", + "lightcyan": "#E0FFFF", + "lightgoldenrodyellow": "#FAFAD2", + "lightgray": "#D3D3D3", + "lightgrey": "#D3D3D3", + "lightgreen": "#90EE90", + "lightpink": "#FFB6C1", + "lightsalmon": "#FFA07A", + "lightseagreen": "#20B2AA", + "lightskyblue": "#87CEFA", + "lightslategray": "#778899", + "lightslategrey": "#778899", + "lightsteelblue": "#B0C4DE", + "lightyellow": "#FFFFE0", + "lime": "#00FF00", + "limegreen": "#32CD32", + "linen": "#FAF0E6", + "magenta": "#FF00FF", + "maroon": "#800000", + "mediumaquamarine": "#66CDAA", + "mediumblue": "#0000CD", + "mediumorchid": "#BA55D3", + "mediumpurple": "#9370DB", + "mediumseagreen": "#3CB371", + "mediumslateblue": "#7B68EE", + "mediumspringgreen": "#00FA9A", + "mediumturquoise": "#48D1CC", + "mediumvioletred": "#C71585", + "midnightblue": "#191970", + "mintcream": "#F5FFFA", + "mistyrose": "#FFE4E1", + "moccasin": "#FFE4B5", + "navajowhite": "#FFDEAD", + "navy": "#000080", + "oldlace": "#FDF5E6", + "olive": "#808000", + "olivedrab": "#6B8E23", + "orange": "#FFA500", + "orangered": "#FF4500", + "orchid": "#DA70D6", + "palegoldenrod": "#EEE8AA", + "palegreen": "#98FB98", + "paleturquoise": "#AFEEEE", + "palevioletred": "#DB7093", + "papayawhip": "#FFEFD5", + "peachpuff": "#FFDAB9", + "peru": "#CD853F", + "pink": "#FFC0CB", + "plum": "#DDA0DD", + "powderblue": "#B0E0E6", + "purple": "#800080", + "red": "#FF0000", + "rosybrown": "#BC8F8F", + "royalblue": "#4169E1", + "saddlebrown": "#8B4513", + "salmon": "#FA8072", + "sandybrown": "#F4A460", + "seagreen": "#2E8B57", + "seashell": "#FFF5EE", + "sienna": "#A0522D", + "silver": "#C0C0C0", + "skyblue": "#87CEEB", + "slateblue": "#6A5ACD", + "slategray": "#708090", + "slategrey": "#708090", + "snow": "#FFFAFA", + "springgreen": "#00FF7F", + "steelblue": "#4682B4", + "tan": "#D2B48C", + "teal": "#008080", + "thistle": "#D8BFD8", + "tomato": "#FF6347", + "turquoise": "#40E0D0", + "violet": "#EE82EE", + "wheat": "#F5DEB3", + "white": "#FFFFFF", + "whitesmoke": "#F5F5F5", + "yellow": "#FFFF00", + "yellowgreen": "#9ACD32", }; diff --git a/src/util/ColorMixin.js b/src/util/ColorMixin.js index 2379bae..e52207e 100644 --- a/src/util/ColorMixin.js +++ b/src/util/ColorMixin.js @@ -7,9 +7,7 @@ * @license Distributed under the terms of the MIT License */ - -import {Color} from './Color.js'; - +import { Color } from "./Color.js"; /** *

This mixin implement color and contrast changes for visual stimuli

@@ -17,15 +15,15 @@ import {Color} from './Color.js'; * @name module:util.ColorMixin * @mixin */ -export let ColorMixin = (superclass) => class extends superclass -{ - constructor(args) +export let ColorMixin = (superclass) => + class extends superclass { - super(args); - } + constructor(args) + { + super(args); + } - - /** + /** * Setter for Color attribute. * * @name module:util.ColorMixin#setColor @@ -34,16 +32,15 @@ export let ColorMixin = (superclass) => class extends superclass * @param {Color} color - the new color * @param {boolean} [log= false] - whether or not to log */ - setColor(color, log) - { - this._setAttribute('color', color, log); + setColor(color, log) + { + this._setAttribute("color", color, log); - this._needUpdate = true; - this._needPixiUpdate = true; - } + this._needUpdate = true; + this._needPixiUpdate = true; + } - - /** + /** * Setter for Contrast attribute. * * @name module:util.ColorMixin#setContrast @@ -52,16 +49,15 @@ export let ColorMixin = (superclass) => class extends superclass * @param {number} contrast - the new contrast (must be between 0 and 1) * @param {boolean} [log= false] - whether or not to log */ - setContrast(contrast, log) - { - this._setAttribute('contrast', contrast, log); + setContrast(contrast, log) + { + this._setAttribute("contrast", contrast, log); - this._needUpdate = true; - this._needPixiUpdate = true; - } + this._needUpdate = true; + this._needPixiUpdate = true; + } - - /** + /** * Get a new contrasted Color. * * @name module:util.ColorMixin#getContrastedColor @@ -70,10 +66,9 @@ export let ColorMixin = (superclass) => class extends superclass * @param {string|number|Array.} color - the color * @param {number} contrast - the contrast (must be between 0 and 1) */ - getContrastedColor(color, contrast) - { - const rgb = color.rgb.map(c => (c * 2.0 - 1.0) * contrast); - return new Color(rgb, Color.COLOR_SPACE.RGB); - } - -}; + getContrastedColor(color, contrast) + { + const rgb = color.rgb.map((c) => (c * 2.0 - 1.0) * contrast); + return new Color(rgb, Color.COLOR_SPACE.RGB); + } + }; diff --git a/src/util/EventEmitter.js b/src/util/EventEmitter.js index 3f5814a..d0e6bff 100644 --- a/src/util/EventEmitter.js +++ b/src/util/EventEmitter.js @@ -7,9 +7,7 @@ * @license Distributed under the terms of the MIT License */ - -import * as util from './Util.js'; - +import * as util from "./Util.js"; /** *

EventEmitter implements the classic observer/observable pattern.

@@ -34,7 +32,6 @@ export class EventEmitter this._onceUuids = new Map(); } - /** * Listener called when this instance emits an event for which it is registered. * @@ -42,7 +39,6 @@ export class EventEmitter * @param {object} data - the data passed to the listener */ - /** * Register a new listener for events with the given name emitted by this instance. * @@ -56,9 +52,9 @@ export class EventEmitter on(name, listener) { // check that the listener is a function: - if (typeof listener !== 'function') + if (typeof listener !== "function") { - throw new TypeError('listener must be a function'); + throw new TypeError("listener must be a function"); } // generate a new uuid: @@ -69,12 +65,11 @@ export class EventEmitter { this._listeners.set(name, []); } - this._listeners.get(name).push({uuid, listener}); + this._listeners.get(name).push({ uuid, listener }); return uuid; } - /** * Register a new listener for the given event name, and remove it as soon as the event has been emitted. * @@ -98,7 +93,6 @@ export class EventEmitter return uuid; } - /** * Remove the listener with the given uuid associated to the given event name. * @@ -114,13 +108,12 @@ export class EventEmitter if (relevantUuidListeners && relevantUuidListeners.length) { - this._listeners.set(name, relevantUuidListeners.filter(uuidlistener => (uuidlistener.uuid != uuid))); + this._listeners.set(name, relevantUuidListeners.filter((uuidlistener) => (uuidlistener.uuid != uuid))); return true; } return false; } - /** * Emit an event with a given name and associated data. * @@ -138,11 +131,11 @@ export class EventEmitter { let onceUuids = this._onceUuids.get(name); let self = this; - relevantUuidListeners.forEach(({uuid, listener}) => + relevantUuidListeners.forEach(({ uuid, listener }) => { listener(data); - if (typeof onceUuids !== 'undefined' && onceUuids.includes(uuid)) + if (typeof onceUuids !== "undefined" && onceUuids.includes(uuid)) { self.off(name, uuid); } @@ -152,6 +145,4 @@ export class EventEmitter return false; } - - } diff --git a/src/util/PsychObject.js b/src/util/PsychObject.js index fe955bd..019c947 100644 --- a/src/util/PsychObject.js +++ b/src/util/PsychObject.js @@ -8,10 +8,8 @@ * @license Distributed under the terms of the MIT License */ - -import {EventEmitter} from './EventEmitter.js'; -import * as util from './Util.js'; - +import { EventEmitter } from "./EventEmitter.js"; +import * as util from "./Util.js"; /** *

PsychoObject is the base class for all PsychoJS objects. @@ -32,14 +30,13 @@ export class PsychObject extends EventEmitter this._userAttributes = new Set(); // name: - if (typeof name === 'undefined') + if (typeof name === "undefined") { name = this.constructor.name; } - this._addAttribute('name', name); + this._addAttribute("name", name); } - /** * Get the PsychoJS instance. * @@ -51,7 +48,6 @@ export class PsychObject extends EventEmitter return this._psychoJS; } - /** * Setter for the PsychoJS attribute. * @@ -63,7 +59,6 @@ export class PsychObject extends EventEmitter this._psychoJS = psychoJS; } - /** * String representation of the PsychObject. * @@ -74,38 +69,37 @@ export class PsychObject extends EventEmitter */ toString() { - let representation = this.constructor.name + '( '; + let representation = this.constructor.name + "( "; let addComma = false; for (const attribute of this._userAttributes) { if (addComma) { - representation += ', '; + representation += ", "; } addComma = true; - let value = util.toString(this['_' + attribute]); + let value = util.toString(this["_" + attribute]); const l = value.length; if (l > 50) { - if (value[l - 1] === ')') + if (value[l - 1] === ")") { - value = value.substring(0, 50) + '~)'; + value = value.substring(0, 50) + "~)"; } else { - value = value.substring(0, 50) + '~'; + value = value.substring(0, 50) + "~"; } } - representation += attribute + '=' + value; + representation += attribute + "=" + value; } - representation += ' )'; + representation += " )"; return representation; } - /** * Set the value of an attribute. * @@ -121,31 +115,30 @@ export class PsychObject extends EventEmitter _setAttribute(attributeName, attributeValue, log = false, operation = undefined, stealth = false) { const response = { - origin: 'PsychObject.setAttribute', - context: 'when setting the attribute of an object' + origin: "PsychObject.setAttribute", + context: "when setting the attribute of an object", }; - if (typeof attributeName == 'undefined') + if (typeof attributeName == "undefined") { throw Object.assign(response, { - error: 'the attribute name cannot be' + - ' undefined' + error: "the attribute name cannot be" + + " undefined", }); } - if (typeof attributeValue == 'undefined') + if (typeof attributeValue == "undefined") { - this._psychoJS.logger.warn('setting the value of attribute: ' + attributeName + ' in PsychObject: ' + this._name + ' as: undefined'); + this._psychoJS.logger.warn("setting the value of attribute: " + attributeName + " in PsychObject: " + this._name + " as: undefined"); } // (*) apply operation to old and new values: - if (typeof operation !== 'undefined' && this.hasOwnProperty('_' + attributeName)) + if (typeof operation !== "undefined" && this.hasOwnProperty("_" + attributeName)) { - let oldValue = this['_' + attributeName]; + let oldValue = this["_" + attributeName]; // operations can only be applied to numbers and array of numbers (which can be empty): - if (typeof attributeValue == 'number' || (Array.isArray(attributeValue) && (attributeValue.length === 0 || typeof attributeValue[0] == 'number'))) + if (typeof attributeValue == "number" || (Array.isArray(attributeValue) && (attributeValue.length === 0 || typeof attributeValue[0] == "number"))) { - // value is an array: if (Array.isArray(attributeValue)) { @@ -155,160 +148,158 @@ export class PsychObject extends EventEmitter if (attributeValue.length !== oldValue.length) { throw Object.assign(response, { - error: 'old and new' + - ' value should have' + - ' the same size when they are both arrays' + error: "old and new" + + " value should have" + + " the same size when they are both arrays", }); } switch (operation) { - case '': + case "": // no change to value; break; - case '+': + case "+": attributeValue = attributeValue.map((v, i) => oldValue[i] + v); break; - case '*': + case "*": attributeValue = attributeValue.map((v, i) => oldValue[i] * v); break; - case '-': + case "-": attributeValue = attributeValue.map((v, i) => oldValue[i] - v); break; - case '/': + case "/": attributeValue = attributeValue.map((v, i) => oldValue[i] / v); break; - case '**': + case "**": attributeValue = attributeValue.map((v, i) => oldValue[i] ** v); break; - case '%': + case "%": attributeValue = attributeValue.map((v, i) => oldValue[i] % v); break; default: throw Object.assign(response, { - error: 'unsupported' + - ' operation: ' + operation + ' when setting: ' + attributeName + ' in: ' + this.name + error: "unsupported" + + " operation: " + operation + " when setting: " + attributeName + " in: " + this.name, }); } - } - else // old value is a scalar + else { switch (operation) { - case '': + case "": // no change to value; break; - case '+': - attributeValue = attributeValue.map(v => oldValue + v); + case "+": + attributeValue = attributeValue.map((v) => oldValue + v); break; - case '*': - attributeValue = attributeValue.map(v => oldValue * v); + case "*": + attributeValue = attributeValue.map((v) => oldValue * v); break; - case '-': - attributeValue = attributeValue.map(v => oldValue - v); + case "-": + attributeValue = attributeValue.map((v) => oldValue - v); break; - case '/': - attributeValue = attributeValue.map(v => oldValue / v); + case "/": + attributeValue = attributeValue.map((v) => oldValue / v); break; - case '**': - attributeValue = attributeValue.map(v => oldValue ** v); + case "**": + attributeValue = attributeValue.map((v) => oldValue ** v); break; - case '%': - attributeValue = attributeValue.map(v => oldValue % v); + case "%": + attributeValue = attributeValue.map((v) => oldValue % v); break; default: throw Object.assign(response, { - error: 'unsupported' + - ' value: ' + JSON.stringify(attributeValue) + ' for' + - ' operation: ' + operation + ' when setting: ' + attributeName + ' in: ' + this.name + error: "unsupported" + + " value: " + JSON.stringify(attributeValue) + " for" + + " operation: " + operation + " when setting: " + attributeName + " in: " + this.name, }); } } } - else // value is a scalar + else { // old value is an array if (Array.isArray(oldValue)) { switch (operation) { - case '': - attributeValue = oldValue.map(v => attributeValue); + case "": + attributeValue = oldValue.map((v) => attributeValue); break; - case '+': - attributeValue = oldValue.map(v => v + attributeValue); + case "+": + attributeValue = oldValue.map((v) => v + attributeValue); break; - case '*': - attributeValue = oldValue.map(v => v * attributeValue); + case "*": + attributeValue = oldValue.map((v) => v * attributeValue); break; - case '-': - attributeValue = oldValue.map(v => v - attributeValue); + case "-": + attributeValue = oldValue.map((v) => v - attributeValue); break; - case '/': - attributeValue = oldValue.map(v => v / attributeValue); + case "/": + attributeValue = oldValue.map((v) => v / attributeValue); break; - case '**': - attributeValue = oldValue.map(v => v ** attributeValue); + case "**": + attributeValue = oldValue.map((v) => v ** attributeValue); break; - case '%': - attributeValue = oldValue.map(v => v % attributeValue); + case "%": + attributeValue = oldValue.map((v) => v % attributeValue); break; default: throw Object.assign(response, { - error: 'unsupported' + - ' operation: ' + operation + ' when setting: ' + attributeName + ' in: ' + this.name + error: "unsupported" + + " operation: " + operation + " when setting: " + attributeName + " in: " + this.name, }); } - } - else // old value is a scalar + else { switch (operation) { - case '': + case "": // no change to value; break; - case '+': + case "+": attributeValue = oldValue + attributeValue; break; - case '*': + case "*": attributeValue = oldValue * attributeValue; break; - case '-': + case "-": attributeValue = oldValue - attributeValue; break; - case '/': + case "/": attributeValue = oldValue / attributeValue; break; - case '**': + case "**": attributeValue = oldValue ** attributeValue; break; - case '%': + case "%": attributeValue = oldValue % attributeValue; break; default: throw Object.assign(response, { - error: 'unsupported' + - ' value: ' + JSON.stringify(attributeValue) + ' for operation: ' + operation + ' when setting: ' + attributeName + ' in: ' + this.name + error: "unsupported" + + " value: " + JSON.stringify(attributeValue) + " for operation: " + operation + " when setting: " + attributeName + " in: " + this.name, }); } } } - } else { - throw Object.assign(response, {error: 'operation: ' + operation + ' is invalid for old value: ' + JSON.stringify(oldValue) + ' and new value: ' + JSON.stringify(attributeValue)}); + throw Object.assign(response, { + error: "operation: " + operation + " is invalid for old value: " + JSON.stringify(oldValue) + " and new value: " + JSON.stringify(attributeValue), + }); } } - // (*) log if appropriate: - if (!stealth && (log || this._autoLog) && (typeof this.win !== 'undefined')) + if (!stealth && (log || this._autoLog) && (typeof this.win !== "undefined")) { const msg = this.name + ": " + attributeName + " = " + util.toString(attributeValue); this.win.logOnFlip({ @@ -317,13 +308,12 @@ export class PsychObject extends EventEmitter }); } - // (*) set the value of the attribute and return whether it has changed: - const previousAttributeValue = this['_' + attributeName]; - this['_' + attributeName] = attributeValue; + const previousAttributeValue = this["_" + attributeName]; + this["_" + attributeName] = attributeValue; // Things seem OK without this check except for 'vertices' - if (typeof previousAttributeValue === 'undefined') + if (typeof previousAttributeValue === "undefined") { // Not that any of the following lines should throw, but evaluating // `this._vertices.map` on `ShapeStim._getVertices_px()` seems to @@ -342,10 +332,9 @@ export class PsychObject extends EventEmitter // `Util.toString()` might try, but fail to stringify in a meaningful way are assigned // an 'Object (circular)' string representation. For being opaque as to their raw // value, those types of input are liable to produce PIXI updates. - return prev === 'Object (circular)' || next === 'Object (circular)' || prev !== next; + return prev === "Object (circular)" || next === "Object (circular)" || prev !== next; } - /** * Add an attribute to this instance (e.g. define setters and getters) and affect a value to it. * @@ -355,20 +344,21 @@ export class PsychObject extends EventEmitter * @param {object} [defaultValue] - the default value for the attribute * @param {function} [onChange] - function called upon changes to the attribute value */ - _addAttribute(name, value, defaultValue = undefined, onChange = () => {}) + _addAttribute(name, value, defaultValue = undefined, onChange = () => + {}) { - const getPropertyName = 'get' + name[0].toUpperCase() + name.substr(1); - if (typeof this[getPropertyName] === 'undefined') + const getPropertyName = "get" + name[0].toUpperCase() + name.substr(1); + if (typeof this[getPropertyName] === "undefined") { - this[getPropertyName] = () => this['_' + name]; + this[getPropertyName] = () => this["_" + name]; } - const setPropertyName = 'set' + name[0].toUpperCase() + name.substr(1); - if (typeof this[setPropertyName] === 'undefined') + const setPropertyName = "set" + name[0].toUpperCase() + name.substr(1); + if (typeof this[setPropertyName] === "undefined") { this[setPropertyName] = (value, log = false) => { - if (typeof value === 'undefined' || value === null) + if (typeof value === "undefined" || value === null) { value = defaultValue; } @@ -382,7 +372,7 @@ export class PsychObject extends EventEmitter else { // deal with default value: - if (typeof value === 'undefined' || value === null) + if (typeof value === "undefined" || value === null) { value = defaultValue; } @@ -397,16 +387,14 @@ export class PsychObject extends EventEmitter set(value) { this[setPropertyName](value); - } + }, }); - // note: we use this[name] instead of this['_' + name] since a this.set method may available // in the object, in which case we need to call it this[name] = value; - //this['_' + name] = value; + // this['_' + name] = value; this._userAttributes.add(name); } - } diff --git a/src/util/Scheduler.js b/src/util/Scheduler.js index 4198b06..1521970 100644 --- a/src/util/Scheduler.js +++ b/src/util/Scheduler.js @@ -7,7 +7,6 @@ * @license Distributed under the terms of the MIT License */ - /** *

A scheduler helps run the main loop by managing scheduled functions, * called tasks, after each frame is displayed.

@@ -53,7 +52,6 @@ export class Scheduler this._status = Scheduler.Status.STOPPED; } - /** * Get the status of the scheduler. * @@ -66,7 +64,6 @@ export class Scheduler return this._status; } - /** * Task to be run by the scheduler. * @@ -87,7 +84,6 @@ export class Scheduler this._argsList.push(args); } - /** * Condition evaluated when the task is run. * @@ -108,7 +104,7 @@ export class Scheduler addConditional(condition, thenScheduler, elseScheduler) { const self = this; - let task = function () + let task = function() { if (condition()) { @@ -125,7 +121,6 @@ export class Scheduler this.add(task); } - /** * Start this scheduler. * @@ -173,7 +168,6 @@ export class Scheduler requestAnimationFrame(update); } - /** * Stop this scheduler. * @@ -187,7 +181,6 @@ export class Scheduler this._stopAtNextUpdate = true; } - /** * Run the next scheduled tasks, in sequence, until a rendering of the scene is requested. * @@ -209,9 +202,8 @@ export class Scheduler } // if there is no current task, we look for the next one in the list or quit if there is none: - if (typeof this._currentTask == 'undefined') + if (typeof this._currentTask == "undefined") { - // a task is available in the taskList: if (this._taskList.length > 0) { @@ -259,15 +251,12 @@ export class Scheduler this._currentTask = undefined; this._currentArgs = undefined; } - } return state; } - } - /** * Events. * @@ -280,25 +269,24 @@ Scheduler.Event = { /** * Move onto the next task *without* rendering the scene first. */ - NEXT: Symbol.for('NEXT'), + NEXT: Symbol.for("NEXT"), /** * Render the scene and repeat the task. */ - FLIP_REPEAT: Symbol.for('FLIP_REPEAT'), + FLIP_REPEAT: Symbol.for("FLIP_REPEAT"), /** * Render the scene and move onto the next task. */ - FLIP_NEXT: Symbol.for('FLIP_NEXT'), + FLIP_NEXT: Symbol.for("FLIP_NEXT"), /** * Quit the scheduler. */ - QUIT: Symbol.for('QUIT') + QUIT: Symbol.for("QUIT"), }; - /** * Status. * @@ -311,10 +299,10 @@ Scheduler.Status = { /** * The Scheduler is running. */ - RUNNING: Symbol.for('RUNNING'), + RUNNING: Symbol.for("RUNNING"), /** * The Scheduler is stopped. */ - STOPPED: Symbol.for('STOPPED') + STOPPED: Symbol.for("STOPPED"), }; diff --git a/src/util/Util.js b/src/util/Util.js index 3a2291c..56a9bf0 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -42,7 +42,6 @@ class MixinBuilder } } - /** * Convert the resulting value of a promise into a tupple. * @@ -56,11 +55,10 @@ class MixinBuilder export function promiseToTupple(promise) { return promise - .then(data => [null, data]) - .catch(error => [error, null]); + .then((data) => [null, data]) + .catch((error) => [error, null]); } - /** * Get a Universally Unique Identifier (RFC4122 version 4) *

See details here: https://www.ietf.org/rfc/rfc4122.txt

@@ -72,14 +70,13 @@ export function promiseToTupple(promise) */ export function makeUuid() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - const r = Math.random() * 16 | 0, v = (c === 'x') ? r : (r & 0x3 | 0x8); + const r = Math.random() * 16 | 0, v = (c === "x") ? r : (r & 0x3 | 0x8); return v.toString(16); }); } - /** * Get the error stack of the calling, exception-throwing function. * @@ -92,7 +89,7 @@ export function getErrorStack() { try { - throw Error(''); + throw Error(""); } catch (error) { @@ -100,11 +97,10 @@ export function getErrorStack() let stack = error.stack.split("\n"); stack.splice(1, 1); - return JSON.stringify(stack.join('\n')); + return JSON.stringify(stack.join("\n")); } } - /** * Test if x is an 'empty' value. * @@ -116,7 +112,7 @@ export function getErrorStack() */ export function isEmpty(x) { - if (typeof x === 'undefined') + if (typeof x === "undefined") { return true; } @@ -128,7 +124,7 @@ export function isEmpty(x) { return true; } - if (x.length === 1 && typeof x[0] === 'undefined') + if (x.length === 1 && typeof x[0] === "undefined") { return true; } @@ -136,7 +132,6 @@ export function isEmpty(x) return false; } - /** * Detect the user's browser. * @@ -152,71 +147,70 @@ export function isEmpty(x) export function detectBrowser() { // Opera 8.0+ - const isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + const isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(" OPR/") >= 0; if (isOpera) { - return 'Opera'; + return "Opera"; } // Firefox 1.0+ - const isFirefox = (typeof InstallTrigger !== 'undefined'); + const isFirefox = (typeof InstallTrigger !== "undefined"); if (isFirefox) { - return 'Firefox'; + return "Firefox"; } - // Safari 3.0+ "[object HTMLElementConstructor]" - const isSafari = /constructor/i.test(window.HTMLElement) || (function (p) + // Safari 3.0+ "[object HTMLElementConstructor]" + const isSafari = /constructor/i.test(window.HTMLElement) || (function(p) { return p.toString() === "[object SafariRemoteNotification]"; - })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification)); + })(!window["safari"] || (typeof safari !== "undefined" && safari.pushNotification)); if (isSafari) { - return 'Safari'; + return "Safari"; } // Internet Explorer 6-11 // const isIE6 = !window.XMLHttpRequest; // const isIE7 = document.all && window.XMLHttpRequest && !XDomainRequest && !window.opera; // const isIE8 = document.documentMode==8; - const isIE = /*@cc_on!@*/false || !!document.documentMode; + const isIE = /*@cc_on!@*/ false || !!document.documentMode; if (isIE) { - return 'IE'; + return "IE"; } // Edge 20+ const isEdge = !isIE && !!window.StyleMedia; if (isEdge) { - return 'Edge'; + return "Edge"; } // Chrome 1+ const isChrome = window.chrome; if (isChrome) { - return 'Chrome'; + return "Chrome"; } // Chromium-based Edge: const isEdgeChromium = isChrome && (navigator.userAgent.indexOf("Edg") !== -1); if (isEdgeChromium) { - return 'EdgeChromium'; + return "EdgeChromium"; } // Blink engine detection const isBlink = (isChrome || isOpera) && !!window.CSS; if (isBlink) { - return 'Blink'; + return "Blink"; } - return 'unknown'; + return "unknown"; } - /** * Convert obj to its numerical form. * @@ -236,24 +230,23 @@ export function detectBrowser() export function toNumerical(obj) { const response = { - origin: 'util.toNumerical', - context: 'when converting an object to its numerical form' + origin: "util.toNumerical", + context: "when converting an object to its numerical form", }; try { - if (obj === null) { - throw 'unable to convert null to a number'; + throw "unable to convert null to a number"; } - if (typeof obj === 'undefined') + if (typeof obj === "undefined") { - throw 'unable to convert undefined to a number'; + throw "unable to convert undefined to a number"; } - if (typeof obj === 'number') + if (typeof obj === "number") { return obj; } @@ -282,18 +275,17 @@ export function toNumerical(obj) return arrayMaybe.map(convertToNumber); } - if (typeof obj === 'string') + if (typeof obj === "string") { return convertToNumber(obj); } - throw 'unable to convert the object to a number'; + throw "unable to convert the object to a number"; } catch (error) { throw Object.assign(response, { error }); } - } /** @@ -305,11 +297,11 @@ export function toNumerical(obj) * @param {*} input - Some value * @return {boolean} Whether or not the value can be converted into a number */ -export function isNumeric(input) { +export function isNumeric(input) +{ return Number.isNaN(Number(input)) === false; } - /** * Check whether a point lies within a polygon *

We are using the algorithm described here: https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html

@@ -341,7 +333,6 @@ export function IsPointInsidePolygon(point, vertices) return isInside; } - /** * Shuffle an array in place using the Fisher-Yastes's modern algorithm *

See details here: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm

@@ -355,7 +346,8 @@ export function IsPointInsidePolygon(point, vertices) */ export function shuffle(array, randomNumberGenerator = undefined) { - if (randomNumberGenerator === undefined) { + if (randomNumberGenerator === undefined) + { randomNumberGenerator = Math.random; } for (let i = array.length - 1; i > 0; i--) @@ -366,8 +358,6 @@ export function shuffle(array, randomNumberGenerator = undefined) return array; } - - /** * Get the position of the object, in pixel units * @@ -381,21 +371,21 @@ export function shuffle(array, randomNumberGenerator = undefined) export function getPositionFromObject(object, units) { const response = { - origin: 'util.getPositionFromObject', - context: 'when getting the position of an object' + origin: "util.getPositionFromObject", + context: "when getting the position of an object", }; try { - if (typeof object === 'undefined') + if (typeof object === "undefined") { - throw 'cannot get the position of an undefined object'; + throw "cannot get the position of an undefined object"; } let objectWin = undefined; // the object has a getPos function: - if (typeof object.getPos === 'function') + if (typeof object.getPos === "function") { units = object.units; objectWin = object.win; @@ -407,12 +397,10 @@ export function getPositionFromObject(object, units) } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Convert the position to pixel units. * @@ -428,28 +416,28 @@ export function getPositionFromObject(object, units) export function to_px(pos, posUnit, win, integerCoordinates = false) { const response = { - origin: 'util.to_px', - context: 'when converting a position to pixel units' + origin: "util.to_px", + context: "when converting a position to pixel units", }; let pos_px; - if (posUnit === 'pix') + if (posUnit === "pix") { pos_px = pos; } - else if (posUnit === 'norm') + else if (posUnit === "norm") { pos_px = [pos[0] * win.size[0] / 2.0, pos[1] * win.size[1] / 2.0]; } - else if (posUnit === 'height') + else if (posUnit === "height") { const minSize = Math.min(win.size[0], win.size[1]); pos_px = [pos[0] * minSize, pos[1] * minSize]; } else { - throw Object.assign(response, {error: `unknown position units: ${posUnit}`}); + throw Object.assign(response, { error: `unknown position units: ${posUnit}` }); } if (integerCoordinates) @@ -462,7 +450,6 @@ export function to_px(pos, posUnit, win, integerCoordinates = false) } } - /** * Convert the position to norm units. * @@ -476,26 +463,25 @@ export function to_px(pos, posUnit, win, integerCoordinates = false) */ export function to_norm(pos, posUnit, win) { - const response = {origin: 'util.to_norm', context: 'when converting a position to norm units'}; + const response = { origin: "util.to_norm", context: "when converting a position to norm units" }; - if (posUnit === 'norm') + if (posUnit === "norm") { return pos; } - if (posUnit === 'pix') + if (posUnit === "pix") { return [pos[0] / (win.size[0] / 2.0), pos[1] / (win.size[1] / 2.0)]; } - if (posUnit === 'height') + if (posUnit === "height") { const minSize = Math.min(win.size[0], win.size[1]); return [pos[0] * minSize / (win.size[0] / 2.0), pos[1] * minSize / (win.size[1] / 2.0)]; } - throw Object.assign(response, {error: `unknown position units: ${posUnit}`}); + throw Object.assign(response, { error: `unknown position units: ${posUnit}` }); } - /** * Convert the position to height units. * @@ -510,29 +496,28 @@ export function to_norm(pos, posUnit, win) export function to_height(pos, posUnit, win) { const response = { - origin: 'util.to_height', - context: 'when converting a position to height units' + origin: "util.to_height", + context: "when converting a position to height units", }; - if (posUnit === 'height') + if (posUnit === "height") { return pos; } - if (posUnit === 'pix') + if (posUnit === "pix") { const minSize = Math.min(win.size[0], win.size[1]); return [pos[0] / minSize, pos[1] / minSize]; } - if (posUnit === 'norm') + if (posUnit === "norm") { const minSize = Math.min(win.size[0], win.size[1]); return [pos[0] * win.size[0] / 2.0 / minSize, pos[1] * win.size[1] / 2.0 / minSize]; } - throw Object.assign(response, {error: `unknown position units: ${posUnit}`}); + throw Object.assign(response, { error: `unknown position units: ${posUnit}` }); } - /** * Convert the position to window units. * @@ -546,19 +531,19 @@ export function to_height(pos, posUnit, win) */ export function to_win(pos, posUnit, win) { - const response = {origin: 'util.to_win', context: 'when converting a position to window units'}; + const response = { origin: "util.to_win", context: "when converting a position to window units" }; try { - if (win._units === 'pix') + if (win._units === "pix") { return to_px(pos, posUnit, win); } - if (win._units === 'norm') + if (win._units === "norm") { return to_norm(pos, posUnit, win); } - if (win._units === 'height') + if (win._units === "height") { return to_height(pos, posUnit, win); } @@ -567,11 +552,10 @@ export function to_win(pos, posUnit, win) } catch (error) { - throw Object.assign(response, {response, error}); + throw Object.assign(response, { response, error }); } } - /** * Convert the position to given units. * @@ -586,19 +570,19 @@ export function to_win(pos, posUnit, win) */ export function to_unit(pos, posUnit, win, targetUnit) { - const response = {origin: 'util.to_unit', context: 'when converting a position to different units'}; + const response = { origin: "util.to_unit", context: "when converting a position to different units" }; try { - if (targetUnit === 'pix') + if (targetUnit === "pix") { return to_px(pos, posUnit, win); } - if (targetUnit === 'norm') + if (targetUnit === "norm") { return to_norm(pos, posUnit, win); } - if (targetUnit === 'height') + if (targetUnit === "height") { return to_height(pos, posUnit, win); } @@ -607,11 +591,10 @@ export function to_unit(pos, posUnit, win, targetUnit) } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Convert an object to its string representation, taking care of symbols. * @@ -625,23 +608,23 @@ export function to_unit(pos, posUnit, win, targetUnit) */ export function toString(object) { - if (typeof object === 'undefined') + if (typeof object === "undefined") { - return 'undefined'; + return "undefined"; } if (!object) { - return 'null'; + return "null"; } - if (typeof object === 'string') + if (typeof object === "string") { return object; } // if the object is a class and has a toString method: - if (object.constructor.toString().substring(0, 5) === 'class' && typeof object.toString === 'function') + if (object.constructor.toString().substring(0, 5) === "class" && typeof object.toString === "function") { return object.toString(); } @@ -650,7 +633,7 @@ export function toString(object) { const symbolReplacer = (key, value) => { - if (typeof value === 'symbol') + if (typeof value === "symbol") { value = Symbol.keyFor(value); } @@ -660,30 +643,28 @@ export function toString(object) } catch (e) { - return 'Object (circular)'; + return "Object (circular)"; } } - if (!String.prototype.format) { - String.prototype.format = function () + String.prototype.format = function() { var args = arguments; return this - .replace(/{(\d+)}/g, function (match, number) + .replace(/{(\d+)}/g, function(match, number) { - return typeof args[number] != 'undefined' ? args[number] : match; + return typeof args[number] != "undefined" ? args[number] : match; }) - .replace(/{([$_a-zA-Z][$_a-zA-Z0-9]*)}/g, function (match, name) + .replace(/{([$_a-zA-Z][$_a-zA-Z0-9]*)}/g, function(match, name) { - //console.log("n=" + name + " args[0][name]=" + args[0][name]); + // console.log("n=" + name + " args[0][name]=" + args[0][name]); return args.length > 0 && args[0][name] !== undefined ? args[0][name] : match; }); }; } - /** * Get the most informative error from the server response from a jquery server request. * @@ -696,17 +677,17 @@ if (!String.prototype.format) */ export function getRequestError(jqXHR, textStatus, errorThrown) { - let errorMsg = 'unknown error'; + let errorMsg = "unknown error"; - if (typeof jqXHR.responseJSON !== 'undefined') + if (typeof jqXHR.responseJSON !== "undefined") { errorMsg = jqXHR.responseJSON; } - else if (typeof jqXHR.responseText !== 'undefined') + else if (typeof jqXHR.responseText !== "undefined") { errorMsg = jqXHR.responseText; } - else if (typeof errorThrown !== 'undefined') + else if (typeof errorThrown !== "undefined") { errorMsg = errorThrown; } @@ -714,7 +695,6 @@ export function getRequestError(jqXHR, textStatus, errorThrown) return errorMsg; } - /** * Test whether an object is either an integer or the string representation of an integer. *

This is adapted from: https://stackoverflow.com/a/14794066

@@ -736,7 +716,6 @@ export function isInt(obj) return (x | 0) === x; } - /** * Get the URL parameters. * @@ -763,7 +742,6 @@ export function getUrlParameters() return urlMap;*/ } - /** * Add info extracted from the URL to the given dictionary. * @@ -784,7 +762,7 @@ export function addInfoFromUrl(info) // for (const [key, value] of infoFromUrl) infoFromUrl.forEach((value, key) => { - if (key.indexOf('__') !== 0) + if (key.indexOf("__") !== 0) { info[key] = value; } @@ -793,7 +771,6 @@ export function addInfoFromUrl(info) return info; } - /** * Select values from an array. * @@ -816,28 +793,30 @@ export function addInfoFromUrl(info) */ export function selectFromArray(array, selection) { - // if selection is an integer, or a string representing an integer, we treat it as an index in the array // and return that entry: if (isInt(selection)) { return [array[parseInt(selection)]]; - }// if selection is an array, we treat it as a list of indices + } + // if selection is an array, we treat it as a list of indices // and return an array with the entries corresponding to those indices: else if (Array.isArray(selection)) { // Pick out `array` items matching indices contained in `selection` in order - return selection.map(i => array[i]); - }// if selection is a string, we decode it: - else if (typeof selection === 'string') + return selection.map((i) => array[i]); + } + // if selection is a string, we decode it: + else if (typeof selection === "string") { - if (selection.indexOf(',') > -1) + if (selection.indexOf(",") > -1) { - return selection.split(',').map(a => selectFromArray(array, a)); - }// return flattenArray( selection.split(',').map(a => selectFromArray(array, a)) ); - else if (selection.indexOf(':') > -1) + return selection.split(",").map((a) => selectFromArray(array, a)); + } + // return flattenArray( selection.split(',').map(a => selectFromArray(array, a)) ); + else if (selection.indexOf(":") > -1) { - let sliceParams = selection.split(':').map(a => parseInt(a)); + let sliceParams = selection.split(":").map((a) => parseInt(a)); if (sliceParams.length === 3) { return sliceArray(array, sliceParams[0], sliceParams[2], sliceParams[1]); @@ -848,18 +827,16 @@ export function selectFromArray(array, selection) } } } - else { throw { - origin: 'selectFromArray', - context: 'when selecting entries from an array', - error: 'unknown selection type: ' + (typeof selection) + origin: "selectFromArray", + context: "when selecting entries from an array", + error: "unknown selection type: " + (typeof selection), }; } } - /** * Recursively flatten an array of arrays. * @@ -877,11 +854,10 @@ export function flattenArray(array) flat.push((Array.isArray(next) && Array.isArray(next[0])) ? flattenArray(next) : next); return flat; }, - [] + [], ); } - /** * Slice an array. * @@ -928,7 +904,6 @@ export function sliceArray(array, from = NaN, to = NaN, step = NaN) } } - /** * Offer data as download in the browser. * @@ -941,14 +916,14 @@ export function sliceArray(array, from = NaN, to = NaN, step = NaN) */ export function offerDataForDownload(filename, data, type) { - const blob = new Blob([data], {type}); + const blob = new Blob([data], { type }); if (window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveBlob(blob, filename); } else { - const anchor = document.createElement('a'); + const anchor = document.createElement("a"); anchor.href = window.URL.createObjectURL(blob); anchor.download = filename; document.body.appendChild(anchor); @@ -957,7 +932,6 @@ export function offerDataForDownload(filename, data, type) } } - /** * Convert a string representing a JSON array, e.g. "[1, 2]" into an array, e.g. ["1","2"]. * This approach overcomes the built-in JSON parsing limitations when it comes to eg. floats @@ -991,14 +965,13 @@ export function turnSquareBracketsIntoArrays(input, max = 1) // Reformat content for each match const matches = matchesMaybe.map((data) => - { - return data - // Remove the square brackets - .replace(/[\[\]]+/g, '') - // Eat up space after comma - .split(/[, ]+/); - } - ); + { + return data + // Remove the square brackets + .replace(/[\[\]]+/g, "") + // Eat up space after comma + .split(/[, ]+/); + }); if (max < 2) { @@ -1008,7 +981,6 @@ export function turnSquareBracketsIntoArrays(input, max = 1) return matches; } - /** * Generates random integers a-la NumPy's in the "half-open" interval [min, max). In other words, from min inclusive to max exclusive. When max is undefined, as is the case by default, results are chosen from [0, min). An error is thrown if max is less than min. * @@ -1024,7 +996,7 @@ export function randint(min = 0, max) let lo = min; let hi = max; - if (typeof max === 'undefined') + if (typeof max === "undefined") { hi = lo; lo = 0; @@ -1033,16 +1005,15 @@ export function randint(min = 0, max) if (hi < lo) { throw { - origin: 'util.randint', - context: 'when generating a random integer', - error: 'min should be <= max' + origin: "util.randint", + context: "when generating a random integer", + error: "min should be <= max", }; } return Math.floor(Math.random() * (hi - lo)) + lo; } - /** * Round to a certain number of decimal places. * @@ -1061,7 +1032,6 @@ export function round(input, places = 0) return +(Math.round(`${input}e+${places}`) + `e-${places}`); } - /** * Calculate the sum of the elements in the input array. * @@ -1085,14 +1055,13 @@ export function sum(input = [], start = 0) return input // type cast everything as a number - .map(value => Number(value)) + .map((value) => Number(value)) // drop non numeric looking entries (note: needs transpiling for IE11) - .filter(value => Number.isNaN(value) === false) + .filter((value) => Number.isNaN(value) === false) // add up each successive entry, starting with start .reduce(add, start); } - /** * Calculate the average of the elements in the input array. * @@ -1119,10 +1088,9 @@ export function average(input = []) return sum(input, 0) / input.length; } - /** * Sort the elements of the input array, in increasing alphabetical or numerical order. - * + * * @name module:util.sort * @function * @public @@ -1134,44 +1102,43 @@ export function average(input = []) export function sort(input) { const response = { - origin: 'util.sort', - context: 'when sorting the elements of an array' + origin: "util.sort", + context: "when sorting the elements of an array", }; try { if (!Array.isArray(input)) { - throw 'the input argument should be an array'; + throw "the input argument should be an array"; } // check the type and consistency of the array, and sort it accordingly: - const isNumberArray = input.every(element => typeof element === "number"); + const isNumberArray = input.every((element) => typeof element === "number"); if (isNumberArray) { return input.sort((a, b) => (a - b)); } - const isStringArray = input.every(element => typeof element === "string"); + const isStringArray = input.every((element) => typeof element === "string"); if (isStringArray) { return input.sort(); } - - throw 'the input array should either consist entirely of strings or of numbers'; + + throw "the input array should either consist entirely of strings or of numbers"; } catch (error) { - throw {...response, error}; - } - } - - + throw { ...response, error }; + } +} + /** * Create a sequence of integers. - * + * * The sequence is such that the integer at index i is: start + step * i, with i >= 0 and start + step * i < stop - * + * *

Note: this is a JavaScript implement of the Python range function, which explains the unusual management of arguments.

* * @name module:util.range @@ -1185,8 +1152,8 @@ export function sort(input) export function range(...args) { const response = { - origin: 'util.range', - context: 'when building a range of numbers' + origin: "util.range", + context: "when building a range of numbers", }; try @@ -1196,9 +1163,10 @@ export function range(...args) switch (args.length) { case 0: - throw 'at least one argument is required'; + throw "at least one argument is required"; // 1 arg: start = 0, stop = arg, step = 1 + case 1: start = 0; stop = args[0]; @@ -1206,6 +1174,7 @@ export function range(...args) break; // 2 args: start = arg1, stop = arg2 + case 2: start = args[0]; stop = args[1]; @@ -1213,6 +1182,7 @@ export function range(...args) break; // 3 args: + case 3: start = args[0]; stop = args[1]; @@ -1220,17 +1190,20 @@ export function range(...args) break; default: - throw 'range requires at least one and at most 3 arguments' + throw "range requires at least one and at most 3 arguments"; } - if (!Number.isInteger(start)) { - throw 'start should be an integer'; + if (!Number.isInteger(start)) + { + throw "start should be an integer"; } - if (!Number.isInteger(stop)) { - throw 'stop should be an integer'; + if (!Number.isInteger(stop)) + { + throw "stop should be an integer"; } - if (!Number.isInteger(step)) { - throw 'step should be an integer'; + if (!Number.isInteger(step)) + { + throw "step should be an integer"; } // if start >= stop, the range is empty: @@ -1248,14 +1221,13 @@ export function range(...args) } catch (error) { - throw {...response, error}; + throw { ...response, error }; } } - /** * Create a boolean function that compares an input element to the given value. - * + * * @name module:util._match * @function * @private @@ -1265,16 +1237,16 @@ export function range(...args) function _match(value) { const response = { - origin: 'util._match', - context: 'when creating a function that compares an input element to the given value' + origin: "util._match", + context: "when creating a function that compares an input element to the given value", }; try { // function: - if (typeof value === 'function') + if (typeof value === "function") { - throw 'the value cannot be a function'; + throw "the value cannot be a function"; } // NaN: @@ -1290,19 +1262,19 @@ function _match(value) } // object: we compare using JSON.stringify - if (typeof value === 'object') + if (typeof value === "object") { const jsonValue = JSON.stringify(value); - if (typeof jsonValue === 'undefined') + if (typeof jsonValue === "undefined") { - throw 'value could not be converted to a JSON string'; + throw "value could not be converted to a JSON string"; } return (element) => { const jsonElement = JSON.stringify(element); return (jsonElement === jsonValue); - } + }; } // everything else: @@ -1310,16 +1282,15 @@ function _match(value) } catch (error) { - throw {...response, error}; - } - } - + throw { ...response, error }; + } +} - /** +/** * Count the number of elements in the input array that match the given value. - * + * *

Note: count is able to handle NaN, null, as well as any value convertible to a JSON string.

- * + * * @name module:util.count * @function * @public @@ -1327,44 +1298,43 @@ function _match(value) * @param {Number|string|object|null} value the matching value * @returns the number of matching elements */ - export function count(input, value) - { +export function count(input, value) +{ const response = { - origin: 'util.count', - context: 'when counting how many elements in the input array match the given value' + origin: "util.count", + context: "when counting how many elements in the input array match the given value", }; try { if (!Array.isArray(input)) { - throw 'the input argument should be an array'; + throw "the input argument should be an array"; } const match = _match(value); let nbMatches = 0; - input.forEach(element => + input.forEach((element) => + { + if (match(element)) { - if (match(element)) - { - ++ nbMatches; - } - }); + ++nbMatches; + } + }); return nbMatches; } catch (error) { - throw {...response, error}; + throw { ...response, error }; } - } - +} - /** +/** * Get the index in the input array of the first element that matches the given value. - * + * *

Note: index is able to handle NaN, null, as well as any value convertible to a JSON string.

- * + * * @name module:util.index * @function * @public @@ -1373,18 +1343,18 @@ function _match(value) * @returns the index of the first element that matches the value * @throws if the input array does not contain any matching element */ - export function index(input, value) - { +export function index(input, value) +{ const response = { - origin: 'util.index', - context: 'when getting the index in the input array of the first element that matches the given value' + origin: "util.index", + context: "when getting the index in the input array of the first element that matches the given value", }; try { if (!Array.isArray(input)) { - throw 'the input argument should be an array'; + throw "the input argument should be an array"; } const match = _match(value); @@ -1392,18 +1362,16 @@ function _match(value) if (index === -1) { - throw 'no element in the input array matches the value'; + throw "no element in the input array matches the value"; } return index; - } catch (error) { - throw {...response, error}; + throw { ...response, error }; } - } - +} /** * Return the file extension corresponding to an audio mime type. @@ -1418,25 +1386,25 @@ function _match(value) */ export function extensionFromMimeType(mimeType) { - if (typeof mimeType !== 'string') + if (typeof mimeType !== "string") { - return '.dat'; + return ".dat"; } - if (mimeType.indexOf('audio/webm') === 0) + if (mimeType.indexOf("audio/webm") === 0) { - return '.webm'; + return ".webm"; } - if (mimeType.indexOf('audio/ogg') === 0) + if (mimeType.indexOf("audio/ogg") === 0) { - return '.ogg'; + return ".ogg"; } - if (mimeType.indexOf('audio/wav') === 0) + if (mimeType.indexOf("audio/wav") === 0) { - return '.wav'; + return ".wav"; } - return '.dat'; + return ".dat"; } diff --git a/src/util/index.js b/src/util/index.js index 9f7c607..f100215 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -1,8 +1,8 @@ -export * from './Clock.js'; -export * from './Color.js'; -export * from './ColorMixin.js'; -export * from './EventEmitter.js'; +export * from "./Clock.js"; +export * from "./Color.js"; +export * from "./ColorMixin.js"; +export * from "./EventEmitter.js"; export * from "./Pixi.js"; -export * from './PsychObject.js'; -export * from './Scheduler.js'; -export * from './Util.js'; +export * from "./PsychObject.js"; +export * from "./Scheduler.js"; +export * from "./Util.js"; From 5468898716358a2e45e9795eacedbe78b705485f Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 14:07:55 +0100 Subject: [PATCH 26/29] sound: enforce formatting rules --- src/sound/AudioClip.js | 204 ++++++++++++++++------------------- src/sound/AudioClipPlayer.js | 51 ++++----- src/sound/Microphone.js | 145 +++++++++++-------------- src/sound/Sound.js | 107 +++++++++--------- src/sound/SoundPlayer.js | 53 ++++----- src/sound/TonePlayer.js | 102 ++++++++---------- src/sound/TrackPlayer.js | 56 +++++----- src/sound/index.js | 16 +-- 8 files changed, 325 insertions(+), 409 deletions(-) diff --git a/src/sound/AudioClip.js b/src/sound/AudioClip.js index 72c07db..61f05af 100644 --- a/src/sound/AudioClip.js +++ b/src/sound/AudioClip.js @@ -7,11 +7,10 @@ * @license Distributed under the terms of the MIT License */ -import {PsychObject} from '../util/PsychObject.js'; -import {PsychoJS} from '../core/PsychoJS.js'; -import {ExperimentHandler} from '../data/ExperimentHandler.js'; -import * as util from '../util/Util.js'; - +import { PsychoJS } from "../core/PsychoJS.js"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; /** *

AudioClip encapsulates an audio recording.

@@ -28,20 +27,19 @@ import * as util from '../util/Util.js'; */ export class AudioClip extends PsychObject { - - constructor({psychoJS, name, sampleRateHz, format, data, autoLog} = {}) + constructor({ psychoJS, name, sampleRateHz, format, data, autoLog } = {}) { super(psychoJS); - this._addAttribute('name', name, 'audioclip'); - this._addAttribute('format', format); - this._addAttribute('sampleRateHz', sampleRateHz); - this._addAttribute('data', data); - this._addAttribute('autoLog', false, autoLog); - this._addAttribute('status', AudioClip.Status.CREATED); + this._addAttribute("name", name, "audioclip"); + this._addAttribute("format", format); + this._addAttribute("sampleRateHz", sampleRateHz); + this._addAttribute("data", data); + this._addAttribute("autoLog", false, autoLog); + this._addAttribute("status", AudioClip.Status.CREATED); // add a volume attribute, for playback: - this._addAttribute('volume', 1.0); + this._addAttribute("volume", 1.0); if (this._autoLog) { @@ -52,7 +50,6 @@ export class AudioClip extends PsychObject this._decodeAudio(); } - /** * Set the volume of the playback. * @@ -66,7 +63,6 @@ export class AudioClip extends PsychObject this._volume = volume; } - /** * Start playing the audio clip. * @@ -76,7 +72,7 @@ export class AudioClip extends PsychObject */ async startPlayback() { - this._psychoJS.logger.debug('request to play the audio clip'); + this._psychoJS.logger.debug("request to play the audio clip"); // wait for the decoding to complete: await this._decodeAudio(); @@ -103,7 +99,6 @@ export class AudioClip extends PsychObject this._source.start(); } - /** * Stop playing the audio clip. * @@ -120,7 +115,6 @@ export class AudioClip extends PsychObject this._source.stop(); } - /** * Get the duration of the audio clip, in seconds. * @@ -137,7 +131,6 @@ export class AudioClip extends PsychObject return this._audioBuffer.duration; } - /** * Upload the audio clip to the pavlovia server. * @@ -147,17 +140,18 @@ export class AudioClip extends PsychObject */ upload() { - this._psychoJS.logger.debug('request to upload the audio clip to pavlovia.org'); + this._psychoJS.logger.debug("request to upload the audio clip to pavlovia.org"); // add a format-dependent audio extension to the name: const filename = this._name + util.extensionFromMimeType(this._format); - // if the audio recording cannot be uploaded, e.g. the experiment is running locally, or // if it is piloting mode, then we offer the audio clip as a file for download: - if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER || - this._psychoJS.config.experiment.status !== 'RUNNING' || - this._psychoJS._serverMsg.has('__pilotToken')) + if ( + this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER + || this._psychoJS.config.experiment.status !== "RUNNING" + || this._psychoJS._serverMsg.has("__pilotToken") + ) { return this.download(filename); } @@ -166,8 +160,6 @@ export class AudioClip extends PsychObject return this._psychoJS.serverManager.uploadAudio(this._data, filename); } - - /** * Offer the audio clip to the participant as a sound file to download. * @@ -175,9 +167,9 @@ export class AudioClip extends PsychObject * @function * @public */ - download(filename = 'audio.webm') + download(filename = "audio.webm") { - const anchor = document.createElement('a'); + const anchor = document.createElement("a"); anchor.href = window.URL.createObjectURL(this._data); anchor.download = filename; document.body.appendChild(anchor); @@ -185,7 +177,6 @@ export class AudioClip extends PsychObject document.body.removeChild(anchor); } - /** * Transcribe the audio clip. * @@ -196,10 +187,10 @@ export class AudioClip extends PsychObject * @return {Promise<>} a promise resolving to the transcript and associated * transcription confidence */ - async transcribe({engine, languageCode} = {}) + async transcribe({ engine, languageCode } = {}) { const response = { - origin: 'AudioClip.transcribe', + origin: "AudioClip.transcribe", context: `when transcribing audio clip: ${this._name}`, }; @@ -215,11 +206,11 @@ export class AudioClip extends PsychObject transcriptionKey = key.value; } } - if (typeof transcriptionKey === 'undefined') + if (typeof transcriptionKey === "undefined") { throw { ...response, - error: `missing key for engine: ${fullEngineName}` + error: `missing key for engine: ${fullEngineName}`, }; } @@ -235,13 +226,11 @@ export class AudioClip extends PsychObject { throw { ...response, - error: `unsupported speech-to-text engine: ${engine}` + error: `unsupported speech-to-text engine: ${engine}`, }; } - } - /** * Transcribe the audio clip using the Google Cloud Speech-To-Text Engine. * @@ -272,31 +261,31 @@ export class AudioClip extends PsychObject // query the Google speech-to-text service: const body = { config: { - encoding: 'LINEAR16', + encoding: "LINEAR16", sampleRateHertz: this._sampleRateHz, - languageCode + languageCode, }, audio: { - content: base64Data + content: base64Data, }, }; const url = `https://speech.googleapis.com/v1/speech:recognize?key=${transcriptionKey}`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, - body: JSON.stringify(body) + body: JSON.stringify(body), }); // convert the response to json: const decodedResponse = await response.json(); - this._psychoJS.logger.debug('speech.googleapis.com response:', JSON.stringify(decodedResponse)); + this._psychoJS.logger.debug("speech.googleapis.com response:", JSON.stringify(decodedResponse)); // TODO deal with more than one results and/or alternatives - if (('results' in decodedResponse) && (decodedResponse.results.length > 0)) + if (("results" in decodedResponse) && (decodedResponse.results.length > 0)) { resolve(decodedResponse.results[0].alternatives[0]); } @@ -304,21 +293,20 @@ export class AudioClip extends PsychObject { // no transcription available: resolve({ - transcript: '', - confidence: -1 + transcript: "", + confidence: -1, }); } }); } - /** * Decode the formatted audio data (e.g. webm) into a 32bit float PCM audio buffer. * */ _decodeAudio() { - this._psychoJS.logger.debug('request to decode the data of the audio clip'); + this._psychoJS.logger.debug("request to decode the data of the audio clip"); // if the audio clip is ready, the PCM audio data is available in _audioData, a Float32Array: if (this._status === AudioClip.Status.READY) @@ -326,12 +314,11 @@ export class AudioClip extends PsychObject return; } - // if we are already decoding, wait until the process completed: if (this._status === AudioClip.Status.DECODING) { const self = this; - return new Promise(function (resolve, reject) + return new Promise(function(resolve, reject) { self._decodingCallbacks.push(resolve); @@ -339,7 +326,6 @@ export class AudioClip extends PsychObject }.bind(this)); } - // otherwise, start decoding the input formatted audio data: this._status = AudioClip.Status.DECODING; this._audioData = null; @@ -348,7 +334,7 @@ export class AudioClip extends PsychObject this._decodingCallbacks = []; this._audioContext = new (window.AudioContext || window.webkitAudioContext)({ - sampleRate: this._sampleRateHz + sampleRate: this._sampleRateHz, }); const reader = new window.FileReader(); @@ -383,12 +369,11 @@ export class AudioClip extends PsychObject reader.onerror = (error) => { // TODO - } + }; reader.readAsArrayBuffer(this._data); } - /** * Convert an array buffer to a base64 string. * @@ -403,63 +388,65 @@ export class AudioClip extends PsychObject */ _base64ArrayBuffer(arrayBuffer) { - let base64 = ''; - const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + let base64 = ""; + const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - const bytes = new Uint8Array(arrayBuffer); - const byteLength = bytes.byteLength; - const byteRemainder = byteLength % 3; - const mainLength = byteLength - byteRemainder; + const bytes = new Uint8Array(arrayBuffer); + const byteLength = bytes.byteLength; + const byteRemainder = byteLength % 3; + const mainLength = byteLength - byteRemainder; - let a; - let b; - let c; - let d; - let chunk; + let a; + let b; + let c; + let d; + let chunk; - // Main loop deals with bytes in chunks of 3 - for (let i = 0; i < mainLength; i += 3) { - // Combine the three bytes into a single integer - chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; + // Main loop deals with bytes in chunks of 3 + for (let i = 0; i < mainLength; i += 3) + { + // Combine the three bytes into a single integer + chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; - // Use bitmasks to extract 6-bit segments from the triplet - a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18 - b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 - c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 - d = chunk & 63; // 63 = 2^6 - 1 + // Use bitmasks to extract 6-bit segments from the triplet + a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18 + b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 + c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 + d = chunk & 63; // 63 = 2^6 - 1 - // Convert the raw binary segments to the appropriate ASCII encoding - base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; + // Convert the raw binary segments to the appropriate ASCII encoding + base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; + } + + // Deal with the remaining bytes and padding + if (byteRemainder === 1) + { + chunk = bytes[mainLength]; + + a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 + + // Set the 4 least significant bits to zero + b = (chunk & 3) << 4; // 3 = 2^2 - 1 + + base64 += `${encodings[a]}${encodings[b]}==`; + } + else if (byteRemainder === 2) + { + chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]; + + a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 + b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 + + // Set the 2 least significant bits to zero + c = (chunk & 15) << 2; // 15 = 2^4 - 1 + + base64 += `${encodings[a]}${encodings[b]}${encodings[c]}=`; + } + + return base64; } - - // Deal with the remaining bytes and padding - if (byteRemainder === 1) { - chunk = bytes[mainLength]; - - a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 - - // Set the 4 least significant bits to zero - b = (chunk & 3) << 4; // 3 = 2^2 - 1 - - base64 += `${encodings[a]}${encodings[b]}==`; - } else if (byteRemainder === 2) { - chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]; - - a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 - b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 - - // Set the 2 least significant bits to zero - c = (chunk & 15) << 2; // 15 = 2^4 - 1 - - base64 += `${encodings[a]}${encodings[b]}${encodings[c]}=`; - } - - return base64; } -} - - /** * Recognition engines. * @@ -472,10 +459,9 @@ AudioClip.Engine = { /** * Google Cloud Speech-to-Text. */ - GOOGLE: Symbol.for('GOOGLE') + GOOGLE: Symbol.for("GOOGLE"), }; - /** * AudioClip status. * @@ -484,9 +470,9 @@ AudioClip.Engine = { * @public */ AudioClip.Status = { - CREATED: Symbol.for('CREATED'), + CREATED: Symbol.for("CREATED"), - DECODING: Symbol.for('DECODING'), + DECODING: Symbol.for("DECODING"), - READY: Symbol.for('READY') + READY: Symbol.for("READY"), }; diff --git a/src/sound/AudioClipPlayer.js b/src/sound/AudioClipPlayer.js index 6e819be..082a71a 100644 --- a/src/sound/AudioClipPlayer.js +++ b/src/sound/AudioClipPlayer.js @@ -7,9 +7,8 @@ * @license Distributed under the terms of the MIT License */ -import {SoundPlayer} from './SoundPlayer.js'; -import {AudioClip} from "./AudioClip.js"; - +import { AudioClip } from "./AudioClip.js"; +import { SoundPlayer } from "./SoundPlayer.js"; /** *

This class handles the playback of an audio clip, e.g. a microphone recording.

@@ -29,28 +28,27 @@ import {AudioClip} from "./AudioClip.js"; export class AudioClipPlayer extends SoundPlayer { constructor({ - psychoJS, - audioClip, - startTime = 0, - stopTime = -1, - stereo = true, - volume = 0, - loops = 0 - } = {}) + psychoJS, + audioClip, + startTime = 0, + stopTime = -1, + stereo = true, + volume = 0, + loops = 0, + } = {}) { super(psychoJS); - this._addAttribute('audioClip', audioClip); - this._addAttribute('startTime', startTime); - this._addAttribute('stopTime', stopTime); - this._addAttribute('stereo', stereo); - this._addAttribute('loops', loops); - this._addAttribute('volume', volume); + this._addAttribute("audioClip", audioClip); + this._addAttribute("startTime", startTime); + this._addAttribute("stopTime", stopTime); + this._addAttribute("stereo", stereo); + this._addAttribute("loops", loops); + this._addAttribute("volume", volume); this._currentLoopIndex = -1; } - /** * Determine whether this player can play the given sound. * @@ -73,7 +71,7 @@ export class AudioClipPlayer extends SoundPlayer stopTime: sound.stopTime, stereo: sound.stereo, loops: sound.loops, - volume: sound.volume + volume: sound.volume, }); return player; } @@ -82,7 +80,6 @@ export class AudioClipPlayer extends SoundPlayer return undefined; } - /** * Get the duration of the AudioClip, in seconds. * @@ -96,7 +93,6 @@ export class AudioClipPlayer extends SoundPlayer return this._audioClip.getDuration(); } - /** * Set the duration of the audio clip. * @@ -110,13 +106,12 @@ export class AudioClipPlayer extends SoundPlayer // TODO throw { - origin: 'AudioClipPlayer.setDuration', - context: 'when setting the duration of the playback for audio clip player: ' + this._name, - error: 'not implemented yet' + origin: "AudioClipPlayer.setDuration", + context: "when setting the duration of the playback for audio clip player: " + this._name, + error: "not implemented yet", }; } - /** * Set the volume of the playback. * @@ -133,7 +128,6 @@ export class AudioClipPlayer extends SoundPlayer this._audioClip.setVolume((mute) ? 0.0 : volume); } - /** * Set the number of loops. * @@ -150,7 +144,6 @@ export class AudioClipPlayer extends SoundPlayer // TODO } - /** * Start playing the sound. * @@ -162,7 +155,7 @@ export class AudioClipPlayer extends SoundPlayer */ play(loops, fadeDuration = 17) { - if (typeof loops !== 'undefined') + if (typeof loops !== "undefined") { this.setLoops(loops); } @@ -176,7 +169,6 @@ export class AudioClipPlayer extends SoundPlayer this._audioClip.startPlayback(); } - /** * Stop playing the sound immediately. * @@ -189,5 +181,4 @@ export class AudioClipPlayer extends SoundPlayer { this._audioClip.stopPlayback(fadeDuration); } - } diff --git a/src/sound/Microphone.js b/src/sound/Microphone.js index a4dbc17..a042bc9 100644 --- a/src/sound/Microphone.js +++ b/src/sound/Microphone.js @@ -7,12 +7,12 @@ * @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 {AudioClip} from "./AudioClip.js"; +import { PsychoJS } from "../core/PsychoJS.js"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { Clock } from "../util/Clock.js"; +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; +import { AudioClip } from "./AudioClip.js"; /** *

This manager handles the recording of audio signal.

@@ -29,18 +29,17 @@ import {AudioClip} from "./AudioClip.js"; */ export class Microphone extends PsychObject { - - constructor({win, name, format, sampleRateHz, clock, autoLog} = {}) + constructor({ win, name, format, sampleRateHz, clock, autoLog } = {}) { 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); - this._addAttribute('clock', clock, new Clock()); - this._addAttribute('autoLog', false, autoLog); - this._addAttribute('status', PsychoJS.Status.NOT_STARTED); + 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); + this._addAttribute("clock", clock, new Clock()); + this._addAttribute("autoLog", false, autoLog); + this._addAttribute("status", PsychoJS.Status.NOT_STARTED); // prepare the recording: this._prepareRecording(); @@ -51,7 +50,6 @@ export class Microphone extends PsychObject } } - /** * Submit a request to start the recording. * @@ -68,19 +66,18 @@ export class Microphone extends PsychObject // with a new recording: if (this._status === PsychoJS.Status.PAUSED) { - return this.resume({clear: true}); + return this.resume({ clear: true }); } - if (this._status !== PsychoJS.Status.STARTED) { - this._psychoJS.logger.debug('request to start audio recording'); + this._psychoJS.logger.debug("request to start audio recording"); try { if (!this._recorder) { - throw 'the recorder has not been created yet, possibly because the participant has not given the authorisation to record audio'; + throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record audio"; } this._recorder.start(); @@ -96,21 +93,18 @@ export class Microphone extends PsychObject } catch (error) { - this._psychoJS.logger.error('unable to start the audio recording: ' + JSON.stringify(error)); + this._psychoJS.logger.error("unable to start the audio recording: " + JSON.stringify(error)); this._status = PsychoJS.Status.ERROR; throw { - origin: 'Microphone.start', - context: 'when starting the audio recording for microphone: ' + this._name, - error + origin: "Microphone.start", + context: "when starting the audio recording for microphone: " + this._name, + error, }; } - } - } - /** * Submit a request to stop the recording. * @@ -122,14 +116,14 @@ export class Microphone extends PsychObject * @return {Promise} promise fulfilled when the recording actually stopped, and the recorded * data was made available */ - stop({filename} = {}) + stop({ filename } = {}) { if (this._status === PsychoJS.Status.STARTED || this._status === PsychoJS.Status.PAUSED) { - this._psychoJS.logger.debug('request to stop audio recording'); + this._psychoJS.logger.debug("request to stop audio recording"); this._stopOptions = { - filename + filename, }; // note: calling the stop method of the MediaRecorder will first raise a dataavailable event, @@ -148,7 +142,6 @@ export class Microphone extends PsychObject } } - /** * Submit a request to pause the recording. * @@ -160,13 +153,13 @@ export class Microphone extends PsychObject { if (this._status === PsychoJS.Status.STARTED) { - this._psychoJS.logger.debug('request to pause audio recording'); + this._psychoJS.logger.debug("request to pause audio recording"); try { if (!this._recorder) { - throw 'the recorder has not been created yet, possibly because the participant has not given the authorisation to record audio'; + throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record audio"; } // note: calling the pause method of the MediaRecorder raises a pause event @@ -182,20 +175,18 @@ export class Microphone extends PsychObject } catch (error) { - self._psychoJS.logger.error('unable to pause the audio recording: ' + JSON.stringify(error)); + self._psychoJS.logger.error("unable to pause the audio recording: " + JSON.stringify(error)); this._status = PsychoJS.Status.ERROR; throw { - origin: 'Microphone.pause', - context: 'when pausing the audio recording for microphone: ' + this._name, - error + origin: "Microphone.pause", + context: "when pausing the audio recording for microphone: " + this._name, + error, }; } - } } - /** * Submit a request to resume the recording. * @@ -207,17 +198,17 @@ export class Microphone extends PsychObject * resuming the recording * @return {Promise} promise fulfilled when the recording actually resumed */ - resume({clear = false } = {}) + resume({ clear = false } = {}) { if (this._status === PsychoJS.Status.PAUSED) { - this._psychoJS.logger.debug('request to resume audio recording'); + this._psychoJS.logger.debug("request to resume audio recording"); try { if (!this._recorder) { - throw 'the recorder has not been created yet, possibly because the participant has not given the authorisation to record audio'; + throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record audio"; } // empty the audio buffer is needed: @@ -239,20 +230,18 @@ export class Microphone extends PsychObject } catch (error) { - self._psychoJS.logger.error('unable to resume the audio recording: ' + JSON.stringify(error)); + self._psychoJS.logger.error("unable to resume the audio recording: " + JSON.stringify(error)); this._status = PsychoJS.Status.ERROR; throw { - origin: 'Microphone.resume', - context: 'when resuming the audio recording for microphone: ' + this._name, - error + origin: "Microphone.resume", + context: "when resuming the audio recording for microphone: " + this._name, + error, }; } - } } - /** * Submit a request to flush the recording. * @@ -264,7 +253,7 @@ export class Microphone extends PsychObject { if (this._status === PsychoJS.Status.STARTED || this._status === PsychoJS.Status.PAUSED) { - this._psychoJS.logger.debug('request to flush audio recording'); + this._psychoJS.logger.debug("request to flush audio recording"); // note: calling the requestData method of the MediaRecorder will raise a // dataavailable event @@ -281,7 +270,6 @@ export class Microphone extends PsychObject } } - /** * Offer the audio recording to the participant as a sound file to download. * @@ -290,11 +278,11 @@ export class Microphone extends PsychObject * @public * @param {string} filename the filename */ - download(filename = 'audio.webm') + download(filename = "audio.webm") { const audioBlob = new Blob(this._audioBuffer); - const anchor = document.createElement('a'); + const anchor = document.createElement("a"); anchor.href = window.URL.createObjectURL(audioBlob); anchor.download = filename; document.body.appendChild(anchor); @@ -302,7 +290,6 @@ export class Microphone extends PsychObject document.body.removeChild(anchor); } - /** * Upload the audio recording to the pavlovia server. * @@ -311,10 +298,10 @@ export class Microphone extends PsychObject * @public * @param {string} tag an optional tag for the audio file */ - async upload({tag} = {}) + async upload({ tag } = {}) { // default tag: the name of this Microphone object - if (typeof tag === 'undefined') + if (typeof tag === "undefined") { tag = this._name; } @@ -322,12 +309,13 @@ export class Microphone extends PsychObject // add a format-dependent audio extension to the tag: tag += util.extensionFromMimeType(this._format); - // if the audio recording cannot be uploaded, e.g. the experiment is running locally, or // if it is piloting mode, then we offer the audio recording as a file for download: - if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER || - this._psychoJS.config.experiment.status !== 'RUNNING' || - this._psychoJS._serverMsg.has('__pilotToken')) + if ( + this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER + || this._psychoJS.config.experiment.status !== "RUNNING" + || this._psychoJS._serverMsg.has("__pilotToken") + ) { return this.download(tag); } @@ -337,7 +325,6 @@ export class Microphone extends PsychObject return this._psychoJS.serverManager.uploadAudio(audioBlob, tag); } - /** * Get the current audio recording as an AudioClip in the given format. * @@ -347,27 +334,25 @@ export class Microphone extends PsychObject * @param {string} tag an optional tag for the audio clip * @param {boolean} [flush=false] whether or not to first flush the recording */ - async getRecording({tag, flush = false} = {}) + async getRecording({ tag, flush = false } = {}) { // default tag: the name of this Microphone object - if (typeof tag === 'undefined') + if (typeof tag === "undefined") { tag = this._name; } - const audioClip = new AudioClip({ psychoJS: this._psychoJS, name: tag, format: this._format, sampleRateHz: this._sampleRateHz, - data: new Blob(this._audioBuffer) + data: new Blob(this._audioBuffer), }); return audioClip; } - /** * Callback for changes to the recording settings. * @@ -389,7 +374,6 @@ export class Microphone extends PsychObject this.start(); } - /** * Prepare the recording. * @@ -409,15 +393,15 @@ export class Microphone extends PsychObject advanced: [ { channelCount: 1, - sampleRate: this._sampleRateHz - } - ] - } + sampleRate: this._sampleRateHz, + }, + ], + }, }); // check that the specified format is supported, use default if it is not: let options; - if (typeof this._format === 'string' && MediaRecorder.isTypeSupported(this._format)) + if (typeof this._format === "string" && MediaRecorder.isTypeSupported(this._format)) { options = { type: this._format }; } @@ -428,7 +412,6 @@ export class Microphone extends PsychObject this._recorder = new MediaRecorder(stream, options); - // setup the callbacks: const self = this; @@ -440,7 +423,7 @@ export class Microphone extends PsychObject self._audioBuffer.length = 0; self._clock.reset(); self._status = PsychoJS.Status.STARTED; - self._psychoJS.logger.debug('audio recording started'); + self._psychoJS.logger.debug("audio recording started"); // resolve the Microphone.start promise: if (self._startCallback) @@ -453,7 +436,7 @@ export class Microphone extends PsychObject this._recorder.onpause = () => { self._status = PsychoJS.Status.PAUSED; - self._psychoJS.logger.debug('audio recording paused'); + self._psychoJS.logger.debug("audio recording paused"); // resolve the Microphone.pause promise: if (self._pauseCallback) @@ -466,7 +449,7 @@ export class Microphone extends PsychObject this._recorder.onresume = () => { self._status = PsychoJS.Status.STARTED; - self._psychoJS.logger.debug('audio recording resumed'); + self._psychoJS.logger.debug("audio recording resumed"); // resolve the Microphone.resume promise: if (self._resumeCallback) @@ -482,7 +465,7 @@ export class Microphone extends PsychObject // add data to the buffer: self._audioBuffer.push(data); - self._psychoJS.logger.debug('audio data added to the buffer'); + self._psychoJS.logger.debug("audio data added to the buffer"); // resolve the data available promise, if needed: if (self._dataAvailableCallback) @@ -494,7 +477,7 @@ export class Microphone extends PsychObject // called upon Microphone.stop(), after data has been made available: this._recorder.onstop = () => { - self._psychoJS.logger.debug('audio recording stopped'); + self._psychoJS.logger.debug("audio recording stopped"); self._status = PsychoJS.Status.NOT_STARTED; // resolve the Microphone.stop promise: @@ -506,7 +489,7 @@ export class Microphone extends PsychObject // treat stop options if there are any: // download to a file, immediately offered to the participant: - if (typeof self._stopOptions.filename === 'string') + if (typeof self._stopOptions.filename === "string") { self.download(self._stopOptions.filename); } @@ -516,12 +499,8 @@ export class Microphone extends PsychObject this._recorder.onerror = (event) => { // TODO - self._psychoJS.logger.error('audio recording error: ' + JSON.stringify(event)); + self._psychoJS.logger.error("audio recording error: " + JSON.stringify(event)); self._status = PsychoJS.Status.ERROR; }; - } - } - - diff --git a/src/sound/Sound.js b/src/sound/Sound.js index 6d9e0c1..51f1b01 100644 --- a/src/sound/Sound.js +++ b/src/sound/Sound.js @@ -8,12 +8,11 @@ * @license Distributed under the terms of the MIT License */ -import {PsychoJS} from '../core/PsychoJS.js'; -import {PsychObject} from '../util/PsychObject.js'; -import {TonePlayer} from './TonePlayer.js'; -import {TrackPlayer} from './TrackPlayer.js'; -import {AudioClipPlayer} from './AudioClipPlayer.js'; - +import { PsychoJS } from "../core/PsychoJS.js"; +import { PsychObject } from "../util/PsychObject.js"; +import { AudioClipPlayer } from "./AudioClipPlayer.js"; +import { TonePlayer } from "./TonePlayer.js"; +import { TrackPlayer } from "./TrackPlayer.js"; /** *

This class handles sound playing (tones and tracks)

@@ -54,35 +53,35 @@ import {AudioClipPlayer} from './AudioClipPlayer.js'; export class Sound extends PsychObject { constructor({ - name, - win, - value = 'C', - octave = 4, - secs = 0.5, - startTime = 0, - stopTime = -1, - stereo = true, - volume = 1.0, - loops = 0, - //hamming = true, - autoLog = true - } = {}) + name, + win, + value = "C", + octave = 4, + secs = 0.5, + startTime = 0, + stopTime = -1, + stereo = true, + volume = 1.0, + loops = 0, + // hamming = true, + autoLog = true, + } = {}) { super(win._psychoJS, name); // the SoundPlayer, e.g. TonePlayer: this._player = undefined; - this._addAttribute('win', win); - this._addAttribute('value', value); - this._addAttribute('octave', octave); - this._addAttribute('secs', secs); - this._addAttribute('startTime', startTime); - this._addAttribute('stopTime', stopTime); - this._addAttribute('stereo', stereo); - this._addAttribute('volume', volume); - this._addAttribute('loops', loops); - this._addAttribute('autoLog', autoLog); + this._addAttribute("win", win); + this._addAttribute("value", value); + this._addAttribute("octave", octave); + this._addAttribute("secs", secs); + this._addAttribute("startTime", startTime); + this._addAttribute("stopTime", stopTime); + this._addAttribute("stereo", stereo); + this._addAttribute("volume", volume); + this._addAttribute("loops", loops); + this._addAttribute("autoLog", autoLog); // identify an appropriate player: this._getPlayer(); @@ -90,7 +89,6 @@ export class Sound extends PsychObject this.status = PsychoJS.Status.NOT_STARTED; } - /** * Start playing the sound. * @@ -107,7 +105,6 @@ export class Sound extends PsychObject this._player.play(loops); } - /** * Stop playing the sound immediately. * @@ -116,14 +113,13 @@ export class Sound extends PsychObject * @param {boolean} [options.log= true] - whether or not to log */ stop({ - log = true - } = {}) + log = true, + } = {}) { this._player.stop(); this.status = PsychoJS.Status.STOPPED; } - /** * Get the duration of the sound, in seconds. * @@ -135,7 +131,6 @@ export class Sound extends PsychObject return this._player.getDuration(); } - /** * Set the playing volume of the sound. * @@ -146,15 +141,14 @@ export class Sound extends PsychObject */ setVolume(volume, mute = false, log = true) { - this._setAttribute('volume', volume, log); + this._setAttribute("volume", volume, log); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { this._player.setVolume(volume, mute); } } - /** * Set the sound value on demand past initialisation. * @@ -166,9 +160,9 @@ export class Sound extends PsychObject { if (sound instanceof Sound) { - this._setAttribute('value', sound.value, log); + this._setAttribute("value", sound.value, log); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { this._player = this._player.constructor.accept(this); } @@ -178,13 +172,12 @@ export class Sound extends PsychObject } throw { - origin: 'Sound.setSound', - context: 'when replacing the current sound', - error: 'invalid input, need an instance of the Sound class.' + origin: "Sound.setSound", + context: "when replacing the current sound", + error: "invalid input, need an instance of the Sound class.", }; } - /** * Set the number of loops. * @@ -194,15 +187,14 @@ export class Sound extends PsychObject */ setLoops(loops = 0, log = true) { - this._setAttribute('loops', loops, log); + this._setAttribute("loops", loops, log); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { this._player.setLoops(loops); } } - /** * Set the duration (in seconds) * @@ -212,15 +204,14 @@ export class Sound extends PsychObject */ setSecs(secs = 0.5, log = true) { - this._setAttribute('secs', secs, log); + this._setAttribute("secs", secs, log); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { this._player.setDuration(secs); } } - /** * Identify the appropriate player for the sound. * @@ -231,26 +222,24 @@ export class Sound extends PsychObject _getPlayer() { const acceptFns = [ - sound => TonePlayer.accept(sound), - sound => TrackPlayer.accept(sound), - sound => AudioClipPlayer.accept(sound) + (sound) => TonePlayer.accept(sound), + (sound) => TrackPlayer.accept(sound), + (sound) => AudioClipPlayer.accept(sound), ]; for (const acceptFn of acceptFns) { this._player = acceptFn(this); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { return this._player; } } throw { - origin: 'SoundPlayer._getPlayer', - context: 'when finding a player for the sound', - error: 'could not find an appropriate player.' + origin: "SoundPlayer._getPlayer", + context: "when finding a player for the sound", + error: "could not find an appropriate player.", }; } - - } diff --git a/src/sound/SoundPlayer.js b/src/sound/SoundPlayer.js index 9b55d0e..4ba5bdb 100644 --- a/src/sound/SoundPlayer.js +++ b/src/sound/SoundPlayer.js @@ -7,8 +7,7 @@ * @license Distributed under the terms of the MIT License */ -import {PsychObject} from '../util/PsychObject.js'; - +import { PsychObject } from "../util/PsychObject.js"; /** *

SoundPlayer is an interface for the sound players, who are responsible for actually playing the sounds, i.e. the tracks or the tones.

@@ -25,7 +24,6 @@ export class SoundPlayer extends PsychObject super(psychoJS); } - /** * Determine whether this player can play the given sound. * @@ -40,13 +38,12 @@ export class SoundPlayer extends PsychObject static accept(sound) { throw { - origin: 'SoundPlayer.accept', - context: 'when evaluating whether this player can play a given sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.accept", + context: "when evaluating whether this player can play a given sound", + error: "this method is abstract and should not be called.", }; } - /** * Start playing the sound. * @@ -59,13 +56,12 @@ export class SoundPlayer extends PsychObject play(loops) { throw { - origin: 'SoundPlayer.play', - context: 'when starting the playback of a sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.play", + context: "when starting the playback of a sound", + error: "this method is abstract and should not be called.", }; } - /** * Stop playing the sound immediately. * @@ -77,13 +73,12 @@ export class SoundPlayer extends PsychObject stop() { throw { - origin: 'SoundPlayer.stop', - context: 'when stopping the playback of a sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.stop", + context: "when stopping the playback of a sound", + error: "this method is abstract and should not be called.", }; } - /** * Get the duration of the sound, in seconds. * @@ -95,13 +90,12 @@ export class SoundPlayer extends PsychObject getDuration() { throw { - origin: 'SoundPlayer.getDuration', - context: 'when getting the duration of the sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.getDuration", + context: "when getting the duration of the sound", + error: "this method is abstract and should not be called.", }; } - /** * Set the duration of the sound, in seconds. * @@ -113,13 +107,12 @@ export class SoundPlayer extends PsychObject setDuration(duration_s) { throw { - origin: 'SoundPlayer.setDuration', - context: 'when setting the duration of the sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.setDuration", + context: "when setting the duration of the sound", + error: "this method is abstract and should not be called.", }; } - /** * Set the number of loops. * @@ -132,13 +125,12 @@ export class SoundPlayer extends PsychObject setLoops(loops) { throw { - origin: 'SoundPlayer.setLoops', - context: 'when setting the number of loops', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.setLoops", + context: "when setting the number of loops", + error: "this method is abstract and should not be called.", }; } - /** * Set the volume of the tone. * @@ -152,10 +144,9 @@ export class SoundPlayer extends PsychObject setVolume(volume, mute = false) { throw { - origin: 'SoundPlayer.setVolume', - context: 'when setting the volume of the sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.setVolume", + context: "when setting the volume of the sound", + error: "this method is abstract and should not be called.", }; } - } diff --git a/src/sound/TonePlayer.js b/src/sound/TonePlayer.js index a7a064e..a552162 100644 --- a/src/sound/TonePlayer.js +++ b/src/sound/TonePlayer.js @@ -7,10 +7,9 @@ * @license Distributed under the terms of the MIT License */ -import * as Tone from 'tone'; +import * as Tone from "tone"; import { isNumeric } from "../util/Util.js"; -import {SoundPlayer} from './SoundPlayer.js'; - +import { SoundPlayer } from "./SoundPlayer.js"; /** *

This class handles the playing of tones.

@@ -28,23 +27,23 @@ import {SoundPlayer} from './SoundPlayer.js'; export class TonePlayer extends SoundPlayer { constructor({ - psychoJS, - note = 'C4', - duration_s = 0.5, - volume = 1.0, - loops = 0, - soundLibrary = TonePlayer.SoundLibrary.TONE_JS, - autoLog = true - } = {}) + psychoJS, + note = "C4", + duration_s = 0.5, + volume = 1.0, + loops = 0, + soundLibrary = TonePlayer.SoundLibrary.TONE_JS, + autoLog = true, + } = {}) { super(psychoJS); - this._addAttribute('note', note); - this._addAttribute('duration_s', duration_s); - this._addAttribute('volume', volume); - this._addAttribute('loops', loops); - this._addAttribute('soundLibrary', soundLibrary); - this._addAttribute('autoLog', autoLog); + this._addAttribute("note", note); + this._addAttribute("duration_s", duration_s); + this._addAttribute("volume", volume); + this._addAttribute("loops", loops); + this._addAttribute("soundLibrary", soundLibrary); + this._addAttribute("autoLog", autoLog); // initialise the sound library: this._initSoundLibrary(); @@ -58,7 +57,6 @@ export class TonePlayer extends SoundPlayer } } - /** * Determine whether this player can play the given sound. * @@ -82,32 +80,32 @@ export class TonePlayer extends SoundPlayer note: sound.value, duration_s: sound.secs, volume: sound.volume, - loops: sound.loops + loops: sound.loops, }); } // if the sound's value is a string, we check whether it is a note: - if (typeof sound.value === 'string') + if (typeof sound.value === "string") { // mapping between the PsychoPY notes and the standard ones: let psychopyToToneMap = new Map(); - for (const note of ['A', 'B', 'C', 'D', 'E', 'F', 'G']) + for (const note of ["A", "B", "C", "D", "E", "F", "G"]) { psychopyToToneMap.set(note, note); - psychopyToToneMap.set(note + 'fl', note + 'b'); - psychopyToToneMap.set(note + 'sh', note + '#'); + psychopyToToneMap.set(note + "fl", note + "b"); + psychopyToToneMap.set(note + "sh", note + "#"); } // check whether the sound's value is a recognised note: const note = psychopyToToneMap.get(sound.value); - if (typeof note !== 'undefined') + if (typeof note !== "undefined") { return new TonePlayer({ psychoJS: sound.psychoJS, note: note + sound.octave, duration_s: sound.secs, volume: sound.volume, - loops: sound.loops + loops: sound.loops, }); } } @@ -116,7 +114,6 @@ export class TonePlayer extends SoundPlayer return undefined; } - /** * Get the duration of the sound. * @@ -130,7 +127,6 @@ export class TonePlayer extends SoundPlayer return this.duration_s; } - /** * Set the duration of the tone. * @@ -144,7 +140,6 @@ export class TonePlayer extends SoundPlayer this.duration_s = duration_s; } - /** * Set the number of loops. * @@ -158,7 +153,6 @@ export class TonePlayer extends SoundPlayer this._loops = loops; } - /** * Set the volume of the tone. * @@ -174,7 +168,7 @@ export class TonePlayer extends SoundPlayer if (this._soundLibrary === TonePlayer.SoundLibrary.TONE_JS) { - if (typeof this._volumeNode !== 'undefined') + if (typeof this._volumeNode !== "undefined") { this._volumeNode.mute = mute; this._volumeNode.volume.value = -60 + volume * 66; @@ -191,7 +185,6 @@ export class TonePlayer extends SoundPlayer } } - /** * Start playing the sound. * @@ -202,7 +195,7 @@ export class TonePlayer extends SoundPlayer */ play(loops) { - if (typeof loops !== 'undefined') + if (typeof loops !== "undefined") { this._loops = loops; } @@ -223,7 +216,7 @@ export class TonePlayer extends SoundPlayer playToneCallback = () => { self._webAudioOscillator = self._audioContext.createOscillator(); - self._webAudioOscillator.type = 'sine'; + self._webAudioOscillator.type = "sine"; self._webAudioOscillator.frequency.value = 440; self._webAudioOscillator.connect(self._audioContext.destination); const contextCurrentTime = self._audioContext.currentTime; @@ -237,7 +230,6 @@ export class TonePlayer extends SoundPlayer { playToneCallback(); } - // repeat forever: else if (this.loops === -1) { @@ -245,22 +237,21 @@ export class TonePlayer extends SoundPlayer playToneCallback, this.duration_s, Tone.now(), - Infinity + Infinity, ); } - else // repeat this._loops times: + else { this._toneId = Tone.Transport.scheduleRepeat( playToneCallback, this.duration_s, Tone.now(), - this.duration_s * (this._loops + 1) + this.duration_s * (this._loops + 1), ); } } - /** * Stop playing the sound immediately. * @@ -288,7 +279,6 @@ export class TonePlayer extends SoundPlayer } } - /** * Initialise the sound library. * @@ -302,24 +292,24 @@ export class TonePlayer extends SoundPlayer _initSoundLibrary() { const response = { - origin: 'TonePlayer._initSoundLibrary', - context: 'when initialising the sound library' + origin: "TonePlayer._initSoundLibrary", + context: "when initialising the sound library", }; if (this._soundLibrary === TonePlayer.SoundLibrary.TONE_JS) { // check that Tone.js is available: - if (typeof Tone === 'undefined') + if (typeof Tone === "undefined") { throw Object.assign(response, { - error: "Tone.js is not available. A different sound library must be selected. Please contact the experiment designer." + error: "Tone.js is not available. A different sound library must be selected. Please contact the experiment designer.", }); } // start the Tone Transport if it has not started already: - if (typeof Tone !== 'undefined' && Tone.Transport.state !== 'started') + if (typeof Tone !== "undefined" && Tone.Transport.state !== "started") { - this.psychoJS.logger.info('[PsychoJS] start Tone Transport'); + this.psychoJS.logger.info("[PsychoJS] start Tone Transport"); Tone.Transport.start(Tone.now()); // this is necessary to prevent Tone from introducing a delay when triggering a note @@ -330,14 +320,14 @@ export class TonePlayer extends SoundPlayer // create a synth: we use a triangular oscillator with hardly any envelope: this._synthOtions = { oscillator: { - type: 'square' //'triangle' + type: "square", // 'triangle' }, envelope: { attack: 0.001, // 1ms - decay: 0.001, // 1ms + decay: 0.001, // 1ms sustain: 1, - release: 0.001 // 1ms - } + release: 0.001, // 1ms + }, }; this._synth = new Tone.Synth(this._synthOtions); @@ -346,7 +336,7 @@ export class TonePlayer extends SoundPlayer this._synth.connect(this._volumeNode); // connect the volume node to the master output: - if (typeof this._volumeNode.toDestination === 'function') + if (typeof this._volumeNode.toDestination === "function") { this._volumeNode.toDestination(); } @@ -358,15 +348,15 @@ export class TonePlayer extends SoundPlayer else { // create an AudioContext: - if (typeof this._audioContext === 'undefined') + if (typeof this._audioContext === "undefined") { const AudioContext = window.AudioContext || window.webkitAudioContext; // if AudioContext is not available (e.g. on IE), we throw an exception: - if (typeof AudioContext === 'undefined') + if (typeof AudioContext === "undefined") { throw Object.assign(response, { - error: `AudioContext is not available on your browser, ${this._psychoJS.browser}, please contact the experiment designer.` + error: `AudioContext is not available on your browser, ${this._psychoJS.browser}, please contact the experiment designer.`, }); } @@ -374,15 +364,13 @@ export class TonePlayer extends SoundPlayer } } } - } - /** * * @type {{TONE_JS: *, AUDIO_CONTEXT: *}} */ TonePlayer.SoundLibrary = { - AUDIO_CONTEXT: Symbol.for('AUDIO_CONTEXT'), - TONE_JS: Symbol.for('TONE_JS') + AUDIO_CONTEXT: Symbol.for("AUDIO_CONTEXT"), + TONE_JS: Symbol.for("TONE_JS"), }; diff --git a/src/sound/TrackPlayer.js b/src/sound/TrackPlayer.js index a481a66..a5dcbda 100644 --- a/src/sound/TrackPlayer.js +++ b/src/sound/TrackPlayer.js @@ -7,8 +7,7 @@ * @license Distributed under the terms of the MIT License */ -import {SoundPlayer} from './SoundPlayer.js'; - +import { SoundPlayer } from "./SoundPlayer.js"; /** *

This class handles the playback of sound tracks.

@@ -30,28 +29,27 @@ import {SoundPlayer} from './SoundPlayer.js'; export class TrackPlayer extends SoundPlayer { constructor({ - psychoJS, - howl, - startTime = 0, - stopTime = -1, - stereo = true, - volume = 0, - loops = 0 - } = {}) + psychoJS, + howl, + startTime = 0, + stopTime = -1, + stereo = true, + volume = 0, + loops = 0, + } = {}) { super(psychoJS); - this._addAttribute('howl', howl); - this._addAttribute('startTime', startTime); - this._addAttribute('stopTime', stopTime); - this._addAttribute('stereo', stereo); - this._addAttribute('loops', loops); - this._addAttribute('volume', volume); + this._addAttribute("howl", howl); + this._addAttribute("startTime", startTime); + this._addAttribute("stopTime", stopTime); + this._addAttribute("stereo", stereo); + this._addAttribute("loops", loops); + this._addAttribute("volume", volume); this._currentLoopIndex = -1; } - /** * Determine whether this player can play the given sound. * @@ -66,10 +64,10 @@ export class TrackPlayer extends SoundPlayer static accept(sound) { // if the sound's value is a string, we check whether it is the name of a resource: - if (typeof sound.value === 'string') + if (typeof sound.value === "string") { const howl = sound.psychoJS.serverManager.getResource(sound.value); - if (typeof howl !== 'undefined') + if (typeof howl !== "undefined") { // build the player: const player = new TrackPlayer({ @@ -79,7 +77,7 @@ export class TrackPlayer extends SoundPlayer stopTime: sound.stopTime, stereo: sound.stereo, loops: sound.loops, - volume: sound.volume + volume: sound.volume, }); return player; } @@ -89,7 +87,6 @@ export class TrackPlayer extends SoundPlayer return undefined; } - /** * Get the duration of the sound, in seconds. * @@ -103,7 +100,6 @@ export class TrackPlayer extends SoundPlayer return this._howl.duration(); } - /** * Set the duration of the track. * @@ -114,14 +110,13 @@ export class TrackPlayer extends SoundPlayer */ setDuration(duration_s) { - if (typeof this._howl !== 'undefined') + if (typeof this._howl !== "undefined") { // Unfortunately Howler.js provides duration setting method this._howl._duration = duration_s; } } - /** * Set the volume of the tone. * @@ -139,7 +134,6 @@ export class TrackPlayer extends SoundPlayer this._howl.mute(mute); } - /** * Set the number of loops. * @@ -163,7 +157,6 @@ export class TrackPlayer extends SoundPlayer } } - /** * Start playing the sound. * @@ -175,7 +168,7 @@ export class TrackPlayer extends SoundPlayer */ play(loops, fadeDuration = 17) { - if (typeof loops !== 'undefined') + if (typeof loops !== "undefined") { this.setLoops(loops); } @@ -184,7 +177,7 @@ export class TrackPlayer extends SoundPlayer if (loops > 0) { const self = this; - this._howl.on('end', (event) => + this._howl.on("end", (event) => { ++this._currentLoopIndex; if (self._currentLoopIndex > self._loops) @@ -205,7 +198,6 @@ export class TrackPlayer extends SoundPlayer this._howl.fade(0, this._volume, fadeDuration, this._id); } - /** * Stop playing the sound immediately. * @@ -216,11 +208,11 @@ export class TrackPlayer extends SoundPlayer */ stop(fadeDuration = 17) { - this._howl.once('fade', (id) => { + this._howl.once("fade", (id) => + { this._howl.stop(id); - this._howl.off('end'); + this._howl.off("end"); }); this._howl.fade(this._howl.volume(), 0, fadeDuration, this._id); } - } diff --git a/src/sound/index.js b/src/sound/index.js index 6cec21e..81637d7 100644 --- a/src/sound/index.js +++ b/src/sound/index.js @@ -1,9 +1,9 @@ -export * from './Sound.js'; -export * from './SoundPlayer.js'; -export * from './TonePlayer.js'; -export * from './TrackPlayer.js'; +export * from "./Sound.js"; +export * from "./SoundPlayer.js"; +export * from "./TonePlayer.js"; +export * from "./TrackPlayer.js"; -export * from './Microphone.js'; -export * from './AudioClip.js'; -export * from './AudioClipPlayer.js'; -//export * from './Transcriber.js'; +export * from "./AudioClip.js"; +export * from "./AudioClipPlayer.js"; +export * from "./Microphone.js"; +// export * from './Transcriber.js'; From 27d08ba42f7aed8f8fb1c6edf7748d98d4c0a7e7 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 14:08:08 +0100 Subject: [PATCH 27/29] data: enforce formatting rules --- src/data/ExperimentHandler.js | 103 ++++++++++------------ src/data/TrialHandler.js | 156 ++++++++++++++-------------------- src/data/index.js | 6 +- 3 files changed, 108 insertions(+), 157 deletions(-) diff --git a/src/data/ExperimentHandler.js b/src/data/ExperimentHandler.js index bc50cd8..3bae7f2 100644 --- a/src/data/ExperimentHandler.js +++ b/src/data/ExperimentHandler.js @@ -7,12 +7,10 @@ * @license Distributed under the terms of the MIT License */ - -import * as XLSX from 'xlsx'; -import {PsychObject} from '../util/PsychObject.js'; -import {MonotonicClock} from '../util/Clock.js'; -import * as util from '../util/Util.js'; - +import * as XLSX from "xlsx"; +import { MonotonicClock } from "../util/Clock.js"; +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; /** *

An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful @@ -29,7 +27,6 @@ import * as util from '../util/Util.js'; */ export class ExperimentHandler extends PsychObject { - /** * Getter for experimentEnded. * @@ -54,7 +51,6 @@ export class ExperimentHandler extends PsychObject this._experimentEnded = ended; } - /** * Legacy experiment getters. */ @@ -68,16 +64,15 @@ export class ExperimentHandler extends PsychObject return this._trialsData; } - constructor({ - psychoJS, - name, - extraInfo - } = {}) + psychoJS, + name, + extraInfo, + } = {}) { super(psychoJS, name); - this._addAttribute('extraInfo', extraInfo); + this._addAttribute("extraInfo", extraInfo); // loop handlers: this._loops = []; @@ -91,7 +86,6 @@ export class ExperimentHandler extends PsychObject this._experimentEnded = false; } - /** * Whether or not the current entry (i.e. trial data) is empty. *

Note: this is mostly useful at the end of an experiment, in order to ensure that the last entry is saved.

@@ -106,7 +100,6 @@ export class ExperimentHandler extends PsychObject return (Object.keys(this._currentTrialData).length > 0); } - /** * Add a loop. * @@ -125,7 +118,6 @@ export class ExperimentHandler extends PsychObject loop.experimentHandler = this; } - /** * Remove the given loop from the list of unfinished loops, e.g. when it has completed. * @@ -143,7 +135,6 @@ export class ExperimentHandler extends PsychObject } } - /** * Add the key/value pair. * @@ -172,7 +163,6 @@ export class ExperimentHandler extends PsychObject this._currentTrialData[key] = value; } - /** * Inform this ExperimentHandler that the current trial has ended. Further calls to {@link addData} * will be associated with the next trial. @@ -184,7 +174,7 @@ export class ExperimentHandler extends PsychObject */ nextEntry(snapshots) { - if (typeof snapshots !== 'undefined') + if (typeof snapshots !== "undefined") { // turn single snapshot into a one-element array: if (!Array.isArray(snapshots)) @@ -203,7 +193,6 @@ export class ExperimentHandler extends PsychObject } } } - } // this is to support legacy generated JavaScript code and does not properly handle // loops within loops: @@ -236,7 +225,6 @@ export class ExperimentHandler extends PsychObject this._currentTrialData = {}; } - /** * Save the results of the experiment. * @@ -254,11 +242,11 @@ export class ExperimentHandler extends PsychObject * @param {Array.} [options.sync] - whether or not to communicate with the server in a synchronous manner */ async save({ - attributes = [], - sync = false - } = {}) + attributes = [], + sync = false, + } = {}) { - this._psychoJS.logger.info('[PsychoJS] Save experiment results.'); + this._psychoJS.logger.info("[PsychoJS] Save experiment results."); // (*) get attributes: if (attributes.length === 0) @@ -286,16 +274,14 @@ export class ExperimentHandler extends PsychObject } } - // (*) get various experiment info: const info = this.extraInfo; - const __experimentName = (typeof info.expName !== 'undefined') ? info.expName : this.psychoJS.config.experiment.name; - const __participant = ((typeof info.participant === 'string' && info.participant.length > 0) ? info.participant : 'PARTICIPANT'); - const __session = ((typeof info.session === 'string' && info.session.length > 0) ? info.session : 'SESSION'); - const __datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr()); + const __experimentName = (typeof info.expName !== "undefined") ? info.expName : this.psychoJS.config.experiment.name; + const __participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT"); + const __session = ((typeof info.session === "string" && info.session.length > 0) ? info.session : "SESSION"); + const __datetime = ((typeof info.date !== "undefined") ? info.date : MonotonicClock.getDateStr()); const gitlabConfig = this._psychoJS.config.gitlab; - const __projectId = (typeof gitlabConfig !== 'undefined' && typeof gitlabConfig.projectId !== 'undefined') ? gitlabConfig.projectId : undefined; - + const __projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined; // (*) save to a .csv file: if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.CSV) @@ -304,23 +290,23 @@ export class ExperimentHandler extends PsychObject // newlines, etc. const worksheet = XLSX.utils.json_to_sheet(this._trialsData); // prepend BOM - const csv = '\ufeff' + XLSX.utils.sheet_to_csv(worksheet); + const csv = "\ufeff" + XLSX.utils.sheet_to_csv(worksheet); // upload data to the pavlovia server or offer them for download: - const key = __participant + '_' + __experimentName + '_' + __datetime + '.csv'; - if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && - this._psychoJS.config.experiment.status === 'RUNNING' && - !this._psychoJS._serverMsg.has('__pilotToken')) + const key = __participant + "_" + __experimentName + "_" + __datetime + ".csv"; + if ( + this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER + && this._psychoJS.config.experiment.status === "RUNNING" + && !this._psychoJS._serverMsg.has("__pilotToken") + ) { return /*await*/ this._psychoJS.serverManager.uploadData(key, csv, sync); } else { - util.offerDataForDownload(key, csv, 'text/csv'); + util.offerDataForDownload(key, csv, "text/csv"); } } - - // (*) save in the database on the remote server: else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE) { @@ -328,7 +314,7 @@ export class ExperimentHandler extends PsychObject for (let r = 0; r < this._trialsData.length; r++) { - let doc = {__projectId, __experimentName, __participant, __session, __datetime}; + let doc = { __projectId, __experimentName, __participant, __session, __datetime }; for (let h = 0; h < attributes.length; h++) { doc[attributes[h]] = this._trialsData[r][attributes[h]]; @@ -338,22 +324,22 @@ export class ExperimentHandler extends PsychObject } // upload data to the pavlovia server or offer them for download: - if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && - this._psychoJS.config.experiment.status === 'RUNNING' && - !this._psychoJS._serverMsg.has('__pilotToken')) + if ( + this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER + && this._psychoJS.config.experiment.status === "RUNNING" + && !this._psychoJS._serverMsg.has("__pilotToken") + ) { - const key = 'results'; // name of the mongoDB collection + const key = "results"; // name of the mongoDB collection return /*await*/ this._psychoJS.serverManager.uploadData(key, JSON.stringify(documents), sync); } else { - util.offerDataForDownload('results.json', JSON.stringify(documents), 'application/json'); + util.offerDataForDownload("results.json", JSON.stringify(documents), "application/json"); } - } } - /** * Get the attribute names and values for the current trial of a given loop. *

Only info relating to the trial execution are returned.

@@ -367,20 +353,20 @@ export class ExperimentHandler extends PsychObject static _getLoopAttributes(loop) { // standard trial attributes: - const properties = ['thisRepN', 'thisTrialN', 'thisN', 'thisIndex', 'stepSizeCurrent', 'ran', 'order']; + const properties = ["thisRepN", "thisTrialN", "thisN", "thisIndex", "stepSizeCurrent", "ran", "order"]; let attributes = {}; const loopName = loop.name; for (const loopProperty in loop) { if (properties.includes(loopProperty)) { - const key = (loopProperty === 'stepSizeCurrent') ? loopName + '.stepSize' : loopName + '.' + loopProperty; + const key = (loopProperty === "stepSizeCurrent") ? loopName + ".stepSize" : loopName + "." + loopProperty; attributes[key] = loop[loopProperty]; } } // specific trial attributes: - if (typeof loop.getCurrentTrial === 'function') + if (typeof loop.getCurrentTrial === "function") { const currentTrial = loop.getCurrentTrial(); for (const trialProperty in currentTrial) @@ -404,7 +390,7 @@ export class ExperimentHandler extends PsychObject else: names.append(loopName+'.thisTrial') vals.append(trial) - + // single StairHandler elif hasattr(loop, 'intensities'): names.append(loopName+'.intensity') @@ -415,10 +401,8 @@ export class ExperimentHandler extends PsychObject return attributes; } - } - /** * Experiment result format * @@ -431,15 +415,14 @@ ExperimentHandler.SaveFormat = { /** * Results are saved to a .csv file */ - CSV: Symbol.for('CSV'), + CSV: Symbol.for("CSV"), /** * Results are saved to a database */ - DATABASE: Symbol.for('DATABASE') + DATABASE: Symbol.for("DATABASE"), }; - /** * Experiment environment. * @@ -448,6 +431,6 @@ ExperimentHandler.SaveFormat = { * @public */ ExperimentHandler.Environment = { - SERVER: Symbol.for('SERVER'), - LOCAL: Symbol.for('LOCAL') + SERVER: Symbol.for("SERVER"), + LOCAL: Symbol.for("LOCAL"), }; diff --git a/src/data/TrialHandler.js b/src/data/TrialHandler.js index e2fc982..098afa7 100644 --- a/src/data/TrialHandler.js +++ b/src/data/TrialHandler.js @@ -9,11 +9,10 @@ * @license Distributed under the terms of the MIT License */ - -import seedrandom from 'seedrandom'; -import * as XLSX from 'xlsx'; -import {PsychObject} from '../util/PsychObject.js'; -import * as util from '../util/Util.js'; +import seedrandom from "seedrandom"; +import * as XLSX from "xlsx"; +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; /** *

A Trial Handler handles the importing and sequencing of conditions.

@@ -31,7 +30,6 @@ import * as util from '../util/Util.js'; */ export class TrialHandler extends PsychObject { - /** * Getter for experimentHandler. * @@ -56,7 +54,6 @@ export class TrialHandler extends PsychObject this._experimentHandler = exp; } - /** * @constructor * @public @@ -64,27 +61,27 @@ export class TrialHandler extends PsychObject * @todo extraInfo is not taken into account, we use the expInfo of the ExperimentHandler instead */ constructor({ - psychoJS, - trialList = [undefined], - nReps, - method = TrialHandler.Method.RANDOM, - extraInfo = [], - seed, - name, - autoLog = true - } = {}) + psychoJS, + trialList = [undefined], + nReps, + method = TrialHandler.Method.RANDOM, + extraInfo = [], + seed, + name, + autoLog = true, + } = {}) { super(psychoJS); - this._addAttribute('trialList', trialList); - this._addAttribute('nReps', nReps); - this._addAttribute('method', method); - this._addAttribute('extraInfo', extraInfo); - this._addAttribute('name', name); - this._addAttribute('autoLog', autoLog); - this._addAttribute('seed', seed); + this._addAttribute("trialList", trialList); + this._addAttribute("nReps", nReps); + this._addAttribute("method", method); + this._addAttribute("extraInfo", extraInfo); + this._addAttribute("name", name); + this._addAttribute("autoLog", autoLog); + this._addAttribute("seed", seed); this._prepareTrialList(trialList); - + // number of stimuli this.nStim = this.trialList.length; @@ -112,7 +109,6 @@ export class TrialHandler extends PsychObject // array of current snapshots: this._snapshots = []; - // setup the trial sequence: this._prepareSequence(); @@ -121,18 +117,17 @@ export class TrialHandler extends PsychObject this._finished = false; } - /** * Helps go through each trial in the sequence one by one, mirrors PsychoPy. */ - next() { + next() + { const trialIterator = this[Symbol.iterator](); const { value } = trialIterator.next(); return value; } - /** * Iterator over the trial sequence. * @@ -168,7 +163,7 @@ export class TrialHandler extends PsychObject if (this.thisRepN >= this.nReps) { this.thisTrial = null; - return {done: true}; + return { done: true }; } this.thisIndex = this._trialSequence[this.thisRepN][this.thisTrialN]; @@ -181,12 +176,11 @@ export class TrialHandler extends PsychObject vals = (self.thisRepN, self.thisTrialN, self.thisTrial) logging.exp(msg % vals, obj=self.thisTrial)*/ - return {value: this.thisTrial, done: false}; - } + return { value: this.thisTrial, done: false }; + }, }; } - /** * Execute the callback for each trial in the sequence. * @@ -208,7 +202,6 @@ export class TrialHandler extends PsychObject } } - /** * @typedef {Object} Snapshot * @property {TrialHandler} handler - the trialHandler @@ -253,12 +246,12 @@ export class TrialHandler extends PsychObject getCurrentTrial: () => this.getTrial(currentIndex), getTrial: (index = 0) => this.getTrial(index), - addData: (key, value) => this.addData(key, value) + addData: (key, value) => this.addData(key, value), }; // add to the snapshots the current trial's attributes: const currentTrial = this.getCurrentTrial(); - const excludedAttributes = ['handler', 'name', 'nStim', 'nRemaining', 'thisRepN', 'thisTrialN', 'thisN', 'thisIndex', 'ran', 'finished']; + const excludedAttributes = ["handler", "name", "nStim", "nRemaining", "thisRepN", "thisTrialN", "thisN", "thisIndex", "ran", "finished"]; const trialAttributes = []; for (const attribute in currentTrial) { @@ -280,7 +273,6 @@ export class TrialHandler extends PsychObject return snapshot; } - /** * Setter for the seed attribute. * @@ -289,9 +281,9 @@ export class TrialHandler extends PsychObject */ setSeed(seed, log) { - this._setAttribute('seed', seed, log); + this._setAttribute("seed", seed, log); - if (typeof seed !== 'undefined') + if (typeof seed !== "undefined") { this._randomNumberGenerator = seedrandom(seed); } @@ -301,7 +293,6 @@ export class TrialHandler extends PsychObject } } - /** * Set the internal state of this trial handler from the given snapshot. * @@ -312,12 +303,11 @@ export class TrialHandler extends PsychObject static fromSnapshot(snapshot) { // if snapshot is undefined, do nothing: - if (typeof snapshot === 'undefined') + if (typeof snapshot === "undefined") { return; } - snapshot.handler.nStim = snapshot.nStim; snapshot.handler.nTotal = snapshot.nTotal; snapshot.handler.nRemaining = snapshot.nRemaining; @@ -330,13 +320,12 @@ export class TrialHandler extends PsychObject snapshot.handler.thisTrial = snapshot.handler.getCurrentTrial(); - // add the snapshot's trial attributes to a global variable, whose name is derived from // that of the handler: loops -> thisLoop (note the dropped s): let name = snapshot.name; - if (name[name.length-1] === 's') + if (name[name.length - 1] === "s") { - name = name.substr(0, name.length-1); + name = name.substr(0, name.length - 1); } name = `this${name[0].toUpperCase()}${name.substr(1)}`; @@ -348,7 +337,6 @@ export class TrialHandler extends PsychObject window[name] = value; } - /** * Getter for the finished attribute. * @@ -359,7 +347,6 @@ export class TrialHandler extends PsychObject return this._finished; } - /** * Setter for the finished attribute. * @@ -368,14 +355,13 @@ export class TrialHandler extends PsychObject set finished(isFinished) { this._finished = isFinished; - - this._snapshots.forEach( snapshot => + + this._snapshots.forEach((snapshot) => { snapshot.finished = isFinished; }); } - /** * Get the trial index. * @@ -387,7 +373,6 @@ export class TrialHandler extends PsychObject return this.thisIndex; } - /** * Set the trial index. * @@ -398,7 +383,6 @@ export class TrialHandler extends PsychObject this.thisIndex = index; } - /** * Get the attributes of the trials. * @@ -424,7 +408,6 @@ export class TrialHandler extends PsychObject return Object.keys(this.trialList[0]); } - /** * Get the current trial. * @@ -436,7 +419,6 @@ export class TrialHandler extends PsychObject return this.trialList[this.thisIndex]; } - /** * Get the nth trial. * @@ -453,7 +435,6 @@ export class TrialHandler extends PsychObject return this.trialList[index]; } - /** * Get the nth future or past trial, without advancing through the trial list. * @@ -472,7 +453,6 @@ export class TrialHandler extends PsychObject return this.trialList[this.thisIndex + n]; } - /** * Get the nth previous trial. *

Note: this is useful for comparisons in n-back tasks.

@@ -486,7 +466,6 @@ export class TrialHandler extends PsychObject return getFutureTrial(-abs(n)); } - /** * Add a key/value pair to data about the current trial held by the experiment handler * @@ -502,7 +481,6 @@ export class TrialHandler extends PsychObject } } - /** * Import a list of conditions from a .xls, .xlsx, .odp, or .csv resource. * @@ -542,8 +520,8 @@ export class TrialHandler extends PsychObject { try { - let resourceExtension = resourceName.split('.').pop(); - if (['csv', 'odp', 'xls', 'xlsx'].indexOf(resourceExtension) > -1) + let resourceExtension = resourceName.split(".").pop(); + if (["csv", "odp", "xls", "xlsx"].indexOf(resourceExtension) > -1) { // (*) read conditions from resource: const resourceValue = serverManager.getResource(resourceName, true); @@ -552,20 +530,20 @@ export class TrialHandler extends PsychObject // which is then read in as a string const decodedResourceMaybe = new Uint8Array(resourceValue); // Could be set to 'buffer' for ASCII .csv - const type = resourceExtension === 'csv' ? 'string' : 'array'; - const decodedResource = type === 'string' ? (new TextDecoder()).decode(decodedResourceMaybe) : decodedResourceMaybe; + const type = resourceExtension === "csv" ? "string" : "array"; + const decodedResource = type === "string" ? (new TextDecoder()).decode(decodedResourceMaybe) : decodedResourceMaybe; const workbook = XLSX.read(decodedResource, { type }); // we consider only the first worksheet: if (workbook.SheetNames.length === 0) { - throw 'workbook should contain at least one worksheet'; + throw "workbook should contain at least one worksheet"; } const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; // worksheet to array of arrays (the first array contains the fields): - const sheet = XLSX.utils.sheet_to_json(worksheet, {header: 1, blankrows: false}); + const sheet = XLSX.utils.sheet_to_json(worksheet, { header: 1, blankrows: false }); const fields = sheet.shift(); // (*) select conditions: @@ -574,8 +552,8 @@ export class TrialHandler extends PsychObject // (*) return the selected conditions as an array of 'object as map': // [ // {field0: value0-0, field1: value0-1, ...} - // {field0: value1-0, field1: value1-1, ...} - // ... + // {field0: value1-0, field1: value1-1, ...} + // ... // ] let trialList = new Array(selectedRows.length - 1); for (let r = 0; r < selectedRows.length; ++r) @@ -598,7 +576,7 @@ export class TrialHandler extends PsychObject value = arrayMaybe; } - if (typeof value === 'string') + if (typeof value === "string") { const numberMaybe = Number.parseFloat(value); @@ -610,7 +588,7 @@ export class TrialHandler extends PsychObject else { // Parse doubly escaped line feeds - value = value.replace(/(\n)/g, '\n'); + value = value.replace(/(\n)/g, "\n"); } } @@ -621,23 +599,21 @@ export class TrialHandler extends PsychObject return trialList; } - else { - throw 'extension: ' + resourceExtension + ' currently not supported.'; + throw "extension: " + resourceExtension + " currently not supported."; } } catch (error) { throw { - origin: 'TrialHandler.importConditions', + origin: "TrialHandler.importConditions", context: `when importing condition: ${resourceName}`, - error + error, }; } } - /** * Prepare the trial list. * @@ -647,16 +623,15 @@ export class TrialHandler extends PsychObject _prepareTrialList(trialList) { const response = { - origin: 'TrialHandler._prepareTrialList', - context: 'when preparing the trial list' + origin: "TrialHandler._prepareTrialList", + context: "when preparing the trial list", }; // we treat undefined trialList as a list with a single empty entry: - if (typeof trialList === 'undefined') + if (typeof trialList === "undefined") { this.trialList = [undefined]; } - // if trialList is an array, we make sure it is not empty: else if (Array.isArray(trialList)) { @@ -665,30 +640,27 @@ export class TrialHandler extends PsychObject this.trialList = [undefined]; } } - // if trialList is a string, we treat it as the name of the condition resource: - else if (typeof trialList === 'string') + else if (typeof trialList === "string") { this.trialList = TrialHandler.importConditions(this.psychoJS.serverManager, trialList); } - // unknown type: else { throw Object.assign(response, { - error: 'unable to prepare trial list: unknown type: ' + (typeof trialList) + error: "unable to prepare trial list: unknown type: " + (typeof trialList), }); } } - /* * Prepare the sequence of trials. * *

The returned sequence is a matrix (an array of arrays) of trial indices * with nStim columns and nReps rows. Note that this is the transpose of the * matrix return by PsychoPY. - * + * * Example: with 3 trial and 5 repetitions, we get: * - sequential: * [[0 1 2] @@ -711,8 +683,8 @@ export class TrialHandler extends PsychObject _prepareSequence() { const response = { - origin: 'TrialHandler._prepareSequence', - context: 'when preparing a sequence of trials' + origin: "TrialHandler._prepareSequence", + context: "when preparing a sequence of trials", }; // get an array of the indices of the elements of trialList : @@ -722,9 +694,8 @@ export class TrialHandler extends PsychObject { this._trialSequence = Array(this.nReps).fill(indices); // transposed version: - //this._trialSequence = indices.reduce( (seq, e) => { seq.push( Array(this.nReps).fill(e) ); return seq; }, [] ); + // this._trialSequence = indices.reduce( (seq, e) => { seq.push( Array(this.nReps).fill(e) ); return seq; }, [] ); } - else if (this.method === TrialHandler.Method.RANDOM) { this._trialSequence = []; @@ -733,7 +704,6 @@ export class TrialHandler extends PsychObject this._trialSequence.push(util.shuffle(indices.slice(), this._randomNumberGenerator)); } } - else if (this.method === TrialHandler.Method.FULL_RANDOM) { // create a flat sequence with nReps repeats of indices: @@ -755,15 +725,13 @@ export class TrialHandler extends PsychObject } else { - throw Object.assign(response, {error: 'unknown method'}); + throw Object.assign(response, { error: "unknown method" }); } return this._trialSequence; } - } - /** * TrialHandler method * @@ -775,20 +743,20 @@ TrialHandler.Method = { /** * Conditions are presented in the order they are given. */ - SEQUENTIAL: Symbol.for('SEQUENTIAL'), + SEQUENTIAL: Symbol.for("SEQUENTIAL"), /** * Conditions are shuffled within each repeat. */ - RANDOM: Symbol.for('RANDOM'), + RANDOM: Symbol.for("RANDOM"), /** * Conditions are fully randomised across all repeats. */ - FULL_RANDOM: Symbol.for('FULL_RANDOM'), + FULL_RANDOM: Symbol.for("FULL_RANDOM"), /** * Same as above, but named to reflect PsychoPy boileplate. */ - FULLRANDOM: Symbol.for('FULL_RANDOM') + FULLRANDOM: Symbol.for("FULL_RANDOM"), }; diff --git a/src/data/index.js b/src/data/index.js index e8a9929..fcd486e 100644 --- a/src/data/index.js +++ b/src/data/index.js @@ -1,3 +1,3 @@ -export * from './ExperimentHandler.js'; -export * from './TrialHandler.js'; -//export * from './Shelf.js'; +export * from "./ExperimentHandler.js"; +export * from "./TrialHandler.js"; +// export * from './Shelf.js'; From f96cff27d383236bb544f1558e7949eaa301a30c Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Fri, 9 Jul 2021 14:09:14 +0100 Subject: [PATCH 28/29] core: format --- src/core/EventManager.js | 64 ++--- src/core/GUI.js | 399 +++++++++++++++-------------- src/core/Keyboard.js | 94 +++---- src/core/Logger.js | 170 ++++++------- src/core/MinimalStim.js | 60 ++--- src/core/Mouse.js | 88 +++---- src/core/PsychoJS.js | 299 ++++++++++------------ src/core/ServerManager.js | 515 ++++++++++++++++++-------------------- src/core/Window.js | 139 +++++----- src/core/WindowMixin.js | 247 +++++++++--------- src/core/index.js | 20 +- 11 files changed, 962 insertions(+), 1133 deletions(-) diff --git a/src/core/EventManager.js b/src/core/EventManager.js index c2f7716..a291f1b 100644 --- a/src/core/EventManager.js +++ b/src/core/EventManager.js @@ -7,9 +7,8 @@ * @license Distributed under the terms of the MIT License */ -import {MonotonicClock, Clock} from '../util/Clock.js'; -import {PsychoJS} from './PsychoJS.js'; - +import { Clock, MonotonicClock } from "../util/Clock.js"; +import { PsychoJS } from "./PsychoJS.js"; /** * @class @@ -22,7 +21,6 @@ import {PsychoJS} from './PsychoJS.js'; */ export class EventManager { - constructor(psychoJS) { this._psychoJS = psychoJS; @@ -47,14 +45,13 @@ export class EventManager pressed: [0, 0, 0], clocks: [new Clock(), new Clock(), new Clock()], // time elapsed from last reset of the button.Clocks: - times: [0.0, 0.0, 0.0] + times: [0.0, 0.0, 0.0], }, // clock reset when mouse is moved: - moveClock: new Clock() + moveClock: new Clock(), }; } - /** * Get the list of keys pressed by the participant. * @@ -69,9 +66,9 @@ export class EventManager * @return {string[]} the list of keys that were pressed. */ getKeys({ - keyList = null, - timeStamped = false - } = {}) + keyList = null, + timeStamped = false, + } = {}) { if (keyList != null) { @@ -123,7 +120,6 @@ export class EventManager return keys; } - /** * @typedef EventManager.ButtonInfo * @property {Array.number} pressed - the status of each mouse button [left, center, right]: 1 for pressed, 0 for released @@ -151,7 +147,6 @@ export class EventManager return this._mouseInfo; } - /** * Clear all events from the event buffer. * @@ -166,7 +161,6 @@ export class EventManager this.clearKeys(); } - /** * Clear all keys from the key buffer. * @@ -179,7 +173,6 @@ export class EventManager this._keyBuffer = []; } - /** * Start the move clock. * @@ -193,7 +186,6 @@ export class EventManager { } - /** * Stop the move clock. * @@ -207,7 +199,6 @@ export class EventManager { } - /** * Reset the move clock. * @@ -221,7 +212,6 @@ export class EventManager { } - /** * Add various mouse listeners to the Pixi renderer of the {@link Window}. * @@ -246,7 +236,6 @@ export class EventManager this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - renderer.view.addEventListener("touchstart", (event) => { event.preventDefault(); @@ -261,7 +250,6 @@ export class EventManager this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - renderer.view.addEventListener("pointerup", (event) => { event.preventDefault(); @@ -273,7 +261,6 @@ export class EventManager this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - renderer.view.addEventListener("touchend", (event) => { event.preventDefault(); @@ -288,7 +275,6 @@ export class EventManager this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - renderer.view.addEventListener("pointermove", (event) => { event.preventDefault(); @@ -297,7 +283,6 @@ export class EventManager self._mouseInfo.pos = [event.offsetX, event.offsetY]; }, false); - renderer.view.addEventListener("touchmove", (event) => { event.preventDefault(); @@ -309,19 +294,16 @@ export class EventManager self._mouseInfo.pos = [touches[0].pageX, touches[0].pageY]; }, false); - // (*) wheel - renderer.view.addEventListener("wheel", event => + renderer.view.addEventListener("wheel", (event) => { self._mouseInfo.wheelRel[0] += event.deltaX; self._mouseInfo.wheelRel[1] += event.deltaY; this._psychoJS.experimentLogger.data("Mouse: wheel shift=(" + event.deltaX + "," + event.deltaY + "), pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - } - /** * Add key listeners to the document. * @@ -336,14 +318,14 @@ export class EventManager // add a keydown listener // note: IE11 is not happy with document.addEventListener window.addEventListener("keydown", (event) => -// document.addEventListener("keydown", (event) => + // document.addEventListener("keydown", (event) => { const timestamp = MonotonicClock.getReferenceTime(); let code = event.code; // take care of legacy Microsoft browsers (IE11 and pre-Chromium Edge): - if (typeof code === 'undefined') + if (typeof code === "undefined") { code = EventManager.keycode2w3c(event.keyCode); } @@ -352,17 +334,15 @@ export class EventManager code, key: event.key, keyCode: event.keyCode, - timestamp + timestamp, }); - self._psychoJS.logger.trace('keydown: ', event.key); - self._psychoJS.experimentLogger.data('Keydown: ' + event.key); + self._psychoJS.logger.trace("keydown: ", event.key); + self._psychoJS.experimentLogger.data("Keydown: " + event.key); event.stopPropagation(); }); - } - /** * Convert a keylist that uses pyglet key names to one that uses W3C KeyboardEvent.code values. *

This allows key lists that work in the builder environment to work in psychoJS web experiments.

@@ -378,7 +358,7 @@ export class EventManager let w3cKeyList = []; for (let i = 0; i < pygletKeyList.length; i++) { - if (typeof EventManager._pygletMap[pygletKeyList[i]] === 'undefined') + if (typeof EventManager._pygletMap[pygletKeyList[i]] === "undefined") { w3cKeyList.push(pygletKeyList[i]); } @@ -391,7 +371,6 @@ export class EventManager return w3cKeyList; } - /** * Convert a W3C Key Code into a pyglet key. * @@ -410,11 +389,10 @@ export class EventManager } else { - return 'N/A'; + return "N/A"; } } - /** * Convert a keycode to a W3C UI Event code. *

This is for legacy browsers.

@@ -432,7 +410,6 @@ export class EventManager } } - /** *

This map provides support for browsers that have not yet * adopted the W3C KeyboardEvent.code standard for detecting key presses. @@ -522,10 +499,9 @@ EventManager._keycodeMap = { 39: "ArrowRight", 40: "ArrowDown", 27: "Escape", - 32: "Space" + 32: "Space", }; - /** * This map associates pyglet key names to the corresponding W3C KeyboardEvent codes values. *

More information can be found [here]{@link https://www.w3.org/TR/uievents-code}

@@ -625,10 +601,9 @@ EventManager._pygletMap = { "num_multiply": "NumpadMultiply", "num_divide": "NumpadDivide", "num_equal": "NumpadEqual", - "num_numlock": "NumpadNumlock" + "num_numlock": "NumpadNumlock", }; - /** *

This map associates W3C KeyboardEvent.codes to the corresponding pyglet key names. * @@ -639,7 +614,6 @@ EventManager._pygletMap = { */ EventManager._reversePygletMap = {}; - /** * 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) * @@ -656,8 +630,8 @@ export class BuilderKeyResponse this.status = PsychoJS.Status.NOT_STARTED; this.keys = []; // the key(s) pressed - this.corr = 0; // was the resp correct this trial? (0=no, 1=yes) - this.rt = []; // response time(s) + this.corr = 0; // was the resp correct this trial? (0=no, 1=yes) + this.rt = []; // response time(s) this.clock = new Clock(); // we'll use this to measure the rt } } diff --git a/src/core/GUI.js b/src/core/GUI.js index 66e84ab..84c924f 100644 --- a/src/core/GUI.js +++ b/src/core/GUI.js @@ -8,14 +8,13 @@ * @license Distributed under the terms of the MIT License */ -import * as Tone from 'tone'; -import {PsychoJS} from './PsychoJS.js'; -import {ServerManager} from './ServerManager.js'; -import {Scheduler} from '../util/Scheduler.js'; -import {Clock} from '../util/Clock.js'; -import {ExperimentHandler} from '../data/ExperimentHandler.js'; -import * as util from '../util/Util.js'; - +import * as Tone from "tone"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { Clock } from "../util/Clock.js"; +import { Scheduler } from "../util/Scheduler.js"; +import * as util from "../util/Util.js"; +import { PsychoJS } from "./PsychoJS.js"; +import { ServerManager } from "./ServerManager.js"; /** * @class @@ -27,7 +26,6 @@ import * as util from '../util/Util.js'; */ export class GUI { - get dialogComponent() { return this._dialogComponent; @@ -46,7 +44,6 @@ export class GUI this._dialogScalingFactor = 0; } - /** *

Create a dialog box that (a) enables the participant to set some * experimental values (e.g. the session name), (b) shows progress of resource @@ -71,22 +68,21 @@ export class GUI * @param {String} options.title - name of the project */ DlgFromDict({ - logoUrl, - text, - dictionary, - title - }) + logoUrl, + text, + dictionary, + title, + }) { // get info from URL: const infoFromUrl = util.getUrlParameters(); - this._progressMsg = ' '; + this._progressMsg = " "; this._progressBarMax = 0; this._allResourcesDownloaded = false; this._requiredKeys = []; this._setRequiredKeys = new Map(); - // prepare PsychoJS component: this._dialogComponent = {}; this._dialogComponent.status = PsychoJS.Status.NOT_STARTED; @@ -104,122 +100,118 @@ 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 && - typeof self._psychoJS.config.experiment.license !== 'undefined' && - self._psychoJS.config.experiment.runMode === 'LICENSE' && - typeof self._psychoJS.config.experiment.license.institutionLogo !== 'undefined') + 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" + ) { logoUrl = self._psychoJS.config.experiment.license.institutionLogo; } // prepare jquery UI dialog box: - let htmlCode = - '

'; + let htmlCode = '
'; // uncomment for older version of the library: // htmlCode += '

⚠ 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).

'+ // logo: - if (typeof logoUrl === 'string') + if (typeof logoUrl === "string") { htmlCode += ''; } // information text: - if (typeof text === 'string' && text.length > 0) + if (typeof text === "string" && text.length > 0) { - htmlCode += '

' + text + '

'; + htmlCode += "

" + text + "

"; } - // 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]; + const keyId = "form-input-" + keyIdx; + + // only create an input if the key is not in the URL: + let inUrl = false; + const cleanedDictKey = key.trim().toLowerCase(); + infoFromUrl.forEach((urlValue, urlKey) => { - const value = dictionary[key]; - const keyId = 'form-input-' + keyIdx; - - // only create an input if the key is not in the URL: - let inUrl = false; - const cleanedDictKey = key.trim().toLowerCase(); - infoFromUrl.forEach((urlValue, urlKey) => - { - const cleanedUrlKey = urlKey.trim().toLowerCase(); - if (cleanedUrlKey === cleanedDictKey) - { - inUrl = true; - // break; - } - }); - - if (!inUrl) + const cleanedUrlKey = urlKey.trim().toLowerCase(); + if (cleanedUrlKey === cleanedDictKey) { - htmlCode += ''; + inUrl = true; + // break; + } + }); - // if the field is required: - if (key.slice(-1) === '*') + if (!inUrl) + { + htmlCode += '"; + + // if the field is required: + if (key.slice(-1) === "*") + { + self._requiredKeys.push(keyId); + } + + // if value is an array, we create a select drop-down menu: + if (Array.isArray(value)) + { + htmlCode += ''; - - // if the field is required, we add an empty option and select it: - if (key.slice(-1) === '*') - { - htmlCode += ''; - } - - for (const option of value) - { - htmlCode += ''; - } - - htmlCode += ''; - jQuery('#' + keyId).selectmenu({classes: {}}); + htmlCode += ""; } - // otherwise we use a single string input: - else /*if (typeof value === 'string')*/ - { - htmlCode += ''; - } + htmlCode += ""; + jQuery("#" + keyId).selectmenu({ classes: {} }); + } + // otherwise we use a single string input: + /*if (typeof value === 'string')*/ + else + { + htmlCode += ''; } } - ); + }); htmlCode += '

Fields marked with an asterisk (*) are required.

'; // add a progress bar: - htmlCode += '
' + self._progressMsg + '
'; + htmlCode += '
' + self._progressMsg + "
"; htmlCode += '
'; - // replace root by the html code: - const dialogElement = document.getElementById('root'); + const dialogElement = document.getElementById("root"); dialogElement.innerHTML = htmlCode; - // setup change event handlers for all required keys: this._requiredKeys.forEach((keyId) => + { + const input = document.getElementById(keyId); + if (input) { - const input = document.getElementById(keyId); - if (input) - { - input.oninput = (event) => GUI._onKeyChange(self, event); - } + input.oninput = (event) => GUI._onKeyChange(self, event); } - ); + }); // init and open the dialog box: - self._dialogComponent.button = 'Cancel'; + self._dialogComponent.button = "Cancel"; jQuery("#expDialog").dialog({ width: "500", @@ -233,68 +225,62 @@ export class GUI { id: "buttonCancel", text: "Cancel", - click: function () + click: function() { - self._dialogComponent.button = 'Cancel'; - jQuery("#expDialog").dialog('close'); - } + self._dialogComponent.button = "Cancel"; + jQuery("#expDialog").dialog("close"); + }, }, { id: "buttonOk", text: "Ok", - click: function () + click: function() { - // update dictionary: Object.keys(dictionary).forEach((key, keyIdx) => + { + const input = document.getElementById("form-input-" + keyIdx); + if (input) { - const input = document.getElementById('form-input-' + keyIdx); - if (input) - { - dictionary[key] = input.value; - } + dictionary[key] = input.value; } - ); + }); - - self._dialogComponent.button = 'OK'; - jQuery("#expDialog").dialog('close'); + 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(); - } - } + + // 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 () + close: function() { - //jQuery.unblockUI(); - jQuery(this).dialog('destroy').remove(); + // jQuery.unblockUI(); + jQuery(this).dialog("destroy").remove(); self._dialogComponent.status = PsychoJS.Status.FINISHED; - } - + }, }) - // change colour of title bar + // 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}); + // jQuery.blockUI({ message: "", baseZ: 1}); // show dialog box: - jQuery("#progressbar").progressbar({value: self._progressBarCurrentValue}); + jQuery("#progressbar").progressbar({ value: self._progressBarCurrentValue }); jQuery("#progressbar").progressbar("option", "max", self._progressBarMax); } @@ -309,7 +295,6 @@ export class GUI }; } - /** * @callback GUI.onOK */ @@ -329,14 +314,13 @@ export class GUI * @param {GUI.onOK} [options.onOK] - function called when the participant presses the OK button */ dialog({ - message, - warning, - error, - showOK = true, - onOK - } = {}) + message, + warning, + error, + showOK = true, + onOK, + } = {}) { - // close the previously opened dialog box, if there is one: const expDialog = jQuery("#expDialog"); if (expDialog.length) @@ -353,31 +337,30 @@ export class GUI let titleColour; // we are displaying an error: - if (typeof error !== 'undefined') + if (typeof error !== "undefined") { this._psychoJS.logger.fatal(util.toString(error)); // deal with null error: if (!error) { - error = 'Unspecified JavaScript error'; + error = "Unspecified JavaScript error"; } let errorCode = null; // go through the error stack and look for errorCode if there is one: - let stackCode = '
    '; + let stackCode = "
      "; while (true) { - - if (typeof error === 'object' && 'errorCode' in error) + if (typeof error === "object" && "errorCode" in error) { errorCode = error.errorCode; } - if (typeof error === 'object' && 'context' in error) + if (typeof error === "object" && "context" in error) { - stackCode += '
    • ' + error.context + '
    • '; + stackCode += "
    • " + error.context + "
    • "; error = error.error; } else @@ -388,11 +371,11 @@ export class GUI error = error.substring(1, 1000); } - stackCode += '
    • ' + error + '
    • '; + stackCode += "
    • " + error + "
    • "; break; } } - stackCode += '
    '; + stackCode += "
"; // if we found an errorCode, we replace the stack-based message by a more user-friendly one: if (errorCode) @@ -406,40 +389,37 @@ export class GUI htmlCode = '
'; htmlCode += '

Unfortunately we encountered the following error:

'; htmlCode += stackCode; - htmlCode += '

Try to run the experiment again. If the error persists, contact the experiment designer.

'; - htmlCode += '
'; + htmlCode += "

Try to run the experiment again. If the error persists, contact the experiment designer.

"; + htmlCode += "
"; - titleColour = 'red'; + titleColour = "red"; } } - // we are displaying a message: - else if (typeof message !== 'undefined') + else if (typeof message !== "undefined") { - htmlCode = '
' + - '

' + message + '

' + - '
'; - titleColour = 'green'; + htmlCode = '
' + + '

' + message + "

" + + "
"; + titleColour = "green"; } - // we are displaying a warning: - else if (typeof warning !== 'undefined') + else if (typeof warning !== "undefined") { - htmlCode = '
' + - '

' + warning + '

' + - '
'; - titleColour = 'orange'; + htmlCode = '
' + + '

' + warning + "

" + + "
"; + titleColour = "orange"; } - // replace root by the html code: - const dialogElement = document.getElementById('root'); + const dialogElement = document.getElementById("root"); dialogElement.innerHTML = htmlCode; // init and open the dialog box: const self = this; jQuery("#msgDialog").dialog({ - dialogClass: 'no-close', + dialogClass: "no-close", width: "500", @@ -452,23 +432,22 @@ export class GUI buttons: (!showOK) ? [] : [{ id: "buttonOk", text: "Ok", - click: function () + click: function() { jQuery(this).dialog("destroy").remove(); // execute callback function: - if (typeof onOK !== 'undefined') + if (typeof onOK !== "undefined") { onOK(); } - } + }, }], }) - // change colour of title bar + // change colour of title bar .prev(".ui-dialog-titlebar").css("background", titleColour); } - /** * Listener for resource event from the [Server Manager]{@link ServerManager}. * @@ -479,7 +458,7 @@ export class GUI */ _onResourceEvents(signal) { - this._psychoJS.logger.debug('signal: ' + util.toString(signal)); + this._psychoJS.logger.debug("signal: " + util.toString(signal)); // the download of the specified resources has started: if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCES) @@ -490,20 +469,20 @@ export class GUI this._progressBarCurrentValue = 0; } - // 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.'); + jQuery("#progressMsg").text("all resources downloaded."); this._updateOkButtonStatus(); } - // update progress bar: - else if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCE - || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) + else if ( + signal.message === ServerManager.Event.DOWNLOADING_RESOURCE + || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED + ) { - if (typeof this._progressBarCurrentValue === 'undefined') + if (typeof this._progressBarCurrentValue === "undefined") { this._progressBarCurrentValue = 0; } @@ -511,16 +490,15 @@ export class GUI if (signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) { - jQuery("#progressMsg").text('downloaded ' + (this._progressBarCurrentValue / 2) + ' / ' + (this._progressBarMax / 2)); + jQuery("#progressMsg").text("downloaded " + (this._progressBarCurrentValue / 2) + " / " + (this._progressBarMax / 2)); } else { - jQuery("#progressMsg").text('downloading ' + (this._progressBarCurrentValue / 2) + ' / ' + (this._progressBarMax / 2)); + jQuery("#progressMsg").text("downloading " + (this._progressBarCurrentValue / 2) + " / " + (this._progressBarMax / 2)); } // $("#progressMsg").text(signal.resource + ': downloaded.'); jQuery("#progressbar").progressbar("option", "value", this._progressBarCurrentValue); } - // unknown message: we just display it else { @@ -528,7 +506,6 @@ export class GUI } } - /** * Update the status of the OK button. * @@ -539,14 +516,17 @@ export class GUI */ _updateOkButtonStatus(changeFocus = true) { - if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL || (this._allResourcesDownloaded && this._setRequiredKeys && this._setRequiredKeys.size >= this._requiredKeys.length)) + if ( + this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL + || (this._allResourcesDownloaded && this._setRequiredKeys && this._setRequiredKeys.size >= this._requiredKeys.length) + ) { if (changeFocus) - { - jQuery("#buttonOk").button("option", "disabled", false).focus(); - } - else - { + { + jQuery("#buttonOk").button("option", "disabled", false).focus(); + } + else + { jQuery("#buttonOk").button("option", "disabled", false); } } @@ -563,7 +543,6 @@ export class GUI }); } - /** * Listener for change event for required keys. * @@ -579,7 +558,7 @@ export class GUI const element = event.target; const value = element.value; - if (typeof value !== 'undefined' && value.length > 0) + if (typeof value !== "undefined" && value.length > 0) { gui._setRequiredKeys.set(event.target, true); } @@ -591,7 +570,6 @@ export class GUI gui._updateOkButtonStatus(false); } - /** * Get a more user-friendly html message. * @@ -606,91 +584,111 @@ export class GUI // INTERNAL_ERROR case 1: return { - htmlCode: '

Oops we encountered an internal server error.

Try to run the experiment again. If the error persists, contact the experiment designer.

', - titleColour: 'red' + htmlCode: + '

Oops we encountered an internal server error.

Try to run the experiment again. If the error persists, contact the experiment designer.

', + titleColour: "red", }; // MONGODB_ERROR + case 2: return { - htmlCode: '

Oops we encountered a database error.

Try to run the experiment again. If the error persists, contact the experiment designer.

', - titleColour: 'red' + htmlCode: + '

Oops we encountered a database error.

Try to run the experiment again. If the error persists, contact the experiment designer.

', + titleColour: "red", }; // STATUS_NONE + case 20: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} does not have any status and cannot be run.

If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

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.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} does not have any status and cannot be run.

If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

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.

`, + titleColour: "orange", }; // STATUS_INACTIVE + case 21: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} is currently inactive and cannot be run.

If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

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.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} is currently inactive and cannot be run.

If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

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.

`, + titleColour: "orange", }; // STATUS_DELETED + case 22: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} has been deleted and cannot be run.

If you are the experiment designer, either go to your experiment page and change the experiment status to either PILOTING or RUNNING, or generate a new experiment.

Otherwise please contact the experiment designer to let him or her know that the experiment has been deleted and cannot be run any longer.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} has been deleted and cannot be run.

If you are the experiment designer, either go to your experiment page and change the experiment status to either PILOTING or RUNNING, or generate a new experiment.

Otherwise please contact the experiment designer to let him or her know that the experiment has been deleted and cannot be run any longer.

`, + titleColour: "orange", }; // STATUS_ARCHIVED + case 23: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} has been archived and cannot be run.

If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

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.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} has been archived and cannot be run.

If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

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.

`, + titleColour: "orange", }; // PILOTING_NO_TOKEN + case 30: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} is currently in PILOTING mode but the pilot token is missing from the URL.

If you are the experiment designer, you can pilot it by pressing the pilot button on your experiment page.

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.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} is currently in PILOTING mode but the pilot token is missing from the URL.

If you are the experiment designer, you can pilot it by pressing the pilot button on your experiment page.

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.

`, + titleColour: "orange", }; // PILOTING_INVALID_TOKEN + case 31: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} cannot be run because the pilot token in the URL is invalid, possibly because it has expired.

If you are the experiment designer, you can generate a new token by pressing the pilot button on your experiment page.

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.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} cannot be run because the pilot token in the URL is invalid, possibly because it has expired.

If you are the experiment designer, you can generate a new token by pressing the pilot button on your experiment page.

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.

`, + titleColour: "orange", }; // LICENSE_EXPIRED + case 50: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} is covered by a license that has expired.

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 experiment page, where you will also be able to change its running mode to CREDIT.

Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license having expired.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} is covered by a license that has expired.

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 experiment page, where you will also be able to change its running mode to CREDIT.

Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license having expired.

`, + titleColour: "orange", }; // LICENSE_APPROVAL_NEEDED + case 51: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} is covered by a license that requires one or more documents to be approved before the experiment can be run.

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 experiment page.

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.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} is covered by a license that requires one or more documents to be approved before the experiment can be run.

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 experiment page.

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.

`, + titleColour: "orange", }; // CREDIT_NOT_ENOUGH + case 60: return { - htmlCode: `

${this._psychoJS.config.experiment.fullpath} does not have any assigned credit left and cannot be run.

If you are the experiment designer, you can assign more credits to it on your experiment page.

Otherwise please contact the experiment designer to let him or her know that the experiment requires more assigned credits to run.

`, - titleColour: 'orange' + htmlCode: + `

${this._psychoJS.config.experiment.fullpath} does not have any assigned credit left and cannot be run.

If you are the experiment designer, you can assign more credits to it on your experiment page.

Otherwise please contact the experiment designer to let him or her know that the experiment requires more assigned credits to run.

`, + titleColour: "orange", }; default: return { - htmlCode: `

Unfortunately we encountered an unspecified error (error code: ${errorCode}.

Try to run the experiment again. If the error persists, contact the experiment designer.

`, - titleColour: 'red' + htmlCode: + `

Unfortunately we encountered an unspecified error (error code: ${errorCode}.

Try to run the experiment again. If the error persists, contact the experiment designer.

`, + titleColour: "red", }; } } - } - /** * Maximal dimensions of the dialog window. * @@ -701,7 +699,6 @@ export class GUI */ GUI.dialogMaxSize = [500, 600]; - /** * Dialog window margins. * diff --git a/src/core/Keyboard.js b/src/core/Keyboard.js index 1d09b5d..32d86bb 100644 --- a/src/core/Keyboard.js +++ b/src/core/Keyboard.js @@ -7,11 +7,10 @@ * @license Distributed under the terms of the MIT License */ -import {Clock, MonotonicClock} from "../util/Clock.js"; -import {PsychObject} from "../util/PsychObject.js"; -import {PsychoJS} from "./PsychoJS.js"; -import {EventManager} from "./EventManager.js"; - +import { Clock, MonotonicClock } from "../util/Clock.js"; +import { PsychObject } from "../util/PsychObject.js"; +import { EventManager } from "./EventManager.js"; +import { PsychoJS } from "./PsychoJS.js"; /** * @name module:core.KeyPress @@ -27,7 +26,7 @@ export class KeyPress { this.code = code; this.tDown = tDown; - this.name = (typeof name !== 'undefined') ? name : EventManager.w3c2pyglet(code); + this.name = (typeof name !== "undefined") ? name : EventManager.w3c2pyglet(code); // duration of the keypress (time between keydown and keyup events) or undefined if there was no keyup this.duration = undefined; @@ -37,7 +36,6 @@ export class KeyPress } } - /** *

This manager handles all keyboard events. It is a substitute for the keyboard component of EventManager.

* @@ -53,39 +51,35 @@ export class KeyPress */ export class Keyboard extends PsychObject { - constructor({ - psychoJS, - bufferSize = 10000, - waitForStart = false, - clock, - autoLog = false, - } = {}) + psychoJS, + bufferSize = 10000, + waitForStart = false, + clock, + autoLog = false, + } = {}) { - super(psychoJS); - if (typeof clock === 'undefined') + if (typeof clock === "undefined") { clock = new Clock(); - } //this._psychoJS.monotonicClock; + } // this._psychoJS.monotonicClock; - this._addAttribute('bufferSize', bufferSize); - this._addAttribute('waitForStart', waitForStart); - this._addAttribute('clock', clock); - this._addAttribute('autoLog', autoLog); + this._addAttribute("bufferSize", bufferSize); + this._addAttribute("waitForStart", waitForStart); + this._addAttribute("clock", clock); + this._addAttribute("autoLog", autoLog); // start recording key events if need be: - this._addAttribute('status', (waitForStart) ? PsychoJS.Status.NOT_STARTED : PsychoJS.Status.STARTED); + this._addAttribute("status", (waitForStart) ? PsychoJS.Status.NOT_STARTED : PsychoJS.Status.STARTED); // setup circular buffer: this.clearEvents(); // add key listeners: this._addKeyListeners(); - } - /** * Start recording keyboard events. * @@ -99,7 +93,6 @@ export class Keyboard extends PsychObject this._status = PsychoJS.Status.STARTED; } - /** * Stop recording keyboard events. * @@ -113,7 +106,6 @@ export class Keyboard extends PsychObject this._status = PsychoJS.Status.STOPPED; } - /** * @typedef Keyboard.KeyEvent * @@ -139,7 +131,6 @@ export class Keyboard extends PsychObject return []; } - // iterate over the buffer, from start to end, and discard the null event: let filteredEvents = []; const bufferWrap = (this._bufferLength === this._bufferSize); @@ -157,7 +148,6 @@ export class Keyboard extends PsychObject return filteredEvents; } - /** * Get the list of keys pressed or pushed by the participant. * @@ -174,12 +164,11 @@ export class Keyboard extends PsychObject * (keydown with no subsequent keyup at the time getKeys is called). */ getKeys({ - keyList = [], - waitRelease = true, - clear = true - } = {}) + keyList = [], + waitRelease = true, + clear = true, + } = {}) { - // if nothing in the buffer, return immediately: if (this._bufferLength === 0) { @@ -203,7 +192,7 @@ export class Keyboard extends PsychObject { // look for a corresponding, preceding keydown event: const precedingKeydownIndex = keyEvent.keydownIndex; - if (typeof precedingKeydownIndex !== 'undefined') + if (typeof precedingKeydownIndex !== "undefined") { const precedingKeydownEvent = this._circularBuffer[precedingKeydownIndex]; if (precedingKeydownEvent) @@ -250,13 +239,10 @@ export class Keyboard extends PsychObject { this._circularBuffer[i] = null; } - } } - } while (i !== this._bufferIndex); - // if waitRelease = false, we iterate again over the map of unmatched keydown events: if (!waitRelease) { @@ -303,18 +289,15 @@ export class Keyboard extends PsychObject } while (i !== this._bufferIndex);*/ } - // if clear = true and the keyList is empty, we clear all the events: if (clear && keyList.length === 0) { this.clearEvents(); } - return keyPresses; } - /** * Clear all events and resets the circular buffers. * @@ -333,7 +316,6 @@ export class Keyboard extends PsychObject this._unmatchedKeydownMap = new Map(); } - /** * Test whether a list of KeyPress's contains one with a particular name. * @@ -352,10 +334,9 @@ export class Keyboard extends PsychObject } const value = keypressList.find((keypress) => keypress.name === keyName); - return (typeof value !== 'undefined'); + return (typeof value !== "undefined"); } - /** * Add key listeners to the document. * @@ -368,10 +349,9 @@ export class Keyboard extends PsychObject this._previousKeydownKey = undefined; const self = this; - // add a keydown listener: window.addEventListener("keydown", (event) => - // document.addEventListener("keydown", (event) => + // document.addEventListener("keydown", (event) => { // only consider non-repeat events, i.e. only the first keydown event associated with a participant // holding a key down: @@ -398,14 +378,13 @@ export class Keyboard extends PsychObject let code = event.code; // take care of legacy Microsoft browsers (IE11 and pre-Chromium Edge): - if (typeof code === 'undefined') + if (typeof code === "undefined") { code = EventManager.keycode2w3c(event.keyCode); } let pigletKey = EventManager.w3c2pyglet(code); - self._bufferIndex = (self._bufferIndex + 1) % self._bufferSize; self._bufferLength = Math.min(self._bufferLength + 1, self._bufferSize); self._circularBuffer[self._bufferIndex] = { @@ -413,20 +392,19 @@ export class Keyboard extends PsychObject key: event.key, pigletKey, status: Keyboard.KeyStatus.KEY_DOWN, - timestamp + timestamp, }; self._unmatchedKeydownMap.set(event.code, self._bufferIndex); - self._psychoJS.logger.trace('keydown: ', event.key); + self._psychoJS.logger.trace("keydown: ", event.key); event.stopPropagation(); }); - // add a keyup listener: window.addEventListener("keyup", (event) => - // document.addEventListener("keyup", (event) => + // document.addEventListener("keyup", (event) => { const timestamp = MonotonicClock.getReferenceTime(); // timestamp in seconds @@ -440,7 +418,7 @@ export class Keyboard extends PsychObject let code = event.code; // take care of legacy Microsoft Edge: - if (typeof code === 'undefined') + if (typeof code === "undefined") { code = EventManager.keycode2w3c(event.keyCode); } @@ -454,28 +432,26 @@ export class Keyboard extends PsychObject key: event.key, pigletKey, status: Keyboard.KeyStatus.KEY_UP, - timestamp + timestamp, }; // get the corresponding keydown event // note: if more keys are down than there are slots in the circular buffer, there might // not be a corresponding keydown event const correspondingKeydownIndex = self._unmatchedKeydownMap.get(event.code); - if (typeof correspondingKeydownIndex !== 'undefined') + if (typeof correspondingKeydownIndex !== "undefined") { self._circularBuffer[self._bufferIndex].keydownIndex = correspondingKeydownIndex; self._unmatchedKeydownMap.delete(event.code); } - self._psychoJS.logger.trace('keyup: ', event.key); + self._psychoJS.logger.trace("keyup: ", event.key); event.stopPropagation(); }); - } } - /** * Keyboard KeyStatus. * @@ -485,6 +461,6 @@ export class Keyboard extends PsychObject * @public */ Keyboard.KeyStatus = { - KEY_DOWN: Symbol.for('KEY_DOWN'), - KEY_UP: Symbol.for('KEY_UP') + KEY_DOWN: Symbol.for("KEY_DOWN"), + KEY_UP: Symbol.for("KEY_UP"), }; diff --git a/src/core/Logger.js b/src/core/Logger.js index 603e73d..a244073 100644 --- a/src/core/Logger.js +++ b/src/core/Logger.js @@ -7,12 +7,11 @@ * @license Distributed under the terms of the MIT License */ - -import log4javascript from 'log4javascript'; -import pako from 'pako'; -import * as util from '../util/Util.js'; -import {MonotonicClock} from '../util/Clock.js'; -import {ExperimentHandler} from '../data/ExperimentHandler.js'; +import log4javascript from "log4javascript"; +import pako from "pako"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { MonotonicClock } from "../util/Clock.js"; +import * as util from "../util/Util.js"; /** *

This class handles a variety of loggers, e.g. a browser console one (mostly for debugging), @@ -26,13 +25,12 @@ import {ExperimentHandler} from '../data/ExperimentHandler.js'; */ export class Logger { - constructor(psychoJS, threshold) { this._psychoJS = psychoJS; // browser console logger: - this.consoleLogger = log4javascript.getLogger('psychojs'); + this.consoleLogger = log4javascript.getLogger("psychojs"); const appender = new log4javascript.BrowserConsoleAppender(); appender.setLayout(this._customConsoleLayout()); @@ -41,7 +39,6 @@ export class Logger this.consoleLogger.addAppender(appender); this.consoleLogger.setLevel(threshold); - // server logger: this._serverLogs = []; this._serverLevel = Logger.ServerLevel.WARNING; @@ -65,12 +62,10 @@ export class Logger // throttling message index: index: 0, // whether or not the designer has already been warned: - designerWasWarned: false + designerWasWarned: false, }; } - - /** * Change the logging level. * @@ -84,8 +79,6 @@ export class Logger this._serverLevelValue = this._getValue(this._serverLevel); } - - /** * Log a server message at the EXP level. * @@ -100,8 +93,6 @@ export class Logger this.log(msg, Logger.ServerLevel.EXP, time, obj); } - - /** * Log a server message at the DATA level. * @@ -116,8 +107,6 @@ export class Logger this.log(msg, Logger.ServerLevel.DATA, time, obj); } - - /** * Log a server message. * @@ -137,7 +126,7 @@ export class Logger return; } - if (typeof time === 'undefined') + if (typeof time === "undefined") { time = MonotonicClock.getReferenceTime(); } @@ -154,12 +143,10 @@ export class Logger msg, level, time, - obj: util.toString(obj) + obj: util.toString(obj), }); } - - /** * Check whether or not a log messages must be throttled. * @@ -181,24 +168,26 @@ export class Logger // warn the designer if we are not already throttling: if (!this._throttling.isThrottling) { - const msg = `

[time= ${time.toFixed(3)}] More than ${this._throttling.threshold} messages were logged in the past ${this._throttling.window}s.

` + - `

We are now throttling: only 1 in ${this._throttling.factor} messages will be logged.

` + - `

You may want to change your experiment's logging level. Please see psychopy.org/api/logging.html for details.

`; + const msg = `

[time= ${time.toFixed(3)}] More than ${this._throttling.threshold} messages were logged in the past ${this._throttling.window}s.

` + + `

We are now throttling: only 1 in ${this._throttling.factor} messages will be logged.

` + + `

You may want to change your experiment's logging level. Please see psychopy.org/api/logging.html for details.

`; // console warning: this._psychoJS.logger.warn(msg); // in PILOTING mode and locally, we also warn the experimenter with a dialog box, // but only once: - if (!this._throttling.designerWasWarned && - (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL || - this._psychoJS.config.experiment.status === 'PILOTING')) + if ( + !this._throttling.designerWasWarned + && (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL + || this._psychoJS.config.experiment.status === "PILOTING") + ) { this._throttling.designerWasWarned = true; this._psychoJS.gui.dialog({ warning: msg, - showOK: true + showOK: true, }); } @@ -207,7 +196,7 @@ export class Logger this._throttling.index = 0; } - ++ this._throttling.index; + ++this._throttling.index; if (this._throttling.index < this._throttling.factor) { // no logging @@ -220,8 +209,10 @@ export class Logger } else { - if (this._throttling.isThrottling && - (time - this._throttling.startOfThrottling) > this._throttling.minimumDuration) + if ( + this._throttling.isThrottling + && (time - this._throttling.startOfThrottling) > this._throttling.minimumDuration + ) { this._psychoJS.logger.info(`[time= ${time.toFixed(3)}] Log messages are not throttled any longer.`); this._throttling.isThrottling = false; @@ -232,8 +223,6 @@ export class Logger return false; } - - /** * Flush all server logs to the server. * @@ -246,39 +235,41 @@ export class Logger async flush() { const response = { - origin: 'Logger.flush', - context: 'when flushing participant\'s logs for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "Logger.flush", + context: "when flushing participant's logs for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.info('[PsychoJS] Flush server logs.'); + this._psychoJS.logger.info("[PsychoJS] Flush server logs."); // prepare the formatted logs: - let formattedLogs = ''; + let formattedLogs = ""; for (const log of this._serverLogs) { - let formattedLog = util.toString(log.time) + - '\t' + Symbol.keyFor(log.level) + - '\t' + log.msg; - if (log.obj !== 'undefined') + let formattedLog = util.toString(log.time) + + "\t" + Symbol.keyFor(log.level) + + "\t" + log.msg; + if (log.obj !== "undefined") { - formattedLog += '\t' + log.obj; + formattedLog += "\t" + log.obj; } - formattedLog += '\n'; + formattedLog += "\n"; formattedLogs += formattedLog; } // send logs to the server or display them in the console: - if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && - this._psychoJS.config.experiment.status === 'RUNNING' && - !this._psychoJS._serverMsg.has('__pilotToken')) + if ( + this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER + && this._psychoJS.config.experiment.status === "RUNNING" + && !this._psychoJS._serverMsg.has("__pilotToken") + ) { // if the pako compression library is present, we compress the logs: - if (typeof pako !== 'undefined') + if (typeof pako !== "undefined") { try { - const utf16DeflatedLogs = pako.deflate(formattedLogs, {to: 'string'}); + const utf16DeflatedLogs = pako.deflate(formattedLogs, { to: "string" }); // const utf16DeflatedLogs = pako.deflate(unescape(encodeURIComponent(formattedLogs)), {to: 'string'}); const base64DeflatedLogs = btoa(utf16DeflatedLogs); @@ -286,24 +277,22 @@ export class Logger } catch (error) { - console.error('log compression error:', error); - throw Object.assign(response, {error: error}); + console.error("log compression error:", error); + throw Object.assign(response, { error: error }); } } - else // the pako compression library is not present, we do not compress the logs: + else { return await this._psychoJS.serverManager.uploadLog(formattedLogs, false); } } else { - this._psychoJS.logger.debug('\n' + formattedLogs); + this._psychoJS.logger.debug("\n" + formattedLogs); } } - - /** * Create a custom console layout. * @@ -316,59 +305,59 @@ export class Logger const detectedBrowser = util.detectBrowser(); const customLayout = new log4javascript.PatternLayout("%p %d{HH:mm:ss.SSS} %f{1} | %m"); - customLayout.setCustomField('location', function (layout, loggingReference) + customLayout.setCustomField("location", function(layout, loggingReference) { // we throw a fake exception to retrieve the stack trace try { // (0)(); - throw Error('fake exception'); + throw Error("fake exception"); } catch (e) { - const stackEntries = e.stack.replace(/^.*?\n/, '').replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anon}(').split("\n"); + const stackEntries = e.stack.replace(/^.*?\n/, "").replace(/(?:\n@:0)?\s+$/m, "").replace(/^\(/gm, "{anon}(").split("\n"); let relevantEntry; - if (detectedBrowser === 'Firefox') + if (detectedBrowser === "Firefox") { // look for entry immediately after those of log4javascript: for (let entry of stackEntries) { - if (entry.indexOf('log4javascript.min.js') <= 0) + if (entry.indexOf("log4javascript.min.js") <= 0) { relevantEntry = entry; break; } } - const buf = relevantEntry.split(':'); + const buf = relevantEntry.split(":"); const line = buf[buf.length - 2]; - const file = buf[buf.length - 3].split('/').pop(); - const method = relevantEntry.split('@')[0]; + const file = buf[buf.length - 3].split("/").pop(); + const method = relevantEntry.split("@")[0]; - return method + ' ' + file + ':' + line; + return method + " " + file + ":" + line; } - else if (detectedBrowser === 'Safari') + else if (detectedBrowser === "Safari") { - return 'unknown'; + return "unknown"; } - else if (detectedBrowser === 'Chrome') + else if (detectedBrowser === "Chrome") { relevantEntry = stackEntries.pop(); - let buf = relevantEntry.split(' '); + let buf = relevantEntry.split(" "); let fileLine = buf.pop(); const method = buf.pop(); - buf = fileLine.split(':'); + buf = fileLine.split(":"); buf.pop(); const line = buf.pop(); - const file = buf.pop().split('/').pop(); + const file = buf.pop().split("/").pop(); - return method + ' ' + file + ':' + line; + return method + " " + file + ":" + line; } else { - return 'unknown'; + return "unknown"; } } }); @@ -376,8 +365,6 @@ export class Logger return customLayout; } - - /** * Get the integer value associated with a logging level. * @@ -393,8 +380,6 @@ export class Logger } } - - /** * Server logging level. * @@ -406,17 +391,16 @@ export class Logger * @note These are similar to PsychoPy's logging levels, as defined in logging.py */ Logger.ServerLevel = { - CRITICAL: Symbol.for('CRITICAL'), - ERROR: Symbol.for('ERROR'), - WARNING: Symbol.for('WARNING'), - DATA: Symbol.for('DATA'), - EXP: Symbol.for('EXP'), - INFO: Symbol.for('INFO'), - DEBUG: Symbol.for('DEBUG'), - NOTSET: Symbol.for('NOTSET') + CRITICAL: Symbol.for("CRITICAL"), + ERROR: Symbol.for("ERROR"), + WARNING: Symbol.for("WARNING"), + DATA: Symbol.for("DATA"), + EXP: Symbol.for("EXP"), + INFO: Symbol.for("INFO"), + DEBUG: Symbol.for("DEBUG"), + NOTSET: Symbol.for("NOTSET"), }; - /** * Server logging level values. * @@ -428,12 +412,12 @@ Logger.ServerLevel = { * @protected */ Logger._ServerLevelValue = { - 'CRITICAL': 50, - 'ERROR': 40, - 'WARNING': 30, - 'DATA': 25, - 'EXP': 22, - 'INFO': 20, - 'DEBUG': 10, - 'NOTSET': 0 + "CRITICAL": 50, + "ERROR": 40, + "WARNING": 30, + "DATA": 25, + "EXP": 22, + "INFO": 20, + "DEBUG": 10, + "NOTSET": 0, }; diff --git a/src/core/MinimalStim.js b/src/core/MinimalStim.js index b459cc9..215ed44 100644 --- a/src/core/MinimalStim.js +++ b/src/core/MinimalStim.js @@ -7,12 +7,9 @@ * @license Distributed under the terms of the MIT License */ - -import {PsychObject} from '../util/PsychObject.js'; -import {PsychoJS} from './PsychoJS.js'; -import * as util from '../util/Util.js'; - - +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; +import { PsychoJS } from "./PsychoJS.js"; /** *

MinimalStim is the base class for all stimuli.

@@ -28,7 +25,7 @@ import * as util from '../util/Util.js'; */ export class MinimalStim extends PsychObject { - constructor({name, win, autoDraw, autoLog} = {}) + constructor({ name, win, autoDraw, autoLog } = {}) { super(win._psychoJS, name); @@ -36,27 +33,25 @@ export class MinimalStim extends PsychObject this._pixi = undefined; this._addAttribute( - 'win', + "win", win, - undefined + undefined, ); this._addAttribute( - 'autoDraw', + "autoDraw", autoDraw, - false + false, ); this._addAttribute( - 'autoLog', + "autoLog", autoLog, - (typeof win !== 'undefined' && win !== null) ? win.autoLog : false + (typeof win !== "undefined" && win !== null) ? win.autoLog : false, ); this._needUpdate = false; this.status = PsychoJS.Status.NOT_STARTED; } - - /** * Setter for the autoDraw attribute. * @@ -68,14 +63,13 @@ export class MinimalStim extends PsychObject */ setAutoDraw(autoDraw, log = false) { - this._setAttribute('autoDraw', autoDraw, log); + this._setAttribute("autoDraw", autoDraw, log); // autoDraw = true: add the stimulus to the draw list if it's not there already if (this._autoDraw) { this.draw(); } - // autoDraw = false: remove the stimulus from the draw list (and from the root container if it's already there) else { @@ -83,8 +77,6 @@ export class MinimalStim extends PsychObject } } - - /** * Draw this stimulus on the next frame draw. * @@ -103,9 +95,9 @@ export class MinimalStim extends PsychObject { // update the stimulus if need be before we add its PIXI representation to the window container: this._updateIfNeeded(); - if (typeof this._pixi === 'undefined') + if (typeof this._pixi === "undefined") { - this.psychoJS.logger.warn('the Pixi.js representation of this stimulus is undefined.'); + this.psychoJS.logger.warn("the Pixi.js representation of this stimulus is undefined."); } else { @@ -117,7 +109,7 @@ export class MinimalStim extends PsychObject { // the stimulus is already in the list, if it needs to be updated, we remove it // from the window container, update it, then put it back: - if (this._needUpdate && typeof this._pixi !== 'undefined') + if (this._needUpdate && typeof this._pixi !== "undefined") { this.win._rootContainer.removeChild(this._pixi); this._updateIfNeeded(); @@ -129,8 +121,6 @@ export class MinimalStim extends PsychObject this.status = PsychoJS.Status.STARTED; } - - /** * Hide this stimulus on the next frame draw. * @@ -148,7 +138,7 @@ export class MinimalStim extends PsychObject this._win._drawList.splice(index, 1); // if the stimulus has a pixi representation, remove it from the root container: - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._win._rootContainer.removeChild(this._pixi); } @@ -157,8 +147,6 @@ export class MinimalStim extends PsychObject } } - - /** * Determine whether an object is inside this stimulus. * @@ -172,14 +160,12 @@ export class MinimalStim extends PsychObject contains(object, units) { throw { - origin: 'MinimalStim.contains', + origin: "MinimalStim.contains", context: `when determining whether stimulus: ${this._name} contains object: ${util.toString(object)}`, - error: 'this method is abstract and should not be called.' + error: "this method is abstract and should not be called.", }; } - - /** * Release the PIXI representation, if there is one. * @@ -191,18 +177,16 @@ export class MinimalStim extends PsychObject */ release(log = false) { - this._setAttribute('autoDraw', false, log); + this._setAttribute("autoDraw", false, log); this.status = PsychoJS.Status.STOPPED; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); this._pixi = undefined; } } - - /** * Update the stimulus, if necessary. * @@ -216,9 +200,9 @@ export class MinimalStim extends PsychObject _updateIfNeeded() { throw { - origin: 'MinimalStim._updateIfNeeded', - context: 'when updating stimulus: ' + this._name, - error: 'this method is abstract and should not be called.' + origin: "MinimalStim._updateIfNeeded", + context: "when updating stimulus: " + this._name, + error: "this method is abstract and should not be called.", }; } } diff --git a/src/core/Mouse.js b/src/core/Mouse.js index 6effb21..ab85431 100644 --- a/src/core/Mouse.js +++ b/src/core/Mouse.js @@ -8,10 +8,9 @@ * @license Distributed under the terms of the MIT License */ -import {PsychoJS} from './PsychoJS.js'; -import {PsychObject} from '../util/PsychObject.js'; -import * as util from '../util/Util.js'; - +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; +import { PsychoJS } from "./PsychoJS.js"; /** *

This manager handles the interactions between the experiment's stimuli and the mouse.

@@ -29,12 +28,11 @@ import * as util from '../util/Util.js'; */ export class Mouse extends PsychObject { - constructor({ - name, - win, - autoLog = true - } = {}) + name, + win, + autoLog = true, + } = {}) { super(win._psychoJS, name); @@ -45,15 +43,14 @@ export class Mouse extends PsychObject const units = win.units; const visible = 1; - this._addAttribute('win', win); - this._addAttribute('units', units); - this._addAttribute('visible', visible); - this._addAttribute('autoLog', autoLog); + this._addAttribute("win", win); + this._addAttribute("units", units); + this._addAttribute("visible", visible); + this._addAttribute("autoLog", autoLog); this.status = PsychoJS.Status.NOT_STARTED; } - /** * Get the current position of the mouse in mouse/Window units. * @@ -73,12 +70,11 @@ export class Mouse extends PsychObject pos_px[1] = this.win.size[1] / 2 - pos_px[1]; // convert to window units: - this._lastPos = util.to_win(pos_px, 'pix', this._win); + this._lastPos = util.to_win(pos_px, "pix", this._win); return this._lastPos; } - /** * Get the position of the mouse relative to that at the last call to getRel * or getPos, in mouse/Window units. @@ -90,7 +86,7 @@ export class Mouse extends PsychObject */ getRel() { - if (typeof this._lastPos === 'undefined') + if (typeof this._lastPos === "undefined") { return this.getPos(); } @@ -103,7 +99,6 @@ export class Mouse extends PsychObject } } - /** * Get the travel of the mouse scroll wheel since the last call to getWheelRel. * @@ -121,13 +116,12 @@ export class Mouse extends PsychObject const wheelRel_px = mouseInfo.wheelRel.slice(); // convert to window units: - const wheelRel = util.to_win(wheelRel_px, 'pix', this._win); + const wheelRel = util.to_win(wheelRel_px, "pix", this._win); mouseInfo.wheelRel = [0, 0]; return wheelRel; } - /** * Get the status of each button (pressed or released) and, optionally, the time elapsed between the last call to [clickReset]{@link module:core.Mouse#clickReset} and the pressing or releasing of the buttons. * @@ -153,7 +147,6 @@ export class Mouse extends PsychObject } } - /** * Helper method for checking whether a stimulus has had any button presses within bounds. * @@ -170,14 +163,14 @@ export class Mouse extends PsychObject isPressedIn(...args) { // Look for options given in object literal form, cut out falsy inputs - const [{ shape: shapeMaybe, buttons: buttonsMaybe } = {}] = args.filter(v => !!v); + const [{ shape: shapeMaybe, buttons: buttonsMaybe } = {}] = args.filter((v) => !!v); // Helper to check if some object features a certain key - const hasKey = key => object => !!(object && object[key]); + const hasKey = (key) => (object) => !!(object && object[key]); // Shapes are expected to be instances of stimuli, or at // the very least objects featuring a `contains()` method - const isShape = hasKey('contains'); + const isShape = hasKey("contains"); // Go through arguments array looking for a shape if options object offers none const shapeFound = isShape(shapeMaybe) ? shapeMaybe : args.find(isShape); @@ -187,23 +180,23 @@ export class Mouse extends PsychObject // Buttons values may be extracted from an object // featuring the `buttons` key, or found as integers // in the arguments array - const hasButtons = hasKey('buttons'); + const hasButtons = hasKey("buttons"); const { isInteger } = Number; // Prioritize buttons value given as part of an options object, // then look for the first occurrence in the arguments array of either // an integer or an extra object with a `buttons` key - const buttonsFound = isInteger(buttonsMaybe) ? buttonsMaybe : args.find(o => hasButtons(o) || isInteger(o)); + const buttonsFound = isInteger(buttonsMaybe) ? buttonsMaybe : args.find((o) => hasButtons(o) || isInteger(o)); // Worst case scenario `wanted` ends up being an empty object const { buttons: wanted = buttonsFound || buttonsMaybe } = buttonsFound || {}; // Will throw if stimulus is falsy or non-object like - if (typeof shape.contains === 'function') + if (typeof shape.contains === "function") { const mouseInfo = this.psychoJS.eventManager.getMouseInfo(); const { pressed } = mouseInfo.buttons; // If no specific button wanted, any pressed will do - const hasButtonPressed = isInteger(wanted) ? pressed[wanted] > 0 : pressed.some(v => v > 0); + const hasButtonPressed = isInteger(wanted) ? pressed[wanted] > 0 : pressed.some((v) => v > 0); return hasButtonPressed && shape.contains(this); } @@ -211,7 +204,6 @@ export class Mouse extends PsychObject return false; } - /** * Determine whether the mouse has moved beyond a certain distance. * @@ -240,24 +232,26 @@ export class Mouse extends PsychObject mouseMoved(distance, reset = false) { // make sure that _lastPos is defined: - if (typeof this._lastPos === 'undefined') + if (typeof this._lastPos === "undefined") { this.getPos(); } this._prevPos = this._lastPos.slice(); this.getPos(); - if (typeof reset === 'boolean' && reset == false) + if (typeof reset === "boolean" && reset == false) { - if (typeof distance === 'undefined') + if (typeof distance === "undefined") { return (this._prevPos[0] != this._lastPos[0]) || (this._prevPos[1] != this._lastPos[1]); } else { - if (typeof distance === 'number') + if (typeof distance === "number") { - this._movedistance = Math.sqrt((this._prevPos[0] - this._lastPos[0]) * (this._prevPos[0] - this._lastPos[0]) + (this._prevPos[1] - this._lastPos[1]) * (this._prevPos[1] - this._lastPos[1])); + this._movedistance = Math.sqrt( + (this._prevPos[0] - this._lastPos[0]) * (this._prevPos[0] - this._lastPos[0]) + (this._prevPos[1] - this._lastPos[1]) * (this._prevPos[1] - this._lastPos[1]), + ); return (this._movedistance > distance); } if (this._prevPos[0] + distance[0] - this._lastPos[0] > 0.0) @@ -271,21 +265,18 @@ export class Mouse extends PsychObject return false; } } - - else if (typeof reset === 'boolean' && reset == true) + else if (typeof reset === "boolean" && reset == true) { // reset the moveClock: this.psychoJS.eventManager.getMouseInfo().moveClock.reset(); return false; } - - else if (reset === 'here') + else if (reset === "here") { // set to wherever we are this._prevPos = this._lastPos.clone(); return false; } - else if (reset instanceof Array) { // an (x,y) array @@ -294,36 +285,37 @@ export class Mouse extends PsychObject if (!distance) { return false; - }// just resetting prevPos, not checking distance + } + // just resetting prevPos, not checking distance else { // checking distance of current pos to newly reset prevposition - if (typeof distance === 'number') + if (typeof distance === "number") { - this._movedistance = Math.sqrt((this._prevPos[0] - this._lastPos[0]) * (this._prevPos[0] - this._lastPos[0]) + (this._prevPos[1] - this._lastPos[1]) * (this._prevPos[1] - this._lastPos[1])); + this._movedistance = Math.sqrt( + (this._prevPos[0] - this._lastPos[0]) * (this._prevPos[0] - this._lastPos[0]) + (this._prevPos[1] - this._lastPos[1]) * (this._prevPos[1] - this._lastPos[1]), + ); return (this._movedistance > distance); } if (Math.abs(this._lastPos[0] - this._prevPos[0]) > distance[0]) { return true; - } // moved on X-axis + } // moved on X-axis if (Math.abs(this._lastPos[1] - this._prevPos[1]) > distance[1]) { return true; - } // moved on Y-axis + } // moved on Y-axis return false; } } - else { return false; } } - /** * Get the amount of time elapsed since the last mouse movement. * @@ -337,7 +329,6 @@ export class Mouse extends PsychObject return this.psychoJS.eventManager.getMouseInfo().moveClock.getTime(); } - /** * Reset the clocks associated to the given mouse buttons. * @@ -355,7 +346,4 @@ export class Mouse extends PsychObject mouseInfo.buttons.times[b] = 0.0; } } - - } - diff --git a/src/core/PsychoJS.js b/src/core/PsychoJS.js index dc60025..b5bf92f 100644 --- a/src/core/PsychoJS.js +++ b/src/core/PsychoJS.js @@ -8,19 +8,18 @@ * @license Distributed under the terms of the MIT License */ -import log4javascript from 'log4javascript'; -import {Scheduler} from '../util/Scheduler.js'; -import {ServerManager} from './ServerManager.js'; -import {ExperimentHandler} from '../data/ExperimentHandler.js'; -import {EventManager} from './EventManager.js'; -import {Window} from './Window.js'; -import {GUI} from './GUI.js'; -import {MonotonicClock} from '../util/Clock.js'; -import {Logger} from './Logger.js'; -import * as util from '../util/Util.js'; +import log4javascript from "log4javascript"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { MonotonicClock } from "../util/Clock.js"; +import { Scheduler } from "../util/Scheduler.js"; +import * as util from "../util/Util.js"; +import { EventManager } from "./EventManager.js"; +import { GUI } from "./GUI.js"; +import { Logger } from "./Logger.js"; +import { ServerManager } from "./ServerManager.js"; +import { Window } from "./Window.js"; // import {Shelf} from "../data/Shelf"; - /** *

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.

* @@ -31,7 +30,6 @@ import * as util from '../util/Util.js'; */ export class PsychoJS { - /** * Properties */ @@ -116,17 +114,16 @@ export class PsychoJS // return this._shelf; // } - /** * @constructor * @public */ constructor({ - debug = true, - collectIP = false, - hosts = [], - topLevelStatus = true - } = {}) + debug = true, + collectIP = false, + hosts = [], + topLevelStatus = true, + } = {}) { // logging: this._logger = new Logger(this, (debug) ? log4javascript.Level.DEBUG : log4javascript.Level.INFO); @@ -134,7 +131,7 @@ export class PsychoJS // detect the browser: this._browser = util.detectBrowser(); - this.logger.info('[PsychoJS] Detected browser:', this._browser); + this.logger.info("[PsychoJS] Detected browser:", this._browser); // core clock: this._monotonicClock = new MonotonicClock(); @@ -142,11 +139,11 @@ export class PsychoJS // managers: this._eventManager = new EventManager(this); this._serverManager = new ServerManager({ - psychoJS: this + psychoJS: this, }); // to be loading `configURL` files in `_configure` calls from - const hostsEvidently = new Set([...hosts, 'https://pavlovia.org/run/', 'https://run.pavlovia.org/']); + const hostsEvidently = new Set([...hosts, "https://pavlovia.org/run/", "https://run.pavlovia.org/"]); this._hosts = Array.from(hostsEvidently); // GUI: @@ -178,14 +175,13 @@ export class PsychoJS this._makeStatusTopLevel(); } - this.logger.info('[PsychoJS] Initialised.'); - this.logger.info('[PsychoJS] @version 2021.2.0'); + this.logger.info("[PsychoJS] Initialised."); + this.logger.info("[PsychoJS] @version 2021.2.0"); // Hide #root::after - jQuery('#root').addClass('is-ready'); + jQuery("#root").addClass("is-ready"); } - /** * Get the experiment's environment. * @@ -193,14 +189,13 @@ export class PsychoJS */ getEnvironment() { - if (typeof this._config === 'undefined') + if (typeof this._config === "undefined") { return undefined; } return this._config.environment; } - /** * Open a PsychoJS Window. * @@ -220,22 +215,22 @@ export class PsychoJS * @public */ openWindow({ - name, - fullscr, - color, - units, - waitBlanking, - autoLog - } = {}) + name, + fullscr, + color, + units, + waitBlanking, + autoLog, + } = {}) { - this.logger.info('[PsychoJS] Open Window.'); + this.logger.info("[PsychoJS] Open Window."); - if (typeof this._window !== 'undefined') + if (typeof this._window !== "undefined") { throw { - origin: 'PsychoJS.openWindow', - context: 'when opening a Window', - error: 'A Window has already been opened.' + origin: "PsychoJS.openWindow", + context: "when opening a Window", + error: "A Window has already been opened.", }; } @@ -246,11 +241,10 @@ export class PsychoJS color, units, waitBlanking, - autoLog + autoLog, }); } - /** * Set the completion and cancellation URL to which the participant will be redirect at the end of the experiment. * @@ -263,7 +257,6 @@ export class PsychoJS this._cancellationUrl = cancellationUrl; } - /** * Schedule a task. * @@ -273,12 +266,11 @@ export class PsychoJS */ schedule(task, args) { - this.logger.debug('schedule task: ', task.toString().substring(0, 50), '...'); + this.logger.debug("schedule task: ", task.toString().substring(0, 50), "..."); this._scheduler.add(task, args); } - /** * @callback PsychoJS.condition * @return {boolean} true if the thenScheduler is to be run, false if the elseScheduler is to be run @@ -293,12 +285,11 @@ export class PsychoJS */ scheduleCondition(condition, thenScheduler, elseScheduler) { - this.logger.debug('schedule condition: ', condition.toString().substring(0, 50), '...'); + this.logger.debug("schedule condition: ", condition.toString().substring(0, 50), "..."); this._scheduler.addConditional(condition, thenScheduler, elseScheduler); } - /** * Start the experiment. * @@ -321,11 +312,11 @@ export class PsychoJS * @async * @public */ - async start({configURL = 'config.json', expName = 'UNKNOWN', expInfo = {}, resources = []} = {}) + async start({ configURL = "config.json", expName = "UNKNOWN", expInfo = {}, resources = [] } = {}) { this.logger.debug(); - const response = {origin: 'PsychoJS.start', context: 'when starting the experiment'}; + const response = { origin: "PsychoJS.start", context: "when starting the experiment" }; try { @@ -340,24 +331,24 @@ export class PsychoJS else { this._IP = { - IP: 'X', - hostname: 'X', - city: 'X', - region: 'X', - country: 'X', - location: 'X' + IP: "X", + hostname: "X", + city: "X", + region: "X", + country: "X", + location: "X", }; } // setup the experiment handler: this._experiment = new ExperimentHandler({ psychoJS: this, - extraInfo: expInfo + extraInfo: expInfo, }); // setup the logger: - //my.logger.console.setLevel(psychoJS.logging.WARNING); - //my.logger.server.set({'level':psychoJS.logging.WARNING, 'experimentInfo': my.expInfo}); + // my.logger.console.setLevel(psychoJS.logging.WARNING); + // my.logger.server.set({'level':psychoJS.logging.WARNING, 'experimentInfo': my.expInfo}); // if the experiment is running on the server: if (this.getEnvironment() === ExperimentHandler.Environment.SERVER) @@ -372,54 +363,49 @@ export class PsychoJS event.preventDefault(); // Chrome requires returnValue to be set: - event.returnValue = ''; + event.returnValue = ""; }; - window.addEventListener('beforeunload', this.beforeunloadCallback); - + window.addEventListener("beforeunload", this.beforeunloadCallback); // when the user closes the tab or browser, we attempt to close the session, // optionally save the results, and release the WebGL context // note: we communicate with the server using the Beacon API const self = this; - window.addEventListener('unload', (event) => + window.addEventListener("unload", (event) => { - if (self._config.session.status === 'OPEN') + if (self._config.session.status === "OPEN") { // save the incomplete results if need be: if (self._config.experiment.saveIncompleteResults) { - self._experiment.save({sync: true}); + self._experiment.save({ sync: true }); } // close the session: self._serverManager.closeSession(false, true); } - if (typeof self._window !== 'undefined') + if (typeof self._window !== "undefined") { self._window.close(); } }); - } - // start the asynchronous download of resources: await this._serverManager.prepareResources(resources); // start the experiment: - this.logger.info('[PsychoJS] Start Experiment.'); + this.logger.info("[PsychoJS] Start Experiment."); await this._scheduler.start(); } catch (error) { // this._gui.dialog({ error: { ...response, error } }); - this._gui.dialog({error: Object.assign(response, {error})}); + this._gui.dialog({ error: Object.assign(response, { error }) }); } } - - /** * Block the experiment until the specified resources have been downloaded. * @@ -439,8 +425,8 @@ export class PsychoJS waitForResources(resources = []) { const response = { - origin: 'PsychoJS.waitForResources', - context: 'while waiting for resources to be downloaded' + origin: "PsychoJS.waitForResources", + context: "while waiting for resources to be downloaded", }; try @@ -450,12 +436,10 @@ export class PsychoJS catch (error) { // this._gui.dialog({ error: { ...response, error } }); - this._gui.dialog({error: Object.assign(response, {error})}); + this._gui.dialog({ error: Object.assign(response, { error }) }); } } - - /** * Make the attributes of the given object those of PsychoJS and those of * the top level variable (e.g. window) as well. @@ -465,9 +449,9 @@ export class PsychoJS */ importAttributes(obj) { - this.logger.debug('import attributes from: ', util.toString(obj)); + this.logger.debug("import attributes from: ", util.toString(obj)); - if (typeof obj === 'undefined') + if (typeof obj === "undefined") { return; } @@ -479,7 +463,6 @@ export class PsychoJS } } - /** * Close everything and exit nicely at the end of the experiment, * potentially redirecting to one of the URLs previously specified by setRedirectUrls. @@ -493,9 +476,9 @@ export class PsychoJS * @async * @public */ - async quit({message, isCompleted = false} = {}) + async quit({ message, isCompleted = false } = {}) { - this.logger.info('[PsychoJS] Quit.'); + this.logger.info("[PsychoJS] Quit."); this._experiment.experimentEnded = true; this._status = PsychoJS.Status.FINISHED; @@ -508,17 +491,17 @@ export class PsychoJS // remove the beforeunload listener: if (this.getEnvironment() === ExperimentHandler.Environment.SERVER) { - window.removeEventListener('beforeunload', this.beforeunloadCallback); + 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 + warning: "Closing the session. Please wait a few moments.", + showOK: false, }); if (isCompleted || this._config.experiment.saveIncompleteResults) { - if (!this._serverMsg.has('__noOutput')) + if (!this._serverMsg.has("__noOutput")) { await this._experiment.save(); await this._logger.flush(); @@ -532,8 +515,8 @@ export class PsychoJS } // thank participant for waiting and either quit or redirect: - let text = 'Thank you for your patience.

'; - text += (typeof message !== 'undefined') ? message : 'Goodbye!'; + let text = "Thank you for your patience.

"; + text += (typeof message !== "undefined") ? message : "Goodbye!"; const self = this; this._gui.dialog({ message: text, @@ -552,26 +535,24 @@ export class PsychoJS this._window.closeFullScreen(); // redirect if redirection URLs have been provided: - if (isCompleted && typeof self._completionUrl !== 'undefined') + if (isCompleted && typeof self._completionUrl !== "undefined") { window.location = self._completionUrl; } - else if (!isCompleted && typeof self._cancellationUrl !== 'undefined') + else if (!isCompleted && typeof self._cancellationUrl !== "undefined") { window.location = self._cancellationUrl; } - } + }, }); - } catch (error) { console.error(error); - this._gui.dialog({error}); + this._gui.dialog({ error }); } } - /** * Configure PsychoJS for the running experiment. * @@ -583,8 +564,8 @@ export class PsychoJS async _configure(configURL, name) { const response = { - origin: 'PsychoJS.configure', - context: 'when configuring PsychoJS for the experiment' + origin: "PsychoJS.configure", + context: "when configuring PsychoJS for the experiment", }; try @@ -594,7 +575,7 @@ export class PsychoJS // if the experiment is running from the pavlovia.org server, we read the configuration file: const experimentUrl = window.location.href; // go through each url in allow list - const isHost = this._hosts.some(url => experimentUrl.indexOf(url) === 0); + const isHost = this._hosts.some((url) => experimentUrl.indexOf(url) === 0); if (isHost) { const serverResponse = await this._serverManager.getConfiguration(configURL); @@ -602,49 +583,48 @@ export class PsychoJS // legacy experiments had a psychoJsManager block instead of a pavlovia block, // and the URL pointed to https://pavlovia.org/server - if ('psychoJsManager' in this._config) + if ("psychoJsManager" in this._config) { delete this._config.psychoJsManager; this._config.pavlovia = { - URL: 'https://pavlovia.org' + URL: "https://pavlovia.org", }; } // tests for the presence of essential blocks in the configuration: - if (!('experiment' in this._config)) + if (!("experiment" in this._config)) { - throw 'missing experiment block in configuration'; + throw "missing experiment block in configuration"; } - if (!('name' in this._config.experiment)) + if (!("name" in this._config.experiment)) { - throw 'missing name in experiment block in configuration'; + throw "missing name in experiment block in configuration"; } - if (!('fullpath' in this._config.experiment)) + if (!("fullpath" in this._config.experiment)) { - throw 'missing fullpath in experiment block in configuration'; + throw "missing fullpath in experiment block in configuration"; } - if (!('pavlovia' in this._config)) + if (!("pavlovia" in this._config)) { - throw 'missing pavlovia block in configuration'; + throw "missing pavlovia block in configuration"; } - if (!('URL' in this._config.pavlovia)) + if (!("URL" in this._config.pavlovia)) { - throw 'missing URL in pavlovia block in configuration'; + throw "missing URL in pavlovia block in configuration"; } - if (!('gitlab' in this._config)) + if (!("gitlab" in this._config)) { - throw 'missing gitlab block in configuration'; + throw "missing gitlab block in configuration"; } - if (!('projectId' in this._config.gitlab)) + if (!("projectId" in this._config.gitlab)) { - throw 'missing projectId in gitlab block in configuration'; + throw "missing projectId in gitlab block in configuration"; } this._config.environment = ExperimentHandler.Environment.SERVER; - } - else // otherwise we create an ad-hoc configuration: + else { this._config = { environment: ExperimentHandler.Environment.LOCAL, @@ -652,8 +632,8 @@ export class PsychoJS name, saveFormat: ExperimentHandler.SaveFormat.CSV, saveIncompleteResults: true, - keys: [] - } + keys: [], + }, }; } @@ -661,24 +641,22 @@ export class PsychoJS this._serverMsg = new Map(); util.getUrlParameters().forEach((value, key) => { - if (key.indexOf('__') === 0) + if (key.indexOf("__") === 0) { this._serverMsg.set(key, value); } }); - this.status = PsychoJS.Status.CONFIGURED; - this.logger.debug('configuration:', util.toString(this._config)); + this.logger.debug("configuration:", util.toString(this._config)); } catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the IP information of the participant, asynchronously. * @@ -688,33 +666,32 @@ export class PsychoJS async _getParticipantIPInfo() { const response = { - origin: 'PsychoJS._getParticipantIPInfo', - context: 'when getting the IP information of the participant' + origin: "PsychoJS._getParticipantIPInfo", + context: "when getting the IP information of the participant", }; - this.logger.debug('getting the IP information of the participant'); + this.logger.debug("getting the IP information of the participant"); this._IP = {}; try { - const geoResponse = await jQuery.get('http://www.geoplugin.net/json.gp'); + const geoResponse = await jQuery.get("http://www.geoplugin.net/json.gp"); const geoData = JSON.parse(geoResponse); this._IP = { IP: geoData.geoplugin_request, country: geoData.geoplugin_countryName, latitude: geoData.geoplugin_latitude, - longitude: geoData.geoplugin_longitude + longitude: geoData.geoplugin_longitude, }; - this.logger.debug('IP information of the participant: ' + util.toString(this._IP)); + this.logger.debug("IP information of the participant: " + util.toString(this._IP)); } catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Capture all errors and display them in a pop-up error box. * @@ -722,41 +699,46 @@ export class PsychoJS */ _captureErrors() { - this.logger.debug('capturing all errors and showing them in a pop up window'); + this.logger.debug("capturing all errors and showing them in a pop up window"); const self = this; - window.onerror = function (message, source, lineno, colno, error) + window.onerror = function(message, source, lineno, colno, error) { console.error(error); - document.body.setAttribute('data-error', JSON.stringify({ - message: message, - source: source, - lineno: lineno, - colno: colno, - error: error - })); - - self._gui.dialog({"error": error}); - + document.body.setAttribute( + "data-error", + JSON.stringify({ + message: message, + source: source, + lineno: lineno, + colno: colno, + error: error, + }), + ); + + self._gui.dialog({ "error": error }); + return true; }; - window.onunhandledrejection = function (error) + window.onunhandledrejection = function(error) { console.error(error?.reason); - if (error?.reason?.stack === undefined) { + 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)); + document.body.setAttribute("data-error", JSON.stringify(error?.reason)); } - self._gui.dialog({error: 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; }; } - /** * Make the various Status top level, in order to accommodate PsychoPy's Code Components. * @private @@ -768,10 +750,8 @@ export class PsychoJS window[status] = PsychoJS.Status[status]; } } - } - /** * PsychoJS status. * @@ -784,14 +764,13 @@ export class PsychoJS * STOPPED in PsychoJS, but the Symbol is the same as that of FINISHED. */ PsychoJS.Status = { - NOT_CONFIGURED: Symbol.for('NOT_CONFIGURED'), - CONFIGURING: Symbol.for('CONFIGURING'), - CONFIGURED: Symbol.for('CONFIGURED'), - NOT_STARTED: Symbol.for('NOT_STARTED'), - STARTED: Symbol.for('STARTED'), - PAUSED: Symbol.for('PAUSED'), - FINISHED: Symbol.for('FINISHED'), - STOPPED: Symbol.for('FINISHED'), //Symbol.for('STOPPED') - ERROR: Symbol.for('ERROR') + NOT_CONFIGURED: Symbol.for("NOT_CONFIGURED"), + CONFIGURING: Symbol.for("CONFIGURING"), + CONFIGURED: Symbol.for("CONFIGURED"), + NOT_STARTED: Symbol.for("NOT_STARTED"), + STARTED: Symbol.for("STARTED"), + PAUSED: Symbol.for("PAUSED"), + FINISHED: Symbol.for("FINISHED"), + STOPPED: Symbol.for("FINISHED"), // Symbol.for('STOPPED') + ERROR: Symbol.for("ERROR"), }; - diff --git a/src/core/ServerManager.js b/src/core/ServerManager.js index 2142a34..2d81001 100644 --- a/src/core/ServerManager.js +++ b/src/core/ServerManager.js @@ -7,13 +7,12 @@ * @license Distributed under the terms of the MIT License */ -import { Howl } from 'howler'; -import {PsychoJS} from './PsychoJS.js'; -import {PsychObject} from '../util/PsychObject.js'; -import * as util from '../util/Util.js'; -import {ExperimentHandler} from "../data/ExperimentHandler.js"; -import {MonotonicClock, Clock} from "../util/Clock.js"; - +import { Howl } from "howler"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { Clock, MonotonicClock } from "../util/Clock.js"; +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; +import { PsychoJS } from "./PsychoJS.js"; /** *

This manager handles all communications between the experiment running in the participant's browser and the [pavlovia.org]{@link http://pavlovia.org} server, in an asynchronous manner.

@@ -36,13 +35,12 @@ export class ServerManager extends PsychObject * @readonly * @public */ - static ALL_RESOURCES = Symbol.for('ALL_RESOURCES'); - + static ALL_RESOURCES = Symbol.for("ALL_RESOURCES"); constructor({ - psychoJS, - autoLog = false - } = {}) + psychoJS, + autoLog = false, + } = {}) { super(psychoJS); @@ -52,11 +50,10 @@ export class ServerManager extends PsychObject // resources is a map of this._resources = new Map(); - this._addAttribute('autoLog', autoLog); - this._addAttribute('status', ServerManager.Status.READY); + this._addAttribute("autoLog", autoLog); + this._addAttribute("status", ServerManager.Status.READY); } - /** * @typedef ServerManager.GetConfigurationPromise * @property {string} origin the calling method @@ -77,33 +74,32 @@ export class ServerManager extends PsychObject getConfiguration(configURL) { const response = { - origin: 'ServerManager.getConfiguration', - context: 'when reading the configuration file: ' + configURL + origin: "ServerManager.getConfiguration", + context: "when reading the configuration file: " + configURL, }; - this._psychoJS.logger.debug('reading the configuration file: ' + configURL); + this._psychoJS.logger.debug("reading the configuration file: " + configURL); const self = this; return new Promise((resolve, reject) => { - jQuery.get(configURL, 'json') + jQuery.get(configURL, "json") .done((config, textStatus) => { // resolve({ ...response, config }); - resolve(Object.assign(response, {config})); + resolve(Object.assign(response, { config })); }) .fail((jqXHR, textStatus, errorThrown) => { self.setStatus(ServerManager.Status.ERROR); const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + console.error("error:", errorMsg); - reject(Object.assign(response, {error: errorMsg})); + reject(Object.assign(response, { error: errorMsg })); }); }); } - /** * @typedef ServerManager.OpenSessionPromise * @property {string} origin the calling method @@ -122,45 +118,45 @@ export class ServerManager extends PsychObject openSession() { const response = { - origin: 'ServerManager.openSession', - context: 'when opening a session for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.openSession", + context: "when opening a session for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug('opening a session for experiment: ' + this._psychoJS.config.experiment.fullpath); + this._psychoJS.logger.debug("opening a session for experiment: " + this._psychoJS.config.experiment.fullpath); this.setStatus(ServerManager.Status.BUSY); // prepare POST query: let data = {}; - if (this._psychoJS._serverMsg.has('__pilotToken')) + if (this._psychoJS._serverMsg.has("__pilotToken")) { - data.pilotToken = this._psychoJS._serverMsg.get('__pilotToken'); + data.pilotToken = this._psychoJS._serverMsg.get("__pilotToken"); } // query pavlovia server: const self = this; return new Promise((resolve, reject) => { - const url = this._psychoJS.config.pavlovia.URL + '/api/v2/experiments/' + encodeURIComponent(self._psychoJS.config.experiment.fullpath) + '/sessions'; - jQuery.post(url, data, null, 'json') + const url = this._psychoJS.config.pavlovia.URL + "/api/v2/experiments/" + encodeURIComponent(self._psychoJS.config.experiment.fullpath) + "/sessions"; + jQuery.post(url, data, null, "json") .done((data, textStatus) => { - if (!('token' in data)) + if (!("token" in data)) { self.setStatus(ServerManager.Status.ERROR); - reject(Object.assign(response, {error: 'unexpected answer from server: no token'})); + reject(Object.assign(response, { error: "unexpected answer from server: no token" })); // reject({...response, error: 'unexpected answer from server: no token'}); } - if (!('experiment' in data)) + if (!("experiment" in data)) { self.setStatus(ServerManager.Status.ERROR); // reject({...response, error: 'unexpected answer from server: no experiment'}); - reject(Object.assign(response, {error: 'unexpected answer from server: no experiment'})); + reject(Object.assign(response, { error: "unexpected answer from server: no experiment" })); } self._psychoJS.config.session = { token: data.token, - status: 'OPEN' + status: "OPEN", }; self._psychoJS.config.experiment.status = data.experiment.status2; self._psychoJS.config.experiment.saveFormat = Symbol.for(data.experiment.saveFormat); @@ -169,7 +165,7 @@ export class ServerManager extends PsychObject self._psychoJS.config.experiment.runMode = data.experiment.runMode; // secret keys for various services, e.g. Google Speech API - if ('keys' in data.experiment) + if ("keys" in data.experiment) { self._psychoJS.config.experiment.keys = data.experiment.keys; } @@ -180,21 +176,20 @@ export class ServerManager extends PsychObject self.setStatus(ServerManager.Status.READY); // resolve({ ...response, token: data.token, status: data.status }); - resolve(Object.assign(response, {token: data.token, status: data.status})); + resolve(Object.assign(response, { token: data.token, status: data.status })); }) .fail((jqXHR, textStatus, errorThrown) => { self.setStatus(ServerManager.Status.ERROR); const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + console.error("error:", errorMsg); - reject(Object.assign(response, {error: errorMsg})); + reject(Object.assign(response, { error: errorMsg })); }); }); } - /** * @typedef ServerManager.CloseSessionPromise * @property {string} origin the calling method @@ -214,16 +209,17 @@ export class ServerManager extends PsychObject async closeSession(isCompleted = false, sync = false) { const response = { - origin: 'ServerManager.closeSession', - context: 'when closing the session for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.closeSession", + context: "when closing the session for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug('closing the session for experiment: ' + this._psychoJS.config.experiment.name); + this._psychoJS.logger.debug("closing the session for experiment: " + this._psychoJS.config.experiment.name); this.setStatus(ServerManager.Status.BUSY); // prepare DELETE query: - const url = this._psychoJS.config.pavlovia.URL + '/api/v2/experiments/' + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + '/sessions/' + this._psychoJS.config.session.token; + const url = this._psychoJS.config.pavlovia.URL + "/api/v2/experiments/" + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + "/sessions/" + + this._psychoJS.config.session.token; // synchronous query the pavlovia server: if (sync) @@ -244,9 +240,9 @@ export class ServerManager extends PsychObject }); */ const formData = new FormData(); - formData.append('isCompleted', isCompleted); - navigator.sendBeacon(url + '/delete', formData); - this._psychoJS.config.session.status = 'CLOSED'; + formData.append("isCompleted", isCompleted); + navigator.sendBeacon(url + "/delete", formData); + this._psychoJS.config.session.status = "CLOSED"; } // asynchronously query the pavlovia server: else @@ -256,32 +252,31 @@ export class ServerManager extends PsychObject { jQuery.ajax({ url, - type: 'delete', - data: {isCompleted}, - dataType: 'json' + type: "delete", + data: { isCompleted }, + dataType: "json", }) .done((data, textStatus) => { self.setStatus(ServerManager.Status.READY); - self._psychoJS.config.session.status = 'CLOSED'; + self._psychoJS.config.session.status = "CLOSED"; // resolve({ ...response, data }); - resolve(Object.assign(response, {data})); + resolve(Object.assign(response, { data })); }) .fail((jqXHR, textStatus, errorThrown) => { self.setStatus(ServerManager.Status.ERROR); const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + console.error("error:", errorMsg); - reject(Object.assign(response, {error: errorMsg})); + reject(Object.assign(response, { error: errorMsg })); }); }); } } - /** * Get the value of a resource. * @@ -298,30 +293,29 @@ export class ServerManager extends PsychObject getResource(name, errorIfNotDownloaded = false) { const response = { - origin: 'ServerManager.getResource', - context: 'when getting the value of resource: ' + name + origin: "ServerManager.getResource", + context: "when getting the value of resource: " + name, }; const pathStatusData = this._resources.get(name); - if (typeof pathStatusData === 'undefined') + if (typeof pathStatusData === "undefined") { // throw { ...response, error: 'unknown resource' }; - throw Object.assign(response, {error: 'unknown resource'}); + throw Object.assign(response, { error: "unknown resource" }); } if (errorIfNotDownloaded && pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED) { throw Object.assign(response, { - error: name + ' is not available for use (yet), its current status is: ' + - util.toString(pathStatusData.status) + error: name + " is not available for use (yet), its current status is: " + + util.toString(pathStatusData.status), }); } return pathStatusData.data; } - /** * Get the status of a resource. * @@ -335,21 +329,20 @@ export class ServerManager extends PsychObject getResourceStatus(name) { const response = { - origin: 'ServerManager.getResourceStatus', - context: 'when getting the status of resource: ' + name + origin: "ServerManager.getResourceStatus", + context: "when getting the status of resource: " + name, }; const pathStatusData = this._resources.get(name); - if (typeof pathStatusData === 'undefined') + if (typeof pathStatusData === "undefined") { // throw { ...response, error: 'unknown resource' }; - throw Object.assign(response, {error: 'unknown resource'}); + throw Object.assign(response, { error: "unknown resource" }); } return pathStatusData.status; } - /** * Set the resource manager status. * @@ -360,21 +353,19 @@ export class ServerManager extends PsychObject setStatus(status) { const response = { - origin: 'ServerManager.setStatus', - context: 'when changing the status of the server manager to: ' + util.toString(status) + origin: "ServerManager.setStatus", + context: "when changing the status of the server manager to: " + util.toString(status), }; // check status: - const statusKey = (typeof status === 'symbol') ? Symbol.keyFor(status) : null; + const statusKey = (typeof status === "symbol") ? Symbol.keyFor(status) : null; if (!statusKey) - // throw { ...response, error: 'status must be a symbol' }; - { - throw Object.assign(response, {error: 'status must be a symbol'}); + { // throw { ...response, error: 'status must be a symbol' }; + throw Object.assign(response, { error: "status must be a symbol" }); } if (!ServerManager.Status.hasOwnProperty(statusKey)) - // throw { ...response, error: 'unknown status' }; - { - throw Object.assign(response, {error: 'unknown status'}); + { // throw { ...response, error: 'unknown status' }; + throw Object.assign(response, { error: "unknown status" }); } this._status = status; @@ -385,7 +376,6 @@ export class ServerManager extends PsychObject return this._status; } - /** * Reset the resource manager status to ServerManager.Status.READY. * @@ -399,7 +389,6 @@ export class ServerManager extends PsychObject return this.setStatus(ServerManager.Status.READY); } - /** * Prepare resources for the experiment: register them with the server manager and possibly * start downloading them right away. @@ -421,11 +410,11 @@ export class ServerManager extends PsychObject async prepareResources(resources = []) { const response = { - origin: 'ServerManager.prepareResources', - context: 'when preparing resources for experiment: ' + this._psychoJS.config.experiment.name + origin: "ServerManager.prepareResources", + context: "when preparing resources for experiment: " + this._psychoJS.config.experiment.name, }; - this._psychoJS.logger.debug('preparing resources for experiment: ' + this._psychoJS.config.experiment.name); + this._psychoJS.logger.debug("preparing resources for experiment: " + this._psychoJS.config.experiment.name); try { @@ -445,8 +434,10 @@ export class ServerManager extends PsychObject // if the experiment is hosted on the pavlovia.org server and // resources is [ServerManager.ALL_RESOURCES], then we register all the resources // in the "resources" sub-directory - if (this._psychoJS.config.environment === ExperimentHandler.Environment.SERVER - && allResources) + if ( + this._psychoJS.config.environment === ExperimentHandler.Environment.SERVER + && allResources + ) { // list the resources from the resources directory of the experiment on the server: const serverResponse = await this._listResources(); @@ -457,53 +448,56 @@ export class ServerManager extends PsychObject { if (!this._resources.has(name)) { - const path = serverResponse.resourceDirectory + '/' + name; + const path = serverResponse.resourceDirectory + "/" + name; this._resources.set(name, { status: ServerManager.ResourceStatus.REGISTERED, path, - data: undefined + data: undefined, }); - this._psychoJS.logger.debug('registered resource:', name, path); + this._psychoJS.logger.debug("registered resource:", name, path); resourcesToDownload.add(name); } } } - // if the experiment is hosted locally (localhost) or if specific resources were given // then we register those specific resources, if they have not been registered already else { // we cannot ask for all resources to be registered locally, since we cannot list // them: - if (this._psychoJS.config.environment === ExperimentHandler.Environment.LOCAL - && allResources) + if ( + this._psychoJS.config.environment === ExperimentHandler.Environment.LOCAL + && allResources + ) { throw "resources must be manually specified when the experiment is running locally: ALL_RESOURCES cannot be used"; } - for (let {name, path, download} of resources) + for (let { name, path, download } of resources) { if (!this._resources.has(name)) { // to deal with potential CORS issues, we use the pavlovia.org proxy for resources // not hosted on pavlovia.org: - if ((path.toLowerCase().indexOf('www.') === 0 || - path.toLowerCase().indexOf('http:') === 0 || - path.toLowerCase().indexOf('https:') === 0) && - (path.indexOf('pavlovia.org') === -1)) + if ( + (path.toLowerCase().indexOf("www.") === 0 + || path.toLowerCase().indexOf("http:") === 0 + || path.toLowerCase().indexOf("https:") === 0) + && (path.indexOf("pavlovia.org") === -1) + ) { - path = 'https://pavlovia.org/api/v2/proxy/' + path; + path = "https://pavlovia.org/api/v2/proxy/" + path; } this._resources.set(name, { status: ServerManager.ResourceStatus.REGISTERED, path, - data: undefined + data: undefined, }); - this._psychoJS.logger.debug('registered resource:', name, path); + this._psychoJS.logger.debug("registered resource:", name, path); // download resources by default: - if (typeof download === 'undefined' || download) + if (typeof download === "undefined" || download) { resourcesToDownload.add(name); } @@ -517,13 +511,12 @@ export class ServerManager extends PsychObject } catch (error) { - console.log('error', error); - throw Object.assign(response, {error}); + console.log("error", error); + throw Object.assign(response, { error }); // throw { ...response, error: error }; } } - /** * Block the experiment until the specified resources have been downloaded. * @@ -538,7 +531,7 @@ export class ServerManager extends PsychObject this._waitForDownloadComponent = { status: PsychoJS.Status.NOT_STARTED, clock: new Clock(), - resources: new Set() + resources: new Set(), }; const self = this; @@ -555,7 +548,7 @@ export class ServerManager extends PsychObject // if resources is an empty array, we consider all registered resources: if (resources.length === 0) { - for (const [name, {status, path, data}] of this._resources) + for (const [name, { status, path, data }] of this._resources) { resources.append({ name, path }); } @@ -563,40 +556,39 @@ export class ServerManager extends PsychObject // only download those resources not already downloaded or downloading: const resourcesToDownload = new Set(); - for (let {name, path} of resources) + for (let { name, path } of resources) { // to deal with potential CORS issues, we use the pavlovia.org proxy for resources // not hosted on pavlovia.org: - if ( (path.toLowerCase().indexOf('www.') === 0 || - path.toLowerCase().indexOf('http:') === 0 || - path.toLowerCase().indexOf('https:') === 0) && - (path.indexOf('pavlovia.org') === -1) ) + if ( + (path.toLowerCase().indexOf("www.") === 0 + || path.toLowerCase().indexOf("http:") === 0 + || path.toLowerCase().indexOf("https:") === 0) + && (path.indexOf("pavlovia.org") === -1) + ) { - path = 'https://devlovia.org/api/v2/proxy/' + path; + path = "https://devlovia.org/api/v2/proxy/" + path; } const pathStatusData = this._resources.get(name); // the resource has not been registered yet: - if (typeof pathStatusData === 'undefined') + if (typeof pathStatusData === "undefined") { self._resources.set(name, { status: ServerManager.ResourceStatus.REGISTERED, path, - data: undefined + data: undefined, }); self._waitForDownloadComponent.resources.add(name); resourcesToDownload.add(name); - self._psychoJS.logger.debug('registered resource:', name, path); + self._psychoJS.logger.debug("registered resource:", name, path); } - // the resource has been registered but is not downloaded yet: else if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED) - // else if (typeof pathStatusData.data === 'undefined') - { + { // else if (typeof pathStatusData.data === 'undefined') self._waitForDownloadComponent.resources.add(name); } - } // start the download: @@ -610,8 +602,7 @@ export class ServerManager extends PsychObject // the resource has not been downloaded yet: loop this component if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED) - // if (typeof pathStatusData.data === 'undefined') - { + { // if (typeof pathStatusData.data === 'undefined') return Scheduler.Event.FLIP_REPEAT; } } @@ -620,10 +611,8 @@ export class ServerManager extends PsychObject self._waitForDownloadComponent.status = PsychoJS.Status.FINISHED; return Scheduler.Event.NEXT; }; - } - /** * @typedef ServerManager.UploadDataPromise * @property {string} origin the calling method @@ -645,24 +634,24 @@ export class ServerManager extends PsychObject uploadData(key, value, sync = false) { const response = { - origin: 'ServerManager.uploadData', - context: 'when uploading participant\'s results for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.uploadData", + context: "when uploading participant's results for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug('uploading data for experiment: ' + this._psychoJS.config.experiment.fullpath); + this._psychoJS.logger.debug("uploading data for experiment: " + this._psychoJS.config.experiment.fullpath); this.setStatus(ServerManager.Status.BUSY); - const url = this._psychoJS.config.pavlovia.URL + - '/api/v2/experiments/' + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + - '/sessions/' + this._psychoJS.config.session.token + - '/results'; + const url = this._psychoJS.config.pavlovia.URL + + "/api/v2/experiments/" + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + + "/sessions/" + this._psychoJS.config.session.token + + "/results"; // synchronous query the pavlovia server: if (sync) { const formData = new FormData(); - formData.append('key', key); - formData.append('value', value); + formData.append("key", key); + formData.append("value", value); navigator.sendBeacon(url, formData); } // asynchronously query the pavlovia server: @@ -673,30 +662,28 @@ export class ServerManager extends PsychObject { const data = { key, - value + value, }; - jQuery.post(url, data, null, 'json') + jQuery.post(url, data, null, "json") .done((serverData, textStatus) => { self.setStatus(ServerManager.Status.READY); - resolve(Object.assign(response, {serverData})); + resolve(Object.assign(response, { serverData })); }) .fail((jqXHR, textStatus, errorThrown) => { self.setStatus(ServerManager.Status.ERROR); const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + console.error("error:", errorMsg); - reject(Object.assign(response, {error: errorMsg})); + reject(Object.assign(response, { error: errorMsg })); }); }); } } - - /** * Asynchronously upload experiment logs to the pavlovia server. * @@ -710,54 +697,52 @@ export class ServerManager extends PsychObject uploadLog(logs, compressed = false) { const response = { - origin: 'ServerManager.uploadLog', - context: 'when uploading participant\'s log for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.uploadLog", + context: "when uploading participant's log for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug('uploading server log for experiment: ' + this._psychoJS.config.experiment.fullpath); + this._psychoJS.logger.debug("uploading server log for experiment: " + this._psychoJS.config.experiment.fullpath); this.setStatus(ServerManager.Status.BUSY); // prepare the POST query: const info = this.psychoJS.experiment.extraInfo; - const participant = ((typeof info.participant === 'string' && info.participant.length > 0) ? info.participant : 'PARTICIPANT'); - const experimentName = (typeof info.expName !== 'undefined') ? info.expName : this.psychoJS.config.experiment.name; - const datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr()); - const filename = participant + '_' + experimentName + '_' + datetime + '.log'; + const participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT"); + const experimentName = (typeof info.expName !== "undefined") ? info.expName : this.psychoJS.config.experiment.name; + const datetime = ((typeof info.date !== "undefined") ? info.date : MonotonicClock.getDateStr()); + const filename = participant + "_" + experimentName + "_" + datetime + ".log"; const data = { filename, logs, - compressed + compressed, }; // query the pavlovia server: const self = this; return new Promise((resolve, reject) => { - const url = self._psychoJS.config.pavlovia.URL + - '/api/v2/experiments/' + encodeURIComponent(self._psychoJS.config.experiment.fullpath) + - '/sessions/' + self._psychoJS.config.session.token + - '/logs'; + const url = self._psychoJS.config.pavlovia.URL + + "/api/v2/experiments/" + encodeURIComponent(self._psychoJS.config.experiment.fullpath) + + "/sessions/" + self._psychoJS.config.session.token + + "/logs"; - jQuery.post(url, data, null, 'json') + jQuery.post(url, data, null, "json") .done((serverData, textStatus) => { self.setStatus(ServerManager.Status.READY); - resolve(Object.assign(response, {serverData})); + resolve(Object.assign(response, { serverData })); }) .fail((jqXHR, textStatus, errorThrown) => { self.setStatus(ServerManager.Status.ERROR); const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + console.error("error:", errorMsg); - reject(Object.assign(response, {error: errorMsg})); + reject(Object.assign(response, { error: errorMsg })); }); }); } - - /** * Asynchronously upload audio data to the pavlovia server. * @@ -771,46 +756,48 @@ export class ServerManager extends PsychObject async uploadAudio(audioBlob, tag) { const response = { - origin: 'ServerManager.uploadAudio', - context: 'when uploading audio data for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.uploadAudio", + context: "when uploading audio data for experiment: " + this._psychoJS.config.experiment.fullpath, }; try { - if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER || - this._psychoJS.config.experiment.status !== 'RUNNING' || - this._psychoJS._serverMsg.has('__pilotToken')) + if ( + this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER + || this._psychoJS.config.experiment.status !== "RUNNING" + || this._psychoJS._serverMsg.has("__pilotToken") + ) { - throw 'audio recordings can only be uploaded to the server for experiments running on the server'; + throw "audio recordings can only be uploaded to the server for experiments running on the server"; } - this._psychoJS.logger.debug('uploading audio data for experiment: ' + this._psychoJS.config.experiment.fullpath); + this._psychoJS.logger.debug("uploading audio data for experiment: " + this._psychoJS.config.experiment.fullpath); this.setStatus(ServerManager.Status.BUSY); // prepare the request: const info = this.psychoJS.experiment.extraInfo; - const participant = ((typeof info.participant === 'string' && info.participant.length > 0) ? info.participant : 'PARTICIPANT'); - const experimentName = (typeof info.expName !== 'undefined') ? info.expName : this.psychoJS.config.experiment.name; - const datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr()); - const filename = participant + '_' + experimentName + '_' + datetime + '_' + tag; + const participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT"); + const experimentName = (typeof info.expName !== "undefined") ? info.expName : this.psychoJS.config.experiment.name; + const datetime = ((typeof info.date !== "undefined") ? info.date : MonotonicClock.getDateStr()); + const filename = participant + "_" + experimentName + "_" + datetime + "_" + tag; const formData = new FormData(); - formData.append('audio', audioBlob, filename); + formData.append("audio", audioBlob, filename); - const url = this._psychoJS.config.pavlovia.URL + - '/api/v2/experiments/' + this._psychoJS.config.gitlab.projectId + - '/sessions/' + this._psychoJS.config.session.token + - '/audio'; + const url = this._psychoJS.config.pavlovia.URL + + "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId + + "/sessions/" + this._psychoJS.config.session.token + + "/audio"; // query the pavlovia server: const response = await fetch(url, { - method: 'POST', - mode: 'cors', // no-cors, *cors, same-origin - cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached - credentials: 'same-origin', // include, *same-origin, omit - redirect: 'follow', // manual, *follow, error - referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url - body: formData + method: "POST", + mode: "cors", // no-cors, *cors, same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached + credentials: "same-origin", // include, *same-origin, omit + redirect: "follow", // manual, *follow, error + referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + body: formData, }); const jsonResponse = await response.json(); @@ -828,13 +815,10 @@ export class ServerManager extends PsychObject this.setStatus(ServerManager.Status.ERROR); console.error(error); - throw {...response, error}; + throw { ...response, error }; } - } - - /** * List the resources available to the experiment. @@ -845,49 +829,51 @@ export class ServerManager extends PsychObject _listResources() { const response = { - origin: 'ServerManager._listResourcesSession', - context: 'when listing the resources for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager._listResourcesSession", + context: "when listing the resources for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug('listing the resources for experiment: ' + - this._psychoJS.config.experiment.fullpath); + this._psychoJS.logger.debug( + "listing the resources for experiment: " + + this._psychoJS.config.experiment.fullpath, + ); this.setStatus(ServerManager.Status.BUSY); // prepare GET data: const data = { - 'token': this._psychoJS.config.session.token + "token": this._psychoJS.config.session.token, }; // query pavlovia server: const self = this; return new Promise((resolve, reject) => { - const url = this._psychoJS.config.pavlovia.URL + - '/api/v2/experiments/' + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + - '/resources'; + const url = this._psychoJS.config.pavlovia.URL + + "/api/v2/experiments/" + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + + "/resources"; - jQuery.get(url, data, null, 'json') + jQuery.get(url, data, null, "json") .done((data, textStatus) => { - if (!('resources' in data)) + if (!("resources" in data)) { self.setStatus(ServerManager.Status.ERROR); // reject({ ...response, error: 'unexpected answer from server: no resources' }); - reject(Object.assign(response, {error: 'unexpected answer from server: no resources'})); + reject(Object.assign(response, { error: "unexpected answer from server: no resources" })); } - if (!('resourceDirectory' in data)) + if (!("resourceDirectory" in data)) { self.setStatus(ServerManager.Status.ERROR); // reject({ ...response, error: 'unexpected answer from server: no resourceDirectory' }); - reject(Object.assign(response, {error: 'unexpected answer from server: no resourceDirectory'})); + reject(Object.assign(response, { error: "unexpected answer from server: no resourceDirectory" })); } self.setStatus(ServerManager.Status.READY); // resolve({ ...response, resources: data.resources, resourceDirectory: data.resourceDirectory }); resolve(Object.assign(response, { resources: data.resources, - resourceDirectory: data.resourceDirectory + resourceDirectory: data.resourceDirectory, })); }) .fail((jqXHR, textStatus, errorThrown) => @@ -895,16 +881,13 @@ export class ServerManager extends PsychObject self.setStatus(ServerManager.Status.ERROR); const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + console.error("error:", errorMsg); - reject(Object.assign(response, {error: errorMsg})); + reject(Object.assign(response, { error: errorMsg })); }); }); - } - - /** * Download the specified resources. * @@ -918,135 +901,131 @@ export class ServerManager extends PsychObject _downloadResources(resources) { const response = { - origin: 'ServerManager._downloadResources', - context: 'when downloading resources for experiment: ' + this._psychoJS.config.experiment.name + origin: "ServerManager._downloadResources", + context: "when downloading resources for experiment: " + this._psychoJS.config.experiment.name, }; - this._psychoJS.logger.debug('downloading resources for experiment: ' + this._psychoJS.config.experiment.name); + this._psychoJS.logger.debug("downloading resources for experiment: " + this._psychoJS.config.experiment.name); this.setStatus(ServerManager.Status.BUSY); this.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOADING_RESOURCES, - count: resources.size + count: resources.size, }); this._nbLoadedResources = 0; - // (*) set-up preload.js: - this._resourceQueue = new createjs.LoadQueue(true, '', true); + this._resourceQueue = new createjs.LoadQueue(true, "", true); const self = this; // the loading of a specific resource has started: - this._resourceQueue.addEventListener("filestart", event => + this._resourceQueue.addEventListener("filestart", (event) => { const pathStatusData = self._resources.get(event.item.id); - pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOADING_RESOURCE, - resource: event.item.id + resource: event.item.id, }); }); // the loading of a specific resource has completed: - this._resourceQueue.addEventListener("fileload", event => + this._resourceQueue.addEventListener("fileload", (event) => { const pathStatusData = self._resources.get(event.item.id); pathStatusData.data = event.result; - pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; - ++ self._nbLoadedResources; + ++self._nbLoadedResources; self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.RESOURCE_DOWNLOADED, - resource: event.item.id + resource: event.item.id, }); }); // the loading of all given resources completed: - this._resourceQueue.addEventListener("complete", event => + this._resourceQueue.addEventListener("complete", (event) => { self._resourceQueue.close(); if (self._nbLoadedResources === resources.size) { self.setStatus(ServerManager.Status.READY); self.emit(ServerManager.Event.RESOURCE, { - message: ServerManager.Event.DOWNLOAD_COMPLETED + message: ServerManager.Event.DOWNLOAD_COMPLETED, }); } }); // error: we throw an exception - this._resourceQueue.addEventListener("error", event => + this._resourceQueue.addEventListener("error", (event) => { self.setStatus(ServerManager.Status.ERROR); - if (typeof event.item !== 'undefined') + if (typeof event.item !== "undefined") { const pathStatusData = self._resources.get(event.item.id); - pathStatusData.status = ServerManager.ResourceStatus.ERROR; + pathStatusData.status = ServerManager.ResourceStatus.ERROR; throw Object.assign(response, { - error: 'unable to download resource: ' + event.item.id + ' (' + event.title + ')' + error: "unable to download resource: " + event.item.id + " (" + event.title + ")", }); } else { console.error(event); - if (event.title === 'FILE_LOAD_ERROR' && typeof event.data !== 'undefined') + if (event.title === "FILE_LOAD_ERROR" && typeof event.data !== "undefined") { const id = event.data.id; const title = event.data.src; throw Object.assign(response, { - error: 'unable to download resource: ' + id + ' (' + title + ')' + error: "unable to download resource: " + id + " (" + title + ")", }); } - else { throw Object.assign(response, { - error: 'unspecified download error' + error: "unspecified download error", }); } - } }); - // (*) dispatch resources to preload.js or howler.js based on extension: let manifest = []; const soundResources = new Set(); for (const name of resources) { - const nameParts = name.toLowerCase().split('.'); + const nameParts = name.toLowerCase().split("."); const extension = (nameParts.length > 1) ? nameParts.pop() : undefined; // warn the user if the resource does not have any extension: - if (typeof extension === 'undefined') + if (typeof extension === "undefined") { this.psychoJS.logger.warn(`"${name}" does not appear to have an extension, which may negatively impact its loading. We highly recommend you add an extension.`); } const pathStatusData = this._resources.get(name); - if (typeof pathStatusData === 'undefined') + if (typeof pathStatusData === "undefined") { - throw Object.assign(response, {error: name + ' has not been previously registered'}); + throw Object.assign(response, { error: name + " has not been previously registered" }); } if (pathStatusData.status !== ServerManager.ResourceStatus.REGISTERED) { - throw Object.assign(response, {error: name + ' is already downloaded or is currently already downloading'}); + throw Object.assign(response, { error: name + " is already downloaded or is currently already downloading" }); } // preload.js with forced binary for xls and xlsx: - if (['csv', 'odp', 'xls', 'xlsx', 'json'].indexOf(extension) > -1) + if (["csv", "odp", "xls", "xlsx", "json"].indexOf(extension) > -1) { - manifest.push(/*new createjs.LoadItem().set(*/{ + manifest.push(/*new createjs.LoadItem().set(*/ { id: name, src: pathStatusData.path, type: createjs.Types.BINARY, - crossOrigin: 'Anonymous' - }/*)*/); + crossOrigin: "Anonymous", + } /*)*/); } /* ascii .csv are adequately handled in binary format // forced text for .csv: @@ -1055,28 +1034,26 @@ export class ServerManager extends PsychObject */ // sound files are loaded through howler.js: - else if (['mp3', 'mpeg', 'opus', 'ogg', 'oga', 'wav', 'aac', 'caf', 'm4a', 'weba', 'dolby', 'flac'].indexOf(extension) > -1) + else if (["mp3", "mpeg", "opus", "ogg", "oga", "wav", "aac", "caf", "m4a", "weba", "dolby", "flac"].indexOf(extension) > -1) { soundResources.add(name); - if (extension === 'wav') + if (extension === "wav") { this.psychoJS.logger.warn(`wav files are not supported by all browsers. We recommend you convert "${name}" to another format, e.g. mp3`); } } - // preload.js for the other extensions (download type decided by preload.js): else { - manifest.push(/*new createjs.LoadItem().set(*/{ + manifest.push(/*new createjs.LoadItem().set(*/ { id: name, src: pathStatusData.path, - crossOrigin: 'Anonymous' - }/*)*/); + crossOrigin: "Anonymous", + } /*)*/); } } - // (*) start loading non-sound resources: if (manifest.length > 0) { @@ -1088,59 +1065,57 @@ export class ServerManager extends PsychObject { this.setStatus(ServerManager.Status.READY); this.emit(ServerManager.Event.RESOURCE, { - message: ServerManager.Event.DOWNLOAD_COMPLETED}); + message: ServerManager.Event.DOWNLOAD_COMPLETED, + }); } } - // (*) prepare and start loading sound resources: for (const name of soundResources) { const pathStatusData = this._resources.get(name); - pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; this.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOADING_RESOURCE, - resource: name + resource: name, }); const howl = new Howl({ src: pathStatusData.path, preload: false, - autoplay: false + autoplay: false, }); - howl.on('load', (event) => + howl.on("load", (event) => { - ++ self._nbLoadedResources; + ++self._nbLoadedResources; pathStatusData.data = howl; - pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.RESOURCE_DOWNLOADED, - resource: name + resource: name, }); if (self._nbLoadedResources === resources.size) { self.setStatus(ServerManager.Status.READY); self.emit(ServerManager.Event.RESOURCE, { - message: ServerManager.Event.DOWNLOAD_COMPLETED}); + message: ServerManager.Event.DOWNLOAD_COMPLETED, + }); } }); - howl.on('loaderror', (id, error) => + howl.on("loaderror", (id, error) => { // throw { ...response, error: 'unable to download resource: ' + name + ' (' + util.toString(error) + ')' }; - throw Object.assign(response, {error: 'unable to download resource: ' + name + ' (' + util.toString(error) + ')'}); + throw Object.assign(response, { error: "unable to download resource: " + name + " (" + util.toString(error) + ")" }); }); howl.load(); } - } - } - /** * Server event * @@ -1155,35 +1130,34 @@ ServerManager.Event = { /** * Event type: resource event */ - RESOURCE: Symbol.for('RESOURCE'), + RESOURCE: Symbol.for("RESOURCE"), /** * Event: resources have started to download */ - DOWNLOADING_RESOURCES: Symbol.for('DOWNLOADING_RESOURCES'), + DOWNLOADING_RESOURCES: Symbol.for("DOWNLOADING_RESOURCES"), /** * Event: a specific resource download has started */ - DOWNLOADING_RESOURCE: Symbol.for('DOWNLOADING_RESOURCE'), + DOWNLOADING_RESOURCE: Symbol.for("DOWNLOADING_RESOURCE"), /** * Event: a specific resource has been downloaded */ - RESOURCE_DOWNLOADED: Symbol.for('RESOURCE_DOWNLOADED'), + RESOURCE_DOWNLOADED: Symbol.for("RESOURCE_DOWNLOADED"), /** * Event: resources have all downloaded */ - DOWNLOADS_COMPLETED: Symbol.for('DOWNLOAD_COMPLETED'), + DOWNLOADS_COMPLETED: Symbol.for("DOWNLOAD_COMPLETED"), /** * Event type: status event */ - STATUS: Symbol.for('STATUS') + STATUS: Symbol.for("STATUS"), }; - /** * Server status * @@ -1196,20 +1170,19 @@ ServerManager.Status = { /** * The manager is ready. */ - READY: Symbol.for('READY'), + READY: Symbol.for("READY"), /** * The manager is busy, e.g. it is downloaded resources. */ - BUSY: Symbol.for('BUSY'), + BUSY: Symbol.for("BUSY"), /** * The manager has encountered an error, e.g. it was unable to download a resource. */ - ERROR: Symbol.for('ERROR') + ERROR: Symbol.for("ERROR"), }; - /** * Resource status * @@ -1222,20 +1195,20 @@ ServerManager.ResourceStatus = { /** * The resource has been registered. */ - REGISTERED: Symbol.for('REGISTERED'), + REGISTERED: Symbol.for("REGISTERED"), /** * The resource is currently downloading. */ - DOWNLOADING: Symbol.for('DOWNLOADING'), + DOWNLOADING: Symbol.for("DOWNLOADING"), /** * The resource has been downloaded. */ - DOWNLOADED: Symbol.for('DOWNLOADED'), + DOWNLOADED: Symbol.for("DOWNLOADED"), /** * There was an error during downloading, or the resource is in an unknown state. */ - ERROR: Symbol.for('ERROR'), + ERROR: Symbol.for("ERROR"), }; diff --git a/src/core/Window.js b/src/core/Window.js index 8926e68..7b11cc6 100644 --- a/src/core/Window.js +++ b/src/core/Window.js @@ -7,11 +7,11 @@ * @license Distributed under the terms of the MIT License */ -import * as PIXI from 'pixi.js-legacy'; -import {Color} from '../util/Color.js'; -import {PsychObject} from '../util/PsychObject.js'; -import {MonotonicClock} from '../util/Clock.js'; -import {Logger} from "./Logger.js"; +import * as PIXI from "pixi.js-legacy"; +import { MonotonicClock } from "../util/Clock.js"; +import { Color } from "../util/Color.js"; +import { PsychObject } from "../util/PsychObject.js"; +import { Logger } from "./Logger.js"; /** *

Window displays the various stimuli of the experiment.

@@ -32,7 +32,6 @@ import {Logger} from "./Logger.js"; */ export class Window extends PsychObject { - /** * Getter for monitorFramePeriod. * @@ -46,14 +45,14 @@ export class Window extends PsychObject } constructor({ - psychoJS, - name, - fullscr = false, - color = new Color('black'), - units = 'pix', - waitBlanking = false, - autoLog = true - } = {}) + psychoJS, + name, + fullscr = false, + color = new Color("black"), + units = "pix", + waitBlanking = false, + autoLog = true, + } = {}) { super(psychoJS, name); @@ -63,13 +62,12 @@ export class Window extends PsychObject // list of all elements, in the order they are currently drawn: this._drawList = []; - this._addAttribute('fullscr', fullscr); - this._addAttribute('color', color); - this._addAttribute('units', units); - this._addAttribute('waitBlanking', waitBlanking); - this._addAttribute('autoLog', autoLog); - this._addAttribute('size', []); - + this._addAttribute("fullscr", fullscr); + this._addAttribute("color", color); + this._addAttribute("units", units); + this._addAttribute("waitBlanking", waitBlanking); + this._addAttribute("autoLog", autoLog); + this._addAttribute("size", []); // setup PIXI: this._setupPixi(); @@ -78,15 +76,14 @@ export class Window extends PsychObject this._flipCallbacks = []; - // fullscreen listener: this._windowAlreadyInFullScreen = false; const self = this; - document.addEventListener('fullscreenchange', (event) => + document.addEventListener("fullscreenchange", (event) => { self._windowAlreadyInFullScreen = !!document.fullscreenElement; - console.log('windowAlreadyInFullScreen:', self._windowAlreadyInFullScreen); + console.log("windowAlreadyInFullScreen:", self._windowAlreadyInFullScreen); // the Window and all of the stimuli need to be updated: self._needUpdate = true; @@ -96,14 +93,12 @@ export class Window extends PsychObject } }); - if (this._autoLog) { this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); } } - /** * Close the window. * @@ -126,21 +121,20 @@ export class Window extends PsychObject } // destroy the renderer and the WebGL context: - if (typeof this._renderer.gl !== 'undefined') + if (typeof this._renderer.gl !== "undefined") { - const extension = this._renderer.gl.getExtension('WEBGL_lose_context'); + const extension = this._renderer.gl.getExtension("WEBGL_lose_context"); extension.loseContext(); } this._renderer.destroy(); - window.removeEventListener('resize', this._resizeCallback); - window.removeEventListener('orientationchange', this._resizeCallback); + window.removeEventListener("resize", this._resizeCallback); + window.removeEventListener("orientationchange", this._resizeCallback); this._renderer = null; } - /** * Estimate the frame rate. * @@ -158,7 +152,6 @@ export class Window extends PsychObject return fps; } - /** * Take the browser full screen if possible. * @@ -173,39 +166,37 @@ export class Window extends PsychObject // test whether the window is already fullscreen. // this._windowAlreadyInFullScreen = (!window.screenTop && !window.screenY); - if (this.fullscr/* && !this._windowAlreadyInFullScreen*/) + if (this.fullscr /* && !this._windowAlreadyInFullScreen*/) { - this._psychoJS.logger.debug('Resizing Window: ', this._name, 'to full screen.'); + this._psychoJS.logger.debug("Resizing Window: ", this._name, "to full screen."); - if (typeof document.documentElement.requestFullscreen === 'function') + if (typeof document.documentElement.requestFullscreen === "function") { document.documentElement.requestFullscreen() .catch(() => { - this.psychoJS.logger.warn('Unable to go fullscreen.'); + this.psychoJS.logger.warn("Unable to go fullscreen."); }); } - else if (typeof document.documentElement.mozRequestFullScreen === 'function') + else if (typeof document.documentElement.mozRequestFullScreen === "function") { document.documentElement.mozRequestFullScreen(); } - else if (typeof document.documentElement.webkitRequestFullscreen === 'function') + else if (typeof document.documentElement.webkitRequestFullscreen === "function") { document.documentElement.webkitRequestFullscreen(); } - else if (typeof document.documentElement.msRequestFullscreen === 'function') + else if (typeof document.documentElement.msRequestFullscreen === "function") { document.documentElement.msRequestFullscreen(); } else { - this.psychoJS.logger.warn('Unable to go fullscreen.'); + this.psychoJS.logger.warn("Unable to go fullscreen."); } } - } - /** * Take the browser back from full screen if needed. * @@ -217,37 +208,35 @@ export class Window extends PsychObject { if (this.fullscr) { - this._psychoJS.logger.debug('Resizing Window: ', this._name, 'back from full screen.'); + this._psychoJS.logger.debug("Resizing Window: ", this._name, "back from full screen."); - if (typeof document.exitFullscreen === 'function') + if (typeof document.exitFullscreen === "function") { document.exitFullscreen() .catch(() => { - this.psychoJS.logger.warn('Unable to close fullscreen.'); + this.psychoJS.logger.warn("Unable to close fullscreen."); }); } - else if (typeof document.mozCancelFullScreen === 'function') + else if (typeof document.mozCancelFullScreen === "function") { document.mozCancelFullScreen(); } - else if (typeof document.webkitExitFullscreen === 'function') + else if (typeof document.webkitExitFullscreen === "function") { document.webkitExitFullscreen(); } - else if (typeof document.msExitFullscreen === 'function') + else if (typeof document.msExitFullscreen === "function") { document.msExitFullscreen(); } else { - this.psychoJS.logger.warn('Unable to close fullscreen.'); + this.psychoJS.logger.warn("Unable to close fullscreen."); } } - } - /** * Log a message. * @@ -262,15 +251,14 @@ export class Window extends PsychObject * @param {Object} [obj] the object associated with the message */ logOnFlip({ - msg, - level = Logger.ServerLevel.EXP, - obj - } = {}) + msg, + level = Logger.ServerLevel.EXP, + obj, + } = {}) { - this._msgToBeLogged.push({msg, level, obj}); + this._msgToBeLogged.push({ msg, level, obj }); } - /** * Callback function for callOnFlip. * @@ -291,10 +279,9 @@ export class Window extends PsychObject */ callOnFlip(flipCallback, ...flipCallbackArgs) { - this._flipCallbacks.push({function: flipCallback, arguments: flipCallbackArgs}); + this._flipCallbacks.push({ function: flipCallback, arguments: flipCallbackArgs }); } - /** * Render the stimuli onto the canvas. * @@ -309,13 +296,12 @@ export class Window extends PsychObject return; } - this._frameCount++; // render the PIXI container: this._renderer.render(this._rootContainer); - if (typeof this._renderer.gl !== 'undefined') + if (typeof this._renderer.gl !== "undefined") { // this is to make sure that the GPU is done rendering, it may not be necessary // [http://www.html5gamedevs.com/topic/27849-detect-when-view-has-been-rendered/] @@ -331,7 +317,7 @@ export class Window extends PsychObject // call the callOnFlip functions and remove them: for (let callback of this._flipCallbacks) { - callback['function'](...callback['arguments']); + callback["function"](...callback["arguments"]); } this._flipCallbacks = []; @@ -342,7 +328,6 @@ export class Window extends PsychObject this._refresh(); } - /** * Update this window, if need be. * @@ -367,7 +352,6 @@ export class Window extends PsychObject } } - /** * Recompute this window's draw list and _container children for the next animation frame. * @@ -383,7 +367,7 @@ export class Window extends PsychObject // update it, then put it back for (const stimulus of this._drawList) { - if (stimulus._needUpdate && typeof stimulus._pixi !== 'undefined') + if (stimulus._needUpdate && typeof stimulus._pixi !== "undefined") { this._rootContainer.removeChild(stimulus._pixi); stimulus._updateIfNeeded(); @@ -392,7 +376,6 @@ export class Window extends PsychObject } } - /** * Force an update of all stimuli in this window's drawlist. * @@ -412,7 +395,6 @@ export class Window extends PsychObject this._refresh(); } - /** * Setup PIXI. * @@ -434,10 +416,10 @@ export class Window extends PsychObject width: this._size[0], height: this._size[1], backgroundColor: this.color.int, - resolution: window.devicePixelRatio + resolution: window.devicePixelRatio, }); - this._renderer.view.style.transform = 'translatez(0)'; - this._renderer.view.style.position = 'absolute'; + this._renderer.view.style.transform = "translatez(0)"; + this._renderer.view.style.position = "absolute"; document.body.appendChild(this._renderer.view); // we also change the background color of the body since the dialog popup may be longer than the window's height: @@ -459,11 +441,10 @@ export class Window extends PsychObject Window._resizePixiRenderer(this, e); this._fullRefresh(); }; - window.addEventListener('resize', this._resizeCallback); - window.addEventListener('orientationchange', this._resizeCallback); + window.addEventListener("resize", this._resizeCallback); + window.addEventListener("orientationchange", this._resizeCallback); } - /** * Adjust the size of the renderer and the position of the root container * in response to a change in the browser's size. @@ -476,17 +457,17 @@ export class Window extends PsychObject */ static _resizePixiRenderer(pjsWindow, event) { - pjsWindow._psychoJS.logger.debug('resizing Window: ', pjsWindow._name, 'event:', JSON.stringify(event)); + pjsWindow._psychoJS.logger.debug("resizing Window: ", pjsWindow._name, "event:", JSON.stringify(event)); // update the size of the PsychoJS Window: pjsWindow._size[0] = window.innerWidth; pjsWindow._size[1] = window.innerHeight; // update the PIXI renderer: - pjsWindow._renderer.view.style.width = pjsWindow._size[0] + 'px'; - pjsWindow._renderer.view.style.height = pjsWindow._size[1] + 'px'; - pjsWindow._renderer.view.style.left = '0px'; - pjsWindow._renderer.view.style.top = '0px'; + pjsWindow._renderer.view.style.width = pjsWindow._size[0] + "px"; + pjsWindow._renderer.view.style.height = pjsWindow._size[1] + "px"; + pjsWindow._renderer.view.style.left = "0px"; + pjsWindow._renderer.view.style.top = "0px"; pjsWindow._renderer.resize(pjsWindow._size[0], pjsWindow._size[1]); // setup the container such that (0,0) is at the centre of the window @@ -496,7 +477,6 @@ export class Window extends PsychObject pjsWindow._rootContainer.scale.y = -1; } - /** * Send all logged messages to the {@link Logger}. * @@ -514,5 +494,4 @@ export class Window extends PsychObject this._msgToBeLogged = []; } - } diff --git a/src/core/WindowMixin.js b/src/core/WindowMixin.js index 80e896a..92e82d7 100644 --- a/src/core/WindowMixin.js +++ b/src/core/WindowMixin.js @@ -7,7 +7,6 @@ * @license Distributed under the terms of the MIT License */ - /** *

This mixin implements various unit-handling measurement methods.

* @@ -19,16 +18,15 @@ * @mixin * */ -export let WindowMixin = (superclass) => class extends superclass -{ - constructor(args) +export let WindowMixin = (superclass) => + class extends superclass { - super(args); - } + constructor(args) + { + super(args); + } - - - /** + /** * Convert the given length from stimulus unit to pixel units. * * @name module:core.WindowMixin#_getLengthPix @@ -38,47 +36,46 @@ export let WindowMixin = (superclass) => class extends superclass * @param {boolean} [integerCoordinates = false] - whether or not to round the length. * @return {number} - the length in pixel units */ - _getLengthPix(length, integerCoordinates = false) - { - let response = { - origin: 'WindowMixin._getLengthPix', - context: 'when converting a length from stimulus unit to pixel units' - }; + _getLengthPix(length, integerCoordinates = false) + { + let response = { + origin: "WindowMixin._getLengthPix", + context: "when converting a length from stimulus unit to pixel units", + }; - let length_px; + let length_px; - if (this._units === 'pix') - { - length_px = length; - } - else if (typeof this._units === 'undefined' || this._units === 'norm') - { - var winSize = this.win.size; - length_px = length * winSize[1] / 2; // TODO: how do we handle norm when width != height? - } - else if (this._units === 'height') - { - const minSize = Math.min(this.win.size[0], this.win.size[1]); - length_px = length * minSize; - } - else - { - // throw { ...response, error: 'unable to deal with unit: ' + this._units }; - throw Object.assign(response, {error: 'unable to deal with unit: ' + this._units}); + if (this._units === "pix") + { + length_px = length; + } + else if (typeof this._units === "undefined" || this._units === "norm") + { + var winSize = this.win.size; + length_px = length * winSize[1] / 2; // TODO: how do we handle norm when width != height? + } + else if (this._units === "height") + { + const minSize = Math.min(this.win.size[0], this.win.size[1]); + length_px = length * minSize; + } + else + { + // throw { ...response, error: 'unable to deal with unit: ' + this._units }; + throw Object.assign(response, { error: "unable to deal with unit: " + this._units }); + } + + if (integerCoordinates) + { + return Math.round(length_px); + } + else + { + return length_px; + } } - if (integerCoordinates) - { - return Math.round(length_px); - } - else - { - return length_px; - } - } - - - /** + /** * Convert the given length from pixel units to the stimulus units * * @name module:core.WindowMixin#_getLengthUnits @@ -87,36 +84,35 @@ export let WindowMixin = (superclass) => class extends superclass * @param {number} length_px - the length in pixel units * @return {number} - the length in stimulus units */ - _getLengthUnits(length_px) - { - let response = { - origin: 'WindowMixin._getLengthUnits', - context: 'when converting a length from pixel unit to stimulus units' - }; + _getLengthUnits(length_px) + { + let response = { + origin: "WindowMixin._getLengthUnits", + context: "when converting a length from pixel unit to stimulus units", + }; - if (this._units === 'pix') - { - return length_px; + if (this._units === "pix") + { + return length_px; + } + else if (typeof this._units === "undefined" || this._units === "norm") + { + const winSize = this.win.size; + return length_px / (winSize[1] / 2); // TODO: how do we handle norm when width != height? + } + else if (this._units === "height") + { + const minSize = Math.min(this.win.size[0], this.win.size[1]); + return length_px / minSize; + } + else + { + // throw { ...response, error: 'unable to deal with unit: ' + this._units }; + throw Object.assign(response, { error: "unable to deal with unit: " + this._units }); + } } - else if (typeof this._units === 'undefined' || this._units === 'norm') - { - const winSize = this.win.size; - return length_px / (winSize[1] / 2); // TODO: how do we handle norm when width != height? - } - else if (this._units === 'height') - { - const minSize = Math.min(this.win.size[0], this.win.size[1]); - return length_px / minSize; - } - else - { - // throw { ...response, error: 'unable to deal with unit: ' + this._units }; - throw Object.assign(response, {error: 'unable to deal with unit: ' + this._units}); - } - } - - /** + /** * Convert the given length from stimulus units to pixel units * * @name module:core.WindowMixin#_getHorLengthPix @@ -125,35 +121,35 @@ export let WindowMixin = (superclass) => class extends superclass * @param {number} length - the length in stimulus units * @return {number} - the length in pixels */ - _getHorLengthPix(length) - { - let response = { - origin: 'WindowMixin._getHorLengthPix', - context: 'when converting a length from stimulus units to pixel units' - }; + _getHorLengthPix(length) + { + let response = { + origin: "WindowMixin._getHorLengthPix", + context: "when converting a length from stimulus units to pixel units", + }; - if (this._units === 'pix') - { - return length; + if (this._units === "pix") + { + return length; + } + else if (typeof this._units === "undefined" || this._units === "norm") + { + var winSize = this.win.size; + return length * winSize[0] / 2; + } + else if (this._units === "height") + { + const minSize = Math.min(this.win.size[0], this.win.size[1]); + return length * minSize; + } + else + { + // throw { ...response, error: 'unable to deal with unit: ' + this._units }; + throw Object.assign(response, { error: "unable to deal with unit: " + this._units }); + } } - else if (typeof this._units === 'undefined' || this._units === 'norm') - { - var winSize = this.win.size; - return length * winSize[0] / 2; - } - else if (this._units === 'height') - { - const minSize = Math.min(this.win.size[0], this.win.size[1]); - return length * minSize; - } - else - { - // throw { ...response, error: 'unable to deal with unit: ' + this._units }; - throw Object.assign(response, {error: 'unable to deal with unit: ' + this._units}); - } - } - /** + /** * Convert the given length from pixel units to the stimulus units * * @name module:core.WindowMixin#_getVerLengthPix @@ -162,32 +158,31 @@ export let WindowMixin = (superclass) => class extends superclass * @param {number} length - the length in pixel units * @return {number} - the length in stimulus units */ - _getVerLengthPix(length) - { - let response = { - origin: 'WindowMixin._getVerLengthPix', - context: 'when converting a length from pixel unit to stimulus units' - }; + _getVerLengthPix(length) + { + let response = { + origin: "WindowMixin._getVerLengthPix", + context: "when converting a length from pixel unit to stimulus units", + }; - if (this._units === 'pix') - { - return length; + if (this._units === "pix") + { + return length; + } + else if (typeof this._units === "undefined" || this._units === "norm") + { + var winSize = this.win.size; + return length * winSize[1] / 2; + } + else if (this._units === "height") + { + const minSize = Math.min(this.win.size[0], this.win.size[1]); + return length * minSize; + } + else + { + // throw { ...response, error: 'unable to deal with unit: ' + this._units }; + throw Object.assign(response, { error: "unable to deal with unit: " + this._units }); + } } - else if (typeof this._units === 'undefined' || this._units === 'norm') - { - var winSize = this.win.size; - return length * winSize[1] / 2; - } - else if (this._units === 'height') - { - const minSize = Math.min(this.win.size[0], this.win.size[1]); - return length * minSize; - } - else - { - // throw { ...response, error: 'unable to deal with unit: ' + this._units }; - throw Object.assign(response, {error: 'unable to deal with unit: ' + this._units}); - } - } - -}; + }; diff --git a/src/core/index.js b/src/core/index.js index d8a94d9..330ad8a 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -1,10 +1,10 @@ -export * from './EventManager.js'; -export * from './GUI.js'; -export * from './Keyboard.js'; -export * from './Logger.js'; -export * from './MinimalStim.js'; -export * from './Mouse.js'; -export * from './PsychoJS.js'; -export * from './ServerManager.js'; -export * from './Window.js'; -export * from './WindowMixin.js'; +export * from "./EventManager.js"; +export * from "./GUI.js"; +export * from "./Keyboard.js"; +export * from "./Logger.js"; +export * from "./MinimalStim.js"; +export * from "./Mouse.js"; +export * from "./PsychoJS.js"; +export * from "./ServerManager.js"; +export * from "./Window.js"; +export * from "./WindowMixin.js"; From 9992bbde93ccd9a11b4352df0dd1d0a911507544 Mon Sep 17 00:00:00 2001 From: Sotiri Bakagiannis Date: Wed, 14 Jul 2021 11:07:34 +0100 Subject: [PATCH 29/29] util/Pixi: import missing Util.to_px --- src/util/Pixi.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/Pixi.js b/src/util/Pixi.js index fcc5cf8..b805768 100644 --- a/src/util/Pixi.js +++ b/src/util/Pixi.js @@ -8,6 +8,7 @@ */ import * as PIXI from "pixi.js-legacy"; +import { to_px } from "./Util.js"; /** * Convert a position to a PIXI Point.