jspsych-tangram 0.0.11 → 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 +4805 -3935
- 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 +289 -71
- package/dist/construct/index.cjs.map +1 -1
- package/dist/construct/index.d.ts +36 -0
- package/dist/construct/index.js +289 -71
- package/dist/construct/index.js.map +1 -1
- package/dist/index.cjs +399 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +84 -0
- package/dist/index.js +399 -100
- 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 +4803 -3941
- 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 +285 -75
- package/dist/prep/index.cjs.map +1 -1
- package/dist/prep/index.d.ts +24 -0
- package/dist/prep/index.js +285 -75
- 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 +128 -91
- 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
package/dist/prep/index.cjs
CHANGED
|
@@ -7,30 +7,40 @@ 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
|
-
|
|
19
|
-
blueprint: { fill: "#374151",
|
|
20
|
-
tangramDecomposition: { stroke: "#fef2cc" }
|
|
18
|
+
// validFill used here for placed composites
|
|
19
|
+
piece: { draggingFill: "#8e7cc3", validFill: "#8e7cc3", invalidFill: "#d55c00", invalidStroke: "#dc2626", allGreenStroke: "#86efac"},
|
|
20
|
+
blueprint: { fill: "#374151", badgeFill: "#000000", labelFill: "#ffffff" },
|
|
21
|
+
tangramDecomposition: { stroke: "#fef2cc" },
|
|
22
|
+
primitiveColors: [
|
|
23
|
+
// from seaborn "colorblind" palette, 6 colors, with red omitted
|
|
24
|
+
"#0173b2",
|
|
25
|
+
"#de8f05",
|
|
26
|
+
"#029e73",
|
|
27
|
+
"#cc78bc",
|
|
28
|
+
"#ca9161"
|
|
29
|
+
]
|
|
21
30
|
},
|
|
22
31
|
opacity: {
|
|
23
|
-
blueprint: 0.
|
|
32
|
+
blueprint: 0.6,
|
|
24
33
|
silhouetteMask: 0.25,
|
|
25
34
|
//anchors: { valid: 0.80, invalid: 0.50 },
|
|
26
35
|
anchors: { invalid: 0, valid: 0 },
|
|
27
|
-
piece: { invalid:
|
|
36
|
+
piece: { invalid: 1, dragging: 1, locked: 1, normal: 1 }
|
|
28
37
|
},
|
|
29
38
|
size: {
|
|
30
|
-
stroke: { bandPx: 5,
|
|
39
|
+
stroke: { bandPx: 5, allGreenStrokePx: 10, tangramDecompositionPx: 1 },
|
|
31
40
|
anchorRadiusPx: { valid: 1, invalid: 1 },
|
|
32
41
|
badgeFontPx: 16,
|
|
33
|
-
centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 }
|
|
42
|
+
centerBadge: { fractionOfOuterR: 0.15, minPx: 20, marginPx: 4 },
|
|
43
|
+
invalidMarker: { sizePx: 10, strokePx: 4 }
|
|
34
44
|
},
|
|
35
45
|
layout: {
|
|
36
46
|
grid: { stepPx: 20, unitPx: 40 },
|
|
@@ -42,14 +52,10 @@ const CONFIG = {
|
|
|
42
52
|
quickstashDiamAnchors: 7,
|
|
43
53
|
// num anchors req'd to be in single quickstash slot
|
|
44
54
|
primitiveDiamAnchors: 5
|
|
45
|
-
},
|
|
46
|
-
defaults: { maxQuickstashSlots: 1 }
|
|
47
|
-
},
|
|
55
|
+
}},
|
|
48
56
|
game: {
|
|
49
57
|
snapRadiusPx: 15,
|
|
50
|
-
showBorders: false
|
|
51
|
-
hideTouchingBorders: true
|
|
52
|
-
}
|
|
58
|
+
showBorders: false}
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
function isComposite(bp) {
|
|
@@ -832,6 +838,31 @@ var unlockedIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAYAAA
|
|
|
832
838
|
function pathD(poly) {
|
|
833
839
|
return `M ${poly.map((pt) => `${pt.x} ${pt.y}`).join(" L ")} Z`;
|
|
834
840
|
}
|
|
841
|
+
function getPieceColor(blueprint, usePrimitiveColors, defaultColor, primitiveColorIndices) {
|
|
842
|
+
if (!usePrimitiveColors) {
|
|
843
|
+
return defaultColor;
|
|
844
|
+
}
|
|
845
|
+
if ("kind" in blueprint) {
|
|
846
|
+
const kind = blueprint.kind;
|
|
847
|
+
const kindToIndex = {
|
|
848
|
+
"square": 0,
|
|
849
|
+
"smalltriangle": 1,
|
|
850
|
+
"parallelogram": 2,
|
|
851
|
+
"medtriangle": 3,
|
|
852
|
+
"largetriangle": 4
|
|
853
|
+
};
|
|
854
|
+
const primitiveIndex = kindToIndex[kind];
|
|
855
|
+
if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
|
|
856
|
+
const colorIndex = primitiveColorIndices[primitiveIndex];
|
|
857
|
+
const color = CONFIG.color.primitiveColors[colorIndex];
|
|
858
|
+
if (color) {
|
|
859
|
+
return color;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return defaultColor;
|
|
863
|
+
}
|
|
864
|
+
return defaultColor;
|
|
865
|
+
}
|
|
835
866
|
function BoardView(props) {
|
|
836
867
|
const {
|
|
837
868
|
controller,
|
|
@@ -851,6 +882,9 @@ function BoardView(props) {
|
|
|
851
882
|
dragInvalid,
|
|
852
883
|
lockedPieceId,
|
|
853
884
|
showTangramDecomposition,
|
|
885
|
+
usePrimitiveColorsBlueprints,
|
|
886
|
+
usePrimitiveColorsTargets,
|
|
887
|
+
primitiveColorIndices,
|
|
854
888
|
svgRef,
|
|
855
889
|
setPieceRef,
|
|
856
890
|
onPiecePointerDown,
|
|
@@ -861,6 +895,144 @@ function BoardView(props) {
|
|
|
861
895
|
onCenterBadgePointerDown
|
|
862
896
|
} = props;
|
|
863
897
|
const VW = viewBox.w, VH = viewBox.h;
|
|
898
|
+
const renderSilhouettes = () => layout.sectors.map((s) => {
|
|
899
|
+
const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
|
|
900
|
+
if (showTangramDecomposition && sectorCfg?.silhouette.primitiveDecomposition) {
|
|
901
|
+
const primitiveDecomposition = sectorCfg.silhouette.primitiveDecomposition;
|
|
902
|
+
const rect = rectForBand(layout, s, "silhouette", 1);
|
|
903
|
+
const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
|
|
904
|
+
const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, { cx: rect.cx, cy: rect.cy });
|
|
905
|
+
return /* @__PURE__ */ React.createElement("g", { key: `sil-decomposed-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => {
|
|
906
|
+
const primInfo = primitiveDecomposition[i];
|
|
907
|
+
let fillColor = CONFIG.color.silhouetteMask;
|
|
908
|
+
if (usePrimitiveColorsTargets && primInfo?.kind && primitiveColorIndices) {
|
|
909
|
+
const kindToIndex = {
|
|
910
|
+
"square": 0,
|
|
911
|
+
"smalltriangle": 1,
|
|
912
|
+
"parallelogram": 2,
|
|
913
|
+
"medtriangle": 3,
|
|
914
|
+
"largetriangle": 4
|
|
915
|
+
};
|
|
916
|
+
const primitiveIndex = kindToIndex[primInfo.kind];
|
|
917
|
+
if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
|
|
918
|
+
const colorIndex = primitiveColorIndices[primitiveIndex];
|
|
919
|
+
const color = CONFIG.color.primitiveColors[colorIndex];
|
|
920
|
+
if (color) {
|
|
921
|
+
fillColor = color;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return /* @__PURE__ */ React.createElement(
|
|
926
|
+
"path",
|
|
927
|
+
{
|
|
928
|
+
key: `prim-fill-${i}`,
|
|
929
|
+
d: pathD(scaledPoly),
|
|
930
|
+
fill: fillColor,
|
|
931
|
+
opacity: CONFIG.opacity.silhouetteMask,
|
|
932
|
+
stroke: "none"
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
}));
|
|
936
|
+
} else {
|
|
937
|
+
const placedPolys = placedSilBySector.get(s.id) ?? [];
|
|
938
|
+
if (!placedPolys.length) return null;
|
|
939
|
+
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 })));
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
const renderPieces = (piecesFilter) => {
|
|
943
|
+
const piecesToRender = pieces;
|
|
944
|
+
return piecesToRender.sort((a, b) => {
|
|
945
|
+
if (draggingId === a.id) return 1;
|
|
946
|
+
if (draggingId === b.id) return -1;
|
|
947
|
+
return 0;
|
|
948
|
+
}).map((p) => {
|
|
949
|
+
const bp = controller.getBlueprint(p.blueprintId);
|
|
950
|
+
const bb = boundsOfBlueprint(bp, (k) => controller.getPrimitive(k));
|
|
951
|
+
const isDragging = draggingId === p.id;
|
|
952
|
+
const locked = p.sectorId && controller.isSectorCompleted(p.sectorId);
|
|
953
|
+
const isConnectivityLocked = lockedPieceId === p.id;
|
|
954
|
+
selectedPieceId === p.id;
|
|
955
|
+
const isCarriedInvalid = isDragging && dragInvalid;
|
|
956
|
+
const translateX = p.x - bb.min.x;
|
|
957
|
+
const translateY = p.y - bb.min.y;
|
|
958
|
+
const validFillColor = getPieceColor(
|
|
959
|
+
bp,
|
|
960
|
+
usePrimitiveColorsBlueprints || false,
|
|
961
|
+
CONFIG.color.piece.validFill,
|
|
962
|
+
primitiveColorIndices || [0, 1, 2, 3, 4]
|
|
963
|
+
);
|
|
964
|
+
const draggingFillColor = getPieceColor(
|
|
965
|
+
bp,
|
|
966
|
+
usePrimitiveColorsBlueprints || false,
|
|
967
|
+
CONFIG.color.piece.draggingFill,
|
|
968
|
+
primitiveColorIndices || [0, 1, 2, 3, 4]
|
|
969
|
+
);
|
|
970
|
+
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) => {
|
|
971
|
+
const showBorders = shouldShowBorders();
|
|
972
|
+
shouldUseSelectiveBorders(p.blueprintId);
|
|
973
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, { key: idx }, /* @__PURE__ */ React.createElement(
|
|
974
|
+
"path",
|
|
975
|
+
{
|
|
976
|
+
d: pathD(poly),
|
|
977
|
+
fill: isConnectivityLocked ? CONFIG.color.piece.invalidFill : isCarriedInvalid ? CONFIG.color.piece.invalidFill : isDragging ? draggingFillColor : validFillColor,
|
|
978
|
+
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,
|
|
979
|
+
stroke: "none",
|
|
980
|
+
onPointerDown: (e) => onPiecePointerDown(e, p)
|
|
981
|
+
}
|
|
982
|
+
), showBorders);
|
|
983
|
+
}), isDragging && isCarriedInvalid && "shape" in bp && bp.shape.length > 0 && (() => {
|
|
984
|
+
const { cx, cy } = polysAABB$1(bp.shape);
|
|
985
|
+
const size = CONFIG.size.invalidMarker.sizePx;
|
|
986
|
+
const strokeWidth = CONFIG.size.invalidMarker.strokePx;
|
|
987
|
+
const borderWidth = strokeWidth + 2;
|
|
988
|
+
return /* @__PURE__ */ React.createElement("g", { key: "invalid-marker" }, /* @__PURE__ */ React.createElement(
|
|
989
|
+
"line",
|
|
990
|
+
{
|
|
991
|
+
x1: cx - size,
|
|
992
|
+
y1: cy - size,
|
|
993
|
+
x2: cx + size,
|
|
994
|
+
y2: cy + size,
|
|
995
|
+
stroke: "white",
|
|
996
|
+
strokeWidth: borderWidth,
|
|
997
|
+
strokeLinecap: "round"
|
|
998
|
+
}
|
|
999
|
+
), /* @__PURE__ */ React.createElement(
|
|
1000
|
+
"line",
|
|
1001
|
+
{
|
|
1002
|
+
x1: cx - size,
|
|
1003
|
+
y1: cy - size,
|
|
1004
|
+
x2: cx + size,
|
|
1005
|
+
y2: cy + size,
|
|
1006
|
+
stroke: CONFIG.color.piece.invalidStroke,
|
|
1007
|
+
strokeWidth,
|
|
1008
|
+
strokeLinecap: "round"
|
|
1009
|
+
}
|
|
1010
|
+
), /* @__PURE__ */ React.createElement(
|
|
1011
|
+
"line",
|
|
1012
|
+
{
|
|
1013
|
+
x1: cx + size,
|
|
1014
|
+
y1: cy - size,
|
|
1015
|
+
x2: cx - size,
|
|
1016
|
+
y2: cy + size,
|
|
1017
|
+
stroke: "white",
|
|
1018
|
+
strokeWidth: borderWidth,
|
|
1019
|
+
strokeLinecap: "round"
|
|
1020
|
+
}
|
|
1021
|
+
), /* @__PURE__ */ React.createElement(
|
|
1022
|
+
"line",
|
|
1023
|
+
{
|
|
1024
|
+
x1: cx + size,
|
|
1025
|
+
y1: cy - size,
|
|
1026
|
+
x2: cx - size,
|
|
1027
|
+
y2: cy + size,
|
|
1028
|
+
stroke: CONFIG.color.piece.invalidStroke,
|
|
1029
|
+
strokeWidth,
|
|
1030
|
+
strokeLinecap: "round"
|
|
1031
|
+
}
|
|
1032
|
+
));
|
|
1033
|
+
})());
|
|
1034
|
+
});
|
|
1035
|
+
};
|
|
864
1036
|
const centerView = controller.state.blueprintView;
|
|
865
1037
|
const bps = centerView === "primitives" ? controller.state.primitives : controller.state.quickstash;
|
|
866
1038
|
const QS_SLOTS = controller.state.cfg.maxQuickstashSlots;
|
|
@@ -881,6 +1053,12 @@ function BoardView(props) {
|
|
|
881
1053
|
const bb = boundsOfBlueprint(bp, (k) => controller.getPrimitive(k));
|
|
882
1054
|
const cx = bb.min.x + bb.width / 2;
|
|
883
1055
|
const cy = bb.min.y + bb.height / 2;
|
|
1056
|
+
const fillColor = getPieceColor(
|
|
1057
|
+
bp,
|
|
1058
|
+
usePrimitiveColorsBlueprints || false,
|
|
1059
|
+
CONFIG.color.blueprint.fill,
|
|
1060
|
+
primitiveColorIndices || [0, 1, 2, 3, 4]
|
|
1061
|
+
);
|
|
884
1062
|
return /* @__PURE__ */ React.createElement(
|
|
885
1063
|
"g",
|
|
886
1064
|
{
|
|
@@ -892,7 +1070,7 @@ function BoardView(props) {
|
|
|
892
1070
|
{
|
|
893
1071
|
key: idx,
|
|
894
1072
|
d: pathD(poly),
|
|
895
|
-
fill:
|
|
1073
|
+
fill: fillColor,
|
|
896
1074
|
opacity: CONFIG.opacity.blueprint,
|
|
897
1075
|
stroke: "none",
|
|
898
1076
|
strokeWidth: 0,
|
|
@@ -916,7 +1094,7 @@ function BoardView(props) {
|
|
|
916
1094
|
onPointerDown: (e) => {
|
|
917
1095
|
onRootPointerDown(e);
|
|
918
1096
|
},
|
|
919
|
-
style: { background:
|
|
1097
|
+
style: { background: CONFIG.color.background, touchAction: "none", userSelect: "none" }
|
|
920
1098
|
},
|
|
921
1099
|
layout.sectors.map((s, i) => {
|
|
922
1100
|
const done = !!controller.state.sectors[s.id].completedAt;
|
|
@@ -926,35 +1104,7 @@ function BoardView(props) {
|
|
|
926
1104
|
const work = layout.bands.workspace;
|
|
927
1105
|
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
1106
|
}),
|
|
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
|
-
}),
|
|
1107
|
+
/* @__PURE__ */ React.createElement(React.Fragment, null, renderSilhouettes(), renderPieces()) ,
|
|
958
1108
|
layout.sectors.map((s) => {
|
|
959
1109
|
const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
|
|
960
1110
|
if (showTangramDecomposition && sectorCfg?.silhouette.primitiveDecomposition) {
|
|
@@ -962,23 +1112,32 @@ function BoardView(props) {
|
|
|
962
1112
|
const rect = rectForBand(layout, s, "silhouette", 1);
|
|
963
1113
|
const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
|
|
964
1114
|
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
|
|
1115
|
+
return /* @__PURE__ */ React.createElement("g", { key: `sil-decomposed-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => {
|
|
1116
|
+
const primInfo = primitiveDecomposition[i];
|
|
1117
|
+
if (usePrimitiveColorsTargets && primInfo?.kind && primitiveColorIndices) {
|
|
1118
|
+
const kindToIndex = {
|
|
1119
|
+
"square": 0,
|
|
1120
|
+
"smalltriangle": 1,
|
|
1121
|
+
"parallelogram": 2,
|
|
1122
|
+
"medtriangle": 3,
|
|
1123
|
+
"largetriangle": 4
|
|
1124
|
+
};
|
|
1125
|
+
const primitiveIndex = kindToIndex[primInfo.kind];
|
|
1126
|
+
if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
|
|
1127
|
+
primitiveColorIndices[primitiveIndex];
|
|
1128
|
+
}
|
|
980
1129
|
}
|
|
981
|
-
|
|
1130
|
+
return /* @__PURE__ */ React.createElement(
|
|
1131
|
+
"path",
|
|
1132
|
+
{
|
|
1133
|
+
key: `prim-fill-${i}`,
|
|
1134
|
+
d: pathD(scaledPoly),
|
|
1135
|
+
fill: "none",
|
|
1136
|
+
opacity: 0,
|
|
1137
|
+
stroke: "none"
|
|
1138
|
+
}
|
|
1139
|
+
);
|
|
1140
|
+
}));
|
|
982
1141
|
} else {
|
|
983
1142
|
const placedPolys = placedSilBySector.get(s.id) ?? [];
|
|
984
1143
|
if (!placedPolys.length) return null;
|
|
@@ -1069,7 +1228,25 @@ function BoardView(props) {
|
|
|
1069
1228
|
const by = layout.cy + blueprintRingR * Math.sin(theta);
|
|
1070
1229
|
return renderBlueprintGlyph(bp, bx, by);
|
|
1071
1230
|
}),
|
|
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" })))
|
|
1231
|
+
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" }))),
|
|
1232
|
+
showTangramDecomposition && layout.sectors.map((s) => {
|
|
1233
|
+
const sectorCfg = controller.state.cfg.sectors.find((ss) => ss.id === s.id);
|
|
1234
|
+
if (!sectorCfg?.silhouette.primitiveDecomposition) return null;
|
|
1235
|
+
const primitiveDecomposition = sectorCfg.silhouette.primitiveDecomposition;
|
|
1236
|
+
const rect = rectForBand(layout, s, "silhouette", 1);
|
|
1237
|
+
const rawPolys = primitiveDecomposition.map((primInfo) => primInfo.polygon);
|
|
1238
|
+
const placedPolys = placeSilhouetteGridAlignedAsPolys(rawPolys, scaleS, { cx: rect.cx, cy: rect.cy });
|
|
1239
|
+
return /* @__PURE__ */ React.createElement("g", { key: `sil-borders-${s.id}`, pointerEvents: "none" }, placedPolys.map((scaledPoly, i) => /* @__PURE__ */ React.createElement(
|
|
1240
|
+
"path",
|
|
1241
|
+
{
|
|
1242
|
+
key: `prim-border-${i}`,
|
|
1243
|
+
d: pathD(scaledPoly),
|
|
1244
|
+
fill: "none",
|
|
1245
|
+
stroke: CONFIG.color.tangramDecomposition.stroke,
|
|
1246
|
+
strokeWidth: CONFIG.size.stroke.tangramDecompositionPx
|
|
1247
|
+
}
|
|
1248
|
+
)));
|
|
1249
|
+
})
|
|
1073
1250
|
);
|
|
1074
1251
|
}
|
|
1075
1252
|
|
|
@@ -2954,7 +3131,10 @@ function GameBoard(props) {
|
|
|
2954
3131
|
onPieceRemove,
|
|
2955
3132
|
onInteraction,
|
|
2956
3133
|
onTrialEnd,
|
|
2957
|
-
onControllerReady
|
|
3134
|
+
onControllerReady,
|
|
3135
|
+
usePrimitiveColorsBlueprints,
|
|
3136
|
+
usePrimitiveColorsTargets,
|
|
3137
|
+
primitiveColorIndices
|
|
2958
3138
|
} = props;
|
|
2959
3139
|
const [timeRemaining, setTimeRemaining] = React.useState(timeLimitMs > 0 ? Math.floor(timeLimitMs / 1e3) : 0);
|
|
2960
3140
|
const controller = React.useMemo(() => {
|
|
@@ -3275,19 +3455,28 @@ function GameBoard(props) {
|
|
|
3275
3455
|
const headerStyle = {
|
|
3276
3456
|
display: "flex",
|
|
3277
3457
|
flexDirection: "row",
|
|
3278
|
-
justifyContent: "
|
|
3458
|
+
justifyContent: "center",
|
|
3279
3459
|
alignItems: "center",
|
|
3280
3460
|
padding: "20px",
|
|
3281
|
-
background:
|
|
3461
|
+
background: CONFIG.color.background,
|
|
3282
3462
|
flex: "0 0 auto"
|
|
3283
3463
|
};
|
|
3464
|
+
const headerContentStyle = {
|
|
3465
|
+
position: "relative",
|
|
3466
|
+
width: `${svgDimensions.width}px`,
|
|
3467
|
+
maxWidth: "100%",
|
|
3468
|
+
display: "flex",
|
|
3469
|
+
justifyContent: "center",
|
|
3470
|
+
alignItems: "center"
|
|
3471
|
+
};
|
|
3284
3472
|
const instructionsStyle = {
|
|
3285
|
-
flexGrow: 1,
|
|
3286
3473
|
fontSize: "20px",
|
|
3287
3474
|
lineHeight: 1.5,
|
|
3288
|
-
|
|
3475
|
+
textAlign: "center"
|
|
3289
3476
|
};
|
|
3290
3477
|
const timerStyle = {
|
|
3478
|
+
position: "absolute",
|
|
3479
|
+
right: 0,
|
|
3291
3480
|
fontSize: "24px",
|
|
3292
3481
|
fontWeight: "bold",
|
|
3293
3482
|
fontFamily: "monospace",
|
|
@@ -3302,14 +3491,14 @@ function GameBoard(props) {
|
|
|
3302
3491
|
justifyContent: "center",
|
|
3303
3492
|
overflow: "hidden"
|
|
3304
3493
|
};
|
|
3305
|
-
return /* @__PURE__ */ React.createElement("div", { className: "tangram-trial-container", style: containerStyle }, (instructions || timeLimitMs > 0) && /* @__PURE__ */ React.createElement("div", { className: "tangram-header", style: headerStyle }, instructions && /* @__PURE__ */ React.createElement(
|
|
3494
|
+
return /* @__PURE__ */ React.createElement("div", { className: "tangram-trial-container", style: containerStyle }, (instructions || timeLimitMs > 0) && /* @__PURE__ */ React.createElement("div", { className: "tangram-header", style: headerStyle }, /* @__PURE__ */ React.createElement("div", { className: "tangram-header-content", style: headerContentStyle }, instructions && /* @__PURE__ */ React.createElement(
|
|
3306
3495
|
"div",
|
|
3307
3496
|
{
|
|
3308
3497
|
className: "tangram-instructions",
|
|
3309
3498
|
style: instructionsStyle,
|
|
3310
3499
|
dangerouslySetInnerHTML: { __html: instructions }
|
|
3311
3500
|
}
|
|
3312
|
-
), timeLimitMs > 0 && /* @__PURE__ */ React.createElement("div", { className: "tangram-timer", style: timerStyle }, formatTime(timeRemaining))), /* @__PURE__ */ React.createElement("div", { className: "tangram-gameboard-wrapper", style: gameboardWrapperStyle }, /* @__PURE__ */ React.createElement("div", { className: "tangram-gameboard", style: getGameboardStyle() }, /* @__PURE__ */ React.createElement(
|
|
3501
|
+
), timeLimitMs > 0 && /* @__PURE__ */ React.createElement("div", { className: "tangram-timer", style: timerStyle }, formatTime(timeRemaining)))), /* @__PURE__ */ React.createElement("div", { className: "tangram-gameboard-wrapper", style: gameboardWrapperStyle }, /* @__PURE__ */ React.createElement("div", { className: "tangram-gameboard", style: getGameboardStyle() }, /* @__PURE__ */ React.createElement(
|
|
3313
3502
|
BoardView,
|
|
3314
3503
|
{
|
|
3315
3504
|
controller,
|
|
@@ -3337,6 +3526,9 @@ function GameBoard(props) {
|
|
|
3337
3526
|
onCenterBadgePointerDown,
|
|
3338
3527
|
showTangramDecomposition: showTangramDecomposition ?? false,
|
|
3339
3528
|
scaleS,
|
|
3529
|
+
usePrimitiveColorsBlueprints: usePrimitiveColorsBlueprints ?? false,
|
|
3530
|
+
usePrimitiveColorsTargets: usePrimitiveColorsTargets ?? false,
|
|
3531
|
+
primitiveColorIndices: primitiveColorIndices ?? [0, 1, 2, 3, 4],
|
|
3340
3532
|
...eventCallbacks
|
|
3341
3533
|
}
|
|
3342
3534
|
))));
|
|
@@ -3503,7 +3695,9 @@ function startPrepTrial(display_element, params, jsPsych) {
|
|
|
3503
3695
|
quickstashMacros,
|
|
3504
3696
|
primitiveOrder,
|
|
3505
3697
|
onInteraction,
|
|
3506
|
-
onTrialEnd
|
|
3698
|
+
onTrialEnd,
|
|
3699
|
+
usePrimitiveColorsBlueprints,
|
|
3700
|
+
primitiveColorIndices
|
|
3507
3701
|
} = params;
|
|
3508
3702
|
const { onInteraction: _, onTrialEnd: __, ...trialParams } = params;
|
|
3509
3703
|
const PRIMITIVE_BLUEPRINTS_ORDERED = [...PRIMITIVE_BLUEPRINTS].sort((a, b) => primitiveOrder.indexOf(a.kind) - primitiveOrder.indexOf(b.kind));
|
|
@@ -3595,7 +3789,9 @@ function startPrepTrial(display_element, params, jsPsych) {
|
|
|
3595
3789
|
...params.instructions && { instructions: params.instructions },
|
|
3596
3790
|
onControllerReady: handleControllerReady,
|
|
3597
3791
|
...onInteraction && { onInteraction },
|
|
3598
|
-
...onTrialEnd && { onTrialEnd }
|
|
3792
|
+
...onTrialEnd && { onTrialEnd },
|
|
3793
|
+
...usePrimitiveColorsBlueprints !== void 0 && { usePrimitiveColorsBlueprints },
|
|
3794
|
+
...primitiveColorIndices !== void 0 && { primitiveColorIndices }
|
|
3599
3795
|
}));
|
|
3600
3796
|
return { root, display_element, jsPsych };
|
|
3601
3797
|
}
|
|
@@ -3661,6 +3857,18 @@ const info = {
|
|
|
3661
3857
|
onTrialEnd: {
|
|
3662
3858
|
type: jspsych.ParameterType.FUNCTION,
|
|
3663
3859
|
default: void 0
|
|
3860
|
+
},
|
|
3861
|
+
/** Whether to use distinct colors for each primitive shape type in blueprints */
|
|
3862
|
+
use_primitive_colors_blueprints: {
|
|
3863
|
+
type: jspsych.ParameterType.BOOL,
|
|
3864
|
+
default: false,
|
|
3865
|
+
description: "Whether each primitive shape type should have its own distinct color in the blueprint dock area"
|
|
3866
|
+
},
|
|
3867
|
+
/** Indices mapping primitives to colors from the color palette */
|
|
3868
|
+
primitive_color_indices: {
|
|
3869
|
+
type: jspsych.ParameterType.OBJECT,
|
|
3870
|
+
default: [0, 1, 2, 3, 4],
|
|
3871
|
+
description: "Array of 5 integers indexing into primitiveColors array, mapping [square, smalltriangle, parallelogram, medtriangle, largetriangle] to colors"
|
|
3664
3872
|
}
|
|
3665
3873
|
},
|
|
3666
3874
|
data: {
|
|
@@ -3705,7 +3913,9 @@ class TangramPrepPlugin {
|
|
|
3705
3913
|
primitiveOrder: trial.primitive_order,
|
|
3706
3914
|
instructions: trial.instructions,
|
|
3707
3915
|
onInteraction: trial.onInteraction,
|
|
3708
|
-
onTrialEnd: wrappedOnTrialEnd
|
|
3916
|
+
onTrialEnd: wrappedOnTrialEnd,
|
|
3917
|
+
usePrimitiveColorsBlueprints: trial.use_primitive_colors_blueprints,
|
|
3918
|
+
primitiveColorIndices: trial.primitive_color_indices
|
|
3709
3919
|
};
|
|
3710
3920
|
const { root, display_element: element, jsPsych } = startPrepTrial(display_element, params, this.jsPsych);
|
|
3711
3921
|
element.__reactContext = { root, jsPsych };
|