mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
#23 added labs layout
This commit is contained in:
parent
8316b46efe
commit
18ec220a4e
14 changed files with 135 additions and 65 deletions
|
@ -1,42 +1,15 @@
|
||||||
import React, { DetailsHTMLAttributes, FC, useEffect, useRef } from 'react';
|
import React, { DetailsHTMLAttributes, FC } from 'react';
|
||||||
import styles from './styles.module.scss';
|
import StickyBox from 'react-sticky-box/dist/esnext';
|
||||||
|
|
||||||
import ResizeSensor from 'resize-sensor';
|
interface IProps extends DetailsHTMLAttributes<HTMLDivElement> {
|
||||||
(window as any).ResizeSensor = ResizeSensor;
|
offsetTop?: number;
|
||||||
|
}
|
||||||
import StickySidebar from 'sticky-sidebar';
|
|
||||||
(window as any).StickySidebar = StickySidebar;
|
|
||||||
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
interface IProps extends DetailsHTMLAttributes<HTMLDivElement> {}
|
|
||||||
|
|
||||||
const Sticky: FC<IProps> = ({ children }) => {
|
|
||||||
const ref = useRef(null);
|
|
||||||
const sb = useRef<StickySidebar>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ref.current) return;
|
|
||||||
|
|
||||||
sb.current = new StickySidebar(ref.current, {
|
|
||||||
resizeSensor: true,
|
|
||||||
topSpacing: 72,
|
|
||||||
bottomSpacing: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => sb.current?.destroy();
|
|
||||||
}, [ref.current, sb.current, children]);
|
|
||||||
|
|
||||||
if (sb) {
|
|
||||||
sb.current?.updateSticky();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const Sticky: FC<IProps> = ({ children, offsetTop = 65 }) => {
|
||||||
return (
|
return (
|
||||||
<div className={classnames(styles.wrap, 'sidebar_container')}>
|
<StickyBox offsetTop={offsetTop} offsetBottom={10}>
|
||||||
<div className="sidebar" ref={ref}>
|
{children}
|
||||||
<div className={classnames(styles.sticky, 'sidebar__inner')}>{children}</div>
|
</StickyBox>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
@import "src/styles/variables";
|
|
||||||
|
|
||||||
.wrap {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
:global(.sidebar) {
|
|
||||||
will-change: min-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.sidebar__inner) {
|
|
||||||
transform: translate(0, 0); /* For browsers don't support translate3d. */
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
will-change: position, transform;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -82,6 +82,15 @@ const HeaderUnconnected: FC<IProps> = memo(
|
||||||
<Filler />
|
<Filler />
|
||||||
|
|
||||||
<div className={styles.plugs}>
|
<div className={styles.plugs}>
|
||||||
|
{is_user && (
|
||||||
|
<Link
|
||||||
|
className={classNames(styles.item, { [styles.is_active]: pathname === URLS.BASE })}
|
||||||
|
to={URLS.LAB}
|
||||||
|
>
|
||||||
|
ЛАБ
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
className={classNames(styles.item, { [styles.is_active]: pathname === URLS.BASE })}
|
className={classNames(styles.item, { [styles.is_active]: pathname === URLS.BASE })}
|
||||||
to={URLS.BASE}
|
to={URLS.BASE}
|
||||||
|
@ -122,9 +131,6 @@ const HeaderUnconnected: FC<IProps> = memo(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const Header = connect(
|
const Header = connect(mapStateToProps, mapDispatchToProps)(HeaderUnconnected);
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(HeaderUnconnected);
|
|
||||||
|
|
||||||
export { Header };
|
export { Header };
|
||||||
|
|
|
@ -4,14 +4,12 @@ import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||||
import { AudioPlayer } from '~/components/media/AudioPlayer';
|
import { AudioPlayer } from '~/components/media/AudioPlayer';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { INodeComponentProps } from '~/redux/node/constants';
|
import { INodeComponentProps } from '~/redux/node/constants';
|
||||||
|
import { useNodeAudios } from '~/utils/hooks/node/useNodeAudios';
|
||||||
|
|
||||||
interface IProps extends INodeComponentProps {}
|
interface IProps extends INodeComponentProps {}
|
||||||
|
|
||||||
const NodeAudioBlock: FC<IProps> = ({ node }) => {
|
const NodeAudioBlock: FC<IProps> = ({ node }) => {
|
||||||
const audios = useMemo(
|
const audios = useNodeAudios(node);
|
||||||
() => node.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO),
|
|
||||||
[node.files]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
|
|
|
@ -6,14 +6,12 @@ import { path } from 'ramda';
|
||||||
import { getURL } from '~/utils/dom';
|
import { getURL } from '~/utils/dom';
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from '~/constants/urls';
|
||||||
import { INodeComponentProps } from '~/redux/node/constants';
|
import { INodeComponentProps } from '~/redux/node/constants';
|
||||||
|
import { useNodeImages } from '~/utils/hooks/node/useNodeImages';
|
||||||
|
|
||||||
interface IProps extends INodeComponentProps {}
|
interface IProps extends INodeComponentProps {}
|
||||||
|
|
||||||
const NodeAudioImageBlock: FC<IProps> = ({ node }) => {
|
const NodeAudioImageBlock: FC<IProps> = ({ node }) => {
|
||||||
const images = useMemo(
|
const images = useNodeImages(node);
|
||||||
() => node.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE),
|
|
||||||
[node.files]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (images.length === 0) return null;
|
if (images.length === 0) return null;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { INode } from '~/redux/types';
|
||||||
|
|
||||||
export const URLS = {
|
export const URLS = {
|
||||||
BASE: '/',
|
BASE: '/',
|
||||||
|
LAB: '/lab',
|
||||||
BORIS: '/boris',
|
BORIS: '/boris',
|
||||||
AUTH: {
|
AUTH: {
|
||||||
LOGIN: '/auth/login',
|
LOGIN: '/auth/login',
|
||||||
|
|
21
src/containers/lab/LabGrid/index.tsx
Normal file
21
src/containers/lab/LabGrid/index.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||||
|
import { selectFlowNodes } from '~/redux/flow/selectors';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { LabNode } from '~/containers/lab/LabNode';
|
||||||
|
|
||||||
|
interface IProps {}
|
||||||
|
|
||||||
|
const LabGrid: FC<IProps> = () => {
|
||||||
|
const nodes = useShallowSelect(selectFlowNodes);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
{nodes.map(node => (
|
||||||
|
<LabNode node={node} key={node.id} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { LabGrid };
|
8
src/containers/lab/LabGrid/styles.module.scss
Normal file
8
src/containers/lab/LabGrid/styles.module.scss
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
@import "~/styles/variables.scss";
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-auto-rows: auto;
|
||||||
|
grid-row-gap: $gap;
|
||||||
|
}
|
27
src/containers/lab/LabLayout/index.tsx
Normal file
27
src/containers/lab/LabLayout/index.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { Card } from '~/components/containers/Card';
|
||||||
|
import { Sticky } from '~/components/containers/Sticky';
|
||||||
|
import { Container } from '~/containers/main/Container';
|
||||||
|
import { LabGrid } from '~/containers/lab/LabGrid';
|
||||||
|
|
||||||
|
interface IProps {}
|
||||||
|
|
||||||
|
const LabLayout: FC<IProps> = () => (
|
||||||
|
<div>
|
||||||
|
<Container>
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<LabGrid />
|
||||||
|
</div>
|
||||||
|
<div className={styles.panel}>
|
||||||
|
<Sticky>
|
||||||
|
<Card>Test</Card>
|
||||||
|
</Sticky>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { LabLayout };
|
11
src/containers/lab/LabLayout/styles.module.scss
Normal file
11
src/containers/lab/LabLayout/styles.module.scss
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
@import "~/styles/variables.scss";
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3fr 1fr;
|
||||||
|
column-gap: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
margin-top: -7px;
|
||||||
|
}
|
34
src/containers/lab/LabNode/index.tsx
Normal file
34
src/containers/lab/LabNode/index.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { INode } from '~/redux/types';
|
||||||
|
import { NodePanelInner } from '~/components/node/NodePanelInner';
|
||||||
|
import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
node: INode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LabNode: FC<IProps> = ({ node }) => {
|
||||||
|
const { inline, block, head } = useNodeBlocks(node, false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<NodePanelInner
|
||||||
|
node={node}
|
||||||
|
canEdit
|
||||||
|
canLike
|
||||||
|
canStar
|
||||||
|
isLoading={false}
|
||||||
|
onEdit={console.log}
|
||||||
|
onLike={console.log}
|
||||||
|
onStar={console.log}
|
||||||
|
onLock={console.log}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{inline}
|
||||||
|
{block}
|
||||||
|
{head}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { LabNode };
|
|
@ -6,6 +6,7 @@ import { BorisLayout } from '~/containers/node/BorisLayout';
|
||||||
import { ErrorNotFound } from '~/containers/pages/ErrorNotFound';
|
import { ErrorNotFound } from '~/containers/pages/ErrorNotFound';
|
||||||
import { ProfilePage } from '~/containers/profile/ProfilePage';
|
import { ProfilePage } from '~/containers/profile/ProfilePage';
|
||||||
import { Redirect, Route, Switch, useLocation } from 'react-router';
|
import { Redirect, Route, Switch, useLocation } from 'react-router';
|
||||||
|
import { LabLayout } from '~/containers/lab/LabLayout';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ const MainRouter: FC<IProps> = () => {
|
||||||
return (
|
return (
|
||||||
<Switch location={location}>
|
<Switch location={location}>
|
||||||
<Route exact path={URLS.BASE} component={FlowLayout} />
|
<Route exact path={URLS.BASE} component={FlowLayout} />
|
||||||
|
<Route exact path={URLS.LAB} component={LabLayout} />
|
||||||
<Route path={URLS.NODE_URL(':id')} component={NodeLayout} />
|
<Route path={URLS.NODE_URL(':id')} component={NodeLayout} />
|
||||||
<Route path={URLS.BORIS} component={BorisLayout} />
|
<Route path={URLS.BORIS} component={BorisLayout} />
|
||||||
<Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
|
<Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
|
||||||
|
|
|
@ -3,6 +3,10 @@ import { useMemo } from 'react';
|
||||||
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||||
|
|
||||||
export const useNodeAudios = (node: INode) => {
|
export const useNodeAudios = (node: INode) => {
|
||||||
|
if (!node?.files) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return useMemo(() => node.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO), [
|
return useMemo(() => node.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO), [
|
||||||
node.files,
|
node.files,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -3,6 +3,10 @@ import { useMemo } from 'react';
|
||||||
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||||
|
|
||||||
export const useNodeImages = (node: INode) => {
|
export const useNodeImages = (node: INode) => {
|
||||||
|
if (!node?.files) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return useMemo(() => node.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE), [
|
return useMemo(() => node.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE), [
|
||||||
node.files,
|
node.files,
|
||||||
]);
|
]);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue