jspsych-tangram 0.0.12 → 0.0.13
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 +4809 -3948
- 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 +275 -66
- package/dist/construct/index.cjs.map +1 -1
- package/dist/construct/index.d.ts +36 -0
- package/dist/construct/index.js +275 -66
- package/dist/construct/index.js.map +1 -1
- package/dist/index.cjs +385 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +84 -0
- package/dist/index.js +385 -95
- package/dist/index.js.map +1 -1
- package/dist/nback/index.browser.js +4629 -3939
- package/dist/nback/index.browser.js.map +1 -1
- package/dist/nback/index.browser.min.js +12 -12
- package/dist/nback/index.browser.min.js.map +1 -1
- package/dist/nback/index.cjs +102 -64
- package/dist/nback/index.cjs.map +1 -1
- package/dist/nback/index.d.ts +24 -0
- package/dist/nback/index.js +102 -64
- package/dist/nback/index.js.map +1 -1
- package/dist/prep/index.browser.js +4805 -3952
- 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 +271 -70
- package/dist/prep/index.cjs.map +1 -1
- package/dist/prep/index.d.ts +24 -0
- package/dist/prep/index.js +271 -70
- package/dist/prep/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/components/board/BoardView.tsx +372 -124
- package/src/core/components/board/GameBoard.tsx +26 -2
- package/src/core/components/pieces/BlueprintRing.tsx +105 -47
- package/src/core/config/config.ts +25 -10
- package/src/plugins/tangram-construct/ConstructionApp.tsx +7 -1
- package/src/plugins/tangram-construct/index.ts +22 -1
- package/src/plugins/tangram-nback/NBackApp.tsx +87 -28
- package/src/plugins/tangram-nback/index.ts +14 -0
- package/src/plugins/tangram-prep/PrepApp.tsx +7 -1
- package/src/plugins/tangram-prep/index.ts +14 -0
- package/tangram-construct.min.js +13 -13
- package/tangram-nback.min.js +12 -12
- package/tangram-prep.min.js +13 -13
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BlueprintRing - Reusable blueprint selection interface component
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This component provides the central blueprint selection interface that displays
|
|
5
5
|
* available piece blueprints in a circular or semicircular arrangement around
|
|
6
6
|
* a center badge. It handles both primitive pieces and quickstash macros.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* ## Key Features
|
|
9
9
|
* - **Dual Mode Display**: Switches between primitives and quickstash collections
|
|
10
|
-
* - **Flexible Layout**: Adapts to circle and semicircle layout modes
|
|
10
|
+
* - **Flexible Layout**: Adapts to circle and semicircle layout modes
|
|
11
11
|
* - **Interactive Badge**: Center button to toggle between piece collections
|
|
12
12
|
* - **Responsive Geometry**: Automatically calculates ring radius based on content
|
|
13
13
|
* - **Event Integration**: Provides structured callbacks for piece selection
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* ## Architecture
|
|
16
16
|
* - Pure presentation component (no internal state management)
|
|
17
17
|
* - Receives all data and callbacks via props (dependency injection)
|
|
18
18
|
* - Computes ring geometry based on layout constraints
|
|
19
19
|
* - Renders SVG blueprint glyphs with proper scaling and positioning
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
21
|
* ## Usage
|
|
22
22
|
* Typically used within GameBoard or plugin components:
|
|
23
23
|
* ```typescript
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
* onCenterBadgePointerDown={handleViewToggle}
|
|
31
31
|
* />
|
|
32
32
|
* ```
|
|
33
|
-
*
|
|
33
|
+
*
|
|
34
34
|
* @see {@link GameBoard} Primary container that uses this component
|
|
35
35
|
* @see {@link useBlueprintRing} Hook for managing blueprint ring state
|
|
36
|
-
* @since Phase 3.3 - Extracted from monolithic Board component
|
|
36
|
+
* @since Phase 3.3 - Extracted from monolithic Board component
|
|
37
37
|
* @author Claude Code Assistant
|
|
38
38
|
*/
|
|
39
39
|
|
|
@@ -47,45 +47,94 @@ function pathD(poly: any[]) {
|
|
|
47
47
|
return `M ${poly.map((pt: any) => `${pt.x} ${pt.y}`).join(" L ")} Z`;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Get the fill color for a blueprint based on its type
|
|
52
|
+
*
|
|
53
|
+
* REQUIRES: blueprint is valid, primitiveColorIndices is an array of 5 valid indices
|
|
54
|
+
* EFFECTS: Returns color based on primitive type if usePrimitiveColors is true,
|
|
55
|
+
* otherwise returns the default color. Composites always use default color.
|
|
56
|
+
*/
|
|
57
|
+
function getBlueprintColor(
|
|
58
|
+
blueprint: Blueprint,
|
|
59
|
+
usePrimitiveColors: boolean,
|
|
60
|
+
defaultColor: string,
|
|
61
|
+
primitiveColorIndices: number[]
|
|
62
|
+
): string {
|
|
63
|
+
if (!usePrimitiveColors) {
|
|
64
|
+
return defaultColor;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// For primitive blueprints, use the mapped color from CONFIG
|
|
68
|
+
if ('kind' in blueprint) {
|
|
69
|
+
const kind = blueprint.kind as TanKind;
|
|
70
|
+
// Map primitive kind to index: square=0, smalltriangle=1, parallelogram=2, medtriangle=3, largetriangle=4
|
|
71
|
+
const kindToIndex: Record<TanKind, number> = {
|
|
72
|
+
'square': 0,
|
|
73
|
+
'smalltriangle': 1,
|
|
74
|
+
'parallelogram': 2,
|
|
75
|
+
'medtriangle': 3,
|
|
76
|
+
'largetriangle': 4
|
|
77
|
+
};
|
|
78
|
+
const primitiveIndex = kindToIndex[kind];
|
|
79
|
+
if (primitiveIndex !== undefined && primitiveColorIndices[primitiveIndex] !== undefined) {
|
|
80
|
+
const colorIndex = primitiveColorIndices[primitiveIndex];
|
|
81
|
+
const color = CONFIG.color.primitiveColors[colorIndex];
|
|
82
|
+
if (color) {
|
|
83
|
+
return color;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return defaultColor;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// For composite blueprints, always use the default color
|
|
90
|
+
return defaultColor;
|
|
91
|
+
}
|
|
92
|
+
|
|
50
93
|
/**
|
|
51
94
|
* Props interface for BlueprintRing component
|
|
52
|
-
*
|
|
95
|
+
*
|
|
53
96
|
* This interface defines all the data and callbacks needed to render
|
|
54
97
|
* the blueprint selection interface. All props use dependency injection.
|
|
55
98
|
*/
|
|
56
99
|
export interface BlueprintRingProps {
|
|
57
100
|
/** Array of primitive tangram piece blueprints */
|
|
58
101
|
primitives: PrimitiveBlueprint[];
|
|
59
|
-
|
|
102
|
+
|
|
60
103
|
/** Array of quickstash/macro piece blueprints */
|
|
61
104
|
quickstash: Blueprint[];
|
|
62
|
-
|
|
63
|
-
/**
|
|
105
|
+
|
|
106
|
+
/**
|
|
64
107
|
* Current view mode for blueprint ring
|
|
65
108
|
* - "primitives": Show primitive tangram pieces
|
|
66
109
|
* - "quickstash": Show macro/composite pieces
|
|
67
110
|
*/
|
|
68
111
|
currentView: "primitives" | "quickstash";
|
|
69
|
-
|
|
112
|
+
|
|
70
113
|
/** Computed circle layout containing geometry information */
|
|
71
114
|
layout: CircleLayout;
|
|
72
|
-
|
|
115
|
+
|
|
73
116
|
/** Radius of the center toggle badge in pixels */
|
|
74
117
|
badgeR: number;
|
|
75
|
-
|
|
118
|
+
|
|
76
119
|
/** Center position of the toggle badge */
|
|
77
120
|
badgeCenter: { x: number; y: number };
|
|
78
|
-
|
|
121
|
+
|
|
79
122
|
/** Maximum number of quickstash slots to display */
|
|
80
123
|
maxQuickstashSlots: number;
|
|
81
|
-
|
|
124
|
+
|
|
82
125
|
/** ID of currently dragging piece (null if none) - used for interaction state */
|
|
83
126
|
draggingId: string | null;
|
|
84
|
-
|
|
85
|
-
/**
|
|
127
|
+
|
|
128
|
+
/** Whether to use distinct colors for each primitive shape type in blueprints */
|
|
129
|
+
usePrimitiveColorsBlueprints?: boolean;
|
|
130
|
+
|
|
131
|
+
/** Array of 5 integers mapping primitives to colors from CONFIG.color.primitiveColors */
|
|
132
|
+
primitiveColorIndices?: number[];
|
|
133
|
+
|
|
134
|
+
/**
|
|
86
135
|
* Callback fired when user starts interacting with a blueprint
|
|
87
136
|
* @param e - Pointer event from the interaction
|
|
88
|
-
* @param bp - Blueprint that was selected
|
|
137
|
+
* @param bp - Blueprint that was selected
|
|
89
138
|
* @param bpGeom - Geometry information for the selected blueprint
|
|
90
139
|
*/
|
|
91
140
|
onBlueprintPointerDown: (
|
|
@@ -93,14 +142,14 @@ export interface BlueprintRingProps {
|
|
|
93
142
|
bp: Blueprint,
|
|
94
143
|
bpGeom: { bx: number; by: number; cx: number; cy: number }
|
|
95
144
|
) => void;
|
|
96
|
-
|
|
97
|
-
/**
|
|
145
|
+
|
|
146
|
+
/**
|
|
98
147
|
* Callback fired when user clicks the center badge to toggle views
|
|
99
148
|
* @param e - Pointer event from the badge click
|
|
100
149
|
*/
|
|
101
150
|
onCenterBadgePointerDown: (e: React.PointerEvent) => void;
|
|
102
|
-
|
|
103
|
-
/**
|
|
151
|
+
|
|
152
|
+
/**
|
|
104
153
|
* Helper function to lookup primitive blueprints by kind
|
|
105
154
|
* Used for rendering composite pieces that reference primitives
|
|
106
155
|
*/
|
|
@@ -119,22 +168,24 @@ export default function BlueprintRing(props: BlueprintRingProps) {
|
|
|
119
168
|
draggingId,
|
|
120
169
|
onBlueprintPointerDown,
|
|
121
170
|
onCenterBadgePointerDown,
|
|
122
|
-
getPrimitive
|
|
171
|
+
getPrimitive,
|
|
172
|
+
usePrimitiveColorsBlueprints,
|
|
173
|
+
primitiveColorIndices
|
|
123
174
|
} = props;
|
|
124
|
-
|
|
175
|
+
|
|
125
176
|
// Determine which blueprints to show
|
|
126
177
|
const blueprints: Blueprint[] = currentView === "primitives" ? primitives : quickstash;
|
|
127
|
-
|
|
178
|
+
|
|
128
179
|
// Calculate ring geometry
|
|
129
180
|
const QS_SLOTS = maxQuickstashSlots;
|
|
130
181
|
const PRIM_SLOTS = primitives.length;
|
|
131
182
|
const slotsForView = currentView === "primitives" ? Math.max(1, PRIM_SLOTS) : Math.max(1, QS_SLOTS);
|
|
132
183
|
const sweep = layout.mode === "circle" ? Math.PI * 2 : Math.PI;
|
|
133
184
|
const delta = sweep / slotsForView;
|
|
134
|
-
|
|
185
|
+
|
|
135
186
|
const start = layout.mode === "circle" ? -Math.PI / 2 : Math.PI;
|
|
136
187
|
const blueprintTheta = (i: number) => start + (i + 0.5) * delta;
|
|
137
|
-
|
|
188
|
+
|
|
138
189
|
// Chord requirement calculation
|
|
139
190
|
const anchorsDiameterToPx = (anchorsDiag: number, gridPx: number = CONFIG.layout.grid.stepPx) =>
|
|
140
191
|
anchorsDiag * Math.SQRT2 * gridPx;
|
|
@@ -143,22 +194,29 @@ export default function BlueprintRing(props: BlueprintRingProps) {
|
|
|
143
194
|
: CONFIG.layout.constraints.quickstashDiamAnchors;
|
|
144
195
|
const D_slot = anchorsDiameterToPx(reqAnchors);
|
|
145
196
|
const R_needed = D_slot / (2 * Math.max(1e-9, Math.sin(delta / 2)));
|
|
146
|
-
|
|
197
|
+
|
|
147
198
|
// Minimum radius to avoid overlapping with badge
|
|
148
199
|
// Badge takes up: badgeR + margin, plus we need half the slot diameter for clearance
|
|
149
200
|
const R_min = badgeR + CONFIG.size.centerBadge.marginPx + D_slot / 2;
|
|
150
201
|
const ringMax = layout.innerR - (badgeR + CONFIG.size.centerBadge.marginPx);
|
|
151
|
-
|
|
202
|
+
|
|
152
203
|
// Clamp to [R_min, ringMax]
|
|
153
204
|
const blueprintRingR = Math.min(Math.max(R_needed, R_min), ringMax);
|
|
154
|
-
|
|
205
|
+
|
|
155
206
|
// Blueprint glyph renderer
|
|
156
207
|
const renderBlueprintGlyph = (bp: Blueprint, bx: number, by: number) => {
|
|
157
208
|
const bb = boundsOfBlueprint(bp, (k: string) => getPrimitive(k as TanKind)!);
|
|
158
209
|
const cx = bb.min.x + bb.width / 2;
|
|
159
210
|
const cy = bb.min.y + bb.height / 2;
|
|
160
211
|
const selected = false; // Future enhancement: add selection highlighting
|
|
161
|
-
|
|
212
|
+
|
|
213
|
+
const fillColor = getBlueprintColor(
|
|
214
|
+
bp,
|
|
215
|
+
usePrimitiveColorsBlueprints || false,
|
|
216
|
+
CONFIG.color.blueprint.fill,
|
|
217
|
+
primitiveColorIndices || [0, 1, 2, 3, 4]
|
|
218
|
+
);
|
|
219
|
+
|
|
162
220
|
return (
|
|
163
221
|
<g
|
|
164
222
|
key={bp.id}
|
|
@@ -168,7 +226,7 @@ export default function BlueprintRing(props: BlueprintRingProps) {
|
|
|
168
226
|
<path
|
|
169
227
|
key={idx}
|
|
170
228
|
d={pathD(poly)}
|
|
171
|
-
fill={
|
|
229
|
+
fill={fillColor}
|
|
172
230
|
opacity={CONFIG.opacity.blueprint}
|
|
173
231
|
stroke={selected ? CONFIG.color.blueprint.selectedStroke : "none"}
|
|
174
232
|
strokeWidth={selected ? 2 : 0}
|
|
@@ -180,21 +238,21 @@ export default function BlueprintRing(props: BlueprintRingProps) {
|
|
|
180
238
|
</g>
|
|
181
239
|
);
|
|
182
240
|
};
|
|
183
|
-
|
|
241
|
+
|
|
184
242
|
return (
|
|
185
243
|
<g className="blueprint-ring">
|
|
186
244
|
{/* Center badge */}
|
|
187
|
-
<g
|
|
188
|
-
transform={`translate(${badgeCenter.x}, ${badgeCenter.y})`}
|
|
189
|
-
style={{ cursor: draggingId ? "default" : "pointer" }}
|
|
245
|
+
<g
|
|
246
|
+
transform={`translate(${badgeCenter.x}, ${badgeCenter.y})`}
|
|
247
|
+
style={{ cursor: draggingId ? "default" : "pointer" }}
|
|
190
248
|
onPointerDown={onCenterBadgePointerDown}
|
|
191
249
|
>
|
|
192
250
|
<circle r={badgeR} fill={CONFIG.color.blueprint.badgeFill} />
|
|
193
|
-
<text
|
|
194
|
-
textAnchor="middle"
|
|
195
|
-
dominantBaseline="middle"
|
|
196
|
-
fontSize={CONFIG.size.badgeFontPx}
|
|
197
|
-
fill={CONFIG.color.blueprint.labelFill}
|
|
251
|
+
<text
|
|
252
|
+
textAnchor="middle"
|
|
253
|
+
dominantBaseline="middle"
|
|
254
|
+
fontSize={CONFIG.size.badgeFontPx}
|
|
255
|
+
fill={CONFIG.color.blueprint.labelFill}
|
|
198
256
|
pointerEvents="none"
|
|
199
257
|
>
|
|
200
258
|
{currentView}
|
|
@@ -217,18 +275,18 @@ export default function BlueprintRing(props: BlueprintRingProps) {
|
|
|
217
275
|
* Useful for components that need more control over blueprint ring behavior
|
|
218
276
|
*/
|
|
219
277
|
export function useBlueprintRing(
|
|
220
|
-
primitives: PrimitiveBlueprint[],
|
|
221
|
-
quickstash: Blueprint[],
|
|
278
|
+
primitives: PrimitiveBlueprint[],
|
|
279
|
+
quickstash: Blueprint[],
|
|
222
280
|
initialView: "primitives" | "quickstash" = "quickstash"
|
|
223
281
|
) {
|
|
224
282
|
const [currentView, setCurrentView] = React.useState<"primitives" | "quickstash">(initialView);
|
|
225
|
-
|
|
283
|
+
|
|
226
284
|
const switchView = React.useCallback(() => {
|
|
227
285
|
setCurrentView(prev => prev === "primitives" ? "quickstash" : "primitives");
|
|
228
286
|
}, []);
|
|
229
|
-
|
|
287
|
+
|
|
230
288
|
const blueprints = currentView === "primitives" ? primitives : quickstash;
|
|
231
|
-
|
|
289
|
+
|
|
232
290
|
return {
|
|
233
291
|
currentView,
|
|
234
292
|
blueprints,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/core/config/config.ts
|
|
2
2
|
export type Config = {
|
|
3
3
|
color: {
|
|
4
|
+
background: string;
|
|
4
5
|
bands: {
|
|
5
6
|
silhouette: { fillEven: string; fillOdd: string; stroke: string };
|
|
6
7
|
workspace: { fillEven: string; fillOdd: string; stroke: string };
|
|
@@ -12,6 +13,7 @@ export type Config = {
|
|
|
12
13
|
ui: { light: string; dark: string };
|
|
13
14
|
blueprint: { fill: string; selectedStroke: string; badgeFill: string; labelFill: string };
|
|
14
15
|
tangramDecomposition: { stroke: string };
|
|
16
|
+
primitiveColors: string[];
|
|
15
17
|
};
|
|
16
18
|
opacity: {
|
|
17
19
|
blueprint: number;
|
|
@@ -24,6 +26,7 @@ export type Config = {
|
|
|
24
26
|
anchorRadiusPx: { valid: number; invalid: number };
|
|
25
27
|
badgeFontPx: number;
|
|
26
28
|
centerBadge: { fractionOfOuterR: number; minPx: number; marginPx: number };
|
|
29
|
+
invalidMarker: { sizePx: number; strokePx: number };
|
|
27
30
|
};
|
|
28
31
|
layout: {
|
|
29
32
|
grid: { stepPx: number; unitPx: number };
|
|
@@ -41,35 +44,46 @@ export type Config = {
|
|
|
41
44
|
snapRadiusPx: number;
|
|
42
45
|
showBorders: boolean;
|
|
43
46
|
hideTouchingBorders: boolean;
|
|
47
|
+
silhouettesBelowPieces: boolean;
|
|
44
48
|
};
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
export const CONFIG: Config = {
|
|
48
52
|
color: {
|
|
53
|
+
background: "#fff7e0ff",
|
|
49
54
|
bands: {
|
|
50
|
-
silhouette: { fillEven: "#
|
|
51
|
-
workspace: { fillEven: "#
|
|
55
|
+
silhouette: { fillEven: "#ffffff", fillOdd: "#ffffff", stroke: "#b1b1b1" },
|
|
56
|
+
workspace: { fillEven: "#ffffff", fillOdd: "#ffffff", stroke: "#b1b1b1" }
|
|
52
57
|
},
|
|
53
|
-
completion: { fill: "#
|
|
58
|
+
completion: { fill: "#ccffcc", stroke: "#13da57" },
|
|
54
59
|
silhouetteMask: "#374151",
|
|
55
60
|
anchors: { invalid: "#7dd3fc", valid: "#475569" },
|
|
56
|
-
|
|
61
|
+
// validFill used here for placed composites
|
|
62
|
+
piece: { draggingFill: "#8e7cc3", validFill: "#8e7cc3", invalidFill: "#d55c00", invalidStroke: "#dc2626", selectedStroke: "#674ea7", allGreenStroke: "#86efac", borderStroke: "#674ea7" },
|
|
57
63
|
ui: { light: "#60a5fa", dark: "#1d4ed8" },
|
|
58
64
|
blueprint: { fill: "#374151", selectedStroke: "#111827", badgeFill: "#000000", labelFill: "#ffffff" },
|
|
59
|
-
tangramDecomposition: { stroke: "#fef2cc" }
|
|
65
|
+
tangramDecomposition: { stroke: "#fef2cc" },
|
|
66
|
+
primitiveColors: [ // from seaborn "colorblind" palette, 6 colors, with red omitted
|
|
67
|
+
'#0173b2',
|
|
68
|
+
'#de8f05',
|
|
69
|
+
'#029e73',
|
|
70
|
+
'#cc78bc',
|
|
71
|
+
'#ca9161'
|
|
72
|
+
]
|
|
60
73
|
},
|
|
61
74
|
opacity: {
|
|
62
|
-
blueprint: 0.
|
|
75
|
+
blueprint: 0.6,
|
|
63
76
|
silhouetteMask: 0.25,
|
|
64
77
|
//anchors: { valid: 0.80, invalid: 0.50 },
|
|
65
78
|
anchors: { invalid: 0.0, valid: 0.0 },
|
|
66
|
-
piece: { invalid:
|
|
79
|
+
piece: { invalid: 1, dragging: 1, locked: 1, normal: 1 },
|
|
67
80
|
},
|
|
68
81
|
size: {
|
|
69
|
-
stroke: { bandPx: 5, pieceSelectedPx:
|
|
82
|
+
stroke: { bandPx: 5, pieceSelectedPx: 5, allGreenStrokePx: 10, pieceBorderPx: 2, tangramDecompositionPx: 1 },
|
|
70
83
|
anchorRadiusPx: { valid: 1.0, invalid: 1.0 },
|
|
71
84
|
badgeFontPx: 16,
|
|
72
|
-
centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 }
|
|
85
|
+
centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 },
|
|
86
|
+
invalidMarker: { sizePx: 10, strokePx: 4 }
|
|
73
87
|
},
|
|
74
88
|
layout: {
|
|
75
89
|
grid: { stepPx: 20, unitPx: 40 },
|
|
@@ -85,6 +99,7 @@ export const CONFIG: Config = {
|
|
|
85
99
|
game: {
|
|
86
100
|
snapRadiusPx: 15,
|
|
87
101
|
showBorders: false,
|
|
88
|
-
hideTouchingBorders: true
|
|
102
|
+
hideTouchingBorders: true,
|
|
103
|
+
silhouettesBelowPieces: true
|
|
89
104
|
}
|
|
90
105
|
};
|
|
@@ -34,6 +34,9 @@ export interface StartConstructionTrialParams {
|
|
|
34
34
|
instructions?: string;
|
|
35
35
|
onInteraction?: (event: any) => void;
|
|
36
36
|
onTrialEnd?: (data: any) => void;
|
|
37
|
+
usePrimitiveColorsBlueprints?: boolean;
|
|
38
|
+
usePrimitiveColorsTargets?: boolean;
|
|
39
|
+
primitiveColorIndices?: number[];
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
// Type for anchor-based composite definitions
|
|
@@ -143,7 +146,10 @@ export function startConstructionTrial(
|
|
|
143
146
|
trialParams,
|
|
144
147
|
...(params.instructions && { instructions: params.instructions }),
|
|
145
148
|
...(params.onInteraction && { onInteraction: params.onInteraction }),
|
|
146
|
-
...(params.onTrialEnd && { onTrialEnd: params.onTrialEnd })
|
|
149
|
+
...(params.onTrialEnd && { onTrialEnd: params.onTrialEnd }),
|
|
150
|
+
...(params.usePrimitiveColorsBlueprints !== undefined && { usePrimitiveColorsBlueprints: params.usePrimitiveColorsBlueprints }),
|
|
151
|
+
...(params.usePrimitiveColorsTargets !== undefined && { usePrimitiveColorsTargets: params.usePrimitiveColorsTargets }),
|
|
152
|
+
...(params.primitiveColorIndices !== undefined && { primitiveColorIndices: params.primitiveColorIndices })
|
|
147
153
|
};
|
|
148
154
|
|
|
149
155
|
const root = createRoot(display_element);
|
|
@@ -79,6 +79,24 @@ const info = {
|
|
|
79
79
|
type: ParameterType.FUNCTION,
|
|
80
80
|
default: undefined,
|
|
81
81
|
description: "Callback when trial completes with full data"
|
|
82
|
+
},
|
|
83
|
+
/** Whether to use distinct colors for each primitive shape type in blueprints */
|
|
84
|
+
use_primitive_colors_blueprints: {
|
|
85
|
+
type: ParameterType.BOOL,
|
|
86
|
+
default: false,
|
|
87
|
+
description: "Whether each primitive shape type should have its own distinct color in the blueprint dock area"
|
|
88
|
+
},
|
|
89
|
+
/** Whether to use distinct colors for each primitive shape type in target tangrams */
|
|
90
|
+
use_primitive_colors_targets: {
|
|
91
|
+
type: ParameterType.BOOL,
|
|
92
|
+
default: false,
|
|
93
|
+
description: "Whether each primitive shape type should have its own distinct color in target tangram silhouettes"
|
|
94
|
+
},
|
|
95
|
+
/** Indices mapping primitives to colors from the color palette */
|
|
96
|
+
primitive_color_indices: {
|
|
97
|
+
type: ParameterType.OBJECT,
|
|
98
|
+
default: [0, 1, 2, 3, 4],
|
|
99
|
+
description: "Array of 5 integers indexing into primitiveColors array, mapping [square, smalltriangle, parallelogram, medtriangle, largetriangle] to colors"
|
|
82
100
|
}
|
|
83
101
|
},
|
|
84
102
|
data: {
|
|
@@ -163,7 +181,10 @@ class TangramConstructPlugin implements JsPsychPlugin<Info> {
|
|
|
163
181
|
show_tangram_decomposition: trial.show_tangram_decomposition,
|
|
164
182
|
instructions: trial.instructions,
|
|
165
183
|
onInteraction: trial.onInteraction,
|
|
166
|
-
onTrialEnd: wrappedOnTrialEnd
|
|
184
|
+
onTrialEnd: wrappedOnTrialEnd,
|
|
185
|
+
usePrimitiveColorsBlueprints: trial.use_primitive_colors_blueprints,
|
|
186
|
+
usePrimitiveColorsTargets: trial.use_primitive_colors_targets,
|
|
187
|
+
primitiveColorIndices: trial.primitive_color_indices
|
|
167
188
|
};
|
|
168
189
|
|
|
169
190
|
// Use React wrapper to start the trial
|
|
@@ -19,6 +19,8 @@ export interface StartNBackTrialParams {
|
|
|
19
19
|
instructions?: string;
|
|
20
20
|
button_text?: string;
|
|
21
21
|
duration: number;
|
|
22
|
+
usePrimitiveColors?: boolean;
|
|
23
|
+
primitiveColorIndices?: number[];
|
|
22
24
|
onTrialEnd?: (data: any) => void;
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -49,6 +51,8 @@ function NBackView({ params }: NBackViewProps) {
|
|
|
49
51
|
instructions,
|
|
50
52
|
button_text,
|
|
51
53
|
duration,
|
|
54
|
+
usePrimitiveColors,
|
|
55
|
+
primitiveColorIndices,
|
|
52
56
|
onTrialEnd
|
|
53
57
|
} = params;
|
|
54
58
|
|
|
@@ -135,7 +139,12 @@ function NBackView({ params }: NBackViewProps) {
|
|
|
135
139
|
...data,
|
|
136
140
|
accuracy,
|
|
137
141
|
tangram_id: tangram.tangramID,
|
|
138
|
-
is_match: isMatch
|
|
142
|
+
is_match: isMatch,
|
|
143
|
+
show_tangram_decomposition: show_tangram_decomposition,
|
|
144
|
+
use_primitive_colors: usePrimitiveColors,
|
|
145
|
+
primitive_color_indices: primitiveColorIndices,
|
|
146
|
+
duration: duration,
|
|
147
|
+
button_text: button_text
|
|
139
148
|
};
|
|
140
149
|
|
|
141
150
|
if (onTrialEnd) {
|
|
@@ -208,25 +217,50 @@ function NBackView({ params }: NBackViewProps) {
|
|
|
208
217
|
|
|
209
218
|
return (
|
|
210
219
|
<g key="sil-decomposed" pointerEvents="none">
|
|
211
|
-
{placedPolys.map((scaledPoly, i) =>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
d={pathD(scaledPoly)}
|
|
216
|
-
fill={CONFIG.color.silhouetteMask}
|
|
217
|
-
opacity={CONFIG.opacity.silhouetteMask}
|
|
218
|
-
stroke="none"
|
|
219
|
-
/>
|
|
220
|
+
{placedPolys.map((scaledPoly, i) => {
|
|
221
|
+
// Get color for this primitive based on its kind
|
|
222
|
+
const primInfo = primitiveDecomposition[i];
|
|
223
|
+
let fillColor = CONFIG.color.silhouetteMask;
|
|
220
224
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
225
|
+
if (usePrimitiveColors && primInfo?.kind && primitiveColorIndices) {
|
|
226
|
+
// Map primitive kind to index
|
|
227
|
+
const kindToIndex: Record<TanKind, number> = {
|
|
228
|
+
'square': 0,
|
|
229
|
+
'smalltriangle': 1,
|
|
230
|
+
'parallelogram': 2,
|
|
231
|
+
'medtriangle': 3,
|
|
232
|
+
'largetriangle': 4
|
|
233
|
+
};
|
|
234
|
+
const primitiveIndex = kindToIndex[primInfo.kind as TanKind];
|
|
235
|
+
if (primitiveIndex !== undefined && primitiveColorIndices[primitiveIndex] !== undefined) {
|
|
236
|
+
const colorIndex = primitiveColorIndices[primitiveIndex];
|
|
237
|
+
const color = CONFIG.color.primitiveColors[colorIndex];
|
|
238
|
+
if (color) {
|
|
239
|
+
fillColor = color;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<React.Fragment key={`prim-${i}`}>
|
|
246
|
+
{/* Fill path */}
|
|
247
|
+
<path
|
|
248
|
+
d={pathD(scaledPoly)}
|
|
249
|
+
fill={fillColor}
|
|
250
|
+
opacity={CONFIG.opacity.silhouetteMask}
|
|
251
|
+
stroke="none"
|
|
252
|
+
/>
|
|
253
|
+
|
|
254
|
+
{/* Full perimeter border */}
|
|
255
|
+
<path
|
|
256
|
+
d={pathD(scaledPoly)}
|
|
257
|
+
fill="none"
|
|
258
|
+
stroke={CONFIG.color.tangramDecomposition.stroke}
|
|
259
|
+
strokeWidth={CONFIG.size.stroke.tangramDecompositionPx}
|
|
260
|
+
/>
|
|
261
|
+
</React.Fragment>
|
|
262
|
+
);
|
|
263
|
+
})}
|
|
230
264
|
</g>
|
|
231
265
|
);
|
|
232
266
|
} else {
|
|
@@ -235,15 +269,40 @@ function NBackView({ params }: NBackViewProps) {
|
|
|
235
269
|
|
|
236
270
|
return (
|
|
237
271
|
<g key="sil-unified" pointerEvents="none">
|
|
238
|
-
{placedPolys.map((scaledPoly, i) =>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
272
|
+
{placedPolys.map((scaledPoly, i) => {
|
|
273
|
+
// Get color for this primitive based on its kind
|
|
274
|
+
const primInfo = primitiveDecomposition[i];
|
|
275
|
+
let fillColor = CONFIG.color.silhouetteMask;
|
|
276
|
+
|
|
277
|
+
if (usePrimitiveColors && primInfo?.kind && primitiveColorIndices) {
|
|
278
|
+
// Map primitive kind to index
|
|
279
|
+
const kindToIndex: Record<TanKind, number> = {
|
|
280
|
+
'square': 0,
|
|
281
|
+
'smalltriangle': 1,
|
|
282
|
+
'parallelogram': 2,
|
|
283
|
+
'medtriangle': 3,
|
|
284
|
+
'largetriangle': 4
|
|
285
|
+
};
|
|
286
|
+
const primitiveIndex = kindToIndex[primInfo.kind as TanKind];
|
|
287
|
+
if (primitiveIndex !== undefined && primitiveColorIndices[primitiveIndex] !== undefined) {
|
|
288
|
+
const colorIndex = primitiveColorIndices[primitiveIndex];
|
|
289
|
+
const color = CONFIG.color.primitiveColors[colorIndex];
|
|
290
|
+
if (color) {
|
|
291
|
+
fillColor = color;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<path
|
|
298
|
+
key={`sil-${i}`}
|
|
299
|
+
d={pathD(scaledPoly)}
|
|
300
|
+
fill={fillColor}
|
|
301
|
+
opacity={CONFIG.opacity.silhouetteMask}
|
|
302
|
+
stroke="none"
|
|
303
|
+
/>
|
|
304
|
+
);
|
|
305
|
+
})}
|
|
247
306
|
</g>
|
|
248
307
|
);
|
|
249
308
|
}
|
|
@@ -41,6 +41,18 @@ const info = {
|
|
|
41
41
|
default: 3000,
|
|
42
42
|
description: "Duration in milliseconds to display tangram and accept responses"
|
|
43
43
|
},
|
|
44
|
+
/** Whether to use distinct colors for each primitive shape type */
|
|
45
|
+
use_primitive_colors: {
|
|
46
|
+
type: ParameterType.BOOL,
|
|
47
|
+
default: false,
|
|
48
|
+
description: "Whether each primitive shape type should have its own distinct color in the displayed tangram"
|
|
49
|
+
},
|
|
50
|
+
/** Indices mapping primitives to colors from the color palette */
|
|
51
|
+
primitive_color_indices: {
|
|
52
|
+
type: ParameterType.OBJECT,
|
|
53
|
+
default: [0, 1, 2, 3, 4],
|
|
54
|
+
description: "Array of 5 integers indexing into primitiveColors array, mapping [square, smalltriangle, parallelogram, medtriangle, largetriangle] to colors"
|
|
55
|
+
},
|
|
44
56
|
/** Callback fired when trial ends */
|
|
45
57
|
onTrialEnd: {
|
|
46
58
|
type: ParameterType.FUNCTION,
|
|
@@ -127,6 +139,8 @@ class TangramNBackPlugin implements JsPsychPlugin<Info> {
|
|
|
127
139
|
instructions: trial.instructions,
|
|
128
140
|
button_text: trial.button_text,
|
|
129
141
|
duration: trial.duration,
|
|
142
|
+
usePrimitiveColors: trial.use_primitive_colors,
|
|
143
|
+
primitiveColorIndices: trial.primitive_color_indices,
|
|
130
144
|
onTrialEnd: wrappedOnTrialEnd
|
|
131
145
|
};
|
|
132
146
|
|
|
@@ -36,6 +36,8 @@ export interface StartPrepTrialParams {
|
|
|
36
36
|
instructions?: string;
|
|
37
37
|
onInteraction?: (event: any) => void;
|
|
38
38
|
onTrialEnd?: (data: any) => void;
|
|
39
|
+
usePrimitiveColorsBlueprints?: boolean;
|
|
40
|
+
primitiveColorIndices?: number[];
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -58,6 +60,8 @@ export function startPrepTrial(
|
|
|
58
60
|
primitiveOrder,
|
|
59
61
|
onInteraction,
|
|
60
62
|
onTrialEnd,
|
|
63
|
+
usePrimitiveColorsBlueprints,
|
|
64
|
+
primitiveColorIndices,
|
|
61
65
|
} = params;
|
|
62
66
|
|
|
63
67
|
// Extract non-callback params for trial data
|
|
@@ -188,7 +192,9 @@ export function startPrepTrial(
|
|
|
188
192
|
...(params.instructions && { instructions: params.instructions }),
|
|
189
193
|
onControllerReady: handleControllerReady,
|
|
190
194
|
...(onInteraction && { onInteraction }),
|
|
191
|
-
...(onTrialEnd && { onTrialEnd })
|
|
195
|
+
...(onTrialEnd && { onTrialEnd }),
|
|
196
|
+
...(usePrimitiveColorsBlueprints !== undefined && { usePrimitiveColorsBlueprints }),
|
|
197
|
+
...(primitiveColorIndices !== undefined && { primitiveColorIndices })
|
|
192
198
|
}));
|
|
193
199
|
|
|
194
200
|
return { root, display_element, jsPsych };
|
|
@@ -62,6 +62,18 @@ const info = {
|
|
|
62
62
|
onTrialEnd: {
|
|
63
63
|
type: ParameterType.FUNCTION,
|
|
64
64
|
default: undefined
|
|
65
|
+
},
|
|
66
|
+
/** Whether to use distinct colors for each primitive shape type in blueprints */
|
|
67
|
+
use_primitive_colors_blueprints: {
|
|
68
|
+
type: ParameterType.BOOL,
|
|
69
|
+
default: false,
|
|
70
|
+
description: "Whether each primitive shape type should have its own distinct color in the blueprint dock area"
|
|
71
|
+
},
|
|
72
|
+
/** Indices mapping primitives to colors from the color palette */
|
|
73
|
+
primitive_color_indices: {
|
|
74
|
+
type: ParameterType.OBJECT,
|
|
75
|
+
default: [0, 1, 2, 3, 4],
|
|
76
|
+
description: "Array of 5 integers indexing into primitiveColors array, mapping [square, smalltriangle, parallelogram, medtriangle, largetriangle] to colors"
|
|
65
77
|
}
|
|
66
78
|
},
|
|
67
79
|
data: {
|
|
@@ -124,6 +136,8 @@ class TangramPrepPlugin implements JsPsychPlugin<Info> {
|
|
|
124
136
|
instructions: trial.instructions,
|
|
125
137
|
onInteraction: trial.onInteraction,
|
|
126
138
|
onTrialEnd: wrappedOnTrialEnd,
|
|
139
|
+
usePrimitiveColorsBlueprints: trial.use_primitive_colors_blueprints,
|
|
140
|
+
primitiveColorIndices: trial.primitive_color_indices,
|
|
127
141
|
};
|
|
128
142
|
|
|
129
143
|
const { root, display_element: element, jsPsych } = startPrepTrial(display_element, params, this.jsPsych);
|