jspsych-tangram 0.0.12 → 0.0.14
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 +283 -64
- package/dist/construct/index.browser.js.map +1 -1
- package/dist/construct/index.browser.min.js +11 -11
- package/dist/construct/index.browser.min.js.map +1 -1
- package/dist/construct/index.cjs +283 -64
- package/dist/construct/index.cjs.map +1 -1
- package/dist/construct/index.d.ts +36 -0
- package/dist/construct/index.js +283 -64
- package/dist/construct/index.js.map +1 -1
- package/dist/index.cjs +394 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +84 -0
- package/dist/index.js +394 -94
- package/dist/index.js.map +1 -1
- package/dist/nback/index.browser.js +112 -37
- package/dist/nback/index.browser.js.map +1 -1
- package/dist/nback/index.browser.min.js +11 -11
- package/dist/nback/index.browser.min.js.map +1 -1
- package/dist/nback/index.cjs +112 -37
- package/dist/nback/index.cjs.map +1 -1
- package/dist/nback/index.d.ts +24 -0
- package/dist/nback/index.js +112 -37
- package/dist/nback/index.js.map +1 -1
- package/dist/prep/index.browser.js +278 -65
- 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 +278 -65
- package/dist/prep/index.cjs.map +1 -1
- package/dist/prep/index.d.ts +24 -0
- package/dist/prep/index.js +278 -65
- 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 +39 -44
- package/src/core/components/board/useGameBoard.ts +52 -0
- package/src/core/components/index.ts +4 -2
- package/src/core/components/pieces/BlueprintRing.tsx +100 -67
- package/src/core/components/pieces/useBlueprintRing.ts +39 -0
- 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 +88 -29
- 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 +11 -11
- package/tangram-nback.min.js +11 -11
- package/tangram-prep.min.js +11 -11
package/dist/construct/index.cjs
CHANGED
|
@@ -7,30 +7,41 @@ var uuid = require('uuid');
|
|
|
7
7
|
|
|
8
8
|
const CONFIG = {
|
|
9
9
|
color: {
|
|
10
|
+
background: "#fff7e0ff",
|
|
10
11
|
bands: {
|
|
11
|
-
silhouette: { fillEven: "#
|
|
12
|
-
workspace: { fillEven: "#
|
|
12
|
+
silhouette: { fillEven: "#ffffff", fillOdd: "#ffffff", stroke: "#b1b1b1" },
|
|
13
|
+
workspace: { fillEven: "#ffffff", fillOdd: "#ffffff", stroke: "#b1b1b1" }
|
|
13
14
|
},
|
|
14
|
-
completion: { fill: "#
|
|
15
|
+
completion: { fill: "#ccffcc", stroke: "#13da57" },
|
|
15
16
|
silhouetteMask: "#374151",
|
|
16
17
|
anchors: { invalid: "#7dd3fc", valid: "#475569" },
|
|
17
|
-
|
|
18
|
+
// validFill used here for placed composites
|
|
19
|
+
piece: { draggingFill: "#8e7cc3", validFill: "#8e7cc3", invalidFill: "#d55c00", invalidStroke: "#dc2626", selectedStroke: "#674ea7", allGreenStroke: "#86efac", borderStroke: "#674ea7" },
|
|
18
20
|
ui: { light: "#60a5fa", dark: "#1d4ed8" },
|
|
19
21
|
blueprint: { fill: "#374151", selectedStroke: "#111827", badgeFill: "#000000", labelFill: "#ffffff" },
|
|
20
|
-
tangramDecomposition: { stroke: "#fef2cc" }
|
|
22
|
+
tangramDecomposition: { stroke: "#fef2cc" },
|
|
23
|
+
primitiveColors: [
|
|
24
|
+
// from seaborn "colorblind" palette, 6 colors, with red omitted
|
|
25
|
+
"#0173b2",
|
|
26
|
+
"#de8f05",
|
|
27
|
+
"#029e73",
|
|
28
|
+
"#cc78bc",
|
|
29
|
+
"#ca9161"
|
|
30
|
+
]
|
|
21
31
|
},
|
|
22
32
|
opacity: {
|
|
23
|
-
blueprint: 0.
|
|
33
|
+
blueprint: 0.6,
|
|
24
34
|
silhouetteMask: 0.25,
|
|
25
35
|
//anchors: { valid: 0.80, invalid: 0.50 },
|
|
26
36
|
anchors: { invalid: 0, valid: 0 },
|
|
27
|
-
piece: { invalid:
|
|
37
|
+
piece: { invalid: 1, dragging: 1, locked: 1, normal: 1 }
|
|
28
38
|
},
|
|
29
39
|
size: {
|
|
30
|
-
stroke: { bandPx: 5, pieceSelectedPx:
|
|
40
|
+
stroke: { bandPx: 5, pieceSelectedPx: 5, allGreenStrokePx: 10, pieceBorderPx: 2, tangramDecompositionPx: 1 },
|
|
31
41
|
anchorRadiusPx: { valid: 1, invalid: 1 },
|
|
32
42
|
badgeFontPx: 16,
|
|
33
|
-
centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 }
|
|
43
|
+
centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 },
|
|
44
|
+
invalidMarker: { sizePx: 10, strokePx: 4 }
|
|
34
45
|
},
|
|
35
46
|
layout: {
|
|
36
47
|
grid: { stepPx: 20, unitPx: 40 },
|
|
@@ -48,7 +59,8 @@ const CONFIG = {
|
|
|
48
59
|
game: {
|
|
49
60
|
snapRadiusPx: 15,
|
|
50
61
|
showBorders: false,
|
|
51
|
-
hideTouchingBorders: true
|
|
62
|
+
hideTouchingBorders: true,
|
|
63
|
+
silhouettesBelowPieces: true
|
|
52
64
|
}
|
|
53
65
|
};
|
|
54
66
|
|
|
@@ -832,6 +844,31 @@ var unlockedIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAYAAA
|
|
|
832
844
|
function pathD(poly) {
|
|
833
845
|
return `M ${poly.map((pt) => `${pt.x} ${pt.y}`).join(" L ")} Z`;
|
|
834
846
|
}
|
|
847
|
+
function getPieceColor(blueprint, usePrimitiveColors, defaultColor, primitiveColorIndices) {
|
|
848
|
+
if (!usePrimitiveColors) {
|
|
849
|
+
return defaultColor;
|
|
850
|
+
}
|
|
851
|
+
if ("kind" in blueprint) {
|
|
852
|
+
const kind = blueprint.kind;
|
|
853
|
+
const kindToIndex = {
|
|
854
|
+
"square": 0,
|
|
855
|
+
"smalltriangle": 1,
|
|
856
|
+
"parallelogram": 2,
|
|
857
|
+
"medtriangle": 3,
|
|
858
|
+
"largetriangle": 4
|
|
859
|
+
};
|
|
860
|
+
const primitiveIndex = kindToIndex[kind];
|
|
861
|
+
if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
|
|
862
|
+
const colorIndex = primitiveColorIndices[primitiveIndex];
|
|
863
|
+
const color = CONFIG.color.primitiveColors[colorIndex];
|
|
864
|
+
if (color) {
|
|
865
|
+
return color;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return defaultColor;
|
|
869
|
+
}
|
|
870
|
+
return defaultColor;
|
|
871
|
+
}
|
|
835
872
|
function BoardView(props) {
|
|
836
873
|
const {
|
|
837
874
|
controller,
|
|
@@ -851,6 +888,9 @@ function BoardView(props) {
|
|
|
851
888
|
dragInvalid,
|
|
852
889
|
lockedPieceId,
|
|
853
890
|
showTangramDecomposition,
|
|
891
|
+
usePrimitiveColorsBlueprints,
|
|
892
|
+
usePrimitiveColorsTargets,
|
|
893
|
+
primitiveColorIndices,
|
|
854
894
|
svgRef,
|
|
855
895
|
setPieceRef,
|
|
856
896
|
onPiecePointerDown,
|
|
@@ -861,6 +901,144 @@ function BoardView(props) {
|
|
|
861
901
|
onCenterBadgePointerDown
|
|
862
902
|
} = props;
|
|
863
903
|
const VW = viewBox.w, VH = viewBox.h;
|
|
904
|
+
const renderSilhouettes = () => layout.sectors.map((s) => {
|
|
905
|
+
const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
|
|
906
|
+
if (showTangramDecomposition && sectorCfg?.silhouette.primitiveDecomposition) {
|
|
907
|
+
const primitiveDecomposition = sectorCfg.silhouette.primitiveDecomposition;
|
|
908
|
+
const rect = rectForBand(layout, s, "silhouette", 1);
|
|
909
|
+
const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
|
|
910
|
+
const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, { cx: rect.cx, cy: rect.cy });
|
|
911
|
+
return /* @__PURE__ */ React.createElement("g", { key: `sil-decomposed-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => {
|
|
912
|
+
const primInfo = primitiveDecomposition[i];
|
|
913
|
+
let fillColor = CONFIG.color.silhouetteMask;
|
|
914
|
+
if (usePrimitiveColorsTargets && primInfo?.kind && primitiveColorIndices) {
|
|
915
|
+
const kindToIndex = {
|
|
916
|
+
"square": 0,
|
|
917
|
+
"smalltriangle": 1,
|
|
918
|
+
"parallelogram": 2,
|
|
919
|
+
"medtriangle": 3,
|
|
920
|
+
"largetriangle": 4
|
|
921
|
+
};
|
|
922
|
+
const primitiveIndex = kindToIndex[primInfo.kind];
|
|
923
|
+
if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
|
|
924
|
+
const colorIndex = primitiveColorIndices[primitiveIndex];
|
|
925
|
+
const color = CONFIG.color.primitiveColors[colorIndex];
|
|
926
|
+
if (color) {
|
|
927
|
+
fillColor = color;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return /* @__PURE__ */ React.createElement(
|
|
932
|
+
"path",
|
|
933
|
+
{
|
|
934
|
+
key: `prim-fill-${i}`,
|
|
935
|
+
d: pathD(scaledPoly),
|
|
936
|
+
fill: fillColor,
|
|
937
|
+
opacity: CONFIG.opacity.silhouetteMask,
|
|
938
|
+
stroke: "none"
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
}));
|
|
942
|
+
} else {
|
|
943
|
+
const placedPolys = placedSilBySector.get(s.id) ?? [];
|
|
944
|
+
if (!placedPolys.length) return null;
|
|
945
|
+
return /* @__PURE__ */ React.createElement("g", { key: `sil-${s.id}`, pointerEvents: "none" }, placedPolys.map((poly, i) => /* @__PURE__ */ React.createElement("path", { key: i, d: pathD(poly), fill: CONFIG.color.silhouetteMask, opacity: CONFIG.opacity.silhouetteMask })));
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
const renderPieces = (piecesFilter) => {
|
|
949
|
+
const piecesToRender = pieces;
|
|
950
|
+
return piecesToRender.sort((a, b) => {
|
|
951
|
+
if (draggingId === a.id) return 1;
|
|
952
|
+
if (draggingId === b.id) return -1;
|
|
953
|
+
return 0;
|
|
954
|
+
}).map((p) => {
|
|
955
|
+
const bp = controller.getBlueprint(p.blueprintId);
|
|
956
|
+
const bb = boundsOfBlueprint(bp, (k) => controller.getPrimitive(k));
|
|
957
|
+
const isDragging = draggingId === p.id;
|
|
958
|
+
const locked = p.sectorId && controller.isSectorCompleted(p.sectorId);
|
|
959
|
+
const isConnectivityLocked = lockedPieceId === p.id;
|
|
960
|
+
selectedPieceId === p.id;
|
|
961
|
+
const isCarriedInvalid = isDragging && dragInvalid;
|
|
962
|
+
const translateX = p.x - bb.min.x;
|
|
963
|
+
const translateY = p.y - bb.min.y;
|
|
964
|
+
const validFillColor = getPieceColor(
|
|
965
|
+
bp,
|
|
966
|
+
usePrimitiveColorsBlueprints || false,
|
|
967
|
+
CONFIG.color.piece.validFill,
|
|
968
|
+
primitiveColorIndices || [0, 1, 2, 3, 4]
|
|
969
|
+
);
|
|
970
|
+
const draggingFillColor = getPieceColor(
|
|
971
|
+
bp,
|
|
972
|
+
usePrimitiveColorsBlueprints || false,
|
|
973
|
+
CONFIG.color.piece.draggingFill,
|
|
974
|
+
primitiveColorIndices || [0, 1, 2, 3, 4]
|
|
975
|
+
);
|
|
976
|
+
return /* @__PURE__ */ React.createElement("g", { key: p.id, ref: setPieceRef(p.id), transform: `translate(${translateX}, ${translateY})`, style: { cursor: locked ? "default" : clickMode ? "pointer" : "grab" }, pointerEvents: clickMode && isDragging ? "none" : "auto" }, ("shape" in bp ? bp.shape : []).map((poly, idx) => {
|
|
977
|
+
const showBorders = shouldShowBorders();
|
|
978
|
+
shouldUseSelectiveBorders(p.blueprintId);
|
|
979
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, { key: idx }, /* @__PURE__ */ React.createElement(
|
|
980
|
+
"path",
|
|
981
|
+
{
|
|
982
|
+
d: pathD(poly),
|
|
983
|
+
fill: isConnectivityLocked ? CONFIG.color.piece.invalidFill : isCarriedInvalid ? CONFIG.color.piece.invalidFill : isDragging ? draggingFillColor : validFillColor,
|
|
984
|
+
opacity: isConnectivityLocked ? CONFIG.opacity.piece.invalid : isCarriedInvalid ? CONFIG.opacity.piece.invalid : isDragging ? CONFIG.opacity.piece.dragging : locked ? CONFIG.opacity.piece.locked : CONFIG.opacity.piece.normal,
|
|
985
|
+
stroke: "none",
|
|
986
|
+
onPointerDown: (e) => onPiecePointerDown(e, p)
|
|
987
|
+
}
|
|
988
|
+
), showBorders);
|
|
989
|
+
}), isDragging && isCarriedInvalid && "shape" in bp && bp.shape.length > 0 && (() => {
|
|
990
|
+
const { cx, cy } = polysAABB$1(bp.shape);
|
|
991
|
+
const size = CONFIG.size.invalidMarker.sizePx;
|
|
992
|
+
const strokeWidth = CONFIG.size.invalidMarker.strokePx;
|
|
993
|
+
const borderWidth = strokeWidth + 2;
|
|
994
|
+
return /* @__PURE__ */ React.createElement("g", { key: "invalid-marker" }, /* @__PURE__ */ React.createElement(
|
|
995
|
+
"line",
|
|
996
|
+
{
|
|
997
|
+
x1: cx - size,
|
|
998
|
+
y1: cy - size,
|
|
999
|
+
x2: cx + size,
|
|
1000
|
+
y2: cy + size,
|
|
1001
|
+
stroke: "white",
|
|
1002
|
+
strokeWidth: borderWidth,
|
|
1003
|
+
strokeLinecap: "round"
|
|
1004
|
+
}
|
|
1005
|
+
), /* @__PURE__ */ React.createElement(
|
|
1006
|
+
"line",
|
|
1007
|
+
{
|
|
1008
|
+
x1: cx - size,
|
|
1009
|
+
y1: cy - size,
|
|
1010
|
+
x2: cx + size,
|
|
1011
|
+
y2: cy + size,
|
|
1012
|
+
stroke: CONFIG.color.piece.invalidStroke,
|
|
1013
|
+
strokeWidth,
|
|
1014
|
+
strokeLinecap: "round"
|
|
1015
|
+
}
|
|
1016
|
+
), /* @__PURE__ */ React.createElement(
|
|
1017
|
+
"line",
|
|
1018
|
+
{
|
|
1019
|
+
x1: cx + size,
|
|
1020
|
+
y1: cy - size,
|
|
1021
|
+
x2: cx - size,
|
|
1022
|
+
y2: cy + size,
|
|
1023
|
+
stroke: "white",
|
|
1024
|
+
strokeWidth: borderWidth,
|
|
1025
|
+
strokeLinecap: "round"
|
|
1026
|
+
}
|
|
1027
|
+
), /* @__PURE__ */ React.createElement(
|
|
1028
|
+
"line",
|
|
1029
|
+
{
|
|
1030
|
+
x1: cx + size,
|
|
1031
|
+
y1: cy - size,
|
|
1032
|
+
x2: cx - size,
|
|
1033
|
+
y2: cy + size,
|
|
1034
|
+
stroke: CONFIG.color.piece.invalidStroke,
|
|
1035
|
+
strokeWidth,
|
|
1036
|
+
strokeLinecap: "round"
|
|
1037
|
+
}
|
|
1038
|
+
));
|
|
1039
|
+
})());
|
|
1040
|
+
});
|
|
1041
|
+
};
|
|
864
1042
|
const centerView = controller.state.blueprintView;
|
|
865
1043
|
const bps = centerView === "primitives" ? controller.state.primitives : controller.state.quickstash;
|
|
866
1044
|
const QS_SLOTS = controller.state.cfg.maxQuickstashSlots;
|
|
@@ -881,6 +1059,12 @@ function BoardView(props) {
|
|
|
881
1059
|
const bb = boundsOfBlueprint(bp, (k) => controller.getPrimitive(k));
|
|
882
1060
|
const cx = bb.min.x + bb.width / 2;
|
|
883
1061
|
const cy = bb.min.y + bb.height / 2;
|
|
1062
|
+
const fillColor = getPieceColor(
|
|
1063
|
+
bp,
|
|
1064
|
+
usePrimitiveColorsBlueprints || false,
|
|
1065
|
+
CONFIG.color.blueprint.fill,
|
|
1066
|
+
primitiveColorIndices || [0, 1, 2, 3, 4]
|
|
1067
|
+
);
|
|
884
1068
|
return /* @__PURE__ */ React.createElement(
|
|
885
1069
|
"g",
|
|
886
1070
|
{
|
|
@@ -892,7 +1076,7 @@ function BoardView(props) {
|
|
|
892
1076
|
{
|
|
893
1077
|
key: idx,
|
|
894
1078
|
d: pathD(poly),
|
|
895
|
-
fill:
|
|
1079
|
+
fill: fillColor,
|
|
896
1080
|
opacity: CONFIG.opacity.blueprint,
|
|
897
1081
|
stroke: "none",
|
|
898
1082
|
strokeWidth: 0,
|
|
@@ -916,7 +1100,7 @@ function BoardView(props) {
|
|
|
916
1100
|
onPointerDown: (e) => {
|
|
917
1101
|
onRootPointerDown(e);
|
|
918
1102
|
},
|
|
919
|
-
style: { background:
|
|
1103
|
+
style: { background: CONFIG.color.background, touchAction: "none", userSelect: "none" }
|
|
920
1104
|
},
|
|
921
1105
|
layout.sectors.map((s, i) => {
|
|
922
1106
|
const done = !!controller.state.sectors[s.id].completedAt;
|
|
@@ -926,35 +1110,7 @@ function BoardView(props) {
|
|
|
926
1110
|
const work = layout.bands.workspace;
|
|
927
1111
|
return /* @__PURE__ */ React.createElement("g", { key: `bands-${s.id}` }, controller.state.cfg.target === "workspace" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("path", { d: wedgePath(layout.cx, layout.cy, sil[0], sil[1], s.start, s.end), fill: baseSil, stroke: CONFIG.color.bands.silhouette.stroke, strokeWidth: CONFIG.size.stroke.bandPx, pointerEvents: "none" }), /* @__PURE__ */ React.createElement("path", { d: wedgePath(layout.cx, layout.cy, work[0], work[1], s.start, s.end), fill: done ? CONFIG.color.completion.fill : baseWork, stroke: done ? CONFIG.color.completion.stroke : CONFIG.color.bands.workspace.stroke, strokeWidth: CONFIG.size.stroke.bandPx, pointerEvents: "none" })) : /* @__PURE__ */ React.createElement("path", { d: wedgePath(layout.cx, layout.cy, sil[0], sil[1], s.start, s.end), fill: done ? CONFIG.color.completion.fill : baseSil, stroke: done ? CONFIG.color.completion.stroke : CONFIG.color.bands.silhouette.stroke, strokeWidth: CONFIG.size.stroke.bandPx, pointerEvents: "none" }));
|
|
928
1112
|
}),
|
|
929
|
-
|
|
930
|
-
if (draggingId === a.id) return 1;
|
|
931
|
-
if (draggingId === b.id) return -1;
|
|
932
|
-
return 0;
|
|
933
|
-
}).map((p) => {
|
|
934
|
-
const bp = controller.getBlueprint(p.blueprintId);
|
|
935
|
-
const bb = boundsOfBlueprint(bp, (k) => controller.getPrimitive(k));
|
|
936
|
-
const isDragging = draggingId === p.id;
|
|
937
|
-
const locked = p.sectorId && controller.isSectorCompleted(p.sectorId);
|
|
938
|
-
const isConnectivityLocked = lockedPieceId === p.id;
|
|
939
|
-
selectedPieceId === p.id;
|
|
940
|
-
const isCarriedInvalid = isDragging && dragInvalid;
|
|
941
|
-
const translateX = p.x - bb.min.x;
|
|
942
|
-
const translateY = p.y - bb.min.y;
|
|
943
|
-
return /* @__PURE__ */ React.createElement("g", { key: p.id, ref: setPieceRef(p.id), transform: `translate(${translateX}, ${translateY})`, style: { cursor: locked ? "default" : clickMode ? "pointer" : "grab" }, pointerEvents: clickMode && isDragging ? "none" : "auto" }, ("shape" in bp ? bp.shape : []).map((poly, idx) => {
|
|
944
|
-
const showBorders = shouldShowBorders();
|
|
945
|
-
shouldUseSelectiveBorders(p.blueprintId);
|
|
946
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, { key: idx }, /* @__PURE__ */ React.createElement(
|
|
947
|
-
"path",
|
|
948
|
-
{
|
|
949
|
-
d: pathD(poly),
|
|
950
|
-
fill: isConnectivityLocked ? CONFIG.color.piece.invalidFill : isCarriedInvalid ? CONFIG.color.piece.invalidFill : isDragging ? CONFIG.color.piece.draggingFill : CONFIG.color.piece.validFill,
|
|
951
|
-
opacity: isConnectivityLocked ? CONFIG.opacity.piece.invalid : isCarriedInvalid ? CONFIG.opacity.piece.invalid : isDragging ? CONFIG.opacity.piece.dragging : locked ? CONFIG.opacity.piece.locked : CONFIG.opacity.piece.normal,
|
|
952
|
-
stroke: "none",
|
|
953
|
-
onPointerDown: (e) => onPiecePointerDown(e, p)
|
|
954
|
-
}
|
|
955
|
-
), showBorders);
|
|
956
|
-
}));
|
|
957
|
-
}),
|
|
1113
|
+
/* @__PURE__ */ React.createElement(React.Fragment, null, renderSilhouettes(), renderPieces()) ,
|
|
958
1114
|
layout.sectors.map((s) => {
|
|
959
1115
|
const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
|
|
960
1116
|
if (showTangramDecomposition && sectorCfg?.silhouette.primitiveDecomposition) {
|
|
@@ -962,23 +1118,32 @@ function BoardView(props) {
|
|
|
962
1118
|
const rect = rectForBand(layout, s, "silhouette", 1);
|
|
963
1119
|
const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
|
|
964
1120
|
const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, { cx: rect.cx, cy: rect.cy });
|
|
965
|
-
return /* @__PURE__ */ React.createElement("g", { key: `sil-decomposed-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) =>
|
|
966
|
-
|
|
967
|
-
{
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
strokeWidth: CONFIG.size.stroke.tangramDecompositionPx
|
|
1121
|
+
return /* @__PURE__ */ React.createElement("g", { key: `sil-decomposed-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => {
|
|
1122
|
+
const primInfo = primitiveDecomposition[i];
|
|
1123
|
+
if (usePrimitiveColorsTargets && primInfo?.kind && primitiveColorIndices) {
|
|
1124
|
+
const kindToIndex = {
|
|
1125
|
+
"square": 0,
|
|
1126
|
+
"smalltriangle": 1,
|
|
1127
|
+
"parallelogram": 2,
|
|
1128
|
+
"medtriangle": 3,
|
|
1129
|
+
"largetriangle": 4
|
|
1130
|
+
};
|
|
1131
|
+
const primitiveIndex = kindToIndex[primInfo.kind];
|
|
1132
|
+
if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
|
|
1133
|
+
primitiveColorIndices[primitiveIndex];
|
|
1134
|
+
}
|
|
980
1135
|
}
|
|
981
|
-
|
|
1136
|
+
return /* @__PURE__ */ React.createElement(
|
|
1137
|
+
"path",
|
|
1138
|
+
{
|
|
1139
|
+
key: `prim-fill-${i}`,
|
|
1140
|
+
d: pathD(scaledPoly),
|
|
1141
|
+
fill: "none",
|
|
1142
|
+
opacity: 0,
|
|
1143
|
+
stroke: "none"
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
}));
|
|
982
1147
|
} else {
|
|
983
1148
|
const placedPolys = placedSilBySector.get(s.id) ?? [];
|
|
984
1149
|
if (!placedPolys.length) return null;
|
|
@@ -1069,7 +1234,25 @@ function BoardView(props) {
|
|
|
1069
1234
|
const by = layout.cy + blueprintRingR * Math.sin(theta);
|
|
1070
1235
|
return renderBlueprintGlyph(bp, bx, by);
|
|
1071
1236
|
}),
|
|
1072
|
-
controller.state.endedAt && /* @__PURE__ */ React.createElement("g", { pointerEvents: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: layout.cx, cy: layout.cy, r: layout.outerR - 3, fill: "none", stroke: CONFIG.color.piece.allGreenStroke, strokeWidth: CONFIG.size.stroke.allGreenStrokePx, opacity: 0 }, /* @__PURE__ */ React.createElement("animate", { attributeName: "opacity", from: "0", to: "1", dur: "160ms", fill: "freeze" }), /* @__PURE__ */ React.createElement("animate", { attributeName: "opacity", from: "1", to: "0", begin: "560ms", dur: "520ms", fill: "freeze" })))
|
|
1237
|
+
controller.state.endedAt && /* @__PURE__ */ React.createElement("g", { pointerEvents: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: layout.cx, cy: layout.cy, r: layout.outerR - 3, fill: "none", stroke: CONFIG.color.piece.allGreenStroke, strokeWidth: CONFIG.size.stroke.allGreenStrokePx, opacity: 0 }, /* @__PURE__ */ React.createElement("animate", { attributeName: "opacity", from: "0", to: "1", dur: "160ms", fill: "freeze" }), /* @__PURE__ */ React.createElement("animate", { attributeName: "opacity", from: "1", to: "0", begin: "560ms", dur: "520ms", fill: "freeze" }))),
|
|
1238
|
+
showTangramDecomposition && layout.sectors.map((s) => {
|
|
1239
|
+
const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
|
|
1240
|
+
if (!sectorCfg?.silhouette.primitiveDecomposition) return null;
|
|
1241
|
+
const primitiveDecomposition = sectorCfg.silhouette.primitiveDecomposition;
|
|
1242
|
+
const rect = rectForBand(layout, s, "silhouette", 1);
|
|
1243
|
+
const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
|
|
1244
|
+
const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, { cx: rect.cx, cy: rect.cy });
|
|
1245
|
+
return /* @__PURE__ */ React.createElement("g", { key: `sil-borders-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => /* @__PURE__ */ React.createElement(
|
|
1246
|
+
"path",
|
|
1247
|
+
{
|
|
1248
|
+
key: `prim-border-${i}`,
|
|
1249
|
+
d: pathD(scaledPoly),
|
|
1250
|
+
fill: "none",
|
|
1251
|
+
stroke: CONFIG.color.tangramDecomposition.stroke,
|
|
1252
|
+
strokeWidth: CONFIG.size.stroke.tangramDecompositionPx
|
|
1253
|
+
}
|
|
1254
|
+
)));
|
|
1255
|
+
})
|
|
1073
1256
|
);
|
|
1074
1257
|
}
|
|
1075
1258
|
|
|
@@ -2954,7 +3137,10 @@ function GameBoard(props) {
|
|
|
2954
3137
|
onPieceRemove,
|
|
2955
3138
|
onInteraction,
|
|
2956
3139
|
onTrialEnd,
|
|
2957
|
-
onControllerReady
|
|
3140
|
+
onControllerReady,
|
|
3141
|
+
usePrimitiveColorsBlueprints,
|
|
3142
|
+
usePrimitiveColorsTargets,
|
|
3143
|
+
primitiveColorIndices
|
|
2958
3144
|
} = props;
|
|
2959
3145
|
const [timeRemaining, setTimeRemaining] = React.useState(timeLimitMs > 0 ? Math.floor(timeLimitMs / 1e3) : 0);
|
|
2960
3146
|
const controller = React.useMemo(() => {
|
|
@@ -3278,7 +3464,7 @@ function GameBoard(props) {
|
|
|
3278
3464
|
justifyContent: "center",
|
|
3279
3465
|
alignItems: "center",
|
|
3280
3466
|
padding: "20px",
|
|
3281
|
-
background:
|
|
3467
|
+
background: CONFIG.color.background,
|
|
3282
3468
|
flex: "0 0 auto"
|
|
3283
3469
|
};
|
|
3284
3470
|
const headerContentStyle = {
|
|
@@ -3294,14 +3480,20 @@ function GameBoard(props) {
|
|
|
3294
3480
|
lineHeight: 1.5,
|
|
3295
3481
|
textAlign: "center"
|
|
3296
3482
|
};
|
|
3483
|
+
const scaleX = svgDimensions.width / viewBox.w;
|
|
3484
|
+
const rightEdgeOfSemicircleLogical = viewBox.w / 2 + layout.outerR;
|
|
3485
|
+
const distanceFromRightEdgeLogical = viewBox.w - rightEdgeOfSemicircleLogical;
|
|
3486
|
+
const distanceFromRightEdgePx = distanceFromRightEdgeLogical * scaleX;
|
|
3487
|
+
const charWidth = 24 * 0.6;
|
|
3488
|
+
const offsetLeft = charWidth * 5;
|
|
3297
3489
|
const timerStyle = {
|
|
3298
3490
|
position: "absolute",
|
|
3299
|
-
right:
|
|
3491
|
+
right: `${distanceFromRightEdgePx + offsetLeft}px`,
|
|
3300
3492
|
fontSize: "24px",
|
|
3301
3493
|
fontWeight: "bold",
|
|
3302
3494
|
fontFamily: "monospace",
|
|
3303
3495
|
color: "#333",
|
|
3304
|
-
|
|
3496
|
+
whiteSpace: "nowrap",
|
|
3305
3497
|
textAlign: "right"
|
|
3306
3498
|
};
|
|
3307
3499
|
const gameboardWrapperStyle = {
|
|
@@ -3346,6 +3538,9 @@ function GameBoard(props) {
|
|
|
3346
3538
|
onCenterBadgePointerDown,
|
|
3347
3539
|
showTangramDecomposition: showTangramDecomposition ?? false,
|
|
3348
3540
|
scaleS,
|
|
3541
|
+
usePrimitiveColorsBlueprints: usePrimitiveColorsBlueprints ?? false,
|
|
3542
|
+
usePrimitiveColorsTargets: usePrimitiveColorsTargets ?? false,
|
|
3543
|
+
primitiveColorIndices: primitiveColorIndices ?? [0, 1, 2, 3, 4],
|
|
3349
3544
|
...eventCallbacks
|
|
3350
3545
|
}
|
|
3351
3546
|
))));
|
|
@@ -3565,7 +3760,10 @@ function startConstructionTrial(display_element, params, _jsPsych) {
|
|
|
3565
3760
|
trialParams,
|
|
3566
3761
|
...params.instructions && { instructions: params.instructions },
|
|
3567
3762
|
...params.onInteraction && { onInteraction: params.onInteraction },
|
|
3568
|
-
...params.onTrialEnd && { onTrialEnd: params.onTrialEnd }
|
|
3763
|
+
...params.onTrialEnd && { onTrialEnd: params.onTrialEnd },
|
|
3764
|
+
...params.usePrimitiveColorsBlueprints !== void 0 && { usePrimitiveColorsBlueprints: params.usePrimitiveColorsBlueprints },
|
|
3765
|
+
...params.usePrimitiveColorsTargets !== void 0 && { usePrimitiveColorsTargets: params.usePrimitiveColorsTargets },
|
|
3766
|
+
...params.primitiveColorIndices !== void 0 && { primitiveColorIndices: params.primitiveColorIndices }
|
|
3569
3767
|
};
|
|
3570
3768
|
const root = client.createRoot(display_element);
|
|
3571
3769
|
root.render(React.createElement(GameBoard, gameBoardProps));
|
|
@@ -3650,6 +3848,24 @@ const info = {
|
|
|
3650
3848
|
type: jspsych.ParameterType.FUNCTION,
|
|
3651
3849
|
default: void 0,
|
|
3652
3850
|
description: "Callback when trial completes with full data"
|
|
3851
|
+
},
|
|
3852
|
+
/** Whether to use distinct colors for each primitive shape type in blueprints */
|
|
3853
|
+
use_primitive_colors_blueprints: {
|
|
3854
|
+
type: jspsych.ParameterType.BOOL,
|
|
3855
|
+
default: false,
|
|
3856
|
+
description: "Whether each primitive shape type should have its own distinct color in the blueprint dock area"
|
|
3857
|
+
},
|
|
3858
|
+
/** Whether to use distinct colors for each primitive shape type in target tangrams */
|
|
3859
|
+
use_primitive_colors_targets: {
|
|
3860
|
+
type: jspsych.ParameterType.BOOL,
|
|
3861
|
+
default: false,
|
|
3862
|
+
description: "Whether each primitive shape type should have its own distinct color in target tangram silhouettes"
|
|
3863
|
+
},
|
|
3864
|
+
/** Indices mapping primitives to colors from the color palette */
|
|
3865
|
+
primitive_color_indices: {
|
|
3866
|
+
type: jspsych.ParameterType.OBJECT,
|
|
3867
|
+
default: [0, 1, 2, 3, 4],
|
|
3868
|
+
description: "Array of 5 integers indexing into primitiveColors array, mapping [square, smalltriangle, parallelogram, medtriangle, largetriangle] to colors"
|
|
3653
3869
|
}
|
|
3654
3870
|
},
|
|
3655
3871
|
data: {
|
|
@@ -3715,7 +3931,10 @@ class TangramConstructPlugin {
|
|
|
3715
3931
|
show_tangram_decomposition: trial.show_tangram_decomposition,
|
|
3716
3932
|
instructions: trial.instructions,
|
|
3717
3933
|
onInteraction: trial.onInteraction,
|
|
3718
|
-
onTrialEnd: wrappedOnTrialEnd
|
|
3934
|
+
onTrialEnd: wrappedOnTrialEnd,
|
|
3935
|
+
usePrimitiveColorsBlueprints: trial.use_primitive_colors_blueprints,
|
|
3936
|
+
usePrimitiveColorsTargets: trial.use_primitive_colors_targets,
|
|
3937
|
+
primitiveColorIndices: trial.primitive_color_indices
|
|
3719
3938
|
};
|
|
3720
3939
|
const { root, display_element: element, jsPsych } = startConstructionTrial(display_element, params, this.jsPsych);
|
|
3721
3940
|
element.__reactContext = { root, jsPsych };
|