From e213b7b609b21bf2577c62a270b357d999140afe Mon Sep 17 00:00:00 2001 From: Aminejv Date: Tue, 26 Oct 2021 17:24:54 +0100 Subject: [PATCH] feat(GlobalCarousel/Jumpers/MoreInfo): add More info jumper --- .../GlobalCarousel/jumpers/MoreInfo.js | 444 ++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 components/system/components/GlobalCarousel/jumpers/MoreInfo.js diff --git a/components/system/components/GlobalCarousel/jumpers/MoreInfo.js b/components/system/components/GlobalCarousel/jumpers/MoreInfo.js new file mode 100644 index 00000000..1915ad81 --- /dev/null +++ b/components/system/components/GlobalCarousel/jumpers/MoreInfo.js @@ -0,0 +1,444 @@ +import * as React from "react"; +import * as Styles from "~/common/styles"; +import * as System from "~/components/system"; +import * as Jumper from "~/components/core/Jumper"; +import * as SVG from "~/common/svg"; +import * as Strings from "~/common/strings"; +import * as Utilities from "~/common/utilities"; +import * as Actions from "~/common/actions"; +import * as Events from "~/common/custom-events"; +import * as UserBehaviors from "~/common/user-behaviors"; +import * as Constants from "~/common/constants"; +import * as FileUtilities from "~/common/file-utilities"; +import * as Validations from "~/common/validations"; +import * as MobileJumper from "~/components/system/components/GlobalCarousel/jumpers/MobileLayout"; + +import { LoaderSpinner } from "~/components/system/components/Loaders"; +import { Show } from "~/components/utility/Show"; +import { css } from "@emotion/react"; +import { useEventListener } from "~/common/hooks"; +import { AnimatePresence, motion } from "framer-motion"; + +const useCoverImgDrop = ({ onUpload, ref }) => { + const [isDropping, setDroppingState] = React.useState(false); + + const handleDragEnter = (e) => (e.preventDefault(), e.stopPropagation(), setDroppingState(true)); + const handleDragLeave = (e) => (e.preventDefault(), e.stopPropagation()); + + const timerRef = React.useRef(); + const handleDragOver = (e) => { + e.preventDefault(); + e.stopPropagation(); + // NOTE(amine): Hack to hide the indicator if the user drags files outside of the drop zone + clearTimeout(timerRef.current); + timerRef.current = setTimeout(() => { + setDroppingState(false); + }, 100); + }; + + const handleDrop = async (e) => { + e.preventDefault(); + e.stopPropagation(); + + const { files, error } = await FileUtilities.formatDroppedFiles({ + dataTransfer: e.dataTransfer, + }); + + if (error) return null; + + const coverImg = files[0]; + onUpload(coverImg); + }; + + useEventListener({ type: "dragenter", handler: handleDragEnter, ref }, []); + useEventListener({ type: "dragleave", handler: handleDragLeave, ref }, []); + useEventListener({ type: "dragover", handler: handleDragOver, ref }, []); + useEventListener({ type: "drop", handler: handleDrop, ref }, []); + return { isDroppingCoverImg: isDropping }; +}; + +const useCoverImgUpload = ({ file, viewer }) => { + const [isUploading, setUploadingState] = React.useState(false); + + const handleCoverImgUpload = async (coverImg) => { + let previousCoverId = file.coverImage?.id; + setUploadingState(true); + let coverImage = await UserBehaviors.uploadImage(coverImg); + if (!coverImage) { + setUploadingState(false); + return; + } + + //TODO(martina): create an endpoint specifically for cover images instead of this, which will delete original cover image etc + let updateReponse = await Actions.updateFile({ + id: file.id, + coverImage, + }); + + setUploadingState(false); + if (Events.hasError(updateReponse)) return; + + if (previousCoverId) { + if (!viewer.library.some((obj) => obj.id === previousCoverId)) { + await UserBehaviors.deleteFiles(previousCoverId, true); + } + } + }; + + return [{ isUploadingCoverImg: isUploading }, { handleCoverImgUpload: handleCoverImgUpload }]; +}; + +const STYLES_FILE_HIDDEN = css` + height: 1px; + width: 1px; + opacity: 0; + visibility: hidden; + position: fixed; + top: -1px; + left: -1px; +`; + +const STYLES_IMAGE_PREVIEW = (theme) => css` + width: 200px; + height: 200px; + border-radius: 16px; + margin-top: 8px; + box-shadow: ${theme.shadow.lightSmall}; + border: 1px solid ${theme.semantic.borderGrayLight}; + overflow: hidden; + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + @media (max-width: ${theme.sizes.mobile}px) { + width: 100%; + } +`; + +const STYLES_COVER_IMG_DROP = (theme) => css` + ${Styles.CONTAINER_CENTERED}; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 16px; + background-color: ${theme.semantic.bgWhite}; + border: 1px solid ${theme.semantic.borderGrayLight}; + @supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) { + -webkit-backdrop-filter: blur(75px); + backdrop-filter: blur(75px); + background-color: ${theme.semantic.bgBlurWhiteTRN}; + } +`; + +function CoverImageUpload({ file, viewer, isMobile, isFileOwner }) { + const { coverImage } = file; + + const [{ isUploadingCoverImg }, { handleCoverImgUpload }] = useCoverImgUpload({ + file, + viewer, + }); + + const coverImgDropzoneRef = React.useRef(); + const { isDroppingCoverImg } = useCoverImgDrop({ + onUpload: handleCoverImgUpload, + ref: coverImgDropzoneRef, + }); + + const handleInputChange = (e) => { + e.persist(); + if (!e || !e.target) return; + + handleCoverImgUpload(e.target.files[0]); + }; + + return ( + + ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +function ImageDimension({ file }) { + const [dimensions, setDimensions] = React.useState(); + const url = Strings.getURLfromCID(file.cid); + + React.useEffect(() => { + const img = new Image(); + img.src = url; + img.onload = () => { + setDimensions({ width: img.naturalWidth, height: img.naturalHeight }); + }; + }, [url]); + + return dimensions ? dimensions.width + " x " + dimensions.height : null; +} + +function FileMetadata({ file, ...props }) { + return ( +
+ Object info +
+ Type + {Strings.capitalize(file.type)} +
+ +
+ Size + {Strings.bytesToSize(file.size, 0)} +
+ {Validations.isPreviewableImage(file?.type || "") ? ( + <> + +
+ Dimension + + + +
+ + ) : null} + +
+ Created + {Utilities.formatDateToString(file.createdAt)} +
+ +
+ CID + + {file.cid} + +
+
+ ); +} + +/* -----------------------------------------------------------------------------------------------*/ + +const useFileDownload = ({ file, viewer, downloadRef }) => { + const [isDownloading, setDownloadingState] = React.useState(false); + const handleDownload = async () => { + if (!viewer) { + Events.dispatchCustomEvent({ name: "slate-global-open-cta", detail: {} }); + return; + } + setDownloadingState(true); + const response = await UserBehaviors.download(file, downloadRef); + setDownloadingState(false); + Events.hasError(response); + }; + + return [isDownloading, handleDownload]; +}; + +function DownloadButton({ file, viewer, ...props }) { + /**NOTE(amine): UserBehaviors.download creates a link and clicks it to trigger a download, + which triggers the Boundary component and closes the jumper. + To fix this we create the link inside the downloadRef element */ + const downloadRef = React.useRef(); + const [isDownloading, handleDownload] = useFileDownload({ file, viewer, downloadRef }); + + return !file.isLink ? ( +
+ + Download + +
+ ) : null; +} + +/* -----------------------------------------------------------------------------------------------*/ + +const STYLES_DOWNLOAD_SECTION = (theme) => css` + ${Styles.CONTAINER_CENTERED}; + justify-content: flex-end; + background-color: ${theme.semantic.bgWhite}; + @supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) { + -webkit-backdrop-filter: blur(75px); + backdrop-filter: blur(75px); + background-color: ${theme.semantic.bgBlurLight}; + } +`; + +export function MoreInfo({ external, viewer, isOwner, file, isOpen, onClose }) { + const isFileOwner = !external && isOwner && viewer; + + return isOpen ? ( + + More info + + + + + + + + + + + ) : null; +} + +export function MoreInfoMobile({ external, viewer, isOwner, file, isOpen, onClose }) { + const isFileOwner = !external && isOwner && viewer; + + return isOpen ? ( + + + + More Info + + + +
+ +
+ + + + + + + +
+ +
+
+
+ ) : null; +}