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

refactored flow page

This commit is contained in:
Fedor Katurov 2021-11-06 19:48:07 +07:00
parent 93a1d4104b
commit 0b77e87778
10 changed files with 182 additions and 88 deletions

View file

@ -1,7 +1,5 @@
import React, { FC, Fragment } from 'react'; import React, { FC, Fragment } from 'react';
import { FlowDisplay, IFlowNode, INode } from '~/redux/types';
import { IFlowState } from '~/redux/flow/reducer';
import { FlowDisplay, INode } from '~/redux/types';
import { IUser } from '~/redux/auth/types'; import { IUser } from '~/redux/auth/types';
import { PRESETS, URLS } from '~/constants/urls'; import { PRESETS, URLS } from '~/constants/urls';
import { FlowCell } from '~/components/flow/FlowCell'; import { FlowCell } from '~/components/flow/FlowCell';
@ -10,7 +8,8 @@ import styles from './styles.module.scss';
import { getURLFromString } from '~/utils/dom'; import { getURLFromString } from '~/utils/dom';
import { canEditNode } from '~/utils/node'; import { canEditNode } from '~/utils/node';
type IProps = Partial<IFlowState> & { type IProps = {
nodes: IFlowNode[];
user: Partial<IUser>; user: Partial<IUser>;
onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void; onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void;
}; };

View file

@ -1,16 +1,17 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { IFlowState } from '~/redux/flow/reducer';
import { LoaderCircle } from '~/components/input/LoaderCircle'; import { LoaderCircle } from '~/components/input/LoaderCircle';
import { FlowRecentItem } from '../FlowRecentItem'; import { FlowRecentItem } from '../FlowRecentItem';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { INode } from '~/redux/types';
interface IProps { interface IProps {
search: IFlowState['search']; isLoading: boolean;
results: INode[];
onLoadMore: () => void; onLoadMore: () => void;
} }
const FlowSearchResults: FC<IProps> = ({ search, onLoadMore }) => { const FlowSearchResults: FC<IProps> = ({ results, isLoading, onLoadMore }) => {
const onScroll = useCallback( const onScroll = useCallback(
event => { event => {
const el = event.target; const el = event.target;
@ -23,7 +24,7 @@ const FlowSearchResults: FC<IProps> = ({ search, onLoadMore }) => {
[onLoadMore] [onLoadMore]
); );
if (search.is_loading) { if (isLoading) {
return ( return (
<div className={styles.loading}> <div className={styles.loading}>
<LoaderCircle size={64} /> <LoaderCircle size={64} />
@ -31,7 +32,7 @@ const FlowSearchResults: FC<IProps> = ({ search, onLoadMore }) => {
); );
} }
if (!search.results.length) { if (!results.length) {
return ( return (
<div className={styles.loading}> <div className={styles.loading}>
<Icon size={96} icon="search" /> <Icon size={96} icon="search" />
@ -42,7 +43,7 @@ const FlowSearchResults: FC<IProps> = ({ search, onLoadMore }) => {
return ( return (
<div className={styles.wrap} onScroll={onScroll}> <div className={styles.wrap} onScroll={onScroll}>
{search.results.map(node => ( {results.map(node => (
<FlowRecentItem node={node} key={node.id} /> <FlowRecentItem node={node} key={node.id} />
))} ))}
</div> </div>

View file

@ -1,5 +1,4 @@
import React, { FC, FormEvent, useCallback, useMemo } from 'react'; import React, { FC, FormEvent, useCallback, useMemo } from 'react';
import { IFlowState } from '~/redux/flow/reducer';
import { InputText } from '~/components/input/InputText'; import { InputText } from '~/components/input/InputText';
import { FlowRecent } from '../FlowRecent'; import { FlowRecent } from '../FlowRecent';
@ -11,25 +10,34 @@ import { Toggle } from '~/components/input/Toggle';
import classNames from 'classnames'; import classNames from 'classnames';
import { Superpower } from '~/components/boris/Superpower'; import { Superpower } from '~/components/boris/Superpower';
import { experimentalFeatures } from '~/constants/features'; import { experimentalFeatures } from '~/constants/features';
import { IFlowNode, INode } from '~/redux/types';
interface IProps { interface IProps {
recent: IFlowState['recent']; searchText: string;
updated: IFlowState['updated']; searchTotal: number;
search: IFlowState['search']; searchIsLoading: boolean;
isFluid: boolean; searchResults: INode[];
onSearchChange: (text: string) => void; onSearchChange: (text: string) => void;
onLoadMore: () => void; onSearchLoadMore: () => void;
toggleLayout: () => void;
recent: IFlowNode[];
updated: IFlowNode[];
isFluid: boolean;
onToggleLayout: () => void;
} }
const FlowStamp: FC<IProps> = ({ const FlowStamp: FC<IProps> = ({
searchText,
searchIsLoading,
searchTotal,
searchResults,
onSearchChange,
onSearchLoadMore,
recent, recent,
updated, updated,
search,
onSearchChange,
onLoadMore,
isFluid, isFluid,
toggleLayout, onToggleLayout,
}) => { }) => {
const onSearchSubmit = useCallback((event: FormEvent) => { const onSearchSubmit = useCallback((event: FormEvent) => {
event.preventDefault(); event.preventDefault();
@ -48,12 +56,12 @@ const FlowStamp: FC<IProps> = ({
const after = useMemo( const after = useMemo(
() => () =>
search.text ? ( searchText ? (
<Icon icon="close" size={24} className={styles.close_icon} onClick={onClearSearch} /> <Icon icon="close" size={24} className={styles.close_icon} onClick={onClearSearch} />
) : ( ) : (
<Icon icon="search" size={24} className={styles.search_icon} /> <Icon icon="search" size={24} className={styles.search_icon} />
), ),
[search.text] [searchText]
); );
return ( return (
@ -61,7 +69,7 @@ const FlowStamp: FC<IProps> = ({
<form className={styles.search} onSubmit={onSearchSubmit}> <form className={styles.search} onSubmit={onSearchSubmit}>
<InputText <InputText
title="Поиск" title="Поиск"
value={search.text} value={searchText}
handler={onSearchChange} handler={onSearchChange}
after={after} after={after}
onKeyUp={onKeyUp} onKeyUp={onKeyUp}
@ -69,16 +77,20 @@ const FlowStamp: FC<IProps> = ({
</form> </form>
<div className={styles.grid}> <div className={styles.grid}>
{search.text ? ( {searchText ? (
<> <>
<div className={styles.label}> <div className={styles.label}>
<span className={styles.label_text}>Результаты поиска</span> <span className={styles.label_text}>Результаты поиска</span>
<span className="line" /> <span className="line" />
<span>{!search.is_loading && search.total}</span> <span>{!searchIsLoading && searchTotal}</span>
</div> </div>
<div className={styles.items}> <div className={styles.items}>
<FlowSearchResults search={search} onLoadMore={onLoadMore} /> <FlowSearchResults
isLoading={searchIsLoading}
results={searchResults}
onLoadMore={onSearchLoadMore}
/>
</div> </div>
</> </>
) : ( ) : (
@ -98,7 +110,7 @@ const FlowStamp: FC<IProps> = ({
{experimentalFeatures.liquidFlow && ( {experimentalFeatures.liquidFlow && (
<Superpower> <Superpower>
<div className={styles.toggles}> <div className={styles.toggles}>
<Group horizontal onClick={toggleLayout} className={styles.fluid_toggle}> <Group horizontal onClick={onToggleLayout} className={styles.fluid_toggle}>
<Toggle value={isFluid} /> <Toggle value={isFluid} />
<div className={styles.toggles__label}>Жидкое течение</div> <div className={styles.toggles__label}>Жидкое течение</div>
</Group> </Group>

View file

@ -1,6 +1,5 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { URLS } from '~/constants/urls'; import { URLS } from '~/constants/urls';
import { FlowLayout } from '~/layouts/FlowLayout';
import { NodeLayout } from '~/layouts/NodeLayout'; import { NodeLayout } from '~/layouts/NodeLayout';
import { BorisLayout } from '~/layouts/BorisLayout'; import { BorisLayout } from '~/layouts/BorisLayout';
import { ErrorNotFound } from '~/containers/pages/ErrorNotFound'; import { ErrorNotFound } from '~/containers/pages/ErrorNotFound';
@ -9,6 +8,7 @@ import { LabLayout } from '~/layouts/LabLayout';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectAuthUser } from '~/redux/auth/selectors'; import { selectAuthUser } from '~/redux/auth/selectors';
import { ProfileLayout } from '~/layouts/ProfileLayout'; import { ProfileLayout } from '~/layouts/ProfileLayout';
import FlowPage from '~/pages';
interface IProps {} interface IProps {}
@ -25,7 +25,7 @@ const MainRouter: FC<IProps> = () => {
{is_user && <Route path={URLS.LAB} component={LabLayout} />} {is_user && <Route path={URLS.LAB} component={LabLayout} />}
<Route path={URLS.BASE} component={FlowLayout} /> <Route path={URLS.BASE} component={FlowPage} />
<Redirect to="/" /> <Redirect to="/" />
</Switch> </Switch>
); );

View file

@ -1,61 +1,50 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import React, { FC } from 'react';
import { useDispatch } from 'react-redux';
import { FlowGrid } from '~/components/flow/FlowGrid'; import { FlowGrid } from '~/components/flow/FlowGrid';
import { selectFlow } from '~/redux/flow/selectors';
import {
flowChangeSearch,
flowGetMore,
flowLoadMoreSearch,
flowSetCellView,
} from '~/redux/flow/actions';
import { selectUser } from '~/redux/auth/selectors';
import { FlowHero } from '~/components/flow/FlowHero';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { FlowStamp } from '~/components/flow/FlowStamp'; import { FlowStamp } from '~/components/flow/FlowStamp';
import { Container } from '~/containers/main/Container';
import { SidebarRouter } from '~/containers/main/SidebarRouter'; import { SidebarRouter } from '~/containers/main/SidebarRouter';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; import { FlowDisplay, IFlowNode, INode } from '~/redux/types';
import { FlowDisplay, INode } from '~/redux/types';
import { selectLabUpdatesNodes } from '~/redux/lab/selectors';
import { usePersistedState } from '~/utils/hooks/usePersistedState';
import classNames from 'classnames'; import classNames from 'classnames';
import { useFlowLayout } from '~/utils/hooks/flow/useFlowLayout';
import { useFlowPagination } from '~/utils/hooks/flow/useFlowPagination';
import { FlowSwiperHero } from '~/components/flow/FlowSwiperHero'; import { FlowSwiperHero } from '~/components/flow/FlowSwiperHero';
import { IUser } from '~/redux/auth/types';
const FlowLayout: FC = () => { interface Props {
const { nodes, heroes, recent, updated, isLoading, search } = useShallowSelect(selectFlow); updates: IFlowNode[];
const { isFluid, toggleLayout } = useFlowLayout(); recent: IFlowNode[];
const labUpdates = useShallowSelect(selectLabUpdatesNodes); heroes: IFlowNode[];
const user = useShallowSelect(selectUser); nodes: IFlowNode[];
const dispatch = useDispatch(); user: IUser;
isFluid: boolean;
onToggleLayout: () => void;
onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void;
useFlowPagination({ isLoading }); searchText: string;
searchTotal: number;
searchIsLoading: boolean;
searchResults: INode[];
onSearchChange: (text: string) => void;
onSearchLoadMore: () => void;
}
const onLoadMoreSearch = useCallback(() => { const FlowLayout: FC<Props> = ({
if (search.is_loading_more) return; updates,
dispatch(flowLoadMoreSearch()); heroes,
}, [search.is_loading_more, dispatch]); recent,
nodes,
const onChangeSearch = useCallback( user,
(text: string) => { isFluid,
dispatch(flowChangeSearch({ text })); onToggleLayout,
}, onChangeCellView,
[dispatch]
);
const cumulativeUpdates = useMemo(() => [...updated, ...labUpdates].slice(0, 10), [
updated,
labUpdates,
]);
const onChangeCellView = useCallback(
(id: INode['id'], val: FlowDisplay) => dispatch(flowSetCellView(id, val)),
[]
);
searchText,
searchTotal,
searchIsLoading,
searchResults,
onSearchChange,
onSearchLoadMore,
}) => {
return ( return (
<div className={classNames(styles.container, { [styles.fluid]: isFluid })}> <div className={classNames(styles.container)}>
<div className={styles.grid}> <div className={styles.grid}>
<div className={styles.hero}> <div className={styles.hero}>
<FlowSwiperHero heroes={heroes} /> <FlowSwiperHero heroes={heroes} />
@ -63,13 +52,16 @@ const FlowLayout: FC = () => {
<div className={styles.stamp}> <div className={styles.stamp}>
<FlowStamp <FlowStamp
recent={recent}
updated={cumulativeUpdates}
search={search}
isFluid={isFluid} isFluid={isFluid}
onSearchChange={onChangeSearch} recent={recent}
onLoadMore={onLoadMoreSearch} updated={updates}
toggleLayout={toggleLayout} searchText={searchText}
searchIsLoading={searchIsLoading}
searchTotal={searchTotal}
searchResults={searchResults}
onSearchChange={onSearchChange}
onSearchLoadMore={onSearchLoadMore}
onToggleLayout={onToggleLayout}
/> />
</div> </div>

34
src/pages/index.tsx Normal file
View file

@ -0,0 +1,34 @@
import React, { FC } from 'react';
import { FlowLayout } from '~/layouts/FlowLayout';
import { useFlow } from '~/utils/hooks/flow/useFlow';
import { useSearch } from '~/utils/hooks/search/useSearch';
import { useUser } from '~/utils/hooks/user/userUser';
interface Props {}
const FlowPage: FC<Props> = () => {
const { updates, nodes, heroes, recent, isFluid, toggleLayout, onChangeCellView } = useFlow();
const user = useUser();
const { search, onSearchLoadMore, onSearchChange } = useSearch();
return (
<FlowLayout
updates={updates}
recent={recent}
heroes={heroes}
nodes={nodes}
user={user}
isFluid={isFluid}
onToggleLayout={toggleLayout}
onChangeCellView={onChangeCellView}
searchResults={search.results}
searchText={search.text}
searchTotal={search.total}
searchIsLoading={search.is_loading}
onSearchLoadMore={onSearchLoadMore}
onSearchChange={onSearchChange}
/>
);
};
export default FlowPage;

View file

@ -1,13 +1,13 @@
import { createReducer } from '~/utils/reducer'; import { createReducer } from '~/utils/reducer';
import { INode, IError } from '../types'; import { IError, IFlowNode, INode } from '../types';
import { FLOW_HANDLERS } from './handlers'; import { FLOW_HANDLERS } from './handlers';
export type IFlowState = Readonly<{ export type IFlowState = Readonly<{
isLoading: boolean; isLoading: boolean;
nodes: INode[]; nodes: IFlowNode[];
heroes: Partial<INode>[]; heroes: IFlowNode[];
recent: Partial<INode>[]; recent: IFlowNode[];
updated: Partial<INode>[]; updated: IFlowNode[];
search: { search: {
text: string; text: string;
results: INode[]; results: INode[];

View file

@ -147,6 +147,11 @@ export interface INode {
commented_at?: string; commented_at?: string;
} }
export type IFlowNode = Pick<
INode,
'id' | 'flow' | 'description' | 'title' | 'thumbnail' | 'created_at'
>;
export interface IComment { export interface IComment {
id: number; id: number;
text: string; text: string;

View file

@ -0,0 +1,27 @@
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectFlow } from '~/redux/flow/selectors';
import { useFlowLayout } from '~/utils/hooks/flow/useFlowLayout';
import { selectLabUpdatesNodes } from '~/redux/lab/selectors';
import { useDispatch } from 'react-redux';
import { useFlowPagination } from '~/utils/hooks/flow/useFlowPagination';
import { useCallback, useMemo } from 'react';
import { FlowDisplay, INode } from '~/redux/types';
import { flowSetCellView } from '~/redux/flow/actions';
export const useFlow = () => {
const { nodes, heroes, recent, updated, isLoading } = useShallowSelect(selectFlow);
const { isFluid, toggleLayout } = useFlowLayout();
const labUpdates = useShallowSelect(selectLabUpdatesNodes);
const dispatch = useDispatch();
useFlowPagination({ isLoading });
const updates = useMemo(() => [...updated, ...labUpdates].slice(0, 10), [updated, labUpdates]);
const onChangeCellView = useCallback(
(id: INode['id'], val: FlowDisplay) => dispatch(flowSetCellView(id, val)),
[]
);
return { nodes, heroes, recent, updates, isFluid, toggleLayout, onChangeCellView };
};

View file

@ -0,0 +1,24 @@
import { useCallback } from 'react';
import { flowChangeSearch, flowLoadMoreSearch } from '~/redux/flow/actions';
import { useDispatch } from 'react-redux';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectFlow } from '~/redux/flow/selectors';
export const useSearch = () => {
const dispatch = useDispatch();
const { search } = useShallowSelect(selectFlow);
const onSearchLoadMore = useCallback(() => {
if (search.is_loading_more) return;
dispatch(flowLoadMoreSearch());
}, [search.is_loading_more, dispatch]);
const onSearchChange = useCallback(
(text: string) => {
dispatch(flowChangeSearch({ text }));
},
[dispatch]
);
return { onSearchChange, onSearchLoadMore, search };
};