diff --git a/.github/workflows/delete-preview.yml b/.github/workflows/delete-preview.yml new file mode 100644 index 0000000000..d2faf5dec6 --- /dev/null +++ b/.github/workflows/delete-preview.yml @@ -0,0 +1,32 @@ +name: Delete PR Preview + +on: + pull_request: + types: [closed] + +jobs: + delete-preview: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Make empty directory + run: mkdir -p public + + - name: Delete preview folder + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + destination_dir: "PR${{ github.event.number }}" + + - name: Comment on PR + uses: hasura/comment-progress@v2.2.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + number: ${{ github.event.number }} + id: deploy-preview + message: "🪓PR closed, deleted preview." diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 06333c40b4..8226f63ee7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,6 +4,8 @@ on: push: branches: - main + workflow_dispatch: + pull_request: jobs: deploy: @@ -12,6 +14,7 @@ jobs: contents: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} + if: ${{ github.ref == 'refs/heads/main' }} steps: - uses: actions/checkout@v4 @@ -35,3 +38,55 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./build cname: revisit.dev + + pr-deploy: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + if: ${{ github.event_name == 'pull_request' }} + + steps: + - name: Comment on PR + uses: hasura/comment-progress@v2.2.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + number: ${{ github.event.number }} + id: deploy-preview + message: "Starting deployment of preview ⏳..." + + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Build PR preview + run: yarn build + env: + BASE_URL: "/PR${{ github.event.number }}/" + + - name: Push PR deploy preview + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./build + destination_dir: "PR${{ github.event.number }}" + keep_files: true + + - name: Update comment + uses: hasura/comment-progress@v2.2.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + number: ${{ github.event.number }} + id: deploy-preview + message: "A preview of ${{ github.sha }} is uploaded and can be seen here:\n\n ✨ https://revisit.dev/PR${{ github.event.number }} ✨\n\nChanges may take a few minutes to propagate." diff --git a/docs/getting-started/img/clone-repo.png b/docs/getting-started/img/clone-repo.png new file mode 100644 index 0000000000..4739816b78 Binary files /dev/null and b/docs/getting-started/img/clone-repo.png differ diff --git a/docs/getting-started/img/create-new-repo.png b/docs/getting-started/img/create-new-repo.png new file mode 100644 index 0000000000..15f8c129c6 Binary files /dev/null and b/docs/getting-started/img/create-new-repo.png differ diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 5d31848fc7..b17ddcb4d8 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -4,12 +4,54 @@ ReVISit project is open-source – meaning anyone can see the entire codebase. M For most users, the best place to start is the template repository (first option below). If you want all demos and tighter upstream parity, or are considering contributing to reVISit, we recommend you fork the repository instead. +## Installing Required Software + +Install these tools before cloning and running a study locally: + +- [Visual Studio Code](https://code.visualstudio.com/), or another editor with JSON support. +- [Git](https://git-scm.com/downloads), so you can clone the template repository and share changes with collaborators. +- The Active LTS version of [Node.js](https://nodejs.org/), which also installs NPM. + +:::info +NPM is installed with Node.js. You usually do not need to install NPM separately: install Node.js first, then use NPM to install Yarn. If NPM is not installed for any reason, review the [NPM documentation](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) to get started. +::: + +You can check whether Git, Node.js, and NPM are already installed with: + +```bash +git --version +node --version +npm --version +``` + +Yarn can be installed using NPM. Run the following command to install Yarn: + +```bash +npm i -g yarn +``` + +:::note +If your machine restricts global installs, run the command with administrator permissions: + +```bash +sudo npm i -g yarn +``` +::: + +After installing Yarn, check that it was installed correctly: + +```bash +yarn --version +``` + ## Starting from the Template Repository (Recommended) Navigate to the [template repository](https://github.com/revisit-studies/template), and click the "Use this template" button. This will create a new repository in your GitHub account with the same files as the template repository, based on the latest stable release of reVISit. ![Use this template button](./img/template-repo.png) +![Create a new repository from the template](./img/create-new-repo.png) + :::info You can choose a name for the repository to suit your needs, but if you choose anything other than `study`, you also need to adjust the `VITE_BASE_PATH` in your [`.env`](https://github.com/revisit-studies/study/blob/main/.env) file to reflect that change. ::: @@ -25,68 +67,93 @@ Most likely, you will **receive a warning from GitHub about a potential security You can safely ignore this warning. The reason for this is that the Firebase API key is not a secret key, and it is intended to be shared publicly in client-side code. For more information, see the [Firebase documentation on API keys](https://firebase.google.com/docs/projects/api-keys#api_key_security_recommendations). ::: -## Forking Repository (Advanced Alternative to Template Repository) - -Forking the repository is a more advanced option that allows you to have a copy of the entire `study` repository in your GitHub account. This means that you will have access to all the demo studies and that you can choose to follow the latest changes from the main repository (e.g., by following the `dev` branch). However, it also means that your repository will be linked to the upstream repository's fork network, which can make it more complex to manage. +### Clone your template repository -To fork, start by navigating to the following GitHub repository: https://github.com/revisit-studies/study +After GitHub creates your repository from the template, open the repository page and click "Code". Copy the clone URL from the HTTPS and clone it to your computer: -You should see a "fork" button on the same row as the name of the repository. When you fork a repository, you are essentially creating your own copy of the repository in your GitHub account. This means that any changes you commit and push to this new repository will not affect the main source code. +```bash +git clone https://github.com/your-github-name/your-repository-name.git +cd your-repository-name +``` -![Fork button](./img/fork-repo.png) +![Copy the repository clone URL from GitHub](./img/clone-repo.png) -:::info -GitHub only allows you to fork a repository once. If you have already forked the repository, you will need to clone the repository to your local machine, create a new repo on your account, and run `git remote set-url origin new.git.url/here` to allow you to have 2 versions of the repository in your account. +:::note +If you have not configured GitHub authentication locally, you can use "Download ZIP". ::: -When forking the repository, you will be prompted for some basic information about this repository (such as the desired name). Once you've forked the repository into your own GitHub account, you can [clone the repository to your local computer](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository). +## Run the Local Server -:::info -You can rename the repository to suit your needs, but if you change the name, you also need to adjust the `VITE_BASE_PATH` in your [`.env`](https://github.com/revisit-studies/study/blob/main/.env) file to reflect that change. -::: +After cloning your repository, make sure you are inside the repository folder: -After the repository is on your local machine, you will have the entire codebase for your personal use. Any changes that you make to this repository can be committed and then pushed to your forked repository for other users in your organization to see. +```bash +cd your-repository-name +``` -## Installing Required Software +Then install the packages that reVISit needs to run: -To continue, you will need the Active LTS [Node.js](https://nodejs.org/) version, and [Yarn](https://yarnpkg.com/) installed. If you already have Yarn installed, you can go to Step 2 below. +```bash +yarn install +``` -:::info -Yarn requires that you have the package manager NPM installed. If you do not have NPM installed, please review the [NPM documentation](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) to get started. These docs will also direct you on how to install Node – a prerequisite of NPM. +Once this is finished, start the local server: + +```bash +yarn serve +``` + +This will launch a local web server where you can view and interact with reVISit. By default, you can access it by visiting [http://localhost:8080/](http://localhost:8080/). Any change you make to the code will automatically update the website. + +:::warning +If `yarn install` or `yarn serve` says it cannot find `package.json`, you are probably not inside the repository folder. Run `cd your-repository-name` first, then try the command again. ::: -**Step 1: Install Yarn on your local computer using NPM:** +When you visit the site, you'll see the studies registered in your local `public/global.json` file. You can interact with any of these studies to get some familiarity (and hopefully some inspiration) for how reVISit can help you quickly launch a crowd-sourced visualization study. -```npm i -g yarn``` +:::note +If you started from the template repository, this will be a smaller set of starter tutorial studies. +::: -**Step 2: Once Yarn is successfully installed, navigate to your local repository and run the following Yarn command:** +:::warning +We do not support using `npm` to run reVISit. Please use `yarn` for all package management and running commands. +::: -```yarn install``` +## Forking Repository (Advanced Alternative to Template Repository) -This will install all the packages that the reVISit requires to run. +Forking the repository is a more advanced option that allows you to have a copy of the entire `study` repository in your GitHub account. This means that you will have access to all the demo studies and that you can choose to follow the latest changes from the main repository (e.g., by following the `dev` branch). However, it also means that your repository will be linked to the upstream repository's fork network, which can make it more complex to manage. -**Step 3: Once this is finished, you can now start the program:** +To fork, start by navigating to the following GitHub repository: https://github.com/revisit-studies/study -```yarn serve``` +You should see a "fork" button on the same row as the name of the repository. When you fork a repository, you are essentially creating your own copy of the repository in your GitHub account. This means that any changes you commit and push to this new repository will not affect the main source code. -This will launch a local web server which can be accessed to view and interact with reVISit. By default, you can access this by visiting [http://localhost:8080/](http://localhost:8080/). Any change you make to the code will automatically update the website. +![Fork button](./img/fork-repo.png) -When you visit the site, you'll see a list of demo studies, identical to [the demo page](https://revisit.dev/study/). You can interact with any of these studies to get some familiarity (and hopefully some inspiration) for how reVISit can help you quickly launch a crowd-sourced visualization study. +:::info +GitHub only allows you to fork a repository once. If you have already forked the repository, you will need to clone the repository to your local machine, create a new repo on your account, and run `git remote set-url origin new.git.url/here` to allow you to have 2 versions of the repository in your account. +::: -:::warning -We do not support using `npm` to run reVISit. Please use `yarn` for all package management and running commands. +When forking the repository, you will be prompted for some basic information about this repository (such as the desired name). Once you've forked the repository into your own GitHub account, you can [clone the repository to your local computer](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository). + +:::info +You can rename the repository to suit your needs, but if you change the name, you also need to adjust the `VITE_BASE_PATH` in your [`.env`](https://github.com/revisit-studies/study/blob/main/.env) file to reflect that change. ::: +After the repository is on your local machine, you will have the entire codebase for your personal use. Any changes that you make to this repository can be committed and then pushed to your forked repository for other users in your organization to see. + import StructuredLinks from '@site/src/components/StructuredLinks/StructuredLinks.tsx'; \ No newline at end of file +/> diff --git a/docs/tutorial/config.json.md b/docs/tutorial/config.json.md new file mode 100644 index 0000000000..3f9d6216c7 --- /dev/null +++ b/docs/tutorial/config.json.md @@ -0,0 +1,945 @@ +# config.json + +In this part of the tutorial, you will build the [Study Config](../typedoc/interfaces/StudyConfig.md), [`public/tutorial/config.json`](https://github.com/revisit-studies/template/blob/main/public/tutorial/config.json). The completed version is [`public/tutorial/_answers/config.json`](https://github.com/revisit-studies/template/blob/main/public/tutorial/_answers/config.json). Use the completed version to check the step you just finished, not as something to copy all at once. + +:::info +Before you start editing tutorial files, complete the [Installation guide](../getting-started/installation.md) using the **Starting from the Template Repository** workflow. +::: + +## Step 1: Run the local server + +Start the local server from the root of your template repository: + +```bash +yarn serve +``` + +Before editing the tutorial Study Config, open [`public/global.json`](https://github.com/revisit-studies/template/blob/main/public/global.json). This file follows the [Global Config](../typedoc/interfaces/GlobalConfig.md) schema. The template should be empty for now. + +Let's add a tutorial study here. + +```json title="public/global.json" +{ + "$schema": "https://raw.githubusercontent.com/revisit-studies/study/v2.4.3/src/parser/GlobalConfigSchema.json", + "configsList": ["tutorial"], + "configs": { + "tutorial": { + "path": "tutorial/config.json" + } + } +} +``` + +Open [http://localhost:8080](http://localhost:8080). You should see the tutorial study listed. + +![The tutorial study appears on the local reVISit page with an empty sequence warning.](./img/config.json/step1.png) + +:::warning +At this point, the tutorial config should show a warning that the sequence is empty. You can ignore this warning for now. It is intentional because `public/tutorial/config.json` currently has an empty [`sequence.components`](../typedoc/interfaces/Sequence.md#components) array. If you enter the study now, reVISit may go directly to the study end page because no components have been added to the sequence yet. +::: + +## Step 2: Add the welcome component + +If you go into the tutorial study right now, you will be directed straight to the end page, which should look like this. + +![The tutorial study with the end page](./img/config.json/step2-1.png) + +Next, open `public/tutorial/config.json`. Inside the empty `components` object, add a basic [Markdown component](../typedoc/interfaces/MarkdownComponent.md) named `welcome`. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { + "type": "markdown", + "path": "tutorial/assets/welcome.md", + "response": [] + } +} +``` + +This component displays the Markdown file at [`public/tutorial/assets/welcome.md`](https://github.com/revisit-studies/template/blob/main/public/tutorial/assets/welcome.md). + +Now add `welcome` to the sequence: + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome" + ] +} +``` + +Refresh the local study or click "Next participant" to reload the Study Config and start a fresh preview. You should now see the welcome page. + + +![The tutorial study with the welcome page](./img/config.json/step2-2.png) + +:::warning +A common mistake is to add the component but forget the sequence entry. If the component exists in `components` but is not listed in `sequence.components`, the component will not show up. +::: + +## Step 3: Add the consent component + +Add a comma after the `welcome` component, then add a second Markdown component named `consent`. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { + "type": "markdown", + "path": "tutorial/assets/consent.md", + "nextButtonText": "I agree", + "response": [] + } +} +``` + +This component displays [`public/tutorial/assets/consent.md`](https://github.com/revisit-studies/template/blob/main/public/tutorial/assets/consent.md). The `nextButtonText` field changes the text on the next button, which is useful for consent pages. + +Add `consent` after `welcome` in the sequence: + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent" + ] +} +``` + +Refresh the study or click "Next participant". You should see the welcome page first, then the consent page with an "I agree" button. + +![The tutorial study with the consent page with I agree button](./img/config.json/step3.png) + +## Step 4: Add demographics with several form elements + +Add a questionnaire component named `demographics`. A [`questionnaire`](../typedoc/interfaces/QuestionnaireComponent.md) component is used to collect form-based answers from the participant, such as demographic information, survey responses, or post-task feedback. + +ReVISit supports many [form response types](../designing-studies/forms.md) inside a questionnaire, including numerical inputs, Likert scales, dropdowns, checkboxes, sliders, dividers, and matrix questions. For the full list of available response types, see the [Response reference](../typedoc/type-aliases/Response.md). + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + "demographics": { + "type": "questionnaire", + "response": [ + { + "id": "age", + "type": "numerical", + "prompt": "What is your age?" + }, + { + "id": "health", + "type": "likert", + "prompt": "How would you rate your overall health?", + "secondaryText": "1 being the worst health and 5 being the best health", + "numItems": 5, + "rightLabel": "Best health", + "leftLabel": "Worst health" + }, + { + "id": "dividerResponse", + "type": "divider", + "location": "belowStimulus" + }, + { + "id": "fruits", + "type": "matrix-checkbox", + "prompt": "Which of these fruits do you like at each time of day?", + "answerOptions": ["Breakfast", "Lunch", "Dinner"], + "questionOptions": ["Banana", "Apple", "Orange", "Grapes", "Strawberry"] + }, + { + "id": "q-short-text", + "type": "shortText", + "prompt": "What is your favorite sports team?", + "placeholder": "Enter your team here" + }, + { + "id": "operating-systems", + "type": "checkbox", + "prompt": "Which of these operating systems do you use?", + "minSelections": 1, + "maxSelections": 2, + "options": ["Windows", "macOS", "Linux"], + "withOther": true + }, + { + "id": "q-slider", + "type": "slider", + "prompt": "How would you rate this tutorial so far?", + "secondaryText": "Your answer is not legally binding.", + "startingValue": 50, + "options": [ + { "label": "Bad", "value": 0 }, + { "label": "Alright", "value": 50 }, + { "label": "Good", "value": 100 } + ] + } + ] + } +} +``` + +This one component introduces several form elements: [numerical input](../typedoc/interfaces/NumericalResponse.md), [Likert scale](../typedoc/interfaces/LikertResponse.md), [divider](../typedoc/interfaces/DividerResponse.md), [matrix checkbox](../typedoc/interfaces/MatrixCheckboxResponse.md), [short text](../typedoc/interfaces/ShortTextResponse.md), [checkbox](../typedoc/interfaces/CheckboxResponse.md), and [slider](../typedoc/interfaces/SliderResponse.md). + + +Add `demographics` to the sequence: + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + "demographics" + ] +} +``` + +Click "Next participant" and confirm that the demographics page appears after consent. + +![The tutorial study with demographics](./img/config.json/step4.png) + +## Step 5: Add training with feedback + +Add a questionnaire component named `trainingWithFeedback`. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + "demographics": { ... }, + "trainingWithFeedback": { + "type": "questionnaire", + "response": [ + { + "id": "training", + "type": "radio", + "prompt": "Yes is the correct answer, click it", + "options": ["Yes", "No"] + } + ], + "correctAnswer": [ + { + "id": "training", + "answer": "Yes" + } + ], + "provideFeedback": true, + "trainingAttempts": 2, + "allowFailedTraining": false, + "nextButtonDisableTime": 5000 + } +} +``` + +- [`correctAnswer`](../typedoc/interfaces/Answer.md) says which answer is correct. The `id` must match the response id, `training`. +- `provideFeedback: true` tells to show feedback after the participant answers. +- `trainingAttempts: 2` gives the participant two attempts. +- `allowFailedTraining: false` prevents the participant from continuing after failing the allowed attempts. +- `nextButtonDisableTime: 5000` disables the next button after 5 seconds. + +Add `trainingWithFeedback` to the sequence after `demographics`. + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + "demographics", + "trainingWithFeedback" + ] +} +``` + +When you preview this page, the next button becomes a **Check answer** button. If the participant answers incorrectly twice, reVISit stops them from continuing. If they answer correctly, they can move forward. + +![The tutorial study with the training with feedback page](./img/config.json/step5.png) + +## Step 6: Add the coin image component + +Add an [image component](../typedoc/interfaces/ImageComponent.md) named `coinImage`. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "trainingWithFeedback": { ... }, + "coinImage": { + "type": "image", + "path": "tutorial/assets/coins.png", + "nextButtonLocation": "sidebar", + "response": [ + { + "id": "cost-effective", + "type": "radio", + "prompt": "Which coin is most effective to produce?", + "location": "sidebar", + "options": ["Penny", "Nickel", "Dime", "Quarter", "Half Dollar"] + }, + { + "id": "cost-ineffective", + "type": "dropdown", + "prompt": "Which coin is least cost effective to produce?", + "location": "sidebar", + "options": ["Penny", "Nickel", "Dime", "Quarter", "Half Dollar"] + } + ] + } +} +``` + +This component displays [`public/tutorial/assets/coins.png`](https://github.com/revisit-studies/template/blob/main/public/tutorial/assets/coins.png) and places the questions in the sidebar. +Add `coinImage` to the sequence after `trainingWithFeedback`. + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + "demographics", + "trainingWithFeedback", + "coinImage" + ] +} +``` + +![The tutorial study with the coin image and sidebar questions](./img/config.json/step6.png) + +## Step 7: Add Vega components + +Add [`vegaPath`](../typedoc/interfaces/VegaComponentPath.md), which loads a Vega chart from a separate file. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "coinImage": { ... }, + "vegaPath": { + "type": "vega", + "path": "tutorial/assets/simpleChart.json", + "response": [ + { + "id": "simple-vega", + "type": "radio", + "prompt": "What is the value of bar A?", + "options": ["10", "28", "50"] + } + ] + } +} +``` + +Then add [`vegaConfig`](../typedoc/interfaces/VegaComponentConfig.md), which puts the Vega-Lite chart definition directly in the Study Config. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "vegaPath": { ... }, + "vegaConfig": { + "type": "vega", + "config": { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "description": "A simple bar chart with embedded data.", + "data": { + "values": [ + { "category": "A", "value": 28 }, + { "category": "B", "value": 55 }, + { "category": "C", "value": 43 } + ] + }, + "mark": "bar", + "encoding": { + "x": { + "field": "category", + "type": "nominal", + "axis": { "labelAngle": 0 } + }, + "y": { + "field": "value", + "type": "quantitative" + } + } + }, + "response": [ + { + "id": "dynamic-vega", + "type": "radio", + "prompt": "What is the value of bar A?", + "options": ["10", "28", "50"] + } + ] + } +} +``` + +:::note +There are two ways of defining Vega components in reVISit. If you'd like to learn more, visit [Vega stimulus docs](../designing-studies/vega-stimulus.md). Use `path` when the visualization specification is easier to maintain as its own file. Use `config` when the chart is small enough to keep inside the Study Config. +::: +Add `vegaPath` and `vegaConfig` to the sequence. + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + "demographics", + "trainingWithFeedback", + "coinImage", + "vegaPath", + "vegaConfig" + ] +} +``` + +![The tutorial study with the Vega chart components](./img/config.json/step7.png) + +## Step 8: Add reactive Vega + +Add `reactiveVega`. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "vegaConfig": { ... }, + "reactiveVega": { + "type": "vega", + "path": "tutorial/assets/reactive.json", + "response": [ + { + "id": "reactiveResponse", + "type": "reactive", + "prompt": "What is the value of bar A? Click it to show here" + } + ] + } +} +``` + +A [reactive response](../typedoc/interfaces/ReactiveResponse.md) records an interaction from the visualization itself. In this example, the participant clicks a mark in the Vega chart and that interaction becomes the response. + +Add `reactiveVega` to the sequence. + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + ..., + "vegaConfig", + "reactiveVega" + ] +} +``` + +![The tutorial study with the reactive Vega chart](./img/config.json/step8.png) + +## Step 9: Add website components + +First, add a simple [website component](../typedoc/interfaces/WebsiteComponent.md). This embeds a web page in the study as an iframe. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "reactiveVega": { ... }, + "website": { + "type": "website", + "path": "https://revisit.dev", + "response": [] + } +} +``` + +This is useful when a study asks participants to inspect a website or web-based visualization. The empty `response` array means the page is shown without collecting a form response. + +Add `website` to the sequence. + + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + ..., + "reactiveVega", + "website" + ] +} +``` + +![The tutorial study with the embedded website](./img/config.json/step9.png) + +Next, add a reactive website named `reactiveWebsite`. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "website": { ... }, + "reactiveWebsite": { + "type": "website", + "path": "tutorial/assets/bar-chart-interaction.html", + "instructionLocation": "aboveStimulus", + "description": "A trial for the user to click the largest bar", + "instruction": "Click on the largest bar", + "response": [ + { + "id": "barChart", + "prompt": "Your selected answer:", + "location": "sidebar", + "type": "reactive" + } + ], + "parameters": { + "barData": [0.32, 0.01, 1.2, 1.3, 0.82, 0.4, 0.3] + } + } +} +``` + +This component loads a local HTML page and passes `barData` into it through [`parameters`](../typedoc/interfaces/WebsiteComponent.md#parameters). The page can render a different chart based on those values. The response is `reactive`, so the HTML page can send the participant's selection back to reVISit. + +Add `reactiveWebsite` to the sequence after `website`. + + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + ..., + "website", + "reactiveWebsite" + ] +} +``` + +![The tutorial study with the reactive website bar chart](./img/config.json/step9-2.png) + +## Step 10: Add reactExampleCars + +Add the first [React component](../typedoc/interfaces/ReactComponent.md) trial. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "reactiveWebsite": { ... }, + "reactExampleCars": { + "type": "react-component", + "path": "tutorial/assets/ReactExample.tsx", + "instruction": "How many cars from Japan have a Miles Per Gallon value greater than 35?", + "response": [ + { + "id": "response", + "prompt": "Answer:", + "location": "sidebar", + "type": "numerical", + "max": 100, + "min": 0 + } + ], + "correctAnswer": [ + { + "id": "response", + "answer": 17 + } + ], + "parameters": { + "dataset": "cars", + "x": "Miles per Gallon", + "y": "Weight (lbs)", + "category": "Origin", + "ids": "id", + "brushType": "Rectangular Selection" + } + } +} +``` + +This component renders [`ReactExample.tsx`](https://github.com/revisit-studies/template/blob/main/src/public/tutorial/assets/ReactExample.tsx). The [`parameters`](../typedoc/interfaces/ReactComponent.md#parameters) object tells the React component which dataset and fields to use. + + +:::info +[React component](../designing-studies/react-stimulus.md) paths are relative to `src/public/`, not the root `public/` folder. The path `tutorial/assets/ReactExample.tsx` points to `src/public/tutorial/assets/ReactExample.tsx`. +::: + +Add `reactExampleCars` to the sequence. + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + ..., + "reactiveWebsite", + "reactExampleCars" + ] +} +``` + +![The tutorial study with the reactExampleCars trial](./img/config.json/step10.png) + +## Step 11: Add reactExamplePenguins + +Add a second React component trial that uses the same React file with different parameters. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "reactExampleCars": { ... }, + "reactExamplePenguins": { + "type": "react-component", + "path": "tutorial/assets/ReactExample.tsx", + "instruction": "How many Gentoo penguins weigh less than 4.5k grams (g)?", + "response": [ + { + "id": "response", + "prompt": "Answer:", + "location": "sidebar", + "type": "numerical", + "max": 100, + "min": 0 + } + ], + "correctAnswer": [ + { + "id": "response", + "answer": 15 + } + ], + "parameters": { + "dataset": "penguin", + "x": "Body Mass (g)", + "y": "Flipper Length (mm)", + "category": "Species", + "ids": "id", + "brushType": "Slider Selection" + } + } +} +``` + +This step shows why parameters are useful. The Study Config can reuse the same React component while changing the task, dataset, fields, and interaction style. + +Add `reactExamplePenguins` to the sequence. + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + ..., + "reactExampleCars", + "reactExamplePenguins" + ] +} +``` + +![The tutorial study with the reactExamplePenguins trial](./img/config.json/step11.png) + +## Step 12: Add questionnaire examples: example1 and example2 + +Add two simple questionnaire components. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "reactExamplePenguins": { ... }, + "example1": { + "type": "questionnaire", + "response": [ + { + "id": "q-example-1", + "type": "shortText", + "prompt": "Example question" + } + ] + }, + "example2": { + "type": "questionnaire", + "response": [ + { + "id": "q-example-2", + "type": "dropdown", + "prompt": "Example question", + "options": ["Option 1", "Option 2"] + } + ] + } +} +``` + +These components are intentionally simple. You will reuse them in the interruption examples in the next step. + +## Step 13: Add attention checks and interruptions + +[Attention checks and interruptions](../designing-studies/sequences/study-sequences.md#attention-checks-and-breaks) help you add quality-control moments without rewriting the main study flow. An attention check can catch participants who are not reading carefully, while an interruption can insert a check, break, or reminder between normal tasks. + +A [sequence block](../typedoc/interfaces/ComponentBlock.md) can be `fixed`, `random`, or `latinSquare`. A fixed block shows components in the order you list them. A random block shuffles the components for each participant. A Latin square block balances ordering across participants. See the [study sequence guide](../designing-studies/sequences/study-sequences.md) for more sequence patterns. + +:::info +You can also nest sequence blocks. For example, the following sequence keeps `welcome` and `consent` fixed, then randomizes later tasks: + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + { + "id": "randomizedTasks", + "order": "random", + "components": [ + "coinImage", + "vegaPath", + "reactiveVega" + ] + } + ] +} +``` +::: + +First, add an `attentionCheck` component. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "example2": { ... }, + "attentionCheck": { + "type": "questionnaire", + "response": [ + { + "id": "q-example-2", + "type": "dropdown", + "prompt": "Attention check question", + "options": ["Option 1", "Option 2"] + } + ] + } +} +``` + +Then add a [deterministic interruption](../typedoc/interfaces/DeterministicInterruption.md) block to the sequence: + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + ..., + { + "id": "attentionDeterministic", + "order": "fixed", + "components": [ + "example1", + "example2", + "example1", + "example2" + ], + "interruptions": [ + { + "firstLocation": 0, + "spacing": 2, + "components": [ + "attentionCheck" + ] + } + ] + } + ] +} +``` + +This is deterministic because the attention check appears at a predictable spacing. + +Now add a [random interruption](../typedoc/interfaces/RandomInterruption.md) block: + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + ..., + { + "id": "attentionRandom", + "order": "fixed", + "components": [ + "example1", + "example2", + "example1", + "example2" + ], + "interruptions": [ + { + "spacing": "random", + "numInterruptions": 3, + "components": [ + "attentionCheck" + ] + } + ] + } + ] +} +``` + +## Step 14: Add skip logic + +[Skip logic](../designing-studies/sequences/study-sequences.md#skip-logic) lets a study respond to a participant's answer. You can use it to branch around questions that do not apply, end a block when someone fails an attention check, or send participants to a follow-up task. The exact skip condition shapes are listed in [`SkipConditions`](../typedoc/type-aliases/SkipConditions.md). + +First, add `exampleWithAnswer`. + +```json title="public/tutorial/config.json" +"components": { + "welcome": { ... }, + "consent": { ... }, + ..., + "attentionCheck": { ... }, + "exampleWithAnswer": { + "type": "questionnaire", + "response": [ + { + "id": "q-example-1", + "type": "numerical", + "prompt": "What is 2 + 2?" + } + ] + } +} +``` + +Then add a [`skip`](../typedoc/type-aliases/SkipConditions.md) block to the sequence: + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + ..., + { + "id": "skipResponse", + "order": "fixed", + "components": [ + "exampleWithAnswer", + "example1" + ], + "skip": [ + { + "name": "exampleWithAnswer", + "check": "response", + "responseId": "q-example-1", + "value": 4, + "comparison": "notEqual", + "to": "end" + } + ] + } + ] +} +``` + +This block asks the participant `What is 2 + 2?`. If the response is not equal to `4`, reVISit skips to the end of this nested block. If the response is `4`, the participant continues to `example1`. + +The `responseId` must match the response id inside `exampleWithAnswer`. + +## Step 15: Add the microphone library and audio settings + +Finally, add the [microphone check library](../designing-studies/plugin-libraries.md) and turn on [audio recording](../designing-studies/think-aloud.md) for the study. + +Add [`importedLibraries`](../typedoc/interfaces/StudyConfig.md#importedlibraries) after `studyMetadata`: + +```json title="public/tutorial/config.json" +"importedLibraries": [ + "mic-check" +], +``` + +Then add [`recordAudio`](../typedoc/interfaces/UIConfig.md#recordaudio) to [`uiConfig`](../typedoc/interfaces/UIConfig.md): + +```json title="public/tutorial/config.json" +"uiConfig": { + "recordAudio": true, + "contactEmail": "contact@revisit.dev", + "helpTextPath": "tutorial/assets/help.md", + "logoPath": "revisitAssets/revisitLogoSquare.svg", + "withProgressBar": true, + "autoDownloadStudy": false, + "withSidebar": true +} +``` + +Add the microphone check component to the sequence after `consent`: + +```json title="public/tutorial/config.json" +"sequence": { + "order": "fixed", + "components": [ + "welcome", + "consent", + "$mic-check.components.audioTest", + "demographics", + ... + ] +} +``` + +:::note +The `${library}.components.X` syntax references a component defined in an imported library. The `$` prefix tells reVISit to look up the component in the library namespace rather than in your local `components` object. The same syntax works for sequences (`${library}.sequences.X`). +::: + +Because `uiConfig.recordAudio` enables audio recording for the study, turn audio recording off for the welcome and consent pages: + +```json title="public/tutorial/config.json" +"components": { + "welcome": { + "type": "markdown", + "recordAudio": false, + "path": "tutorial/assets/welcome.md", + "response": [] + }, + "consent": { + "type": "markdown", + "recordAudio": false, + "path": "tutorial/assets/consent.md", + "nextButtonText": "I agree", + "response": [] + }, + ... +} +``` + + + +import StructuredLinks from '@site/src/components/StructuredLinks/StructuredLinks.tsx'; + + diff --git a/docs/tutorial/img/config.json/step1.png b/docs/tutorial/img/config.json/step1.png new file mode 100644 index 0000000000..fb43b8d7f9 Binary files /dev/null and b/docs/tutorial/img/config.json/step1.png differ diff --git a/docs/tutorial/img/config.json/step10.png b/docs/tutorial/img/config.json/step10.png new file mode 100644 index 0000000000..747bbec055 Binary files /dev/null and b/docs/tutorial/img/config.json/step10.png differ diff --git a/docs/tutorial/img/config.json/step11.png b/docs/tutorial/img/config.json/step11.png new file mode 100644 index 0000000000..da9bd37893 Binary files /dev/null and b/docs/tutorial/img/config.json/step11.png differ diff --git a/docs/tutorial/img/config.json/step2-1.png b/docs/tutorial/img/config.json/step2-1.png new file mode 100644 index 0000000000..1e62fea276 Binary files /dev/null and b/docs/tutorial/img/config.json/step2-1.png differ diff --git a/docs/tutorial/img/config.json/step2-2.png b/docs/tutorial/img/config.json/step2-2.png new file mode 100644 index 0000000000..87dda23743 Binary files /dev/null and b/docs/tutorial/img/config.json/step2-2.png differ diff --git a/docs/tutorial/img/config.json/step3.png b/docs/tutorial/img/config.json/step3.png new file mode 100644 index 0000000000..7d08098d91 Binary files /dev/null and b/docs/tutorial/img/config.json/step3.png differ diff --git a/docs/tutorial/img/config.json/step4.png b/docs/tutorial/img/config.json/step4.png new file mode 100644 index 0000000000..6940e4c31a Binary files /dev/null and b/docs/tutorial/img/config.json/step4.png differ diff --git a/docs/tutorial/img/config.json/step5.png b/docs/tutorial/img/config.json/step5.png new file mode 100644 index 0000000000..852596f10a Binary files /dev/null and b/docs/tutorial/img/config.json/step5.png differ diff --git a/docs/tutorial/img/config.json/step6.png b/docs/tutorial/img/config.json/step6.png new file mode 100644 index 0000000000..4d16984ec5 Binary files /dev/null and b/docs/tutorial/img/config.json/step6.png differ diff --git a/docs/tutorial/img/config.json/step7.png b/docs/tutorial/img/config.json/step7.png new file mode 100644 index 0000000000..f6d6295406 Binary files /dev/null and b/docs/tutorial/img/config.json/step7.png differ diff --git a/docs/tutorial/img/config.json/step8.png b/docs/tutorial/img/config.json/step8.png new file mode 100644 index 0000000000..0f0437bf01 Binary files /dev/null and b/docs/tutorial/img/config.json/step8.png differ diff --git a/docs/tutorial/img/config.json/step9-2.png b/docs/tutorial/img/config.json/step9-2.png new file mode 100644 index 0000000000..dd9f2bb651 Binary files /dev/null and b/docs/tutorial/img/config.json/step9-2.png differ diff --git a/docs/tutorial/img/config.json/step9.png b/docs/tutorial/img/config.json/step9.png new file mode 100644 index 0000000000..ccc4582695 Binary files /dev/null and b/docs/tutorial/img/config.json/step9.png differ diff --git a/docs/tutorial/img/replication-config.json/step1.png b/docs/tutorial/img/replication-config.json/step1.png new file mode 100644 index 0000000000..13c5b87b2b Binary files /dev/null and b/docs/tutorial/img/replication-config.json/step1.png differ diff --git a/docs/tutorial/img/replication-config.json/step3.png b/docs/tutorial/img/replication-config.json/step3.png new file mode 100644 index 0000000000..113a278da2 Binary files /dev/null and b/docs/tutorial/img/replication-config.json/step3.png differ diff --git a/docs/tutorial/img/replication-config.json/step4.png b/docs/tutorial/img/replication-config.json/step4.png new file mode 100644 index 0000000000..989d3307c1 Binary files /dev/null and b/docs/tutorial/img/replication-config.json/step4.png differ diff --git a/docs/tutorial/img/replication-config.json/step5.png b/docs/tutorial/img/replication-config.json/step5.png new file mode 100644 index 0000000000..3f2aa38b2c Binary files /dev/null and b/docs/tutorial/img/replication-config.json/step5.png differ diff --git a/docs/tutorial/img/replication-config.json/step6.png b/docs/tutorial/img/replication-config.json/step6.png new file mode 100644 index 0000000000..a0fe912d00 Binary files /dev/null and b/docs/tutorial/img/replication-config.json/step6.png differ diff --git a/docs/tutorial/replication-config.json.md b/docs/tutorial/replication-config.json.md new file mode 100644 index 0000000000..630f02695b --- /dev/null +++ b/docs/tutorial/replication-config.json.md @@ -0,0 +1,355 @@ +# replication-config.json + +In this part of the tutorial, you will build a [Study Config](../typedoc/interfaces/StudyConfig.md) for a replication study, [`public/tutorial/replication-config.json`](https://github.com/revisit-studies/template/blob/main/public/tutorial/replication-config.json). The completed version is [`public/tutorial/_answers/replication-config.json`](https://github.com/revisit-studies/template/blob/main/public/tutorial/_answers/replication-config.json). Use the completed version to check the step you just finished, not as something to copy all at once. + +:::info +Before you start editing tutorial files, complete the [Installation guide](../getting-started/installation.md) using the **Starting from the Template Repository** workflow. +::: + +## Step 1: Run the local server and register the config + +Start the local server from the root of your template repository: + +```bash +yarn serve +``` + +Before editing the replication Study Config, open [`public/global.json`](https://github.com/revisit-studies/template/blob/main/public/global.json). Add `replication` to `configsList` and `configs`. + +```json title="public/global.json" +{ + "$schema": "https://raw.githubusercontent.com/revisit-studies/study/v2.4.3/src/parser/GlobalConfigSchema.json", + "configsList": ["tutorial", "replication"], + "configs": { + "tutorial": { + "path": "tutorial/config.json" + }, + "replication": { + "path": "tutorial/replication-config.json" + } + } +} +``` + +Open [http://localhost:8080](http://localhost:8080). You should now see the replication study listed. + +![The replication study appears on the local reVISit page](./img/replication-config.json/step1.png) + +## Step 2: Add the reusable scatter plot base component + +First, create the React wrapper that the Study Config will load. In `src/public/tutorial/assets/replication/`, add a file named `ScatterWrapper.tsx`. + +```tsx title="src/public/tutorial/assets/replication/ScatterWrapper.tsx" +/** + * Authors: The ReVISit team + * Description: + * This file is the wrapper component for the Scatter plots + */ + +import { + Center, Group, Stack, Text, +} from '@mantine/core'; +import { Scatter } from './Scatter'; +import { StimulusParams } from '../../../../../store/types'; + +/** + * Holds 2 Scatter Plots + * @param param0 - r1 is the correlation value for 1, r2 is the correlation value for 2, + * onClick is a function that determines the functionality when a graph is clicked. + * @returns 2 Scatter Plots + */ +export default function ScatterWrapper({ parameters }: StimulusParams<{ r1: number; r2: number }>) { + const { r1, r2 } = parameters; + const r1DatasetName = `dataset_${r1.toFixed(2)}_size_100.csv`; + const r2DatasetName = `dataset_${r2.toFixed(2)}_size_100.csv`; + + return ( + + + Please select the visualization that appears to have a larger correlation. + +
+ + + + +
+
+ ); +} +``` + +This wrapper reads `r1` and `r2` from the component [`parameters`](../typedoc/interfaces/ReactComponent.md#parameters), turns them into dataset file names, and renders two scatter plots side by side. + +Replace the empty [`baseComponents`](../typedoc/interfaces/StudyConfig.md#basecomponents) object with `scatterBase`. + +```json title="public/tutorial/replication-config.json" +"baseComponents": { + "scatterBase": { + "type": "react-component", + "path": "tutorial/assets/replication/ScatterWrapper.tsx", + "response": [ + { + "id": "buttonsResponse", + "type": "buttons", + "prompt": "Choose the plot with the higher correlation:", + "required": true, + "location": "belowStimulus", + "options": [ + { + "label": "Left Plot", + "value": "left" + }, + { + "label": "Right Plot", + "value": "right" + } + ] + } + ] + } +} +``` + +`baseComponents` are templates. They are not added to the sequence directly. Other components inherit from them via `"baseComponent": "scatterBase"` and override only the fields that change, usually [`parameters`](../typedoc/interfaces/ReactComponent.md#parameters). + +## Step 3: Add the first practice trial + +Replace the empty [`components`](../typedoc/interfaces/StudyConfig.md#components) object with the first practice trial. + +```json title="public/tutorial/replication-config.json" +"components": { + "practice T1 A:0.3 B:0.7": { + "baseComponent": "scatterBase", + "parameters": { + "r1": 0.3, + "r2": 0.7 + }, + "correctAnswer": [ + { + "id": "buttonsResponse", + "answer": "right" + } + ], + "provideFeedback": true + } +} +``` + +This trial [inherits](../typedoc/type-aliases/InheritedComponent.md) the stimulus and response from `scatterBase`. The [`parameters`](../typedoc/interfaces/ReactComponent.md#parameters) values tell the React component which correlations to show in the left and right plots. + +For this practice trial, `r2` is larger than `r1`, so the correct answer is `"right"`. The `id` in [`correctAnswer`](../typedoc/interfaces/Answer.md) must match the response id from the base component: `buttonsResponse`. + +Add the first practice trial to the sequence: + +```json title="public/tutorial/replication-config.json" +"sequence": { + "order": "fixed", + "components": [ + { + "order": "fixed", + "components": [ + "practice T1 A:0.3 B:0.7" + ] + } + ] +} +``` + +![The replication study with the first practice trial](./img/replication-config.json/step3.png) + +## Step 4: Add the second practice trial + +Add a comma after the first practice trial, then add the second practice trial. + +```json title="public/tutorial/replication-config.json" +"components": { + "practice T1 A:0.3 B:0.7": { ... }, + "practice T2 A:0.9 B:0.6": { + "baseComponent": "scatterBase", + "parameters": { + "r1": 0.9, + "r2": 0.6 + }, + "correctAnswer": [ + { + "id": "buttonsResponse", + "answer": "left" + } + ], + "provideFeedback": true + } +} +``` + +This trial uses the same base component, but with different correlation values. Here, `r1` is larger than `r2`, so the correct answer is `"left"`. + +Add the second practice trial to the same fixed sequence block: + +```json title="public/tutorial/replication-config.json" +"sequence": { + "order": "fixed", + "components": [ + { + "order": "fixed", + "components": [ + "practice T1 A:0.3 B:0.7", + "practice T2 A:0.9 B:0.6" + ] + } + ] +} +``` + +![The replication study with the first two practice trials](./img/replication-config.json/step4.png) + +## Step 5: Add the third practice trial + +Add the third practice trial after the second. + +```json title="public/tutorial/replication-config.json" +"components": { + "practice T1 A:0.3 B:0.7": { ... }, + "practice T2 A:0.9 B:0.6": { ... }, + "practice T3 A:0.6 B:0.3": { + "baseComponent": "scatterBase", + "parameters": { + "r1": 0.6, + "r2": 0.3 + }, + "correctAnswer": [ + { + "id": "buttonsResponse", + "answer": "left" + } + ], + "provideFeedback": true + } +} +``` + +Again, this trial inherits from `scatterBase`. The left plot has the higher correlation, so the answer is `"left"`. + +Add the third practice trial to the sequence: + +```json title="public/tutorial/replication-config.json" +"sequence": { + "order": "fixed", + "components": [ + { + "order": "fixed", + "components": [ + "practice T1 A:0.3 B:0.7", + "practice T2 A:0.9 B:0.6", + "practice T3 A:0.6 B:0.3" + ] + } + ] +} +``` + +All three practice trials use [`provideFeedback`](../designing-studies/answers-trainings.md) so participants can learn what the task is asking before the study moves into the dynamic trial section. + +![The replication study with all three practice trials](./img/replication-config.json/step5.png) + +## Step 6: Add the dynamic JND block + +Navigate to `src/public/tutorial/assets/replication/` and open the file named `JNDDynamic.tsx`. + +This file currently contains a placeholder. Let's write a function that dynamically changes which component appears next. + +```ts title="src/public/tutorial/assets/replication/JNDDynamic.tsx" +import { JumpFunctionParameters, JumpFunctionReturnVal, StoredAnswer } from '../../../../store/types'; + +const findLatestTrial = (allDynamicAnswers: StoredAnswer[]) => { + const trials = allDynamicAnswers + .sort((a, b) => parseInt(a.trialOrder.split('_').at(-1) || '0', 10) - parseInt(b.trialOrder.split('_').at(-1) || '0', 10)); + + return trials.at(-1)!; +}; + +export default function func({ answers }: JumpFunctionParameters<{ r1: number, r2: number, counter: number }>): JumpFunctionReturnVal { + const allDynamicAnswers = Object.values(answers) + .filter((answer) => answer.componentName === 'trial'); + + // First trial + if (allDynamicAnswers.length === 0) { + return { + component: 'trial', + parameters: { + r1: 0.1, + r2: 0.9, + }, + correctAnswer: [{ id: 'buttonsResponse', answer: 'right' }], + }; + } + + if (allDynamicAnswers.length === 9) { + return { component: null }; + } + + const latestTrial = findLatestTrial(allDynamicAnswers); + + const right = latestTrial.parameters.r2 === 0.9; + + const approachingValue = right ? latestTrial.parameters.r1 + 0.1 : latestTrial.parameters.r2 + 0.1; + + const r1 = right ? 0.9 : approachingValue; + const r2 = right ? approachingValue : 0.9; + + return { + component: 'trial', + parameters: { + r1, + r2, + }, + correctAnswer: [{ id: 'buttonsResponse', answer: right ? 'left' : 'right' }], + }; +} +``` + +This function looks at the participant's previous dynamic trial answers. It starts with a large correlation difference, then moves the smaller correlation closer to `0.9` until the dynamic block has shown nine trials. + +Then, inside the nested fixed block, add the [dynamic block](../typedoc/interfaces/DynamicBlock.md) after the three practice trials. + +```json title="public/tutorial/replication-config.json" +"sequence": { + "order": "fixed", + "components": [ + { + "order": "fixed", + "components": [ + "practice T1 A:0.3 B:0.7", + "practice T2 A:0.9 B:0.6", + "practice T3 A:0.6 B:0.3", + { + "order": "dynamic", + "id": "steppedSequence", + "functionPath": "tutorial/assets/replication/JNDDynamic.tsx", + "parameters": {} + } + ] + } + ] +} +``` + +The dynamic block calls the function at `tutorial/assets/replication/JNDDynamic.tsx`. That function decides what trial comes next and can pass `parameters` and `correctAnswer` into the next generated trial. + +![The replication study with the dynamic JND block](./img/replication-config.json/step6.png) + + +import StructuredLinks from '@site/src/components/StructuredLinks/StructuredLinks.tsx'; + + diff --git a/docs/tutorial/tutorial.md b/docs/tutorial/tutorial.md new file mode 100644 index 0000000000..a4e28018c1 --- /dev/null +++ b/docs/tutorial/tutorial.md @@ -0,0 +1,30 @@ +# Tutorial + +
+