@learnpack/learnpack 5.0.276 → 5.0.277
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/lib/commands/serve.js +52 -0
- package/lib/utils/export/epub.d.ts +2 -0
- package/lib/utils/export/epub.js +298 -0
- package/lib/utils/export/index.d.ts +3 -0
- package/lib/utils/export/index.js +7 -0
- package/lib/utils/export/scorm.d.ts +2 -0
- package/lib/utils/export/scorm.js +84 -0
- package/lib/utils/export/shared.d.ts +4 -0
- package/lib/utils/export/shared.js +61 -0
- package/lib/utils/export/types.d.ts +15 -0
- package/lib/utils/export/types.js +2 -0
- package/package.json +2 -1
- package/src/commands/serve.ts +61 -1
- package/src/creator/eslint.config.js +28 -28
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +355 -355
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/export/README.md +178 -0
- package/src/utils/export/epub.ts +400 -0
- package/src/utils/export/index.ts +3 -0
- package/src/utils/export/scorm.ts +121 -0
- package/src/utils/export/shared.ts +61 -0
- package/src/utils/export/types.ts +17 -0
- package/src/utils/templates/epub/epub.css +133 -0
- package/src/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -0
- package/src/utils/templates/scorm/config/api.js +175 -0
- package/src/utils/templates/scorm/config/index.html +210 -0
- package/src/utils/templates/scorm/ims_xml.xsd +1 -0
- package/src/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -0
- package/src/utils/templates/scorm/imsmanifest.xml +38 -0
- package/src/utils/templates/scorm/imsmd_rootv1p2p1.xsd +573 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
import * as path from "path";
|
2
|
+
import * as fs from "fs";
|
3
|
+
import * as mkdirp from "mkdirp";
|
4
|
+
|
5
|
+
// Utility function to copy directory recursively
|
6
|
+
export function copyDir(src: string, dest: string) {
|
7
|
+
if (!fs.existsSync(dest)) mkdirp.sync(dest);
|
8
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
9
|
+
const srcPath = path.join(src, entry.name);
|
10
|
+
const destPath = path.join(dest, entry.name);
|
11
|
+
if (entry.isDirectory()) {
|
12
|
+
copyDir(srcPath, destPath);
|
13
|
+
} else {
|
14
|
+
fs.copyFileSync(srcPath, destPath);
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
// Download all files from a GCS Bucket prefix to a local folder
|
20
|
+
export async function downloadS3Folder(
|
21
|
+
bucket: any,
|
22
|
+
prefix: string,
|
23
|
+
localPath: string
|
24
|
+
) {
|
25
|
+
const [files] = await bucket.getFiles({ prefix });
|
26
|
+
for (const file of files) {
|
27
|
+
const relPath = file.name.replace(prefix, "");
|
28
|
+
if (!relPath) continue;
|
29
|
+
const outPath = path.join(localPath, relPath);
|
30
|
+
mkdirp.sync(path.dirname(outPath));
|
31
|
+
// esling-disable-next-line
|
32
|
+
const [buf] = await file.download();
|
33
|
+
fs.writeFileSync(outPath, buf);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
// Generate file list for manifest, given a folder
|
38
|
+
export function getFilesList(folder: string, base: string) {
|
39
|
+
let list: string[] = [];
|
40
|
+
const files = fs.readdirSync(folder, { withFileTypes: true });
|
41
|
+
for (const file of files) {
|
42
|
+
const filePath = path.join(folder, file.name);
|
43
|
+
if (file.isDirectory()) {
|
44
|
+
// esling-disable-next-line
|
45
|
+
list = list.concat(getFilesList(filePath, path.join(base, file.name)));
|
46
|
+
} else {
|
47
|
+
list.push(
|
48
|
+
`<file href="${path.join(base, file.name).replace(/\\/g, "/")}" />`
|
49
|
+
);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
return list;
|
53
|
+
}
|
54
|
+
|
55
|
+
// Read course metadata from learn.json
|
56
|
+
export async function getCourseMetadata(bucket: any, courseSlug: string) {
|
57
|
+
const [learnBuf] = await bucket
|
58
|
+
.file(`courses/${courseSlug}/learn.json`)
|
59
|
+
.download();
|
60
|
+
return JSON.parse(learnBuf.toString());
|
61
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
export type ExportFormat = "scorm" | "epub";
|
2
|
+
|
3
|
+
export interface ExportOptions {
|
4
|
+
courseSlug: string;
|
5
|
+
format: ExportFormat;
|
6
|
+
bucket: any; // Google Cloud Storage Bucket
|
7
|
+
outDir: string;
|
8
|
+
language?: string; // Optional language parameter for EPUB export
|
9
|
+
}
|
10
|
+
|
11
|
+
export interface CourseMetadata {
|
12
|
+
title: string;
|
13
|
+
description?: string;
|
14
|
+
language?: string;
|
15
|
+
technologies?: string[];
|
16
|
+
difficulty?: string;
|
17
|
+
}
|
@@ -0,0 +1,133 @@
|
|
1
|
+
/* Basic EPUB styling */
|
2
|
+
body {
|
3
|
+
font-family: "Georgia", serif;
|
4
|
+
line-height: 1.6;
|
5
|
+
margin: 2em;
|
6
|
+
color: #333;
|
7
|
+
}
|
8
|
+
|
9
|
+
h1,
|
10
|
+
h2,
|
11
|
+
h3,
|
12
|
+
h4,
|
13
|
+
h5,
|
14
|
+
h6 {
|
15
|
+
color: #2c3e50;
|
16
|
+
margin-top: 1.5em;
|
17
|
+
margin-bottom: 0.5em;
|
18
|
+
}
|
19
|
+
|
20
|
+
h1 {
|
21
|
+
font-size: 2em;
|
22
|
+
border-bottom: 2px solid #3498db;
|
23
|
+
padding-bottom: 0.3em;
|
24
|
+
}
|
25
|
+
|
26
|
+
h2 {
|
27
|
+
font-size: 1.5em;
|
28
|
+
border-bottom: 1px solid #bdc3c7;
|
29
|
+
padding-bottom: 0.2em;
|
30
|
+
}
|
31
|
+
|
32
|
+
p {
|
33
|
+
margin-bottom: 1em;
|
34
|
+
text-align: justify;
|
35
|
+
}
|
36
|
+
|
37
|
+
code {
|
38
|
+
background-color: #f8f9fa;
|
39
|
+
padding: 0.2em 0.4em;
|
40
|
+
border-radius: 3px;
|
41
|
+
font-family: "Courier New", monospace;
|
42
|
+
font-size: 0.9em;
|
43
|
+
}
|
44
|
+
|
45
|
+
pre {
|
46
|
+
background-color: #f8f9fa;
|
47
|
+
padding: 1em;
|
48
|
+
border-radius: 5px;
|
49
|
+
overflow-x: auto;
|
50
|
+
border-left: 4px solid #3498db;
|
51
|
+
}
|
52
|
+
|
53
|
+
pre code {
|
54
|
+
background-color: transparent;
|
55
|
+
padding: 0;
|
56
|
+
}
|
57
|
+
|
58
|
+
blockquote {
|
59
|
+
border-left: 4px solid #bdc3c7;
|
60
|
+
margin: 1em 0;
|
61
|
+
padding-left: 1em;
|
62
|
+
font-style: italic;
|
63
|
+
color: #7f8c8d;
|
64
|
+
}
|
65
|
+
|
66
|
+
ul,
|
67
|
+
ol {
|
68
|
+
margin-bottom: 1em;
|
69
|
+
padding-left: 2em;
|
70
|
+
}
|
71
|
+
|
72
|
+
li {
|
73
|
+
margin-bottom: 0.5em;
|
74
|
+
}
|
75
|
+
|
76
|
+
a {
|
77
|
+
color: #3498db;
|
78
|
+
text-decoration: none;
|
79
|
+
}
|
80
|
+
|
81
|
+
a:hover {
|
82
|
+
text-decoration: underline;
|
83
|
+
}
|
84
|
+
|
85
|
+
img {
|
86
|
+
max-width: 100%;
|
87
|
+
height: auto;
|
88
|
+
display: block;
|
89
|
+
margin: 1em auto;
|
90
|
+
}
|
91
|
+
|
92
|
+
table {
|
93
|
+
border-collapse: collapse;
|
94
|
+
width: 100%;
|
95
|
+
margin: 1em 0;
|
96
|
+
}
|
97
|
+
|
98
|
+
th,
|
99
|
+
td {
|
100
|
+
border: 1px solid #ddd;
|
101
|
+
padding: 8px;
|
102
|
+
text-align: left;
|
103
|
+
}
|
104
|
+
|
105
|
+
th {
|
106
|
+
background-color: #f2f2f2;
|
107
|
+
font-weight: bold;
|
108
|
+
}
|
109
|
+
|
110
|
+
#toc {
|
111
|
+
background-color: #f8f9fa;
|
112
|
+
padding: 1em;
|
113
|
+
border-radius: 5px;
|
114
|
+
margin-bottom: 2em;
|
115
|
+
}
|
116
|
+
|
117
|
+
#toc h2 {
|
118
|
+
border-bottom: none;
|
119
|
+
margin-top: 0;
|
120
|
+
}
|
121
|
+
|
122
|
+
#toc ol {
|
123
|
+
padding-left: 1em;
|
124
|
+
}
|
125
|
+
|
126
|
+
#toc li {
|
127
|
+
margin-bottom: 0.3em;
|
128
|
+
}
|
129
|
+
|
130
|
+
#toc a {
|
131
|
+
color: #2c3e50;
|
132
|
+
font-weight: 500;
|
133
|
+
}
|
@@ -0,0 +1,110 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<!-- filename=adlcp_rootv1p2.xsd -->
|
3
|
+
<!-- Conforms to w3c http://www.w3.org/TR/xmlschema-1/ 2000-10-24-->
|
4
|
+
|
5
|
+
<xsd:schema xmlns="http://www.adlnet.org/xsd/adlcp_rootv1p2"
|
6
|
+
targetNamespace="http://www.adlnet.org/xsd/adlcp_rootv1p2"
|
7
|
+
xmlns:xml="http://www.w3.org/XML/1998/namespace"
|
8
|
+
xmlns:imscp="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
|
9
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
10
|
+
elementFormDefault="unqualified"
|
11
|
+
version="ADL Version 1.2">
|
12
|
+
|
13
|
+
<xsd:import namespace="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
|
14
|
+
schemaLocation="imscp_rootv1p1p2.xsd"/>
|
15
|
+
|
16
|
+
<xsd:element name="location" type="locationType"/>
|
17
|
+
<xsd:element name="prerequisites" type="prerequisitesType"/>
|
18
|
+
<xsd:element name="maxtimeallowed" type="maxtimeallowedType"/>
|
19
|
+
<xsd:element name="timelimitaction" type="timelimitactionType"/>
|
20
|
+
<xsd:element name="datafromlms" type="datafromlmsType"/>
|
21
|
+
<xsd:element name="masteryscore" type="masteryscoreType"/>
|
22
|
+
|
23
|
+
|
24
|
+
<xsd:element name="schema" type="newSchemaType"/>
|
25
|
+
<xsd:simpleType name="newSchemaType">
|
26
|
+
<xsd:restriction base="imscp:schemaType">
|
27
|
+
<xsd:enumeration value="ADL SCORM"/>
|
28
|
+
</xsd:restriction>
|
29
|
+
</xsd:simpleType>
|
30
|
+
|
31
|
+
<xsd:element name="schemaversion" type="newSchemaversionType"/>
|
32
|
+
<xsd:simpleType name="newSchemaversionType">
|
33
|
+
<xsd:restriction base="imscp:schemaversionType">
|
34
|
+
<xsd:enumeration value="1.2"/>
|
35
|
+
</xsd:restriction>
|
36
|
+
</xsd:simpleType>
|
37
|
+
|
38
|
+
|
39
|
+
<xsd:attribute name="scormtype">
|
40
|
+
<xsd:simpleType>
|
41
|
+
<xsd:restriction base="xsd:string">
|
42
|
+
<xsd:enumeration value="asset"/>
|
43
|
+
<xsd:enumeration value="sco"/>
|
44
|
+
</xsd:restriction>
|
45
|
+
</xsd:simpleType>
|
46
|
+
</xsd:attribute>
|
47
|
+
|
48
|
+
<xsd:simpleType name="locationType">
|
49
|
+
<xsd:restriction base="xsd:string">
|
50
|
+
<xsd:maxLength value="2000"/>
|
51
|
+
</xsd:restriction>
|
52
|
+
</xsd:simpleType>
|
53
|
+
|
54
|
+
|
55
|
+
<xsd:complexType name="prerequisitesType">
|
56
|
+
<xsd:simpleContent>
|
57
|
+
<xsd:extension base="prerequisiteStringType">
|
58
|
+
<xsd:attributeGroup ref="attr.prerequisitetype"/>
|
59
|
+
</xsd:extension>
|
60
|
+
</xsd:simpleContent>
|
61
|
+
</xsd:complexType>
|
62
|
+
|
63
|
+
<xsd:attributeGroup name="attr.prerequisitetype">
|
64
|
+
<xsd:attribute name="type" use="required">
|
65
|
+
<xsd:simpleType>
|
66
|
+
<xsd:restriction base="xsd:string">
|
67
|
+
<xsd:enumeration value="aicc_script"/>
|
68
|
+
</xsd:restriction>
|
69
|
+
</xsd:simpleType>
|
70
|
+
</xsd:attribute>
|
71
|
+
</xsd:attributeGroup>
|
72
|
+
|
73
|
+
<xsd:simpleType name="maxtimeallowedType">
|
74
|
+
<xsd:restriction base="xsd:string">
|
75
|
+
<xsd:maxLength value="13"/>
|
76
|
+
</xsd:restriction>
|
77
|
+
</xsd:simpleType>
|
78
|
+
|
79
|
+
<xsd:simpleType name="timelimitactionType">
|
80
|
+
<xsd:restriction base="stringType">
|
81
|
+
<xsd:enumeration value="exit,no message"/>
|
82
|
+
<xsd:enumeration value="exit,message"/>
|
83
|
+
<xsd:enumeration value="continue,no message"/>
|
84
|
+
<xsd:enumeration value="continue,message"/>
|
85
|
+
</xsd:restriction>
|
86
|
+
</xsd:simpleType>
|
87
|
+
|
88
|
+
<xsd:simpleType name="datafromlmsType">
|
89
|
+
<xsd:restriction base="xsd:string">
|
90
|
+
<xsd:maxLength value="255"/>
|
91
|
+
</xsd:restriction>
|
92
|
+
</xsd:simpleType>
|
93
|
+
|
94
|
+
<xsd:simpleType name="masteryscoreType">
|
95
|
+
<xsd:restriction base="xsd:string">
|
96
|
+
<xsd:maxLength value="200"/>
|
97
|
+
</xsd:restriction>
|
98
|
+
</xsd:simpleType>
|
99
|
+
|
100
|
+
<xsd:simpleType name="stringType">
|
101
|
+
<xsd:restriction base="xsd:string"/>
|
102
|
+
</xsd:simpleType>
|
103
|
+
|
104
|
+
<xsd:simpleType name="prerequisiteStringType">
|
105
|
+
<xsd:restriction base="xsd:string">
|
106
|
+
<xsd:maxLength value="200"/>
|
107
|
+
</xsd:restriction>
|
108
|
+
</xsd:simpleType>
|
109
|
+
|
110
|
+
</xsd:schema>
|
@@ -0,0 +1,175 @@
|
|
1
|
+
var findAPITries = 0;
|
2
|
+
|
3
|
+
function findAPI(win) {
|
4
|
+
while (win.API == null && win.parent != null && win.parent != win) {
|
5
|
+
findAPITries++;
|
6
|
+
|
7
|
+
if (findAPITries > 7) {
|
8
|
+
alert("Error finding API -- too deeply nested.");
|
9
|
+
return null;
|
10
|
+
}
|
11
|
+
|
12
|
+
win = win.parent;
|
13
|
+
}
|
14
|
+
return win.API;
|
15
|
+
}
|
16
|
+
|
17
|
+
function getAPI() {
|
18
|
+
var theAPI = findAPI(window);
|
19
|
+
|
20
|
+
if (
|
21
|
+
theAPI == null &&
|
22
|
+
window.opener != null &&
|
23
|
+
typeof window.opener != "undefined"
|
24
|
+
) {
|
25
|
+
theAPI = findAPI(window.opener);
|
26
|
+
}
|
27
|
+
if (theAPI == null) {
|
28
|
+
alert("Unable to find an API adapter");
|
29
|
+
}
|
30
|
+
return theAPI;
|
31
|
+
}
|
32
|
+
|
33
|
+
var SCORM_TRUE = "true";
|
34
|
+
var SCORM_FALSE = "false";
|
35
|
+
var SCORM_NO_ERROR = "0";
|
36
|
+
|
37
|
+
var finishCalled = false;
|
38
|
+
|
39
|
+
var initialized = false;
|
40
|
+
|
41
|
+
var API = null;
|
42
|
+
|
43
|
+
function ScormProcessInitialize() {
|
44
|
+
var result;
|
45
|
+
|
46
|
+
API = getAPI();
|
47
|
+
|
48
|
+
if (API == null) {
|
49
|
+
alert(
|
50
|
+
"ERROR - Could not establish a connection with the LMS.\n\nYour results may not be recorded."
|
51
|
+
);
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
|
55
|
+
result = API.LMSInitialize("");
|
56
|
+
|
57
|
+
if (result == SCORM_FALSE) {
|
58
|
+
var errorNumber = API.LMSGetLastError();
|
59
|
+
var errorString = API.LMSGetErrorString(errorNumber);
|
60
|
+
var diagnostic = API.LMSGetDiagnostic(errorNumber);
|
61
|
+
|
62
|
+
var errorDescription =
|
63
|
+
"Number: " +
|
64
|
+
errorNumber +
|
65
|
+
"\nDescription: " +
|
66
|
+
errorString +
|
67
|
+
"\nDiagnostic: " +
|
68
|
+
diagnostic;
|
69
|
+
|
70
|
+
alert(
|
71
|
+
"Error - Could not initialize communication with the LMS.\n\nYour results may not be recorded.\n\n" +
|
72
|
+
errorDescription
|
73
|
+
);
|
74
|
+
return;
|
75
|
+
}
|
76
|
+
|
77
|
+
initialized = true;
|
78
|
+
}
|
79
|
+
|
80
|
+
function ScormProcessFinish() {
|
81
|
+
var result;
|
82
|
+
|
83
|
+
if (initialized == false || finishCalled == true) {
|
84
|
+
return;
|
85
|
+
}
|
86
|
+
|
87
|
+
result = API.LMSFinish("");
|
88
|
+
|
89
|
+
finishCalled = true;
|
90
|
+
|
91
|
+
if (result == SCORM_FALSE) {
|
92
|
+
var errorNumber = API.LMSGetLastError();
|
93
|
+
var errorString = API.LMSGetErrorString(errorNumber);
|
94
|
+
var diagnostic = API.LMSGetDiagnostic(errorNumber);
|
95
|
+
|
96
|
+
var errorDescription =
|
97
|
+
"Number: " +
|
98
|
+
errorNumber +
|
99
|
+
"\nDescription: " +
|
100
|
+
errorString +
|
101
|
+
"\nDiagnostic: " +
|
102
|
+
diagnostic;
|
103
|
+
|
104
|
+
alert(
|
105
|
+
"Error - Could not terminate communication with the LMS.\n\nYour results may not be recorded.\n\n" +
|
106
|
+
errorDescription
|
107
|
+
);
|
108
|
+
return;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
function ScormProcessGetValue(element) {
|
113
|
+
var result;
|
114
|
+
|
115
|
+
if (initialized == false || finishCalled == true) {
|
116
|
+
return;
|
117
|
+
}
|
118
|
+
|
119
|
+
result = API.LMSGetValue(element);
|
120
|
+
|
121
|
+
if (result == "") {
|
122
|
+
var errorNumber = API.LMSGetLastError();
|
123
|
+
|
124
|
+
if (errorNumber != SCORM_NO_ERROR) {
|
125
|
+
var errorString = API.LMSGetErrorString(errorNumber);
|
126
|
+
var diagnostic = API.LMSGetDiagnostic(errorNumber);
|
127
|
+
|
128
|
+
var errorDescription =
|
129
|
+
"Number: " +
|
130
|
+
errorNumber +
|
131
|
+
"\nDescription: " +
|
132
|
+
errorString +
|
133
|
+
"\nDiagnostic: " +
|
134
|
+
diagnostic;
|
135
|
+
|
136
|
+
alert(
|
137
|
+
"Error - Could not retrieve a value from the LMS.\n\n" +
|
138
|
+
errorDescription
|
139
|
+
);
|
140
|
+
return "";
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
return result;
|
145
|
+
}
|
146
|
+
|
147
|
+
function ScormProcessSetValue(element, value) {
|
148
|
+
var result;
|
149
|
+
|
150
|
+
if (initialized == false || finishCalled == true) {
|
151
|
+
return;
|
152
|
+
}
|
153
|
+
|
154
|
+
result = API.LMSSetValue(element, value);
|
155
|
+
|
156
|
+
if (result == SCORM_FALSE) {
|
157
|
+
var errorNumber = API.LMSGetLastError();
|
158
|
+
var errorString = API.LMSGetErrorString(errorNumber);
|
159
|
+
var diagnostic = API.LMSGetDiagnostic(errorNumber);
|
160
|
+
|
161
|
+
var errorDescription =
|
162
|
+
"Number: " +
|
163
|
+
errorNumber +
|
164
|
+
"\nDescription: " +
|
165
|
+
errorString +
|
166
|
+
"\nDiagnostic: " +
|
167
|
+
diagnostic;
|
168
|
+
|
169
|
+
alert(
|
170
|
+
"Error - Could not store a value in the LMS.\n\nYour results may not be recorded.\n\n" +
|
171
|
+
errorDescription
|
172
|
+
);
|
173
|
+
return;
|
174
|
+
}
|
175
|
+
}
|
@@ -0,0 +1,210 @@
|
|
1
|
+
<!DOCTYPE html
|
2
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
4
|
+
|
5
|
+
<head>
|
6
|
+
<meta charset="UTF-8">
|
7
|
+
<title>{{title}}</title>
|
8
|
+
<script src="api.js" type="text/javascript"></script>
|
9
|
+
<script type="text/javascript">
|
10
|
+
|
11
|
+
|
12
|
+
var currentPage = null;
|
13
|
+
var startTimeStamp = null;
|
14
|
+
var processedUnload = false;
|
15
|
+
var reachedEnd = false;
|
16
|
+
var language = 'es';
|
17
|
+
|
18
|
+
function doStart() {
|
19
|
+
startTimeStamp = new Date();
|
20
|
+
|
21
|
+
ScormProcessInitialize();
|
22
|
+
|
23
|
+
var completionStatus = ScormProcessGetValue('cmi.core.lesson_status');
|
24
|
+
if (completionStatus == 'not attempted') {
|
25
|
+
ScormProcessSetValue('cmi.core.lesson_status', 'incomplete');
|
26
|
+
}
|
27
|
+
|
28
|
+
var bookmark = ScormProcessGetValue('cmi.core.lesson_location');
|
29
|
+
|
30
|
+
if (bookmark == '') {
|
31
|
+
currentPage = 0;
|
32
|
+
} else {
|
33
|
+
if (
|
34
|
+
confirm(
|
35
|
+
'Would you like to resume from where you previously left off?'
|
36
|
+
)
|
37
|
+
) {
|
38
|
+
currentPage = parseInt(bookmark, 10);
|
39
|
+
} else {
|
40
|
+
currentPage = 0;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
goToPage();
|
45
|
+
}
|
46
|
+
|
47
|
+
function goToPage() {
|
48
|
+
ScormProcessSetValue('cmi.core.lesson_location', currentPage);
|
49
|
+
}
|
50
|
+
|
51
|
+
function doUnload(pressedExit) {
|
52
|
+
if (processedUnload == true) {
|
53
|
+
return;
|
54
|
+
}
|
55
|
+
|
56
|
+
processedUnload = true;
|
57
|
+
|
58
|
+
var endTimeStamp = new Date();
|
59
|
+
var totalMilliseconds =
|
60
|
+
endTimeStamp.getTime() - startTimeStamp.getTime();
|
61
|
+
var scormTime = ConvertMilliSecondsToSCORMTime(
|
62
|
+
totalMilliseconds,
|
63
|
+
false
|
64
|
+
);
|
65
|
+
|
66
|
+
ScormProcessSetValue('cmi.core.session_time', scormTime);
|
67
|
+
|
68
|
+
if (pressedExit == false && reachedEnd == false) {
|
69
|
+
ScormProcessSetValue('cmi.core.exit', 'suspend');
|
70
|
+
}
|
71
|
+
|
72
|
+
ScormProcessFinish();
|
73
|
+
}
|
74
|
+
|
75
|
+
function doPrevious() {
|
76
|
+
if (currentPage > 0) {
|
77
|
+
currentPage--;
|
78
|
+
}
|
79
|
+
goToPage();
|
80
|
+
}
|
81
|
+
|
82
|
+
function doNext() {
|
83
|
+
if (currentPage < pageArray.length - 1) {
|
84
|
+
currentPage++;
|
85
|
+
}
|
86
|
+
goToPage();
|
87
|
+
}
|
88
|
+
|
89
|
+
function doExit() {
|
90
|
+
if (
|
91
|
+
reachedEnd == false &&
|
92
|
+
confirm('Would you like to save your progress to resume later?')
|
93
|
+
) {
|
94
|
+
ScormProcessSetValue('cmi.core.exit', 'suspend');
|
95
|
+
} else {
|
96
|
+
ScormProcessSetValue('cmi.core.exit', '');
|
97
|
+
}
|
98
|
+
|
99
|
+
doUnload(true);
|
100
|
+
}
|
101
|
+
|
102
|
+
function ConvertMilliSecondsToSCORMTime(
|
103
|
+
intTotalMilliseconds,
|
104
|
+
blnIncludeFraction
|
105
|
+
) {
|
106
|
+
var intHours;
|
107
|
+
var intintMinutes;
|
108
|
+
var intSeconds;
|
109
|
+
var intMilliseconds;
|
110
|
+
var intHundredths;
|
111
|
+
var strCMITimeSpan;
|
112
|
+
|
113
|
+
if (blnIncludeFraction == null || blnIncludeFraction == undefined) {
|
114
|
+
blnIncludeFraction = true;
|
115
|
+
}
|
116
|
+
|
117
|
+
intMilliseconds = intTotalMilliseconds % 1000;
|
118
|
+
|
119
|
+
intSeconds = ((intTotalMilliseconds - intMilliseconds) / 1000) % 60;
|
120
|
+
|
121
|
+
intMinutes =
|
122
|
+
((intTotalMilliseconds - intMilliseconds - intSeconds * 1000) /
|
123
|
+
60000) %
|
124
|
+
60;
|
125
|
+
|
126
|
+
intHours =
|
127
|
+
(intTotalMilliseconds -
|
128
|
+
intMilliseconds -
|
129
|
+
intSeconds * 1000 -
|
130
|
+
intMinutes * 60000) /
|
131
|
+
3600000;
|
132
|
+
|
133
|
+
if (intHours == 10000) {
|
134
|
+
intHours = 9999;
|
135
|
+
|
136
|
+
intMinutes = (intTotalMilliseconds - intHours * 3600000) / 60000;
|
137
|
+
if (intMinutes == 100) {
|
138
|
+
intMinutes = 99;
|
139
|
+
}
|
140
|
+
intMinutes = Math.floor(intMinutes);
|
141
|
+
|
142
|
+
intSeconds =
|
143
|
+
(intTotalMilliseconds - intHours * 3600000 - intMinutes * 60000) /
|
144
|
+
1000;
|
145
|
+
if (intSeconds == 100) {
|
146
|
+
intSeconds = 99;
|
147
|
+
}
|
148
|
+
intSeconds = Math.floor(intSeconds);
|
149
|
+
|
150
|
+
intMilliseconds =
|
151
|
+
intTotalMilliseconds -
|
152
|
+
intHours * 3600000 -
|
153
|
+
intMinutes * 60000 -
|
154
|
+
intSeconds * 1000;
|
155
|
+
}
|
156
|
+
|
157
|
+
intHundredths = Math.floor(intMilliseconds / 10);
|
158
|
+
|
159
|
+
strCMITimeSpan =
|
160
|
+
ZeroPad(intHours, 4) +
|
161
|
+
':' +
|
162
|
+
ZeroPad(intMinutes, 2) +
|
163
|
+
':' +
|
164
|
+
ZeroPad(intSeconds, 2);
|
165
|
+
|
166
|
+
if (blnIncludeFraction) {
|
167
|
+
strCMITimeSpan += '.' + intHundredths;
|
168
|
+
}
|
169
|
+
|
170
|
+
if (intHours > 9999) {
|
171
|
+
strCMITimeSpan = '9999:99:99';
|
172
|
+
|
173
|
+
if (blnIncludeFraction) {
|
174
|
+
strCMITimeSpan += '.99';
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
return strCMITimeSpan;
|
179
|
+
}
|
180
|
+
|
181
|
+
function ZeroPad(intNum, intNumDigits) {
|
182
|
+
var strTemp;
|
183
|
+
var intLen;
|
184
|
+
var i;
|
185
|
+
|
186
|
+
strTemp = new String(intNum);
|
187
|
+
intLen = strTemp.length;
|
188
|
+
|
189
|
+
if (intLen > intNumDigits) {
|
190
|
+
strTemp = strTemp.substr(0, intNumDigits);
|
191
|
+
} else {
|
192
|
+
for (i = intLen; i < intNumDigits; i++) {
|
193
|
+
strTemp = '0' + strTemp;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
return strTemp;
|
197
|
+
}
|
198
|
+
|
199
|
+
|
200
|
+
</script>
|
201
|
+
<link rel="stylesheet" href="app.css">
|
202
|
+
</head>
|
203
|
+
|
204
|
+
<body onload="doStart();" onbeforeunload="doUnload(false);" onunload="doUnload();">
|
205
|
+
<div id="root"></div>
|
206
|
+
</body>
|
207
|
+
|
208
|
+
<script type="module" src="app.js"></script>
|
209
|
+
|
210
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><!-- filename=ims_xml.xsd --><xsd:schema xmlns="http://www.w3.org/XML/1998/namespace" targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> <!-- 2001-02-22 edited by Thomas Wason IMS Global Learning Consortium, Inc. --> <xsd:annotation> <xsd:documentation>In namespace-aware XML processors, the "xml" prefix is bound to the namespace name http://www.w3.org/XML/1998/namespace.</xsd:documentation> <xsd:documentation>Do not reference this file in XML instances</xsd:documentation> <xsd:documentation>Schawn Thropp: Changed the uriReference type to string type</xsd:documentation> </xsd:annotation> <xsd:attribute name="lang" type="xsd:language"> <xsd:annotation> <xsd:documentation>Refers to universal XML 1.0 lang attribute</xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="base" type="xsd:string"> <xsd:annotation> <xsd:documentation>Refers to XML Base: http://www.w3.org/TR/xmlbase</xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="link" type="xsd:string"/></xsd:schema>
|