|
1 | | -import * as c from '@frogpond/colors' |
| 1 | +import React from 'react' |
| 2 | +import {View, SectionList, SafeAreaView, StyleSheet} from 'react-native' |
| 3 | + |
| 4 | +import {ListRow, ListSectionHeader, ListSeparator, Title} from '@frogpond/lists' |
| 5 | +import {Column} from '@frogpond/layout' |
2 | 6 | import {LoadingView, NoticeView} from '@frogpond/notice' |
3 | | -import {SearchButton} from '@frogpond/navigation-buttons' |
| 7 | +import * as c from '@frogpond/colors' |
4 | 8 | import {debounceSearch} from '@frogpond/use-debounce' |
| 9 | +import {NetworkLoggerButton} from '@frogpond/navigation-buttons' |
| 10 | + |
| 11 | +import {ServerRoute, useServerRoutes} from './query' |
| 12 | +import {ChangeTextEvent} from '../../../../navigation/types' |
5 | 13 | import {useNavigation} from '@react-navigation/native' |
6 | 14 | import {NativeStackNavigationOptions} from '@react-navigation/native-stack' |
7 | | -import {fromPairs} from 'lodash' |
8 | | -import * as React from 'react' |
9 | | -import {ScrollView, StyleSheet, View} from 'react-native' |
10 | | -import {ChangeTextEvent} from '../../../navigation/types' |
11 | | -import {useAppSelector} from '../../../redux' |
12 | | -import { |
13 | | - selectRecentFilters, |
14 | | - selectRecentSearches, |
15 | | -} from '../../../redux/parts/courses' |
16 | | -import {RecentItemsList} from '../components/recents-list' |
17 | | -import {useFilters} from './lib/build-filters' |
18 | | - |
19 | | -export const NavigationOptions: NativeStackNavigationOptions = { |
20 | | - title: 'Course Catalog', |
21 | | -} |
22 | 15 |
|
23 | | -export const CourseSearchView = (): JSX.Element => { |
| 16 | +export const APITestView = (): JSX.Element => { |
24 | 17 | let navigation = useNavigation() |
25 | 18 |
|
26 | | - let {data: basicFilters = [], isLoading, error} = useFilters() |
| 19 | + let [filterPath, setFilterPath] = React.useState<string>('') |
27 | 20 |
|
28 | | - let recentFilters = useAppSelector(selectRecentFilters) |
29 | | - let recentSearches = useAppSelector(selectRecentSearches) |
30 | | - |
31 | | - let [typedQuery, setTypedQuery] = React.useState('') |
| 21 | + let { |
| 22 | + data: groupedRoutes = [], |
| 23 | + error: routesError, |
| 24 | + isLoading: isRoutesLoading, |
| 25 | + isError: isRoutesError, |
| 26 | + refetch: routesRefetch, |
| 27 | + } = useServerRoutes() |
32 | 28 |
|
33 | 29 | React.useLayoutEffect(() => { |
34 | 30 | navigation.setOptions({ |
35 | | - headerRight: () => ( |
36 | | - <SearchButton |
37 | | - onPress={() => |
38 | | - navigation.navigate('CourseSearchResults', {initialQuery: ''}) |
39 | | - } |
40 | | - title="Browse" |
41 | | - /> |
42 | | - ), |
43 | 31 | headerSearchBarOptions: { |
44 | | - barTintColor: c.quaternarySystemFill, |
45 | | - onChangeText: (event: ChangeTextEvent) => { |
46 | | - setTypedQuery(event.nativeEvent.text) |
47 | | - }, |
| 32 | + autoCapitalize: 'none', |
| 33 | + barTintColor: c.systemFill, |
| 34 | + // android-only |
| 35 | + autoFocus: true, |
| 36 | + hideNavigationBar: false, |
| 37 | + hideWhenScrolling: false, |
| 38 | + onChangeText: (event: ChangeTextEvent) => |
| 39 | + setFilterPath(event.nativeEvent.text), |
| 40 | + placeholder: '/path/to/uri', |
48 | 41 | }, |
| 42 | + headerRight: () => <NetworkLoggerButton />, |
49 | 43 | }) |
50 | | - }, [navigation, typedQuery]) |
51 | | - |
52 | | - let showSearchResult = React.useCallback( |
53 | | - (query: string) => { |
54 | | - navigation.navigate('CourseSearchResults', {initialQuery: query}) |
55 | | - }, |
56 | | - [navigation], |
57 | | - ) |
58 | | - |
59 | | - React.useEffect(() => { |
60 | | - debounceSearch(typedQuery, () => { |
61 | | - showSearchResult(typedQuery) |
| 44 | + }, [navigation]) |
| 45 | + |
| 46 | + let showSearchResult = React.useCallback(() => { |
| 47 | + navigation.navigate('APITestDetail', { |
| 48 | + query: { |
| 49 | + displayName: filterPath, |
| 50 | + path: filterPath, |
| 51 | + params: [], |
| 52 | + }, |
62 | 53 | }) |
63 | | - }, [showSearchResult, typedQuery]) |
64 | | - |
65 | | - let onRecentFilterPress = React.useCallback( |
66 | | - (text: string) => { |
67 | | - let selectedFilterCombo = recentFilters.find( |
68 | | - (f) => f.description === text, |
69 | | - ) |
| 54 | + }, [filterPath, navigation]) |
70 | 55 |
|
71 | | - let selectedFilters = basicFilters |
72 | | - if (selectedFilterCombo) { |
73 | | - let filterLookup = fromPairs( |
74 | | - selectedFilterCombo.filters.map((f) => [f.key, f]), |
75 | | - ) |
76 | | - selectedFilters = basicFilters.map((f) => filterLookup[f.key] || f) |
77 | | - } |
78 | | - |
79 | | - navigation.navigate('CourseSearchResults', { |
80 | | - initialFilters: selectedFilters, |
81 | | - }) |
82 | | - }, |
83 | | - [basicFilters, navigation, recentFilters], |
| 56 | + React.useEffect(() => { |
| 57 | + debounceSearch(filterPath, () => showSearchResult()) |
| 58 | + }, [filterPath, navigation, showSearchResult]) |
| 59 | + |
| 60 | + const renderItem = React.useCallback( |
| 61 | + (item: ServerRoute) => ( |
| 62 | + <SafeAreaView> |
| 63 | + <ListRow |
| 64 | + fullWidth={false} |
| 65 | + onPress={() => navigation.navigate('APITestDetail', {query: item})} |
| 66 | + style={styles.serverRouteRow} |
| 67 | + > |
| 68 | + <Column flex={1}> |
| 69 | + <Title lines={1}>{item.displayName}</Title> |
| 70 | + </Column> |
| 71 | + </ListRow> |
| 72 | + </SafeAreaView> |
| 73 | + ), |
| 74 | + [navigation], |
84 | 75 | ) |
85 | 76 |
|
86 | | - if (isLoading) { |
87 | | - return <LoadingView text="Loading Course Data…" /> |
88 | | - } |
89 | | - |
90 | | - if (error) { |
91 | | - return ( |
92 | | - <NoticeView |
93 | | - buttonText="Try Again" |
94 | | - // onPress={refetch} // TODO: implement refetch here |
95 | | - text={`A problem occured while loading: ${error}`} |
96 | | - /> |
97 | | - ) |
98 | | - } |
99 | | - |
100 | | - let recentFilterDescriptions = recentFilters.map((f) => f.description) |
101 | | - |
102 | 77 | return ( |
103 | | - <View style={[styles.container, styles.common]}> |
104 | | - <ScrollView |
105 | | - // needed for handling native searchbar alignment |
106 | | - contentInsetAdjustmentBehavior="automatic" |
107 | | - keyboardDismissMode="interactive" |
108 | | - style={[styles.common, styles.bottomContainer]} |
109 | | - > |
110 | | - <RecentItemsList |
111 | | - emptyHeader="No recent searches" |
112 | | - emptyText="Your recent searches will appear here." |
113 | | - items={recentSearches} |
114 | | - onItemPress={showSearchResult} |
115 | | - title="Recent" |
| 78 | + <View style={styles.serverRouteContainer}> |
| 79 | + {isRoutesLoading ? ( |
| 80 | + <LoadingView /> |
| 81 | + ) : isRoutesError && routesError instanceof Error ? ( |
| 82 | + <NoticeView |
| 83 | + buttonText="Try Again" |
| 84 | + onPress={routesRefetch} |
| 85 | + text={`A problem occured while loading: ${routesError}`} |
116 | 86 | /> |
117 | | - <RecentItemsList |
118 | | - emptyHeader="No recent filter combinations" |
119 | | - emptyText="Your recent filter combinations will appear here." |
120 | | - items={recentFilterDescriptions} |
121 | | - onItemPress={onRecentFilterPress} |
122 | | - title="Browse" |
| 87 | + ) : !groupedRoutes ? ( |
| 88 | + <NoticeView text="No routes were found." /> |
| 89 | + ) : ( |
| 90 | + <SectionList |
| 91 | + ItemSeparatorComponent={ListSeparator} |
| 92 | + contentInsetAdjustmentBehavior="automatic" |
| 93 | + keyExtractor={(item, index) => `${item.path}-${index}`} |
| 94 | + keyboardDismissMode="on-drag" |
| 95 | + keyboardShouldPersistTaps="never" |
| 96 | + onRefresh={routesRefetch} |
| 97 | + refreshing={isRoutesLoading} |
| 98 | + renderItem={({item}) => renderItem(item)} |
| 99 | + renderSectionHeader={({section: {title}}) => ( |
| 100 | + <ListSectionHeader title={title} /> |
| 101 | + )} |
| 102 | + sections={groupedRoutes} |
123 | 103 | /> |
124 | | - </ScrollView> |
| 104 | + )} |
125 | 105 | </View> |
126 | 106 | ) |
127 | 107 | } |
128 | 108 |
|
129 | | -let styles = StyleSheet.create({ |
130 | | - bottomContainer: { |
131 | | - paddingTop: 12, |
132 | | - }, |
133 | | - container: { |
| 109 | +const styles = StyleSheet.create({ |
| 110 | + serverRouteContainer: { |
134 | 111 | flex: 1, |
135 | | - }, |
136 | | - common: { |
137 | 112 | backgroundColor: c.systemBackground, |
138 | 113 | }, |
| 114 | + serverRouteRow: { |
| 115 | + flexDirection: 'row', |
| 116 | + alignItems: 'center', |
| 117 | + }, |
139 | 118 | }) |
| 119 | + |
| 120 | +export const NavigationOptions: NativeStackNavigationOptions = { |
| 121 | + title: 'API Tester', |
| 122 | +} |
0 commit comments