mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
Merge branch 'master' into develop
This commit is contained in:
commit
2b519a07b7
85 changed files with 948 additions and 566 deletions
4
.env.local
Normal file
4
.env.local
Normal file
|
@ -0,0 +1,4 @@
|
|||
#REACT_APP_API_HOST=http://localhost:3334/
|
||||
NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
|
||||
#REACT_APP_API_HOST=https://pig.vault48.org/
|
||||
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
|
13
next.config.js
Normal file
13
next.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const withTM = require('next-transpile-modules')(['ramda']);
|
||||
|
||||
module.exports = withTM({
|
||||
/* Your Next.js config */
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/post:id',
|
||||
destination: '/node/:id',
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
|
@ -40,6 +40,7 @@
|
|||
"react-scripts": "^5.0.0",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"react-sticky-box": "^0.9.3",
|
||||
"react-stickynode": "^4.0.0",
|
||||
"sticky-sidebar": "^3.3.1",
|
||||
"swiper": "^6.8.4",
|
||||
"swr": "^1.0.1",
|
||||
|
@ -86,6 +87,7 @@
|
|||
"craco-alias": "^2.3.1",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.1.6",
|
||||
"next-transpile-modules": "^9.0.0",
|
||||
"prettier": "^1.18.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
|
|
@ -5,11 +5,14 @@ import markdown from '~/styles/common/markdown.module.scss';
|
|||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import { useShowModal } from '~/hooks/modal/useShowModal';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const BorisUIDemo: FC<IProps> = () => {
|
||||
const [text, setText] = useState('');
|
||||
const openProfileSidebar = useShowModal(Dialog.ProfileSidebar);
|
||||
|
||||
return (
|
||||
<Card className={styles.card}>
|
||||
|
@ -20,6 +23,9 @@ const BorisUIDemo: FC<IProps> = () => {
|
|||
разработке
|
||||
</p>
|
||||
|
||||
<h2>Тестовые фичи</h2>
|
||||
<Button onClick={() => openProfileSidebar({})}>Профиль в сайдбаре</Button>
|
||||
|
||||
<h2>Инпуты</h2>
|
||||
|
||||
<form autoComplete="off">
|
||||
|
|
|
@ -36,9 +36,9 @@ const CommentAvatar: FC<Props> = ({ user, withDetails, className }) => {
|
|||
url={path(['photo', 'url'], user)}
|
||||
username={user.username}
|
||||
className={className}
|
||||
innerRef={ref}
|
||||
onMouseOver={onMouseOver}
|
||||
onMouseOut={onMouseOut}
|
||||
ref={ref}
|
||||
/>
|
||||
)}
|
||||
</Reference>
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import React, { forwardRef, KeyboardEventHandler, TextareaHTMLAttributes, useCallback } from 'react';
|
||||
import React, {
|
||||
forwardRef,
|
||||
KeyboardEventHandler,
|
||||
TextareaHTMLAttributes,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { Textarea } from '~/components/input/Textarea';
|
||||
import { useCommentFormContext } from '~/hooks/comments/useCommentFormFormik';
|
||||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
|
@ -11,8 +16,8 @@ const LocalCommentFormTextarea = forwardRef<HTMLTextAreaElement, IProps>(({ ...r
|
|||
const { values, handleChange, handleSubmit, isSubmitting } = useCommentFormContext();
|
||||
|
||||
const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
|
||||
({ ctrlKey, key }) => {
|
||||
if (ctrlKey && key === 'Enter') handleSubmit(undefined);
|
||||
({ ctrlKey, key, metaKey }) => {
|
||||
if ((ctrlKey || metaKey) && key === 'Enter') handleSubmit(undefined);
|
||||
},
|
||||
[handleSubmit]
|
||||
);
|
||||
|
|
18
src/components/common/Anchor/index.tsx
Normal file
18
src/components/common/Anchor/index.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React, { VFC } from 'react';
|
||||
import { LinkProps } from '~/utils/types';
|
||||
import { CONFIG } from '~/utils/config';
|
||||
import NextLink from 'next/link';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface AnchorProps extends LinkProps {}
|
||||
|
||||
const Anchor: VFC<AnchorProps> = ({ ref, href, ...rest }) =>
|
||||
CONFIG.isNextEnvironment ? (
|
||||
<NextLink href={href ?? ''} passHref>
|
||||
<a {...rest} />
|
||||
</NextLink>
|
||||
) : (
|
||||
<Link {...rest} to={href ?? ''} />
|
||||
);
|
||||
|
||||
export { Anchor };
|
|
@ -1,40 +1,34 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import React, { FC, forwardRef, useCallback } from 'react';
|
||||
import { getURLFromString } from '~/utils/dom';
|
||||
import { PRESETS } from '~/constants/urls';
|
||||
import styles from './styles.module.scss';
|
||||
import classNames from 'classnames';
|
||||
import { openUserProfile } from '~/utils/user';
|
||||
import { DivProps } from '~/utils/types';
|
||||
import { Square } from '~/components/common/Square';
|
||||
|
||||
interface Props extends DivProps {
|
||||
url?: string;
|
||||
username?: string;
|
||||
size?: number;
|
||||
preset?: typeof PRESETS[keyof typeof PRESETS];
|
||||
innerRef?: React.Ref<any>;
|
||||
}
|
||||
|
||||
const Avatar: FC<Props> = ({
|
||||
url,
|
||||
username,
|
||||
size,
|
||||
className,
|
||||
innerRef,
|
||||
preset = PRESETS.avatar,
|
||||
...rest
|
||||
}) => {
|
||||
const backgroundImage = !!url ? `url('${getURLFromString(url, preset)}')` : undefined;
|
||||
const Avatar = forwardRef<HTMLDivElement, Props>(
|
||||
({ url, username, size, className, preset = PRESETS.avatar, ...rest }, ref) => {
|
||||
const onOpenProfile = useCallback(() => openUserProfile(username), [username]);
|
||||
|
||||
return (
|
||||
<div
|
||||
<Square
|
||||
{...rest}
|
||||
image={getURLFromString(url, preset)}
|
||||
className={classNames(styles.avatar, className)}
|
||||
style={{ backgroundImage }}
|
||||
onClick={onOpenProfile}
|
||||
ref={innerRef}
|
||||
size={size}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export { Avatar };
|
||||
|
|
27
src/components/common/ScrollHelperBottom/index.tsx
Normal file
27
src/components/common/ScrollHelperBottom/index.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React, { VFC } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { useScrollTop } from '~/hooks/dom/useScrollTop';
|
||||
import { useScrollHeight } from '~/hooks/dom/useScrollHeight';
|
||||
import classNames from 'classnames';
|
||||
import { useScrollToBottom } from '~/hooks/dom/useScrollToBottom';
|
||||
|
||||
interface ScrollHelperBottomProps {}
|
||||
|
||||
const ScrollHelperBottom: VFC<ScrollHelperBottomProps> = () => {
|
||||
const top = useScrollTop();
|
||||
const scrollHeight = useScrollHeight();
|
||||
const scrollToBottom = useScrollToBottom();
|
||||
|
||||
const isVisible = scrollHeight > 2000 && top < scrollHeight * 0.25;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.helper, { [styles.visible]: isVisible })}
|
||||
onClick={scrollToBottom}
|
||||
>
|
||||
Вниз
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ScrollHelperBottom };
|
26
src/components/common/ScrollHelperBottom/styles.module.scss
Normal file
26
src/components/common/ScrollHelperBottom/styles.module.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.helper {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
background: radial-gradient($red, transparent) 50% 24px no-repeat;
|
||||
background-size: 100% 48px;
|
||||
display: none;
|
||||
width: calc(100% - 20px);
|
||||
z-index: 4;
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
height: 64px;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: $gap;
|
||||
text-transform: uppercase;
|
||||
font: $font_16_medium;
|
||||
border-radius: $radius $radius 0 0;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.visible {
|
||||
display: flex;
|
||||
}
|
||||
}
|
29
src/components/common/Square/index.tsx
Normal file
29
src/components/common/Square/index.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { DivProps } from '~/utils/types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface SquareProps extends DivProps {
|
||||
image?: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const Square = forwardRef<HTMLDivElement, SquareProps>(
|
||||
({ image, size, children, ...rest }, ref) => {
|
||||
const backgroundImage = image ? `url('${image}')` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...rest}
|
||||
className={classNames(styles.wrapper, rest.className)}
|
||||
style={{ backgroundImage, width: size }}
|
||||
ref={ref}
|
||||
>
|
||||
<svg className={styles.svg} viewBox="0 0 1 1" />
|
||||
{!!children && <div className={styles.content}>{children}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { Square };
|
21
src/components/common/Square/styles.module.scss
Normal file
21
src/components/common/Square/styles.module.scss
Normal file
|
@ -0,0 +1,21 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.svg {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
border-radius: $radius;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
|
@ -5,12 +5,10 @@ interface IProps extends DetailsHTMLAttributes<HTMLDivElement> {
|
|||
offsetTop?: number;
|
||||
}
|
||||
|
||||
const Sticky: FC<IProps> = ({ children, offsetTop = 65 }) => {
|
||||
return (
|
||||
const Sticky: FC<IProps> = ({ children, offsetTop = 65 }) => (
|
||||
<StickyBox offsetTop={offsetTop} offsetBottom={10}>
|
||||
{children}
|
||||
</StickyBox>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
export { Sticky };
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
flex-direction: column;
|
||||
min-width: $cell;
|
||||
max-width: 400px;
|
||||
max-height: 100%;
|
||||
max-height: calc(100vh - 75px);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
@ -43,7 +42,6 @@
|
|||
.footer {
|
||||
@include outer_shadow();
|
||||
|
||||
// padding: 10px;
|
||||
background: darken($content_bg, 2%);
|
||||
}
|
||||
|
||||
|
@ -66,13 +64,15 @@
|
|||
}
|
||||
|
||||
.close {
|
||||
background: darken($content_bg, 2%);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
@include outer_shadow;
|
||||
|
||||
background: lighten($content_bg, 4%);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
position: absolute;
|
||||
top: -58px;
|
||||
right: 50%;
|
||||
transform: translate(50%, 0);
|
||||
top: -14px;
|
||||
right: 4px;
|
||||
transform: translate(50%, 0) scale(1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
@ -80,15 +80,21 @@
|
|||
cursor: pointer;
|
||||
transition: transform 0.25s, background-color 0.25s;
|
||||
animation: appear 0.5s forwards;
|
||||
z-index: 10;
|
||||
|
||||
@include tablet {
|
||||
top: -16px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $red;
|
||||
transform: translate(50%, -5px);
|
||||
transform: translate(50%, 0) scale(1.25);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { useFlowCellControls } from '~/hooks/flow/useFlowCellControls';
|
|||
import { useClickOutsideFocus } from '~/hooks/dom/useClickOutsideFocus';
|
||||
import { MenuDots } from '~/components/common/MenuDots';
|
||||
import { FlowCellImage } from '~/components/flow/FlowCellImage';
|
||||
import { Anchor } from '~/components/common/Anchor';
|
||||
|
||||
interface Props {
|
||||
id: INode['id'];
|
||||
|
@ -71,7 +72,7 @@ const FlowCell: FC<Props> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<NavLink className={styles.link} to={to}>
|
||||
<Anchor className={styles.link} href={to}>
|
||||
{withText && (
|
||||
<FlowCellText className={styles.text} heading={<h4 className={styles.title}>{title}</h4>}>
|
||||
{text!}
|
||||
|
@ -94,7 +95,7 @@ const FlowCell: FC<Props> = ({
|
|||
<h4 className={styles.title}>{title}</h4>
|
||||
</div>
|
||||
)}
|
||||
</NavLink>
|
||||
</Anchor>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import React, { FC } from 'react';
|
||||
import React, { FC, MouseEventHandler } from 'react';
|
||||
import { INode } from '~/types';
|
||||
import styles from './styles.module.scss';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { NodeRelatedItem } from '~/components/node/NodeRelatedItem';
|
||||
import { getPrettyDate } from '~/utils/dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { Anchor } from '~/components/common/Anchor';
|
||||
|
||||
interface IProps {
|
||||
node: Partial<INode>;
|
||||
has_new?: boolean;
|
||||
onClick?: MouseEventHandler;
|
||||
}
|
||||
|
||||
const FlowRecentItem: FC<IProps> = ({ node, has_new }) => {
|
||||
const FlowRecentItem: FC<IProps> = ({ node, has_new, onClick }) => {
|
||||
return (
|
||||
<Link key={node.id} className={styles.item} to={URLS.NODE_URL(node.id)}>
|
||||
<Anchor key={node.id} className={styles.item} href={URLS.NODE_URL(node.id)} onClick={onClick}>
|
||||
<div
|
||||
className={classNames(styles.thumb, {
|
||||
[styles.new]: has_new,
|
||||
|
@ -33,7 +34,7 @@ const FlowRecentItem: FC<IProps> = ({ node, has_new }) => {
|
|||
<span>{getPrettyDate(node.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Anchor>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { useHistory } from 'react-router';
|
|||
import classNames from 'classnames';
|
||||
import { IFlowNode } from '~/types';
|
||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||
import { useNavigation } from '~/hooks/navigation/useNavigation';
|
||||
|
||||
SwiperCore.use([EffectFade, Lazy, Autoplay, Navigation]);
|
||||
|
||||
|
@ -21,13 +22,13 @@ interface Props {
|
|||
|
||||
export const FlowSwiperHero: FC<Props> = ({ heroes }) => {
|
||||
const { innerWidth } = useWindowSize();
|
||||
const { push } = useNavigation();
|
||||
|
||||
const [controlledSwiper, setControlledSwiper] = useState<SwiperClass | undefined>(undefined);
|
||||
const [currentIndex, setCurrentIndex] = useState(heroes.length);
|
||||
const preset = useMemo(() => (innerWidth <= 768 ? PRESETS.cover : PRESETS.small_hero), [
|
||||
innerWidth,
|
||||
]);
|
||||
const history = useHistory();
|
||||
|
||||
const onNext = useCallback(() => {
|
||||
controlledSwiper?.slideNext(1);
|
||||
|
@ -63,9 +64,9 @@ export const FlowSwiperHero: FC<Props> = ({ heroes }) => {
|
|||
|
||||
const onClick = useCallback(
|
||||
(sw: SwiperClass) => {
|
||||
history.push(URLS.NODE_URL(heroes[sw.realIndex]?.id));
|
||||
push(URLS.NODE_URL(heroes[sw.realIndex]?.id));
|
||||
},
|
||||
[history, heroes]
|
||||
[push, heroes]
|
||||
);
|
||||
|
||||
if (!heroes.length) {
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
}
|
||||
|
||||
.button {
|
||||
@include outer_shadow();
|
||||
|
||||
position: relative;
|
||||
height: $input_height;
|
||||
border: none;
|
||||
|
@ -34,12 +36,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
filter: grayscale(0);
|
||||
|
||||
transition: opacity 0.25s, filter 0.25s, box-shadow 0.25s, background-color 0.5s;
|
||||
opacity: 0.8;
|
||||
|
||||
@include outer_shadow();
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { FC } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Square } from '~/components/containers/Square';
|
||||
import { LabSquare } from '~/components/lab/LabSquare';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const LabBanner: FC<IProps> = () => (
|
||||
<Square className={styles.wrap}>
|
||||
<LabSquare className={styles.wrap}>
|
||||
<Group>
|
||||
<div className={styles.title}>Лаборатория!</div>
|
||||
|
||||
|
@ -19,7 +19,7 @@ const LabBanner: FC<IProps> = () => (
|
|||
</p>
|
||||
</Group>
|
||||
</Group>
|
||||
</Square>
|
||||
</LabSquare>
|
||||
);
|
||||
|
||||
export { LabBanner };
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Grid } from '~/components/containers/Grid';
|
|||
import { useHistory } from 'react-router';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { useNavigation } from '~/hooks/navigation/useNavigation';
|
||||
|
||||
type Props = {
|
||||
node: INode;
|
||||
|
@ -19,8 +20,8 @@ type Props = {
|
|||
};
|
||||
|
||||
const LabBottomPanel: FC<Props> = ({ node, hasNewComments, commentCount, isLoading }) => {
|
||||
const history = useHistory();
|
||||
const onClick = useCallback(() => history.push(URLS.NODE_URL(node.id)), [history, node.id]);
|
||||
const { push } = useNavigation();
|
||||
const onClick = useCallback(() => push(URLS.NODE_URL(node.id)), [push, node.id]);
|
||||
|
||||
return (
|
||||
<Group horizontal className={styles.wrap} onClick={onClick}>
|
||||
|
|
|
@ -7,6 +7,7 @@ import { INode } from '~/types';
|
|||
import { getPrettyDate } from '~/utils/dom';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigation } from '~/hooks/navigation/useNavigation';
|
||||
|
||||
interface IProps {
|
||||
node?: Partial<INode>;
|
||||
|
@ -14,10 +15,10 @@ interface IProps {
|
|||
}
|
||||
|
||||
const LabHero: FC<IProps> = ({ node, isLoading }) => {
|
||||
const history = useHistory();
|
||||
const { push } = useNavigation();
|
||||
const onClick = useCallback(() => {
|
||||
history.push(URLS.NODE_URL(node?.id));
|
||||
}, [history, node]);
|
||||
push(URLS.NODE_URL(node?.id));
|
||||
}, [push, node]);
|
||||
|
||||
if (!node || isLoading) {
|
||||
return (
|
||||
|
|
|
@ -5,7 +5,7 @@ import classNames from 'classnames';
|
|||
|
||||
interface IProps extends DivProps {}
|
||||
|
||||
const Square: FC<IProps> = ({ children, ...rest }) => (
|
||||
const LabSquare: FC<IProps> = ({ children, ...rest }) => (
|
||||
<div className={styles.square}>
|
||||
<div {...rest} className={classNames(styles.content, rest.className)}>
|
||||
{children}
|
||||
|
@ -13,4 +13,4 @@ const Square: FC<IProps> = ({ children, ...rest }) => (
|
|||
</div>
|
||||
);
|
||||
|
||||
export { Square };
|
||||
export { LabSquare };
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Anchor } from '~/components/common/Anchor';
|
||||
import { URLS } from '~/constants/urls';
|
||||
|
||||
export const Logo = () => (
|
||||
<Link className={styles.logo} to="/">
|
||||
<Anchor className={styles.logo} href={URLS.BASE}>
|
||||
VAULT
|
||||
</Link>
|
||||
</Anchor>
|
||||
);
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
:global(.swiper-button-next),
|
||||
:global(.swiper-button-prev) {
|
||||
@include outer_shadow;
|
||||
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
width: 40px;
|
||||
|
@ -79,7 +81,7 @@
|
|||
|
||||
}
|
||||
|
||||
.slide {
|
||||
.slide.slide {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font: $font_32_bold;
|
||||
|
@ -90,7 +92,8 @@
|
|||
width: auto;
|
||||
max-width: 100vw;
|
||||
opacity: 1;
|
||||
transform: translate(0, 10px);
|
||||
//transform: translate(0, 10px);
|
||||
transform: scale(0.99);
|
||||
filter: brightness(50%) saturate(0.5);
|
||||
transition: opacity 0.5s, filter 0.5s, transform 0.5s;
|
||||
padding-bottom: $gap * 1.5;
|
||||
|
@ -99,7 +102,8 @@
|
|||
&:global(.swiper-slide-active) {
|
||||
opacity: 1;
|
||||
filter: brightness(100%);
|
||||
transform: translate(0, 0);
|
||||
//transform: translate(0, 0);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@include tablet {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { NodeRelated } from '~/components/node/NodeRelated';
|
|||
import { URLS } from '~/constants/urls';
|
||||
import { INode } from '~/types';
|
||||
import { INodeRelated } from '~/types/node';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Anchor } from '~/components/common/Anchor';
|
||||
|
||||
interface IProps {
|
||||
isLoading: boolean;
|
||||
|
@ -27,7 +27,9 @@ const NodeRelatedBlock: FC<IProps> = ({ isLoading, node, related }) => {
|
|||
.map(album => (
|
||||
<NodeRelated
|
||||
title={
|
||||
<Link to={URLS.NODE_TAG_URL(node.id!, encodeURIComponent(album))}>{album}</Link>
|
||||
<Anchor href={URLS.NODE_TAG_URL(node.id!, encodeURIComponent(album))}>
|
||||
{album}
|
||||
</Anchor>
|
||||
}
|
||||
items={related.albums[album]}
|
||||
key={album}
|
||||
|
|
|
@ -3,12 +3,13 @@ import styles from './styles.module.scss';
|
|||
import classNames from 'classnames';
|
||||
import { INode } from '~/types';
|
||||
import { PRESETS, URLS } from '~/constants/urls';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { getURL } from '~/utils/dom';
|
||||
import { Avatar } from '~/components/common/Avatar';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { getURL, getURLFromString } from '~/utils/dom';
|
||||
import { useColorGradientFromString } from '~/hooks/color/useColorGradientFromString';
|
||||
import { Square } from '~/components/common/Square';
|
||||
import { useGotoNode } from '~/hooks/node/useGotoNode';
|
||||
|
||||
type IProps = RouteComponentProps & {
|
||||
type IProps = {
|
||||
item: Partial<INode>;
|
||||
};
|
||||
|
||||
|
@ -28,11 +29,11 @@ const getTitleLetters = (title?: string): string => {
|
|||
: words[0].substr(0, 2).toUpperCase();
|
||||
};
|
||||
|
||||
const NodeRelatedItemUnconnected: FC<IProps> = memo(({ item, history }) => {
|
||||
const NodeRelatedItem: FC<IProps> = memo(({ item }) => {
|
||||
const onClick = useGotoNode(item.id);
|
||||
const [is_loaded, setIsLoaded] = useState(false);
|
||||
const [width, setWidth] = useState(0);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const onClick = useCallback(() => history.push(URLS.NODE_URL(item.id)), [item, history]);
|
||||
|
||||
const thumb = useMemo(
|
||||
() => (item.thumbnail ? getURL({ url: item.thumbnail }, PRESETS.avatar) : ''),
|
||||
|
@ -68,11 +69,13 @@ const NodeRelatedItemUnconnected: FC<IProps> = memo(({ item, history }) => {
|
|||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
<Avatar
|
||||
username={item.title}
|
||||
url={item.thumbnail}
|
||||
{item.thumbnail && (
|
||||
<Square
|
||||
image={getURLFromString(item.thumbnail, 'avatar')}
|
||||
onClick={onClick}
|
||||
className={classNames(styles.thumb, { [styles.is_loaded]: is_loaded })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!item.thumbnail && size === 'small' && (
|
||||
<div className={styles.letters} style={{ background }}>
|
||||
|
@ -91,6 +94,4 @@ const NodeRelatedItemUnconnected: FC<IProps> = memo(({ item, history }) => {
|
|||
);
|
||||
});
|
||||
|
||||
const NodeRelatedItem = withRouter(NodeRelatedItemUnconnected);
|
||||
|
||||
export { NodeRelatedItem };
|
||||
|
|
|
@ -2,16 +2,17 @@ import React, { ChangeEvent, FC, useCallback } from 'react';
|
|||
import styles from './styles.module.scss';
|
||||
import { getURL } from '~/utils/dom';
|
||||
import { PRESETS } from '~/constants/urls';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { IFile } from '~/types';
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
export interface ProfileAvatarProps {
|
||||
size?: number;
|
||||
canEdit: boolean;
|
||||
photo?: IFile;
|
||||
onChangePhoto: (file: File) => void;
|
||||
}
|
||||
|
||||
const ProfileAvatar: FC<ProfileAvatarProps> = ({ photo, onChangePhoto, canEdit }) => {
|
||||
const ProfileAvatar: FC<ProfileAvatarProps> = ({ photo, onChangePhoto, canEdit, size }) => {
|
||||
const onInputChange = useCallback(
|
||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!event.target.files?.length) {
|
||||
|
@ -30,14 +31,12 @@ const ProfileAvatar: FC<ProfileAvatarProps> = ({ photo, onChangePhoto, canEdit }
|
|||
className={styles.avatar}
|
||||
style={{
|
||||
backgroundImage,
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
>
|
||||
{canEdit && <input type="file" onInput={onInputChange} />}
|
||||
{canEdit && (
|
||||
<div className={styles.can_edit}>
|
||||
<Icon icon="photo_add" />
|
||||
</div>
|
||||
)}
|
||||
{canEdit && <Button iconLeft="photo_add" round iconOnly className={styles.can_edit} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,10 +8,8 @@
|
|||
height: 100px;
|
||||
background: $content_bg 50% 50% no-repeat;
|
||||
background-size: cover;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: $gap;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
|
@ -21,35 +19,12 @@
|
|||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.can_edit {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
touch-action: none;
|
||||
// background: red;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
padding: $gap / 2;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(330deg, $content_bg, transparentize($content_bg, 1));
|
||||
border-radius: $radius;
|
||||
|
||||
svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
fill: white;
|
||||
transition: fill 0.25s;
|
||||
}
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface IProps {
|
||||
path: string;
|
||||
}
|
||||
|
||||
const ProfileSidebarMenu: FC<IProps> = ({ path }) => {
|
||||
const cleaned = path.replace(/\/$/, '');
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Link className={styles.row} to={`${cleaned}/settings`}>
|
||||
<Icon icon="settings" />
|
||||
<span>Настройки</span>
|
||||
</Link>
|
||||
|
||||
<div className={styles.row}>
|
||||
<Icon icon="messages" />
|
||||
<span>Сообщения</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ProfileSidebarMenu };
|
|
@ -1,47 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin: 0 $gap;
|
||||
box-sizing: border-box;
|
||||
box-shadow: $sidebar_border 0 0 0 1px;
|
||||
border-radius: $radius;
|
||||
background-color: transparentize(black, 0.9)
|
||||
}
|
||||
|
||||
.row {
|
||||
padding: $gap;
|
||||
font: $font_18_semibold;
|
||||
box-shadow: $sidebar_border 0 -1px;
|
||||
cursor: pointer;
|
||||
background-color: transparentize(black, 1);
|
||||
transition: all 250ms;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: 30px;
|
||||
opacity: 0.5;
|
||||
text-decoration: none;
|
||||
fill: white;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: transparentize($wisegreen, 0.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: $radius $radius 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 $radius $radius;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-right: $gap * 1.2;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ const ProfileSidebarSettings: FC<IProps> = () => (
|
|||
<div className={styles.scroller}>
|
||||
<ProfileSettings />
|
||||
</div>
|
||||
|
||||
<div className={styles.buttons}>
|
||||
<Filler />
|
||||
<Button color="outline">Отмена</Button>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
@include sidebar_content(600px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
@ -18,7 +17,6 @@
|
|||
.buttons {
|
||||
width: 100%;
|
||||
padding: $gap;
|
||||
box-shadow: $sidebar_border 0 -1px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
|
|
28
src/components/sidebar/SidebarStack/index.tsx
Normal file
28
src/components/sidebar/SidebarStack/index.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React, { FC, useMemo } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
|
||||
interface SidebarStackProps {}
|
||||
|
||||
const SidebarStack: FC<SidebarStackProps> = ({ children }) => {
|
||||
const nonEmptyChildren = useMemo(() => {
|
||||
if (!children) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.isArray(children) ? children.filter(it => it) : [children];
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<div className={styles.stack}>
|
||||
{nonEmptyChildren.map((child, i) => (
|
||||
<div className={styles.card} key={i}>
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { SidebarStack };
|
29
src/components/sidebar/SidebarStack/styles.module.scss
Normal file
29
src/components/sidebar/SidebarStack/styles.module.scss
Normal file
|
@ -0,0 +1,29 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.stack {
|
||||
background: transparentize($content_bg, 0.1);
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
width: auto;
|
||||
border-radius: $radius 0 0 $radius;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: transparentize(white, 0.9) -1px 0, transparentize(black, 0.7) -5px 0 15px;
|
||||
border-radius: $radius 0 0 $radius;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
border-radius: $radius;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: $gap;
|
||||
}
|
31
src/components/sidebar/SidebarStackCard/index.tsx
Normal file
31
src/components/sidebar/SidebarStackCard/index.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import React, { FC, useMemo } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
interface SidebarStackCardProps {
|
||||
width?: number;
|
||||
headerFeature?: 'back' | 'close';
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const SidebarStackCard: FC<SidebarStackCardProps> = ({ children, title, width, headerFeature }) => {
|
||||
const style = useMemo(() => ({ maxWidth: width, flexBasis: width }), [width]);
|
||||
|
||||
return (
|
||||
<div style={style} className={styles.card}>
|
||||
{(headerFeature || title) && (
|
||||
<div className={styles.head}>
|
||||
<Filler>{!!title && <h5>{title}</h5>}</Filler>
|
||||
|
||||
{headerFeature === 'back' && <Button color="link" iconRight="right" />}
|
||||
{headerFeature === 'close' && <Button color="link" iconRight="close" />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.content}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { SidebarStackCard };
|
26
src/components/sidebar/SidebarStackCard/styles.module.scss
Normal file
26
src/components/sidebar/SidebarStackCard/styles.module.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.card {
|
||||
width: 100vw;
|
||||
max-width: 400px;
|
||||
flex: 1 1 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.head {
|
||||
@include row_shadow;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: $gap $gap $gap $gap * 2;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
import React, { FC } from 'react';
|
||||
import React, { FC, MouseEventHandler } from 'react';
|
||||
import { INode } from '~/types';
|
||||
import styles from './styles.module.scss';
|
||||
import { FlowRecentItem } from '~/components/flow/FlowRecentItem';
|
||||
|
||||
interface IProps {
|
||||
nodes: INode[];
|
||||
onClick?: MouseEventHandler;
|
||||
}
|
||||
|
||||
const TagSidebarList: FC<IProps> = ({ nodes }) => (
|
||||
const TagSidebarList: FC<IProps> = ({ nodes, onClick }) => (
|
||||
<div className={styles.list}>
|
||||
{nodes.map(node => (
|
||||
<FlowRecentItem node={node} key={node.id} />
|
||||
<FlowRecentItem node={node} key={node.id} onClick={onClick} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -35,6 +35,7 @@ const TagAutocomplete: VFC<TagAutocompleteProps> = ({
|
|||
|
||||
const pop = usePopper(wrapper?.current?.parentElement, wrapper.current, {
|
||||
placement: 'bottom-end',
|
||||
strategy: 'fixed',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'offset',
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { IComment, INode, ITag } from '~/types';
|
||||
import { OAuthProvider } from '~/types/auth';
|
||||
import { CONFIG } from '~/utils/config';
|
||||
|
||||
export const API = {
|
||||
BASE: process.env.REACT_APP_API_HOST,
|
||||
BASE: CONFIG.apiHost,
|
||||
USER: {
|
||||
LOGIN: '/user/login',
|
||||
OAUTH_WINDOW: (provider: OAuthProvider) =>
|
||||
`${process.env.REACT_APP_API_HOST}oauth/${provider}/redirect`,
|
||||
OAUTH_WINDOW: (provider: OAuthProvider) => `${CONFIG.apiHost}oauth/${provider}/redirect`,
|
||||
ME: '/user/',
|
||||
PROFILE: (username: string) => `/user/user/${username}/profile`,
|
||||
MESSAGES: (username: string) => `/user/user/${username}/messages`,
|
||||
|
|
|
@ -9,6 +9,7 @@ import { PhotoSwipe } from '~/containers/dialogs/PhotoSwipe';
|
|||
import { EditorCreateDialog } from '~/containers/dialogs/EditorCreateDialog';
|
||||
import { EditorEditDialog } from '~/containers/dialogs/EditorEditDialog';
|
||||
import { TagSidebar } from '~/containers/sidebars/TagSidebar';
|
||||
import { ProfileSidebar } from '~/containers/sidebars/ProfileSidebar';
|
||||
|
||||
export enum Dialog {
|
||||
Login = 'Login',
|
||||
|
@ -22,6 +23,7 @@ export enum Dialog {
|
|||
CreateNode = 'CreateNode',
|
||||
EditNode = 'EditNode',
|
||||
TagSidebar = 'TagNodes',
|
||||
ProfileSidebar = 'ProfileSidebar',
|
||||
}
|
||||
|
||||
export const DIALOG_CONTENT = {
|
||||
|
@ -36,4 +38,5 @@ export const DIALOG_CONTENT = {
|
|||
[Dialog.CreateNode]: EditorCreateDialog,
|
||||
[Dialog.EditNode]: EditorEditDialog,
|
||||
[Dialog.TagSidebar]: TagSidebar,
|
||||
[Dialog.ProfileSidebar]: ProfileSidebar,
|
||||
} as const;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React, { FC, useCallback, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Logo } from '~/components/main/Logo';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
|
@ -19,6 +18,7 @@ import { useModal } from '~/hooks/modal/useModal';
|
|||
import { useScrollTop } from '~/hooks/dom/useScrollTop';
|
||||
import { useFlow } from '~/hooks/flow/useFlow';
|
||||
import { useUpdates } from '~/hooks/updates/useUpdates';
|
||||
import { Anchor } from '~/components/common/Anchor';
|
||||
|
||||
type HeaderProps = {};
|
||||
|
||||
|
@ -62,32 +62,32 @@ const Header: FC<HeaderProps> = observer(() => {
|
|||
|
||||
<div className={styles.plugs}>
|
||||
<Authorized>
|
||||
<Link
|
||||
<Anchor
|
||||
className={classNames(styles.item, {
|
||||
[styles.has_dot]: hasFlowUpdates,
|
||||
})}
|
||||
to={URLS.BASE}
|
||||
href={URLS.BASE}
|
||||
>
|
||||
ФЛОУ
|
||||
</Link>
|
||||
</Anchor>
|
||||
|
||||
<Link
|
||||
<Anchor
|
||||
className={classNames(styles.item, styles.lab, {
|
||||
[styles.has_dot]: hasLabUpdates,
|
||||
})}
|
||||
to={URLS.LAB}
|
||||
href={URLS.LAB}
|
||||
>
|
||||
ЛАБ
|
||||
</Link>
|
||||
</Anchor>
|
||||
|
||||
<Link
|
||||
<Anchor
|
||||
className={classNames(styles.item, styles.boris, {
|
||||
[styles.has_dot]: hasBorisUpdates,
|
||||
})}
|
||||
to={URLS.BORIS}
|
||||
href={URLS.BORIS}
|
||||
>
|
||||
БОРИС
|
||||
</Link>
|
||||
</Anchor>
|
||||
</Authorized>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import * as React from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { Header } from '~/containers/main/Header';
|
||||
import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
||||
|
||||
export const MainLayout = ({ children }) => (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.content}>
|
||||
<Header />
|
||||
{children}
|
||||
<SidebarRouter />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React, { FC } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Authorized } from '~/components/containers/Authorized';
|
||||
import { SubmitBar } from '~/components/bars/SubmitBar';
|
||||
|
||||
|
@ -8,13 +7,10 @@ interface IProps {
|
|||
isLab?: boolean;
|
||||
}
|
||||
|
||||
const SidebarRouter: FC<IProps> = ({ prefix = '', isLab }) => {
|
||||
return createPortal(
|
||||
const SidebarRouter: FC<IProps> = ({ isLab }) => (
|
||||
<Authorized>
|
||||
<SubmitBar isLab={isLab} />
|
||||
</Authorized>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
</Authorized>
|
||||
);
|
||||
|
||||
export { SidebarRouter };
|
||||
|
|
|
@ -16,6 +16,7 @@ import { NodeComments } from '~/containers/node/NodeComments';
|
|||
import { useUserContext } from '~/utils/context/UserContextProvider';
|
||||
import { useNodeRelatedContext } from '~/utils/context/NodeRelatedContextProvider';
|
||||
import { useAuthProvider } from '~/utils/providers/AuthProvider';
|
||||
import { Sticky } from '~/components/containers/Sticky';
|
||||
|
||||
interface IProps {
|
||||
commentsOrder: 'ASC' | 'DESC';
|
||||
|
@ -52,8 +53,8 @@ const NodeBottomBlock: FC<IProps> = ({ commentsOrder }) => {
|
|||
</Group>
|
||||
|
||||
<div className={styles.panel}>
|
||||
<StickyBox className={styles.sticky} offsetTop={72}>
|
||||
<div className={styles.left}>
|
||||
<Sticky>
|
||||
<div className={styles.left_item}>
|
||||
<NodeAuthorBlock user={node?.user} />
|
||||
</div>
|
||||
|
@ -64,8 +65,8 @@ const NodeBottomBlock: FC<IProps> = ({ commentsOrder }) => {
|
|||
<div className={styles.left_item}>
|
||||
<NodeRelatedBlock isLoading={isLoadingRelated} node={node} related={related} />
|
||||
</div>
|
||||
</Sticky>
|
||||
</div>
|
||||
</StickyBox>
|
||||
</div>
|
||||
</Group>
|
||||
</Padder>
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
|
||||
@media (max-width: 1024px) {
|
||||
padding-left: 0;
|
||||
padding-top: $comment_height / 2;
|
||||
flex: 1 2;
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +48,7 @@
|
|||
.left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left_item {
|
||||
|
|
|
@ -27,7 +27,9 @@ const ProfileInfo: FC<IProps> = ({ isOwn }) => {
|
|||
return (
|
||||
<div>
|
||||
<Group className={styles.wrap} horizontal>
|
||||
<div className={styles.avatar}>
|
||||
<ProfileAvatar canEdit={isOwn} onChangePhoto={updatePhoto} photo={photo} />
|
||||
</div>
|
||||
|
||||
<div className={styles.field}>
|
||||
<div className={styles.name}>
|
||||
|
|
|
@ -8,13 +8,6 @@
|
|||
}
|
||||
|
||||
.avatar {
|
||||
@include outer_shadow();
|
||||
|
||||
border-radius: $radius;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: $content_bg 50% 50% no-repeat;
|
||||
background-size: cover;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: $gap;
|
||||
|
|
25
src/containers/profile/ProfileSidebarHead/index.tsx
Normal file
25
src/containers/profile/ProfileSidebarHead/index.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React, { VFC } from 'react';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { ProfileAvatar } from '~/components/profile/ProfileAvatar';
|
||||
import { usePatchUser } from '~/hooks/auth/usePatchUser';
|
||||
import { useUser } from '~/hooks/auth/useUser';
|
||||
|
||||
interface ProfileSidebarHeadProps {}
|
||||
|
||||
const ProfileSidebarHead: VFC<ProfileSidebarHeadProps> = () => {
|
||||
const { user } = useUser();
|
||||
const { updatePhoto } = usePatchUser();
|
||||
|
||||
return (
|
||||
<Group horizontal>
|
||||
<ProfileAvatar canEdit onChangePhoto={updatePhoto} photo={user.photo} size={72} />
|
||||
|
||||
<Filler>
|
||||
<h2>{user.fullname || user.username}</h2>
|
||||
</Filler>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export { ProfileSidebarHead };
|
67
src/containers/profile/ProfileSidebarMenu/index.tsx
Normal file
67
src/containers/profile/ProfileSidebarMenu/index.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import React, { VFC } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { ProfileSidebarHead } from '~/containers/profile/ProfileSidebarHead';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import classNames from 'classnames';
|
||||
import markdown from '~/styles/common/markdown.module.scss';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Grid } from '~/components/containers/Grid';
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Square } from '~/components/common/Square';
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
interface ProfileSidebarMenuProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ProfileSidebarMenu: VFC<ProfileSidebarMenuProps> = ({ onClose }) => (
|
||||
<div className={styles.wrap}>
|
||||
<div>
|
||||
<ProfileSidebarHead />
|
||||
</div>
|
||||
|
||||
<Filler className={classNames(markdown.wrapper, styles.text)}>
|
||||
<Group>
|
||||
<ul className={styles.menu}>
|
||||
<li>Настройки</li>
|
||||
<li>Заметки</li>
|
||||
<li>Удалённые посты</li>
|
||||
</ul>
|
||||
|
||||
<Grid columns="2fr 1fr">
|
||||
<Card>
|
||||
<h4>1 год 2 месяца</h4>
|
||||
<small>в убежище</small>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Square>
|
||||
<h4>24 поста</h4>
|
||||
<small>Создано</small>
|
||||
</Square>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid columns="1fr 2fr">
|
||||
<Card>
|
||||
<Square>
|
||||
<h4>16545 лайка</h4>
|
||||
<small>получено</small>
|
||||
</Square>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h4>123123 комментария</h4>
|
||||
<small>под постами</small>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Group>
|
||||
</Filler>
|
||||
|
||||
<Button round onClick={onClose} color="secondary">
|
||||
Закрыть
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { ProfileSidebarMenu };
|
42
src/containers/profile/ProfileSidebarMenu/styles.module.scss
Normal file
42
src/containers/profile/ProfileSidebarMenu/styles.module.scss
Normal file
|
@ -0,0 +1,42 @@
|
|||
@import "../../../styles/variables";
|
||||
|
||||
.wrap {
|
||||
padding: $gap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: $gap * 2;
|
||||
}
|
||||
|
||||
.menu {
|
||||
@include outer_shadow;
|
||||
|
||||
list-style: none;
|
||||
border-radius: $radius;
|
||||
padding: 0 !important;
|
||||
|
||||
& > li {
|
||||
@include row_shadow;
|
||||
|
||||
list-style: none;
|
||||
margin: 0 !important;
|
||||
padding: $gap;
|
||||
font: $font_16_semibold;
|
||||
cursor: pointer;
|
||||
background-color: transparentize($secondary, 1);
|
||||
transition: background-color 0.25s;
|
||||
|
||||
&:hover {
|
||||
background-color: transparentize($secondary, 0.5);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: $radius $radius 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 $radius $radius;
|
||||
}
|
||||
}
|
||||
}
|
27
src/containers/sidebars/ProfileSidebar/index.tsx
Normal file
27
src/containers/sidebars/ProfileSidebar/index.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React, { VFC } from 'react';
|
||||
import { SidebarWrapper } from '~/containers/sidebars/SidebarWrapper';
|
||||
import { DialogComponentProps } from '~/types/modal';
|
||||
import { ProfileSidebarMenu } from '~/containers/profile/ProfileSidebarMenu';
|
||||
import { SidebarStack } from '~/components/sidebar/SidebarStack';
|
||||
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
|
||||
import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard';
|
||||
|
||||
interface ProfileSidebarProps extends DialogComponentProps {}
|
||||
|
||||
const ProfileSidebar: VFC<ProfileSidebarProps> = ({ onRequestClose }) => {
|
||||
return (
|
||||
<SidebarWrapper onClose={onRequestClose}>
|
||||
<SidebarStack>
|
||||
<SidebarStackCard headerFeature="close" title="Профиль">
|
||||
<ProfileSidebarMenu onClose={onRequestClose} />
|
||||
</SidebarStackCard>
|
||||
|
||||
<SidebarStackCard width={600} headerFeature="back" title="Настройки">
|
||||
<ProfileSidebarSettings />
|
||||
</SidebarStackCard>
|
||||
</SidebarStack>
|
||||
</SidebarWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export { ProfileSidebar };
|
|
@ -8,6 +8,7 @@ import { InfiniteScroll } from '~/components/containers/InfiniteScroll';
|
|||
import { Tag } from '~/components/tags/Tag';
|
||||
import { useTagNodes } from '~/hooks/tag/useTagNodes';
|
||||
import { DialogComponentProps } from '~/types/modal';
|
||||
import { SidebarStack } from '~/components/sidebar/SidebarStack';
|
||||
|
||||
interface TagSidebarProps extends DialogComponentProps {
|
||||
tag: string;
|
||||
|
@ -19,6 +20,7 @@ const TagSidebar: VFC<TagSidebarProps> = ({ tag, onRequestClose }) => {
|
|||
|
||||
return (
|
||||
<SidebarWrapper onClose={onRequestClose}>
|
||||
<SidebarStack>
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.head}>
|
||||
|
@ -51,11 +53,12 @@ const TagSidebar: VFC<TagSidebarProps> = ({ tag, onRequestClose }) => {
|
|||
</div>
|
||||
) : (
|
||||
<InfiniteScroll hasMore={hasMore} loadMore={loadMore} className={styles.list}>
|
||||
<TagSidebarList nodes={nodes} />
|
||||
<TagSidebarList nodes={nodes} onClick={onRequestClose} />
|
||||
</InfiniteScroll>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SidebarStack>
|
||||
</SidebarWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
.wrap {
|
||||
@include sidebar_content(400px);
|
||||
width: 100vw;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
@ -17,11 +19,12 @@
|
|||
}
|
||||
|
||||
.head {
|
||||
@include row_shadow;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $gap;
|
||||
box-shadow: transparentize(white, 0.95) 0 1px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
|
|
|
@ -101,8 +101,8 @@ const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
|||
|
||||
const onAutocompleteSelect = useCallback(
|
||||
(val: string) => {
|
||||
onAppend([val]);
|
||||
setInput('');
|
||||
onAppend([val]);
|
||||
},
|
||||
[onAppend, setInput]
|
||||
);
|
||||
|
|
|
@ -24,7 +24,6 @@ export const useLoginForm = (
|
|||
try {
|
||||
await fetcher(values.username, values.password);
|
||||
onSuccess();
|
||||
showToastSuccess(getRandomPhrase('WELCOME'));
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
import { useAuthStore } from '~/store/auth/useAuthStore';
|
||||
import { useCallback } from 'react';
|
||||
import { apiUserLogin } from '~/api/auth';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { getRandomPhrase } from '~/constants/phrases';
|
||||
import { showToastInfo, showToastSuccess } from '~/utils/toast';
|
||||
|
||||
export const useLoginLogoutRestore = () => {
|
||||
const auth = useAuthStore();
|
||||
|
||||
const logout = useCallback(() => auth.logout(), [auth]);
|
||||
const logout = useCallback(() => {
|
||||
auth.logout();
|
||||
showToastInfo(getRandomPhrase('GOODBYE'));
|
||||
}, [auth]);
|
||||
|
||||
const login = useCallback(
|
||||
async (username: string, password: string) => {
|
||||
const result = await apiUserLogin({ username, password });
|
||||
auth.setToken(result.token);
|
||||
showToastInfo(getRandomPhrase('WELCOME'));
|
||||
return result.user;
|
||||
},
|
||||
[auth]
|
||||
|
|
|
@ -20,7 +20,7 @@ export const useMessageEventReactions = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('caught event:', type);
|
||||
console.log('caught event:', type, event.data);
|
||||
|
||||
switch (type) {
|
||||
case EventMessageType.OAuthLogin:
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useModal } from '~/hooks/modal/useModal';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
import { useNavigation } from '~/hooks/navigation/useNavigation';
|
||||
|
||||
/** redirects to the password redirect modal */
|
||||
export const useRestorePasswordRedirect = () => {
|
||||
const history = useHistory();
|
||||
const { push } = useNavigation();
|
||||
const { showModal } = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -15,7 +15,7 @@ export const useRestorePasswordRedirect = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
history.push('/');
|
||||
push('/');
|
||||
showModal(Dialog.RestorePassword, { code: match[1] });
|
||||
}, [showModal, history]);
|
||||
}, [showModal, push]);
|
||||
};
|
||||
|
|
17
src/hooks/dom/useScrollHeight.ts
Normal file
17
src/hooks/dom/useScrollHeight.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
const getHeight = () => {
|
||||
if (typeof document === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const body = document.body;
|
||||
const html = document.documentElement;
|
||||
|
||||
return Math.max(
|
||||
body.scrollHeight,
|
||||
body.offsetHeight,
|
||||
html.clientHeight,
|
||||
html.scrollHeight,
|
||||
html.offsetHeight
|
||||
);
|
||||
};
|
||||
export const useScrollHeight = () => getHeight();
|
10
src/hooks/dom/useScrollToBottom.ts
Normal file
10
src/hooks/dom/useScrollToBottom.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useScrollHeight } from '~/hooks/dom/useScrollHeight';
|
||||
|
||||
export const useScrollToBottom = () => {
|
||||
const top = useScrollHeight();
|
||||
|
||||
return useCallback(() => {
|
||||
window.scrollTo({ top });
|
||||
}, [top]);
|
||||
};
|
|
@ -14,7 +14,6 @@ export const useScrollToTop = (deps?: any[]) => {
|
|||
const bounds = targetElement.getBoundingClientRect();
|
||||
window.scrollTo({
|
||||
top: bounds.top - 100,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
},
|
||||
deps && Array.isArray(deps) ? deps : []
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useScrollTop = () => {
|
||||
const [top, setTop] = useState(0);
|
||||
const [top, setTop] = useState(typeof window !== 'undefined' ? window.scrollY : 0);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useState } from 'react';
|
|||
import { getNodeDiff } from '~/api/node';
|
||||
import { uniq } from 'ramda';
|
||||
import { useFlowStore } from '~/store/flow/useFlowStore';
|
||||
import { runInAction } from 'mobx';
|
||||
import { runInAction, toJS } from 'mobx';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
|
||||
export const useFlowLoader = () => {
|
||||
|
@ -81,5 +81,7 @@ export const useFlowLoader = () => {
|
|||
}
|
||||
}, [flow]);
|
||||
|
||||
console.log(toJS(flow.nodes));
|
||||
|
||||
return { getInitialNodes, isSyncing, loadMore };
|
||||
};
|
||||
|
|
22
src/hooks/navigation/useNavigation.ts
Normal file
22
src/hooks/navigation/useNavigation.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { useCallback } from 'react';
|
||||
import { CONFIG } from '~/utils/config';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
export const useNavigation = () => {
|
||||
const nextRouter = useRouter();
|
||||
const craHistory = useHistory();
|
||||
|
||||
const push = useCallback(
|
||||
(url: string) => {
|
||||
if (CONFIG.isNextEnvironment) {
|
||||
nextRouter.push(url);
|
||||
} else {
|
||||
craHistory.push(url);
|
||||
}
|
||||
},
|
||||
[craHistory, nextRouter]
|
||||
);
|
||||
|
||||
return { push };
|
||||
};
|
|
@ -1,10 +1,10 @@
|
|||
import { INode } from '~/types';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useCallback } from 'react';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { useNavigation } from '~/hooks/navigation/useNavigation';
|
||||
|
||||
// useGotoNode returns fn, that navigates to node
|
||||
export const useGotoNode = (id: INode['id']) => {
|
||||
const history = useHistory();
|
||||
return useCallback(() => history.push(URLS.NODE_URL(id)), [history, id]);
|
||||
const { push } = useNavigation();
|
||||
return useCallback(() => push(URLS.NODE_URL(id)), [push, id]);
|
||||
};
|
||||
|
|
9
src/hooks/node/useNodePageParams.ts
Normal file
9
src/hooks/node/useNodePageParams.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { useRouteMatch } from 'react-router';
|
||||
import { useRouter } from 'next/router';
|
||||
import { CONFIG } from '~/utils/config';
|
||||
|
||||
export const useNodePageParams = () => {
|
||||
return CONFIG.isNextEnvironment
|
||||
? (useRouter().query.id as string)
|
||||
: useRouteMatch<{ id: string }>().params.id;
|
||||
};
|
|
@ -2,7 +2,6 @@ import React, { FC } from 'react';
|
|||
import styles from './styles.module.scss';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Container } from '~/containers/main/Container';
|
||||
import StickyBox from 'react-sticky-box';
|
||||
import { BorisComments } from '~/containers/boris/BorisComments';
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
||||
|
@ -14,6 +13,7 @@ import { BorisUIDemo } from '~/components/boris/BorisUIDemo';
|
|||
|
||||
import boris from '~/sprites/boris_robot.svg';
|
||||
import { useAuthProvider } from '~/utils/providers/AuthProvider';
|
||||
import { Sticky } from '~/components/containers/Sticky';
|
||||
|
||||
type IProps = {
|
||||
title: string;
|
||||
|
@ -56,7 +56,7 @@ const BorisLayout: FC<IProps> = ({ title, setIsBetaTester, isTester, stats, isLo
|
|||
</Card>
|
||||
|
||||
<Group className={styles.stats}>
|
||||
<StickyBox className={styles.sticky} offsetTop={72} offsetBottom={10}>
|
||||
<Sticky>
|
||||
<BorisSidebar
|
||||
isTester={isTester}
|
||||
stats={stats}
|
||||
|
@ -64,7 +64,7 @@ const BorisLayout: FC<IProps> = ({ title, setIsBetaTester, isTester, stats, isLo
|
|||
isUser={isUser}
|
||||
isLoading={isLoadingStats}
|
||||
/>
|
||||
</StickyBox>
|
||||
</Sticky>
|
||||
</Group>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,6 +17,8 @@ import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
|||
import { useNodePermissions } from '~/hooks/node/useNodePermissions';
|
||||
import { useNodeActions } from '~/hooks/node/useNodeActions';
|
||||
import { NodeTitle } from '~/components/node/NodeTitle';
|
||||
import { ScrollHelperBottom } from '~/components/common/ScrollHelperBottom';
|
||||
import { Superpower } from '~/components/boris/Superpower';
|
||||
|
||||
type IProps = {};
|
||||
|
||||
|
@ -65,7 +67,9 @@ const NodeLayout: FC<IProps> = () => {
|
|||
|
||||
<SidebarRouter prefix="/post:id" />
|
||||
|
||||
<Route path={URLS.NODE_EDIT_URL(':id')} component={EditorEditDialog} />
|
||||
<Superpower>
|
||||
<ScrollHelperBottom />
|
||||
</Superpower>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
50
src/pages/_app.tsx
Normal file
50
src/pages/_app.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { StoreContextProvider } from '~/utils/context/StoreContextProvider';
|
||||
import { getMOBXStore } from '~/store';
|
||||
import '~/styles/main.scss';
|
||||
import { ToastProvider } from '~/utils/providers/ToastProvider';
|
||||
import { Modal } from '~/containers/dialogs/Modal';
|
||||
import { Sprites } from '~/sprites/Sprites';
|
||||
import React from 'react';
|
||||
import { BottomContainer } from '~/containers/main/BottomContainer';
|
||||
import { SWRConfigProvider } from '~/utils/providers/SWRConfigProvider';
|
||||
import { UserContextProvider } from '~/utils/context/UserContextProvider';
|
||||
import { DragDetectorProvider } from '~/hooks/dom/useDragDetector';
|
||||
import { PageCoverProvider } from '~/components/containers/PageCoverProvider';
|
||||
import { SearchProvider } from '~/utils/providers/SearchProvider';
|
||||
import { AudioPlayerProvider } from '~/utils/providers/AudioPlayerProvider';
|
||||
import { MetadataProvider } from '~/utils/providers/MetadataProvider';
|
||||
import { AuthProvider } from '~/utils/providers/AuthProvider';
|
||||
import { MainLayout } from '~/containers/main/MainLayout';
|
||||
import { useGlobalLoader } from '~/hooks/dom/useGlobalLoader';
|
||||
|
||||
const mobxStore = getMOBXStore();
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<StoreContextProvider store={mobxStore}>
|
||||
<SWRConfigProvider>
|
||||
<UserContextProvider>
|
||||
<DragDetectorProvider>
|
||||
<PageCoverProvider>
|
||||
<SearchProvider>
|
||||
<AudioPlayerProvider>
|
||||
<MetadataProvider>
|
||||
<AuthProvider>
|
||||
<MainLayout>
|
||||
<ToastProvider />
|
||||
<Modal />
|
||||
<Sprites />
|
||||
<Component {...pageProps} />
|
||||
</MainLayout>
|
||||
<BottomContainer />
|
||||
</AuthProvider>
|
||||
</MetadataProvider>
|
||||
</AudioPlayerProvider>
|
||||
</SearchProvider>
|
||||
</PageCoverProvider>
|
||||
</DragDetectorProvider>
|
||||
</UserContextProvider>
|
||||
</SWRConfigProvider>
|
||||
</StoreContextProvider>
|
||||
);
|
||||
}
|
|
@ -3,10 +3,13 @@ import { FlowLayout } from '~/layouts/FlowLayout';
|
|||
import { useFlow } from '~/hooks/flow/useFlow';
|
||||
import { FlowContextProvider } from '~/utils/context/FlowContextProvider';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useGlobalLoader } from '~/hooks/dom/useGlobalLoader';
|
||||
|
||||
interface Props {}
|
||||
|
||||
const FlowPage: FC<Props> = observer(() => {
|
||||
useGlobalLoader();
|
||||
|
||||
const {
|
||||
updates,
|
||||
nodes,
|
||||
|
|
|
@ -12,15 +12,12 @@ import { useNodePermissions } from '~/hooks/node/useNodePermissions';
|
|||
import { NodeRelatedProvider } from '~/utils/providers/NodeRelatedProvider';
|
||||
import { useLoadNode } from '~/hooks/node/useLoadNode';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useNodePageParams } from '~/hooks/node/useNodePageParams';
|
||||
|
||||
type Props = RouteComponentProps<{ id: string }> & {};
|
||||
|
||||
const NodePage: FC<Props> = observer(
|
||||
({
|
||||
match: {
|
||||
params: { id },
|
||||
},
|
||||
}) => {
|
||||
const NodePage: FC<Props> = observer(() => {
|
||||
const id = useNodePageParams();
|
||||
const { node, isLoading, update, lastSeen } = useLoadNode(parseInt(id, 10));
|
||||
|
||||
const onShowImageModal = useImageModal();
|
||||
|
@ -72,7 +69,6 @@ const NodePage: FC<Props> = observer(
|
|||
</NodeRelatedProvider>
|
||||
</NodeContextProvider>
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export default NodePage;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { IUser } from '~/types/auth';
|
|||
import { EMPTY_USER } from '~/constants/auth';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import { isHydrated, makePersistable } from 'mobx-persist-store';
|
||||
import { CONFIG } from '~/utils/config';
|
||||
|
||||
export class AuthStore {
|
||||
token: string = '';
|
||||
|
@ -12,7 +13,7 @@ export class AuthStore {
|
|||
makeAutoObservable(this);
|
||||
|
||||
void makePersistable(this, {
|
||||
name: `vault48_auth_${process.env.REACT_APP_API_URL}`,
|
||||
name: `vault48_auth_${CONFIG.apiHost}`,
|
||||
properties: ['token', 'user', 'isTesterInternal'],
|
||||
storage: typeof window !== 'undefined' ? window.localStorage : undefined,
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ import { FlowStore } from '~/store/flow/FlowStore';
|
|||
import { ModalStore } from '~/store/modal/ModalStore';
|
||||
import { LabStore } from '~/store/lab/LabStore';
|
||||
import { AuthStore } from '~/store/auth/AuthStore';
|
||||
import { useStaticRendering } from 'mobx-react-lite';
|
||||
|
||||
export class Store {
|
||||
flow = new FlowStore();
|
||||
|
@ -22,3 +23,6 @@ export class Store {
|
|||
const defaultStore = new Store();
|
||||
|
||||
export const getMOBXStore = () => defaultStore;
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useStaticRendering(typeof window === 'undefined');
|
||||
|
|
|
@ -128,3 +128,36 @@ button {
|
|||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
h5, h4, h3, h2, h1 {
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: $gap;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,8 +164,6 @@
|
|||
flex: 0 1 $width;
|
||||
max-width: 100vw;
|
||||
position: relative;
|
||||
background: transparentize($content_bg, 0.1);
|
||||
box-shadow: transparentize(white, 0.95) -1px 0;
|
||||
border-radius: $radius 0 0 $radius;
|
||||
}
|
||||
|
||||
|
|
|
@ -53,17 +53,7 @@ $margin: 1em;
|
|||
margin-bottom: $margin;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: $margin;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h5, h4, h3, h2, h1 {
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
line-height: 1.2em;
|
||||
margin: $margin * 1.5 0 $margin / 2;
|
||||
|
||||
|
@ -72,26 +62,6 @@ $margin: 1em;
|
|||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc;
|
||||
padding-left: 20px;
|
||||
|
|
7
src/utils/config/index.ts
Normal file
7
src/utils/config/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export const CONFIG = {
|
||||
apiHost: process.env.REACT_APP_API_HOST || process.env.NEXT_PUBLIC_API_HOST || '',
|
||||
remoteCurrent:
|
||||
process.env.REACT_APP_REMOTE_CURRENT || process.env.NEXT_PUBLIC_REMOTE_CURRENT || '',
|
||||
// transitional prop, marks migration to nextjs
|
||||
isNextEnvironment: !!process.env.NEXT_PUBLIC_REMOTE_CURRENT || typeof window === 'undefined',
|
||||
};
|
|
@ -19,6 +19,7 @@ import {
|
|||
formatTextTodos,
|
||||
} from '~/utils/formatText';
|
||||
import { splitTextByYoutube, splitTextOmitEmpty } from '~/utils/splitText';
|
||||
import { CONFIG } from '~/utils/config';
|
||||
|
||||
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
|
||||
const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;
|
||||
|
@ -67,13 +68,10 @@ export const getURLFromString = (
|
|||
size?: typeof PRESETS[keyof typeof PRESETS]
|
||||
): string => {
|
||||
if (size) {
|
||||
return (url || '').replace(
|
||||
'REMOTE_CURRENT://',
|
||||
`${process.env.REACT_APP_REMOTE_CURRENT}cache/${size}/`
|
||||
);
|
||||
return (url || '').replace('REMOTE_CURRENT://', `${CONFIG.remoteCurrent}cache/${size}/`);
|
||||
}
|
||||
|
||||
return (url || '').replace('REMOTE_CURRENT://', process.env.REACT_APP_REMOTE_CURRENT);
|
||||
return (url || '').replace('REMOTE_CURRENT://', CONFIG.remoteCurrent);
|
||||
};
|
||||
|
||||
export const getURL = (
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import React, { createContext, FC, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React, {
|
||||
createContext,
|
||||
FC,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { IFile } from '~/types';
|
||||
import { getURL } from '~/utils/dom';
|
||||
import { path } from 'ramda';
|
||||
|
@ -33,41 +41,47 @@ const PlayerContext = createContext<AudioPlayerProps>({
|
|||
toPercent: () => {},
|
||||
});
|
||||
|
||||
const audio = new Audio();
|
||||
|
||||
export const AudioPlayerProvider: FC = ({ children }) => {
|
||||
const audio = useRef(typeof Audio !== 'undefined' ? new Audio() : undefined).current;
|
||||
const [status, setStatus] = useState(PlayerState.UNSET);
|
||||
const [file, setFile] = useState<IFile | undefined>();
|
||||
const [progress, setProgress] = useState<PlayerProgress>({ progress: 0, current: 0, total: 0 });
|
||||
|
||||
/** controls */
|
||||
const play = audio.play.bind(audio);
|
||||
const pause = audio.pause.bind(audio);
|
||||
const play = async () => audio?.play();
|
||||
const pause = () => audio?.pause();
|
||||
const stop = useCallback(() => {
|
||||
audio.pause();
|
||||
audio.dispatchEvent(new CustomEvent('stop'));
|
||||
audio?.pause();
|
||||
audio?.dispatchEvent(new CustomEvent('stop'));
|
||||
setFile(undefined);
|
||||
setStatus(PlayerState.UNSET);
|
||||
}, [setFile]);
|
||||
}, [audio, setFile]);
|
||||
|
||||
const toTime = useCallback(
|
||||
(time: number) => {
|
||||
if (!audio) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toTime = useCallback((time: number) => {
|
||||
audio.currentTime = time;
|
||||
}, []);
|
||||
},
|
||||
[audio]
|
||||
);
|
||||
|
||||
const toPercent = useCallback(
|
||||
(percent: number) => {
|
||||
audio.currentTime = (progress.total * percent) / 100;
|
||||
toTime((progress.total * percent) / 100);
|
||||
},
|
||||
[progress]
|
||||
[progress, toTime]
|
||||
);
|
||||
|
||||
/** handles progress update */
|
||||
useEffect(() => {
|
||||
const onProgress = () => {
|
||||
setProgress({
|
||||
total: audio.duration,
|
||||
current: audio.currentTime,
|
||||
progress: (audio.currentTime / audio.duration) * 100,
|
||||
total: audio?.duration ?? 0,
|
||||
current: audio?.currentTime ?? 0,
|
||||
progress: audio ? (audio.currentTime / audio.duration) * 100 : 0,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -79,23 +93,25 @@ export const AudioPlayerProvider: FC = ({ children }) => {
|
|||
setStatus(PlayerState.PLAYING);
|
||||
};
|
||||
|
||||
audio.addEventListener('playprogress', onProgress);
|
||||
audio.addEventListener('timeupdate', onProgress);
|
||||
audio.addEventListener('pause', onPause);
|
||||
audio.addEventListener('playing', onPlay);
|
||||
audio?.addEventListener('playprogress', onProgress);
|
||||
audio?.addEventListener('timeupdate', onProgress);
|
||||
audio?.addEventListener('pause', onPause);
|
||||
audio?.addEventListener('playing', onPlay);
|
||||
|
||||
return () => {
|
||||
audio.removeEventListener('playprogress', onProgress);
|
||||
audio.removeEventListener('timeupdate', onProgress);
|
||||
audio.removeEventListener('pause', onPause);
|
||||
audio.removeEventListener('playing', onPlay);
|
||||
audio?.removeEventListener('playprogress', onProgress);
|
||||
audio?.removeEventListener('timeupdate', onProgress);
|
||||
audio?.removeEventListener('pause', onPause);
|
||||
audio?.removeEventListener('playing', onPlay);
|
||||
};
|
||||
}, []);
|
||||
}, [audio]);
|
||||
|
||||
/** update audio src */
|
||||
useEffect(() => {
|
||||
if (!audio) return;
|
||||
|
||||
audio.src = file ? getURL(file) : '';
|
||||
}, [file]);
|
||||
}, [file, audio]);
|
||||
|
||||
const metadata: IFile['metadata'] = path(['metadata'], file);
|
||||
const title =
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
.toast {
|
||||
@include outer_shadow;
|
||||
|
||||
cursor: pointer;
|
||||
font: $font_14_semibold;
|
||||
user-select: none;
|
||||
|
|
|
@ -16,3 +16,8 @@ export type ButtonProps = React.DetailedHTMLProps<
|
|||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
>;
|
||||
|
||||
export type LinkProps = React.DetailedHTMLProps<
|
||||
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
HTMLAnchorElement
|
||||
>;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
|
|
1
tsconfig.tsbuildinfo
Normal file
1
tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
201
yarn.lock
201
yarn.lock
|
@ -1036,7 +1036,7 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.1.5", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.1.5", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
|
||||
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
|
||||
|
@ -1496,50 +1496,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
|
||||
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
|
||||
|
||||
"@redux-saga/core@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4"
|
||||
integrity sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.3"
|
||||
"@redux-saga/deferred" "^1.1.2"
|
||||
"@redux-saga/delay-p" "^1.1.2"
|
||||
"@redux-saga/is" "^1.1.2"
|
||||
"@redux-saga/symbols" "^1.1.2"
|
||||
"@redux-saga/types" "^1.1.0"
|
||||
redux "^4.0.4"
|
||||
typescript-tuple "^2.2.1"
|
||||
|
||||
"@redux-saga/deferred@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@redux-saga/deferred/-/deferred-1.1.2.tgz#59937a0eba71fff289f1310233bc518117a71888"
|
||||
integrity sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ==
|
||||
|
||||
"@redux-saga/delay-p@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@redux-saga/delay-p/-/delay-p-1.1.2.tgz#8f515f4b009b05b02a37a7c3d0ca9ddc157bb355"
|
||||
integrity sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g==
|
||||
dependencies:
|
||||
"@redux-saga/symbols" "^1.1.2"
|
||||
|
||||
"@redux-saga/is@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@redux-saga/is/-/is-1.1.2.tgz#ae6c8421f58fcba80faf7cadb7d65b303b97e58e"
|
||||
integrity sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w==
|
||||
dependencies:
|
||||
"@redux-saga/symbols" "^1.1.2"
|
||||
"@redux-saga/types" "^1.1.0"
|
||||
|
||||
"@redux-saga/symbols@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@redux-saga/symbols/-/symbols-1.1.2.tgz#216a672a487fc256872b8034835afc22a2d0595d"
|
||||
integrity sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ==
|
||||
|
||||
"@redux-saga/types@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204"
|
||||
integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==
|
||||
|
||||
"@rollup/plugin-babel@^5.2.0":
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
|
||||
|
@ -1931,14 +1887,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
|
||||
integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.0":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/html-minifier-terser@^6.0.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
|
||||
|
@ -2050,16 +1998,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-redux@^7.1.11", "@types/react-redux@^7.1.20":
|
||||
version "7.1.21"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.21.tgz#f32bbaf7cbc4b93f51e10d340aa54035c084b186"
|
||||
integrity sha512-bLdglUiBSQNzWVVbmNPKGYYjrzp3/YDPwfOH3nLEz99I4awLlaRAPWjo6bZ2POpxztFWtDDXIPxBLVykXqBt+w==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-router-dom@^5.1.7":
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.2.tgz#ebd8e145cf056db5c66eb1dac63c72f52e8542ee"
|
||||
|
@ -3449,7 +3387,7 @@ cjs-module-lexer@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
|
||||
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
|
||||
|
||||
classnames@*, classnames@^2.2.6:
|
||||
classnames@*, classnames@^2.0.0, classnames@^2.2.6:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||
|
@ -3652,17 +3590,6 @@ connect-history-api-fallback@^1.6.0:
|
|||
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
||||
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
||||
|
||||
connected-react-router@^6.5.2:
|
||||
version "6.9.2"
|
||||
resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.9.2.tgz#f89fa87f0e977fcabf17475fb4552e170cc7e48e"
|
||||
integrity sha512-bE8kNBiZv9Mivp7pYn9JvLH5ItTjLl45kk1/Vha0rmAK9I/ETb5JPJrAm0h2KCG9qLfv7vqU3Jo4UUDo0oJnQg==
|
||||
dependencies:
|
||||
lodash.isequalwith "^4.4.0"
|
||||
prop-types "^15.7.2"
|
||||
optionalDependencies:
|
||||
immutable "^3.8.1 || ^4.0.0"
|
||||
seamless-immutable "^7.1.3"
|
||||
|
||||
console-control-strings@^1.0.0, console-control-strings@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
|
@ -3722,7 +3649,7 @@ core-js-pure@^3.19.0, core-js-pure@^3.8.1:
|
|||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.20.2.tgz#5d263565f0e34ceeeccdc4422fae3e84ca6b8c0f"
|
||||
integrity sha512-CmWHvSKn2vNL6p6StNp1EmMIfVY/pqn3JLAjfZQ8WZGPOlGoO92EkX9/Mk81i6GxvoPXjUqEQnpM3rJ5QxxIOg==
|
||||
|
||||
core-js@^3.19.2:
|
||||
core-js@^3.19.2, core-js@^3.6.5:
|
||||
version "3.20.2"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.20.2.tgz#46468d8601eafc8b266bd2dd6bf9dee622779581"
|
||||
integrity sha512-nuqhq11DcOAbFBV4zCbKeGbKQsUDRqTX0oqx7AttUBuqe3h20ixsE039QHelbL6P4h+9kytVqyEtyZ6gsiwEYw==
|
||||
|
@ -4521,7 +4448,7 @@ encoding@0.1.13:
|
|||
dependencies:
|
||||
iconv-lite "^0.6.2"
|
||||
|
||||
enhanced-resolve@^5.8.3:
|
||||
enhanced-resolve@^5.7.0, enhanced-resolve@^5.8.3:
|
||||
version "5.8.3"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0"
|
||||
integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==
|
||||
|
@ -4898,6 +4825,11 @@ etag@1.8.1, etag@~1.8.1:
|
|||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
eventemitter3@^3.0.0:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
|
||||
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
|
||||
|
||||
eventemitter3@^4.0.0:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
||||
|
@ -5614,7 +5546,7 @@ hmac-drbg@^1.0.1:
|
|||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
|
@ -5853,11 +5785,6 @@ immer@^9.0.7:
|
|||
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075"
|
||||
integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==
|
||||
|
||||
"immutable@^3.8.1 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
|
||||
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
||||
|
||||
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
|
@ -7040,11 +6967,6 @@ lodash.debounce@^4.0.8:
|
|||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.isequalwith@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz#266726ddd528f854f21f4ea98a065606e0fbc6b0"
|
||||
integrity sha1-Jmcm3dUo+FTyH06pigZWBuD7xrA=
|
||||
|
||||
lodash.memoize@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
|
@ -7317,18 +7239,11 @@ mobx-persist-store@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/mobx-persist-store/-/mobx-persist-store-1.0.4.tgz#233f0c03a708c0f58948c5ff223aa59610a2711a"
|
||||
integrity sha512-zxYvRSGKIJG/2brJC8+u6rJOnhe6HvvIOSaPSoOKdRFw4R91EV+e8PsraiScH3STNkGQlArbjnAUpd7cga1VCA==
|
||||
|
||||
mobx-react-lite@^3.2.0:
|
||||
mobx-react-lite@^3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.2.3.tgz#83d2b32ebf4383cd0dc0d397acbf53a8e9c66765"
|
||||
integrity sha512-7exWp1FV0M9dP08H9PIeHlJqDw4IdkQVRMfLYaZFMmlbzSS6ZU6p/kx392KN+rVf81hH3IQYewvRGQ70oiwmbw==
|
||||
|
||||
mobx-react@^7.2.1:
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-7.2.1.tgz#e9d4c04dc63d05e1139ce773f5fee7a5b4cb7c78"
|
||||
integrity sha512-LZS99KFLn75VWDXPdRJhILzVQ7qLcRjQbzkK+wVs0Qg4kWw5hOI2USp7tmu+9zP9KYsVBmKyx2k/8cTTBfsymw==
|
||||
dependencies:
|
||||
mobx-react-lite "^3.2.0"
|
||||
|
||||
mobx@^6.3.10:
|
||||
version "6.3.12"
|
||||
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.12.tgz#2ceb4f632081a8bf8757ba7e5e01b5810800022f"
|
||||
|
@ -7392,6 +7307,14 @@ neo-async@^2.6.2:
|
|||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
||||
next-transpile-modules@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/next-transpile-modules/-/next-transpile-modules-9.0.0.tgz#133b1742af082e61cc76b02a0f12ffd40ce2bf90"
|
||||
integrity sha512-VCNFOazIAnXn1hvgYYSTYMnoWgKgwlYh4lm1pKbSfiB3kj5ZYLcKVhfh3jkPOg1cnd9DP+pte9yCUocdPEUBTQ==
|
||||
dependencies:
|
||||
enhanced-resolve "^5.7.0"
|
||||
escalade "^3.1.1"
|
||||
|
||||
next@^12.0.7:
|
||||
version "12.0.7"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-12.0.7.tgz#33ebf229b81b06e583ab5ae7613cffe1ca2103fc"
|
||||
|
@ -8681,7 +8604,7 @@ prompts@^2.0.1, prompts@^2.4.2:
|
|||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.5.7, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.5.7, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
@ -8767,7 +8690,7 @@ quick-lru@^5.1.1:
|
|||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||
|
||||
raf@^3.4.1:
|
||||
raf@^3.0.0, raf@^3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
||||
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
|
||||
|
@ -8906,7 +8829,7 @@ react-hot-toast@^2.1.1:
|
|||
dependencies:
|
||||
goober "^2.1.1"
|
||||
|
||||
react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2:
|
||||
react-is@17.0.2, react-is@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
@ -8934,18 +8857,6 @@ react-popper@^2.2.3:
|
|||
react-fast-compare "^3.0.1"
|
||||
warning "^4.0.2"
|
||||
|
||||
react-redux@^7.2.2:
|
||||
version "7.2.6"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa"
|
||||
integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
"@types/react-redux" "^7.1.20"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^17.0.2"
|
||||
|
||||
react-refresh@0.8.3:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
||||
|
@ -9057,6 +8968,17 @@ react-sticky-box@^0.9.3:
|
|||
"@babel/runtime" "^7.1.5"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
|
||||
react-stickynode@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-stickynode/-/react-stickynode-4.0.0.tgz#ca1deeda866aeace3d522d01eb868f286cdb49d1"
|
||||
integrity sha512-H6Ae6l8soAc188eFAmE4CGJz3oidQa88jNO/fnJWjpFw4DwGRS6boL9gHNE4DCvbMPgek1AAP85pUPIEKUMgtw==
|
||||
dependencies:
|
||||
classnames "^2.0.0"
|
||||
core-js "^3.6.5"
|
||||
prop-types "^15.6.0"
|
||||
shallowequal "^1.0.0"
|
||||
subscribe-ui-event "^2.0.6"
|
||||
|
||||
react@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||
|
@ -9141,25 +9063,6 @@ redent@^3.0.0:
|
|||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
redux-persist@^5.10.0:
|
||||
version "5.10.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-5.10.0.tgz#5d8d802c5571e55924efc1c3a9b23575283be62b"
|
||||
integrity sha512-sSJAzNq7zka3qVHKce1hbvqf0Vf5DuTVm7dr4GtsqQVOexnrvbV47RWFiPxQ8fscnyiuWyD2O92DOxPl0tGCRg==
|
||||
|
||||
redux-saga@^1.1.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112"
|
||||
integrity sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw==
|
||||
dependencies:
|
||||
"@redux-saga/core" "^1.1.3"
|
||||
|
||||
redux@^4.0.0, redux@^4.0.4, redux@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
|
||||
integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
|
||||
regenerate-unicode-properties@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326"
|
||||
|
@ -9539,11 +9442,6 @@ scss-tokenizer@^0.2.3:
|
|||
js-base64 "^2.1.8"
|
||||
source-map "^0.4.2"
|
||||
|
||||
seamless-immutable@^7.1.3:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/seamless-immutable/-/seamless-immutable-7.1.4.tgz#6e9536def083ddc4dea0207d722e0e80d0f372f8"
|
||||
integrity sha512-XiUO1QP4ki4E2PHegiGAlu6r82o5A+6tRh7IkGGTVg/h+UoeX4nFBeCGPOhb4CYjvkqsfm/TUtvOMYC1xmV30A==
|
||||
|
||||
select-hose@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
||||
|
@ -9672,6 +9570,11 @@ sha.js@^2.4.0, sha.js@^2.4.8:
|
|||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
shallowequal@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
||||
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
|
||||
|
||||
shebang-command@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||
|
@ -10198,6 +10101,15 @@ stylis@3.5.4:
|
|||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe"
|
||||
integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==
|
||||
|
||||
subscribe-ui-event@^2.0.6:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/subscribe-ui-event/-/subscribe-ui-event-2.0.7.tgz#8d18b6339c35b25246a5335775573f0e5dc461f8"
|
||||
integrity sha512-Acrtf9XXl6lpyHAWYeRD1xTPUQHDERfL4GHeNuYAtZMc4Z8Us2iDBP0Fn3xiRvkQ1FO+hx+qRLmPEwiZxp7FDQ==
|
||||
dependencies:
|
||||
eventemitter3 "^3.0.0"
|
||||
lodash "^4.17.15"
|
||||
raf "^3.0.0"
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
|
@ -10649,25 +10561,6 @@ typedarray-to-buffer@^3.1.5:
|
|||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typescript-compare@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425"
|
||||
integrity sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==
|
||||
dependencies:
|
||||
typescript-logic "^0.0.0"
|
||||
|
||||
typescript-logic@^0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/typescript-logic/-/typescript-logic-0.0.0.tgz#66ebd82a2548f2b444a43667bec120b496890196"
|
||||
integrity sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==
|
||||
|
||||
typescript-tuple@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript-tuple/-/typescript-tuple-2.2.1.tgz#7d9813fb4b355f69ac55032e0363e8bb0f04dad2"
|
||||
integrity sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==
|
||||
dependencies:
|
||||
typescript-compare "^0.0.2"
|
||||
|
||||
typescript@^4.0.5:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue