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

several bug fixes

This commit is contained in:
Fedor Katurov 2019-11-15 13:36:13 +07:00
parent f23b9166e1
commit 11348aa411
13 changed files with 281 additions and 110 deletions

View file

@ -228,12 +228,14 @@ const NodeImageSlideBlock: FC<IProps> = ({
</div>
)}
<ImageSwitcher
total={images.length}
current={current}
onChange={changeCurrent}
loaded={loaded}
/>
{!is_loading && (
<ImageSwitcher
total={images.length}
current={current}
onChange={changeCurrent}
loaded={loaded}
/>
)}
<div
className={styles.image_container}
@ -253,7 +255,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
is_active: index === current && loaded[index]
})}
ref={setRef(index)}
key={file.id}
key={node.updated_at + file.id}
>
<img
className={styles.image}

View file

@ -1,10 +1,10 @@
import React, { FC } from 'react';
import * as styles from './styles.scss';
import { Group } from '~/components/containers/Group';
import classNames from 'classnames';
import { Filler } from '~/components/containers/Filler';
import { ERRORS } from '~/constants/errors';
import { t } from '~/utils/trans';
import React, { FC } from "react";
import * as styles from "./styles.scss";
import { Group } from "~/components/containers/Group";
import classNames from "classnames";
import { Filler } from "~/components/containers/Filler";
import { ERRORS } from "~/constants/errors";
import { t } from "~/utils/trans";
interface IProps {
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} />
</Group>
<Filler />
</>
);

View file

@ -1,30 +1,34 @@
@keyframes fade {
0% {
opacity: 0.25;
// opacity: 0.25;
}
100% {
opacity: 0.1;
// opacity: 0.1;
}
}
.wrap {
user-select: none;
height: 300px;
overflow: hidden;
position: relative;
@include after_shade($node_bg);
// @include after_shade($node_bg);
&:global(.is_loading) {
opacity: 1;
.card {
background: $placeholder_bg;
animation: fade 0.5s infinite alternate;
}
}
}
.card {
opacity: 0.2;
opacity: 0.3;
border-radius: $radius;
height: 96px;
background: $comment_bg;
background: $placeholder_bg;
display: flex;
align-items: center;
justify-content: center;
@ -33,7 +37,7 @@
color: transparentize(white, 0.5);
flex: 0 0 $comment_height;
@include outer_shadow();
// @include outer_shadow();
&:nth-child(2) {
// animation-delay: -300ms !important;

View file

@ -1,9 +1,16 @@
import React, { FC, useCallback, useEffect, useRef, 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';
import React, {
FC,
useCallback,
useEffect,
useRef,
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 {
node: Partial<INode>;
@ -13,13 +20,25 @@ interface IProps {
can_like: boolean;
can_star: boolean;
is_loading?: boolean;
onEdit: () => void;
onLike: () => void;
onStar: () => void;
}
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 ref = useRef(null);
@ -35,12 +54,12 @@ const NodePanel: FC<IProps> = memo(
useEffect(() => {
getPlace();
window.addEventListener('scroll', getPlace);
window.addEventListener('resize', getPlace);
window.addEventListener("scroll", getPlace);
window.addEventListener("resize", getPlace);
return () => {
window.removeEventListener('scroll', getPlace);
window.removeEventListener('resize', getPlace);
window.removeEventListener("scroll", getPlace);
window.removeEventListener("resize", getPlace);
};
}, [layout]);
@ -57,6 +76,7 @@ const NodePanel: FC<IProps> = memo(
can_edit={can_edit}
can_like={can_like}
can_star={can_star}
is_loading={is_loading}
/>,
document.body
)
@ -69,6 +89,7 @@ const NodePanel: FC<IProps> = memo(
can_edit={can_edit}
can_like={can_like}
can_star={can_star}
is_loading={is_loading}
/>
)}
</div>

View file

@ -1,10 +1,11 @@
import React, { FC } from 'react';
import * as styles from './styles.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';
import React, { FC } from "react";
import * as styles from "./styles.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";
import { Placeholder } from "~/components/placeholders/Placeholder";
interface IProps {
node: Partial<INode>;
@ -13,6 +14,9 @@ interface IProps {
can_edit: boolean;
can_like: boolean;
can_star: boolean;
is_loading: boolean;
onEdit: () => void;
onLike: () => void;
onStar: () => void;
@ -21,20 +25,34 @@ interface IProps {
const NodePanelInner: FC<IProps> = ({
node: { title, user, is_liked, is_heroic },
stack,
can_star,
can_edit,
can_like,
is_loading,
onStar,
onEdit,
onLike,
onLike
}) => {
return (
<div className={classNames(styles.wrap, { stack })}>
<div className={styles.content}>
<Group horizontal className={styles.panel}>
<Filler>
<div className={styles.title}>{title || '...'}</div>
{user && user.username && <div className={styles.name}>~{user.username}</div>}
<div className={styles.title}>
{is_loading ? <Placeholder width="40%" /> : title || "..."}
</div>
{user && user.username && (
<div className={styles.name}>
{is_loading ? (
<Placeholder width="100px" />
) : (
`~${user.username}`
)}
</div>
)}
</Filler>
</Group>

View 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 };

View file

@ -36,3 +36,15 @@
.text {
margin: 0 $gap;
}
.placeholder {
.text {
opacity: 1;
}
.grid {
div {
background: $placeholder_bg;
}
}
}

View 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 };

View file

@ -6,13 +6,12 @@ import React, {
useEffect,
KeyboardEvent,
ChangeEvent,
useRef,
} from 'react';
import { TagField } from '~/components/containers/TagField';
import { ITag } from '~/redux/types';
import { Tag } from '~/components/node/Tag';
import uniq from 'ramda/es/uniq';
import assocPath from 'ramda/es/assocPath';
useRef
} from "react";
import { TagField } from "~/components/containers/TagField";
import { ITag } from "~/redux/types";
import { Tag } from "~/components/node/Tag";
import uniq from "ramda/es/uniq";
type IProps = HTMLAttributes<HTMLDivElement> & {
tags: Partial<ITag>[];
@ -20,8 +19,13 @@ type IProps = HTMLAttributes<HTMLDivElement> & {
onTagsChange?: (tags: string[]) => void;
};
export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props }) => {
const [input, setInput] = useState('');
export const Tags: FC<IProps> = ({
tags,
is_editable,
onTagsChange,
...props
}) => {
const [input, setInput] = useState("");
const [data, setData] = useState([]);
const timer = useRef(null);
@ -35,17 +39,17 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props })
const onKeyUp = useCallback(
({ key }: KeyboardEvent) => {
if (key === 'Backspace' && input === '' && data.length) {
if (key === "Backspace" && input === "" && data.length) {
setData(data.slice(0, data.length - 1));
setInput(data[data.length - 1].title);
}
if (key === 'Enter' || key === ',' || key === 'Comma') {
if (key === "Enter" || key === "," || key === "Comma") {
setData(
uniq([
...data,
...input
.split(',')
.split(",")
.map((title: string) =>
title
.trim()
@ -55,11 +59,11 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props })
.filter(el => el.length > 0)
.filter(el => !tags.some(tag => tag.title.trim() === el.trim()))
.map(title => ({
title,
})),
title
}))
])
);
setInput('');
setInput("");
}
},
[input, setInput, data, setData]
@ -71,12 +75,16 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props })
if (!items.length) return;
setData(items);
setInput('');
setInput("");
onTagsChange(uniq([...tags, ...items]).map(tag => tag.title));
}, [tags, data, onTagsChange, input, setInput]);
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]);
return (
@ -90,7 +98,12 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, ...props })
))}
{is_editable && (
<Tag tag={{ title: input }} onInput={onInput} onKeyUp={onKeyUp} onBlur={onSubmit} />
<Tag
tag={{ title: input }}
onInput={onInput}
onKeyUp={onKeyUp}
onBlur={onSubmit}
/>
)}
</TagField>
);

View file

@ -1,6 +1,6 @@
.placeholder {
height: 1em;
width: 120px;
background: transparentize(white, 0.95);
background: $placeholder_bg;
border-radius: 1em;
}

View file

@ -1,27 +1,36 @@
import React, { FC, createElement, useEffect, useCallback, 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 React, {
FC,
createElement,
useEffect,
useCallback,
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 { Group } from '~/components/containers/Group';
import { Padder } from '~/components/containers/Padder';
import { NodeNoComments } from '~/components/node/NodeNoComments';
import { NodeRelated } from '~/components/node/NodeRelated';
import * as styles from './styles.scss';
import { NodeComments } from '~/components/node/NodeComments';
import { NodeTags } from '~/components/node/NodeTags';
import { NODE_COMPONENTS, NODE_INLINES } from '~/redux/node/constants';
import * as NODE_ACTIONS from '~/redux/node/actions';
import { CommentForm } from '~/components/node/CommentForm';
import { selectUser } from '~/redux/auth/selectors';
import pick from 'ramda/es/pick';
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 * as styles from "./styles.scss";
import { NodeComments } from "~/components/node/NodeComments";
import { NodeTags } from "~/components/node/NodeTags";
import { NODE_COMPONENTS, NODE_INLINES } from "~/redux/node/constants";
import * as NODE_ACTIONS from "~/redux/node/actions";
import { CommentForm } from "~/components/node/CommentForm";
import { selectUser } from "~/redux/auth/selectors";
import pick from "ramda/es/pick";
import { NodeRelatedPlaceholder } from "~/components/node/NodeRelated/placeholder";
const mapStateToProps = state => ({
node: selectNode(state),
user: selectUser(state),
user: selectUser(state)
});
const mapDispatchToProps = {
@ -30,7 +39,7 @@ const mapDispatchToProps = {
nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage,
nodeEdit: NODE_ACTIONS.nodeEdit,
nodeLike: NODE_ACTIONS.nodeLike,
nodeStar: NODE_ACTIONS.nodeStar,
nodeStar: NODE_ACTIONS.nodeStar
};
type IProps = ReturnType<typeof mapStateToProps> &
@ -40,9 +49,15 @@ type IProps = ReturnType<typeof mapStateToProps> &
const NodeLayoutUnconnected: FC<IProps> = memo(
({
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: { is_user },
nodeGotoNode,
@ -50,8 +65,10 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
nodeEdit,
nodeLike,
nodeStar,
nodeSetCoverImage,
nodeSetCoverImage
}) => {
// const is_loading = true;
const [layout, setLayout] = useState({});
const updateLayout = useCallback(() => setLayout({}), []);
@ -86,11 +103,12 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
}, [nodeSetCoverImage, node.cover]);
return (
<Card className={styles.node} seamless key={id}>
{block && createElement(block, { node, is_loading, updateLayout, layout })}
<Card className={styles.node} seamless>
{block &&
createElement(block, { node, is_loading, updateLayout, layout })}
<NodePanel
node={pick(['title', 'user', 'is_liked', 'is_heroic'], node)}
node={pick(["title", "user", "is_liked", "is_heroic"], node)}
layout={layout}
can_edit={can_edit}
can_like={can_like}
@ -98,6 +116,7 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
onEdit={onEdit}
onLike={onLike}
onStar={onStar}
is_loading={is_loading}
/>
<Group>
@ -106,32 +125,57 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
<Group className={styles.comments}>
{inline_block && (
<div className={styles.inline_block}>
{createElement(inline_block, { node, is_loading, updateLayout, layout })}
{createElement(inline_block, {
node,
is_loading,
updateLayout,
layout
})}
</div>
)}
{is_loading_comments || !comments.length ? (
<NodeNoComments is_loading={is_loading_comments} />
{is_loading ||
is_loading_comments ||
(!comments.length && !inline_block) ? (
<NodeNoComments
is_loading={is_loading_comments || is_loading}
/>
) : (
<NodeComments comments={comments} />
)}
{is_user && <CommentForm id={0} />}
{is_user && !is_loading && <CommentForm id={0} />}
</Group>
<div className={styles.panel}>
<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 &&
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 && (
<NodeRelated title="ПОХОЖИЕ" items={related.similar} />
)}
{!is_loading &&
related &&
related.similar &&
related.similar.length > 0 && (
<NodeRelated title="ПОХОЖИЕ" items={related.similar} />
)}
</Group>
</div>
</Group>

View file

@ -7,6 +7,10 @@
.comments {
flex: 3 1;
min-width: 0;
display: flex;
align-items: stretch;
justify-content: flex-start;
flex-direction: column;
}
.panel {

View file

@ -1,4 +1,4 @@
@import 'colors';
@import "colors";
$cell: 280px;
$gap: 10px;
@ -13,6 +13,7 @@ $cell_radius: $radius;
$panel_radius: $radius;
$input_radius: $radius;
$dialog_radius: $radius * 2;
$placeholder_bg: transparentize(white, 0.96);
$input_height: 36px;
$info_height: 24px;
@ -30,9 +31,9 @@ $extra_light: 200;
$upload_button_height: 52px;
$font: Montserrat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
$font: Montserrat, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
$font_48_semibold: $semibold 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_hero_title: $bold 40px $font;
$shadow_depth_1: transparentize(black, 0.8) 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;
$shadow_depth_1: transparentize(black, 0.8) 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;
$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;
@ -73,15 +77,18 @@ $input_shadow_error: inset $red 0 0 0 1px;
$input_shadow_filled: $input_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() {
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() {
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() {
@ -97,7 +104,7 @@ $input_shadow_filled: $input_shadow;
position: $position;
&::after {
content: ' ';
content: " ";
position: absolute;
bottom: 0;
left: 0;