jclic 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +5 -7
  2. package/dist-node/jclic-node.js +14157 -0
  3. package/dist-node/jclic-node.umd.cjs +530 -0
  4. package/package.json +38 -26
  5. package/.vscode/launch.json +0 -33
  6. package/.vscode/settings.json +0 -13
  7. package/CHANGELOG.md +0 -672
  8. package/TRANSLATIONS.md +0 -11
  9. package/build-locales.mjs +0 -82
  10. package/dist/jclic-node.js +0 -31680
  11. package/dist/jclic-node.js.map +0 -1
  12. package/dist/jclic.components.LICENSE +0 -2254
  13. package/dist/jclic.min.js +0 -27
  14. package/dist/jclic.min.js.map +0 -1
  15. package/eslint.config.mjs +0 -31
  16. package/jsdoc.config.js +0 -71
  17. package/locales/ar.po +0 -244
  18. package/locales/ast.po +0 -246
  19. package/locales/bs.po +0 -247
  20. package/locales/ca.po +0 -248
  21. package/locales/ca_ES@valencia.po +0 -248
  22. package/locales/cs.po +0 -244
  23. package/locales/da.po +0 -244
  24. package/locales/de.po +0 -246
  25. package/locales/el.po +0 -244
  26. package/locales/es.po +0 -248
  27. package/locales/eu.po +0 -244
  28. package/locales/fr.po +0 -244
  29. package/locales/gl.po +0 -244
  30. package/locales/he.po +0 -244
  31. package/locales/hr.po +0 -245
  32. package/locales/it.po +0 -246
  33. package/locales/ja.po +0 -242
  34. package/locales/jclic.js.pot +0 -241
  35. package/locales/nb_NO.po +0 -244
  36. package/locales/nl.po +0 -244
  37. package/locales/pl.po +0 -244
  38. package/locales/pt.po +0 -244
  39. package/locales/pt_BR.po +0 -248
  40. package/locales/ro.po +0 -248
  41. package/locales/ru.po +0 -245
  42. package/locales/ta.po +0 -244
  43. package/locales/tr.po +0 -246
  44. package/locales/uk.po +0 -247
  45. package/locales/vec.po +0 -244
  46. package/locales/zh_TW.po +0 -246
  47. package/patches/po2json+1.0.0-beta-3.patch +0 -12
  48. package/src/AWT.js +0 -2067
  49. package/src/Activity.js +0 -1311
  50. package/src/Deps.js +0 -232
  51. package/src/GlobalData.js +0 -5
  52. package/src/JClic.js +0 -196
  53. package/src/JClicPlayer.js +0 -1308
  54. package/src/PlayerHistory.js +0 -305
  55. package/src/Utils.js +0 -1355
  56. package/src/activities/associations/ComplexAssociation.js +0 -321
  57. package/src/activities/associations/SimpleAssociation.js +0 -519
  58. package/src/activities/memory/MemoryGame.js +0 -423
  59. package/src/activities/panels/Explore.js +0 -349
  60. package/src/activities/panels/Identify.js +0 -356
  61. package/src/activities/panels/InformationScreen.js +0 -262
  62. package/src/activities/panels/Menu.js +0 -209
  63. package/src/activities/panels/icons/ico00.png +0 -0
  64. package/src/activities/panels/icons/ico01.png +0 -0
  65. package/src/activities/panels/icons/ico02.png +0 -0
  66. package/src/activities/panels/icons/ico03.png +0 -0
  67. package/src/activities/panels/icons/icofolder.png +0 -0
  68. package/src/activities/puzzles/DoublePuzzle.js +0 -424
  69. package/src/activities/puzzles/ExchangePuzzle.js +0 -374
  70. package/src/activities/puzzles/HolePuzzle.js +0 -360
  71. package/src/activities/text/Complete.js +0 -127
  72. package/src/activities/text/Evaluator.js +0 -534
  73. package/src/activities/text/FillInBlanks.js +0 -426
  74. package/src/activities/text/IdentifyText.js +0 -253
  75. package/src/activities/text/OrderText.js +0 -421
  76. package/src/activities/text/TextActivityBase.js +0 -557
  77. package/src/activities/text/TextActivityDocument.js +0 -660
  78. package/src/activities/text/WrittenAnswer.js +0 -557
  79. package/src/activities/textGrid/CrossWord.js +0 -565
  80. package/src/activities/textGrid/WordSearch.js +0 -458
  81. package/src/activities/textGrid/icons/hIcon.svg +0 -3
  82. package/src/activities/textGrid/icons/vIcon.svg +0 -3
  83. package/src/automation/AutoContentProvider.js +0 -182
  84. package/src/automation/arith/Arith.js +0 -864
  85. package/src/bags/ActivitySequence.js +0 -318
  86. package/src/bags/ActivitySequenceElement.js +0 -161
  87. package/src/bags/ActivitySequenceJump.js +0 -140
  88. package/src/bags/ConditionalJumpInfo.js +0 -113
  89. package/src/bags/JumpInfo.js +0 -136
  90. package/src/bags/MediaBag.js +0 -215
  91. package/src/bags/MediaBagElement.js +0 -516
  92. package/src/boxes/AbstractBox.js +0 -699
  93. package/src/boxes/ActiveBagContent.js +0 -494
  94. package/src/boxes/ActiveBox.js +0 -810
  95. package/src/boxes/ActiveBoxBag.js +0 -357
  96. package/src/boxes/ActiveBoxContent.js +0 -484
  97. package/src/boxes/ActiveBoxGrid.js +0 -179
  98. package/src/boxes/BoxBag.js +0 -500
  99. package/src/boxes/BoxBase.js +0 -398
  100. package/src/boxes/BoxConnector.js +0 -325
  101. package/src/boxes/TextGrid.js +0 -887
  102. package/src/boxes/TextGridContent.js +0 -215
  103. package/src/init-jsdom.js +0 -65
  104. package/src/jclic-node.js +0 -219
  105. package/src/media/ActiveMediaBag.js +0 -145
  106. package/src/media/ActiveMediaPlayer.js +0 -297
  107. package/src/media/AudioBuffer.js +0 -219
  108. package/src/media/EventSounds.js +0 -169
  109. package/src/media/EventSoundsElement.js +0 -155
  110. package/src/media/MediaContent.js +0 -328
  111. package/src/media/MidiAudioPlayer.js +0 -254
  112. package/src/media/icons/audio.svg +0 -3
  113. package/src/media/icons/generic.svg +0 -3
  114. package/src/media/icons/mic.svg +0 -3
  115. package/src/media/icons/movie.svg +0 -3
  116. package/src/media/icons/music.svg +0 -3
  117. package/src/media/icons/url.svg +0 -3
  118. package/src/media/sounds/actionError.mp3 +0 -0
  119. package/src/media/sounds/actionOk.mp3 +0 -0
  120. package/src/media/sounds/click.mp3 +0 -0
  121. package/src/media/sounds/finishedError.mp3 +0 -0
  122. package/src/media/sounds/finishedOk.mp3 +0 -0
  123. package/src/media/sounds/start.mp3 +0 -0
  124. package/src/project/JClicProject.js +0 -282
  125. package/src/project/ProjectSettings.js +0 -273
  126. package/src/report/ActionReg.js +0 -123
  127. package/src/report/ActivityReg.js +0 -271
  128. package/src/report/EncryptMin.js +0 -210
  129. package/src/report/Reporter.js +0 -727
  130. package/src/report/SCORM.js +0 -272
  131. package/src/report/SequenceReg.js +0 -275
  132. package/src/report/SessionReg.js +0 -340
  133. package/src/report/SessionStorageReporter.js +0 -131
  134. package/src/report/TCPReporter.js +0 -628
  135. package/src/shapers/ClassicJigSaw.js +0 -138
  136. package/src/shapers/Holes.js +0 -77
  137. package/src/shapers/JigSaw.js +0 -161
  138. package/src/shapers/Rectangular.js +0 -78
  139. package/src/shapers/Shaper.js +0 -386
  140. package/src/shapers/TriangularJigSaw.js +0 -121
  141. package/src/skins/BlueSkin.js +0 -80
  142. package/src/skins/Counter.js +0 -152
  143. package/src/skins/CustomSkin.js +0 -412
  144. package/src/skins/DefaultSkin.js +0 -376
  145. package/src/skins/EmptySkin.js +0 -82
  146. package/src/skins/GreenSkin.js +0 -94
  147. package/src/skins/MiniSkin.js +0 -130
  148. package/src/skins/OrangeSkin.js +0 -78
  149. package/src/skins/SimpleSkin.js +0 -92
  150. package/src/skins/Skin.js +0 -1021
  151. package/src/skins/assets/actionsIcon.svg +0 -3
  152. package/src/skins/assets/appLogo.svg +0 -8
  153. package/src/skins/assets/basic.css +0 -41
  154. package/src/skins/assets/closeDialogIcon.svg +0 -3
  155. package/src/skins/assets/closeIcon.svg +0 -3
  156. package/src/skins/assets/copyIcon.svg +0 -3
  157. package/src/skins/assets/fullScreenExitIcon.svg +0 -3
  158. package/src/skins/assets/fullScreenIcon.svg +0 -3
  159. package/src/skins/assets/infoIcon.svg +0 -3
  160. package/src/skins/assets/main.css +0 -43
  161. package/src/skins/assets/mainHalf.css +0 -23
  162. package/src/skins/assets/mainTwoThirds.css +0 -23
  163. package/src/skins/assets/mini.css +0 -15
  164. package/src/skins/assets/nextIcon.svg +0 -3
  165. package/src/skins/assets/okDialogIcon.svg +0 -3
  166. package/src/skins/assets/prevIcon.svg +0 -3
  167. package/src/skins/assets/reports.css +0 -156
  168. package/src/skins/assets/reportsIcon.svg +0 -3
  169. package/src/skins/assets/scoreIcon.svg +0 -3
  170. package/src/skins/assets/simple.css +0 -16
  171. package/src/skins/assets/simpleHalf.css +0 -11
  172. package/src/skins/assets/simpleTwoThirds.css +0 -11
  173. package/src/skins/assets/timeIcon.svg +0 -4
  174. package/src/skins/assets/waitAnim.css +0 -54
  175. package/src/skins/assets/waitImgBig.svg +0 -3
  176. package/src/skins/assets/waitImgSmall.svg +0 -3
  177. package/webpack.config.mjs +0 -169
@@ -1,1308 +0,0 @@
1
- /**
2
- * File : JClicPlayer.js
3
- * Created : 28/04/2015
4
- * By : Francesc Busquets <francesc@gmail.com>
5
- *
6
- * JClic.js
7
- * An HTML5 player of JClic activities
8
- * https://projectestac.github.io/jclic.js
9
- *
10
- * @source https://github.com/projectestac/jclic.js
11
- *
12
- * @license EUPL-1.2
13
- * @licstart
14
- * (c) 2000-2022 Educational Telematic Network of Catalonia (XTEC)
15
- *
16
- * Licensed under the EUPL, Version 1.1 or -as soon they will be approved by
17
- * the European Commission- subsequent versions of the EUPL (the "Licence");
18
- * You may not use this work except in compliance with the Licence.
19
- *
20
- * You may obtain a copy of the Licence at:
21
- * https://joinup.ec.europa.eu/software/page/eupl
22
- *
23
- * Unless required by applicable law or agreed to in writing, software
24
- * distributed under the Licence is distributed on an "AS IS" basis, WITHOUT
25
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
26
- * Licence for the specific language governing permissions and limitations
27
- * under the Licence.
28
- * @licend
29
- * @module
30
- */
31
-
32
- /* global JSON, Promise, location, window, document */
33
-
34
- import $ from 'jquery';
35
- import JSZip from 'jszip';
36
- import JSZipUtils from 'jszip-utils';
37
- import { log, init, settings, getPath, endsWith, getBasePath, getRelativePath, isNullOrUndef, mReplace, toCssSize } from './Utils.js';
38
- import { Container, Point, Action, Timer, Rectangle } from './AWT.js';
39
- import PlayerHistory from './PlayerHistory.js';
40
- import ActiveMediaBag from './media/ActiveMediaBag.js';
41
- import Skin from './skins/Skin.js';
42
- import EventSounds from './media/EventSounds.js';
43
- import JClicProject from './project/JClicProject.js';
44
- import JumpInfo from './bags/JumpInfo.js';
45
- import ActiveBoxContent from './boxes/ActiveBoxContent.js';
46
- import Reporter from './report/Reporter.js';
47
- import MediaBagElement from './bags/MediaBagElement.js';
48
-
49
- /**
50
- * JClicPlayer is one of the the main classes of the JClic system. It implements the
51
- * {@link http://projectestac.github.io/jclic/apidoc/edu/xtec/jclic/PlayStation.html PlayStation}
52
- * interface, needed to host JClic activities.
53
- * JClicPlayer offers to {@link module:Activity.ActivityPanel ActivityPanel} objects all the necessary resources and functions:
54
- * media bags (to load and realize images and other media contents), sequence control, management
55
- * of the reporting system, user interface, display of system messages, etc.
56
- * @extends module:AWT.Container
57
- */
58
- export class JClicPlayer extends Container {
59
-
60
- /**
61
- * JClicPlayer constructor
62
- * @param {external:jQuery} $topDiv - The HTML `div` element where this JClicPlayer will deploy.
63
- * @param {object} [options] - A set of optional customized options.
64
- */
65
- constructor($topDiv, options) {
66
-
67
- // JClicPlayer extends Container
68
- super();
69
- // Build cascading options
70
- options = init(options);
71
- this.options = $.extend(Object.create(this.options), options);
72
- // Generate unique ID
73
- this.id = `JC${(0x10000 + Math.round(Math.random() * 0xFFFF)).toString(16).toUpperCase().substring(1)}`;
74
- // Identify the HTML element where this player will deploy
75
- this.$topDiv = $topDiv || $('<div/>');
76
- // Avoid side effects of 'align=center' in old HTML pages
77
- this.$topDiv.css({ 'text-align': 'initial' });
78
-
79
- // Special case: $topDiv inside a TD (like in http://clic.xtec.cat/gali)
80
- if (this.$topDiv.parent().is('td')) {
81
- // Set explicit width and height to fill-in the TD
82
- this.$topDiv.css({
83
- width: toCssSize(this.options.width, null, null, '100%'),
84
- height: toCssSize(this.options.height, null, null, '100%'),
85
- });
86
- }
87
-
88
- // Build the main container
89
- this.$mainContainer = $('<div/>', { class: 'JClicContainer', id: this.id })
90
- .css({ width: '100%', height: '100%' })
91
- .appendTo(this.$topDiv);
92
-
93
- // Intitialize other elements
94
- this.localFS = location && location.protocol === 'file:';
95
- this.$div = $('<div/>', { class: 'JClicPlayer' });
96
- this.project = new JClicProject();
97
- this.activeMediaBag = new ActiveMediaBag();
98
- this.counterVal = { score: 0, actions: 0, time: 0 };
99
- this.bgImageOrigin = new Point();
100
- this.buildActions();
101
- this.history = new PlayerHistory(this);
102
- this.audioEnabled = this.options.audioEnabled;
103
- this.navButtonsAlways = this.options.navButtonsAlways;
104
- this.defaultSkin = Skin.getSkin(this.options.skin, this);
105
- this.setSkin(Skin.getSkin('@empty.xml', this));
106
- this.initTimers();
107
- this.listenTouchEvents();
108
- log('info', 'JClicPlayer ready');
109
- }
110
-
111
- /**
112
- *
113
- * Detects swipe-right, swipe-left and double touch gestures on touch devices,
114
- * associating them with 'next activity', 'previous activity' and 'toggle full screen' actions
115
- */
116
- listenTouchEvents() {
117
-
118
- // Enable listeners only in touch devices
119
- //if ('ontouchstart' in window) {
120
-
121
- let startTouch = null;
122
- let startTouchTime = 0;
123
- let thisDiv = this.$div[0];
124
- const { minSwipeX, maxSwipeY, rightToLeft } = this.options;
125
-
126
- // Generic handler for touch events
127
- const touchEventHandler = event => {
128
- // Process only single-finger events targeted to our main 'div'
129
- if (event.target === thisDiv && event.changedTouches && event.changedTouches.length === 1) {
130
- const touch = event.changedTouches[0];
131
- const dx = startTouch ? touch.clientX - startTouch.clientX : 0;
132
- const dy = startTouch ? touch.clientY - startTouch.clientY : 0;
133
- const dist = Math.sqrt(dx * dx + dy * dy);
134
-
135
- switch (event.type) {
136
- case 'touchstart':
137
- const currentTime = new Date();
138
- // Detect double taps, done in less than 800 ms and at short distance
139
- if (
140
- document && document.fullscreenEnabled
141
- && startTouch && startTouchTime
142
- && currentTime - startTouchTime < 800
143
- && dist < minSwipeX
144
- ) {
145
- event.preventDefault();
146
- log('info', 'Toggle full screen mode from double touch');
147
- this.skin.setScreenFull();
148
- startTouch = null;
149
- }
150
- else {
151
- startTouch = touch;
152
- startTouchTime = currentTime;
153
- }
154
- break;
155
-
156
- case 'touchend':
157
- // Discard non-horizontal gestures and those that do not have sufficient length
158
- if (startTouch && Math.abs(dx) > minSwipeX && Math.abs(dy) < maxSwipeY) {
159
- const actionName = dx < 0 && !rightToLeft ? 'next' : 'prev';
160
- const action = this.actions[actionName];
161
- if (action && action.enabled) {
162
- event.preventDefault();
163
- log('info', `Performing action "${actionName}" from touch gesture`);
164
- action.actionPerformed(event);
165
- }
166
- startTouch = null;
167
- }
168
- // Cancel double touch detection when long gestures detected
169
- else if (dist > minSwipeX)
170
- startTouch = null;
171
- break;
172
-
173
- case 'touchcancel':
174
- startTouch = null;
175
- break;
176
- }
177
- }
178
- else
179
- // Cancel any started gesture
180
- startTouch = null;
181
- };
182
-
183
- // Handle touch events
184
- thisDiv.addEventListener('touchstart', touchEventHandler);
185
- thisDiv.addEventListener('touchend', touchEventHandler);
186
- thisDiv.addEventListener('touchcancel', touchEventHandler);
187
- //}
188
- }
189
-
190
- /**
191
- * Generates an unique ID for elements being used with this player
192
- * @param {string} lb - The element's label
193
- * @returns {string}
194
- */
195
- getUniqueId(lb) {
196
- return `${this.id}-${lb}`;
197
- }
198
-
199
- /**
200
- * Builds the {@link module:AWT.Action} objects for this player
201
- */
202
- buildActions() {
203
- this.actions = {
204
- 'next': new Action('next', () => this.history.processJump(this.project.activitySequence.getJump(false, this.reporter), false)),
205
- 'prev': new Action('prev', () => this.history.processJump(this.project.activitySequence.getJump(true, this.reporter), false)),
206
- 'return': new Action('return', () => this.history.pop()),
207
- 'reset': new Action('reset', () => { if (this.actPanel && this.actPanel.act.canReinit()) this.initActivity(); }),
208
- 'help': new Action('help', () => { if (this.actPanel) this.actPanel.showHelp(); }),
209
- 'info': new Action('info', () => {
210
- if (this.actPanel && this.actPanel.act.hasInfo()) {
211
- if (this.actPanel.act.infoUrl)
212
- this.displayURL(this.act.infoUrl, true);
213
- else if (this.actPanel.act.infoCmd)
214
- this.runCmd(this.actPanel.act.infoCmd);
215
- }
216
- }),
217
- 'reports': new Action('reports', () => this.showReports()),
218
- 'audio': new Action('audio', () => {
219
- this.audioEnabled = !this.audioEnabled;
220
- if (!this.audioEnabled)
221
- this.stopMedia();
222
- EventSounds.prototype.globalEnabled = this.audioEnabled;
223
- })
224
- };
225
-
226
- $.each(this.actions, (key, value) => {
227
- value.addStatusListener(action => { if (this.skin) this.skin.actionStatusChanged(action); });
228
- });
229
- }
230
-
231
- /**
232
- * Resets the main components of this player
233
- */
234
- reset() {
235
- log('info', 'Restoring player');
236
- this.removeActivity();
237
- this.end();
238
- this.activeMediaBag = new ActiveMediaBag();
239
- this.history.clearHistory();
240
- this.setSkin(null);
241
- this.setMsg(null);
242
- this.setCounterValue('score', 0);
243
- this.setCounterValue('actions', 0);
244
- this.setCounterValue('time', 0);
245
- if (this.skin)
246
- this.skin.setWaitCursor('reset');
247
- }
248
-
249
- /**
250
- * Instructs the player to stop working
251
- */
252
- stop() {
253
- this.stopMedia(-1);
254
- }
255
-
256
- /**
257
- * Executes miscellaneous finalization routines.
258
- * @returns {external:Promise} - A promise to be fullfilled when all pending tasks are finished.
259
- */
260
- end() {
261
- let result = null;
262
- this.stopMedia();
263
- this.closeHelpWindow();
264
- if (this.reporter) {
265
- result = this.reporter.end();
266
- this.reporter = null;
267
- }
268
- if (this.actPanel) {
269
- this.actPanel.end();
270
- this.actPanel.$div.remove();
271
- this.actPanel = null;
272
- }
273
- if (this.project) {
274
- this.project.end();
275
- this.project = null;
276
- }
277
- if (this.activeMediaBag)
278
- this.activeMediaBag.removeAll();
279
- return result || Promise.resolve(true);
280
- }
281
-
282
- /**
283
- * Creates and initializes the {@link module:Reporter.Reporter Reporter} member
284
- * @returns {external:Promise}
285
- */
286
- initReporter() {
287
- if (this.reporter) {
288
- this.reporter.end();
289
- this.reporter = null;
290
- }
291
- this.reporter = Reporter.getReporter(null, this);
292
- return this.reporter.init();
293
- }
294
-
295
- /**
296
- * Creates and initializes objects of type {@link module:AWT.Timer}
297
- */
298
- initTimers() {
299
- // Main timer
300
- if (this.timer)
301
- this.timer.stop();
302
- this.timer = new Timer(() => {
303
- this.incCounterValue('time');
304
- if (this.actPanel && this.actPanel.act.maxTime > 0
305
- && this.actPanel.playing
306
- && this.counterVal['time'] >= this.actPanel.act.maxTime)
307
- this.actPanel.finishActivity(false);
308
- }, 1000, false);
309
-
310
- // One-time timer, for delayed actions
311
- if (this.delayedTimer)
312
- this.delayedTimer.stop();
313
- this.delayedTimer = new Timer(() => {
314
- if (this.delayedAction)
315
- this.delayedAction.processEvent(this.delayedAction, null);
316
- }, 1000, false);
317
- this.delayedTimer.repeats = false;
318
- }
319
-
320
- /**
321
- * Opens the reports dialog
322
- */
323
- showReports() {
324
- if (this.skin) this.skin.showReports(this.reporter);
325
- }
326
-
327
- /**
328
- * Closes the help dialog window
329
- */
330
- closeHelpWindow() {
331
- if (this.skin) this.skin._closeDlg(false);
332
- }
333
-
334
- /**
335
- * Sets the current skin
336
- * @param {module:skins/Skin.Skin} [newSkin] - The skin to use. When `null`, `defaultSkin` will be used.
337
- */
338
- setSkin(newSkin) {
339
- newSkin = newSkin || (this.project && this.project.skin ? this.project.skin : this.defaultSkin);
340
- if (newSkin && (this.skin === null || newSkin.name !== this.skin.name)) {
341
- newSkin.attach(this);
342
- this.skin = newSkin;
343
- this.skin.doLayout();
344
- }
345
- }
346
-
347
- /**
348
- * Sets the current project of this player, without starting any activity
349
- * @param {module:project/JClicProject.JClicProject} project - The project to be set
350
- */
351
- setProject(project) {
352
- if (this.project) {
353
- if (this.project !== project)
354
- this.project.end();
355
- this.removeActivity();
356
- }
357
- this.project = project || new JClicProject();
358
- this.project.realize(this);
359
- }
360
-
361
- /**
362
- * Loads the specified project and starts playing at the specified activity or sequence tag.
363
- * @param {string|JClicProject} [project] - The project to load (if it's a string) or to use (if it's an object of type {@link module:project/JClicProject.JClicProject JClicProject}).
364
- * When it's a `string`, it can be the absolute or relative path to:
365
- * - A `.jclic` project file, in XML format
366
- * - A `.jclic.json` project file in JSON format
367
- * - A `.jclic.zip` compressed project file (containing one file of type '.jclic' or '.jclic.json')
368
- * - A `.scorm.zip` file, as exported by JClic Author.
369
- * - A `project.json` file, as exported by JClic Author
370
- * When `null` or `undefined`, refers to the current project.
371
- * @param {string|number} [sequence] - Sequence tag or numeric order in the {@link module:bags/ActivitySequence.ActivitySequence ActivitySequence}
372
- * to be loaded. If _sequence_ and _activity_ are both `null`, the first {@link module:bags/ActivitySequenceElement.ActivitySequenceElement ActivitySequenceElement}
373
- * will be loaded.
374
- * @param {string} [activity] - Name of the activity to be loaded (usually `null`)
375
- */
376
- load(project, sequence, activity) {
377
-
378
- this.forceFinishActivity();
379
- this.setWaitCursor(true);
380
-
381
- // The ActivityPanel object to be obtained as a result of the loading process
382
- let actp = null;
383
-
384
- // step one: load the project
385
- if (project) {
386
- if (typeof project === 'string') {
387
-
388
- // Param `project` is a file name or URL (otherwise, is a realized `JClicProject` object)
389
- const fullPath = getPath(this.basePath, project);
390
-
391
- // Previous step: Check if `project` points to a "project.json" file
392
- if (fullPath.endsWith('project.json')) {
393
- log('info', `Loading JSON info from: ${fullPath}`);
394
- $.getJSON(fullPath).done(({ mainFile }) => {
395
- // Read the `mainFile` field of `project.json`
396
- if (mainFile && endsWith(mainFile, '.jclic') || endsWith(mainFile, '.jclic.json')) {
397
- // Load project's main file
398
- this.load(getPath(getBasePath(fullPath), mainFile), sequence, activity);
399
- } else {
400
- log('error', `Invalid or null "mainFile" specified in ${fullPath} - "project.json".`);
401
- }
402
- }).fail((jqhxr, textStatus, error) => {
403
- const errMsg = `${textStatus} (${error}) while loading ${project}`;
404
- log(errMsg);
405
- window.alert(`Error!\n${errMsg}`);
406
- }).always(
407
- () => this.setWaitCursor(false)
408
- );
409
- return;
410
- }
411
-
412
- // Step 0: Check if `project` points to a ZIP file
413
- if (fullPath.endsWith('.zip')) {
414
- // TODO: Implement register of zip files in PlayerHistory
415
- this.zip = null;
416
- log('info', `Loading ZIP file: ${fullPath}`);
417
-
418
- // Launch loading of ZIP file in a separated thread
419
- JSZipUtils.getBinaryContent(fullPath, (err, data) => {
420
- if (err) {
421
- this.setWaitCursor(false);
422
- log('error', `Error loading ZIP file: ${err.toString()}`);
423
- return;
424
- }
425
- new JSZip().loadAsync(data).then(zip => {
426
- this.zip = zip;
427
- this.zip.fullZipPath = fullPath;
428
- this.zip.zipBasePath = getBasePath(fullPath);
429
- let fileName = null;
430
- // Check if ZIP contains a "project.json" file (as in the ".scorm.zip" files generated by JClic Author)
431
- if (this.zip.files['project.json']) {
432
- this.zip.files['project.json'].async('string').then(content => {
433
- try {
434
- const json = JSON.parse(content);
435
- // Read the `mainFile` field of `project.json`
436
- if (endsWith(json['mainFile'], '.jclic')) {
437
- // Load project's main file
438
- this.load(getPath(this.zip.zipBasePath, json['mainFile']), sequence, activity);
439
- } else {
440
- log('error', `Invalid or null "mainFile" specified in ${fullPath} - "project.json".`);
441
- }
442
- } catch (err) {
443
- log('error', `Error reading "project.json" in ${fullPath}: ${err ? err.toString() : 'unknown error'}`);
444
- }
445
- }).catch(reason => {
446
- log('error', `Error reading ZIP file: ${reason ? reason.toString() : 'unknown reason'}`);
447
- });
448
- } else {
449
- // Find first file with extension '.jclic' inside the zip file
450
- fileName = Object.keys(this.zip.files).find(fn => fn.endsWith('.jclic')) || null;
451
- if (fileName)
452
- this.load(getPath(this.zip.zipBasePath, fileName), sequence, activity);
453
- else
454
- log('error', 'This ZIP file does not contain any JClic project!');
455
- }
456
- this.setWaitCursor(false);
457
- }).catch(reason => {
458
- log('error', `Error reading ZIP file: ${reason ? reason.toString() : 'unknown reason'}`);
459
- this.setWaitCursor(false);
460
- });
461
- });
462
- return;
463
- } else if (this.localFS && window.JClicObject && !window.JClicObject.projectFiles[fullPath]) {
464
- const scriptTag = document.createElement('script');
465
- scriptTag.src = `${fullPath}.js`;
466
- scriptTag.onload = () => {
467
- // 25 Mar 20201:
468
- // Workaround for a bug on Chrome and Firefox XML parsers, throwing errors whith hexadecimal character entities
469
- if (window.JClicObject.projectFiles[fullPath]) {
470
- window.JClicObject.projectFiles[fullPath] = mReplace([
471
- [/&#xD;/g, '\r'],
472
- [/&#xA;/g, '\n'],
473
- [/&#x9;/g, '\t'],
474
- ], window.JClicObject.projectFiles[fullPath]);
475
- this.load(project, sequence, activity);
476
- }
477
- };
478
- document.head.appendChild(scriptTag);
479
- this.setWaitCursor(false);
480
- return;
481
- }
482
-
483
- // Step one: load the project file
484
- const processProjectFile = fp => {
485
- const isXml = fp.indexOf('data:text/xml;') === 0 || fp.endsWith('.jclic');
486
-
487
- const loader = isXml ? $.get(fp, null, null, 'xml') : $.getJSON(fp);
488
-
489
- loader.done(data => {
490
- if (data === null || typeof data !== 'object') {
491
- log('error', `Bad data. Project not loaded: ${project}`);
492
- return;
493
- }
494
- const prj = new JClicProject();
495
- if (isXml)
496
- prj.setProperties($(data).find('JClicProject'), fullPath, this.zip, this.options);
497
- else
498
- prj.setAttributes(data, fullPath, this.zip, this.options);
499
-
500
- log('info', `Project file loaded and parsed: ${project}`);
501
- const elements = prj.mediaBag.buildAll(null, element => {
502
- log('trace', `"${element.name}" ready.`);
503
- this.incProgress(1);
504
- }, this);
505
- log('info', `Media elements to be loaded: ${elements}`);
506
- this.setProgress(0, elements);
507
- let loops = 0;
508
- const interval = 500;
509
- this.setWaitCursor(true);
510
- const checkMedia = window.setInterval(() => {
511
- // Wait for a maximum time of two minutes
512
- if (++loops > this.options.maxWaitTime / interval) {
513
- window.clearInterval(checkMedia);
514
- this.setProgress(-1);
515
- this.setWaitCursor(false);
516
- log('error', 'Error loading media');
517
- }
518
- const waitingObjects = prj.mediaBag.countWaitingElements();
519
- // player.setProgress(waiting)
520
- if (waitingObjects === -1) {
521
- window.clearInterval(checkMedia);
522
- this.setProgress(-1);
523
- this.setWaitCursor(false);
524
- // Call `load` again, passing the loaded [JClicProject](JClicProject.html) as a parameter
525
- this.load(prj, sequence, activity);
526
- }
527
- }, interval);
528
- }).fail((jqXHR, textStatus, errorThrown) => {
529
- const errMsg = `${textStatus} (${errorThrown}) while loading ${project}`;
530
- log(errMsg);
531
- window.alert(`Error!\n${errMsg}`);
532
- }).always(() => this.setWaitCursor(false));
533
- };
534
-
535
-
536
- log('info', `Loading project: ${project}`);
537
- let fp = fullPath;
538
-
539
- // Special case for ZIP files
540
- if (this.zip) {
541
- const fName = getRelativePath(fp, this.zip.zipBasePath);
542
- if (this.zip.files[fName]) {
543
- this.zip.file(fName).async('string').then(text => {
544
- processProjectFile(`data:${fName.endsWith('.jclic') ? 'text/xml' : 'application/json'};charset=UTF-8,${encodeURIComponent(text)}`);
545
- }).catch(reason => {
546
- log('error', `Unable to extract "${fName}" from ZIP file because of: ${reason ? reason.toString() : 'unknown reason'}`);
547
- this.setWaitCursor(false);
548
- });
549
- return;
550
- }
551
- }
552
- // Special case for local file systems (using `file` protocol)
553
- else if (this.localFS) {
554
- // Check if file is already loaded in the global variable `JClicObject`
555
- if (window.JClicObject && window.JClicObject.projectFiles[fullPath]) {
556
- fp = `data:${fullPath.endsWith('.jclic') ? 'text/xml' : 'application/json'};charset=UTF-8,${encodeURIComponent(window.JClicObject.projectFiles[fullPath])}`;
557
- } else {
558
- log('error', `Unable to load: ${fullPath}.js`);
559
- this.setWaitCursor(false);
560
- return;
561
- }
562
- }
563
- processProjectFile(fp);
564
- return;
565
- }
566
-
567
- // From here, assume that `project` is a [JClicProject](JClicProject.html)
568
- this.setProject(project);
569
-
570
- // If none specified, start with the first element of the sequence
571
- if (!sequence && !activity)
572
- sequence = '0';
573
-
574
- // start reporter session
575
- if (this.reporter)
576
- this.reporter.newSession(project);
577
-
578
- }
579
-
580
- // Step two: load the ActivitySequenceElement
581
- if (!isNullOrUndef(sequence)) {
582
- log('info', `Loading sequence: ${sequence}`);
583
- this.navButtonsDisabled = false;
584
- // Try to load sequence by tag
585
- let ase = null;
586
- if (typeof sequence === 'string')
587
- ase = this.project.activitySequence.getElementByTag(sequence, true);
588
- if (ase === null) {
589
- // Try to treat 'sequence' as a number
590
- const n = parseInt(sequence, 10);
591
- if (typeof n === 'number')
592
- ase = this.project.activitySequence.getElement(n, true);
593
- }
594
-
595
- if (ase !== null) {
596
- // Success! We have a real [ActivitySequenceElement](ActivitySequenceElement.html)
597
- if (this.reporter)
598
- this.reporter.newSequence(ase);
599
- activity = ase.activity;
600
- }
601
- }
602
-
603
- // Step three: load the activity
604
- if (activity) {
605
- const act = this.project.getActivity(activity);
606
- if (act) {
607
- // Success! We have a real [Activity](Activity.html)
608
- log('info', `Loading activity: ${activity}`);
609
- // Clean static references to previous audio elements
610
- MediaBagElement.resetAudioElements();
611
- act.prepareMedia(this);
612
- this.project.activitySequence.checkCurrentActivity(act.name);
613
- actp = act.getActivityPanel(this);
614
- actp.buildVisualComponents();
615
- } else {
616
- log('error', `Missing activity: ${activity}`);
617
- }
618
- }
619
-
620
- // Step four: put the activity panel on place
621
-
622
- // Remove the current ActivityPanel
623
- if (this.actPanel !== null) {
624
- this.actPanel.end();
625
- this.actPanel.$div.remove();
626
- this.actPanel = null;
627
- this.setCounterValue('time', 0);
628
- }
629
-
630
- // Attach the new ActivityPanel
631
- if (actp) {
632
- // Sets the actPanel member to this ActivityPanel
633
- this.actPanel = actp;
634
-
635
- if (this.options.fade > 0)
636
- this.actPanel.$div.css('display', 'none');
637
-
638
- // Places the JQuery DOM element of actPanel within the player main panel
639
- this.$div.prepend(this.actPanel.$div);
640
- if (this.skin)
641
- this.skin.resetAllCounters(false);
642
-
643
- // Sets the current skin
644
- if (this.actPanel.skin)
645
- this.setSkin(this.actPanel.skin);
646
- else if (this.project.skin) {
647
- this.setSkin(this.project.skin);
648
- this.lastProjectSkin = this.project.skin;
649
- }
650
- else if (this.lastProjectSkin)
651
- this.setSkin(this.lastProjectSkin);
652
- else
653
- this.setSkin(null);
654
-
655
- if (this.skin) {
656
- // Enable or disable actions
657
- const hasReturn = this.history.storedElementsCount() > 0 || this.options.returnAsExit;
658
- const navBtnFlag = this.navButtonsAlways ?
659
- 'both' : this.navButtonsDisabled ?
660
- 'none' : this.project.activitySequence.getNavButtonsFlag();
661
- this.actions['next'].setEnabled((navBtnFlag === 'fwd' || navBtnFlag === 'both') &&
662
- this.project.activitySequence.hasNextAct(hasReturn));
663
- this.actions['prev'].setEnabled((navBtnFlag === 'back' || navBtnFlag === 'both') &&
664
- this.project.activitySequence.hasPrevAct(hasReturn));
665
- this.actions['return'].setEnabled(hasReturn);
666
- this.actions['help'].setEnabled(this.actPanel.act.helpWindowAllowed());
667
- this.actions['reset'].setEnabled(this.actPanel.act.canReinit());
668
- this.actions['info'].setEnabled(this.actPanel.act.hasInfo());
669
- }
670
- this.doLayout();
671
- this.initActivity();
672
-
673
- this.history.pushBrowserHistory();
674
-
675
- this.actPanel.$div.fadeIn(this.options.fade, () => this.activityReady());
676
- }
677
- this.setWaitCursor(false);
678
- }
679
-
680
- /**
681
- * Forces the current activity to stop playing.
682
- */
683
- forceFinishActivity() {
684
- this.timer.stop();
685
- this.delayedTimer.stop();
686
- if (this.actPanel) {
687
- this.closeHelpWindow();
688
- this.actPanel.forceFinishActivity();
689
- this.stopMedia();
690
- this.activeMediaBag.removeAll();
691
- }
692
- }
693
-
694
- /**
695
- *
696
- * Removes the current {@link module:Activity.ActivityPanel ActivityPanel} from this player
697
- */
698
- removeActivity() {
699
- this.forceFinishActivity();
700
- if (this.actPanel) {
701
- this.actPanel.end();
702
- this.actPanel.$div.remove();
703
- this.actPanel = null;
704
- this.setMsg(null);
705
- this.doLayout();
706
- }
707
- }
708
-
709
- /**
710
- *
711
- * Initializes the activity
712
- */
713
- initActivity() {
714
- this.setWaitCursor(true);
715
- this.timer.stop();
716
- this.delayedTimer.stop();
717
- this.setCounterValue('time', 0);
718
- this.stopMedia();
719
- if (this.actPanel) {
720
- this.actPanel.initActivity();
721
- this.timer.start();
722
- if (!this.actPanel.act.mustPauseSequence())
723
- this.startAutoPassTimer();
724
- log('info', `Activity "${this.actPanel.act.name}" running`);
725
- }
726
- this.setWaitCursor(false);
727
- }
728
-
729
- /**
730
- * Called by {@link module:JClicPlayer.JClicPlayer#load} when the {@link module:Activity.ActivityPanel ActivityPanel} is fully visible, just
731
- * after the JQuery animation effect.
732
- */
733
- activityReady() {
734
- if (this.actPanel) {
735
- this.actPanel.activityReady();
736
- log('info', 'Activity ready');
737
- }
738
- }
739
-
740
- /**
741
- * Starts the activity. This method is usually called from text activities with previous text.
742
- */
743
- startActivity() {
744
- this.setWaitCursor(true);
745
- if (this.actPanel)
746
- this.actPanel.startActivity();
747
- this.setWaitCursor(false);
748
- }
749
-
750
- /**
751
- * Configures the layout and visual aspect of the player area.
752
- */
753
- doLayout() {
754
-
755
- // Main player area settings
756
- const
757
- width = this.dim.width = this.$div.width(),
758
- height = this.dim.height = this.$div.height(),
759
- mainCss = {
760
- 'background-color': this.actPanel ? this.actPanel.act.bgColor : 'azure',
761
- 'background-image': ''
762
- };
763
-
764
- if (this.actPanel) {
765
- const act = this.actPanel.act;
766
- if (act.bgGradient)
767
- // Canvas version also available
768
- mainCss['background-image'] = act.bgGradient.getCss();
769
-
770
- if (act.bgImageFile && act.bgImageFile.length > 0) {
771
- this.project.mediaBag.getElement(act.bgImageFile, true).getFullPathPromise().then(bgImageUrl => {
772
- this.$div.css({
773
- 'background-image': 'url(\'' + bgImageUrl + '\')',
774
- 'background-repeat': act.tiledBgImg ? 'repeat' : 'no-repeat',
775
- 'background-position': act.tiledBgImg ? '' : 'center center'
776
- });
777
- });
778
- }
779
-
780
- // Activity panel settings
781
- // Calc the maximum rectangle available for the activity panel
782
- const
783
- m = settings.BoxBase.AC_MARGIN,
784
- proposedRect = new Rectangle(m, m, width - 2 * m, height - 2 * m);
785
-
786
- if (this.actPanel.bgImage && !act.tiledBgImg && act.absolutePositioned) {
787
- // Special case: when the activity has a background image not tiled, and an absolute
788
- // position has been specified, the ActivityPanel must be placed at this absolute
789
- // position, relative to the background image
790
- this.bgImageOrigin.x = (width - this.actPanel.bgImage.width) / 2;
791
- this.bgImageOrigin.y = (height - this.actPanel.bgImage.height) / 2;
792
- proposedRect.pos.moveTo(this.bgImageOrigin);
793
- proposedRect.dim.width -= this.bgImageOrigin.x - m;
794
- proposedRect.dim.height -= this.bgImageOrigin.y - m;
795
- proposedRect.dim.width = Math.min(proposedRect.dim.width, width);
796
- proposedRect.dim.height = Math.min(proposedRect.dim.height, height);
797
- }
798
-
799
- // ActivityPanel will calculate and set its position and size based on the maximum and optimal
800
- // available space
801
- /* TODO: Try with a computed rectangle instead of "this", to avoid the loss of the right margin
802
- * in narrow displays */
803
- this.actPanel.fitTo(proposedRect, this);
804
- }
805
- this.$div.css(mainCss);
806
- }
807
-
808
- /**
809
- * Plays the specified media.
810
- * @param {module:media/MediaContent.MediaContent} mediaContent - The media to be played
811
- * @param {module:boxes/ActiveBox.ActiveBox} [mediaPlacement] - The cell where the graphic component of this media should be placed (used with video objects)
812
- * @param {function[]} [delayedActions] - If set, store the the action in this array for future execution
813
- */
814
- playMedia(mediaContent, mediaPlacement = null, delayedActions = null) {
815
-
816
- let ji = null;
817
- const fn = mediaContent.file;
818
- let action = null;
819
-
820
- switch (mediaContent.type) {
821
- case 'PLAY_AUDIO':
822
- case 'PLAY_VIDEO':
823
- case 'PLAY_MIDI':
824
- case 'RECORD_AUDIO':
825
- case 'PLAY_RECORDED_AUDIO':
826
- if (this.audioEnabled) {
827
- const amp = this.activeMediaBag.getActiveMediaPlayer(mediaContent, this.project.mediaBag, this);
828
- if (amp)
829
- action = () => amp.play(mediaPlacement);
830
- }
831
- break;
832
-
833
- case 'RUN_CLIC_PACKAGE':
834
- ji = new JumpInfo('JUMP', fn);
835
- if (mediaContent.externalParam) {
836
- // Relative path computed in History.processJump
837
- ji.projectPath = mediaContent.externalParam;
838
- }
839
- action = () => this.history.processJump(ji, true);
840
- break;
841
-
842
- case 'RUN_CLIC_ACTIVITY':
843
- this.history.push();
844
- action = () => this.load(null, null, fn);
845
- break;
846
-
847
- case 'RETURN':
848
- if (this.history.storedElementsCount() > 0 || !this.options.returnAsExit) {
849
- action = () => this.history.pop();
850
- break;
851
- }
852
- case 'EXIT':
853
- ji = new JumpInfo('EXIT', fn);
854
- action = () => this.history.processJump(ji, false);
855
- break;
856
-
857
- case 'RUN_EXTERNAL':
858
- action = () => this.runCmd(fn);
859
- break;
860
-
861
- case 'URL':
862
- if (fn)
863
- // When mediaContent.level is 2 or more, the URL will be opened in a separate window.
864
- action = () => this.displayURL(fn, mediaContent.level > 1);
865
- break;
866
-
867
- default:
868
- log('error', `Unknown media type: ${mediaContent.type}`);
869
- break;
870
- }
871
-
872
- // Execute the action or pass callback
873
- if (delayedActions && action)
874
- delayedActions.push(action);
875
- else if (action)
876
- action();
877
- }
878
-
879
- /**
880
- * Stops currently playing media
881
- * @param {number} [level=-1] - Sets the threshold above which all media objects with equal
882
- * or greater `level` will also also be muted.
883
- */
884
- stopMedia(level) {
885
- if (typeof level !== 'number')
886
- level = -1;
887
- this.activeMediaBag.stopAll(level);
888
- }
889
-
890
- /**
891
- * Launches the specified system command.
892
- * Currently not implemented.
893
- * @param {string} cmd
894
- */
895
- runCmd(cmd) {
896
- log('warn', `Unsupported call to external command: "${cmd}"`);
897
- }
898
-
899
- /**
900
- * Called from {@link module:Activity.Activity Activity} when finished.
901
- * @param {boolean} _completedOK - `true` when the activity was successfully completed, `false`
902
- * otherwise.
903
- */
904
- activityFinished(_completedOK) {
905
- this.closeHelpWindow();
906
- log('info', 'Activity finished');
907
- this.timer.stop();
908
- this.startAutoPassTimer();
909
- }
910
-
911
- /**
912
- * Starts the automatic jump to next activity, when applicable.
913
- */
914
- startAutoPassTimer() {
915
- const ase = this.project.activitySequence.getCurrentAct();
916
- if (ase !== null && ase.delay > 0 && !this.delayedTimer.isRunning() && !this.navButtonsDisabled) {
917
- this.delayedAction = this.actions['next'];
918
- this.delayedTimer.interval = ase.delay * 1000;
919
- this.delayedTimer.start();
920
- }
921
- }
922
-
923
- /**
924
- *
925
- * Sets the current main message.
926
- * @param {module:boxes/ActiveBoxContent.ActiveBoxContent} abc - The content of the message
927
- */
928
- setMsg(abc) {
929
- const ab = this.skin ? this.skin.getMsgBox() : null;
930
- if (ab) {
931
- ab.clear();
932
- this.skin.invalidate(ab).update();
933
- ab.setContent(abc ? abc : ActiveBoxContent.EMPTY_CONTENT);
934
- // TODO: Move this method to Skin
935
- this.skin.invalidate(ab).update();
936
- ab.playMedia(this);
937
- }
938
- }
939
-
940
- /**
941
- * Launches the active media content associated to the main message, if any.
942
- */
943
- playMsg() {
944
- if (this.skin && this.skin.getMsgBox())
945
- this.skin.getMsgBox().playMedia(this);
946
- }
947
-
948
- /**
949
- * Sets a value to the specified counter
950
- * @param {string} counter - The id of the counter ('score', 'actions' or 'time')
951
- * @param {number} newValue - The value to be set
952
- */
953
- setCounterValue(counter, newValue) {
954
- this.counterVal[counter] = newValue;
955
- if (this.skin && this.skin.counters[counter])
956
- this.skin.counters[counter].setValue(newValue);
957
- }
958
-
959
- /**
960
- * Gets the current value for the specified counter
961
- * @param {string} counter - The id of the counter ('score', 'actions' or 'time')
962
- * @returns {number}
963
- */
964
- getCounterValue(counter) {
965
- return this.counterVal[counter];
966
- }
967
-
968
- /**
969
- * Enables or disables a specific counter
970
- * @param {string} counter - The id of the counter ('score', 'actions' or 'time')
971
- * @param {boolean} bEnabled - When `true`, the counter will be enabled.
972
- */
973
- setCounterEnabled(counter, bEnabled) {
974
- if (this.skin) {
975
- this.skin.enableCounter(counter, bEnabled);
976
- this.setCountDown(counter, 0);
977
- }
978
- }
979
-
980
- /**
981
- * Increments by 1 the value of the specified counter
982
- * @param {string} counter - The id of the counter ('score', 'actions' or 'time')
983
- */
984
- incCounterValue(counter) {
985
- this.counterVal[counter]++;
986
-
987
- const
988
- actp = this.actPanel,
989
- cnt = this.skin ? this.skin.counters[counter] : null;
990
-
991
- if (cnt)
992
- cnt.setValue(this.counterVal[counter]);
993
- if (counter === 'actions' && actp !== null && actp.act.maxActions > 0 && actp.playing && this.counterVal['actions'] >= actp.act.maxActions)
994
- window.setTimeout(() => { actp.finishActivity(actp.solved); }, 0);
995
- }
996
-
997
- /**
998
- * Sets the specified counter in count-down status, starting at `maxValue`.
999
- * @param {string} counter - The id of the counter ('score', 'actions' or 'time')
1000
- * @param {number} maxValue - The value from which to start counting down
1001
- */
1002
- setCountDown(counter, maxValue) {
1003
- //this.counterVal[counter] = maxValue
1004
- if (this.skin && this.skin.counters[counter])
1005
- this.skin.counters[counter].setCountDown(maxValue);
1006
- }
1007
-
1008
- /**
1009
- * Set/unset the panel in 'wait' state
1010
- * @param {boolean} status
1011
- */
1012
- setWaitCursor(status) {
1013
- if (this.skin)
1014
- this.skin.setWaitCursor(status);
1015
- }
1016
-
1017
- /**
1018
- * Sets the current value of the progress bar
1019
- * @param {number} val - The current value. Should be less or equal than `max`. When -1, the progress bar will be hidden.
1020
- * @param {number} [max] - Optional parameter representing the maximum value. When passed, the progress bar will be displayed.
1021
- */
1022
- setProgress(val, max) {
1023
- if (this.skin)
1024
- this.skin.setProgress(val, max);
1025
- }
1026
-
1027
- /**
1028
- * Increments the progress bar value by the specified amount, only when the progress bar is running.
1029
- * @param {number} [val=1] - The amount to increment. When not defined, it's 1.
1030
- */
1031
- incProgress(val = 1) {
1032
- if (this.skin)
1033
- this.skin.incProgress(val);
1034
- }
1035
-
1036
- /**
1037
- * Builds an {@link module:media/ActiveMediaPlayer.ActiveMediaPlayer ActiveMediaPlayer} for the specified {@link module:media/MediaContent.MediaContent}
1038
- * @param {module:media/MediaContent.MediaContent} mediaContent - The media content to be played
1039
- * @returns {module:media/ActiveMediaPlayer.ActiveMediaPlayer}
1040
- */
1041
- getActiveMediaPlayer(mediaContent) {
1042
- return this.activeMediaBag && mediaContent ? this.activeMediaBag.getActiveMediaPlayer(mediaContent, this.project.mediaBag, this) : null;
1043
- }
1044
-
1045
- /**
1046
- * Notifies the reporting system that a new activity has started
1047
- * @param {module:Activity.Activity} act - The activity that is sending the notification
1048
- */
1049
- reportNewActivity(act) {
1050
- const ase = this.project.activitySequence.getCurrentAct();
1051
- if (this.reporter) {
1052
- if (ase.tag === this.reporter.getCurrentSequenceTag())
1053
- // Notify that the sequence has changed
1054
- this.reporter.newSequence(ase);
1055
- if (act.includeInReports)
1056
- this.reporter.newActivity(act);
1057
- }
1058
- this.setCounterValue('actions', 0);
1059
- this.setCounterValue('score', 0);
1060
- }
1061
-
1062
- /**
1063
- * Notifies the reporting system that a new action has been performed on the current activity
1064
- * @param {module:Activity.Activity} act - The activity that is sending the notification
1065
- * @param {string} type - Type of action (match, move, switch...)
1066
- * @param {string} source - Object acting as a source of the action (cell, grid, clue...)
1067
- * @param {string} dest - When applicable, object acting as a receiver of the action (cell, grid...)
1068
- * @param {boolean} ok - Whether the action was OK or not
1069
- * @param {number} currentScore - The current score of the activity
1070
- */
1071
- reportNewAction(act, type, source, dest, ok, currentScore) {
1072
- if (this.reporter && act.includeInReports && act.reportActions)
1073
- this.reporter.newAction(type, source, dest, ok);
1074
- if (currentScore >= 0) {
1075
- this.incCounterValue('actions');
1076
- this.setCounterValue('score', currentScore);
1077
- }
1078
- }
1079
-
1080
- /**
1081
- * Notifies the reporting system that the current activity has finished
1082
- * @param {module:Activity.Activity} act - The activity that is sending the notification
1083
- * @param {boolean} solved - Whether the activity was successfully completed or not.
1084
- */
1085
- reportEndActivity(act, solved) {
1086
- if (this.reporter && act.includeInReports)
1087
- this.reporter.endActivity(this.counterVal['score'], this.counterVal['actions'], solved);
1088
- }
1089
-
1090
- /**
1091
- * Shows the help info provided by the activity
1092
- * @param {external:jQuery} $hlpComponent - The jQuery DOM component to be shown.
1093
- * @returns {boolean} - True when the component was successfully displayed
1094
- */
1095
- showHelp($hlpComponent) {
1096
- return this.skin ? this.skin.showHelp($hlpComponent) : false;
1097
- }
1098
-
1099
- /**
1100
- * Navigates to the requested URL
1101
- * @param {string} url - The URL to navigate to
1102
- * @param {boolean} inFrame - When `true` opens in a new frame
1103
- */
1104
- displayURL(url, inFrame) {
1105
- if (url) {
1106
- if (inFrame)
1107
- window.open(url, this.options.infoUrlFrame);
1108
- else {
1109
- this.end().then(() => { window.location.href = url; });
1110
- }
1111
- }
1112
- }
1113
-
1114
- /**
1115
- * Only when `exitUrl` has been specified in `options`, navigates to the specified URL
1116
- * @param {string} url - The URL to navigate to.
1117
- */
1118
- exit(url) {
1119
- this.displayURL(url || this.options.exitUrl, false);
1120
- }
1121
-
1122
- /**
1123
- * Sets a title in a specific HTML element, if provided.
1124
- * @param {string} docTitle
1125
- */
1126
- setWindowTitle(docTitle) {
1127
- log('info', `running ${docTitle}`);
1128
- }
1129
- }
1130
-
1131
- Object.assign(JClicPlayer.prototype, {
1132
- /**
1133
- * Object with miscellaneous options.
1134
- * @name module:JClicPlayer.JClicPlayer#options
1135
- * @type {object} */
1136
- options: {
1137
- //
1138
- // Max waiting time to have all media loaded (milliseconds)
1139
- maxWaitTime: 120000,
1140
- //
1141
- // Name of the frame where to open links
1142
- infoUrlFrame: '_blank',
1143
- //
1144
- // URL where to navigate to on exit
1145
- exitUrl: null,
1146
- //
1147
- // When `true` and no elements on sequence stack, RETURN acts as EXIT
1148
- returnAsExit: false,
1149
- //
1150
- // At the beginning, the audio should be enabled or disabled
1151
- audioEnabled: true,
1152
- //
1153
- // Navigation buttons are always visible (for debugging purposes)
1154
- navButtonsAlways: false,
1155
- //
1156
- // Time (milliseconds) of the fade-in animation of the activity panel. When 0, no animation
1157
- // is performed
1158
- fade: 300,
1159
- // Minimum horizontal swipe length to be considered an activity change gesture
1160
- minSwipeX: 40,
1161
- // Maximum vertical swipe length to be considered an activity change gesture
1162
- maxSwipeY: 100,
1163
- // Read swipe gestures as in right-to-left languages (default is left-to-right)
1164
- rightToLeft: false,
1165
- },
1166
- /**
1167
- * Unique ID of this player, randomly generated by the constructor
1168
- * @name module:JClicPlayer.JClicPlayer#id
1169
- * @type {string} */
1170
- id: 'JC0000',
1171
- /**
1172
- * The JQuery "div" element used by this player as stage for activities
1173
- * @name module:JClicPlayer.JClicPlayer#$div
1174
- * @type {external:jQuery} */
1175
- $div: null,
1176
- /**
1177
- * The JQuery top container where this player will deploy
1178
- * @name module:JClicPlayer.JClicPlayer#$topDiv
1179
- * @type {external:jQuery} */
1180
- $topDiv: null,
1181
- /**
1182
- * The main container of all JClic components
1183
- * @name module:JClicPlayer.JClicPlayer#$mainContainer
1184
- * @type {external:jQuery} */
1185
- $mainContainer: null,
1186
- /**
1187
- * Flag indicatig that this player has switched to full screen at least once
1188
- * @name module:JClicPlayer.JClicPlayer#fullScreenChecked
1189
- * @type {boolean} */
1190
- fullScreenChecked: false,
1191
- /**
1192
- * The {@link module:project/JClicProject.JClicProject JClicProject} currently hosted in this player
1193
- * @name module:JClicPlayer.JClicPlayer#project
1194
- * @type {module:project/JClicProject.JClicProject} */
1195
- project: null,
1196
- /**
1197
- * Relative path or absolute URL to be used as a base to access files
1198
- * @name module:JClicPlayer.JClicPlayer#basePath
1199
- * @type {string} */
1200
- basePath: '',
1201
- /**
1202
- * A {@link external:JSZip} object pointing to a `jclic.zip` or `jclic.scorm.zip` file containing
1203
- * the current project.
1204
- * Two extra properties will be added to this object when loaded:
1205
- * - __zip.fullZipPath__ {string} - The full path of the ZIP file
1206
- * - __zip.zipBasePath__ {string} - The path to the folder containing the ZIP file
1207
- * @name module:JClicPlayer.JClicPlayer#zip
1208
- * @type {external:JSZip} */
1209
- zip: null,
1210
- /**
1211
- * This flag indicates if the player is running inside a document loaded by `file:` protocol
1212
- * @name module:JClicPlayer.JClicPlayer#localFS
1213
- * @type {boolean}
1214
- */
1215
- localFS: false,
1216
- /**
1217
- * The {@link module:Activity.ActivityPanel ActivityPanel} currently running on this player.
1218
- * @name module:JClicPlayer.JClicPlayer#actPanel
1219
- * @type {module:Activity.Activity#Panel} */
1220
- actPanel: null,
1221
- /**
1222
- * This object records the list of the activities played during the current session.
1223
- * @name module:JClicPlayer.JClicPlayer#history
1224
- * @type {module:PlayerHistory.PlayerHistory} */
1225
- history: null,
1226
- /**
1227
- * The Skin currently used by this player.
1228
- * @name module:JClicPlayer.JClicPlayer#skin
1229
- * @type {module:skins/Skin.Skin} */
1230
- skin: null,
1231
- /**
1232
- * The default Skin to be used if none specified
1233
- * @name module:JClicPlayer.JClicPlayer#defaultSkin
1234
- * @type {module:skins/Skin.Skin} */
1235
- defaultSkin: null,
1236
- /**
1237
- * The last skin directly specified by a {@link module:project/JClicProject.JClicProject JClicProject}
1238
- * @name module:JClicPlayer.JClicPlayer#defaultSkin
1239
- * @type {module:skins/Skin.Skin} */
1240
- lastProjectSkin: null,
1241
- /**
1242
- * Object containing references to realized media objects, ready to play.
1243
- * @name module:JClicPlayer.JClicPlayer#activeMediaBag
1244
- * @type {module:media/ActiveMediaBag.ActiveMediaBag} */
1245
- activeMediaBag: null,
1246
- /**
1247
- * Object responsible for passing the scores obtained by users to a external reporting systems
1248
- * when playing activities.
1249
- * @name module:JClicPlayer.JClicPlayer#reporter
1250
- * @type {module:report/Reporter.Reporter} */
1251
- reporter: null,
1252
- /**
1253
- * Collection of {@link module:AWT.Action} objects used by this player.
1254
- * @name module:JClicPlayer.JClicPlayer#actions
1255
- * @type {module:AWT.Action[]} */
1256
- actions: {},
1257
- /**
1258
- * Main timer object used to feed the time counter. Ticks every second.
1259
- * @name module:JClicPlayer.JClicPlayer#timer
1260
- * @type {module:AWT.Timer} */
1261
- timer: null,
1262
- /**
1263
- * Timer for delayed actions
1264
- * @name module:JClicPlayer.JClicPlayer#delayedTimer
1265
- * @type {module:AWT.Timer} */
1266
- delayedTimer: null,
1267
- /**
1268
- * This variable holds the action to be executed by `delayedTimer`
1269
- * @name module:JClicPlayer.JClicPlayer#delayedAction
1270
- * @type {module:AWT.Action} */
1271
- delayedAction: null,
1272
- /**
1273
- * @typedef JClicPlayer~counterValType
1274
- * @type {object}
1275
- * @property {number} score
1276
- * @property {number} actions
1277
- * @property {number} time */
1278
- /**
1279
- * Current values of the counters
1280
- * @name module:JClicPlayer.JClicPlayer#counterVal
1281
- * @type {module:JClicPlayer.JClicPlayer~counterValType} */
1282
- counterVal: { score: 0, actions: 0, time: 0 },
1283
- /**
1284
- * Point indicating the upper-left corner of the current background image
1285
- * @name module:JClicPlayer.JClicPlayer#bgImageOrigin
1286
- * @type {module:AWT.Point} */
1287
- bgImageOrigin: null,
1288
- /**
1289
- * Whether the player must play all sounds (including system sounds) and other media content
1290
- * of the activities.
1291
- * @name module:JClicPlayer.JClicPlayer#audioEnabled
1292
- * @type {boolean} */
1293
- audioEnabled: true,
1294
- /**
1295
- * Whether the navigation buttons `next` and `back` are enabled o disabled.
1296
- * @name module:JClicPlayer.JClicPlayer#navButtonsDisabled
1297
- * @type {boolean} */
1298
- navButtonsDisabled: false,
1299
- /**
1300
- * When this flag is `true`, the navigation buttons are always enabled despite
1301
- * of the indications made by the activities or the sequence control system.
1302
- * This is used only to debug projects with complicated sequence chaining.
1303
- * @name module:JClicPlayer.JClicPlayer#navButtonsAlways
1304
- * @type {boolean} */
1305
- navButtonsAlways: false,
1306
- });
1307
-
1308
- export default JClicPlayer;