jspsych-tangram 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/construct/index.browser.js +4538 -3889
- package/dist/construct/index.browser.js.map +1 -1
- package/dist/construct/index.browser.min.js +13 -13
- package/dist/construct/index.browser.min.js.map +1 -1
- package/dist/construct/index.cjs +4 -7
- package/dist/construct/index.cjs.map +1 -1
- package/dist/construct/index.js +4 -7
- package/dist/construct/index.js.map +1 -1
- package/dist/index.cjs +332 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +180 -8
- package/dist/index.js +333 -13
- package/dist/index.js.map +1 -1
- package/dist/nback/index.browser.js +17703 -0
- package/dist/nback/index.browser.js.map +1 -0
- package/dist/nback/index.browser.min.js +42 -0
- package/dist/nback/index.browser.min.js.map +1 -0
- package/dist/nback/index.cjs +395 -0
- package/dist/nback/index.cjs.map +1 -0
- package/dist/nback/index.d.ts +175 -0
- package/dist/nback/index.js +393 -0
- package/dist/nback/index.js.map +1 -0
- package/dist/prep/index.browser.js +4538 -3891
- package/dist/prep/index.browser.js.map +1 -1
- package/dist/prep/index.browser.min.js +13 -13
- package/dist/prep/index.browser.min.js.map +1 -1
- package/dist/prep/index.cjs +5 -10
- package/dist/prep/index.cjs.map +1 -1
- package/dist/prep/index.js +5 -10
- package/dist/prep/index.js.map +1 -1
- package/package.json +9 -3
- package/src/index.ts +2 -1
- package/src/plugins/tangram-nback/NBackApp.tsx +316 -0
- package/src/plugins/tangram-nback/index.ts +141 -0
- package/tangram-construct.min.js +13 -13
- package/tangram-nback.min.js +42 -0
- package/tangram-prep.min.js +13 -13
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { JsPsychPlugin, ParameterType, JsPsych, TrialType } from 'jspsych';
|
|
2
2
|
|
|
3
|
-
declare const info$
|
|
3
|
+
declare const info$2: {
|
|
4
4
|
name: string;
|
|
5
5
|
version: string;
|
|
6
6
|
parameters: {
|
|
@@ -109,7 +109,7 @@ declare const info$1: {
|
|
|
109
109
|
};
|
|
110
110
|
citations: string;
|
|
111
111
|
};
|
|
112
|
-
type Info$
|
|
112
|
+
type Info$2 = typeof info$2;
|
|
113
113
|
/**
|
|
114
114
|
* **tangram-construct**
|
|
115
115
|
*
|
|
@@ -118,7 +118,7 @@ type Info$1 = typeof info$1;
|
|
|
118
118
|
* @author Justin Yang & Sean Paul Anderson
|
|
119
119
|
* @see {@link https://github.com/cogtoolslab/tangram_construction.git/tree/main/experiments/jspsych-tangram-prep}
|
|
120
120
|
*/
|
|
121
|
-
declare class TangramConstructPlugin implements JsPsychPlugin<Info$
|
|
121
|
+
declare class TangramConstructPlugin implements JsPsychPlugin<Info$2> {
|
|
122
122
|
private jsPsych;
|
|
123
123
|
static info: {
|
|
124
124
|
name: string;
|
|
@@ -234,10 +234,10 @@ declare class TangramConstructPlugin implements JsPsychPlugin<Info$1> {
|
|
|
234
234
|
* Launches the trial by invoking startConstructionTrial
|
|
235
235
|
* with the display element, parameters, and jsPsych instance.
|
|
236
236
|
*/
|
|
237
|
-
trial(display_element: HTMLElement, trial: TrialType<Info$
|
|
237
|
+
trial(display_element: HTMLElement, trial: TrialType<Info$2>): void;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
declare const info: {
|
|
240
|
+
declare const info$1: {
|
|
241
241
|
name: string;
|
|
242
242
|
version: string;
|
|
243
243
|
parameters: {
|
|
@@ -308,7 +308,7 @@ declare const info: {
|
|
|
308
308
|
};
|
|
309
309
|
citations: string;
|
|
310
310
|
};
|
|
311
|
-
type Info = typeof info;
|
|
311
|
+
type Info$1 = typeof info$1;
|
|
312
312
|
/**
|
|
313
313
|
* **tangram-prep**
|
|
314
314
|
*
|
|
@@ -316,7 +316,7 @@ type Info = typeof info;
|
|
|
316
316
|
*
|
|
317
317
|
* @author Justin Yang & Sean Paul Anderson
|
|
318
318
|
*/
|
|
319
|
-
declare class TangramPrepPlugin implements JsPsychPlugin<Info> {
|
|
319
|
+
declare class TangramPrepPlugin implements JsPsychPlugin<Info$1> {
|
|
320
320
|
private jsPsych;
|
|
321
321
|
static info: {
|
|
322
322
|
name: string;
|
|
@@ -394,7 +394,179 @@ declare class TangramPrepPlugin implements JsPsychPlugin<Info> {
|
|
|
394
394
|
* Launches the trial by invoking startPrepTrial
|
|
395
395
|
* with the display element, parameters, and jsPsych instance.
|
|
396
396
|
*/
|
|
397
|
+
trial(display_element: HTMLElement, trial: TrialType<Info$1>): void;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
declare const info: {
|
|
401
|
+
name: string;
|
|
402
|
+
version: string;
|
|
403
|
+
parameters: {
|
|
404
|
+
/** Single tangram specification to display */
|
|
405
|
+
tangram: {
|
|
406
|
+
type: ParameterType;
|
|
407
|
+
default: undefined;
|
|
408
|
+
description: string;
|
|
409
|
+
};
|
|
410
|
+
/** Whether this trial is a match (for computing accuracy) */
|
|
411
|
+
isMatch: {
|
|
412
|
+
type: ParameterType;
|
|
413
|
+
default: undefined;
|
|
414
|
+
description: string;
|
|
415
|
+
};
|
|
416
|
+
/** Whether to show tangram decomposed into individual primitives with borders */
|
|
417
|
+
show_tangram_decomposition: {
|
|
418
|
+
type: ParameterType;
|
|
419
|
+
default: boolean;
|
|
420
|
+
description: string;
|
|
421
|
+
};
|
|
422
|
+
/** HTML content to display above the tangram as instructions */
|
|
423
|
+
instructions: {
|
|
424
|
+
type: ParameterType;
|
|
425
|
+
default: string;
|
|
426
|
+
description: string;
|
|
427
|
+
};
|
|
428
|
+
/** Text to display on response button */
|
|
429
|
+
button_text: {
|
|
430
|
+
type: ParameterType;
|
|
431
|
+
default: string;
|
|
432
|
+
description: string;
|
|
433
|
+
};
|
|
434
|
+
/** Duration to display tangram and accept responses (milliseconds) */
|
|
435
|
+
duration: {
|
|
436
|
+
type: ParameterType;
|
|
437
|
+
default: number;
|
|
438
|
+
description: string;
|
|
439
|
+
};
|
|
440
|
+
/** Callback fired when trial ends */
|
|
441
|
+
onTrialEnd: {
|
|
442
|
+
type: ParameterType;
|
|
443
|
+
default: undefined;
|
|
444
|
+
description: string;
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
data: {
|
|
448
|
+
/** Whether participant clicked the response button before duration expired */
|
|
449
|
+
responded_match: {
|
|
450
|
+
type: ParameterType;
|
|
451
|
+
description: string;
|
|
452
|
+
};
|
|
453
|
+
/** Reaction time in milliseconds (NaN if no response or response after duration) */
|
|
454
|
+
rt: {
|
|
455
|
+
type: ParameterType;
|
|
456
|
+
description: string;
|
|
457
|
+
};
|
|
458
|
+
/** Accuracy: 1 if correct, 0 if incorrect, NaN if isMatch not provided */
|
|
459
|
+
accuracy: {
|
|
460
|
+
type: ParameterType;
|
|
461
|
+
description: string;
|
|
462
|
+
};
|
|
463
|
+
/** Whether response occurred after duration expired */
|
|
464
|
+
responded_after_duration: {
|
|
465
|
+
type: ParameterType;
|
|
466
|
+
description: string;
|
|
467
|
+
};
|
|
468
|
+
/** Time of late response (NaN if no late response) */
|
|
469
|
+
rt_after_duration: {
|
|
470
|
+
type: ParameterType;
|
|
471
|
+
description: string;
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
citations: string;
|
|
475
|
+
};
|
|
476
|
+
type Info = typeof info;
|
|
477
|
+
/**
|
|
478
|
+
* **tangram-nback**
|
|
479
|
+
*
|
|
480
|
+
* A jsPsych plugin for n-back matching trials displaying a single tangram
|
|
481
|
+
* with a response button.
|
|
482
|
+
*
|
|
483
|
+
* @author Justin Yang & Sean Paul Anderson
|
|
484
|
+
* @see {@link https://github.com/cogtoolslab/tangram_construction.git/tree/main/experiments/jspsych-tangram-prep}
|
|
485
|
+
*/
|
|
486
|
+
declare class TangramNBackPlugin implements JsPsychPlugin<Info> {
|
|
487
|
+
private jsPsych;
|
|
488
|
+
static info: {
|
|
489
|
+
name: string;
|
|
490
|
+
version: string;
|
|
491
|
+
parameters: {
|
|
492
|
+
/** Single tangram specification to display */
|
|
493
|
+
tangram: {
|
|
494
|
+
type: ParameterType;
|
|
495
|
+
default: undefined;
|
|
496
|
+
description: string;
|
|
497
|
+
};
|
|
498
|
+
/** Whether this trial is a match (for computing accuracy) */
|
|
499
|
+
isMatch: {
|
|
500
|
+
type: ParameterType;
|
|
501
|
+
default: undefined;
|
|
502
|
+
description: string;
|
|
503
|
+
};
|
|
504
|
+
/** Whether to show tangram decomposed into individual primitives with borders */
|
|
505
|
+
show_tangram_decomposition: {
|
|
506
|
+
type: ParameterType;
|
|
507
|
+
default: boolean;
|
|
508
|
+
description: string;
|
|
509
|
+
};
|
|
510
|
+
/** HTML content to display above the tangram as instructions */
|
|
511
|
+
instructions: {
|
|
512
|
+
type: ParameterType;
|
|
513
|
+
default: string;
|
|
514
|
+
description: string;
|
|
515
|
+
};
|
|
516
|
+
/** Text to display on response button */
|
|
517
|
+
button_text: {
|
|
518
|
+
type: ParameterType;
|
|
519
|
+
default: string;
|
|
520
|
+
description: string;
|
|
521
|
+
};
|
|
522
|
+
/** Duration to display tangram and accept responses (milliseconds) */
|
|
523
|
+
duration: {
|
|
524
|
+
type: ParameterType;
|
|
525
|
+
default: number;
|
|
526
|
+
description: string;
|
|
527
|
+
};
|
|
528
|
+
/** Callback fired when trial ends */
|
|
529
|
+
onTrialEnd: {
|
|
530
|
+
type: ParameterType;
|
|
531
|
+
default: undefined;
|
|
532
|
+
description: string;
|
|
533
|
+
};
|
|
534
|
+
};
|
|
535
|
+
data: {
|
|
536
|
+
/** Whether participant clicked the response button before duration expired */
|
|
537
|
+
responded_match: {
|
|
538
|
+
type: ParameterType;
|
|
539
|
+
description: string;
|
|
540
|
+
};
|
|
541
|
+
/** Reaction time in milliseconds (NaN if no response or response after duration) */
|
|
542
|
+
rt: {
|
|
543
|
+
type: ParameterType;
|
|
544
|
+
description: string;
|
|
545
|
+
};
|
|
546
|
+
/** Accuracy: 1 if correct, 0 if incorrect, NaN if isMatch not provided */
|
|
547
|
+
accuracy: {
|
|
548
|
+
type: ParameterType;
|
|
549
|
+
description: string;
|
|
550
|
+
};
|
|
551
|
+
/** Whether response occurred after duration expired */
|
|
552
|
+
responded_after_duration: {
|
|
553
|
+
type: ParameterType;
|
|
554
|
+
description: string;
|
|
555
|
+
};
|
|
556
|
+
/** Time of late response (NaN if no late response) */
|
|
557
|
+
rt_after_duration: {
|
|
558
|
+
type: ParameterType;
|
|
559
|
+
description: string;
|
|
560
|
+
};
|
|
561
|
+
};
|
|
562
|
+
citations: string;
|
|
563
|
+
};
|
|
564
|
+
constructor(jsPsych: JsPsych);
|
|
565
|
+
/**
|
|
566
|
+
* Launches the trial by invoking startNBackTrial
|
|
567
|
+
* with the display element, parameters, and jsPsych instance.
|
|
568
|
+
*/
|
|
397
569
|
trial(display_element: HTMLElement, trial: TrialType<Info>): void;
|
|
398
570
|
}
|
|
399
571
|
|
|
400
|
-
export { TangramConstructPlugin, TangramPrepPlugin };
|
|
572
|
+
export { TangramConstructPlugin, TangramNBackPlugin, TangramPrepPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ParameterType } from 'jspsych';
|
|
2
|
-
import React from 'react';
|
|
2
|
+
import React, { useRef, useState, useEffect } from 'react';
|
|
3
3
|
import { createRoot } from 'react-dom/client';
|
|
4
4
|
import { v4 } from 'uuid';
|
|
5
5
|
|
|
@@ -12,9 +12,8 @@ const CONFIG = {
|
|
|
12
12
|
completion: { fill: "#ccfff2", stroke: "#13da57" },
|
|
13
13
|
silhouetteMask: "#374151",
|
|
14
14
|
anchors: { invalid: "#7dd3fc", valid: "#475569" },
|
|
15
|
-
piece: { draggingFill: "#8e7cc3ff", validFill: "#8e7cc3ff", invalidFill: "#ef4444",
|
|
16
|
-
|
|
17
|
-
blueprint: { fill: "#374151", selectedStroke: "#111827", badgeFill: "#000000", labelFill: "#ffffff" },
|
|
15
|
+
piece: { draggingFill: "#8e7cc3ff", validFill: "#8e7cc3ff", invalidFill: "#ef4444", allGreenStroke: "#86efac"},
|
|
16
|
+
blueprint: { fill: "#374151", badgeFill: "#000000", labelFill: "#ffffff" },
|
|
18
17
|
tangramDecomposition: { stroke: "#fef2cc" }
|
|
19
18
|
},
|
|
20
19
|
opacity: {
|
|
@@ -25,7 +24,7 @@ const CONFIG = {
|
|
|
25
24
|
piece: { invalid: 0.35, dragging: 0.75, locked: 1, normal: 1 }
|
|
26
25
|
},
|
|
27
26
|
size: {
|
|
28
|
-
stroke: { bandPx: 5,
|
|
27
|
+
stroke: { bandPx: 5, allGreenStrokePx: 10, tangramDecompositionPx: 1 },
|
|
29
28
|
anchorRadiusPx: { valid: 1, invalid: 1 },
|
|
30
29
|
badgeFontPx: 16,
|
|
31
30
|
centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 }
|
|
@@ -45,9 +44,7 @@ const CONFIG = {
|
|
|
45
44
|
},
|
|
46
45
|
game: {
|
|
47
46
|
snapRadiusPx: 15,
|
|
48
|
-
showBorders: false
|
|
49
|
-
hideTouchingBorders: true
|
|
50
|
-
}
|
|
47
|
+
showBorders: false}
|
|
51
48
|
};
|
|
52
49
|
|
|
53
50
|
function isComposite(bp) {
|
|
@@ -3561,7 +3558,7 @@ function startConstructionTrial(display_element, params, _jsPsych) {
|
|
|
3561
3558
|
return { root, display_element, jsPsych: _jsPsych };
|
|
3562
3559
|
}
|
|
3563
3560
|
|
|
3564
|
-
const info$
|
|
3561
|
+
const info$2 = {
|
|
3565
3562
|
name: "tangram-construct",
|
|
3566
3563
|
version: "1.0.0",
|
|
3567
3564
|
parameters: {
|
|
@@ -3675,7 +3672,7 @@ class TangramConstructPlugin {
|
|
|
3675
3672
|
this.jsPsych = jsPsych;
|
|
3676
3673
|
}
|
|
3677
3674
|
static {
|
|
3678
|
-
this.info = info$
|
|
3675
|
+
this.info = info$2;
|
|
3679
3676
|
}
|
|
3680
3677
|
/**
|
|
3681
3678
|
* Launches the trial by invoking startConstructionTrial
|
|
@@ -3819,7 +3816,7 @@ function startPrepTrial(display_element, params, jsPsych) {
|
|
|
3819
3816
|
return { root, display_element, jsPsych };
|
|
3820
3817
|
}
|
|
3821
3818
|
|
|
3822
|
-
const info = {
|
|
3819
|
+
const info$1 = {
|
|
3823
3820
|
name: "tangram-prep",
|
|
3824
3821
|
version: "1.0.0",
|
|
3825
3822
|
parameters: {
|
|
@@ -3895,7 +3892,7 @@ class TangramPrepPlugin {
|
|
|
3895
3892
|
this.jsPsych = jsPsych;
|
|
3896
3893
|
}
|
|
3897
3894
|
static {
|
|
3898
|
-
this.info = info;
|
|
3895
|
+
this.info = info$1;
|
|
3899
3896
|
}
|
|
3900
3897
|
/**
|
|
3901
3898
|
* Launches the trial by invoking startPrepTrial
|
|
@@ -3931,5 +3928,328 @@ class TangramPrepPlugin {
|
|
|
3931
3928
|
}
|
|
3932
3929
|
}
|
|
3933
3930
|
|
|
3934
|
-
|
|
3931
|
+
function startNBackTrial(display_element, params, _jsPsych) {
|
|
3932
|
+
const root = createRoot(display_element);
|
|
3933
|
+
root.render(React.createElement(NBackView, { params }));
|
|
3934
|
+
return { root, display_element, jsPsych: _jsPsych };
|
|
3935
|
+
}
|
|
3936
|
+
function NBackView({ params }) {
|
|
3937
|
+
const {
|
|
3938
|
+
tangram,
|
|
3939
|
+
isMatch,
|
|
3940
|
+
show_tangram_decomposition,
|
|
3941
|
+
instructions,
|
|
3942
|
+
button_text,
|
|
3943
|
+
duration,
|
|
3944
|
+
onTrialEnd
|
|
3945
|
+
} = params;
|
|
3946
|
+
const trialStartTime = useRef(Date.now());
|
|
3947
|
+
const buttonEnabledRef = useRef(true);
|
|
3948
|
+
const timeoutIdRef = useRef(null);
|
|
3949
|
+
const hasRespondedRef = useRef(false);
|
|
3950
|
+
const responseDataRef = useRef(null);
|
|
3951
|
+
const [buttonDisabled, setButtonDisabled] = useState(false);
|
|
3952
|
+
const CANON = /* @__PURE__ */ new Set([
|
|
3953
|
+
"square",
|
|
3954
|
+
"smalltriangle",
|
|
3955
|
+
"parallelogram",
|
|
3956
|
+
"medtriangle",
|
|
3957
|
+
"largetriangle"
|
|
3958
|
+
]);
|
|
3959
|
+
const filteredTans = tangram.solutionTans.filter((tan) => {
|
|
3960
|
+
const tanName = tan.name ?? tan.kind;
|
|
3961
|
+
return CANON.has(tanName);
|
|
3962
|
+
});
|
|
3963
|
+
const mask = filteredTans.map((tan) => {
|
|
3964
|
+
const polygon = tan.vertices.map(([x, y]) => ({ x: x ?? 0, y: -(y ?? 0) }));
|
|
3965
|
+
return polygon;
|
|
3966
|
+
});
|
|
3967
|
+
const primitiveDecomposition = filteredTans.map((tan) => ({
|
|
3968
|
+
kind: tan.name ?? tan.kind,
|
|
3969
|
+
polygon: tan.vertices.map(([x, y]) => ({ x: x ?? 0, y: -(y ?? 0) }))
|
|
3970
|
+
}));
|
|
3971
|
+
const DISPLAY_SIZE = 400;
|
|
3972
|
+
const viewport = {
|
|
3973
|
+
w: DISPLAY_SIZE,
|
|
3974
|
+
h: DISPLAY_SIZE
|
|
3975
|
+
};
|
|
3976
|
+
const scaleS = React.useMemo(() => {
|
|
3977
|
+
const u = inferUnitFromPolys$1(mask);
|
|
3978
|
+
return u ? CONFIG.layout.grid.unitPx / u : 1;
|
|
3979
|
+
}, [mask]);
|
|
3980
|
+
const centerPos = {
|
|
3981
|
+
cx: viewport.w / 2,
|
|
3982
|
+
cy: viewport.h / 2
|
|
3983
|
+
};
|
|
3984
|
+
const pathD = (poly) => {
|
|
3985
|
+
if (!poly || poly.length === 0) return "";
|
|
3986
|
+
const moves = poly.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`);
|
|
3987
|
+
return moves.join(" ") + " Z";
|
|
3988
|
+
};
|
|
3989
|
+
const endTrial = (data) => {
|
|
3990
|
+
if (timeoutIdRef.current) {
|
|
3991
|
+
clearTimeout(timeoutIdRef.current);
|
|
3992
|
+
timeoutIdRef.current = null;
|
|
3993
|
+
}
|
|
3994
|
+
const accuracy = isMatch !== void 0 ? isMatch === data.responded_match ? 1 : 0 : NaN;
|
|
3995
|
+
const trialData = {
|
|
3996
|
+
...data,
|
|
3997
|
+
accuracy,
|
|
3998
|
+
tangram_id: tangram.tangramID,
|
|
3999
|
+
is_match: isMatch
|
|
4000
|
+
};
|
|
4001
|
+
if (onTrialEnd) {
|
|
4002
|
+
onTrialEnd(trialData);
|
|
4003
|
+
}
|
|
4004
|
+
};
|
|
4005
|
+
const handleButtonClick = () => {
|
|
4006
|
+
if (!buttonEnabledRef.current) {
|
|
4007
|
+
const rt_late = Date.now() - trialStartTime.current;
|
|
4008
|
+
hasRespondedRef.current = true;
|
|
4009
|
+
responseDataRef.current = {
|
|
4010
|
+
responded_match: true,
|
|
4011
|
+
rt: NaN,
|
|
4012
|
+
responded_after_duration: true,
|
|
4013
|
+
rt_after_duration: rt_late
|
|
4014
|
+
};
|
|
4015
|
+
endTrial(responseDataRef.current);
|
|
4016
|
+
} else {
|
|
4017
|
+
const rt = Date.now() - trialStartTime.current;
|
|
4018
|
+
buttonEnabledRef.current = false;
|
|
4019
|
+
setButtonDisabled(true);
|
|
4020
|
+
hasRespondedRef.current = true;
|
|
4021
|
+
responseDataRef.current = {
|
|
4022
|
+
responded_match: true,
|
|
4023
|
+
rt,
|
|
4024
|
+
responded_after_duration: false,
|
|
4025
|
+
rt_after_duration: NaN
|
|
4026
|
+
};
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
useEffect(() => {
|
|
4030
|
+
timeoutIdRef.current = setTimeout(() => {
|
|
4031
|
+
buttonEnabledRef.current = false;
|
|
4032
|
+
if (hasRespondedRef.current && responseDataRef.current) {
|
|
4033
|
+
endTrial(responseDataRef.current);
|
|
4034
|
+
} else {
|
|
4035
|
+
endTrial({
|
|
4036
|
+
responded_match: false,
|
|
4037
|
+
rt: NaN,
|
|
4038
|
+
responded_after_duration: false,
|
|
4039
|
+
rt_after_duration: NaN
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
}, duration);
|
|
4043
|
+
return () => {
|
|
4044
|
+
if (timeoutIdRef.current) {
|
|
4045
|
+
clearTimeout(timeoutIdRef.current);
|
|
4046
|
+
}
|
|
4047
|
+
};
|
|
4048
|
+
}, []);
|
|
4049
|
+
const renderSilhouette = () => {
|
|
4050
|
+
if (show_tangram_decomposition) {
|
|
4051
|
+
const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
|
|
4052
|
+
const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, centerPos);
|
|
4053
|
+
return /* @__PURE__ */ React.createElement("g", { key: "sil-decomposed", pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => /* @__PURE__ */ React.createElement(React.Fragment, { key: `prim-${i}` }, /* @__PURE__ */ React.createElement(
|
|
4054
|
+
"path",
|
|
4055
|
+
{
|
|
4056
|
+
d: pathD(scaledPoly),
|
|
4057
|
+
fill: CONFIG.color.silhouetteMask,
|
|
4058
|
+
opacity: CONFIG.opacity.silhouetteMask,
|
|
4059
|
+
stroke: "none"
|
|
4060
|
+
}
|
|
4061
|
+
), /* @__PURE__ */ React.createElement(
|
|
4062
|
+
"path",
|
|
4063
|
+
{
|
|
4064
|
+
d: pathD(scaledPoly),
|
|
4065
|
+
fill: "none",
|
|
4066
|
+
stroke: CONFIG.color.tangramDecomposition.stroke,
|
|
4067
|
+
strokeWidth: CONFIG.size.stroke.tangramDecompositionPx
|
|
4068
|
+
}
|
|
4069
|
+
))));
|
|
4070
|
+
} else {
|
|
4071
|
+
const placedPolys = placeSilhouetteGridAlignedAsPolys(mask, scaleS, centerPos);
|
|
4072
|
+
return /* @__PURE__ */ React.createElement("g", { key: "sil-unified", pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => /* @__PURE__ */ React.createElement(
|
|
4073
|
+
"path",
|
|
4074
|
+
{
|
|
4075
|
+
key: `sil-${i}`,
|
|
4076
|
+
d: pathD(scaledPoly),
|
|
4077
|
+
fill: CONFIG.color.silhouetteMask,
|
|
4078
|
+
opacity: CONFIG.opacity.silhouetteMask,
|
|
4079
|
+
stroke: "none"
|
|
4080
|
+
}
|
|
4081
|
+
)));
|
|
4082
|
+
}
|
|
4083
|
+
};
|
|
4084
|
+
return /* @__PURE__ */ React.createElement("div", { style: {
|
|
4085
|
+
display: "flex",
|
|
4086
|
+
flexDirection: "column",
|
|
4087
|
+
alignItems: "center",
|
|
4088
|
+
justifyContent: "flex-start",
|
|
4089
|
+
minHeight: "100vh",
|
|
4090
|
+
padding: "40px 20px",
|
|
4091
|
+
background: "#f5f5f5"
|
|
4092
|
+
} }, instructions && /* @__PURE__ */ React.createElement(
|
|
4093
|
+
"div",
|
|
4094
|
+
{
|
|
4095
|
+
style: {
|
|
4096
|
+
maxWidth: "800px",
|
|
4097
|
+
width: "100%",
|
|
4098
|
+
marginBottom: "30px",
|
|
4099
|
+
textAlign: "center",
|
|
4100
|
+
fontSize: "18px",
|
|
4101
|
+
lineHeight: "1.5"
|
|
4102
|
+
},
|
|
4103
|
+
dangerouslySetInnerHTML: { __html: instructions }
|
|
4104
|
+
}
|
|
4105
|
+
), /* @__PURE__ */ React.createElement("div", { style: {
|
|
4106
|
+
display: "flex",
|
|
4107
|
+
flexDirection: "column",
|
|
4108
|
+
alignItems: "center",
|
|
4109
|
+
gap: "30px"
|
|
4110
|
+
} }, /* @__PURE__ */ React.createElement(
|
|
4111
|
+
"svg",
|
|
4112
|
+
{
|
|
4113
|
+
width: viewport.w,
|
|
4114
|
+
height: viewport.h,
|
|
4115
|
+
viewBox: `0 0 ${viewport.w} ${viewport.h}`,
|
|
4116
|
+
style: {
|
|
4117
|
+
display: "block",
|
|
4118
|
+
background: CONFIG.color.bands.silhouette.fillEven,
|
|
4119
|
+
border: `${CONFIG.size.stroke.bandPx}px solid ${CONFIG.color.bands.silhouette.stroke}`,
|
|
4120
|
+
borderRadius: "8px"
|
|
4121
|
+
}
|
|
4122
|
+
},
|
|
4123
|
+
renderSilhouette()
|
|
4124
|
+
), /* @__PURE__ */ React.createElement(
|
|
4125
|
+
"button",
|
|
4126
|
+
{
|
|
4127
|
+
className: "jspsych-btn",
|
|
4128
|
+
onClick: handleButtonClick,
|
|
4129
|
+
disabled: buttonDisabled,
|
|
4130
|
+
style: {
|
|
4131
|
+
padding: "12px 30px",
|
|
4132
|
+
fontSize: "16px",
|
|
4133
|
+
cursor: buttonDisabled ? "not-allowed" : "pointer",
|
|
4134
|
+
opacity: buttonDisabled ? 0.5 : 1
|
|
4135
|
+
}
|
|
4136
|
+
},
|
|
4137
|
+
button_text
|
|
4138
|
+
)));
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4141
|
+
const info = {
|
|
4142
|
+
name: "tangram-nback",
|
|
4143
|
+
version: "1.0.0",
|
|
4144
|
+
parameters: {
|
|
4145
|
+
/** Single tangram specification to display */
|
|
4146
|
+
tangram: {
|
|
4147
|
+
type: ParameterType.COMPLEX,
|
|
4148
|
+
default: void 0,
|
|
4149
|
+
description: "TangramSpec object defining target shape to display"
|
|
4150
|
+
},
|
|
4151
|
+
/** Whether this trial is a match (for computing accuracy) */
|
|
4152
|
+
isMatch: {
|
|
4153
|
+
type: ParameterType.BOOL,
|
|
4154
|
+
default: void 0,
|
|
4155
|
+
description: "Whether this tangram matches the previous one (optional)"
|
|
4156
|
+
},
|
|
4157
|
+
/** Whether to show tangram decomposed into individual primitives with borders */
|
|
4158
|
+
show_tangram_decomposition: {
|
|
4159
|
+
type: ParameterType.BOOL,
|
|
4160
|
+
default: false,
|
|
4161
|
+
description: "Whether to show tangram decomposed into individual primitives with borders"
|
|
4162
|
+
},
|
|
4163
|
+
/** HTML content to display above the tangram as instructions */
|
|
4164
|
+
instructions: {
|
|
4165
|
+
type: ParameterType.STRING,
|
|
4166
|
+
default: "",
|
|
4167
|
+
description: "HTML content to display above the tangram as instructions"
|
|
4168
|
+
},
|
|
4169
|
+
/** Text to display on response button */
|
|
4170
|
+
button_text: {
|
|
4171
|
+
type: ParameterType.STRING,
|
|
4172
|
+
default: "Same as previous!",
|
|
4173
|
+
description: "Text to display on response button"
|
|
4174
|
+
},
|
|
4175
|
+
/** Duration to display tangram and accept responses (milliseconds) */
|
|
4176
|
+
duration: {
|
|
4177
|
+
type: ParameterType.INT,
|
|
4178
|
+
default: 3e3,
|
|
4179
|
+
description: "Duration in milliseconds to display tangram and accept responses"
|
|
4180
|
+
},
|
|
4181
|
+
/** Callback fired when trial ends */
|
|
4182
|
+
onTrialEnd: {
|
|
4183
|
+
type: ParameterType.FUNCTION,
|
|
4184
|
+
default: void 0,
|
|
4185
|
+
description: "Callback when trial completes with full data"
|
|
4186
|
+
}
|
|
4187
|
+
},
|
|
4188
|
+
data: {
|
|
4189
|
+
/** Whether participant clicked the response button before duration expired */
|
|
4190
|
+
responded_match: {
|
|
4191
|
+
type: ParameterType.BOOL,
|
|
4192
|
+
description: "True if participant clicked response button, false otherwise"
|
|
4193
|
+
},
|
|
4194
|
+
/** Reaction time in milliseconds (NaN if no response or response after duration) */
|
|
4195
|
+
rt: {
|
|
4196
|
+
type: ParameterType.INT,
|
|
4197
|
+
description: "Milliseconds between trial start and button click (NaN if no response or late response)"
|
|
4198
|
+
},
|
|
4199
|
+
/** Accuracy: 1 if correct, 0 if incorrect, NaN if isMatch not provided */
|
|
4200
|
+
accuracy: {
|
|
4201
|
+
type: ParameterType.FLOAT,
|
|
4202
|
+
description: "1 if response matches isMatch parameter, 0 otherwise (NaN if isMatch not provided)"
|
|
4203
|
+
},
|
|
4204
|
+
/** Whether response occurred after duration expired */
|
|
4205
|
+
responded_after_duration: {
|
|
4206
|
+
type: ParameterType.BOOL,
|
|
4207
|
+
description: "True if button clicked after duration expired, false otherwise"
|
|
4208
|
+
},
|
|
4209
|
+
/** Time of late response (NaN if no late response) */
|
|
4210
|
+
rt_after_duration: {
|
|
4211
|
+
type: ParameterType.INT,
|
|
4212
|
+
description: "Milliseconds between trial start and late button click (NaN if no late response)"
|
|
4213
|
+
}
|
|
4214
|
+
},
|
|
4215
|
+
citations: ""
|
|
4216
|
+
};
|
|
4217
|
+
class TangramNBackPlugin {
|
|
4218
|
+
constructor(jsPsych) {
|
|
4219
|
+
this.jsPsych = jsPsych;
|
|
4220
|
+
}
|
|
4221
|
+
static {
|
|
4222
|
+
this.info = info;
|
|
4223
|
+
}
|
|
4224
|
+
/**
|
|
4225
|
+
* Launches the trial by invoking startNBackTrial
|
|
4226
|
+
* with the display element, parameters, and jsPsych instance.
|
|
4227
|
+
*/
|
|
4228
|
+
trial(display_element, trial) {
|
|
4229
|
+
const wrappedOnTrialEnd = (data) => {
|
|
4230
|
+
if (trial.onTrialEnd) {
|
|
4231
|
+
trial.onTrialEnd(data);
|
|
4232
|
+
}
|
|
4233
|
+
const reactContext = display_element.__reactContext;
|
|
4234
|
+
if (reactContext?.root) {
|
|
4235
|
+
reactContext.root.unmount();
|
|
4236
|
+
}
|
|
4237
|
+
display_element.innerHTML = "";
|
|
4238
|
+
this.jsPsych.finishTrial(data);
|
|
4239
|
+
};
|
|
4240
|
+
const params = {
|
|
4241
|
+
tangram: trial.tangram,
|
|
4242
|
+
isMatch: trial.isMatch,
|
|
4243
|
+
show_tangram_decomposition: trial.show_tangram_decomposition,
|
|
4244
|
+
instructions: trial.instructions,
|
|
4245
|
+
button_text: trial.button_text,
|
|
4246
|
+
duration: trial.duration,
|
|
4247
|
+
onTrialEnd: wrappedOnTrialEnd
|
|
4248
|
+
};
|
|
4249
|
+
const { root, display_element: element, jsPsych } = startNBackTrial(display_element, params, this.jsPsych);
|
|
4250
|
+
element.__reactContext = { root, jsPsych };
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
|
|
4254
|
+
export { TangramConstructPlugin, TangramNBackPlugin, TangramPrepPlugin };
|
|
3935
4255
|
//# sourceMappingURL=index.js.map
|