Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/* global ns */
2
/**
3
 * Callback for setting new parameters.
4
 *
5
 * @callback H5PEditor.newParams
6
 * @param {Object} field Current field details.
7
 * @param {Object} params New parameters.
8
 */
9
 
10
/**
11
 * Create a field where one can select and include another library to the form.
12
 *
13
 * @class H5PEditor.Library
14
 * @extends H5P.EventDispatcher
15
 * @param {Object} parent Parent field in editor.
16
 * @param {Object} field Details for current field.
17
 * @param {Object} params Default parameters.
18
 * @param {newParams} setValue Callback for setting new parameters.
19
 */
20
ns.Library = function (parent, field, params, setValue) {
21
  var self = this;
22
 
23
  H5P.EventDispatcher.call(this);
24
  if (params === undefined) {
25
    this.params = {
26
      params: {}
27
    };
28
    // If you do a console log here it might show that this.params is
29
    // something else than what we set it to. One of life's big mysteries...
30
    setValue(field, this.params);
31
  }
32
  else {
33
    this.params = params;
34
  }
35
  this.field = field;
36
  this.parent = parent;
37
  this.changes = [];
38
  this.optionsLoaded = false;
39
  this.library = parent.library + '/' + field.name;
40
 
41
  this.passReadies = true;
42
  parent.ready(function () {
43
    self.passReadies = false;
44
  });
45
 
46
  // Confirmation dialog for changing library
47
  this.confirmChangeLibrary = new H5P.ConfirmationDialog({
48
    headerText: H5PEditor.t('core', 'changeLibrary'),
49
    dialogText: H5PEditor.t('core', 'confirmChangeLibrary')
50
  }).appendTo(document.body);
51
 
52
  // Load library on confirmation
53
  this.confirmChangeLibrary.on('confirmed', function () {
54
    self.loadLibrary(self.$select.val());
55
  });
56
 
57
  // Revert to current library on cancel
58
  this.confirmChangeLibrary.on('canceled', function () {
59
    self.$select.val(self.currentLibrary);
60
  });
61
 
62
  H5P.externalDispatcher.on('datainclipboard', this.updateCopyPasteButtons.bind(this));
63
};
64
 
65
ns.Library.prototype = Object.create(H5P.EventDispatcher.prototype);
66
ns.Library.prototype.constructor = ns.Library;
67
 
68
 
69
/**
70
 * Update state of copy and paste buttons dependent on what is currently in
71
 * the clipboard
72
 */
73
ns.Library.prototype.updateCopyPasteButtons = function () {
74
  if (!window.localStorage || !this.libraries) {
75
    return;
76
  }
77
 
78
  // Check if content type is supported here
79
  const pasteCheck = ns.canPastePlus(H5P.getClipboard(), this.libraries);
80
  const canPaste = pasteCheck.canPaste;
81
  const canCopy = (this.currentLibrary !== undefined && this.currentLibrary !== '-');
82
 
83
  this.$copyButton
84
    .prop('disabled', !canCopy)
85
    .toggleClass('disabled', !canCopy);
86
 
87
  this.$pasteButton
88
    .text(ns.t('core', canCopy ? 'pasteAndReplaceButton' : 'pasteButton'))
89
    .attr('title', canPaste ? H5PEditor.t('core', 'pasteFromClipboard') : pasteCheck.description)
90
    .toggleClass('disabled', !canPaste)
91
    .prop('disabled', !canPaste);
92
};
93
 
94
/**
95
 * Append the library selector to the form.
96
 *
97
 * @alias H5PEditor.Library#appendTo
98
 * @param {H5P.jQuery} $wrapper
99
 */
100
ns.Library.prototype.appendTo = function ($wrapper) {
101
  var that = this;
102
  var html = '<div class="field ' + this.field.type + '">';
103
  const id = ns.getNextFieldId(this.field);
104
 
105
  if (this.field.label !== 0 && this.field.label !== undefined) {
106
    html += '<div class="h5p-editor-flex-wrapper">' +
107
        '<label class="h5peditor-label-wrapper" for="' + id + '">' +
108
          '<span class="h5peditor-label' +
109
            (this.field.optional ? '' : ' h5peditor-required') + '">' +
110
              this.field.label +
111
          '</span>' +
112
        '</label>' +
113
      '</div>';
114
  }
115
 
116
  html += ns.createDescription(this.field.description, id);
117
 
118
  html += '<select id="' + id + '"';
119
  if (this.field.description !== undefined) {
120
    html += ' aria-describedby="' + ns.getDescriptionId(id) + '"';
121
  }
122
  html += '>' + ns.createOption('-', 'Loading...') + '</select>';
123
 
124
  /**
125
   * For some content types with custom editors, we don't want to add the copy
126
   * and paste button, since it is handled by the custom editors themself.
127
   *
128
   * @return {boolean}
129
   */
130
  var enableCopyAndPaste = function () {
131
    var librarySelector = ns.findLibraryAncestor(that.parent);
132
    if (librarySelector.currentLibrary !== undefined) {
133
 
134
      var library = ns.libraryFromString(librarySelector.currentLibrary);
135
 
136
      var config = {
137
        'H5P.CoursePresentation': {
138
          major: 1,
139
          minor: 20
140
        },
141
        'H5P.InteractiveVideo': {
142
          major: 1,
143
          minor: 20
144
        },
145
        'H5P.DragQuestion': {
146
          major: 1,
147
          minor: 13
148
        }
149
      }[library.machineName];
150
 
151
      if (config === undefined) {
152
        return true;
153
      }
154
 
155
      return library.majorVersion > config.major ||
156
        (library.majorVersion == config.major && library.minorVersion >= config.minor);
157
    }
158
 
159
    return true;
160
  };
161
 
162
  if (window.localStorage && enableCopyAndPaste()) {
163
    html += ns.createCopyPasteButtons();
164
  }
165
 
166
  html += '<div class="libwrap"></div>';
167
 
168
  html += '</div>';
169
 
170
  this.$myField = ns.$(html).appendTo($wrapper);
171
  this.$select = this.$myField.children('select');
172
  this.$label = this.$myField.find('.h5peditor-label');
173
  this.$clearfix = this.$myField.children('.h5peditor-clearfix');
174
  this.$libraryWrapper = this.$myField.children('.libwrap');
175
  if (window.localStorage) {
176
    this.$copyButton = this.$myField.find('.h5peditor-copy-button').click(function () {
177
      that.validate(); // Make sure all values are up-to-date
178
      H5P.clipboardify(that.params);
179
 
180
      ns.attachToastTo(
181
        that.$copyButton.get(0),
182
        H5PEditor.t('core', 'copiedToClipboard'),
183
        {position: {horizontal: 'center', vertical: 'above', noOverflowX: true}}
184
      );
185
    });
186
    this.$pasteButton = this.$myField.find('.h5peditor-paste-button')
187
      .click(that.pasteContent.bind(this));
188
  }
189
  ns.LibraryListCache.getLibraries(that.field.options, that.librariesLoaded, that);
190
};
191
 
192
/**
193
 * Hide fields that are not required.
194
 */
195
ns.Library.prototype.hide = function () {
196
  this.$label.hide();
197
  this.$libraryWrapper.addClass('no-margin');
198
  this.hideLibrarySelector();
199
  this.hideCopyPaste();
200
};
201
 
202
/**
203
 * Hide library selector.
204
 */
205
ns.Library.prototype.hideLibrarySelector = function () {
206
  this.$myField.children('select').hide();
207
};
208
 
209
/**
210
 * Hide copy button and paste button.
211
 */
212
ns.Library.prototype.hideCopyPaste = function () {
213
  this.$myField.children('.h5peditor-copypaste-wrap').hide();
214
};
215
 
216
/**
217
 * Replace library content using the object in clipboard
218
 */
219
ns.Library.prototype.pasteContent = function () {
220
  var self = this;
221
 
222
  const clipboard = H5P.getClipboard();
223
 
224
  // Check if content type is supported here
225
  if (!ns.canPaste(clipboard, self.libraries)) {
226
    console.error('Tried to paste unsupported sub-content');
227
    return;
228
  }
229
 
230
  // Load library on confirmation
231
  ns.confirmReplace(this.params.library, this.$select.offset().top, function () {
232
    // Update UI
233
    self.$select.val(clipboard.generic.library);
234
 
235
    // Delete old params (to keep object ref)
236
    for (var prop in self.params) {
237
      if (self.params.hasOwnProperty(prop)) {
238
        delete self.params[prop];
239
      }
240
    }
241
 
242
    // Update params
243
    for (prop in clipboard.generic) {
244
      if (clipboard.generic.hasOwnProperty(prop)) {
245
        self.params[prop] = clipboard.generic[prop];
246
      }
247
    }
248
 
249
    // Load form
250
    self.loadLibrary(clipboard.generic.library, true);
251
  });
252
};
253
 
254
/**
255
 * Confirm replace if there is content selected
256
 *
257
 * @param {function} next
258
 */
259
ns.Library.prototype.confirmReplace = function (next) {
260
  if (this.params.library) {
261
    // Confirm changing library
262
    var confirmReplace = new H5P.ConfirmationDialog({
263
      headerText: H5PEditor.t('core', 'changeLibrary'),
264
      dialogText: H5PEditor.t('core', 'confirmChangeLibrary')
265
    }).appendTo(document.body);
266
    confirmReplace.on('confirmed', next);
267
    confirmReplace.show(this.$select.offset().top);
268
  }
269
  else {
270
    // No need to confirm
271
    next();
272
  }
273
};
274
 
275
/**
276
 * Handler for when the library list has been loaded
277
 *
278
 * @alias H5PEditor.Library#librariesLoaded
279
 * @param {Array} libList
280
 */
281
ns.Library.prototype.librariesLoaded = function (libList) {
282
  var self = this;
283
  this.libraries = libList;
284
 
285
  var options = ns.createOption('-', '-');
286
  for (var i = 0; i < self.libraries.length; i++) {
287
    var library = self.libraries[i];
288
    if (library.uberName === self.params.library ||
289
        (library.title !== undefined && (library.restricted === undefined || !library.restricted))) {
290
      options += ns.createOption(library.uberName, library.title, library.uberName === self.params.library);
291
    }
292
  }
293
 
294
  self.$select.html(options).change(function () {
295
    // Use timeout to avoid bug in Chrome >44, when confirm is used inside change event.
296
    // Ref. https://code.google.com/p/chromium/issues/detail?id=525629
297
    setTimeout(function () {
298
      // Check if library is selected
299
      if (self.params.library) {
300
        // Confirm changing library
301
        self.confirmChangeLibrary.show(self.$select.offset().top);
302
      }
303
      else {
304
        // Load new library
305
        self.loadLibrary(self.$select.val());
306
      }
307
    }, 0);
308
  });
309
 
310
  if (self.libraries.length === 1) {
311
    self.$select.hide();
312
    self.$myField.children('.h5p-editor-flex-wrapper').hide();
313
    self.$clearfix.hide();
314
    self.loadLibrary(self.$select.children(':last').val(), true);
315
  }
316
 
317
  self.updateCopyPasteButtons();
318
 
319
  if (self.runChangeCallback === true) {
320
    // In case a library has been selected programmatically trigger change events, e.g. a default library.
321
    self.change();
322
    self.runChangeCallback = false;
323
  }
324
  // Load default library.
325
  if (this.params.library !== undefined) {
326
    self.loadLibrary(this.params.library, true);
327
  }
328
};
329
 
330
/**
331
 * Load the selected library.
332
 *
333
 * @alias H5PEditor.Library#loadLibrary
334
 * @param {string} libraryName On the form machineName.majorVersion.minorVersion
335
 * @param {boolean} [preserveParams]
336
 */
337
ns.Library.prototype.loadLibrary = function (libraryName, preserveParams) {
338
  var that = this;
339
 
340
  this.removeChildren();
341
 
342
  if (libraryName === '-') {
343
    delete this.params.library;
344
    delete this.params.params;
345
    delete this.params.subContentId;
346
    delete this.params.metadata;
347
 
348
    this.$libraryWrapper.attr('class', 'libwrap');
349
    this.updateCopyPasteButtons();
350
    this.change();
351
    return;
352
  }
353
 
354
  this.$libraryWrapper.html(ns.t('core', 'loading')).attr('class', 'libwrap ' + libraryName.split(' ')[0].toLowerCase().replace('.', '-') + '-editor' + (this.libraries.length === 1 ? ' no-margin' : ''));
355
 
356
  ns.loadLibrary(libraryName, function (semantics) {
357
    // Locate selected library object
358
    const library = that.findLibrary(libraryName);
359
    if (library === undefined) {
360
      that.loadLibrary('-');
361
      that.$libraryWrapper.html(ns.createError(ns.t('core', 'unknownLibrary', {'%lib': libraryName}))).attr('class', 'libwrap errors ' + libraryName.split(' ')[0].toLowerCase().replace('.', '-') + '-editor' + (that.libraries.length === 1 ? ' no-margin' : ''));
362
      return;
363
    }
364
 
365
    that.currentLibrary = libraryName;
366
    that.params.library = libraryName;
367
 
368
    if (preserveParams === undefined || !preserveParams) {
369
      // Reset params
370
      delete that.params.subContentId;
371
      that.params.params = {};
372
      that.params.metadata = {};
373
    }
374
    if (that.params.subContentId === undefined) {
375
      that.params.subContentId = H5P.createUUID();
376
    }
377
    if (that.params.metadata === undefined) {
378
      that.params.metadata = {};
379
    }
380
 
381
    // Reset wrapper content
382
    that.$libraryWrapper.html('');
383
 
384
    // Locate form
385
    const ancestor = ns.findAncestor(that.parent);
386
 
387
    // Update the main language switcher
388
    ancestor.addLanguages(library.uberName, ns.libraryCache[library.uberName].languages);
389
 
390
    // Store selected Content Type title in metadata for Copyright usage
391
    that.params.metadata.contentType = library.title;
392
 
393
    // Add metadata form for subcontent
394
    const metadataSettings = that.getLibraryMetadataSettings(library);
395
    if (!metadataSettings.disable) {
396
      that.metadataForm = new ns.MetadataForm(that, that.params.metadata, that.$libraryWrapper, !metadataSettings.disableExtraTitleField, true);
397
    }
398
    else {
399
      that.metadataForm = null; // Prevent usage of last selected content's metadata form
400
    }
401
 
402
    ns.processSemanticsChunk(semantics, that.params.params, that.$libraryWrapper, that);
403
    that.updateCopyPasteButtons();
404
 
405
    if (that.metadataForm && metadataSettings.disableExtraTitleField) {
406
      // Find another location for the metadata button
407
      for (let i = 0; i < that.children.length; i++) {
408
        if (that.children[i].$item) {
409
          // Use the first field with a valid $item
410
          that.metadataForm.appendButtonTo(that.children[i].$item);
411
          break;
412
        }
413
      }
414
    }
415
 
416
    if (that.libraries !== undefined) {
417
      that.change();
418
    }
419
    else {
420
      that.runChangeCallback = true;
421
    }
422
  });
423
};
424
 
425
/**
426
 * Locate the Library object for the given library name.
427
 *
428
 * @param {String} libraryName
429
 * @return {Object}
430
 */
431
ns.Library.prototype.findLibrary = function (libraryName) {
432
  const self = this;
433
 
434
  for (let i = 0; i < self.libraries.length; i++) {
435
    if (self.libraries[i].uberName === libraryName)  {
436
      return self.libraries[i];
437
    }
438
  }
439
};
440
 
441
 
442
/**
443
 * Locate the Library Metadata Settings object for the given library.
444
 *
445
 * @param {String} libraryName
446
 * @return {Object}
447
 */
448
ns.Library.prototype.getLibraryMetadataSettings = function (library) {
449
  return library.metadataSettings ? library.metadataSettings : {
450
    disable: !ns.enableMetadata(library.uberName),
451
    disableExtraTitleField: false
452
  };
453
};
454
 
455
/**
456
 * Add the given callback or run it.
457
 *
458
 * @alias H5PEditor.Library#change
459
 * @param {Function} callback
460
 */
461
ns.Library.prototype.change = function (callback) {
462
  if (callback !== undefined) {
463
    // Add callback
464
    this.changes.push(callback);
465
  }
466
  else {
467
    // Find library
468
    var library, i;
469
    for (i = 0; i < this.libraries.length; i++) {
470
      if (this.libraries[i].uberName === this.params.library) {
471
        library = this.libraries[i];
472
        break;
473
      }
474
    }
475
 
476
    // Run callbacks
477
    for (i = 0; i < this.changes.length; i++) {
478
      this.changes[i](library);
479
    }
480
  }
481
};
482
 
483
/**
484
 * Validate this field and its children.
485
 *
486
 * @alias H5PEditor.Library#validate
487
 * @returns {boolean}
488
 */
489
ns.Library.prototype.validate = function () {
490
  var valid = true;
491
 
492
  if (this.metadataForm && this.metadataForm.children) {
493
    for (var i = 0; i < this.metadataForm.children.length; i++) {
494
      if (this.metadataForm.children[i].validate() === false) {
495
        valid = false;
496
      }
497
    }
498
  }
499
 
500
  if (this.children) {
501
    for (var i = 0; i < this.children.length; i++) {
502
      if (this.children[i].validate() === false) {
503
        valid = false;
504
      }
505
    }
506
  }
507
  else if (this.libraries && this.libraries.length) {
508
    valid = false;
509
  }
510
 
511
  return (this.field.optional ? true : valid);
512
};
513
 
514
/**
515
 * Collect functions to execute once the tree is complete.
516
 *
517
 * @alias H5PEditor.Library#ready
518
 * @param {Function} ready
519
 */
520
ns.Library.prototype.ready = function (ready) {
521
  if (this.passReadies) {
522
    this.parent.ready(ready);
523
  }
524
  else {
525
    this.readies.push(ready);
526
  }
527
};
528
 
529
/**
530
 * Custom remove children that supports common fields.
531
 *
532
 * * @alias H5PEditor.Library#removeChildren
533
 */
534
ns.Library.prototype.removeChildren = function () {
535
  if (this.metadataForm && this.metadataForm.children !== undefined) {
536
    ns.removeChildren(this.metadataForm.children);
537
  }
538
 
539
  if (this.currentLibrary === '-' || this.children === undefined) {
540
    return;
541
  }
542
 
543
  // Remove old metadata form and button
544
  if (this.$metadataFormWrapper) {
545
    this.$metadataFormWrapper.remove();
546
    delete this.$metadataFormWrapper;
547
    this.$metadataButton.remove();
548
    delete this.$metadataButton;
549
  }
550
 
551
  var ancestor = ns.findAncestor(this.parent);
552
  for (var library in ancestor.commonFields) {
553
    if (library === this.currentLibrary) {
554
      var remove = false;
555
 
556
      for (var fieldName in ancestor.commonFields[library]) {
557
        var field = ancestor.commonFields[library][fieldName];
558
        if (field.parents.length === 1) {
559
          field.instance.remove();
560
          remove = true;
561
        }
562
 
563
        for (var i = 0; i < field.parents.length; i++) {
564
          if (field.parents[i] === this) {
565
            field.parents.splice(i, 1);
566
            field.setValues.splice(i, 1);
567
          }
568
        }
569
      }
570
 
571
      if (remove) {
572
        delete ancestor.commonFields[library];
573
        ns.$(ns.renderableCommonFields[library].wrapper).remove();
574
      }
575
    }
576
  }
577
 
578
  // Locate selected library object
579
  const lib = this.findLibrary(this.currentLibrary);
580
 
581
  // Update the main language switcher
582
  ancestor.removeLanguages(lib.uberName, ns.libraryCache[lib.uberName].languages);
583
 
584
  ns.removeChildren(this.children);
585
};
586
 
587
/**
588
 * Allows ancestors and widgets to do stuff with our children.
589
 *
590
 * @alias H5PEditor.Library#forEachChild
591
 * @param {Function} task
592
 */
593
ns.Library.prototype.forEachChild = function (task) {
594
  for (var i = 0; i < this.children.length; i++) {
595
    if (task(this.children[i], i)) {
596
      return;
597
    }
598
  }
599
};
600
 
601
/**
602
 * Called when this item is being removed.
603
 *
604
 * @alias H5PEditor.Library#remove
605
 */
606
ns.Library.prototype.remove = function () {
607
  this.removeChildren();
608
  if (this.$select !== undefined) {
609
    this.$select.parent().remove();
610
  }
611
};
612
 
613
// Tell the editor what widget we are.
614
ns.widgets.library = ns.Library;