diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index e038dac88b..3b4b704626 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -8,7 +8,6 @@ import { browserHistory } from 'flavours/glitch/components/router'; import { search as emojiSearch } from 'flavours/glitch/features/emoji/emoji_mart_search_light'; import { tagHistory } from 'flavours/glitch/settings'; import { recoverHashtags } from 'flavours/glitch/utils/hashtag'; -import resizeImage from 'flavours/glitch/utils/resize_image'; import { showAlert, showAlertForError } from './alerts'; import { useEmoji } from './emojis'; @@ -350,46 +349,42 @@ export function uploadCompose(files) { dispatch(uploadComposeRequest()); - for (const [i, f] of Array.from(files).entries()) { + for (const [i, file] of Array.from(files).entries()) { if (media.size + i > (uploadLimit - 1)) break; - resizeImage(f).then(file => { - const data = new FormData(); - data.append('file', file); - // Account for disparity in size of original image and resized data - total += file.size - f.size; + const data = new FormData(); + data.append('file', file); - return api().post('/api/v2/media', data, { - onUploadProgress: function({ loaded }){ - progress[i] = loaded; - dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); - }, - }).then(({ status, data }) => { - // If server-side processing of the media attachment has not completed yet, - // poll the server until it is, before showing the media attachment as uploaded + api().post('/api/v2/media', data, { + onUploadProgress: function({ loaded }){ + progress[i] = loaded; + dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); + }, + }).then(({ status, data }) => { + // If server-side processing of the media attachment has not completed yet, + // poll the server until it is, before showing the media attachment as uploaded - if (status === 200) { - dispatch(uploadComposeSuccess(data, f)); - } else if (status === 202) { - dispatch(uploadComposeProcessing()); + if (status === 200) { + dispatch(uploadComposeSuccess(data, file)); + } else if (status === 202) { + dispatch(uploadComposeProcessing()); - let tryCount = 1; + let tryCount = 1; - const poll = () => { - api().get(`/api/v1/media/${data.id}`).then(response => { - if (response.status === 200) { - dispatch(uploadComposeSuccess(response.data, f)); - } else if (response.status === 206) { - const retryAfter = (Math.log2(tryCount) || 1) * 1000; - tryCount += 1; - setTimeout(() => poll(), retryAfter); - } - }).catch(error => dispatch(uploadComposeFail(error))); - }; + const poll = () => { + api().get(`/api/v1/media/${data.id}`).then(response => { + if (response.status === 200) { + dispatch(uploadComposeSuccess(response.data, file)); + } else if (response.status === 206) { + const retryAfter = (Math.log2(tryCount) || 1) * 1000; + tryCount += 1; + setTimeout(() => poll(), retryAfter); + } + }).catch(error => dispatch(uploadComposeFail(error))); + }; - poll(); - } - }); + poll(); + } }).catch(error => dispatch(uploadComposeFail(error))); } }; diff --git a/app/javascript/flavours/glitch/utils/resize_image.js b/app/javascript/flavours/glitch/utils/resize_image.js deleted file mode 100644 index c579fe5a2c..0000000000 --- a/app/javascript/flavours/glitch/utils/resize_image.js +++ /dev/null @@ -1,193 +0,0 @@ -import EXIF from 'exif-js'; - -const MAX_IMAGE_PIXELS = 2073600; // 1920x1080px - -const _browser_quirks = {}; - -// Some browsers will automatically draw images respecting their EXIF orientation -// while others won't, and the safest way to detect that is to examine how it -// is done on a known image. -// See https://github.com/w3c/csswg-drafts/issues/4666 -// and https://github.com/blueimp/JavaScript-Load-Image/commit/1e4df707821a0afcc11ea0720ee403b8759f3881 -const dropOrientationIfNeeded = (orientation) => new Promise(resolve => { - switch (_browser_quirks['image-orientation-automatic']) { - case true: - resolve(1); - break; - case false: - resolve(orientation); - break; - default: { - // black 2x1 JPEG, with the following meta information set: - // - EXIF Orientation: 6 (Rotated 90° CCW) - const testImageURL = - 'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' + - 'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' + - 'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' + - 'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' + - 'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' + - 'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q=='; - const img = new Image(); - img.onload = () => { - const automatic = (img.width === 1 && img.height === 2); - _browser_quirks['image-orientation-automatic'] = automatic; - resolve(automatic ? 1 : orientation); - }; - img.onerror = () => { - _browser_quirks['image-orientation-automatic'] = false; - resolve(orientation); - }; - img.src = testImageURL; - } - } -}); - -// Some browsers don't allow reading from a canvas and instead return all-white -// or randomized data. Use a pre-defined image to check if reading the canvas -// works. -const checkCanvasReliability = () => new Promise((resolve, reject) => { - switch(_browser_quirks['canvas-read-unreliable']) { - case true: - reject('Canvas reading unreliable'); - break; - case false: - resolve(); - break; - default: { - // 2×2 GIF with white, red, green and blue pixels - const testImageURL = - 'data:image/gif;base64,R0lGODdhAgACAKEDAAAA//8AAAD/AP///ywAAAAAAgACAAACA1wEBQA7'; - const refData = - [255, 255, 255, 255, 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255]; - const img = new Image(); - img.onload = () => { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - context.drawImage(img, 0, 0, 2, 2); - const imageData = context.getImageData(0, 0, 2, 2); - if (imageData.data.every((x, i) => refData[i] === x)) { - _browser_quirks['canvas-read-unreliable'] = false; - resolve(); - } else { - _browser_quirks['canvas-read-unreliable'] = true; - reject('Canvas reading unreliable'); - } - }; - img.onerror = () => { - _browser_quirks['canvas-read-unreliable'] = true; - reject('Failed to load test image'); - }; - img.src = testImageURL; - } - } -}); - -const getImageUrl = inputFile => new Promise((resolve, reject) => { - if (window.URL && URL.createObjectURL) { - try { - resolve(URL.createObjectURL(inputFile)); - } catch (error) { - reject(error); - } - return; - } - - const reader = new FileReader(); - reader.onerror = (...args) => reject(...args); - reader.onload = ({ target }) => resolve(target.result); - - reader.readAsDataURL(inputFile); -}); - -const loadImage = inputFile => new Promise((resolve, reject) => { - getImageUrl(inputFile).then(url => { - const img = new Image(); - - img.onerror = (...args) => reject(...args); - img.onload = () => resolve(img); - - img.src = url; - }).catch(reject); -}); - -const getOrientation = (img, type = 'image/png') => new Promise(resolve => { - if (!['image/jpeg', 'image/webp'].includes(type)) { - resolve(1); - return; - } - - EXIF.getData(img, () => { - const orientation = EXIF.getTag(img, 'Orientation'); - if (orientation !== 1) { - dropOrientationIfNeeded(orientation).then(resolve).catch(() => resolve(orientation)); - } else { - resolve(orientation); - } - }); -}); - -const processImage = (img, { width, height, orientation, type = 'image/png' }) => new Promise(resolve => { - const canvas = document.createElement('canvas'); - - if (4 < orientation && orientation < 9) { - canvas.width = height; - canvas.height = width; - } else { - canvas.width = width; - canvas.height = height; - } - - const context = canvas.getContext('2d'); - - switch (orientation) { - case 2: context.transform(-1, 0, 0, 1, width, 0); break; - case 3: context.transform(-1, 0, 0, -1, width, height); break; - case 4: context.transform(1, 0, 0, -1, 0, height); break; - case 5: context.transform(0, 1, 1, 0, 0, 0); break; - case 6: context.transform(0, 1, -1, 0, height, 0); break; - case 7: context.transform(0, -1, -1, 0, height, width); break; - case 8: context.transform(0, -1, 1, 0, 0, width); break; - } - - context.drawImage(img, 0, 0, width, height); - - canvas.toBlob(resolve, type); -}); - -const resizeImage = (img, type = 'image/png') => new Promise((resolve, reject) => { - const { width, height } = img; - - const newWidth = Math.round(Math.sqrt(MAX_IMAGE_PIXELS * (width / height))); - const newHeight = Math.round(Math.sqrt(MAX_IMAGE_PIXELS * (height / width))); - - checkCanvasReliability() - .then(getOrientation(img, type)) - .then(orientation => processImage(img, { - width: newWidth, - height: newHeight, - orientation, - type, - })) - .then(resolve) - .catch(reject); -}); - -const resizeFile = (inputFile) => new Promise((resolve) => { - if (!inputFile.type.match(/image.*/) || inputFile.type === 'image/gif') { - resolve(inputFile); - return; - } - - loadImage(inputFile).then(img => { - if (img.width * img.height < MAX_IMAGE_PIXELS) { - resolve(inputFile); - return; - } - - resizeImage(img, inputFile.type) - .then(resolve) - .catch(() => resolve(inputFile)); - }).catch(() => resolve(inputFile)); -}); - -export default resizeFile; diff --git a/package.json b/package.json index 3d570f4d17..21369cc64f 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "detect-passive-events": "^2.0.3", "emoji-mart": "npm:emoji-mart-lazyload@latest", "escape-html": "^1.0.3", - "exif-js": "^2.3.0", "favico.js": "^0.3.10", "file-loader": "^6.2.0", "fuzzysort": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 067b7d0e6b..dba313ee5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2900,7 +2900,6 @@ __metadata: eslint-plugin-promise: "npm:~7.2.1" eslint-plugin-react: "npm:^7.37.4" eslint-plugin-react-hooks: "npm:^5.2.0" - exif-js: "npm:^2.3.0" favico.js: "npm:^0.3.10" file-loader: "npm:^6.2.0" fuzzysort: "npm:^3.0.0" @@ -8567,13 +8566,6 @@ __metadata: languageName: node linkType: hard -"exif-js@npm:^2.3.0": - version: 2.3.0 - resolution: "exif-js@npm:2.3.0" - checksum: 10c0/6bcb081cfd141147e9e13c412fa52fa8a6b915d441bdaefb8c9882058f0e12843b8249ecf6f620709ce43ef069ebdd45181176560160a066f03e2cae384a10f7 - languageName: node - linkType: hard - "exit@npm:^0.1.2": version: 0.1.2 resolution: "exit@npm:0.1.2"