mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-05-03 16:46:41 +07:00
several bug fixes
This commit is contained in:
parent
f23b9166e1
commit
11348aa411
13 changed files with 281 additions and 110 deletions
src
components
node
NodeImageSlideBlock
NodeNoComments
NodePanel
NodePanelInner
NodeRelated
NodeTags
Tags
placeholders/Placeholder
containers/node/NodeLayout
styles
|
@ -228,12 +228,14 @@ const NodeImageSlideBlock: FC<IProps> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ImageSwitcher
|
{!is_loading && (
|
||||||
total={images.length}
|
<ImageSwitcher
|
||||||
current={current}
|
total={images.length}
|
||||||
onChange={changeCurrent}
|
current={current}
|
||||||
loaded={loaded}
|
onChange={changeCurrent}
|
||||||
/>
|
loaded={loaded}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.image_container}
|
className={styles.image_container}
|
||||||
|
@ -253,7 +255,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
|
||||||
is_active: index === current && loaded[index]
|
is_active: index === current && loaded[index]
|
||||||
})}
|
})}
|
||||||
ref={setRef(index)}
|
ref={setRef(index)}
|
||||||
key={file.id}
|
key={node.updated_at + file.id}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from "react";
|
||||||
import * as styles from './styles.scss';
|
import * as styles from "./styles.scss";
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from "~/components/containers/Group";
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
import { Filler } from '~/components/containers/Filler';
|
import { Filler } from "~/components/containers/Filler";
|
||||||
import { ERRORS } from '~/constants/errors';
|
import { ERRORS } from "~/constants/errors";
|
||||||
import { t } from '~/utils/trans';
|
import { t } from "~/utils/trans";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
is_loading?: boolean;
|
is_loading?: boolean;
|
||||||
|
@ -17,8 +17,6 @@ const NodeNoComments: FC<IProps> = ({ is_loading = false }) => (
|
||||||
<div className={styles.card}>{!is_loading && t(ERRORS.NO_COMMENTS)}</div>
|
<div className={styles.card}>{!is_loading && t(ERRORS.NO_COMMENTS)}</div>
|
||||||
<div className={styles.card} />
|
<div className={styles.card} />
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Filler />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,34 @@
|
||||||
@keyframes fade {
|
@keyframes fade {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.25;
|
// opacity: 0.25;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 0.1;
|
// opacity: 0.1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.wrap {
|
.wrap {
|
||||||
|
user-select: none;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@include after_shade($node_bg);
|
// @include after_shade($node_bg);
|
||||||
|
|
||||||
&:global(.is_loading) {
|
&:global(.is_loading) {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
|
background: $placeholder_bg;
|
||||||
animation: fade 0.5s infinite alternate;
|
animation: fade 0.5s infinite alternate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
opacity: 0.2;
|
opacity: 0.3;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
height: 96px;
|
height: 96px;
|
||||||
background: $comment_bg;
|
background: $placeholder_bg;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -33,7 +37,7 @@
|
||||||
color: transparentize(white, 0.5);
|
color: transparentize(white, 0.5);
|
||||||
flex: 0 0 $comment_height;
|
flex: 0 0 $comment_height;
|
||||||
|
|
||||||
@include outer_shadow();
|
// @include outer_shadow();
|
||||||
|
|
||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
// animation-delay: -300ms !important;
|
// animation-delay: -300ms !important;
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import React, { FC, useCallback, useEffect, useRef, useState, memo } from 'react';
|
import React, {
|
||||||
import * as styles from './styles.scss';
|
FC,
|
||||||
import { INode } from '~/redux/types';
|
useCallback,
|
||||||
import { createPortal } from 'react-dom';
|
useEffect,
|
||||||
import { NodePanelInner } from '~/components/node/NodePanelInner';
|
useRef,
|
||||||
import pick from 'ramda/es/pick';
|
useState,
|
||||||
|
memo
|
||||||
|
} from "react";
|
||||||
|
import * as styles from "./styles.scss";
|
||||||
|
import { INode } from "~/redux/types";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { NodePanelInner } from "~/components/node/NodePanelInner";
|
||||||
|
import pick from "ramda/es/pick";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: Partial<INode>;
|
node: Partial<INode>;
|
||||||
|
@ -13,13 +20,25 @@ interface IProps {
|
||||||
can_like: boolean;
|
can_like: boolean;
|
||||||
can_star: boolean;
|
can_star: boolean;
|
||||||
|
|
||||||
|
is_loading?: boolean;
|
||||||
|
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onLike: () => void;
|
onLike: () => void;
|
||||||
onStar: () => void;
|
onStar: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodePanel: FC<IProps> = memo(
|
const NodePanel: FC<IProps> = memo(
|
||||||
({ node, layout, can_edit, can_like, can_star, onEdit, onLike, onStar }) => {
|
({
|
||||||
|
node,
|
||||||
|
layout,
|
||||||
|
can_edit,
|
||||||
|
can_like,
|
||||||
|
can_star,
|
||||||
|
is_loading,
|
||||||
|
onEdit,
|
||||||
|
onLike,
|
||||||
|
onStar
|
||||||
|
}) => {
|
||||||
const [stack, setStack] = useState(false);
|
const [stack, setStack] = useState(false);
|
||||||
|
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
@ -35,12 +54,12 @@ const NodePanel: FC<IProps> = memo(
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPlace();
|
getPlace();
|
||||||
window.addEventListener('scroll', getPlace);
|
window.addEventListener("scroll", getPlace);
|
||||||
window.addEventListener('resize', getPlace);
|
window.addEventListener("resize", getPlace);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('scroll', getPlace);
|
window.removeEventListener("scroll", getPlace);
|
||||||
window.removeEventListener('resize', getPlace);
|
window.removeEventListener("resize", getPlace);
|
||||||
};
|
};
|
||||||
}, [layout]);
|
}, [layout]);
|
||||||
|
|
||||||
|
@ -57,6 +76,7 @@ const NodePanel: FC<IProps> = memo(
|
||||||
can_edit={can_edit}
|
can_edit={can_edit}
|
||||||
can_like={can_like}
|
can_like={can_like}
|
||||||
can_star={can_star}
|
can_star={can_star}
|
||||||
|
is_loading={is_loading}
|
||||||
/>,
|
/>,
|
||||||
document.body
|
document.body
|
||||||
)
|
)
|
||||||
|
@ -69,6 +89,7 @@ const NodePanel: FC<IProps> = memo(
|
||||||
can_edit={can_edit}
|
can_edit={can_edit}
|
||||||
can_like={can_like}
|
can_like={can_like}
|
||||||
can_star={can_star}
|
can_star={can_star}
|
||||||
|
is_loading={is_loading}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from "react";
|
||||||
import * as styles from './styles.scss';
|
import * as styles from "./styles.scss";
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from "~/components/containers/Group";
|
||||||
import { Filler } from '~/components/containers/Filler';
|
import { Filler } from "~/components/containers/Filler";
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from "~/components/input/Icon";
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from "~/redux/types";
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
|
import { Placeholder } from "~/components/placeholders/Placeholder";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: Partial<INode>;
|
node: Partial<INode>;
|
||||||
|
@ -13,6 +14,9 @@ interface IProps {
|
||||||
can_edit: boolean;
|
can_edit: boolean;
|
||||||
can_like: boolean;
|
can_like: boolean;
|
||||||
can_star: boolean;
|
can_star: boolean;
|
||||||
|
|
||||||
|
is_loading: boolean;
|
||||||
|
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onLike: () => void;
|
onLike: () => void;
|
||||||
onStar: () => void;
|
onStar: () => void;
|
||||||
|
@ -21,20 +25,34 @@ interface IProps {
|
||||||
const NodePanelInner: FC<IProps> = ({
|
const NodePanelInner: FC<IProps> = ({
|
||||||
node: { title, user, is_liked, is_heroic },
|
node: { title, user, is_liked, is_heroic },
|
||||||
stack,
|
stack,
|
||||||
|
|
||||||
can_star,
|
can_star,
|
||||||
can_edit,
|
can_edit,
|
||||||
can_like,
|
can_like,
|
||||||
|
|
||||||
|
is_loading,
|
||||||
|
|
||||||
onStar,
|
onStar,
|
||||||
onEdit,
|
onEdit,
|
||||||
onLike,
|
onLike
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.wrap, { stack })}>
|
<div className={classNames(styles.wrap, { stack })}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<Group horizontal className={styles.panel}>
|
<Group horizontal className={styles.panel}>
|
||||||
<Filler>
|
<Filler>
|
||||||
<div className={styles.title}>{title || '...'}</div>
|
<div className={styles.title}>
|
||||||
{user && user.username && <div className={styles.name}>~{user.username}</div>}
|
{is_loading ? <Placeholder width="40%" /> : title || "..."}
|
||||||
|
</div>
|
||||||
|
{user && user.username && (
|
||||||
|
<div className={styles.name}>
|
||||||
|
{is_loading ? (
|
||||||
|
<Placeholder width="100px" />
|
||||||
|
) : (
|
||||||
|
`~${user.username}`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Filler>
|
</Filler>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
|
31
src/components/node/NodeRelated/placeholder.tsx
Normal file
31
src/components/node/NodeRelated/placeholder.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React, { FC, memo } from "react";
|
||||||
|
import styles from "./styles.scss";
|
||||||
|
import cell_style from "~/components/node/NodeRelatedItem/styles.scss";
|
||||||
|
import { Group } from "~/components/containers/Group";
|
||||||
|
import { Placeholder } from "~/components/placeholders/Placeholder";
|
||||||
|
import range from "ramda/es/range";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
interface IProps {}
|
||||||
|
|
||||||
|
const NodeRelatedPlaceholder: FC<IProps> = memo(() => {
|
||||||
|
return (
|
||||||
|
<Group className={classNames(styles.wrap, styles.placeholder)}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
<div className={styles.line} />
|
||||||
|
<div className={styles.text}>
|
||||||
|
<Placeholder />
|
||||||
|
</div>
|
||||||
|
<div className={styles.line} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.grid}>
|
||||||
|
{range(0, 6).map(el => (
|
||||||
|
<div className={cell_style.item} key={el} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { NodeRelatedPlaceholder };
|
|
@ -36,3 +36,15 @@
|
||||||
.text {
|
.text {
|
||||||
margin: 0 $gap;
|
margin: 0 $gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
.text {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
div {
|
||||||
|
background: $placeholder_bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
17
src/components/node/NodeTags/placeholder.tsx
Normal file
17
src/components/node/NodeTags/placeholder.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import React, { FC, memo } from "react";
|
||||||
|
import { Tags } from "../Tags";
|
||||||
|
import { ITag } from "~/redux/types";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
is_editable?: boolean;
|
||||||
|
tags: ITag[];
|
||||||
|
onChange?: (tags: string[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodeTagsPlaceholder: FC<IProps> = memo(
|
||||||
|
({ is_editable, tags, onChange }) => (
|
||||||
|
<Tags tags={tags} is_editable={is_editable} onTagsChange={onChange} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export { NodeTagsPlaceholder };
|
|
@ -6,13 +6,12 @@ import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
KeyboardEvent,
|
KeyboardEvent,
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
useRef,
|
useRef
|
||||||
} from 'react';
|
} from "react";
|
||||||
import { TagField } from '~/components/containers/TagField';
|
import { TagField } from "~/components/containers/TagField";
|
||||||
import { ITag } from '~/redux/types';
|
import { ITag } from "~/redux/types";
|
||||||
import { Tag } from '~/components/node/Tag';
|
import { Tag } from "~/components/node/Tag";
|
||||||
import uniq from 'ramda/es/uniq';
|
import uniq from "ramda/es/uniq";
|
||||||
import assocPath from 'ramda/es/assocPath';
|
|
||||||
|
|
||||||
type IProps = HTMLAttributes<HTMLDivElement> & {
|
type IProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
tags: Partial<ITag>[];
|
tags: Partial<ITag>[];
|
||||||
|
@ -20,8 +19,13 @@ type IProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
onTagsChange?: (tags: string[]) => void;
|
onTagsChange?: (tags: string[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props }) => {
|
export const Tags: FC<IProps> = ({
|
||||||
const [input, setInput] = useState('');
|
tags,
|
||||||
|
is_editable,
|
||||||
|
onTagsChange,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [input, setInput] = useState("");
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
const timer = useRef(null);
|
const timer = useRef(null);
|
||||||
|
|
||||||
|
@ -35,17 +39,17 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props })
|
||||||
|
|
||||||
const onKeyUp = useCallback(
|
const onKeyUp = useCallback(
|
||||||
({ key }: KeyboardEvent) => {
|
({ key }: KeyboardEvent) => {
|
||||||
if (key === 'Backspace' && input === '' && data.length) {
|
if (key === "Backspace" && input === "" && data.length) {
|
||||||
setData(data.slice(0, data.length - 1));
|
setData(data.slice(0, data.length - 1));
|
||||||
setInput(data[data.length - 1].title);
|
setInput(data[data.length - 1].title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'Enter' || key === ',' || key === 'Comma') {
|
if (key === "Enter" || key === "," || key === "Comma") {
|
||||||
setData(
|
setData(
|
||||||
uniq([
|
uniq([
|
||||||
...data,
|
...data,
|
||||||
...input
|
...input
|
||||||
.split(',')
|
.split(",")
|
||||||
.map((title: string) =>
|
.map((title: string) =>
|
||||||
title
|
title
|
||||||
.trim()
|
.trim()
|
||||||
|
@ -55,11 +59,11 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props })
|
||||||
.filter(el => el.length > 0)
|
.filter(el => el.length > 0)
|
||||||
.filter(el => !tags.some(tag => tag.title.trim() === el.trim()))
|
.filter(el => !tags.some(tag => tag.title.trim() === el.trim()))
|
||||||
.map(title => ({
|
.map(title => ({
|
||||||
title,
|
title
|
||||||
})),
|
}))
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
setInput('');
|
setInput("");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[input, setInput, data, setData]
|
[input, setInput, data, setData]
|
||||||
|
@ -71,12 +75,16 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props })
|
||||||
|
|
||||||
if (!items.length) return;
|
if (!items.length) return;
|
||||||
setData(items);
|
setData(items);
|
||||||
setInput('');
|
setInput("");
|
||||||
onTagsChange(uniq([...tags, ...items]).map(tag => tag.title));
|
onTagsChange(uniq([...tags, ...items]).map(tag => tag.title));
|
||||||
}, [tags, data, onTagsChange, input, setInput]);
|
}, [tags, data, onTagsChange, input, setInput]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setData(data.filter(({ title }) => !tags.some(tag => tag.title.trim() === title.trim())));
|
setData(
|
||||||
|
data.filter(
|
||||||
|
({ title }) => !tags.some(tag => tag.title.trim() === title.trim())
|
||||||
|
)
|
||||||
|
);
|
||||||
}, [tags]);
|
}, [tags]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -90,7 +98,12 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props })
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{is_editable && (
|
{is_editable && (
|
||||||
<Tag tag={{ title: input }} onInput={onInput} onKeyUp={onKeyUp} onBlur={onSubmit} />
|
<Tag
|
||||||
|
tag={{ title: input }}
|
||||||
|
onInput={onInput}
|
||||||
|
onKeyUp={onKeyUp}
|
||||||
|
onBlur={onSubmit}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</TagField>
|
</TagField>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.placeholder {
|
.placeholder {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
background: transparentize(white, 0.95);
|
background: $placeholder_bg;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,36 @@
|
||||||
import React, { FC, createElement, useEffect, useCallback, useState, useMemo, memo } from 'react';
|
import React, {
|
||||||
import { RouteComponentProps } from 'react-router';
|
FC,
|
||||||
import { connect } from 'react-redux';
|
createElement,
|
||||||
import { canEditNode, canLikeNode, canStarNode } from '~/utils/node';
|
useEffect,
|
||||||
import { selectNode } from '~/redux/node/selectors';
|
useCallback,
|
||||||
import { Card } from '~/components/containers/Card';
|
useState,
|
||||||
|
useMemo,
|
||||||
|
memo
|
||||||
|
} from "react";
|
||||||
|
import { RouteComponentProps } from "react-router";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { canEditNode, canLikeNode, canStarNode } from "~/utils/node";
|
||||||
|
import { selectNode } from "~/redux/node/selectors";
|
||||||
|
import { Card } from "~/components/containers/Card";
|
||||||
|
|
||||||
import { NodePanel } from '~/components/node/NodePanel';
|
import { NodePanel } from "~/components/node/NodePanel";
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from "~/components/containers/Group";
|
||||||
import { Padder } from '~/components/containers/Padder';
|
import { Padder } from "~/components/containers/Padder";
|
||||||
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
import { NodeNoComments } from "~/components/node/NodeNoComments";
|
||||||
import { NodeRelated } from '~/components/node/NodeRelated';
|
import { NodeRelated } from "~/components/node/NodeRelated";
|
||||||
import * as styles from './styles.scss';
|
import * as styles from "./styles.scss";
|
||||||
import { NodeComments } from '~/components/node/NodeComments';
|
import { NodeComments } from "~/components/node/NodeComments";
|
||||||
import { NodeTags } from '~/components/node/NodeTags';
|
import { NodeTags } from "~/components/node/NodeTags";
|
||||||
import { NODE_COMPONENTS, NODE_INLINES } from '~/redux/node/constants';
|
import { NODE_COMPONENTS, NODE_INLINES } from "~/redux/node/constants";
|
||||||
import * as NODE_ACTIONS from '~/redux/node/actions';
|
import * as NODE_ACTIONS from "~/redux/node/actions";
|
||||||
import { CommentForm } from '~/components/node/CommentForm';
|
import { CommentForm } from "~/components/node/CommentForm";
|
||||||
import { selectUser } from '~/redux/auth/selectors';
|
import { selectUser } from "~/redux/auth/selectors";
|
||||||
import pick from 'ramda/es/pick';
|
import pick from "ramda/es/pick";
|
||||||
|
import { NodeRelatedPlaceholder } from "~/components/node/NodeRelated/placeholder";
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
node: selectNode(state),
|
node: selectNode(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state)
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
@ -30,7 +39,7 @@ const mapDispatchToProps = {
|
||||||
nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage,
|
nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage,
|
||||||
nodeEdit: NODE_ACTIONS.nodeEdit,
|
nodeEdit: NODE_ACTIONS.nodeEdit,
|
||||||
nodeLike: NODE_ACTIONS.nodeLike,
|
nodeLike: NODE_ACTIONS.nodeLike,
|
||||||
nodeStar: NODE_ACTIONS.nodeStar,
|
nodeStar: NODE_ACTIONS.nodeStar
|
||||||
};
|
};
|
||||||
|
|
||||||
type IProps = ReturnType<typeof mapStateToProps> &
|
type IProps = ReturnType<typeof mapStateToProps> &
|
||||||
|
@ -40,9 +49,15 @@ type IProps = ReturnType<typeof mapStateToProps> &
|
||||||
const NodeLayoutUnconnected: FC<IProps> = memo(
|
const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
({
|
({
|
||||||
match: {
|
match: {
|
||||||
params: { id },
|
params: { id }
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
is_loading,
|
||||||
|
is_loading_comments,
|
||||||
|
comments = [],
|
||||||
|
current: node,
|
||||||
|
related
|
||||||
},
|
},
|
||||||
node: { is_loading, is_loading_comments, comments = [], current: node, related },
|
|
||||||
user,
|
user,
|
||||||
user: { is_user },
|
user: { is_user },
|
||||||
nodeGotoNode,
|
nodeGotoNode,
|
||||||
|
@ -50,8 +65,10 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
nodeEdit,
|
nodeEdit,
|
||||||
nodeLike,
|
nodeLike,
|
||||||
nodeStar,
|
nodeStar,
|
||||||
nodeSetCoverImage,
|
nodeSetCoverImage
|
||||||
}) => {
|
}) => {
|
||||||
|
// const is_loading = true;
|
||||||
|
|
||||||
const [layout, setLayout] = useState({});
|
const [layout, setLayout] = useState({});
|
||||||
|
|
||||||
const updateLayout = useCallback(() => setLayout({}), []);
|
const updateLayout = useCallback(() => setLayout({}), []);
|
||||||
|
@ -86,11 +103,12 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
}, [nodeSetCoverImage, node.cover]);
|
}, [nodeSetCoverImage, node.cover]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={styles.node} seamless key={id}>
|
<Card className={styles.node} seamless>
|
||||||
{block && createElement(block, { node, is_loading, updateLayout, layout })}
|
{block &&
|
||||||
|
createElement(block, { node, is_loading, updateLayout, layout })}
|
||||||
|
|
||||||
<NodePanel
|
<NodePanel
|
||||||
node={pick(['title', 'user', 'is_liked', 'is_heroic'], node)}
|
node={pick(["title", "user", "is_liked", "is_heroic"], node)}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
can_edit={can_edit}
|
can_edit={can_edit}
|
||||||
can_like={can_like}
|
can_like={can_like}
|
||||||
|
@ -98,6 +116,7 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onLike={onLike}
|
onLike={onLike}
|
||||||
onStar={onStar}
|
onStar={onStar}
|
||||||
|
is_loading={is_loading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
|
@ -106,32 +125,57 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
<Group className={styles.comments}>
|
<Group className={styles.comments}>
|
||||||
{inline_block && (
|
{inline_block && (
|
||||||
<div className={styles.inline_block}>
|
<div className={styles.inline_block}>
|
||||||
{createElement(inline_block, { node, is_loading, updateLayout, layout })}
|
{createElement(inline_block, {
|
||||||
|
node,
|
||||||
|
is_loading,
|
||||||
|
updateLayout,
|
||||||
|
layout
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{is_loading_comments || !comments.length ? (
|
{is_loading ||
|
||||||
<NodeNoComments is_loading={is_loading_comments} />
|
is_loading_comments ||
|
||||||
|
(!comments.length && !inline_block) ? (
|
||||||
|
<NodeNoComments
|
||||||
|
is_loading={is_loading_comments || is_loading}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<NodeComments comments={comments} />
|
<NodeComments comments={comments} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{is_user && <CommentForm id={0} />}
|
{is_user && !is_loading && <CommentForm id={0} />}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<div className={styles.panel}>
|
<div className={styles.panel}>
|
||||||
<Group style={{ flex: 1, minWidth: 0 }}>
|
<Group style={{ flex: 1, minWidth: 0 }}>
|
||||||
<NodeTags is_editable={is_user} tags={node.tags} onChange={onTagsChange} />
|
{!is_loading && (
|
||||||
|
<NodeTags
|
||||||
|
is_editable={is_user}
|
||||||
|
tags={node.tags}
|
||||||
|
onChange={onTagsChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{related &&
|
{is_loading && <NodeRelatedPlaceholder />}
|
||||||
|
|
||||||
|
{!is_loading &&
|
||||||
|
related &&
|
||||||
related.albums &&
|
related.albums &&
|
||||||
Object.keys(related.albums).map(album => (
|
Object.keys(related.albums).map(album => (
|
||||||
<NodeRelated title={album} items={related.albums[album]} key={album} />
|
<NodeRelated
|
||||||
|
title={album}
|
||||||
|
items={related.albums[album]}
|
||||||
|
key={album}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{related && related.similar && related.similar.length > 0 && (
|
{!is_loading &&
|
||||||
<NodeRelated title="ПОХОЖИЕ" items={related.similar} />
|
related &&
|
||||||
)}
|
related.similar &&
|
||||||
|
related.similar.length > 0 && (
|
||||||
|
<NodeRelated title="ПОХОЖИЕ" items={related.similar} />
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
.comments {
|
.comments {
|
||||||
flex: 3 1;
|
flex: 3 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@import 'colors';
|
@import "colors";
|
||||||
|
|
||||||
$cell: 280px;
|
$cell: 280px;
|
||||||
$gap: 10px;
|
$gap: 10px;
|
||||||
|
@ -13,6 +13,7 @@ $cell_radius: $radius;
|
||||||
$panel_radius: $radius;
|
$panel_radius: $radius;
|
||||||
$input_radius: $radius;
|
$input_radius: $radius;
|
||||||
$dialog_radius: $radius * 2;
|
$dialog_radius: $radius * 2;
|
||||||
|
$placeholder_bg: transparentize(white, 0.96);
|
||||||
|
|
||||||
$input_height: 36px;
|
$input_height: 36px;
|
||||||
$info_height: 24px;
|
$info_height: 24px;
|
||||||
|
@ -30,9 +31,9 @@ $extra_light: 200;
|
||||||
|
|
||||||
$upload_button_height: 52px;
|
$upload_button_height: 52px;
|
||||||
|
|
||||||
$font: Montserrat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
$font: Montserrat, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
|
||||||
'Noto Color Emoji';
|
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
|
||||||
$font_48_semibold: $semibold 48px $font;
|
$font_48_semibold: $semibold 48px $font;
|
||||||
$font_48_bold: $bold 48px $font;
|
$font_48_bold: $bold 48px $font;
|
||||||
|
@ -60,11 +61,14 @@ $font_8_semibold: $semibold 8px $font;
|
||||||
$font_cell_title: $bold 30px $font;
|
$font_cell_title: $bold 30px $font;
|
||||||
$font_hero_title: $bold 40px $font;
|
$font_hero_title: $bold 40px $font;
|
||||||
|
|
||||||
$shadow_depth_1: transparentize(black, 0.8) 0 1px, inset transparentize(white, 0.98) 0 1px;
|
$shadow_depth_1: transparentize(black, 0.8) 0 1px,
|
||||||
$shadow_depth_2: transparentize(black, 0.8) 0 2px, inset transparentize(white, 0.98) 0 1px;
|
inset transparentize(white, 0.98) 0 1px;
|
||||||
|
$shadow_depth_2: transparentize(black, 0.8) 0 2px,
|
||||||
|
inset transparentize(white, 0.98) 0 1px;
|
||||||
|
|
||||||
$comment_shadow: $shadow_depth_2;
|
$comment_shadow: $shadow_depth_2;
|
||||||
$node_shadow: transparentize(black, 0.8) 0 2px, transparentize(black, 0.8) 0 2px 4px;
|
$node_shadow: transparentize(black, 0.8) 0 2px,
|
||||||
|
transparentize(black, 0.8) 0 2px 4px;
|
||||||
|
|
||||||
$tag_height: 26px;
|
$tag_height: 26px;
|
||||||
|
|
||||||
|
@ -73,15 +77,18 @@ $input_shadow_error: inset $red 0 0 0 1px;
|
||||||
$input_shadow_filled: $input_shadow;
|
$input_shadow_filled: $input_shadow;
|
||||||
|
|
||||||
@mixin outer_shadow() {
|
@mixin outer_shadow() {
|
||||||
box-shadow: inset transparentize(white, 0.95) 0 1px, transparentize(black, 0.8) -1px -1px;
|
box-shadow: inset transparentize(white, 0.95) 0 1px,
|
||||||
|
transparentize(black, 0.8) -1px -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin inner_shadow() {
|
@mixin inner_shadow() {
|
||||||
box-shadow: inset transparentize(white, 0.95) 0 -1px, inset transparentize(black, 0.5) 0 1px;
|
box-shadow: inset transparentize(white, 0.95) 0 -1px,
|
||||||
|
inset transparentize(black, 0.5) 0 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin input_shadow() {
|
@mixin input_shadow() {
|
||||||
box-shadow: inset transparentize(white, 0.92) 0 -1px, inset transparentize(black, 0.8) 0 1px;
|
box-shadow: inset transparentize(white, 0.92) 0 -1px,
|
||||||
|
inset transparentize(black, 0.8) 0 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin modal_mixin() {
|
@mixin modal_mixin() {
|
||||||
|
@ -97,7 +104,7 @@ $input_shadow_filled: $input_shadow;
|
||||||
position: $position;
|
position: $position;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: ' ';
|
content: " ";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue