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

@ -1,254 +1,64 @@
import React, { createElement, FC, memo, useCallback, useEffect, useMemo, useState } from 'react';
import { RouteComponentProps, useHistory } from 'react-router';
import { connect } from 'react-redux';
import { canEditNode, canLikeNode, canStarNode } from '~/utils/node';
import React, { FC, memo } from 'react';
import { RouteComponentProps } from 'react-router';
import { selectNode } from '~/redux/node/selectors';
import { Card } from '~/components/containers/Card';
import { NodePanel } from '~/components/node/NodePanel';
import { Group } from '~/components/containers/Group';
import { Padder } from '~/components/containers/Padder';
import { NodeNoComments } from '~/components/node/NodeNoComments';
import { NodeRelated } from '~/components/node/NodeRelated';
import { NodeComments } from '~/components/node/NodeComments';
import { NodeTags } from '~/components/node/NodeTags';
import { INodeComponentProps, NODE_COMPONENTS, NODE_HEADS, NODE_INLINES } from '~/redux/node/constants';
import { selectUser } from '~/redux/auth/selectors';
import { pick, prop } from 'ramda';
import { NodeRelatedPlaceholder } from '~/components/node/NodeRelated/placeholder';
import { NodeDeletedBadge } from '~/components/node/NodeDeletedBadge';
import { NodeCommentForm } from '~/components/node/NodeCommentForm';
import { Sticky } from '~/components/containers/Sticky';
import { Footer } from '~/components/main/Footer';
import { Link } from 'react-router-dom';
import styles from './styles.module.scss';
import * as NODE_ACTIONS from '~/redux/node/actions';
import * as MODAL_ACTIONS from '~/redux/modal/actions';
import { IState } from '~/redux/store';
import { selectModal } from '~/redux/modal/selectors';
import { SidebarRouter } from '~/containers/main/SidebarRouter';
import { ITag } from '~/redux/types';
import { URLS } from '~/constants/urls';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { Container } from '~/containers/main/Container';
import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
import { NodeBottomBlock } from '~/components/node/NodeBottomBlock';
import { useNodeCoverImage } from '~/utils/hooks/node/useNodeCoverImage';
import { useScrollToTop } from '~/utils/hooks/useScrollToTop';
import { useLoadNode } from '~/utils/hooks/node/useLoadNode';
const mapStateToProps = (state: IState) => ({
node: selectNode(state),
user: selectUser(state),
modal: pick(['is_shown'])(selectModal(state)),
});
type IProps = RouteComponentProps<{ id: string }> & {};
const mapDispatchToProps = {
nodeGotoNode: NODE_ACTIONS.nodeGotoNode,
nodeUpdateTags: NODE_ACTIONS.nodeUpdateTags,
nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage,
nodeEdit: NODE_ACTIONS.nodeEdit,
nodeLike: NODE_ACTIONS.nodeLike,
nodeStar: NODE_ACTIONS.nodeStar,
nodeLock: NODE_ACTIONS.nodeLock,
nodeLockComment: NODE_ACTIONS.nodeLockComment,
nodeEditComment: NODE_ACTIONS.nodeEditComment,
nodeLoadMoreComments: NODE_ACTIONS.nodeLoadMoreComments,
modalShowPhotoswipe: MODAL_ACTIONS.modalShowPhotoswipe,
};
type IProps = ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps &
RouteComponentProps<{ id: string }> & {};
const NodeLayoutUnconnected: FC<IProps> = memo(
const NodeLayout: FC<IProps> = memo(
({
match: {
params: { id },
},
modal: { is_shown: is_modal_shown },
user,
user: { is_user },
nodeGotoNode,
nodeUpdateTags,
nodeEdit,
nodeLike,
nodeStar,
nodeLock,
nodeSetCoverImage,
modalShowPhotoswipe,
}) => {
const [layout, setLayout] = useState({});
const history = useHistory();
const {
is_loading,
is_loading_comments,
comments = [],
current: node,
related,
current,
comments,
comment_count,
is_loading_comments,
related,
} = useShallowSelect(selectNode);
const updateLayout = useCallback(() => setLayout({}), []);
useEffect(() => {
if (is_loading) return;
nodeGotoNode(parseInt(id, 10), null);
}, [nodeGotoNode, id]);
useNodeCoverImage(current);
useScrollToTop([id]);
useLoadNode(id, is_loading);
const onTagsChange = useCallback(
(tags: string[]) => {
nodeUpdateTags(node.id, tags);
},
[node, nodeUpdateTags]
);
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.id]
);
const can_edit = useMemo(() => canEditNode(node, user), [node, user]);
const can_like = useMemo(() => canLikeNode(node, user), [node, user]);
const can_star = useMemo(() => canStarNode(node, user), [node, user]);
const head = useMemo(() => node?.type && prop(node?.type, NODE_HEADS), [node.type]);
const block = useMemo(() => node?.type && prop(node?.type, NODE_COMPONENTS), [node.type]);
const inline = useMemo(() => node?.type && prop(node?.type, NODE_INLINES), [node.type]);
const onEdit = useCallback(() => nodeEdit(node.id), [nodeEdit, node]);
const onLike = useCallback(() => nodeLike(node.id), [nodeLike, node]);
const onStar = useCallback(() => nodeStar(node.id), [nodeStar, node]);
const onLock = useCallback(() => nodeLock(node.id, !node.deleted_at), [nodeStar, node]);
const createNodeBlock = useCallback(
(block: FC<INodeComponentProps>) =>
block &&
createElement(block, {
node,
is_loading,
updateLayout,
layout,
modalShowPhotoswipe,
is_modal_shown,
}),
[node, is_loading, updateLayout, layout, modalShowPhotoswipe, is_modal_shown]
);
useEffect(() => {
if (!node.cover) return;
nodeSetCoverImage(node.cover);
return () => nodeSetCoverImage(null);
}, [nodeSetCoverImage, node.cover]);
useEffect(() => {
window.scrollTo(0, 0);
}, [id]);
const { head, block } = useNodeBlocks(current, is_loading);
return (
<>
{!!head && createNodeBlock(head)}
{head}
<Container>
<Card className={styles.node} seamless>
{!!block && createNodeBlock(block)}
{block}
<NodePanel
node={pick(
[
'title',
'user',
'is_liked',
'is_heroic',
'deleted_at',
'created_at',
'like_count',
],
node
)}
layout={layout}
can_edit={can_edit}
can_like={can_like}
can_star={can_star}
onEdit={onEdit}
onLike={onLike}
onStar={onStar}
onLock={onLock}
is_loading={is_loading}
<NodePanel node={current} isLoading={is_loading} />
<NodeBottomBlock
node={current}
isLoadingComments={is_loading_comments}
comments={comments}
isLoading={is_loading}
commentsCount={comment_count}
commentsOrder="DESC"
related={related}
/>
{node.deleted_at ? (
<NodeDeletedBadge />
) : (
<Group>
<Padder>
<Group horizontal className={styles.content}>
<Group className={styles.comments}>
{inline && <div className={styles.inline}>{createNodeBlock(inline)}</div>}
{is_loading || is_loading_comments || (!comments.length && !inline) ? (
<NodeNoComments is_loading={is_loading_comments || is_loading} />
) : (
<NodeComments
count={comment_count}
comments={comments}
user={user}
order="DESC"
/>
)}
{is_user && !is_loading && <NodeCommentForm nodeId={node.id} />}
</Group>
<div className={styles.panel}>
<Sticky>
<Group style={{ flex: 1, minWidth: 0 }}>
{!is_loading && (
<NodeTags
is_editable={is_user}
tags={node.tags}
onChange={onTagsChange}
onTagClick={onTagClick}
/>
)}
{is_loading && <NodeRelatedPlaceholder />}
{!is_loading &&
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}
/>
))}
{!is_loading &&
related &&
related.similar &&
related.similar.length > 0 && (
<NodeRelated title="ПОХОЖИЕ" items={related.similar} />
)}
</Group>
</Sticky>
</div>
</Group>
</Padder>
</Group>
)}
<Footer />
</Card>
</Container>
@ -259,6 +69,4 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
}
);
const NodeLayout = connect(mapStateToProps, mapDispatchToProps)(NodeLayoutUnconnected);
export { NodeLayout, NodeLayoutUnconnected };
export { NodeLayout };