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

#23 added lab node layout (sample)

This commit is contained in:
Fedor Katurov 2021-03-12 13:56:23 +07:00
parent 18ec220a4e
commit 3aa2d4f609
18 changed files with 218 additions and 38 deletions

View file

@ -31,8 +31,6 @@
.wrap {
display: flex;
align-items: center;
justify-content: stretch;
position: relative;
width: 100%;
flex-direction: row;
@ -88,7 +86,7 @@
@include tablet {
white-space: nowrap;
padding-bottom: 0;
font: $font_20_semibold;
font: $font_16_semibold;
}
}

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

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

View file

@ -50,4 +50,7 @@ export const API = {
NODES: `/tag/nodes`,
AUTOCOMPLETE: `/tag/autocomplete`,
},
LAB: {
NODES: `/lab/`,
},
};

View file

@ -1,13 +1,13 @@
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';
import { selectLabListNodes } from '~/redux/lab/selectors';
interface IProps {}
const LabGrid: FC<IProps> = () => {
const nodes = useShallowSelect(selectFlowNodes);
const nodes = useShallowSelect(selectLabListNodes);
return (
<div className={styles.wrap}>

View file

@ -1,13 +1,22 @@
import React, { FC } from 'react';
import React, { FC, useEffect } 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';
import { useDispatch } from 'react-redux';
import { labGetList } from '~/redux/lab/actions';
interface IProps {}
const LabLayout: FC<IProps> = () => (
const LabLayout: FC<IProps> = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(labGetList());
}, [dispatch]);
return (
<div>
<Container>
<div className={styles.wrap}>
@ -22,6 +31,7 @@ const LabLayout: FC<IProps> = () => (
</div>
</Container>
</div>
);
);
};
export { LabLayout };

View file

@ -2,6 +2,9 @@ import React, { FC } from 'react';
import { INode } from '~/redux/types';
import { NodePanelInner } from '~/components/node/NodePanelInner';
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 {
node: INode;
@ -10,24 +13,18 @@ interface IProps {
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}
/>
console.log(node.id, { inline, block, head });
{inline}
{block}
{head}
return (
<Card seamless className={styles.wrap}>
<div className={styles.head}>
<NodePanelLab node={node} />
</div>
{head}
{block}
{inline}
</Card>
);
};

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

View file

@ -7,21 +7,29 @@ import { ErrorNotFound } from '~/containers/pages/ErrorNotFound';
import { ProfilePage } from '~/containers/profile/ProfilePage';
import { Redirect, Route, Switch, useLocation } from 'react-router';
import { LabLayout } from '~/containers/lab/LabLayout';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectAuthUser } from '~/redux/auth/selectors';
interface IProps {}
const MainRouter: FC<IProps> = () => {
const { is_user } = useShallowSelect(selectAuthUser);
const location = useLocation();
return (
<Switch location={location}>
<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.BORIS} component={BorisLayout} />
<Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
<Route path={URLS.PROFILE_PAGE(':username')} component={ProfilePage} />
{is_user && (
<>
<Route exact path={URLS.LAB} component={LabLayout} />
</>
)}
<Redirect to="/" />
</Switch>
);

12
src/redux/lab/actions.ts Normal file
View 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
View 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);

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

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

View file

@ -17,6 +17,10 @@ import nodeSaga from '~/redux/node/sagas';
import flow, { IFlowState } from '~/redux/flow/reducer';
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 uploadSaga from '~/redux/uploads/sagas';
@ -69,6 +73,7 @@ export interface IState {
boris: IBorisState;
messages: IMessagesState;
tag: ITagState;
lab: ILabState;
}
export const sagaMiddleware = createSagaMiddleware();
@ -93,6 +98,7 @@ export const store = createStore(
player: persistReducer(playerPersistConfig, player),
messages,
tag: tag,
lab: lab,
}),
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
);
@ -110,6 +116,7 @@ export function configureStore(): {
sagaMiddleware.run(borisSaga);
sagaMiddleware.run(messagesSaga);
sagaMiddleware.run(tagSaga);
sagaMiddleware.run(labSaga);
window.addEventListener('message', message => {
if (message && message.data && message.data.type === 'oauth_login' && message.data.token)