@intellectif/lk-core 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +1 -0
- package/dist/{activity-BVkg9VDo.d.cts → activity-C7cR8U9n.d.cts} +35 -2
- package/dist/{activity-BVkg9VDo.d.ts → activity-C7cR8U9n.d.ts} +35 -2
- package/dist/{chunk-R7PV3XIC.js → chunk-54XYQ3CS.js} +7 -4
- package/dist/chunk-54XYQ3CS.js.map +1 -0
- package/dist/{chunk-J5OIRIOW.cjs → chunk-5KWF34LJ.cjs} +31 -31
- package/dist/{chunk-J5OIRIOW.cjs.map → chunk-5KWF34LJ.cjs.map} +1 -1
- package/dist/{chunk-CZRUMHKW.cjs → chunk-7ETU6EXQ.cjs} +13 -10
- package/dist/chunk-7ETU6EXQ.cjs.map +1 -0
- package/dist/{chunk-6RND47QE.js → chunk-DPHX4E7X.js} +62 -3
- package/dist/chunk-DPHX4E7X.js.map +1 -0
- package/dist/{chunk-NBADQ3JU.js → chunk-EEMVWBPP.js} +195 -4
- package/dist/chunk-EEMVWBPP.js.map +1 -0
- package/dist/{chunk-RTIR6R2U.js → chunk-L6YBQXSB.js} +2 -2
- package/dist/{chunk-VCXIPLEW.cjs → chunk-QFWEF5ST.cjs} +63 -4
- package/dist/chunk-QFWEF5ST.cjs.map +1 -0
- package/dist/chunk-W7AQP6AV.cjs +418 -0
- package/dist/chunk-W7AQP6AV.cjs.map +1 -0
- package/dist/{index-v2_-Fyn3.d.ts → index-BWyMDlUS.d.cts} +367 -2
- package/dist/{index-BE1bM2Wi.d.cts → index-D10NaztZ.d.ts} +367 -2
- package/dist/index.cjs +397 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -7
- package/dist/index.d.ts +103 -7
- package/dist/index.js +310 -9
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +11 -3
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +10 -2
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2NIALQEH.cjs +0 -227
- package/dist/chunk-2NIALQEH.cjs.map +0 -1
- package/dist/chunk-6RND47QE.js.map +0 -1
- package/dist/chunk-CZRUMHKW.cjs.map +0 -1
- package/dist/chunk-NBADQ3JU.js.map +0 -1
- package/dist/chunk-R7PV3XIC.js.map +0 -1
- package/dist/chunk-VCXIPLEW.cjs.map +0 -1
- /package/dist/{chunk-RTIR6R2U.js.map → chunk-L6YBQXSB.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -16,22 +16,31 @@ import {
|
|
|
16
16
|
roundGrade,
|
|
17
17
|
score,
|
|
18
18
|
validateSpeechAssessment
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-L6YBQXSB.js";
|
|
20
20
|
import {
|
|
21
21
|
XAPIVerb,
|
|
22
22
|
XAPI_VERB_DISPLAY,
|
|
23
23
|
validateXAPIStatement,
|
|
24
24
|
xAPIBuilder,
|
|
25
25
|
xapiDefinitionFor
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-54XYQ3CS.js";
|
|
27
27
|
import {
|
|
28
|
+
INTERACTIVE_VIDEO_ITEM_TYPES,
|
|
28
29
|
ItemGroupSchema,
|
|
30
|
+
MediaTimelineSchema,
|
|
29
31
|
RedactedItemGroupSchema,
|
|
30
32
|
RedactedStimulusSchema,
|
|
31
33
|
StimulusSchema,
|
|
34
|
+
TIMELINE_MAX_CHAPTERS,
|
|
35
|
+
TIMELINE_MAX_ITEMS,
|
|
36
|
+
TIMELINE_MAX_QUIZZES,
|
|
37
|
+
TIMELINE_MAX_TITLE_LENGTH,
|
|
38
|
+
TimelineChapterSchema,
|
|
39
|
+
TimelineCueSchema,
|
|
32
40
|
dictationJsonSchema,
|
|
33
41
|
fillInTheBlanksJsonSchema,
|
|
34
42
|
gapSelectJsonSchema,
|
|
43
|
+
isInteractiveVideoItemType,
|
|
35
44
|
itemGroupJsonSchema,
|
|
36
45
|
jsonSchemaFor,
|
|
37
46
|
multipleChoiceJsonSchema,
|
|
@@ -40,7 +49,7 @@ import {
|
|
|
40
49
|
validateActivity,
|
|
41
50
|
validateItemGroup,
|
|
42
51
|
writtenResponseJsonSchema
|
|
43
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-EEMVWBPP.js";
|
|
44
53
|
import {
|
|
45
54
|
ActivitySchemaError,
|
|
46
55
|
BlankConfigSchema,
|
|
@@ -65,6 +74,7 @@ import {
|
|
|
65
74
|
MEDIA_FIELD_POLICY,
|
|
66
75
|
MediaPlaybackSchema,
|
|
67
76
|
MediaSchema,
|
|
77
|
+
MediaTrackSchema,
|
|
68
78
|
MediaUrlSchema,
|
|
69
79
|
MultipleChoiceDataSchema,
|
|
70
80
|
MultipleChoiceOptionMediaSchema,
|
|
@@ -124,7 +134,7 @@ import {
|
|
|
124
134
|
registeredActivityTypes,
|
|
125
135
|
withValidationScope,
|
|
126
136
|
writtenResponseType
|
|
127
|
-
} from "./chunk-
|
|
137
|
+
} from "./chunk-DPHX4E7X.js";
|
|
128
138
|
|
|
129
139
|
// src/content-hash.ts
|
|
130
140
|
function canonicalJson(value, seen = /* @__PURE__ */ new Set()) {
|
|
@@ -226,6 +236,20 @@ function keyOf(entry) {
|
|
|
226
236
|
}
|
|
227
237
|
return key;
|
|
228
238
|
}
|
|
239
|
+
function inQuizOrder(items, timeline) {
|
|
240
|
+
const placement = /* @__PURE__ */ new Map();
|
|
241
|
+
timeline.cues.forEach((cue, cueIndex) => {
|
|
242
|
+
cue.itemIds.forEach((itemId, position) => {
|
|
243
|
+
if (!placement.has(itemId)) {
|
|
244
|
+
placement.set(itemId, { at: cue.at, cue: cueIndex, position });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
const unplaced = { at: Number.POSITIVE_INFINITY, cue: Number.POSITIVE_INFINITY, position: 0 };
|
|
249
|
+
return items.map((entry) => ({ entry, place: placement.get(entry.item.id) ?? unplaced })).sort(
|
|
250
|
+
(a, b) => a.place.at - b.place.at || a.place.cue - b.place.cue || a.place.position - b.place.position || a.entry.itemIndex - b.entry.itemIndex
|
|
251
|
+
).map(({ entry }) => entry);
|
|
252
|
+
}
|
|
229
253
|
function flattenSequence(entries, options = {}) {
|
|
230
254
|
const shuffleEntries = options.shuffleEntries === true;
|
|
231
255
|
const needsSeed = shuffleEntries || entries.some((entry) => isItemGroup(entry) && entry.shuffle === "within-group");
|
|
@@ -249,11 +273,20 @@ function flattenSequence(entries, options = {}) {
|
|
|
249
273
|
`flattenSequence: item group "${entry.id}" has no items. An empty group would silently remove its stimulus and its questions from the presented sequence.`
|
|
250
274
|
);
|
|
251
275
|
}
|
|
276
|
+
const timeline = entry.timeline;
|
|
277
|
+
if (timeline !== void 0 && entry.shuffle === "within-group") {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`flattenSequence: item group "${entry.id}" has a timeline and shuffle: "within-group". An interactive video presents its questions in the order its quizzes open.`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
252
282
|
const items = entry.items.map((item, itemIndex) => ({ item, itemIndex }));
|
|
253
|
-
const presented = entry.shuffle === "within-group" ? seededShuffle(items, `${seed}:group:${entry.id}`) : items;
|
|
283
|
+
const presented = timeline !== void 0 ? inQuizOrder(items, timeline) : entry.shuffle === "within-group" ? seededShuffle(items, `${seed}:group:${entry.id}`) : items;
|
|
254
284
|
const size = presented.length;
|
|
255
285
|
presented.forEach(({ item, itemIndex }, position) => {
|
|
286
|
+
const cue = timeline?.cues.find((candidate) => candidate.itemIds.includes(item.id));
|
|
256
287
|
slots.push({
|
|
288
|
+
// Slot ids stay AUTHORED — the item's index or its key — whatever the
|
|
289
|
+
// presented order: moving a quiz must never re-map a stored answer.
|
|
257
290
|
slotId: `${entryKey}.${keyOf(item) ?? String(itemIndex)}`,
|
|
258
291
|
index: slots.length,
|
|
259
292
|
activity: item,
|
|
@@ -262,7 +295,9 @@ function flattenSequence(entries, options = {}) {
|
|
|
262
295
|
...entry.title !== void 0 ? { title: entry.title } : {},
|
|
263
296
|
stimulus: entry.stimulus,
|
|
264
297
|
position,
|
|
265
|
-
size
|
|
298
|
+
size,
|
|
299
|
+
...timeline !== void 0 ? { timeline } : {},
|
|
300
|
+
...cue !== void 0 ? { cue } : {}
|
|
266
301
|
}
|
|
267
302
|
});
|
|
268
303
|
});
|
|
@@ -445,7 +480,14 @@ function planSlot(slot, points) {
|
|
|
445
480
|
group: {
|
|
446
481
|
id: slot.group.id,
|
|
447
482
|
...slot.group.title !== void 0 ? { title: slot.group.title } : {},
|
|
448
|
-
stimulusHash: contentHash(slot.group.stimulus)
|
|
483
|
+
stimulusHash: contentHash(slot.group.stimulus),
|
|
484
|
+
...slot.group.cue !== void 0 ? {
|
|
485
|
+
cue: {
|
|
486
|
+
id: slot.group.cue.id,
|
|
487
|
+
at: slot.group.cue.at,
|
|
488
|
+
...slot.group.cue.required !== void 0 ? { required: slot.group.cue.required } : {}
|
|
489
|
+
}
|
|
490
|
+
} : {}
|
|
449
491
|
}
|
|
450
492
|
} : {},
|
|
451
493
|
...mediaBudgets !== void 0 ? { mediaBudgets } : {}
|
|
@@ -479,6 +521,7 @@ function verifyAttemptPlan(plan, current) {
|
|
|
479
521
|
const addedSlotIds = current.slots.filter((s) => !before.has(s.slotId)).map((s) => s.slotId);
|
|
480
522
|
const changedSlotIds = [];
|
|
481
523
|
const changedStimulusSlotIds = [];
|
|
524
|
+
const changedCueSlotIds = [];
|
|
482
525
|
const changedPointsSlotIds = [];
|
|
483
526
|
const reorderedSlotIds = [];
|
|
484
527
|
for (const slot of plan.slots) {
|
|
@@ -492,6 +535,9 @@ function verifyAttemptPlan(plan, current) {
|
|
|
492
535
|
if (now.group?.stimulusHash !== slot.group?.stimulusHash) {
|
|
493
536
|
changedStimulusSlotIds.push(slot.slotId);
|
|
494
537
|
}
|
|
538
|
+
if (contentHash(now.group?.cue ?? null) !== contentHash(slot.group?.cue ?? null)) {
|
|
539
|
+
changedCueSlotIds.push(slot.slotId);
|
|
540
|
+
}
|
|
495
541
|
if (now.points !== slot.points) {
|
|
496
542
|
changedPointsSlotIds.push(slot.slotId);
|
|
497
543
|
}
|
|
@@ -500,11 +546,12 @@ function verifyAttemptPlan(plan, current) {
|
|
|
500
546
|
}
|
|
501
547
|
}
|
|
502
548
|
return {
|
|
503
|
-
matches: missingSlotIds.length === 0 && addedSlotIds.length === 0 && changedSlotIds.length === 0 && changedStimulusSlotIds.length === 0 && changedPointsSlotIds.length === 0 && reorderedSlotIds.length === 0,
|
|
549
|
+
matches: missingSlotIds.length === 0 && addedSlotIds.length === 0 && changedSlotIds.length === 0 && changedStimulusSlotIds.length === 0 && changedCueSlotIds.length === 0 && changedPointsSlotIds.length === 0 && reorderedSlotIds.length === 0,
|
|
504
550
|
missingSlotIds,
|
|
505
551
|
addedSlotIds,
|
|
506
552
|
changedSlotIds,
|
|
507
553
|
changedStimulusSlotIds,
|
|
554
|
+
changedCueSlotIds,
|
|
508
555
|
changedPointsSlotIds,
|
|
509
556
|
reorderedSlotIds
|
|
510
557
|
};
|
|
@@ -652,6 +699,14 @@ function createItemGroupDraft(context) {
|
|
|
652
699
|
items: []
|
|
653
700
|
};
|
|
654
701
|
}
|
|
702
|
+
function createInteractiveVideoDraft(context) {
|
|
703
|
+
const draft = createItemGroupDraft(context);
|
|
704
|
+
return {
|
|
705
|
+
...draft,
|
|
706
|
+
stimulus: { id: draft.stimulus.id, kind: "video", media: { type: "video", url: "" } },
|
|
707
|
+
timeline: { cues: [] }
|
|
708
|
+
};
|
|
709
|
+
}
|
|
655
710
|
function validateItemGroupDraft(draft) {
|
|
656
711
|
return withValidationScope(() => validateItemGroupDraftInScope(draft));
|
|
657
712
|
}
|
|
@@ -676,6 +731,9 @@ function validateItemGroupDraftInScope(draft) {
|
|
|
676
731
|
);
|
|
677
732
|
}
|
|
678
733
|
issues.push(...checkItems(draft));
|
|
734
|
+
if (draft.timeline !== void 0 && draft.timeline !== null) {
|
|
735
|
+
issues.push(...checkTimeline(draft));
|
|
736
|
+
}
|
|
679
737
|
if (groupCaptionsRevealDictation(draft, (captionsUrl) => !isUnwritten(captionsUrl))) {
|
|
680
738
|
issues.push(
|
|
681
739
|
issue(
|
|
@@ -722,6 +780,183 @@ function validateItemGroupDraftInScope(draft) {
|
|
|
722
780
|
const invalid = issues.some((found) => found.severity !== "incomplete");
|
|
723
781
|
return { status: invalid ? "invalid" : "incomplete", issues };
|
|
724
782
|
}
|
|
783
|
+
function checkTimeline(draft) {
|
|
784
|
+
const issues = [];
|
|
785
|
+
const timeline = draft.timeline;
|
|
786
|
+
if (!isRecord(timeline)) {
|
|
787
|
+
return issues;
|
|
788
|
+
}
|
|
789
|
+
const stimulus = isRecord(draft.stimulus) ? draft.stimulus : void 0;
|
|
790
|
+
if (stimulus !== void 0 && !isUnwritten(stimulus.kind) && stimulus.kind !== "video") {
|
|
791
|
+
issues.push(
|
|
792
|
+
issue(
|
|
793
|
+
"ig_timeline_stimulus_kind",
|
|
794
|
+
["stimulus", "kind"],
|
|
795
|
+
"An interactive video needs a video: quizzes open at moments of it."
|
|
796
|
+
)
|
|
797
|
+
);
|
|
798
|
+
} else if (stimulus !== void 0 && isRecord(stimulus.media) && !isUnwritten(stimulus.media.type) && stimulus.media.type !== "video") {
|
|
799
|
+
issues.push(
|
|
800
|
+
issue(
|
|
801
|
+
"ig_timeline_stimulus_kind",
|
|
802
|
+
["stimulus", "media", "type"],
|
|
803
|
+
"Upload a video file. An embedded player from another site cannot be paused when a quiz opens."
|
|
804
|
+
)
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
if (draft.shuffle === "within-group") {
|
|
808
|
+
issues.push(
|
|
809
|
+
issue(
|
|
810
|
+
"ig_timeline_shuffle",
|
|
811
|
+
["shuffle"],
|
|
812
|
+
"The questions of an interactive video appear in the order their quizzes open, so they cannot be shuffled."
|
|
813
|
+
)
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
const items = Array.isArray(draft.items) ? draft.items : [];
|
|
817
|
+
const itemIndexById = /* @__PURE__ */ new Map();
|
|
818
|
+
items.forEach((item, index) => {
|
|
819
|
+
if (!isRecord(item)) {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (typeof item.id === "string" && item.id !== "") {
|
|
823
|
+
itemIndexById.set(item.id, index);
|
|
824
|
+
}
|
|
825
|
+
const type = item.type;
|
|
826
|
+
if (typeof type === "string" && type !== "" && type !== "item-group" && !isInteractiveVideoItemType(type) && getActivityTypeDescriptor(type) !== void 0) {
|
|
827
|
+
issues.push(
|
|
828
|
+
issue(
|
|
829
|
+
"ig_timeline_item_type",
|
|
830
|
+
["items", index, "type"],
|
|
831
|
+
`An interactive video can hold ${INTERACTIVE_VIDEO_ITEM_TYPES.join(", ")} questions only.`
|
|
832
|
+
)
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
if (type === "dictation" && isUnset(item.media)) {
|
|
836
|
+
issues.push(
|
|
837
|
+
issue(
|
|
838
|
+
"ig_timeline_dictation_media",
|
|
839
|
+
["items", index, "media"],
|
|
840
|
+
"Add the recording this dictation plays: inside a video it cannot play the video."
|
|
841
|
+
)
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
const cues = Array.isArray(timeline.cues) ? timeline.cues : [];
|
|
846
|
+
const quizIds = /* @__PURE__ */ new Set();
|
|
847
|
+
const placed = /* @__PURE__ */ new Set();
|
|
848
|
+
cues.forEach((cue, cueIndex) => {
|
|
849
|
+
if (!isRecord(cue)) {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const at = (field, ...rest) => [
|
|
853
|
+
"timeline",
|
|
854
|
+
"cues",
|
|
855
|
+
cueIndex,
|
|
856
|
+
field,
|
|
857
|
+
...rest
|
|
858
|
+
];
|
|
859
|
+
if (isMissingId(cue.id)) {
|
|
860
|
+
issues.push(issue("ig_timeline_quiz_id_required", at("id"), "This quiz has no id."));
|
|
861
|
+
} else if (typeof cue.id === "string") {
|
|
862
|
+
if (quizIds.has(cue.id)) {
|
|
863
|
+
issues.push(
|
|
864
|
+
issue("ig_timeline_quiz_id_duplicate", at("id"), `Two quizzes share the id "${cue.id}".`)
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
quizIds.add(cue.id);
|
|
868
|
+
}
|
|
869
|
+
if (isUnset(cue.at)) {
|
|
870
|
+
issues.push(
|
|
871
|
+
issue(
|
|
872
|
+
"ig_timeline_quiz_time_required",
|
|
873
|
+
at("at"),
|
|
874
|
+
"Choose when in the video this quiz opens."
|
|
875
|
+
)
|
|
876
|
+
);
|
|
877
|
+
} else if (typeof cue.at !== "number" || !Number.isFinite(cue.at) || cue.at < 0) {
|
|
878
|
+
issues.push(
|
|
879
|
+
issue(
|
|
880
|
+
"ig_timeline_quiz_time_invalid",
|
|
881
|
+
at("at"),
|
|
882
|
+
"A quiz opens at a number of seconds, 0 or more."
|
|
883
|
+
)
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (isUnset(cue.itemIds) || Array.isArray(cue.itemIds) && cue.itemIds.length === 0) {
|
|
887
|
+
issues.push(
|
|
888
|
+
issue("ig_timeline_quiz_empty", at("itemIds"), "Add at least one question to this quiz.")
|
|
889
|
+
);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (!Array.isArray(cue.itemIds)) {
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
cue.itemIds.forEach((itemId, position) => {
|
|
896
|
+
if (typeof itemId !== "string") {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (!itemIndexById.has(itemId)) {
|
|
900
|
+
issues.push(
|
|
901
|
+
issue(
|
|
902
|
+
"ig_timeline_quiz_unknown_item",
|
|
903
|
+
at("itemIds", position),
|
|
904
|
+
`This quiz names a question that is not in the video ("${itemId}").`
|
|
905
|
+
)
|
|
906
|
+
);
|
|
907
|
+
} else if (placed.has(itemId)) {
|
|
908
|
+
issues.push(
|
|
909
|
+
issue(
|
|
910
|
+
"ig_timeline_item_duplicate",
|
|
911
|
+
at("itemIds", position),
|
|
912
|
+
"This question is already in a quiz: each question belongs to one quiz."
|
|
913
|
+
)
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
placed.add(itemId);
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
for (const [itemId, index] of itemIndexById) {
|
|
920
|
+
if (!placed.has(itemId)) {
|
|
921
|
+
issues.push(
|
|
922
|
+
issue(
|
|
923
|
+
"ig_timeline_item_unplaced",
|
|
924
|
+
["items", index],
|
|
925
|
+
"Add this question to a quiz, or the video never shows it."
|
|
926
|
+
)
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const chapters = Array.isArray(timeline.chapters) ? timeline.chapters : [];
|
|
931
|
+
let previousAt;
|
|
932
|
+
chapters.forEach((chapter, index) => {
|
|
933
|
+
if (!isRecord(chapter)) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (isUnwritten(chapter.title)) {
|
|
937
|
+
issues.push(
|
|
938
|
+
issue(
|
|
939
|
+
"ig_timeline_chapter_title",
|
|
940
|
+
["timeline", "chapters", index, "title"],
|
|
941
|
+
"Give this chapter a title."
|
|
942
|
+
)
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
if (typeof chapter.at === "number" && Number.isFinite(chapter.at)) {
|
|
946
|
+
if (previousAt !== void 0 && chapter.at <= previousAt) {
|
|
947
|
+
issues.push(
|
|
948
|
+
issue(
|
|
949
|
+
"ig_timeline_chapter_order",
|
|
950
|
+
["timeline", "chapters", index, "at"],
|
|
951
|
+
"Chapters must start in order, each after the one before it."
|
|
952
|
+
)
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
previousAt = chapter.at;
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
return issues;
|
|
959
|
+
}
|
|
725
960
|
function checkGroupIdentity(draft) {
|
|
726
961
|
const issues = [];
|
|
727
962
|
if (draft.schemaVersion !== "1.0") {
|
|
@@ -1127,7 +1362,25 @@ var ITEM_GROUP_FIELD_POLICY = {
|
|
|
1127
1362
|
// and identity has to cross the redaction boundary intact.
|
|
1128
1363
|
slotKey: "public",
|
|
1129
1364
|
shuffle: "public",
|
|
1130
|
-
stimulus: STIMULUS_FIELD_POLICY
|
|
1365
|
+
stimulus: STIMULUS_FIELD_POLICY,
|
|
1366
|
+
// Public, and classified key by key rather than as one leaf: the learner's
|
|
1367
|
+
// player needs every part of it, none of it is a key — and a leaf would hand
|
|
1368
|
+
// over the author's object by reference, with whatever else was parked in it.
|
|
1369
|
+
timeline: {
|
|
1370
|
+
// Applies to every element of the array.
|
|
1371
|
+
cues: {
|
|
1372
|
+
id: "public",
|
|
1373
|
+
at: "public",
|
|
1374
|
+
itemIds: "public",
|
|
1375
|
+
title: "public",
|
|
1376
|
+
required: "public"
|
|
1377
|
+
},
|
|
1378
|
+
chapters: {
|
|
1379
|
+
at: "public",
|
|
1380
|
+
title: "public"
|
|
1381
|
+
},
|
|
1382
|
+
navigation: "public"
|
|
1383
|
+
}
|
|
1131
1384
|
};
|
|
1132
1385
|
function redactItemGroup(group, options = {}) {
|
|
1133
1386
|
const reveal = options.reveal ?? "none";
|
|
@@ -1197,6 +1450,41 @@ function assertRedactedItemGroup(data) {
|
|
|
1197
1450
|
}
|
|
1198
1451
|
});
|
|
1199
1452
|
}
|
|
1453
|
+
|
|
1454
|
+
// src/timeline.ts
|
|
1455
|
+
function readMediaProgress(value, duration) {
|
|
1456
|
+
let version;
|
|
1457
|
+
let at;
|
|
1458
|
+
let furthest;
|
|
1459
|
+
try {
|
|
1460
|
+
if (typeof value !== "object" || value === null) {
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
({ progressVersion: version, at, furthest } = value);
|
|
1464
|
+
} catch {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
if (version !== "1.0" || typeof at !== "number" || !Number.isFinite(at)) {
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
const end = typeof duration === "number" && Number.isFinite(duration) && duration >= 0 ? duration : Number.POSITIVE_INFINITY;
|
|
1471
|
+
const clampedAt = Math.min(Math.max(0, at), end);
|
|
1472
|
+
const reached = typeof furthest === "number" && Number.isFinite(furthest) ? furthest : clampedAt;
|
|
1473
|
+
return {
|
|
1474
|
+
progressVersion: "1.0",
|
|
1475
|
+
at: clampedAt,
|
|
1476
|
+
furthest: Math.min(Math.max(clampedAt, reached), end)
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function composeTimelineScore(entryKey, items, policy) {
|
|
1480
|
+
const own = items.filter((item) => entryKeyOf(item.slotId) === entryKey);
|
|
1481
|
+
if (own.length === 0) {
|
|
1482
|
+
throw new Error(
|
|
1483
|
+
`composeTimelineScore: no scored item belongs to entry "${entryKey}". Pass the video's slotKey (or its position in the sequence), and the items of the attempt that holds it.`
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
return composeAssessmentScore([{ id: entryKey, weight: 1, items: own }], policy);
|
|
1487
|
+
}
|
|
1200
1488
|
export {
|
|
1201
1489
|
ActivitySchemaError,
|
|
1202
1490
|
BlankConfigSchema,
|
|
@@ -1217,9 +1505,12 @@ export {
|
|
|
1217
1505
|
GapSelectChoiceSchema,
|
|
1218
1506
|
GapSelectDataSchema,
|
|
1219
1507
|
GapSelectGapSchema,
|
|
1508
|
+
INTERACTIVE_VIDEO_ITEM_TYPES,
|
|
1220
1509
|
ItemGroupSchema,
|
|
1221
1510
|
MediaPlaybackSchema,
|
|
1222
1511
|
MediaSchema,
|
|
1512
|
+
MediaTimelineSchema,
|
|
1513
|
+
MediaTrackSchema,
|
|
1223
1514
|
MediaUrlSchema,
|
|
1224
1515
|
MultipleChoiceDataSchema,
|
|
1225
1516
|
MultipleChoiceOptionMediaSchema,
|
|
@@ -1250,7 +1541,13 @@ export {
|
|
|
1250
1541
|
RedactedWrittenResponseDataSchema,
|
|
1251
1542
|
SPEECH_ASSESSMENT_MAX_WORDS,
|
|
1252
1543
|
StimulusSchema,
|
|
1544
|
+
TIMELINE_MAX_CHAPTERS,
|
|
1545
|
+
TIMELINE_MAX_ITEMS,
|
|
1546
|
+
TIMELINE_MAX_QUIZZES,
|
|
1547
|
+
TIMELINE_MAX_TITLE_LENGTH,
|
|
1253
1548
|
TextMatchPolicySchema,
|
|
1549
|
+
TimelineChapterSchema,
|
|
1550
|
+
TimelineCueSchema,
|
|
1254
1551
|
UnknownActivityTypeError,
|
|
1255
1552
|
WrittenResponseDataSchema,
|
|
1256
1553
|
WrittenResponseRubricCriterionSchema,
|
|
@@ -1264,10 +1561,12 @@ export {
|
|
|
1264
1561
|
canonicalJson,
|
|
1265
1562
|
classifyBand,
|
|
1266
1563
|
composeAssessmentScore,
|
|
1564
|
+
composeTimelineScore,
|
|
1267
1565
|
computePassThreshold,
|
|
1268
1566
|
contentHash,
|
|
1269
1567
|
countWords,
|
|
1270
1568
|
createDraft,
|
|
1569
|
+
createInteractiveVideoDraft,
|
|
1271
1570
|
createItemGroupDraft,
|
|
1272
1571
|
defineActivityType,
|
|
1273
1572
|
dictationJsonSchema,
|
|
@@ -1290,6 +1589,7 @@ export {
|
|
|
1290
1589
|
hasGrade,
|
|
1291
1590
|
hashSeed,
|
|
1292
1591
|
inspectWav,
|
|
1592
|
+
isInteractiveVideoItemType,
|
|
1293
1593
|
isItemGroup,
|
|
1294
1594
|
itemGroupJsonSchema,
|
|
1295
1595
|
jsonSchemaFor,
|
|
@@ -1303,6 +1603,7 @@ export {
|
|
|
1303
1603
|
planMediaBudgets,
|
|
1304
1604
|
readAloudJsonSchema,
|
|
1305
1605
|
readAloudType,
|
|
1606
|
+
readMediaProgress,
|
|
1306
1607
|
redact,
|
|
1307
1608
|
redactItemGroup,
|
|
1308
1609
|
registerActivityType,
|