From 2f37c57d22010f4aa78558dfc3a189d4ac0bbedb Mon Sep 17 00:00:00 2001 From: bjoluc Date: Tue, 12 Sep 2023 17:33:19 +0200 Subject: [PATCH] Refactor free-sort plugin to use pointer events (#2893) --- .changeset/dull-dragons-wonder.md | 5 + packages/plugin-free-sort/src/index.ts | 142 +++++-------------------- packages/plugin-free-sort/src/utils.ts | 46 ++++++++ 3 files changed, 78 insertions(+), 115 deletions(-) create mode 100644 .changeset/dull-dragons-wonder.md create mode 100644 packages/plugin-free-sort/src/utils.ts diff --git a/.changeset/dull-dragons-wonder.md b/.changeset/dull-dragons-wonder.md new file mode 100644 index 00000000..81ab9964 --- /dev/null +++ b/.changeset/dull-dragons-wonder.md @@ -0,0 +1,5 @@ +--- +"@jspsych/plugin-free-sort": patch +--- + +Fix event handling on non-touch devices diff --git a/packages/plugin-free-sort/src/index.ts b/packages/plugin-free-sort/src/index.ts index 75512fb8..ccfad66c 100644 --- a/packages/plugin-free-sort/src/index.ts +++ b/packages/plugin-free-sort/src/index.ts @@ -1,5 +1,7 @@ import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych"; +import { inside_ellipse, make_arr, random_coordinate, shuffle } from "./utils"; + const info = { name: "free-sort", parameters: { @@ -240,17 +242,15 @@ class FreeSortPlugin implements JsPsychPlugin { 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)) { if (x > (trial.sort_area_width - trial.stim_width) * 0.5) { - //r_coords.push({ x:x, y:y } ) r_coords.push({ x: x + trial.sort_area_width * (0.5 * trial.column_spread_factor), - y: y, + y, }); } else { l_coords.push({ 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 { stimuli = shuffle(stimuli); } - let inside = []; for (let i = 0; i < stimuli.length; i++) { var coords; if (trial.stim_starts_inside) { @@ -312,21 +311,19 @@ class FreeSortPlugin implements JsPsychPlugin { x: coords.x, 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 - let moves = []; + const moves = []; // are objects currently inside let cur_in = false; // draggable items - const draggables = display_element.querySelectorAll(".jspsych-free-sort-draggable"); + const draggables = Array.from( + display_element.querySelectorAll(".jspsych-free-sort-draggable") + ); // button (will show when all items are inside) and border (will change color) const border: HTMLElement = display_element.querySelector("#jspsych-free-sort-border"); @@ -345,48 +342,13 @@ class FreeSortPlugin implements JsPsychPlugin { trial.counter_text_finished; } - let start_event_name = "mousedown"; - let move_event_name = "mousemove"; - let end_event_name = "mouseup"; - if (typeof document.ontouchend !== "undefined") { - // for touch devices - 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; - } + for (const draggable of draggables) { + draggable.addEventListener("pointerdown", function ({ clientX: pageX, clientY: pageY }) { + let x = pageX - this.offsetLeft; + let y = pageY - this.offsetTop - window.scrollY; + this.style.transform = "scale(" + trial.scale_factor + "," + trial.scale_factor + ")"; + const on_pointer_move = ({ clientX, clientY }: PointerEvent) => { cur_in = inside_ellipse( clientX - x, clientY - y, @@ -396,12 +358,12 @@ class FreeSortPlugin implements JsPsychPlugin { trial.sort_area_height * 0.5, trial.sort_area_shape == "square" ); - elem.style.top = + this.style.top = Math.min( trial.sort_area_height - trial.stim_height * 0.5, Math.max(-trial.stim_height * 0.5, clientY - y) ) + "px"; - elem.style.left = + this.style.left = Math.min( trial.sort_area_width * 1.5 - trial.stim_width, Math.max(-trial.sort_area_width * 0.5, clientX - x) @@ -419,7 +381,7 @@ class FreeSortPlugin implements JsPsychPlugin { } // 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); // modify text and background if all items are inside @@ -437,11 +399,11 @@ class FreeSortPlugin implements JsPsychPlugin { 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) => { - document.removeEventListener(move_event_name, move_event); - elem.style.transform = "scale(1, 1)"; + const on_pointer_up = (e) => { + document.removeEventListener("pointermove", on_pointer_move); + this.style.transform = "scale(1, 1)"; if (trial.change_border_background_color) { if (inside.every(Boolean)) { border.style.background = trial.border_color_in; @@ -452,13 +414,13 @@ class FreeSortPlugin implements JsPsychPlugin { } } moves.push({ - src: elem.dataset.src, - x: elem.offsetLeft, - y: elem.offsetTop, + src: this.dataset.src, + x: this.offsetLeft, + 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 { } 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, - }; - } } } diff --git a/packages/plugin-free-sort/src/utils.ts b/packages/plugin-free-sort/src/utils.ts new file mode 100644 index 00000000..1a5e5421 --- /dev/null +++ b/packages/plugin-free-sort/src/utils.ts @@ -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, + }; +}