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

fixed some paths according to new backend

This commit is contained in:
Fedor Katurov 2022-08-24 17:25:06 +07:00
parent 2f6207feaa
commit 4912fd255c
27 changed files with 228 additions and 217 deletions

View file

@ -1,6 +1,6 @@
NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/ # NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/ # NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
# NEXT_PUBLIC_API_HOST=http://localhost:8888/ NEXT_PUBLIC_API_HOST=http://localhost:8888/
# NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/ NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
# NEXT_PUBLIC_API_HOST=https://pig.vault48.org/ # NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
# NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/ # NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/

View file

@ -23,6 +23,18 @@ module.exports = {
pathGroupsExcludedImportTypes: ['react'], pathGroupsExcludedImportTypes: ['react'],
}, },
], ],
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'ramda',
message:
'import from \'~/utils/ramda\' instead',
},
],
},
]
}, },
parserOptions: { parserOptions: {
ecmaVersion: 7, ecmaVersion: 7,

View file

@ -5,10 +5,16 @@ import { api, cleanResult } from '~/utils/api';
export const postCellView = ({ id, flow }: PostCellViewRequest) => export const postCellView = ({ id, flow }: PostCellViewRequest) =>
api api
.post<PostCellViewResult>(API.NODE.SET_CELL_VIEW(id), { flow }) .post<PostCellViewResult>(API.NODES.SET_CELL_VIEW(id), { flow })
.then(cleanResult); .then(cleanResult);
export const getSearchResults = ({ text, skip, take }: GetSearchResultsRequest) => export const getSearchResults = ({
text,
skip,
take,
}: GetSearchResultsRequest) =>
api api
.get<GetSearchResultsResult>(API.SEARCH.NODES, { params: { text, skip, take } }) .get<GetSearchResultsResult>(API.SEARCH.NODES, {
params: { text, skip, take },
})
.then(cleanResult); .then(cleanResult);

View file

@ -44,7 +44,7 @@ export type ApiGetNodeCommentsResponse = {
}; };
export const apiPostNode = ({ node }: ApiPostNodeRequest) => export const apiPostNode = ({ node }: ApiPostNodeRequest) =>
api.post<ApiPostNodeResult>(API.NODE.SAVE, node).then(cleanResult); api.post<ApiPostNodeResult>(API.NODES.SAVE, node).then(cleanResult);
export const getNodeDiff = ({ export const getNodeDiff = ({
start, start,
@ -56,7 +56,7 @@ export const getNodeDiff = ({
with_valid, with_valid,
}: GetNodeDiffRequest) => }: GetNodeDiffRequest) =>
api api
.get<GetNodeDiffResult>(API.NODE.GET_DIFF, { .get<GetNodeDiffResult>(API.NODES.LIST, {
params: { params: {
start, start,
end, end,
@ -74,7 +74,7 @@ export const apiGetNode = (
config?: AxiosRequestConfig, config?: AxiosRequestConfig,
) => ) =>
api api
.get<ApiGetNodeResponse>(API.NODE.GET_NODE(id), config) .get<ApiGetNodeResponse>(API.NODES.GET(id), config)
.then(cleanResult) .then(cleanResult)
.then((data) => ({ node: data.node, last_seen: data.last_seen })); .then((data) => ({ node: data.node, last_seen: data.last_seen }));
@ -82,7 +82,7 @@ export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => {
const cancelToken = axios.CancelToken.source(); const cancelToken = axios.CancelToken.source();
return { return {
request: api request: api
.get<ApiGetNodeResponse>(API.NODE.GET_NODE(id), { .get<ApiGetNodeResponse>(API.NODES.GET(id), {
cancelToken: cancelToken.token, cancelToken: cancelToken.token,
}) })
.then(cleanResult), .then(cleanResult),
@ -91,7 +91,7 @@ export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => {
}; };
export const apiPostComment = ({ id, data }: ApiPostCommentRequest) => export const apiPostComment = ({ id, data }: ApiPostCommentRequest) =>
api.post<ApiPostCommentResult>(API.NODE.COMMENT(id), data).then(cleanResult); api.post<ApiPostCommentResult>(API.NODES.COMMENT(id), data).then(cleanResult);
export const apiGetNodeComments = ({ export const apiGetNodeComments = ({
id, id,
@ -99,35 +99,33 @@ export const apiGetNodeComments = ({
skip = 0, skip = 0,
}: ApiGetNodeCommentsRequest) => }: ApiGetNodeCommentsRequest) =>
api api
.get<ApiGetNodeCommentsResponse>(API.NODE.COMMENT(id), { .get<ApiGetNodeCommentsResponse>(API.NODES.COMMENT(id), {
params: { take, skip }, params: { take, skip },
}) })
.then(cleanResult); .then(cleanResult);
export const apiGetNodeRelated = ({ id }: ApiGetNodeRelatedRequest) => export const apiGetNodeRelated = ({ id }: ApiGetNodeRelatedRequest) =>
api.get<ApiGetNodeRelatedResult>(API.NODE.RELATED(id)).then(cleanResult); api.get<ApiGetNodeRelatedResult>(API.NODES.RELATED(id)).then(cleanResult);
export const apiPostNodeTags = ({ id, tags }: ApiPostNodeTagsRequest) => export const apiPostNodeTags = ({ id, tags }: ApiPostNodeTagsRequest) =>
api api
.post<ApiPostNodeTagsResult>(API.NODE.UPDATE_TAGS(id), { tags }) .post<ApiPostNodeTagsResult>(API.NODES.UPDATE_TAGS(id), { tags })
.then(cleanResult); .then(cleanResult);
export const apiDeleteNodeTag = ({ id, tagId }: ApiDeleteNodeTagsRequest) => export const apiDeleteNodeTag = ({ id, tagId }: ApiDeleteNodeTagsRequest) =>
api api
.delete<ApiDeleteNodeTagsResult>(API.NODE.DELETE_TAG(id, tagId)) .delete<ApiDeleteNodeTagsResult>(API.NODES.DELETE_TAG(id, tagId))
.then(cleanResult); .then(cleanResult);
export const apiPostNodeLike = ({ id }: ApiPostNodeLikeRequest) => export const apiPostNodeLike = ({ id }: ApiPostNodeLikeRequest) =>
api.post<ApiPostNodeLikeResult>(API.NODE.POST_LIKE(id)).then(cleanResult); api.post<ApiPostNodeLikeResult>(API.NODES.LIKE(id)).then(cleanResult);
export const apiPostNodeHeroic = ({ id }: ApiPostNodeHeroicRequest) => export const apiPostNodeHeroic = ({ id }: ApiPostNodeHeroicRequest) =>
api api.post<ApiPostNodeHeroicResponse>(API.NODES.HEROIC(id)).then(cleanResult);
.post<ApiPostNodeHeroicResponse>(API.NODE.POST_HEROIC(id))
.then(cleanResult);
export const apiLockNode = ({ id, is_locked }: ApiLockNodeRequest) => export const apiLockNode = ({ id, is_locked }: ApiLockNodeRequest) =>
api api
.post<ApiLockNodeResult>(API.NODE.POST_LOCK(id), { is_locked }) .delete<ApiLockNodeResult>(API.NODES.DELETE(id), { params: { is_locked } })
.then(cleanResult); .then(cleanResult);
export const apiLockComment = ({ export const apiLockComment = ({
@ -136,7 +134,7 @@ export const apiLockComment = ({
nodeId, nodeId,
}: ApiLockCommentRequest) => }: ApiLockCommentRequest) =>
api api
.delete<ApiLockcommentResult>(API.NODE.LOCK_COMMENT(nodeId, id), { .delete<ApiLockcommentResult>(API.NODES.LOCK_COMMENT(nodeId, id), {
params: { params: {
is_locked: isLocked, is_locked: isLocked,
}, },

View file

@ -1,7 +1,7 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { Avatar } from '~/components/common/Avatar'; import { Avatar } from '~/components/common/Avatar';
import { MenuButton } from '~/components/menu'; import { MenuButton } from '~/components/menu/MenuButton';
import { ProfileQuickInfo } from '~/containers/profile/ProfileQuickInfo'; import { ProfileQuickInfo } from '~/containers/profile/ProfileQuickInfo';
import { IUser } from '~/types/auth'; import { IUser } from '~/types/auth';
import { path } from '~/utils/ramda'; import { path } from '~/utils/ramda';
@ -16,7 +16,11 @@ const CommentAvatar: FC<Props> = ({ user, className }) => {
<MenuButton <MenuButton
position="auto" position="auto"
icon={ icon={
<Avatar url={path(['photo', 'url'], user)} username={user.username} className={className} /> <Avatar
url={path(['photo', 'url'], user)}
username={user.username}
className={className}
/>
} }
> >
<ProfileQuickInfo user={user} /> <ProfileQuickInfo user={user} />

View file

@ -1,6 +1,7 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import { SortableAudioGrid, SortableImageGrid } from '~/components/sortable'; import { SortableAudioGrid } from '~/components/sortable/SortableAudioGrid';
import { SortableImageGrid } from '~/components/sortable/SortableImageGrid';
import { COMMENT_FILE_TYPES } from '~/constants/uploads'; import { COMMENT_FILE_TYPES } from '~/constants/uploads';
import { useFileDropZone } from '~/hooks'; import { useFileDropZone } from '~/hooks';
import { IFile } from '~/types'; import { IFile } from '~/types';
@ -27,34 +28,36 @@ const CommentFormAttaches: FC = () => {
const onImageMove = useCallback( const onImageMove = useCallback(
(newFiles: IFile[]) => { (newFiles: IFile[]) => {
setFiles([...filesAudios, ...newFiles.filter(it => it)]); setFiles([...filesAudios, ...newFiles.filter((it) => it)]);
}, },
[setFiles, filesImages, filesAudios] [setFiles, filesImages, filesAudios],
); );
const onAudioMove = useCallback( const onAudioMove = useCallback(
(newFiles: IFile[]) => { (newFiles: IFile[]) => {
setFiles([...filesImages, ...newFiles]); setFiles([...filesImages, ...newFiles]);
}, },
[setFiles, filesImages, filesAudios] [setFiles, filesImages, filesAudios],
); );
const onFileDelete = useCallback( const onFileDelete = useCallback(
(fileId: IFile['id']) => { (fileId: IFile['id']) => {
setFiles(files.filter(file => file.id !== fileId)); setFiles(files.filter((file) => file.id !== fileId));
}, },
[files, setFiles] [files, setFiles],
); );
const onAudioTitleChange = useCallback( const onAudioTitleChange = useCallback(
(fileId: IFile['id'], title: string) => { (fileId: IFile['id'], title: string) => {
setFiles( setFiles(
files.map(file => files.map((file) =>
file.id === fileId ? { ...file, metadata: { ...file.metadata, title } } : file file.id === fileId
) ? { ...file, metadata: { ...file.metadata, title } }
: file,
),
); );
}, },
[files, setFiles] [files, setFiles],
); );
if (!hasAttaches) return null; if (!hasAttaches) return null;

View file

@ -1,6 +1,6 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import { SortableAudioGrid } from '~/components/sortable'; import { SortableAudioGrid } from '~/components/sortable/SortableAudioGrid';
import { UploadStatus } from '~/store/uploader/UploaderStore'; import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types'; import { IFile } from '~/types';
@ -15,25 +15,27 @@ const AudioGrid: FC<IProps> = ({ files, setFiles, locked }) => {
(newFiles: IFile[]) => { (newFiles: IFile[]) => {
setFiles(newFiles); setFiles(newFiles);
}, },
[setFiles, files] [setFiles, files],
); );
const onDrop = useCallback( const onDrop = useCallback(
(remove_id: IFile['id']) => { (remove_id: IFile['id']) => {
setFiles(files.filter(file => file && file.id !== remove_id)); setFiles(files.filter((file) => file && file.id !== remove_id));
}, },
[setFiles, files] [setFiles, files],
); );
const onTitleChange = useCallback( const onTitleChange = useCallback(
(changeId: IFile['id'], title: string) => { (changeId: IFile['id'], title: string) => {
setFiles( setFiles(
files.map(file => files.map((file) =>
file && file.id === changeId ? { ...file, metadata: { ...file.metadata, title } } : file file && file.id === changeId
) ? { ...file, metadata: { ...file.metadata, title } }
: file,
),
); );
}, },
[setFiles, files] [setFiles, files],
); );
return ( return (

View file

@ -1,6 +1,6 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import { SortableImageGrid } from '~/components/sortable'; import { SortableImageGrid } from '~/components/sortable/SortableImageGrid';
import { useWindowSize } from '~/hooks/dom/useWindowSize'; import { useWindowSize } from '~/hooks/dom/useWindowSize';
import { UploadStatus } from '~/store/uploader/UploaderStore'; import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types'; import { IFile } from '~/types';
@ -16,14 +16,14 @@ const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
const onMove = useCallback( const onMove = useCallback(
(newFiles: IFile[]) => { (newFiles: IFile[]) => {
setFiles(newFiles.filter(it => it)); setFiles(newFiles.filter((it) => it));
}, },
[setFiles, files], [setFiles, files],
); );
const onDrop = useCallback( const onDrop = useCallback(
(id: IFile['id']) => { (id: IFile['id']) => {
setFiles(files.filter(file => file && file.id !== id)); setFiles(files.filter((file) => file && file.id !== id));
}, },
[setFiles, files], [setFiles, files],
); );

View file

@ -1,5 +0,0 @@
export * from './VerticalMenu';
export * from './HorizontalMenu';
export * from './MenuButton';
export * from './MenuItemWithIcon';
export * from './SeparatedMenu';

View file

@ -1,38 +0,0 @@
import React, { FC } from 'react';
import { Filler } from '~/components/containers/Filler';
import { Group } from '~/components/containers/Group';
import styles from './styles.module.scss';
interface IProps {
title: string;
description?: string;
icon: string;
}
const MenuButton: FC<IProps> = ({
title,
icon,
description,
}) => (
<div
className={styles.button}
>
<Group horizontal>
<div className={styles.icon}>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
<path fill="none" d="M0 0h24v24H0V0z" />
<path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" />
</svg>
</div>
<Filler>
<div className={styles.title}>{title}</div>
{ description && <div className={styles.description}>{description}</div> }
</Filler>
</Group>
</div>
);
export { MenuButton };

View file

@ -1,29 +0,0 @@
@import 'src/styles/variables';
.button {
fill: white;
height: 48px;
display: flex;
align-items: center;
}
.icon {
flex: 0 0 38px;
align-items: center;
justify-content: center;
display: flex;
}
.title {
font: $font_16_semibold;
text-transform: uppercase;
margin-bottom: 2px;
white-space: nowrap;
flex: 1;
}
.description {
font: $font_12_regular;
color: $gray_75;
white-space: nowrap;
}

View file

@ -3,7 +3,9 @@ import React, { VFC } from 'react';
import Tippy from '@tippyjs/react'; import Tippy from '@tippyjs/react';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { MenuButton, MenuItemWithIcon, SeparatedMenu } from '~/components/menu'; import { MenuButton } from '~/components/menu/MenuButton';
import { MenuItemWithIcon } from '~/components/menu/MenuItemWithIcon';
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
import { useWindowSize } from '~/hooks/dom/useWindowSize'; import { useWindowSize } from '~/hooks/dom/useWindowSize';
import styles from './styles.module.scss'; import styles from './styles.module.scss';

View file

@ -3,7 +3,7 @@ import React, { memo, VFC } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { SeparatedMenu } from '~/components/menu'; import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
import { NodeEditMenu } from '~/components/node/NodeEditMenu'; import { NodeEditMenu } from '~/components/node/NodeEditMenu';
import { Placeholder } from '~/components/placeholders/Placeholder'; import { Placeholder } from '~/components/placeholders/Placeholder';
import { getPrettyDate } from '~/utils/dom'; import { getPrettyDate } from '~/utils/dom';
@ -35,7 +35,6 @@ interface IProps {
const NodeTitle: VFC<IProps> = memo( const NodeTitle: VFC<IProps> = memo(
({ ({
id,
title, title,
username, username,
createdAt, createdAt,
@ -69,7 +68,9 @@ const NodeTitle: VFC<IProps> = memo(
{isLoading ? ( {isLoading ? (
<Placeholder width="100px" /> <Placeholder width="100px" />
) : ( ) : (
`~${username.toLocaleLowerCase()}, ${getPrettyDate(createdAt)}` `~${username.toLocaleLowerCase()}, ${getPrettyDate(
createdAt,
)}`
)} )}
</aside> </aside>
)} )}
@ -90,7 +91,9 @@ const NodeTitle: VFC<IProps> = memo(
{canLike && ( {canLike && (
<div <div
className={classNames(styles.button, styles.like, { [styles.is_liked]: isLiked })} className={classNames(styles.button, styles.like, {
[styles.is_liked]: isLiked,
})}
> >
{isLiked ? ( {isLiked ? (
<Icon icon="heart_full" size={24} onClick={onLike} /> <Icon icon="heart_full" size={24} onClick={onLike} />
@ -107,7 +110,7 @@ const NodeTitle: VFC<IProps> = memo(
</div> </div>
</div> </div>
); );
} },
); );
export { NodeTitle }; export { NodeTitle };

View file

@ -5,7 +5,7 @@ import { rectSortingStrategy, SortableContext } from '@dnd-kit/sortable';
import classNames from 'classnames'; import classNames from 'classnames';
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem'; import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
import { useSortableActions } from '~/hooks/sortable'; import { useSortableActions } from '~/hooks/sortable/useSortableActions';
import { DivProps } from '~/utils/types'; import { DivProps } from '~/utils/types';
import { SortableItem } from '../SortableItem'; import { SortableItem } from '../SortableItem';
@ -16,7 +16,7 @@ interface SortableGridProps<
ItemRendererProps extends {}, ItemRendererProps extends {},
LockedRendererProps extends {}, LockedRendererProps extends {},
Item extends {}, Item extends {},
Locked extends {} Locked extends {},
> { > {
items: Item[]; items: Item[];
locked: Locked[]; locked: Locked[];
@ -44,18 +44,18 @@ const SortableGrid = <RIP, RLP, I, L>({
onSortEnd, onSortEnd,
size, size,
}: SortableGridProps<RIP, RLP, I, L>) => { }: SortableGridProps<RIP, RLP, I, L>) => {
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions( const { sensors, onDragEnd, onDragStart, draggingItem, ids } =
items, useSortableActions(items, getID, onSortEnd);
getID,
onSortEnd
);
const gridStyle = useMemo<DivProps['style']>( const gridStyle = useMemo<DivProps['style']>(
() => () =>
size size
? { gridTemplateColumns: size && `repeat(auto-fill, minmax(${size}px, 1fr))` } ? {
gridTemplateColumns:
size && `repeat(auto-fill, minmax(${size}px, 1fr))`,
}
: undefined, : undefined,
[size] [size],
); );
return ( return (
@ -67,30 +67,35 @@ const SortableGrid = <RIP, RLP, I, L>({
> >
<SortableContext items={ids} strategy={rectSortingStrategy}> <SortableContext items={ids} strategy={rectSortingStrategy}>
<div className={classNames(styles.grid, className)} style={gridStyle}> <div className={classNames(styles.grid, className)} style={gridStyle}>
{items.map(item => ( {items.map((item) => (
<SortableItem <SortableItem
key={getID(item)} key={getID(item)}
id={getID(item)} id={getID(item)}
className={ className={
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined draggingItem && getID(item) === getID(draggingItem)
? styles.dragging
: undefined
} }
> >
{createElement(renderItem, { ...renderItemProps, item })} {createElement(renderItem, { ...renderItemProps, item })}
</SortableItem> </SortableItem>
))} ))}
{locked.map(item => {locked.map((item) =>
createElement(renderLocked, { createElement(renderLocked, {
...renderLockedProps, ...renderLockedProps,
locked: item, locked: item,
key: getLockedID(item), key: getLockedID(item),
}) }),
)} )}
<DragOverlay> <DragOverlay>
{draggingItem ? ( {draggingItem ? (
<DragOverlayItem> <DragOverlayItem>
{createElement(renderItem, { ...renderItemProps, item: draggingItem })} {createElement(renderItem, {
...renderItemProps,
item: draggingItem,
})}
</DragOverlayItem> </DragOverlayItem>
) : null} ) : null}
</DragOverlay> </DragOverlay>

View file

@ -1,12 +1,15 @@
import React, { createElement, FC } from 'react'; import React, { createElement, FC } from 'react';
import { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core'; import { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import {
SortableContext,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import classNames from 'classnames'; import classNames from 'classnames';
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem'; import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
import { SortableItem } from '~/components/sortable/SortableItem'; import { SortableItem } from '~/components/sortable/SortableItem';
import { useSortableActions } from '~/hooks/sortable'; import { useSortableActions } from '~/hooks/sortable/useSortableActions';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
@ -14,7 +17,7 @@ interface SortableListProps<
RenderItemProps extends {}, RenderItemProps extends {},
RenderLockedProps extends {}, RenderLockedProps extends {},
Item extends {}, Item extends {},
Locked extends {} Locked extends {},
> { > {
items: Item[]; items: Item[];
locked: Locked[]; locked: Locked[];
@ -40,11 +43,8 @@ const SortableList = <RIP, RLP, I, L>({
renderLockedProps, renderLockedProps,
onSortEnd, onSortEnd,
}: SortableListProps<RIP, RLP, I, L>) => { }: SortableListProps<RIP, RLP, I, L>) => {
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions( const { sensors, onDragEnd, onDragStart, draggingItem, ids } =
items, useSortableActions(items, getID, onSortEnd);
getID,
onSortEnd
);
return ( return (
<DndContext <DndContext
@ -55,30 +55,39 @@ const SortableList = <RIP, RLP, I, L>({
> >
<SortableContext items={ids} strategy={verticalListSortingStrategy}> <SortableContext items={ids} strategy={verticalListSortingStrategy}>
<div className={classNames(styles.grid, className)}> <div className={classNames(styles.grid, className)}>
{items.map(item => ( {items.map((item) => (
<SortableItem <SortableItem
key={getID(item)} key={getID(item)}
id={getID(item)} id={getID(item)}
className={ className={
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined draggingItem && getID(item) === getID(draggingItem)
? styles.dragging
: undefined
} }
> >
{createElement(renderItem, { ...renderItemProps, item, key: getID(item) })} {createElement(renderItem, {
...renderItemProps,
item,
key: getID(item),
})}
</SortableItem> </SortableItem>
))} ))}
{locked.map(item => {locked.map((item) =>
createElement(renderLocked, { createElement(renderLocked, {
...renderLockedProps, ...renderLockedProps,
locked: item, locked: item,
key: getLockedID(item), key: getLockedID(item),
}) }),
)} )}
<DragOverlay> <DragOverlay>
{draggingItem ? ( {draggingItem ? (
<DragOverlayItem> <DragOverlayItem>
{createElement(renderItem, { ...renderItemProps, item: draggingItem })} {createElement(renderItem, {
...renderItemProps,
item: draggingItem,
})}
</DragOverlayItem> </DragOverlayItem>
) : null} ) : null}
</DragOverlay> </DragOverlay>

View file

@ -1,2 +0,0 @@
export * from './SortableImageGrid';
export * from './SortableAudioGrid';

View file

@ -23,23 +23,23 @@ export const API = {
ATTACH_SOCIAL: `/oauth/attach`, ATTACH_SOCIAL: `/oauth/attach`,
LOGIN_WITH_SOCIAL: `/oauth/login`, LOGIN_WITH_SOCIAL: `/oauth/login`,
}, },
NODE: { NODES: {
SAVE: '/nodes/', SAVE: '/nodes/',
GET_DIFF: '/nodes/', LIST: '/nodes/',
GET_NODE: (id: number | string) => `/nodes/${id}`, GET: (id: number | string) => `/nodes/${id}`,
DELETE: (id: INode['id']) => `/nodes/${id}`,
LIKE: (id: INode['id']) => `/nodes/${id}/like`,
HEROIC: (id: INode['id']) => `/nodes/${id}/heroic`,
SET_CELL_VIEW: (id: INode['id']) => `/nodes/${id}/cell-view`,
RELATED: (id: INode['id']) => `/nodes/${id}/related`, RELATED: (id: INode['id']) => `/nodes/${id}/related`,
UPDATE_TAGS: (id: INode['id']) => `/nodes/${id}/tags`, UPDATE_TAGS: (id: INode['id']) => `/nodes/${id}/tags`,
DELETE_TAG: (id: INode['id'], tagId: ITag['ID']) => DELETE_TAG: (id: INode['id'], tagId: ITag['ID']) =>
`/nodes/${id}/tags/${tagId}`, `/nodes/${id}/tags/${tagId}`,
POST_LIKE: (id: INode['id']) => `/nodes/${id}/like`,
POST_HEROIC: (id: INode['id']) => `/nodes/${id}/heroic`,
POST_LOCK: (id: INode['id']) => `/nodes/${id}/lock`,
SET_CELL_VIEW: (id: INode['id']) => `/nodes/${id}/cell-view`,
COMMENT: (id: INode['id'] | string) => `/nodes/${id}/comment`, COMMENT: (id: INode['id'] | string) => `/nodes/${id}/comments`,
LOCK_COMMENT: (id: INode['id'], comment_id: IComment['id']) => LOCK_COMMENT: (id: INode['id'], comment_id: IComment['id']) =>
`/nodes/${id}/comment/${comment_id}`, `/nodes/${id}/comments/${comment_id}`,
}, },
SEARCH: { SEARCH: {
NODES: '/search/nodes', NODES: '/search/nodes',

View file

@ -3,8 +3,7 @@ import React, { FC } from 'react';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import { Padder } from '~/components/containers/Padder'; import { Padder } from '~/components/containers/Padder';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
import { Icon } from '~/components/input/Icon'; import { MenuButton } from '~/components/menu/MenuButton';
import { MenuButton } from '~/components/menu';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
@ -12,17 +11,30 @@ interface ProfileSidebarLogoutButtonProps {
onLogout?: () => void; onLogout?: () => void;
} }
const ProfileSidebarLogoutButton: FC<ProfileSidebarLogoutButtonProps> = ({ onLogout }) => ( const ProfileSidebarLogoutButton: FC<ProfileSidebarLogoutButtonProps> = ({
<MenuButton icon={<Button color="link" iconRight="logout">Выйти</Button>} position="top-end"> onLogout,
}) => (
<MenuButton
icon={
<Button color="link" iconRight="logout">
Выйти
</Button>
}
position="top-end"
>
<Padder className={styles.wrapper}> <Padder className={styles.wrapper}>
<Group> <Group>
<h5>Захотелось наружу?</h5> <h5>Захотелось наружу?</h5>
<div>Там холодно, страшно и больше не раздают пончики!</div> <div>Там холодно, страшно и больше не раздают пончики!</div>
<div /> <div />
<div><Button onClick={onLogout} color="primary" stretchy>Выпустите меня!</Button></div> <div>
<Button onClick={onLogout} color="primary" stretchy>
Выпустите меня!
</Button>
</div>
</Group> </Group>
</Padder> </Padder>
</MenuButton> </MenuButton>
); );
export { ProfileSidebarLogoutButton } export { ProfileSidebarLogoutButton };

View file

@ -1,7 +1,5 @@
import React, { useCallback, useEffect, useMemo, VFC } from 'react'; import React, { useCallback, useEffect, useMemo, VFC } from 'react';
import { isNil } from 'ramda';
import { CoverBackdrop } from '~/components/containers/CoverBackdrop'; import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes'; import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes';
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings'; import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
@ -13,6 +11,7 @@ import { ProfileSidebarMenu } from '~/containers/profile/ProfileSidebarMenu';
import { useAuth } from '~/hooks/auth/useAuth'; import { useAuth } from '~/hooks/auth/useAuth';
import { useUser } from '~/hooks/auth/useUser'; import { useUser } from '~/hooks/auth/useUser';
import type { SidebarComponentProps } from '~/types/sidebar'; import type { SidebarComponentProps } from '~/types/sidebar';
import { isNil } from '~/utils/ramda';
const tabs = ['profile', 'bookmarks'] as const; const tabs = ['profile', 'bookmarks'] as const;
type TabName = typeof tabs[number]; type TabName = typeof tabs[number];

View file

@ -8,16 +8,14 @@ import { COMMENTS_DISPLAY } from '~/constants/node';
import { IComment } from '~/types'; import { IComment } from '~/types';
import { flatten, isNil } from '~/utils/ramda'; import { flatten, isNil } from '~/utils/ramda';
const getKey: (nodeId: number) => SWRInfiniteKeyLoader = (nodeId: number) => ( const getKey: (nodeId: number) => SWRInfiniteKeyLoader =
pageIndex, (nodeId: number) => (pageIndex, previousPageData: IComment[]) => {
previousPageData: IComment[] if (pageIndex > 0 && !previousPageData?.length) return null;
) => { return `${API.NODES.COMMENT(nodeId)}?page=${pageIndex}`;
if (pageIndex > 0 && !previousPageData?.length) return null; };
return `${API.NODE.COMMENT(nodeId)}?page=${pageIndex}`;
};
const extractKey = (key: string) => { const extractKey = (key: string) => {
const re = new RegExp(`${API.NODE.COMMENT('\\d+')}\\?page=(\\d+)`); const re = new RegExp(`${API.NODES.COMMENT('\\d+')}\\?page=(\\d+)`);
const match = key.match(re); const match = key.match(re);
if (!match || !Array.isArray(match) || isNil(match[1])) { if (!match || !Array.isArray(match) || isNil(match[1])) {
@ -41,14 +39,26 @@ export const useGetComments = (nodeId: number, fallbackData?: IComment[]) => {
}, },
{ {
fallbackData: fallbackData && [fallbackData], fallbackData: fallbackData && [fallbackData],
} },
); );
const comments = useMemo(() => flatten(data || []), [data]); const comments = useMemo(() => flatten(data || []), [data]);
const hasMore = (data?.[size - 1]?.length || 0) >= COMMENTS_DISPLAY || const hasMore =
(data?.[size - 1]?.length || 0) >= COMMENTS_DISPLAY ||
(!!data?.length && data?.length > 0 && isValidating); (!!data?.length && data?.length > 0 && isValidating);
const onLoadMoreComments = useCallback(() => setSize(size + 1), [setSize, size]); const onLoadMoreComments = useCallback(
() => setSize(size + 1),
[setSize, size],
);
return { comments, hasMore, onLoadMoreComments, isLoading: !data && isValidating, mutate, data, isLoadingMore: !!data?.length && isValidating }; return {
comments,
hasMore,
onLoadMoreComments,
isLoading: !data && isValidating,
mutate,
data,
isLoadingMore: !!data?.length && isValidating,
};
}; };

View file

@ -8,8 +8,9 @@ import { INode } from '~/types';
import { ApiGetNodeRelatedResult } from '~/types/node'; import { ApiGetNodeRelatedResult } from '~/types/node';
export const useGetNodeRelated = (id?: INode['id']) => { export const useGetNodeRelated = (id?: INode['id']) => {
const { data, isValidating, mutate } = useSWR<ApiGetNodeRelatedResult>(API.NODE.RELATED(id), () => const { data, isValidating, mutate } = useSWR<ApiGetNodeRelatedResult>(
apiGetNodeRelated({ id }) API.NODES.RELATED(id),
() => apiGetNodeRelated({ id }),
); );
const refresh = useCallback(() => mutate(data, true), [data, mutate]); const refresh = useCallback(() => mutate(data, true), [data, mutate]);

View file

@ -12,7 +12,7 @@ import { ApiGetNodeResponse } from '~/types/node';
const getKey = (nodeId: number, userId = 0) => const getKey = (nodeId: number, userId = 0) =>
JSON.stringify({ JSON.stringify({
url: API.NODE.GET_NODE(nodeId), url: API.NODES.GET(nodeId),
userId, userId,
}); });
@ -21,7 +21,7 @@ export const useLoadNode = (id: number, fallbackData?: ApiGetNodeResponse) => {
const { data, isValidating, mutate } = useSWR<ApiGetNodeResponse>( const { data, isValidating, mutate } = useSWR<ApiGetNodeResponse>(
getKey(id, user.id), getKey(id, user.id),
() => apiGetNode({ id }), () => apiGetNode({ id }),
{ fallbackData, revalidateOnMount: true } { fallbackData, revalidateOnMount: true },
); );
const update = useCallback( const update = useCallback(
@ -33,7 +33,7 @@ export const useLoadNode = (id: number, fallbackData?: ApiGetNodeResponse) => {
await mutate({ node: { ...data.node, ...node } }, true); await mutate({ node: { ...data.node, ...node } }, true);
}, },
[data, mutate] [data, mutate],
); );
useOnNodeSeen(data?.node); useOnNodeSeen(data?.node);

View file

@ -1 +0,0 @@
export * from './useSortableActions';

View file

@ -23,6 +23,13 @@ import { NodeRelatedProvider } from '~/utils/providers/NodeRelatedProvider';
import { uniqBy } from '~/utils/ramda'; import { uniqBy } from '~/utils/ramda';
export const getStaticPaths = async () => { export const getStaticPaths = async () => {
if (process.env.NODE_ENV === 'development') {
return {
paths: [],
fallback: 'blocking',
};
}
const recent = await getNodeDiff({ const recent = await getNodeDiff({
with_heroes: false, with_heroes: false,
with_recent: true, with_recent: true,
@ -30,40 +37,48 @@ export const getStaticPaths = async () => {
with_valid: false, with_valid: false,
}); });
const recentIDs = uniqBy(it => it.id, [ const recentIDs = uniqBy(
...(recent.after || []), (it) => it.id,
...(recent.before || []), [
...(recent.recent || []), ...(recent.after || []),
]) ...(recent.before || []),
.filter(it => it.id) ...(recent.recent || []),
.map(it => it.id!.toString()); ],
)
.filter((it) => it.id)
.map((it) => it.id!.toString());
return { return {
paths: recentIDs.map(id => ({ params: { id } })), paths: recentIDs.map((id) => ({ params: { id } })),
fallback: 'blocking', fallback: 'blocking',
}; };
}; };
export const getStaticProps = async ( export const getStaticProps = async (
context context,
): Promise<GetStaticPropsResult<{ fallbackData: ApiGetNodeResponse; comments?: IComment[] }>> => { ): Promise<
GetStaticPropsResult<{
fallbackData: ApiGetNodeResponse;
comments?: IComment[];
}>
> => {
try { try {
if (!context.params?.id) { if (!context.params?.id) {
return { notFound: true }; return { notFound: true };
} }
const id = parseInt(context.params.id, 10); const id = parseInt(context.params.id, 10);
if (!id) { if (!id) {
return { notFound: true }; return { notFound: true };
} }
const fallbackData = await apiGetNode({ id }); const [fallbackData, { comments }] = await Promise.all([
apiGetNode({ id }),
const comments = await apiGetNodeComments({ apiGetNodeComments({
id, id,
take: COMMENTS_DISPLAY, take: COMMENTS_DISPLAY,
}); }),
]);
return { return {
props: { props: {
@ -71,7 +86,7 @@ export const getStaticProps = async (
...fallbackData, ...fallbackData,
last_seen: fallbackData.last_seen ?? null, last_seen: fallbackData.last_seen ?? null,
}, },
comments: comments.comments, comments,
}, },
revalidate: 7 * 86400, // every week revalidate: 7 * 86400, // every week
}; };
@ -83,11 +98,15 @@ export const getStaticProps = async (
} }
}; };
type Props = RouteComponentProps<{ id: string }> & InferGetStaticPropsType<typeof getStaticProps>; type Props = RouteComponentProps<{ id: string }> &
InferGetStaticPropsType<typeof getStaticProps>;
const NodePage: FC<Props> = observer(props => { const NodePage: FC<Props> = observer((props) => {
const id = useNodePageParams(); const id = useNodePageParams();
const { node, isLoading, update, lastSeen } = useLoadNode(parseInt(id, 10), props.fallbackData); const { node, isLoading, update, lastSeen } = useLoadNode(
parseInt(id, 10),
props.fallbackData,
);
const onShowImageModal = useImageModal(); const onShowImageModal = useImageModal();
@ -101,9 +120,11 @@ const NodePage: FC<Props> = observer(props => {
isLoadingMore: isLoadingMoreComments, isLoadingMore: isLoadingMoreComments,
} = useNodeComments(parseInt(id, 10), props.comments); } = useNodeComments(parseInt(id, 10), props.comments);
const { onDelete: onTagDelete, onChange: onTagsChange, onClick: onTagClick } = useNodeTags( const {
parseInt(id, 10) onDelete: onTagDelete,
); onChange: onTagsChange,
onClick: onTagClick,
} = useNodeTags(parseInt(id, 10));
const [canEdit] = useNodePermissions(node); const [canEdit] = useNodePermissions(node);
if (!node) { if (!node) {

View file

@ -9,12 +9,12 @@ import {
} from 'react'; } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { has, omit } from 'ramda';
import { ModalWrapper } from '~/components/dialogs/ModalWrapper'; import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
import { SidebarName } from '~/constants/sidebar'; import { SidebarName } from '~/constants/sidebar';
import { sidebarComponents } from '~/constants/sidebar/components'; import { sidebarComponents } from '~/constants/sidebar/components';
import { SidebarComponent, SidebarProps } from '~/types/sidebar'; import { SidebarComponent, SidebarProps } from '~/types/sidebar';
import { has, omit } from '~/utils/ramda';
type ContextValue = typeof SidebarContext extends Context<infer U> ? U : never; type ContextValue = typeof SidebarContext extends Context<infer U> ? U : never;

View file

@ -7,9 +7,8 @@ import React, {
useState, useState,
} from 'react'; } from 'react';
import { keys } from 'ramda';
import { Theme } from '~/constants/themes'; import { Theme } from '~/constants/themes';
import { keys } from '~/utils/ramda';
interface ProvidersProps {} interface ProvidersProps {}

File diff suppressed because one or more lines are too long