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

node deletion (lock)

This commit is contained in:
Fedor Katurov 2019-11-29 10:40:43 +07:00
parent b1279e8d4d
commit 8f0625f734
12 changed files with 113 additions and 58 deletions

View file

@ -47,7 +47,7 @@ const NodeImageSlideBlock: FC<IProps> = ({ node, is_loading, updateLayout }) =>
const images = useMemo( const images = useMemo(
() => () =>
(node && node.files && node.files.filter(({ type }) => type === UPLOAD_TYPES.IMAGE)) || [], (node && node.files && node.files.filter(({ type }) => type === UPLOAD_TYPES.IMAGE)) || [],
[node] [node.files]
); );
useEffect(() => { useEffect(() => {

View file

@ -3,7 +3,6 @@ import * as styles from './styles.scss';
import { INode } from '~/redux/types'; import { INode } from '~/redux/types';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { NodePanelInner } from '~/components/node/NodePanelInner'; import { NodePanelInner } from '~/components/node/NodePanelInner';
import pick from 'ramda/es/pick';
interface IProps { interface IProps {
node: Partial<INode>; node: Partial<INode>;
@ -18,10 +17,11 @@ interface IProps {
onEdit: () => void; onEdit: () => void;
onLike: () => void; onLike: () => void;
onStar: () => void; onStar: () => void;
onLock: () => void;
} }
const NodePanel: FC<IProps> = memo( const NodePanel: FC<IProps> = memo(
({ node, layout, can_edit, can_like, can_star, is_loading, onEdit, onLike, onStar }) => { ({ node, layout, can_edit, can_like, can_star, is_loading, onEdit, onLike, onStar, onLock }) => {
const [stack, setStack] = useState(false); const [stack, setStack] = useState(false);
const ref = useRef(null); const ref = useRef(null);
@ -51,14 +51,15 @@ const NodePanel: FC<IProps> = memo(
createPortal( createPortal(
<NodePanelInner <NodePanelInner
node={node} node={node}
stack
onEdit={onEdit}
onLike={onLike}
onStar={onStar}
can_edit={can_edit} can_edit={can_edit}
can_like={can_like} can_like={can_like}
can_star={can_star} can_star={can_star}
onEdit={onEdit}
onLike={onLike}
onStar={onStar}
onLock={onLock}
is_loading={is_loading} is_loading={is_loading}
stack
/>, />,
document.body document.body
) )
@ -68,6 +69,7 @@ const NodePanel: FC<IProps> = memo(
onEdit={onEdit} onEdit={onEdit}
onLike={onLike} onLike={onLike}
onStar={onStar} onStar={onStar}
onLock={onLock}
can_edit={can_edit} can_edit={can_edit}
can_like={can_like} can_like={can_like}
can_star={can_star} can_star={can_star}

View file

@ -1,11 +1,11 @@
import React, { FC } from "react"; import React, { FC } from 'react';
import * as styles from "./styles.scss"; import * as styles from './styles.scss';
import { Group } from "~/components/containers/Group"; import { Group } from '~/components/containers/Group';
import { Filler } from "~/components/containers/Filler"; import { Filler } from '~/components/containers/Filler';
import { Icon } from "~/components/input/Icon"; import { Icon } from '~/components/input/Icon';
import { INode } from "~/redux/types"; import { INode } from '~/redux/types';
import classNames from "classnames"; import classNames from 'classnames';
import { Placeholder } from "~/components/placeholders/Placeholder"; import { Placeholder } from '~/components/placeholders/Placeholder';
interface IProps { interface IProps {
node: Partial<INode>; node: Partial<INode>;
@ -20,10 +20,11 @@ interface IProps {
onEdit: () => void; onEdit: () => void;
onLike: () => void; onLike: () => void;
onStar: () => void; onStar: () => void;
onLock: () => void;
} }
const NodePanelInner: FC<IProps> = ({ const NodePanelInner: FC<IProps> = ({
node: { title, user, is_liked, is_heroic }, node: { title, user, is_liked, is_heroic, deleted_at },
stack, stack,
can_star, can_star,
@ -34,7 +35,8 @@ const NodePanelInner: FC<IProps> = ({
onStar, onStar,
onEdit, onEdit,
onLike onLike,
onLock,
}) => { }) => {
return ( return (
<div className={classNames(styles.wrap, { stack })}> <div className={classNames(styles.wrap, { stack })}>
@ -42,15 +44,11 @@ const NodePanelInner: FC<IProps> = ({
<Group horizontal className={styles.panel}> <Group horizontal className={styles.panel}>
<Filler> <Filler>
<div className={styles.title}> <div className={styles.title}>
{is_loading ? <Placeholder width="40%" /> : title || "..."} {is_loading ? <Placeholder width="40%" /> : title || '...'}
</div> </div>
{user && user.username && ( {user && user.username && (
<div className={styles.name}> <div className={styles.name}>
{is_loading ? ( {is_loading ? <Placeholder width="100px" /> : `~${user.username}`}
<Placeholder width="100px" />
) : (
`~${user.username}`
)}
</div> </div>
)} )}
</Filler> </Filler>
@ -66,11 +64,19 @@ const NodePanelInner: FC<IProps> = ({
)} )}
</div> </div>
)} )}
{can_edit && ( {can_edit && (
<>
<div>
<Icon icon={deleted_at ? 'locked' : 'unlocked'} size={24} onClick={onLock} />
</div>
<div> <div>
<Icon icon="edit" size={24} onClick={onEdit} /> <Icon icon="edit" size={24} onClick={onEdit} />
</div> </div>
</>
)} )}
{can_like && ( {can_like && (
<div className={classNames(styles.like, { is_liked })}> <div className={classNames(styles.like, { is_liked })}>
{is_liked ? ( {is_liked ? (
@ -87,5 +93,3 @@ const NodePanelInner: FC<IProps> = ({
}; };
export { NodePanelInner }; export { NodePanelInner };
// <div className={styles.mark} />

View file

@ -25,6 +25,7 @@ export const API = {
UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`, UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`,
POST_LIKE: (id: INode['id']) => `/node/${id}/like`, POST_LIKE: (id: INode['id']) => `/node/${id}/like`,
POST_STAR: (id: INode['id']) => `/node/${id}/heroic`, POST_STAR: (id: INode['id']) => `/node/${id}/heroic`,
POST_LOCK: (id: INode['id']) => `/node/${id}/lock`,
SET_CELL_VIEW: (id: INode['id']) => `/node/${id}/cell-view`, SET_CELL_VIEW: (id: INode['id']) => `/node/${id}/cell-view`,
}, },
}; };

View file

@ -32,6 +32,7 @@ const mapDispatchToProps = {
nodeEdit: NODE_ACTIONS.nodeEdit, nodeEdit: NODE_ACTIONS.nodeEdit,
nodeLike: NODE_ACTIONS.nodeLike, nodeLike: NODE_ACTIONS.nodeLike,
nodeStar: NODE_ACTIONS.nodeStar, nodeStar: NODE_ACTIONS.nodeStar,
nodeLock: NODE_ACTIONS.nodeLock,
}; };
type IProps = ReturnType<typeof mapStateToProps> & type IProps = ReturnType<typeof mapStateToProps> &
@ -51,6 +52,7 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
nodeEdit, nodeEdit,
nodeLike, nodeLike,
nodeStar, nodeStar,
nodeLock,
nodeSetCoverImage, nodeSetCoverImage,
}) => { }) => {
// const is_loading = true; // const is_loading = true;
@ -81,6 +83,7 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
const onEdit = useCallback(() => nodeEdit(node.id), [nodeEdit, node]); const onEdit = useCallback(() => nodeEdit(node.id), [nodeEdit, node]);
const onLike = useCallback(() => nodeLike(node.id), [nodeLike, node]); const onLike = useCallback(() => nodeLike(node.id), [nodeLike, node]);
const onStar = useCallback(() => nodeStar(node.id), [nodeStar, node]); const onStar = useCallback(() => nodeStar(node.id), [nodeStar, node]);
const onLock = useCallback(() => nodeLock(node.id, !node.deleted_at), [nodeStar, node]);
useEffect(() => { useEffect(() => {
if (!node.cover) return; if (!node.cover) return;
@ -93,7 +96,7 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
{block && createElement(block, { node, is_loading, updateLayout, layout })} {block && createElement(block, { node, is_loading, updateLayout, layout })}
<NodePanel <NodePanel
node={pick(['title', 'user', 'is_liked', 'is_heroic'], node)} node={pick(['title', 'user', 'is_liked', 'is_heroic', 'deleted_at'], node)}
layout={layout} layout={layout}
can_edit={can_edit} can_edit={can_edit}
can_like={can_like} can_like={can_like}
@ -101,6 +104,7 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
onEdit={onEdit} onEdit={onEdit}
onLike={onLike} onLike={onLike}
onStar={onStar} onStar={onStar}
onLock={onLock}
is_loading={is_loading} is_loading={is_loading}
/> />

View file

@ -19,7 +19,6 @@ render(
); );
/* /*
[Stage 0]: [Stage 0]:
- set file target on comment save, node save, profile upload - set file target on comment save, node save, profile upload
- check if email is registered at social login - check if email is registered at social login
@ -28,10 +27,10 @@ render(
- delete nodes - delete nodes
- illustrate 404 - illustrate 404
- illustrate restoreRequestDialog
- illustrate login
[stage 1] [stage 1]
- illustrate login
- illustrate restoreRequestDialog
- friendship - friendship
- signup? - signup?
- import videos - import videos

View file

@ -96,6 +96,12 @@ export const nodeStar = (id: INode['id']) => ({
id, id,
}); });
export const nodeLock = (id: INode['id'], is_locked: boolean) => ({
type: NODE_ACTIONS.LOCK,
id,
is_locked,
});
export const nodeSetEditor = (editor: INode) => ({ export const nodeSetEditor = (editor: INode) => ({
type: NODE_ACTIONS.SET_EDITOR, type: NODE_ACTIONS.SET_EDITOR,
editor, editor,

View file

@ -1,17 +1,12 @@
import { import { api, configWithToken, resultMiddleware, errorMiddleware } from '~/utils/api';
api, import { INode, IResultWithStatus, IComment } from '../types';
configWithToken, import { API } from '~/constants/api';
resultMiddleware, import { nodeUpdateTags, nodeLike, nodeStar, nodeLock } from './actions';
errorMiddleware import { INodeState } from './reducer';
} from "~/utils/api";
import { INode, IResultWithStatus, IComment } from "../types";
import { API } from "~/constants/api";
import { nodeUpdateTags, nodeLike, nodeStar } from "./actions";
import { INodeState } from "./reducer";
export const postNode = ({ export const postNode = ({
access, access,
node node,
}: { }: {
access: string; access: string;
node: INode; node: INode;
@ -24,7 +19,7 @@ export const postNode = ({
export const getNodes = ({ export const getNodes = ({
from = null, from = null,
access access,
}: { }: {
from?: string; from?: string;
access: string; access: string;
@ -42,7 +37,7 @@ export const getNodeDiff = ({
with_updated, with_updated,
with_recent, with_recent,
with_valid, with_valid,
access access,
}: { }: {
start?: string; start?: string;
end?: string; end?: string;
@ -64,8 +59,8 @@ export const getNodeDiff = ({
with_heroes, with_heroes,
with_updated, with_updated,
with_recent, with_recent,
with_valid with_valid,
} },
}) })
) )
.then(resultMiddleware) .then(resultMiddleware)
@ -73,7 +68,7 @@ export const getNodeDiff = ({
export const getNode = ({ export const getNode = ({
id, id,
access access,
}: { }: {
id: string | number; id: string | number;
access: string; access: string;
@ -86,7 +81,7 @@ export const getNode = ({
export const postNodeComment = ({ export const postNodeComment = ({
id, id,
data, data,
access access,
}: { }: {
access: string; access: string;
id: number; id: number;
@ -100,11 +95,11 @@ export const postNodeComment = ({
export const getNodeComments = ({ export const getNodeComments = ({
id, id,
access, access,
order = "ASC" order = 'ASC',
}: { }: {
id: number; id: number;
access: string; access: string;
order: "ASC" | "DESC"; order: 'ASC' | 'DESC';
}): Promise<IResultWithStatus<{ comments: Comment[] }>> => }): Promise<IResultWithStatus<{ comments: Comment[] }>> =>
api api
.get(API.NODE.COMMENT(id), configWithToken(access, { params: { order } })) .get(API.NODE.COMMENT(id), configWithToken(access, { params: { order } }))
@ -113,11 +108,11 @@ export const getNodeComments = ({
export const getNodeRelated = ({ export const getNodeRelated = ({
id, id,
access access,
}: { }: {
id: number; id: number;
access: string; access: string;
}): Promise<IResultWithStatus<{ related: INodeState["related"] }>> => }): Promise<IResultWithStatus<{ related: INodeState['related'] }>> =>
api api
.get(API.NODE.RELATED(id), configWithToken(access)) .get(API.NODE.RELATED(id), configWithToken(access))
.then(resultMiddleware) .then(resultMiddleware)
@ -126,7 +121,7 @@ export const getNodeRelated = ({
export const updateNodeTags = ({ export const updateNodeTags = ({
id, id,
tags, tags,
access access,
}: ReturnType<typeof nodeUpdateTags> & { access: string }): Promise< }: ReturnType<typeof nodeUpdateTags> & { access: string }): Promise<
IResultWithStatus<{ node: INode }> IResultWithStatus<{ node: INode }>
> => > =>
@ -137,9 +132,9 @@ export const updateNodeTags = ({
export const postNodeLike = ({ export const postNodeLike = ({
id, id,
access access,
}: ReturnType<typeof nodeLike> & { access: string }): Promise< }: ReturnType<typeof nodeLike> & { access: string }): Promise<
IResultWithStatus<{ is_liked: INode["is_liked"] }> IResultWithStatus<{ is_liked: INode['is_liked'] }>
> => > =>
api api
.post(API.NODE.POST_LIKE(id), {}, configWithToken(access)) .post(API.NODE.POST_LIKE(id), {}, configWithToken(access))
@ -148,11 +143,23 @@ export const postNodeLike = ({
export const postNodeStar = ({ export const postNodeStar = ({
id, id,
access access,
}: ReturnType<typeof nodeStar> & { access: string }): Promise< }: ReturnType<typeof nodeStar> & { access: string }): Promise<
IResultWithStatus<{ is_liked: INode["is_liked"] }> IResultWithStatus<{ is_liked: INode['is_liked'] }>
> => > =>
api api
.post(API.NODE.POST_STAR(id), {}, configWithToken(access)) .post(API.NODE.POST_STAR(id), {}, configWithToken(access))
.then(resultMiddleware) .then(resultMiddleware)
.catch(errorMiddleware); .catch(errorMiddleware);
export const postNodeLock = ({
id,
is_locked,
access,
}: ReturnType<typeof nodeLock> & { access: string }): Promise<
IResultWithStatus<{ deleted_at: INode['deleted_at'] }>
> =>
api
.post(API.NODE.POST_LOCK(id), { is_locked }, configWithToken(access))
.then(resultMiddleware)
.catch(errorMiddleware);

View file

@ -23,6 +23,7 @@ export const NODE_ACTIONS = {
EDIT: `${prefix}EDIT`, EDIT: `${prefix}EDIT`,
LIKE: `${prefix}LIKE`, LIKE: `${prefix}LIKE`,
STAR: `${prefix}STAR`, STAR: `${prefix}STAR`,
LOCK: `${prefix}LOCK`,
CREATE: `${prefix}CREATE`, CREATE: `${prefix}CREATE`,
SET_SAVE_ERRORS: `${prefix}SET_SAVE_ERRORS`, SET_SAVE_ERRORS: `${prefix}SET_SAVE_ERRORS`,

View file

@ -21,6 +21,7 @@ import {
nodeLike, nodeLike,
nodeSetRelated, nodeSetRelated,
nodeGotoNode, nodeGotoNode,
nodeLock,
} from './actions'; } from './actions';
import { import {
postNode, postNode,
@ -31,6 +32,7 @@ import {
postNodeLike, postNodeLike,
postNodeStar, postNodeStar,
getNodeRelated, getNodeRelated,
postNodeLock,
} from './api'; } from './api';
import { reqWrapper } from '../auth/sagas'; import { reqWrapper } from '../auth/sagas';
import { flowSetNodes, flowSetUpdated } from '../flow/actions'; import { flowSetNodes, flowSetUpdated } from '../flow/actions';
@ -234,6 +236,23 @@ function* onStarSaga({ id }: ReturnType<typeof nodeLike>) {
yield call(updateNodeEverywhere, { ...current, is_heroic }); yield call(updateNodeEverywhere, { ...current, is_heroic });
} }
function* onLockSaga({ id, is_locked }: ReturnType<typeof nodeLock>) {
const {
current,
current: { deleted_at },
} = yield select(selectNode);
yield call(updateNodeEverywhere, {
...current,
deleted_at: is_locked ? new Date().toISOString() : null,
});
const { data, error } = yield call(reqWrapper, postNodeLock, { id, is_locked });
if (error || !data.deleted_at)
return yield call(updateNodeEverywhere, { ...current, deleted_at }); // ok and matches
}
export default function* nodeSaga() { export default function* nodeSaga() {
yield takeLatest(NODE_ACTIONS.SAVE, onNodeSave); yield takeLatest(NODE_ACTIONS.SAVE, onNodeSave);
yield takeLatest(NODE_ACTIONS.GOTO_NODE, onNodeGoto); yield takeLatest(NODE_ACTIONS.GOTO_NODE, onNodeGoto);
@ -244,4 +263,5 @@ export default function* nodeSaga() {
yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga); yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga);
yield takeLatest(NODE_ACTIONS.LIKE, onLikeSaga); yield takeLatest(NODE_ACTIONS.LIKE, onLikeSaga);
yield takeLatest(NODE_ACTIONS.STAR, onStarSaga); yield takeLatest(NODE_ACTIONS.STAR, onStarSaga);
yield takeLatest(NODE_ACTIONS.LOCK, onLockSaga);
} }

View file

@ -132,6 +132,7 @@ export interface INode {
created_at?: string; created_at?: string;
updated_at?: string; updated_at?: string;
deleted_at?: string;
commented_at?: string; commented_at?: string;
} }

View file

@ -192,6 +192,16 @@ const Sprites: FC<{}> = () => (
<path d="M3 4V1h2v3h3v2H5v3H3V6H0V4h3zm3 6V7h3V4h7l1.83 2H21c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V10h3zm7 9c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-3.2-5c0 1.77 1.43 3.2 3.2 3.2s3.2-1.43 3.2-3.2-1.43-3.2-3.2-3.2-3.2 1.43-3.2 3.2z" /> <path d="M3 4V1h2v3h3v2H5v3H3V6H0V4h3zm3 6V7h3V4h7l1.83 2H21c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V10h3zm7 9c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-3.2-5c0 1.77 1.43 3.2 3.2 3.2s3.2-1.43 3.2-3.2-1.43-3.2-3.2-3.2-3.2 1.43-3.2 3.2z" />
</g> </g>
<g id="locked" stroke="none">
<path fill="none" d="M0 0h24v24H0V0z" />
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" />
</g>
<g id="unlocked" stroke="none">
<path fill="none" d="M0 0h24v24H0V0z" />
<path d="M12 17c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm6-9h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6h1.9c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm0 12H6V10h12v10z" />
</g>
<g id="youtube" stroke="none"> <g id="youtube" stroke="none">
<path fill="none" d="M0 0h24v24H0V0z" /> <path fill="none" d="M0 0h24v24H0V0z" />
<g transform="scale(0.1) translate(-30 -30)"> <g transform="scale(0.1) translate(-30 -30)">