Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/* global ns Darkroom */
2
H5PEditor.ImageEditingPopup = (function ($, EventDispatcher) {
3
  var instanceCounter = 0;
4
  var scriptsLoaded = false;
5
 
6
  /**
7
   * Popup for editing images
8
   *
9
   * @param {number} [ratio] Ratio that cropping must keep
10
   * @constructor
11
   */
12
  function ImageEditingPopup(ratio) {
13
    EventDispatcher.call(this);
14
    var self = this;
15
    var uniqueId = instanceCounter;
16
    var isShowing = false;
17
    var isReset = false;
18
    var topOffset = 0;
19
    var maxWidth;
20
    var maxHeight;
21
 
22
    // Create elements
23
    var background = document.createElement('div');
24
    background.className = 'h5p-editing-image-popup-background hidden';
25
 
26
    var popup = document.createElement('div');
27
    popup.className = 'h5p-editing-image-popup';
28
    background.appendChild(popup);
29
 
30
    var header = document.createElement('div');
31
    header.className = 'h5p-editing-image-header';
32
    popup.appendChild(header);
33
 
34
    var headerTitle = document.createElement('div');
35
    headerTitle.className = 'h5p-editing-image-header-title';
36
    headerTitle.textContent = H5PEditor.t('core', 'editImage');
37
    header.appendChild(headerTitle);
38
 
39
    var headerButtons = document.createElement('div');
40
    headerButtons.className = 'h5p-editing-image-header-buttons';
41
    header.appendChild(headerButtons);
42
 
43
    var editingContainer = document.createElement('div');
44
    editingContainer.className = 'h5p-editing-image-editing-container';
45
    popup.appendChild(editingContainer);
46
 
47
    var imageLoading = document.createElement('div');
48
    imageLoading.className = 'h5p-editing-image-loading';
49
    imageLoading.textContent = ns.t('core', 'loadingImageEditor');
50
    popup.appendChild(imageLoading);
51
 
52
    // Create editing image
53
    var editingImage = new Image();
54
    editingImage.className = 'h5p-editing-image hidden';
55
    editingImage.id = 'h5p-editing-image-' + uniqueId;
56
    editingContainer.appendChild(editingImage);
57
 
58
    // Close popup on background click
59
    background.addEventListener('click', function () {
60
      this.hide();
61
    }.bind(this));
62
 
63
    // Prevent closing popup
64
    popup.addEventListener('click', function (e) {
65
      e.stopPropagation();
66
    });
67
 
68
    // Make sure each ImageEditingPopup instance has a unique ID
69
    instanceCounter += 1;
70
 
71
    /**
72
     * Create header button
73
     *
74
     * @param {string} coreString Must be specified in core translations
75
     * @param {string} className Unique button identifier that will be added to classname
76
     * @param {function} clickEvent OnClick function
77
     */
78
    var createButton = function (coreString, className, clickEvent) {
79
      var button = document.createElement('button');
80
      button.textContent = ns.t('core', coreString);
81
      button.className = className;
82
      button.addEventListener('click', clickEvent);
83
      headerButtons.appendChild(button);
84
    };
85
 
86
    /**
87
     * Set max width and height for image editing tool
88
     */
89
    var setDarkroomDimensions = function () {
90
      // Set max dimensions
91
      var dims = ImageEditingPopup.staticDimensions;
92
      maxWidth = background.offsetWidth - dims.backgroundPaddingWidth -
93
        dims.darkroomPadding;
94
 
95
      // Only use 65% of screen height
96
      var maxScreenHeight = screen.height * dims.maxScreenHeightPercentage;
97
 
98
      // Calculate editor max height
99
      var editorHeight = background.offsetHeight -
100
        dims.backgroundPaddingHeight - dims.popupHeaderHeight -
101
        dims.darkroomToolbarHeight - dims.darkroomPadding;
102
 
103
      // Use smallest of screen height and editor height,
104
      // we don't want to overflow editor or screen
105
      maxHeight = maxScreenHeight < editorHeight ? maxScreenHeight : editorHeight;
106
    };
107
 
108
    /**
109
     * Create image editing tool from image.
110
     */
111
    var createDarkroom = function () {
112
      window.requestAnimationFrame(function () {
113
        self.darkroom = new Darkroom('#h5p-editing-image-' + uniqueId, {
114
          initialize: function () {
115
            // Reset transformations
116
            this.transformations = [];
117
 
118
            H5P.$body.get(0).classList.add('h5p-editor-image-popup');
119
            background.classList.remove('hidden');
120
            imageLoading.classList.add('hidden');
121
            self.trigger('initialized');
122
          },
123
          maxWidth: maxWidth,
124
          maxHeight: maxHeight,
125
          plugins: {
126
            crop: {
127
              ratio: ratio || null
128
            },
129
            save : false
130
          }
131
        });
132
      });
133
    };
134
 
135
    /**
136
     * Load a script dynamically
137
     *
138
     * @param {string} path Path to script
139
     * @param {function} [callback]
140
     */
141
    var loadScript = function (path, callback) {
142
      $.ajax({
143
        url: path,
144
        dataType: 'script',
145
        success: function () {
146
          if (callback) {
147
            callback();
148
          }
149
        },
150
        async: true
151
      });
152
    };
153
 
154
    /**
155
     * Load scripts dynamically
156
     */
157
    var loadScripts = function () {
158
      loadScript(H5PEditor.basePath + 'libs/fabric.js', function () {
159
        loadScript(H5PEditor.basePath + 'libs/darkroom.js', function () {
160
          createDarkroom();
161
          scriptsLoaded = true;
162
        });
163
      });
164
    };
165
 
166
    /**
167
     * Grab canvas data and pass data to listeners.
168
     */
169
    var saveImage = function () {
170
 
171
      var isCropped = self.darkroom.plugins.crop.hasFocus();
172
      var canvas = self.darkroom.canvas.getElement();
173
 
174
      var convertData = function () {
175
        const finished = function (blob) {
176
          self.trigger('savedImage', blob);
177
          canvas.removeEventListener('crop:update', convertData, false);
178
        };
179
 
180
        if (self.darkroom.canvas.contextContainer.canvas.toBlob) {
181
          // Export canvas as blob to save processing time and bandwidth
182
          self.darkroom.canvas.contextContainer.canvas.toBlob(finished, self.mime);
183
        }
184
        else {
185
          // Blob export not supported by canvas, export as dataURL and export
186
          // to blob before uploading (saves processing resources on server)
187
          finished(dataURLtoBlob(self.darkroom.canvas.toDataURL({
188
            format: self.mime.split('/')[1]
189
          })));
190
        }
191
      };
192
 
193
      // Check if image has changed
194
      if (self.darkroom.transformations.length || isReset || isCropped) {
195
 
196
        if (isCropped) {
197
          //self.darkroom.plugins.crop.okButton.element.click();
198
          self.darkroom.plugins.crop.cropCurrentZone();
199
 
200
          canvas.addEventListener('crop:update', convertData, false);
201
        }
202
        else {
203
          convertData();
204
        }
205
      }
206
 
207
      isReset = false;
208
    };
209
 
210
    /**
211
     * Adjust popup offset.
212
     * Make sure it is centered on top of offset.
213
     *
214
     * @param {Object} [offset] Offset that popup should center on.
215
     * @param {number} [offset.top] Offset to top.
216
     */
217
    this.adjustPopupOffset = function (offset) {
218
      if (offset) {
219
        topOffset = offset.top;
220
      }
221
 
222
      // Only use 65% of screen height
223
      var maxScreenHeight = screen.height * 0.65;
224
 
225
      // Calculate editor max height
226
      var dims = ImageEditingPopup.staticDimensions;
227
      var backgroundHeight = H5P.$body.get(0).offsetHeight - dims.backgroundPaddingHeight;
228
      var popupHeightNoImage = dims.darkroomToolbarHeight + dims.popupHeaderHeight +
229
        dims.darkroomPadding;
230
      var editorHeight =  backgroundHeight - popupHeightNoImage;
231
 
232
      // Available editor height
233
      var availableHeight = maxScreenHeight < editorHeight ? maxScreenHeight : editorHeight;
234
 
235
      // Check if image is smaller than available height
236
      var actualImageHeight;
237
      if (editingImage.naturalHeight < availableHeight) {
238
        actualImageHeight = editingImage.naturalHeight;
239
      }
240
      else {
241
        actualImageHeight = availableHeight;
242
 
243
        // We must check ratio as well
244
        var imageRatio = editingImage.naturalHeight / editingImage.naturalWidth;
245
        var maxActualImageHeight = maxWidth * imageRatio;
246
        if (maxActualImageHeight < actualImageHeight) {
247
          actualImageHeight = maxActualImageHeight;
248
        }
249
      }
250
 
251
      var popupHeightWImage = actualImageHeight + popupHeightNoImage;
252
      var offsetCentered = topOffset - (popupHeightWImage / 2) -
253
        (dims.backgroundPaddingHeight / 2);
254
 
255
      // Min offset is 0
256
      offsetCentered = offsetCentered > 0 ? offsetCentered : 0;
257
 
258
      // Check that popup does not overflow editor
259
      if (popupHeightWImage + offsetCentered > backgroundHeight) {
260
        var newOffset = backgroundHeight - popupHeightWImage;
261
        offsetCentered = newOffset < 0 ? 0 : newOffset;
262
      }
263
 
264
      popup.style.top = offsetCentered + 'px';
265
    };
266
 
267
    /**
268
     * Set new image in editing tool
269
     *
270
     * @param {string} imgSrc Source of new image
271
     */
272
    this.setImage = function (imgSrc) {
273
      // Set new image
274
      var darkroom = popup.querySelector('.darkroom-container');
275
      if (darkroom) {
276
        darkroom.parentNode.removeChild(darkroom);
277
      }
278
 
279
      H5P.setSource(editingImage, imgSrc, H5PEditor.contentId);
280
      editingImage.onload = function () {
281
        createDarkroom();
282
        editingImage.onload = null;
283
      };
284
      imageLoading.classList.remove('hidden');
285
      editingImage.classList.add('hidden');
286
      editingContainer.appendChild(editingImage);
287
    };
288
 
289
    /**
290
     * Show popup
291
     *
292
     * @param {Object} [offset] Offset that popup should center on.
293
     * @param {string} [imageSrc] Source of image that will be edited
294
     */
295
    this.show = function (offset, imageSrc) {
296
      H5P.$body.get(0).appendChild(background);
297
      background.classList.remove('hidden');
298
      setDarkroomDimensions();
299
      background.classList.add('hidden');
300
      if (imageSrc) {
301
        // Load image editing scripts dynamically
302
        if (!scriptsLoaded) {
303
          H5P.setSource(editingImage, imageSrc, H5PEditor.contentId);
304
          loadScripts();
305
        }
306
        else {
307
          self.setImage(imageSrc);
308
        }
309
 
310
        if (offset) {
311
          var imageLoaded = function () {
312
            this.adjustPopupOffset(offset);
313
            editingImage.removeEventListener('load', imageLoaded);
314
          }.bind(this);
315
 
316
          editingImage.addEventListener('load', imageLoaded);
317
        }
318
      }
319
      else {
320
        H5P.$body.get(0).classList.add('h5p-editor-image-popup');
321
        background.classList.remove('hidden');
322
        self.trigger('initialized');
323
      }
324
 
325
      isShowing = true;
326
    };
327
 
328
    /**
329
     * Hide popup
330
     */
331
    this.hide = function () {
332
      isShowing = false;
333
      H5P.$body.get(0).classList.remove('h5p-editor-image-popup');
334
      background.classList.add('hidden');
335
      H5P.$body.get(0).removeChild(background);
336
    };
337
 
338
    /**
339
     * Toggle popup visibility
340
     */
341
    this.toggle = function () {
342
      if (isShowing) {
343
        this.hide();
344
      }
345
      else {
346
        this.show();
347
      }
348
    };
349
 
350
    // Create header buttons
351
    createButton('resetToOriginalLabel', 'h5p-editing-image-reset-button h5p-remove', function () {
352
      self.trigger('resetImage');
353
      isReset = true;
354
    });
355
    createButton('cancelLabel', 'h5p-editing-image-cancel-button', function () {
356
      self.trigger('canceled');
357
      self.hide();
358
    });
359
    createButton('saveLabel', 'h5p-editing-image-save-button h5p-done', function () {
360
      saveImage();
361
      self.hide();
362
    });
363
  }
364
 
365
  ImageEditingPopup.prototype = Object.create(EventDispatcher.prototype);
366
  ImageEditingPopup.prototype.constructor = ImageEditingPopup;
367
 
368
  ImageEditingPopup.staticDimensions = {
369
    backgroundPaddingWidth: 32,
370
    backgroundPaddingHeight: 96,
371
    darkroomPadding: 64,
372
    darkroomToolbarHeight: 40,
373
    maxScreenHeightPercentage: 0.65,
374
    popupHeaderHeight: 59
375
  };
376
 
377
  /**
378
   * Convert a data URL(base64) into blob.
379
   *
380
   * @param {string} dataURL
381
   * @return {Blob}
382
   */
383
  const dataURLtoBlob = function (dataURL) {
384
    const split = dataURL.split(',');
385
 
386
    // First part is the mime type
387
    const mime = split[0].match(/data:(.*);base64/i)[1];
388
 
389
    // Second part is the base64 data
390
    const bytes = atob(split[1]);
391
 
392
    // Convert string into char code array
393
    const bits = new Uint8Array(bytes.length);
394
    for (let i = 0; i < bytes.length; i++) {
395
      bits[i] = bytes.charCodeAt(i);
396
    }
397
 
398
    // Make the codes into a Blob, and we're done!
399
    return new Blob([bits], {
400
      type: mime
401
    });
402
  }
403
 
404
  return ImageEditingPopup;
405
 
406
}(H5P.jQuery, H5P.EventDispatcher));