diff --git a/common/constants.js b/common/constants.js index 61ba7de2..c586eee0 100644 --- a/common/constants.js +++ b/common/constants.js @@ -209,8 +209,8 @@ export const grids = { mobile: { width: 166, rowGap: 12 }, }, collection: { - desktop: { width: 432, rowGap: 16 }, - mobile: { width: 300, rowGap: 8 }, + desktop: { width: 382, rowGap: 16 }, + mobile: { width: 280, rowGap: 8 }, }, profile: { desktop: { width: 248, rowGap: 16 }, diff --git a/components/core/CollectionPreviewBlock/index.js b/components/core/CollectionPreviewBlock/index.js index 009df195..deaa2b81 100644 --- a/components/core/CollectionPreviewBlock/index.js +++ b/components/core/CollectionPreviewBlock/index.js @@ -1,22 +1,16 @@ import * as React from "react"; -import * as Validations from "~/common/validations"; import * as Typography from "~/components/system/components/Typography"; import * as Styles from "~/common/styles"; -import * as Strings from "~/common/strings"; import * as Constants from "~/common/constants"; import * as SVG from "~/common/svg"; -import { Logo } from "~/common/logo"; -import { useInView } from "~/common/hooks"; -import { isBlurhashValid } from "blurhash"; -import { Blurhash } from "react-blurhash"; import { css } from "@emotion/react"; import { FollowButton } from "~/components/core/CollectionPreviewBlock/components"; import { useFollowHandler } from "~/components/core/CollectionPreviewBlock/hooks"; import { Link } from "~/components/core/Link"; import { motion } from "framer-motion"; - -import ObjectPlaceholder from "~/components/core/ObjectPreview/placeholders"; +import { Preview } from "~/components/core/CollectionPreviewBlock/components"; +import { AspectRatio } from "~/components/system"; const STYLES_CONTAINER = (theme) => css` position: relative; @@ -27,35 +21,25 @@ const STYLES_CONTAINER = (theme) => css` border-radius: 16px; width: 100%; overflow: hidden; - - height: 304px; - @media (max-width: ${theme.sizes.mobile}px) { - height: 281px; - } `; -const STYLES_PREVIEW = css` - flex-grow: 1; - background-size: cover; - overflow: hidden; - img { - height: 100%; - width: 100%; - object-fit: cover; - } -`; - -const STYLES_DESCRIPTION_CONTAINER = (theme) => css` - background-color: ${theme.semantic.bgLight}; - position: absolute; - bottom: 0%; - display: flex; - flex-direction: column; - padding: 9px 16px 12px; +const STYLES_DESCRIPTION = (theme) => css` + position: relative; border-radius: 0px 0px 16px 16px; - box-shadow: 0 -0.5px 0.5px ${theme.system.grayLight4}; + box-sizing: border-box; width: 100%; - margin-top: auto; + background-color: ${theme.semantic.bgLight}; + z-index: 1; +`; + +const STYLES_INNER_DESCRIPTION = (theme) => css` + position: absolute; + top: 0; + left: 0; + width: 100%; + background-color: ${theme.semantic.bgLight}; + padding: 9px 16px 0px; + box-shadow: 0 -0.5px 0.5px ${theme.system.grayLight4}; `; const STYLES_SPACE_BETWEEN = css` @@ -71,26 +55,14 @@ const STYLES_PROFILE_IMAGE = (theme) => css` `; const STYLES_METRICS = css` + position: relative; + z-index: 1; margin-top: auto; + padding: 4px 16px 8px; ${Styles.CONTAINER_CENTERED}; ${STYLES_SPACE_BETWEEN} `; -const STYLES_PLACEHOLDER_CONTAINER = css` - height: 100%; - width: 100%; -`; - -const STYLES_EMPTY_CONTAINER = css` - display: flex; - overflow: hidden; - flex-direction: column; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; -`; - const STYLES_CONTROLS = css` position: absolute; z-index: 1; @@ -104,97 +76,24 @@ const STYLES_TEXT_GRAY = (theme) => css` color: ${theme.semantic.textGray}; `; -const getFileBlurHash = (file) => { - const coverImage = file?.data?.coverImage; - const coverImageBlurHash = coverImage?.data?.blurhash; - if (coverImage && isBlurhashValid(coverImageBlurHash)) return coverImageBlurHash; - - const blurhash = file?.data?.blurhash; - if (isBlurhashValid(blurhash)) return blurhash; - - return null; -}; - -const getObjectToPreview = (objects = []) => { - let objectIdx = 0; - let isImage = false; - - objects.some((object, i) => { - const isPreviewableImage = Validations.isPreviewableImage(object.data.type); - if (isPreviewableImage) (objectIdx = i), (isImage = true); - return isPreviewableImage; - }); - - return { ...objects[objectIdx], isImage }; -}; - -const Preview = ({ collection, children, ...props }) => { - const [isLoading, setLoading] = React.useState(true); - const handleOnLoaded = () => setLoading(false); - - const previewerRef = React.useRef(); - const { isInView } = useInView({ - ref: previewerRef, - }); - - const object = React.useMemo(() => getObjectToPreview(collection.objects), [collection.objects]); - - const isCollectionEmpty = collection.objects.length === 0; - if (isCollectionEmpty) { - return ( -
- {children} - - This collection is empty -
- ); - } - - if (object.isImage) { - const { coverImage } = object.data; - const blurhash = getFileBlurHash(object); - const previewImage = coverImage - ? Strings.getURLfromCID(coverImage?.cid) - : Strings.getURLfromCID(object.cid); - - return ( -
- {children} - {isInView && ( - <> - {isLoading && blurhash && ( - - )} - Collection preview - - )} -
- ); - } - - return ( -
- {children} - -
- ); -}; - export default function CollectionPreview({ collection, viewer, owner, onAction }) { const [areControlsVisible, setShowControls] = React.useState(false); const showControls = () => setShowControls(true); const hideControls = () => setShowControls(false); - const { isDescriptionVisible, showDescription, hideDescription } = useShowDescription(); + const descriptionRef = React.useRef(); + const descriptionHeight = React.useRef(); + React.useEffect(() => { + const element = descriptionRef.current; + if (element) { + descriptionHeight.current = element.offsetHeight; + } + }, []); + const description = collection?.data?.body; + const { isDescriptionVisible, showDescription, hideDescription } = useShowDescription({ + disabled: !description, + }); const { follow, followCount, isFollowed } = useFollowHandler({ collection, viewer }); @@ -202,110 +101,125 @@ export default function CollectionPreview({ collection, viewer, owner, onAction return (
- - - - - - -
- -
- - {collection.slatename} - -
+ + + + + + + + +
+ + {collection.slatename} + + + - - {description || ""} - - -
-
- - - {fileCount} - -
- {owner && ( -
- + {collection.slatename} + + {description && ( +
+ - {`${owner.username} (e.target.src = Constants.profileDefaultPicture)} - /> - - - - {owner.username} - - + {description} +
)} -
- -
+ +
+ +
); } -const useShowDescription = () => { +function Metrics({ fileCount, owner, onAction }) { + return ( +
+
+ + + {fileCount} + +
+ {owner && ( +
+ + {`${owner.username} (e.target.src = Constants.profileDefaultPicture)} + /> + + + + {owner.username} + + +
+ )} +
+ ); +} + +const useShowDescription = ({ disabled }) => { const [isDescriptionVisible, setShowDescription] = React.useState(false); const timeoutId = React.useRef(); const showDescription = () => { + if (disabled) return; + clearTimeout(timeoutId.current); const id = setTimeout(() => setShowDescription(true), 250); timeoutId.current = id; }; const hideDescription = () => { + if (disabled) return; + clearTimeout(timeoutId.current); setShowDescription(false); }; diff --git a/components/core/ObjectPreview/ObjectPreviewPrimitive.js b/components/core/ObjectPreview/ObjectPreviewPrimitive.js index 91e457aa..85aa0e82 100644 --- a/components/core/ObjectPreview/ObjectPreviewPrimitive.js +++ b/components/core/ObjectPreview/ObjectPreviewPrimitive.js @@ -33,6 +33,10 @@ const STYLES_DESCRIPTION = (theme) => css` `; const STYLES_INNER_DESCRIPTION = (theme) => css` + position: absolute; + top: 0; + left: 0; + width: 100%; background-color: ${theme.semantic.bgLight}; padding: 9px 16px 0px; box-shadow: 0 -0.5px 0.5px ${theme.system.grayLight4}; @@ -156,7 +160,6 @@ export default function ObjectPreviewPrimitive({