Skip to content

Commit 3163cae

Browse files
committed
feat: switched from using qualtrics surveys to using custom-made components.
* updated CSV file download to use JSON format and return all data collected within the study. * Increased the Text Size in the Text Task * Optimized image loading to prevent placeholders
1 parent d3339dd commit 3163cae

18 files changed

+925
-269
lines changed

webcamstudy/asset-manifest.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"files": {
3-
"main.css": "https://seresl.unl.edu/webcamstudy/static/css/main.ea5d9395.css",
4-
"main.js": "https://seresl.unl.edu/webcamstudy/static/js/main.0cf5287e.js",
3+
"main.css": "https://seresl.unl.edu/webcamstudy/static/css/main.3de88975.css",
4+
"main.js": "https://seresl.unl.edu/webcamstudy/static/js/main.a12b748b.js",
55
"static/js/453.e44939a0.chunk.js": "https://seresl.unl.edu/webcamstudy/static/js/453.e44939a0.chunk.js",
66
"static/media/clip2.mp4": "https://seresl.unl.edu/webcamstudy/static/media/clip2.1e6e426f2cdb61fe74ec.mp4",
77
"static/media/clip3.mp4": "https://seresl.unl.edu/webcamstudy/static/media/clip3.3341365941d9bce4b98b.mp4",
@@ -69,12 +69,12 @@
6969
"static/media/image-10.jpg": "https://seresl.unl.edu/webcamstudy/static/media/image-10.fcdfd9fbc352940b0d95.jpg",
7070
"static/media/image-16.jpg": "https://seresl.unl.edu/webcamstudy/static/media/image-16.8583b0dd5e9bb251fc9c.jpg",
7171
"index.html": "https://seresl.unl.edu/webcamstudy/index.html",
72-
"main.ea5d9395.css.map": "https://seresl.unl.edu/webcamstudy/static/css/main.ea5d9395.css.map",
73-
"main.0cf5287e.js.map": "https://seresl.unl.edu/webcamstudy/static/js/main.0cf5287e.js.map",
72+
"main.3de88975.css.map": "https://seresl.unl.edu/webcamstudy/static/css/main.3de88975.css.map",
73+
"main.a12b748b.js.map": "https://seresl.unl.edu/webcamstudy/static/js/main.a12b748b.js.map",
7474
"453.e44939a0.chunk.js.map": "https://seresl.unl.edu/webcamstudy/static/js/453.e44939a0.chunk.js.map"
7575
},
7676
"entrypoints": [
77-
"static/css/main.ea5d9395.css",
78-
"static/js/main.0cf5287e.js"
77+
"static/css/main.3de88975.css",
78+
"static/js/main.a12b748b.js"
7979
]
8080
}

webcamstudy/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="https://seresl.unl.edu/webcamstudy/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="https://seresl.unl.edu/webcamstudy/logo192.png"/><link rel="manifest" href="https://seresl.unl.edu/webcamstudy/manifest.json"/><title>React App</title><script type="module">import EmbeddedPageSdk from"https://app.realeye.io/sdk/js/testRunnerEmbeddableSdk-1.7.1.js";window.addEventListener("DOMContentLoaded",()=>{new EmbeddedPageSdk(!1,null,!1)})</script><script defer="defer" src="https://seresl.unl.edu/webcamstudy/static/js/main.0cf5287e.js"></script><link href="https://seresl.unl.edu/webcamstudy/static/css/main.ea5d9395.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="https://seresl.unl.edu/webcamstudy/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="https://seresl.unl.edu/webcamstudy/logo192.png"/><link rel="manifest" href="https://seresl.unl.edu/webcamstudy/manifest.json"/><title>React App</title><script type="module">import EmbeddedPageSdk from"https://app.realeye.io/sdk/js/testRunnerEmbeddableSdk-1.7.1.js";window.addEventListener("DOMContentLoaded",()=>{new EmbeddedPageSdk(!1,null,!1)})</script><script defer="defer" src="https://seresl.unl.edu/webcamstudy/static/js/main.a12b748b.js"></script><link href="https://seresl.unl.edu/webcamstudy/static/css/main.3de88975.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

webcamstudy/package-lock.json

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webcamstudy/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"@testing-library/jest-dom": "^6.6.3",
99
"@testing-library/react": "^16.3.0",
1010
"@testing-library/user-event": "^13.5.0",
11-
"papaparse": "^5.5.3",
1211
"react": "^19.1.0",
1312
"react-dom": "^19.1.0",
1413
"react-scripts": "5.0.1",

webcamstudy/src/App.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ body {
2525
margin: 10px auto;
2626
}
2727

28+
.button:hover {
29+
background-color: #2980b9 !important;
30+
transform: translateY(-2px);
31+
box-shadow: 0 6px 16px rgba(52, 152, 219, 0.4) !important;
32+
}
33+
34+
.button:active {
35+
transform: translateY(0);
36+
}
37+
2838
.hidden {
2939
display: none;
3040
}

webcamstudy/src/App.jsx

Lines changed: 202 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@ import './App.css';
44
// Import task components
55
import ConsentForm from './components/ConsentForm';
66
import TextTask from './components/TextTask';
7+
import TextSurvey from './components/TextSurvey';
78
import VideoTask from './components/VideoTask';
9+
import VideoSurvey from './components/VideoSurvey';
810
import FaceTask from './components/FaceTask';
911

1012
function App() {
1113
const [currentTask, setCurrentTask] = useState(0);
1214
const [taskFiles, setTaskFiles] = useState([]);
13-
const [clickCount, setClickCount] = useState(0);
14-
const [buttonVisible, setButtonVisible] = useState(true);
15+
const [studyData, setStudyData] = useState({
16+
consent: null,
17+
textTask: null,
18+
videoTask: null,
19+
faceTask: null
20+
});
1521

1622
useEffect(() => {
1723
generateTaskSequence();
@@ -21,72 +27,244 @@ function App() {
2127
const tasks = [
2228
'ConsentForm',
2329
'TextTask',
30+
'TextSurvey',
2431
'VideoTask',
32+
'VideoSurvey',
2533
'FaceTask',
2634
];
2735
setTaskFiles(tasks);
2836
};
2937

38+
const handleTaskComplete = (taskType, data) => {
39+
setStudyData(prev => ({
40+
...prev,
41+
[taskType]: data
42+
}));
43+
44+
// Auto-advance to next task
45+
setTimeout(() => {
46+
incrementTask();
47+
}, 500);
48+
};
49+
3050
const incrementTask = () => {
31-
setClickCount((prev) => prev + 1);
3251
setCurrentTask((prev) => prev + 1);
33-
setButtonVisible(false);
34-
35-
let delay = 0;
36-
if (clickCount === 0) {
37-
delay = 15000; // 15 seconds
38-
} else if (clickCount === 1) {
39-
delay = 20000; // 20 seconds
40-
} else {
41-
delay = 150000; // 2 min 30 seconds
52+
};
53+
54+
const downloadAllData = () => {
55+
// Create the JSON structure
56+
const jsonData = {
57+
id: parseInt(studyData.consent?.id) || 0,
58+
ageRange: studyData.consent?.ageRange || '',
59+
gender: studyData.consent?.gender || '',
60+
ethnicity: studyData.consent?.ethnicity || '',
61+
education: studyData.consent?.classRank || '',
62+
major: studyData.consent?.major || '',
63+
responses: [],
64+
timestamp: new Date().toISOString()
65+
};
66+
67+
// Add text task response
68+
if (studyData.textTask?.selectedAnswer) {
69+
jsonData.responses.push({
70+
task: "Text",
71+
response: studyData.textTask.selectedAnswer,
72+
isCorrect: determineTextCorrectness(studyData.textTask.selectedAnswer)
73+
});
4274
}
4375

44-
setTimeout(() => {
45-
setButtonVisible(true);
46-
}, delay);
76+
// Add video task response
77+
if (studyData.videoTask?.selectedAnswer) {
78+
jsonData.responses.push({
79+
task: "Video",
80+
response: studyData.videoTask.selectedAnswer,
81+
isCorrect: determineVideoCorrectness(studyData.videoTask.selectedAnswer)
82+
});
83+
}
84+
85+
// Add face task responses
86+
if (studyData.faceTask && Array.isArray(studyData.faceTask)) {
87+
const face2x2Results = studyData.faceTask.filter(result => result.gridSize === 2);
88+
const face3x3Results = studyData.faceTask.filter(result => result.gridSize === 3);
89+
90+
if (face2x2Results.length > 0) {
91+
const face2x2Response = {
92+
task: "Face2x2",
93+
response: face2x2Results.map(result => ({
94+
selectedRow: result.selectedRow || 1,
95+
selectedColumn: result.selectedColumn || 1,
96+
isCorrect: result.correct === 'Yes'
97+
})),
98+
isCorrect: calculateOverallFaceCorrectness(face2x2Results)
99+
};
100+
jsonData.responses.push(face2x2Response);
101+
}
102+
103+
if (face3x3Results.length > 0) {
104+
const face3x3Response = {
105+
task: "Face3x3",
106+
response: face3x3Results.map(result => ({
107+
selectedRow: result.selectedRow || 1,
108+
selectedColumn: result.selectedColumn || 1,
109+
isCorrect: result.correct === 'Yes'
110+
})),
111+
isCorrect: calculateOverallFaceCorrectness(face3x3Results)
112+
};
113+
jsonData.responses.push(face3x3Response);
114+
}
115+
}
116+
117+
// Download JSON file
118+
const jsonString = JSON.stringify(jsonData, null, 2);
119+
const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8;' });
120+
const url = URL.createObjectURL(blob);
121+
const link = document.createElement('a');
122+
link.setAttribute('href', url);
123+
link.setAttribute('download', `study-results-${jsonData.id}.json`);
124+
document.body.appendChild(link);
125+
link.click();
126+
document.body.removeChild(link);
127+
};
128+
129+
// Helper function to determine text task correctness
130+
const determineTextCorrectness = (answer) => {
131+
// Define correct answers for text comprehension
132+
const correctAnswers = {
133+
'Adventure and heroism': true,
134+
'Love and romance': false,
135+
'Science and technology': false,
136+
'Family relationships': false
137+
};
138+
return correctAnswers[answer] || false;
139+
};
140+
141+
// Helper function to determine video task correctness
142+
const determineVideoCorrectness = (answer) => {
143+
// Define correct answers for video comprehension
144+
const correctAnswers = {
145+
'Sports performance': true,
146+
'Educational content': false,
147+
'Entertainment': false,
148+
'Documentary footage': false
149+
};
150+
return correctAnswers[answer] || false;
151+
};
152+
153+
// Helper function to calculate overall face task correctness
154+
const calculateOverallFaceCorrectness = (results) => {
155+
if (!results || results.length === 0) return false;
156+
const correctCount = results.filter(result => result.correct === 'Yes').length;
157+
const threshold = Math.ceil(results.length * 0.6); // 60% threshold
158+
return correctCount >= threshold;
47159
};
48160

49161
const renderCurrentTask = () => {
50162
if (currentTask >= taskFiles.length) {
51-
return <div>All tasks completed!</div>;
163+
return (
164+
<div style={styles.completionContainer}>
165+
<h2>All tasks completed!</h2>
166+
<button onClick={downloadAllData} style={styles.downloadButton}>
167+
Download Results
168+
</button>
169+
</div>
170+
);
52171
}
53172

54173
const taskName = taskFiles[currentTask];
55174

56175
switch (taskName) {
57176
case 'ConsentForm':
58-
return <ConsentForm />;
177+
return <ConsentForm onSubmit={(data) => handleTaskComplete('consent', data)} />;
59178
case 'TextTask':
60179
return <TextTask />;
180+
case 'TextSurvey':
181+
return <TextSurvey onSubmit={(data) => handleTaskComplete('textTask', data)} />;
61182
case 'VideoTask':
62183
return <VideoTask />;
184+
case 'VideoSurvey':
185+
return <VideoSurvey onSubmit={(data) => handleTaskComplete('videoTask', data)} />;
63186
case 'FaceTask':
64-
return <FaceTask />;
187+
return <FaceTask onSubmit={(data) => handleTaskComplete('faceTask', data)} />;
65188
default:
66189
return <div>Unknown task</div>;
67190
}
68191
};
69192

70193
const isTaskComplete = currentTask >= taskFiles.length;
194+
const currentTaskName = taskFiles[currentTask];
195+
196+
// Show Next Task button only for tasks that don't have their own Continue button
197+
const showNextButton = !isTaskComplete &&
198+
!['ConsentForm', 'TextSurvey', 'VideoSurvey', 'FaceTask'].includes(currentTaskName);
71199

72200
return (
73-
<div id="app">
74-
<div className="task-container">
201+
<div id="app" style={styles.appContainer}>
202+
<div className="task-container" style={styles.taskContainer}>
75203
{renderCurrentTask()}
76204
</div>
77-
<div className="button-container">
78-
{!isTaskComplete && buttonVisible && (
205+
{showNextButton && (
206+
<div className="button-container" style={styles.buttonContainer}>
79207
<button
80208
id="nextTaskButton"
81209
className="button"
210+
style={styles.nextButton}
82211
onClick={incrementTask}
83212
>
84213
Next Task
85214
</button>
86-
)}
87-
</div>
215+
</div>
216+
)}
88217
</div>
89218
);
90219
}
91220

221+
const styles = {
222+
appContainer: {
223+
height: '100vh',
224+
width: '100vw',
225+
display: 'flex',
226+
flexDirection: 'column',
227+
position: 'relative',
228+
},
229+
taskContainer: {
230+
flex: 1,
231+
overflow: 'hidden',
232+
},
233+
buttonContainer: {
234+
position: 'fixed',
235+
bottom: '30px',
236+
left: '50%',
237+
transform: 'translateX(-50%)',
238+
zIndex: 1000,
239+
},
240+
nextButton: {
241+
fontSize: '18px',
242+
backgroundColor: '#3498db',
243+
color: 'white',
244+
border: 'none',
245+
borderRadius: '5px',
246+
marginTop: '20px',
247+
minWidth: '200px',
248+
boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
249+
transition: 'all 0.3s ease',
250+
},
251+
completionContainer: {
252+
display: 'flex',
253+
flexDirection: 'column',
254+
alignItems: 'center',
255+
justifyContent: 'center',
256+
height: '100vh',
257+
gap: '20px',
258+
},
259+
downloadButton: {
260+
padding: '15px 30px',
261+
fontSize: '18px',
262+
backgroundColor: '#27ae60',
263+
color: 'white',
264+
border: 'none',
265+
borderRadius: '5px',
266+
cursor: 'pointer',
267+
},
268+
};
269+
92270
export default App;

0 commit comments

Comments
 (0)