Refactor resizeImage method (#7236)
- Use URL.createObjectURL (replace from FileReader) - Use HTMLCanvasElement.prototype.toBlob (replace from HTMLCanvasElement.prototype.toDataURL) - Use Promise (replace callback interface)
This commit is contained in:
parent
660cb058e1
commit
0758b00bfd
@ -4,6 +4,7 @@ import { throttle } from 'lodash';
|
|||||||
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
|
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
|
||||||
import { tagHistory } from '../settings';
|
import { tagHistory } from '../settings';
|
||||||
import { useEmoji } from './emojis';
|
import { useEmoji } from './emojis';
|
||||||
|
import resizeImage from '../utils/resize_image';
|
||||||
import { importFetchedAccounts } from './importer';
|
import { importFetchedAccounts } from './importer';
|
||||||
import { updateTimeline } from './timelines';
|
import { updateTimeline } from './timelines';
|
||||||
import { showAlertForError } from './alerts';
|
import { showAlertForError } from './alerts';
|
||||||
@ -174,79 +175,6 @@ export function submitComposeFail(error) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_IMAGE_DIMENSION = 1280;
|
|
||||||
|
|
||||||
const dataURLtoBlob = dataURL => {
|
|
||||||
const BASE64_MARKER = ';base64,';
|
|
||||||
|
|
||||||
if (dataURL.indexOf(BASE64_MARKER) === -1) {
|
|
||||||
const parts = dataURL.split(',');
|
|
||||||
const contentType = parts[0].split(':')[1];
|
|
||||||
const raw = parts[1];
|
|
||||||
|
|
||||||
return new Blob([raw], { type: contentType });
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = dataURL.split(BASE64_MARKER);
|
|
||||||
const contentType = parts[0].split(':')[1];
|
|
||||||
const raw = window.atob(parts[1]);
|
|
||||||
const rawLength = raw.length;
|
|
||||||
|
|
||||||
const uInt8Array = new Uint8Array(rawLength);
|
|
||||||
|
|
||||||
for (let i = 0; i < rawLength; ++i) {
|
|
||||||
uInt8Array[i] = raw.charCodeAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Blob([uInt8Array], { type: contentType });
|
|
||||||
};
|
|
||||||
|
|
||||||
const resizeImage = (inputFile, callback) => {
|
|
||||||
if (inputFile.type.match(/image.*/) && inputFile.type !== 'image/gif') {
|
|
||||||
const reader = new FileReader();
|
|
||||||
|
|
||||||
reader.onload = e => {
|
|
||||||
const img = new Image();
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const { width, height } = img;
|
|
||||||
|
|
||||||
let newWidth, newHeight;
|
|
||||||
|
|
||||||
if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) {
|
|
||||||
callback(inputFile);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width > height) {
|
|
||||||
newHeight = height * MAX_IMAGE_DIMENSION / width;
|
|
||||||
newWidth = MAX_IMAGE_DIMENSION;
|
|
||||||
} else if (height > width) {
|
|
||||||
newWidth = width * MAX_IMAGE_DIMENSION / height;
|
|
||||||
newHeight = MAX_IMAGE_DIMENSION;
|
|
||||||
} else {
|
|
||||||
newWidth = MAX_IMAGE_DIMENSION;
|
|
||||||
newHeight = MAX_IMAGE_DIMENSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.width = newWidth;
|
|
||||||
canvas.height = newHeight;
|
|
||||||
|
|
||||||
canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight);
|
|
||||||
|
|
||||||
callback(dataURLtoBlob(canvas.toDataURL(inputFile.type)));
|
|
||||||
};
|
|
||||||
|
|
||||||
img.src = e.target.result;
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsDataURL(inputFile);
|
|
||||||
} else {
|
|
||||||
callback(inputFile);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function uploadCompose(files) {
|
export function uploadCompose(files) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
if (getState().getIn(['compose', 'media_attachments']).size > 3) {
|
if (getState().getIn(['compose', 'media_attachments']).size > 3) {
|
||||||
@ -255,20 +183,14 @@ export function uploadCompose(files) {
|
|||||||
|
|
||||||
dispatch(uploadComposeRequest());
|
dispatch(uploadComposeRequest());
|
||||||
|
|
||||||
resizeImage(files[0], file => {
|
resizeImage(files[0]).then(file => {
|
||||||
let data = new FormData();
|
const data = new FormData();
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
|
|
||||||
api(getState).post('/api/v1/media', data, {
|
return api(getState).post('/api/v1/media', data, {
|
||||||
onUploadProgress: function (e) {
|
onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)),
|
||||||
dispatch(uploadComposeProgress(e.loaded, e.total));
|
}).then(({ data }) => dispatch(uploadComposeSuccess(data)));
|
||||||
},
|
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||||
}).then(function (response) {
|
|
||||||
dispatch(uploadComposeSuccess(response.data));
|
|
||||||
}).catch(function (error) {
|
|
||||||
dispatch(uploadComposeFail(error));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
|
import { decode as decodeBase64 } from '../../utils/base64';
|
||||||
import { pushNotificationsSetting } from '../../settings';
|
import { pushNotificationsSetting } from '../../settings';
|
||||||
import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
|
import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
|
||||||
import { me } from '../../initial_state';
|
import { me } from '../../initial_state';
|
||||||
@ -10,13 +11,7 @@ const urlBase64ToUint8Array = (base64String) => {
|
|||||||
.replace(/\-/g, '+')
|
.replace(/\-/g, '+')
|
||||||
.replace(/_/g, '/');
|
.replace(/_/g, '/');
|
||||||
|
|
||||||
const rawData = window.atob(base64);
|
return decodeBase64(base64);
|
||||||
const outputArray = new Uint8Array(rawData.length);
|
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; ++i) {
|
|
||||||
outputArray[i] = rawData.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return outputArray;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content');
|
const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content');
|
||||||
|
@ -5,6 +5,7 @@ import includes from 'array-includes';
|
|||||||
import assign from 'object-assign';
|
import assign from 'object-assign';
|
||||||
import values from 'object.values';
|
import values from 'object.values';
|
||||||
import isNaN from 'is-nan';
|
import isNaN from 'is-nan';
|
||||||
|
import { decode as decodeBase64 } from './utils/base64';
|
||||||
|
|
||||||
if (!Array.prototype.includes) {
|
if (!Array.prototype.includes) {
|
||||||
includes.shim();
|
includes.shim();
|
||||||
@ -21,3 +22,23 @@ if (!Object.values) {
|
|||||||
if (!Number.isNaN) {
|
if (!Number.isNaN) {
|
||||||
Number.isNaN = isNaN;
|
Number.isNaN = isNaN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!HTMLCanvasElement.prototype.toBlob) {
|
||||||
|
const BASE64_MARKER = ';base64,';
|
||||||
|
|
||||||
|
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
|
||||||
|
value(callback, type = 'image/png', quality) {
|
||||||
|
const dataURL = this.toDataURL(type, quality);
|
||||||
|
let data;
|
||||||
|
|
||||||
|
if (dataURL.indexOf(BASE64_MARKER) >= 0) {
|
||||||
|
const [, base64] = dataURL.split(BASE64_MARKER);
|
||||||
|
data = decodeBase64(base64);
|
||||||
|
} else {
|
||||||
|
[, data] = dataURL.split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(new Blob([data], { type }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -12,12 +12,13 @@ function importExtraPolyfills() {
|
|||||||
|
|
||||||
function loadPolyfills() {
|
function loadPolyfills() {
|
||||||
const needsBasePolyfills = !(
|
const needsBasePolyfills = !(
|
||||||
|
Array.prototype.includes &&
|
||||||
|
HTMLCanvasElement.prototype.toBlob &&
|
||||||
window.Intl &&
|
window.Intl &&
|
||||||
|
Number.isNaN &&
|
||||||
Object.assign &&
|
Object.assign &&
|
||||||
Object.values &&
|
Object.values &&
|
||||||
Number.isNaN &&
|
window.Symbol
|
||||||
window.Symbol &&
|
|
||||||
Array.prototype.includes
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Latest version of Firefox and Safari do not have IntersectionObserver.
|
// Latest version of Firefox and Safari do not have IntersectionObserver.
|
||||||
|
10
app/javascript/mastodon/utils/__tests__/base64-test.js
Normal file
10
app/javascript/mastodon/utils/__tests__/base64-test.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as base64 from '../base64';
|
||||||
|
|
||||||
|
describe('base64', () => {
|
||||||
|
describe('decode', () => {
|
||||||
|
it('returns a uint8 array', () => {
|
||||||
|
const arr = base64.decode('dGVzdA==');
|
||||||
|
expect(arr).toEqual(new Uint8Array([116, 101, 115, 116]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
10
app/javascript/mastodon/utils/base64.js
Normal file
10
app/javascript/mastodon/utils/base64.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const decode = base64 => {
|
||||||
|
const rawData = window.atob(base64);
|
||||||
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
outputArray[i] = rawData.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputArray;
|
||||||
|
};
|
66
app/javascript/mastodon/utils/resize_image.js
Normal file
66
app/javascript/mastodon/utils/resize_image.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
const MAX_IMAGE_DIMENSION = 1280;
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default inputFile => new Promise((resolve, reject) => {
|
||||||
|
if (!inputFile.type.match(/image.*/) || inputFile.type === 'image/gif') {
|
||||||
|
resolve(inputFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage(inputFile).then(img => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const { width, height } = img;
|
||||||
|
|
||||||
|
let newWidth, newHeight;
|
||||||
|
|
||||||
|
if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) {
|
||||||
|
resolve(inputFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width > height) {
|
||||||
|
newHeight = height * MAX_IMAGE_DIMENSION / width;
|
||||||
|
newWidth = MAX_IMAGE_DIMENSION;
|
||||||
|
} else if (height > width) {
|
||||||
|
newWidth = width * MAX_IMAGE_DIMENSION / height;
|
||||||
|
newHeight = MAX_IMAGE_DIMENSION;
|
||||||
|
} else {
|
||||||
|
newWidth = MAX_IMAGE_DIMENSION;
|
||||||
|
newHeight = MAX_IMAGE_DIMENSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.width = newWidth;
|
||||||
|
canvas.height = newHeight;
|
||||||
|
|
||||||
|
canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight);
|
||||||
|
|
||||||
|
canvas.toBlob(resolve, inputFile.type);
|
||||||
|
}).catch(reject);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user