pl-fe: Remove location search reducer
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
df3b5dfc35
commit
6c61f749fa
10 changed files with 43 additions and 113 deletions
|
@ -679,7 +679,7 @@ const selectComposeSuggestion = (composeId: string, position: number, token: str
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
let completion = '', startPosition = position;
|
||||
|
||||
if (typeof suggestion === 'object' && suggestion.id) {
|
||||
if (typeof suggestion === 'object' && 'id' in suggestion) {
|
||||
completion = isNativeEmoji(suggestion) ? suggestion.native : suggestion.colons;
|
||||
startPosition = position - 1;
|
||||
|
||||
|
|
|
@ -9,10 +9,6 @@ import { STATUS_FETCH_SOURCE_FAIL, STATUS_FETCH_SOURCE_REQUEST, STATUS_FETCH_SOU
|
|||
import type { Account, CreateEventParams, Location, MediaAttachment, PaginatedResponse, Status } from 'pl-api';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const LOCATION_SEARCH_REQUEST = 'LOCATION_SEARCH_REQUEST' as const;
|
||||
const LOCATION_SEARCH_SUCCESS = 'LOCATION_SEARCH_SUCCESS' as const;
|
||||
const LOCATION_SEARCH_FAIL = 'LOCATION_SEARCH_FAIL' as const;
|
||||
|
||||
const EVENT_SUBMIT_REQUEST = 'EVENT_SUBMIT_REQUEST' as const;
|
||||
const EVENT_SUBMIT_SUCCESS = 'EVENT_SUBMIT_SUCCESS' as const;
|
||||
const EVENT_SUBMIT_FAIL = 'EVENT_SUBMIT_FAIL' as const;
|
||||
|
@ -73,18 +69,6 @@ const messages = defineMessages({
|
|||
rejected: { id: 'compose_event.participation_requests.reject_success', defaultMessage: 'User rejected' },
|
||||
});
|
||||
|
||||
const locationSearch = (query: string, signal?: AbortSignal) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: LOCATION_SEARCH_REQUEST, query });
|
||||
return getClient(getState).search.searchLocation(query, { signal }).then((locations) => {
|
||||
dispatch({ type: LOCATION_SEARCH_SUCCESS, locations });
|
||||
return locations;
|
||||
}).catch(error => {
|
||||
dispatch({ type: LOCATION_SEARCH_FAIL });
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
const submitEvent = ({
|
||||
statusId,
|
||||
name,
|
||||
|
@ -509,9 +493,6 @@ type EventsAction =
|
|||
| EventFormSetAction;
|
||||
|
||||
export {
|
||||
LOCATION_SEARCH_REQUEST,
|
||||
LOCATION_SEARCH_SUCCESS,
|
||||
LOCATION_SEARCH_FAIL,
|
||||
EVENT_SUBMIT_REQUEST,
|
||||
EVENT_SUBMIT_SUCCESS,
|
||||
EVENT_SUBMIT_FAIL,
|
||||
|
@ -547,7 +528,6 @@ export {
|
|||
JOINED_EVENTS_FETCH_REQUEST,
|
||||
JOINED_EVENTS_FETCH_SUCCESS,
|
||||
JOINED_EVENTS_FETCH_FAIL,
|
||||
locationSearch,
|
||||
submitEvent,
|
||||
submitEventRequest,
|
||||
submitEventSuccess,
|
||||
|
|
16
packages/pl-fe/src/api/hooks/search/use-search-location.ts
Normal file
16
packages/pl-fe/src/api/hooks/search/use-search-location.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useClient } from 'pl-fe/hooks/use-client';
|
||||
|
||||
const useSearchLocation = (query: string) => {
|
||||
const client = useClient();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['search', 'location', query],
|
||||
queryFn: ({ signal }) => client.search.searchLocation(query, { signal }),
|
||||
gcTime: 60 * 1000,
|
||||
enabled: !!query.trim(),
|
||||
});
|
||||
};
|
||||
|
||||
export { useSearchLocation };
|
|
@ -8,15 +8,18 @@ import Portal from 'pl-fe/components/ui/portal';
|
|||
import AutosuggestAccount from 'pl-fe/features/compose/components/autosuggest-account';
|
||||
import { textAtCursorMatchesToken } from 'pl-fe/utils/suggestions';
|
||||
|
||||
import AutosuggestLocation from './autosuggest-location';
|
||||
|
||||
import type { Location } from 'pl-api';
|
||||
import type { Menu, MenuItem } from 'pl-fe/components/dropdown-menu';
|
||||
import type { InputThemes } from 'pl-fe/components/ui/input';
|
||||
import type { Emoji } from 'pl-fe/features/emoji';
|
||||
|
||||
type AutoSuggestion = string | Emoji;
|
||||
type AutoSuggestion = string | Emoji | Location;
|
||||
|
||||
interface IAutosuggestInput extends Pick<React.HTMLAttributes<HTMLInputElement>, 'onChange' | 'onKeyUp' | 'onKeyDown'> {
|
||||
value: string;
|
||||
suggestions: Array<any>;
|
||||
suggestions: Array<AutoSuggestion>;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
onSuggestionSelected: (tokenStart: number, lastToken: string | null, suggestion: AutoSuggestion) => void;
|
||||
|
@ -29,7 +32,6 @@ interface IAutosuggestInput extends Pick<React.HTMLAttributes<HTMLInputElement>,
|
|||
searchTokens?: string[];
|
||||
maxLength?: number;
|
||||
menu?: Menu;
|
||||
renderSuggestion?: React.FC<{ id: string }>;
|
||||
hidePortal?: boolean;
|
||||
theme?: InputThemes;
|
||||
}
|
||||
|
@ -165,16 +167,12 @@ const AutosuggestInput: React.FC<IAutosuggestInput> = ({
|
|||
const renderSuggestion = (suggestion: AutoSuggestion, i: number) => {
|
||||
let inner, key;
|
||||
|
||||
if (props.renderSuggestion && typeof suggestion === 'string') {
|
||||
const RenderSuggestion = props.renderSuggestion;
|
||||
inner = <RenderSuggestion id={suggestion} />;
|
||||
key = suggestion;
|
||||
if (typeof suggestion === 'object' && 'origin_id' in suggestion) {
|
||||
inner = <AutosuggestLocation location={suggestion} />;
|
||||
key = suggestion.origin_id;
|
||||
} else if (typeof suggestion === 'object') {
|
||||
inner = <AutosuggestEmoji emoji={suggestion} />;
|
||||
key = suggestion.id;
|
||||
} else if (suggestion[0] === '#') {
|
||||
inner = suggestion;
|
||||
key = suggestion;
|
||||
} else {
|
||||
inner = <AutosuggestAccount id={suggestion} />;
|
||||
key = suggestion;
|
||||
|
|
|
@ -4,7 +4,8 @@ import HStack from 'pl-fe/components/ui/hstack';
|
|||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
|
||||
import type { Location } from 'pl-api';
|
||||
|
||||
const buildingCommunityIcon = require('@tabler/icons/outline/building-community.svg');
|
||||
const homeIcon = require('@tabler/icons/outline/home-2.svg');
|
||||
|
@ -21,12 +22,10 @@ const ADDRESS_ICONS: Record<string, string> = {
|
|||
};
|
||||
|
||||
interface IAutosuggestLocation {
|
||||
id: string;
|
||||
location: Location;
|
||||
}
|
||||
|
||||
const AutosuggestLocation: React.FC<IAutosuggestLocation> = ({ id }) => {
|
||||
const location = useAppSelector((state) => state.locations.get(id));
|
||||
|
||||
const AutosuggestLocation: React.FC<IAutosuggestLocation> = ({ location }) => {
|
||||
if (!location) return null;
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { useDebounce } from '@uidotdev/usehooks';
|
||||
import clsx from 'clsx';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { locationSearch } from 'pl-fe/actions/events';
|
||||
import { useSearchLocation } from 'pl-fe/api/hooks/search/use-search-location';
|
||||
import AutosuggestInput, { AutoSuggestion } from 'pl-fe/components/autosuggest-input';
|
||||
import Icon from 'pl-fe/components/icon';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
|
||||
import AutosuggestLocation from './autosuggest-location';
|
||||
import type { Location } from 'pl-api';
|
||||
|
||||
const noOp = () => {};
|
||||
|
||||
|
@ -17,27 +16,24 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
interface ILocationSearch {
|
||||
onSelected: (locationId: string) => void;
|
||||
onSelected: (location: Location) => void;
|
||||
}
|
||||
|
||||
const LocationSearch: React.FC<ILocationSearch> = ({ onSelected }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const [locationIds, setLocationIds] = useState<Array<string>>([]);
|
||||
const controller = useRef(new AbortController());
|
||||
|
||||
const [value, setValue] = useState('');
|
||||
const debouncedValue = useDebounce(value, 400);
|
||||
const locationsQuery = useSearchLocation(debouncedValue);
|
||||
|
||||
const empty = !(value.length > 0);
|
||||
|
||||
const handleChange: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
||||
refreshCancelToken();
|
||||
handleLocationSearch(target.value);
|
||||
setValue(target.value);
|
||||
};
|
||||
|
||||
const handleSelected = (_tokenStart: number, _lastToken: string | null, suggestion: AutoSuggestion) => {
|
||||
if (typeof suggestion === 'string') {
|
||||
if (typeof suggestion === 'object' && 'origin_id' in suggestion) {
|
||||
onSelected(suggestion);
|
||||
}
|
||||
};
|
||||
|
@ -56,30 +52,6 @@ const LocationSearch: React.FC<ILocationSearch> = ({ onSelected }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const refreshCancelToken = () => {
|
||||
controller.current.abort();
|
||||
controller.current = new AbortController();
|
||||
};
|
||||
|
||||
const clearResults = () => {
|
||||
setLocationIds([]);
|
||||
};
|
||||
|
||||
const handleLocationSearch = useCallback(throttle(q => {
|
||||
dispatch(locationSearch(q, controller.current.signal))
|
||||
.then((locations: { origin_id: string }[]) => {
|
||||
const locationIds = locations.map(location => location.origin_id);
|
||||
setLocationIds(locationIds);
|
||||
})
|
||||
.catch(noOp);
|
||||
}, 900, { leading: true, trailing: true }), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (value === '') {
|
||||
clearResults();
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<AutosuggestInput
|
||||
|
@ -87,13 +59,12 @@ const LocationSearch: React.FC<ILocationSearch> = ({ onSelected }) => {
|
|||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
suggestions={locationIds}
|
||||
suggestions={locationsQuery.data || []}
|
||||
onSuggestionsFetchRequested={noOp}
|
||||
onSuggestionsClearRequested={noOp}
|
||||
onSuggestionSelected={handleSelected}
|
||||
searchTokens={[]}
|
||||
onKeyDown={handleKeyDown}
|
||||
renderSuggestion={AutosuggestLocation}
|
||||
/>
|
||||
<div role='button' tabIndex={0} className='absolute inset-y-0 right-0 flex cursor-pointer items-center px-3 rtl:left-0 rtl:right-auto' onClick={handleClear}>
|
||||
<Icon src={require('@tabler/icons/outline/search.svg')} className={clsx('size-5 text-gray-600', { 'hidden': !empty })} />
|
||||
|
|
|
@ -93,16 +93,8 @@ const EditEvent: React.FC<IEditEvent> = ({ statusId }) => {
|
|||
setApprovalRequired(target.checked);
|
||||
};
|
||||
|
||||
const onChangeLocation = (value: string | null) => {
|
||||
dispatch((_, getState) => {
|
||||
let location = null;
|
||||
|
||||
if (value) {
|
||||
location = getState().locations.get(value, null);
|
||||
}
|
||||
|
||||
setLocation(location);
|
||||
});
|
||||
const onChangeLocation = (location: Location | null) => {
|
||||
setLocation(location);
|
||||
};
|
||||
|
||||
const handleFiles = (files: FileList) => {
|
||||
|
|
|
@ -44,7 +44,9 @@ import AutosuggestAccount from '../../components/autosuggest-account';
|
|||
import { $createEmojiNode } from '../nodes/emoji-node';
|
||||
import { $createMentionNode } from '../nodes/mention-node';
|
||||
|
||||
import type { AutoSuggestion } from 'pl-fe/components/autosuggest-input';
|
||||
import type { Emoji } from 'pl-fe/features/emoji';
|
||||
|
||||
type AutoSuggestion = string | Emoji;
|
||||
|
||||
type QueryMatch = {
|
||||
leadOffset: number;
|
||||
|
|
|
@ -23,7 +23,6 @@ import instance from './instance';
|
|||
import listAdder from './list-adder';
|
||||
import listEditor from './list-editor';
|
||||
import lists from './lists';
|
||||
import locations from './locations';
|
||||
import me from './me';
|
||||
import meta from './meta';
|
||||
import notifications from './notifications';
|
||||
|
@ -65,7 +64,6 @@ const reducers = {
|
|||
listAdder,
|
||||
listEditor,
|
||||
lists,
|
||||
locations,
|
||||
me,
|
||||
meta,
|
||||
notifications,
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { Location } from 'pl-api';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { LOCATION_SEARCH_SUCCESS } from 'pl-fe/actions/events';
|
||||
|
||||
type State = ImmutableMap<any, Location>;
|
||||
|
||||
const initialState: State = ImmutableMap();
|
||||
|
||||
const normalizeLocations = (state: State, locations: Array<Location>) =>
|
||||
locations.reduce(
|
||||
(state: State, location: Location) => state.set(location.origin_id, location),
|
||||
state,
|
||||
);
|
||||
|
||||
const locations = (state: State = initialState, action: AnyAction): State => {
|
||||
switch (action.type) {
|
||||
case LOCATION_SEARCH_SUCCESS:
|
||||
return normalizeLocations(state, action.locations);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { locations as default };
|
Loading…
Reference in a new issue