@citolab/pci-3d-blocks 0.0.0-stage → 1.2.0

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/README.md CHANGED
@@ -1,3 +1,89 @@
1
- # Temporary Holding Version
1
+ # @citolab/pci-3d-blocks
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A QTI 3 Portable Custom Interaction (PCI) in which the candidate builds with toy bricks on a baseplate, for example to
4
+ recreate a building shown in the item from its front, side and top view. Made with
5
+ [tspci](https://github.com/Citolab/tspci).
6
+
7
+ <p align="center">
8
+ <img src="https://raw.githubusercontent.com/Citolab/tspci-examples/main/tspci-3d-blocks/example.png" alt="Bricks on a baseplate, with the label front" width="320">
9
+ </p>
10
+
11
+ ## What it does
12
+
13
+ - **Build**: click a cell of the baseplate to place a brick, click the top of a brick to stack one on it. A brick
14
+ shows where it will land before you click.
15
+ - **Remove**: click the side of a brick to take it away (a brick in the top layer goes with any click); the brick that
16
+ would go is shown in see-through red first.
17
+ - **Look around**: drag to turn the baseplate. The arrow and its label mark the front, so the candidate can tell
18
+ front, side and top apart.
19
+ - **Limits**: bricks stay on the plate, and a stack is at most as high as the grid is wide (5 × 5 × 5 by default).
20
+ - **Colours**: a brick's colour follows from where it stands, so a build looks the same when the candidate comes back
21
+ to it. Colours do not count for the answer.
22
+ - **Delivery**: the build is saved as the item's state and restored. In review (`status: "solution"`) the PCI shows the
23
+ correct response when the delivery system passes it.
24
+
25
+ ## What can be configured
26
+
27
+ Every setting is a `data-` attribute of the interaction, as text. `dist/authoring.json` describes them for authoring
28
+ systems; TAO shows them as a form, with a dropdown for the colour mode and colour pickers.
29
+
30
+ | setting | attribute | default | what |
31
+ | -------------------- | ----------------------- | ------------------------------------------------------ | --------------------------------------------------------- |
32
+ | Grid size | `data-grid-divisions` | `5` | cells per side of the baseplate (1 to 6); also the maximum height |
33
+ | Brick size | `data-cube-pixel-size` | `60` | size of a brick (at least 10) |
34
+ | Label at the front | `data-front-label` | `front` | the text under the arrow, for the item's language |
35
+ | Brick colours | `data-brick-color-mode` | `mixed` | `mixed`: the bricks get the colours of the list; `single`: all bricks get the first colour |
36
+ | Colours | `data-brick-colors` | `#c91a09, #f2cd37, #0055bf, #237841, #fe8a18, #5aa5dc` | a list of CSS colours |
37
+ | Baseplate colour | `data-baseplate-color` | `#4b9f4a` | a CSS colour |
38
+ | Width, height | `data-width`, `data-height` | `400`, `400` | size of the interaction in pixels (at least 200) |
39
+
40
+ ## Scoring
41
+
42
+ The PCI is made for `match_correct` (`authoring.json`: `"score": ["MATCH_CORRECT", "NONE"]`).
43
+
44
+ **The response** is a string (`cardinality="single" base-type="string"`): the three views of the build, as JSON. Per
45
+ view, every cell of the grid is `found: true` when a brick is seen there.
46
+
47
+ ```json
48
+ { "xPlane": [{ "y": 0, "z": 0, "found": true }, …], "yPlane": [{ "x": 0, "z": 0, "found": false }, …], "zPlane": [{ "x": 0, "y": 0, "found": true }, …] }
49
+ ```
50
+
51
+ - **What counts**: only the views. Two builds that look the same from the front, the side and the top give the same
52
+ response, even when bricks inside differ, so there is one correct response for all of them.
53
+ - **What does not count**: colours, the order of building, and how the candidate turned the plate.
54
+ - **Setting the correct response**: answer the PCI. In TAO switch the interaction to *Response* and build the answer;
55
+ in qti-construction or the PCI showcase, build it and take it as the correct response. A string comparison then
56
+ scores it: 1 when the views match, otherwise 0.
57
+ - The views depend on the grid size: change the grid size and set the correct response again.
58
+ - **No response**: until the candidate places a brick, `getResponse()` reports nothing.
59
+ - `NONE`: use the PCI without scoring, for instance as a tool or for a human rater.
60
+
61
+ ## Using it
62
+
63
+ ```sh
64
+ npm install @citolab/pci-3d-blocks
65
+ ```
66
+
67
+ | file | what |
68
+ | ------------------------------------ | ------------------------------------------------------------------------- |
69
+ | `dist/index.js` | the PCI (AMD module, everything bundled: the package has no dependencies) |
70
+ | `dist/authoring.json` | settings, response and scoring, for authoring systems |
71
+ | `dist/sbom.cdx.json` | CycloneDX SBOM of the bundle |
72
+ | `dist/tao-pci-blocks_<version>.zip` | the PCI for TAO: Settings → Portable Custom Interactions → Add |
73
+ | `blocks.svg`, `example.png` | icon and screenshot |
74
+
75
+ In a QTI 3 item, with `index.js` at `resources/pci/index.js` of the package:
76
+
77
+ ```xml
78
+ <qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="string"/>
79
+ …
80
+ <qti-portable-custom-interaction response-identifier="RESPONSE" module="pci" custom-interaction-type-identifier="blocks"
81
+ data-grid-divisions="4" data-brick-color-mode="single">
82
+ <qti-interaction-modules>
83
+ <qti-interaction-module id="pci" primary-path="../resources/pci/index"></qti-interaction-module>
84
+ </qti-interaction-modules>
85
+ <qti-interaction-markup></qti-interaction-markup>
86
+ </qti-portable-custom-interaction>
87
+ ```
88
+
89
+ Source and the other examples: [Citolab/tspci-examples](https://github.com/Citolab/tspci-examples).
package/blocks.svg ADDED
@@ -0,0 +1,62 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
2
+ <defs>
3
+ <clipPath id="a">
4
+ <path d="M4.038,12.935V4.517l6.629-2.569,0,8.333Z" transform="translate(-4.038 -1.948)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" />
5
+ </clipPath>
6
+ <clipPath id="c">
7
+ <path d="M3.767,12.372V4.518L10.4,1.949l-.231,7.856Z" transform="translate(-3.767 -1.949)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" />
8
+ </clipPath>
9
+ </defs>
10
+ <g transform="translate(-8 -8)">
11
+ <rect width="32" height="32" transform="translate(8 8)" fill="rgba(255,0,0,0)" />
12
+ <g transform="translate(2.939 4.939)">
13
+ <rect width="32" height="32" transform="translate(5.061 3.061)" fill="rgba(255,0,0,0)" />
14
+ <g transform="translate(-0.553 -0.501)">
15
+ <g transform="translate(8.42 7.444)">
16
+ <path d="M8.738,10.415l6.634-2.654,6.634,2.654-6.634,2.654Z" transform="translate(-8.407 -7.431)" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="0.5" />
17
+ <path d="M8.518,10.194a.331.331,0,0,1,.112-.073l6.634-2.653a.331.331,0,0,1,.246,0l6.634,2.653a.331.331,0,0,1,0,.616l-6.635,2.655a.331.331,0,0,1-.246,0L8.628,10.736a.331.331,0,0,1-.112-.542Zm6.865-2.061-5.737,2.3,5.737,2.3,5.737-2.3Z" transform="translate(-8.42 -7.444)" fill="none" />
18
+ <path d="M15.1,20.917V12.958L21.735,10.3v7.96Z" transform="translate(-8.137 -7.323)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" />
19
+ <path d="M21.978,18.511a.33.33,0,0,1-.112.073l-6.634,2.654a.331.331,0,0,1-.454-.307V12.97a.331.331,0,0,1,.208-.307l6.634-2.654a.331.331,0,0,1,.454.307v7.96a.331.331,0,0,1-.1.235ZM15.444,20.44l5.969-2.387V10.809L15.447,13.2Z" transform="translate(-8.151 -7.337)" fill="none" />
20
+ <path d="M15.37,20.915v-7.96L8.736,10.3v7.96Z" transform="translate(-8.407 -7.323)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" />
21
+ <path d="M8.517,18.511a.331.331,0,0,1-.1-.233v-7.96a.331.331,0,0,1,.454-.307l6.634,2.654a.331.331,0,0,1,.208.306v7.96a.331.331,0,0,1-.454.307L8.628,18.584A.331.331,0,0,1,8.517,18.511Zm.566-7.7,0,7.238L15.05,20.44V13.2Z" transform="translate(-8.42 -7.337)" fill="none" />
22
+ </g>
23
+ <g transform="translate(8.42 15.401)">
24
+ <path d="M8.518,17.827a.331.331,0,0,1,.112-.073L15.263,15.1a.331.331,0,0,1,.246,0l6.634,2.653a.331.331,0,0,1,0,.616l-6.634,2.654a.331.331,0,0,1-.246,0L8.628,18.369a.331.331,0,0,1-.112-.542Zm6.865-2.061-5.737,2.3,5.737,2.3,5.737-2.3Z" transform="translate(-8.42 -15.077)" fill="none" />
25
+ <path d="M21.978,26.144a.33.33,0,0,1-.112.073l-6.634,2.654a.331.331,0,0,1-.454-.307V20.6a.331.331,0,0,1,.208-.307l6.634-2.654a.331.331,0,0,1,.454.307v7.96a.331.331,0,0,1-.1.235Zm-6.533,1.928,5.969-2.387V18.442l-5.964,2.391Z" transform="translate(-8.151 -14.97)" fill="none" />
26
+ <path d="M15.37,28.548v-7.96L8.736,17.934v7.96Z" transform="translate(-8.407 -14.956)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" />
27
+ <path d="M8.517,26.144a.331.331,0,0,1-.1-.233V17.95a.331.331,0,0,1,.454-.307L15.508,20.3a.331.331,0,0,1,.208.306v7.96a.331.331,0,0,1-.454.307L8.628,26.217a.331.331,0,0,1-.112-.073Zm.566-7.7,0,7.238,5.964,2.391V20.83Z" transform="translate(-8.42 -14.97)" fill="none" />
28
+ </g>
29
+ <g transform="translate(15.05 10.52)">
30
+ <path d="M15.1,20.593l6.634-2.654,6.634,2.654-6.634,2.654Z" transform="translate(-14.768 -10.075)" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="0.5" />
31
+ <path d="M14.879,20.372a.331.331,0,0,1,.112-.073l6.634-2.653a.331.331,0,0,1,.246,0L28.5,20.3a.331.331,0,0,1,0,.616L21.87,23.569a.331.331,0,0,1-.246,0L14.99,20.914a.331.331,0,0,1-.112-.542Zm6.865-2.061-5.737,2.3,5.737,2.3,5.737-2.3Z" transform="translate(-14.781 -10.089)" fill="none" />
32
+ <path d="M28.339,28.689a.33.33,0,0,1-.112.073l-6.634,2.654a.331.331,0,0,1-.454-.307v-7.96a.331.331,0,0,1,.208-.307l6.634-2.654a.331.331,0,0,1,.454.307v7.96A.331.331,0,0,1,28.339,28.689Zm-6.533,1.928,5.969-2.387V20.987l-5.964,2.391Z" transform="translate(-14.512 -9.981)" fill="none" />
33
+ <path d="M21.731,31.093v-7.96L15.1,20.479v7.96Z" transform="translate(-14.768 -9.968)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" />
34
+ <path d="M14.878,28.689a.331.331,0,0,1-.1-.233V20.5a.331.331,0,0,1,.454-.307l6.634,2.654a.331.331,0,0,1,.208.306v7.96a.331.331,0,0,1-.454.307l-6.634-2.654a.331.331,0,0,1-.112-.073Zm.566-7.7,0,7.238,5.964,2.391V23.375Z" transform="translate(-14.781 -9.981)" fill="none" />
35
+ <g transform="translate(6.916 10.608)" clip-path="url(#a)">
36
+ <g transform="translate(-13.618 -1.987)">
37
+ <path d="M-9.027,14.929-3.259,4.36Zm.566,2.115L-1.246,3.821Zm.567,2.114L.768,3.282Zm1.482.428L2.78,2.742Zm2.014-.54L4.8,2.2Zm2.014-.54L6.81,1.662Zm2.014-.54L8.823,1.122Zm2.014-.54L10.837.582Zm2.014-.54L12.851.042Zm2.014-.54L14.485.2Zm2.014-.54,7.366-13.5Zm2.014-.54L15.617,4.423Zm2.014-.54,4.472-8.191Z" transform="translate(9.027 -0.042)" fill="none" stroke="#000" stroke-width="0.5" />
38
+ </g>
39
+ </g>
40
+ <g transform="translate(13.777 7.502)" clip-path="url(#a)">
41
+ <g transform="translate(-13.618 -1.987)">
42
+ <path d="M-9.027,14.929-3.259,4.36Zm.566,2.115L-1.246,3.821Zm.567,2.114L.768,3.282Zm1.482.428L2.78,2.742Zm2.014-.54L4.8,2.2Zm2.014-.54L6.81,1.662Zm2.014-.54L8.823,1.122Zm2.014-.54L10.837.582Zm2.014-.54L12.851.042Zm2.014-.54L14.485.2Zm2.014-.54,7.366-13.5Zm2.014-.54L15.617,4.423Zm2.014-.54,4.472-8.191Z" transform="translate(9.027 -0.042)" fill="none" stroke="#000" stroke-width="0.5" />
43
+ </g>
44
+ </g>
45
+ <g transform="translate(0.515 0)" clip-path="url(#c)">
46
+ <g transform="translate(-13.617 -1.988)">
47
+ <path d="M-9.3,14.929-3.529,4.36Zm.566,2.115L-1.516,3.821Zm.567,2.114L.5,3.282Zm1.482.428L2.51,2.742Zm2.014-.54L4.526,2.2Zm2.014-.54L6.54,1.662Zm2.014-.54L8.553,1.122Zm2.014-.54L10.567.582Zm2.014-.54L12.581.042Zm2.014-.54L14.215.2Zm2.014-.54,7.366-13.5Zm2.014-.54L15.347,4.423Zm2.014-.54,4.472-8.191Z" transform="translate(9.297 -0.042)" fill="none" stroke="#000" stroke-width="0.5" />
48
+ </g>
49
+ </g>
50
+ </g>
51
+ <g transform="translate(21.682 15.401)">
52
+ <path d="M21.461,18.048l6.634-2.654,6.634,2.654L28.095,20.7Z" transform="translate(-21.13 -15.064)" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="0.5" />
53
+ <path d="M21.241,17.827a.331.331,0,0,1,.112-.073L27.986,15.1a.331.331,0,0,1,.246,0l6.634,2.653a.331.331,0,0,1,0,.616l-6.634,2.654a.331.331,0,0,1-.246,0l-6.634-2.655a.331.331,0,0,1-.112-.542Zm6.865-2.061-5.737,2.3,5.737,2.3,5.737-2.3Z" transform="translate(-21.143 -15.077)" fill="none" />
54
+ <path d="M27.824,28.551v-7.96l6.634-2.654V25.9Z" transform="translate(-20.86 -14.956)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" />
55
+ <path d="M34.7,26.144a.33.33,0,0,1-.112.073l-6.634,2.654a.331.331,0,0,1-.454-.307V20.6a.331.331,0,0,1,.208-.307l6.634-2.654a.331.331,0,0,1,.454.307v7.96a.331.331,0,0,1-.1.235Zm-6.533,1.928,5.969-2.387V18.442l-5.964,2.391Z" transform="translate(-20.874 -14.97)" fill="none" />
56
+ <path d="M21.24,26.144a.331.331,0,0,1-.1-.233V17.95a.331.331,0,0,1,.454-.307L28.231,20.3a.331.331,0,0,1,.208.306v7.96a.331.331,0,0,1-.454.307l-6.634-2.654A.331.331,0,0,1,21.24,26.144Zm.566-7.7,0,7.238,5.964,2.391V20.83Z" transform="translate(-21.143 -14.97)" fill="none" />
57
+ </g>
58
+ <path d="M8.7,26.145V10.226l6.676-2.653,6.634,2.654v7.96l6.634-2.654,6.906,2.652v7.96L22.013,31.774Z" transform="translate(0.012 0.005)" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="1.5" />
59
+ </g>
60
+ </g>
61
+ </g>
62
+ </svg>
@@ -0,0 +1,102 @@
1
+ {
2
+ "descriptorVersion": "0",
3
+ "typeIdentifier": "blocks",
4
+ "version": "1.2.0",
5
+ "label": "3d blocks",
6
+ "properties": {
7
+ "gridDivisions": {
8
+ "editor": "integer",
9
+ "default": "5",
10
+ "min": 1,
11
+ "max": 6,
12
+ "label": {
13
+ "en": "Grid size (cells per side)",
14
+ "nl": "Rastergrootte (vakken per zijde)"
15
+ }
16
+ },
17
+ "cubePixelSize": {
18
+ "editor": "integer",
19
+ "default": "60",
20
+ "min": 10,
21
+ "label": {
22
+ "en": "Brick size",
23
+ "nl": "Grootte van een blok"
24
+ }
25
+ },
26
+ "frontLabel": {
27
+ "editor": "text",
28
+ "default": "front",
29
+ "label": {
30
+ "en": "Label at the front",
31
+ "nl": "Tekst bij de voorkant"
32
+ }
33
+ },
34
+ "brickColorMode": {
35
+ "editor": "enum",
36
+ "default": "mixed",
37
+ "options": [
38
+ {
39
+ "value": "mixed",
40
+ "label": {
41
+ "en": "Multiple colours",
42
+ "nl": "Meerdere kleuren"
43
+ }
44
+ },
45
+ {
46
+ "value": "single",
47
+ "label": {
48
+ "en": "One colour",
49
+ "nl": "Eén kleur"
50
+ }
51
+ }
52
+ ],
53
+ "label": {
54
+ "en": "Brick colours",
55
+ "nl": "Kleuren van de blokken"
56
+ }
57
+ },
58
+ "brickColors": {
59
+ "editor": "colorList",
60
+ "default": "#c91a09, #f2cd37, #0055bf, #237841, #fe8a18, #5aa5dc",
61
+ "label": {
62
+ "en": "Colours (one colour: the first)",
63
+ "nl": "Kleuren (één kleur: de eerste)"
64
+ }
65
+ },
66
+ "baseplateColor": {
67
+ "editor": "color",
68
+ "default": "#4b9f4a",
69
+ "label": {
70
+ "en": "Baseplate colour",
71
+ "nl": "Kleur van de bouwplaat"
72
+ }
73
+ },
74
+ "width": {
75
+ "editor": "integer",
76
+ "default": "400",
77
+ "min": 200,
78
+ "label": {
79
+ "en": "Width (px)",
80
+ "nl": "Breedte (px)"
81
+ }
82
+ },
83
+ "height": {
84
+ "editor": "integer",
85
+ "default": "400",
86
+ "min": 200,
87
+ "label": {
88
+ "en": "Height (px)",
89
+ "nl": "Hoogte (px)"
90
+ }
91
+ }
92
+ },
93
+ "markup": {},
94
+ "response": {
95
+ "cardinality": "single",
96
+ "baseType": "string"
97
+ },
98
+ "score": [
99
+ "MATCH_CORRECT",
100
+ "NONE"
101
+ ]
102
+ }