UNPKG

13.9 kBJavaScriptView Raw
1var _class, _temp;
2
3function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
4
5function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
6
7var _require = require('@uppy/core'),
8 Plugin = _require.Plugin;
9
10var Translator = require('@uppy/utils/lib/Translator');
11
12var dataURItoBlob = require('@uppy/utils/lib/dataURItoBlob');
13
14var isObjectURL = require('@uppy/utils/lib/isObjectURL');
15
16var isPreviewSupported = require('@uppy/utils/lib/isPreviewSupported');
17
18var MathLog2 = require('math-log2'); // Polyfill for IE.
19
20
21var exifr = require('exifr/dist/mini.legacy.umd.js');
22/**
23 * The Thumbnail Generator plugin
24 */
25
26
27module.exports = (_temp = _class = /*#__PURE__*/function (_Plugin) {
28 _inheritsLoose(ThumbnailGenerator, _Plugin);
29
30 function ThumbnailGenerator(uppy, opts) {
31 var _this;
32
33 _this = _Plugin.call(this, uppy, opts) || this;
34
35 _this.onFileAdded = function (file) {
36 if (!file.preview && isPreviewSupported(file.type) && !file.isRemote) {
37 _this.addToQueue(file.id);
38 }
39 };
40
41 _this.onCancelRequest = function (file) {
42 var index = _this.queue.indexOf(file.id);
43
44 if (index !== -1) {
45 _this.queue.splice(index, 1);
46 }
47 };
48
49 _this.onFileRemoved = function (file) {
50 var index = _this.queue.indexOf(file.id);
51
52 if (index !== -1) {
53 _this.queue.splice(index, 1);
54 } // Clean up object URLs.
55
56
57 if (file.preview && isObjectURL(file.preview)) {
58 URL.revokeObjectURL(file.preview);
59 }
60 };
61
62 _this.onRestored = function () {
63 var _this$uppy$getState = _this.uppy.getState(),
64 files = _this$uppy$getState.files;
65
66 var fileIDs = Object.keys(files);
67 fileIDs.forEach(function (fileID) {
68 var file = _this.uppy.getFile(fileID);
69
70 if (!file.isRestored) return; // Only add blob URLs; they are likely invalid after being restored.
71
72 if (!file.preview || isObjectURL(file.preview)) {
73 _this.addToQueue(file.id);
74 }
75 });
76 };
77
78 _this.waitUntilAllProcessed = function (fileIDs) {
79 fileIDs.forEach(function (fileID) {
80 var file = _this.uppy.getFile(fileID);
81
82 _this.uppy.emit('preprocess-progress', file, {
83 mode: 'indeterminate',
84 message: _this.i18n('generatingThumbnails')
85 });
86 });
87
88 var emitPreprocessCompleteForAll = function emitPreprocessCompleteForAll() {
89 fileIDs.forEach(function (fileID) {
90 var file = _this.uppy.getFile(fileID);
91
92 _this.uppy.emit('preprocess-complete', file);
93 });
94 };
95
96 return new Promise(function (resolve, reject) {
97 if (_this.queueProcessing) {
98 _this.uppy.once('thumbnail:all-generated', function () {
99 emitPreprocessCompleteForAll();
100 resolve();
101 });
102 } else {
103 emitPreprocessCompleteForAll();
104 resolve();
105 }
106 });
107 };
108
109 _this.type = 'modifier';
110 _this.id = _this.opts.id || 'ThumbnailGenerator';
111 _this.title = 'Thumbnail Generator';
112 _this.queue = [];
113 _this.queueProcessing = false;
114 _this.defaultThumbnailDimension = 200;
115 _this.defaultLocale = {
116 strings: {
117 generatingThumbnails: 'Generating thumbnails...'
118 }
119 };
120 var defaultOptions = {
121 thumbnailWidth: null,
122 thumbnailHeight: null,
123 waitForThumbnailsBeforeUpload: false,
124 lazy: false
125 };
126 _this.opts = _extends({}, defaultOptions, opts);
127
128 if (_this.opts.lazy && _this.opts.waitForThumbnailsBeforeUpload) {
129 throw new Error('ThumbnailGenerator: The `lazy` and `waitForThumbnailsBeforeUpload` options are mutually exclusive. Please ensure at most one of them is set to `true`.');
130 }
131
132 _this.i18nInit();
133
134 return _this;
135 }
136
137 var _proto = ThumbnailGenerator.prototype;
138
139 _proto.setOptions = function setOptions(newOpts) {
140 _Plugin.prototype.setOptions.call(this, newOpts);
141
142 this.i18nInit();
143 };
144
145 _proto.i18nInit = function i18nInit() {
146 this.translator = new Translator([this.defaultLocale, this.uppy.locale, this.opts.locale]);
147 this.i18n = this.translator.translate.bind(this.translator);
148 this.setPluginState(); // so that UI re-renders and we see the updated locale
149 }
150 /**
151 * Create a thumbnail for the given Uppy file object.
152 *
153 * @param {{data: Blob}} file
154 * @param {number} targetWidth
155 * @param {number} targetHeight
156 * @returns {Promise}
157 */
158 ;
159
160 _proto.createThumbnail = function createThumbnail(file, targetWidth, targetHeight) {
161 var _this2 = this;
162
163 // bug in the compatibility data
164 // eslint-disable-next-line compat/compat
165 var originalUrl = URL.createObjectURL(file.data);
166 var onload = new Promise(function (resolve, reject) {
167 var image = new Image();
168 image.src = originalUrl;
169 image.addEventListener('load', function () {
170 // bug in the compatibility data
171 // eslint-disable-next-line compat/compat
172 URL.revokeObjectURL(originalUrl);
173 resolve(image);
174 });
175 image.addEventListener('error', function (event) {
176 // bug in the compatibility data
177 // eslint-disable-next-line compat/compat
178 URL.revokeObjectURL(originalUrl);
179 reject(event.error || new Error('Could not create thumbnail'));
180 });
181 });
182 var orientationPromise = exifr.rotation(file.data).catch(function (_err) {
183 return 1;
184 });
185 return Promise.all([onload, orientationPromise]).then(function (_ref) {
186 var image = _ref[0],
187 orientation = _ref[1];
188
189 var dimensions = _this2.getProportionalDimensions(image, targetWidth, targetHeight, orientation.deg);
190
191 var rotatedImage = _this2.rotateImage(image, orientation);
192
193 var resizedImage = _this2.resizeImage(rotatedImage, dimensions.width, dimensions.height);
194
195 return _this2.canvasToBlob(resizedImage, 'image/jpeg', 80);
196 }).then(function (blob) {
197 // bug in the compatibility data
198 // eslint-disable-next-line compat/compat
199 return URL.createObjectURL(blob);
200 });
201 }
202 /**
203 * Get the new calculated dimensions for the given image and a target width
204 * or height. If both width and height are given, only width is taken into
205 * account. If neither width nor height are given, the default dimension
206 * is used.
207 */
208 ;
209
210 _proto.getProportionalDimensions = function getProportionalDimensions(img, width, height, rotation) {
211 var aspect = img.width / img.height;
212
213 if (rotation === 90 || rotation === 270) {
214 aspect = img.height / img.width;
215 }
216
217 if (width != null) {
218 return {
219 width: width,
220 height: Math.round(width / aspect)
221 };
222 }
223
224 if (height != null) {
225 return {
226 width: Math.round(height * aspect),
227 height: height
228 };
229 }
230
231 return {
232 width: this.defaultThumbnailDimension,
233 height: Math.round(this.defaultThumbnailDimension / aspect)
234 };
235 }
236 /**
237 * Make sure the image doesn’t exceed browser/device canvas limits.
238 * For ios with 256 RAM and ie
239 */
240 ;
241
242 _proto.protect = function protect(image) {
243 // https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element
244 var ratio = image.width / image.height;
245 var maxSquare = 5000000; // ios max canvas square
246
247 var maxSize = 4096; // ie max canvas dimensions
248
249 var maxW = Math.floor(Math.sqrt(maxSquare * ratio));
250 var maxH = Math.floor(maxSquare / Math.sqrt(maxSquare * ratio));
251
252 if (maxW > maxSize) {
253 maxW = maxSize;
254 maxH = Math.round(maxW / ratio);
255 }
256
257 if (maxH > maxSize) {
258 maxH = maxSize;
259 maxW = Math.round(ratio * maxH);
260 }
261
262 if (image.width > maxW) {
263 var canvas = document.createElement('canvas');
264 canvas.width = maxW;
265 canvas.height = maxH;
266 canvas.getContext('2d').drawImage(image, 0, 0, maxW, maxH);
267 image = canvas;
268 }
269
270 return image;
271 }
272 /**
273 * Resize an image to the target `width` and `height`.
274 *
275 * Returns a Canvas with the resized image on it.
276 */
277 ;
278
279 _proto.resizeImage = function resizeImage(image, targetWidth, targetHeight) {
280 // Resizing in steps refactored to use a solution from
281 // https://blog.uploadcare.com/image-resize-in-browsers-is-broken-e38eed08df01
282 image = this.protect(image);
283 var steps = Math.ceil(MathLog2(image.width / targetWidth));
284
285 if (steps < 1) {
286 steps = 1;
287 }
288
289 var sW = targetWidth * Math.pow(2, steps - 1);
290 var sH = targetHeight * Math.pow(2, steps - 1);
291 var x = 2;
292
293 while (steps--) {
294 var canvas = document.createElement('canvas');
295 canvas.width = sW;
296 canvas.height = sH;
297 canvas.getContext('2d').drawImage(image, 0, 0, sW, sH);
298 image = canvas;
299 sW = Math.round(sW / x);
300 sH = Math.round(sH / x);
301 }
302
303 return image;
304 };
305
306 _proto.rotateImage = function rotateImage(image, translate) {
307 var w = image.width;
308 var h = image.height;
309
310 if (translate.deg === 90 || translate.deg === 270) {
311 w = image.height;
312 h = image.width;
313 }
314
315 var canvas = document.createElement('canvas');
316 canvas.width = w;
317 canvas.height = h;
318 var context = canvas.getContext('2d');
319 context.translate(w / 2, h / 2);
320
321 if (translate.canvas) {
322 context.rotate(translate.rad);
323 context.scale(translate.scaleX, translate.scaleY);
324 }
325
326 context.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height);
327 return canvas;
328 }
329 /**
330 * Save a <canvas> element's content to a Blob object.
331 *
332 * @param {HTMLCanvasElement} canvas
333 * @returns {Promise}
334 */
335 ;
336
337 _proto.canvasToBlob = function canvasToBlob(canvas, type, quality) {
338 try {
339 canvas.getContext('2d').getImageData(0, 0, 1, 1);
340 } catch (err) {
341 if (err.code === 18) {
342 return Promise.reject(new Error('cannot read image, probably an svg with external resources'));
343 }
344 }
345
346 if (canvas.toBlob) {
347 return new Promise(function (resolve) {
348 canvas.toBlob(resolve, type, quality);
349 }).then(function (blob) {
350 if (blob === null) {
351 throw new Error('cannot read image, probably an svg with external resources');
352 }
353
354 return blob;
355 });
356 }
357
358 return Promise.resolve().then(function () {
359 return dataURItoBlob(canvas.toDataURL(type, quality), {});
360 }).then(function (blob) {
361 if (blob === null) {
362 throw new Error('could not extract blob, probably an old browser');
363 }
364
365 return blob;
366 });
367 }
368 /**
369 * Set the preview URL for a file.
370 */
371 ;
372
373 _proto.setPreviewURL = function setPreviewURL(fileID, preview) {
374 this.uppy.setFileState(fileID, {
375 preview: preview
376 });
377 };
378
379 _proto.addToQueue = function addToQueue(item) {
380 this.queue.push(item);
381
382 if (this.queueProcessing === false) {
383 this.processQueue();
384 }
385 };
386
387 _proto.processQueue = function processQueue() {
388 var _this3 = this;
389
390 this.queueProcessing = true;
391
392 if (this.queue.length > 0) {
393 var current = this.uppy.getFile(this.queue.shift());
394
395 if (!current) {
396 this.uppy.log('[ThumbnailGenerator] file was removed before a thumbnail could be generated, but not removed from the queue. This is probably a bug', 'error');
397 return;
398 }
399
400 return this.requestThumbnail(current).catch(function (err) {}) // eslint-disable-line handle-callback-err
401 .then(function () {
402 return _this3.processQueue();
403 });
404 } else {
405 this.queueProcessing = false;
406 this.uppy.log('[ThumbnailGenerator] Emptied thumbnail queue');
407 this.uppy.emit('thumbnail:all-generated');
408 }
409 };
410
411 _proto.requestThumbnail = function requestThumbnail(file) {
412 var _this4 = this;
413
414 if (isPreviewSupported(file.type) && !file.isRemote) {
415 return this.createThumbnail(file, this.opts.thumbnailWidth, this.opts.thumbnailHeight).then(function (preview) {
416 _this4.setPreviewURL(file.id, preview);
417
418 _this4.uppy.log("[ThumbnailGenerator] Generated thumbnail for " + file.id);
419
420 _this4.uppy.emit('thumbnail:generated', _this4.uppy.getFile(file.id), preview);
421 }).catch(function (err) {
422 _this4.uppy.log("[ThumbnailGenerator] Failed thumbnail for " + file.id + ":", 'warning');
423
424 _this4.uppy.log(err, 'warning');
425
426 _this4.uppy.emit('thumbnail:error', _this4.uppy.getFile(file.id), err);
427 });
428 }
429
430 return Promise.resolve();
431 };
432
433 _proto.install = function install() {
434 this.uppy.on('file-removed', this.onFileRemoved);
435
436 if (this.opts.lazy) {
437 this.uppy.on('thumbnail:request', this.onFileAdded);
438 this.uppy.on('thumbnail:cancel', this.onCancelRequest);
439 } else {
440 this.uppy.on('file-added', this.onFileAdded);
441 this.uppy.on('restored', this.onRestored);
442 }
443
444 if (this.opts.waitForThumbnailsBeforeUpload) {
445 this.uppy.addPreProcessor(this.waitUntilAllProcessed);
446 }
447 };
448
449 _proto.uninstall = function uninstall() {
450 this.uppy.off('file-removed', this.onFileRemoved);
451
452 if (this.opts.lazy) {
453 this.uppy.off('thumbnail:request', this.onFileAdded);
454 this.uppy.off('thumbnail:cancel', this.onCancelRequest);
455 } else {
456 this.uppy.off('file-added', this.onFileAdded);
457 this.uppy.off('restored', this.onRestored);
458 }
459
460 if (this.opts.waitForThumbnailsBeforeUpload) {
461 this.uppy.removePreProcessor(this.waitUntilAllProcessed);
462 }
463 };
464
465 return ThumbnailGenerator;
466}(Plugin), _class.VERSION = "1.6.6", _temp);
\No newline at end of file