11import React from "react" ;
2- import PropTypes from "prop-types" ;
32
43import _isEqual from "lodash/fp/isEqual" ;
54import SearchBox from "./SearchBox" ;
@@ -11,69 +10,81 @@ import { Checkbox } from "../UtilComponents/Checkbox";
1110import { KeyboardShortcut } from "../UtilComponents/KeyboardShortcut" ;
1211import Select from "react-select" ;
1312
14- import { searchResultsPropType } from "../../types/SearchResults" ;
1513import _get from "lodash/get" ;
1614import _debounce from "lodash/debounce" ;
1715
18- import { suggestedFieldsPropType } from "../../types/SuggestedFields" ;
1916import { keyboardShortcuts } from "../../util/keyboardShortcuts" ;
2017import SearchVisualizations from "./SearchVisualizations" ;
2118import { calculateSearchTitle } from "../UtilComponents/documentTitle" ;
2219
2320import { connect } from "react-redux" ;
2421import { bindActionCreators } from "redux" ;
25-
26- import { updateSearchText } from "../../actions/urlParams/updateSearchQuery" ;
22+ import { AnyAction } from "redux" ;
23+ import { ThunkDispatch } from "redux-thunk" ;
24+
25+ import { GiantState } from "../../types/redux/GiantState" ;
26+ import {
27+ updateSearchText ,
28+ updateSearchQueryFilters ,
29+ updatePage ,
30+ updatePageSize ,
31+ updateSortBy ,
32+ } from "../../actions/urlParams/updateSearchQuery" ;
2733import { performSearch } from "../../actions/search/performSearch" ;
2834import { clearSearch } from "../../actions/search/clearSearch" ;
29- import { updateSearchQueryFilters } from "../../actions/urlParams/updateSearchQuery" ;
30- import { updatePage } from "../../actions/urlParams/updateSearchQuery" ;
31- import { updatePageSize } from "../../actions/urlParams/updateSearchQuery" ;
32- import { updateSortBy } from "../../actions/urlParams/updateSearchQuery" ;
3335import { getSuggestedFields } from "../../actions/search/getSuggestedFields" ;
3436import { resetResource } from "../../actions/resources/getResource" ;
3537import { updatePreference } from "../../actions/preferences" ;
3638
37- class Search extends React . Component {
38- static propTypes = {
39- urlParams : PropTypes . shape ( {
40- q : PropTypes . string ,
41- page : PropTypes . any ,
42- pageSize : PropTypes . any ,
43- sortBy : PropTypes . string ,
44- filters : PropTypes . any ,
45- } ) ,
46- lastUri : PropTypes . string ,
47- updateSearchText : PropTypes . func . isRequired ,
48- updatePage : PropTypes . func . isRequired ,
49- updatePageSize : PropTypes . func . isRequired ,
50- updateSortBy : PropTypes . func . isRequired ,
51- performSearch : PropTypes . func . isRequired ,
52- clearSearch : PropTypes . func . isRequired ,
53- updateSearchQueryFilters : PropTypes . func . isRequired ,
54- resetResource : PropTypes . func . isRequired ,
55- getSuggestedFields : PropTypes . func . isRequired ,
56- updatePreference : PropTypes . func . isRequired ,
57- preferences : PropTypes . object ,
58- search : PropTypes . shape ( {
59- isSearchInProgress : PropTypes . bool . isRequired ,
60- currentQuery : PropTypes . object ,
61- currentResults : searchResultsPropType ,
62- suggestedFields : PropTypes . arrayOf ( suggestedFieldsPropType ) ,
63- searchFailed : PropTypes . bool ,
64- } ) . isRequired ,
65- } ;
39+ interface StateProps {
40+ urlParams : GiantState [ "urlParams" ] ;
41+ search : GiantState [ "search" ] ;
42+ lastUri : string | undefined ;
43+ preferences : GiantState [ "app" ] [ "preferences" ] ;
44+ }
45+
46+ interface DispatchProps {
47+ getSuggestedFields : ( ) => void ;
48+ updateSearchText : ( text : string ) => void ;
49+ updatePage : ( page : string ) => void ;
50+ updatePageSize : ( pageSize : string ) => void ;
51+ updateSortBy : ( sortBy : string ) => void ;
52+ performSearch : ( query : GiantState [ "urlParams" ] ) => void ;
53+ clearSearch : ( ) => void ;
54+ updateSearchQueryFilters : ( filters : object ) => void ;
55+ resetResource : ( ) => void ;
56+ updatePreference : ( key : string , value : unknown ) => void ;
57+ }
58+
59+ type SearchProps = StateProps & DispatchProps ;
60+
61+ interface SearchState {
62+ visibleText : string ;
63+ }
64+
65+ interface SelectOption {
66+ value : string ;
67+ label : string ;
68+ }
6669
67- state = {
70+ interface SearchBoxHandle {
71+ focus ( ) : void ;
72+ select ( ) : void ;
73+ }
74+
75+ class Search extends React . Component < SearchProps , SearchState > {
76+ searchBox : SearchBoxHandle | null = null ;
77+
78+ state : SearchState = {
6879 visibleText : "" ,
6980 } ;
7081
71- selectSearchBox = ( e ) => {
82+ selectSearchBox = ( e : KeyboardEvent ) => {
7283 e . preventDefault ( ) ;
73- this . searchBox . focus ( ) ;
84+ this . searchBox ? .focus ( ) ;
7485 } ;
7586
76- clearSearch = ( e ) => {
87+ clearSearch = ( e : React . MouseEvent < HTMLButtonElement > ) => {
7788 e . preventDefault ( ) ;
7889
7990 this . updateVisibleText ( "" ) ;
@@ -82,25 +93,27 @@ class Search extends React.Component {
8293 this . props . updateSearchQueryFilters ( { } ) ;
8394 this . props . updatePage ( "1" ) ;
8495 this . setState ( { visibleText : "" } ) ;
85- this . searchBox . select ( ) ;
96+ this . searchBox ? .select ( ) ;
8697 } ;
8798
88- debouncedUpdate = _debounce ( ( text ) => {
99+ debouncedUpdate = _debounce ( ( text : string ) => {
89100 if ( text !== this . props . urlParams . q ) {
90101 this . props . updatePage ( "1" ) ;
91102 }
92103 this . props . updateSearchText ( text ) ;
93104
94- this . triggerSearch ( this . props . urlParams ) ;
105+ // Use the freshly-typed text rather than this.props.urlParams.q, which
106+ // is the pre-dispatch value and would lag by one Enter press.
107+ this . triggerSearch ( { ...this . props . urlParams , q : text } ) ;
95108 } , 500 ) ;
96109
97- updateVisibleText = ( text ) => {
110+ updateVisibleText = ( text : string ) => {
98111 this . setState ( {
99112 visibleText : text ,
100113 } ) ;
101114 } ;
102115
103- triggerSearch ( query ) {
116+ triggerSearch ( query : GiantState [ "urlParams" ] ) {
104117 if ( query . q ) {
105118 this . props . resetResource ( ) ;
106119 this . props . performSearch ( query ) ;
@@ -131,7 +144,7 @@ class Search extends React.Component {
131144 document . title = calculateSearchTitle ( this . props . search . currentQuery ) ;
132145 }
133146
134- UNSAFE_componentWillReceiveProps ( props ) {
147+ UNSAFE_componentWillReceiveProps ( props : SearchProps ) {
135148 const before = {
136149 filters : props . urlParams . filters ,
137150 page : props . urlParams . page ,
@@ -159,7 +172,7 @@ class Search extends React.Component {
159172 document . title = "Giant" ;
160173 }
161174
162- pageSelectCallback = ( page ) => {
175+ pageSelectCallback = ( page : number ) => {
163176 this . props . updatePage ( page . toString ( ) ) ;
164177 } ;
165178
@@ -179,9 +192,33 @@ class Search extends React.Component {
179192
180193 renderControls ( ) {
181194 // TODO replace with user preferences for page size
182- const pageSize = this . props . urlParams . pageSize || "100" ;
195+ const pageSizeValue = this . props . urlParams . pageSize ?? "100" ;
183196 // TODO replace with user preferences for sort order
184- const sortBy = this . props . urlParams . sortBy || "relevance" ;
197+ const sortByValue = this . props . urlParams . sortBy || "relevance" ;
198+
199+ const sortByOptions : SelectOption [ ] = [
200+ { value : "relevance" , label : "Sort by relevance" } ,
201+ { value : "size-asc" , label : "Sort by size (smallest first)" } ,
202+ { value : "size-desc" , label : "Sort by size (largest first)" } ,
203+ {
204+ value : "date-created-asc" ,
205+ label : "Sort by date created (oldest first)" ,
206+ } ,
207+ {
208+ value : "date-created-desc" ,
209+ label : "Sort by date created (newest first)" ,
210+ } ,
211+ ] ;
212+ const pageSizeOptions : SelectOption [ ] = [
213+ { value : "25" , label : "25 results per page" } ,
214+ { value : "50" , label : "50 results per page" } ,
215+ { value : "100" , label : "100 results per page" } ,
216+ ] ;
217+
218+ const currentSortBy = sortByOptions . find ( ( o ) => o . value === sortByValue ) ;
219+ const currentPageSize = pageSizeOptions . find (
220+ ( o ) => o . value === pageSizeValue ,
221+ ) ;
185222
186223 return (
187224 < div className = "search__controls" >
@@ -199,37 +236,25 @@ class Search extends React.Component {
199236 </ Checkbox >
200237 < Select
201238 className = "search__control"
202- value = { sortBy }
203- options = { [
204- { value : "relevance" , label : "Sort by relevance" } ,
205- { value : "size-asc" , label : "Sort by size (smallest first)" } ,
206- { value : "size-desc" , label : "Sort by size (largest first)" } ,
207- {
208- value : "date-created-asc" ,
209- label : "Sort by date created (oldest first)" ,
210- } ,
211- {
212- value : "date-created-desc" ,
213- label : "Sort by date created (newest first)" ,
214- } ,
215- ] }
239+ value = { currentSortBy }
240+ options = { sortByOptions }
216241 onChange = { ( v ) => {
242+ const option = v as SelectOption | null ;
243+ if ( ! option ) return ;
217244 this . props . updatePage ( "1" ) ;
218- this . props . updateSortBy ( v . value ) ;
245+ this . props . updateSortBy ( option . value ) ;
219246 } }
220247 clearable = { false }
221248 />
222249 < Select
223250 className = "search__control"
224- value = { pageSize }
225- options = { [
226- { value : "25" , label : "25 results per page" } ,
227- { value : "50" , label : "50 results per page" } ,
228- { value : "100" , label : "100 results per page" } ,
229- ] }
251+ value = { currentPageSize }
252+ options = { pageSizeOptions }
230253 onChange = { ( v ) => {
254+ const option = v as SelectOption | null ;
255+ if ( ! option ) return ;
231256 this . props . updatePage ( "1" ) ;
232- this . props . updatePageSize ( v . value ) ;
257+ this . props . updatePageSize ( option . value ) ;
233258 } }
234259 clearable = { false }
235260 />
@@ -267,10 +292,9 @@ class Search extends React.Component {
267292 func = { this . selectSearchBox }
268293 />
269294 < SearchBox
270- ref = { ( input ) => ( this . searchBox = input ) }
295+ ref = { ( input : SearchBoxHandle | null ) => ( this . searchBox = input ) }
271296 updateVisibleText = { this . updateVisibleText }
272297 resetQuery = { this . clearSearch }
273- addQuery = { this . addQuery }
274298 q = { this . state . visibleText }
275299 isSearchInProgress = { this . props . search . isSearchInProgress }
276300 suggestedFields = { this . props . search . suggestedFields }
@@ -308,7 +332,7 @@ class Search extends React.Component {
308332 }
309333}
310334
311- function mapStateToProps ( state ) {
335+ function mapStateToProps ( state : GiantState ) : StateProps {
312336 return {
313337 urlParams : state . urlParams ,
314338 search : state . search ,
@@ -317,7 +341,9 @@ function mapStateToProps(state) {
317341 } ;
318342}
319343
320- function mapDispatchToProps ( dispatch ) {
344+ function mapDispatchToProps (
345+ dispatch : ThunkDispatch < GiantState , undefined , AnyAction > ,
346+ ) : DispatchProps {
321347 return {
322348 getSuggestedFields : bindActionCreators ( getSuggestedFields , dispatch ) ,
323349 updateSearchText : bindActionCreators ( updateSearchText , dispatch ) ,
@@ -335,4 +361,12 @@ function mapDispatchToProps(dispatch) {
335361 } ;
336362}
337363
338- export default connect ( mapStateToProps , mapDispatchToProps ) ( Search ) ;
364+ export default connect <
365+ StateProps ,
366+ DispatchProps ,
367+ Record < string , never > ,
368+ GiantState
369+ > (
370+ mapStateToProps ,
371+ mapDispatchToProps ,
372+ ) ( Search ) ;
0 commit comments