From d1cb74febea9725b028dca372e1b7d7e157ad24d Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 14 Nov 2023 19:51:23 -0600 Subject: Desktop user autocomplete search (#1906) * Fix notification provider order, add comments * Remove log * Add actor typeahead handling * Trim down desktop search styles and hooks * Clean up moderation --- src/view/shell/desktop/Search.tsx | 163 ++++++++++++++++++++++++++++---------- 1 file changed, 119 insertions(+), 44 deletions(-) (limited to 'src/view/shell/desktop/Search.tsx') diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index f54858b8a..115e0f7ae 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -1,59 +1,136 @@ import React from 'react' -import {TextInput, View, StyleSheet, TouchableOpacity} from 'react-native' +import { + ViewStyle, + TextInput, + View, + StyleSheet, + TouchableOpacity, +} from 'react-native' import {useNavigation, StackActions} from '@react-navigation/native' -import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete' +import { + AppBskyActorDefs, + moderateProfile, + ProfileModeration, +} from '@atproto/api' import {observer} from 'mobx-react-lite' -import {useStores} from 'state/index' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {s} from '#/lib/styles' +import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {sanitizeHandle} from '#/lib/strings/handles' +import {makeProfileLink} from '#/lib/routes/links' +import {Link} from '#/view/com/util/Link' import {usePalette} from 'lib/hooks/usePalette' import {MagnifyingGlassIcon2} from 'lib/icons' import {NavigationProp} from 'lib/routes/types' -import {ProfileCard} from 'view/com/profile/ProfileCard' import {Text} from 'view/com/util/text/Text' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {useActorSearch} from '#/state/queries/actor-autocomplete' +import {useModerationOpts} from '#/state/queries/preferences' -export const DesktopSearch = observer(function DesktopSearch() { - const store = useStores() +export function SearchResultCard({ + profile, + style, + moderation, +}: { + profile: AppBskyActorDefs.ProfileViewBasic + style: ViewStyle + moderation: ProfileModeration +}) { const pal = usePalette('default') + + return ( + + + + + + {sanitizeDisplayName( + profile.displayName || sanitizeHandle(profile.handle), + moderation.profile, + )} + + + {sanitizeHandle(profile.handle, '@')} + + + + + ) +} + +export const DesktopSearch = observer(function DesktopSearch() { const {_} = useLingui() - const textInput = React.useRef(null) + const pal = usePalette('default') + const navigation = useNavigation() + const searchDebounceTimeout = React.useRef( + undefined, + ) const [isInputFocused, setIsInputFocused] = React.useState(false) const [query, setQuery] = React.useState('') - const autocompleteView = React.useMemo( - () => new UserAutocompleteModel(store), - [store], - ) - const navigation = useNavigation() + const [searchResults, setSearchResults] = React.useState< + AppBskyActorDefs.ProfileViewBasic[] + >([]) - // initial setup - React.useEffect(() => { - if (store.me.did) { - autocompleteView.setup() - } - }, [autocompleteView, store.me.did]) + const moderationOpts = useModerationOpts() + const search = useActorSearch() - const onChangeQuery = React.useCallback( - (text: string) => { + const onChangeText = React.useCallback( + async (text: string) => { setQuery(text) + if (text.length > 0 && isInputFocused) { - autocompleteView.setActive(true) - autocompleteView.setPrefix(text) + if (searchDebounceTimeout.current) + clearTimeout(searchDebounceTimeout.current) + + searchDebounceTimeout.current = setTimeout(async () => { + const results = await search({query: text}) + + if (results) { + setSearchResults(results) + } + }, 300) } else { - autocompleteView.setActive(false) + if (searchDebounceTimeout.current) + clearTimeout(searchDebounceTimeout.current) + setSearchResults([]) } }, - [setQuery, autocompleteView, isInputFocused], + [setQuery, isInputFocused, search, setSearchResults], ) const onPressCancelSearch = React.useCallback(() => { - setQuery('') - autocompleteView.setActive(false) - }, [setQuery, autocompleteView]) + onChangeText('') + }, [onChangeText]) const onSubmit = React.useCallback(() => { navigation.dispatch(StackActions.push('Search', {q: query})) - autocompleteView.setActive(false) - }, [query, navigation, autocompleteView]) + }, [query, navigation]) return ( @@ -66,7 +143,6 @@ export const DesktopSearch = observer(function DesktopSearch() { /> setIsInputFocused(true)} onBlur={() => setIsInputFocused(false)} - onChangeText={onChangeQuery} + onChangeText={onChangeText} onSubmitEditing={onSubmit} accessibilityRole="search" accessibilityLabel={_(msg`Search`)} @@ -100,16 +176,19 @@ export const DesktopSearch = observer(function DesktopSearch() { {query !== '' && ( - {autocompleteView.suggestions.length ? ( - <> - {autocompleteView.suggestions.map((item, i) => ( - - ))} - + {searchResults.length && moderationOpts ? ( + searchResults.map((item, i) => ( + + )) ) : ( - No results found for {autocompleteView.prefix} + No results found for {query} )} @@ -153,15 +232,11 @@ const styles = StyleSheet.create({ paddingVertical: 7, }, resultsContainer: { - // @ts-ignore supported by web - // position: 'fixed', marginTop: 10, - flexDirection: 'column', width: 300, borderWidth: 1, borderRadius: 6, - paddingVertical: 4, }, noResults: { textAlign: 'center', -- cgit 1.4.1