@thanh01.pmt/interactive-quiz-kit 1.0.23 → 1.0.25
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/dist/ai-ecosystem-BJ5RR5Ys.d.ts +228 -0
- package/dist/ai-ecosystem-CL30v1Lg.d.cts +228 -0
- package/dist/ai.cjs +181 -227
- package/dist/ai.d.cts +1881 -0
- package/dist/ai.d.ts +1881 -0
- package/dist/ai.js +181 -227
- package/dist/authoring.cjs +6401 -2528
- package/dist/authoring.d.cts +12 -0
- package/dist/authoring.d.ts +12 -0
- package/dist/authoring.js +6281 -2425
- package/dist/index.cjs +374 -174
- package/dist/index.d.cts +444 -0
- package/dist/index.d.ts +444 -0
- package/dist/index.js +373 -175
- package/dist/player.cjs +1491 -1086
- package/dist/player.d.cts +13 -0
- package/dist/player.d.ts +13 -0
- package/dist/player.js +1424 -1019
- package/dist/quiz-config-1gNNhljP.d.cts +197 -0
- package/dist/quiz-config-1gNNhljP.d.ts +197 -0
- package/dist/react-ui.cjs +8621 -3688
- package/dist/react-ui.d.cts +207 -0
- package/dist/react-ui.d.ts +207 -0
- package/dist/react-ui.js +8321 -3404
- package/dist/toaster-CtnxWhfE.d.ts +133 -0
- package/dist/toaster-DUq851l_.d.cts +133 -0
- package/package.json +13 -11
package/dist/index.js
CHANGED
|
@@ -5,26 +5,6 @@ import JSZip from 'jszip';
|
|
|
5
5
|
import { clsx } from 'clsx';
|
|
6
6
|
import { twMerge } from 'tailwind-merge';
|
|
7
7
|
|
|
8
|
-
var __defProp = Object.defineProperty;
|
|
9
|
-
var __defProps = Object.defineProperties;
|
|
10
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
11
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
12
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
14
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
15
|
-
var __spreadValues = (a, b) => {
|
|
16
|
-
for (var prop in b || (b = {}))
|
|
17
|
-
if (__hasOwnProp.call(b, prop))
|
|
18
|
-
__defNormalProp(a, prop, b[prop]);
|
|
19
|
-
if (__getOwnPropSymbols)
|
|
20
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
21
|
-
if (__propIsEnum.call(b, prop))
|
|
22
|
-
__defNormalProp(a, prop, b[prop]);
|
|
23
|
-
}
|
|
24
|
-
return a;
|
|
25
|
-
};
|
|
26
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
27
|
-
|
|
28
8
|
// src/services/SCORMService.ts
|
|
29
9
|
var SCORM_TRUE = "true";
|
|
30
10
|
var SCORM_NO_ERROR = "0";
|
|
@@ -44,11 +24,12 @@ var SCORMService = class {
|
|
|
44
24
|
this.isInitialized = false;
|
|
45
25
|
this.isTerminated = false;
|
|
46
26
|
this.studentName = null;
|
|
47
|
-
this.settings =
|
|
27
|
+
this.settings = {
|
|
48
28
|
setCompletionOnFinish: true,
|
|
49
29
|
setSuccessOnPass: true,
|
|
50
|
-
autoCommit: true
|
|
51
|
-
|
|
30
|
+
autoCommit: true,
|
|
31
|
+
...settings
|
|
32
|
+
};
|
|
52
33
|
if (typeof window !== "undefined") {
|
|
53
34
|
this._findAPI();
|
|
54
35
|
}
|
|
@@ -154,14 +135,13 @@ var SCORMService = class {
|
|
|
154
135
|
}
|
|
155
136
|
}
|
|
156
137
|
getValue(element) {
|
|
157
|
-
var _a;
|
|
158
138
|
if (!this.hasAPI() || !this.isInitialized) return null;
|
|
159
139
|
const value = this.scormVersionFound === "2004" ? this.scormAPI.GetValue(element) : this.scormAPI.LMSGetValue(element);
|
|
160
140
|
const error = this.getLastError();
|
|
161
141
|
if (error.code !== SCORM_NO_ERROR && error.code !== "403" && error.code !== "0") {
|
|
162
142
|
console.warn(`SCORMService: GetValue for ${element} produced an error ${error.code}: ${error.message}. Returning raw value:`, value);
|
|
163
143
|
}
|
|
164
|
-
return
|
|
144
|
+
return value?.toString() ?? null;
|
|
165
145
|
}
|
|
166
146
|
commit() {
|
|
167
147
|
if (!this.hasAPI() || !this.isInitialized) {
|
|
@@ -233,7 +213,6 @@ var SCORMService = class {
|
|
|
233
213
|
}
|
|
234
214
|
}
|
|
235
215
|
getLastError() {
|
|
236
|
-
var _a, _b;
|
|
237
216
|
if (!this.hasAPI()) return { code: "-1", message: "SCORM API not found." };
|
|
238
217
|
const errorCode = this.scormVersionFound === "2004" ? this.scormAPI.GetLastError() : this.scormAPI.LMSGetLastError();
|
|
239
218
|
if (errorCode === SCORM_NO_ERROR || errorCode === 0 || errorCode === "0") {
|
|
@@ -243,8 +222,8 @@ var SCORMService = class {
|
|
|
243
222
|
const diagnostic = this.scormVersionFound === "2004" ? this.scormAPI.GetDiagnostic(errorCode.toString()) : this.scormAPI.LMSGetDiagnostic(errorCode.toString());
|
|
244
223
|
return {
|
|
245
224
|
code: errorCode.toString(),
|
|
246
|
-
message:
|
|
247
|
-
diagnostic:
|
|
225
|
+
message: errorMessage?.toString() ?? "Unknown error.",
|
|
226
|
+
diagnostic: diagnostic?.toString() ?? void 0
|
|
248
227
|
};
|
|
249
228
|
}
|
|
250
229
|
formatCMITime(totalSeconds) {
|
|
@@ -275,14 +254,13 @@ var SCORMService = class {
|
|
|
275
254
|
// src/services/evaluators/multiple-choice-evaluator.ts
|
|
276
255
|
var MultipleChoiceEvaluator = class {
|
|
277
256
|
async evaluate(question, answer) {
|
|
278
|
-
|
|
279
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
257
|
+
const points = question.points ?? 0;
|
|
280
258
|
const correctAnswerId = question.correctAnswerId;
|
|
281
259
|
const isCorrect = answer === correctAnswerId;
|
|
282
260
|
const correctOption = question.options.find((opt) => opt.id === correctAnswerId);
|
|
283
261
|
const correctAnswerDetail = {
|
|
284
262
|
id: correctAnswerId,
|
|
285
|
-
value:
|
|
263
|
+
value: correctOption?.text || ""
|
|
286
264
|
};
|
|
287
265
|
return Promise.resolve({
|
|
288
266
|
isCorrect,
|
|
@@ -295,8 +273,7 @@ var MultipleChoiceEvaluator = class {
|
|
|
295
273
|
// src/services/evaluators/multiple-response-evaluator.ts
|
|
296
274
|
var MultipleResponseEvaluator = class {
|
|
297
275
|
async evaluate(question, answer) {
|
|
298
|
-
|
|
299
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
276
|
+
const points = question.points ?? 0;
|
|
300
277
|
const correctAnswerIds = question.correctAnswerIds;
|
|
301
278
|
let isCorrect = false;
|
|
302
279
|
if (Array.isArray(answer)) {
|
|
@@ -305,10 +282,7 @@ var MultipleResponseEvaluator = class {
|
|
|
305
282
|
isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
|
|
306
283
|
}
|
|
307
284
|
const correctValues = correctAnswerIds.map(
|
|
308
|
-
(id) =>
|
|
309
|
-
var _a2;
|
|
310
|
-
return ((_a2 = question.options.find((opt) => opt.id === id)) == null ? void 0 : _a2.text) || "";
|
|
311
|
-
}
|
|
285
|
+
(id) => question.options.find((opt) => opt.id === id)?.text || ""
|
|
312
286
|
);
|
|
313
287
|
const correctAnswerDetail = {
|
|
314
288
|
id: correctAnswerIds,
|
|
@@ -325,8 +299,7 @@ var MultipleResponseEvaluator = class {
|
|
|
325
299
|
// src/services/evaluators/true-false-evaluator.ts
|
|
326
300
|
var TrueFalseEvaluator = class {
|
|
327
301
|
async evaluate(question, answer) {
|
|
328
|
-
|
|
329
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
302
|
+
const points = question.points ?? 0;
|
|
330
303
|
const correctAnswer = question.correctAnswer;
|
|
331
304
|
let userAnswer = answer;
|
|
332
305
|
if (typeof answer === "string") {
|
|
@@ -348,12 +321,11 @@ var TrueFalseEvaluator = class {
|
|
|
348
321
|
// src/services/evaluators/short-answer-evaluator.ts
|
|
349
322
|
var ShortAnswerEvaluator = class {
|
|
350
323
|
async evaluate(question, answer) {
|
|
351
|
-
|
|
352
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
324
|
+
const points = question.points ?? 0;
|
|
353
325
|
let isCorrect = false;
|
|
354
326
|
if (typeof answer === "string") {
|
|
355
327
|
const userAnswerTrimmed = answer.trim();
|
|
356
|
-
const caseSensitive =
|
|
328
|
+
const caseSensitive = question.isCaseSensitive ?? false;
|
|
357
329
|
isCorrect = question.acceptedAnswers.some(
|
|
358
330
|
(accAns) => caseSensitive ? accAns.trim() === userAnswerTrimmed : accAns.trim().toLowerCase() === userAnswerTrimmed.toLowerCase()
|
|
359
331
|
);
|
|
@@ -373,8 +345,7 @@ var ShortAnswerEvaluator = class {
|
|
|
373
345
|
// src/services/evaluators/numeric-evaluator.ts
|
|
374
346
|
var NumericEvaluator = class {
|
|
375
347
|
async evaluate(question, answer) {
|
|
376
|
-
|
|
377
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
348
|
+
const points = question.points ?? 0;
|
|
378
349
|
let isCorrect = false;
|
|
379
350
|
if (typeof answer === "string" || typeof answer === "number") {
|
|
380
351
|
const userAnswerNum = parseFloat(String(answer));
|
|
@@ -397,17 +368,13 @@ var NumericEvaluator = class {
|
|
|
397
368
|
// src/services/evaluators/sequence-evaluator.ts
|
|
398
369
|
var SequenceEvaluator = class {
|
|
399
370
|
async evaluate(question, answer) {
|
|
400
|
-
|
|
401
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
371
|
+
const points = question.points ?? 0;
|
|
402
372
|
let isCorrect = false;
|
|
403
373
|
if (Array.isArray(answer) && answer.length === question.correctOrder.length) {
|
|
404
374
|
isCorrect = answer.every((itemId, index) => itemId === question.correctOrder[index]);
|
|
405
375
|
}
|
|
406
376
|
const correctValues = question.correctOrder.map(
|
|
407
|
-
(id) =>
|
|
408
|
-
var _a2;
|
|
409
|
-
return ((_a2 = question.items.find((item) => item.id === id)) == null ? void 0 : _a2.content) || "";
|
|
410
|
-
}
|
|
377
|
+
(id) => question.items.find((item) => item.id === id)?.content || ""
|
|
411
378
|
);
|
|
412
379
|
const correctAnswerDetail = {
|
|
413
380
|
id: question.correctOrder,
|
|
@@ -424,17 +391,15 @@ var SequenceEvaluator = class {
|
|
|
424
391
|
// src/services/evaluators/matching-evaluator.ts
|
|
425
392
|
var MatchingEvaluator = class {
|
|
426
393
|
async evaluate(question, answer) {
|
|
427
|
-
|
|
428
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
394
|
+
const points = question.points ?? 0;
|
|
429
395
|
let isCorrect = false;
|
|
430
396
|
if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
|
|
431
397
|
const userAnswerMap = answer;
|
|
432
398
|
isCorrect = question.correctAnswerMap.length === Object.keys(userAnswerMap).length && question.correctAnswerMap.every((map) => userAnswerMap[map.promptId] === map.optionId);
|
|
433
399
|
}
|
|
434
400
|
const correctMap = question.correctAnswerMap.reduce((acc, curr) => {
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
const optionText = ((_b = question.options.find((o) => o.id === curr.optionId)) == null ? void 0 : _b.content) || "";
|
|
401
|
+
const promptText = question.prompts.find((p) => p.id === curr.promptId)?.content || "";
|
|
402
|
+
const optionText = question.options.find((o) => o.id === curr.optionId)?.content || "";
|
|
438
403
|
acc[promptText] = optionText;
|
|
439
404
|
return acc;
|
|
440
405
|
}, {});
|
|
@@ -453,16 +418,14 @@ var MatchingEvaluator = class {
|
|
|
453
418
|
// src/services/evaluators/fill-in-the-blanks-evaluator.ts
|
|
454
419
|
var FillInTheBlanksEvaluator = class {
|
|
455
420
|
async evaluate(question, answer) {
|
|
456
|
-
|
|
457
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
421
|
+
const points = question.points ?? 0;
|
|
458
422
|
let isCorrect = false;
|
|
459
423
|
if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
|
|
460
424
|
const userAnswerMap = answer;
|
|
461
425
|
isCorrect = question.answers.length > 0 && question.answers.every((correctAnsDef) => {
|
|
462
|
-
|
|
463
|
-
const userValForBlank = (_a2 = userAnswerMap[correctAnsDef.blankId]) == null ? void 0 : _a2.trim();
|
|
426
|
+
const userValForBlank = userAnswerMap[correctAnsDef.blankId]?.trim();
|
|
464
427
|
if (userValForBlank === void 0) return false;
|
|
465
|
-
const caseSensitive =
|
|
428
|
+
const caseSensitive = question.isCaseSensitive ?? false;
|
|
466
429
|
return correctAnsDef.acceptedValues.some(
|
|
467
430
|
(accVal) => caseSensitive ? accVal.trim() === userValForBlank : accVal.trim().toLowerCase() === userValForBlank.toLowerCase()
|
|
468
431
|
);
|
|
@@ -487,17 +450,15 @@ var FillInTheBlanksEvaluator = class {
|
|
|
487
450
|
// src/services/evaluators/drag-and-drop-evaluator.ts
|
|
488
451
|
var DragAndDropEvaluator = class {
|
|
489
452
|
async evaluate(question, answer) {
|
|
490
|
-
|
|
491
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
453
|
+
const points = question.points ?? 0;
|
|
492
454
|
let isCorrect = false;
|
|
493
455
|
if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
|
|
494
456
|
const userAnswerMap = answer;
|
|
495
457
|
isCorrect = question.answerMap.length === Object.keys(userAnswerMap).length && question.answerMap.every((map) => userAnswerMap[map.draggableId] === map.dropZoneId);
|
|
496
458
|
}
|
|
497
459
|
const correctMap = question.answerMap.reduce((acc, curr) => {
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
const dropZoneText = ((_b = question.dropZones.find((z4) => z4.id === curr.dropZoneId)) == null ? void 0 : _b.label) || "";
|
|
460
|
+
const draggableText = question.draggableItems.find((d) => d.id === curr.draggableId)?.content || "";
|
|
461
|
+
const dropZoneText = question.dropZones.find((z4) => z4.id === curr.dropZoneId)?.label || "";
|
|
501
462
|
acc[draggableText] = dropZoneText;
|
|
502
463
|
return acc;
|
|
503
464
|
}, {});
|
|
@@ -516,8 +477,7 @@ var DragAndDropEvaluator = class {
|
|
|
516
477
|
// src/services/evaluators/hotspot-evaluator.ts
|
|
517
478
|
var HotspotEvaluator = class {
|
|
518
479
|
async evaluate(question, answer) {
|
|
519
|
-
|
|
520
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
480
|
+
const points = question.points ?? 0;
|
|
521
481
|
let isCorrect = false;
|
|
522
482
|
if (Array.isArray(answer)) {
|
|
523
483
|
const userAnswerSet = new Set(answer);
|
|
@@ -525,10 +485,7 @@ var HotspotEvaluator = class {
|
|
|
525
485
|
isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
|
|
526
486
|
}
|
|
527
487
|
const correctValues = question.correctHotspotIds.map(
|
|
528
|
-
(id) =>
|
|
529
|
-
var _a2;
|
|
530
|
-
return ((_a2 = question.hotspots.find((h) => h.id === id)) == null ? void 0 : _a2.description) || id;
|
|
531
|
-
}
|
|
488
|
+
(id) => question.hotspots.find((h) => h.id === id)?.description || id
|
|
532
489
|
);
|
|
533
490
|
const correctAnswerDetail = {
|
|
534
491
|
id: question.correctHotspotIds,
|
|
@@ -545,11 +502,10 @@ var HotspotEvaluator = class {
|
|
|
545
502
|
// src/services/evaluators/programming-evaluator.ts
|
|
546
503
|
var ProgrammingEvaluator = class {
|
|
547
504
|
async evaluate(question, answer) {
|
|
548
|
-
|
|
549
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
505
|
+
const points = question.points ?? 0;
|
|
550
506
|
let isCorrect = false;
|
|
551
507
|
if (typeof answer === "string" && typeof question.solutionGeneratedCode === "string") {
|
|
552
|
-
if (typeof window !== "undefined" &&
|
|
508
|
+
if (typeof window !== "undefined" && window.Blockly?.JavaScript) {
|
|
553
509
|
const LocalBlockly = window.Blockly;
|
|
554
510
|
let generatedUserCode = "";
|
|
555
511
|
try {
|
|
@@ -623,10 +579,10 @@ var JsonRepairEngine = class {
|
|
|
623
579
|
if (breakIndex !== -1) {
|
|
624
580
|
const stringContent = afterUnterminated.substring(0, breakIndex);
|
|
625
581
|
const remainder = afterUnterminated.substring(breakIndex);
|
|
626
|
-
const escapedContent = stringContent.replace(
|
|
582
|
+
const escapedContent = stringContent.replace(/(?<!\\)"/g, '\\"');
|
|
627
583
|
repaired = beforeUnterminated + escapedContent + '"' + remainder;
|
|
628
584
|
} else {
|
|
629
|
-
const escapedContent = afterUnterminated.replace(
|
|
585
|
+
const escapedContent = afterUnterminated.replace(/(?<!\\)"/g, '\\"');
|
|
630
586
|
repaired = beforeUnterminated + escapedContent + '"';
|
|
631
587
|
}
|
|
632
588
|
}
|
|
@@ -702,7 +658,6 @@ var JsonRepairEngine = class {
|
|
|
702
658
|
* Main repair function that attempts multiple strategies.
|
|
703
659
|
*/
|
|
704
660
|
static repairJson(jsonStr) {
|
|
705
|
-
var _a;
|
|
706
661
|
let current = jsonStr.trim();
|
|
707
662
|
const maxAttempts = 5;
|
|
708
663
|
let lastError = "";
|
|
@@ -732,7 +687,7 @@ var JsonRepairEngine = class {
|
|
|
732
687
|
}
|
|
733
688
|
lastError = validation.error || "";
|
|
734
689
|
lastPosition = validation.position;
|
|
735
|
-
if (
|
|
690
|
+
if (validation.error?.includes("Unterminated string")) {
|
|
736
691
|
current = this.repairUnterminatedStrings(current);
|
|
737
692
|
} else {
|
|
738
693
|
current = this.applyCommonFixes(current);
|
|
@@ -1027,9 +982,10 @@ var CodeEvaluationService = class {
|
|
|
1027
982
|
userCode,
|
|
1028
983
|
testCase
|
|
1029
984
|
}, this.apiKey);
|
|
1030
|
-
return
|
|
1031
|
-
testCaseId: testCase.id
|
|
1032
|
-
|
|
985
|
+
return {
|
|
986
|
+
testCaseId: testCase.id,
|
|
987
|
+
...aiResult
|
|
988
|
+
};
|
|
1033
989
|
}
|
|
1034
990
|
/**
|
|
1035
991
|
* Evaluates user's code against all test cases for a given question.
|
|
@@ -1066,8 +1022,7 @@ var CodeEvaluationService = class {
|
|
|
1066
1022
|
// src/services/evaluators/coding-evaluator.ts
|
|
1067
1023
|
var CodingEvaluator = class {
|
|
1068
1024
|
async evaluate(question, answer) {
|
|
1069
|
-
|
|
1070
|
-
const points = (_a = question.points) != null ? _a : 0;
|
|
1025
|
+
const points = question.points ?? 0;
|
|
1071
1026
|
if (typeof answer !== "string" || !answer.trim()) {
|
|
1072
1027
|
return {
|
|
1073
1028
|
isCorrect: false,
|
|
@@ -1124,17 +1079,16 @@ var QuizEngine = class {
|
|
|
1124
1079
|
this.quizResultState = { scormStatus: "idle" };
|
|
1125
1080
|
this.questionStartTime = null;
|
|
1126
1081
|
this.questionTimings = /* @__PURE__ */ new Map();
|
|
1127
|
-
var _a, _b, _c, _d, _e;
|
|
1128
1082
|
this.config = options.config;
|
|
1129
1083
|
this.callbacks = options.callbacks || {};
|
|
1130
|
-
this.questions =
|
|
1084
|
+
this.questions = this.config.settings?.shuffleQuestions ? [...this.config.questions].sort(() => Math.random() - 0.5) : this.config.questions;
|
|
1131
1085
|
this.overallStartTime = Date.now();
|
|
1132
1086
|
this.evaluators = /* @__PURE__ */ new Map();
|
|
1133
1087
|
this.registerEvaluators();
|
|
1134
|
-
if (
|
|
1088
|
+
if (this.config.settings?.timeLimitMinutes && this.config.settings.timeLimitMinutes > 0) {
|
|
1135
1089
|
this.timeLeftInSeconds = this.config.settings.timeLimitMinutes * 60;
|
|
1136
1090
|
}
|
|
1137
|
-
if (
|
|
1091
|
+
if (this.config.settings?.scorm) {
|
|
1138
1092
|
this.quizResultState.scormStatus = "initializing";
|
|
1139
1093
|
this.scormService = new SCORMService(this.config.settings.scorm);
|
|
1140
1094
|
if (this.scormService.hasAPI()) {
|
|
@@ -1167,7 +1121,7 @@ var QuizEngine = class {
|
|
|
1167
1121
|
if (this.timeLeftInSeconds !== null) {
|
|
1168
1122
|
this.startTimer();
|
|
1169
1123
|
}
|
|
1170
|
-
|
|
1124
|
+
this.callbacks.onQuestionChange?.(initialQ, this.getCurrentQuestionNumber(), this.getTotalQuestions());
|
|
1171
1125
|
}
|
|
1172
1126
|
registerEvaluators() {
|
|
1173
1127
|
this.evaluators.set("multiple_choice", new MultipleChoiceEvaluator());
|
|
@@ -1205,15 +1159,14 @@ var QuizEngine = class {
|
|
|
1205
1159
|
}
|
|
1206
1160
|
}
|
|
1207
1161
|
handleTick() {
|
|
1208
|
-
var _a, _b, _c, _d;
|
|
1209
1162
|
if (this.timeLeftInSeconds === null) return;
|
|
1210
1163
|
if (this.timeLeftInSeconds > 0) {
|
|
1211
1164
|
this.timeLeftInSeconds--;
|
|
1212
|
-
|
|
1165
|
+
this.callbacks.onTimeTick?.(this.timeLeftInSeconds);
|
|
1213
1166
|
}
|
|
1214
1167
|
if (this.timeLeftInSeconds <= 0) {
|
|
1215
1168
|
this.stopTimer();
|
|
1216
|
-
|
|
1169
|
+
this.callbacks.onQuizTimeUp?.();
|
|
1217
1170
|
this.calculateResults();
|
|
1218
1171
|
}
|
|
1219
1172
|
}
|
|
@@ -1236,43 +1189,39 @@ var QuizEngine = class {
|
|
|
1236
1189
|
return this.quizResultState.score !== void 0;
|
|
1237
1190
|
}
|
|
1238
1191
|
submitAnswer(questionId, answer) {
|
|
1239
|
-
var _a, _b;
|
|
1240
1192
|
this.userAnswers.set(questionId, answer);
|
|
1241
1193
|
const question = this.questions.find((q) => q.id === questionId);
|
|
1242
|
-
if (question)
|
|
1194
|
+
if (question) this.callbacks.onAnswerSubmit?.(question, answer);
|
|
1243
1195
|
}
|
|
1244
1196
|
nextQuestion() {
|
|
1245
|
-
var _a, _b;
|
|
1246
1197
|
this._recordCurrentQuestionTime();
|
|
1247
1198
|
if (this.currentQuestionIndex < this.questions.length - 1) {
|
|
1248
1199
|
this.currentQuestionIndex++;
|
|
1249
1200
|
const currentQ = this.getCurrentQuestion();
|
|
1250
1201
|
this.questionStartTime = Date.now();
|
|
1251
|
-
|
|
1202
|
+
this.callbacks.onQuestionChange?.(currentQ, this.getCurrentQuestionNumber(), this.getTotalQuestions());
|
|
1252
1203
|
return currentQ;
|
|
1253
1204
|
}
|
|
1254
1205
|
return null;
|
|
1255
1206
|
}
|
|
1256
1207
|
previousQuestion() {
|
|
1257
|
-
var _a, _b;
|
|
1258
1208
|
this._recordCurrentQuestionTime();
|
|
1259
1209
|
if (this.currentQuestionIndex > 0) {
|
|
1260
1210
|
this.currentQuestionIndex--;
|
|
1261
1211
|
const currentQ = this.getCurrentQuestion();
|
|
1262
1212
|
this.questionStartTime = Date.now();
|
|
1263
|
-
|
|
1213
|
+
this.callbacks.onQuestionChange?.(currentQ, this.getCurrentQuestionNumber(), this.getTotalQuestions());
|
|
1264
1214
|
return currentQ;
|
|
1265
1215
|
}
|
|
1266
1216
|
return null;
|
|
1267
1217
|
}
|
|
1268
1218
|
goToQuestion(index) {
|
|
1269
|
-
var _a, _b;
|
|
1270
1219
|
if (index >= 0 && index < this.questions.length && index !== this.currentQuestionIndex) {
|
|
1271
1220
|
this._recordCurrentQuestionTime();
|
|
1272
1221
|
this.currentQuestionIndex = index;
|
|
1273
1222
|
const currentQ = this.getCurrentQuestion();
|
|
1274
1223
|
this.questionStartTime = Date.now();
|
|
1275
|
-
|
|
1224
|
+
this.callbacks.onQuestionChange?.(currentQ, this.getCurrentQuestionNumber(), this.getTotalQuestions());
|
|
1276
1225
|
return currentQ;
|
|
1277
1226
|
}
|
|
1278
1227
|
return this.getCurrentQuestion();
|
|
@@ -1298,7 +1247,6 @@ var QuizEngine = class {
|
|
|
1298
1247
|
}
|
|
1299
1248
|
// (Tiếp theo từ Phần 1)
|
|
1300
1249
|
async calculateResults() {
|
|
1301
|
-
var _a, _b, _c, _d, _e;
|
|
1302
1250
|
this.stopTimer();
|
|
1303
1251
|
this._recordCurrentQuestionTime();
|
|
1304
1252
|
let totalScore = 0;
|
|
@@ -1307,7 +1255,7 @@ var QuizEngine = class {
|
|
|
1307
1255
|
let accumulatedTotalTimeSpent = 0;
|
|
1308
1256
|
for (const question of this.questions) {
|
|
1309
1257
|
const userAnswerRaw = this.userAnswers.get(question.id) || null;
|
|
1310
|
-
maxScore +=
|
|
1258
|
+
maxScore += question.points ?? 0;
|
|
1311
1259
|
const evaluator = this.evaluators.get(question.questionType);
|
|
1312
1260
|
if (!evaluator) {
|
|
1313
1261
|
console.warn(`No evaluator found for question type: ${question.questionType}`);
|
|
@@ -1347,13 +1295,13 @@ var QuizEngine = class {
|
|
|
1347
1295
|
}
|
|
1348
1296
|
const percentage = maxScore > 0 ? parseFloat((totalScore / maxScore * 100).toFixed(2)) : 0;
|
|
1349
1297
|
let passed = void 0;
|
|
1350
|
-
if (
|
|
1298
|
+
if (this.config.settings?.passingScorePercent != null) {
|
|
1351
1299
|
passed = percentage >= this.config.settings.passingScorePercent;
|
|
1352
1300
|
}
|
|
1353
1301
|
const totalQuizTimeSpentSeconds = parseFloat(accumulatedTotalTimeSpent.toFixed(2));
|
|
1354
1302
|
const averageTimePerQuestionSeconds = this.questions.length > 0 ? parseFloat((totalQuizTimeSpentSeconds / this.questions.length).toFixed(2)) : 0;
|
|
1355
1303
|
const metadataPerformance = await this._calculateMetadataPerformance();
|
|
1356
|
-
const finalResults =
|
|
1304
|
+
const finalResults = {
|
|
1357
1305
|
score: totalScore,
|
|
1358
1306
|
maxScore,
|
|
1359
1307
|
percentage,
|
|
@@ -1365,39 +1313,33 @@ var QuizEngine = class {
|
|
|
1365
1313
|
scormError: this.quizResultState.scormError,
|
|
1366
1314
|
studentName: this.quizResultState.studentName,
|
|
1367
1315
|
totalTimeSpentSeconds: totalQuizTimeSpentSeconds,
|
|
1368
|
-
averageTimePerQuestionSeconds
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1316
|
+
averageTimePerQuestionSeconds,
|
|
1317
|
+
...metadataPerformance
|
|
1318
|
+
};
|
|
1319
|
+
this.quizResultState = { ...this.quizResultState, ...finalResults };
|
|
1320
|
+
if (this.config.settings?.scorm) this._sendResultsToSCORM(finalResults);
|
|
1372
1321
|
await this._sendResultsToWebhook(finalResults);
|
|
1373
|
-
|
|
1322
|
+
this.callbacks.onQuizFinish?.(finalResults);
|
|
1374
1323
|
return finalResults;
|
|
1375
1324
|
}
|
|
1376
1325
|
formatUserAnswerDetail(question, userAnswerRaw) {
|
|
1377
|
-
var _a, _b, _c, _d, _e;
|
|
1378
1326
|
if (userAnswerRaw === null) return null;
|
|
1379
1327
|
switch (question.questionType) {
|
|
1380
1328
|
case "multiple_choice": {
|
|
1381
1329
|
const q = question;
|
|
1382
1330
|
const id = userAnswerRaw;
|
|
1383
|
-
return { id, value:
|
|
1331
|
+
return { id, value: q.options.find((opt) => opt.id === id)?.text || "" };
|
|
1384
1332
|
}
|
|
1385
1333
|
case "multiple_response": {
|
|
1386
1334
|
const q = question;
|
|
1387
1335
|
const ids = userAnswerRaw;
|
|
1388
|
-
const values = ids.map((id) =>
|
|
1389
|
-
var _a2;
|
|
1390
|
-
return ((_a2 = q.options.find((opt) => opt.id === id)) == null ? void 0 : _a2.text) || "";
|
|
1391
|
-
});
|
|
1336
|
+
const values = ids.map((id) => q.options.find((opt) => opt.id === id)?.text || "");
|
|
1392
1337
|
return { id: ids, value: values };
|
|
1393
1338
|
}
|
|
1394
1339
|
case "sequence": {
|
|
1395
1340
|
const q = question;
|
|
1396
1341
|
const ids = userAnswerRaw;
|
|
1397
|
-
const values = ids.map((id) =>
|
|
1398
|
-
var _a2;
|
|
1399
|
-
return ((_a2 = q.items.find((item) => item.id === id)) == null ? void 0 : _a2.content) || "";
|
|
1400
|
-
});
|
|
1342
|
+
const values = ids.map((id) => q.items.find((item) => item.id === id)?.content || "");
|
|
1401
1343
|
return { id: ids, value: values };
|
|
1402
1344
|
}
|
|
1403
1345
|
case "matching": {
|
|
@@ -1406,8 +1348,8 @@ var QuizEngine = class {
|
|
|
1406
1348
|
const valueMap = {};
|
|
1407
1349
|
for (const promptId in userAnswerMap) {
|
|
1408
1350
|
const optionId = userAnswerMap[promptId];
|
|
1409
|
-
const promptText =
|
|
1410
|
-
const optionText =
|
|
1351
|
+
const promptText = q.prompts.find((p) => p.id === promptId)?.content || "";
|
|
1352
|
+
const optionText = q.options.find((o) => o.id === optionId)?.content || "";
|
|
1411
1353
|
valueMap[promptText] = optionText;
|
|
1412
1354
|
}
|
|
1413
1355
|
return { id: null, value: valueMap };
|
|
@@ -1419,8 +1361,8 @@ var QuizEngine = class {
|
|
|
1419
1361
|
const enrichedUserAnswerMap = {};
|
|
1420
1362
|
for (const draggableId in userAnswerMapByIds) {
|
|
1421
1363
|
const dropZoneId = userAnswerMapByIds[draggableId];
|
|
1422
|
-
const draggableText =
|
|
1423
|
-
const dropZoneText =
|
|
1364
|
+
const draggableText = q.draggableItems.find((d) => d.id === draggableId)?.content || `(ID: ${draggableId})`;
|
|
1365
|
+
const dropZoneText = q.dropZones.find((z4) => z4.id === dropZoneId)?.label || `(ID: ${dropZoneId})`;
|
|
1424
1366
|
enrichedUserAnswerMap[draggableText] = dropZoneText;
|
|
1425
1367
|
}
|
|
1426
1368
|
return { id: null, value: enrichedUserAnswerMap };
|
|
@@ -1432,7 +1374,6 @@ var QuizEngine = class {
|
|
|
1432
1374
|
}
|
|
1433
1375
|
}
|
|
1434
1376
|
async _calculateMetadataPerformance() {
|
|
1435
|
-
var _a;
|
|
1436
1377
|
const loPerformanceMap = /* @__PURE__ */ new Map();
|
|
1437
1378
|
const categoryPerformanceMap = /* @__PURE__ */ new Map();
|
|
1438
1379
|
const topicPerformanceMap = /* @__PURE__ */ new Map();
|
|
@@ -1454,7 +1395,7 @@ var QuizEngine = class {
|
|
|
1454
1395
|
const evaluator = this.evaluators.get(q.questionType);
|
|
1455
1396
|
if (evaluator) {
|
|
1456
1397
|
const { isCorrect } = await evaluator.evaluate(q, userAnswer);
|
|
1457
|
-
const pointsForThisQuestion =
|
|
1398
|
+
const pointsForThisQuestion = q.points ?? 0;
|
|
1458
1399
|
updateMap(loPerformanceMap, q.learningObjective, pointsForThisQuestion, isCorrect);
|
|
1459
1400
|
updateMap(categoryPerformanceMap, q.category, pointsForThisQuestion, isCorrect);
|
|
1460
1401
|
updateMap(topicPerformanceMap, q.topic, pointsForThisQuestion, isCorrect);
|
|
@@ -1481,8 +1422,7 @@ var QuizEngine = class {
|
|
|
1481
1422
|
};
|
|
1482
1423
|
}
|
|
1483
1424
|
async _sendResultsToWebhook(results) {
|
|
1484
|
-
|
|
1485
|
-
if (!((_a = this.config.settings) == null ? void 0 : _a.webhookUrl)) {
|
|
1425
|
+
if (!this.config.settings?.webhookUrl) {
|
|
1486
1426
|
results.webhookStatus = "idle";
|
|
1487
1427
|
return;
|
|
1488
1428
|
}
|
|
@@ -1510,12 +1450,11 @@ var QuizEngine = class {
|
|
|
1510
1450
|
}
|
|
1511
1451
|
}
|
|
1512
1452
|
_sendResultsToSCORM(results) {
|
|
1513
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1514
1453
|
if (!this.scormService || !this.scormService.hasAPI() || this.quizResultState.scormStatus === "no_api") {
|
|
1515
1454
|
results.scormStatus = this.quizResultState.scormStatus || "idle";
|
|
1516
1455
|
return;
|
|
1517
1456
|
}
|
|
1518
|
-
if (this.quizResultState.scormStatus === "error" &&
|
|
1457
|
+
if (this.quizResultState.scormStatus === "error" && this.quizResultState.scormError?.includes("initialization failed")) {
|
|
1519
1458
|
results.scormStatus = "error";
|
|
1520
1459
|
results.scormError = this.quizResultState.scormError;
|
|
1521
1460
|
return;
|
|
@@ -1524,15 +1463,15 @@ var QuizEngine = class {
|
|
|
1524
1463
|
try {
|
|
1525
1464
|
this.scormService.setScore(results.score, results.maxScore, 0);
|
|
1526
1465
|
let lessonStatusSetting = "completed";
|
|
1527
|
-
if (
|
|
1466
|
+
if (this.config.settings?.passingScorePercent !== void 0 && this.config.settings?.passingScorePercent !== null) {
|
|
1528
1467
|
lessonStatusSetting = results.passed ? "passed" : "failed";
|
|
1529
|
-
} else if (
|
|
1468
|
+
} else if (this.config.settings?.scorm?.setCompletionOnFinish) {
|
|
1530
1469
|
lessonStatusSetting = "completed";
|
|
1531
1470
|
}
|
|
1532
1471
|
this.scormService.setLessonStatus(lessonStatusSetting, results.passed);
|
|
1533
1472
|
if (results.totalTimeSpentSeconds !== void 0 && this.scormService.formatCMITime) {
|
|
1534
1473
|
const cmiTime = this.scormService.formatCMITime(results.totalTimeSpentSeconds);
|
|
1535
|
-
const sessionTimeVar =
|
|
1474
|
+
const sessionTimeVar = this.config.settings?.scorm?.sessionTimeVar || (this.scormService.getSCORMVersion() === "2004" ? "cmi.session_time" : "cmi.core.session_time");
|
|
1536
1475
|
if (sessionTimeVar) this.scormService.setValue(sessionTimeVar, cmiTime);
|
|
1537
1476
|
}
|
|
1538
1477
|
const commitResult = this.scormService.commit();
|
|
@@ -1572,18 +1511,19 @@ var QuizEditorService = class {
|
|
|
1572
1511
|
};
|
|
1573
1512
|
switch (type) {
|
|
1574
1513
|
case "true_false":
|
|
1575
|
-
return
|
|
1514
|
+
return { ...baseNewQuestion, questionType: "true_false", correctAnswer: true };
|
|
1576
1515
|
case "multiple_choice":
|
|
1577
|
-
return
|
|
1516
|
+
return { ...baseNewQuestion, questionType: "multiple_choice", options: [], correctAnswerId: "" };
|
|
1578
1517
|
case "multiple_response":
|
|
1579
|
-
return
|
|
1518
|
+
return { ...baseNewQuestion, questionType: "multiple_response", options: [], correctAnswerIds: [] };
|
|
1580
1519
|
case "short_answer":
|
|
1581
|
-
return
|
|
1520
|
+
return { ...baseNewQuestion, questionType: "short_answer", acceptedAnswers: [""], isCaseSensitive: false };
|
|
1582
1521
|
case "numeric":
|
|
1583
|
-
return
|
|
1522
|
+
return { ...baseNewQuestion, questionType: "numeric", answer: 0 };
|
|
1584
1523
|
case "fill_in_the_blanks": {
|
|
1585
1524
|
const blankId = generateUniqueId("blank_");
|
|
1586
|
-
return
|
|
1525
|
+
return {
|
|
1526
|
+
...baseNewQuestion,
|
|
1587
1527
|
questionType: "fill_in_the_blanks",
|
|
1588
1528
|
segments: [
|
|
1589
1529
|
{ type: "text", content: "Your text before " },
|
|
@@ -1592,49 +1532,52 @@ var QuizEditorService = class {
|
|
|
1592
1532
|
],
|
|
1593
1533
|
answers: [{ blankId, acceptedValues: [""] }],
|
|
1594
1534
|
isCaseSensitive: false
|
|
1595
|
-
}
|
|
1535
|
+
};
|
|
1596
1536
|
}
|
|
1597
1537
|
case "sequence":
|
|
1598
|
-
return
|
|
1538
|
+
return { ...baseNewQuestion, questionType: "sequence", items: [], correctOrder: [] };
|
|
1599
1539
|
case "matching":
|
|
1600
|
-
return
|
|
1540
|
+
return { ...baseNewQuestion, questionType: "matching", prompts: [], options: [], correctAnswerMap: [], shuffleOptions: true };
|
|
1601
1541
|
case "drag_and_drop":
|
|
1602
|
-
return
|
|
1542
|
+
return { ...baseNewQuestion, questionType: "drag_and_drop", draggableItems: [], dropZones: [], answerMap: [] };
|
|
1603
1543
|
case "hotspot":
|
|
1604
|
-
return
|
|
1544
|
+
return { ...baseNewQuestion, questionType: "hotspot", imageUrl: "", hotspots: [], correctHotspotIds: [] };
|
|
1605
1545
|
case "blockly_programming":
|
|
1606
|
-
return
|
|
1546
|
+
return {
|
|
1547
|
+
...baseNewQuestion,
|
|
1607
1548
|
questionType: "blockly_programming",
|
|
1608
1549
|
toolboxDefinition: '<xml xmlns="https://developers.google.com/blockly/xml"></xml>',
|
|
1609
1550
|
initialWorkspace: "",
|
|
1610
1551
|
solutionWorkspaceXML: "",
|
|
1611
1552
|
solutionGeneratedCode: ""
|
|
1612
|
-
}
|
|
1553
|
+
};
|
|
1613
1554
|
case "scratch_programming":
|
|
1614
|
-
return
|
|
1555
|
+
return {
|
|
1556
|
+
...baseNewQuestion,
|
|
1615
1557
|
questionType: "scratch_programming",
|
|
1616
1558
|
toolboxDefinition: '<xml xmlns="https://developers.google.com/blockly/xml"></xml>',
|
|
1617
1559
|
initialWorkspace: "",
|
|
1618
1560
|
solutionWorkspaceXML: "",
|
|
1619
1561
|
solutionGeneratedCode: ""
|
|
1620
|
-
}
|
|
1562
|
+
};
|
|
1621
1563
|
case "coding":
|
|
1622
|
-
return
|
|
1564
|
+
return {
|
|
1565
|
+
...baseNewQuestion,
|
|
1623
1566
|
questionType: "coding",
|
|
1624
|
-
|
|
1567
|
+
codingLanguage: "javascript",
|
|
1625
1568
|
solutionCode: "",
|
|
1626
1569
|
testCases: [],
|
|
1627
1570
|
functionSignature: "",
|
|
1628
1571
|
points: 25
|
|
1629
1572
|
// Coding questions are worth more by default
|
|
1630
|
-
}
|
|
1573
|
+
};
|
|
1631
1574
|
default:
|
|
1632
1575
|
const _exhaustiveCheck = type;
|
|
1633
1576
|
throw new Error(`Question type "${_exhaustiveCheck}" is not supported for creation.`);
|
|
1634
1577
|
}
|
|
1635
1578
|
}
|
|
1636
1579
|
addQuestion(question) {
|
|
1637
|
-
const newQuestion =
|
|
1580
|
+
const newQuestion = { ...question };
|
|
1638
1581
|
if (newQuestion.id.startsWith("new_")) {
|
|
1639
1582
|
newQuestion.id = generateUniqueId(`${newQuestion.questionType}_`);
|
|
1640
1583
|
}
|
|
@@ -1748,8 +1691,7 @@ var QuestionImportService = class {
|
|
|
1748
1691
|
const values = line.split(" ");
|
|
1749
1692
|
const rowObject = {};
|
|
1750
1693
|
header.forEach((h, i) => {
|
|
1751
|
-
|
|
1752
|
-
rowObject[h] = ((_a = values[i]) == null ? void 0 : _a.trim()) || "";
|
|
1694
|
+
rowObject[h] = values[i]?.trim() || "";
|
|
1753
1695
|
});
|
|
1754
1696
|
try {
|
|
1755
1697
|
const transformedObject = this.transformTsvRowToRawObject(rowObject);
|
|
@@ -1791,17 +1733,17 @@ var QuestionImportService = class {
|
|
|
1791
1733
|
};
|
|
1792
1734
|
switch (questionType) {
|
|
1793
1735
|
case "multiple_choice":
|
|
1794
|
-
return
|
|
1736
|
+
return { ...base, options: options.split("|"), correctAnswer };
|
|
1795
1737
|
case "multiple_response":
|
|
1796
|
-
return
|
|
1738
|
+
return { ...base, options: options.split("|"), correctAnswers: correctAnswer.split("|") };
|
|
1797
1739
|
case "true_false":
|
|
1798
|
-
return
|
|
1740
|
+
return { ...base, correctAnswer: correctAnswer.toLowerCase() === "true" };
|
|
1799
1741
|
case "short_answer":
|
|
1800
|
-
return
|
|
1742
|
+
return { ...base, acceptedAnswers: correctAnswer.split("|") };
|
|
1801
1743
|
case "numeric":
|
|
1802
|
-
return
|
|
1744
|
+
return { ...base, answer: parseFloat(correctAnswer), tolerance: tolerance ? parseFloat(tolerance) : void 0 };
|
|
1803
1745
|
case "sequence":
|
|
1804
|
-
return
|
|
1746
|
+
return { ...base, items: options.split("|"), correctOrder: correctAnswer.split("|") };
|
|
1805
1747
|
case "matching": {
|
|
1806
1748
|
const [promptsStr, optionsStr] = options.split("#");
|
|
1807
1749
|
const prompts = promptsStr.replace("prompts:", "").split("|");
|
|
@@ -1811,7 +1753,7 @@ var QuestionImportService = class {
|
|
|
1811
1753
|
acc[key] = valParts.join(":");
|
|
1812
1754
|
return acc;
|
|
1813
1755
|
}, {});
|
|
1814
|
-
return
|
|
1756
|
+
return { ...base, prompts, options: opts, correctAnswerMap };
|
|
1815
1757
|
}
|
|
1816
1758
|
case "fill_in_the_blanks": {
|
|
1817
1759
|
const blanks = correctAnswer.split("#").reduce((acc, part) => {
|
|
@@ -1819,7 +1761,7 @@ var QuestionImportService = class {
|
|
|
1819
1761
|
acc[key] = valuesStr.split("|");
|
|
1820
1762
|
return acc;
|
|
1821
1763
|
}, {});
|
|
1822
|
-
return
|
|
1764
|
+
return { ...base, sentenceWithPlaceholders: options, blanks };
|
|
1823
1765
|
}
|
|
1824
1766
|
default:
|
|
1825
1767
|
throw new Error(`Unsupported questionType "${questionType}" in TSV.`);
|
|
@@ -1840,20 +1782,20 @@ var QuestionImportService = class {
|
|
|
1840
1782
|
const options = validatedRawQ.options.map((text) => ({ id: generateUniqueId("opt_"), text }));
|
|
1841
1783
|
const correctOption = options.find((opt) => opt.text === validatedRawQ.correctAnswer);
|
|
1842
1784
|
if (!correctOption) throw new Error(`Correct answer "${validatedRawQ.correctAnswer}" not found in options.`);
|
|
1843
|
-
return
|
|
1785
|
+
return { ...baseQuestionData, questionType: "multiple_choice", options, correctAnswerId: correctOption.id };
|
|
1844
1786
|
}
|
|
1845
1787
|
case "multiple_response": {
|
|
1846
1788
|
const options = validatedRawQ.options.map((text) => ({ id: generateUniqueId("opt_mr_"), text }));
|
|
1847
1789
|
const correctIds = options.filter((opt) => validatedRawQ.correctAnswers.includes(opt.text)).map((opt) => opt.id);
|
|
1848
1790
|
if (correctIds.length !== validatedRawQ.correctAnswers.length) throw new Error("Some correct answers were not found in options.");
|
|
1849
|
-
return
|
|
1791
|
+
return { ...baseQuestionData, questionType: "multiple_response", options, correctAnswerIds: correctIds };
|
|
1850
1792
|
}
|
|
1851
1793
|
case "true_false":
|
|
1852
|
-
return
|
|
1794
|
+
return { ...baseQuestionData, questionType: "true_false", correctAnswer: validatedRawQ.correctAnswer };
|
|
1853
1795
|
case "short_answer":
|
|
1854
|
-
return
|
|
1796
|
+
return { ...baseQuestionData, questionType: "short_answer", acceptedAnswers: validatedRawQ.acceptedAnswers, isCaseSensitive: false };
|
|
1855
1797
|
case "numeric":
|
|
1856
|
-
return
|
|
1798
|
+
return { ...baseQuestionData, questionType: "numeric", answer: validatedRawQ.answer, tolerance: validatedRawQ.tolerance };
|
|
1857
1799
|
case "sequence": {
|
|
1858
1800
|
if (validatedRawQ.items.length !== validatedRawQ.correctOrder.length) {
|
|
1859
1801
|
throw new Error("The number of items must match the number of items in the correct order for a sequence question.");
|
|
@@ -1864,7 +1806,7 @@ var QuestionImportService = class {
|
|
|
1864
1806
|
if (!foundItem) throw new Error(`Sequence item "${orderText}" in correctOrder not found in items list.`);
|
|
1865
1807
|
return foundItem.id;
|
|
1866
1808
|
});
|
|
1867
|
-
return
|
|
1809
|
+
return { ...baseQuestionData, questionType: "sequence", items, correctOrder };
|
|
1868
1810
|
}
|
|
1869
1811
|
case "matching": {
|
|
1870
1812
|
if (validatedRawQ.prompts.length !== Object.keys(validatedRawQ.correctAnswerMap).length) {
|
|
@@ -1878,7 +1820,7 @@ var QuestionImportService = class {
|
|
|
1878
1820
|
if (!prompt || !option) throw new Error(`Matching pair "${promptText}":"${optionText}" not found in prompts/options.`);
|
|
1879
1821
|
return { promptId: prompt.id, optionId: option.id };
|
|
1880
1822
|
});
|
|
1881
|
-
return
|
|
1823
|
+
return { ...baseQuestionData, questionType: "matching", prompts, options, correctAnswerMap, shuffleOptions: true };
|
|
1882
1824
|
}
|
|
1883
1825
|
case "fill_in_the_blanks": {
|
|
1884
1826
|
const { sentenceWithPlaceholders, blanks } = validatedRawQ;
|
|
@@ -1906,7 +1848,7 @@ var QuestionImportService = class {
|
|
|
1906
1848
|
if (lastIndex < sentenceWithPlaceholders.length) {
|
|
1907
1849
|
segments.push({ type: "text", content: sentenceWithPlaceholders.substring(lastIndex) });
|
|
1908
1850
|
}
|
|
1909
|
-
return
|
|
1851
|
+
return { ...baseQuestionData, questionType: "fill_in_the_blanks", segments, answers, isCaseSensitive: false };
|
|
1910
1852
|
}
|
|
1911
1853
|
}
|
|
1912
1854
|
throw new Error(`Unhandled question type in createQuestionFromRawObject: ${validatedRawQ.questionType}`);
|
|
@@ -1967,8 +1909,7 @@ var UserConfigService = class {
|
|
|
1967
1909
|
this.setConfig("weeklyGoal", goal);
|
|
1968
1910
|
}
|
|
1969
1911
|
static getLanguage() {
|
|
1970
|
-
|
|
1971
|
-
return (_a = this.getConfig("language", "en")) != null ? _a : "en";
|
|
1912
|
+
return this.getConfig("language", "en") ?? "en";
|
|
1972
1913
|
}
|
|
1973
1914
|
static setLanguage(language) {
|
|
1974
1915
|
this.setConfig("language", language);
|
|
@@ -1986,10 +1927,11 @@ var UserConfigService = class {
|
|
|
1986
1927
|
*/
|
|
1987
1928
|
static addGoal(newGoal) {
|
|
1988
1929
|
const goals = this.getGoals();
|
|
1989
|
-
const goalToAdd =
|
|
1930
|
+
const goalToAdd = {
|
|
1931
|
+
...newGoal,
|
|
1990
1932
|
id: generateUniqueId("goal_"),
|
|
1991
1933
|
isAchieved: false
|
|
1992
|
-
}
|
|
1934
|
+
};
|
|
1993
1935
|
this.saveGoals([...goals, goalToAdd]);
|
|
1994
1936
|
}
|
|
1995
1937
|
static updateGoal(updatedGoal) {
|
|
@@ -2622,6 +2564,263 @@ var KnowledgeCardService = class {
|
|
|
2622
2564
|
}
|
|
2623
2565
|
};
|
|
2624
2566
|
|
|
2567
|
+
// src/services/metadataService.ts
|
|
2568
|
+
var LocalStorageManager = class {
|
|
2569
|
+
constructor(key) {
|
|
2570
|
+
this.key = `iqk_metadata_${key}`;
|
|
2571
|
+
}
|
|
2572
|
+
getAll() {
|
|
2573
|
+
if (typeof window === "undefined") return [];
|
|
2574
|
+
try {
|
|
2575
|
+
const stored = localStorage.getItem(this.key);
|
|
2576
|
+
return stored ? JSON.parse(stored) : [];
|
|
2577
|
+
} catch (e) {
|
|
2578
|
+
console.error(`Error reading from localStorage key ${this.key}:`, e);
|
|
2579
|
+
return [];
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
saveAll(items) {
|
|
2583
|
+
if (typeof window === "undefined") return;
|
|
2584
|
+
try {
|
|
2585
|
+
localStorage.setItem(this.key, JSON.stringify(items));
|
|
2586
|
+
} catch (e) {
|
|
2587
|
+
console.error(`Error writing to localStorage key ${this.key}:`, e);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
add(item) {
|
|
2591
|
+
const items = this.getAll();
|
|
2592
|
+
if (items.some((i) => i.code === item.code)) {
|
|
2593
|
+
throw new Error(`An item with code "${item.code}" already exists for ${this.key}.`);
|
|
2594
|
+
}
|
|
2595
|
+
const newItem = { ...item, id: generateUniqueId(`${this.key}_`) };
|
|
2596
|
+
this.saveAll([...items, newItem]);
|
|
2597
|
+
return newItem;
|
|
2598
|
+
}
|
|
2599
|
+
// ===== FIX IS HERE =====
|
|
2600
|
+
// Changed the type of 'updates' to allow 'code' to be part of the update object.
|
|
2601
|
+
update(id, updates) {
|
|
2602
|
+
const items = this.getAll();
|
|
2603
|
+
const index = items.findIndex((i) => i.id === id);
|
|
2604
|
+
if (index === -1) {
|
|
2605
|
+
console.warn(`Item with id "${id}" not found in ${this.key} for update.`);
|
|
2606
|
+
return null;
|
|
2607
|
+
}
|
|
2608
|
+
const updatedItem = { ...items[index], ...updates };
|
|
2609
|
+
items[index] = updatedItem;
|
|
2610
|
+
this.saveAll(items);
|
|
2611
|
+
return updatedItem;
|
|
2612
|
+
}
|
|
2613
|
+
// =======================
|
|
2614
|
+
delete(code) {
|
|
2615
|
+
const items = this.getAll();
|
|
2616
|
+
const newItems = items.filter((i) => i.code !== code);
|
|
2617
|
+
if (items.length === newItems.length) {
|
|
2618
|
+
return false;
|
|
2619
|
+
}
|
|
2620
|
+
this.saveAll(newItems);
|
|
2621
|
+
return true;
|
|
2622
|
+
}
|
|
2623
|
+
};
|
|
2624
|
+
var subjectManager = new LocalStorageManager("subjects");
|
|
2625
|
+
var gradeLevelManager = new LocalStorageManager("grade_levels");
|
|
2626
|
+
var topicManager = new LocalStorageManager("topics");
|
|
2627
|
+
var categoryManager = new LocalStorageManager("categories");
|
|
2628
|
+
var bloomLevelManager = new LocalStorageManager("bloom_levels");
|
|
2629
|
+
var questionTypeManager = new LocalStorageManager("question_types");
|
|
2630
|
+
var learningObjectiveManager = new LocalStorageManager("learning_objectives");
|
|
2631
|
+
var contextManager = new LocalStorageManager("contexts");
|
|
2632
|
+
var approachManager = new LocalStorageManager("approaches");
|
|
2633
|
+
function mapRawDifficultyToStandard(rawDifficulty) {
|
|
2634
|
+
switch (rawDifficulty) {
|
|
2635
|
+
case "E":
|
|
2636
|
+
case "E~M":
|
|
2637
|
+
return "easy";
|
|
2638
|
+
case "M":
|
|
2639
|
+
case "M~H":
|
|
2640
|
+
return "medium";
|
|
2641
|
+
case "H":
|
|
2642
|
+
return "hard";
|
|
2643
|
+
default:
|
|
2644
|
+
return "medium";
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
var _MetadataService = class _MetadataService {
|
|
2648
|
+
};
|
|
2649
|
+
// --- Subject Services ---
|
|
2650
|
+
_MetadataService.getSubjects = () => subjectManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2651
|
+
_MetadataService.addSubject = (name, code) => {
|
|
2652
|
+
return subjectManager.add({ code, name, createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2653
|
+
};
|
|
2654
|
+
_MetadataService.updateSubject = (id, name, code) => {
|
|
2655
|
+
return subjectManager.update(id, { name, code, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2656
|
+
};
|
|
2657
|
+
_MetadataService.deleteSubject = (code) => {
|
|
2658
|
+
const topics = _MetadataService.getTopics(code);
|
|
2659
|
+
if (topics.length > 0) {
|
|
2660
|
+
throw new Error("Cannot delete subject: It is referenced by topics.");
|
|
2661
|
+
}
|
|
2662
|
+
return subjectManager.delete(code);
|
|
2663
|
+
};
|
|
2664
|
+
// --- GradeLevel Services ---
|
|
2665
|
+
_MetadataService.getGradeLevels = () => gradeLevelManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2666
|
+
_MetadataService.addGradeLevel = (name, code) => gradeLevelManager.add({ name, code });
|
|
2667
|
+
_MetadataService.updateGradeLevel = (id, name, code) => gradeLevelManager.update(id, { name, code });
|
|
2668
|
+
_MetadataService.deleteGradeLevel = (code) => gradeLevelManager.delete(code);
|
|
2669
|
+
// --- Topic Services ---
|
|
2670
|
+
_MetadataService.getTopics = (subjectCode) => {
|
|
2671
|
+
const allTopics = topicManager.getAll();
|
|
2672
|
+
const filtered = subjectCode ? allTopics.filter((t) => t.subjectCode === subjectCode) : allTopics;
|
|
2673
|
+
return filtered.sort((a, b) => a.name.localeCompare(b.name));
|
|
2674
|
+
};
|
|
2675
|
+
_MetadataService.addTopic = (name, code, subjectCode) => topicManager.add({ name, code, subjectCode });
|
|
2676
|
+
_MetadataService.updateTopic = (id, name, code, subjectCode) => topicManager.update(id, { name, code, subjectCode });
|
|
2677
|
+
_MetadataService.deleteTopic = (code) => topicManager.delete(code);
|
|
2678
|
+
// --- BloomLevel Services ---
|
|
2679
|
+
_MetadataService.getBloomLevels = () => bloomLevelManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2680
|
+
_MetadataService.addBloomLevel = (name, code, description) => bloomLevelManager.add({ name, code, description });
|
|
2681
|
+
_MetadataService.updateBloomLevel = (id, name, code, description) => bloomLevelManager.update(id, { name, code, description });
|
|
2682
|
+
_MetadataService.deleteBloomLevel = (code) => bloomLevelManager.delete(code);
|
|
2683
|
+
// --- QuestionType Services ---
|
|
2684
|
+
_MetadataService.getQuestionTypes = () => questionTypeManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2685
|
+
_MetadataService.addQuestionType = (name, code, description) => questionTypeManager.add({ name, code, description });
|
|
2686
|
+
_MetadataService.updateQuestionType = (id, name, code, description) => questionTypeManager.update(id, { name, code, description });
|
|
2687
|
+
_MetadataService.deleteQuestionType = (code) => questionTypeManager.delete(code);
|
|
2688
|
+
// --- Category Services ---
|
|
2689
|
+
_MetadataService.getCategories = () => categoryManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2690
|
+
_MetadataService.addCategory = (name, code, description) => categoryManager.add({ name, code, description });
|
|
2691
|
+
_MetadataService.updateCategory = (id, name, code, description) => categoryManager.update(id, { name, code, description });
|
|
2692
|
+
_MetadataService.deleteCategory = (code) => categoryManager.delete(code);
|
|
2693
|
+
// --- Context Services ---
|
|
2694
|
+
_MetadataService.getContexts = () => contextManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2695
|
+
_MetadataService.addContext = (name, code, description) => contextManager.add({ name, code, description });
|
|
2696
|
+
_MetadataService.updateContext = (id, name, code, description) => contextManager.update(id, { name, code, description });
|
|
2697
|
+
_MetadataService.deleteContext = (code) => contextManager.delete(code);
|
|
2698
|
+
// --- Approach Services ---
|
|
2699
|
+
_MetadataService.getApproaches = () => approachManager.getAll().sort((a, b) => a.code.localeCompare(b.code));
|
|
2700
|
+
_MetadataService.addApproach = (approachData) => {
|
|
2701
|
+
const difficulty = mapRawDifficultyToStandard(approachData.rawDifficulty);
|
|
2702
|
+
return approachManager.add({ ...approachData, difficulty });
|
|
2703
|
+
};
|
|
2704
|
+
_MetadataService.updateApproach = (id, approachData) => {
|
|
2705
|
+
const updates = { ...approachData };
|
|
2706
|
+
if (approachData.rawDifficulty) {
|
|
2707
|
+
updates.difficulty = mapRawDifficultyToStandard(approachData.rawDifficulty);
|
|
2708
|
+
}
|
|
2709
|
+
return approachManager.update(id, updates);
|
|
2710
|
+
};
|
|
2711
|
+
_MetadataService.deleteApproach = (code) => approachManager.delete(code);
|
|
2712
|
+
// --- LearningObjective Services ---
|
|
2713
|
+
_MetadataService.getLearningObjectives = (subjectCode) => {
|
|
2714
|
+
const allLOs = learningObjectiveManager.getAll();
|
|
2715
|
+
const filtered = subjectCode ? allLOs.filter((lo) => lo.subjectCode === subjectCode) : allLOs;
|
|
2716
|
+
return filtered.sort((a, b) => a.name.localeCompare(b.name));
|
|
2717
|
+
};
|
|
2718
|
+
_MetadataService.addLearningObjective = (name, code, subjectCode, description) => learningObjectiveManager.add({ name, code, subjectCode, description });
|
|
2719
|
+
_MetadataService.updateLearningObjective = (id, name, code, subjectCode, description) => learningObjectiveManager.update(id, { name, code, subjectCode, description });
|
|
2720
|
+
_MetadataService.deleteLearningObjective = (code) => learningObjectiveManager.delete(code);
|
|
2721
|
+
var MetadataService = _MetadataService;
|
|
2722
|
+
|
|
2723
|
+
// src/services/questionBankService.ts
|
|
2724
|
+
var LocalStorageManager2 = class {
|
|
2725
|
+
constructor(key) {
|
|
2726
|
+
this.key = key;
|
|
2727
|
+
}
|
|
2728
|
+
getAll() {
|
|
2729
|
+
if (typeof window === "undefined") return [];
|
|
2730
|
+
try {
|
|
2731
|
+
const stored = localStorage.getItem(this.key);
|
|
2732
|
+
return stored ? JSON.parse(stored) : [];
|
|
2733
|
+
} catch (e) {
|
|
2734
|
+
console.error(`Error reading from localStorage key ${this.key}:`, e);
|
|
2735
|
+
return [];
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
saveAll(items) {
|
|
2739
|
+
if (typeof window === "undefined") return;
|
|
2740
|
+
try {
|
|
2741
|
+
localStorage.setItem(this.key, JSON.stringify(items));
|
|
2742
|
+
} catch (e) {
|
|
2743
|
+
console.error(`Error writing to localStorage key ${this.key}:`, e);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
var questionBankManager = new LocalStorageManager2("iqk_question_bank");
|
|
2748
|
+
var QuestionBankService = class {
|
|
2749
|
+
static getQuestions(filters) {
|
|
2750
|
+
let questions = questionBankManager.getAll();
|
|
2751
|
+
if (filters) {
|
|
2752
|
+
if (filters.subjectCode) {
|
|
2753
|
+
questions = questions.filter((q) => q.subjectCode === filters.subjectCode);
|
|
2754
|
+
}
|
|
2755
|
+
if (filters.topicCode) {
|
|
2756
|
+
questions = questions.filter((q) => q.topicCode === filters.topicCode);
|
|
2757
|
+
}
|
|
2758
|
+
if (filters.gradeLevelCode) {
|
|
2759
|
+
questions = questions.filter((q) => q.gradeLevelCode === filters.gradeLevelCode);
|
|
2760
|
+
}
|
|
2761
|
+
if (filters.bloomLevelCode) {
|
|
2762
|
+
questions = questions.filter((q) => q.bloomLevelCode === filters.bloomLevelCode);
|
|
2763
|
+
}
|
|
2764
|
+
if (filters.questionTypeCode) {
|
|
2765
|
+
questions = questions.filter((q) => q.questionTypeCode === filters.questionTypeCode);
|
|
2766
|
+
}
|
|
2767
|
+
if (filters.difficulty) {
|
|
2768
|
+
questions = questions.filter((q) => q.difficulty === filters.difficulty);
|
|
2769
|
+
}
|
|
2770
|
+
if (filters.searchTerm) {
|
|
2771
|
+
const lowercasedTerm = filters.searchTerm.toLowerCase();
|
|
2772
|
+
questions = questions.filter(
|
|
2773
|
+
(q) => q.text.toLowerCase().includes(lowercasedTerm) || q.code.toLowerCase().includes(lowercasedTerm)
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
return questions.sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime());
|
|
2778
|
+
}
|
|
2779
|
+
static getQuestionByCode(code) {
|
|
2780
|
+
return questionBankManager.getAll().find((q) => q.code === code);
|
|
2781
|
+
}
|
|
2782
|
+
// CHANGE 2: Simplified function signature. It now takes the full object to be added.
|
|
2783
|
+
static addQuestion(questionData) {
|
|
2784
|
+
const allQuestions = questionBankManager.getAll();
|
|
2785
|
+
if (allQuestions.some((q) => q.code === questionData.code)) {
|
|
2786
|
+
throw new Error(`A question with code "${questionData.code}" already exists.`);
|
|
2787
|
+
}
|
|
2788
|
+
const newQuestion = {
|
|
2789
|
+
...questionData,
|
|
2790
|
+
id: generateUniqueId("qb_"),
|
|
2791
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
2792
|
+
};
|
|
2793
|
+
questionBankManager.saveAll([...allQuestions, newQuestion]);
|
|
2794
|
+
return newQuestion;
|
|
2795
|
+
}
|
|
2796
|
+
// CHANGE 2: Simplified function signature.
|
|
2797
|
+
static updateQuestion(id, updates) {
|
|
2798
|
+
const allQuestions = questionBankManager.getAll();
|
|
2799
|
+
const index = allQuestions.findIndex((q) => q.id === id);
|
|
2800
|
+
if (index === -1) {
|
|
2801
|
+
console.warn(`Question with id "${id}" not found for update.`);
|
|
2802
|
+
return null;
|
|
2803
|
+
}
|
|
2804
|
+
const updatedQuestion = {
|
|
2805
|
+
...allQuestions[index],
|
|
2806
|
+
...updates,
|
|
2807
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
2808
|
+
};
|
|
2809
|
+
allQuestions[index] = updatedQuestion;
|
|
2810
|
+
questionBankManager.saveAll(allQuestions);
|
|
2811
|
+
return updatedQuestion;
|
|
2812
|
+
}
|
|
2813
|
+
static deleteQuestionByCode(code) {
|
|
2814
|
+
const allQuestions = questionBankManager.getAll();
|
|
2815
|
+
const newQuestions = allQuestions.filter((q) => q.code !== code);
|
|
2816
|
+
if (allQuestions.length === newQuestions.length) {
|
|
2817
|
+
return false;
|
|
2818
|
+
}
|
|
2819
|
+
questionBankManager.saveAll(newQuestions);
|
|
2820
|
+
return true;
|
|
2821
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
|
|
2625
2824
|
// src/services/HTMLLauncherGenerator.ts
|
|
2626
2825
|
var escapeAttribute = (unsafe) => {
|
|
2627
2826
|
if (typeof unsafe !== "string") return "";
|
|
@@ -2754,13 +2953,12 @@ var escapeXML = (unsafe) => {
|
|
|
2754
2953
|
});
|
|
2755
2954
|
};
|
|
2756
2955
|
var generateSCORMManifest = (quizConfig, scormVersion, launcherFile = "index.html", libraryJSPath = "scorm-bundle/player.js", quizDataPath = "quiz_data.json", blocklyCSSPath = "blockly-styles.css", mainCSSPath = "styles.css") => {
|
|
2757
|
-
var _a;
|
|
2758
2956
|
const uniqueId = `iqk_${quizConfig.id.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
2759
2957
|
const organizationId = `ORG-${uniqueId}`;
|
|
2760
2958
|
const itemId = `ITEM-${uniqueId}`;
|
|
2761
2959
|
const resourceId = `RES-${uniqueId}`;
|
|
2762
2960
|
const quizTitle = escapeXML(quizConfig.title);
|
|
2763
|
-
const passingScore =
|
|
2961
|
+
const passingScore = quizConfig.settings?.passingScorePercent;
|
|
2764
2962
|
const effectiveScormVersion = scormVersion;
|
|
2765
2963
|
const schemaVersion = effectiveScormVersion === "2004" ? "2004 4th Edition" : "1.2";
|
|
2766
2964
|
const adlcpNamespace = effectiveScormVersion === "2004" ? "http://www.adlnet.org/xsd/adlcp_v1p3" : "http://www.adlnet.org/xsd/adlcp_rootv1p2";
|
|
@@ -3243,4 +3441,4 @@ function cn(...inputs) {
|
|
|
3243
3441
|
return twMerge(clsx(inputs));
|
|
3244
3442
|
}
|
|
3245
3443
|
|
|
3246
|
-
export { APIKeyService, AchievementService, GEMINI_API_KEY_SERVICE_NAME, KnowledgeCardService, PracticeHistoryService, QuestionImportService, QuizEditorService, QuizEngine, QuoteService, SCORMService, UserConfigService, cn, emptyQuiz, exportQuizAsSCORMZip, generateLauncherHTML, generateSCORMManifest, generateUniqueId, sampleQuiz };
|
|
3444
|
+
export { APIKeyService, AchievementService, GEMINI_API_KEY_SERVICE_NAME, KnowledgeCardService, MetadataService, PracticeHistoryService, QuestionBankService, QuestionImportService, QuizEditorService, QuizEngine, QuoteService, SCORMService, UserConfigService, cn, emptyQuiz, exportQuizAsSCORMZip, generateLauncherHTML, generateSCORMManifest, generateUniqueId, sampleQuiz };
|