jspsych-tangram 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/construct/index.browser.js +4809 -3948
- package/dist/construct/index.browser.js.map +1 -1
- package/dist/construct/index.browser.min.js +13 -13
- package/dist/construct/index.browser.min.js.map +1 -1
- package/dist/construct/index.cjs +275 -66
- package/dist/construct/index.cjs.map +1 -1
- package/dist/construct/index.d.ts +36 -0
- package/dist/construct/index.js +275 -66
- package/dist/construct/index.js.map +1 -1
- package/dist/index.cjs +385 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +84 -0
- package/dist/index.js +385 -95
- package/dist/index.js.map +1 -1
- package/dist/nback/index.browser.js +4629 -3939
- package/dist/nback/index.browser.js.map +1 -1
- package/dist/nback/index.browser.min.js +12 -12
- package/dist/nback/index.browser.min.js.map +1 -1
- package/dist/nback/index.cjs +102 -64
- package/dist/nback/index.cjs.map +1 -1
- package/dist/nback/index.d.ts +24 -0
- package/dist/nback/index.js +102 -64
- package/dist/nback/index.js.map +1 -1
- package/dist/prep/index.browser.js +4805 -3952
- package/dist/prep/index.browser.js.map +1 -1
- package/dist/prep/index.browser.min.js +13 -13
- package/dist/prep/index.browser.min.js.map +1 -1
- package/dist/prep/index.cjs +271 -70
- package/dist/prep/index.cjs.map +1 -1
- package/dist/prep/index.d.ts +24 -0
- package/dist/prep/index.js +271 -70
- package/dist/prep/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/components/board/BoardView.tsx +372 -124
- package/src/core/components/board/GameBoard.tsx +26 -2
- package/src/core/components/pieces/BlueprintRing.tsx +105 -47
- package/src/core/config/config.ts +25 -10
- package/src/plugins/tangram-construct/ConstructionApp.tsx +7 -1
- package/src/plugins/tangram-construct/index.ts +22 -1
- package/src/plugins/tangram-nback/NBackApp.tsx +87 -28
- package/src/plugins/tangram-nback/index.ts +14 -0
- package/src/plugins/tangram-prep/PrepApp.tsx +7 -1
- package/src/plugins/tangram-prep/index.ts +14 -0
- package/tangram-construct.min.js +13 -13
- package/tangram-nback.min.js +12 -12
- package/tangram-prep.min.js +13 -13
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(() => {
|
|
@@ -3276,7 +3456,7 @@ function GameBoard(props) {
|
|
|
3276
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
|
};
|
|
3282
3462
|
const headerContentStyle = {
|
|
@@ -3344,6 +3524,9 @@ function GameBoard(props) {
|
|
|
3344
3524
|
onCenterBadgePointerDown,
|
|
3345
3525
|
showTangramDecomposition: showTangramDecomposition ?? false,
|
|
3346
3526
|
scaleS,
|
|
3527
|
+
usePrimitiveColorsBlueprints: usePrimitiveColorsBlueprints ?? false,
|
|
3528
|
+
usePrimitiveColorsTargets: usePrimitiveColorsTargets ?? false,
|
|
3529
|
+
primitiveColorIndices: primitiveColorIndices ?? [0, 1, 2, 3, 4],
|
|
3347
3530
|
...eventCallbacks
|
|
3348
3531
|
}
|
|
3349
3532
|
))));
|
|
@@ -3510,7 +3693,9 @@ function startPrepTrial(display_element, params, jsPsych) {
|
|
|
3510
3693
|
quickstashMacros,
|
|
3511
3694
|
primitiveOrder,
|
|
3512
3695
|
onInteraction,
|
|
3513
|
-
onTrialEnd
|
|
3696
|
+
onTrialEnd,
|
|
3697
|
+
usePrimitiveColorsBlueprints,
|
|
3698
|
+
primitiveColorIndices
|
|
3514
3699
|
} = params;
|
|
3515
3700
|
const { onInteraction: _, onTrialEnd: __, ...trialParams } = params;
|
|
3516
3701
|
const PRIMITIVE_BLUEPRINTS_ORDERED = [...PRIMITIVE_BLUEPRINTS].sort((a, b) => primitiveOrder.indexOf(a.kind) - primitiveOrder.indexOf(b.kind));
|
|
@@ -3602,7 +3787,9 @@ function startPrepTrial(display_element, params, jsPsych) {
|
|
|
3602
3787
|
...params.instructions && { instructions: params.instructions },
|
|
3603
3788
|
onControllerReady: handleControllerReady,
|
|
3604
3789
|
...onInteraction && { onInteraction },
|
|
3605
|
-
...onTrialEnd && { onTrialEnd }
|
|
3790
|
+
...onTrialEnd && { onTrialEnd },
|
|
3791
|
+
...usePrimitiveColorsBlueprints !== void 0 && { usePrimitiveColorsBlueprints },
|
|
3792
|
+
...primitiveColorIndices !== void 0 && { primitiveColorIndices }
|
|
3606
3793
|
}));
|
|
3607
3794
|
return { root, display_element, jsPsych };
|
|
3608
3795
|
}
|
|
@@ -3668,6 +3855,18 @@ const info = {
|
|
|
3668
3855
|
onTrialEnd: {
|
|
3669
3856
|
type: ParameterType.FUNCTION,
|
|
3670
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"
|
|
3671
3870
|
}
|
|
3672
3871
|
},
|
|
3673
3872
|
data: {
|
|
@@ -3712,7 +3911,9 @@ class TangramPrepPlugin {
|
|
|
3712
3911
|
primitiveOrder: trial.primitive_order,
|
|
3713
3912
|
instructions: trial.instructions,
|
|
3714
3913
|
onInteraction: trial.onInteraction,
|
|
3715
|
-
onTrialEnd: wrappedOnTrialEnd
|
|
3914
|
+
onTrialEnd: wrappedOnTrialEnd,
|
|
3915
|
+
usePrimitiveColorsBlueprints: trial.use_primitive_colors_blueprints,
|
|
3916
|
+
primitiveColorIndices: trial.primitive_color_indices
|
|
3716
3917
|
};
|
|
3717
3918
|
const { root, display_element: element, jsPsych } = startPrepTrial(display_element, params, this.jsPsych);
|
|
3718
3919
|
element.__reactContext = { root, jsPsych };
|