mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
fixed user hydration
This commit is contained in:
parent
ed9694c246
commit
75dc20ca0b
13 changed files with 194 additions and 95 deletions
|
@ -1,9 +1,19 @@
|
|||
import React, { createElement, FC, Fragment, memo, ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
import React, {
|
||||
createElement,
|
||||
FC,
|
||||
Fragment,
|
||||
memo,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CommentForm } from '~/components/comment/CommentForm';
|
||||
import { Authorized } from '~/components/containers/Authorized';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { AudioPlayer } from '~/components/media/AudioPlayer';
|
||||
import { COMMENT_BLOCK_RENDERERS } from '~/constants/comment';
|
||||
|
@ -28,7 +38,15 @@ interface IProps {
|
|||
}
|
||||
|
||||
const CommentContent: FC<IProps> = memo(
|
||||
({ comment, canEdit, nodeId, saveComment, onDelete, onShowImageModal, prefix }) => {
|
||||
({
|
||||
comment,
|
||||
canEdit,
|
||||
nodeId,
|
||||
saveComment,
|
||||
onDelete,
|
||||
onShowImageModal,
|
||||
prefix,
|
||||
}) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const startEditing = useCallback(() => setIsEditing(true), [setIsEditing]);
|
||||
|
@ -38,11 +56,13 @@ const CommentContent: FC<IProps> = memo(
|
|||
() =>
|
||||
reduce(
|
||||
(group, file) =>
|
||||
file.type ? assocPath([file.type], append(file, group[file.type]), group) : group,
|
||||
file.type
|
||||
? assocPath([file.type], append(file, group[file.type]), group)
|
||||
: group,
|
||||
{} as Record<UploadType, IFile[]>,
|
||||
comment.files
|
||||
comment.files,
|
||||
),
|
||||
[comment]
|
||||
[comment],
|
||||
);
|
||||
|
||||
const onLockClick = useCallback(() => {
|
||||
|
@ -50,8 +70,9 @@ const CommentContent: FC<IProps> = memo(
|
|||
}, [comment, onDelete]);
|
||||
|
||||
const menu = useMemo(
|
||||
() => canEdit && <CommentMenu onDelete={onLockClick} onEdit={startEditing} />,
|
||||
[canEdit, startEditing, onLockClick]
|
||||
() =>
|
||||
canEdit && <CommentMenu onDelete={onLockClick} onEdit={startEditing} />,
|
||||
[canEdit, startEditing, onLockClick],
|
||||
);
|
||||
|
||||
const blocks = useMemo(
|
||||
|
@ -59,7 +80,7 @@ const CommentContent: FC<IProps> = memo(
|
|||
!!comment.text.trim()
|
||||
? formatCommentText(path(['user', 'username'], comment), comment.text)
|
||||
: [],
|
||||
[comment]
|
||||
[comment],
|
||||
);
|
||||
|
||||
if (isEditing) {
|
||||
|
@ -78,17 +99,22 @@ const CommentContent: FC<IProps> = memo(
|
|||
{!!prefix && <div className={styles.prefix}>{prefix}</div>}
|
||||
{comment.text.trim() && (
|
||||
<Group className={classnames(styles.block, styles.block_text)}>
|
||||
{menu}
|
||||
<Authorized>{menu}</Authorized>
|
||||
|
||||
<Group className={styles.renderers}>
|
||||
{blocks.map(
|
||||
(block, key) =>
|
||||
COMMENT_BLOCK_RENDERERS[block.type] &&
|
||||
createElement(COMMENT_BLOCK_RENDERERS[block.type], { block, key })
|
||||
createElement(COMMENT_BLOCK_RENDERERS[block.type], {
|
||||
block,
|
||||
key,
|
||||
}),
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<div className={styles.date}>{getPrettyDate(comment.created_at)}</div>
|
||||
<div className={styles.date}>
|
||||
{getPrettyDate(comment.created_at)}
|
||||
</div>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
|
@ -102,32 +128,45 @@ const CommentContent: FC<IProps> = memo(
|
|||
})}
|
||||
>
|
||||
{groupped.image.map((file, index) => (
|
||||
<div key={file.id} onClick={() => onShowImageModal(groupped.image, index)}>
|
||||
<img src={getURL(file, ImagePresets['600'])} alt={file.name} />
|
||||
<div
|
||||
key={file.id}
|
||||
onClick={() => onShowImageModal(groupped.image, index)}
|
||||
>
|
||||
<img
|
||||
src={getURL(file, ImagePresets['600'])}
|
||||
alt={file.name}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.date}>{getPrettyDate(comment.created_at)}</div>
|
||||
<div className={styles.date}>
|
||||
{getPrettyDate(comment.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{groupped.audio && groupped.audio.length > 0 && (
|
||||
<Fragment>
|
||||
{groupped.audio.map(file => (
|
||||
<div className={classnames(styles.block, styles.block_audio)} key={file.id}>
|
||||
{groupped.audio.map((file) => (
|
||||
<div
|
||||
className={classnames(styles.block, styles.block_audio)}
|
||||
key={file.id}
|
||||
>
|
||||
{menu}
|
||||
|
||||
<AudioPlayer file={file} />
|
||||
|
||||
<div className={styles.date}>{getPrettyDate(comment.created_at)}</div>
|
||||
<div className={styles.date}>
|
||||
{getPrettyDate(comment.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export { CommentContent };
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
|
||||
interface IProps {}
|
||||
interface IProps {
|
||||
// don't wait for user refetch, trust hydration
|
||||
hydratedOnly?: boolean;
|
||||
}
|
||||
|
||||
const Authorized: FC<IProps> = ({ children }) => {
|
||||
const { isUser } = useAuth();
|
||||
const Authorized: FC<IProps> = observer(({ children, hydratedOnly }) => {
|
||||
const { isUser, fetched } = useAuth();
|
||||
|
||||
if (!isUser) return null;
|
||||
if (!isUser || (!hydratedOnly && !fetched)) return null;
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
});
|
||||
|
||||
export { Authorized };
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import React, { FC, Fragment } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { FlowCell } from '~/components/flow/FlowCell';
|
||||
import { flowDisplayToPreset, URLS } from '~/constants/urls';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
import { FlowDisplay, IFlowNode, INode } from '~/types';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { getURLFromString } from '~/utils/dom';
|
||||
|
@ -17,28 +19,38 @@ interface Props {
|
|||
onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void;
|
||||
}
|
||||
|
||||
export const FlowGrid: FC<Props> = ({ user, nodes, onChangeCellView }) => {
|
||||
if (!nodes) {
|
||||
return null;
|
||||
}
|
||||
export const FlowGrid: FC<Props> = observer(
|
||||
({ user, nodes, onChangeCellView }) => {
|
||||
const { fetched, isUser } = useAuth();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{nodes.map(node => (
|
||||
<div className={classNames(styles.cell, styles[node.flow.display])} key={node.id}>
|
||||
<FlowCell
|
||||
id={node.id}
|
||||
color={node.flow.dominant_color}
|
||||
to={URLS.NODE_URL(node.id)}
|
||||
image={getURLFromString(node.thumbnail, flowDisplayToPreset[node.flow.display])}
|
||||
flow={node.flow}
|
||||
text={node.description}
|
||||
title={node.title}
|
||||
canEdit={canEditNode(node, user)}
|
||||
onChangeCellView={onChangeCellView}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
if (!nodes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{nodes.map((node) => (
|
||||
<div
|
||||
className={classNames(styles.cell, styles[node.flow.display])}
|
||||
key={node.id}
|
||||
>
|
||||
<FlowCell
|
||||
id={node.id}
|
||||
color={node.flow.dominant_color}
|
||||
to={URLS.NODE_URL(node.id)}
|
||||
image={getURLFromString(
|
||||
node.thumbnail,
|
||||
flowDisplayToPreset[node.flow.display],
|
||||
)}
|
||||
flow={node.flow}
|
||||
text={node.description}
|
||||
title={node.title}
|
||||
canEdit={fetched && isUser && canEditNode(node, user)}
|
||||
onChangeCellView={onChangeCellView}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import classNames from 'classnames';
|
|||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface SeparatedMenuProps {
|
||||
export interface SeparatedMenuProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ const SeparatedMenu: FC<SeparatedMenuProps> = ({ children, className }) => {
|
|||
return [];
|
||||
}
|
||||
|
||||
return (Array.isArray(children) ? children : [children]).filter(it => it);
|
||||
return (Array.isArray(children) ? children : [children]).filter((it) => it);
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { memo, VFC } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Authorized } from '~/components/containers/Authorized';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
|
||||
import { NodeEditMenu } from '~/components/node/NodeEditMenu';
|
||||
|
@ -76,37 +77,39 @@ const NodeTitle: VFC<IProps> = memo(
|
|||
)}
|
||||
</div>
|
||||
|
||||
<SeparatedMenu className={styles.buttons}>
|
||||
{canEdit && (
|
||||
<NodeEditMenu
|
||||
className={styles.button}
|
||||
canStar={canStar}
|
||||
isHeroic={isHeroic}
|
||||
isLocked={isLocked}
|
||||
onStar={onStar}
|
||||
onLock={onLock}
|
||||
onEdit={onEdit}
|
||||
/>
|
||||
)}
|
||||
<Authorized>
|
||||
<SeparatedMenu className={styles.buttons}>
|
||||
{canEdit && (
|
||||
<NodeEditMenu
|
||||
className={styles.button}
|
||||
canStar={canStar}
|
||||
isHeroic={isHeroic}
|
||||
isLocked={isLocked}
|
||||
onStar={onStar}
|
||||
onLock={onLock}
|
||||
onEdit={onEdit}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canLike && (
|
||||
<div
|
||||
className={classNames(styles.button, styles.like, {
|
||||
[styles.is_liked]: isLiked,
|
||||
})}
|
||||
>
|
||||
{isLiked ? (
|
||||
<Icon icon="heart_full" size={24} onClick={onLike} />
|
||||
) : (
|
||||
<Icon icon="heart" size={24} onClick={onLike} />
|
||||
)}
|
||||
{canLike && (
|
||||
<div
|
||||
className={classNames(styles.button, styles.like, {
|
||||
[styles.is_liked]: isLiked,
|
||||
})}
|
||||
>
|
||||
{isLiked ? (
|
||||
<Icon icon="heart_full" size={24} onClick={onLike} />
|
||||
) : (
|
||||
<Icon icon="heart" size={24} onClick={onLike} />
|
||||
)}
|
||||
|
||||
{!!likeCount && likeCount > 0 && (
|
||||
<div className={styles.like_count}>{likeCount}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SeparatedMenu>
|
||||
{!!likeCount && likeCount > 0 && (
|
||||
<div className={styles.like_count}>{likeCount}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SeparatedMenu>
|
||||
</Authorized>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue