Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions screenshots/run_all_tasks_fixed.sh
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ describe('Task Screenshot Capture', () => {
'.jspsych-btn', // Standard jsPsych buttons
'button[data-choice]', // Choice buttons
'.lev-response-row button', // Levante framework buttons
'button:not(.replay):not(#replay-btn-revisited)', // Any button except replay
'button:not(#replay-btn-revisited):not(#fullscreen)', // Any button except participant replay / fullscreen
'button' // Fallback to any button
];

Expand Down Expand Up @@ -290,11 +290,11 @@ describe('Task Screenshot Capture', () => {
// Binary choice interaction
function performBinaryChoiceInteraction() {
cy.get('body').then($body => {
const buttons = $body.find('button:not(.replay):not(#replay-btn-revisited)');
const buttons = $body.find('button:not(#replay-btn-revisited):not(#fullscreen)');
if (buttons.length === 2) {
// Exactly 2 buttons, pick randomly
const randomIndex = Math.floor(Math.random() * 2);
cy.get('button:not(.replay):not(#replay-btn-revisited)').eq(randomIndex).click({ force: true });
cy.get('button:not(#replay-btn-revisited):not(#fullscreen)').eq(randomIndex).click({ force: true });
} else {
performAFCInteraction();
}
Expand Down Expand Up @@ -329,7 +329,7 @@ describe('Task Screenshot Capture', () => {
cy.get('body').then($body => {
// Try to find any clickable element
const genericSelectors = [
'button:not(.replay):not(#replay-btn-revisited):visible',
'button:not(#replay-btn-revisited):not(#fullscreen):visible',
'input[type="submit"]:visible',
'[role="button"]:visible',
'.clickable:visible',
Expand Down
8 changes: 4 additions & 4 deletions screenshots/run_all_tasks_smart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ describe('Task Screenshot Capture', () => {
'.jspsych-btn', // Standard jsPsych buttons
'button[data-choice]', // Choice buttons
'.lev-response-row button', // Levante framework buttons
'button:not(.replay):not(#replay-btn-revisited)', // Any button except replay
'button:not(#replay-btn-revisited):not(#fullscreen)', // Any button except participant replay / fullscreen
'button' // Fallback to any button
];

Expand Down Expand Up @@ -310,11 +310,11 @@ describe('Task Screenshot Capture', () => {
// Binary choice interaction
function performBinaryChoiceInteraction() {
cy.get('body').then($body => {
const buttons = $body.find('button:not(.replay):not(#replay-btn-revisited)');
const buttons = $body.find('button:not(#replay-btn-revisited):not(#fullscreen)');
if (buttons.length === 2) {
// Exactly 2 buttons, pick randomly
const randomIndex = Math.floor(Math.random() * 2);
cy.get('button:not(.replay):not(#replay-btn-revisited)').eq(randomIndex).click({ force: true });
cy.get('button:not(#replay-btn-revisited):not(#fullscreen)').eq(randomIndex).click({ force: true });
} else {
performAFCInteraction();
}
Expand Down Expand Up @@ -349,7 +349,7 @@ describe('Task Screenshot Capture', () => {
cy.get('body').then($body => {
// Try to find any clickable element
const genericSelectors = [
'button:not(.replay):not(#replay-btn-revisited):visible',
'button:not(#replay-btn-revisited):not(#fullscreen):visible',
'input[type="submit"]:visible',
'[role="button"]:visible',
'.clickable:visible',
Expand Down
2 changes: 2 additions & 0 deletions task-launcher/serve/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const sequentialStimulus = stringToBoolean(urlParams.get('sequentialStimulus'),
const storeItemId = stringToBoolean(urlParams.get('storeItemId'), false);
const cat = stringToBoolean(urlParams.get('cat'), false);
const heavyInstructions = stringToBoolean(urlParams.get('heavyInstructions'), false);
const experimenterButtons = stringToBoolean(urlParams.get('experimenterButtons'), false);
const debug = stringToBoolean(urlParams.get('debug'), false);

const emulatorConfig = EMULATORS ? firebaseJSON.emulators : undefined;
Expand Down Expand Up @@ -103,6 +104,7 @@ async function startWebApp() {
semThreshold,
startingTheta,
heavyInstructions,
experimenterButtons,
demoMode,
version,
debug,
Expand Down
2 changes: 2 additions & 0 deletions task-launcher/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export class TaskLauncher {
jsPsych.run(timeline);
const translations = taskStore().translations;
const pageSetup = new InitPageSetup(4000, translations);
taskStore('pageSetup', pageSetup);

pageSetup.init();
const checkTaskFinished = this.gameParams.demoMode
? () => taskStore().taskComplete
Expand Down
4 changes: 4 additions & 0 deletions task-launcher/src/styles/abstracts/_variables.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
$button-primary-padding: min(20px, 2.5vh) min(72px, 10vh);
$button-primary-padding-small: min(12px, 1.7vh) min(60px, 8vh);
$button-primary-padding-xs: min(8px, 1.1vh) min(48px, 6.5vh);
$button-secondary-padding: min(48px, 6.5vh) min(48px, 6.5vh);
$button-secondary-padding-small: min(40px, 5.5vh) min(40px, 5.5vh);
$button-default-border-radius: min(32px, 4vh);
Expand All @@ -26,6 +27,9 @@ $response-size-m: min($response-size-m-width, $response-size-m-height);
$replay_btn_size: min(64px, 8vh);
$replay_btn_pos: min(8px, 1vh);

// experimenter buttons
$experimenter_btn_size: calc($replay_btn_size / 2);

// progress bar
$progress_bar_pos: min(8px, 1vh);
$progress_bar_width: 70vw;
Expand Down
19 changes: 19 additions & 0 deletions task-launcher/src/styles/base/_animations.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,22 @@
transform: rotate(0deg);
}
}

.pulse {
width: 200px;
height: 200px;
animation: pulse 2s infinite;

&:hover {
cursor: pointer;
}
}

@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
30 changes: 25 additions & 5 deletions task-launcher/src/styles/components/_buttons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ button {
box-shadow: $button-default-box-shadow $btn-disabled-inset inset;
cursor: no-drop;
}

&.small {
font-size: $font-size-xxxs;
font-weight: $font-weight-normal;
color: $txt-white;
padding: $button-primary-padding-xs;
border-radius: $button-default-border-radius-small;
display: flex;
align-items: center;
justify-content: center;
}
}

&.secondary {
Expand Down Expand Up @@ -206,11 +217,7 @@ button {
height: 16x;
}

&.replay {
// This is also used by the Hearts and Flowers task
position: absolute;
top: $replay_btn_pos;
right: $replay_btn_pos;
&.utility {
cursor: pointer;
border-radius: min(16px, 2vh);
padding: min(16px, 2vh);
Expand Down Expand Up @@ -248,6 +255,19 @@ button {
}
}
}

&.open {
background-color: rgba($bg-button-primary-clicked, 0.6);
border: 2px solid $border-button-primary-clicked;
}

&.transparent {
background-color: transparent;
border: transparent;
width: $experimenter_btn_size;
height: $experimenter_btn_size;
padding: 0;
}
}

&.image {
Expand Down
23 changes: 23 additions & 0 deletions task-launcher/src/styles/components/_popups.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,26 @@
height: 100%;
background: $bg-white-80;
}


.exit-confirmation-popup {
position: absolute;
top: 10%;
left: 50vw;
transform: translateX(-50%);
border: 3px solid $bg-accent_secondary;
border-radius: $button-default-border-radius;
background-color: $bg-bubble-base;
padding: 20px;
text-align: center;
font-weight: $font-weight-dark;
}

.exit-confirmation-popup-buttons {
display: flex;
flex-direction: row;
gap: 20px;
margin-top: 20px;
justify-content: center;
align-items: center;
}
23 changes: 23 additions & 0 deletions task-launcher/src/styles/layout/_containers.scss
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,26 @@
font-weight: $font-weight-bold;
padding: 10px;
}

// holds experimenter buttons for pausing, leaving the task, or reentering fullscreen
.experimenter-button-container {
position: absolute;
top: $replay_btn_pos;
left: $replay_btn_pos;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: min(8px, 1vh);
};

.participant-button-container {
position: absolute;
top: $replay_btn_pos;
right: $replay_btn_pos;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: min(8px, 1vh);
}
17 changes: 16 additions & 1 deletion task-launcher/src/taskStore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { InputCapability } from '../utils/detectInput';
* @property {Object} assetsPerTask - Object containing list of assets belonging to each task.
* @property {boolean} demoMode - Whether the task is running in demo mode (no interaction with Firestore), default is false.
* @property {boolean} debug - Shows theta estimate on the screen for cat debugging when enabled.
* @property {boolean} experimenterButtons - When true, experimenter utility controls (pause, exit) are available.
* @property {number} currentCatBlock - The current block number to select trials from in a CAT.
* @property {number[]} blockThresholds - Array of theta thresholds.
* @property {number} totalTrialCount - Total number of trials, including practice and instructions.
Expand All @@ -37,6 +38,10 @@ import { InputCapability } from '../utils/detectInput';
* @property {Object} translations - Object containing the translations.
* @property {Object} nextStimulus - Object containing the next stimulus.
* @property {boolean} testPhase - True if not running practice/instruction trial
* @property {any} taskTimer - The timer ID for the task, stored here so the task can be paused.
* @property {number} taskTimerPausedMs - Cumulative ms excluded from max-time while experimenter pause is active.
* @property {number|null} taskTimerPauseBeganAt - Wall time when the current experimenter pause began, or null.
* @property {boolean} isPaused - Whether the task is paused, default is false.
* ------- AFC and SDS only -------
* @property {string} target - Target item.
* @property {Array} choices - List of choices.
Expand All @@ -62,7 +67,7 @@ import { InputCapability } from '../utils/detectInput';
* @property {Object} inputCapability - Object containing the input capability of the user's device.
* --------- ToM only ---------
* @property {Array} previousChoices - Array containing previously randomized order of choices for the current block.
* @property {number} currentStoryGroup - The current story group to select trials from in the ToM CAT..
* @property {number} currentStoryGroup - The current story group to select trials from in the ToM CAT.
* ------- SDS only -------
* @property {StimulusType[]} sequentialTrials - Should be run sequentially in blocks by trial number in an SDS CAT.
* @property {number} version - A version number for the task, default is 1. Can be used as a feature flag.
Expand Down Expand Up @@ -92,12 +97,17 @@ export type TaskStoreDataType = {
language?: string;
maxTime?: number;
demoMode: boolean;
experimenterButtons: boolean;
debug: boolean;
version: number;
currentCatBlock?: number;
blockThresholds?: number[];
displayPromptDurations: Record<string, number>;
inputCapability?: InputCapability;
taskTimer: any;
taskTimerPausedMs?: number;
taskTimerPauseBeganAt?: number | null;
isPaused: boolean;
};

/**
Expand Down Expand Up @@ -149,9 +159,14 @@ export const setTaskStore = (config: TaskStoreDataType) => {
testPhase: false,
maxTime: config.maxTime,
demoMode: config.demoMode,
experimenterButtons: config.experimenterButtons && config.heavyInstructions,
debug: config.debug,
version: config.version || 1,
currentStoryGroup: 0,
taskTimer: null,
taskTimerPausedMs: 0,
taskTimerPauseBeganAt: null,
isPaused: false,
});
};

Expand Down
3 changes: 1 addition & 2 deletions task-launcher/src/tasks/adult-reasoning/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
setupStimulus,
taskFinished,
enterFullscreen,
finishExperiment,
fixationOnly,
} from '../shared/trials';
import { getLayoutConfig } from './helpers/config';
Expand All @@ -18,7 +17,7 @@ export default function buildAdultReasoningTimeline(config: Record<string, any>,
const preloadTrials = createPreloadTrials(mediaAssets).default;

initTrialSaving(config);
const initialTimeline = initTimeline(config, enterFullscreen, finishExperiment);
const initialTimeline = initTimeline(config, enterFullscreen);
const timeline = [preloadTrials, initialTimeline];
const corpus: StimulusType[] = taskStore().corpora.stimulus;
const translations: Record<string, string> = taskStore().translations;
Expand Down
13 changes: 6 additions & 7 deletions task-launcher/src/tasks/child-survey/helpers/stimulus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import {
handleStaggeredButtons,
PageAudioHandler,
PageStateHandler,
replayButtonSvg,
addExperimenterButtons,
getParticipantUtilityButtonsHtml,
setSentryContext,
setupReplayAudio,
setupFullscreenButton,
} from '../../shared/helpers';
import { mediaAssets } from '../../..';
import { jsPsych } from '../../taskSetup';
Expand Down Expand Up @@ -50,12 +52,7 @@ export const surveyItem = ({
<div class="lev-progress-bar">
<div id="progress-fill" class="progress-fill"></div>
</div>
<button
id="${replayButtonHtmlId}"
class="replay"
>
${replayButtonSvg}
</button>
${getParticipantUtilityButtonsHtml(replayButtonHtmlId)}
<div class="lev-row-container instruction-small">
<p>${t[camelize(prompt)]}</p>
</div>
Expand Down Expand Up @@ -105,6 +102,8 @@ export const surveyItem = ({

// set up replay button
setupReplayAudio(pageStateHandler);
addExperimenterButtons();
setupFullscreenButton();

// enable response buttons immediately after prompt audio finishes so stagger effect can be interrupted
const audioConfig: AudioConfigType = {
Expand Down
4 changes: 2 additions & 2 deletions task-launcher/src/tasks/child-survey/timeline.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { taskStore } from '../../taskStore';
import { getLayoutConfig } from '../child-survey/helpers/config';
import { createPreloadTrials, getRealTrials, initTimeline, initTrialSaving } from '../shared/helpers';
import { enterFullscreen, exitFullscreen, finishExperiment, setupStimulus, taskFinished } from '../shared/trials';
import { enterFullscreen, exitFullscreen, setupStimulus, taskFinished } from '../shared/trials';
import { initializeCat, jsPsych } from '../taskSetup';
import { surveyItem } from './helpers/stimulus';

export default function buildChildSurveyTimeline(config: Record<string, any>, mediaAssets: MediaAssetsType) {
const preloadTrials = createPreloadTrials(mediaAssets).default;

initTrialSaving(config);
const initialTimeline = initTimeline(config, enterFullscreen, finishExperiment);
const initialTimeline = initTimeline(config, enterFullscreen);

const timeline = [preloadTrials, initialTimeline];

Expand Down
13 changes: 5 additions & 8 deletions task-launcher/src/tasks/hearts-and-flowers/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { replayButtonSvg } from '../../shared/helpers';
import { getParticipantUtilityButtonsHtml } from '../../shared/helpers';
import { taskStore } from '../../../taskStore';

export const StimulusType = Object.freeze({
Expand Down Expand Up @@ -81,14 +81,11 @@ export function getCorrectInputSide(stimulusType, stimulusSideType) {
*/
export const getStimulusLayout = (imageSrc, isLeft, promptText = undefined, replayButtonHtmlId = undefined) => {
const stimulusClass = isLeft ? 'stimulus-left' : 'stimulus-right';
const includeReplayButton = replayButtonHtmlId !== undefined;

let template = '<div class="haf-stimulus-holder">';
if (replayButtonHtmlId) {
template += `
<button id='${replayButtonHtmlId}' class="replay">
${replayButtonSvg}
</button>
`;
}
template += getParticipantUtilityButtonsHtml(replayButtonHtmlId, includeReplayButton);

if (promptText) {
template += `
<div class='lev-row-container instruction'>
Expand Down
Loading
Loading