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