mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-23 05:54:49 +03:00
Merge pull request #851 from filecoin-project/@aminejv/preview-tweaks
Tweaks: Block Previews
This commit is contained in:
commit
a0174ad8f8
@ -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}
|
||||||
|
@ -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,89 +203,112 @@ 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>
|
<motion.div
|
||||||
{areControlsVisible && (
|
initial={{ opacity: 0 }}
|
||||||
<motion.div
|
animate={{ opacity: areControlsVisible ? 1 : 0 }}
|
||||||
initial={{ opacity: 0 }}
|
css={STYLES_CONTROLS}
|
||||||
animate={{ opacity: 1 }}
|
>
|
||||||
exit={{ opacity: 0 }}
|
<FollowButton
|
||||||
css={STYLES_CONTROLS}
|
onClick={follow}
|
||||||
>
|
isFollowed={isFollowed}
|
||||||
<FollowButton
|
followCount={followCount}
|
||||||
onClick={follow}
|
disabled={collection.ownerId === viewer?.id}
|
||||||
isFollowed={isFollowed}
|
/>
|
||||||
followCount={followCount}
|
</motion.div>
|
||||||
disabled={collection.ownerId === viewer?.id}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</Preview>
|
</Preview>
|
||||||
<div
|
|
||||||
css={STYLES_DESCRIPTION_CONTAINER}
|
<div style={{ position: "relative", height: 61 }}>
|
||||||
// onMouseEnter={showBody} onMouseLeave={hideBody}
|
<motion.div
|
||||||
>
|
css={STYLES_DESCRIPTION_CONTAINER}
|
||||||
<div
|
onMouseEnter={showDescription}
|
||||||
css={STYLES_DESCRIPTION_INNER}
|
onMouseLeave={hideDescription}
|
||||||
// initial={{ y: 0 }}
|
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||||
// 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
|
||||||
|
as={motion.p}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: isDescriptionVisible ? 1 : 0,
|
||||||
|
}}
|
||||||
|
transition={{ delay: isDescriptionVisible ? 0.25 : 0 }}
|
||||||
|
color="textGrayDark"
|
||||||
|
nbrOflines={5}
|
||||||
>
|
>
|
||||||
<Typography.P2 color="textGrayDark" nbrOflines={5}>
|
{description || ""}
|
||||||
{body || ""}
|
</Typography.P2>
|
||||||
</Typography.P2>
|
</motion.div>
|
||||||
|
<div css={STYLES_METRICS}>
|
||||||
|
<div css={[Styles.CONTAINER_CENTERED, STYLES_TEXT_GRAY]}>
|
||||||
|
<SVG.Box />
|
||||||
|
<Typography.P3 style={{ marginLeft: 4 }} color="textGray">
|
||||||
|
{fileCount}
|
||||||
|
</Typography.P3>
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
{owner && (
|
||||||
</div>
|
<div style={{ alignItems: "end" }} css={Styles.CONTAINER_CENTERED}>
|
||||||
|
<Link
|
||||||
<div css={STYLES_METRICS}>
|
href={`/$/user/${owner.id}`}
|
||||||
<div css={[Styles.CONTAINER_CENTERED, STYLES_TEXT_GRAY]}>
|
onAction={onAction}
|
||||||
<SVG.Box />
|
aria-label={`Visit ${owner.username}'s profile`}
|
||||||
<Typography.P3 style={{ marginLeft: 4 }} color="textGray">
|
title={`Visit ${owner.username}'s profile`}
|
||||||
{fileCount}
|
>
|
||||||
</Typography.P3>
|
<img
|
||||||
|
css={STYLES_PROFILE_IMAGE}
|
||||||
|
src={owner?.data?.photo}
|
||||||
|
alt={`${owner.username} profile`}
|
||||||
|
onError={(e) => (e.target.src = Constants.profileDefaultPicture)}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href={`/$/user/${owner.id}`}
|
||||||
|
onAction={onAction}
|
||||||
|
aria-label={`Visit ${owner.username}'s profile`}
|
||||||
|
title={`Visit ${owner.username}'s profile`}
|
||||||
|
>
|
||||||
|
<Typography.P3 style={{ marginLeft: 8 }} color="textGray">
|
||||||
|
{owner.username}
|
||||||
|
</Typography.P3>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{owner && (
|
</motion.div>
|
||||||
<div style={{ alignItems: "end" }} css={Styles.CONTAINER_CENTERED}>
|
|
||||||
<Link
|
|
||||||
href={`/$/user/${owner.id}`}
|
|
||||||
onAction={onAction}
|
|
||||||
aria-label={`Visit ${owner.username}'s profile`}
|
|
||||||
title={`Visit ${owner.username}'s profile`}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
css={STYLES_PROFILE_IMAGE}
|
|
||||||
src={owner?.data?.photo}
|
|
||||||
alt={`${owner.username} profile`}
|
|
||||||
onError={(e) => (e.target.src = Constants.profileDefaultPicture)}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href={`/$/user/${owner.id}`}
|
|
||||||
onAction={onAction}
|
|
||||||
aria-label={`Visit ${owner.username}'s profile`}
|
|
||||||
title={`Visit ${owner.username}'s profile`}
|
|
||||||
>
|
|
||||||
<Typography.P3 style={{ marginLeft: 8 }} color="textGray">
|
|
||||||
{owner.username}
|
|
||||||
</Typography.P3>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</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 };
|
||||||
|
};
|
||||||
|
@ -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)",
|
||||||
|
@ -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 };
|
||||||
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user