diff --git a/.changeset/brave-lions-arrive.md b/.changeset/brave-lions-arrive.md
new file mode 100644
index 0000000..00a556a
--- /dev/null
+++ b/.changeset/brave-lions-arrive.md
@@ -0,0 +1,5 @@
+---
+"@jspsych/plugin-emoji-screen": minor
+---
+
+Added a button that the user can click to toggle between placing emojis or vowels on the screen
diff --git a/.changeset/thirty-onions-eat.md b/.changeset/thirty-onions-eat.md
new file mode 100644
index 0000000..00334d6
--- /dev/null
+++ b/.changeset/thirty-onions-eat.md
@@ -0,0 +1,5 @@
+---
+"@jspsych/plugin-emoji-screen": minor
+---
+
+Added a button to toggle between placing emojis and vowels on the screen when the user clicks on the canvas
diff --git a/packages/plugin-emoji-screen/examples/index.html b/packages/plugin-emoji-screen/examples/index.html
index d540376..b5bca36 100644
--- a/packages/plugin-emoji-screen/examples/index.html
+++ b/packages/plugin-emoji-screen/examples/index.html
@@ -15,9 +15,8 @@
var trial = {
type: jsPsychEmojiScreen,
- emojis: ["😀", "🎉", "⭐", "🌟", "🎈", "🦄", "🍕", "🎸"],
key_to_finish: "Enter",
- prompt: "
Click anywhere to place emojis. Press Enter when you are done.
",
+ prompt: "Click anywhere to place emojis. Use the button to switch to vowels. Press Enter when done.
",
};
jsPsych.run([trial]);
diff --git a/packages/plugin-emoji-screen/src/index.ts b/packages/plugin-emoji-screen/src/index.ts
index 87d2050..ab5602c 100644
--- a/packages/plugin-emoji-screen/src/index.ts
+++ b/packages/plugin-emoji-screen/src/index.ts
@@ -2,11 +2,12 @@ import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
import { version } from "../package.json";
-/** Represents a single placed emoji with its screen coordinates. */
-interface EmojiLocation {
+/** Represents a single placed symbol with its screen coordinates. */
+interface SymbolLocation {
x: number;
y: number;
- emoji: string;
+ symbol: string;
+ mode: "emoji" | "vowel";
}
const info = {
@@ -19,6 +20,17 @@ const info = {
array: true,
default: ["😀", "🎉", "⭐", "🌟", "🎈", "🦄", "🍕", "🎸"],
},
+ /** The pool of vowels to randomly select from when the participant clicks. */
+ vowels: {
+ type: ParameterType.STRING,
+ array: true,
+ default: ["a", "e", "i", "o", "u"],
+ },
+ /** The mode of the trial. Valid values are "emoji" or "vowel". */
+ mode: {
+ type: ParameterType.STRING,
+ default: "emoji",
+ },
/** The key that ends the trial and records the final emoji arrangement. */
key_to_finish: {
type: ParameterType.KEY,
@@ -31,8 +43,8 @@ const info = {
},
},
data: {
- /** Array of objects describing each placed emoji: {x, y, emoji}. x and y are pixel coordinates relative to the canvas. */
- emoji_locations: {
+ /** Array of objects describing each placed symbol: {x, y, symbol, mode}. x and y are pixel coordinates relative to the canvas. */
+ symbol_locations: {
type: ParameterType.COMPLEX,
array: true,
},
@@ -68,7 +80,7 @@ class EmojiScreenPlugin implements JsPsychPlugin {
constructor(private jsPsych: JsPsych) {}
trial(display_element: HTMLElement, trial: TrialType) {
- const emoji_locations: EmojiLocation[] = [];
+ const symbol_locations: SymbolLocation[] = [];
const start_time = performance.now();
// Build the trial layout using DOM APIs
@@ -76,9 +88,10 @@ class EmojiScreenPlugin implements JsPsychPlugin {
wrapper.id = "jspsych-emoji-screen-wrapper";
wrapper.style.cssText = "position: relative; width: 100%; min-height: 400px;";
+ const promptDiv = document.createElement("div");
+ promptDiv.id = "jspsych-emoji-screen-prompt";
+
if (trial.prompt !== null) {
- const promptDiv = document.createElement("div");
- promptDiv.id = "jspsych-emoji-screen-prompt";
promptDiv.innerHTML = trial.prompt;
wrapper.appendChild(promptDiv);
}
@@ -89,6 +102,19 @@ class EmojiScreenPlugin implements JsPsychPlugin {
"position: relative; width: 100%; height: 400px; border: 2px solid #ccc; cursor: crosshair; overflow: hidden;";
wrapper.appendChild(canvas);
+ const modeButton = document.createElement("button");
+ let currentMode: "emoji" | "vowel" = trial.mode as "emoji" | "vowel";
+ modeButton.textContent = currentMode === "emoji" ? "Switch from Emojis to Vowels" : "Switch from Vowels to Emojis";
+ modeButton.addEventListener("click", () => {
+ event.stopPropagation(); // Prevent the click from placing an emoji when toggling modes
+ currentMode = currentMode === "emoji" ? "vowel" : "emoji";
+ modeButton.textContent = currentMode === "emoji" ? "Switch from Emojis to Vowels" : "Switch from Vowels to Emojis";
+ promptDiv.innerHTML = currentMode === "emoji"
+ ? "Click anywhere to place emojis. Use the button to switch to vowels. Press Enter when done.
"
+ : "Click anywhere to place vowels. Use the button to switch to emojis. Press Enter when done.
";
+ });
+ wrapper.appendChild(modeButton);
+
display_element.appendChild(wrapper);
// Handle clicks on the canvas to place emojis
@@ -98,23 +124,30 @@ class EmojiScreenPlugin implements JsPsychPlugin {
const y = Math.round(event.clientY - rect.top);
// Pick a random emoji from the pool
- const emoji = trial.emojis[Math.floor(Math.random() * trial.emojis.length)];
+ let symbol;
+ if (currentMode === "emoji") {
+ const emoji = trial.emojis[Math.floor(Math.random() * trial.emojis.length)];
+ symbol = emoji;
+ } else {
+ const vowel = trial.vowels[Math.floor(Math.random() * trial.vowels.length)];
+ symbol = vowel;
+ }
// Place the emoji at the click location
const emojiEl = document.createElement("span");
- emojiEl.textContent = emoji;
+ emojiEl.textContent = symbol;
emojiEl.style.position = "absolute";
emojiEl.style.left = `${x}px`;
emojiEl.style.top = `${y}px`;
emojiEl.style.fontSize = "2rem";
emojiEl.style.transform = "translate(-50%, -50%)";
emojiEl.style.userSelect = "none";
- emojiEl.setAttribute("data-emoji", emoji);
+ emojiEl.setAttribute("data-symbol", symbol);
emojiEl.setAttribute("data-x", x.toString());
emojiEl.setAttribute("data-y", y.toString());
canvas.appendChild(emojiEl);
- emoji_locations.push({ x, y, emoji });
+ symbol_locations.push({ x, y, symbol, mode: currentMode });
});
// End trial function
@@ -126,7 +159,7 @@ class EmojiScreenPlugin implements JsPsychPlugin {
display_element.innerHTML = "";
this.jsPsych.finishTrial({
- emoji_locations: emoji_locations,
+ symbol_locations: symbol_locations,
key_pressed: key,
rt: rt,
});
@@ -159,18 +192,20 @@ class EmojiScreenPlugin implements JsPsychPlugin {
}
private create_simulation_data(trial: TrialType, simulation_options) {
- const num_clicks = this.jsPsych.randomization.randomInt(1, 5);
- const simulated_locations: EmojiLocation[] = [];
- for (let i = 0; i < num_clicks; i++) {
- simulated_locations.push({
- x: this.jsPsych.randomization.randomInt(0, 800),
- y: this.jsPsych.randomization.randomInt(0, 400),
- emoji: trial.emojis[this.jsPsych.randomization.randomInt(0, trial.emojis.length - 1)],
- });
+ const simulated_locations: SymbolLocation[] = [];
+ const num_symbols = this.jsPsych.randomization.sampleWithoutReplacement([1, 2, 3, 4, 5], 1)[0];
+ for (let i = 0; i < num_symbols; i++) {
+ const x = this.jsPsych.randomization.randomInt(0, 800);
+ const y = this.jsPsych.randomization.randomInt(0, 400);
+ const current_mode = this.jsPsych.randomization.sampleWithoutReplacement(["emoji", "vowel"], 1)[0] as "emoji" | "vowel";
+ const symbol = current_mode === "emoji"
+ ? trial.emojis[Math.floor(Math.random() * trial.emojis.length)]
+ : trial.vowels[Math.floor(Math.random() * trial.vowels.length)];
+ simulated_locations.push({ x, y, symbol, mode: current_mode });
}
const default_data = {
- emoji_locations: simulated_locations,
+ symbol_locations: simulated_locations,
key_pressed: trial.key_to_finish,
rt: this.jsPsych.randomization.sampleExGaussian(2000, 200, 1 / 500, true),
};
@@ -194,8 +229,14 @@ class EmojiScreenPlugin implements JsPsychPlugin {
// Simulate clicks to place emojis
const canvas = display_element.querySelector("#jspsych-emoji-screen-canvas");
if (canvas) {
- for (const loc of data.emoji_locations) {
+ const modeBtn = display_element.querySelector("button");
+ let simulatedMode = trial.mode as "emoji" | "vowel";
+ for (const loc of data.symbol_locations) {
const rect = canvas.getBoundingClientRect();
+ if (loc.mode !== simulatedMode) {
+ modeBtn?.click();
+ simulatedMode = loc.mode;
+ }
const clickEvent = new MouseEvent("click", {
clientX: rect.left + loc.x,
clientY: rect.top + loc.y,