Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Product media fixes #247

Merged
merged 8 commits into from
Apr 1, 2020
43 changes: 39 additions & 4 deletions imports/plugins/core/ui/client/components/media/mediaUploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from "react";
import PropTypes from "prop-types";
import gql from "graphql-tag";
import { useDropzone } from "react-dropzone";
import decodeOpaqueId from "@reactioncommerce/api-utils/decodeOpaqueId.js";
import { useMutation } from "@apollo/react-hooks";
import Button from "@reactioncommerce/catalyst/Button";
import LinearProgress from "@material-ui/core/LinearProgress";
Expand All @@ -25,14 +26,13 @@ const createMediaRecordMutation = gql`
* @returns {Node} React component
*/
function MediaUploader(props) {
const { canUploadMultiple, metadata, onError, onFiles, shopId } = props;
const { canUploadMultiple, metadata, onError, onFiles, refetchProduct, shopId } = props;

const [isUploading, setIsUploading] = useState(false);
const [createMediaRecord] = useMutation(createMediaRecordMutation, { ignoreResults: true });

const uploadFiles = (acceptedFiles) => {
const filesArray = Array.from(acceptedFiles);

setIsUploading(true);

const promises = filesArray.map(async (browserFile) => {
Expand All @@ -59,9 +59,43 @@ function MediaUploader(props) {
});
});


Promise.all(promises)
.then(() => {
setIsUploading(false);
.then((responses) => {
// NOTE: This is a temporary workaround due to the fact that on the server,
// the sharp library generates product images in an async manner.
// A better solution would be to use subscriptions
const uploadedMediaIds = responses.map((response) => response.data.createMediaRecord.mediaRecord._id);

// Poll server every half a second to determine if all media has been successfully processed
let isAllMediaProcessed = false;
const timerId = setInterval(async () => {
const { data: { product } } = await refetchProduct();
const productMedia = [];
product.media.forEach((media) => {
const { id } = decodeOpaqueId(media._id);
productMedia.push({ id, thumbnailUrl: media.URLs.small });
});

isAllMediaProcessed = uploadedMediaIds.every((uploadedMediaId) => {
const mediaItem = productMedia.find((item) => item.id === uploadedMediaId);

// If a url has been generated, then these media items has been processed successfully.
return mediaItem && mediaItem.thumbnailUrl !== String(null);
});

if (isAllMediaProcessed) {
setIsUploading(false);
clearTimeout(timerId);
}
}, 500);
willopez marked this conversation as resolved.
Show resolved Hide resolved

// Stop polling after 20 seconds
setTimeout(() => {
clearTimeout(timerId);
setIsUploading(false);
}, 20000);

willopez marked this conversation as resolved.
Show resolved Hide resolved
return null;
})
.catch((error) => {
Expand Down Expand Up @@ -108,6 +142,7 @@ MediaUploader.propTypes = {
metadata: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
onError: PropTypes.func,
onFiles: PropTypes.func,
refetchProduct: PropTypes.func,
shopId: PropTypes.string
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import gql from "graphql-tag";
import PropTypes from "prop-types";
import Logger from "/client/modules/logger";
Expand All @@ -10,6 +10,9 @@ import TableHead from "@material-ui/core/TableHead";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import { useConfirmDialog } from "@reactioncommerce/catalyst";
import { useSnackbar } from "notistack";
import useProduct from "../hooks/useProduct";
import ProductMediaItem from "./ProductMediaItem";

const archiveMediaRecordMutation = gql`
Expand Down Expand Up @@ -46,35 +49,52 @@ function ProductMediaGallery(props) {
variantId
} = props;

const { enqueueSnackbar } = useSnackbar();
const { refetchProduct } = useProduct();
const [mediaItemToRemove, setMediaItemToRemove] = useState(null);
const [archiveMediaRecord] = useMutation(archiveMediaRecordMutation, { ignoreResults: true });
const [updateMediaRecordPriority] = useMutation(updateMediaRecordPriorityMutation, { ignoreResults: true });

const handleRemoveMedia = (mediaToRemove) => {
const imageUrl = mediaToRemove.URLs.medium;
Alerts.alert({
title: "Remove Media?",
type: "warning",
showCancelButton: true,
imageUrl,
imageHeight: 150
}, async (isConfirm) => {
if (isConfirm) {
archiveMediaRecord({
variables: {
input: {
mediaRecordId: mediaToRemove._id,
shopId
}
},
onError(error) {
Logger.error(error);
Alerts.toast("Unable to remove media", "error", {
autoHide: 10000
});
}
});
const handleRemoveMedia = async () => {
await archiveMediaRecord({
variables: {
input: {
mediaRecordId: mediaItemToRemove._id,
shopId
}
},
optimisticResponse: {
__typename: "Mutation",
archiveMediaRecord: {
id: mediaItemToRemove._id,
__typename: "MediaRecord",
mediaRecord: null
}
},
onError(error) {
Logger.error(error);
enqueueSnackbar("Unable to remove media", { variant: "error" });
}
});

// Re-fetch product data
refetchProduct();
};

const {
openDialog: openRemoveMediaDialog,
ConfirmDialog: RemoveMediaConfirmDialog
} = useConfirmDialog({
title: "Remove Media",
message: "Are you sure you want to remove this media item?",
onConfirm: () => {
handleRemoveMedia();
}
});

const confirmRemoveMediaItem = (mediaItem) => {
setMediaItemToRemove(mediaItem);
openRemoveMediaDialog();
};

const handleSetMediaPriority = async (mediaRecord, priority) => {
Expand All @@ -88,14 +108,11 @@ function ProductMediaGallery(props) {
},
onError(error) {
Logger.error(error);
Alerts.toast("Unable to update media priority", "error", {
autoHide: 10000
});
enqueueSnackbar("Unable to update media priority", { variant: "error" });
}
});
};


let count = (Array.isArray(media) && media.length) || 0;
const hasMedia = count > 0;

Expand All @@ -109,7 +126,7 @@ function ProductMediaGallery(props) {
};

const onUploadError = (error) => {
Alerts.toast(error.reason || error.message, "error");
enqueueSnackbar(error.reason || error.message, { variant: "error" });
};

return (
Expand All @@ -128,7 +145,7 @@ function ProductMediaGallery(props) {
<ProductMediaItem
editable={editable}
key={mediaItem._id}
onRemoveMedia={handleRemoveMedia}
onRemoveMedia={confirmRemoveMediaItem}
onSetMediaPriority={handleSetMediaPriority}
size="small"
source={mediaItem}
Expand All @@ -142,13 +159,15 @@ function ProductMediaGallery(props) {
canUploadMultiple
metadata={getFileMetadata}
onError={onUploadError}
refetchProduct={refetchProduct}
shopId={shopId}
/>
</TableCell>
</TableRow>
}
</TableBody>
</Table>
<RemoveMediaConfirmDialog />
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const useStyles = makeStyles(() => ({
height: 100
},
priorityField: {
width: 120
width: 70
}
}));

Expand All @@ -38,12 +38,15 @@ function ProductMediaItem(props) {
size,
source
} = props;

const classes = useStyles();
const [priority, setPriority] = useState(source.priority);


let imageSrc = source.URLs[size];

// If there is no img src, then render nothing
if (imageSrc === String(null)) return null;

if (imageSrc) {
imageSrc = `${filesBaseUrl}${imageSrc}`;
} else {
Expand All @@ -69,9 +72,8 @@ function ProductMediaItem(props) {
onChange={(event) => {
setPriority(() => {
const intValue = parseInt(event.target.value, 10);
return {
priority: isInteger(intValue) ? intValue : null
};
const newPriority = isInteger(intValue) ? intValue : null;
return newPriority;
});
}}
/>
Expand All @@ -83,7 +85,6 @@ function ProductMediaItem(props) {
src={imageSrc}
/>
</TableCell>

<TableCell align="right">
<IconButton
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ export default gql`
}
minOrderQuantity
optionTitle
media {
_id
URLs {
original
small
}
priority
}
originCountry
pricing {
compareAtPrice {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export default gql`
key
value
}
media {
_id
URLs {
small
}
priority
}
originCountry
pageTitle
productType
Expand Down