import React, { Fragment, useEffect, useState } from 'react'; import { platformNames, PlatformWithLabel } from '../platforms'; import searchAccordingToQueryData from '../worker/searchAccordingToQueryData'; import ButtonsForStars from './buttonsForStars'; import Form from './form'; import MultiSelectPlatform from './multiSelectPlatforms'; import { SliderElement } from './slider'; interface QueryParametersWithoutNum { query: string; starsThreshold: number; forecastsThreshold: number; forecastingPlatforms: PlatformWithLabel[]; } export interface QueryParameters extends QueryParametersWithoutNum { numDisplay: number; } interface Props { defaultResults: 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; } /* Helper functions */ // URL slugs let transformObjectIntoUrlSlug = (obj: QueryParameters) => { let results = []; for (let key in obj) { if (typeof obj[key] === "number" || typeof obj[key] === "string") { results.push(`${key}=${obj[key]}`); } else if (key === "forecastingPlatforms") { let arr = obj[key].map((x) => x.value); let arrstring = arr.join("|"); results.push(`${key}=${arrstring}`); } } let string = "?" + results.join("&"); return string; }; /* Body */ const CommonDisplay: React.FC = ({ defaultResults, initialQueryParameters, hasSearchbar, hasCapture, hasAdvancedOptions, placeholder, displaySeeMoreHint, displayForecastsWrapper, }) => { /* States */ const [queryParameters, setQueryParameters] = useState(initialQueryParameters); const [numDisplay, setNumDisplay] = useState( initialQueryParameters.numDisplay || 21 ); const [results, setResults] = useState([]); 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( queryData: QueryParameters ) { // the queryData object has the same contents as queryParameters. // but I wanted to spare myself having to think about namespace conflicts. 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, }); }; useEffect(() => { setResults([]); let newTimeoutId = setTimeout(async () => { let urlSlug = transformObjectIntoUrlSlug({ ...queryParameters, numDisplay, }); let urlWithoutDefaultParameters = urlSlug .replace("?query=&", "?") .replace("&starsThreshold=2", "") .replace("&numDisplay=21", "") .replace("&forecastsThreshold=0", "") .replace(`&forecastingPlatforms=${platformNames.join("|")}`, ""); console.log(urlWithoutDefaultParameters); if (urlWithoutDefaultParameters != "?query=") { if (typeof window !== "undefined") { if (!window.location.href.includes(urlWithoutDefaultParameters)) { window.history.replaceState( null, "Metaforecast", urlWithoutDefaultParameters ); } } } executeSearchOrAnswerWithDefaultResults({ ...queryParameters, numDisplay, }); }, 500); // avoid sending results if user has not stopped typing. return () => { clearTimeout(newTimeoutId); }; }, [queryParameters]); /* 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])); // FIXME - force new search if 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;