Refactor free-sort plugin to use pointer events (#2893)

This commit is contained in:
bjoluc 2023-09-12 17:33:19 +02:00 committed by GitHub
parent cb87df4c10
commit 2f37c57d22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 115 deletions

View File

@ -0,0 +1,5 @@
---
"@jspsych/plugin-free-sort": patch
---
Fix event handling on non-touch devices

View File

@ -1,5 +1,7 @@
import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych"; import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
import { inside_ellipse, make_arr, random_coordinate, shuffle } from "./utils";
const info = <const>{ const info = <const>{
name: "free-sort", name: "free-sort",
parameters: { parameters: {
@ -240,17 +242,15 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
for (const x of make_arr(0, trial.sort_area_width - trial.stim_width, num_rows)) { for (const x of make_arr(0, trial.sort_area_width - trial.stim_width, num_rows)) {
for (const y of make_arr(0, trial.sort_area_height - trial.stim_height, num_rows)) { for (const y of make_arr(0, trial.sort_area_height - trial.stim_height, num_rows)) {
if (x > (trial.sort_area_width - trial.stim_width) * 0.5) { if (x > (trial.sort_area_width - trial.stim_width) * 0.5) {
//r_coords.push({ x:x, y:y } )
r_coords.push({ r_coords.push({
x: x + trial.sort_area_width * (0.5 * trial.column_spread_factor), x: x + trial.sort_area_width * (0.5 * trial.column_spread_factor),
y: y, y,
}); });
} else { } else {
l_coords.push({ l_coords.push({
x: x - trial.sort_area_width * (0.5 * trial.column_spread_factor), x: x - trial.sort_area_width * (0.5 * trial.column_spread_factor),
y: y, y,
}); });
//l_coords.push({ x:x, y:y } )
} }
} }
} }
@ -267,7 +267,6 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
stimuli = shuffle(stimuli); stimuli = shuffle(stimuli);
} }
let inside = [];
for (let i = 0; i < stimuli.length; i++) { for (let i = 0; i < stimuli.length; i++) {
var coords; var coords;
if (trial.stim_starts_inside) { if (trial.stim_starts_inside) {
@ -312,21 +311,19 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
x: coords.x, x: coords.x,
y: coords.y, y: coords.y,
}); });
if (trial.stim_starts_inside) {
inside.push(true);
} else {
inside.push(false);
}
} }
const inside = stimuli.map(() => trial.stim_starts_inside);
// moves within a trial // moves within a trial
let moves = []; const moves = [];
// are objects currently inside // are objects currently inside
let cur_in = false; let cur_in = false;
// draggable items // draggable items
const draggables = display_element.querySelectorAll(".jspsych-free-sort-draggable"); const draggables = Array.from(
display_element.querySelectorAll<HTMLImageElement>(".jspsych-free-sort-draggable")
);
// button (will show when all items are inside) and border (will change color) // button (will show when all items are inside) and border (will change color)
const border: HTMLElement = display_element.querySelector("#jspsych-free-sort-border"); const border: HTMLElement = display_element.querySelector("#jspsych-free-sort-border");
@ -345,48 +342,13 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
trial.counter_text_finished; trial.counter_text_finished;
} }
let start_event_name = "mousedown"; for (const draggable of draggables) {
let move_event_name = "mousemove"; draggable.addEventListener("pointerdown", function ({ clientX: pageX, clientY: pageY }) {
let end_event_name = "mouseup"; let x = pageX - this.offsetLeft;
if (typeof document.ontouchend !== "undefined") { let y = pageY - this.offsetTop - window.scrollY;
// for touch devices this.style.transform = "scale(" + trial.scale_factor + "," + trial.scale_factor + ")";
start_event_name = "touchstart";
move_event_name = "touchmove";
end_event_name = "touchend";
}
for (let i = 0; i < draggables.length; i++) {
draggables[i].addEventListener(start_event_name, (event: MouseEvent | TouchEvent) => {
let pageX: number;
let pageY: number;
if (event instanceof MouseEvent) {
pageX = event.pageX;
pageY = event.pageY;
}
//if (typeof document.ontouchend !== "undefined") {
if (event instanceof TouchEvent) {
// for touch devices
event.preventDefault();
const touchObject = event.changedTouches[0];
pageX = touchObject.pageX;
pageY = touchObject.pageY;
}
let elem = event.currentTarget as HTMLImageElement;
let x = pageX - elem.offsetLeft;
let y = pageY - elem.offsetTop - window.scrollY;
elem.style.transform = "scale(" + trial.scale_factor + "," + trial.scale_factor + ")";
let move_event = (e) => {
let clientX = e.clientX;
let clientY = e.clientY;
if (typeof document.ontouchend !== "undefined") {
// for touch devices
const touchObject = e.changedTouches[0];
clientX = touchObject.clientX;
clientY = touchObject.clientY;
}
const on_pointer_move = ({ clientX, clientY }: PointerEvent) => {
cur_in = inside_ellipse( cur_in = inside_ellipse(
clientX - x, clientX - x,
clientY - y, clientY - y,
@ -396,12 +358,12 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
trial.sort_area_height * 0.5, trial.sort_area_height * 0.5,
trial.sort_area_shape == "square" trial.sort_area_shape == "square"
); );
elem.style.top = this.style.top =
Math.min( Math.min(
trial.sort_area_height - trial.stim_height * 0.5, trial.sort_area_height - trial.stim_height * 0.5,
Math.max(-trial.stim_height * 0.5, clientY - y) Math.max(-trial.stim_height * 0.5, clientY - y)
) + "px"; ) + "px";
elem.style.left = this.style.left =
Math.min( Math.min(
trial.sort_area_width * 1.5 - trial.stim_width, trial.sort_area_width * 1.5 - trial.stim_width,
Math.max(-trial.sort_area_width * 0.5, clientX - x) Math.max(-trial.sort_area_width * 0.5, clientX - x)
@ -419,7 +381,7 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
} }
// replace in overall array, grab index from item id // replace in overall array, grab index from item id
var elem_number = parseInt(elem.id.split("jspsych-free-sort-draggable-")[1], 10); var elem_number = parseInt(this.id.split("jspsych-free-sort-draggable-")[1], 10);
inside.splice(elem_number, 1, cur_in); inside.splice(elem_number, 1, cur_in);
// modify text and background if all items are inside // modify text and background if all items are inside
@ -437,11 +399,11 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
get_counter_text(inside.length - inside.filter(Boolean).length); get_counter_text(inside.length - inside.filter(Boolean).length);
} }
}; };
document.addEventListener(move_event_name, move_event); document.addEventListener("pointermove", on_pointer_move);
var end_event = (e) => { const on_pointer_up = (e) => {
document.removeEventListener(move_event_name, move_event); document.removeEventListener("pointermove", on_pointer_move);
elem.style.transform = "scale(1, 1)"; this.style.transform = "scale(1, 1)";
if (trial.change_border_background_color) { if (trial.change_border_background_color) {
if (inside.every(Boolean)) { if (inside.every(Boolean)) {
border.style.background = trial.border_color_in; border.style.background = trial.border_color_in;
@ -452,13 +414,13 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
} }
} }
moves.push({ moves.push({
src: elem.dataset.src, src: this.dataset.src,
x: elem.offsetLeft, x: this.offsetLeft,
y: elem.offsetTop, y: this.offsetTop,
}); });
document.removeEventListener(end_event_name, end_event); document.removeEventListener("pointerup", on_pointer_up);
}; };
document.addEventListener(end_event_name, end_event); document.addEventListener("pointerup", on_pointer_up);
}); });
} }
@ -507,56 +469,6 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
} }
return text_out; return text_out;
} }
// helper functions
function shuffle(array) {
// define three variables
let cur_idx = array.length,
tmp_val,
rand_idx;
// While there remain elements to shuffle...
while (0 !== cur_idx) {
// Pick a remaining element...
rand_idx = Math.floor(Math.random() * cur_idx);
cur_idx -= 1;
// And swap it with the current element.
tmp_val = array[cur_idx];
array[cur_idx] = array[rand_idx];
array[rand_idx] = tmp_val;
}
return array;
}
function make_arr(startValue, stopValue, cardinality) {
const step = (stopValue - startValue) / (cardinality - 1);
let arr = [];
for (let i = 0; i < cardinality; i++) {
arr.push(startValue + step * i);
}
return arr;
}
function inside_ellipse(x, y, x0, y0, rx, ry, square = false) {
const results = [];
if (square) {
return Math.abs(x - x0) <= rx && Math.abs(y - y0) <= ry;
} else {
return (
(x - x0) * (x - x0) * (ry * ry) + (y - y0) * (y - y0) * (rx * rx) <= rx * rx * (ry * ry)
);
}
}
function random_coordinate(max_width, max_height) {
const rnd_x = Math.floor(Math.random() * (max_width - 1));
const rnd_y = Math.floor(Math.random() * (max_height - 1));
return {
x: rnd_x,
y: rnd_y,
};
}
} }
} }

View File

@ -0,0 +1,46 @@
export function shuffle(array) {
// define three variables
let cur_idx = array.length,
tmp_val,
rand_idx;
// While there remain elements to shuffle...
while (0 !== cur_idx) {
// Pick a remaining element...
rand_idx = Math.floor(Math.random() * cur_idx);
cur_idx -= 1;
// And swap it with the current element.
tmp_val = array[cur_idx];
array[cur_idx] = array[rand_idx];
array[rand_idx] = tmp_val;
}
return array;
}
export function make_arr(startValue, stopValue, cardinality) {
const step = (stopValue - startValue) / (cardinality - 1);
let arr = [];
for (let i = 0; i < cardinality; i++) {
arr.push(startValue + step * i);
}
return arr;
}
export function inside_ellipse(x, y, x0, y0, rx, ry, square = false) {
const results = [];
if (square) {
return Math.abs(x - x0) <= rx && Math.abs(y - y0) <= ry;
} else {
return (x - x0) * (x - x0) * (ry * ry) + (y - y0) * (y - y0) * (rx * rx) <= rx * rx * (ry * ry);
}
}
export function random_coordinate(max_width, max_height) {
const rnd_x = Math.floor(Math.random() * (max_width - 1));
const rnd_y = Math.floor(Math.random() * (max_height - 1));
return {
x: rnd_x,
y: rnd_y,
};
}