mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 21:06:42 +07:00
removed tag reducer
This commit is contained in:
parent
11b39b8766
commit
31e433af3e
17 changed files with 41 additions and 192 deletions
|
@ -5,7 +5,7 @@ import {
|
||||||
ApiGetNodesOfTagResult,
|
ApiGetNodesOfTagResult,
|
||||||
ApiGetTagSuggestionsRequest,
|
ApiGetTagSuggestionsRequest,
|
||||||
ApiGetTagSuggestionsResult,
|
ApiGetTagSuggestionsResult,
|
||||||
} from '~/redux/tag/types';
|
} from '~/types/tags';
|
||||||
|
|
||||||
export const apiGetNodesOfTag = ({ tag, offset, limit }: ApiGetNodesOfTagRequest) =>
|
export const apiGetNodesOfTag = ({ tag, offset, limit }: ApiGetNodesOfTagRequest) =>
|
||||||
api
|
api
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC, memo } from 'react';
|
import React, { FC, memo } from 'react';
|
||||||
import { ITag } from '~/redux/types';
|
import { ITag } from '~/redux/types';
|
||||||
import { Tags } from '~/components/tags/Tags';
|
import { Tags } from '~/containers/tags/Tags';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
is_deletable?: boolean;
|
is_deletable?: boolean;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC, memo } from 'react';
|
import React, { FC, memo } from 'react';
|
||||||
import { ITag } from '~/redux/types';
|
import { ITag } from '~/redux/types';
|
||||||
import { Tags } from '~/components/tags/Tags';
|
import { Tags } from '~/containers/tags/Tags';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
is_editable?: boolean;
|
is_editable?: boolean;
|
||||||
|
|
|
@ -1,34 +1,23 @@
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState, VFC } from 'react';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import * as TAG_ACTIONS from '~/redux/tag/actions';
|
|
||||||
import { selectTagAutocomplete } from '~/redux/tag/selectors';
|
|
||||||
import { separateTagOptions } from '~/utils/tag';
|
import { separateTagOptions } from '~/utils/tag';
|
||||||
import { TagAutocompleteRow } from '~/components/tags/TagAutocompleteRow';
|
import { TagAutocompleteRow } from '~/components/tags/TagAutocompleteRow';
|
||||||
import { usePopper } from 'react-popper';
|
import { usePopper } from 'react-popper';
|
||||||
|
|
||||||
const mapStateToProps = selectTagAutocomplete;
|
interface TagAutocompleteProps {
|
||||||
const mapDispatchToProps = {
|
|
||||||
tagSetAutocomplete: TAG_ACTIONS.tagSetAutocomplete,
|
|
||||||
tagLoadAutocomplete: TAG_ACTIONS.tagLoadAutocomplete,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = ReturnType<typeof mapStateToProps> &
|
|
||||||
typeof mapDispatchToProps & {
|
|
||||||
exclude: string[];
|
exclude: string[];
|
||||||
input: HTMLInputElement;
|
input: HTMLInputElement;
|
||||||
onSelect: (val: string) => void;
|
onSelect: (val: string) => void;
|
||||||
search: string;
|
search: string;
|
||||||
};
|
options: string[];
|
||||||
|
}
|
||||||
|
|
||||||
const TagAutocompleteUnconnected: FC<Props> = ({
|
const TagAutocomplete: VFC<TagAutocompleteProps> = ({
|
||||||
exclude,
|
exclude,
|
||||||
input,
|
input,
|
||||||
onSelect,
|
onSelect,
|
||||||
search,
|
search,
|
||||||
tagSetAutocomplete,
|
|
||||||
tagLoadAutocomplete,
|
|
||||||
options,
|
options,
|
||||||
}) => {
|
}) => {
|
||||||
const [selected, setSelected] = useState(-1);
|
const [selected, setSelected] = useState(-1);
|
||||||
|
@ -80,19 +69,6 @@ const TagAutocompleteUnconnected: FC<Props> = ({
|
||||||
return () => input.removeEventListener('keydown', onKeyDown);
|
return () => input.removeEventListener('keydown', onKeyDown);
|
||||||
}, [input, onKeyDown]);
|
}, [input, onKeyDown]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelected(-1);
|
|
||||||
tagLoadAutocomplete(search, exclude);
|
|
||||||
}, [exclude, search, tagLoadAutocomplete]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
tagSetAutocomplete({ options: [] });
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
tagSetAutocomplete({ options: [] });
|
|
||||||
};
|
|
||||||
}, [tagSetAutocomplete]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!scroll.current || !scroll.current?.children[selected + 1]) return;
|
if (!scroll.current || !scroll.current?.children[selected + 1]) return;
|
||||||
const el = scroll.current?.children[selected + 1] as HTMLDivElement;
|
const el = scroll.current?.children[selected + 1] as HTMLDivElement;
|
||||||
|
@ -143,6 +119,4 @@ const TagAutocompleteUnconnected: FC<Props> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TagAutocomplete = connect(mapStateToProps, mapDispatchToProps)(TagAutocompleteUnconnected);
|
|
||||||
|
|
||||||
export { TagAutocomplete };
|
export { TagAutocomplete };
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { TagAutocomplete } from '~/components/tags/TagAutocomplete';
|
import { TagAutocomplete } from '~/components/tags/TagAutocomplete';
|
||||||
import { TagWrapper } from '~/components/tags/TagWrapper';
|
import { TagWrapper } from '~/components/tags/TagWrapper';
|
||||||
|
import { useTagAutocomplete } from '~/hooks/tag/useTagAutocomplete';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
const placeholder = 'Добавить';
|
const placeholder = 'Добавить';
|
||||||
|
@ -29,6 +30,7 @@ const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
const wrapper = useRef<HTMLDivElement>(null);
|
const wrapper = useRef<HTMLDivElement>(null);
|
||||||
|
const options = useTagAutocomplete(input, exclude);
|
||||||
|
|
||||||
const onInput = useCallback(
|
const onInput = useCallback(
|
||||||
({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
|
({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -136,6 +138,7 @@ const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
||||||
input={ref.current}
|
input={ref.current}
|
||||||
onSelect={onAutocompleteSelect}
|
onSelect={onAutocompleteSelect}
|
||||||
search={input}
|
search={input}
|
||||||
|
options={options}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
|
@ -1,4 +1,4 @@
|
||||||
@import "src/styles/variables";
|
@import "~/styles/variables";
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
position: relative;
|
position: relative;
|
|
@ -3,7 +3,7 @@ import { TagField } from '~/components/containers/TagField';
|
||||||
import { ITag } from '~/redux/types';
|
import { ITag } from '~/redux/types';
|
||||||
import { uniq } from 'ramda';
|
import { uniq } from 'ramda';
|
||||||
import { Tag } from '~/components/tags/Tag';
|
import { Tag } from '~/components/tags/Tag';
|
||||||
import { TagInput } from '~/components/tags/TagInput';
|
import { TagInput } from '~/containers/tags/TagInput';
|
||||||
import { separateTags } from '~/utils/tag';
|
import { separateTags } from '~/utils/tag';
|
||||||
|
|
||||||
type IProps = HTMLAttributes<HTMLDivElement> & {
|
type IProps = HTMLAttributes<HTMLDivElement> & {
|
23
src/hooks/tag/useTagAutocomplete.ts
Normal file
23
src/hooks/tag/useTagAutocomplete.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { API } from '~/constants/api';
|
||||||
|
import { apiGetTagSuggestions } from '~/api/tags';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const useTagAutocomplete = (input: string, exclude: string[]): string[] => {
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => setSearch(input), 200);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [input]);
|
||||||
|
|
||||||
|
const { data } = useSWR(
|
||||||
|
`${API.TAG.AUTOCOMPLETE}?tag=${search}&exclude=${exclude.join(',')}`,
|
||||||
|
async () => {
|
||||||
|
const result = await apiGetTagSuggestions({ search, exclude });
|
||||||
|
return result.tags || [];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return data || [];
|
||||||
|
};
|
|
@ -4,8 +4,7 @@ import { API } from '~/constants/api';
|
||||||
import { flatten, isNil } from 'ramda';
|
import { flatten, isNil } from 'ramda';
|
||||||
import useSWRInfinite from 'swr/infinite';
|
import useSWRInfinite from 'swr/infinite';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { apiGetNodesOfTag } from '~/redux/tag/api';
|
import { apiGetNodesOfTag } from '~/api/tags';
|
||||||
import { COMMENTS_DISPLAY } from '~/constants/node';
|
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,6 @@ import { authLogout, authOpenProfile, gotAuthPostMessage } from './auth/actions'
|
||||||
import messages, { IMessagesState } from './messages';
|
import messages, { IMessagesState } from './messages';
|
||||||
import messagesSaga from './messages/sagas';
|
import messagesSaga from './messages/sagas';
|
||||||
|
|
||||||
import tag, { ITagState } from './tag';
|
|
||||||
import tagSaga from './tag/sagas';
|
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
import { assocPath } from 'ramda';
|
import { assocPath } from 'ramda';
|
||||||
|
@ -64,7 +62,6 @@ export interface IState {
|
||||||
flow: IFlowState;
|
flow: IFlowState;
|
||||||
player: IPlayerState;
|
player: IPlayerState;
|
||||||
messages: IMessagesState;
|
messages: IMessagesState;
|
||||||
tag: ITagState;
|
|
||||||
lab: ILabState;
|
lab: ILabState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +84,6 @@ export const store = createStore(
|
||||||
flow: persistReducer(flowPersistConfig, flow),
|
flow: persistReducer(flowPersistConfig, flow),
|
||||||
player: persistReducer(playerPersistConfig, player),
|
player: persistReducer(playerPersistConfig, player),
|
||||||
messages,
|
messages,
|
||||||
tag: tag,
|
|
||||||
lab: lab,
|
lab: lab,
|
||||||
}),
|
}),
|
||||||
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
|
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
|
||||||
|
@ -103,7 +99,6 @@ export function configureStore(): {
|
||||||
sagaMiddleware.run(playerSaga);
|
sagaMiddleware.run(playerSaga);
|
||||||
sagaMiddleware.run(modalSaga);
|
sagaMiddleware.run(modalSaga);
|
||||||
sagaMiddleware.run(messagesSaga);
|
sagaMiddleware.run(messagesSaga);
|
||||||
sagaMiddleware.run(tagSaga);
|
|
||||||
sagaMiddleware.run(labSaga);
|
sagaMiddleware.run(labSaga);
|
||||||
|
|
||||||
window.addEventListener('message', message => {
|
window.addEventListener('message', message => {
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { ITagState } from '~/redux/tag/index';
|
|
||||||
import { TAG_ACTIONS } from '~/redux/tag/constants';
|
|
||||||
|
|
||||||
export const tagSetNodes = (nodes: Partial<ITagState['nodes']>) => ({
|
|
||||||
type: TAG_ACTIONS.SET_TAG_NODES,
|
|
||||||
nodes,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const tagLoadNodes = (tag: string) => ({
|
|
||||||
type: TAG_ACTIONS.LOAD_NODES,
|
|
||||||
tag,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const tagSetAutocomplete = (autocomplete: Partial<ITagState['autocomplete']>) => ({
|
|
||||||
type: TAG_ACTIONS.SET_TAG_AUTOCOMPLETE,
|
|
||||||
autocomplete,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const tagLoadAutocomplete = (search: string, exclude: string[]) => ({
|
|
||||||
type: TAG_ACTIONS.LOAD_AUTOCOMPLETE,
|
|
||||||
search,
|
|
||||||
exclude,
|
|
||||||
});
|
|
|
@ -1,8 +0,0 @@
|
||||||
const prefix = 'TAG.';
|
|
||||||
|
|
||||||
export const TAG_ACTIONS = {
|
|
||||||
LOAD_NODES: `${prefix}LOAD_TAG_NODES`,
|
|
||||||
SET_TAG_NODES: `${prefix}SET_TAG_NODES`,
|
|
||||||
SET_TAG_AUTOCOMPLETE: `${prefix}SET_TAG_AUTOCOMPLETE`,
|
|
||||||
LOAD_AUTOCOMPLETE: `${prefix}LOAD_TAG_AUTOCOMPLETE`,
|
|
||||||
};
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { TAG_ACTIONS } from '~/redux/tag/constants';
|
|
||||||
import { ITagState } from '~/redux/tag/index';
|
|
||||||
import { tagSetAutocomplete, tagSetNodes } from '~/redux/tag/actions';
|
|
||||||
|
|
||||||
const setNodes = (state: ITagState, { nodes }: ReturnType<typeof tagSetNodes>) => ({
|
|
||||||
...state,
|
|
||||||
nodes: {
|
|
||||||
...state.nodes,
|
|
||||||
...nodes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const setAutocomplete = (
|
|
||||||
state: ITagState,
|
|
||||||
{ autocomplete }: ReturnType<typeof tagSetAutocomplete>
|
|
||||||
) => ({
|
|
||||||
...state,
|
|
||||||
autocomplete: {
|
|
||||||
...state.autocomplete,
|
|
||||||
...autocomplete,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const TAG_HANDLERS = {
|
|
||||||
[TAG_ACTIONS.SET_TAG_NODES]: setNodes,
|
|
||||||
[TAG_ACTIONS.SET_TAG_AUTOCOMPLETE]: setAutocomplete,
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { createReducer } from '~/utils/reducer';
|
|
||||||
import { INode } from '~/redux/types';
|
|
||||||
import { TAG_HANDLERS } from '~/redux/tag/handlers';
|
|
||||||
|
|
||||||
export interface ITagState {
|
|
||||||
nodes: {
|
|
||||||
list: INode[];
|
|
||||||
count: number;
|
|
||||||
isLoading: boolean;
|
|
||||||
};
|
|
||||||
autocomplete: {
|
|
||||||
isLoading: boolean;
|
|
||||||
options: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const INITIAL_STATE: ITagState = {
|
|
||||||
nodes: {
|
|
||||||
list: [],
|
|
||||||
count: 0,
|
|
||||||
isLoading: true,
|
|
||||||
},
|
|
||||||
autocomplete: {
|
|
||||||
isLoading: true,
|
|
||||||
options: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createReducer(INITIAL_STATE, TAG_HANDLERS);
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { TAG_ACTIONS } from '~/redux/tag/constants';
|
|
||||||
import { call, delay, put, select, takeLatest } from 'redux-saga/effects';
|
|
||||||
import {
|
|
||||||
tagLoadAutocomplete,
|
|
||||||
tagLoadNodes,
|
|
||||||
tagSetAutocomplete,
|
|
||||||
tagSetNodes,
|
|
||||||
} from '~/redux/tag/actions';
|
|
||||||
import { selectTagNodes } from '~/redux/tag/selectors';
|
|
||||||
import { apiGetNodesOfTag, apiGetTagSuggestions } from '~/redux/tag/api';
|
|
||||||
import { Unwrap } from '~/redux/types';
|
|
||||||
|
|
||||||
function* loadTagNodes({ tag }: ReturnType<typeof tagLoadNodes>) {
|
|
||||||
yield put(tagSetNodes({ isLoading: true }));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { list }: ReturnType<typeof selectTagNodes> = yield select(selectTagNodes);
|
|
||||||
const data: Unwrap<typeof apiGetNodesOfTag> = yield call(apiGetNodesOfTag, {
|
|
||||||
tag,
|
|
||||||
limit: 18,
|
|
||||||
offset: list.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
yield put(tagSetNodes({ list: [...list, ...data.nodes], count: data.count }));
|
|
||||||
} catch {
|
|
||||||
} finally {
|
|
||||||
yield put(tagSetNodes({ isLoading: false }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* loadAutocomplete({ search, exclude }: ReturnType<typeof tagLoadAutocomplete>) {
|
|
||||||
if (search.length < 2) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
yield put(tagSetAutocomplete({ isLoading: true }));
|
|
||||||
yield delay(200);
|
|
||||||
|
|
||||||
const data: Unwrap<typeof apiGetTagSuggestions> = yield call(apiGetTagSuggestions, {
|
|
||||||
search,
|
|
||||||
exclude,
|
|
||||||
});
|
|
||||||
|
|
||||||
yield put(tagSetAutocomplete({ options: data.tags }));
|
|
||||||
} catch {
|
|
||||||
} finally {
|
|
||||||
yield put(tagSetAutocomplete({ isLoading: false }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function* tagSaga() {
|
|
||||||
yield takeLatest(TAG_ACTIONS.LOAD_NODES, loadTagNodes);
|
|
||||||
yield takeLatest(TAG_ACTIONS.LOAD_AUTOCOMPLETE, loadAutocomplete);
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { IState } from '~/redux/store';
|
|
||||||
|
|
||||||
export const selectTag = (state: IState) => state.tag;
|
|
||||||
export const selectTagNodes = (state: IState) => state.tag.nodes;
|
|
||||||
export const selectTagAutocomplete = (state: IState) => state.tag.autocomplete;
|
|
Loading…
Add table
Add a link
Reference in a new issue