Skip to content

Commit 114ad06

Browse files
committed
Code task
1 parent e6075f7 commit 114ad06

2 files changed

Lines changed: 586 additions & 16 deletions

File tree

itrace-webcam/index.html

Lines changed: 197 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,21 @@
234234
#doc-content li {
235235
margin-bottom: .4rem;
236236
}
237+
238+
#code-content pre {
239+
margin: 0;
240+
background: transparent;
241+
}
242+
243+
#code-content [data-stim-id] {
244+
border-left: 2px solid transparent;
245+
padding-left: .5rem;
246+
transition: border-color .15s;
247+
}
248+
249+
#code-content [data-stim-id]:hover {
250+
border-left-color: var(--border);
251+
}
237252
</style>
238253
</head>
239254

@@ -296,6 +311,8 @@
296311
let _selectedStimId = null;
297312
let _docResponse = '';
298313
let _diagramResponse = '';
314+
let _codeResponse = '';
315+
299316
function selectStim(el, id) {
300317
document.querySelectorAll('.stim-clickable').forEach(c => c.classList.remove('selected'));
301318
el.classList.add('selected');
@@ -434,7 +451,9 @@
434451
return {
435452
type: jsPsychHtmlButtonResponse,
436453
stimulus: `
437-
<div style="padding-top:10rem;">
454+
<div style="padding-top:3rem;">
455+
<p style="font-size:1rem; color:var(--text-muted); margin-bottom:1rem;">Using the following diagram, answer the prompt below.</p>
456+
438457
<div style="position:relative; width:100%; margin-bottom:1.5rem;">
439458
<img
440459
id="diag-img"
@@ -530,6 +549,171 @@
530549
}
531550

532551

552+
function codeTrial({ codeSrc, question, data = {} }) {
553+
let responseText = '';
554+
555+
return {
556+
type: jsPsychHtmlButtonResponse,
557+
stimulus: `
558+
<div style="padding-top:3rem;">
559+
<p style="font-size:1rem; color:var(--text-muted); margin-bottom:1rem;">
560+
Read the following source code, then answer the question below.
561+
</p>
562+
<div id="code-content" style="
563+
background:var(--surface);
564+
border:1px solid var(--border);
565+
border-radius:var(--radius);
566+
box-shadow:var(--shadow);
567+
padding:1.5rem 2rem;
568+
margin-bottom:1.5rem;
569+
font-size:.9rem;
570+
text-align:left;
571+
"></div>
572+
<p class="trial-question">${question}</p>
573+
<textarea
574+
id="code-response"
575+
rows="5"
576+
style="
577+
width:100%; padding:.75rem 1rem; font-size:.95rem;
578+
font-family:inherit; border:1px solid var(--border);
579+
border-radius:var(--radius); resize:vertical; margin-bottom:1rem;
580+
"
581+
placeholder="Type your response here…"
582+
></textarea>
583+
</div>
584+
`,
585+
choices: ['Submit'],
586+
data: { trial_type_custom: 'code', ...data },
587+
on_load() {
588+
fetch(codeSrc)
589+
.then(r => r.text())
590+
.then(src => {
591+
const blocks = parseJavaBlocks(src);
592+
const container = document.getElementById('code-content');
593+
container.innerHTML = blocks.map(b => `
594+
<div data-stim-id="${b.id}" style="margin-bottom:.25rem;">
595+
<pre style="margin:0;"><code class="language-java">${escapeHtml(b.code)}</code></pre>
596+
</div>
597+
`).join('');
598+
container.querySelectorAll('code').forEach(el => hljs.highlightElement(el));
599+
});
600+
const ta = document.getElementById('code-response');
601+
ta.addEventListener('input', () => { _codeResponse = ta.value; });
602+
},
603+
on_finish(trialData) {
604+
trialData.free_response = _codeResponse;
605+
snapshotStimRects(jsPsych);
606+
},
607+
};
608+
}
609+
610+
function escapeHtml(str) {
611+
return str
612+
.replace(/&/g, '&amp;')
613+
.replace(/</g, '&lt;')
614+
.replace(/>/g, '&gt;');
615+
}
616+
617+
function parseJavaBlocks(src) {
618+
const lines = src.split('\n');
619+
const blocks = [];
620+
let i = 0;
621+
622+
// ── Block 1: everything up to the first method/constructor ──────────
623+
// Collect imports, class declaration, and field declarations
624+
const headerLines = [];
625+
while (i < lines.length) {
626+
const trimmed = lines[i].trim();
627+
// Stop when we hit a Javadoc comment or a method-looking line
628+
if (trimmed.startsWith('/**') || isMethodOrConstructorStart(lines, i)) break;
629+
headerLines.push(lines[i]);
630+
i++;
631+
}
632+
if (headerLines.some(l => l.trim())) {
633+
blocks.push({ id: 'code-header', code: headerLines.join('\n').trim() });
634+
}
635+
636+
// ── Remaining blocks: Javadoc + method pairs ─────────────────────────
637+
while (i < lines.length) {
638+
const trimmed = lines[i].trim();
639+
640+
// Javadoc comment block
641+
if (trimmed.startsWith('/**')) {
642+
const commentLines = [];
643+
while (i < lines.length) {
644+
commentLines.push(lines[i]);
645+
if (lines[i].trim().endsWith('*/')) { i++; break; }
646+
i++;
647+
}
648+
// Peek ahead to get method name for the ID
649+
const methodName = peekMethodName(lines, i);
650+
blocks.push({
651+
id: `code-comment-${methodName}`,
652+
code: commentLines.join('\n').trim(),
653+
});
654+
continue;
655+
}
656+
657+
// Method or constructor body
658+
if (isMethodOrConstructorStart(lines, i)) {
659+
const methodName = extractMethodName(lines[i]);
660+
const methodLines = [];
661+
let braceDepth = 0;
662+
let started = false;
663+
while (i < lines.length) {
664+
const line = lines[i];
665+
methodLines.push(line);
666+
for (const ch of line) {
667+
if (ch === '{') { braceDepth++; started = true; }
668+
if (ch === '}') braceDepth--;
669+
}
670+
i++;
671+
if (started && braceDepth === 0) break;
672+
}
673+
blocks.push({
674+
id: `code-method-${methodName}`,
675+
code: methodLines.join('\n').trim(),
676+
});
677+
continue;
678+
}
679+
680+
// Skip blank lines between blocks
681+
i++;
682+
}
683+
684+
return blocks;
685+
}
686+
687+
function isMethodOrConstructorStart(lines, i) {
688+
const line = lines[i].trim();
689+
if (!line) return false;
690+
// Annotations like @Override don't count on their own
691+
if (line.startsWith('@')) return false;
692+
// Must contain ( and either end with { or have { somewhere
693+
// Look ahead a few lines for the opening brace
694+
const chunk = lines.slice(i, i + 4).join(' ');
695+
return /[\w<>\[\]]+\s+\w+\s*\(/.test(chunk) && chunk.includes('{');
696+
}
697+
698+
function extractMethodName(line) {
699+
// Match: [modifiers] [returnType] methodName(
700+
const match = line.match(/(?:public|private|protected|static|final|override|\s)*[\w<>\[\]]+\s+(\w+)\s*\(/);
701+
if (match) return match[1];
702+
// Constructor fallback
703+
const ctorMatch = line.match(/(?:public|private|protected)?\s+(\w+)\s*\(/);
704+
if (ctorMatch) return ctorMatch[1];
705+
return 'unknown';
706+
}
707+
708+
function peekMethodName(lines, i) {
709+
for (let j = i; j < Math.min(i + 5, lines.length); j++) {
710+
const name = extractMethodName(lines[j]);
711+
if (name !== 'unknown') return name;
712+
}
713+
return 'unknown';
714+
}
715+
716+
533717
// ═══════════════════════════════════════════════════════════════
534718
// TIMELINE ← Edit everything below here
535719
// ═══════════════════════════════════════════════════════════════
@@ -548,29 +732,17 @@
548732
stimulus: `
549733
<div class="instructions-body stim-card">
550734
<h2>Welcome</h2>
551-
<p>Thank you for participating in this study. You will be shown a series of stimuli and asked to make judgments about them.</p>
552-
<p>Your responses will be recorded and downloaded at the end of the session. The study takes approximately <strong>X minutes</strong>.</p>
735+
<p>Thank you for participating in this study. You will be shown a series of software engineering related stimuli and asked questions about them.</p>
736+
<p>Your responses will be recorded and downloaded at the end of the session. Your eyes will be tracked during this study.</p>
553737
<p>Click <strong>Begin</strong> when you are ready.</p>
554738
</div>
555739
`,
556740
choices: ['Begin'],
557741
});
558742

559743
// ── 2. Trials ──────────────────────────────────────────────────
560-
// Example A: single text stimulus with button response
561-
timeline.push(singleButtonTrial({
562-
stim: {
563-
id: 'trial1-text',
564-
type: 'text',
565-
content: 'This is an example text stimulus. Replace with your actual content.',
566-
label: 'Passage',
567-
},
568-
question: 'How difficult was this text to understand?',
569-
buttons: ['Very easy', 'Easy', 'Difficult', 'Very difficult'],
570-
data: { condition: 'text-example' },
571-
}));
572744

573-
// DIAGRAM (Working)
745+
// DIAGRAM
574746
timeline.push(diagramTrial({
575747
src: './tasks/diagram/class-example-library-domain.png',
576748
aois: DIAGRAM_AOIS,
@@ -585,6 +757,15 @@ <h2>Welcome</h2>
585757
data: { condition: 'doc-chromium-profile' },
586758
}));
587759

760+
761+
// CODE SUMMARY
762+
timeline.push(codeTrial({
763+
codeSrc: './tasks/code/CircularFifoQueue.java',
764+
question: 'In your own words, describe what the <code>add()</code> method does when the queue is already at full capacity. Be specific about what happens to existing elements and how the internal state changes.',
765+
data: { condition: 'code-circular-fifo' },
766+
}));
767+
768+
588769
// Example C: click-to-select between two images
589770
timeline.push(clickSelectTrial({
590771
stims: [

0 commit comments

Comments
 (0)