Merge pull request #851 from filecoin-project/@aminejv/preview-tweaks

Tweaks: Block Previews
This commit is contained in:
martinalong 2021-08-04 17:28:31 -07:00 committed by GitHub
commit a0174ad8f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 147 additions and 114 deletions

View File

@ -18,6 +18,7 @@ export const LINK = css`
const TEXT = css` const TEXT = css`
box-sizing: border-box; box-sizing: border-box;
overflow-wrap: break-word; overflow-wrap: break-word;
text-align: left;
a { a {
${LINK} ${LINK}

View File

@ -14,7 +14,7 @@ import { css } from "@emotion/react";
import { FollowButton } from "~/components/core/CollectionPreviewBlock/components"; import { FollowButton } from "~/components/core/CollectionPreviewBlock/components";
import { useFollowHandler } from "~/components/core/CollectionPreviewBlock/hooks"; import { useFollowHandler } from "~/components/core/CollectionPreviewBlock/hooks";
import { Link } from "~/components/core/Link"; import { Link } from "~/components/core/Link";
import { AnimatePresence, motion } from "framer-motion"; import { motion } from "framer-motion";
import ObjectPlaceholder from "~/components/core/ObjectPreview/placeholders"; import ObjectPlaceholder from "~/components/core/ObjectPreview/placeholders";
@ -46,9 +46,11 @@ const STYLES_PREVIEW = css`
`; `;
const STYLES_DESCRIPTION_CONTAINER = (theme) => css` const STYLES_DESCRIPTION_CONTAINER = (theme) => css`
background-color: ${theme.semantic.bgLight};
position: absolute;
bottom: 0%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative;
padding: 9px 16px 12px; padding: 9px 16px 12px;
border-radius: 0px 0px 16px 16px; border-radius: 0px 0px 16px 16px;
box-shadow: 0 -0.5px 0.5px ${theme.system.grayLight4}; box-shadow: 0 -0.5px 0.5px ${theme.system.grayLight4};
@ -68,11 +70,8 @@ const STYLES_PROFILE_IMAGE = (theme) => css`
object-fit: cover; object-fit: cover;
`; `;
const STYLES_METRICS = (theme) => css` const STYLES_METRICS = css`
margin-top: 7px; margin-top: auto;
@media (max-width: ${theme.sizes.mobile}px) {
margin-top: 12px;
}
${Styles.CONTAINER_CENTERED}; ${Styles.CONTAINER_CENTERED};
${STYLES_SPACE_BETWEEN} ${STYLES_SPACE_BETWEEN}
`; `;
@ -129,11 +128,6 @@ const getObjectToPreview = (objects = []) => {
return { ...objects[objectIdx], isImage }; return { ...objects[objectIdx], isImage };
}; };
const STYLES_DESCRIPTION_INNER = (theme) => css`
background-color: ${theme.semantic.bgLight};
border-radius: 16px;
`;
const Preview = ({ collection, children, ...props }) => { const Preview = ({ collection, children, ...props }) => {
const [isLoading, setLoading] = React.useState(true); const [isLoading, setLoading] = React.useState(true);
const handleOnLoaded = () => setLoading(false); const handleOnLoaded = () => setLoading(false);
@ -199,10 +193,8 @@ export default function CollectionPreview({ collection, viewer, owner, onAction
const showControls = () => setShowControls(true); const showControls = () => setShowControls(true);
const hideControls = () => setShowControls(false); const hideControls = () => setShowControls(false);
// const [isBodyVisible, setShowBody] = React.useState(false); const { isDescriptionVisible, showDescription, hideDescription } = useShowDescription();
// const showBody = () => setShowBody(true); const description = collection?.data?.body;
// const hideBody = () => setShowBody(false);
// const body = collection?.data?.body;
const { follow, followCount, isFollowed } = useFollowHandler({ collection, viewer }); const { follow, followCount, isFollowed } = useFollowHandler({ collection, viewer });
@ -211,12 +203,9 @@ export default function CollectionPreview({ collection, viewer, owner, onAction
return ( return (
<div css={STYLES_CONTAINER}> <div css={STYLES_CONTAINER}>
<Preview collection={collection} onMouseEnter={showControls} onMouseLeave={hideControls}> <Preview collection={collection} onMouseEnter={showControls} onMouseLeave={hideControls}>
<AnimatePresence>
{areControlsVisible && (
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: areControlsVisible ? 1 : 0 }}
exit={{ opacity: 0 }}
css={STYLES_CONTROLS} css={STYLES_CONTROLS}
> >
<FollowButton <FollowButton
@ -226,38 +215,46 @@ export default function CollectionPreview({ collection, viewer, owner, onAction
disabled={collection.ownerId === viewer?.id} disabled={collection.ownerId === viewer?.id}
/> />
</motion.div> </motion.div>
)}
</AnimatePresence>
</Preview> </Preview>
<div
<div style={{ position: "relative", height: 61 }}>
<motion.div
css={STYLES_DESCRIPTION_CONTAINER} css={STYLES_DESCRIPTION_CONTAINER}
// onMouseEnter={showBody} onMouseLeave={hideBody} onMouseEnter={showDescription}
> onMouseLeave={hideDescription}
<div transition={{ duration: 0.4, ease: "easeOut" }}
css={STYLES_DESCRIPTION_INNER}
// initial={{ y: 0 }}
// animate={{ y: isBodyVisible ? -170 : 0 }}
// transition={{ type: "spring", stiffness: 170, damping: 26 }}
> >
<div css={[Styles.HORIZONTAL_CONTAINER_CENTERED, STYLES_SPACE_BETWEEN]}> <div css={[Styles.HORIZONTAL_CONTAINER_CENTERED, STYLES_SPACE_BETWEEN]}>
<Typography.H5 color="textBlack" nbrOflines={1}> <Typography.H5 color="textBlack" nbrOflines={1} title={collection.slatename}>
{collection.slatename} {collection.slatename}
</Typography.H5> </Typography.H5>
</div> </div>
<motion.div
{/* {isBodyVisible && (
<div
style={{ marginTop: 4 }} style={{ marginTop: 4 }}
initial={{ opacity: 0 }} initial={{ height: 0 }}
animate={{ opacity: isBodyVisible ? 1 : 0 }} animate={{
height: isDescriptionVisible ? 108 : 0,
}}
transition={{
type: "spring",
stiffness: 170,
damping: 26,
delay: isDescriptionVisible ? 0 : 0.25,
}}
> >
<Typography.P2 color="textGrayDark" nbrOflines={5}> <Typography.P2
{body || ""} as={motion.p}
initial={{ opacity: 0 }}
animate={{
opacity: isDescriptionVisible ? 1 : 0,
}}
transition={{ delay: isDescriptionVisible ? 0.25 : 0 }}
color="textGrayDark"
nbrOflines={5}
>
{description || ""}
</Typography.P2> </Typography.P2>
</div> </motion.div>
)} */}
</div>
<div css={STYLES_METRICS}> <div css={STYLES_METRICS}>
<div css={[Styles.CONTAINER_CENTERED, STYLES_TEXT_GRAY]}> <div css={[Styles.CONTAINER_CENTERED, STYLES_TEXT_GRAY]}>
<SVG.Box /> <SVG.Box />
@ -293,7 +290,25 @@ export default function CollectionPreview({ collection, viewer, owner, onAction
</div> </div>
)} )}
</div> </div>
</motion.div>
</div> </div>
</div> </div>
); );
} }
const useShowDescription = () => {
const [isDescriptionVisible, setShowDescription] = React.useState(false);
const timeoutId = React.useRef();
const showDescription = () => {
clearTimeout(timeoutId.current);
const id = setTimeout(() => setShowDescription(true), 250);
timeoutId.current = id;
};
const hideDescription = () => {
clearTimeout(timeoutId.current);
setShowDescription(false);
};
return { isDescriptionVisible, showDescription, hideDescription };
};

View File

@ -753,7 +753,6 @@ export default class DataView extends React.Component {
height: 24, height: 24,
width: 24, width: 24,
borderRadius: "8px", borderRadius: "8px",
boxShadow: `0 0 0 1px ${Constants.system.white}`,
backgroundColor: this.state.checked[i] backgroundColor: this.state.checked[i]
? Constants.system.blue ? Constants.system.blue
: "rgba(255, 255, 255, 0.75)", : "rgba(255, 255, 255, 0.75)",

View File

@ -20,24 +20,22 @@ const STYLES_WRAPPER = (theme) => css`
`; `;
const STYLES_DESCRIPTION = (theme) => css` const STYLES_DESCRIPTION = (theme) => css`
position: relative;
box-shadow: 0 -0.5px 0.5px ${theme.system.grayLight4}; box-shadow: 0 -0.5px 0.5px ${theme.system.grayLight4};
border-radius: 0px 0px 16px 16px; border-radius: 0px 0px 16px 16px;
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
max-height: 61px; background-color: ${theme.semantic.bgLight};
border-radius: 16px;
height: calc(170px + 61px);
padding: 9px 16px 8px;
z-index: 1;
@media (max-width: ${theme.sizes.mobile}px) { @media (max-width: ${theme.sizes.mobile}px) {
padding: 8px; padding: 8px;
} }
`; `;
const STYLES_DESCRIPTION_INNER = (theme) => css`
background-color: ${theme.semantic.bgLight};
padding: 9px 16px 8px;
border-radius: 16px;
height: calc(170px + 61px);
`;
const STYLES_PREVIEW = css` const STYLES_PREVIEW = css`
overflow: hidden; overflow: hidden;
position: relative; position: relative;
@ -73,6 +71,7 @@ export default function ObjectPreviewPrimitive({
isImage, isImage,
onAction, onAction,
}) { }) {
const { isDescriptionVisible, showDescription, hideDescription } = useShowDescription();
// const { like, isLiked, likeCount } = useLikeHandler({ file, viewer }); // const { like, isLiked, likeCount } = useLikeHandler({ file, viewer });
// const { save, isSaved, saveCount } = useSaveHandler({ file, viewer }); // const { save, isSaved, saveCount } = useSaveHandler({ file, viewer });
// const showSaveButton = viewer?.id !== file?.ownerId; // const showSaveButton = viewer?.id !== file?.ownerId;
@ -81,11 +80,8 @@ export default function ObjectPreviewPrimitive({
// const showControls = () => setShowControls(true); // const showControls = () => setShowControls(true);
// const hideControls = () => setShowControls(false); // const hideControls = () => setShowControls(false);
const [isBodyVisible, setShowBody] = React.useState(false);
const showBody = () => setShowBody(true);
const hideBody = () => setShowBody(false);
const body = file?.data?.body; const body = file?.data?.body;
const isLink = file.isLink; const { isLink } = file;
const title = file.data.name || file.filename; const title = file.data.name || file.filename;
@ -126,17 +122,22 @@ export default function ObjectPreviewPrimitive({
<div>{children}</div> <div>{children}</div>
</AspectRatio> </AspectRatio>
</div> </div>
<article css={STYLES_DESCRIPTION} onMouseEnter={showBody} onMouseLeave={hideBody}> <div style={{ maxHeight: 61 }}>
<motion.div <motion.article
css={STYLES_DESCRIPTION_INNER} css={STYLES_DESCRIPTION}
onMouseMove={showDescription}
onMouseLeave={hideDescription}
initial={{ y: 0 }} initial={{ y: 0 }}
animate={{ y: isBodyVisible ? -170 : 0 }} animate={{
y: isDescriptionVisible ? -170 : 0,
borderRadius: isDescriptionVisible ? "16px" : "0px",
}}
transition={{ type: "spring", stiffness: 170, damping: 26 }} transition={{ type: "spring", stiffness: 170, damping: 26 }}
> >
<H5 as="h2" nbrOflines={1} color="textBlack"> <H5 as="h2" nbrOflines={1} color="textBlack" title={title}>
{title} {title}
</H5> </H5>
<div style={{ marginTop: 3 }}> <div style={{ marginTop: 3, display: "flex" }}>
{typeof tag === "string" ? ( {typeof tag === "string" ? (
<P3 as="small" css={STYLES_UPPERCASE} color="textGray"> <P3 as="small" css={STYLES_UPPERCASE} color="textGray">
{tag} {tag}
@ -148,15 +149,32 @@ export default function ObjectPreviewPrimitive({
<H5 <H5
as={motion.p} as={motion.p}
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: isBodyVisible ? 1 : 0 }} animate={{ opacity: isDescriptionVisible ? 1 : 0 }}
style={{ marginTop: 5 }} style={{ marginTop: 5 }}
nbrOflines={8} nbrOflines={8}
color="textGrayDark" color="textGrayDark"
> >
{body || ""} {body || ""}
</H5> </H5>
</motion.div> </motion.article>
</article> </div>
</div> </div>
); );
} }
const useShowDescription = () => {
const [isDescriptionVisible, setShowDescription] = React.useState(false);
const timeoutId = React.useRef();
const showDescription = () => {
clearTimeout(timeoutId.current);
const id = setTimeout(() => setShowDescription(true), 250);
timeoutId.current = id;
};
const hideDescription = () => {
clearTimeout(timeoutId.current);
setShowDescription(false);
};
return { isDescriptionVisible, showDescription, hideDescription };
};

View File

@ -17,7 +17,7 @@ const STYLES_CHECKBOX = css`
const STYLES_CHECKBOX_FIGURE = css` const STYLES_CHECKBOX_FIGURE = css`
box-sizing: border-box; box-sizing: border-box;
box-shadow: 0 0 0 1px ${Constants.semantic.borderGrayLight}; box-shadow: 0 0 0 1px ${Constants.system.grayLight4};
background-color: ${Constants.system.white}; background-color: ${Constants.system.white};
border-radius: 4px; border-radius: 4px;
display: inline-flex; display: inline-flex;

View File

@ -11,9 +11,9 @@ export default async ({ ownerId }) => {
// const slateFiles = () => // const slateFiles = () =>
// DB.raw("json_agg(?? order by ?? asc) as ??", ["files", "slate_files.createdAt", "objects"]); // DB.raw("json_agg(?? order by ?? asc) as ??", ["files", "slate_files.createdAt", "objects"]);
const ownerQueryFields = ["*", ...Constants.userPreviewProperties, "owner"]; const ownerQueryFields = [...Constants.userPreviewProperties, "owner"];
const ownerQuery = DB.raw( const ownerQuery = DB.raw(
`??, json_build_object('id', ??, 'data', ??, 'username', ??) as ??`, `json_build_object('id', ??, 'data', ??, 'username', ??) as ??`,
ownerQueryFields ownerQueryFields
); );
@ -36,7 +36,7 @@ export default async ({ ownerId }) => {
// .orderBy("subscriptions.createdAt", "desc"); // .orderBy("subscriptions.createdAt", "desc");
.groupBy("slates.id") .groupBy("slates.id")
) )
.select(ownerQuery) .select(...Serializers.slateProperties, "objects", ownerQuery)
.from("slates") .from("slates")
.join("users", "slates.ownerId", "users.id"); .join("users", "slates.ownerId", "users.id");