Expand timelines loading state fix, improve search behavior
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
2abf4a4c8c
commit
f705e14d2e
8 changed files with 65 additions and 63 deletions
|
@ -7,7 +7,6 @@ import type { Search } from 'pl-api';
|
|||
import type { SearchFilter } from 'soapbox/reducers/search';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
const SEARCH_CHANGE = 'SEARCH_CHANGE' as const;
|
||||
const SEARCH_CLEAR = 'SEARCH_CLEAR' as const;
|
||||
const SEARCH_SHOW = 'SEARCH_SHOW' as const;
|
||||
const SEARCH_RESULTS_CLEAR = 'SEARCH_RESULTS_CLEAR' as const;
|
||||
|
@ -24,23 +23,6 @@ const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL' as const;
|
|||
|
||||
const SEARCH_ACCOUNT_SET = 'SEARCH_ACCOUNT_SET' as const;
|
||||
|
||||
const changeSearch = (value: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
// If backspaced all the way, clear the search
|
||||
if (value.length === 0) {
|
||||
dispatch(clearSearchResults());
|
||||
return dispatch({
|
||||
type: SEARCH_CHANGE,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
return dispatch({
|
||||
type: SEARCH_CHANGE,
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const clearSearch = () => ({
|
||||
type: SEARCH_CLEAR,
|
||||
});
|
||||
|
@ -49,9 +31,8 @@ const clearSearchResults = () => ({
|
|||
type: SEARCH_RESULTS_CLEAR,
|
||||
});
|
||||
|
||||
const submitSearch = (filter?: SearchFilter) =>
|
||||
const submitSearch = (value: string, filter?: SearchFilter) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const value = getState().search.value;
|
||||
const type = filter || getState().search.filter || 'accounts';
|
||||
const accountId = getState().search.accountId;
|
||||
|
||||
|
@ -103,11 +84,11 @@ const fetchSearchFail = (error: unknown) => ({
|
|||
error,
|
||||
});
|
||||
|
||||
const setFilter = (filterType: SearchFilter) =>
|
||||
const setFilter = (value: string, filterType: SearchFilter) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
dispatch(submitSearch(filterType));
|
||||
dispatch(submitSearch(value, filterType));
|
||||
|
||||
dispatch({
|
||||
return dispatch({
|
||||
type: SEARCH_FILTER_SET,
|
||||
path: ['search', 'filter'],
|
||||
value: filterType,
|
||||
|
@ -116,7 +97,7 @@ const setFilter = (filterType: SearchFilter) =>
|
|||
|
||||
const expandSearch = (type: SearchFilter) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (type === 'links') return;
|
||||
const value = getState().search.value;
|
||||
const value = getState().search.submittedValue;
|
||||
const offset = getState().search.results[type].size;
|
||||
const accountId = getState().search.accountId;
|
||||
|
||||
|
@ -170,8 +151,24 @@ const setSearchAccount = (accountId: string | null) => ({
|
|||
accountId,
|
||||
});
|
||||
|
||||
type SearchAction =
|
||||
| ReturnType<typeof clearSearch>
|
||||
| ReturnType<typeof clearSearchResults>
|
||||
| ReturnType<typeof fetchSearchRequest>
|
||||
| ReturnType<typeof fetchSearchSuccess>
|
||||
| ReturnType<typeof fetchSearchFail>
|
||||
| ReturnType<typeof expandSearchRequest>
|
||||
| ReturnType<typeof expandSearchSuccess>
|
||||
| ReturnType<typeof expandSearchFail>
|
||||
| {
|
||||
type: typeof SEARCH_FILTER_SET;
|
||||
path: (['search', 'filter']);
|
||||
value: SearchFilter;
|
||||
}
|
||||
| ReturnType<typeof showSearch>
|
||||
| ReturnType<typeof setSearchAccount>
|
||||
|
||||
export {
|
||||
SEARCH_CHANGE,
|
||||
SEARCH_CLEAR,
|
||||
SEARCH_SHOW,
|
||||
SEARCH_RESULTS_CLEAR,
|
||||
|
@ -183,7 +180,6 @@ export {
|
|||
SEARCH_EXPAND_SUCCESS,
|
||||
SEARCH_EXPAND_FAIL,
|
||||
SEARCH_ACCOUNT_SET,
|
||||
changeSearch,
|
||||
clearSearch,
|
||||
clearSearchResults,
|
||||
submitSearch,
|
||||
|
@ -197,4 +193,5 @@ export {
|
|||
expandSearchFail,
|
||||
showSearch,
|
||||
setSearchAccount,
|
||||
type SearchAction,
|
||||
};
|
||||
|
|
|
@ -163,8 +163,10 @@ const deduplicateStatuses = (statuses: any[]) => {
|
|||
};
|
||||
|
||||
const handleTimelineExpand = (timelineId: string, fn: Promise<PaginatedResponse<BaseStatus>>, isLoadingRecent: boolean, done = noOp) =>
|
||||
(dispatch: AppDispatch) =>
|
||||
fn.then(response => {
|
||||
(dispatch: AppDispatch) => {
|
||||
dispatch(expandTimelineRequest(timelineId));
|
||||
|
||||
return fn.then(response => {
|
||||
dispatch(importFetchedStatuses(response.items));
|
||||
|
||||
const statuses = deduplicateStatuses(response.items);
|
||||
|
@ -183,6 +185,7 @@ const handleTimelineExpand = (timelineId: string, fn: Promise<PaginatedResponse<
|
|||
dispatch(expandTimelineFail(timelineId, error));
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
const fetchHomeTimeline = (expand = false, done = noOp) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
|
|
|
@ -36,8 +36,8 @@ const AccountTimeline: React.FC<IAccountTimeline> = ({ params, withReplies = fal
|
|||
|
||||
const isBlocked = useAppSelector(state => state.relationships.getIn([account?.id, 'blocked_by']) === true);
|
||||
const unavailable = isBlocked && !features.blockersVisible;
|
||||
const isLoading = useAppSelector(state => state.getIn(['timelines', `account:${path}`, 'isLoading']) === true);
|
||||
const hasMore = useAppSelector(state => state.getIn(['timelines', `account:${path}`, 'hasMore']) === true);
|
||||
const isLoading = useAppSelector(state => state.timelines.get(`account:${path}`)?.isLoading === true);
|
||||
const hasMore = useAppSelector(state => state.timelines.get(`account:${path}`)?.hasMore === true);
|
||||
|
||||
const accountUsername = account?.username || params.username;
|
||||
|
||||
|
|
|
@ -47,12 +47,13 @@ const SearchResults = () => {
|
|||
const filterByAccount = useAppSelector((state) => state.search.accountId || undefined);
|
||||
const { trendingLinks } = useTrendingLinks();
|
||||
const { account } = useAccount(filterByAccount);
|
||||
useAppSelector((state) => console.log(state.search.toJS()));
|
||||
|
||||
const handleLoadMore = () => dispatch(expandSearch(selectedFilter));
|
||||
|
||||
const handleUnsetAccount = () => dispatch(setSearchAccount(null));
|
||||
|
||||
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
|
||||
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(value, newActiveFilter));
|
||||
|
||||
const renderFilterBar = () => {
|
||||
const items = [];
|
||||
|
@ -207,6 +208,8 @@ const SearchResults = () => {
|
|||
}
|
||||
|
||||
if (selectedFilter === 'links') {
|
||||
loaded = true;
|
||||
|
||||
if (submitted) {
|
||||
selectFilter('accounts');
|
||||
setTabKey(key => ++key);
|
||||
|
@ -214,6 +217,8 @@ const SearchResults = () => {
|
|||
searchResults = ImmutableList(trendingLinks.map(trendingLink => <TrendingLink trendingLink={trendingLink} />));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(submitted, loaded, selectedFilter);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import clsx from 'clsx';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
changeSearch,
|
||||
clearSearch,
|
||||
clearSearchResults,
|
||||
setSearchAccount,
|
||||
|
@ -41,6 +40,8 @@ interface ISearch {
|
|||
}
|
||||
|
||||
const Search = (props: ISearch) => {
|
||||
const submittedValue = useAppSelector((state) => state.search.submittedValue);
|
||||
const [value, setValue] = useState(submittedValue);
|
||||
const {
|
||||
autoFocus = false,
|
||||
autoSubmit = false,
|
||||
|
@ -52,20 +53,19 @@ const Search = (props: ISearch) => {
|
|||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
|
||||
const value = useAppSelector((state) => state.search.value);
|
||||
const submitted = useAppSelector((state) => state.search.submitted);
|
||||
|
||||
const debouncedSubmit = useCallback(debounce(() => {
|
||||
dispatch(submitSearch());
|
||||
const debouncedSubmit = useCallback(debounce((value: string) => {
|
||||
dispatch(submitSearch(value));
|
||||
}, 900), []);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
|
||||
dispatch(changeSearch(value));
|
||||
setValue(value);
|
||||
|
||||
if (autoSubmit) {
|
||||
debouncedSubmit();
|
||||
debouncedSubmit(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -80,11 +80,11 @@ const Search = (props: ISearch) => {
|
|||
const handleSubmit = () => {
|
||||
if (openInRoute) {
|
||||
dispatch(setSearchAccount(null));
|
||||
dispatch(submitSearch());
|
||||
dispatch(submitSearch(value));
|
||||
|
||||
history.push('/search');
|
||||
} else {
|
||||
dispatch(submitSearch());
|
||||
dispatch(submitSearch(value));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -129,6 +129,10 @@ const Search = (props: ISearch) => {
|
|||
className: 'pr-10 rtl:pl-10 rtl:pr-3',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== submittedValue) setValue(submittedValue);
|
||||
}, [submittedValue]);
|
||||
|
||||
if (autosuggest) {
|
||||
componentProps.onSelected = handleSelected;
|
||||
componentProps.menu = makeMenu();
|
||||
|
|
|
@ -27,7 +27,7 @@ const TrendsPanel = ({ limit }: ITrendsPanel) => {
|
|||
const { data: trends, isFetching } = useTrends();
|
||||
|
||||
const setHashtagsFilter = () => {
|
||||
dispatch(setFilter('hashtags'));
|
||||
dispatch(setFilter('', 'hashtags'));
|
||||
};
|
||||
|
||||
if (!isFetching && !trends?.length) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { List as ImmutableList, Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord, fromJS } from 'immutable';
|
||||
|
||||
import { COMPOSE_SUBMIT_SUCCESS } from 'soapbox/actions/compose';
|
||||
import { COMPOSE_SUBMIT_SUCCESS, type ComposeAction } from 'soapbox/actions/compose';
|
||||
import { DRAFT_STATUSES_FETCH_SUCCESS, PERSIST_DRAFT_STATUS, CANCEL_DRAFT_STATUS } from 'soapbox/actions/draft-statuses';
|
||||
import KVStore from 'soapbox/storage/kv-store';
|
||||
|
||||
|
@ -48,7 +48,7 @@ const persistState = (state: State, accountUrl: string) => {
|
|||
return state;
|
||||
};
|
||||
|
||||
const scheduled_statuses = (state: State = initialState, action: AnyAction) => {
|
||||
const scheduled_statuses = (state: State = initialState, action: AnyAction | ComposeAction) => {
|
||||
switch (action.type) {
|
||||
case DRAFT_STATUSES_FETCH_SUCCESS:
|
||||
return importStatuses(state, action.statuses);
|
||||
|
|
|
@ -5,9 +5,9 @@ import {
|
|||
COMPOSE_REPLY,
|
||||
COMPOSE_DIRECT,
|
||||
COMPOSE_QUOTE,
|
||||
type ComposeAction,
|
||||
} from '../actions/compose';
|
||||
import {
|
||||
SEARCH_CHANGE,
|
||||
SEARCH_CLEAR,
|
||||
SEARCH_FETCH_REQUEST,
|
||||
SEARCH_FETCH_SUCCESS,
|
||||
|
@ -17,10 +17,10 @@ import {
|
|||
SEARCH_EXPAND_SUCCESS,
|
||||
SEARCH_ACCOUNT_SET,
|
||||
SEARCH_RESULTS_CLEAR,
|
||||
type SearchAction,
|
||||
} from '../actions/search';
|
||||
|
||||
import type { Search, Tag } from 'pl-api';
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const ResultsRecord = ImmutableRecord({
|
||||
|
@ -39,14 +39,12 @@ const ResultsRecord = ImmutableRecord({
|
|||
});
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
value: '',
|
||||
submitted: false,
|
||||
submittedValue: '',
|
||||
hidden: false,
|
||||
results: ResultsRecord(),
|
||||
filter: 'accounts' as SearchFilter,
|
||||
accountId: null as string | null,
|
||||
next: null as string | null,
|
||||
});
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
|
@ -55,18 +53,18 @@ type SearchFilter = 'accounts' | 'statuses' | 'groups' | 'hashtags' | 'links';
|
|||
|
||||
const toIds = (items: APIEntities = []) => ImmutableOrderedSet(items.map(item => item.id));
|
||||
|
||||
const importResults = (state: State, results: Search, searchTerm: string, searchType: SearchFilter, next: string | null) =>
|
||||
const importResults = (state: State, results: Search, searchTerm: string, searchType: SearchFilter) =>
|
||||
state.withMutations(state => {
|
||||
if (state.value === searchTerm && state.filter === searchType) {
|
||||
if (state.submittedValue === searchTerm && state.filter === searchType) {
|
||||
state.set('results', ResultsRecord({
|
||||
accounts: toIds(results.accounts),
|
||||
statuses: toIds(results.statuses),
|
||||
groups: toIds(results.groups),
|
||||
hashtags: ImmutableOrderedSet(results.hashtags), // it's a list of records
|
||||
accountsHasMore: results.accounts.length >= 20,
|
||||
statusesHasMore: results.statuses.length >= 20,
|
||||
groupsHasMore: results.groups?.length >= 20,
|
||||
hashtagsHasMore: results.hashtags.length >= 20,
|
||||
accountsHasMore: results.accounts.length !== 0,
|
||||
statusesHasMore: results.statuses.length !== 0,
|
||||
groupsHasMore: results.groups?.length !== 0,
|
||||
hashtagsHasMore: results.hashtags.length !== 0,
|
||||
accountsLoaded: true,
|
||||
statusesLoaded: true,
|
||||
groupsLoaded: true,
|
||||
|
@ -74,16 +72,14 @@ const importResults = (state: State, results: Search, searchTerm: string, search
|
|||
}));
|
||||
|
||||
state.set('submitted', true);
|
||||
state.set('next', next);
|
||||
}
|
||||
});
|
||||
|
||||
const paginateResults = (state: State, searchType: SearchFilter, results: APIEntity, searchTerm: string, next: string | null) =>
|
||||
const paginateResults = (state: State, searchType: SearchFilter, results: APIEntity, searchTerm: string) =>
|
||||
state.withMutations(state => {
|
||||
if (state.value === searchTerm) {
|
||||
if (state.submittedValue === searchTerm) {
|
||||
state.setIn(['results', `${searchType}HasMore`], results[searchType].length >= 20);
|
||||
state.setIn(['results', `${searchType}Loaded`], true);
|
||||
state.set('next', next);
|
||||
state.updateIn(['results', searchType], items => {
|
||||
const data = results[searchType];
|
||||
// Hashtags are a list of maps. Others are IDs.
|
||||
|
@ -103,15 +99,12 @@ const handleSubmitted = (state: State, value: string) =>
|
|||
state.set('submittedValue', value);
|
||||
});
|
||||
|
||||
const search = (state = ReducerRecord(), action: AnyAction) => {
|
||||
const search = (state = ReducerRecord(), action: SearchAction | ComposeAction) => {
|
||||
switch (action.type) {
|
||||
case SEARCH_CHANGE:
|
||||
return state.set('value', action.value);
|
||||
case SEARCH_CLEAR:
|
||||
return ReducerRecord();
|
||||
case SEARCH_RESULTS_CLEAR:
|
||||
return state.merge({
|
||||
value: '',
|
||||
results: ResultsRecord(),
|
||||
submitted: false,
|
||||
submittedValue: '',
|
||||
|
@ -126,13 +119,13 @@ const search = (state = ReducerRecord(), action: AnyAction) => {
|
|||
case SEARCH_FETCH_REQUEST:
|
||||
return handleSubmitted(state, action.value);
|
||||
case SEARCH_FETCH_SUCCESS:
|
||||
return importResults(state, action.results, action.searchTerm, action.searchType, action.next);
|
||||
return importResults(state, action.results, action.searchTerm, action.searchType);
|
||||
case SEARCH_FILTER_SET:
|
||||
return state.set('filter', action.value);
|
||||
case SEARCH_EXPAND_REQUEST:
|
||||
return state.setIn(['results', `${action.searchType}Loaded`], false);
|
||||
case SEARCH_EXPAND_SUCCESS:
|
||||
return paginateResults(state, action.searchType, action.results, action.searchTerm, action.next);
|
||||
return paginateResults(state, action.searchType, action.results, action.searchTerm);
|
||||
case SEARCH_ACCOUNT_SET:
|
||||
if (!action.accountId) return state.merge({
|
||||
results: ResultsRecord(),
|
||||
|
|
Loading…
Reference in a new issue