Skip to content

Commit

Permalink
Merge pull request #90 from Shaobin-Jiang/main
Browse files Browse the repository at this point in the history
Create countdown extension
  • Loading branch information
jodeleeuw authored Jan 11, 2024
2 parents 2a8b509 + 8a7f30d commit 3cb9958
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/strange-baboons-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@jspsych-contrib/extension-countdown": major
---

Added countdown extension.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Plugin/Extension | Contributor | Description
[audio-multi-response](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-audio-multi-response/README.md) | [Adam Richie-Halford](https://github.com/richford) | This plugin collects responses to an audio file using both button clicks and key presses.
[audio-swipe-response](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-audio-swipe-response/README.md) | [Adam Richie-Halford](https://github.com/richford) | This plugin collects responses to an audio file using swipe gestures and keyboard responses.
[corsi-blocks](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-corsi-blocks/README.md) | [Josh de Leeuw](https://github.com/jodeleeuw) | This plugin displays a configurable Corsi blocks task and records a series of click responses.
[countdown](https://github.com/jspsych/jspsych-contrib/blob/main/packages/extension-countdown/README.md) | [Shaobin Jiang](https://github.com/Shaobin-Jiang) | This extension adds a countdown during a trial.
[gamepad](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-gamepad/README.md) | [Shaobin Jiang](https://github.com/Shaobin-Jiang) | This plugin allows one to use gamepads in a jsPsych experiment.
[html-choice](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-html-choice/README.md) | [Younes Strittmatter](https://github.com/younesStrittmatter) | This plugin displays clickable html elements that can be used to present a choice.
[html-multi-response](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-html-multi-response/README.md) | [Adam Richie-Halford](https://github.com/richford) | This plugin collects responses to an arbitrary HTML string using both button clicks and key presses.
Expand Down
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 95 additions & 0 deletions packages/extension-countdown/docs/jspsych-countdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# countdown

This extension adds a countdown during a trial.

## Parameters

### Initialization Parameters

None

### Trial Parameters

Trial parameters can be set when adding the extension to a trial object.

```javascript
let trial = {
type: jsPsych...,
extensions: [
{type: jsPsychExtensionWebgazer, params: {...}}
]
}
```

| Parameter | Type | Default Value | Description |
| --------- | ---- | ------------- | ----------- |
| time | number | undefined | Time in milliseconds of the countdown |
| update_time | number | 50 | How often to update the countdown display; in milliseconds |
| format | function | (time) => String(Math.floor(time / 1000)) | The displayed content of the countdown. Receives the current time left in milliseconds and returns a string for display. |

## Data Generated

None

## Functions

These functions below are provided to enable a better interaction with the countdown. Note that all of the functions below must be prefixed with `jsPsych.extensions.countdown` (e.g. `jsPsych.extensions.countdown.pause()`).

### `pause()`

Pauses the countdown.

### `resume()`

Resumes the countdown.

## Example

```javascript
let jsPsych = initJsPsych({
extensions: [{ type: jsPsychExtensionCountdown }],
});

let trial = {
type: jsPsychHtmlKeyboardResponse,
stimulus: "Hello world",
extensions: [
{
type: jsPsychExtensionCountdown,
params: {
time: 5000,
update_time: 20,
format: (time) => {
if (time < 3000) {
document.querySelector(".jspsych-extension-countdown").style.color = "red";
}

let time_in_seconds = time / 1000;

let minutes = Math.floor(time_in_seconds / 60);
time_in_seconds -= minutes * 60;

let seconds = Math.floor(time_in_seconds);

let format_number = (number) => {
let temp_str = `0${number}`;
return temp_str.substring(temp_str.length - 2);
};

return `${format_number(minutes)}:${format_number(seconds)}`;
},
},
},
],
on_load: function () {
setTimeout(() => {
jsPsych.extensions.countdown.pause();
setTimeout(() => {
jsPsych.extensions.countdown.resume();
}, 2000);
}, 1000);
},
};

jsPsych.run([trial]);
```
64 changes: 64 additions & 0 deletions packages/extension-countdown/examples/example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Countdown Extension Example</title>
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<script src="../dist/index.browser.min.js"></script>
<link href="https://unpkg.com/[email protected]/css/jspsych.css" rel="stylesheet" />
</head>
<body>
<script>
let jsPsych = initJsPsych({
extensions: [{ type: jsPsychExtensionCountdown }],
});

let trial = {
type: jsPsychHtmlKeyboardResponse,
stimulus: "Hello world",
extensions: [
{
type: jsPsychExtensionCountdown,
params: {
time: 5000,
update_time: 20,
// Change the format of the countdown string
format: (time) => {
if (time < 3000) {
document.querySelector(".jspsych-extension-countdown").style.color = "red";
}

let time_in_seconds = time / 1000;

let minutes = Math.floor(time_in_seconds / 60);
time_in_seconds -= minutes * 60;

let seconds = Math.floor(time_in_seconds);

let format_number = (number) => {
let temp_str = `0${number}`;
return temp_str.substring(temp_str.length - 2);
};

return `${format_number(minutes)}:${format_number(seconds)}`;
},
},
},
],
// Pause / Resume midway
on_load: function () {
setTimeout(() => {
jsPsych.extensions.countdown.pause();
setTimeout(() => {
jsPsych.extensions.countdown.resume();
}, 2000);
}, 1000);
},
};

jsPsych.run([trial]);
</script>
</body>
</html>
1 change: 1 addition & 0 deletions packages/extension-countdown/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname);
44 changes: 44 additions & 0 deletions packages/extension-countdown/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@jspsych-contrib/extension-countdown",
"version": "0.0.1",
"description": "jsPsych extension for adding a countdown during a trial",
"type": "module",
"main": "dist/index.cjs",
"exports": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"typings": "dist/index.d.ts",
"unpkg": "dist/index.browser.min.js",
"files": [
"src",
"dist"
],
"source": "src/index.ts",
"scripts": {
"test": "jest",
"test:watch": "npm test -- --watch",
"tsc": "tsc",
"build": "rollup --config",
"build:watch": "npm run build -- --watch"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jspsych/jspsych-contrib.git",
"directory": "packages/extension-countdown"
},
"author": "Shaobin Jiang",
"license": "MIT",
"bugs": {
"url": "https://github.com/jspsych/jspsych-contrib/issues"
},
"homepage": "https://github.com/jspsych/jspsych-contrib/tree/main/packages/extension-countdown",
"peerDependencies": {
"jspsych": ">=7.0.0"
},
"devDependencies": {
"@jspsych/config": "^2.0.0",
"@jspsych/test-utils": "^1.0.0",
"jspsych": "^7.0.0"
}
}
3 changes: 3 additions & 0 deletions packages/extension-countdown/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { makeRollupConfig } from "@jspsych/config/rollup";

export default makeRollupConfig("jsPsychExtensionCountdown");
21 changes: 21 additions & 0 deletions packages/extension-countdown/src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import htmlButtonResponse from "@jspsych/plugin-html-button-response";
import { initJsPsych } from "jspsych";

import CountdownExtension from ".";

describe("Countdown Extension", () => {
it("should pass", () => {
const jsPsych = initJsPsych({
extensions: [{ type: CountdownExtension }],
});

let trial = {
type: htmlButtonResponse,
stimulus: "Hello world",
choices: ["Foo", "Bar"],
extensions: [{ type: CountdownExtension, params: { time: 5000 } }],
};

jsPsych.run([trial]);
});
});
87 changes: 87 additions & 0 deletions packages/extension-countdown/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { JsPsych, JsPsychExtension, JsPsychExtensionInfo } from "jspsych";

interface OnStartParameters {
format: (time: number) => string;
time: number;
update_time: number;
}

/**
* **Extension-Countdown**
*
* jsPsych extension for adding a countdown for a trial
*
* @author Shaobin Jiang
*/
class CountdownExtension implements JsPsychExtension {
static info: JsPsychExtensionInfo = {
name: "countdown",
};

constructor(private jsPsych: JsPsych) {}

private format: (time: number) => string;
private time: number;
private update_time: number;

private countdown_element: HTMLElement;
private timer: number;
private last_recorded_time: number;
private time_elapsed: number = 0;
private is_running: boolean = true;

initialize = (): Promise<void> => {
return new Promise((resolve, _) => {
resolve();
});
};

on_start = ({
format = (time: number) => String(Math.floor(time / 1000)),
time,
update_time = 50,
}: OnStartParameters): void => {
this.format = format;
this.time = time;
this.update_time = update_time;

this.countdown_element = document.createElement("div");
this.countdown_element.innerHTML = this.format(time);
this.countdown_element.className = "jspsych-extension-countdown";
this.countdown_element.style.cssText = "font-size: 18px; position: fixed; top: 5%; right: 5%;";
};

on_load = (): void => {
this.jsPsych.getDisplayContainerElement().appendChild(this.countdown_element);
this.last_recorded_time = performance.now();
this.timer = window.setInterval(() => {
let now: number = performance.now();
if (this.is_running) {
this.time_elapsed += now - this.last_recorded_time;
}
this.last_recorded_time = now;
let time_left = this.time - this.time_elapsed;
if (time_left <= 0) {
window.clearInterval(this.timer);
} else {
this.countdown_element.innerHTML = this.format(time_left);
}
}, this.update_time);
};

on_finish = () => {
window.clearInterval(this.timer);
this.countdown_element.remove();
return {};
};

pause = (): void => {
this.is_running = false;
};

resume = (): void => {
this.is_running = true;
};
}

export default CountdownExtension;
7 changes: 7 additions & 0 deletions packages/extension-countdown/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@jspsych/config/tsconfig.contrib.json",
"compilerOptions": {
"baseUrl": "."
},
"include": ["src"]
}

0 comments on commit 3cb9958

Please sign in to comment.