import { useRouter } from 'next/router'; import React, { DependencyList, EffectCallback, Fragment, useEffect, useState } from 'react'; import ButtonsForStars from '../display/buttonsForStars'; import Form from '../display/form'; import MultiSelectPlatform from '../display/multiSelectPlatforms'; import { SliderElement } from '../display/slider'; import { platformsWithLabels, PlatformWithLabel } from '../platforms'; import searchAccordingToQueryData from '../worker/searchAccordingToQueryData'; interface QueryParametersWithoutNum { query: string; starsThreshold: number; forecastsThreshold: number; forecastingPlatforms: PlatformWithLabel[]; } export interface QueryParameters extends QueryParametersWithoutNum { numDisplay: number; } interface Props { defaultResults: any; initialResults: any; initialQueryParameters: QueryParameters; hasSearchbar: boolean; hasCapture: boolean; hasAdvancedOptions: boolean; placeholder: string; displaySeeMoreHint: boolean; displayForecastsWrapper: (opts: { results: any; numDisplay: number; whichResultToDisplayAndCapture: number; showIdToggle: boolean; }) => React.ReactNode; } export const defaultQueryParameters: QueryParametersWithoutNum = { query: "", starsThreshold: 2, forecastsThreshold: 0, forecastingPlatforms: platformsWithLabels, // weird key value format, }; export const defaultNumDisplay = 21; const useNoInitialEffect = (effect: EffectCallback, deps: DependencyList) => { const initial = React.useRef(true); useEffect(() => { if (initial.current) { initial.current = false; return; } return effect(); }, deps); }; /* Body */ const CommonDisplay: React.FC = ({ defaultResults, initialResults, initialQueryParameters, hasSearchbar, hasCapture, hasAdvancedOptions, placeholder, displaySeeMoreHint, displayForecastsWrapper, }) => { const router = useRouter(); /* States */ const [queryParameters, setQueryParameters] = useState(initialQueryParameters); const [numDisplay, setNumDisplay] = useState( initialQueryParameters.numDisplay ?? defaultNumDisplay ); // used to distinguish numDisplay updates which force search and don't force search, see effects below const [forceSearch, setForceSearch] = useState(0); const [results, setResults] = useState(initialResults); 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. async function executeSearchOrAnswerWithDefaultResults() { const queryData = { ...queryParameters, numDisplay, }; let filterManually = (queryData: QueryParameters, results) => { if ( queryData.forecastingPlatforms && queryData.forecastingPlatforms.length > 0 ) { let forecastingPlatforms = queryData.forecastingPlatforms.map( (platformObj) => platformObj.value ); results = results.filter((result) => forecastingPlatforms.includes(result.item.platform) ); } if (queryData.starsThreshold === 4) { 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; }; const queryIsEmpty = !queryData || queryData.query == "" || queryData.query == undefined; let results = queryIsEmpty ? filterManually(queryData, defaultResults) : await searchAccordingToQueryData(queryData); setResults(results); } // 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, }) => { let numDisplayRounded = numDisplay % 3 != 0 ? numDisplay + (3 - (Math.round(numDisplay) % 3)) : numDisplay; return displayForecastsWrapper({ results, numDisplay: numDisplayRounded, whichResultToDisplayAndCapture, showIdToggle, }); }; const updateRoute = () => { const stringify = (key: string, value: any) => { if (key === "forecastingPlatforms") { return value.map((x) => x.value).join("|"); } else { return String(value); } }; 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; router.replace({ pathname: router.pathname, query, }); }; useNoInitialEffect(updateRoute, [numDisplay]); useNoInitialEffect(() => { setResults([]); let newTimeoutId = setTimeout(() => { updateRoute(); executeSearchOrAnswerWithDefaultResults(); }, 500); // avoid sending results if user has not stopped typing. return () => { clearTimeout(newTimeoutId); }; }, [queryParameters, forceSearch]); /* State controllers */ /* Change the stars threshold */ let onChangeStars = (value: number) => { setQueryParameters({ ...queryParameters, starsThreshold: value, }); }; /* Change the number of elements to display */ let displayFunctionNumDisplaySlider = (value) => { return ( "Show " + Math.round(value) + " result" + (Math.round(value) === 1 ? "" : "s") ); }; let onChangeSliderForNumDisplay = (event) => { setNumDisplay(Math.round(event[0])); setForceSearch(forceSearch + 1); // FIXME - force new search iff numDisplay is greater than last search limit }; /* Change the forecast threshold */ let displayFunctionNumForecasts = (value: number) => { return "# Forecasts > " + Math.round(value); }; let onChangeSliderForNumForecasts = (event) => { setQueryParameters({ ...queryParameters, forecastsThreshold: Math.round(event[0]), }); }; /* Change on the search bar */ let onChangeSearchBar = (value: string) => { setQueryParameters({ ...queryParameters, query: value, }); }; /* Change selected platforms */ let onChangeSelectedPlatforms = (value) => { setQueryParameters({ ...queryParameters, forecastingPlatforms: value, }); }; // Change show id let onChangeShowId = () => { setShowIdToggle(!showIdToggle); }; // Capture functionality let onClickBack = () => { let decreaseUntil0 = (num: number) => (num - 1 > 0 ? num - 1 : 0); setWhichResultToDisplayAndCapture( decreaseUntil0(whichResultToDisplayAndCapture) ); }; let onClickForward = (whichResultToDisplayAndCapture: number) => { setWhichResultToDisplayAndCapture(whichResultToDisplayAndCapture + 1); }; /* Final return */ return ( {hasAdvancedOptions && advancedOptions ? (
) : null}
{getInfoToDisplayForecastsFunction({ results, whichResultToDisplayAndCapture, showIdToggle, })}
{displaySeeMoreHint && (!results || (results.length != 0 && numDisplay < results.length)) ? (

{"Can't find what you were looking for?"} { setNumDisplay(numDisplay * 2); }} > {" Show more,"} {" or "} suggest a question on Metaculus

) : null}

); }; export default CommonDisplay;