mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
#23 added lab node layout (sample)
This commit is contained in:
parent
18ec220a4e
commit
3aa2d4f609
18 changed files with 218 additions and 38 deletions
|
@ -31,8 +31,6 @@
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: stretch;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -88,7 +86,7 @@
|
||||||
@include tablet {
|
@include tablet {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
font: $font_20_semibold;
|
font: $font_16_semibold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
src/components/node/NodePanelLab/index.tsx
Normal file
19
src/components/node/NodePanelLab/index.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { INode } from '~/redux/types';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { URLS } from '~/constants/urls';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
node: INode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodePanelLab: FC<IProps> = ({ node }) => (
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
<Link to={URLS.NODE_URL(node.id)}>{node.title || '...'}</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { NodePanelLab };
|
24
src/components/node/NodePanelLab/styles.module.scss
Normal file
24
src/components/node/NodePanelLab/styles.module.scss
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
@import "~/styles/variables.scss";
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
padding: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font: $font_24_semibold;
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-bottom: 0;
|
||||||
|
font: $font_16_semibold;
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,4 +50,7 @@ export const API = {
|
||||||
NODES: `/tag/nodes`,
|
NODES: `/tag/nodes`,
|
||||||
AUTOCOMPLETE: `/tag/autocomplete`,
|
AUTOCOMPLETE: `/tag/autocomplete`,
|
||||||
},
|
},
|
||||||
|
LAB: {
|
||||||
|
NODES: `/lab/`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||||
import { selectFlowNodes } from '~/redux/flow/selectors';
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { LabNode } from '~/containers/lab/LabNode';
|
import { LabNode } from '~/containers/lab/LabNode';
|
||||||
|
import { selectLabListNodes } from '~/redux/lab/selectors';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
const LabGrid: FC<IProps> = () => {
|
const LabGrid: FC<IProps> = () => {
|
||||||
const nodes = useShallowSelect(selectFlowNodes);
|
const nodes = useShallowSelect(selectLabListNodes);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useEffect } from 'react';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { Card } from '~/components/containers/Card';
|
import { Card } from '~/components/containers/Card';
|
||||||
import { Sticky } from '~/components/containers/Sticky';
|
import { Sticky } from '~/components/containers/Sticky';
|
||||||
import { Container } from '~/containers/main/Container';
|
import { Container } from '~/containers/main/Container';
|
||||||
import { LabGrid } from '~/containers/lab/LabGrid';
|
import { LabGrid } from '~/containers/lab/LabGrid';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { labGetList } from '~/redux/lab/actions';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
const LabLayout: FC<IProps> = () => (
|
const LabLayout: FC<IProps> = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(labGetList());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Container>
|
<Container>
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
|
@ -23,5 +32,6 @@ const LabLayout: FC<IProps> = () => (
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export { LabLayout };
|
export { LabLayout };
|
||||||
|
|
|
@ -2,6 +2,9 @@ import React, { FC } from 'react';
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from '~/redux/types';
|
||||||
import { NodePanelInner } from '~/components/node/NodePanelInner';
|
import { NodePanelInner } from '~/components/node/NodePanelInner';
|
||||||
import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
|
import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { Card } from '~/components/containers/Card';
|
||||||
|
import { NodePanelLab } from '~/components/node/NodePanelLab';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
|
@ -10,24 +13,18 @@ interface IProps {
|
||||||
const LabNode: FC<IProps> = ({ node }) => {
|
const LabNode: FC<IProps> = ({ node }) => {
|
||||||
const { inline, block, head } = useNodeBlocks(node, false);
|
const { inline, block, head } = useNodeBlocks(node, false);
|
||||||
|
|
||||||
return (
|
console.log(node.id, { inline, block, head });
|
||||||
<div>
|
|
||||||
<NodePanelInner
|
|
||||||
node={node}
|
|
||||||
canEdit
|
|
||||||
canLike
|
|
||||||
canStar
|
|
||||||
isLoading={false}
|
|
||||||
onEdit={console.log}
|
|
||||||
onLike={console.log}
|
|
||||||
onStar={console.log}
|
|
||||||
onLock={console.log}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{inline}
|
return (
|
||||||
{block}
|
<Card seamless className={styles.wrap}>
|
||||||
{head}
|
<div className={styles.head}>
|
||||||
|
<NodePanelLab node={node} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{head}
|
||||||
|
{block}
|
||||||
|
{inline}
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
10
src/containers/lab/LabNode/styles.module.scss
Normal file
10
src/containers/lab/LabNode/styles.module.scss
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
@import "~/styles/variables.scss";
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head {
|
||||||
|
background-color: transparentize(black, 0.9);
|
||||||
|
border-radius: $radius $radius 0 0;
|
||||||
|
}
|
|
@ -7,21 +7,29 @@ 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';
|
import { LabLayout } from '~/containers/lab/LabLayout';
|
||||||
|
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||||
|
import { selectAuthUser } from '~/redux/auth/selectors';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
const MainRouter: FC<IProps> = () => {
|
const MainRouter: FC<IProps> = () => {
|
||||||
|
const { is_user } = useShallowSelect(selectAuthUser);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
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} />
|
||||||
<Route path={URLS.PROFILE_PAGE(':username')} component={ProfilePage} />
|
<Route path={URLS.PROFILE_PAGE(':username')} component={ProfilePage} />
|
||||||
|
|
||||||
|
{is_user && (
|
||||||
|
<>
|
||||||
|
<Route exact path={URLS.LAB} component={LabLayout} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
|
12
src/redux/lab/actions.ts
Normal file
12
src/redux/lab/actions.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { LAB_ACTIONS } from '~/redux/lab/constants';
|
||||||
|
import { ILabState } from '~/redux/lab/types';
|
||||||
|
|
||||||
|
export const labGetList = (after?: string) => ({
|
||||||
|
type: LAB_ACTIONS.GET_LIST,
|
||||||
|
after,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const labSetList = (list: Partial<ILabState['list']>) => ({
|
||||||
|
type: LAB_ACTIONS.SET_LIST,
|
||||||
|
list,
|
||||||
|
});
|
8
src/redux/lab/api.ts
Normal file
8
src/redux/lab/api.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { api, cleanResult } from '~/utils/api';
|
||||||
|
import { API } from '~/constants/api';
|
||||||
|
import { GetLabNodesRequest, GetLabNodesResult } from '~/redux/lab/types';
|
||||||
|
|
||||||
|
export const getLabNodes = ({ after }: GetLabNodesRequest) =>
|
||||||
|
api
|
||||||
|
.get<GetLabNodesResult>(API.LAB.NODES, { params: { after } })
|
||||||
|
.then(cleanResult);
|
6
src/redux/lab/constants.ts
Normal file
6
src/redux/lab/constants.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
const prefix = 'LAB.';
|
||||||
|
|
||||||
|
export const LAB_ACTIONS = {
|
||||||
|
GET_LIST: `${prefix}GET_LIST`,
|
||||||
|
SET_LIST: `${prefix}SET_LIST`,
|
||||||
|
};
|
20
src/redux/lab/handlers.ts
Normal file
20
src/redux/lab/handlers.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { LAB_ACTIONS } from '~/redux/lab/constants';
|
||||||
|
import { labSetList } from '~/redux/lab/actions';
|
||||||
|
import { ILabState } from '~/redux/lab/types';
|
||||||
|
|
||||||
|
type LabHandler<T extends (...args: any) => any> = (
|
||||||
|
state: Readonly<ILabState>,
|
||||||
|
payload: ReturnType<T>
|
||||||
|
) => Readonly<ILabState>;
|
||||||
|
|
||||||
|
const setList: LabHandler<typeof labSetList> = (state, { list }) => ({
|
||||||
|
...state,
|
||||||
|
list: {
|
||||||
|
...state.list,
|
||||||
|
...list,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LAB_HANDLERS = {
|
||||||
|
[LAB_ACTIONS.SET_LIST]: setList,
|
||||||
|
};
|
14
src/redux/lab/index.ts
Normal file
14
src/redux/lab/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { createReducer } from '~/utils/reducer';
|
||||||
|
import { LAB_HANDLERS } from '~/redux/lab/handlers';
|
||||||
|
import { ILabState } from '~/redux/lab/types';
|
||||||
|
|
||||||
|
const INITIAL_STATE: ILabState = {
|
||||||
|
list: {
|
||||||
|
is_loading: false,
|
||||||
|
nodes: [],
|
||||||
|
count: 0,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createReducer(INITIAL_STATE, LAB_HANDLERS);
|
21
src/redux/lab/sagas.ts
Normal file
21
src/redux/lab/sagas.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { takeLeading, call, put } from 'redux-saga/effects';
|
||||||
|
import { labGetList, labSetList } from '~/redux/lab/actions';
|
||||||
|
import { LAB_ACTIONS } from '~/redux/lab/constants';
|
||||||
|
import { Unwrap } from '~/redux/types';
|
||||||
|
import { getLabNodes } from '~/redux/lab/api';
|
||||||
|
|
||||||
|
function* getList({ after = '' }: ReturnType<typeof labGetList>) {
|
||||||
|
try {
|
||||||
|
yield put(labSetList({ is_loading: true }));
|
||||||
|
const { nodes, count }: Unwrap<typeof getLabNodes> = yield call(getLabNodes, { after });
|
||||||
|
yield put(labSetList({ nodes, count }));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(labSetList({ error: error.message }));
|
||||||
|
} finally {
|
||||||
|
yield put(labSetList({ is_loading: false }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function* labSaga() {
|
||||||
|
yield takeLeading(LAB_ACTIONS.GET_LIST, getList);
|
||||||
|
}
|
4
src/redux/lab/selectors.ts
Normal file
4
src/redux/lab/selectors.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { IState } from '~/redux/store';
|
||||||
|
|
||||||
|
export const selectLab = (state: IState) => state.lab;
|
||||||
|
export const selectLabListNodes = (state: IState) => state.lab.list.nodes;
|
19
src/redux/lab/types.ts
Normal file
19
src/redux/lab/types.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { IError, INode } from '~/redux/types';
|
||||||
|
|
||||||
|
export type ILabState = Readonly<{
|
||||||
|
list: {
|
||||||
|
is_loading: boolean;
|
||||||
|
nodes: INode[];
|
||||||
|
count: number;
|
||||||
|
error: IError;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type GetLabNodesRequest = {
|
||||||
|
after?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetLabNodesResult = {
|
||||||
|
nodes: INode[];
|
||||||
|
count: number;
|
||||||
|
};
|
|
@ -17,6 +17,10 @@ import nodeSaga from '~/redux/node/sagas';
|
||||||
import flow, { IFlowState } from '~/redux/flow/reducer';
|
import flow, { IFlowState } from '~/redux/flow/reducer';
|
||||||
import flowSaga from '~/redux/flow/sagas';
|
import flowSaga from '~/redux/flow/sagas';
|
||||||
|
|
||||||
|
import lab from '~/redux/lab';
|
||||||
|
import labSaga from '~/redux/lab/sagas';
|
||||||
|
import { ILabState } from '~/redux/lab/types';
|
||||||
|
|
||||||
import uploads, { IUploadState } from '~/redux/uploads/reducer';
|
import uploads, { IUploadState } from '~/redux/uploads/reducer';
|
||||||
import uploadSaga from '~/redux/uploads/sagas';
|
import uploadSaga from '~/redux/uploads/sagas';
|
||||||
|
|
||||||
|
@ -69,6 +73,7 @@ export interface IState {
|
||||||
boris: IBorisState;
|
boris: IBorisState;
|
||||||
messages: IMessagesState;
|
messages: IMessagesState;
|
||||||
tag: ITagState;
|
tag: ITagState;
|
||||||
|
lab: ILabState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sagaMiddleware = createSagaMiddleware();
|
export const sagaMiddleware = createSagaMiddleware();
|
||||||
|
@ -93,6 +98,7 @@ export const store = createStore(
|
||||||
player: persistReducer(playerPersistConfig, player),
|
player: persistReducer(playerPersistConfig, player),
|
||||||
messages,
|
messages,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
|
lab: lab,
|
||||||
}),
|
}),
|
||||||
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
|
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
|
||||||
);
|
);
|
||||||
|
@ -110,6 +116,7 @@ export function configureStore(): {
|
||||||
sagaMiddleware.run(borisSaga);
|
sagaMiddleware.run(borisSaga);
|
||||||
sagaMiddleware.run(messagesSaga);
|
sagaMiddleware.run(messagesSaga);
|
||||||
sagaMiddleware.run(tagSaga);
|
sagaMiddleware.run(tagSaga);
|
||||||
|
sagaMiddleware.run(labSaga);
|
||||||
|
|
||||||
window.addEventListener('message', message => {
|
window.addEventListener('message', message => {
|
||||||
if (message && message.data && message.data.type === 'oauth_login' && message.data.token)
|
if (message && message.data && message.data.type === 'oauth_login' && message.data.token)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue