@citolab/tspci 2.13.0 → 2.14.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/CHANGELOG.md +11 -0
- package/package.json +2 -2
- package/preset/preact/src/index.tsx +5 -4
- package/preset/preact/src/interaction.tsx +2 -2
- package/preset/preact/src/store.ts +4 -3
- package/readme.md +16 -6
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [2.14.0](https://github.com/Citolab/tspci/compare/v2.13.0...v2.14.0) (2026-10-14)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **tspci:** the preact template uses typed store actions and the unsubscribe function ([5297db7](https://github.com/Citolab/tspci/commit/5297db72a3d2c62cdba58c1b2fe49bbbdee1e024))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# [2.13.0](/compare/v2.12.1...v2.13.0) (2026-10-14)
|
|
7
18
|
|
|
8
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citolab/tspci",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "an opiniated typescript ims-pci builder with tao integration",
|
|
5
5
|
"author": "patrick <patrick.deklein@cito.nl>",
|
|
6
6
|
"repository": {
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"access": "public",
|
|
66
66
|
"registry": "https://registry.npmjs.org/"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "d72febca7cea937f77be4cef910a7f2d72fd524f"
|
|
69
69
|
}
|
|
@@ -17,7 +17,7 @@ import * as ctx from "qtiCustomInteractionContext"; // provided by the delivery
|
|
|
17
17
|
import configProps from "./config.json"; // the settings; the build passes only their defaults (AUTHORING.md)
|
|
18
18
|
import style from "./styles.css"; // Tailwind, compiled to a string, added to the shadow root below
|
|
19
19
|
import Interaction from "./interaction";
|
|
20
|
-
import { StateModel, initStore } from "./store";
|
|
20
|
+
import { Actions, StateModel, initStore } from "./store";
|
|
21
21
|
|
|
22
22
|
// Like every data- attribute, each setting arrives as a string.
|
|
23
23
|
type PropTypes = PciProperties<typeof configProps>;
|
|
@@ -49,7 +49,7 @@ class PciInstance implements IMSpci<PropTypes> {
|
|
|
49
49
|
private config: ConfigProperties<PropTypes>;
|
|
50
50
|
private dom: HTMLElement;
|
|
51
51
|
private shadowdom: ShadowRoot;
|
|
52
|
-
private store: IStore<StateModel> = initStore();
|
|
52
|
+
private store: IStore<StateModel, Actions> = initStore();
|
|
53
53
|
private customValidity = "";
|
|
54
54
|
|
|
55
55
|
constructor(dom: HTMLElement, config: ConfigProperties<PropTypes>, stateString?: string) {
|
|
@@ -79,13 +79,14 @@ class PciInstance implements IMSpci<PropTypes> {
|
|
|
79
79
|
|
|
80
80
|
// From here on, tell the engine when the response changes.
|
|
81
81
|
this.previousResponse = JSON.stringify(this.getResponse() ?? null);
|
|
82
|
-
this.store.subscribe(this.onStoreChange);
|
|
82
|
+
this.unsubscribe = this.store.subscribe(this.onStoreChange);
|
|
83
83
|
|
|
84
84
|
this.render();
|
|
85
85
|
config.onready?.(this, this.getState());
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
private previousResponse: string;
|
|
89
|
+
private unsubscribe = () => {};
|
|
89
90
|
private onStoreChange: Listener<StateModel> = () => {
|
|
90
91
|
const response = JSON.stringify(this.getResponse() ?? null);
|
|
91
92
|
if (response === this.previousResponse) return; // subscribe() calls us right away, and not every action changes it
|
|
@@ -148,7 +149,7 @@ class PciInstance implements IMSpci<PropTypes> {
|
|
|
148
149
|
|
|
149
150
|
/** The engine is done with this instance: unsubscribe and unmount (which also runs the components' cleanups). */
|
|
150
151
|
oncompleted = () => {
|
|
151
|
-
this.
|
|
152
|
+
this.unsubscribe();
|
|
152
153
|
render(null, this.shadowdom);
|
|
153
154
|
};
|
|
154
155
|
}
|
|
@@ -3,14 +3,14 @@ import { IStore, useStore } from "@citolab/preact-store";
|
|
|
3
3
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
|
4
4
|
import configProps from "./config.json";
|
|
5
5
|
import { Pie } from "./pie";
|
|
6
|
-
import { StateModel } from "./store";
|
|
6
|
+
import { Actions, StateModel } from "./store";
|
|
7
7
|
// Images are bundled too: `import logo from "./assets/logo.png"` gives a data URL for <img src={logo} />.
|
|
8
8
|
|
|
9
9
|
type PropTypes = PciProperties<typeof configProps>;
|
|
10
10
|
|
|
11
11
|
type Props = {
|
|
12
12
|
config: PropTypes;
|
|
13
|
-
store: IStore<StateModel>;
|
|
13
|
+
store: IStore<StateModel, Actions>;
|
|
14
14
|
readOnly: boolean; // review, solution, closed: show the answer, but do not change it
|
|
15
15
|
};
|
|
16
16
|
|
|
@@ -5,12 +5,13 @@ import { Store } from "@citolab/preact-store";
|
|
|
5
5
|
* getState() saves both, so a session can be restored, and even replayed (store.replay), action by action.
|
|
6
6
|
*/
|
|
7
7
|
export type StateModel = { value: string }; // the text in the input, also when it is not a number yet
|
|
8
|
-
|
|
8
|
+
/** The actions, as { type: payload }: dispatch and addReducer only accept these. */
|
|
9
|
+
export type Actions = { SET_VALUE: { value: string } };
|
|
9
10
|
|
|
10
11
|
export const initialState: StateModel = { value: "" };
|
|
11
12
|
|
|
12
13
|
export const initStore = (state: StateModel = initialState) => {
|
|
13
|
-
const store = new Store<StateModel>(state);
|
|
14
|
-
store.addReducer
|
|
14
|
+
const store = new Store<StateModel, Actions>(state);
|
|
15
|
+
store.addReducer("SET_VALUE", (current, { value }) => ({ ...current, value }));
|
|
15
16
|
return store;
|
|
16
17
|
};
|
package/readme.md
CHANGED
|
@@ -515,17 +515,27 @@ class VoxelPainterClass {
|
|
|
515
515
|
|
|
516
516
|
### Record user interaction
|
|
517
517
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
518
|
+
With `@citolab/preact-store` (the `preact+tailwind` template) every change of the PCI state is an action. The
|
|
519
|
+
store keeps the actions with a timestamp, so `getState()` can save how a candidate came to an answer, and
|
|
520
|
+
`store.replay(log)` plays it back.
|
|
521
521
|
|
|
522
522
|
```ts
|
|
523
|
-
// src/
|
|
524
|
-
|
|
523
|
+
// src/store.ts
|
|
524
|
+
type Actions = { ADD_CUBE: { x: number }; CLEAR: null }; // { type: payload }
|
|
525
|
+
const store = new Store<StateModel, Actions>({ cubes: [] });
|
|
526
|
+
store.addReducer("ADD_CUBE", (state, cube) => ({ ...state, cubes: [...state.cubes, cube] }));
|
|
527
|
+
|
|
528
|
+
// src/interaction.tsx
|
|
529
|
+
const { cubes } = useStore(store); // re-renders on every change
|
|
530
|
+
store.dispatch({ type: "ADD_CUBE", payload: { x: 1 } });
|
|
525
531
|
|
|
526
|
-
|
|
532
|
+
// src/index.ts
|
|
533
|
+
const unsubscribe = store.subscribe(() => notifyTheEngine(), { immediate: false });
|
|
534
|
+
getState = () => JSON.stringify({ state: store.getState(), log: store.getActions() });
|
|
527
535
|
```
|
|
528
536
|
|
|
537
|
+
See [preact-store](../preact-store/readme.md) for the whole API.
|
|
538
|
+
|
|
529
539
|
### Let authors configure your PCI in QTI
|
|
530
540
|
|
|
531
541
|
Use properties in config.json to let authors configure your PCI. Each one becomes a `data-` attribute of the
|