@citolab/tspci-tao 1.0.1

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/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@citolab/tspci-tao",
3
+ "version": "1.0.1",
4
+ "description": "create a zip file that can be imported into TAO",
5
+ "author": "patrick <patrick.deklein@cito.nl>, Marcel Hoekstra <marcel.hoekstra@cito.nl>",
6
+ "repository": "",
7
+ "type": "module",
8
+ "types": "types/index.d.ts",
9
+ "peerDependencies": {
10
+ "@citolab/tspci": ">= 1.0.0"
11
+ },
12
+ "devDependencies": {
13
+ },
14
+ "dependencies": {
15
+ "archiver": "^5.3.0"
16
+ },
17
+ "scripts": {
18
+ "publish": "npm publish",
19
+ "publish:global": "npm publish --@citolab:registry=https://registry.npmjs.org/ --access public"
20
+ }
21
+ }
Binary file
Binary file
package/readme.md ADDED
@@ -0,0 +1,83 @@
1
+
2
+
3
+ # @citolab/tspci-tao
4
+
5
+ ## Features
6
+ - building a package(.zip) to import in TAO
7
+ - configure PCI in TAO
8
+ - set correct response in TAO
9
+
10
+ This package allows the @citolab/tspci-bundler to create a package which can be imported in the TAO platform.
11
+
12
+ PCI's can be imported in TAO as a ZIP file. The ZIP generated contains the PCI itself and metadata enabling the PCI to be shown in the TAO creator platform.
13
+ Configuration of a PCI can be done by the author because this packager reads the config.json file in the src folder
14
+
15
+ If scoring can be done with a MATCH_CORRECT (single string comparison) the item author can provide the correct answer in TAO by interaction with the PCI.
16
+ This way one PCI can be re-used in different configurations supporting multiple items.
17
+
18
+ To be able to add an IMS-PCI to the TAO authoring system properties are added to the `package.json`
19
+ and implemented lifecycle events that should be implemented in the PCI.
20
+
21
+ To create this zip file run:
22
+
23
+ ``` sh
24
+ tspci --target tao
25
+ ```
26
+
27
+ ## life cycle methods
28
+
29
+ To be TAO compliant the TAOpci interface should be implemented:
30
+
31
+ ```ts
32
+ class App implements IMSpci<PropTypes>, TAOpci {
33
+ // ============== OMMITED IMS IMPLEMENTATION =====================
34
+
35
+ // ============== HERE THE EXTRA TAO IMPLEMENTATION =====================
36
+ off = () => {}; // called when setting correct response in tao, if not implemented TAO gives an error
37
+ on = (val) => {};
38
+
39
+ // THIS IS USED TO RESTORE THE CORRECT ANSWER IN THE AUTHORING PART OF TAO.
40
+ setResponse = (response: any) => {
41
+ try {
42
+ // this is an example of how a string response can be restored.
43
+ if (response.base && response.base.string) {
44
+ // set the response to the pci
45
+ }
46
+ } catch {
47
+ // ignore
48
+ }
49
+ this.render();
50
+ };
51
+
52
+ resetResponse = () => {
53
+ // reset the pci to its initial state
54
+ this.render();
55
+ };
56
+
57
+ // RERENDER THE PCI HERE, SO CHANGING CONFIG PROPERTIES IS DIRECTLY VISIBLE IN TAO.
58
+ trigger = (event: string, value: any) => {
59
+ this.config.properties[event] = value;
60
+ this.render();
61
+ };
62
+ }
63
+ ```
64
+
65
+ ## Configuration
66
+
67
+
68
+ ### package.json
69
+ - typeIdentifier: a field 'typeIdentifier' should be added. This should match the 'typeIdentifier' value in the PCI entry file (The one that implements IMSPci). Only (alpha)numeric values are allowed. This because a hyphen in the identifier causes an error in TAO. To be sure, only alphanumeric are allowed.
70
+ - label: used as label in TAO
71
+ - short: short name used in TAO to label the PCI where you can drag it in an item.
72
+ - score: Array of: "MATCH_CORRECT", "CUSTOM" or "NONE". Can contain one or all three values. The first provided value will be used as default.
73
+ - MATCH_CORRECT: If the item author should be able to set the correct response in TAO; MATCH_CORRECT is the only option. With the MATCH_CORRECT, the item author can interact with the PCI to provide the correct answer.
74
+ - CUSTOM: "CUSTOM" allows to add custom response processing in TAO. This is needed if a string comparison is not possible. The item author cannot interact with the PCI to provided the correct answer. The correct answer should in the response processing template.
75
+ - NONE: if the PCI is used as a tool and/or scoring is not needed the scoring method should be set to NONE.
76
+
77
+ ![provide scoring for the PCI in TAO](readme-images/score.png)
78
+
79
+ ## icon
80
+
81
+ Add a svg file with the name {typeIdentifier}.svg in the root of the PCI directory to have a custom icon for the PCI in TAO.
82
+
83
+ ![custom icon](readme-images/icon.png)
@@ -0,0 +1,298 @@
1
+ import fs, { mkdirSync } from "fs";
2
+ import path from "path";
3
+ import archiver from "archiver";
4
+
5
+ export const bundle = () => {
6
+ const PACKAGE_ROOT_PATH = process.cwd();
7
+
8
+ const PACKAGER_PATH = path.join(PACKAGE_ROOT_PATH + "/node_modules/@citolab/tspci-tao");
9
+
10
+ // PROPERTIES CAN BE OVERRIDEN BY VALUES IN YOUR Package.json INSIDE YOUR PCI FOLDER
11
+ // OTHER CONFIG VALUES ARE: "pciJsFile" THE BUNDLED PCI FILE DEFAULTS TO: "/dist/index.js"
12
+ // AND icon WHICH CAN BE REFERENCED TO A SVG TO OVERRIDE THE 'LIKE' ICON.
13
+ const propertiesToReplace = {
14
+ typeIdentifier: "emptytypeIdentifier",
15
+ version: "1.0.0",
16
+ description: "my interaction description",
17
+ label: "label",
18
+ short: "short",
19
+ score: ["NONE"],
20
+ };
21
+
22
+ const config = path.join(PACKAGE_ROOT_PATH, "package.json");
23
+ if (!fs.existsSync(config)) {
24
+ console.error(`cannot find package.json: ${config}`);
25
+ process.exit();
26
+ }
27
+ const rawConfigData = fs.readFileSync(config);
28
+ const packageData = JSON.parse(rawConfigData);
29
+
30
+ // show warning if not specified
31
+ for (const propertyName in propertiesToReplace) {
32
+ if (propertyName === "typeIdentifier") {
33
+ if (!packageData[propertyName]) {
34
+ console.error(
35
+ "'typeIdentifier' should be added as an option in package.json and should match the typeIdentifier of the PCI code file."
36
+ );
37
+ return;
38
+ } else if (!/^[A-Za-z0-9]*$/.test(packageData[propertyName])) {
39
+ console.error("'typeIdentifier' can only contain (alpha)numeric characters. The pci does not work in TAO if typeindentifier contains a hyphen therefor we restrict all non (alpha)numeric characters. Make sure typeIdentifier of the PCI code file matches the typeidentier in package.json.");
40
+ return;
41
+ }
42
+ } else {
43
+ if (!packageData[propertyName])
44
+ console.warn(
45
+ `${propertyName} not specified in ${packageData.name} in package.json using ${propertiesToReplace[propertyName]}`
46
+ );
47
+ }
48
+ }
49
+
50
+ const outputDirectory = path.join(PACKAGE_ROOT_PATH, "dist");
51
+ const outputDirectoryTemp = path.join(outputDirectory, "tmp");
52
+
53
+ // re-create the directory
54
+ if (!fs.existsSync(outputDirectory)) {
55
+ mkdirSync(outputDirectory);
56
+ }
57
+ if (!fs.existsSync(outputDirectoryTemp)) {
58
+ mkdirSync(outputDirectoryTemp);
59
+ } else {
60
+ fs.rmdirSync(outputDirectoryTemp, { recursive: true });
61
+ mkdirSync(outputDirectoryTemp);
62
+ }
63
+
64
+ // copy all toa files that will be used as base,
65
+ // to override these files for a specific PCI: add them in the same structure in the tao_pci folder
66
+ // of the pci.
67
+
68
+ copyDirectoryRecursiveSync(path.join(PACKAGER_PATH, "toa_pci_default"), outputDirectoryTemp, false);
69
+
70
+ // ---------------------------------------------------------------------
71
+ // ADD THE VALUES THAT ARE SHOULD BE CONFIGURABLE WITHIN TAO
72
+ // ---------------------------------------------------------------------
73
+
74
+ // add variables from taoConfigValues, to be able to configurate PCI's
75
+ const configPath = path.join(PACKAGE_ROOT_PATH, "src/config.json");
76
+
77
+ if (fs.existsSync(configPath)) {
78
+ let configValues;
79
+ let configJson = JSON.parse(fs.readFileSync(configPath, "utf-8"));
80
+ configValues = Object.keys(configJson).reduce((acc, elem) => {
81
+ const defaultValue = configJson[elem];
82
+
83
+ let type = "string";
84
+ if (Number.isInteger(parseInt(defaultValue))) {
85
+ type = "number";
86
+ }
87
+ if (isBoolean(defaultValue)) {
88
+ type = "boolean";
89
+ }
90
+
91
+ acc.push({
92
+ name: `${elem}`,
93
+ type,
94
+ defaultValue: `"${defaultValue}"`, // type === "number" ? `${defaultValue}` : `"${defaultValue}"`,
95
+ optional: true,
96
+ });
97
+ return acc;
98
+ }, []);
99
+
100
+ // add default values in imsPciCreator.js
101
+ const defaultValues = `{
102
+ ${configValues
103
+ .map((cv) => {
104
+ return `${cv.name}: ${cv.defaultValue}`;
105
+ })
106
+ .join(",")}
107
+ };`;
108
+
109
+ // loop through properties with values that should be replaced
110
+ // if the value is not in the config, the default value will be used
111
+ for (const propertyName in propertiesToReplace) {
112
+ propertiesToReplace[propertyName] = packageData[propertyName];
113
+ }
114
+
115
+ const creatorFile = path.join(outputDirectoryTemp, "imsPciCreator.js");
116
+
117
+ replace_string_in_file(
118
+ creatorFile,
119
+ "###_FIRST_SCORING_METHOD_###",
120
+ propertiesToReplace.score?.length > 0 ? propertiesToReplace.score[0] : "NONE"
121
+ );
122
+ replace_string_in_file(creatorFile, `'##__DEFAULT_VALUES__##'`, defaultValues);
123
+
124
+ // add stuff in interaction/creator/widget/states/Question.js
125
+ const variableDeclaration = `
126
+ ${configValues
127
+ .map((cv) => {
128
+ return `var ${cv.name} = interaction.prop('${cv.name}') || ${cv.defaultValue};`;
129
+ })
130
+ .join("\r\n")}
131
+ `;
132
+
133
+ const setVariables = `
134
+ ${configValues
135
+ .map((cv) => {
136
+ return `${cv.name} : ${cv.name},`;
137
+ })
138
+ .join("\r\n")}
139
+ `;
140
+
141
+ const callbacks = `${configValues
142
+ .map((cv) => {
143
+ return `${cv.name} : function(interaction, value){
144
+ //update the pci property value:
145
+ interaction.prop('${cv.name}', value);
146
+
147
+ //trigger change event:
148
+ interaction.triggerPci('${cv.name}', value);
149
+ }`;
150
+ })
151
+ .join(",\r\n")}
152
+ `;
153
+
154
+ const widgetAnswerFile = path.join(outputDirectoryTemp, "interaction/creator/widget/states/Answer.js");
155
+ replace_string_in_file(
156
+ widgetAnswerFile,
157
+ `'###_RD_TEMPLATES_###'`,
158
+ JSON.stringify(propertiesToReplace.score).replace("[", "").replace("]", "")
159
+ );
160
+
161
+ const widgetQuestionFile = path.join(outputDirectoryTemp, "interaction/creator/widget/states/Question.js");
162
+
163
+ replace_string_in_file(widgetQuestionFile, `// ##__SET_VARIABLES__##`, setVariables);
164
+ replace_string_in_file(widgetQuestionFile, `// ##__DECLARE_VARIABLES__##`, variableDeclaration);
165
+ replace_string_in_file(widgetQuestionFile, `// ##__CALLBACK__##`, callbacks);
166
+ // add controls in
167
+ const propertiesFormFile = path.join(outputDirectoryTemp, "interaction/creator/tpl/propertiesForm.tpl");
168
+ const template = `${configValues
169
+ .map((cv) => {
170
+ const capitalizedName = cv.name.charAt(0).toUpperCase() + cv.name.slice(1);
171
+ return `<label for="${cv.name}">{{__ "${capitalizedName}"}}</label>
172
+ <span class="icon-help tooltipstered" data-tooltip="~ .tooltip-content:first" data-tooltip-theme="info"></span>
173
+ <span class="tooltip-content">{{__ "${capitalizedName}"}}</span>
174
+ <input type="text"
175
+ name="${cv.name}"
176
+ type="${cv.type === "string" ? "text" : cv.type === "number" ? "number" : "text"}"
177
+ value="{{${cv.name}}}"
178
+ placeholder="e.g. ${replaceAll(cv.defaultValue, '"', "")}"
179
+ data-validate="${cv.optional ? "" : "$notEmpty"};">`;
180
+ })
181
+ .join("\r\n")}
182
+ `;
183
+ replace_string_in_file(propertiesFormFile, `<!-- ##_HTML_CONTROLS_## -->`, template);
184
+ } else {
185
+ console.warn("Create a config.json with properties to generate a TAO config");
186
+ }
187
+
188
+ // copy specific files
189
+ if (fs.existsSync(path.join(PACKAGE_ROOT_PATH, "tao_pci"))) {
190
+ copyDirectoryRecursiveSync(path.join(PACKAGE_ROOT_PATH, "tao_pci"), outputDirectoryTemp, false);
191
+ }
192
+
193
+ // files that contain values that should be replaced
194
+ const filesToEdit = [
195
+ "imsPciCreator.js",
196
+ "imsPciCreator.json",
197
+ "interaction/creator/widget/Widget.js",
198
+ "interaction/creator/widget/states/states.js",
199
+ "interaction/creator/widget/states/Question.js",
200
+ "interaction/creator/widget/states/Correct.js",
201
+ ];
202
+
203
+ // loop through the files and replace values.
204
+ for (const fileToEdit of filesToEdit) {
205
+ for (const propertyName in propertiesToReplace) {
206
+ const pathToFile = path.join(outputDirectoryTemp, fileToEdit);
207
+ replace_string_in_file(pathToFile, `##${propertyName}##`, propertiesToReplace[propertyName]);
208
+ }
209
+ }
210
+
211
+ // copy the actual PCI file
212
+ const pciFile = config.main || "/dist/index.js";
213
+
214
+ if (!fs.existsSync(path.join(outputDirectoryTemp, "/interaction/runtime/js"))) {
215
+ mkdirSync(path.join(outputDirectoryTemp, "/interaction/runtime/js"));
216
+ }
217
+
218
+ fs.copyFileSync(
219
+ path.join(PACKAGE_ROOT_PATH, pciFile),
220
+ path.join(outputDirectoryTemp, "/interaction/runtime/js/index.js")
221
+ );
222
+
223
+ // the icon can be replaced by a custom icon
224
+ const iconLocation = path.join(PACKAGE_ROOT_PATH, `${propertiesToReplace.typeIdentifier}.svg`);
225
+ if (fs.existsSync(iconLocation)) {
226
+ fs.copyFileSync(iconLocation, path.join(outputDirectoryTemp, "/interaction/creator/img/icon.svg"));
227
+ } else {
228
+ console.warn(`Cannot find svg file at: ${iconLocation} default icon will be used. To have a custom icon for the PCI in TAO add a svg file here: ${iconLocation}`)
229
+ }
230
+ // console.log(propertiesToReplace.short)
231
+
232
+ // zip the folder
233
+ const zipName = `tao-pci-${propertiesToReplace.typeIdentifier}_${packageData.version}.zip`;
234
+ zipDirectory(outputDirectoryTemp, path.join(outputDirectory, zipName)).then(() =>
235
+ // fs.rmdirSync(outputDirectoryTemp, { recursive: true });
236
+ console.log(`created tao pci zip: ${zipName}`)
237
+ );
238
+ };
239
+ function replace_string_in_file(filename, to_replace, replace_with) {
240
+ var data = fs.readFileSync(filename, "utf8");
241
+
242
+ var result = data.replace(new RegExp(to_replace, "g"), replace_with);
243
+ fs.writeFileSync(filename, result, "utf8");
244
+ }
245
+
246
+ function copyDirectoryRecursiveSync(source, target) {
247
+ if (!fs.lstatSync(source).isDirectory() || source.endsWith("tmp")) return;
248
+ // var operation = move ? fs.renameSync : fs.copyFileSync;
249
+ fs.readdirSync(source).forEach(function (itemName) {
250
+ if (!(itemName.indexOf("tao-pci-") !== -1 && itemName.indexOf(".zip") !== -1)) {
251
+ var sourcePath = path.join(source, itemName);
252
+ var targetPath = path.join(target, itemName);
253
+ if (fs.lstatSync(sourcePath).isDirectory()) {
254
+ if (!fs.existsSync(targetPath) && !targetPath.toLocaleLowerCase().endsWith("tmp")) {
255
+ fs.mkdirSync(targetPath);
256
+ }
257
+ copyDirectoryRecursiveSync(sourcePath, targetPath);
258
+ } else {
259
+ if (fs.existsSync(targetPath)) {
260
+ fs.unlinkSync(targetPath);
261
+ }
262
+ fs.copyFileSync(sourcePath, targetPath, 0);
263
+ }
264
+ }
265
+ });
266
+ }
267
+
268
+ function zipDirectory(sourceDir, outPath) {
269
+ const archive = archiver("zip", { zlib: { level: 9 } });
270
+ const stream = fs.createWriteStream(outPath);
271
+
272
+ return new Promise((resolve, reject) => {
273
+ archive
274
+ .directory(sourceDir, false)
275
+ .on("error", (err) => reject(err))
276
+ .pipe(stream);
277
+
278
+ stream.on("close", () => resolve());
279
+ archive.finalize();
280
+ });
281
+ }
282
+
283
+ function replaceAll(str, find, replace) {
284
+ return str.replace(new RegExp(find, "g"), replace);
285
+ }
286
+
287
+ function isBoolean(string) {
288
+ switch (string.toLowerCase().trim()) {
289
+ case "true":
290
+ case "yes":
291
+ case "false":
292
+ case "no":
293
+ return true;
294
+
295
+ default:
296
+ return false;
297
+ }
298
+ }
@@ -0,0 +1,67 @@
1
+ define([
2
+ 'lodash',
3
+ '##typeIdentifier##/interaction/creator/widget/Widget',
4
+ 'tpl!##typeIdentifier##/interaction/creator/tpl/markup'
5
+ ], function(_, Widget, markupTpl){
6
+
7
+ var _typeIdentifier = '##typeIdentifier##';
8
+
9
+ var interactionCreator = {
10
+ /**
11
+ * (required) Get the typeIdentifier of the custom interaction
12
+ *
13
+ * @returns {String}
14
+ */
15
+ gettypeIdentifier : function(){
16
+ return _typeIdentifier;
17
+ },
18
+ /**
19
+ * (required) Get the widget prototype
20
+ * Used in the renderer
21
+ *
22
+ * @returns {Object} Widget
23
+ */
24
+ getWidget : function(){
25
+ return Widget;
26
+ },
27
+ /**
28
+ * (optional) Get the default properties values of the pci.
29
+ * Used on new pci instance creation
30
+ *
31
+ * @returns {Object}
32
+ */
33
+ getDefaultProperties : function(pci){
34
+ return '##__DEFAULT_VALUES__##'
35
+ },
36
+ /**
37
+ * (optional) Callback to execute on the
38
+ * Used on new pci instance creation
39
+ *
40
+ * @returns {Object}
41
+ */
42
+ afterCreate : function(pci){
43
+ pci.getResponseDeclaration().setTemplate('###_FIRST_SCORING_METHOD_###');
44
+ },
45
+ /**
46
+ * (required) Gives the qti pci xml template
47
+ *
48
+ * @returns {function} handlebar template
49
+ */
50
+ getMarkupTemplate : function(){
51
+ return markupTpl;
52
+ },
53
+ /**
54
+ * (optional) Allows passing additional data to xml template
55
+ *
56
+ * @returns {function} handlebar template
57
+ */
58
+ getMarkupData : function(pci, defaultData){
59
+ defaultData.prompt = pci.data('prompt');
60
+ return defaultData;
61
+ }
62
+ };
63
+
64
+ //since we assume we are in a tao context, there is no use to expose the a global object for lib registration
65
+ //all libs should be declared here
66
+ return interactionCreator;
67
+ });
@@ -0,0 +1,42 @@
1
+ {
2
+ "model" : "IMSPCI",
3
+ "typeIdentifier": "##typeIdentifier##",
4
+ "label": "##label##",
5
+ "short": "##short##",
6
+ "description": "##description##",
7
+ "version": "##version##",
8
+ "author": "Citolab",
9
+ "email": "getinspiredbycitolab@gmail.com",
10
+ "tags": [
11
+ "pci"
12
+ ],
13
+ "response": {
14
+ "baseType": "string",
15
+ "cardinality": "single"
16
+ },
17
+ "runtime" : {
18
+ "hook" : "interaction/runtime/js/index.js",
19
+ "modules" : {
20
+ "##typeIdentifier##/interaction/runtime/js/index" : [
21
+ "interaction/runtime/js/index.js"
22
+ ]
23
+ },
24
+ "src" : [
25
+ "./interaction/runtime/js/index.js"
26
+ ]
27
+ },
28
+ "creator" : {
29
+ "icon": "./interaction/creator/img/icon.svg",
30
+ "hook": "./imsPciCreator.js",
31
+ "src" : [
32
+ "./imsPciCreator.js",
33
+ "./interaction/creator/tpl/markup.tpl",
34
+ "./interaction/creator/tpl/propertiesForm.tpl",
35
+ "./interaction/creator/widget/Widget.js",
36
+ "./interaction/creator/widget/states/Question.js",
37
+ "./interaction/creator/widget/states/Answer.js",
38
+ "./interaction/creator/widget/states/Correct.js",
39
+ "./interaction/creator/widget/states/states.js"
40
+ ]
41
+ }
42
+ }
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg xmlns:svg="http://www.w3.org/2000/svg"
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ height="28" width="28">
5
+ <g transform="scale(0.88 0.88)">
6
+ <path d="M29.164 10.472c-1.25-0.328-4.189-0.324-8.488-0.438 0.203-0.938 0.25-1.784 0.25-3.286 0-3.588-2.614-6.748-4.926-6.748-1.633 0-2.979 1.335-3 2.977-0.022 2.014-0.645 5.492-4 7.256-0.246 0.13-0.95 0.477-1.053 0.522l0.053 0.045c-0.525-0.453-1.253-0.8-2-0.8h-3c-1.654 0-3 1.346-3 3v16c0 1.654 1.346 3 3 3h3c1.19 0 2.186-0.719 2.668-1.727 0.012 0.004 0.033 0.010 0.047 0.012 0.066 0.018 0.144 0.037 0.239 0.062 0.018 0.005 0.027 0.007 0.046 0.012 0.576 0.143 1.685 0.408 4.055 0.953 0.508 0.116 3.192 0.688 5.972 0.688h5.467c1.666 0 2.867-0.641 3.582-1.928 0.010-0.020 0.24-0.469 0.428-1.076 0.141-0.457 0.193-1.104 0.023-1.76 1.074-0.738 1.42-1.854 1.645-2.58 0.377-1.191 0.264-2.086 0.002-2.727 0.604-0.57 1.119-1.439 1.336-2.766 0.135-0.822-0.010-1.668-0.389-2.372 0.566-0.636 0.824-1.436 0.854-2.176l0.012-0.209c0.007-0.131 0.013-0.212 0.013-0.5 0-1.263-0.875-2.874-2.836-3.434zM7 29c0 0.553-0.447 1-1 1h-3c-0.553 0-1-0.447-1-1v-16c0-0.553 0.447-1 1-1h3c0.553 0 1 0.447 1 1v16zM29.977 14.535c-0.020 0.494-0.227 1.465-1.977 1.465-1.5 0-2 0-2 0-0.277 0-0.5 0.224-0.5 0.5s0.223 0.5 0.5 0.5c0 0 0.438 0 1.938 0s1.697 1.244 1.6 1.844c-0.124 0.746-0.474 2.156-2.163 2.156-1.687 0-2.375 0-2.375 0-0.277 0-0.5 0.223-0.5 0.5 0 0.275 0.223 0.5 0.5 0.5 0 0 1.188 0 1.969 0 1.688 0 1.539 1.287 1.297 2.055-0.319 1.009-0.514 1.945-2.641 1.945-0.719 0-1.631 0-1.631 0-0.277 0-0.5 0.223-0.5 0.5 0 0.275 0.223 0.5 0.5 0.5 0 0 0.693 0 1.568 0 1.094 0 1.145 1.035 1.031 1.406-0.125 0.406-0.273 0.707-0.279 0.721-0.302 0.545-0.789 0.873-1.82 0.873h-5.467c-2.746 0-5.47-0.623-5.54-0.639-4.154-0.957-4.373-1.031-4.634-1.105 0 0-0.846-0.143-0.846-0.881l-0.007-13.812c0-0.469 0.299-0.893 0.794-1.042 0.062-0.024 0.146-0.050 0.206-0.075 4.568-1.892 5.959-6.040 6-9.446 0.006-0.479 0.375-1 1-1 1.057 0 2.926 2.122 2.926 4.748 0 2.371-0.096 2.781-0.926 5.252 10 0 9.93 0.144 10.812 0.375 1.094 0.313 1.188 1.219 1.188 1.531 0 0.343-0.010 0.293-0.023 0.629zM4.5 26c-0.828 0-1.5 0.672-1.5 1.5s0.672 1.5 1.5 1.5 1.5-0.672 1.5-1.5-0.672-1.5-1.5-1.5zM4.5 28c-0.275 0-0.5-0.225-0.5-0.5s0.225-0.5 0.5-0.5 0.5 0.225 0.5 0.5-0.225 0.5-0.5 0.5z"></path>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,19 @@
1
+ <div class="pciInteraction">
2
+ <style>
3
+ .pciInteraction ul.pci{
4
+ list-style-type: none;
5
+ margin: 0 5px;
6
+ padding: 0;
7
+ display: inline-block;
8
+ position: relative;
9
+ top: 7px;
10
+ }
11
+ .pciInteraction ul.pci li{
12
+ float: left;
13
+ text-align: left;
14
+ list-style-type: none;
15
+ }
16
+ </style>
17
+ <div class="prompt">{{{prompt}}}</div>
18
+ <ul class="pci"></ul>
19
+ </div>
@@ -0,0 +1,8 @@
1
+ <div class="panel">
2
+ <input type="text"
3
+ name="identifier"
4
+ value="{{identifier}}"
5
+ placeholder="e.g. RESPONSE"
6
+ data-validate="$notEmpty; $qtiIdentifier; $availableIdentifier(serial={{serial}});">
7
+ <!-- ##_HTML_CONTROLS_## -->
8
+ </div>
@@ -0,0 +1,15 @@
1
+ define([
2
+ 'taoQtiItem/qtiCreator/widgets/interactions/customInteraction/Widget',
3
+ '##typeIdentifier##/interaction/creator/widget/states/states'
4
+ ], function(Widget, states){
5
+
6
+ var InteractionWidget = Widget.clone();
7
+
8
+ InteractionWidget.initCreator = function(){
9
+ this.registerStates(states);
10
+
11
+ Widget.initCreator.call(this);
12
+ };
13
+
14
+ return InteractionWidget;
15
+ });
@@ -0,0 +1,26 @@
1
+ define([
2
+ 'taoQtiItem/qtiCreator/widgets/states/factory',
3
+ 'taoQtiItem/qtiCreator/widgets/interactions/states/Answer',
4
+ 'taoQtiItem/qtiCreator/widgets/interactions/helpers/answerState'
5
+ ], function(stateFactory, Answer, answerStateHelper){
6
+
7
+ var InteractionStateAnswer = stateFactory.extend(Answer, function(){
8
+
9
+ //forward to one of the available sub state, according to the response processing template
10
+ answerStateHelper.forward(this.widget);
11
+
12
+ }, function(){
13
+
14
+ });
15
+
16
+ InteractionStateAnswer.prototype.initResponseForm = function initResponseForm() {
17
+ answerStateHelper.initResponseForm(
18
+ this.widget,
19
+ {
20
+ rpTemplates: ['###_RD_TEMPLATES_###'],
21
+ }
22
+ );
23
+ };
24
+
25
+ return InteractionStateAnswer;
26
+ });
@@ -0,0 +1,44 @@
1
+ define([
2
+ "taoQtiItem/qtiCreator/widgets/states/factory",
3
+ "taoQtiItem/qtiCreator/widgets/interactions/states/Correct",
4
+ "lodash",
5
+ ], function (stateFactory, Correct, _) {
6
+ var InteractionStateCorrect = stateFactory.create(
7
+ Correct,
8
+ function () {
9
+ var widget = this.widget;
10
+ var interaction = widget.element;
11
+ var responseDeclaration = interaction.getResponseDeclaration();
12
+ var correct = _.values(responseDeclaration.getCorrect());
13
+ // show correct response in the pci.
14
+ if (correct && typeof correct[0] !== "undefined") {
15
+ interaction?.setResponse && interaction.setResponse({ base: { string: correct[0] } });
16
+ }
17
+ },
18
+ function () {
19
+ var widget = this.widget;
20
+ var interaction = widget.element;
21
+ var responseDeclaration = interaction.getResponseDeclaration();
22
+ var correct = interaction && interaction.getResponse ? interaction.getResponse() : undefined;
23
+ var correctString = '';
24
+ if (correct !== undefined && correct.base) {
25
+ if (correct.base.string !== null && (correct.base.string !== undefined)) {
26
+ correctString = correct.base.string;
27
+ } else if (correct.base.integer !== null && (correct.base.integer !== undefined)) {
28
+ correctString = correct.base.integer;
29
+ } else if (correct.base.boolean !== null && (correct.base.boolean !== undefined)) {
30
+ correctString = correct.base.boolean;
31
+ }
32
+ }
33
+
34
+ // getResponse
35
+ // var correct = _.values(responseDeclaration.getCorrect());
36
+ responseDeclaration.setCorrect(correctString);
37
+
38
+ interaction.resetResponse();
39
+ interaction.offPci(".question");
40
+ }
41
+ );
42
+
43
+ return InteractionStateCorrect;
44
+ });
@@ -0,0 +1,51 @@
1
+ define([
2
+ 'taoQtiItem/qtiCreator/widgets/states/factory',
3
+ 'taoQtiItem/qtiCreator/widgets/interactions/states/Question',
4
+ 'taoQtiItem/qtiCreator/widgets/helpers/formElement',
5
+ 'tpl!##typeIdentifier##/interaction/creator/tpl/propertiesForm',
6
+ 'tpl!taoQtiItem/qtiCreator/tpl/notifications/widgetOverlay'
7
+ ], function(stateFactory, Question, formElement, formTpl, overlayTpl){
8
+ ;
9
+ var InteractionStateQuestion = stateFactory.extend(Question, function(){
10
+
11
+ //add transparent protective layer
12
+ this.widget.$container.append(overlayTpl());
13
+
14
+ }, function(){
15
+
16
+ //remove transparent protective layer
17
+ this.widget.$container.children('.overlay').remove();
18
+
19
+ });
20
+
21
+ InteractionStateQuestion.prototype.initForm = function(){
22
+
23
+ var _widget = this.widget,
24
+ $form = _widget.$form,
25
+ interaction = _widget.element,
26
+ response = interaction.getResponseDeclaration();
27
+ // ##__DECLARE_VARIABLES__##
28
+
29
+ //render the form using the form template
30
+ $form.html(formTpl({
31
+ serial : response.serial,
32
+ identifier : interaction.attr('responseIdentifier'),
33
+ // ##__SET_VARIABLES__##
34
+ }));
35
+
36
+ //init form javascript
37
+ formElement.initWidget($form);
38
+
39
+ //init data change callbacks
40
+ formElement.setChangeCallbacks($form, interaction, {
41
+ identifier : function(i, value){
42
+ response.id(value);
43
+ interaction.attr('responseIdentifier', value);
44
+ },
45
+ // ##__CALLBACK__##
46
+ });
47
+
48
+ };
49
+
50
+ return InteractionStateQuestion;
51
+ });
@@ -0,0 +1,9 @@
1
+ define([
2
+ 'taoQtiItem/qtiCreator/widgets/states/factory',
3
+ 'taoQtiItem/qtiCreator/widgets/interactions/customInteraction/states/states',
4
+ '##typeIdentifier##/interaction/creator/widget/states/Question',
5
+ '##typeIdentifier##/interaction/creator/widget/states/Answer',
6
+ '##typeIdentifier##/interaction/creator/widget/states/Correct'
7
+ ], function(factory, states){
8
+ return factory.createBundle(states, arguments, ['map']);
9
+ });
@@ -0,0 +1,18 @@
1
+ export interface TAOpci {
2
+ on: (value) => void; // TAO specific
3
+ off: () => void; // else TAO gave a javascript error
4
+
5
+ resetResponse?: () => void;
6
+ setResponse?: (response) => void;
7
+ trigger?: (event: string, value: any) => void;
8
+ // All below are specific to TAO items, looks like we do not have to use them, keeping for ref
9
+ /*
10
+ gettypeIdentifier?: () => string;
11
+ initialize?: (id, dom, config, assetManager) => void;
12
+ trigger?: (event: string, value: any) => void;
13
+ resetResponse?: () => void;
14
+ setResponse?: (response) => void;
15
+ setSerializedState?: (serializedState) => void;
16
+ getSerializedState?: () => string;
17
+ */
18
+ }