1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 12:56:41 +07:00

#35 refactored node layout

This commit is contained in:
Fedor Katurov 2021-03-06 13:17:39 +07:00
parent 428c7e7a06
commit d3473eab4c
20 changed files with 406 additions and 344 deletions

View file

@ -0,0 +1,75 @@
import React, { FC } from 'react';
import { NodeDeletedBadge } from '~/components/node/NodeDeletedBadge';
import { Group } from '~/components/containers/Group';
import { Padder } from '~/components/containers/Padder';
import styles from '~/containers/node/NodeLayout/styles.module.scss';
import { NodeCommentsBlock } from '~/components/node/NodeCommentsBlock';
import { NodeCommentForm } from '~/components/node/NodeCommentForm';
import { Sticky } from '~/components/containers/Sticky';
import { NodeRelatedBlock } from '~/components/node/NodeRelatedBlock';
import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
import { IComment, INode } from '~/redux/types';
import { useUser } from '~/utils/hooks/user/userUser';
import { NodeTagsBlock } from '~/components/node/NodeTagsBlock';
import { INodeRelated } from '~/redux/node/types';
interface IProps {
node: INode;
isLoading: boolean;
commentsOrder: 'ASC' | 'DESC';
comments: IComment[];
commentsCount: number;
isLoadingComments: boolean;
related: INodeRelated;
}
const NodeBottomBlock: FC<IProps> = ({
node,
isLoading,
isLoadingComments,
comments,
commentsCount,
commentsOrder,
related,
}) => {
const { inline } = useNodeBlocks(node, isLoading);
const { is_user } = useUser();
if (node.deleted_at) {
return <NodeDeletedBadge />;
}
return (
<Group>
<Padder>
<Group horizontal className={styles.content}>
<Group className={styles.comments}>
{inline && <div className={styles.inline}>{inline}</div>}
<NodeCommentsBlock
isLoading={isLoading}
isLoadingComments={isLoadingComments}
comments={comments}
count={commentsCount}
order={commentsOrder}
node={node}
/>
{is_user && !isLoading && <NodeCommentForm nodeId={node.id} />}
</Group>
<div className={styles.panel}>
<Sticky>
<Group style={{ flex: 1, minWidth: 0 }}>
<NodeTagsBlock node={node} isLoading={isLoading} />
<NodeRelatedBlock isLoading={isLoading} node={node} related={related} />
</Group>
</Sticky>
</div>
</Group>
</Padder>
</Group>
);
};
export { NodeBottomBlock };

View file

@ -0,0 +1,28 @@
import React, { FC } from 'react';
import { NodeNoComments } from '~/components/node/NodeNoComments';
import { NodeComments } from '~/components/node/NodeComments';
import { IComment, INode } from '~/redux/types';
import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
import { useUser } from '~/utils/hooks/user/userUser';
interface IProps {
order: 'ASC' | 'DESC';
node: INode;
comments: IComment[];
count: number;
isLoading: boolean;
isLoadingComments: boolean;
}
const NodeCommentsBlock: FC<IProps> = ({ isLoading, isLoadingComments, node, comments, count }) => {
const user = useUser();
const { inline } = useNodeBlocks(node, isLoading);
return isLoading || isLoadingComments || (!comments.length && !inline) ? (
<NodeNoComments is_loading={isLoadingComments || isLoading} />
) : (
<NodeComments count={count} comments={comments} user={user} order="DESC" />
);
};
export { NodeCommentsBlock };

View file

@ -8,21 +8,24 @@ import { PRESETS } from '~/constants/urls';
import { throttle } from 'throttle-debounce';
import { Icon } from '~/components/input/Icon';
import { useArrows } from '~/utils/hooks/keys';
import { useDispatch } from 'react-redux';
import { modalShowPhotoswipe } from '~/redux/modal/actions';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectModal } from '~/redux/modal/selectors';
interface IProps extends INodeComponentProps {}
interface IProps extends INodeComponentProps {
updateLayout?: () => void;
}
const getX = event =>
(event.touches && event.touches.length) || (event.changedTouches && event.changedTouches.length)
? (event.touches.length && event.touches[0].clientX) || event.changedTouches[0].clientX
: event.clientX;
const NodeImageSlideBlock: FC<IProps> = ({
node,
is_loading,
is_modal_shown,
updateLayout,
modalShowPhotoswipe,
}) => {
const NodeImageSlideBlock: FC<IProps> = ({ node, isLoading, updateLayout = () => {} }) => {
const dispatch = useDispatch();
const { is_shown } = useShallowSelect(selectModal);
const [current, setCurrent] = useState(0);
const [height, setHeight] = useState(window.innerHeight - 143);
const [max_height, setMaxHeight] = useState(960);
@ -88,7 +91,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
const { width } = wrap.current.getBoundingClientRect();
const fallback = window.innerHeight - 143;
if (is_loading) {
if (isLoading) {
setHeight(fallback);
return () => clearTimeout(timeout);
}
@ -118,7 +121,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
return () => {
if (timeout) clearTimeout(timeout);
};
}, [is_dragging, wrap, offset, heights, max_height, images, is_loading, updateLayout]);
}, [is_dragging, wrap, offset, heights, max_height, images, isLoading, updateLayout]);
const onDrag = useCallback(
event => {
@ -162,8 +165,8 @@ const NodeImageSlideBlock: FC<IProps> = ({
normalizeOffset();
}, [wrap, setMaxHeight, normalizeOffset]);
const onOpenPhotoSwipe = useCallback(() => modalShowPhotoswipe(images, current), [
modalShowPhotoswipe,
const onOpenPhotoSwipe = useCallback(() => dispatch(modalShowPhotoswipe(images, current)), [
dispatch,
images,
current,
]);
@ -241,7 +244,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
images,
]);
useArrows(onNext, onPrev, is_modal_shown);
useArrows(onNext, onPrev, is_shown);
useEffect(() => {
setOffset(0);
@ -249,7 +252,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
return (
<div className={styles.wrap}>
<div className={classNames(styles.cutter, { [styles.is_loading]: is_loading })} ref={wrap}>
<div className={classNames(styles.cutter, { [styles.is_loading]: isLoading })} ref={wrap}>
<div
className={classNames(styles.image_container, { [styles.is_dragging]: is_dragging })}
style={{
@ -261,7 +264,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
onTouchStart={startDragging}
ref={slide}
>
{!is_loading &&
{!isLoading &&
images.map((file, index) => (
<div
className={classNames(styles.image_wrap, {

View file

@ -2,8 +2,6 @@ import React, { FC } from 'react';
interface IProps {}
const NodeImageSwiperBlock: FC<IProps> = () => (
<div>SWIPER</div>
)
const NodeImageSwiperBlock: FC<IProps> = () => <div>SWIPER</div>;
export { NodeImageSwiperBlock };

View file

@ -1,85 +1,35 @@
import React, { FC, useCallback, useEffect, useRef, useState, memo } from 'react';
import React, { FC, memo } from 'react';
import styles from './styles.module.scss';
import { INode } from '~/redux/types';
import { createPortal } from 'react-dom';
import { NodePanelInner } from '~/components/node/NodePanelInner';
import { useNodePermissions } from '~/utils/hooks/node/useNodePermissions';
import { useNodeActions } from '~/utils/hooks/node/useNodeActions';
import { shallowEqual } from 'react-redux';
interface IProps {
node: Partial<INode>;
layout: {};
can_edit: boolean;
can_like: boolean;
can_star: boolean;
is_loading?: boolean;
onEdit: () => void;
onLike: () => void;
onStar: () => void;
onLock: () => void;
node: INode;
isLoading: boolean;
}
const NodePanel: FC<IProps> = memo(
({ node, layout, can_edit, can_like, can_star, is_loading, onEdit, onLike, onStar, onLock }) => {
const [stack, setStack] = useState(false);
const NodePanel: FC<IProps> = memo(({ node, isLoading }) => {
const [can_edit, can_like, can_star] = useNodePermissions(node);
const { onEdit, onLike, onStar, onLock } = useNodeActions(node);
const ref = useRef<HTMLDivElement>(null);
const getPlace = useCallback(() => {
if (!ref.current) return;
const { bottom } = ref.current!.getBoundingClientRect();
setStack(bottom > window.innerHeight);
}, [ref]);
useEffect(() => getPlace(), [layout]);
useEffect(() => {
window.addEventListener('scroll', getPlace);
window.addEventListener('resize', getPlace);
return () => {
window.removeEventListener('scroll', getPlace);
window.removeEventListener('resize', getPlace);
};
}, [layout, getPlace]);
return (
<div className={styles.place} ref={ref}>
{/*
stack &&
createPortal(
<NodePanelInner
node={node}
can_edit={can_edit}
can_like={can_like}
can_star={can_star}
onEdit={onEdit}
onLike={onLike}
onStar={onStar}
onLock={onLock}
is_loading={is_loading}
stack
/>,
document.body
)
*/}
<NodePanelInner
node={node}
onEdit={onEdit}
onLike={onLike}
onStar={onStar}
onLock={onLock}
can_edit={can_edit}
can_like={can_like}
can_star={can_star}
is_loading={!!is_loading}
/>
</div>
);
}
);
return (
<div className={styles.place}>
<NodePanelInner
node={node}
onEdit={onEdit}
onLike={onLike}
onStar={onStar}
onLock={onLock}
canEdit={can_edit}
canLike={can_like}
canStar={can_star}
isLoading={!!isLoading}
/>
</div>
);
}, shallowEqual);
export { NodePanel };

View file

@ -1,7 +1,5 @@
import React, { FC, memo } from 'react';
import styles from './styles.module.scss';
import { Group } from '~/components/containers/Group';
import { Filler } from '~/components/containers/Filler';
import { Icon } from '~/components/input/Icon';
import { INode } from '~/redux/types';
import classNames from 'classnames';
@ -12,11 +10,11 @@ interface IProps {
node: Partial<INode>;
stack?: boolean;
can_edit: boolean;
can_like: boolean;
can_star: boolean;
canEdit: boolean;
canLike: boolean;
canStar: boolean;
is_loading: boolean;
isLoading: boolean;
onEdit: () => void;
onLike: () => void;
@ -29,11 +27,11 @@ const NodePanelInner: FC<IProps> = memo(
node: { title, user, is_liked, is_heroic, deleted_at, created_at, like_count },
stack,
can_star,
can_edit,
can_like,
canStar,
canEdit,
canLike,
is_loading,
isLoading,
onStar,
onEdit,
@ -45,12 +43,12 @@ const NodePanelInner: FC<IProps> = memo(
<div className={styles.content}>
<div className={styles.panel}>
<div className={styles.title}>
{is_loading ? <Placeholder width="40%" /> : title || '...'}
{isLoading ? <Placeholder width="40%" /> : title || '...'}
</div>
{user && user.username && (
<div className={styles.name}>
{is_loading ? (
{isLoading ? (
<Placeholder width="100px" />
) : (
`~${user.username.toLocaleLowerCase()}, ${getPrettyDate(created_at)}`
@ -59,14 +57,14 @@ const NodePanelInner: FC<IProps> = memo(
)}
</div>
{can_edit && (
{canEdit && (
<div className={styles.editor_menu}>
<div className={styles.editor_menu_button}>
<Icon icon="dots-vertical" size={24} />
</div>
<div className={styles.editor_buttons}>
{can_star && (
{canStar && (
<div className={classNames(styles.star, { is_heroic })}>
{is_heroic ? (
<Icon icon="star_full" size={24} onClick={onStar} />
@ -88,7 +86,7 @@ const NodePanelInner: FC<IProps> = memo(
)}
<div className={styles.buttons}>
{can_like && (
{canLike && (
<div className={classNames(styles.like, { is_liked })}>
{is_liked ? (
<Icon icon="heart_full" size={24} onClick={onLike} />

View file

@ -0,0 +1,44 @@
import React, { FC } from 'react';
import { NodeRelatedPlaceholder } from '~/components/node/NodeRelated/placeholder';
import { NodeRelated } from '~/components/node/NodeRelated';
import { URLS } from '~/constants/urls';
import { INode } from '~/redux/types';
import { INodeRelated } from '~/redux/node/types';
import { Link } from 'react-router-dom';
interface IProps {
isLoading: boolean;
node: INode;
related: INodeRelated;
}
const NodeRelatedBlock: FC<IProps> = ({ isLoading, node, related }) => {
if (isLoading) {
return <NodeRelatedPlaceholder />;
}
return (
<div>
{related &&
related.albums &&
!!node?.id &&
Object.keys(related.albums)
.filter(album => related.albums[album].length > 0)
.map(album => (
<NodeRelated
title={
<Link to={URLS.NODE_TAG_URL(node.id!, encodeURIComponent(album))}>{album}</Link>
}
items={related.albums[album]}
key={album}
/>
))}
{related && related.similar && related.similar.length > 0 && (
<NodeRelated title="ПОХОЖИЕ" items={related.similar} />
)}
</div>
);
};
export { NodeRelatedBlock };

View file

@ -0,0 +1,52 @@
import React, { FC, useCallback } from 'react';
import { INode, ITag } from '~/redux/types';
import { URLS } from '~/constants/urls';
import { nodeUpdateTags } from '~/redux/node/actions';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router';
import { NodeTags } from '~/components/node/NodeTags';
import { useUser } from '~/utils/hooks/user/userUser';
interface IProps {
node: INode;
isLoading: boolean;
}
const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
const dispatch = useDispatch();
const history = useHistory();
const { is_user } = useUser();
const onTagsChange = useCallback(
(tags: string[]) => {
dispatch(nodeUpdateTags(node.id, tags));
},
[dispatch, node]
);
const onTagClick = useCallback(
(tag: Partial<ITag>) => {
if (!node?.id || !tag?.title) {
return;
}
history.push(URLS.NODE_TAG_URL(node.id, encodeURIComponent(tag.title)));
},
[history, node]
);
if (isLoading) {
return null;
}
return (
<NodeTags
is_editable={is_user}
tags={node.tags}
onChange={onTagsChange}
onTagClick={onTagClick}
/>
);
};
export { NodeTagsBlock };