jspsych-tangram 0.0.4 → 0.0.6
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 +18 -49
- package/dist/construct/index.browser.js.map +1 -1
- package/dist/construct/index.browser.min.js +10 -14
- package/dist/construct/index.browser.min.js.map +1 -1
- package/dist/construct/index.cjs +18 -49
- package/dist/construct/index.cjs.map +1 -1
- package/dist/construct/index.d.ts +12 -0
- package/dist/construct/index.js +18 -49
- package/dist/construct/index.js.map +1 -1
- package/dist/index.cjs +30 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.js +30 -52
- package/dist/index.js.map +1 -1
- package/dist/prep/index.browser.js +15 -5
- package/dist/prep/index.browser.js.map +1 -1
- package/dist/prep/index.browser.min.js +11 -11
- package/dist/prep/index.browser.min.js.map +1 -1
- package/dist/prep/index.cjs +15 -5
- package/dist/prep/index.cjs.map +1 -1
- package/dist/prep/index.d.ts +12 -0
- package/dist/prep/index.js +15 -5
- package/dist/prep/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/components/board/GameBoard.tsx +4 -1
- package/src/core/engine/state/BaseGameController.ts +0 -1
- package/src/plugins/tangram-construct/ConstructionApp.tsx +13 -53
- package/src/plugins/tangram-construct/index.ts +8 -1
- package/src/plugins/tangram-prep/PrepApp.tsx +8 -2
- package/src/plugins/tangram-prep/index.ts +8 -1
- package/tangram-construct.min.js +10 -14
- package/tangram-prep.min.js +11 -11
package/package.json
CHANGED
|
@@ -357,8 +357,11 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
357
357
|
setGameCompleted(true);
|
|
358
358
|
|
|
359
359
|
// Finalize trial data tracking (which calls onTrialEnd)
|
|
360
|
+
// Defer to avoid unmounting React while still in commit phase
|
|
360
361
|
if (tracker) {
|
|
361
|
-
|
|
362
|
+
setTimeout(() => {
|
|
363
|
+
tracker.finalizeTrial('auto_complete');
|
|
364
|
+
}, 0);
|
|
362
365
|
}
|
|
363
366
|
}
|
|
364
367
|
};
|
|
@@ -196,7 +196,6 @@ export class BaseGameController {
|
|
|
196
196
|
const allDone = Object.values(this.state.sectors).every((s: SectorState) => !!s.completedAt);
|
|
197
197
|
if (allDone && !this.state.endedAt) {
|
|
198
198
|
this.state.endedAt = NOW();
|
|
199
|
-
console.log("[BaseGameController] all sectors complete");
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
201
|
|
|
@@ -25,6 +25,7 @@ import { CONFIG } from "../../core/config/config";
|
|
|
25
25
|
export interface StartConstructionTrialParams {
|
|
26
26
|
tangrams: any[];
|
|
27
27
|
quickstash_macros?: Blueprint[] | AnchorComposite[];
|
|
28
|
+
primitiveOrder: string[];
|
|
28
29
|
target?: PlacementTarget;
|
|
29
30
|
input?: InputMode;
|
|
30
31
|
layout?: LayoutMode;
|
|
@@ -57,43 +58,28 @@ export function startConstructionTrial(
|
|
|
57
58
|
"largetriangle",
|
|
58
59
|
]);
|
|
59
60
|
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
console.log('[ConstructionApp] Received tangrams:', params.tangrams);
|
|
63
|
-
console.log('[ConstructionApp] Number of tangrams:', params.tangrams.length);
|
|
64
|
-
|
|
65
|
-
const sectors: Sector[] = params.tangrams.map((tangramSpec, index) => {
|
|
66
|
-
console.log(`\n[ConstructionApp] Processing tangram ${index}:`, tangramSpec);
|
|
67
|
-
console.log(`[ConstructionApp] tangramID: ${tangramSpec.tangramID}`);
|
|
68
|
-
console.log(`[ConstructionApp] setLabel: ${tangramSpec.setLabel}`);
|
|
69
|
-
console.log(`[ConstructionApp] solutionTans count: ${tangramSpec.solutionTans?.length}`);
|
|
70
|
-
console.log(`[ConstructionApp] solutionTans:`, tangramSpec.solutionTans);
|
|
61
|
+
// make copy of PRIMITIVE_BLUEPRINTS sorted by primitiveOrder
|
|
62
|
+
const PRIMITIVE_BLUEPRINTS_ORDERED = [...PRIMITIVE_BLUEPRINTS].sort((a, b) => params.primitiveOrder.indexOf(a.kind) - params.primitiveOrder.indexOf(b.kind));
|
|
71
63
|
|
|
64
|
+
// Convert JSON plugin parameters to internal Sector[] format
|
|
65
|
+
const sectors: Sector[] = params.tangrams.map((tangramSpec, index) => {
|
|
66
|
+
|
|
72
67
|
// Filter to canonical pieces only and convert vertices to polygon format
|
|
73
68
|
const filteredTans = tangramSpec.solutionTans.filter((tan: any) => {
|
|
74
69
|
// Support both "name" and "kind" fields (different JSON formats)
|
|
75
70
|
const tanName = tan.name ?? tan.kind;
|
|
76
71
|
const isCanonical = CANON.has(tanName);
|
|
77
|
-
console.log(`[ConstructionApp] Tan "${tanName}": canonical=${isCanonical}, vertices count=${tan.vertices?.length}`);
|
|
78
72
|
return isCanonical;
|
|
79
73
|
});
|
|
80
74
|
|
|
81
|
-
console.log(`[ConstructionApp] Filtered to ${filteredTans.length} canonical pieces`);
|
|
82
|
-
|
|
83
75
|
const mask = filteredTans.map((tan: any, tanIndex: number) => {
|
|
84
|
-
const tanName = tan.name ?? tan.kind;
|
|
85
76
|
const polygon = tan.vertices.map(([x, y]: number[]) => ({ x: x ?? 0, y: -(y ?? 0) }));
|
|
86
|
-
console.log(`[ConstructionApp] Polygon ${tanIndex} (${tanName}): ${tan.vertices.length} vertices -> ${polygon.length} points`);
|
|
87
|
-
console.log(`[ConstructionApp] First vertex: [${tan.vertices[0]?.[0]}, ${tan.vertices[0]?.[1]}] -> {x: ${polygon[0]?.x}, y: ${polygon[0]?.y}}`);
|
|
88
77
|
return polygon;
|
|
89
78
|
});
|
|
90
79
|
|
|
91
80
|
// Assign sector ID from alphabetical sequence
|
|
92
81
|
const sectorId = `sector${index}`;
|
|
93
82
|
|
|
94
|
-
console.log(`[ConstructionApp] Assigned sector ID: ${sectorId}`);
|
|
95
|
-
console.log(`[ConstructionApp] Final mask has ${mask.length} polygons`);
|
|
96
|
-
|
|
97
83
|
const sector = {
|
|
98
84
|
id: sectorId,
|
|
99
85
|
tangramId: tangramSpec.tangramID,
|
|
@@ -103,76 +89,50 @@ export function startConstructionTrial(
|
|
|
103
89
|
},
|
|
104
90
|
};
|
|
105
91
|
|
|
106
|
-
console.log(`[ConstructionApp] Created sector:`, sector);
|
|
107
92
|
return sector;
|
|
108
93
|
});
|
|
109
94
|
|
|
110
|
-
console.log('\n[ConstructionApp] Final sectors array:', sectors);
|
|
111
|
-
console.log(`[ConstructionApp] Total sectors created: ${sectors.length}`);
|
|
112
|
-
|
|
113
95
|
// Convert quickstash_macros to Blueprint[] format
|
|
114
96
|
// Handle both anchor-based composites and pre-converted blueprints
|
|
115
|
-
console.log('\n[ConstructionApp] Processing quickstash macros...');
|
|
116
|
-
console.log('[ConstructionApp] quickstash_macros:', params.quickstash_macros);
|
|
117
|
-
console.log('[ConstructionApp] quickstash_macros count:', params.quickstash_macros?.length ?? 0);
|
|
118
|
-
|
|
119
97
|
let quickstash: Blueprint[] = [];
|
|
120
98
|
|
|
121
99
|
if (params.quickstash_macros && params.quickstash_macros.length > 0) {
|
|
122
100
|
// Check if the first item has anchorOffset (anchor-based) or offset (pixel-based)
|
|
123
101
|
const firstMacro = params.quickstash_macros[0];
|
|
124
|
-
console.log('[ConstructionApp] First macro:', firstMacro);
|
|
125
102
|
if (firstMacro && 'parts' in firstMacro && firstMacro.parts && firstMacro.parts[0] && 'anchorOffset' in firstMacro.parts[0]) {
|
|
126
|
-
console.log('[ConstructionApp] Detected anchor-based composites, converting to pixels...');
|
|
127
103
|
|
|
128
104
|
// Create primitive map for conversion
|
|
129
105
|
const primsByKind = new Map<TanKind, PrimitiveBlueprint>();
|
|
130
|
-
|
|
106
|
+
PRIMITIVE_BLUEPRINTS_ORDERED.forEach(p => primsByKind.set(p.kind, p));
|
|
131
107
|
|
|
132
108
|
// Convert each anchor composite to pixel-based blueprint
|
|
133
109
|
quickstash = (params.quickstash_macros as AnchorComposite[]).map(anchorComposite =>
|
|
134
110
|
convertAnchorCompositeToPixels(anchorComposite, primsByKind, CONFIG.layout.grid.stepPx) // Use current CONFIG grid step
|
|
135
111
|
);
|
|
136
|
-
console.log('[ConstructionApp] Converted to pixel-based blueprints:', quickstash);
|
|
137
112
|
} else {
|
|
138
|
-
console.log('[ConstructionApp] Already pixel-based blueprints');
|
|
139
113
|
// Already pixel-based blueprints
|
|
140
114
|
quickstash = params.quickstash_macros as Blueprint[];
|
|
141
115
|
}
|
|
142
116
|
} else {
|
|
143
|
-
console.log('[ConstructionApp] No quickstash macros provided');
|
|
144
117
|
}
|
|
145
118
|
|
|
146
119
|
// Create React root and render GameBoard
|
|
147
120
|
const gameBoardProps = {
|
|
148
121
|
sectors,
|
|
149
122
|
quickstash,
|
|
150
|
-
primitives:
|
|
151
|
-
layout:
|
|
152
|
-
target:
|
|
153
|
-
input:
|
|
154
|
-
timeLimitMs: params.time_limit_ms
|
|
123
|
+
primitives: PRIMITIVE_BLUEPRINTS_ORDERED,
|
|
124
|
+
layout: params.layout as LayoutMode,
|
|
125
|
+
target: params.target as PlacementTarget,
|
|
126
|
+
input: params.input as InputMode,
|
|
127
|
+
timeLimitMs: params.time_limit_ms,
|
|
155
128
|
maxQuickstashSlots: CONFIG.layout.defaults.maxQuickstashSlots,
|
|
156
|
-
mode: 'construction' as const,
|
|
129
|
+
mode: 'construction' as const,
|
|
157
130
|
...(params.onInteraction && { onInteraction: params.onInteraction }),
|
|
158
131
|
...(params.onTrialEnd && { onTrialEnd: params.onTrialEnd })
|
|
159
132
|
};
|
|
160
133
|
|
|
161
|
-
console.log('\n[ConstructionApp] Final GameBoard props:');
|
|
162
|
-
console.log('[ConstructionApp] sectors count:', gameBoardProps.sectors.length);
|
|
163
|
-
console.log('[ConstructionApp] quickstash count:', gameBoardProps.quickstash.length);
|
|
164
|
-
console.log('[ConstructionApp] primitives count:', gameBoardProps.primitives.length);
|
|
165
|
-
console.log('[ConstructionApp] layout:', gameBoardProps.layout);
|
|
166
|
-
console.log('[ConstructionApp] target:', gameBoardProps.target);
|
|
167
|
-
console.log('[ConstructionApp] input:', gameBoardProps.input);
|
|
168
|
-
console.log('[ConstructionApp] timeLimitMs:', gameBoardProps.timeLimitMs);
|
|
169
|
-
console.log('[ConstructionApp] mode:', gameBoardProps.mode);
|
|
170
|
-
console.log('[ConstructionApp] Full props:', gameBoardProps);
|
|
171
|
-
|
|
172
134
|
const root = createRoot(display_element);
|
|
173
135
|
root.render(React.createElement(GameBoard, gameBoardProps));
|
|
174
136
|
|
|
175
|
-
console.log('[ConstructionApp] GameBoard rendered successfully');
|
|
176
|
-
|
|
177
137
|
return { root, display_element, jsPsych: _jsPsych };
|
|
178
138
|
}
|
|
@@ -17,6 +17,12 @@ const info = {
|
|
|
17
17
|
default: [],
|
|
18
18
|
description: "Array of MacroSpec objects created in prep trial"
|
|
19
19
|
},
|
|
20
|
+
/** Array of primitive names in the order they should be displayed */
|
|
21
|
+
primitive_order: {
|
|
22
|
+
type: ParameterType.OBJECT,
|
|
23
|
+
default: ["square", "smalltriangle", "parallelogram", "medtriangle", "largetriangle"],
|
|
24
|
+
description: "Array of primitive names in the order they should be displayed"
|
|
25
|
+
},
|
|
20
26
|
/** Whether to place pieces in workspace or directly on silhouette */
|
|
21
27
|
target: {
|
|
22
28
|
type: ParameterType.SELECT,
|
|
@@ -137,10 +143,11 @@ class TangramConstructPlugin implements JsPsychPlugin<Info> {
|
|
|
137
143
|
const params: StartConstructionTrialParams = {
|
|
138
144
|
tangrams: trial.tangrams,
|
|
139
145
|
quickstash_macros: trial.quickstash_macros,
|
|
146
|
+
primitiveOrder: trial.primitive_order,
|
|
140
147
|
target: trial.target,
|
|
141
148
|
input: trial.input,
|
|
142
149
|
layout: trial.layout,
|
|
143
|
-
time_limit_ms: trial.time_limit_ms
|
|
150
|
+
time_limit_ms: trial.time_limit_ms,
|
|
144
151
|
onInteraction: trial.onInteraction,
|
|
145
152
|
onTrialEnd: wrappedOnTrialEnd
|
|
146
153
|
};
|
|
@@ -32,6 +32,7 @@ export interface StartPrepTrialParams {
|
|
|
32
32
|
layoutMode: "circle" | "semicircle";
|
|
33
33
|
requireAllSlots: boolean;
|
|
34
34
|
quickstashMacros?: AnchorComposite[];
|
|
35
|
+
primitiveOrder: string[];
|
|
35
36
|
onInteraction?: (event: any) => void;
|
|
36
37
|
onTrialEnd?: (data: any) => void;
|
|
37
38
|
}
|
|
@@ -53,10 +54,14 @@ export function startPrepTrial(
|
|
|
53
54
|
layoutMode,
|
|
54
55
|
requireAllSlots,
|
|
55
56
|
quickstashMacros,
|
|
57
|
+
primitiveOrder,
|
|
56
58
|
onInteraction,
|
|
57
59
|
onTrialEnd,
|
|
58
60
|
} = params;
|
|
59
61
|
|
|
62
|
+
// make copy of PRIMITIVE_BLUEPRINTS sorted by primitiveOrder
|
|
63
|
+
const PRIMITIVE_BLUEPRINTS_ORDERED = [...PRIMITIVE_BLUEPRINTS].sort((a, b) => primitiveOrder.indexOf(a.kind) - primitiveOrder.indexOf(b.kind));
|
|
64
|
+
|
|
60
65
|
// Create blank prep sectors (no silhouettes)
|
|
61
66
|
const prepSectors: Sector[] = Array.from({ length: numQuickstashSlots }, (_, i) => ({
|
|
62
67
|
id: `prep-sector-${i}`,
|
|
@@ -75,7 +80,8 @@ export function startPrepTrial(
|
|
|
75
80
|
if (quickstashMacros && quickstashMacros.length > 0 && layout) {
|
|
76
81
|
// Spawn pieces immediately when controller and layout are ready
|
|
77
82
|
const primsByKind = new Map<TanKind, PrimitiveBlueprint>();
|
|
78
|
-
|
|
83
|
+
|
|
84
|
+
PRIMITIVE_BLUEPRINTS_ORDERED.forEach(p => primsByKind.set(p.kind, p));
|
|
79
85
|
|
|
80
86
|
quickstashMacros.forEach((anchorComposite, macroIndex) => {
|
|
81
87
|
const sectorId = `prep-sector-${macroIndex}`;
|
|
@@ -164,7 +170,7 @@ export function startPrepTrial(
|
|
|
164
170
|
root.render(React.createElement(GameBoard, {
|
|
165
171
|
sectors: prepSectors,
|
|
166
172
|
quickstash: [], // No pre-made macros
|
|
167
|
-
primitives:
|
|
173
|
+
primitives: PRIMITIVE_BLUEPRINTS_ORDERED,
|
|
168
174
|
layout: layoutMode,
|
|
169
175
|
target: 'workspace', // Pieces go in sectors
|
|
170
176
|
input: inputMode,
|
|
@@ -41,6 +41,12 @@ const info = {
|
|
|
41
41
|
default: [],
|
|
42
42
|
description: "Array of AnchorComposite objects to edit as primitive pieces"
|
|
43
43
|
},
|
|
44
|
+
/** Array of primitive names in the order they should be displayed */
|
|
45
|
+
primitive_order: {
|
|
46
|
+
type: ParameterType.OBJECT,
|
|
47
|
+
default: ["square", "smalltriangle", "parallelogram", "medtriangle", "largetriangle"],
|
|
48
|
+
description: "Array of primitive names in the order they should be displayed"
|
|
49
|
+
},
|
|
44
50
|
/** Callback fired after each interaction (optional analytics hook) */
|
|
45
51
|
onInteraction: {
|
|
46
52
|
type: ParameterType.FUNCTION,
|
|
@@ -101,13 +107,14 @@ class TangramPrepPlugin implements JsPsychPlugin<Info> {
|
|
|
101
107
|
};
|
|
102
108
|
|
|
103
109
|
const params: StartPrepTrialParams = {
|
|
104
|
-
numQuickstashSlots: trial.num_quickstash_slots
|
|
110
|
+
numQuickstashSlots: trial.num_quickstash_slots,
|
|
105
111
|
maxPiecesPerMacro: trial.max_pieces_per_macro,
|
|
106
112
|
minPiecesPerMacro: trial.min_pieces_per_macro,
|
|
107
113
|
inputMode: trial.input as "click" | "drag",
|
|
108
114
|
layoutMode: trial.layout as "circle" | "semicircle",
|
|
109
115
|
requireAllSlots: trial.require_all_slots,
|
|
110
116
|
quickstashMacros: trial.quickstash_macros,
|
|
117
|
+
primitiveOrder: trial.primitive_order,
|
|
111
118
|
onInteraction: trial.onInteraction,
|
|
112
119
|
onTrialEnd: wrappedOnTrialEnd,
|
|
113
120
|
};
|