Skip to content

Commit 8f686e1

Browse files
authored
Merge pull request #1003 from Watts-Lab/new-validators
New validators
2 parents 9da03b0 + c9e70f9 commit 8f686e1

17 files changed

Lines changed: 10296 additions & 9403 deletions

README.md

Lines changed: 295 additions & 276 deletions
Large diffs are not rendered by default.

client/src/Stage.jsx

Lines changed: 89 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,89 @@
1-
import { useStage, usePlayer } from "@empirica/core/player/classic/react";
2-
import React, { useEffect, useMemo } from "react";
3-
import {
4-
TimeConditionalRender,
5-
PositionConditionalRender,
6-
ConditionsConditionalRender,
7-
SubmissionConditionalRender,
8-
} from "./components/ConditionalRender";
9-
import { Discussion } from "./elements/Discussion";
10-
import { Element } from "./elements/Element";
11-
12-
export function Stage() {
13-
const stage = useStage();
14-
const player = usePlayer();
15-
16-
const progressLabel = useMemo(
17-
() =>
18-
`game_${stage.get("index")}_${stage
19-
.get("name")
20-
.trim()
21-
.replace(/ /g, "_")}`, // replace ALL spaces with underscores
22-
[stage]
23-
); // memoize so we don't trigger the useEffect on every render
24-
25-
useEffect(() => {
26-
if (player.get("progressLabel") !== progressLabel) {
27-
console.log(`Starting ${progressLabel}`);
28-
player.set("progressLabel", progressLabel);
29-
player.set("localStageStartTime", undefined); // force use of stageTimer
30-
}
31-
}, [progressLabel, player]);
32-
33-
const discussion = stage?.get("discussion");
34-
const elements = stage?.get("elements") || [];
35-
36-
const renderElement = (element, index) => (
37-
<TimeConditionalRender
38-
displayTime={element.displayTime}
39-
hideTime={element.hideTime}
40-
key={`element_${index}`}
41-
>
42-
<PositionConditionalRender
43-
showToPositions={element.showToPositions}
44-
hideFromPositions={element.hideFromPositions}
45-
>
46-
<ConditionsConditionalRender conditions={element.conditions}>
47-
<Element
48-
element={element}
49-
onSubmit={() => player.stage.set("submit", true)}
50-
/>
51-
</ConditionsConditionalRender>
52-
</PositionConditionalRender>
53-
</TimeConditionalRender>
54-
);
55-
56-
const renderDiscussionPage = () => (
57-
// If the page is larger than 'md', render two columns
58-
// with the left being the discussion at a fixed location
59-
// and the right being the elements.
60-
// If the page is smaller than 'md' render the discussion at the top
61-
// and the elements below it.
62-
63-
<>
64-
<div className="md:absolute md:left-0 md:top-0 md:bottom-0 md:right-150">
65-
<Discussion
66-
chatType={discussion.chatType}
67-
showNickname={discussion.showNickname ?? true}
68-
showTitle={discussion.showTitle}
69-
/>
70-
</div>
71-
72-
<div className="pb-4 px-4 md:absolute md:right-0 md:w-150 md:bottom-0 md:top-0 md:overflow-auto md:scroll-smooth">
73-
{elements.map(renderElement)}
74-
</div>
75-
</>
76-
);
77-
78-
const renderNoDiscussionPage = () => (
79-
<div className="mx-auto max-w-2xl pb-2">{elements.map(renderElement)}</div>
80-
);
81-
82-
return (
83-
<SubmissionConditionalRender>
84-
{!!discussion && renderDiscussionPage()}
85-
{!discussion && renderNoDiscussionPage()}
86-
</SubmissionConditionalRender>
87-
);
88-
}
1+
import { useStage, usePlayer } from "@empirica/core/player/classic/react";
2+
import React, { useEffect, useMemo } from "react";
3+
import {
4+
TimeConditionalRender,
5+
PositionConditionalRender,
6+
ConditionsConditionalRender,
7+
SubmissionConditionalRender,
8+
} from "./components/ConditionalRender";
9+
import { Discussion } from "./elements/Discussion";
10+
import { Element } from "./elements/Element";
11+
12+
export function Stage() {
13+
14+
const stage = useStage();
15+
const player = usePlayer();
16+
17+
const progressLabel = useMemo(
18+
() =>
19+
`game_${stage.get("index")}_${stage
20+
.get("name")
21+
.trim()
22+
.replace(/ /g, "_")}`, // replace ALL spaces with underscores
23+
[stage]
24+
); // memoize so we don't trigger the useEffect on every render
25+
26+
useEffect(() => {
27+
if (player.get("progressLabel") !== progressLabel) {
28+
console.log(`Starting ${progressLabel}`);
29+
player.set("progressLabel", progressLabel);
30+
player.set("localStageStartTime", undefined); // force use of stageTimer
31+
}
32+
}, [progressLabel, player]);
33+
34+
const discussion = stage?.get("discussion");
35+
const elements = stage?.get("elements") || [];
36+
37+
const renderElement = (element, index) => (
38+
<TimeConditionalRender
39+
displayTime={element.displayTime}
40+
hideTime={element.hideTime}
41+
key={`element_${index}`}
42+
>
43+
<PositionConditionalRender
44+
showToPositions={element.showToPositions}
45+
hideFromPositions={element.hideFromPositions}
46+
>
47+
<ConditionsConditionalRender conditions={element.conditions}>
48+
<Element
49+
element={element}
50+
onSubmit={() => player.stage.set("submit", true)}
51+
/>
52+
</ConditionsConditionalRender>
53+
</PositionConditionalRender>
54+
</TimeConditionalRender>
55+
);
56+
57+
const renderDiscussionPage = () => (
58+
// If the page is larger than 'md', render two columns
59+
// with the left being the discussion at a fixed location
60+
// and the right being the elements.
61+
// If the page is smaller than 'md' render the discussion at the top
62+
// and the elements below it.
63+
64+
<>
65+
<div className="md:absolute md:left-0 md:top-0 md:bottom-0 md:right-150">
66+
<Discussion
67+
chatType={discussion.chatType}
68+
showNickname={discussion.showNickname ?? true}
69+
showTitle={discussion.showTitle}
70+
/>
71+
</div>
72+
73+
<div className="pb-4 px-4 md:absolute md:right-0 md:w-150 md:bottom-0 md:top-0 md:overflow-auto md:scroll-smooth">
74+
{elements.map(renderElement)}
75+
</div>
76+
</>
77+
);
78+
79+
const renderNoDiscussionPage = () => (
80+
<div className="mx-auto max-w-2xl pb-2">{elements.map(renderElement)}</div>
81+
);
82+
83+
return (
84+
<SubmissionConditionalRender>
85+
{!!discussion && renderDiscussionPage()}
86+
{!discussion && renderNoDiscussionPage()}
87+
</SubmissionConditionalRender>
88+
);
89+
}

client/src/elements/Element.jsx

Lines changed: 105 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,105 @@
1-
/*
2-
A base wrapper for all the elements
3-
4-
*/
5-
6-
import React from "react";
7-
import { useStage } from "@empirica/core/player/classic/react";
8-
import { Prompt } from "./Prompt";
9-
import { Display } from "./Display";
10-
import { Separator } from "./Separator";
11-
import { AudioElement } from "./AudioElement";
12-
import { Survey } from "./Survey";
13-
import { SubmitButton } from "./SubmitButton";
14-
import { KitchenTimer } from "./KitchenTimer";
15-
import { TrainingVideo } from "./TrainingVideo";
16-
import { Qualtrics } from "./Qualtrics";
17-
import { SharedNotepad } from "../components/SharedNotepad";
18-
import { TalkMeter } from "./TalkMeter";
19-
import { Image } from "../components/Image";
20-
21-
export function Element({ element, onSubmit }) {
22-
// Todo: remove stage dependencies to improve robustness in intro/exit steps
23-
const stage = useStage();
24-
25-
switch (element.type) {
26-
case "audio":
27-
return <AudioElement file={element.file} />;
28-
29-
case "display":
30-
return (
31-
<Display
32-
promptName={element.promptName}
33-
reference={element.reference}
34-
position={element.position}
35-
/>
36-
);
37-
38-
case "image":
39-
return <Image file={element.file} width={element.width} />;
40-
41-
case "prompt":
42-
return (
43-
<Prompt
44-
file={element.file}
45-
name={element.name}
46-
shared={element.shared}
47-
/>
48-
);
49-
50-
case "qualtrics":
51-
console.log("qualtrics");
52-
return (
53-
<Qualtrics
54-
url={element.url}
55-
params={element.params}
56-
onSubmit={onSubmit}
57-
/>
58-
);
59-
60-
case "separator":
61-
return <Separator style={element.style} />;
62-
63-
case "submitButton":
64-
return (
65-
<SubmitButton
66-
onSubmit={onSubmit}
67-
name={element.name}
68-
buttonText={element.buttonText}
69-
/>
70-
);
71-
72-
case "survey":
73-
return (
74-
<Survey
75-
surveyName={element.surveyName}
76-
name={element.name}
77-
onSubmit={onSubmit}
78-
/>
79-
); // TODO: pass in the element name so that results can be saved if the survey is completed multiple times
80-
81-
case "timer":
82-
return (
83-
<KitchenTimer
84-
startTime={element.startTime || element.displayTime || 0}
85-
endTime={
86-
element.endTime || element.hideTime || stage?.get("duration")
87-
}
88-
warnTimeRemaining={element.warnTimeRemaining}
89-
/>
90-
);
91-
92-
case "video":
93-
return <TrainingVideo url={element.url} />;
94-
95-
case "talkMeter":
96-
return <TalkMeter />;
97-
98-
case "sharedNotepad":
99-
return <SharedNotepad padName={element.name || stage.get("name")} />;
100-
101-
default:
102-
console.log(`unknown element type ${element.type} in `, element);
103-
return undefined;
104-
}
105-
}
1+
/*
2+
A base wrapper for all the elements
3+
4+
*/
5+
6+
import React from "react";
7+
import { useStage } from "@empirica/core/player/classic/react";
8+
import { Prompt } from "./Prompt";
9+
import { Display } from "./Display";
10+
import { Separator } from "./Separator";
11+
import { AudioElement } from "./AudioElement";
12+
import { Survey } from "./Survey";
13+
import { SubmitButton } from "./SubmitButton";
14+
import { KitchenTimer } from "./KitchenTimer";
15+
import { TrainingVideo } from "./TrainingVideo";
16+
import { Qualtrics } from "./Qualtrics";
17+
import { SharedNotepad } from "../components/SharedNotepad";
18+
import { TalkMeter } from "./TalkMeter";
19+
import { Image } from "../components/Image";
20+
21+
export function Element({ element, onSubmit }) {
22+
// Todo: remove stage dependencies to improve robustness in intro/exit steps
23+
const stage = useStage();
24+
25+
switch (element.type) {
26+
case "audio":
27+
return <AudioElement file={element.file} />;
28+
29+
case "display":
30+
return (
31+
<Display
32+
promptName={element.promptName}
33+
reference={element.reference}
34+
position={element.position}
35+
/>
36+
);
37+
38+
case "image":
39+
return <Image file={element.file} width={element.width} />;
40+
41+
case "prompt":
42+
return (
43+
<Prompt
44+
file={element.file}
45+
name={element.name}
46+
shared={element.shared}
47+
/>
48+
);
49+
50+
case "qualtrics":
51+
console.log("qualtrics");
52+
return (
53+
<Qualtrics
54+
url={element.url}
55+
params={element.params}
56+
onSubmit={onSubmit}
57+
/>
58+
);
59+
60+
case "separator":
61+
return <Separator style={element.style} />;
62+
63+
case "submitButton":
64+
return (
65+
<SubmitButton
66+
onSubmit={onSubmit}
67+
name={element.name}
68+
buttonText={element.buttonText}
69+
/>
70+
);
71+
72+
case "survey":
73+
return (
74+
<Survey
75+
surveyName={element.surveyName}
76+
name={element.name}
77+
onSubmit={onSubmit}
78+
/>
79+
); // TODO: pass in the element name so that results can be saved if the survey is completed multiple times
80+
81+
case "timer":
82+
return (
83+
<KitchenTimer
84+
startTime={element.startTime || element.displayTime || 0}
85+
endTime={
86+
element.endTime || element.hideTime || stage?.get("duration")
87+
}
88+
warnTimeRemaining={element.warnTimeRemaining}
89+
/>
90+
);
91+
92+
case "video":
93+
return <TrainingVideo url={element.url} />;
94+
95+
case "talkMeter":
96+
return <TalkMeter />;
97+
98+
case "sharedNotepad":
99+
return <SharedNotepad padName={element.name || stage.get("name")} />;
100+
101+
default:
102+
console.log(`unknown element type ${element.type} in `, element);
103+
return undefined;
104+
}
105+
}

0 commit comments

Comments
 (0)