2022-03-24 23:45:21 +00:00
import { useRouter } from 'next/router' ;
2022-03-26 11:21:36 +00:00
import React , { DependencyList , EffectCallback , Fragment , useEffect , useState } from 'react' ;
2022-03-16 21:02:34 +00:00
2022-03-25 23:51:11 +00:00
import ButtonsForStars from '../display/buttonsForStars' ;
import Form from '../display/form' ;
import MultiSelectPlatform from '../display/multiSelectPlatforms' ;
import { SliderElement } from '../display/slider' ;
2022-03-24 23:45:21 +00:00
import { platformsWithLabels , PlatformWithLabel } from '../platforms' ;
2022-03-16 21:02:34 +00:00
import searchAccordingToQueryData from '../worker/searchAccordingToQueryData' ;
2022-03-24 21:29:01 +00:00
2022-03-24 22:23:07 +00:00
interface QueryParametersWithoutNum {
2022-03-24 21:29:01 +00:00
query : string ;
starsThreshold : number ;
forecastsThreshold : number ;
forecastingPlatforms : PlatformWithLabel [ ] ;
}
2022-03-16 21:02:34 +00:00
2022-03-24 22:23:07 +00:00
export interface QueryParameters extends QueryParametersWithoutNum {
numDisplay : number ;
}
2022-03-24 21:29:01 +00:00
interface Props {
defaultResults : any ;
2022-03-25 23:51:11 +00:00
initialResults : any ;
initialQueryParameters : QueryParameters ;
2022-03-24 21:29:01 +00:00
hasSearchbar : boolean ;
hasCapture : boolean ;
hasAdvancedOptions : boolean ;
placeholder : string ;
displaySeeMoreHint : boolean ;
displayForecastsWrapper : ( opts : {
results : any ;
numDisplay : number ;
whichResultToDisplayAndCapture : number ;
showIdToggle : boolean ;
} ) = > React . ReactNode ;
}
2022-03-16 21:02:34 +00:00
2022-03-25 23:51:11 +00:00
export const defaultQueryParameters : QueryParametersWithoutNum = {
2022-03-24 23:45:21 +00:00
query : "" ,
starsThreshold : 2 ,
forecastsThreshold : 0 ,
forecastingPlatforms : platformsWithLabels , // weird key value format,
2022-03-16 21:02:34 +00:00
} ;
2022-03-25 23:51:11 +00:00
export const defaultNumDisplay = 21 ;
2022-03-16 21:02:34 +00:00
2022-03-26 11:21:36 +00:00
const useNoInitialEffect = ( effect : EffectCallback , deps : DependencyList ) = > {
const initial = React . useRef ( true ) ;
useEffect ( ( ) = > {
if ( initial . current ) {
initial . current = false ;
return ;
}
return effect ( ) ;
} , deps ) ;
} ;
2022-03-16 21:02:34 +00:00
/* Body */
2022-03-24 21:29:01 +00:00
const CommonDisplay : React.FC < Props > = ( {
2022-03-16 21:02:34 +00:00
defaultResults ,
2022-03-25 23:51:11 +00:00
initialResults ,
initialQueryParameters ,
2022-03-16 21:02:34 +00:00
hasSearchbar ,
hasCapture ,
hasAdvancedOptions ,
placeholder ,
displaySeeMoreHint ,
displayForecastsWrapper ,
2022-03-24 21:29:01 +00:00
} ) = > {
2022-03-24 23:45:21 +00:00
const router = useRouter ( ) ;
2022-03-16 21:02:34 +00:00
/* States */
2022-03-24 22:23:07 +00:00
const [ queryParameters , setQueryParameters ] =
2022-03-25 23:51:11 +00:00
useState < QueryParametersWithoutNum > ( initialQueryParameters ) ;
2022-03-24 23:45:21 +00:00
2022-03-25 23:51:11 +00:00
const [ numDisplay , setNumDisplay ] = useState (
initialQueryParameters . numDisplay ? ? defaultNumDisplay
) ;
2022-03-24 22:23:07 +00:00
2022-03-24 23:45:21 +00:00
// used to distinguish numDisplay updates which force search and don't force search, see effects below
const [ forceSearch , setForceSearch ] = useState ( 0 ) ;
2022-03-24 22:23:07 +00:00
2022-03-25 23:51:11 +00:00
const [ results , setResults ] = useState ( initialResults ) ;
2022-03-16 21:02:34 +00:00
const [ advancedOptions , showAdvancedOptions ] = useState ( false ) ;
const [ whichResultToDisplayAndCapture , setWhichResultToDisplayAndCapture ] =
useState ( 0 ) ;
const [ showIdToggle , setShowIdToggle ] = useState ( false ) ;
/* Functions which I want to have access to the Home namespace */
// I don't want to create an "defaultResults" object for each search.
2022-03-24 23:45:21 +00:00
async function executeSearchOrAnswerWithDefaultResults() {
const queryData = {
. . . queryParameters ,
numDisplay ,
} ;
2022-03-24 21:29:01 +00:00
let filterManually = ( queryData : QueryParameters , results ) = > {
2022-03-16 21:02:34 +00:00
if (
queryData . forecastingPlatforms &&
queryData . forecastingPlatforms . length > 0
) {
let forecastingPlatforms = queryData . forecastingPlatforms . map (
( platformObj ) = > platformObj . value
) ;
results = results . filter ( ( result ) = >
forecastingPlatforms . includes ( result . item . platform )
) ;
}
2022-03-24 21:29:01 +00:00
if ( queryData . starsThreshold === 4 ) {
2022-03-16 21:02:34 +00:00
results = results . filter (
( result ) = > result . item . qualityindicators . stars >= 4
) ;
}
if ( queryData . forecastsThreshold ) {
// results = results.filter(result => (result.qualityindicators && result.item.qualityindicators.numforecasts > forecastsThreshold))
}
return results ;
} ;
2022-03-24 21:29:01 +00:00
const queryIsEmpty =
! queryData || queryData . query == "" || queryData . query == undefined ;
let results = queryIsEmpty
2022-03-24 22:51:20 +00:00
? filterManually ( queryData , defaultResults )
2022-03-24 21:29:01 +00:00
: await searchAccordingToQueryData ( queryData ) ;
setResults ( results ) ;
2022-03-16 21:02:34 +00:00
}
2022-03-24 21:29:01 +00:00
// I don't want the function which display forecasts (displayForecasts) to change with a change in queryParameters. But I want it to have access to the queryParameters, and in particular access to queryParameters.numDisplay. Hence why this function lives inside Home.
let getInfoToDisplayForecastsFunction = ( {
results ,
whichResultToDisplayAndCapture ,
showIdToggle ,
} ) = > {
2022-03-16 21:02:34 +00:00
let numDisplayRounded =
2022-03-24 22:23:07 +00:00
numDisplay % 3 != 0
? numDisplay + ( 3 - ( Math . round ( numDisplay ) % 3 ) )
: numDisplay ;
2022-03-24 21:29:01 +00:00
return displayForecastsWrapper ( {
2022-03-16 21:02:34 +00:00
results ,
numDisplay : numDisplayRounded ,
whichResultToDisplayAndCapture ,
showIdToggle ,
} ) ;
} ;
2022-03-24 23:45:21 +00:00
const updateRoute = ( ) = > {
const stringify = ( key : string , value : any ) = > {
if ( key === "forecastingPlatforms" ) {
return value . map ( ( x ) = > x . value ) . join ( "|" ) ;
} else {
return String ( value ) ;
2022-03-16 21:02:34 +00:00
}
2022-03-24 23:45:21 +00:00
} ;
const query = { } ;
for ( const key of Object . keys ( defaultQueryParameters ) ) {
const value = stringify ( key , queryParameters [ key ] ) ;
const defaultValue = stringify ( key , defaultQueryParameters [ key ] ) ;
if ( value === defaultValue ) continue ;
query [ key ] = value ;
}
if ( numDisplay !== defaultNumDisplay ) query [ "numDisplay" ] = numDisplay ;
2022-03-16 21:02:34 +00:00
2022-03-24 23:45:21 +00:00
router . replace ( {
pathname : router.pathname ,
query ,
} ) ;
} ;
2022-03-26 11:21:36 +00:00
useNoInitialEffect ( updateRoute , [ numDisplay ] ) ;
2022-03-24 23:45:21 +00:00
2022-03-26 11:21:36 +00:00
useNoInitialEffect ( ( ) = > {
2022-03-24 23:45:21 +00:00
setResults ( [ ] ) ;
let newTimeoutId = setTimeout ( ( ) = > {
updateRoute ( ) ;
executeSearchOrAnswerWithDefaultResults ( ) ;
2022-03-24 22:23:07 +00:00
} , 500 ) ;
2022-03-16 21:02:34 +00:00
// avoid sending results if user has not stopped typing.
2022-03-24 22:23:07 +00:00
return ( ) = > {
clearTimeout ( newTimeoutId ) ;
} ;
2022-03-25 23:51:11 +00:00
} , [ queryParameters , forceSearch ] ) ;
2022-03-24 22:23:07 +00:00
/* State controllers */
2022-03-16 21:02:34 +00:00
/* Change the stars threshold */
2022-03-24 21:29:01 +00:00
let onChangeStars = ( value : number ) = > {
2022-03-24 22:23:07 +00:00
setQueryParameters ( {
2022-03-24 21:29:01 +00:00
. . . queryParameters ,
starsThreshold : value ,
2022-03-24 22:23:07 +00:00
} ) ;
2022-03-16 21:02:34 +00:00
} ;
/* Change the number of elements to display */
let displayFunctionNumDisplaySlider = ( value ) = > {
2022-03-24 22:23:07 +00:00
return (
"Show " +
Math . round ( value ) +
" result" +
( Math . round ( value ) === 1 ? "" : "s" )
) ;
2022-03-16 21:02:34 +00:00
} ;
let onChangeSliderForNumDisplay = ( event ) = > {
2022-03-24 22:23:07 +00:00
setNumDisplay ( Math . round ( event [ 0 ] ) ) ;
2022-03-24 23:45:21 +00:00
setForceSearch ( forceSearch + 1 ) ; // FIXME - force new search iff numDisplay is greater than last search limit
2022-03-16 21:02:34 +00:00
} ;
/* Change the forecast threshold */
2022-03-24 21:29:01 +00:00
let displayFunctionNumForecasts = ( value : number ) = > {
2022-03-16 21:02:34 +00:00
return "# Forecasts > " + Math . round ( value ) ;
} ;
let onChangeSliderForNumForecasts = ( event ) = > {
2022-03-24 22:23:07 +00:00
setQueryParameters ( {
2022-03-16 21:02:34 +00:00
. . . queryParameters ,
forecastsThreshold : Math.round ( event [ 0 ] ) ,
2022-03-24 22:23:07 +00:00
} ) ;
2022-03-16 21:02:34 +00:00
} ;
/* Change on the search bar */
2022-03-24 21:29:01 +00:00
let onChangeSearchBar = ( value : string ) = > {
2022-03-24 22:23:07 +00:00
setQueryParameters ( {
. . . queryParameters ,
query : value ,
} ) ;
2022-03-16 21:02:34 +00:00
} ;
2022-03-24 21:29:01 +00:00
/* Change selected platforms */
2022-03-16 21:02:34 +00:00
let onChangeSelectedPlatforms = ( value ) = > {
2022-03-24 22:23:07 +00:00
setQueryParameters ( {
2022-03-16 21:02:34 +00:00
. . . queryParameters ,
forecastingPlatforms : value ,
2022-03-24 22:23:07 +00:00
} ) ;
2022-03-16 21:02:34 +00:00
} ;
// Change show id
let onChangeShowId = ( ) = > {
setShowIdToggle ( ! showIdToggle ) ;
} ;
// Capture functionality
let onClickBack = ( ) = > {
2022-03-24 21:29:01 +00:00
let decreaseUntil0 = ( num : number ) = > ( num - 1 > 0 ? num - 1 : 0 ) ;
2022-03-16 21:02:34 +00:00
setWhichResultToDisplayAndCapture (
decreaseUntil0 ( whichResultToDisplayAndCapture )
) ;
} ;
2022-03-24 21:29:01 +00:00
let onClickForward = ( whichResultToDisplayAndCapture : number ) = > {
2022-03-16 21:02:34 +00:00
setWhichResultToDisplayAndCapture ( whichResultToDisplayAndCapture + 1 ) ;
} ;
/* Final return */
return (
2022-03-24 21:29:01 +00:00
< Fragment >
< label className = "mb-4 mt-4 flex flex-row justify-center items-center" >
{ hasSearchbar ? (
< div className = "w-10/12 mb-2" >
< Form
value = { queryParameters . query }
onChange = { onChangeSearchBar }
placeholder = { placeholder }
2022-03-16 21:02:34 +00:00
/ >
< / div >
2022-03-24 21:29:01 +00:00
) : null }
{ hasAdvancedOptions ? (
< div className = "w-2/12 flex justify-center ml-4 md:ml-2 lg:ml-0" >
< button
className = "text-gray-500 text-sm mb-2"
onClick = { ( ) = > showAdvancedOptions ( ! advancedOptions ) }
>
Advanced options ▼
< / button >
2022-03-16 21:02:34 +00:00
< / div >
2022-03-24 21:29:01 +00:00
) : null }
{ hasCapture ? (
< div className = "w-2/12 flex justify-center ml-4 md:ml-2 gap-1 lg:ml-0" >
< button
className = "text-blue-500 cursor-pointer text-xl mb-3 pr-3 hover:text-blue-600"
onClick = { ( ) = > onClickBack ( ) }
>
◀
< / button >
< button
className = "text-blue-500 cursor-pointer text-xl mb-3 pl-3 hover:text-blue-600"
onClick = { ( ) = > onClickForward ( whichResultToDisplayAndCapture ) }
>
▶
< / button >
2022-03-16 21:02:34 +00:00
< / div >
2022-03-24 21:29:01 +00:00
) : null }
< / label >
{ hasAdvancedOptions && advancedOptions ? (
< div className = "flex-1 flex-col mx-auto justify-center items-center w-full" >
< div className = "grid sm:grid-rows-4 sm:grid-cols-1 md:grid-rows-2 lg:grid-rows-2 grid-cols-1 md:grid-cols-3 lg:grid-cols-3 items-center content-center bg-gray-50 rounded-md px-8 pt-4 pb-1 shadow mb-4" >
< div className = "flex row-start-1 row-end-1 col-start-1 col-end-4 md:row-span-1 md:col-start-1 md:col-end-1 md:row-start-1 md:row-end-1 lg:row-span-1 lg:col-start-1 lg:col-end-1 lg:row-start-1 lg:row-end-1 items-center justify-center mb-4" >
< SliderElement
onChange = { onChangeSliderForNumForecasts }
value = { queryParameters . forecastsThreshold }
displayFunction = { displayFunctionNumForecasts }
/ >
< / div >
< div className = "flex row-start-2 row-end-2 col-start-1 col-end-4 md:row-start-1 md:row-end-1 md:col-start-2 md:col-end-2 lg:row-start-1 lg:row-end-1 lg:col-start-2 items-center justify-center mb-4" >
< ButtonsForStars
onChange = { onChangeStars }
value = { queryParameters . starsThreshold }
/ >
< / div >
< div className = "flex row-start-3 row-end-3 col-start-1 col-end-4 md:col-start-3 md:col-end-3 md:row-start-1 md:row-end-1 lg:col-start-3 lg:col-end-3 lg:row-start-1 lg:row-end-1 items-center justify-center mb-4" >
< SliderElement
2022-03-24 22:23:07 +00:00
value = { numDisplay }
2022-03-24 21:29:01 +00:00
onChange = { onChangeSliderForNumDisplay }
displayFunction = { displayFunctionNumDisplaySlider }
/ >
< / div >
< div className = "flex col-span-3 items-center justify-center" >
< MultiSelectPlatform
value = { queryParameters . forecastingPlatforms }
onChange = { onChangeSelectedPlatforms }
/ >
< / div >
< button
className = "block col-start-1 col-end-4 md:col-start-2 md:col-end-3 md:row-start-4 md:row-end-4 lg:col-start-2 lg:col-end-3 lg:row-start-4 lg:row-end-4 bg-transparent hover:bg-blue-300 text-blue-400 hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5 p-10 text-center mb-2 mr-10 ml-10 items-center justify-center"
onClick = { onChangeShowId }
>
Toggle show id
< / button >
2022-03-16 21:02:34 +00:00
< / div >
< / div >
2022-03-24 21:29:01 +00:00
) : null }
2022-03-16 21:02:34 +00:00
2022-03-24 21:29:01 +00:00
< div >
{ getInfoToDisplayForecastsFunction ( {
2022-03-16 21:02:34 +00:00
results ,
whichResultToDisplayAndCapture ,
showIdToggle ,
} ) }
< / div >
2022-03-24 21:29:01 +00:00
2022-03-24 22:23:07 +00:00
{ displaySeeMoreHint &&
( ! results || ( results . length != 0 && numDisplay < results . length ) ) ? (
2022-03-24 21:29:01 +00:00
< div >
2022-03-24 22:23:07 +00:00
< p className = "mt-4 mb-4" >
2022-03-24 21:29:01 +00:00
{ "Can't find what you were looking for?" }
< span
className = { ` cursor-pointer text-blue-800 ${
! results ? "hidden" : ""
} ` }
onClick = { ( ) = > {
2022-03-24 22:23:07 +00:00
setNumDisplay ( numDisplay * 2 ) ;
2022-03-24 21:29:01 +00:00
} }
>
2022-03-24 22:23:07 +00:00
{ " Show more," }
< / span >
{ " or " }
2022-03-24 21:29:01 +00:00
< a
href = "https://www.metaculus.com/questions/create/"
className = "cursor-pointer text-blue-800 no-underline"
target = "_blank"
>
suggest a question on Metaculus
< / a >
< / p >
< / div >
) : null }
2022-03-16 21:02:34 +00:00
< br > < / br >
< / Fragment >
) ;
2022-03-24 21:29:01 +00:00
} ;
export default CommonDisplay ;