aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json7
-rw-r--r--package.json3
-rw-r--r--src/components/AttachLink/AttachLink.tsx2
-rw-r--r--src/components/FileUpload/FileUpload.tsx36
-rw-r--r--src/containers/Feed/PollSubmission.tsx102
-rw-r--r--src/containers/Feed/PollSubmissionImage.tsx95
-rw-r--r--src/containers/PollCreation/ImageInput.tsx10
-rw-r--r--src/containers/PollCreation/PollCreation.tsx25
-rw-r--r--src/containers/PollCreation/useS3Preupload.ts29
-rw-r--r--src/containers/Profile/ProfileInfo.tsx77
-rw-r--r--src/hooks/useS3Preupload.tsx85
-rw-r--r--src/utils/getLocalFileUrl.ts15
-rw-r--r--src/utils/uploadFileToS3.ts53
13 files changed, 185 insertions, 354 deletions
diff --git a/package-lock.json b/package-lock.json
index 0e86c15..9b4ef60 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "which",
- "version": "1.2.3",
+ "version": "1.2.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -10970,11 +10970,6 @@
"tiny-warning": "^1.0.0"
}
},
- "react-sage": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/react-sage/-/react-sage-0.1.2.tgz",
- "integrity": "sha512-tJk2SATS0Czg5a3Sae4ilvv2W0FedBuvPFKi81qgAO7cIzn5/p1EArOYcRvGzinJVFP3/4l0Uz4gF95cx3cVhg=="
- },
"react-scripts": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.1.tgz",
diff --git a/package.json b/package.json
index 457fa4f..abfcc76 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "which",
- "version": "1.2.3",
+ "version": "1.2.4",
"homepage": "https://which-ecosystem.github.io/",
"dependencies": {
"@material-ui/core": "^4.10.1",
@@ -16,7 +16,6 @@
"react-error-boundary": "^2.3.1",
"react-icons": "^3.10.0",
"react-router-dom": "^5.2.0",
- "react-sage": "^0.1.2",
"react-scripts": "3.4.1",
"react-virtualized": "^9.21.2",
"swr": "^0.3.0",
diff --git a/src/components/AttachLink/AttachLink.tsx b/src/components/AttachLink/AttachLink.tsx
index e73f5c1..742ba65 100644
--- a/src/components/AttachLink/AttachLink.tsx
+++ b/src/components/AttachLink/AttachLink.tsx
@@ -25,7 +25,7 @@ const AttachLink: React.FC<PropTypes> = ({ callback, children }) => {
</Button>
);
- const child = children && React.Children.toArray(children)[0];
+ const child = children && React.Children.only(children);
return (
<>
diff --git a/src/components/FileUpload/FileUpload.tsx b/src/components/FileUpload/FileUpload.tsx
index 67d280d..961fa9a 100644
--- a/src/components/FileUpload/FileUpload.tsx
+++ b/src/components/FileUpload/FileUpload.tsx
@@ -1,28 +1,29 @@
-import React, { useEffect } from 'react';
-import { useFilePicker, utils } from 'react-sage';
+import React, { useRef } from 'react';
import Button from '@material-ui/core/Button';
import CloudUpload from '@material-ui/icons/CloudUpload';
interface PropTypes {
- callback: (fileUrl: string, file: File) => void;
+ callback: (file: File) => void;
}
const FileUpload: React.FC<PropTypes> = ({ callback, children }) => {
- const { files, onClick, HiddenFileInput } = useFilePicker();
+ const inputRef = useRef<HTMLInputElement>(null);
- useEffect(() => {
- if (files?.length) {
- const file = files[0];
- utils.loadFile(file).then(url => callback(url, file));
- }
- }, [files, callback]);
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ const files = event.target?.files;
+ if (files?.length) callback(files[0]);
+ };
- const child = children && React.Children.toArray(children)[0];
+ const handleClick = () => {
+ if (inputRef?.current) inputRef.current.click();
+ };
+
+ const child = children && React.Children.only(children);
const defaultButton = (
<Button
- onClick={onClick}
+ onClick={handleClick}
variant="contained"
color="primary"
size="large"
@@ -34,10 +35,17 @@ const FileUpload: React.FC<PropTypes> = ({ callback, children }) => {
return (
<>
- <HiddenFileInput accept=".jpg, .jpeg, .png, .gif" multiple={false} />
+ <input
+ type="file"
+ ref={inputRef}
+ multiple={false}
+ accept=".jpg, .jpeg, .png, .gif"
+ style={{ display: 'none' }}
+ onChange={handleChange}
+ />
{
React.isValidElement(child)
- ? React.cloneElement(child, { onClick })
+ ? React.cloneElement(child, { onClick: handleClick })
: defaultButton
}
</>
diff --git a/src/containers/Feed/PollSubmission.tsx b/src/containers/Feed/PollSubmission.tsx
deleted file mode 100644
index faaf2bd..0000000
--- a/src/containers/Feed/PollSubmission.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import React, { useState } from 'react';
-import { makeStyles } from '@material-ui/core/styles';
-import Collapse from '@material-ui/core/Collapse';
-import {
- Button,
- Card,
- ClickAwayListener,
- Divider
-} from '@material-ui/core';
-import { Poll } from 'which-types';
-import { useSnackbar } from 'notistack';
-import axios from 'axios';
-
-import PollSubmissionImage from './PollSubmissionImage';
-import UserStrip from '../../components/UserStrip/UserStrip';
-import { get, post } from '../../requests';
-import { useAuth } from '../../hooks/useAuth';
-
-interface PropTypes{
- addPoll: (poll: Poll) => void;
-}
-
-const useStyles = makeStyles(theme => ({
- root: {
- marginBottom: theme.spacing(4)
- },
- images: {
- height: theme.spacing(50),
- display: 'flex'
- }
-}));
-
-const PollSubmission: React.FC<PropTypes> = ({ addPoll }) => {
- const classes = useStyles();
- const [expanded, setExpanded] = useState(false);
- const [left, setLeft] = useState<File>();
- const [right, setRight] = useState<File>();
- const { enqueueSnackbar } = useSnackbar();
- const { user } = useAuth();
-
- const readyToSubmit = left && right;
-
- const handleClickAway = () => {
- setExpanded(false);
- };
-
- const uploadImage = (file?: File) => {
- const headers = { 'Content-Type': 'image/png' };
- return get('/files')
- .then(response => response.data)
- .then(uploadUrl => axios.put(uploadUrl, file, { headers }))
- .then(response => {
- const { config: { url } } = response;
- return url && url.slice(0, url.indexOf('.png') + 4);
- });
- };
-
- const handleClick = async () => {
- if (expanded && readyToSubmit) {
- const [leftUrl, rightUrl] = await Promise.all([uploadImage(left), uploadImage(right)]);
-
- const contents = {
- left: { url: leftUrl },
- right: { url: rightUrl }
- };
-
- post('/polls/', { contents }).then(response => {
- addPoll(response.data);
- enqueueSnackbar('Your poll has been successfully created!', {
- variant: 'success'
- });
- });
- }
- setExpanded(!expanded);
- };
-
- return (
- <ClickAwayListener onClickAway={handleClickAway}>
- <Card className={classes.root}>
- <Collapse in={expanded} timeout="auto" unmountOnExit>
- {user && <UserStrip user={user} info="" />}
- <Divider />
- <div className={classes.images}>
- <PollSubmissionImage file={left} setFile={setLeft} />
- <PollSubmissionImage file={right} setFile={setRight} />
- </div>
- </Collapse>
- <Button
- color="primary"
- disabled={expanded && !readyToSubmit}
- variant={expanded ? 'contained' : 'outlined'}
- onClick={handleClick}
- fullWidth
- >
- {expanded ? 'Submit' : 'Create a Poll'}
- </Button>
- </Card>
- </ClickAwayListener>
- );
-};
-
-export default PollSubmission;
diff --git a/src/containers/Feed/PollSubmissionImage.tsx b/src/containers/Feed/PollSubmissionImage.tsx
deleted file mode 100644
index cd67847..0000000
--- a/src/containers/Feed/PollSubmissionImage.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useFilePicker, utils } from 'react-sage';
-import { makeStyles } from '@material-ui/core/styles';
-import CloudUploadIcon from '@material-ui/icons/CloudUpload';
-import { CardActionArea, CardMedia, Typography } from '@material-ui/core';
-import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined';
-
-interface PropTypes {
- file: File | undefined;
- setFile: (file: File | undefined) => void;
-}
-
-const useStyles = makeStyles({
- root: {
- display: 'flex',
- justifyContent: 'center',
- flexDirection: 'column',
- alignItems: 'center'
- },
- clearIcon: {
- opacity: '.5',
- fontSize: 50
- },
- media: {
- height: '100%',
- width: '100%',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center'
- },
- text: {
- textAlign: 'center'
- }
-});
-
-
-const PollSubmissionImage: React.FC<PropTypes> = ({ file, setFile }) => {
- const classes = useStyles();
- const { files, onClick, HiddenFileInput } = useFilePicker();
- const [url, setUrl] = useState<string>();
- const [isMediaHover, setIsMediaHover] = useState(false);
-
- const handleMouseEnter = (): void => {
- setIsMediaHover(true);
- };
-
- const handleMouseLeave = (): void => {
- setIsMediaHover(false);
- };
-
- useEffect(() => {
- if (files?.length) {
- const chosenFile = files[0];
- setFile(chosenFile);
- utils.loadFile(chosenFile).then(result => setUrl(result));
- }
- }, [files, setFile]);
-
-
- const handleClick = () => {
- if (file) {
- setFile(undefined);
- } else onClick();
- };
-
-
- const Upload = (
- <>
- <CloudUploadIcon fontSize="large" color="primary" />
- <Typography variant="h5" className={classes.text}> Upload an image </Typography>
- <HiddenFileInput accept=".jpg, .jpeg, .png, .gif" multiple={false} />
- </>
- );
-
- const Media = (
- <CardMedia
- image={url}
- className={classes.media}
- onMouseEnter={handleMouseEnter}
- onMouseLeave={handleMouseLeave}
- >
- {isMediaHover && <CancelOutlinedIcon className={classes.clearIcon} />}
- </CardMedia>
- );
-
- return (
- <>
- <CardActionArea onClick={handleClick} className={classes.root}>
- {file ? (url && Media) : Upload}
- </CardActionArea>
- </>
- );
-};
-
-export default PollSubmissionImage;
diff --git a/src/containers/PollCreation/ImageInput.tsx b/src/containers/PollCreation/ImageInput.tsx
index 475d527..e807865 100644
--- a/src/containers/PollCreation/ImageInput.tsx
+++ b/src/containers/PollCreation/ImageInput.tsx
@@ -10,6 +10,7 @@ import { Check, CancelOutlined } from '@material-ui/icons';
import AttachLink from '../../components/AttachLink/AttachLink';
import FileUpload from '../../components/FileUpload/FileUpload';
import BackgroundImage from '../../components/Image/BackgroundImage';
+import getLocalFileUrl from '../../utils/getLocalFileUrl';
interface PropTypes {
callback: (file?: File | string) => void;
@@ -59,9 +60,12 @@ const ImageInput: React.FC<PropTypes> = ({ callback, progress }) => {
callback(undefined);
};
- const childrenCallback = (fileUrl: string, file?: File) => {
- setUrl(fileUrl);
- callback(file || fileUrl);
+ const childrenCallback = (value: File | string) => {
+ if (value instanceof File) {
+ getLocalFileUrl(value).then(localUrl => setUrl(localUrl));
+ } else setUrl(value);
+
+ callback(value);
};
const Upload = (
diff --git a/src/containers/PollCreation/PollCreation.tsx b/src/containers/PollCreation/PollCreation.tsx
index 03ab905..ecc6757 100644
--- a/src/containers/PollCreation/PollCreation.tsx
+++ b/src/containers/PollCreation/PollCreation.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable */
import React from 'react';
import Bluebird from 'bluebird';
import { useHistory } from 'react-router-dom';
@@ -12,12 +11,12 @@ import {
} from '@material-ui/core';
import { useSnackbar } from 'notistack';
+import useS3Preupload from './useS3Preupload';
import ImageInput from './ImageInput';
import UserStrip from '../../components/UserStrip/UserStrip';
import { post } from '../../requests';
import { useAuth } from '../../hooks/useAuth';
import { useFeed } from '../../hooks/APIClient';
-import useS3Preupload from '../../hooks/useS3Preupload';
const useStyles = makeStyles(theme => ({
@@ -38,23 +37,21 @@ const PollCreation: React.FC = () => {
const { user } = useAuth();
const { mutate: updateFeed } = useFeed();
const {
- setValue: setLeft,
- progress: progressLeft,
+ file: left,
+ setFile: setLeft,
resolve: resolveLeft,
- isReady: isLeftReady
+ progress: leftProgress
} = useS3Preupload();
const {
- setValue: setRight,
- progress: progressRight,
+ file: right,
+ setFile: setRight,
resolve: resolveRight,
- isReady: isRightReady
+ progress: rightProgress
} = useS3Preupload();
const handleClick = async () => {
try {
const [leftUrl, rightUrl] = await Bluebird.all([resolveLeft(), resolveRight()]);
- console.log('leftUrl', leftUrl);
- console.log('rightUrl', rightUrl);
const contents = {
left: { url: leftUrl },
@@ -79,16 +76,16 @@ const PollCreation: React.FC = () => {
{user && <UserStrip user={user} info="" />}
<Divider />
<div className={classes.images}>
- <ImageInput callback={setLeft} progress={progressLeft} />
- <ImageInput callback={setRight} progress={progressRight} />
+ <ImageInput callback={setLeft} progress={leftProgress} />
+ <ImageInput callback={setRight} progress={rightProgress} />
</div>
{
- progressLeft || progressRight
+ leftProgress || rightProgress
? <LinearProgress color="primary" />
: (
<Button
color="primary"
- disabled={!(isLeftReady && isRightReady)}
+ disabled={!(left && right)}
variant="contained"
onClick={handleClick}
fullWidth
diff --git a/src/containers/PollCreation/useS3Preupload.ts b/src/containers/PollCreation/useS3Preupload.ts
new file mode 100644
index 0000000..0e2a8c4
--- /dev/null
+++ b/src/containers/PollCreation/useS3Preupload.ts
@@ -0,0 +1,29 @@
+import { useState, useCallback } from 'react';
+import uploadFileToS3 from '../../utils/uploadFileToS3';
+
+interface Hook {
+ file: File | string | undefined;
+ setFile: (value: File | string | undefined) => void;
+ resolve: () => Promise<string>;
+ progress: number;
+}
+
+export default (): Hook => {
+ const [file, setFile] = useState<File | string>();
+ const [progress, setProgress] = useState<number>(0);
+
+ const resolve = useCallback(async (quality?: number): Promise<string> => {
+ // Indicate start
+ setProgress(0.01);
+
+ if (file instanceof File) return uploadFileToS3(file, quality, setProgress);
+ return file || '';
+ }, [file]);
+
+ return {
+ file,
+ setFile,
+ resolve,
+ progress
+ };
+};
diff --git a/src/containers/Profile/ProfileInfo.tsx b/src/containers/Profile/ProfileInfo.tsx
index c9831f3..e2fb0a9 100644
--- a/src/containers/Profile/ProfileInfo.tsx
+++ b/src/containers/Profile/ProfileInfo.tsx
@@ -1,16 +1,16 @@
-import React from 'react';
-import { Badge, Typography } from '@material-ui/core/';
+import React, { useState, useCallback } from 'react';
+import { Badge, Typography, CircularProgress } from '@material-ui/core/';
+import { CameraAlt, CheckCircleOutline } from '@material-ui/icons/';
import { makeStyles } from '@material-ui/core/styles';
-import { User } from 'which-types';
-import CameraAltIcon from '@material-ui/icons/CameraAlt';
-import VerifiedIcon from '@material-ui/icons/CheckCircleOutline';
import Skeleton from '@material-ui/lab/Skeleton';
+import { User } from 'which-types';
+
import Highlight from './Highlight';
-import AttachLink from '../../components/AttachLink/AttachLink';
+import FileUpload from '../../components/FileUpload/FileUpload';
import Avatar from '../../components/Avatar/Avatar';
import { patch } from '../../requests';
import { useAuth } from '../../hooks/useAuth';
-
+import uploadFileToS3 from '../../utils/uploadFileToS3';
interface PropTypes {
savedPolls: number;
@@ -66,7 +66,9 @@ const useStyles = makeStyles(theme => ({
},
avatarContainer: {
position: 'relative',
- textAlign: 'center'
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center'
},
menuNumber: {
fontWeight: 800,
@@ -78,8 +80,11 @@ const useStyles = makeStyles(theme => ({
skeleton: {
margin: '10px auto',
borderRadius: 2
+ },
+ progress: {
+ position: 'absolute',
+ color: 'white'
}
-
}));
@@ -88,14 +93,18 @@ const ProfileInfo: React.FC<PropTypes> = ({
}) => {
const classes = useStyles();
const { user } = useAuth();
+ const [progress, setProgress] = useState<number>(0);
+
const dateSince = new Date(userInfo?.createdAt || '').toLocaleDateString();
- const patchAvatar = (url: string) => {
- const id = user?._id;
- patch(`/users/${id}`, { avatarUrl: url }).then(res => {
- setUserInfo(res.data);
- });
- };
+ const handleUpdateAvatar = useCallback(async (file: File) => {
+ if (user) {
+ uploadFileToS3(file, 0.8, setProgress)
+ .then(avatarUrl => patch(`/users/${user._id}`, { avatarUrl }))
+ .then(response => setUserInfo(response.data))
+ .then(() => setProgress(0));
+ }
+ }, [user, setUserInfo]);
return (
<div className={classes.root}>
@@ -104,24 +113,28 @@ const ProfileInfo: React.FC<PropTypes> = ({
? <Skeleton animation="wave" variant="circle" width={150} height={150} className={classes.avatar} />
: userInfo?._id === user?._id
? (
- <AttachLink callback={patchAvatar}>
- <div className={classes.avatarContainer}>
- <Badge
- overlap="circle"
- anchorOrigin={{
- vertical: 'bottom',
- horizontal: 'right'
- }}
- badgeContent={(
+ <div className={classes.avatarContainer}>
+ <Badge
+ overlap="circle"
+ anchorOrigin={{
+ vertical: 'bottom',
+ horizontal: 'right'
+ }}
+ className={classes.avatarContainer}
+ badgeContent={(
+ <FileUpload callback={handleUpdateAvatar}>
<div className={classes.badge}>
- <CameraAltIcon />
+ <CameraAlt />
</div>
- )}
- >
- <Avatar className={classes.avatar} user={userInfo} />
- </Badge>
- </div>
- </AttachLink>
+ </FileUpload>
+ )}
+ >
+ <Avatar className={classes.avatar} user={userInfo} />
+ {progress > 0 && (
+ <CircularProgress variant="static" value={progress} className={classes.progress} />
+ )}
+ </Badge>
+ </div>
)
: <Avatar className={classes.avatar} user={userInfo} />
}
@@ -131,7 +144,7 @@ const ProfileInfo: React.FC<PropTypes> = ({
: (
<Typography variant="h5" className={classes.name}>
{userInfo?.username}
- {userInfo?.verified && <VerifiedIcon className={classes.verified} color="primary" />}
+ {userInfo?.verified && <CheckCircleOutline className={classes.verified} color="primary" />}
</Typography>
)
}
diff --git a/src/hooks/useS3Preupload.tsx b/src/hooks/useS3Preupload.tsx
deleted file mode 100644
index 651bfb6..0000000
--- a/src/hooks/useS3Preupload.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { useState, useCallback, useMemo } from 'react';
-import axios from 'axios';
-import Bluebird from 'bluebird';
-import Compressor from 'compressorjs';
-import { get } from '../requests';
-
-
-interface ProgressEvent {
- loaded: number;
- total: number;
-}
-
-interface Hook {
- setValue: (value: File | string | undefined) => void;
- isReady: boolean;
- resolve: () => Promise<string>;
- progress: number;
-}
-
-
-const compressFile = (file: File, quality = 0.6): Promise<Blob> => {
- return new Promise((resolve, reject) => {
- return new Compressor(file, {
- success: result => resolve(result),
- error: err => reject(err),
- quality
- });
- });
-};
-
-export default (): Hook => {
- const [url, setUrl] = useState<string>();
- const [file, setFile] = useState<File>();
- const [progress, setProgress] = useState<number>(0);
-
- const isReady = useMemo(() => Boolean(file || url), [file, url]);
-
- const setValue: Hook['setValue'] = useCallback(value => {
- if (value instanceof File) {
- setFile(value);
- setUrl(undefined);
- } else {
- setUrl(value);
- setFile(undefined);
- }
- }, [setUrl, setFile]);
-
- const handleUploadProgress = useCallback((progressEvent: ProgressEvent): void => {
- // Only allow upload progress reach 95%, and set 100% when request is resolved
- setProgress(Math.round((progressEvent.loaded * 95) / progressEvent.total));
- }, [setProgress]);
-
- const resolve = useCallback(async (quality?: number): Promise<string> => {
- if (file) {
- const config = {
- headers: { 'Content-Type': 'image/png' },
- onUploadProgress: handleUploadProgress
- };
-
- setProgress(0.01);
-
- // Add querystring to avoid caching request in some browsers, see
- // https://stackoverflow.com/questions/59339561/safari-skipping-xmlhttprequests
- return Bluebird.all([get(`/files?key=${file.name}`), compressFile(file, quality)])
- .then(([response, compressedFile]) => {
- const uploadUrl = response.data;
- return axios.put(uploadUrl, compressedFile, config);
- })
- .then(response => {
- setProgress(100);
- const uri = response.config.url;
- return uri ? uri.slice(0, uri.indexOf('?')) : '';
- });
- }
- setProgress(100);
- return url || '';
- }, [file, handleUploadProgress, url]);
-
- return {
- setValue,
- isReady,
- resolve,
- progress
- };
-};
diff --git a/src/utils/getLocalFileUrl.ts b/src/utils/getLocalFileUrl.ts
new file mode 100644
index 0000000..1469804
--- /dev/null
+++ b/src/utils/getLocalFileUrl.ts
@@ -0,0 +1,15 @@
+export default (file: File): Promise<string> => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+
+ reader.addEventListener('load', () => {
+ if (typeof reader.result === 'string') resolve(reader.result);
+ }, false);
+
+ reader.addEventListener('error', () => {
+ reject(new Error('Error reading the file'));
+ }, false);
+
+ if (file) reader.readAsDataURL(file);
+ });
+};
diff --git a/src/utils/uploadFileToS3.ts b/src/utils/uploadFileToS3.ts
new file mode 100644
index 0000000..d91dac5
--- /dev/null
+++ b/src/utils/uploadFileToS3.ts
@@ -0,0 +1,53 @@
+import Compressor from 'compressorjs';
+import axios from 'axios';
+import Bluebird from 'bluebird';
+import { get } from '../requests';
+
+interface ProgressEvent {
+ loaded: number;
+ total: number;
+}
+
+type SetProgress = (progress: number) => void;
+
+
+const compressFile = (file: File, quality = 0.6): Promise<File | Blob> => {
+ return new Promise((resolve, reject) => {
+ if (quality === 1) return resolve(file);
+ return new Compressor(file, {
+ success: result => resolve(result),
+ error: err => reject(err),
+ quality
+ });
+ });
+};
+
+export default (file: File, quality?: number, setProgress?: SetProgress): Promise<string> => {
+ const onUploadProgress = (progressEvent: ProgressEvent): void => {
+ if (setProgress) {
+ const { loaded, total } = progressEvent;
+ // Only allow upload progress reach 95%, and set 100% when request is resolved
+ const progress = Math.round((loaded * 95) / total);
+ setProgress(progress);
+ }
+ };
+
+ const config = {
+ headers: { 'Content-Type': 'image/png' },
+ onUploadProgress
+ };
+
+ // Add querystring to avoid caching request in some browsers, see
+ // https://stackoverflow.com/questions/59339561/safari-skipping-xmlhttprequests
+ return Bluebird.all([get(`/files?key=${file.name}`), compressFile(file, quality)])
+ .then(([response, compressedFile]) => {
+ const uploadUrl = response.data;
+ return axios.put(uploadUrl, compressedFile, config);
+ })
+ .then(response => {
+ if (setProgress) setProgress(100);
+ const uri = response.config.url;
+ return uri ? uri.slice(0, uri.indexOf('?')) : '';
+ });
+};
+