scorm-again 3.1.3 → 3.1.5
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/esm/scorm-again.js +1257 -236
- package/dist/esm/scorm-again.js.map +1 -1
- package/dist/esm/scorm-again.min.js +1 -1
- package/dist/esm/scorm-again.min.js.map +1 -1
- package/dist/esm/scorm12.js +14 -3
- package/dist/esm/scorm12.js.map +1 -1
- package/dist/esm/scorm12.min.js +1 -1
- package/dist/esm/scorm12.min.js.map +1 -1
- package/dist/esm/scorm2004.js +1257 -236
- package/dist/esm/scorm2004.js.map +1 -1
- package/dist/esm/scorm2004.min.js +1 -1
- package/dist/esm/scorm2004.min.js.map +1 -1
- package/dist/scorm-again.js +1619 -449
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +1 -1
- package/dist/scorm12.js +54 -14
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +1 -1
- package/dist/scorm2004.js +1614 -444
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +1 -1
- package/dist/types/Scorm2004API.d.ts +12 -0
- package/dist/types/cmi/scorm2004/completion_status_evaluation.d.ts +7 -0
- package/dist/types/cmi/scorm2004/sequencing/activity.d.ts +47 -5
- package/dist/types/cmi/scorm2004/sequencing/handlers/rte_data_transfer.d.ts +1 -0
- package/dist/types/cmi/scorm2004/sequencing/handlers/termination_handler.d.ts +1 -0
- package/dist/types/cmi/scorm2004/sequencing/objectives/global_objective_synchronizer.d.ts +17 -1
- package/dist/types/cmi/scorm2004/sequencing/sequencing_rules.d.ts +6 -1
- package/dist/types/cmi/scorm2004/sequencing/traversal/flow_traversal_service.d.ts +5 -2
- package/dist/types/constants/regex.d.ts +1 -0
- package/dist/types/types/api_types.d.ts +12 -0
- package/dist/types/types/sequencing_types.d.ts +13 -0
- package/dist/types/utilities/core.d.ts +4 -1
- package/package.json +1 -1
package/dist/esm/scorm2004.js
CHANGED
|
@@ -139,12 +139,18 @@ function stringMatches(str, tester) {
|
|
|
139
139
|
}
|
|
140
140
|
return new RegExp(tester).test(str);
|
|
141
141
|
}
|
|
142
|
-
function memoize(fn, keyFn) {
|
|
142
|
+
function memoize(fn, keyFn, options) {
|
|
143
143
|
const cache = /* @__PURE__ */ new Map();
|
|
144
144
|
return ((...args) => {
|
|
145
145
|
const key = keyFn ? keyFn(...args) : JSON.stringify(args);
|
|
146
|
+
if (options?.maxKeyLength !== void 0 && key.length > options.maxKeyLength) {
|
|
147
|
+
return fn(...args);
|
|
148
|
+
}
|
|
146
149
|
return cache.has(key) ? cache.get(key) : (() => {
|
|
147
150
|
const result = fn(...args);
|
|
151
|
+
if (options?.maxEntries !== void 0 && cache.size >= options.maxEntries) {
|
|
152
|
+
cache.delete(cache.keys().next().value);
|
|
153
|
+
}
|
|
148
154
|
cache.set(key, result);
|
|
149
155
|
return result;
|
|
150
156
|
})();
|
|
@@ -896,6 +902,8 @@ const scorm2004_regex = {
|
|
|
896
902
|
CMILang: "^([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?$|^$",
|
|
897
903
|
/** CMILangString250 - String with optional language tag, max 250 chars (RTE C.1.3) */
|
|
898
904
|
CMILangString250: "^({lang=([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?})?((?!{.*$).{0,250}$)?$",
|
|
905
|
+
/** CMILangString - Optional language tag, no length cap; RTE C.1.3 SPM is a floor and is deliberately not enforced */
|
|
906
|
+
CMILangString: "^({lang=([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?})?((?!{.*$).*$)?$",
|
|
899
907
|
/** CMILangcr - Language tag pattern with content */
|
|
900
908
|
CMILangcr: "^(({lang=([a-zA-Z]{1,8}|i|x)?(-[a-zA-Z0-9-]{2,8})?}))(.*?)$",
|
|
901
909
|
/** CMILangString250cr - String with optional language tag (carriage return variant) */
|
|
@@ -1548,12 +1556,6 @@ const HIDE_LMS_UI_TOKENS = [
|
|
|
1548
1556
|
"suspendAll"
|
|
1549
1557
|
];
|
|
1550
1558
|
|
|
1551
|
-
var RuleConditionOperator = /* @__PURE__ */ ((RuleConditionOperator2) => {
|
|
1552
|
-
RuleConditionOperator2["NOT"] = "not";
|
|
1553
|
-
RuleConditionOperator2["AND"] = "and";
|
|
1554
|
-
RuleConditionOperator2["OR"] = "or";
|
|
1555
|
-
return RuleConditionOperator2;
|
|
1556
|
-
})(RuleConditionOperator || {});
|
|
1557
1559
|
var RuleActionType = /* @__PURE__ */ ((RuleActionType2) => {
|
|
1558
1560
|
RuleActionType2["SKIP"] = "skip";
|
|
1559
1561
|
RuleActionType2["DISABLED"] = "disabled";
|
|
@@ -1568,6 +1570,48 @@ var RuleActionType = /* @__PURE__ */ ((RuleActionType2) => {
|
|
|
1568
1570
|
RuleActionType2["EXIT"] = "exit";
|
|
1569
1571
|
return RuleActionType2;
|
|
1570
1572
|
})(RuleActionType || {});
|
|
1573
|
+
function kleeneNot(value) {
|
|
1574
|
+
if (value === "unknown") {
|
|
1575
|
+
return "unknown";
|
|
1576
|
+
}
|
|
1577
|
+
return !value;
|
|
1578
|
+
}
|
|
1579
|
+
function kleeneAnd(values) {
|
|
1580
|
+
let hasUnknown = false;
|
|
1581
|
+
for (const value of values) {
|
|
1582
|
+
if (value === false) {
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
if (value === "unknown") {
|
|
1586
|
+
hasUnknown = true;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return hasUnknown ? "unknown" : true;
|
|
1590
|
+
}
|
|
1591
|
+
function kleeneOr(values) {
|
|
1592
|
+
let hasUnknown = false;
|
|
1593
|
+
for (const value of values) {
|
|
1594
|
+
if (value === true) {
|
|
1595
|
+
return true;
|
|
1596
|
+
}
|
|
1597
|
+
if (value === "unknown") {
|
|
1598
|
+
hasUnknown = true;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
return hasUnknown ? "unknown" : false;
|
|
1602
|
+
}
|
|
1603
|
+
function combineRuleConditionResults(values, conditionCombination) {
|
|
1604
|
+
if (values.length === 0) {
|
|
1605
|
+
return false;
|
|
1606
|
+
}
|
|
1607
|
+
if (conditionCombination === "all" || conditionCombination === "and" /* AND */) {
|
|
1608
|
+
return kleeneAnd(values);
|
|
1609
|
+
}
|
|
1610
|
+
if (conditionCombination === "any" || conditionCombination === "or" /* OR */) {
|
|
1611
|
+
return kleeneOr(values);
|
|
1612
|
+
}
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1571
1615
|
class RuleCondition extends BaseCMI {
|
|
1572
1616
|
_condition = "always" /* ALWAYS */;
|
|
1573
1617
|
_operator = null;
|
|
@@ -1673,51 +1717,72 @@ class RuleCondition extends BaseCMI {
|
|
|
1673
1717
|
/**
|
|
1674
1718
|
* Evaluate the condition for an activity
|
|
1675
1719
|
* @param {Activity} activity - The activity to evaluate the condition for
|
|
1676
|
-
* @return {
|
|
1720
|
+
* @return {RuleConditionEvaluation} - True, false, or unknown per SCORM 2004 4th Ed.
|
|
1677
1721
|
*/
|
|
1678
1722
|
evaluate(activity) {
|
|
1679
1723
|
let result;
|
|
1724
|
+
const hasReferencedObjective = this._referencedObjective !== null;
|
|
1680
1725
|
const referencedObjective = this.resolveReferencedObjective(activity);
|
|
1681
1726
|
switch (this._condition) {
|
|
1682
1727
|
case "satisfied" /* SATISFIED */:
|
|
1683
1728
|
case "objectiveSatisfied" /* OBJECTIVE_SATISFIED */:
|
|
1684
|
-
if (referencedObjective) {
|
|
1685
|
-
result =
|
|
1729
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1730
|
+
result = false;
|
|
1731
|
+
} else if (referencedObjective) {
|
|
1732
|
+
result = referencedObjective.satisfiedStatusKnown || referencedObjective.progressStatus ? referencedObjective.satisfiedStatus === true : "unknown";
|
|
1733
|
+
} else if (activity.objectiveSatisfiedStatusKnown) {
|
|
1734
|
+
result = activity.objectiveSatisfiedStatus === true;
|
|
1735
|
+
} else if (activity.successStatus !== SuccessStatus.UNKNOWN) {
|
|
1736
|
+
result = activity.successStatus === SuccessStatus.PASSED;
|
|
1686
1737
|
} else {
|
|
1687
|
-
result =
|
|
1738
|
+
result = "unknown";
|
|
1688
1739
|
}
|
|
1689
1740
|
break;
|
|
1690
1741
|
case "objectiveStatusKnown" /* OBJECTIVE_STATUS_KNOWN */:
|
|
1691
|
-
result = referencedObjective ? !!referencedObjective.
|
|
1742
|
+
result = hasReferencedObjective && !referencedObjective ? false : referencedObjective ? !!referencedObjective.satisfiedStatusKnown : !!activity.objectiveSatisfiedStatusKnown;
|
|
1692
1743
|
break;
|
|
1693
1744
|
case "objectiveMeasureKnown" /* OBJECTIVE_MEASURE_KNOWN */:
|
|
1694
|
-
result = referencedObjective ? !!referencedObjective.measureStatus : !!activity.objectiveMeasureStatus;
|
|
1745
|
+
result = hasReferencedObjective && !referencedObjective ? false : referencedObjective ? !!referencedObjective.measureStatus : !!activity.objectiveMeasureStatus;
|
|
1695
1746
|
break;
|
|
1696
1747
|
case "objectiveMeasureGreaterThan" /* OBJECTIVE_MEASURE_GREATER_THAN */: {
|
|
1748
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1749
|
+
result = false;
|
|
1750
|
+
break;
|
|
1751
|
+
}
|
|
1697
1752
|
const greaterThanValue = this._parameters.get("threshold") || 0;
|
|
1698
1753
|
const measureStatus = referencedObjective ? referencedObjective.measureStatus : activity.objectiveMeasureStatus;
|
|
1699
1754
|
const measureValue = referencedObjective ? referencedObjective.normalizedMeasure : activity.objectiveNormalizedMeasure;
|
|
1700
|
-
result =
|
|
1755
|
+
result = measureStatus ? measureValue > greaterThanValue : "unknown";
|
|
1701
1756
|
break;
|
|
1702
1757
|
}
|
|
1703
1758
|
case "objectiveMeasureLessThan" /* OBJECTIVE_MEASURE_LESS_THAN */: {
|
|
1759
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1760
|
+
result = false;
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1704
1763
|
const lessThanValue = this._parameters.get("threshold") || 0;
|
|
1705
1764
|
const measureStatus = referencedObjective ? referencedObjective.measureStatus : activity.objectiveMeasureStatus;
|
|
1706
1765
|
const measureValue = referencedObjective ? referencedObjective.normalizedMeasure : activity.objectiveNormalizedMeasure;
|
|
1707
|
-
result =
|
|
1766
|
+
result = measureStatus ? measureValue < lessThanValue : "unknown";
|
|
1708
1767
|
break;
|
|
1709
1768
|
}
|
|
1710
1769
|
case "completed" /* COMPLETED */:
|
|
1711
1770
|
case "activityCompleted" /* ACTIVITY_COMPLETED */:
|
|
1712
|
-
if (referencedObjective) {
|
|
1713
|
-
result =
|
|
1771
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1772
|
+
result = false;
|
|
1773
|
+
} else if (referencedObjective) {
|
|
1774
|
+
result = referencedObjective.completionStatus === CompletionStatus.UNKNOWN ? "unknown" : referencedObjective.completionStatus === CompletionStatus.COMPLETED;
|
|
1775
|
+
} else if (activity.completionStatus === CompletionStatus.UNKNOWN) {
|
|
1776
|
+
result = "unknown";
|
|
1714
1777
|
} else {
|
|
1715
|
-
result = activity.
|
|
1778
|
+
result = activity.completionStatus === CompletionStatus.COMPLETED;
|
|
1716
1779
|
}
|
|
1717
1780
|
break;
|
|
1718
1781
|
case "progressKnown" /* PROGRESS_KNOWN */:
|
|
1719
1782
|
case "activityProgressKnown" /* ACTIVITY_PROGRESS_KNOWN */:
|
|
1720
|
-
if (referencedObjective) {
|
|
1783
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1784
|
+
result = false;
|
|
1785
|
+
} else if (referencedObjective) {
|
|
1721
1786
|
result = referencedObjective.completionStatus !== CompletionStatus.UNKNOWN;
|
|
1722
1787
|
} else {
|
|
1723
1788
|
result = activity.completionStatus !== "unknown";
|
|
@@ -1746,7 +1811,7 @@ class RuleCondition extends BaseCMI {
|
|
|
1746
1811
|
break;
|
|
1747
1812
|
}
|
|
1748
1813
|
if (this._operator === "not" /* NOT */) {
|
|
1749
|
-
result =
|
|
1814
|
+
result = kleeneNot(result);
|
|
1750
1815
|
}
|
|
1751
1816
|
return result;
|
|
1752
1817
|
}
|
|
@@ -1953,15 +2018,10 @@ class SequencingRule extends BaseCMI {
|
|
|
1953
2018
|
* @return {boolean} - True if the rule conditions are met, false otherwise
|
|
1954
2019
|
*/
|
|
1955
2020
|
evaluate(activity) {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
return this._conditions.every((condition) => condition.evaluate(activity));
|
|
1961
|
-
} else if (this._conditionCombination === "any" || this._conditionCombination === "or" /* OR */) {
|
|
1962
|
-
return this._conditions.some((condition) => condition.evaluate(activity));
|
|
1963
|
-
}
|
|
1964
|
-
return false;
|
|
2021
|
+
return combineRuleConditionResults(
|
|
2022
|
+
this._conditions.map((condition) => condition.evaluate(activity)),
|
|
2023
|
+
this._conditionCombination
|
|
2024
|
+
) === true;
|
|
1965
2025
|
}
|
|
1966
2026
|
/**
|
|
1967
2027
|
* toJSON for SequencingRule
|
|
@@ -2794,19 +2854,10 @@ class RuleEvaluationEngine {
|
|
|
2794
2854
|
* Evaluates individual sequencing rule conditions
|
|
2795
2855
|
* @param {Activity} activity - The activity to evaluate the rule for
|
|
2796
2856
|
* @param {SequencingRule} rule - The rule to evaluate
|
|
2797
|
-
* @return {boolean} - True
|
|
2857
|
+
* @return {boolean} - True only when the rule evaluates to definite true
|
|
2798
2858
|
*/
|
|
2799
2859
|
checkRuleSubprocess(activity, rule) {
|
|
2800
|
-
|
|
2801
|
-
return true;
|
|
2802
|
-
}
|
|
2803
|
-
const conditionCombination = rule.conditionCombination;
|
|
2804
|
-
if (conditionCombination === "all" || conditionCombination === RuleConditionOperator.AND) {
|
|
2805
|
-
return rule.conditions.every((condition) => condition.evaluate(activity));
|
|
2806
|
-
} else if (conditionCombination === "any" || conditionCombination === RuleConditionOperator.OR) {
|
|
2807
|
-
return rule.conditions.some((condition) => condition.evaluate(activity));
|
|
2808
|
-
}
|
|
2809
|
-
return false;
|
|
2860
|
+
return rule.evaluate(activity);
|
|
2810
2861
|
}
|
|
2811
2862
|
/**
|
|
2812
2863
|
* Exit Action Rules Subprocess (TB.2.1)
|
|
@@ -3678,16 +3729,21 @@ class FlowTraversalService {
|
|
|
3678
3729
|
* @param {Activity} fromActivity - The activity to flow from
|
|
3679
3730
|
* @param {FlowSubprocessMode} direction - The flow direction
|
|
3680
3731
|
* @return {FlowSubprocessResult} - Result containing the deliverable activity
|
|
3732
|
+
* @spec SN Book: SB.2.3 (Flow Subprocess) - preserves the SB.2.1 effective traversal direction for SB.2.2.
|
|
3733
|
+
* @spec SN Book: SB.2.2 (Flow Activity Traversal Subprocess) - evaluates candidates using the effective direction returned by SB.2.1.
|
|
3681
3734
|
*/
|
|
3682
3735
|
flowSubprocess(fromActivity, direction) {
|
|
3683
3736
|
let candidateActivity = fromActivity;
|
|
3684
3737
|
let firstIteration = true;
|
|
3685
3738
|
let lastCandidateHadNoChildren = false;
|
|
3739
|
+
let currentDirection = direction;
|
|
3740
|
+
let forwardOnlyCluster = null;
|
|
3686
3741
|
while (candidateActivity) {
|
|
3687
3742
|
const traversalResult = this.flowTreeTraversalSubprocess(
|
|
3688
3743
|
candidateActivity,
|
|
3689
|
-
|
|
3690
|
-
firstIteration
|
|
3744
|
+
currentDirection,
|
|
3745
|
+
firstIteration,
|
|
3746
|
+
forwardOnlyCluster
|
|
3691
3747
|
);
|
|
3692
3748
|
if (!traversalResult.activity) {
|
|
3693
3749
|
let exceptionCode = null;
|
|
@@ -3705,17 +3761,22 @@ class FlowTraversalService {
|
|
|
3705
3761
|
traversalResult.endSequencingSession
|
|
3706
3762
|
);
|
|
3707
3763
|
}
|
|
3764
|
+
const effectiveDirection = traversalResult.direction || currentDirection;
|
|
3765
|
+
if (traversalResult.forwardOnlyCluster) {
|
|
3766
|
+
forwardOnlyCluster = traversalResult.forwardOnlyCluster;
|
|
3767
|
+
}
|
|
3708
3768
|
lastCandidateHadNoChildren = traversalResult.activity.children.length > 0 && traversalResult.activity.getAvailableChildren().length === 0;
|
|
3709
3769
|
const deliverable = this.flowActivityTraversalSubprocess(
|
|
3710
3770
|
traversalResult.activity,
|
|
3711
|
-
|
|
3771
|
+
effectiveDirection === FlowSubprocessMode.FORWARD,
|
|
3712
3772
|
true,
|
|
3713
|
-
|
|
3773
|
+
effectiveDirection
|
|
3714
3774
|
);
|
|
3715
3775
|
if (deliverable) {
|
|
3716
3776
|
return new FlowSubprocessResult(deliverable, true, null, false);
|
|
3717
3777
|
}
|
|
3718
3778
|
candidateActivity = traversalResult.activity;
|
|
3779
|
+
currentDirection = effectiveDirection;
|
|
3719
3780
|
firstIteration = false;
|
|
3720
3781
|
}
|
|
3721
3782
|
return new FlowSubprocessResult(null, false, null, false);
|
|
@@ -3726,11 +3787,13 @@ class FlowTraversalService {
|
|
|
3726
3787
|
* @param {Activity} fromActivity - The activity to traverse from
|
|
3727
3788
|
* @param {FlowSubprocessMode} direction - The traversal direction
|
|
3728
3789
|
* @param {boolean} skipChildren - Whether to skip checking children
|
|
3790
|
+
* @param {Activity | null} forwardTraversalBoundary - Cluster boundary for an SB.2.1 forwardOnly direction reversal
|
|
3729
3791
|
* @return {FlowTreeTraversalResult} - The next activity and flags
|
|
3792
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - backward traversal into a forwardOnly cluster selects the first available child and reverses traversal direction to Forward.
|
|
3730
3793
|
*/
|
|
3731
|
-
flowTreeTraversalSubprocess(fromActivity, direction, skipChildren = false) {
|
|
3794
|
+
flowTreeTraversalSubprocess(fromActivity, direction, skipChildren = false, forwardTraversalBoundary = null) {
|
|
3732
3795
|
if (direction === FlowSubprocessMode.FORWARD) {
|
|
3733
|
-
return this.traverseForward(fromActivity, skipChildren);
|
|
3796
|
+
return this.traverseForward(fromActivity, skipChildren, forwardTraversalBoundary);
|
|
3734
3797
|
} else {
|
|
3735
3798
|
return this.traverseBackward(fromActivity);
|
|
3736
3799
|
}
|
|
@@ -3739,10 +3802,15 @@ class FlowTraversalService {
|
|
|
3739
3802
|
* Traverse forward in the activity tree
|
|
3740
3803
|
* @param {Activity} fromActivity - Starting activity
|
|
3741
3804
|
* @param {boolean} skipChildren - Whether to skip children
|
|
3805
|
+
* @param {Activity | null} forwardTraversalBoundary - Cluster boundary for an SB.2.1 forwardOnly direction reversal
|
|
3742
3806
|
* @return {FlowTreeTraversalResult}
|
|
3807
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - a reversed Forward traversal from a forwardOnly cluster remains within that cluster.
|
|
3743
3808
|
*/
|
|
3744
|
-
traverseForward(fromActivity, skipChildren) {
|
|
3745
|
-
if (
|
|
3809
|
+
traverseForward(fromActivity, skipChildren, forwardTraversalBoundary = null) {
|
|
3810
|
+
if (forwardTraversalBoundary && !this.isDescendantOfOrSelf(fromActivity, forwardTraversalBoundary)) {
|
|
3811
|
+
return { activity: null, endSequencingSession: false };
|
|
3812
|
+
}
|
|
3813
|
+
if (!forwardTraversalBoundary && skipChildren && this.isActivityLastOverall(fromActivity)) {
|
|
3746
3814
|
if (this.activityTree.root) {
|
|
3747
3815
|
this.terminateDescendentAttempts(this.activityTree.root);
|
|
3748
3816
|
}
|
|
@@ -3761,6 +3829,9 @@ class FlowTraversalService {
|
|
|
3761
3829
|
if (nextSibling) {
|
|
3762
3830
|
return { activity: nextSibling, endSequencingSession: false };
|
|
3763
3831
|
}
|
|
3832
|
+
if (forwardTraversalBoundary && (current === forwardTraversalBoundary || current.parent === forwardTraversalBoundary)) {
|
|
3833
|
+
return { activity: null, endSequencingSession: false };
|
|
3834
|
+
}
|
|
3764
3835
|
current = current.parent;
|
|
3765
3836
|
}
|
|
3766
3837
|
if (this.activityTree.root) {
|
|
@@ -3772,6 +3843,7 @@ class FlowTraversalService {
|
|
|
3772
3843
|
* Traverse backward in the activity tree
|
|
3773
3844
|
* @param {Activity} fromActivity - Starting activity
|
|
3774
3845
|
* @return {FlowTreeTraversalResult}
|
|
3846
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - backward traversal into a forwardOnly cluster selects the first available child and reverses traversal direction to Forward.
|
|
3775
3847
|
*/
|
|
3776
3848
|
traverseBackward(fromActivity) {
|
|
3777
3849
|
if (fromActivity.parent && fromActivity.parent.sequencingControls.forwardOnly) {
|
|
@@ -3779,10 +3851,7 @@ class FlowTraversalService {
|
|
|
3779
3851
|
}
|
|
3780
3852
|
const previousSibling = this.activityTree.getPreviousSibling(fromActivity);
|
|
3781
3853
|
if (previousSibling) {
|
|
3782
|
-
return
|
|
3783
|
-
activity: this.getLastDescendant(previousSibling),
|
|
3784
|
-
endSequencingSession: false
|
|
3785
|
-
};
|
|
3854
|
+
return this.getBackwardTraversalEntry(previousSibling);
|
|
3786
3855
|
}
|
|
3787
3856
|
let current = fromActivity;
|
|
3788
3857
|
let ancestorIterations = 0;
|
|
@@ -3793,38 +3862,64 @@ class FlowTraversalService {
|
|
|
3793
3862
|
}
|
|
3794
3863
|
const parentPreviousSibling = this.activityTree.getPreviousSibling(current.parent);
|
|
3795
3864
|
if (parentPreviousSibling) {
|
|
3796
|
-
return
|
|
3797
|
-
activity: this.getLastDescendant(parentPreviousSibling),
|
|
3798
|
-
endSequencingSession: false
|
|
3799
|
-
};
|
|
3865
|
+
return this.getBackwardTraversalEntry(parentPreviousSibling);
|
|
3800
3866
|
}
|
|
3801
3867
|
current = current.parent;
|
|
3802
3868
|
}
|
|
3803
3869
|
return { activity: null, endSequencingSession: false };
|
|
3804
3870
|
}
|
|
3805
3871
|
/**
|
|
3806
|
-
* Get the
|
|
3872
|
+
* Get the activity entered by backward traversal.
|
|
3807
3873
|
* @param {Activity} activity - The activity
|
|
3808
|
-
* @return {
|
|
3874
|
+
* @return {FlowTreeTraversalResult} - The entered activity and effective direction
|
|
3875
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - entering a forwardOnly cluster while moving Backward uses the first available child and changes direction to Forward.
|
|
3809
3876
|
*/
|
|
3810
|
-
|
|
3811
|
-
let
|
|
3877
|
+
getBackwardTraversalEntry(activity) {
|
|
3878
|
+
let enteredActivity = activity;
|
|
3812
3879
|
let iterations = 0;
|
|
3813
3880
|
const maxIterations = 1e4;
|
|
3814
3881
|
while (true) {
|
|
3815
3882
|
if (++iterations > maxIterations) {
|
|
3816
|
-
throw new Error("Infinite loop detected while getting
|
|
3883
|
+
throw new Error("Infinite loop detected while getting backward traversal entry");
|
|
3817
3884
|
}
|
|
3818
|
-
this.ensureSelectionAndRandomization(
|
|
3819
|
-
const children =
|
|
3885
|
+
this.ensureSelectionAndRandomization(enteredActivity);
|
|
3886
|
+
const children = enteredActivity.getAvailableChildren();
|
|
3820
3887
|
if (children.length === 0) {
|
|
3821
3888
|
break;
|
|
3822
3889
|
}
|
|
3890
|
+
if (enteredActivity.sequencingControls.forwardOnly) {
|
|
3891
|
+
return {
|
|
3892
|
+
activity: children[0] || null,
|
|
3893
|
+
endSequencingSession: false,
|
|
3894
|
+
direction: FlowSubprocessMode.FORWARD,
|
|
3895
|
+
forwardOnlyCluster: enteredActivity
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3823
3898
|
const lastChild = children[children.length - 1];
|
|
3824
3899
|
if (!lastChild) break;
|
|
3825
|
-
|
|
3900
|
+
enteredActivity = lastChild;
|
|
3901
|
+
}
|
|
3902
|
+
return {
|
|
3903
|
+
activity: enteredActivity,
|
|
3904
|
+
endSequencingSession: false
|
|
3905
|
+
};
|
|
3906
|
+
}
|
|
3907
|
+
/**
|
|
3908
|
+
* Check whether an activity is the same as or beneath an ancestor.
|
|
3909
|
+
* @param {Activity} activity - The activity to check
|
|
3910
|
+
* @param {Activity} ancestor - The expected ancestor
|
|
3911
|
+
* @return {boolean} - True when activity is within ancestor
|
|
3912
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - bounds Forward traversal after a forwardOnly direction reversal to the entered cluster.
|
|
3913
|
+
*/
|
|
3914
|
+
isDescendantOfOrSelf(activity, ancestor) {
|
|
3915
|
+
let current = activity;
|
|
3916
|
+
while (current) {
|
|
3917
|
+
if (current === ancestor) {
|
|
3918
|
+
return true;
|
|
3919
|
+
}
|
|
3920
|
+
current = current.parent;
|
|
3826
3921
|
}
|
|
3827
|
-
return
|
|
3922
|
+
return false;
|
|
3828
3923
|
}
|
|
3829
3924
|
/**
|
|
3830
3925
|
* Flow Activity Traversal Subprocess (SB.2.2)
|
|
@@ -6374,7 +6469,9 @@ const checkValidFormat = memoize(
|
|
|
6374
6469
|
(CMIElement, value, regexPattern, errorCode, _errorClass, allowEmptyString) => {
|
|
6375
6470
|
const valueKey = typeof value === "string" ? value : `[${typeof value}]`;
|
|
6376
6471
|
return `${CMIElement}:${valueKey}:${regexPattern}:${errorCode}:${allowEmptyString || false}`;
|
|
6377
|
-
}
|
|
6472
|
+
},
|
|
6473
|
+
// Normal capped CMI values and regexes fit within 2000 characters; large uncapped values bypass caching.
|
|
6474
|
+
{ maxEntries: 1e3, maxKeyLength: 2e3 }
|
|
6378
6475
|
);
|
|
6379
6476
|
const checkValidRange = memoize(
|
|
6380
6477
|
(CMIElement, value, rangePattern, errorCode, errorClass) => {
|
|
@@ -6397,7 +6494,8 @@ const checkValidRange = memoize(
|
|
|
6397
6494
|
},
|
|
6398
6495
|
// Custom key function that excludes the error class from the cache key
|
|
6399
6496
|
// since it can't be stringified and doesn't affect the validation result
|
|
6400
|
-
(CMIElement, value, rangePattern, errorCode, _errorClass) => `${CMIElement}:${value}:${rangePattern}:${errorCode}
|
|
6497
|
+
(CMIElement, value, rangePattern, errorCode, _errorClass) => `${CMIElement}:${value}:${rangePattern}:${errorCode}`,
|
|
6498
|
+
{ maxEntries: 1e3 }
|
|
6401
6499
|
);
|
|
6402
6500
|
|
|
6403
6501
|
function check2004ValidFormat(CMIElement, value, regexPattern, allowEmptyString) {
|
|
@@ -6906,6 +7004,12 @@ class ActivityObjective {
|
|
|
6906
7004
|
// objectives. It serves as a validity gate for other synced properties.
|
|
6907
7005
|
_measureStatus = false;
|
|
6908
7006
|
_normalizedMeasure = 0;
|
|
7007
|
+
_rawScore = "";
|
|
7008
|
+
_rawScoreKnown = false;
|
|
7009
|
+
_minScore = "";
|
|
7010
|
+
_minScoreKnown = false;
|
|
7011
|
+
_maxScore = "";
|
|
7012
|
+
_maxScoreKnown = false;
|
|
6909
7013
|
_progressMeasure = 0;
|
|
6910
7014
|
_progressMeasureStatus = false;
|
|
6911
7015
|
_completionStatus = CompletionStatus.UNKNOWN;
|
|
@@ -6915,6 +7019,9 @@ class ActivityObjective {
|
|
|
6915
7019
|
_normalizedMeasureDirty = false;
|
|
6916
7020
|
_completionStatusDirty = false;
|
|
6917
7021
|
_progressMeasureDirty = false;
|
|
7022
|
+
_rawScoreDirty = false;
|
|
7023
|
+
_minScoreDirty = false;
|
|
7024
|
+
_maxScoreDirty = false;
|
|
6918
7025
|
constructor(id, options = {}) {
|
|
6919
7026
|
this._id = id;
|
|
6920
7027
|
this._description = options.description ?? null;
|
|
@@ -6960,6 +7067,8 @@ class ActivityObjective {
|
|
|
6960
7067
|
if (this._satisfiedStatus !== value) {
|
|
6961
7068
|
this._satisfiedStatus = value;
|
|
6962
7069
|
this._satisfiedStatusDirty = true;
|
|
7070
|
+
this._satisfiedStatusKnown = true;
|
|
7071
|
+
this._progressStatus = true;
|
|
6963
7072
|
}
|
|
6964
7073
|
}
|
|
6965
7074
|
get satisfiedStatusKnown() {
|
|
@@ -6983,6 +7092,114 @@ class ActivityObjective {
|
|
|
6983
7092
|
this._normalizedMeasureDirty = true;
|
|
6984
7093
|
}
|
|
6985
7094
|
}
|
|
7095
|
+
/**
|
|
7096
|
+
* Return the known raw score value held for ADLSEQ objective score mapping.
|
|
7097
|
+
*
|
|
7098
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score mapInfo
|
|
7099
|
+
*/
|
|
7100
|
+
get rawScore() {
|
|
7101
|
+
return this._rawScore;
|
|
7102
|
+
}
|
|
7103
|
+
/**
|
|
7104
|
+
* Store the RTE raw score associated with this objective.
|
|
7105
|
+
*
|
|
7106
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - raw score mapInfo
|
|
7107
|
+
*/
|
|
7108
|
+
set rawScore(value) {
|
|
7109
|
+
if (this._rawScore !== value) {
|
|
7110
|
+
this._rawScore = value;
|
|
7111
|
+
this._rawScoreDirty = true;
|
|
7112
|
+
}
|
|
7113
|
+
this._rawScoreKnown = value !== "";
|
|
7114
|
+
}
|
|
7115
|
+
/**
|
|
7116
|
+
* Return whether this objective's raw score is known.
|
|
7117
|
+
*
|
|
7118
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score known state
|
|
7119
|
+
*/
|
|
7120
|
+
get rawScoreKnown() {
|
|
7121
|
+
return this._rawScoreKnown;
|
|
7122
|
+
}
|
|
7123
|
+
/**
|
|
7124
|
+
* Set whether this objective's raw score is known.
|
|
7125
|
+
*
|
|
7126
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score known state
|
|
7127
|
+
*/
|
|
7128
|
+
set rawScoreKnown(value) {
|
|
7129
|
+
this._rawScoreKnown = value;
|
|
7130
|
+
}
|
|
7131
|
+
/**
|
|
7132
|
+
* Return the known minimum score value held for ADLSEQ objective score mapping.
|
|
7133
|
+
*
|
|
7134
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score mapInfo
|
|
7135
|
+
*/
|
|
7136
|
+
get minScore() {
|
|
7137
|
+
return this._minScore;
|
|
7138
|
+
}
|
|
7139
|
+
/**
|
|
7140
|
+
* Store the RTE minimum score associated with this objective.
|
|
7141
|
+
*
|
|
7142
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - min score mapInfo
|
|
7143
|
+
*/
|
|
7144
|
+
set minScore(value) {
|
|
7145
|
+
if (this._minScore !== value) {
|
|
7146
|
+
this._minScore = value;
|
|
7147
|
+
this._minScoreDirty = true;
|
|
7148
|
+
}
|
|
7149
|
+
this._minScoreKnown = value !== "";
|
|
7150
|
+
}
|
|
7151
|
+
/**
|
|
7152
|
+
* Return whether this objective's minimum score is known.
|
|
7153
|
+
*
|
|
7154
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score known state
|
|
7155
|
+
*/
|
|
7156
|
+
get minScoreKnown() {
|
|
7157
|
+
return this._minScoreKnown;
|
|
7158
|
+
}
|
|
7159
|
+
/**
|
|
7160
|
+
* Set whether this objective's minimum score is known.
|
|
7161
|
+
*
|
|
7162
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score known state
|
|
7163
|
+
*/
|
|
7164
|
+
set minScoreKnown(value) {
|
|
7165
|
+
this._minScoreKnown = value;
|
|
7166
|
+
}
|
|
7167
|
+
/**
|
|
7168
|
+
* Return the known maximum score value held for ADLSEQ objective score mapping.
|
|
7169
|
+
*
|
|
7170
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score mapInfo
|
|
7171
|
+
*/
|
|
7172
|
+
get maxScore() {
|
|
7173
|
+
return this._maxScore;
|
|
7174
|
+
}
|
|
7175
|
+
/**
|
|
7176
|
+
* Store the RTE maximum score associated with this objective.
|
|
7177
|
+
*
|
|
7178
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - max score mapInfo
|
|
7179
|
+
*/
|
|
7180
|
+
set maxScore(value) {
|
|
7181
|
+
if (this._maxScore !== value) {
|
|
7182
|
+
this._maxScore = value;
|
|
7183
|
+
this._maxScoreDirty = true;
|
|
7184
|
+
}
|
|
7185
|
+
this._maxScoreKnown = value !== "";
|
|
7186
|
+
}
|
|
7187
|
+
/**
|
|
7188
|
+
* Return whether this objective's maximum score is known.
|
|
7189
|
+
*
|
|
7190
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score known state
|
|
7191
|
+
*/
|
|
7192
|
+
get maxScoreKnown() {
|
|
7193
|
+
return this._maxScoreKnown;
|
|
7194
|
+
}
|
|
7195
|
+
/**
|
|
7196
|
+
* Set whether this objective's maximum score is known.
|
|
7197
|
+
*
|
|
7198
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score known state
|
|
7199
|
+
*/
|
|
7200
|
+
set maxScoreKnown(value) {
|
|
7201
|
+
this._maxScoreKnown = value;
|
|
7202
|
+
}
|
|
6986
7203
|
get progressMeasure() {
|
|
6987
7204
|
return this._progressMeasure;
|
|
6988
7205
|
}
|
|
@@ -7013,6 +7230,11 @@ class ActivityObjective {
|
|
|
7013
7230
|
set progressStatus(value) {
|
|
7014
7231
|
this._progressStatus = value;
|
|
7015
7232
|
}
|
|
7233
|
+
/**
|
|
7234
|
+
* Report whether a local objective field has changed since the last global write.
|
|
7235
|
+
*
|
|
7236
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - write maps use known local objective data
|
|
7237
|
+
*/
|
|
7016
7238
|
isDirty(property) {
|
|
7017
7239
|
switch (property) {
|
|
7018
7240
|
case "satisfiedStatus":
|
|
@@ -7023,8 +7245,19 @@ class ActivityObjective {
|
|
|
7023
7245
|
return this._completionStatusDirty;
|
|
7024
7246
|
case "progressMeasure":
|
|
7025
7247
|
return this._progressMeasureDirty;
|
|
7248
|
+
case "rawScore":
|
|
7249
|
+
return this._rawScoreDirty;
|
|
7250
|
+
case "minScore":
|
|
7251
|
+
return this._minScoreDirty;
|
|
7252
|
+
case "maxScore":
|
|
7253
|
+
return this._maxScoreDirty;
|
|
7026
7254
|
}
|
|
7027
7255
|
}
|
|
7256
|
+
/**
|
|
7257
|
+
* Clear a local objective dirty flag after a successful global write.
|
|
7258
|
+
*
|
|
7259
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - write maps update global objective state
|
|
7260
|
+
*/
|
|
7028
7261
|
clearDirty(property) {
|
|
7029
7262
|
switch (property) {
|
|
7030
7263
|
case "satisfiedStatus":
|
|
@@ -7039,13 +7272,31 @@ class ActivityObjective {
|
|
|
7039
7272
|
case "progressMeasure":
|
|
7040
7273
|
this._progressMeasureDirty = false;
|
|
7041
7274
|
break;
|
|
7275
|
+
case "rawScore":
|
|
7276
|
+
this._rawScoreDirty = false;
|
|
7277
|
+
break;
|
|
7278
|
+
case "minScore":
|
|
7279
|
+
this._minScoreDirty = false;
|
|
7280
|
+
break;
|
|
7281
|
+
case "maxScore":
|
|
7282
|
+
this._maxScoreDirty = false;
|
|
7283
|
+
break;
|
|
7042
7284
|
}
|
|
7043
7285
|
}
|
|
7286
|
+
/**
|
|
7287
|
+
* Clear all write-map dirty flags for this objective.
|
|
7288
|
+
*
|
|
7289
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - objective mapInfo writes are field-specific
|
|
7290
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score mapInfo writes are field-specific
|
|
7291
|
+
*/
|
|
7044
7292
|
clearAllDirty() {
|
|
7045
7293
|
this._satisfiedStatusDirty = false;
|
|
7046
7294
|
this._normalizedMeasureDirty = false;
|
|
7047
7295
|
this._completionStatusDirty = false;
|
|
7048
7296
|
this._progressMeasureDirty = false;
|
|
7297
|
+
this._rawScoreDirty = false;
|
|
7298
|
+
this._minScoreDirty = false;
|
|
7299
|
+
this._maxScoreDirty = false;
|
|
7049
7300
|
}
|
|
7050
7301
|
/**
|
|
7051
7302
|
* Initialize objective values from CMI data transfer
|
|
@@ -7055,6 +7306,8 @@ class ActivityObjective {
|
|
|
7055
7306
|
* @param satisfiedStatus - The satisfied status from CMI
|
|
7056
7307
|
* @param normalizedMeasure - The normalized measure from CMI
|
|
7057
7308
|
* @param measureStatus - Whether measure is valid
|
|
7309
|
+
*
|
|
7310
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - objective satisfaction and measure transfer
|
|
7058
7311
|
*/
|
|
7059
7312
|
initializeFromCMI(satisfiedStatus, normalizedMeasure, measureStatus) {
|
|
7060
7313
|
this._satisfiedStatus = satisfiedStatus;
|
|
@@ -7063,17 +7316,93 @@ class ActivityObjective {
|
|
|
7063
7316
|
this._normalizedMeasureDirty = true;
|
|
7064
7317
|
this._measureStatus = measureStatus;
|
|
7065
7318
|
}
|
|
7319
|
+
/**
|
|
7320
|
+
* Initialize raw/min/max objective score values from RTE data transfer.
|
|
7321
|
+
*
|
|
7322
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - objective score transfer
|
|
7323
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score mapInfo writes
|
|
7324
|
+
*/
|
|
7325
|
+
initializeScoreFromCMI(score) {
|
|
7326
|
+
if (score.rawScore !== void 0 && score.rawScore !== "") {
|
|
7327
|
+
this._rawScore = score.rawScore;
|
|
7328
|
+
this._rawScoreKnown = true;
|
|
7329
|
+
this._rawScoreDirty = true;
|
|
7330
|
+
}
|
|
7331
|
+
if (score.minScore !== void 0 && score.minScore !== "") {
|
|
7332
|
+
this._minScore = score.minScore;
|
|
7333
|
+
this._minScoreKnown = true;
|
|
7334
|
+
this._minScoreDirty = true;
|
|
7335
|
+
}
|
|
7336
|
+
if (score.maxScore !== void 0 && score.maxScore !== "") {
|
|
7337
|
+
this._maxScore = score.maxScore;
|
|
7338
|
+
this._maxScoreKnown = true;
|
|
7339
|
+
this._maxScoreDirty = true;
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
/**
|
|
7343
|
+
* Apply read-mapped global objective state without marking the values dirty.
|
|
7344
|
+
*
|
|
7345
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps provide access to global objective state
|
|
7346
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score read maps are access-only
|
|
7347
|
+
*/
|
|
7348
|
+
applyReadMappedState(state) {
|
|
7349
|
+
if (state.satisfiedStatus !== void 0) {
|
|
7350
|
+
this._satisfiedStatus = state.satisfiedStatus;
|
|
7351
|
+
this._satisfiedStatusKnown = true;
|
|
7352
|
+
this._progressStatus = true;
|
|
7353
|
+
}
|
|
7354
|
+
if (state.normalizedMeasure !== void 0) {
|
|
7355
|
+
this._normalizedMeasure = state.normalizedMeasure;
|
|
7356
|
+
this._measureStatus = true;
|
|
7357
|
+
}
|
|
7358
|
+
if (state.completionStatus !== void 0) {
|
|
7359
|
+
this._completionStatus = state.completionStatus;
|
|
7360
|
+
}
|
|
7361
|
+
if (state.progressMeasure !== void 0) {
|
|
7362
|
+
this._progressMeasure = state.progressMeasure;
|
|
7363
|
+
this._progressMeasureStatus = true;
|
|
7364
|
+
}
|
|
7365
|
+
if (state.rawScore !== void 0) {
|
|
7366
|
+
this._rawScore = state.rawScore;
|
|
7367
|
+
this._rawScoreKnown = true;
|
|
7368
|
+
}
|
|
7369
|
+
if (state.minScore !== void 0) {
|
|
7370
|
+
this._minScore = state.minScore;
|
|
7371
|
+
this._minScoreKnown = true;
|
|
7372
|
+
}
|
|
7373
|
+
if (state.maxScore !== void 0) {
|
|
7374
|
+
this._maxScore = state.maxScore;
|
|
7375
|
+
this._maxScoreKnown = true;
|
|
7376
|
+
}
|
|
7377
|
+
}
|
|
7378
|
+
/**
|
|
7379
|
+
* Reset local objective state for a fresh activity attempt.
|
|
7380
|
+
*
|
|
7381
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - unknown objective state before tracking data exists
|
|
7382
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score map fields are unknown until transferred or read
|
|
7383
|
+
*/
|
|
7066
7384
|
resetState() {
|
|
7067
7385
|
this._satisfiedStatus = false;
|
|
7068
7386
|
this._satisfiedStatusKnown = false;
|
|
7069
7387
|
this._measureStatus = false;
|
|
7070
7388
|
this._normalizedMeasure = 0;
|
|
7389
|
+
this._rawScore = "";
|
|
7390
|
+
this._rawScoreKnown = false;
|
|
7391
|
+
this._minScore = "";
|
|
7392
|
+
this._minScoreKnown = false;
|
|
7393
|
+
this._maxScore = "";
|
|
7394
|
+
this._maxScoreKnown = false;
|
|
7071
7395
|
this._progressMeasure = 0;
|
|
7072
7396
|
this._progressMeasureStatus = false;
|
|
7073
7397
|
this._completionStatus = CompletionStatus.UNKNOWN;
|
|
7074
7398
|
this._progressStatus = false;
|
|
7075
7399
|
this.clearAllDirty();
|
|
7076
7400
|
}
|
|
7401
|
+
/**
|
|
7402
|
+
* Copy primary activity objective state back into the primary objective model.
|
|
7403
|
+
*
|
|
7404
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - primary objective state is available to sequencing
|
|
7405
|
+
*/
|
|
7077
7406
|
updateFromActivity(activity) {
|
|
7078
7407
|
if (this._satisfiedStatus !== activity.objectiveSatisfiedStatus) {
|
|
7079
7408
|
this._satisfiedStatus = activity.objectiveSatisfiedStatus;
|
|
@@ -7095,6 +7424,11 @@ class ActivityObjective {
|
|
|
7095
7424
|
this._completionStatusDirty = true;
|
|
7096
7425
|
}
|
|
7097
7426
|
}
|
|
7427
|
+
/**
|
|
7428
|
+
* Apply primary objective state to the owning activity for sequencing rules and rollup.
|
|
7429
|
+
*
|
|
7430
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - primary objective contributes activity state
|
|
7431
|
+
*/
|
|
7098
7432
|
applyToActivity(activity) {
|
|
7099
7433
|
if (!this._isPrimary) {
|
|
7100
7434
|
return;
|
|
@@ -7105,7 +7439,8 @@ class ActivityObjective {
|
|
|
7105
7439
|
this._normalizedMeasure,
|
|
7106
7440
|
this._progressMeasure,
|
|
7107
7441
|
this._progressMeasureStatus,
|
|
7108
|
-
this._completionStatus
|
|
7442
|
+
this._completionStatus,
|
|
7443
|
+
this._progressStatus || this._satisfiedStatusKnown
|
|
7109
7444
|
);
|
|
7110
7445
|
}
|
|
7111
7446
|
}
|
|
@@ -8175,7 +8510,7 @@ class Activity extends BaseCMI {
|
|
|
8175
8510
|
/**
|
|
8176
8511
|
* Setter for primary objective
|
|
8177
8512
|
* @param {ActivityObjective | null} objective
|
|
8178
|
-
|
|
8513
|
+
*/
|
|
8179
8514
|
set primaryObjective(objective) {
|
|
8180
8515
|
this._primaryObjective = objective;
|
|
8181
8516
|
if (this._primaryObjective) {
|
|
@@ -8197,7 +8532,7 @@ class Activity extends BaseCMI {
|
|
|
8197
8532
|
/**
|
|
8198
8533
|
* Replace objectives collection
|
|
8199
8534
|
* @param {ActivityObjective[]} objectives
|
|
8200
|
-
|
|
8535
|
+
*/
|
|
8201
8536
|
set objectives(objectives) {
|
|
8202
8537
|
this._objectives = [...objectives];
|
|
8203
8538
|
this.syncPrimaryObjectiveCollection();
|
|
@@ -8290,12 +8625,12 @@ class Activity extends BaseCMI {
|
|
|
8290
8625
|
this._objectiveNormalizedMeasureDirty = false;
|
|
8291
8626
|
this._objectiveMeasureStatusDirty = false;
|
|
8292
8627
|
}
|
|
8293
|
-
setPrimaryObjectiveState(satisfiedStatus, measureStatus, normalizedMeasure, progressMeasure, progressMeasureStatus, completionStatus) {
|
|
8628
|
+
setPrimaryObjectiveState(satisfiedStatus, measureStatus, normalizedMeasure, progressMeasure, progressMeasureStatus, completionStatus, objectiveProgressStatus = true) {
|
|
8294
8629
|
if (this._objectiveSatisfiedStatus !== satisfiedStatus) {
|
|
8295
8630
|
this._objectiveSatisfiedStatus = satisfiedStatus;
|
|
8296
8631
|
this._objectiveSatisfiedStatusDirty = true;
|
|
8297
8632
|
}
|
|
8298
|
-
this._objectiveSatisfiedStatusKnown =
|
|
8633
|
+
this._objectiveSatisfiedStatusKnown = objectiveProgressStatus;
|
|
8299
8634
|
if (this._objectiveMeasureStatus !== measureStatus) {
|
|
8300
8635
|
this._objectiveMeasureStatus = measureStatus;
|
|
8301
8636
|
this._objectiveMeasureStatusDirty = true;
|
|
@@ -8314,14 +8649,28 @@ class Activity extends BaseCMI {
|
|
|
8314
8649
|
this._primaryObjective.progressMeasure = progressMeasure;
|
|
8315
8650
|
this._primaryObjective.progressMeasureStatus = progressMeasureStatus;
|
|
8316
8651
|
this._primaryObjective.completionStatus = completionStatus;
|
|
8652
|
+
this._primaryObjective.satisfiedStatusKnown = objectiveProgressStatus;
|
|
8653
|
+
this._primaryObjective.progressStatus = objectiveProgressStatus;
|
|
8317
8654
|
}
|
|
8318
8655
|
}
|
|
8656
|
+
/**
|
|
8657
|
+
* Snapshot objective state for sequencing persistence.
|
|
8658
|
+
*
|
|
8659
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - objective state persists across attempts
|
|
8660
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score-map state persists with objective state
|
|
8661
|
+
*/
|
|
8319
8662
|
getObjectiveStateSnapshot() {
|
|
8320
8663
|
const primarySnapshot = this._primaryObjective ? {
|
|
8321
8664
|
id: this._primaryObjective.id,
|
|
8322
8665
|
satisfiedStatus: this.objectiveSatisfiedStatus,
|
|
8323
8666
|
measureStatus: this.objectiveMeasureStatus,
|
|
8324
8667
|
normalizedMeasure: this.objectiveNormalizedMeasure,
|
|
8668
|
+
rawScore: this._primaryObjective.rawScore,
|
|
8669
|
+
rawScoreKnown: this._primaryObjective.rawScoreKnown,
|
|
8670
|
+
minScore: this._primaryObjective.minScore,
|
|
8671
|
+
minScoreKnown: this._primaryObjective.minScoreKnown,
|
|
8672
|
+
maxScore: this._primaryObjective.maxScore,
|
|
8673
|
+
maxScoreKnown: this._primaryObjective.maxScoreKnown,
|
|
8325
8674
|
progressMeasure: this.progressMeasure ?? 0,
|
|
8326
8675
|
progressMeasureStatus: this.progressMeasureStatus,
|
|
8327
8676
|
progressStatus: this._primaryObjective.progressStatus,
|
|
@@ -8334,6 +8683,12 @@ class Activity extends BaseCMI {
|
|
|
8334
8683
|
satisfiedStatus: objective.satisfiedStatus,
|
|
8335
8684
|
measureStatus: objective.measureStatus,
|
|
8336
8685
|
normalizedMeasure: objective.normalizedMeasure,
|
|
8686
|
+
rawScore: objective.rawScore,
|
|
8687
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
8688
|
+
minScore: objective.minScore,
|
|
8689
|
+
minScoreKnown: objective.minScoreKnown,
|
|
8690
|
+
maxScore: objective.maxScore,
|
|
8691
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
8337
8692
|
progressMeasure: objective.progressMeasure,
|
|
8338
8693
|
progressMeasureStatus: objective.progressMeasureStatus,
|
|
8339
8694
|
progressStatus: objective.progressStatus,
|
|
@@ -8346,6 +8701,12 @@ class Activity extends BaseCMI {
|
|
|
8346
8701
|
objectives: additionalSnapshots
|
|
8347
8702
|
};
|
|
8348
8703
|
}
|
|
8704
|
+
/**
|
|
8705
|
+
* Restore objective state from a sequencing persistence snapshot.
|
|
8706
|
+
*
|
|
8707
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - persisted objective state restores sequencing state
|
|
8708
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - persisted score-map state restores objective score fields
|
|
8709
|
+
*/
|
|
8349
8710
|
applyObjectiveStateSnapshot(snapshot) {
|
|
8350
8711
|
if (snapshot.primary) {
|
|
8351
8712
|
const primary = this.getObjectiveById(snapshot.primary.id);
|
|
@@ -8359,8 +8720,10 @@ class Activity extends BaseCMI {
|
|
|
8359
8720
|
state.normalizedMeasure,
|
|
8360
8721
|
state.progressMeasure,
|
|
8361
8722
|
state.progressMeasureStatus,
|
|
8362
|
-
state.completionStatus
|
|
8723
|
+
state.completionStatus,
|
|
8724
|
+
state.progressStatus
|
|
8363
8725
|
);
|
|
8726
|
+
this.applyObjectiveScoreSnapshot(primary.objective, state);
|
|
8364
8727
|
}
|
|
8365
8728
|
}
|
|
8366
8729
|
for (const state of snapshot.objectives) {
|
|
@@ -8373,11 +8736,30 @@ class Activity extends BaseCMI {
|
|
|
8373
8736
|
objective.progressMeasure = state.progressMeasure;
|
|
8374
8737
|
objective.progressMeasureStatus = state.progressMeasureStatus;
|
|
8375
8738
|
objective.completionStatus = state.completionStatus;
|
|
8739
|
+
this.applyObjectiveScoreSnapshot(objective, state);
|
|
8376
8740
|
objective.satisfiedByMeasure = state.satisfiedByMeasure ?? objective.satisfiedByMeasure;
|
|
8377
8741
|
objective.minNormalizedMeasure = state.minNormalizedMeasure !== void 0 ? state.minNormalizedMeasure : objective.minNormalizedMeasure;
|
|
8378
8742
|
}
|
|
8379
8743
|
}
|
|
8380
8744
|
}
|
|
8745
|
+
/**
|
|
8746
|
+
* Restore known raw/min/max score fields from an objective state snapshot.
|
|
8747
|
+
*
|
|
8748
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score known flags restore independently
|
|
8749
|
+
*/
|
|
8750
|
+
applyObjectiveScoreSnapshot(objective, state) {
|
|
8751
|
+
const scoreState = {};
|
|
8752
|
+
if (state.rawScoreKnown) {
|
|
8753
|
+
scoreState.rawScore = state.rawScore;
|
|
8754
|
+
}
|
|
8755
|
+
if (state.minScoreKnown) {
|
|
8756
|
+
scoreState.minScore = state.minScore;
|
|
8757
|
+
}
|
|
8758
|
+
if (state.maxScoreKnown) {
|
|
8759
|
+
scoreState.maxScore = state.maxScore;
|
|
8760
|
+
}
|
|
8761
|
+
objective.applyReadMappedState(scoreState);
|
|
8762
|
+
}
|
|
8381
8763
|
/**
|
|
8382
8764
|
* Get available children with selection and randomization applied
|
|
8383
8765
|
* @return {Activity[]}
|
|
@@ -9768,11 +10150,18 @@ class GlobalObjectiveSynchronizer {
|
|
|
9768
10150
|
*
|
|
9769
10151
|
* @param activity - The activity to process
|
|
9770
10152
|
* @param globalObjectives - Global objective map
|
|
10153
|
+
*
|
|
10154
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - write mapInfo transfers local objective state to global objectives
|
|
10155
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score write maps
|
|
9771
10156
|
*/
|
|
9772
10157
|
syncGlobalObjectivesWritePhase(activity, globalObjectives) {
|
|
10158
|
+
if (!this.canWriteGlobalObjectives(activity)) {
|
|
10159
|
+
return;
|
|
10160
|
+
}
|
|
9773
10161
|
const objectives = activity.getAllObjectives();
|
|
9774
10162
|
for (const objective of objectives) {
|
|
9775
10163
|
const mapInfos = objective.mapInfo.length > 0 ? objective.mapInfo : [this.createDefaultMapInfo(objective)];
|
|
10164
|
+
const dirtyFieldsToClear = /* @__PURE__ */ new Set();
|
|
9776
10165
|
for (const mapInfo of mapInfos) {
|
|
9777
10166
|
const targetId = mapInfo.targetObjectiveID || objective.id;
|
|
9778
10167
|
const globalObjective = this.ensureGlobalObjectiveEntry(
|
|
@@ -9780,36 +10169,54 @@ class GlobalObjectiveSynchronizer {
|
|
|
9780
10169
|
targetId,
|
|
9781
10170
|
objective
|
|
9782
10171
|
);
|
|
9783
|
-
if (mapInfo.writeSatisfiedStatus && objective
|
|
10172
|
+
if (mapInfo.writeSatisfiedStatus && this.hasKnownSatisfiedStatus(objective) && objective.isDirty("satisfiedStatus")) {
|
|
9784
10173
|
globalObjective.satisfiedStatus = objective.satisfiedStatus;
|
|
9785
10174
|
globalObjective.satisfiedStatusKnown = true;
|
|
9786
|
-
|
|
10175
|
+
dirtyFieldsToClear.add("satisfiedStatus");
|
|
9787
10176
|
}
|
|
9788
10177
|
if (mapInfo.writeNormalizedMeasure && objective.measureStatus && objective.isDirty("normalizedMeasure")) {
|
|
9789
10178
|
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
9790
10179
|
globalObjective.normalizedMeasureKnown = true;
|
|
9791
|
-
|
|
9792
|
-
if (
|
|
10180
|
+
dirtyFieldsToClear.add("normalizedMeasure");
|
|
10181
|
+
if (mapInfo.writeSatisfiedStatus && objective.satisfiedByMeasure) {
|
|
9793
10182
|
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
9794
10183
|
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
9795
10184
|
globalObjective.satisfiedStatusKnown = true;
|
|
9796
|
-
|
|
10185
|
+
dirtyFieldsToClear.add("satisfiedStatus");
|
|
9797
10186
|
}
|
|
9798
10187
|
}
|
|
9799
10188
|
if (mapInfo.writeCompletionStatus && objective.completionStatus !== CompletionStatus.UNKNOWN && objective.isDirty("completionStatus")) {
|
|
9800
10189
|
globalObjective.completionStatus = objective.completionStatus;
|
|
9801
10190
|
globalObjective.completionStatusKnown = true;
|
|
9802
|
-
|
|
10191
|
+
dirtyFieldsToClear.add("completionStatus");
|
|
10192
|
+
}
|
|
10193
|
+
if (mapInfo.writeRawScore && objective.rawScoreKnown && objective.isDirty("rawScore")) {
|
|
10194
|
+
globalObjective.rawScore = objective.rawScore;
|
|
10195
|
+
globalObjective.rawScoreKnown = true;
|
|
10196
|
+
dirtyFieldsToClear.add("rawScore");
|
|
10197
|
+
}
|
|
10198
|
+
if (mapInfo.writeMinScore && objective.minScoreKnown && objective.isDirty("minScore")) {
|
|
10199
|
+
globalObjective.minScore = objective.minScore;
|
|
10200
|
+
globalObjective.minScoreKnown = true;
|
|
10201
|
+
dirtyFieldsToClear.add("minScore");
|
|
10202
|
+
}
|
|
10203
|
+
if (mapInfo.writeMaxScore && objective.maxScoreKnown && objective.isDirty("maxScore")) {
|
|
10204
|
+
globalObjective.maxScore = objective.maxScore;
|
|
10205
|
+
globalObjective.maxScoreKnown = true;
|
|
10206
|
+
dirtyFieldsToClear.add("maxScore");
|
|
9803
10207
|
}
|
|
9804
10208
|
if (mapInfo.writeProgressMeasure && objective.progressMeasureStatus && objective.isDirty("progressMeasure")) {
|
|
9805
10209
|
globalObjective.progressMeasure = objective.progressMeasure;
|
|
9806
10210
|
globalObjective.progressMeasureKnown = true;
|
|
9807
|
-
|
|
10211
|
+
dirtyFieldsToClear.add("progressMeasure");
|
|
9808
10212
|
}
|
|
9809
10213
|
if (mapInfo.updateAttemptData) {
|
|
9810
10214
|
this.updateActivityAttemptData(activity, globalObjective, objective);
|
|
9811
10215
|
}
|
|
9812
10216
|
}
|
|
10217
|
+
for (const property of dirtyFieldsToClear) {
|
|
10218
|
+
objective.clearDirty(property);
|
|
10219
|
+
}
|
|
9813
10220
|
}
|
|
9814
10221
|
}
|
|
9815
10222
|
/**
|
|
@@ -9817,6 +10224,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
9817
10224
|
*
|
|
9818
10225
|
* @param activity - The activity to process
|
|
9819
10226
|
* @param globalObjectives - Global objective map
|
|
10227
|
+
*
|
|
10228
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read mapInfo transfers global objective state into the local view
|
|
10229
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps
|
|
9820
10230
|
*/
|
|
9821
10231
|
syncGlobalObjectivesReadPhase(activity, globalObjectives) {
|
|
9822
10232
|
const objectives = activity.getAllObjectives();
|
|
@@ -9827,25 +10237,13 @@ class GlobalObjectiveSynchronizer {
|
|
|
9827
10237
|
const globalObjective = globalObjectives.get(targetId);
|
|
9828
10238
|
if (!globalObjective) continue;
|
|
9829
10239
|
const isPrimary = objective.isPrimary;
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
objective
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
9838
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
9839
|
-
objective.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
9840
|
-
}
|
|
9841
|
-
}
|
|
9842
|
-
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
9843
|
-
objective.progressMeasure = globalObjective.progressMeasure;
|
|
9844
|
-
objective.progressMeasureStatus = true;
|
|
9845
|
-
}
|
|
9846
|
-
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
9847
|
-
objective.completionStatus = globalObjective.completionStatus;
|
|
9848
|
-
}
|
|
10240
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
10241
|
+
activity,
|
|
10242
|
+
objective,
|
|
10243
|
+
mapInfo,
|
|
10244
|
+
globalObjective
|
|
10245
|
+
);
|
|
10246
|
+
this.applyGlobalObjectiveReadState(objective, readState);
|
|
9849
10247
|
if (isPrimary) {
|
|
9850
10248
|
objective.applyToActivity(activity);
|
|
9851
10249
|
}
|
|
@@ -9888,56 +10286,61 @@ class GlobalObjectiveSynchronizer {
|
|
|
9888
10286
|
* @param objective - The objective to sync
|
|
9889
10287
|
* @param mapInfo - Map info for this objective
|
|
9890
10288
|
* @param globalObjective - The global objective
|
|
10289
|
+
*
|
|
10290
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - objective mapInfo read/write synchronization
|
|
10291
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score mapInfo synchronization
|
|
9891
10292
|
*/
|
|
9892
10293
|
syncObjectiveState(activity, objective, mapInfo, globalObjective) {
|
|
9893
10294
|
try {
|
|
9894
10295
|
const isPrimary = objective.isPrimary;
|
|
9895
10296
|
const localObjective = this.getLocalObjectiveState(activity, objective, isPrimary);
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
objective
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
9904
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
9905
|
-
objective.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
9906
|
-
}
|
|
9907
|
-
}
|
|
9908
|
-
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
9909
|
-
objective.progressMeasure = globalObjective.progressMeasure;
|
|
9910
|
-
objective.progressMeasureStatus = true;
|
|
9911
|
-
}
|
|
9912
|
-
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
9913
|
-
objective.completionStatus = globalObjective.completionStatus;
|
|
9914
|
-
}
|
|
10297
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
10298
|
+
activity,
|
|
10299
|
+
objective,
|
|
10300
|
+
mapInfo,
|
|
10301
|
+
globalObjective
|
|
10302
|
+
);
|
|
10303
|
+
this.applyGlobalObjectiveReadState(objective, readState);
|
|
9915
10304
|
if (objective.isPrimary) {
|
|
9916
10305
|
objective.applyToActivity(activity);
|
|
9917
10306
|
}
|
|
9918
|
-
if (
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
}
|
|
9922
|
-
if (mapInfo.writeNormalizedMeasure && objective.measureStatus) {
|
|
9923
|
-
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
9924
|
-
globalObjective.normalizedMeasureKnown = true;
|
|
9925
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
9926
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
9927
|
-
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
10307
|
+
if (this.canWriteGlobalObjectives(activity)) {
|
|
10308
|
+
if (mapInfo.writeSatisfiedStatus && this.hasKnownSatisfiedStatus(objective)) {
|
|
10309
|
+
globalObjective.satisfiedStatus = objective.satisfiedStatus;
|
|
9928
10310
|
globalObjective.satisfiedStatusKnown = true;
|
|
9929
10311
|
}
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
10312
|
+
if (mapInfo.writeNormalizedMeasure && objective.measureStatus) {
|
|
10313
|
+
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
10314
|
+
globalObjective.normalizedMeasureKnown = true;
|
|
10315
|
+
if (mapInfo.writeSatisfiedStatus && objective.satisfiedByMeasure) {
|
|
10316
|
+
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10317
|
+
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
10318
|
+
globalObjective.satisfiedStatusKnown = true;
|
|
10319
|
+
}
|
|
10320
|
+
}
|
|
10321
|
+
if (mapInfo.writeCompletionStatus && objective.completionStatus !== CompletionStatus.UNKNOWN) {
|
|
10322
|
+
globalObjective.completionStatus = objective.completionStatus;
|
|
10323
|
+
globalObjective.completionStatusKnown = true;
|
|
10324
|
+
}
|
|
10325
|
+
if (mapInfo.writeRawScore && objective.rawScoreKnown) {
|
|
10326
|
+
globalObjective.rawScore = objective.rawScore;
|
|
10327
|
+
globalObjective.rawScoreKnown = true;
|
|
10328
|
+
}
|
|
10329
|
+
if (mapInfo.writeMinScore && objective.minScoreKnown) {
|
|
10330
|
+
globalObjective.minScore = objective.minScore;
|
|
10331
|
+
globalObjective.minScoreKnown = true;
|
|
10332
|
+
}
|
|
10333
|
+
if (mapInfo.writeMaxScore && objective.maxScoreKnown) {
|
|
10334
|
+
globalObjective.maxScore = objective.maxScore;
|
|
10335
|
+
globalObjective.maxScoreKnown = true;
|
|
10336
|
+
}
|
|
10337
|
+
if (mapInfo.writeProgressMeasure && objective.progressMeasureStatus) {
|
|
10338
|
+
globalObjective.progressMeasure = objective.progressMeasure;
|
|
10339
|
+
globalObjective.progressMeasureKnown = true;
|
|
10340
|
+
}
|
|
10341
|
+
if (mapInfo.updateAttemptData) {
|
|
10342
|
+
this.updateActivityAttemptData(activity, globalObjective, objective);
|
|
10343
|
+
}
|
|
9941
10344
|
}
|
|
9942
10345
|
this.eventCallback?.("objective_synchronized", {
|
|
9943
10346
|
activityId: activity.id,
|
|
@@ -9955,6 +10358,50 @@ class GlobalObjectiveSynchronizer {
|
|
|
9955
10358
|
});
|
|
9956
10359
|
}
|
|
9957
10360
|
}
|
|
10361
|
+
/**
|
|
10362
|
+
* Project a global objective through one local objective's read mapInfo.
|
|
10363
|
+
*
|
|
10364
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps provide access to mapped global objective fields
|
|
10365
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps are independent fields
|
|
10366
|
+
*/
|
|
10367
|
+
static getGlobalObjectiveReadState(activity, objective, mapInfo, globalObjective) {
|
|
10368
|
+
const readState = {};
|
|
10369
|
+
if (mapInfo.readSatisfiedStatus && globalObjective.satisfiedStatusKnown) {
|
|
10370
|
+
readState.satisfiedStatus = globalObjective.satisfiedStatus;
|
|
10371
|
+
}
|
|
10372
|
+
if (mapInfo.readNormalizedMeasure && globalObjective.normalizedMeasureKnown) {
|
|
10373
|
+
readState.normalizedMeasure = globalObjective.normalizedMeasure;
|
|
10374
|
+
if (objective.satisfiedByMeasure) {
|
|
10375
|
+
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10376
|
+
readState.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
10377
|
+
}
|
|
10378
|
+
}
|
|
10379
|
+
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
10380
|
+
readState.completionStatus = globalObjective.completionStatus;
|
|
10381
|
+
}
|
|
10382
|
+
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
10383
|
+
readState.progressMeasure = globalObjective.progressMeasure;
|
|
10384
|
+
}
|
|
10385
|
+
if (mapInfo.readRawScore && globalObjective.rawScoreKnown) {
|
|
10386
|
+
readState.rawScore = globalObjective.rawScore;
|
|
10387
|
+
}
|
|
10388
|
+
if (mapInfo.readMinScore && globalObjective.minScoreKnown) {
|
|
10389
|
+
readState.minScore = globalObjective.minScore;
|
|
10390
|
+
}
|
|
10391
|
+
if (mapInfo.readMaxScore && globalObjective.maxScoreKnown) {
|
|
10392
|
+
readState.maxScore = globalObjective.maxScore;
|
|
10393
|
+
}
|
|
10394
|
+
return readState;
|
|
10395
|
+
}
|
|
10396
|
+
/**
|
|
10397
|
+
* Apply read-mapped state to an objective without marking those fields dirty.
|
|
10398
|
+
*
|
|
10399
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps are access to global state, not local writes
|
|
10400
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score read maps do not imply score writes
|
|
10401
|
+
*/
|
|
10402
|
+
applyGlobalObjectiveReadState(objective, readState) {
|
|
10403
|
+
objective.applyReadMappedState(readState);
|
|
10404
|
+
}
|
|
9958
10405
|
/**
|
|
9959
10406
|
* Ensure global objective entry exists
|
|
9960
10407
|
*
|
|
@@ -9962,19 +10409,30 @@ class GlobalObjectiveSynchronizer {
|
|
|
9962
10409
|
* @param targetId - Target objective ID
|
|
9963
10410
|
* @param objective - Source objective
|
|
9964
10411
|
* @returns The global objective entry
|
|
10412
|
+
*
|
|
10413
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective entries hold mapped objective state
|
|
10414
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - global entries hold score-map state
|
|
9965
10415
|
*/
|
|
9966
10416
|
ensureGlobalObjectiveEntry(globalObjectives, targetId, objective) {
|
|
9967
10417
|
if (!globalObjectives.has(targetId)) {
|
|
9968
10418
|
globalObjectives.set(targetId, {
|
|
9969
10419
|
id: targetId,
|
|
9970
|
-
satisfiedStatus:
|
|
9971
|
-
satisfiedStatusKnown:
|
|
9972
|
-
normalizedMeasure:
|
|
9973
|
-
normalizedMeasureKnown:
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
10420
|
+
satisfiedStatus: false,
|
|
10421
|
+
satisfiedStatusKnown: false,
|
|
10422
|
+
normalizedMeasure: 0,
|
|
10423
|
+
normalizedMeasureKnown: false,
|
|
10424
|
+
// @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score fields
|
|
10425
|
+
// remain unknown until their corresponding write map explicitly writes them.
|
|
10426
|
+
rawScore: "",
|
|
10427
|
+
rawScoreKnown: false,
|
|
10428
|
+
minScore: "",
|
|
10429
|
+
minScoreKnown: false,
|
|
10430
|
+
maxScore: "",
|
|
10431
|
+
maxScoreKnown: false,
|
|
10432
|
+
progressMeasure: 0,
|
|
10433
|
+
progressMeasureKnown: false,
|
|
10434
|
+
completionStatus: CompletionStatus.UNKNOWN,
|
|
10435
|
+
completionStatusKnown: false,
|
|
9978
10436
|
satisfiedByMeasure: objective.satisfiedByMeasure,
|
|
9979
10437
|
minNormalizedMeasure: objective.minNormalizedMeasure
|
|
9980
10438
|
});
|
|
@@ -9989,6 +10447,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
9989
10447
|
*
|
|
9990
10448
|
* @param objective - The objective to create default map info for
|
|
9991
10449
|
* @returns Default map info
|
|
10450
|
+
*
|
|
10451
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - mapInfo defaults are applied before objective synchronization
|
|
10452
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score maps require explicit read/write flags
|
|
9992
10453
|
*/
|
|
9993
10454
|
createDefaultMapInfo(objective) {
|
|
9994
10455
|
return {
|
|
@@ -10001,9 +10462,34 @@ class GlobalObjectiveSynchronizer {
|
|
|
10001
10462
|
writeCompletionStatus: true,
|
|
10002
10463
|
readProgressMeasure: false,
|
|
10003
10464
|
writeProgressMeasure: true,
|
|
10465
|
+
readRawScore: false,
|
|
10466
|
+
writeRawScore: false,
|
|
10467
|
+
readMinScore: false,
|
|
10468
|
+
writeMinScore: false,
|
|
10469
|
+
readMaxScore: false,
|
|
10470
|
+
writeMaxScore: false,
|
|
10004
10471
|
updateAttemptData: objective.isPrimary
|
|
10005
10472
|
};
|
|
10006
10473
|
}
|
|
10474
|
+
/**
|
|
10475
|
+
* Return whether the local objective has known satisfaction state to write.
|
|
10476
|
+
*
|
|
10477
|
+
* @spec SCORM 2004 4th Ed. SN 4.2.1 Tracking Model - Objective Progress
|
|
10478
|
+
* Status identifies whether Objective Satisfied Status is known; Objective
|
|
10479
|
+
* Measure Status is independent measure knowledge.
|
|
10480
|
+
*/
|
|
10481
|
+
hasKnownSatisfiedStatus(objective) {
|
|
10482
|
+
return objective.progressStatus || objective.satisfiedStatusKnown;
|
|
10483
|
+
}
|
|
10484
|
+
/**
|
|
10485
|
+
* Return whether this activity is allowed to write tracked state to globals.
|
|
10486
|
+
*
|
|
10487
|
+
* @spec SCORM 2004 4th Ed. SN 3.13.1 Tracked - when False, the LMS
|
|
10488
|
+
* "does not initialize, manage or access any tracking status information".
|
|
10489
|
+
*/
|
|
10490
|
+
canWriteGlobalObjectives(activity) {
|
|
10491
|
+
return activity.sequencingControls.tracked !== false;
|
|
10492
|
+
}
|
|
10007
10493
|
/**
|
|
10008
10494
|
* Get local objective state
|
|
10009
10495
|
*
|
|
@@ -10011,6 +10497,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
10011
10497
|
* @param objective - The objective
|
|
10012
10498
|
* @param isPrimary - Whether this is the primary objective
|
|
10013
10499
|
* @returns Local objective state
|
|
10500
|
+
*
|
|
10501
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - local objective state used for synchronization
|
|
10502
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - local score fields are part of mapped objective state
|
|
10014
10503
|
*/
|
|
10015
10504
|
getLocalObjectiveState(activity, objective, isPrimary) {
|
|
10016
10505
|
if (isPrimary) {
|
|
@@ -10019,6 +10508,12 @@ class GlobalObjectiveSynchronizer {
|
|
|
10019
10508
|
satisfiedStatus: activity.objectiveSatisfiedStatus,
|
|
10020
10509
|
measureStatus: activity.objectiveMeasureStatus,
|
|
10021
10510
|
normalizedMeasure: activity.objectiveNormalizedMeasure,
|
|
10511
|
+
rawScore: objective.rawScore,
|
|
10512
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
10513
|
+
minScore: objective.minScore,
|
|
10514
|
+
minScoreKnown: objective.minScoreKnown,
|
|
10515
|
+
maxScore: objective.maxScore,
|
|
10516
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
10022
10517
|
progressMeasure: activity.progressMeasure,
|
|
10023
10518
|
progressMeasureStatus: activity.progressMeasureStatus,
|
|
10024
10519
|
completionStatus: activity.completionStatus,
|
|
@@ -10030,6 +10525,12 @@ class GlobalObjectiveSynchronizer {
|
|
|
10030
10525
|
satisfiedStatus: objective.satisfiedStatus,
|
|
10031
10526
|
measureStatus: objective.measureStatus,
|
|
10032
10527
|
normalizedMeasure: objective.normalizedMeasure,
|
|
10528
|
+
rawScore: objective.rawScore,
|
|
10529
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
10530
|
+
minScore: objective.minScore,
|
|
10531
|
+
minScoreKnown: objective.minScoreKnown,
|
|
10532
|
+
maxScore: objective.maxScore,
|
|
10533
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
10033
10534
|
progressMeasure: objective.progressMeasure,
|
|
10034
10535
|
progressMeasureStatus: objective.progressMeasureStatus,
|
|
10035
10536
|
completionStatus: objective.completionStatus,
|
|
@@ -10052,7 +10553,7 @@ class GlobalObjectiveSynchronizer {
|
|
|
10052
10553
|
(rule) => rule.action === "completed" || rule.action === "incomplete"
|
|
10053
10554
|
);
|
|
10054
10555
|
if (globalObjective.satisfiedStatusKnown && globalObjective.satisfiedStatus) {
|
|
10055
|
-
if (!hasCompletionRollupRules && (activity.completionStatus === CompletionStatus.UNKNOWN || activity.completionStatus === CompletionStatus.INCOMPLETE)) {
|
|
10556
|
+
if (!hasCompletionRollupRules && !activity.sequencingControls.completionSetByContent && !activity.attemptProgressStatus && (activity.completionStatus === CompletionStatus.UNKNOWN || activity.completionStatus === CompletionStatus.INCOMPLETE)) {
|
|
10056
10557
|
activity.completionStatus = CompletionStatus.COMPLETED;
|
|
10057
10558
|
}
|
|
10058
10559
|
if (activity.successStatus === "unknown") {
|
|
@@ -10426,6 +10927,26 @@ class RollupProcess {
|
|
|
10426
10927
|
}
|
|
10427
10928
|
}
|
|
10428
10929
|
|
|
10930
|
+
function evaluateCompletionStatusFromThreshold({
|
|
10931
|
+
completionThreshold,
|
|
10932
|
+
progressMeasure,
|
|
10933
|
+
storedCompletionStatus
|
|
10934
|
+
}) {
|
|
10935
|
+
if (completionThreshold !== "" && completionThreshold !== null && completionThreshold !== void 0) {
|
|
10936
|
+
const thresholdValue = parseFloat(String(completionThreshold));
|
|
10937
|
+
if (!isNaN(thresholdValue)) {
|
|
10938
|
+
if (progressMeasure !== "" && progressMeasure !== null && progressMeasure !== void 0) {
|
|
10939
|
+
const progressValue = parseFloat(String(progressMeasure));
|
|
10940
|
+
if (!isNaN(progressValue)) {
|
|
10941
|
+
return progressValue >= thresholdValue ? CompletionStatus.COMPLETED : CompletionStatus.INCOMPLETE;
|
|
10942
|
+
}
|
|
10943
|
+
}
|
|
10944
|
+
return CompletionStatus.UNKNOWN;
|
|
10945
|
+
}
|
|
10946
|
+
}
|
|
10947
|
+
return storedCompletionStatus || CompletionStatus.UNKNOWN;
|
|
10948
|
+
}
|
|
10949
|
+
|
|
10429
10950
|
const VALID_COMPLETION_STATUSES = [
|
|
10430
10951
|
CompletionStatus.COMPLETED,
|
|
10431
10952
|
CompletionStatus.INCOMPLETE,
|
|
@@ -10478,12 +10999,43 @@ class RteDataTransferService {
|
|
|
10478
10999
|
* Transfer primary objective data from CMI to activity
|
|
10479
11000
|
* @param {Activity} activity - The activity to transfer data to
|
|
10480
11001
|
* @param {CMIDataForTransfer} cmiData - CMI data from runtime
|
|
11002
|
+
*
|
|
11003
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - primary objective status and score data
|
|
11004
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score write-map source data
|
|
10481
11005
|
*/
|
|
10482
11006
|
transferPrimaryObjective(activity, cmiData) {
|
|
10483
|
-
|
|
10484
|
-
if (
|
|
10485
|
-
|
|
10486
|
-
|
|
11007
|
+
let hasProgressMeasure = false;
|
|
11008
|
+
if (cmiData.progress_measure && cmiData.progress_measure !== "") {
|
|
11009
|
+
const progressMeasure = parseFloat(cmiData.progress_measure);
|
|
11010
|
+
if (!isNaN(progressMeasure)) {
|
|
11011
|
+
hasProgressMeasure = true;
|
|
11012
|
+
activity.progressMeasure = progressMeasure;
|
|
11013
|
+
activity.progressMeasureStatus = true;
|
|
11014
|
+
activity.attemptCompletionAmount = progressMeasure;
|
|
11015
|
+
activity.attemptCompletionAmountStatus = true;
|
|
11016
|
+
if (activity.primaryObjective) {
|
|
11017
|
+
activity.primaryObjective.progressMeasure = progressMeasure;
|
|
11018
|
+
activity.primaryObjective.progressMeasureStatus = true;
|
|
11019
|
+
}
|
|
11020
|
+
}
|
|
11021
|
+
}
|
|
11022
|
+
if (!hasProgressMeasure) {
|
|
11023
|
+
activity.attemptCompletionAmountStatus = false;
|
|
11024
|
+
}
|
|
11025
|
+
if (activity.completedByMeasure) {
|
|
11026
|
+
const completionStatus = evaluateCompletionStatusFromThreshold({
|
|
11027
|
+
completionThreshold: activity.minProgressMeasure,
|
|
11028
|
+
progressMeasure: cmiData.progress_measure,
|
|
11029
|
+
storedCompletionStatus: CompletionStatus.UNKNOWN
|
|
11030
|
+
});
|
|
11031
|
+
activity.completionStatus = completionStatus;
|
|
11032
|
+
activity.attemptProgressStatus = completionStatus !== CompletionStatus.UNKNOWN;
|
|
11033
|
+
} else {
|
|
11034
|
+
const validatedCompletionStatus = validateCompletionStatus(cmiData.completion_status);
|
|
11035
|
+
if (validatedCompletionStatus && validatedCompletionStatus !== CompletionStatus.UNKNOWN) {
|
|
11036
|
+
activity.completionStatus = validatedCompletionStatus;
|
|
11037
|
+
activity.attemptProgressStatus = true;
|
|
11038
|
+
}
|
|
10487
11039
|
}
|
|
10488
11040
|
let hasSuccessStatus = false;
|
|
10489
11041
|
let successStatus = false;
|
|
@@ -10496,9 +11048,9 @@ class RteDataTransferService {
|
|
|
10496
11048
|
activity.objectiveSatisfiedStatus = successStatus;
|
|
10497
11049
|
activity.objectiveSatisfiedStatusKnown = true;
|
|
10498
11050
|
activity.successStatus = validatedSuccessStatus;
|
|
10499
|
-
activity.objectiveMeasureStatus = true;
|
|
10500
11051
|
}
|
|
10501
11052
|
if (cmiData.score) {
|
|
11053
|
+
activity.primaryObjective?.initializeScoreFromCMI(this.getObjectiveScoreState(cmiData.score));
|
|
10502
11054
|
const normalized = this.normalizeScore(cmiData.score);
|
|
10503
11055
|
if (normalized !== null) {
|
|
10504
11056
|
normalizedScore = normalized;
|
|
@@ -10510,30 +11062,25 @@ class RteDataTransferService {
|
|
|
10510
11062
|
if (activity.primaryObjective && (hasSuccessStatus || hasNormalizedMeasure)) {
|
|
10511
11063
|
const finalStatus = hasSuccessStatus ? successStatus : activity.primaryObjective.satisfiedStatus;
|
|
10512
11064
|
const finalMeasure = hasNormalizedMeasure ? normalizedScore : activity.primaryObjective.normalizedMeasure;
|
|
10513
|
-
const measureStatus =
|
|
11065
|
+
const measureStatus = hasNormalizedMeasure;
|
|
10514
11066
|
activity.primaryObjective.initializeFromCMI(finalStatus, finalMeasure, measureStatus);
|
|
10515
11067
|
if (hasSuccessStatus) {
|
|
10516
11068
|
activity.primaryObjective.satisfiedStatusKnown = true;
|
|
10517
11069
|
activity.primaryObjective.progressStatus = true;
|
|
10518
11070
|
}
|
|
10519
11071
|
}
|
|
10520
|
-
if (cmiData.progress_measure && cmiData.progress_measure !== "") {
|
|
10521
|
-
const progressMeasure = parseFloat(cmiData.progress_measure);
|
|
10522
|
-
if (!isNaN(progressMeasure)) {
|
|
10523
|
-
activity.progressMeasure = progressMeasure;
|
|
10524
|
-
activity.progressMeasureStatus = true;
|
|
10525
|
-
if (activity.primaryObjective) {
|
|
10526
|
-
activity.primaryObjective.progressMeasure = progressMeasure;
|
|
10527
|
-
activity.primaryObjective.progressMeasureStatus = true;
|
|
10528
|
-
}
|
|
10529
|
-
}
|
|
10530
|
-
}
|
|
10531
11072
|
}
|
|
10532
11073
|
/**
|
|
10533
|
-
* Transfer
|
|
10534
|
-
* Only transfers changed values to protect global objectives
|
|
11074
|
+
* Transfer objective-array data from CMI to matching activity objectives.
|
|
11075
|
+
* Only transfers changed values to protect global objectives.
|
|
10535
11076
|
* @param {Activity} activity - The activity to transfer data to
|
|
10536
11077
|
* @param {CMIDataForTransfer} cmiData - CMI data from runtime
|
|
11078
|
+
*
|
|
11079
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - cmi.objectives.n data, including
|
|
11080
|
+
* the primary objective entry, is part of the RTE objective data model.
|
|
11081
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 and ADLSEQ objectives extension -
|
|
11082
|
+
* mapped objective status and raw/min/max score data transfer through
|
|
11083
|
+
* objective maps to sequencing state.
|
|
10537
11084
|
*/
|
|
10538
11085
|
transferNonPrimaryObjectives(activity, cmiData) {
|
|
10539
11086
|
if (!cmiData.objectives || cmiData.objectives.length === 0) {
|
|
@@ -10544,14 +11091,17 @@ class RteDataTransferService {
|
|
|
10544
11091
|
continue;
|
|
10545
11092
|
}
|
|
10546
11093
|
const activityObjectiveMatch = activity.getObjectiveById(cmiObjective.id);
|
|
10547
|
-
if (!activityObjectiveMatch
|
|
11094
|
+
if (!activityObjectiveMatch) {
|
|
10548
11095
|
continue;
|
|
10549
11096
|
}
|
|
10550
11097
|
const activityObjective = activityObjectiveMatch.objective;
|
|
11098
|
+
const isPrimaryObjective = activityObjectiveMatch.isPrimary;
|
|
10551
11099
|
let hasSuccessStatus = false;
|
|
10552
11100
|
let successStatus = false;
|
|
10553
11101
|
let hasNormalizedMeasure = false;
|
|
10554
11102
|
let normalizedScore = 0;
|
|
11103
|
+
let hasCompletionStatus = false;
|
|
11104
|
+
let hasProgressMeasure = false;
|
|
10555
11105
|
const validatedObjSuccessStatus = validateSuccessStatus(cmiObjective.success_status);
|
|
10556
11106
|
if (validatedObjSuccessStatus && validatedObjSuccessStatus !== SuccessStatus.UNKNOWN) {
|
|
10557
11107
|
successStatus = validatedObjSuccessStatus === SuccessStatus.PASSED;
|
|
@@ -10561,8 +11111,10 @@ class RteDataTransferService {
|
|
|
10561
11111
|
const validatedObjCompletionStatus = validateCompletionStatus(cmiObjective.completion_status);
|
|
10562
11112
|
if (validatedObjCompletionStatus && validatedObjCompletionStatus !== CompletionStatus.UNKNOWN) {
|
|
10563
11113
|
activityObjective.completionStatus = validatedObjCompletionStatus;
|
|
11114
|
+
hasCompletionStatus = true;
|
|
10564
11115
|
}
|
|
10565
11116
|
if (cmiObjective.score) {
|
|
11117
|
+
activityObjective.initializeScoreFromCMI(this.getObjectiveScoreState(cmiObjective.score));
|
|
10566
11118
|
const normalized = this.normalizeScore(cmiObjective.score);
|
|
10567
11119
|
if (normalized !== null) {
|
|
10568
11120
|
normalizedScore = normalized;
|
|
@@ -10574,12 +11126,29 @@ class RteDataTransferService {
|
|
|
10574
11126
|
const finalMeasure = hasNormalizedMeasure ? normalizedScore : activityObjective.normalizedMeasure;
|
|
10575
11127
|
const measureStatus = hasNormalizedMeasure;
|
|
10576
11128
|
activityObjective.initializeFromCMI(finalStatus, finalMeasure, measureStatus);
|
|
11129
|
+
if (hasSuccessStatus) {
|
|
11130
|
+
activityObjective.satisfiedStatusKnown = true;
|
|
11131
|
+
}
|
|
10577
11132
|
}
|
|
10578
11133
|
if (cmiObjective.progress_measure && cmiObjective.progress_measure !== "") {
|
|
10579
11134
|
const progressMeasure = parseFloat(cmiObjective.progress_measure);
|
|
10580
11135
|
if (!isNaN(progressMeasure)) {
|
|
10581
11136
|
activityObjective.progressMeasure = progressMeasure;
|
|
10582
11137
|
activityObjective.progressMeasureStatus = true;
|
|
11138
|
+
hasProgressMeasure = true;
|
|
11139
|
+
}
|
|
11140
|
+
}
|
|
11141
|
+
if (isPrimaryObjective && (hasSuccessStatus || hasNormalizedMeasure || hasCompletionStatus || hasProgressMeasure)) {
|
|
11142
|
+
activityObjective.applyToActivity(activity);
|
|
11143
|
+
if (validatedObjSuccessStatus && validatedObjSuccessStatus !== SuccessStatus.UNKNOWN) {
|
|
11144
|
+
activity.successStatus = validatedObjSuccessStatus;
|
|
11145
|
+
}
|
|
11146
|
+
if (hasCompletionStatus) {
|
|
11147
|
+
activity.attemptProgressStatus = true;
|
|
11148
|
+
}
|
|
11149
|
+
if (hasProgressMeasure) {
|
|
11150
|
+
activity.attemptCompletionAmount = activityObjective.progressMeasure;
|
|
11151
|
+
activity.attemptCompletionAmountStatus = true;
|
|
10583
11152
|
}
|
|
10584
11153
|
}
|
|
10585
11154
|
}
|
|
@@ -10608,6 +11177,25 @@ class RteDataTransferService {
|
|
|
10608
11177
|
}
|
|
10609
11178
|
return null;
|
|
10610
11179
|
}
|
|
11180
|
+
/**
|
|
11181
|
+
* Convert RTE score data into objective score-map state without numeric reformatting.
|
|
11182
|
+
*
|
|
11183
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - score values transfer from RTE to sequencing state
|
|
11184
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score map fields are independent
|
|
11185
|
+
*/
|
|
11186
|
+
getObjectiveScoreState(score) {
|
|
11187
|
+
const scoreState = {};
|
|
11188
|
+
if (score.raw !== void 0) {
|
|
11189
|
+
scoreState.rawScore = score.raw;
|
|
11190
|
+
}
|
|
11191
|
+
if (score.min !== void 0) {
|
|
11192
|
+
scoreState.minScore = score.min;
|
|
11193
|
+
}
|
|
11194
|
+
if (score.max !== void 0) {
|
|
11195
|
+
scoreState.maxScore = score.max;
|
|
11196
|
+
}
|
|
11197
|
+
return scoreState;
|
|
11198
|
+
}
|
|
10611
11199
|
}
|
|
10612
11200
|
|
|
10613
11201
|
class TerminationHandler {
|
|
@@ -10732,6 +11320,18 @@ class TerminationHandler {
|
|
|
10732
11320
|
this.endAttempt(this.activityTree.currentActivity);
|
|
10733
11321
|
}
|
|
10734
11322
|
}
|
|
11323
|
+
const ancestorExitResult = this.applyAncestorExitActionRules(currentActivity);
|
|
11324
|
+
if (ancestorExitResult.action === "EXIT_ALL") {
|
|
11325
|
+
return this.handleExitAll(ancestorExitResult.activity || currentActivity);
|
|
11326
|
+
}
|
|
11327
|
+
if (ancestorExitResult.exception) {
|
|
11328
|
+
return {
|
|
11329
|
+
terminationRequest: SequencingRequestType.EXIT,
|
|
11330
|
+
sequencingRequest: null,
|
|
11331
|
+
exception: ancestorExitResult.exception,
|
|
11332
|
+
valid: false
|
|
11333
|
+
};
|
|
11334
|
+
}
|
|
10735
11335
|
let processedExit;
|
|
10736
11336
|
let postConditionResult;
|
|
10737
11337
|
do {
|
|
@@ -10957,18 +11557,10 @@ class TerminationHandler {
|
|
|
10957
11557
|
recursionDepth++;
|
|
10958
11558
|
const exitRules = activity.sequencingRules.exitConditionRules;
|
|
10959
11559
|
for (const rule of exitRules) {
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
);
|
|
10965
|
-
} else {
|
|
10966
|
-
conditionsMet = rule.conditions.some(
|
|
10967
|
-
(condition) => condition.evaluate(activity)
|
|
10968
|
-
);
|
|
10969
|
-
}
|
|
10970
|
-
if (conditionsMet) {
|
|
10971
|
-
if (rule.action === RuleActionType.EXIT_PARENT) {
|
|
11560
|
+
if (rule.evaluate(activity)) {
|
|
11561
|
+
if (rule.action === RuleActionType.EXIT) {
|
|
11562
|
+
return { action: "EXIT", recursionDepth };
|
|
11563
|
+
} else if (rule.action === RuleActionType.EXIT_PARENT) {
|
|
10972
11564
|
return { action: "EXIT_PARENT", recursionDepth };
|
|
10973
11565
|
} else if (rule.action === RuleActionType.EXIT_ALL) {
|
|
10974
11566
|
return { action: "EXIT_ALL", recursionDepth };
|
|
@@ -11002,6 +11594,55 @@ class TerminationHandler {
|
|
|
11002
11594
|
this.processExitAtLevel(rootActivity, 0);
|
|
11003
11595
|
this.terminateAll(rootActivity);
|
|
11004
11596
|
}
|
|
11597
|
+
/**
|
|
11598
|
+
* Sequencing Exit Action Rules Subprocess (TB.2.1) ancestor walk
|
|
11599
|
+
* @spec SN Book: TB.2.1 (Sequencing Exit Action Rules Subprocess)
|
|
11600
|
+
* @param {Activity} terminatingActivity - The activity whose attempt just ended
|
|
11601
|
+
* @return {{action: string | null, activity?: Activity, exception?: string}}
|
|
11602
|
+
*/
|
|
11603
|
+
applyAncestorExitActionRules(terminatingActivity) {
|
|
11604
|
+
const activityPath = [];
|
|
11605
|
+
let current = terminatingActivity.parent;
|
|
11606
|
+
while (current) {
|
|
11607
|
+
activityPath.unshift(current);
|
|
11608
|
+
current = current.parent;
|
|
11609
|
+
}
|
|
11610
|
+
for (const activity of activityPath) {
|
|
11611
|
+
const exitAction = this.exitActionRulesSubprocess(activity);
|
|
11612
|
+
if (exitAction === "EXIT_ALL") {
|
|
11613
|
+
this.fireEvent("onAncestorExitAction", {
|
|
11614
|
+
activity: activity.id,
|
|
11615
|
+
action: exitAction
|
|
11616
|
+
});
|
|
11617
|
+
return { action: "EXIT_ALL", activity };
|
|
11618
|
+
}
|
|
11619
|
+
if (exitAction === "EXIT_PARENT") {
|
|
11620
|
+
if (!activity.parent) {
|
|
11621
|
+
return { action: "EXIT_PARENT", activity, exception: "TB.2.3-4" };
|
|
11622
|
+
}
|
|
11623
|
+
this.terminateDescendants(activity.parent);
|
|
11624
|
+
this.activityTree.currentActivity = activity.parent;
|
|
11625
|
+
this.endAttempt(activity.parent);
|
|
11626
|
+
this.fireEvent("onAncestorExitAction", {
|
|
11627
|
+
activity: activity.id,
|
|
11628
|
+
action: exitAction,
|
|
11629
|
+
currentActivity: activity.parent.id
|
|
11630
|
+
});
|
|
11631
|
+
return { action: "EXIT_PARENT", activity: activity.parent };
|
|
11632
|
+
}
|
|
11633
|
+
if (exitAction === "EXIT") {
|
|
11634
|
+
this.terminateDescendants(activity);
|
|
11635
|
+
this.activityTree.currentActivity = activity;
|
|
11636
|
+
this.endAttempt(activity);
|
|
11637
|
+
this.fireEvent("onAncestorExitAction", {
|
|
11638
|
+
activity: activity.id,
|
|
11639
|
+
action: exitAction
|
|
11640
|
+
});
|
|
11641
|
+
return { action: "EXIT", activity };
|
|
11642
|
+
}
|
|
11643
|
+
}
|
|
11644
|
+
return { action: null };
|
|
11645
|
+
}
|
|
11005
11646
|
/**
|
|
11006
11647
|
* Process exit actions at specific level
|
|
11007
11648
|
* @param {Activity} activity - Activity to process
|
|
@@ -11082,18 +11723,10 @@ class TerminationHandler {
|
|
|
11082
11723
|
exitActionRulesSubprocess(activity) {
|
|
11083
11724
|
const exitRules = activity.sequencingRules.exitConditionRules;
|
|
11084
11725
|
for (const rule of exitRules) {
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
);
|
|
11090
|
-
} else {
|
|
11091
|
-
conditionsMet = rule.conditions.some(
|
|
11092
|
-
(condition) => condition.evaluate(activity)
|
|
11093
|
-
);
|
|
11094
|
-
}
|
|
11095
|
-
if (conditionsMet) {
|
|
11096
|
-
if (rule.action === RuleActionType.EXIT_PARENT) {
|
|
11726
|
+
if (rule.evaluate(activity)) {
|
|
11727
|
+
if (rule.action === RuleActionType.EXIT) {
|
|
11728
|
+
return "EXIT";
|
|
11729
|
+
} else if (rule.action === RuleActionType.EXIT_PARENT) {
|
|
11097
11730
|
return "EXIT_PARENT";
|
|
11098
11731
|
} else if (rule.action === RuleActionType.EXIT_ALL) {
|
|
11099
11732
|
return "EXIT_ALL";
|
|
@@ -12609,6 +13242,9 @@ class GlobalObjectiveService {
|
|
|
12609
13242
|
* Collect Global Objectives
|
|
12610
13243
|
* Recursively collects global objectives from the activity tree
|
|
12611
13244
|
* @param {Activity} activity - Activity to collect objectives from
|
|
13245
|
+
*
|
|
13246
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective map contains mapped objective state
|
|
13247
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score fields are unknown until written
|
|
12612
13248
|
*/
|
|
12613
13249
|
collectObjectives(activity) {
|
|
12614
13250
|
const objectives = activity.getAllObjectives();
|
|
@@ -12618,9 +13254,15 @@ class GlobalObjectiveService {
|
|
|
12618
13254
|
this.globalObjectiveMap.set(defaultId, {
|
|
12619
13255
|
id: defaultId,
|
|
12620
13256
|
satisfiedStatus: activity.objectiveSatisfiedStatus,
|
|
12621
|
-
satisfiedStatusKnown: activity.
|
|
13257
|
+
satisfiedStatusKnown: activity.objectiveSatisfiedStatusKnown,
|
|
12622
13258
|
normalizedMeasure: activity.objectiveNormalizedMeasure,
|
|
12623
13259
|
normalizedMeasureKnown: activity.objectiveMeasureStatus,
|
|
13260
|
+
rawScore: "",
|
|
13261
|
+
rawScoreKnown: false,
|
|
13262
|
+
minScore: "",
|
|
13263
|
+
minScoreKnown: false,
|
|
13264
|
+
maxScore: "",
|
|
13265
|
+
maxScoreKnown: false,
|
|
12624
13266
|
progressMeasure: activity.progressMeasure,
|
|
12625
13267
|
progressMeasureKnown: activity.progressMeasureStatus,
|
|
12626
13268
|
completionStatus: activity.completionStatus,
|
|
@@ -12633,6 +13275,12 @@ class GlobalObjectiveService {
|
|
|
12633
13275
|
writeCompletionStatus: true,
|
|
12634
13276
|
readProgressMeasure: true,
|
|
12635
13277
|
writeProgressMeasure: true,
|
|
13278
|
+
readRawScore: false,
|
|
13279
|
+
writeRawScore: false,
|
|
13280
|
+
readMinScore: false,
|
|
13281
|
+
writeMinScore: false,
|
|
13282
|
+
readMaxScore: false,
|
|
13283
|
+
writeMaxScore: false,
|
|
12636
13284
|
satisfiedByMeasure: activity.scaledPassingScore !== null,
|
|
12637
13285
|
minNormalizedMeasure: activity.scaledPassingScore,
|
|
12638
13286
|
updateAttemptData: true
|
|
@@ -12660,9 +13308,15 @@ class GlobalObjectiveService {
|
|
|
12660
13308
|
this.globalObjectiveMap.set(targetId, {
|
|
12661
13309
|
id: targetId,
|
|
12662
13310
|
satisfiedStatus: objective.satisfiedStatus,
|
|
12663
|
-
satisfiedStatusKnown: objective.
|
|
13311
|
+
satisfiedStatusKnown: objective.satisfiedStatusKnown || objective.progressStatus,
|
|
12664
13312
|
normalizedMeasure: objective.normalizedMeasure,
|
|
12665
13313
|
normalizedMeasureKnown: objective.measureStatus,
|
|
13314
|
+
rawScore: "",
|
|
13315
|
+
rawScoreKnown: false,
|
|
13316
|
+
minScore: "",
|
|
13317
|
+
minScoreKnown: false,
|
|
13318
|
+
maxScore: "",
|
|
13319
|
+
maxScoreKnown: false,
|
|
12666
13320
|
progressMeasure: objective.progressMeasure,
|
|
12667
13321
|
progressMeasureKnown: objective.progressMeasureStatus,
|
|
12668
13322
|
completionStatus: objective.completionStatus,
|
|
@@ -14645,27 +15299,44 @@ class SequencingService {
|
|
|
14645
15299
|
}
|
|
14646
15300
|
/**
|
|
14647
15301
|
* Update activity properties from current CMI values
|
|
15302
|
+
*
|
|
15303
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - current CMI values update activity objective state
|
|
15304
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max scores are available to objective write maps
|
|
14648
15305
|
*/
|
|
14649
15306
|
updateActivityFromCMI(activity) {
|
|
14650
|
-
|
|
15307
|
+
let hasProgressMeasure = false;
|
|
15308
|
+
if (this.cmi.progress_measure !== "") {
|
|
15309
|
+
const progressMeasure = parseFloat(this.cmi.progress_measure);
|
|
15310
|
+
if (!isNaN(progressMeasure)) {
|
|
15311
|
+
hasProgressMeasure = true;
|
|
15312
|
+
activity.progressMeasure = progressMeasure;
|
|
15313
|
+
activity.progressMeasureStatus = true;
|
|
15314
|
+
activity.attemptCompletionAmount = progressMeasure;
|
|
15315
|
+
activity.attemptCompletionAmountStatus = true;
|
|
15316
|
+
}
|
|
15317
|
+
}
|
|
15318
|
+
if (!hasProgressMeasure) {
|
|
15319
|
+
activity.attemptCompletionAmountStatus = false;
|
|
15320
|
+
}
|
|
15321
|
+
if (activity.completedByMeasure) {
|
|
15322
|
+
const completionStatus = evaluateCompletionStatusFromThreshold({
|
|
15323
|
+
completionThreshold: activity.minProgressMeasure,
|
|
15324
|
+
progressMeasure: this.cmi.progress_measure,
|
|
15325
|
+
storedCompletionStatus: CompletionStatus.UNKNOWN
|
|
15326
|
+
});
|
|
15327
|
+
activity.completionStatus = completionStatus;
|
|
15328
|
+
activity.attemptProgressStatus = completionStatus !== CompletionStatus.UNKNOWN;
|
|
15329
|
+
} else if (this.cmi.completion_status !== "unknown") {
|
|
14651
15330
|
activity.completionStatus = this.cmi.completion_status;
|
|
14652
15331
|
activity.attemptProgressStatus = true;
|
|
14653
15332
|
}
|
|
14654
15333
|
if (this.cmi.success_status !== "unknown") {
|
|
14655
15334
|
activity.successStatus = this.cmi.success_status;
|
|
14656
15335
|
activity.objectiveSatisfiedStatus = this.cmi.success_status === "passed";
|
|
14657
|
-
activity.objectiveMeasureStatus = true;
|
|
14658
15336
|
if (activity.primaryObjective) {
|
|
14659
15337
|
activity.primaryObjective.progressStatus = true;
|
|
14660
15338
|
}
|
|
14661
15339
|
}
|
|
14662
|
-
if (this.cmi.progress_measure !== "") {
|
|
14663
|
-
const progressMeasure = parseFloat(this.cmi.progress_measure);
|
|
14664
|
-
if (!isNaN(progressMeasure)) {
|
|
14665
|
-
activity.progressMeasure = progressMeasure;
|
|
14666
|
-
activity.progressMeasureStatus = true;
|
|
14667
|
-
}
|
|
14668
|
-
}
|
|
14669
15340
|
if (this.cmi.score && this.cmi.score.scaled !== "") {
|
|
14670
15341
|
const scaledScore = parseFloat(this.cmi.score.scaled);
|
|
14671
15342
|
if (!isNaN(scaledScore)) {
|
|
@@ -14676,6 +15347,17 @@ class SequencingService {
|
|
|
14676
15347
|
}
|
|
14677
15348
|
}
|
|
14678
15349
|
}
|
|
15350
|
+
if (activity.primaryObjective && this.cmi.score) {
|
|
15351
|
+
if (this.cmi.score.raw !== "") {
|
|
15352
|
+
activity.primaryObjective.rawScore = this.cmi.score.raw;
|
|
15353
|
+
}
|
|
15354
|
+
if (this.cmi.score.min !== "") {
|
|
15355
|
+
activity.primaryObjective.minScore = this.cmi.score.min;
|
|
15356
|
+
}
|
|
15357
|
+
if (this.cmi.score.max !== "") {
|
|
15358
|
+
activity.primaryObjective.maxScore = this.cmi.score.max;
|
|
15359
|
+
}
|
|
15360
|
+
}
|
|
14679
15361
|
if (activity.primaryObjective) {
|
|
14680
15362
|
activity.primaryObjective.updateFromActivity(activity);
|
|
14681
15363
|
}
|
|
@@ -17188,7 +17870,7 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
17188
17870
|
if (check2004ValidFormat(
|
|
17189
17871
|
this._cmi_element + ".description",
|
|
17190
17872
|
description,
|
|
17191
|
-
scorm2004_regex.
|
|
17873
|
+
scorm2004_regex.CMILangString,
|
|
17192
17874
|
true
|
|
17193
17875
|
)) {
|
|
17194
17876
|
this._description = description;
|
|
@@ -18092,7 +18774,7 @@ class CMIObjectivesObject extends BaseCMI {
|
|
|
18092
18774
|
if (check2004ValidFormat(
|
|
18093
18775
|
this._cmi_element + ".description",
|
|
18094
18776
|
description,
|
|
18095
|
-
scorm2004_regex.
|
|
18777
|
+
scorm2004_regex.CMILangString,
|
|
18096
18778
|
true
|
|
18097
18779
|
)) {
|
|
18098
18780
|
this._description = description;
|
|
@@ -20823,22 +21505,11 @@ class Scorm2004CMIHandler {
|
|
|
20823
21505
|
* @returns {string} The evaluated completion status
|
|
20824
21506
|
*/
|
|
20825
21507
|
evaluateCompletionStatus() {
|
|
20826
|
-
|
|
20827
|
-
|
|
20828
|
-
|
|
20829
|
-
|
|
20830
|
-
|
|
20831
|
-
if (!isNaN(thresholdValue)) {
|
|
20832
|
-
if (progressMeasure !== "" && progressMeasure !== null && progressMeasure !== void 0) {
|
|
20833
|
-
const progressValue = parseFloat(String(progressMeasure));
|
|
20834
|
-
if (!isNaN(progressValue)) {
|
|
20835
|
-
return progressValue >= thresholdValue ? CompletionStatus.COMPLETED : CompletionStatus.INCOMPLETE;
|
|
20836
|
-
}
|
|
20837
|
-
}
|
|
20838
|
-
return CompletionStatus.UNKNOWN;
|
|
20839
|
-
}
|
|
20840
|
-
}
|
|
20841
|
-
return storedStatus || CompletionStatus.UNKNOWN;
|
|
21508
|
+
return evaluateCompletionStatusFromThreshold({
|
|
21509
|
+
completionThreshold: this.context.cmi.completion_threshold,
|
|
21510
|
+
progressMeasure: this.context.cmi.progress_measure,
|
|
21511
|
+
storedCompletionStatus: this.context.cmi.completion_status
|
|
21512
|
+
});
|
|
20842
21513
|
}
|
|
20843
21514
|
/**
|
|
20844
21515
|
* Evaluates success_status per SCORM 2004 RTE Table 4.2.21.1a
|
|
@@ -20946,6 +21617,9 @@ class SequencingConfigurationBuilder {
|
|
|
20946
21617
|
if (settings.objectiveSetByContent !== void 0) {
|
|
20947
21618
|
target.objectiveSetByContent = settings.objectiveSetByContent;
|
|
20948
21619
|
}
|
|
21620
|
+
if (settings.tracked !== void 0) {
|
|
21621
|
+
target.tracked = settings.tracked;
|
|
21622
|
+
}
|
|
20949
21623
|
}
|
|
20950
21624
|
/**
|
|
20951
21625
|
* Applies the sequencing rules settings to the specified target object.
|
|
@@ -21483,6 +22157,12 @@ class ActivityTreeBuilder {
|
|
|
21483
22157
|
activitySettings.sequencingControls
|
|
21484
22158
|
);
|
|
21485
22159
|
}
|
|
22160
|
+
if (activitySettings.deliveryControls) {
|
|
22161
|
+
this.sequencingConfigBuilder.applySequencingControlsSettings(
|
|
22162
|
+
activity.sequencingControls,
|
|
22163
|
+
activitySettings.deliveryControls
|
|
22164
|
+
);
|
|
22165
|
+
}
|
|
21486
22166
|
if (activitySettings.sequencingRules) {
|
|
21487
22167
|
this.sequencingConfigBuilder.applySequencingRulesSettings(
|
|
21488
22168
|
activity.sequencingRules,
|
|
@@ -21498,6 +22178,21 @@ class ActivityTreeBuilder {
|
|
|
21498
22178
|
if (activitySettings.rollupConsiderations) {
|
|
21499
22179
|
activity.applyRollupConsiderations(activitySettings.rollupConsiderations);
|
|
21500
22180
|
}
|
|
22181
|
+
if (activitySettings.completionThreshold) {
|
|
22182
|
+
const threshold = activitySettings.completionThreshold;
|
|
22183
|
+
if (threshold.completedByMeasure !== void 0) {
|
|
22184
|
+
activity.completedByMeasure = threshold.completedByMeasure;
|
|
22185
|
+
}
|
|
22186
|
+
if (threshold.minProgressMeasure !== void 0) {
|
|
22187
|
+
activity.minProgressMeasure = threshold.minProgressMeasure;
|
|
22188
|
+
activity.completionThreshold = threshold.minProgressMeasure.toString();
|
|
22189
|
+
} else if (threshold.completedByMeasure) {
|
|
22190
|
+
activity.completionThreshold = activity.minProgressMeasure.toString();
|
|
22191
|
+
}
|
|
22192
|
+
if (threshold.progressWeight !== void 0) {
|
|
22193
|
+
activity.progressWeight = threshold.progressWeight;
|
|
22194
|
+
}
|
|
22195
|
+
}
|
|
21501
22196
|
if (activitySettings.hideLmsUi) {
|
|
21502
22197
|
const mergedHide = this.sequencingConfigBuilder.mergeHideLmsUi(
|
|
21503
22198
|
activity.hideLmsUi,
|
|
@@ -21557,19 +22252,19 @@ class ActivityTreeBuilder {
|
|
|
21557
22252
|
createActivityObjectiveFromSettings(objectiveSettings, isPrimary) {
|
|
21558
22253
|
const mapInfo = (objectiveSettings.mapInfo || []).map((info) => ({
|
|
21559
22254
|
targetObjectiveID: info.targetObjectiveID,
|
|
21560
|
-
readSatisfiedStatus: info.readSatisfiedStatus ??
|
|
21561
|
-
readNormalizedMeasure: info.readNormalizedMeasure ??
|
|
22255
|
+
readSatisfiedStatus: info.readSatisfiedStatus ?? true,
|
|
22256
|
+
readNormalizedMeasure: info.readNormalizedMeasure ?? true,
|
|
21562
22257
|
writeSatisfiedStatus: info.writeSatisfiedStatus ?? false,
|
|
21563
22258
|
writeNormalizedMeasure: info.writeNormalizedMeasure ?? false,
|
|
21564
|
-
readCompletionStatus: info.readCompletionStatus ??
|
|
22259
|
+
readCompletionStatus: info.readCompletionStatus ?? true,
|
|
21565
22260
|
writeCompletionStatus: info.writeCompletionStatus ?? false,
|
|
21566
|
-
readProgressMeasure: info.readProgressMeasure ??
|
|
22261
|
+
readProgressMeasure: info.readProgressMeasure ?? true,
|
|
21567
22262
|
writeProgressMeasure: info.writeProgressMeasure ?? false,
|
|
21568
|
-
readRawScore: info.readRawScore ??
|
|
22263
|
+
readRawScore: info.readRawScore ?? true,
|
|
21569
22264
|
writeRawScore: info.writeRawScore ?? false,
|
|
21570
|
-
readMinScore: info.readMinScore ??
|
|
22265
|
+
readMinScore: info.readMinScore ?? true,
|
|
21571
22266
|
writeMinScore: info.writeMinScore ?? false,
|
|
21572
|
-
readMaxScore: info.readMaxScore ??
|
|
22267
|
+
readMaxScore: info.readMaxScore ?? true,
|
|
21573
22268
|
writeMaxScore: info.writeMaxScore ?? false,
|
|
21574
22269
|
updateAttemptData: info.updateAttemptData ?? false
|
|
21575
22270
|
}));
|
|
@@ -21751,6 +22446,9 @@ class GlobalObjectiveManager {
|
|
|
21751
22446
|
*
|
|
21752
22447
|
* @param {string} objectiveId - The global objective ID
|
|
21753
22448
|
* @param {CMIObjectivesObject} objective - The CMI objective object with updated values
|
|
22449
|
+
*
|
|
22450
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objectives synchronize CMI objective data
|
|
22451
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score fields synchronize independently
|
|
21754
22452
|
*/
|
|
21755
22453
|
updateGlobalObjectiveFromCMI(objectiveId, objective) {
|
|
21756
22454
|
if (!objectiveId || !this.context.sequencingService) {
|
|
@@ -21776,6 +22474,18 @@ class GlobalObjectiveManager {
|
|
|
21776
22474
|
updatePayload.normalizedMeasure = normalizedMeasure;
|
|
21777
22475
|
updatePayload.normalizedMeasureKnown = true;
|
|
21778
22476
|
}
|
|
22477
|
+
if (objective.score?.raw !== void 0 && objective.score.raw !== "") {
|
|
22478
|
+
updatePayload.rawScore = objective.score.raw;
|
|
22479
|
+
updatePayload.rawScoreKnown = true;
|
|
22480
|
+
}
|
|
22481
|
+
if (objective.score?.min !== void 0 && objective.score.min !== "") {
|
|
22482
|
+
updatePayload.minScore = objective.score.min;
|
|
22483
|
+
updatePayload.minScoreKnown = true;
|
|
22484
|
+
}
|
|
22485
|
+
if (objective.score?.max !== void 0 && objective.score.max !== "") {
|
|
22486
|
+
updatePayload.maxScore = objective.score.max;
|
|
22487
|
+
updatePayload.maxScoreKnown = true;
|
|
22488
|
+
}
|
|
21779
22489
|
const progressMeasure = this.parseObjectiveNumber(objective.progress_measure);
|
|
21780
22490
|
if (progressMeasure !== null) {
|
|
21781
22491
|
updatePayload.progressMeasure = progressMeasure;
|
|
@@ -21795,12 +22505,21 @@ class GlobalObjectiveManager {
|
|
|
21795
22505
|
*
|
|
21796
22506
|
* @param {CMIObjectivesObject} objective - The CMI objectives object containing data about a specific learning objective.
|
|
21797
22507
|
* @return {GlobalObjectiveMapEntry} An object containing mapped properties and their values based on the provided objective.
|
|
22508
|
+
*
|
|
22509
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective map entries capture objective state
|
|
22510
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score entries carry per-field known flags
|
|
21798
22511
|
*/
|
|
21799
22512
|
buildObjectiveMapEntryFromCMI(objective) {
|
|
21800
22513
|
const entry = {
|
|
21801
22514
|
id: objective.id,
|
|
21802
22515
|
satisfiedStatusKnown: false,
|
|
21803
22516
|
normalizedMeasureKnown: false,
|
|
22517
|
+
rawScore: "",
|
|
22518
|
+
rawScoreKnown: false,
|
|
22519
|
+
minScore: "",
|
|
22520
|
+
minScoreKnown: false,
|
|
22521
|
+
maxScore: "",
|
|
22522
|
+
maxScoreKnown: false,
|
|
21804
22523
|
progressMeasureKnown: false,
|
|
21805
22524
|
completionStatusKnown: false,
|
|
21806
22525
|
readSatisfiedStatus: true,
|
|
@@ -21810,7 +22529,13 @@ class GlobalObjectiveManager {
|
|
|
21810
22529
|
readCompletionStatus: true,
|
|
21811
22530
|
writeCompletionStatus: true,
|
|
21812
22531
|
readProgressMeasure: true,
|
|
21813
|
-
writeProgressMeasure: true
|
|
22532
|
+
writeProgressMeasure: true,
|
|
22533
|
+
readRawScore: true,
|
|
22534
|
+
writeRawScore: true,
|
|
22535
|
+
readMinScore: true,
|
|
22536
|
+
writeMinScore: true,
|
|
22537
|
+
readMaxScore: true,
|
|
22538
|
+
writeMaxScore: true
|
|
21814
22539
|
};
|
|
21815
22540
|
if (objective.success_status && objective.success_status !== SuccessStatus.UNKNOWN) {
|
|
21816
22541
|
entry.satisfiedStatus = objective.success_status === SuccessStatus.PASSED;
|
|
@@ -21821,6 +22546,18 @@ class GlobalObjectiveManager {
|
|
|
21821
22546
|
entry.normalizedMeasure = normalizedMeasure;
|
|
21822
22547
|
entry.normalizedMeasureKnown = true;
|
|
21823
22548
|
}
|
|
22549
|
+
if (objective.score?.raw !== void 0 && objective.score.raw !== "") {
|
|
22550
|
+
entry.rawScore = objective.score.raw;
|
|
22551
|
+
entry.rawScoreKnown = true;
|
|
22552
|
+
}
|
|
22553
|
+
if (objective.score?.min !== void 0 && objective.score.min !== "") {
|
|
22554
|
+
entry.minScore = objective.score.min;
|
|
22555
|
+
entry.minScoreKnown = true;
|
|
22556
|
+
}
|
|
22557
|
+
if (objective.score?.max !== void 0 && objective.score.max !== "") {
|
|
22558
|
+
entry.maxScore = objective.score.max;
|
|
22559
|
+
entry.maxScoreKnown = true;
|
|
22560
|
+
}
|
|
21824
22561
|
const progressMeasure = this.parseObjectiveNumber(objective.progress_measure);
|
|
21825
22562
|
if (progressMeasure !== null) {
|
|
21826
22563
|
entry.progressMeasure = progressMeasure;
|
|
@@ -21841,6 +22578,9 @@ class GlobalObjectiveManager {
|
|
|
21841
22578
|
* @return {CMIObjectivesObject[]} An array of `CMIObjectivesObject` instances built
|
|
21842
22579
|
* from the provided snapshot map. Returns an empty array
|
|
21843
22580
|
* if the snapshot is invalid or no valid objectives can be created.
|
|
22581
|
+
*
|
|
22582
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective snapshots restore CMI objective state
|
|
22583
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score known fields restore independently
|
|
21844
22584
|
*/
|
|
21845
22585
|
buildCMIObjectivesFromMap(snapshot) {
|
|
21846
22586
|
const objectives = [];
|
|
@@ -21860,6 +22600,15 @@ class GlobalObjectiveManager {
|
|
|
21860
22600
|
if (entry.normalizedMeasureKnown === true && normalizedMeasure !== null) {
|
|
21861
22601
|
objective.score.scaled = String(normalizedMeasure);
|
|
21862
22602
|
}
|
|
22603
|
+
if (entry.rawScoreKnown === true) {
|
|
22604
|
+
objective.score.raw = String(entry.rawScore ?? "");
|
|
22605
|
+
}
|
|
22606
|
+
if (entry.minScoreKnown === true) {
|
|
22607
|
+
objective.score.min = String(entry.minScore ?? "");
|
|
22608
|
+
}
|
|
22609
|
+
if (entry.maxScoreKnown === true) {
|
|
22610
|
+
objective.score.max = String(entry.maxScore ?? "");
|
|
22611
|
+
}
|
|
21863
22612
|
const progressMeasure = this.parseObjectiveNumber(entry.progressMeasure);
|
|
21864
22613
|
if (entry.progressMeasureKnown === true && progressMeasure !== null) {
|
|
21865
22614
|
objective.progress_measure = String(progressMeasure);
|
|
@@ -21970,6 +22719,9 @@ class GlobalObjectiveManager {
|
|
|
21970
22719
|
* @param {CompletionStatus} completionStatus
|
|
21971
22720
|
* @param {SuccessStatus} successStatus
|
|
21972
22721
|
* @param {ScoreObject} scoreObject
|
|
22722
|
+
*
|
|
22723
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - CMI score data updates the current primary objective
|
|
22724
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score data is available for write maps
|
|
21973
22725
|
*/
|
|
21974
22726
|
syncCmiToSequencingActivity(completionStatus, successStatus, scoreObject) {
|
|
21975
22727
|
if (!this.context.sequencing) {
|
|
@@ -21995,6 +22747,15 @@ class GlobalObjectiveManager {
|
|
|
21995
22747
|
primaryObjective.normalizedMeasure = scoreObject.scaled;
|
|
21996
22748
|
primaryObjective.measureStatus = true;
|
|
21997
22749
|
}
|
|
22750
|
+
if (scoreObject?.raw !== void 0 && scoreObject.raw !== null) {
|
|
22751
|
+
primaryObjective.rawScore = String(scoreObject.raw);
|
|
22752
|
+
}
|
|
22753
|
+
if (scoreObject?.min !== void 0 && scoreObject.min !== null) {
|
|
22754
|
+
primaryObjective.minScore = String(scoreObject.min);
|
|
22755
|
+
}
|
|
22756
|
+
if (scoreObject?.max !== void 0 && scoreObject.max !== null) {
|
|
22757
|
+
primaryObjective.maxScore = String(scoreObject.max);
|
|
22758
|
+
}
|
|
21998
22759
|
}
|
|
21999
22760
|
/**
|
|
22000
22761
|
* Find or create a global objective by ID
|
|
@@ -22541,8 +23302,239 @@ class Scorm2004API extends BaseAPI {
|
|
|
22541
23302
|
reset(settings) {
|
|
22542
23303
|
this.commonReset(settings);
|
|
22543
23304
|
this.cmi?.reset();
|
|
23305
|
+
this.applyCurrentActivityLaunchData();
|
|
22544
23306
|
this.adl?.reset();
|
|
22545
23307
|
}
|
|
23308
|
+
/**
|
|
23309
|
+
* Apply launch-static activity data to CMI while the new SCO is pre-initialize.
|
|
23310
|
+
*
|
|
23311
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
23312
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
23313
|
+
*/
|
|
23314
|
+
applyCurrentActivityLaunchData() {
|
|
23315
|
+
const currentActivity = this._sequencing?.getCurrentActivity();
|
|
23316
|
+
if (!currentActivity) {
|
|
23317
|
+
return;
|
|
23318
|
+
}
|
|
23319
|
+
this.applyActivityLaunchData(currentActivity);
|
|
23320
|
+
}
|
|
23321
|
+
/**
|
|
23322
|
+
* Apply launch-static CMI data when sequencing delivers a new activity before SCO Initialize.
|
|
23323
|
+
*
|
|
23324
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process establishes the delivered activity.
|
|
23325
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold is initialized before SCO access.
|
|
23326
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives is initialized before SCO access.
|
|
23327
|
+
*/
|
|
23328
|
+
applyDeliveredActivityLaunchData(activity) {
|
|
23329
|
+
if (!this.isNotInitialized()) {
|
|
23330
|
+
return;
|
|
23331
|
+
}
|
|
23332
|
+
this.applyActivityLaunchData(activity);
|
|
23333
|
+
}
|
|
23334
|
+
/**
|
|
23335
|
+
* Copy the delivered activity's static launch data into the fresh CMI model.
|
|
23336
|
+
*
|
|
23337
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
23338
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
23339
|
+
*/
|
|
23340
|
+
applyActivityLaunchData(currentActivity) {
|
|
23341
|
+
if (!this.cmi) {
|
|
23342
|
+
return;
|
|
23343
|
+
}
|
|
23344
|
+
const contentActivityData = this._sequencing.overallSequencingProcess?.getContentActivityData(currentActivity);
|
|
23345
|
+
const completionThreshold = contentActivityData?.completionThreshold ?? currentActivity.completionThreshold?.toString() ?? "";
|
|
23346
|
+
this.cmi.completion_threshold = completionThreshold;
|
|
23347
|
+
this.seedCurrentActivityObjectives(currentActivity);
|
|
23348
|
+
}
|
|
23349
|
+
/**
|
|
23350
|
+
* Seed CMI objective ids after Initialize when automatic sequencing starts during Initialize.
|
|
23351
|
+
*
|
|
23352
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
23353
|
+
*/
|
|
23354
|
+
applyCurrentActivityObjectiveData() {
|
|
23355
|
+
const currentActivity = this._sequencing?.getCurrentActivity();
|
|
23356
|
+
if (!this.cmi || !currentActivity) {
|
|
23357
|
+
return;
|
|
23358
|
+
}
|
|
23359
|
+
this.seedCurrentActivityObjectives(currentActivity);
|
|
23360
|
+
}
|
|
23361
|
+
/**
|
|
23362
|
+
* Seed CMI objectives from primary and secondary activity objectives.
|
|
23363
|
+
*
|
|
23364
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - Initialization of Run-Time Objectives from Sequencing Information
|
|
23365
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id and success_status
|
|
23366
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - objective read maps seed the RTE view
|
|
23367
|
+
*/
|
|
23368
|
+
seedCurrentActivityObjectives(currentActivity) {
|
|
23369
|
+
const activityObjectives = [];
|
|
23370
|
+
if (currentActivity.primaryObjective) {
|
|
23371
|
+
activityObjectives.push(currentActivity.primaryObjective);
|
|
23372
|
+
}
|
|
23373
|
+
activityObjectives.push(...currentActivity.objectives);
|
|
23374
|
+
const seededObjectiveIds = /* @__PURE__ */ new Set();
|
|
23375
|
+
for (const activityObjective of activityObjectives) {
|
|
23376
|
+
const objectiveId = this.getSeedableObjectiveId(activityObjective);
|
|
23377
|
+
if (!objectiveId || seededObjectiveIds.has(objectiveId)) {
|
|
23378
|
+
continue;
|
|
23379
|
+
}
|
|
23380
|
+
seededObjectiveIds.add(objectiveId);
|
|
23381
|
+
const index = this.findOrSeedCMIObjective(objectiveId);
|
|
23382
|
+
if (index === null) {
|
|
23383
|
+
continue;
|
|
23384
|
+
}
|
|
23385
|
+
const cmiObjective = this.cmi.objectives.findObjectiveByIndex(index);
|
|
23386
|
+
if (cmiObjective && this.cmi.objectives.initialized && !cmiObjective.initialized) {
|
|
23387
|
+
cmiObjective.initialize();
|
|
23388
|
+
}
|
|
23389
|
+
const successStatus = this.getActivityObjectiveSuccessStatus(activityObjective);
|
|
23390
|
+
if (successStatus) {
|
|
23391
|
+
this._commonSetCMIValue(
|
|
23392
|
+
"SeedActivityObjective",
|
|
23393
|
+
true,
|
|
23394
|
+
`cmi.objectives.${index}.success_status`,
|
|
23395
|
+
successStatus
|
|
23396
|
+
);
|
|
23397
|
+
}
|
|
23398
|
+
this.seedObjectiveReadMapValues(currentActivity, activityObjective, index);
|
|
23399
|
+
}
|
|
23400
|
+
}
|
|
23401
|
+
/**
|
|
23402
|
+
* Seed CMI objective fields from this objective's read-mapped global objectives.
|
|
23403
|
+
*
|
|
23404
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - Run-Time Objectives are initialized from sequencing information
|
|
23405
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read mapInfo grants access to mapped global objective state
|
|
23406
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps seed RTE score fields
|
|
23407
|
+
*/
|
|
23408
|
+
seedObjectiveReadMapValues(currentActivity, activityObjective, objectiveIndex) {
|
|
23409
|
+
const globalObjectiveMap = this._sequencing.overallSequencingProcess?.getGlobalObjectiveMap();
|
|
23410
|
+
if (!globalObjectiveMap || activityObjective.mapInfo.length === 0) {
|
|
23411
|
+
return;
|
|
23412
|
+
}
|
|
23413
|
+
for (const mapInfo of activityObjective.mapInfo) {
|
|
23414
|
+
const targetObjectiveId = mapInfo.targetObjectiveID || activityObjective.id;
|
|
23415
|
+
const globalObjective = globalObjectiveMap.get(targetObjectiveId);
|
|
23416
|
+
if (!globalObjective) {
|
|
23417
|
+
continue;
|
|
23418
|
+
}
|
|
23419
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
23420
|
+
currentActivity,
|
|
23421
|
+
activityObjective,
|
|
23422
|
+
mapInfo,
|
|
23423
|
+
globalObjective
|
|
23424
|
+
);
|
|
23425
|
+
this.applyObjectiveReadStateToCMI(objectiveIndex, readState);
|
|
23426
|
+
}
|
|
23427
|
+
}
|
|
23428
|
+
/**
|
|
23429
|
+
* Copy mapped global objective state into the seeded CMI objective entry.
|
|
23430
|
+
*
|
|
23431
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives launch-time initialization
|
|
23432
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps populate the RTE view without creating local writes
|
|
23433
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps populate CMI objective scores
|
|
23434
|
+
*/
|
|
23435
|
+
applyObjectiveReadStateToCMI(objectiveIndex, readState) {
|
|
23436
|
+
if (readState.satisfiedStatus !== void 0) {
|
|
23437
|
+
this._commonSetCMIValue(
|
|
23438
|
+
"SeedActivityObjectiveReadMap",
|
|
23439
|
+
true,
|
|
23440
|
+
`cmi.objectives.${objectiveIndex}.success_status`,
|
|
23441
|
+
readState.satisfiedStatus ? SuccessStatus.PASSED : SuccessStatus.FAILED
|
|
23442
|
+
);
|
|
23443
|
+
}
|
|
23444
|
+
if (readState.normalizedMeasure !== void 0) {
|
|
23445
|
+
this._commonSetCMIValue(
|
|
23446
|
+
"SeedActivityObjectiveReadMap",
|
|
23447
|
+
true,
|
|
23448
|
+
`cmi.objectives.${objectiveIndex}.score.scaled`,
|
|
23449
|
+
String(readState.normalizedMeasure)
|
|
23450
|
+
);
|
|
23451
|
+
}
|
|
23452
|
+
if (readState.completionStatus !== void 0) {
|
|
23453
|
+
this._commonSetCMIValue(
|
|
23454
|
+
"SeedActivityObjectiveReadMap",
|
|
23455
|
+
true,
|
|
23456
|
+
`cmi.objectives.${objectiveIndex}.completion_status`,
|
|
23457
|
+
readState.completionStatus
|
|
23458
|
+
);
|
|
23459
|
+
}
|
|
23460
|
+
if (readState.progressMeasure !== void 0) {
|
|
23461
|
+
this._commonSetCMIValue(
|
|
23462
|
+
"SeedActivityObjectiveReadMap",
|
|
23463
|
+
true,
|
|
23464
|
+
`cmi.objectives.${objectiveIndex}.progress_measure`,
|
|
23465
|
+
String(readState.progressMeasure)
|
|
23466
|
+
);
|
|
23467
|
+
}
|
|
23468
|
+
if (readState.rawScore !== void 0) {
|
|
23469
|
+
this._commonSetCMIValue(
|
|
23470
|
+
"SeedActivityObjectiveReadMap",
|
|
23471
|
+
true,
|
|
23472
|
+
`cmi.objectives.${objectiveIndex}.score.raw`,
|
|
23473
|
+
readState.rawScore
|
|
23474
|
+
);
|
|
23475
|
+
}
|
|
23476
|
+
if (readState.minScore !== void 0) {
|
|
23477
|
+
this._commonSetCMIValue(
|
|
23478
|
+
"SeedActivityObjectiveReadMap",
|
|
23479
|
+
true,
|
|
23480
|
+
`cmi.objectives.${objectiveIndex}.score.min`,
|
|
23481
|
+
readState.minScore
|
|
23482
|
+
);
|
|
23483
|
+
}
|
|
23484
|
+
if (readState.maxScore !== void 0) {
|
|
23485
|
+
this._commonSetCMIValue(
|
|
23486
|
+
"SeedActivityObjectiveReadMap",
|
|
23487
|
+
true,
|
|
23488
|
+
`cmi.objectives.${objectiveIndex}.score.max`,
|
|
23489
|
+
readState.maxScore
|
|
23490
|
+
);
|
|
23491
|
+
}
|
|
23492
|
+
}
|
|
23493
|
+
/**
|
|
23494
|
+
* Return a manifest-defined objective id that can initialize cmi.objectives.n.id.
|
|
23495
|
+
*
|
|
23496
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id
|
|
23497
|
+
*/
|
|
23498
|
+
getSeedableObjectiveId(activityObjective) {
|
|
23499
|
+
const objectiveId = activityObjective.id;
|
|
23500
|
+
if (typeof objectiveId !== "string" || objectiveId.trim() === "") {
|
|
23501
|
+
return null;
|
|
23502
|
+
}
|
|
23503
|
+
return objectiveId;
|
|
23504
|
+
}
|
|
23505
|
+
/**
|
|
23506
|
+
* Find an existing CMI objective id or create the next CMI objective array entry.
|
|
23507
|
+
*
|
|
23508
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives._count
|
|
23509
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id
|
|
23510
|
+
*/
|
|
23511
|
+
findOrSeedCMIObjective(objectiveId) {
|
|
23512
|
+
const existingIndex = this.cmi.objectives.childArray.findIndex((objective) => {
|
|
23513
|
+
return objective.id === objectiveId;
|
|
23514
|
+
});
|
|
23515
|
+
if (existingIndex >= 0) {
|
|
23516
|
+
return existingIndex;
|
|
23517
|
+
}
|
|
23518
|
+
const index = this.cmi.objectives.childArray.length;
|
|
23519
|
+
const result = this._commonSetCMIValue(
|
|
23520
|
+
"SeedActivityObjective",
|
|
23521
|
+
true,
|
|
23522
|
+
`cmi.objectives.${index}.id`,
|
|
23523
|
+
objectiveId
|
|
23524
|
+
);
|
|
23525
|
+
return result === global_constants.SCORM_TRUE ? index : null;
|
|
23526
|
+
}
|
|
23527
|
+
/**
|
|
23528
|
+
* Translate a known activity objective satisfied status to cmi.objectives.n.success_status.
|
|
23529
|
+
*
|
|
23530
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.success_status
|
|
23531
|
+
*/
|
|
23532
|
+
getActivityObjectiveSuccessStatus(activityObjective) {
|
|
23533
|
+
if (activityObjective.progressStatus || activityObjective.satisfiedStatusKnown) {
|
|
23534
|
+
return activityObjective.satisfiedStatus ? SuccessStatus.PASSED : SuccessStatus.FAILED;
|
|
23535
|
+
}
|
|
23536
|
+
return null;
|
|
23537
|
+
}
|
|
22546
23538
|
/**
|
|
22547
23539
|
* Getter for _version
|
|
22548
23540
|
* @return {string}
|
|
@@ -22622,6 +23614,7 @@ class Scorm2004API extends BaseAPI {
|
|
|
22622
23614
|
);
|
|
22623
23615
|
if (result === global_constants.SCORM_TRUE && this._sequencingService) {
|
|
22624
23616
|
this._sequencingService.initialize();
|
|
23617
|
+
this.applyCurrentActivityObjectiveData();
|
|
22625
23618
|
}
|
|
22626
23619
|
if (result === global_constants.SCORM_TRUE) {
|
|
22627
23620
|
this._globalObjectiveManager.restoreGlobalObjectivesToCMI();
|
|
@@ -22876,7 +23869,7 @@ class Scorm2004API extends BaseAPI {
|
|
|
22876
23869
|
objective_id = objective ? objective.id : void 0;
|
|
22877
23870
|
}
|
|
22878
23871
|
const is_global = objective_id && this.settings.globalObjectiveIds?.includes(objective_id);
|
|
22879
|
-
if (is_global) {
|
|
23872
|
+
if (is_global && this.currentActivityAllowsGlobalObjectiveWrites()) {
|
|
22880
23873
|
const { index: global_index } = this._globalObjectiveManager.findOrCreateGlobalObjective(objective_id);
|
|
22881
23874
|
const global_element = CMIElement.replace(
|
|
22882
23875
|
element_base,
|
|
@@ -22891,6 +23884,15 @@ class Scorm2004API extends BaseAPI {
|
|
|
22891
23884
|
}
|
|
22892
23885
|
return this._commonSetCMIValue("SetValue", true, CMIElement, value);
|
|
22893
23886
|
}
|
|
23887
|
+
/**
|
|
23888
|
+
* Return whether the current activity can update shared global objectives.
|
|
23889
|
+
*
|
|
23890
|
+
* @spec SCORM 2004 4th Ed. SN 3.13.1 Tracked - when False, the LMS
|
|
23891
|
+
* "does not initialize, manage or access any tracking status information".
|
|
23892
|
+
*/
|
|
23893
|
+
currentActivityAllowsGlobalObjectiveWrites() {
|
|
23894
|
+
return this._sequencing?.getCurrentActivity()?.sequencingControls.tracked !== false;
|
|
23895
|
+
}
|
|
22894
23896
|
/**
|
|
22895
23897
|
* Gets or builds a new child element to add to the array
|
|
22896
23898
|
* @param {string} CMIElement
|
|
@@ -23053,14 +24055,14 @@ class Scorm2004API extends BaseAPI {
|
|
|
23053
24055
|
if (this.cmi.mode === "normal") {
|
|
23054
24056
|
if (this.cmi.credit === "credit") {
|
|
23055
24057
|
if (this.cmi.completion_threshold && this.cmi.progress_measure) {
|
|
23056
|
-
if (this.cmi.progress_measure >= this.cmi.completion_threshold) {
|
|
24058
|
+
if (parseFloat(this.cmi.progress_measure) >= parseFloat(this.cmi.completion_threshold)) {
|
|
23057
24059
|
this.cmi.completion_status = "completed";
|
|
23058
24060
|
} else {
|
|
23059
24061
|
this.cmi.completion_status = "incomplete";
|
|
23060
24062
|
}
|
|
23061
24063
|
}
|
|
23062
24064
|
if (this.cmi.scaled_passing_score && this.cmi.score.scaled) {
|
|
23063
|
-
if (this.cmi.score.scaled >= this.cmi.scaled_passing_score) {
|
|
24065
|
+
if (parseFloat(this.cmi.score.scaled) >= parseFloat(this.cmi.scaled_passing_score)) {
|
|
23064
24066
|
this.cmi.success_status = "passed";
|
|
23065
24067
|
} else {
|
|
23066
24068
|
this.cmi.success_status = "failed";
|
|
@@ -23235,9 +24237,9 @@ class Scorm2004API extends BaseAPI {
|
|
|
23235
24237
|
this.loggingService,
|
|
23236
24238
|
sequencingConfig
|
|
23237
24239
|
);
|
|
23238
|
-
|
|
23239
|
-
this.
|
|
23240
|
-
|
|
24240
|
+
this._sequencingService.setEventListeners(
|
|
24241
|
+
this.buildSequencingEventListeners(settings?.sequencing?.eventListeners)
|
|
24242
|
+
);
|
|
23241
24243
|
this._globalObjectiveManager.updateSequencingService(this._sequencingService);
|
|
23242
24244
|
this._dataSerializer.updateSequencingService(this._sequencingService);
|
|
23243
24245
|
if (settings?.sequencingStatePersistence) {
|
|
@@ -23260,6 +24262,22 @@ class Scorm2004API extends BaseAPI {
|
|
|
23260
24262
|
this._sequencingService = null;
|
|
23261
24263
|
}
|
|
23262
24264
|
}
|
|
24265
|
+
/**
|
|
24266
|
+
* Wrap LMS-provided sequencing listeners with API-owned delivery bookkeeping.
|
|
24267
|
+
*
|
|
24268
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process
|
|
24269
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
24270
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
24271
|
+
*/
|
|
24272
|
+
buildSequencingEventListeners(listeners) {
|
|
24273
|
+
return {
|
|
24274
|
+
...listeners,
|
|
24275
|
+
onActivityDelivery: (activity) => {
|
|
24276
|
+
this.applyDeliveredActivityLaunchData(activity);
|
|
24277
|
+
listeners?.onActivityDelivery?.(activity);
|
|
24278
|
+
}
|
|
24279
|
+
};
|
|
24280
|
+
}
|
|
23263
24281
|
/**
|
|
23264
24282
|
* Get the sequencing service
|
|
23265
24283
|
* @return {SequencingService | null}
|
|
@@ -23270,10 +24288,13 @@ class Scorm2004API extends BaseAPI {
|
|
|
23270
24288
|
/**
|
|
23271
24289
|
* Set sequencing event listeners
|
|
23272
24290
|
* @param {SequencingEventListeners} listeners
|
|
24291
|
+
*
|
|
24292
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process
|
|
24293
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5 / 4.2.17 - launch-static CMI data
|
|
23273
24294
|
*/
|
|
23274
24295
|
setSequencingEventListeners(listeners) {
|
|
23275
24296
|
if (this._sequencingService) {
|
|
23276
|
-
this._sequencingService.setEventListeners(listeners);
|
|
24297
|
+
this._sequencingService.setEventListeners(this.buildSequencingEventListeners(listeners));
|
|
23277
24298
|
}
|
|
23278
24299
|
}
|
|
23279
24300
|
/**
|