@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 +21 -0
- package/readme-images/icon.png +0 -0
- package/readme-images/score.png +0 -0
- package/readme.md +83 -0
- package/src/createPackage.mjs +298 -0
- package/toa_pci_default/imsPciCreator.js +67 -0
- package/toa_pci_default/imsPciCreator.json +42 -0
- package/toa_pci_default/interaction/creator/img/icon.svg +8 -0
- package/toa_pci_default/interaction/creator/tpl/markup.tpl +19 -0
- package/toa_pci_default/interaction/creator/tpl/propertiesForm.tpl +8 -0
- package/toa_pci_default/interaction/creator/widget/Widget.js +15 -0
- package/toa_pci_default/interaction/creator/widget/states/Answer.js +26 -0
- package/toa_pci_default/interaction/creator/widget/states/Correct.js +44 -0
- package/toa_pci_default/interaction/creator/widget/states/Question.js +51 -0
- package/toa_pci_default/interaction/creator/widget/states/states.js +9 -0
- package/toa_pci_default/interaction/runtime/config.json +1 -0
- package/types/index.d.ts +18 -0
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
|
+

|
|
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
|
+

|
|
@@ -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,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 @@
|
|
|
1
|
+
{}
|
package/types/index.d.ts
ADDED
|
@@ -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
|
+
}
|