From 73a47d94c3c57534846173510f131c4127a9c3c6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Matyukhin Date: Tue, 26 Apr 2022 01:34:18 +0400 Subject: [PATCH] refactor: question components --- .../DisplayQuestion/QuestionFooter.tsx | 179 ++++++------------ src/web/display/DisplayQuestion/index.tsx | 146 ++------------ .../questions/components/QuestionOptions.tsx | 145 ++++++++++++-- src/web/questions/pages/QuestionPage.tsx | 13 +- 4 files changed, 210 insertions(+), 273 deletions(-) diff --git a/src/web/display/DisplayQuestion/QuestionFooter.tsx b/src/web/display/DisplayQuestion/QuestionFooter.tsx index d7441ea..beebe2b 100644 --- a/src/web/display/DisplayQuestion/QuestionFooter.tsx +++ b/src/web/display/DisplayQuestion/QuestionFooter.tsx @@ -3,57 +3,28 @@ import { QuestionFragment } from "../../search/queries.generated"; type QualityIndicator = QuestionFragment["qualityIndicators"]; type IndicatorName = keyof QualityIndicator; -const formatQualityIndicator = (indicator: IndicatorName) => { - let result: string | null = null; - switch (indicator) { - case "numForecasts": - result = null; - break; +// this duplication can probably be simplified with typescript magic, but this is good enough for now +type UsedIndicatorName = + | "volume" + | "numForecasters" + | "spread" + | "sharesVolume" + | "liquidity" + | "tradeVolume" + | "openInterest"; - case "stars": - result = null; - break; - - case "volume": - result = "Volume"; - break; - - case "numForecasters": - result = "Forecasters"; - break; - - // case "yesBid": - // result = null; // "Yes bid" - // break; - - // case "yesAsk": - // result = null; // "Yes ask" - // break; - - case "spread": - result = "Spread"; - break; - case "sharesVolume": - result = "Shares vol."; - break; - - case "openInterest": - result = "Interest"; - break; - - // case "resolution_data": - // result = null; - // break; - - case "liquidity": - result = "Liquidity"; - break; - - case "tradeVolume": - result = "Volume"; - break; - } - return result; +const qualityIndicatorLabels: { [k in UsedIndicatorName]: string } = { + // numForecasts: null, + // stars: null, + // yesBid: "Yes bid", + // yesAsk: "Yes ask", + volume: "Volume", + numForecasters: "Forecasters", + spread: "Spread", + sharesVolume: "Shares vol.", + liquidity: "Liquidity", + tradeVolume: "Volume", + openInterest: "Interest", }; const formatNumber = (num) => { @@ -66,27 +37,16 @@ const formatNumber = (num) => { } }; -const formatQualityIndicators = (qualityIndicators: QualityIndicator) => { - let newQualityIndicators: { [k: string]: string | number } = {}; - for (const key of Object.keys(qualityIndicators)) { - const newKey = formatQualityIndicator(key as IndicatorName); - if (newKey && qualityIndicators[key] !== null) { - newQualityIndicators[newKey] = qualityIndicators[key]; - } - } - return newQualityIndicators; -}; - /* Display functions*/ const getPercentageSymbolIfNeeded = ({ indicator, platform, }: { - indicator: string; + indicator: UsedIndicatorName; platform: string; }) => { - let indicatorsWhichNeedPercentageSymbol = ["Spread"]; + let indicatorsWhichNeedPercentageSymbol: IndicatorName[] = ["spread"]; if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) { return "%"; } else { @@ -98,10 +58,15 @@ const getCurrencySymbolIfNeeded = ({ indicator, platform, }: { - indicator: string; + indicator: UsedIndicatorName; platform: string; }) => { - let indicatorsWhichNeedCurrencySymbol = ["Volume", "Interest", "Liquidity"]; + const indicatorsWhichNeedCurrencySymbol: IndicatorName[] = [ + "volume", + "tradeVolume", + "openInterest", + "liquidity", + ]; let dollarPlatforms = ["predictit", "kalshi", "polymarket"]; if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) { if (dollarPlatforms.includes(platform)) { @@ -114,66 +79,50 @@ const getCurrencySymbolIfNeeded = ({ } }; -const showFirstQualityIndicator: React.FC<{ +const FirstQualityIndicator: React.FC<{ question: QuestionFragment; - showTimeStamp: boolean; -}> = ({ question, showTimeStamp }) => { - const lastUpdated = new Date(question.timestamp * 1000); - if (!!question.qualityIndicators.numForecasts) { +}> = ({ question }) => { + if (question.qualityIndicators.numForecasts) { return ( -
- {/*{` ${numforecasts == 1 ? "Forecast" : "Forecasts:"}`} */} +
Forecasts:  {Number(question.qualityIndicators.numForecasts).toFixed(0)}
); - } else if (showTimeStamp) { - return ( - - - - - {`Last updated: ${ - lastUpdated ? lastUpdated.toISOString().slice(0, 10) : "unknown" - }`} - - ); } else { return null; } }; -const displayQualityIndicators: React.FC<{ +const QualityIndicatorsList: React.FC<{ question: QuestionFragment; - showTimeStamp: boolean; -}> = ({ question, showTimeStamp }) => { - const { qualityIndicators } = question; +}> = ({ question }) => { return (
- {showFirstQualityIndicator({ - question, - showTimeStamp, + + {Object.entries(question.qualityIndicators).map((entry, i) => { + const indicatorLabel = qualityIndicatorLabels[entry[0]]; + if (!indicatorLabel || entry[1] === null) return; + const indicator = entry[0] as UsedIndicatorName; // guaranteed by the previous line + const value = entry[1]; + + return ( +
+ {indicatorLabel}:  + + {`${getCurrencySymbolIfNeeded({ + indicator, + platform: question.platform.id, + })}${formatNumber(value)}${getPercentageSymbolIfNeeded({ + indicator, + platform: question.platform.id, + })}`} + +
+ ); })} - {Object.entries(formatQualityIndicators(question.qualityIndicators)).map( - (entry, i) => { - return ( -
- ${entry[0]}:  - - {`${getCurrencySymbolIfNeeded({ - indicator: entry[0], - platform: question.platform.id, - })}${formatNumber(entry[1])}${getPercentageSymbolIfNeeded({ - indicator: entry[0], - platform: question.platform.id, - })}`} - -
- ); - } - )}
); }; @@ -235,16 +184,13 @@ function getStarsColor(numstars: number) { interface Props { question: QuestionFragment; - showTimeStamp: boolean; expandFooterToFullWidth: boolean; } export const QuestionFooter: React.FC = ({ question, - showTimeStamp, expandFooterToFullWidth, }) => { - let debuggingWithBackground = false; return (
= ({
{getstars(question.qualityIndicators.stars)}
{question.platform.label .replace("Good Judgment Open", "GJOpen") @@ -272,12 +218,9 @@ export const QuestionFooter: React.FC = ({ expandFooterToFullWidth ? "justify-self-end mr-4" : "justify-self-center" - } col-span-1 ${debuggingWithBackground ? "bg-red-100" : ""}`} + } col-span-1`} > - {displayQualityIndicators({ - question, - showTimeStamp, - })} +
); diff --git a/src/web/display/DisplayQuestion/index.tsx b/src/web/display/DisplayQuestion/index.tsx index 8d84a67..3bb582f 100644 --- a/src/web/display/DisplayQuestion/index.tsx +++ b/src/web/display/DisplayQuestion/index.tsx @@ -4,7 +4,6 @@ import ReactMarkdown from "react-markdown"; import { CopyText } from "../../common/CopyText"; import { QuestionOptions } from "../../questions/components/QuestionOptions"; -import { formatProbability } from "../../questions/utils"; import { QuestionFragment } from "../../search/queries.generated"; import { Card } from "../Card"; import { QuestionFooter } from "./QuestionFooter"; @@ -87,99 +86,12 @@ const cleanText = (text: string): string => { 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)); + const formatted = truncateText(250, cleanText(description)); // overflow-hidden overflow-ellipsis h-24 return formatted === "" ? null : (
@@ -217,19 +129,10 @@ export const DisplayQuestion: React.FC = ({ expandFooterToFullWidth, showIdToggle, }) => { - const { - platform, - description, - options, - qualityIndicators, - timestamp, - visualization, - } = question; - const lastUpdated = new Date(timestamp * 1000); - const displayTimestampAtBottom = - checkIfDisplayTimeStampAtBottom(qualityIndicators); + const { options } = question; + const lastUpdated = new Date(question.timestamp * 1000); - const yesNoOptions = + const isBinary = options.length === 2 && (options[0].name === "Yes" || options[0].name === "No"); @@ -243,7 +146,7 @@ export const DisplayQuestion: React.FC = ({
) : null}
- + = ({
- {yesNoOptions && ( + {isBinary ? (
-
- - {formatProbability(options[0].probability)} - - - {primaryEstimateAsText(options[0].probability)} - -
-
+ +
- )} - {!yesNoOptions && ( + ) : (
-
+
@@ -303,14 +182,14 @@ export const DisplayQuestion: React.FC = ({ {question.platform.id !== "guesstimate" && options.length < 3 && (
- +
)} {question.platform.id === "guesstimate" && ( Guesstimate Screenshot )} @@ -324,7 +203,6 @@ export const DisplayQuestion: React.FC = ({
diff --git a/src/web/questions/components/QuestionOptions.tsx b/src/web/questions/components/QuestionOptions.tsx index 6ff1122..e200018 100644 --- a/src/web/questions/components/QuestionOptions.tsx +++ b/src/web/questions/components/QuestionOptions.tsx @@ -1,18 +1,95 @@ +import { QuestionFragment } from "../../search/queries.generated"; import { formatProbability } from "../utils"; -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"; - } - }; +type Option = QuestionFragment["options"][0]; +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 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 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"; + } +}; + +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"; + } +}; + +const OptionRow: React.FC<{ option: Option }> = ({ option }) => { return (
= ({ option }) => { ); }; -export const QuestionOptions: React.FC<{ options: any[] }> = ({ options }) => { +export const QuestionOptions: React.FC<{ options: Option[] }> = ({ + options, +}) => { + const isBinary = + options.length === 2 && + (options[0].name === "Yes" || options[0].name === "No"); + 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) => ( - - ))} -
- ); + + if (isBinary) { + return ( +
+ + {formatProbability(options[0].probability)} + + + {primaryEstimateAsText(options[0].probability)} + +
+ ); + } else { + return ( +
+ {optionsMax5.map((option, i) => ( + + ))} +
+ ); + } }; diff --git a/src/web/questions/pages/QuestionPage.tsx b/src/web/questions/pages/QuestionPage.tsx index aa2b083..c36ca11 100644 --- a/src/web/questions/pages/QuestionPage.tsx +++ b/src/web/questions/pages/QuestionPage.tsx @@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown"; import { Query } from "../../common/Query"; import { Card } from "../../display/Card"; +import { QuestionFooter } from "../../display/DisplayQuestion/QuestionFooter"; import { Layout } from "../../display/Layout"; import { QuestionFragment } from "../../search/queries.generated"; import { ssrUrql } from "../../urql"; @@ -39,8 +40,18 @@ const QuestionCardContents: React.FC<{ question: QuestionFragment }> = ({ question, }) => (
-

{question.title}

+

+ + {question.title} + +

+ + {question.description}