From ce9bb0eedfd96e6f61948fefc500627bf323c9e2 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Wed, 6 Apr 2022 01:50:50 +0300 Subject: [PATCH] refactor: frontend code, especially DisplayForecast(s) --- src/pages/about.tsx | 4 +- src/pages/capture.tsx | 2 +- src/pages/dashboards.tsx | 23 +- src/pages/index.tsx | 2 +- src/pages/recursion.tsx | 5 +- src/pages/secretDashboard.tsx | 12 +- src/pages/secretEmbed.tsx | 2 +- src/pages/tools.tsx | 110 ++- src/web/display/Card.tsx | 15 + .../DisplayForecast/ForecastFooter.tsx | 299 +++++++ src/web/display/DisplayForecast/index.tsx | 390 +++++++++ ...tPlatforms.tsx => MultiSelectPlatform.tsx} | 0 src/web/display/{form.tsx => QueryForm.tsx} | 14 +- .../display/{slider.tsx => SliderElement.tsx} | 14 +- src/web/display/buttonsForStars.tsx | 6 +- src/web/display/dashboardCreator.tsx | 30 +- src/web/display/displayForecasts.tsx | 746 +----------------- src/web/display/displayForecastsWrappers.tsx | 8 +- .../display/displayOneForecastForCapture.tsx | 2 +- src/web/display/layout.tsx | 4 +- src/web/search/CommonDisplay.tsx | 42 +- src/web/search/anySearchPage.tsx | 4 - 22 files changed, 855 insertions(+), 879 deletions(-) create mode 100644 src/web/display/Card.tsx create mode 100644 src/web/display/DisplayForecast/ForecastFooter.tsx create mode 100644 src/web/display/DisplayForecast/index.tsx rename src/web/display/{multiSelectPlatforms.tsx => MultiSelectPlatform.tsx} (100%) rename src/web/display/{form.tsx => QueryForm.tsx} (77%) rename src/web/display/{slider.tsx => SliderElement.tsx} (93%) diff --git a/src/pages/about.tsx b/src/pages/about.tsx index 4590e84..193d0fe 100644 --- a/src/pages/about.tsx +++ b/src/pages/about.tsx @@ -2,9 +2,9 @@ import React from "react"; import ReactMarkdown from "react-markdown"; import gfm from "remark-gfm"; -import Layout from "../web/display/layout"; +import { Layout } from "../web/display/Layout"; -let readmeMarkdownText = `# About +const readmeMarkdownText = `# About This webpage is a search engine for probabilities. Given a query, it searches for relevant questions in various prediction markets and forecasting platforms. For example, try searching for "China", "North Korea", "Semiconductors", "COVID", "Trump", or "X-risk". In addition to search, we also provide various [tools](http://localhost:3000/tools). diff --git a/src/pages/capture.tsx b/src/pages/capture.tsx index 6cd6905..16712bd 100644 --- a/src/pages/capture.tsx +++ b/src/pages/capture.tsx @@ -2,7 +2,7 @@ import { NextPage } from "next"; import React from "react"; import { displayForecastsWrapperForCapture } from "../web/display/displayForecastsWrappers"; -import Layout from "../web/display/layout"; +import { Layout } from "../web/display/Layout"; import { Props } from "../web/search/anySearchPage"; import CommonDisplay from "../web/search/CommonDisplay"; diff --git a/src/pages/dashboards.tsx b/src/pages/dashboards.tsx index 7cc6057..ca47815 100644 --- a/src/pages/dashboards.tsx +++ b/src/pages/dashboards.tsx @@ -6,9 +6,9 @@ import { useState } from "react"; import { DashboardItem } from "../backend/dashboards"; import { getPlatformsConfig, PlatformConfig } from "../backend/platforms"; -import { DashboardCreator } from "../web/display/dashboardCreator"; -import displayForecasts from "../web/display/displayForecasts"; -import Layout from "../web/display/layout"; +import { DashboardCreator } from "../web/display/DashboardCreator"; +import { DisplayForecasts } from "../web/display/DisplayForecasts"; +import { Layout } from "../web/display/Layout"; import { addLabelsToForecasts, FrontendForecast } from "../web/platforms"; import { getDashboardForecastsByDashboardId } from "../web/worker/getDashboardForecasts"; @@ -70,12 +70,12 @@ const DashboardsPage: NextPage = ({ ); const [dashboardItem, setDashboardItem] = useState(initialDashboardItem); - let handleSubmit = async (data) => { + const handleSubmit = async (data) => { console.log(data); // Send to server to create // Get back the id let response = await axios({ - url: `/api/create-dashboard-from-ids`, + url: "/api/create-dashboard-from-ids", method: "POST", headers: { "Content-Type": "application/json" }, data: JSON.stringify(data), @@ -107,7 +107,8 @@ const DashboardsPage: NextPage = ({ } }; - let isGraubardEasterEgg = (name) => (name == "Clay Graubard" ? true : false); + let isGraubardEasterEgg = (name: string) => + name == "Clay Graubard" ? true : false; return ( @@ -162,11 +163,11 @@ const DashboardsPage: NextPage = ({
- {displayForecasts({ - results: dashboardForecasts, - numDisplay: dashboardForecasts.length, - showIdToggle: false, - })} +
{/* */}

diff --git a/src/pages/index.tsx b/src/pages/index.tsx index f1fa119..786e7a9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -2,7 +2,7 @@ import { NextPage } from "next"; import React from "react"; import { displayForecastsWrapperForSearch } from "../web/display/displayForecastsWrappers"; -import Layout from "../web/display/layout"; +import { Layout } from "../web/display/Layout"; import { Props } from "../web/search/anySearchPage"; import CommonDisplay from "../web/search/CommonDisplay"; diff --git a/src/pages/recursion.tsx b/src/pages/recursion.tsx index 91aac2a..b9bcb37 100644 --- a/src/pages/recursion.tsx +++ b/src/pages/recursion.tsx @@ -1,6 +1,7 @@ +import { NextPage } from "next"; import React, { useEffect } from "react"; -function Recursion() { +const Recursion: NextPage = () => { useEffect(() => { if (typeof window !== "undefined") { window.location.href = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; @@ -12,6 +13,6 @@ function Recursion() {

You have now reached the fourth level of recursion!!

); -} +}; export default Recursion; diff --git a/src/pages/secretDashboard.tsx b/src/pages/secretDashboard.tsx index 21e47f4..e6ca347 100644 --- a/src/pages/secretDashboard.tsx +++ b/src/pages/secretDashboard.tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/router"; // https://nextjs.org/docs/api-referenc import { useState } from "react"; import { getPlatformsConfig } from "../backend/platforms"; -import displayForecasts from "../web/display/displayForecasts"; +import { DisplayForecasts } from "../web/display/DisplayForecasts"; import { addLabelsToForecasts } from "../web/platforms"; import { getDashboardForecastsByDashboardId } from "../web/worker/getDashboardForecasts"; @@ -88,11 +88,11 @@ export default function Home({ numCols || 3 } gap-4 mb-6`} > - {displayForecasts({ - results: dashboardForecasts, - numDisplay: dashboardForecasts.length, - showIdToggle: false, - })} + diff --git a/src/pages/secretEmbed.tsx b/src/pages/secretEmbed.tsx index 6ede915..fa99277 100644 --- a/src/pages/secretEmbed.tsx +++ b/src/pages/secretEmbed.tsx @@ -4,7 +4,7 @@ import { GetServerSideProps, NextPage } from "next"; import React from "react"; import { platforms } from "../backend/platforms"; -import { DisplayForecast } from "../web/display/displayForecasts"; +import { DisplayForecast } from "../web/display/DisplayForecast"; import { FrontendForecast } from "../web/platforms"; import searchAccordingToQueryData from "../web/worker/searchAccordingToQueryData"; diff --git a/src/pages/tools.tsx b/src/pages/tools.tsx index d7e81fa..eef031b 100644 --- a/src/pages/tools.tsx +++ b/src/pages/tools.tsx @@ -1,85 +1,76 @@ import Link from "next/link"; import React from "react"; -import Layout from "../web/display/layout"; +import { Card } from "../web/display/Card"; +import { Layout } from "../web/display/Layout"; + +type AnyTool = { + title: string; + description: string; + img?: string; +}; + +type InnerTool = AnyTool & { innerLink: string }; +type ExternalTool = AnyTool & { externalLink: string }; +type UpcomingTool = AnyTool; + +type Tool = InnerTool | ExternalTool | UpcomingTool; /* Display one tool */ -function displayTool({ - sameWebpage, - title, - description, - link, - url, - img, - i, -}: any) { - switch (sameWebpage) { - case true: - return ( - -
-
-
- {title} -
-
{description}
- {} - -
-
- - ); - break; - default: - return ( - -
-
- {title} -
-
{description}
- {} - -
-
- ); - break; +const ToolCard: React.FC = (tool) => { + const inner = ( + +
+
{tool.title}
+
{tool.description}
+ {tool.img && } +
+
+ ); + + if ("innerLink" in tool) { + return ( + + {inner} + + ); + } else if ("externalLink" in tool) { + return ( + + {inner} + + ); + } else { + return inner; } -} +}; export default function Tools({ lastUpdated }) { - let tools = [ + let tools: Tool[] = [ { title: "Search", - description: "Find forecasting questions on many platforms", - link: "/", - sameWebpage: true, + description: "Find forecasting questions on many platforms.", + innerLink: "/", img: "https://i.imgur.com/Q94gVqG.png", }, { title: "[Beta] Present", description: "Present forecasts in dashboards.", - sameWebpage: true, - link: "/dashboards", + innerLink: "/dashboards", img: "https://i.imgur.com/x8qkuHQ.png", }, { title: "Capture", description: "Capture forecasts save them to Imgur. Useful for posting them somewhere else as images. Currently rate limited by Imgur, so if you get a .gif of a fox falling flat on his face, that's why.", - link: "/capture", - sameWebpage: true, + innerLink: "/capture", img: "https://i.imgur.com/EXkFBzz.png", }, { title: "Summon", description: - "Summon metaforecast on Twitter by mentioning @metaforecast, or on Discord by using Fletcher and !metaforecast, followed by search terms", - url: "https://twitter.com/metaforecast", + "Summon metaforecast on Twitter by mentioning @metaforecast, or on Discord by using Fletcher and !metaforecast, followed by search terms.", + externalLink: "https://twitter.com/metaforecast", img: "https://i.imgur.com/BQ4Zzjw.png", }, { @@ -87,7 +78,6 @@ export default function Tools({ lastUpdated }) { description: "Interact with metaforecast's API and fetch forecasts for your application. Currently possible but documentation is poor, get in touch.", }, - { title: "[Upcoming] Record", description: "Save your forecasts or bets.", @@ -95,8 +85,10 @@ export default function Tools({ lastUpdated }) { ]; return ( -
- {tools.map((tool, i) => displayTool({ ...tool, i }))} +
+ {tools.map((tool, i) => ( + + ))}
); diff --git a/src/web/display/Card.tsx b/src/web/display/Card.tsx new file mode 100644 index 0000000..a77614e --- /dev/null +++ b/src/web/display/Card.tsx @@ -0,0 +1,15 @@ +const CardTitle: React.FC = ({ children }) => ( +
{children}
+); + +type CardType = React.FC & { + Title: typeof CardTitle; +}; + +export const Card: CardType = ({ children }) => ( +
+ {children} +
+); + +Card.Title = CardTitle; diff --git a/src/web/display/DisplayForecast/ForecastFooter.tsx b/src/web/display/DisplayForecast/ForecastFooter.tsx new file mode 100644 index 0000000..aa1707f --- /dev/null +++ b/src/web/display/DisplayForecast/ForecastFooter.tsx @@ -0,0 +1,299 @@ +const formatQualityIndicator = (indicator) => { + let result; + switch (indicator) { + case "numforecasts": + result = null; + break; + + case "stars": + result = null; + break; + + case "volume": + result = "Volume"; + break; + + case "numforecasters": + result = "Forecasters"; + break; + + case "yes_bid": + result = null; // "Yes bid" + break; + + case "yes_ask": + result = null; // "Yes ask" + break; + + case "spread": + result = "Spread"; + break; + case "shares_volume": + result = "Shares vol."; + break; + + case "open_interest": + result = "Interest"; + break; + + case "resolution_data": + result = null; + break; + + case "liquidity": + result = "Liquidity"; + break; + + case "tradevolume": + result = "Volume"; + break; + } + return result; +}; + +const formatNumber = (num) => { + if (Number(num) < 1000) { + return Number(num).toFixed(0); + } else if (num < 10000) { + return (Number(num) / 1000).toFixed(1) + "k"; + } else { + return (Number(num) / 1000).toFixed(0) + "k"; + } +}; + +const formatQualityIndicators = (qualityIndicators: any) => { + let newQualityIndicators = {}; + for (let key in qualityIndicators) { + let newKey = formatQualityIndicator(key); + if (newKey) { + newQualityIndicators[newKey] = qualityIndicators[key]; + } + } + return newQualityIndicators; +}; + +/* Display functions*/ + +const getPercentageSymbolIfNeeded = ({ indicator, platform }) => { + let indicatorsWhichNeedPercentageSymbol = ["Spread"]; + if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) { + return "%"; + } else { + return ""; + } +}; + +const getCurrencySymbolIfNeeded = ({ + indicator, + platform, +}: { + indicator: any; + platform: string; +}) => { + let indicatorsWhichNeedCurrencySymbol = ["Volume", "Interest", "Liquidity"]; + let dollarPlatforms = ["predictit", "kalshi", "polymarket"]; + if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) { + if (dollarPlatforms.includes(platform)) { + return "$"; + } else { + return "£"; + } + } else { + return ""; + } +}; + +const showFirstQualityIndicator = ({ + numforecasts, + timestamp, + showTimeStamp, + qualityindicators, +}) => { + if (!!numforecasts) { + return ( +
+ {/*{` ${numforecasts == 1 ? "Forecast" : "Forecasts:"}`} */} + {"Forecasts:"}  + {Number(numforecasts).toFixed(0)} +
+ ); + } else if (showTimeStamp) { + return ( + + + + + {`Last updated: ${ + timestamp && !!timestamp.slice ? timestamp.slice(0, 10) : "unknown" + }`} + + ); + } else { + return null; + } +}; + +const displayQualityIndicators: React.FC<{ + numforecasts: number; + timestamp: number; + showTimeStamp: boolean; + qualityindicators: any; + platform: string; // id string - e.g. "goodjudgment", not "Good Judgment" +}> = ({ + numforecasts, + timestamp, + showTimeStamp, + qualityindicators, + platform, +}) => { + // grid grid-cols-1 + return ( +
+ {showFirstQualityIndicator({ + numforecasts, + timestamp, + showTimeStamp, + qualityindicators, + })} + {Object.entries(formatQualityIndicators(qualityindicators)).map( + (entry, i) => { + return ( +
+ {`${entry[0]}:`}  + + {`${getCurrencySymbolIfNeeded({ + indicator: entry[0], + platform, + })}${formatNumber(entry[1])}${getPercentageSymbolIfNeeded({ + indicator: entry[0], + platform, + })}`} + +
+ ); + } + )} +
+ ); +}; + +// Database-like functions +export function getstars(numstars: number) { + let stars = "★★☆☆☆"; + switch (numstars) { + case 0: + stars = "☆☆☆☆☆"; + break; + case 1: + stars = "★☆☆☆☆"; + break; + case 2: + stars = "★★☆☆☆"; + break; + case 3: + stars = "★★★☆☆"; + break; + case 4: + stars = "★★★★☆"; + break; + case 5: + stars = "★★★★★"; + break; + default: + stars = "★★☆☆☆"; + } + return stars; +} + +function getStarsColor(numstars: number) { + let color = "text-yellow-400"; + switch (numstars) { + case 0: + color = "text-red-400"; + break; + case 1: + color = "text-red-400"; + break; + case 2: + color = "text-orange-400"; + break; + case 3: + color = "text-yellow-400"; + break; + case 4: + color = "text-green-400"; + break; + case 5: + color = "text-blue-400"; + break; + default: + color = "text-yellow-400"; + } + return color; +} + +interface Props { + stars: any; + platform: string; + platformLabel: string; + numforecasts: any; + qualityindicators: any; + timestamp: any; + showTimeStamp: boolean; + expandFooterToFullWidth: boolean; +} + +export const ForecastFooter: React.FC = ({ + stars, + platform, + platformLabel, + numforecasts, + qualityindicators, + timestamp, + showTimeStamp, + expandFooterToFullWidth, +}) => { + // I experimented with justify-evenly, justify-around, etc., here: https://tailwindcss.com/docs/justify-content + // I came to the conclusion that as long as the description isn't justified too, aligning the footer symmetrically doesn't make sense + // because the contrast is jarring. + let debuggingWithBackground = false; + return ( +
+
+ {getstars(stars)} +
+
+ {platformLabel + .replace("Good Judgment Open", "GJOpen") + .replace(/ /g, "\u00a0")} +
+
+ {displayQualityIndicators({ + numforecasts, + timestamp, + showTimeStamp, + qualityindicators, + platform, + })} +
+
+ ); +}; diff --git a/src/web/display/DisplayForecast/index.tsx b/src/web/display/DisplayForecast/index.tsx new file mode 100644 index 0000000..2eff8be --- /dev/null +++ b/src/web/display/DisplayForecast/index.tsx @@ -0,0 +1,390 @@ +import { FaRegClipboard } from "react-icons/fa"; +import ReactMarkdown from "react-markdown"; + +import { FrontendForecast } from "../../platforms"; +import { Card } from "../Card"; +import { ForecastFooter } from "./ForecastFooter"; + +const truncateText = (length: number, text: string): string => { + if (!text) { + return ""; + } + if (!!text && text.length <= length) { + return text; + } + let breakpoints = " .!?"; + let lastLetter = null; + let lastIndex = null; + for (let index = length; index > 0; index--) { + let letter = text[index]; + if (breakpoints.includes(letter)) { + lastLetter = letter; + lastIndex = index; + break; + } + } + let truncatedText = !!text.slice + ? text.slice(0, lastIndex) + (lastLetter != "." ? "..." : "..") + : ""; + return truncatedText; +}; + +const formatProbability = (probability: number) => { + let percentage = probability * 100; + let percentageCapped = + percentage < 1 + ? "< 1%" + : percentage > 99 + ? "> 99%" + : percentage.toFixed(0) + "%"; + return percentageCapped; +}; + +// replaceAll polyfill +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string +} + +function replaceAll( + originalString: string, + pattern: string | RegExp, + substitute +) { + return originalString.replace( + new RegExp(escapeRegExp(pattern), "g"), + substitute + ); +} + +if (!String.prototype.replaceAll) { + String.prototype.replaceAll = function ( + pattern: string | RegExp, + substitute + ) { + let originalString = this; + + // If a regex pattern + if ( + Object.prototype.toString.call(pattern).toLowerCase() === + "[object regexp]" + ) { + return originalString.replace(pattern, substitute); + } + + // If a string + return replaceAll(originalString, pattern, substitute); + }; +} + +const cleanText = (text: string): string => { + // Note: should no longer be necessary + let textString = !!text ? text : ""; + textString = textString + .replaceAll("] (", "](") + .replaceAll(") )", "))") + .replaceAll("( [", "([") + .replaceAll(") ,", "),") + .replaceAll("==", "") // Denotes a title in markdown + .replaceAll("Background\n", "") + .replaceAll("Context\n", "") + .replaceAll("--- \n", "- ") + .replaceAll(/\[(.*?)\]\(.*?\)/g, "$1"); + textString = textString.slice(0, 1) == "=" ? textString.slice(1) : textString; + //console.log(textString) + return textString; +}; + +const primaryForecastColor = (probability: number) => { + if (probability < 0.03) { + return "bg-red-600"; + } else if (probability < 0.1) { + return "bg-red-600 opacity-80"; + } else if (probability < 0.2) { + return "bg-red-600 opacity-70"; + } else if (probability < 0.3) { + return "bg-red-600 opacity-60"; + } else if (probability < 0.4) { + return "bg-red-600 opacity-50"; + } else if (probability < 0.5) { + return "bg-gray-500"; + } else if (probability < 0.6) { + return "bg-gray-500"; + } else if (probability < 0.7) { + return "bg-green-600 opacity-50"; + } else if (probability < 0.8) { + return "bg-green-600 opacity-60"; + } else if (probability < 0.9) { + return "bg-green-600 opacity-70"; + } else if (probability < 0.97) { + return "bg-green-600 opacity-80"; + } else { + return "bg-green-600"; + } +}; + +const textColor = (probability: number) => { + if (probability < 0.03) { + return "text-red-600"; + } else if (probability < 0.1) { + return "text-red-600 opacity-80"; + } else if (probability < 0.2) { + return "text-red-600 opacity-80"; + } else if (probability < 0.3) { + return "text-red-600 opacity-70"; + } else if (probability < 0.4) { + return "text-red-600 opacity-70"; + } else if (probability < 0.5) { + return "text-gray-500"; + } else if (probability < 0.6) { + return "text-gray-500"; + } else if (probability < 0.7) { + return "text-green-600 opacity-70"; + } else if (probability < 0.8) { + return "text-green-600 opacity-70"; + } else if (probability < 0.9) { + return "text-green-600 opacity-80"; + } else if (probability < 0.97) { + return "text-green-600 opacity-80"; + } else { + return "text-green-600"; + } +}; + +const primaryEstimateAsText = (probability: number) => { + if (probability < 0.03) { + return "Exceptionally unlikely"; + } else if (probability < 0.1) { + return "Very unlikely"; + } else if (probability < 0.4) { + return "Unlikely"; + } else if (probability < 0.6) { + return "About Even"; + } else if (probability < 0.9) { + return "Likely"; + } else if (probability < 0.97) { + return "Very likely"; + } else { + return "Virtually certain"; + } +}; + +// Logical checks + +const checkIfDisplayTimeStampAtBottom = (qualityIndicators: { + [k: string]: any; +}) => { + let indicators = Object.keys(qualityIndicators); + if (indicators.length == 1 && indicators[0] == "stars") { + return true; + } else { + return false; + } +}; + +// Auxiliary components + +const DisplayMarkdown: React.FC<{ description: string }> = ({ + description, +}) => { + let formatted = truncateText(250, cleanText(description)); + // overflow-hidden overflow-ellipsis h-24 + return formatted === "" ? null : ( +
+ + {formatted} + +
+ ); +}; + +const OptionRow: React.FC<{ option: any }> = ({ option }) => { + const chooseColor = (probability: number) => { + if (probability < 0.1) { + return "bg-blue-50 text-blue-500"; + } else if (probability < 0.3) { + return "bg-blue-100 text-blue-600"; + } else if (probability < 0.7) { + return "bg-blue-200 text-blue-700"; + } else { + return "bg-blue-300 text-blue-800"; + } + }; + + return ( +
+
+ {formatProbability(option.probability)} +
+
+ {option.name} +
+
+ ); +}; + +const ForecastOptions: React.FC<{ options: any[] }> = ({ options }) => { + const optionsSorted = options.sort((a, b) => b.probability - a.probability); + const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options. + return ( +
+ {optionsMax5.map((option, i) => ( + + ))} +
+ ); +}; + +const CopyText: React.FC<{ text: string; displayText: string }> = ({ + text, + displayText, +}) => ( +
{ + e.preventDefault(); + navigator.clipboard.writeText(text); + }} + > + {displayText} + +
+); + +const LastUpdated: React.FC<{ timestamp: string }> = ({ timestamp }) => ( +
+ + + + + Last updated: {timestamp ? timestamp.slice(0, 10) : "unknown"} + +
+); + +// Main component + +interface Props { + forecast: FrontendForecast; + showTimeStamp: boolean; + expandFooterToFullWidth: boolean; + showIdToggle?: boolean; +} + +export const DisplayForecast: React.FC = ({ + forecast: { + id, + title, + url, + platform, + platformLabel, + description, + options, + qualityindicators, + timestamp, + visualization, + }, + showTimeStamp, + expandFooterToFullWidth, + showIdToggle, +}) => { + const displayTimestampAtBottom = + checkIfDisplayTimeStampAtBottom(qualityindicators); + + const yesNoOptions = + options.length === 2 && + (options[0].name === "Yes" || options[0].name === "No"); + + return ( + + +
+
+ {showIdToggle ? ( +
+ +
+ ) : null} + {title} + {yesNoOptions && ( +
+
+ + {formatProbability(options[0].probability)} + + + {primaryEstimateAsText(options[0].probability)} + +
+
+ +
+
+ )} + {!yesNoOptions && ( +
+ +
+ +
+
+ )} + + {platform !== "guesstimate" && options.length < 3 && ( +
+ +
+ )} + + {platform === "guesstimate" && ( + Guesstimate Screenshot + )} +
+
+ {/* This one is exclusively for mobile*/} + +
+
+ +
+
+
+
+ ); +}; diff --git a/src/web/display/multiSelectPlatforms.tsx b/src/web/display/MultiSelectPlatform.tsx similarity index 100% rename from src/web/display/multiSelectPlatforms.tsx rename to src/web/display/MultiSelectPlatform.tsx diff --git a/src/web/display/form.tsx b/src/web/display/QueryForm.tsx similarity index 77% rename from src/web/display/form.tsx rename to src/web/display/QueryForm.tsx index b9cc9cc..e5fb700 100644 --- a/src/web/display/form.tsx +++ b/src/web/display/QueryForm.tsx @@ -1,6 +1,16 @@ import React from "react"; -export default function Form({ value, onChange, placeholder }) { +interface Props { + value: string; + onChange: (v: string) => void; + placeholder: string; +} + +export const QueryForm: React.FC = ({ + value, + onChange, + placeholder, +}) => { const handleInputChange = (event) => { event.preventDefault(); onChange(event.target.value); // In this case, the query, e.g. "COVID.19" @@ -21,4 +31,4 @@ export default function Form({ value, onChange, placeholder }) { /> ); -} +}; diff --git a/src/web/display/slider.tsx b/src/web/display/SliderElement.tsx similarity index 93% rename from src/web/display/slider.tsx rename to src/web/display/SliderElement.tsx index a3e9b1a..c14da06 100644 --- a/src/web/display/slider.tsx +++ b/src/web/display/SliderElement.tsx @@ -76,9 +76,19 @@ function Track({ source, target, getTrackProps }) { ); } +interface Props { + value: number; + onChange: (event: any) => void; + displayFunction: (value: number) => string; +} + /* Body */ // Two functions, essentially identical. -export function SliderElement({ onChange, value, displayFunction }) { +export const SliderElement: React.FC = ({ + onChange, + value, + displayFunction, +}) => { return ( ); -} +}; diff --git a/src/web/display/buttonsForStars.tsx b/src/web/display/buttonsForStars.tsx index 471bea1..b76c9d6 100644 --- a/src/web/display/buttonsForStars.tsx +++ b/src/web/display/buttonsForStars.tsx @@ -5,11 +5,11 @@ interface Props { value: number; } -const ButtonsForStars: React.FC = ({ onChange, value }) => { +export const ButtonsForStars: React.FC = ({ onChange, value }) => { const onChangeInner = (buttonPressed: number) => { onChange(buttonPressed); }; - let setStyle = (buttonNumber: number) => + const setStyle = (buttonNumber: number) => `flex row-span-1 col-start-${buttonNumber + 1} col-end-${ buttonNumber + 2 } items-center justify-center text-center${ @@ -37,5 +37,3 @@ const ButtonsForStars: React.FC = ({ onChange, value }) => {
); }; - -export default ButtonsForStars; diff --git a/src/web/display/dashboardCreator.tsx b/src/web/display/dashboardCreator.tsx index d678963..2996e1f 100644 --- a/src/web/display/dashboardCreator.tsx +++ b/src/web/display/dashboardCreator.tsx @@ -1,46 +1,45 @@ import React, { useState } from "react"; -let exampleInput = `{ +const exampleInput = `{ "title": "Random example", "description": "Just a random description of a random example", "ids": [ "metaculus-372", "goodjudgmentopen-2244", "metaculus-7550", "kalshi-09d060ee-b184-4167-b86b-d773e56b4162", "wildeford-5d1a04e1a8", "metaculus-2817" ], "creator": "Peter Parker" }`; -export function DashboardCreator({ handleSubmit }) { - let [value, setValue] = useState(exampleInput); +interface Props { + handleSubmit: (data: any) => Promise; +} + +export const DashboardCreator: React.FC = ({ handleSubmit }) => { + const [value, setValue] = useState(exampleInput); const [displayingDoneMessage, setDisplayingDoneMessage] = useState(false); const [displayingDoneMessageTimer, setDisplayingDoneMessageTimer] = useState(null); - let handleChange = (event) => { + const handleChange = (event) => { setValue(event.target.value); }; - let handleSubmitInner = (event) => { + const handleSubmitInner = (event) => { clearTimeout(displayingDoneMessageTimer); event.preventDefault(); - //console.log(event) - console.log("value@handleSubmitInner@DashboardCreator"); - //console.log(typeof(value)); + console.log(value); try { let newData = JSON.parse(value); - //console.log(typeof(newData)) - //console.log(newData) + if (!newData || !newData.ids || newData.ids.length == 0) { throw Error("Not enough objects"); } else { handleSubmit(newData); setDisplayingDoneMessage(true); - let timer = setTimeout(() => setDisplayingDoneMessage(false), 3000); + const timer = setTimeout(() => setDisplayingDoneMessage(false), 3000); setDisplayingDoneMessageTimer(timer); } } catch (error) { setDisplayingDoneMessage(false); - //alert(error) - //console.log(error) - let substituteText = `Error: ${error.message} + const substituteText = `Error: ${error.message} Try something like: ${exampleInput} @@ -49,6 +48,7 @@ Your old input was: ${value}`; setValue(substituteText); } }; + return (