From 94ac596b925244fbbe03cb310f77271a52057439 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Sat, 18 Apr 2020 16:06:10 +0700 Subject: [PATCH] getting node results for search --- src/components/flow/FlowRecent/index.tsx | 77 ++++++++----------- src/components/flow/FlowRecent/styles.scss | 26 ------- .../flow/FlowSearchResults/index.tsx | 21 +++++ .../flow/FlowSearchResults/styles.scss | 12 +++ src/components/flow/FlowStamp/index.tsx | 60 +++++++++++++++ src/components/flow/FlowStamp/styles.scss | 55 +++++++++++++ src/components/node/NodeRelated/styles.scss | 2 +- src/constants/api.ts | 1 + src/containers/flow/FlowLayout/index.tsx | 18 +++-- src/redux/flow/actions.ts | 45 ++++++----- src/redux/flow/api.ts | 45 +++++------ src/redux/flow/constants.ts | 7 +- src/redux/flow/handlers.ts | 57 +++++++------- src/redux/flow/reducer.ts | 20 +++-- src/redux/flow/sagas.ts | 26 ++++++- 15 files changed, 308 insertions(+), 164 deletions(-) create mode 100644 src/components/flow/FlowSearchResults/index.tsx create mode 100644 src/components/flow/FlowSearchResults/styles.scss create mode 100644 src/components/flow/FlowStamp/index.tsx create mode 100644 src/components/flow/FlowStamp/styles.scss diff --git a/src/components/flow/FlowRecent/index.tsx b/src/components/flow/FlowRecent/index.tsx index 430054e7..f3eee255 100644 --- a/src/components/flow/FlowRecent/index.tsx +++ b/src/components/flow/FlowRecent/index.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState, useCallback, FormEvent } from 'react'; +import React, { FC } from 'react'; import * as styles from './styles.scss'; import { IFlowState } from '~/redux/flow/reducer'; import { getURL, getPrettyDate } from '~/utils/dom'; @@ -6,7 +6,6 @@ import { Link } from 'react-router-dom'; import { URLS, PRESETS } from '~/constants/urls'; import classNames from 'classnames'; import { NodeRelatedItem } from '~/components/node/NodeRelatedItem'; -import { InputText } from '~/components/input/InputText'; interface IProps { recent: IFlowState['recent']; @@ -14,55 +13,39 @@ interface IProps { } const FlowRecent: FC = ({ recent, updated }) => { - const [search, setSearch] = useState(''); - - const onSearchSubmit = useCallback((event: FormEvent) => { - event.preventDefault(); - }, []); - return ( -
-
- - + <> + {updated && + updated.slice(0, 20).map(node => ( + +
-
-
- Что нового? -
+
+
{node.title}
+
{getPrettyDate(node.created_at)}
+
+ + ))} - {updated && - updated.slice(0, 20).map(node => ( - -
+ {recent && + recent.slice(0, 20).map(node => ( + +
+ +
-
-
{node.title}
-
{getPrettyDate(node.created_at)}
-
- - ))} - - {recent && - recent.slice(0, 20).map(node => ( - -
- -
- -
-
{node.title}
-
{getPrettyDate(node.created_at)}
-
- - ))} -
-
+
+
{node.title}
+
{getPrettyDate(node.created_at)}
+
+ + ))} + ); }; diff --git a/src/components/flow/FlowRecent/styles.scss b/src/components/flow/FlowRecent/styles.scss index dfb7b656..67cc6542 100644 --- a/src/components/flow/FlowRecent/styles.scss +++ b/src/components/flow/FlowRecent/styles.scss @@ -1,29 +1,3 @@ -.grid { - display: flex; - justify-content: stretch; - flex-direction: column; - flex: 1; - background: $content_bg; - padding: $gap; - border-radius: 0 0 $radius $radius; - - @include outer_shadow(); -} - -.grid_label { - margin-bottom: $gap; - @include title_with_line(); - color: transparentize(white, $amount: 0.8); -} - -.search { - background: lighten($content_bg, 4%); - border-radius: $radius $radius 0 0; - padding: $gap; - - @include outer_shadow(); -} - .item { display: flex; align-items: center; diff --git a/src/components/flow/FlowSearchResults/index.tsx b/src/components/flow/FlowSearchResults/index.tsx new file mode 100644 index 00000000..7bd3da35 --- /dev/null +++ b/src/components/flow/FlowSearchResults/index.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import styles from './styles.scss'; +import { IFlowState } from '~/redux/flow/reducer'; +import { LoaderCircle } from '~/components/input/LoaderCircle'; + +interface IProps { + search: IFlowState['search']; +} + +const FlowSearchResults: FC = ({ search }) => { + if (search.is_loading) { + return ( +
+ +
+ ); + } + return
SEARCH
; +}; + +export { FlowSearchResults }; diff --git a/src/components/flow/FlowSearchResults/styles.scss b/src/components/flow/FlowSearchResults/styles.scss new file mode 100644 index 00000000..cbfae3c8 --- /dev/null +++ b/src/components/flow/FlowSearchResults/styles.scss @@ -0,0 +1,12 @@ +.wrap { + flex: 1; + background: red; +} + +.loading { + display: flex; + align-items: center; + justify-content: center; + flex: 1; + opacity: 0.3; +} diff --git a/src/components/flow/FlowStamp/index.tsx b/src/components/flow/FlowStamp/index.tsx new file mode 100644 index 00000000..21ecddd2 --- /dev/null +++ b/src/components/flow/FlowStamp/index.tsx @@ -0,0 +1,60 @@ +import React, { FC, useCallback, FormEvent } from 'react'; +import { IFlowState } from '~/redux/flow/reducer'; +import { InputText } from '~/components/input/InputText'; +import { FlowRecent } from '../FlowRecent'; +import classnames from 'classnames'; + +import * as styles from './styles.scss'; +import * as FLOW_ACTIONS from '~/redux/flow/actions'; +import { FlowSearchResults } from '../FlowSearchResults'; + +interface IProps { + recent: IFlowState['recent']; + updated: IFlowState['updated']; + search: IFlowState['search']; + flowChangeSearch: typeof FLOW_ACTIONS.flowChangeSearch; +} + +const FlowStamp: FC = ({ recent, updated, search, flowChangeSearch }) => { + const onSearchChange = useCallback((text: string) => flowChangeSearch({ text }), [ + flowChangeSearch, + ]); + + const onSearchSubmit = useCallback((event: FormEvent) => { + event.preventDefault(); + }, []); + + return ( +
+
+ + + +
+ {search.text ? ( + <> +
+ Результаты поиска +
+ +
+ +
+ + ) : ( + <> +
+ Что нового? +
+ +
+ +
+ + )} +
+
+ ); +}; + +export { FlowStamp }; diff --git a/src/components/flow/FlowStamp/styles.scss b/src/components/flow/FlowStamp/styles.scss new file mode 100644 index 00000000..098ade69 --- /dev/null +++ b/src/components/flow/FlowStamp/styles.scss @@ -0,0 +1,55 @@ +.wrap { + display: flex; + flex-direction: column; + width: 100%; + background: $content_bg; + border-radius: $radius; +} + +.grid { + display: flex; + justify-content: stretch; + flex-direction: column; + flex: 1; + border-radius: $radius; + + @include outer_shadow(); +} + +.items { + padding: 0 $gap 0 $gap; + flex: 1; + display: flex; + flex-direction: column; +} + +.label { + display: flex; + flex-direction: row; + min-width: 0; + padding: $gap; + border-radius: $radius; + + @include title_with_line(); + + color: transparentize(white, $amount: 0.8); + + &_search { + color: white; + padding-left: $gap * 1.2; + } +} + +.label_text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.search { + background: lighten($content_bg, 4%); + border-radius: $radius $radius 0 0; + padding: $gap; + + @include outer_shadow(); +} diff --git a/src/components/node/NodeRelated/styles.scss b/src/components/node/NodeRelated/styles.scss index fabf438e..1a47f904 100644 --- a/src/components/node/NodeRelated/styles.scss +++ b/src/components/node/NodeRelated/styles.scss @@ -23,7 +23,7 @@ } .text { - margin: 0 $gap; + margin-left: $gap / 2; } .placeholder { diff --git a/src/constants/api.ts b/src/constants/api.ts index 0dd2e09c..40923951 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -30,4 +30,5 @@ export const API = { `/node/${id}/comment/${comment_id}/lock`, SET_CELL_VIEW: (id: INode['id']) => `/node/${id}/cell-view`, }, + SEARCH: '/search', }; diff --git a/src/containers/flow/FlowLayout/index.tsx b/src/containers/flow/FlowLayout/index.tsx index 5ce6c641..c0051318 100644 --- a/src/containers/flow/FlowLayout/index.tsx +++ b/src/containers/flow/FlowLayout/index.tsx @@ -7,11 +7,12 @@ import * as FLOW_ACTIONS from '~/redux/flow/actions'; import pick from 'ramda/es/pick'; import { selectUser } from '~/redux/auth/selectors'; import { FlowHero } from '~/components/flow/FlowHero'; -import { FlowRecent } from '~/components/flow/FlowRecent'; import styles from './styles.scss'; +import { IState } from '~/redux/store'; +import { FlowStamp } from '~/components/flow/FlowStamp'; -const mapStateToProps = state => ({ - flow: pick(['nodes', 'heroes', 'recent', 'updated', 'is_loading'], selectFlow(state)), +const mapStateToProps = (state: IState) => ({ + flow: pick(['nodes', 'heroes', 'recent', 'updated', 'is_loading', 'search'], selectFlow(state)), user: pick(['role', 'id'], selectUser(state)), }); @@ -19,16 +20,18 @@ const mapDispatchToProps = { nodeGotoNode: NODE_ACTIONS.nodeGotoNode, flowSetCellView: FLOW_ACTIONS.flowSetCellView, flowGetMore: FLOW_ACTIONS.flowGetMore, + flowChangeSearch: FLOW_ACTIONS.flowChangeSearch, }; type IProps = ReturnType & typeof mapDispatchToProps & {}; const FlowLayoutUnconnected: FC = ({ - flow: { nodes, heroes, recent, updated, is_loading }, + flow: { nodes, heroes, recent, updated, is_loading, search }, user, nodeGotoNode, flowSetCellView, flowGetMore, + flowChangeSearch, }) => { const loadMore = useCallback(() => { const pos = window.scrollY + window.innerHeight - document.body.scrollHeight; @@ -51,7 +54,12 @@ const FlowLayoutUnconnected: FC = ({
- +
({ +export const flowSetNodes = (nodes: IFlowState['nodes']) => ({ nodes, - type: FLOW_ACTIONS.SET_NODES + type: FLOW_ACTIONS.SET_NODES, }); -export const flowSetHeroes = (heroes: IFlowState["heroes"]) => ({ +export const flowSetHeroes = (heroes: IFlowState['heroes']) => ({ heroes, - type: FLOW_ACTIONS.SET_HEROES + type: FLOW_ACTIONS.SET_HEROES, }); -export const flowSetRecent = (recent: IFlowState["recent"]) => ({ +export const flowSetRecent = (recent: IFlowState['recent']) => ({ recent, - type: FLOW_ACTIONS.SET_RECENT + type: FLOW_ACTIONS.SET_RECENT, }); -export const flowSetUpdated = (updated: IFlowState["updated"]) => ({ +export const flowSetUpdated = (updated: IFlowState['updated']) => ({ updated, - type: FLOW_ACTIONS.SET_UPDATED + type: FLOW_ACTIONS.SET_UPDATED, }); -export const flowSetCellView = (id: INode["id"], flow: INode["flow"]) => ({ +export const flowSetCellView = (id: INode['id'], flow: INode['flow']) => ({ type: FLOW_ACTIONS.SET_CELL_VIEW, id, - flow -}); - -export const flowSetRange = (range: IFlowState["range"]) => ({ - range, - type: FLOW_ACTIONS.SET_RANGE + flow, }); export const flowGetMore = () => ({ - type: FLOW_ACTIONS.GET_MORE + type: FLOW_ACTIONS.GET_MORE, }); export const flowSetFlow = (data: Partial) => ({ type: FLOW_ACTIONS.SET_FLOW, - data + data, +}); + +export const flowSetSearch = (search: Partial) => ({ + type: FLOW_ACTIONS.SET_SEARCH, + search, +}); + +export const flowChangeSearch = (search: Partial) => ({ + type: FLOW_ACTIONS.CHANGE_SEARCH, + search, }); diff --git a/src/redux/flow/api.ts b/src/redux/flow/api.ts index 3a7d83fe..5f54ca09 100644 --- a/src/redux/flow/api.ts +++ b/src/redux/flow/api.ts @@ -1,16 +1,11 @@ -import { - api, - configWithToken, - resultMiddleware, - errorMiddleware -} from "~/utils/api"; -import { INode, IResultWithStatus } from "../types"; -import { API } from "~/constants/api"; -import { flowSetCellView } from "~/redux/flow/actions"; +import { api, configWithToken, resultMiddleware, errorMiddleware } from '~/utils/api'; +import { INode, IResultWithStatus } from '../types'; +import { API } from '~/constants/api'; +import { flowSetCellView } from '~/redux/flow/actions'; export const postNode = ({ access, - node + node, }: { access: string; node: INode; @@ -20,24 +15,26 @@ export const postNode = ({ .then(resultMiddleware) .catch(errorMiddleware); -// export const getNodes = ({ -// from = null -// }: { -// from: string; -// }): Promise> => -// api -// .get(API.NODE.GET, { params: { from } }) -// .then(resultMiddleware) -// .catch(errorMiddleware); - export const postCellView = ({ id, flow, - access -}: ReturnType & { access: string }): Promise< - IResultWithStatus<{ is_liked: INode["is_liked"] }> -> => + access, +}: ReturnType & { access: string }): Promise> => api .post(API.NODE.SET_CELL_VIEW(id), { flow }, configWithToken(access)) .then(resultMiddleware) .catch(errorMiddleware); + +export const getSearchResults = ({ + access, + text, +}: { + access: string; + text: string; +}): Promise> => + api + .get(API.SEARCH, configWithToken(access, { params: { text } })) + .then(resultMiddleware) + .catch(errorMiddleware); diff --git a/src/redux/flow/constants.ts b/src/redux/flow/constants.ts index 2da99203..60f9f70b 100644 --- a/src/redux/flow/constants.ts +++ b/src/redux/flow/constants.ts @@ -1,4 +1,4 @@ -const prefix = "FLOW."; +const prefix = 'FLOW.'; export const FLOW_ACTIONS = { GET_FLOW: `${prefix}GET_FLOW`, @@ -9,5 +9,8 @@ export const FLOW_ACTIONS = { SET_UPDATED: `${prefix}SET_UPDATED`, SET_RANGE: `${prefix}SET_RANGE`, SET_CELL_VIEW: `${prefix}SET_CELL_VIEW`, - GET_MORE: `${prefix}GET_MORE` + GET_MORE: `${prefix}GET_MORE`, + + SET_SEARCH: `${prefix}SET_SEARCH`, + CHANGE_SEARCH: `${prefix}CHANGE_SEARCH`, }; diff --git a/src/redux/flow/handlers.ts b/src/redux/flow/handlers.ts index bf85e649..1ae8f803 100644 --- a/src/redux/flow/handlers.ts +++ b/src/redux/flow/handlers.ts @@ -1,46 +1,41 @@ -import assocPath from "ramda/es/assocPath"; -import { FLOW_ACTIONS } from "./constants"; +import assocPath from 'ramda/es/assocPath'; +import { FLOW_ACTIONS } from './constants'; import { flowSetNodes, flowSetHeroes, flowSetRecent, flowSetUpdated, - flowSetRange, - flowSetFlow -} from "./actions"; -import { IFlowState } from "./reducer"; + flowSetFlow, + flowSetSearch, +} from './actions'; +import { IFlowState } from './reducer'; -const setNodes = ( - state: IFlowState, - { nodes }: ReturnType -) => assocPath(["nodes"], nodes, state); +const setNodes = (state: IFlowState, { nodes }: ReturnType) => + assocPath(['nodes'], nodes, state); -const setHeroes = ( - state: IFlowState, - { heroes }: ReturnType -) => assocPath(["heroes"], heroes, state); +const setHeroes = (state: IFlowState, { heroes }: ReturnType) => + assocPath(['heroes'], heroes, state); -const setRecent = ( - state: IFlowState, - { recent }: ReturnType -) => assocPath(["recent"], recent, state); +const setRecent = (state: IFlowState, { recent }: ReturnType) => + assocPath(['recent'], recent, state); -const setUpdated = ( - state: IFlowState, - { updated }: ReturnType -) => assocPath(["updated"], updated, state); +const setUpdated = (state: IFlowState, { updated }: ReturnType) => + assocPath(['updated'], updated, state); -const setRange = ( - state: IFlowState, - { range }: ReturnType -) => assocPath(["range"], range, state); +const setFlow = (state: IFlowState, { data }: ReturnType): IFlowState => ({ + ...state, + ...data, +}); -const setFlow = ( +const setSearch = ( state: IFlowState, - { data }: ReturnType + { search }: ReturnType ): IFlowState => ({ ...state, - ...data + search: { + ...state.search, + ...search, + }, }); export const FLOW_HANDLERS = { @@ -48,6 +43,6 @@ export const FLOW_HANDLERS = { [FLOW_ACTIONS.SET_HEROES]: setHeroes, [FLOW_ACTIONS.SET_RECENT]: setRecent, [FLOW_ACTIONS.SET_UPDATED]: setUpdated, - [FLOW_ACTIONS.SET_RANGE]: setRange, - [FLOW_ACTIONS.SET_FLOW]: setFlow + [FLOW_ACTIONS.SET_FLOW]: setFlow, + [FLOW_ACTIONS.SET_SEARCH]: setSearch, }; diff --git a/src/redux/flow/reducer.ts b/src/redux/flow/reducer.ts index 0b114df4..2a141123 100644 --- a/src/redux/flow/reducer.ts +++ b/src/redux/flow/reducer.ts @@ -1,6 +1,6 @@ -import { createReducer } from "~/utils/reducer"; -import { INode, IError } from "../types"; -import { FLOW_HANDLERS } from "./handlers"; +import { createReducer } from '~/utils/reducer'; +import { INode, IError } from '../types'; +import { FLOW_HANDLERS } from './handlers'; export type IFlowState = Readonly<{ is_loading: boolean; @@ -8,7 +8,11 @@ export type IFlowState = Readonly<{ heroes: Partial[]; recent: Partial[]; updated: Partial[]; - range: [string, string]; + search: { + text: string; + is_loading: boolean; + results: INode[]; + }; error: IError; }>; @@ -17,9 +21,13 @@ const INITIAL_STATE: IFlowState = { heroes: [], recent: [], updated: [], - range: [null, null], // drop it, we use realtime range calc + search: { + text: '', + is_loading: false, + results: [], + }, is_loading: false, - error: null + error: null, }; export default createReducer(INITIAL_STATE, FLOW_HANDLERS); diff --git a/src/redux/flow/sagas.ts b/src/redux/flow/sagas.ts index ccc87592..adb88110 100644 --- a/src/redux/flow/sagas.ts +++ b/src/redux/flow/sagas.ts @@ -9,11 +9,13 @@ import { flowSetRecent, flowSetUpdated, flowSetFlow, + flowChangeSearch, + flowSetSearch, } from './actions'; -import { IResultWithStatus, INode } from '../types'; +import { IResultWithStatus, INode, Unwrap } from '../types'; import { selectFlowNodes } from './selectors'; import { reqWrapper } from '../auth/sagas'; -import { postCellView } from './api'; +import { postCellView, getSearchResults } from './api'; import { IFlowState } from './reducer'; import uniq from 'ramda/es/uniq'; @@ -110,8 +112,28 @@ function* getMore() { yield delay(1000); } +function* changeSearch({ search }: ReturnType) { + yield put( + flowSetSearch({ + ...search, + is_loading: !!search.text, + }) + ); + + if (!search.text) return; + + yield delay(500); + + const res: Unwrap = yield call(reqWrapper, getSearchResults, { + ...search, + }); + + console.log(res); +} + export default function* nodeSaga() { yield takeLatest([FLOW_ACTIONS.GET_FLOW, REHYDRATE], onGetFlow); yield takeLatest(FLOW_ACTIONS.SET_CELL_VIEW, onSetCellView); yield takeLeading(FLOW_ACTIONS.GET_MORE, getMore); + yield takeLatest(FLOW_ACTIONS.CHANGE_SEARCH, changeSearch); }