@pie-element/mc-populated-blank 0.1.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 +31 -0
- package/dist/author/index.d.ts +11 -0
- package/dist/author/index.d.ts.map +1 -0
- package/dist/author/index.js +3641 -0
- package/dist/author/index.js.map +1 -0
- package/dist/controller/defaults.d.ts +38 -0
- package/dist/controller/defaults.d.ts.map +1 -0
- package/dist/controller/index.d.ts +11 -0
- package/dist/controller/index.d.ts.map +1 -0
- package/dist/controller/index.js +237 -0
- package/dist/controller/index.js.map +1 -0
- package/dist/delivery/index.d.ts +22 -0
- package/dist/delivery/index.d.ts.map +1 -0
- package/dist/delivery/index.js +5736 -0
- package/dist/delivery/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.iife.d.ts +6 -0
- package/dist/index.iife.d.ts.map +1 -0
- package/dist/index.iife.js +20 -0
- package/dist/index.js +5736 -0
- package/dist/index.js.map +1 -0
- package/dist/print/index.d.ts +8 -0
- package/dist/print/index.d.ts.map +1 -0
- package/dist/print/index.js +4952 -0
- package/dist/print/index.js.map +1 -0
- package/package.json +61 -0
- package/src/author/Author.svelte +41 -0
- package/src/author/index.ts +28 -0
- package/src/controller/defaults.ts +38 -0
- package/src/controller/index.ts +273 -0
- package/src/delivery/McPopulatedBlank.svelte +1003 -0
- package/src/delivery/index.ts +103 -0
- package/src/index.iife.ts +13 -0
- package/src/index.ts +1 -0
- package/src/print/Print.svelte +95 -0
- package/src/print/index.ts +18 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Token authors must include exactly once in `template` (HTML). */
|
|
2
|
+
export declare const BLANK_TOKEN = "{{blank}}";
|
|
3
|
+
declare const _default: {
|
|
4
|
+
model: {
|
|
5
|
+
id: string;
|
|
6
|
+
element: string;
|
|
7
|
+
prompt: string;
|
|
8
|
+
promptEnabled: boolean;
|
|
9
|
+
interactionMode: "populate_blank";
|
|
10
|
+
layoutProfile: string;
|
|
11
|
+
choiceLayout: string;
|
|
12
|
+
sentenceHtml: string;
|
|
13
|
+
template: string;
|
|
14
|
+
choiceMode: "text";
|
|
15
|
+
choices: {
|
|
16
|
+
id: string;
|
|
17
|
+
labelHtml: string;
|
|
18
|
+
}[];
|
|
19
|
+
correctChoiceId: string;
|
|
20
|
+
hasAudio: boolean;
|
|
21
|
+
autoplayAudioEnabled: boolean;
|
|
22
|
+
completeAudioEnabled: boolean;
|
|
23
|
+
audioUrl: string;
|
|
24
|
+
audioTranscript: string;
|
|
25
|
+
showVisibleTranscript: boolean;
|
|
26
|
+
locale: string;
|
|
27
|
+
teacherInstructions: string;
|
|
28
|
+
teacherInstructionsEnabled: boolean;
|
|
29
|
+
};
|
|
30
|
+
configuration: {
|
|
31
|
+
prompt: {
|
|
32
|
+
label: string;
|
|
33
|
+
settings: boolean;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export default _default;
|
|
38
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/controller/defaults.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,eAAO,MAAM,WAAW,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEvC,wBAkCE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function countBlankTokens(template: string): number;
|
|
2
|
+
export declare const getCorrectness: (question: any, session: any) => "unanswered" | "correct" | "incorrect";
|
|
3
|
+
export declare const getPartialScore: (_question: any, session: any) => 0 | 1;
|
|
4
|
+
export declare const isComplete: (question: any, session: any, audioComplete?: boolean) => boolean;
|
|
5
|
+
export declare const outcome: (question: any, session: any, env: any) => Promise<unknown>;
|
|
6
|
+
export declare const createDefaultModel: (model?: any) => any;
|
|
7
|
+
export declare const normalizeSession: (s: any) => any;
|
|
8
|
+
export declare const model: (question: any, session: any, env: any, updateSession?: any) => Promise<any>;
|
|
9
|
+
export declare const createCorrectResponseSession: (question: any, env: any) => Promise<unknown>;
|
|
10
|
+
export declare const validate: (model?: any, _config?: any) => any;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controller/index.ts"],"names":[],"mappings":"AAGA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,eAAO,MAAM,cAAc,GAAI,UAAU,GAAG,EAAE,SAAS,GAAG,2CASzD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,WAAW,GAAG,EAAE,SAAS,GAAG,UAK3D,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,uBAAqB,YAY5E,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,KAAK,GAAG,qBA+BzD,CAAC;AAEL,eAAO,MAAM,kBAAkB,GAAI,QAAO,GAAQ,QAAsC,CAAC;AAEzF,eAAO,MAAM,gBAAgB,GAAI,GAAG,GAAG,QAAe,CAAC;AAmEvD,eAAO,MAAM,KAAK,GAAU,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,KAAK,GAAG,EAAE,gBAAgB,GAAG,iBA0CrF,CAAC;AAEF,eAAO,MAAM,4BAA4B,GAAI,UAAU,GAAG,EAAE,KAAK,GAAG,qBAYnE,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,QAAO,GAAQ,EAAE,UAAS,GAAQ,QAuE1D,CAAC"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { isEmpty } from "lodash-es";
|
|
2
|
+
//#region src/controller/defaults.ts
|
|
3
|
+
/** Token authors must include exactly once in `template` (HTML). */
|
|
4
|
+
var BLANK_TOKEN = "{{blank}}";
|
|
5
|
+
var defaults_default = {
|
|
6
|
+
model: {
|
|
7
|
+
id: "1",
|
|
8
|
+
element: "mc-populated-blank",
|
|
9
|
+
prompt: "<p><strong>Demo prompt</strong></p>",
|
|
10
|
+
promptEnabled: true,
|
|
11
|
+
interactionMode: "populate_blank",
|
|
12
|
+
layoutProfile: "",
|
|
13
|
+
choiceLayout: "",
|
|
14
|
+
sentenceHtml: "",
|
|
15
|
+
template: `<p>The answer is ${BLANK_TOKEN}.</p>`,
|
|
16
|
+
choiceMode: "text",
|
|
17
|
+
choices: [
|
|
18
|
+
{
|
|
19
|
+
id: "c1",
|
|
20
|
+
labelHtml: "<p>Option A</p>"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "c2",
|
|
24
|
+
labelHtml: "<p>Option B</p>"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "c3",
|
|
28
|
+
labelHtml: "<p>Option C</p>"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
correctChoiceId: "c2",
|
|
32
|
+
hasAudio: false,
|
|
33
|
+
autoplayAudioEnabled: false,
|
|
34
|
+
completeAudioEnabled: false,
|
|
35
|
+
audioUrl: "",
|
|
36
|
+
audioTranscript: "",
|
|
37
|
+
showVisibleTranscript: false,
|
|
38
|
+
locale: "",
|
|
39
|
+
teacherInstructions: "",
|
|
40
|
+
teacherInstructionsEnabled: false
|
|
41
|
+
},
|
|
42
|
+
configuration: { prompt: {
|
|
43
|
+
label: "Prompt",
|
|
44
|
+
settings: true
|
|
45
|
+
} }
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/controller/index.ts
|
|
49
|
+
function countBlankTokens(template) {
|
|
50
|
+
if (!template) return 0;
|
|
51
|
+
return (template.match(/\{\{blank\}\}/g) || []).length;
|
|
52
|
+
}
|
|
53
|
+
var getCorrectness = (question, session) => {
|
|
54
|
+
if (!session || !session.choiceId) return "unanswered";
|
|
55
|
+
const correct = question?.correctChoiceId || "";
|
|
56
|
+
if (session.choiceId === correct) return "correct";
|
|
57
|
+
return "incorrect";
|
|
58
|
+
};
|
|
59
|
+
var getPartialScore = (_question, session) => {
|
|
60
|
+
if (!session || isEmpty(session) || !session.choiceId) return 0;
|
|
61
|
+
return 1;
|
|
62
|
+
};
|
|
63
|
+
var isComplete = (question, session, audioComplete = false) => {
|
|
64
|
+
if (!session || !session.choiceId) return false;
|
|
65
|
+
const autoplayAudioEnabled = !!question?.autoplayAudioEnabled;
|
|
66
|
+
const completeAudioEnabled = !!question?.completeAudioEnabled;
|
|
67
|
+
if (autoplayAudioEnabled && completeAudioEnabled && !!question?.hasAudio && !audioComplete) return false;
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
var outcome = (question, session, env) => new Promise((resolve) => {
|
|
71
|
+
if (!session || isEmpty(session)) {
|
|
72
|
+
resolve({
|
|
73
|
+
score: 0,
|
|
74
|
+
empty: true,
|
|
75
|
+
traceLog: ["Student did not select any answers. Score is 0."]
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
session = normalizeSession(session);
|
|
80
|
+
const correctness = getCorrectness(question, session);
|
|
81
|
+
if (correctness === "unanswered") {
|
|
82
|
+
resolve({
|
|
83
|
+
score: 0,
|
|
84
|
+
empty: true,
|
|
85
|
+
traceLog: ["Student did not select any answers. Score is 0."]
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const score = correctness === "correct" ? 1 : 0;
|
|
90
|
+
resolve({
|
|
91
|
+
score,
|
|
92
|
+
empty: false,
|
|
93
|
+
traceLog: [
|
|
94
|
+
`Mode: ${env?.mode || "unknown"}.`,
|
|
95
|
+
`Student selected choice: ${session.choiceId}.`,
|
|
96
|
+
`Correct choice: ${question?.correctChoiceId || "none"}.`,
|
|
97
|
+
`Final score: ${score}.`
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
var createDefaultModel = (model = {}) => ({
|
|
102
|
+
...defaults_default.model,
|
|
103
|
+
...model
|
|
104
|
+
});
|
|
105
|
+
var normalizeSession = (s) => ({ ...s });
|
|
106
|
+
var shouldShuffleChoices = (question) => !!question?.shuffle;
|
|
107
|
+
var shouldLockChoices = (question, env) => {
|
|
108
|
+
if (question?.lockChoiceOrder) return true;
|
|
109
|
+
if (env?.["@pie-element"]?.lockChoiceOrder) return true;
|
|
110
|
+
return env?.role === "instructor";
|
|
111
|
+
};
|
|
112
|
+
var shuffleArray = (items) => {
|
|
113
|
+
const out = [...items];
|
|
114
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
115
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
116
|
+
[out[i], out[j]] = [out[j], out[i]];
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
};
|
|
120
|
+
var getStoredShuffle = (session) => Array.isArray(session?.data?.shuffledValues) ? session.data.shuffledValues : Array.isArray(session?.shuffledValues) ? session.shuffledValues : [];
|
|
121
|
+
var applyShuffledValues = (choices, shuffledValues, choiceKey) => {
|
|
122
|
+
const orderedChoices = shuffledValues.map((value) => choices.find((choice) => choice?.[choiceKey] === value)).filter(Boolean);
|
|
123
|
+
if (orderedChoices.length === choices.length) return orderedChoices;
|
|
124
|
+
const orderedValues = new Set(orderedChoices.map((choice) => choice[choiceKey]));
|
|
125
|
+
const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));
|
|
126
|
+
return [...orderedChoices, ...leftovers];
|
|
127
|
+
};
|
|
128
|
+
var getOrderedChoices = async (question, session, env, updateSession) => {
|
|
129
|
+
const choices = Array.isArray(question?.choices) ? [...question.choices] : [];
|
|
130
|
+
if (!choices.length || !shouldShuffleChoices(question)) return choices;
|
|
131
|
+
if (shouldLockChoices(question, env || {})) return choices;
|
|
132
|
+
const shuffledValues = getStoredShuffle(session);
|
|
133
|
+
if (shuffledValues.length) return applyShuffledValues(choices, shuffledValues, "id");
|
|
134
|
+
const shuffledChoices = shuffleArray(choices);
|
|
135
|
+
if (updateSession && typeof updateSession === "function" && session?.id && session?.element) {
|
|
136
|
+
const shuffledIds = shuffledChoices.map((choice) => choice?.id).filter(Boolean);
|
|
137
|
+
if (shuffledIds.length) await updateSession(session.id, session.element, { shuffledValues: shuffledIds });
|
|
138
|
+
}
|
|
139
|
+
return shuffledChoices;
|
|
140
|
+
};
|
|
141
|
+
var model = async (question, session, env, updateSession) => {
|
|
142
|
+
session = session || {};
|
|
143
|
+
const normalizedQuestion = createDefaultModel(question);
|
|
144
|
+
const choices = await getOrderedChoices(normalizedQuestion, session, env, updateSession);
|
|
145
|
+
const out = {
|
|
146
|
+
prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,
|
|
147
|
+
interactionMode: normalizedQuestion.interactionMode || "populate_blank",
|
|
148
|
+
layoutProfile: normalizedQuestion.layoutProfile || "",
|
|
149
|
+
choiceLayout: normalizedQuestion.choiceLayout || "",
|
|
150
|
+
sentenceHtml: normalizedQuestion.sentenceHtml || null,
|
|
151
|
+
template: normalizedQuestion.template,
|
|
152
|
+
choiceMode: normalizedQuestion.choiceMode,
|
|
153
|
+
choices,
|
|
154
|
+
hasAudio: normalizedQuestion.hasAudio,
|
|
155
|
+
autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,
|
|
156
|
+
completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,
|
|
157
|
+
audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,
|
|
158
|
+
audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,
|
|
159
|
+
showVisibleTranscript: !!normalizedQuestion.showVisibleTranscript,
|
|
160
|
+
locale: normalizedQuestion.locale || "",
|
|
161
|
+
disabled: env.mode !== "gather",
|
|
162
|
+
view: env.mode === "view",
|
|
163
|
+
mode: env.mode
|
|
164
|
+
};
|
|
165
|
+
if (env.mode === "evaluate") {
|
|
166
|
+
const correctness = getCorrectness(normalizedQuestion, session);
|
|
167
|
+
out.correctness = correctness;
|
|
168
|
+
out.responseCorrect = correctness === "correct";
|
|
169
|
+
out.correctChoiceId = normalizedQuestion.correctChoiceId;
|
|
170
|
+
}
|
|
171
|
+
if (env.role === "instructor" && (env.mode === "view" || env.mode === "evaluate")) out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled ? normalizedQuestion.teacherInstructions : null;
|
|
172
|
+
else out.teacherInstructions = null;
|
|
173
|
+
return out;
|
|
174
|
+
};
|
|
175
|
+
var createCorrectResponseSession = (question, env) => {
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
if (env.mode !== "evaluate" && env.role === "instructor") resolve({
|
|
178
|
+
id: "1",
|
|
179
|
+
element: "mc-populated-blank",
|
|
180
|
+
choiceId: question?.correctChoiceId || ""
|
|
181
|
+
});
|
|
182
|
+
else resolve(null);
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
var validate = (model = {}, _config = {}) => {
|
|
186
|
+
const errors = {};
|
|
187
|
+
if (model.promptEnabled) {
|
|
188
|
+
const p = model.prompt?.trim() || "";
|
|
189
|
+
if (!p || p === "<p></p>") errors.prompt = "Prompt is required when prompt is enabled";
|
|
190
|
+
}
|
|
191
|
+
const interactionMode = model.interactionMode || "populate_blank";
|
|
192
|
+
const n = countBlankTokens(model.template || "");
|
|
193
|
+
if (interactionMode === "audio_mc_only") {
|
|
194
|
+
if (n > 0) errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;
|
|
195
|
+
} else if (interactionMode === "populate_blank") {
|
|
196
|
+
if (n === 0) errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;
|
|
197
|
+
else if (n > 1) errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;
|
|
198
|
+
} else errors.interactionMode = "Unknown interaction mode";
|
|
199
|
+
const choices = Array.isArray(model.choices) ? model.choices : [];
|
|
200
|
+
if (choices.length < 2) errors.choices = "At least two choices are required";
|
|
201
|
+
const mode = model.choiceMode || "text";
|
|
202
|
+
for (let i = 0; i < choices.length; i++) {
|
|
203
|
+
const c = choices[i];
|
|
204
|
+
if (!c?.id) {
|
|
205
|
+
errors.choices = `Choice ${i + 1} is missing an id`;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
if (mode === "text") {
|
|
209
|
+
const lbl = (c.labelHtml || "").trim();
|
|
210
|
+
if (!lbl || lbl === "<p></p>") {
|
|
211
|
+
errors.choices = `Choice ${i + 1} needs label text`;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
if (!c.imageUrl?.trim()) {
|
|
216
|
+
errors.choices = `Choice ${i + 1} needs an image URL`;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
if (!c.imageAlt?.trim()) {
|
|
220
|
+
errors.choices = `Choice ${i + 1} needs image alt text`;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const correct = model.correctChoiceId;
|
|
226
|
+
if (!correct || !choices.some((c) => c.id === correct)) errors.correctChoiceId = "Correct choice must match one of the choice ids";
|
|
227
|
+
if (model.hasAudio) {
|
|
228
|
+
const hasAudioUrl = !!model.audioUrl?.trim();
|
|
229
|
+
const hasTranscript = !!model.audioTranscript?.trim();
|
|
230
|
+
if (!hasAudioUrl && !hasTranscript) errors.audioTranscript = "Audio transcript or audio URL is required when audio is enabled";
|
|
231
|
+
}
|
|
232
|
+
return errors;
|
|
233
|
+
};
|
|
234
|
+
//#endregion
|
|
235
|
+
export { countBlankTokens, createCorrectResponseSession, createDefaultModel, getCorrectness, getPartialScore, isComplete, model, normalizeSession, outcome, validate };
|
|
236
|
+
|
|
237
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/controller/defaults.ts","../../src/controller/index.ts"],"sourcesContent":["/** Token authors must include exactly once in `template` (HTML). */\nexport const BLANK_TOKEN = '{{blank}}';\n\nexport default {\n model: {\n id: '1',\n element: 'mc-populated-blank',\n prompt: '<p><strong>Demo prompt</strong></p>',\n promptEnabled: true,\n interactionMode: 'populate_blank' as const,\n layoutProfile: '',\n choiceLayout: '',\n sentenceHtml: '',\n template: `<p>The answer is ${BLANK_TOKEN}.</p>`,\n choiceMode: 'text' as const,\n choices: [\n { id: 'c1', labelHtml: '<p>Option A</p>' },\n { id: 'c2', labelHtml: '<p>Option B</p>' },\n { id: 'c3', labelHtml: '<p>Option C</p>' },\n ],\n correctChoiceId: 'c2',\n hasAudio: false,\n autoplayAudioEnabled: false,\n completeAudioEnabled: false,\n audioUrl: '',\n audioTranscript: '',\n showVisibleTranscript: false,\n locale: '',\n teacherInstructions: '',\n teacherInstructionsEnabled: false,\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n },\n};\n","import { isEmpty } from 'lodash-es';\nimport defaults, { BLANK_TOKEN } from './defaults';\n\nexport function countBlankTokens(template: string): number {\n if (!template) return 0;\n const re = /\\{\\{blank\\}\\}/g;\n return (template.match(re) || []).length;\n}\n\nexport const getCorrectness = (question: any, session: any) => {\n if (!session || !session.choiceId) {\n return 'unanswered';\n }\n const correct = question?.correctChoiceId || '';\n if (session.choiceId === correct) {\n return 'correct';\n }\n return 'incorrect';\n};\n\nexport const getPartialScore = (_question: any, session: any) => {\n if (!session || isEmpty(session) || !session.choiceId) {\n return 0;\n }\n return 1;\n};\n\nexport const isComplete = (question: any, session: any, audioComplete = false) => {\n if (!session || !session.choiceId) {\n return false;\n }\n const autoplayAudioEnabled = !!question?.autoplayAudioEnabled;\n const completeAudioEnabled = !!question?.completeAudioEnabled;\n const requiresAudioCompletion =\n autoplayAudioEnabled && completeAudioEnabled && !!question?.hasAudio;\n if (requiresAudioCompletion && !audioComplete) {\n return false;\n }\n return true;\n};\n\nexport const outcome = (question: any, session: any, env: any) =>\n new Promise((resolve) => {\n if (!session || isEmpty(session)) {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n session = normalizeSession(session);\n const correctness = getCorrectness(question, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n const score = correctness === 'correct' ? 1 : 0;\n const traceLog = [\n `Mode: ${env?.mode || 'unknown'}.`,\n `Student selected choice: ${session.choiceId}.`,\n `Correct choice: ${question?.correctChoiceId || 'none'}.`,\n `Final score: ${score}.`,\n ];\n resolve({ score, empty: false, traceLog });\n });\n\nexport const createDefaultModel = (model: any = {}) => ({ ...defaults.model, ...model });\n\nexport const normalizeSession = (s: any) => ({ ...s });\n\nconst shouldShuffleChoices = (question: any) => !!question?.shuffle;\n\nconst shouldLockChoices = (question: any, env: any) => {\n if (question?.lockChoiceOrder) return true;\n if (env?.['@pie-element']?.lockChoiceOrder) return true;\n return env?.role === 'instructor';\n};\n\nconst shuffleArray = <T>(items: T[]): T[] => {\n const out = [...items];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [out[i], out[j]] = [out[j], out[i]];\n }\n return out;\n};\n\nconst getStoredShuffle = (session: any): string[] =>\n Array.isArray(session?.data?.shuffledValues)\n ? session.data.shuffledValues\n : Array.isArray(session?.shuffledValues)\n ? session.shuffledValues\n : [];\n\nconst applyShuffledValues = (choices: any[], shuffledValues: string[], choiceKey: string) => {\n const orderedChoices = shuffledValues\n .map((value) => choices.find((choice) => choice?.[choiceKey] === value))\n .filter(Boolean);\n\n if (orderedChoices.length === choices.length) {\n return orderedChoices;\n }\n\n const orderedValues = new Set(orderedChoices.map((choice: any) => choice[choiceKey]));\n const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));\n return [...orderedChoices, ...leftovers];\n};\n\nconst getOrderedChoices = async (question: any, session: any, env: any, updateSession?: any) => {\n const choices = Array.isArray(question?.choices) ? [...question.choices] : [];\n if (!choices.length || !shouldShuffleChoices(question)) {\n return choices;\n }\n\n if (shouldLockChoices(question, env || {})) {\n return choices;\n }\n\n const shuffledValues = getStoredShuffle(session);\n if (shuffledValues.length) {\n return applyShuffledValues(choices, shuffledValues, 'id');\n }\n\n const shuffledChoices = shuffleArray(choices);\n\n if (updateSession && typeof updateSession === 'function' && session?.id && session?.element) {\n const shuffledIds = shuffledChoices.map((choice) => choice?.id).filter(Boolean);\n if (shuffledIds.length) {\n await updateSession(session.id, session.element, { shuffledValues: shuffledIds });\n }\n }\n\n return shuffledChoices;\n};\n\nexport const model = async (question: any, session: any, env: any, updateSession?: any) => {\n session = session || {};\n const normalizedQuestion = createDefaultModel(question);\n const choices = await getOrderedChoices(normalizedQuestion, session, env, updateSession);\n\n const out: any = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n interactionMode: normalizedQuestion.interactionMode || 'populate_blank',\n layoutProfile: normalizedQuestion.layoutProfile || '',\n choiceLayout: normalizedQuestion.choiceLayout || '',\n sentenceHtml: normalizedQuestion.sentenceHtml || null,\n template: normalizedQuestion.template,\n choiceMode: normalizedQuestion.choiceMode,\n choices,\n hasAudio: normalizedQuestion.hasAudio,\n autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,\n completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,\n audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,\n audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,\n showVisibleTranscript: !!normalizedQuestion.showVisibleTranscript,\n locale: normalizedQuestion.locale || '',\n disabled: env.mode !== 'gather',\n view: env.mode === 'view',\n mode: env.mode,\n };\n\n if (env.mode === 'evaluate') {\n const correctness = getCorrectness(normalizedQuestion, session);\n out.correctness = correctness;\n out.responseCorrect = correctness === 'correct';\n out.correctChoiceId = normalizedQuestion.correctChoiceId;\n }\n\n if (env.role === 'instructor' && (env.mode === 'view' || env.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n return out;\n};\n\nexport const createCorrectResponseSession = (question: any, env: any) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n resolve({\n id: '1',\n element: 'mc-populated-blank',\n choiceId: question?.correctChoiceId || '',\n });\n } else {\n resolve(null);\n }\n });\n};\n\nexport const validate = (model: any = {}, _config: any = {}) => {\n const errors: any = {};\n\n if (model.promptEnabled) {\n const p = model.prompt?.trim() || '';\n if (!p || p === '<p></p>') {\n errors.prompt = 'Prompt is required when prompt is enabled';\n }\n }\n\n const interactionMode = model.interactionMode || 'populate_blank';\n const template = model.template || '';\n const n = countBlankTokens(template);\n if (interactionMode === 'audio_mc_only') {\n if (n > 0) {\n errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;\n }\n } else if (interactionMode === 'populate_blank') {\n if (n === 0) {\n errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;\n } else if (n > 1) {\n errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;\n }\n } else {\n errors.interactionMode = 'Unknown interaction mode';\n }\n\n const choices = Array.isArray(model.choices) ? model.choices : [];\n if (choices.length < 2) {\n errors.choices = 'At least two choices are required';\n }\n\n const mode = model.choiceMode || 'text';\n for (let i = 0; i < choices.length; i++) {\n const c = choices[i];\n if (!c?.id) {\n errors.choices = `Choice ${i + 1} is missing an id`;\n break;\n }\n if (mode === 'text') {\n const lbl = (c.labelHtml || '').trim();\n if (!lbl || lbl === '<p></p>') {\n errors.choices = `Choice ${i + 1} needs label text`;\n break;\n }\n } else {\n if (!c.imageUrl?.trim()) {\n errors.choices = `Choice ${i + 1} needs an image URL`;\n break;\n }\n if (!c.imageAlt?.trim()) {\n errors.choices = `Choice ${i + 1} needs image alt text`;\n break;\n }\n }\n }\n\n const correct = model.correctChoiceId;\n if (!correct || !choices.some((c: any) => c.id === correct)) {\n errors.correctChoiceId = 'Correct choice must match one of the choice ids';\n }\n\n if (model.hasAudio) {\n const hasAudioUrl = !!model.audioUrl?.trim();\n const hasTranscript = !!model.audioTranscript?.trim();\n if (!hasAudioUrl && !hasTranscript) {\n errors.audioTranscript = 'Audio transcript or audio URL is required when audio is enabled';\n }\n }\n\n return errors;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare const SvelteElementClass: any;
|
|
2
|
+
declare class McPopulatedBlankElement extends SvelteElementClass {
|
|
3
|
+
_internalSession: any;
|
|
4
|
+
_model: any;
|
|
5
|
+
_options: any;
|
|
6
|
+
audioComplete: boolean;
|
|
7
|
+
_dispatchModelSet: () => void;
|
|
8
|
+
_dispatchSessionChanged: () => void;
|
|
9
|
+
set model(m: any);
|
|
10
|
+
get model(): any;
|
|
11
|
+
set session(s: any);
|
|
12
|
+
get session(): any;
|
|
13
|
+
set options(o: any);
|
|
14
|
+
get options(): any;
|
|
15
|
+
onSessionChange: (updatedSession: any) => void;
|
|
16
|
+
onAudioStarted: () => void;
|
|
17
|
+
onAudioEnded: () => void;
|
|
18
|
+
_isComplete: () => boolean;
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
}
|
|
21
|
+
export default McPopulatedBlankElement;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/delivery/index.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,kBAAkB,KAA6C,CAAC;AAEtE,cAAM,uBAAwB,SAAQ,kBAAkB;IACtD,gBAAgB,EAAE,GAAG,CAAQ;IAC7B,MAAM,EAAE,GAAG,CAAQ;IACnB,QAAQ,EAAE,GAAG,CAAQ;IACrB,aAAa,UAAS;IAEtB,iBAAiB,aAYf;IAEF,uBAAuB,aAWrB;IAEF,IAAI,KAAK,CAAC,CAAC,EAAE,GAAG,EAKf;IAED,IAAI,KAAK,IAPI,GAAG,CASf;IAED,IAAI,OAAO,CAAC,CAAC,EAAE,GAAG,EAIjB;IAED,IAAI,OAAO,IANI,GAAG,CAQjB;IAED,IAAI,OAAO,CAAC,CAAC,EAAE,GAAG,EAGjB;IAED,IAAI,OAAO,IALI,GAAG,CAOjB;IAED,eAAe,GAAI,gBAAgB,GAAG,UAIpC;IAEF,cAAc,aAKZ;IAEF,YAAY,aAOV;IAEF,WAAW,gBAET;IAEF,iBAAiB;CAMlB;AAED,eAAe,uBAAuB,CAAC"}
|