UNPKG

14 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.thumbnailType = _this.opts.thumbnailType || 'image/jpeg';
116 _this.defaultLocale = {
117 strings: {
118 generatingThumbnails: 'Generating thumbnails...'
119 }
120 };
121 var defaultOptions = {
122 thumbnailWidth: null,
123 thumbnailHeight: null,
124 waitForThumbnailsBeforeUpload: false,
125 lazy: false
126 };
127 _this.opts = _extends({}, defaultOptions, opts);
128
129 if (_this.opts.lazy && _this.opts.waitForThumbnailsBeforeUpload) {
130 throw new Error('ThumbnailGenerator: The `lazy` and `waitForThumbnailsBeforeUpload` options are mutually exclusive. Please ensure at most one of them is set to `true`.');
131 }
132
133 _this.i18nInit();
134
135 return _this;
136 }
137
138 var _proto = ThumbnailGenerator.prototype;
139
140 _proto.setOptions = function setOptions(newOpts) {
141 _Plugin.prototype.setOptions.call(this, newOpts);
142
143 this.i18nInit();
144 };
145
146 _proto.i18nInit = function i18nInit() {
147 this.translator = new Translator([this.defaultLocale, this.uppy.locale, this.opts.locale]);
148 this.i18n = this.translator.translate.bind(this.translator);
149 this.setPluginState(); // so that UI re-renders and we see the updated locale
150 }
151 /**
152 * Create a thumbnail for the given Uppy file object.
153 *
154 * @param {{data: Blob}} file
155 * @param {number} targetWidth
156 * @param {number} targetHeight
157 * @returns {Promise}
158 */
159 ;
160
161 _proto.createThumbnail = function createThumbnail(file, targetWidth, targetHeight) {
162 var _this2 = this;
163
164 // bug in the compatibility data
165 // eslint-disable-next-line compat/compat
166 var originalUrl = URL.createObjectURL(file.data);
167 var onload = new Promise(function (resolve, reject) {
168 var image = new Image();
169 image.src = originalUrl;
170 image.addEventListener('load', function () {
171 // bug in the compatibility data
172 // eslint-disable-next-line compat/compat
173 URL.revokeObjectURL(originalUrl);
174 resolve(image);
175 });
176 image.addEventListener('error', function (event) {
177 // bug in the compatibility data
178 // eslint-disable-next-line compat/compat
179 URL.revokeObjectURL(originalUrl);
180 reject(event.error || new Error('Could not create thumbnail'));
181 });
182 });
183 var orientationPromise = exifr.rotation(file.data).catch(function (_err) {
184 return 1;
185 });
186 return Promise.all([onload, orientationPromise]).then(function (_ref) {
187 var image = _ref[0],
188 orientation = _ref[1];
189
190 var dimensions = _this2.getProportionalDimensions(image, targetWidth, targetHeight, orientation.deg);
191
192 var rotatedImage = _this2.rotateImage(image, orientation);
193
194 var resizedImage = _this2.resizeImage(rotatedImage, dimensions.width, dimensions.height);
195
196 return _this2.canvasToBlob(resizedImage, _this2.thumbnailType, 80);
197 }).then(function (blob) {
198 // bug in the compatibility data
199 // eslint-disable-next-line compat/compat
200 return URL.createObjectURL(blob);
201 });
202 }
203 /**
204 * Get the new calculated dimensions for the given image and a target width
205 * or height. If both width and height are given, only width is taken into
206 * account. If neither width nor height are given, the default dimension
207 * is used.
208 */
209 ;
210
211 _proto.getProportionalDimensions = function getProportionalDimensions(img, width, height, rotation) {
212 var aspect = img.width / img.height;
213
214 if (rotation === 90 || rotation === 270) {
215 aspect = img.height / img.width;
216 }
217
218 if (width != null) {
219 return {
220 width: width,
221 height: Math.round(width / aspect)
222 };
223 }
224
225 if (height != null) {
226 return {
227 width: Math.round(height * aspect),
228 height: height
229 };
230 }
231
232 return {
233 width: this.defaultThumbnailDimension,
234 height: Math.round(this.defaultThumbnailDimension / aspect)
235 };
236 }
237 /**
238 * Make sure the image doesn’t exceed browser/device canvas limits.
239 * For ios with 256 RAM and ie
240 */
241 ;
242
243 _proto.protect = function protect(image) {
244 // https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element
245 var ratio = image.width / image.height;
246 var maxSquare = 5000000; // ios max canvas square
247
248 var maxSize = 4096; // ie max canvas dimensions
249
250 var maxW = Math.floor(Math.sqrt(maxSquare * ratio));
251 var maxH = Math.floor(maxSquare / Math.sqrt(maxSquare * ratio));
252
253 if (maxW > maxSize) {
254 maxW = maxSize;
255 maxH = Math.round(maxW / ratio);
256 }
257
258 if (maxH > maxSize) {
259 maxH = maxSize;
260 maxW = Math.round(ratio * maxH);
261 }
262
263 if (image.width > maxW) {
264 var canvas = document.createElement('canvas');
265 canvas.width = maxW;
266 canvas.height = maxH;
267 canvas.getContext('2d').drawImage(image, 0, 0, maxW, maxH);
268 image = canvas;
269 }
270
271 return image;
272 }
273 /**
274 * Resize an image to the target `width` and `height`.
275 *
276 * Returns a Canvas with the resized image on it.
277 */
278 ;
279
280 _proto.resizeImage = function resizeImage(image, targetWidth, targetHeight) {
281 // Resizing in steps refactored to use a solution from
282 // https://blog.uploadcare.com/image-resize-in-browsers-is-broken-e38eed08df01
283 image = this.protect(image);
284 var steps = Math.ceil(MathLog2(image.width / targetWidth));
285
286 if (steps < 1) {
287 steps = 1;
288 }
289
290 var sW = targetWidth * Math.pow(2, steps - 1);
291 var sH = targetHeight * Math.pow(2, steps - 1);
292 var x = 2;
293
294 while (steps--) {
295 var canvas = document.createElement('canvas');
296 canvas.width = sW;
297 canvas.height = sH;
298 canvas.getContext('2d').drawImage(image, 0, 0, sW, sH);
299 image = canvas;
300 sW = Math.round(sW / x);
301 sH = Math.round(sH / x);
302 }
303
304 return image;
305 };
306
307 _proto.rotateImage = function rotateImage(image, translate) {
308 var w = image.width;
309 var h = image.height;
310
311 if (translate.deg === 90 || translate.deg === 270) {
312 w = image.height;
313 h = image.width;
314 }
315
316 var canvas = document.createElement('canvas');
317 canvas.width = w;
318 canvas.height = h;
319 var context = canvas.getContext('2d');
320 context.translate(w / 2, h / 2);
321
322 if (translate.canvas) {
323 context.rotate(translate.rad);
324 context.scale(translate.scaleX, translate.scaleY);
325 }
326
327 context.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height);
328 return canvas;
329 }
330 /**
331 * Save a <canvas> element's content to a Blob object.
332 *
333 * @param {HTMLCanvasElement} canvas
334 * @returns {Promise}
335 */
336 ;
337
338 _proto.canvasToBlob = function canvasToBlob(canvas, type, quality) {
339 try {
340 canvas.getContext('2d').getImageData(0, 0, 1, 1);
341 } catch (err) {
342 if (err.code === 18) {
343 return Promise.reject(new Error('cannot read image, probably an svg with external resources'));
344 }
345 }
346
347 if (canvas.toBlob) {
348 return new Promise(function (resolve) {
349 canvas.toBlob(resolve, type, quality);
350 }).then(function (blob) {
351 if (blob === null) {
352 throw new Error('cannot read image, probably an svg with external resources');
353 }
354
355 return blob;
356 });
357 }
358
359 return Promise.resolve().then(function () {
360 return dataURItoBlob(canvas.toDataURL(type, quality), {});
361 }).then(function (blob) {
362 if (blob === null) {
363 throw new Error('could not extract blob, probably an old browser');
364 }
365
366 return blob;
367 });
368 }
369 /**
370 * Set the preview URL for a file.
371 */
372 ;
373
374 _proto.setPreviewURL = function setPreviewURL(fileID, preview) {
375 this.uppy.setFileState(fileID, {
376 preview: preview
377 });
378 };
379
380 _proto.addToQueue = function addToQueue(item) {
381 this.queue.push(item);
382
383 if (this.queueProcessing === false) {
384 this.processQueue();
385 }
386 };
387
388 _proto.processQueue = function processQueue() {
389 var _this3 = this;
390
391 this.queueProcessing = true;
392
393 if (this.queue.length > 0) {
394 var current = this.uppy.getFile(this.queue.shift());
395
396 if (!current) {
397 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');
398 return;
399 }
400
401 return this.requestThumbnail(current).catch(function (err) {}) // eslint-disable-line handle-callback-err
402 .then(function () {
403 return _this3.processQueue();
404 });
405 } else {
406 this.queueProcessing = false;
407 this.uppy.log('[ThumbnailGenerator] Emptied thumbnail queue');
408 this.uppy.emit('thumbnail:all-generated');
409 }
410 };
411
412 _proto.requestThumbnail = function requestThumbnail(file) {
413 var _this4 = this;
414
415 if (isPreviewSupported(file.type) && !file.isRemote) {
416 return this.createThumbnail(file, this.opts.thumbnailWidth, this.opts.thumbnailHeight).then(function (preview) {
417 _this4.setPreviewURL(file.id, preview);
418
419 _this4.uppy.log("[ThumbnailGenerator] Generated thumbnail for " + file.id);
420
421 _this4.uppy.emit('thumbnail:generated', _this4.uppy.getFile(file.id), preview);
422 }).catch(function (err) {
423 _this4.uppy.log("[ThumbnailGenerator] Failed thumbnail for " + file.id + ":", 'warning');
424
425 _this4.uppy.log(err, 'warning');
426
427 _this4.uppy.emit('thumbnail:error', _this4.uppy.getFile(file.id), err);
428 });
429 }
430
431 return Promise.resolve();
432 };
433
434 _proto.install = function install() {
435 this.uppy.on('file-removed', this.onFileRemoved);
436
437 if (this.opts.lazy) {
438 this.uppy.on('thumbnail:request', this.onFileAdded);
439 this.uppy.on('thumbnail:cancel', this.onCancelRequest);
440 } else {
441 this.uppy.on('file-added', this.onFileAdded);
442 this.uppy.on('restored', this.onRestored);
443 }
444
445 if (this.opts.waitForThumbnailsBeforeUpload) {
446 this.uppy.addPreProcessor(this.waitUntilAllProcessed);
447 }
448 };
449
450 _proto.uninstall = function uninstall() {
451 this.uppy.off('file-removed', this.onFileRemoved);
452
453 if (this.opts.lazy) {
454 this.uppy.off('thumbnail:request', this.onFileAdded);
455 this.uppy.off('thumbnail:cancel', this.onCancelRequest);
456 } else {
457 this.uppy.off('file-added', this.onFileAdded);
458 this.uppy.off('restored', this.onRestored);
459 }
460
461 if (this.opts.waitForThumbnailsBeforeUpload) {
462 this.uppy.removePreProcessor(this.waitUntilAllProcessed);
463 }
464 };
465
466 return ThumbnailGenerator;
467}(Plugin), _class.VERSION = "1.7.4", _temp);
\No newline at end of file