refactor: question components
This commit is contained in:
		
							parent
							
								
									ab6f17ffe0
								
							
						
					
					
						commit
						73a47d94c3
					
				| 
						 | 
					@ -3,57 +3,28 @@ import { QuestionFragment } from "../../search/queries.generated";
 | 
				
			||||||
type QualityIndicator = QuestionFragment["qualityIndicators"];
 | 
					type QualityIndicator = QuestionFragment["qualityIndicators"];
 | 
				
			||||||
type IndicatorName = keyof QualityIndicator;
 | 
					type IndicatorName = keyof QualityIndicator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const formatQualityIndicator = (indicator: IndicatorName) => {
 | 
					// this duplication can probably be simplified with typescript magic, but this is good enough for now
 | 
				
			||||||
  let result: string | null = null;
 | 
					type UsedIndicatorName =
 | 
				
			||||||
  switch (indicator) {
 | 
					  | "volume"
 | 
				
			||||||
    case "numForecasts":
 | 
					  | "numForecasters"
 | 
				
			||||||
      result = null;
 | 
					  | "spread"
 | 
				
			||||||
      break;
 | 
					  | "sharesVolume"
 | 
				
			||||||
 | 
					  | "liquidity"
 | 
				
			||||||
 | 
					  | "tradeVolume"
 | 
				
			||||||
 | 
					  | "openInterest";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case "stars":
 | 
					const qualityIndicatorLabels: { [k in UsedIndicatorName]: string } = {
 | 
				
			||||||
      result = null;
 | 
					  // numForecasts: null,
 | 
				
			||||||
      break;
 | 
					  // stars: null,
 | 
				
			||||||
 | 
					  // yesBid: "Yes bid",
 | 
				
			||||||
    case "volume":
 | 
					  // yesAsk: "Yes ask",
 | 
				
			||||||
      result = "Volume";
 | 
					  volume: "Volume",
 | 
				
			||||||
      break;
 | 
					  numForecasters: "Forecasters",
 | 
				
			||||||
 | 
					  spread: "Spread",
 | 
				
			||||||
    case "numForecasters":
 | 
					  sharesVolume: "Shares vol.",
 | 
				
			||||||
      result = "Forecasters";
 | 
					  liquidity: "Liquidity",
 | 
				
			||||||
      break;
 | 
					  tradeVolume: "Volume",
 | 
				
			||||||
 | 
					  openInterest: "Interest",
 | 
				
			||||||
    // 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 formatNumber = (num) => {
 | 
					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*/
 | 
					/* Display functions*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getPercentageSymbolIfNeeded = ({
 | 
					const getPercentageSymbolIfNeeded = ({
 | 
				
			||||||
  indicator,
 | 
					  indicator,
 | 
				
			||||||
  platform,
 | 
					  platform,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  indicator: string;
 | 
					  indicator: UsedIndicatorName;
 | 
				
			||||||
  platform: string;
 | 
					  platform: string;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  let indicatorsWhichNeedPercentageSymbol = ["Spread"];
 | 
					  let indicatorsWhichNeedPercentageSymbol: IndicatorName[] = ["spread"];
 | 
				
			||||||
  if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) {
 | 
					  if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) {
 | 
				
			||||||
    return "%";
 | 
					    return "%";
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
| 
						 | 
					@ -98,10 +58,15 @@ const getCurrencySymbolIfNeeded = ({
 | 
				
			||||||
  indicator,
 | 
					  indicator,
 | 
				
			||||||
  platform,
 | 
					  platform,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  indicator: string;
 | 
					  indicator: UsedIndicatorName;
 | 
				
			||||||
  platform: string;
 | 
					  platform: string;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  let indicatorsWhichNeedCurrencySymbol = ["Volume", "Interest", "Liquidity"];
 | 
					  const indicatorsWhichNeedCurrencySymbol: IndicatorName[] = [
 | 
				
			||||||
 | 
					    "volume",
 | 
				
			||||||
 | 
					    "tradeVolume",
 | 
				
			||||||
 | 
					    "openInterest",
 | 
				
			||||||
 | 
					    "liquidity",
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
  let dollarPlatforms = ["predictit", "kalshi", "polymarket"];
 | 
					  let dollarPlatforms = ["predictit", "kalshi", "polymarket"];
 | 
				
			||||||
  if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) {
 | 
					  if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) {
 | 
				
			||||||
    if (dollarPlatforms.includes(platform)) {
 | 
					    if (dollarPlatforms.includes(platform)) {
 | 
				
			||||||
| 
						 | 
					@ -114,66 +79,50 @@ const getCurrencySymbolIfNeeded = ({
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showFirstQualityIndicator: React.FC<{
 | 
					const FirstQualityIndicator: React.FC<{
 | 
				
			||||||
  question: QuestionFragment;
 | 
					  question: QuestionFragment;
 | 
				
			||||||
  showTimeStamp: boolean;
 | 
					}> = ({ question }) => {
 | 
				
			||||||
}> = ({ question, showTimeStamp }) => {
 | 
					  if (question.qualityIndicators.numForecasts) {
 | 
				
			||||||
  const lastUpdated = new Date(question.timestamp * 1000);
 | 
					 | 
				
			||||||
  if (!!question.qualityIndicators.numForecasts) {
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="flex col-span-1 row-span-1">
 | 
					      <div className="flex">
 | 
				
			||||||
        {/*<span>{` ${numforecasts == 1 ? "Forecast" : "Forecasts:"}`}</span> */}
 | 
					 | 
				
			||||||
        <span>Forecasts:</span> 
 | 
					        <span>Forecasts:</span> 
 | 
				
			||||||
        <span className="font-bold">
 | 
					        <span className="font-bold">
 | 
				
			||||||
          {Number(question.qualityIndicators.numForecasts).toFixed(0)}
 | 
					          {Number(question.qualityIndicators.numForecasts).toFixed(0)}
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  } else if (showTimeStamp) {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <span className="hidden sm:flex items-center justify-center text-gray-600 mt-2">
 | 
					 | 
				
			||||||
        <svg className="ml-4 mr-1 mt-1" height="10" width="16">
 | 
					 | 
				
			||||||
          <circle cx="4" cy="4" r="4" fill="rgb(29, 78, 216)" />
 | 
					 | 
				
			||||||
        </svg>
 | 
					 | 
				
			||||||
        {`Last updated: ${
 | 
					 | 
				
			||||||
          lastUpdated ? lastUpdated.toISOString().slice(0, 10) : "unknown"
 | 
					 | 
				
			||||||
        }`}
 | 
					 | 
				
			||||||
      </span>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const displayQualityIndicators: React.FC<{
 | 
					const QualityIndicatorsList: React.FC<{
 | 
				
			||||||
  question: QuestionFragment;
 | 
					  question: QuestionFragment;
 | 
				
			||||||
  showTimeStamp: boolean;
 | 
					}> = ({ question }) => {
 | 
				
			||||||
}> = ({ question, showTimeStamp }) => {
 | 
					 | 
				
			||||||
  const { qualityIndicators } = question;
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="text-sm">
 | 
					    <div className="text-sm">
 | 
				
			||||||
      {showFirstQualityIndicator({
 | 
					      <FirstQualityIndicator question={question} />
 | 
				
			||||||
        question,
 | 
					      {Object.entries(question.qualityIndicators).map((entry, i) => {
 | 
				
			||||||
        showTimeStamp,
 | 
					        const indicatorLabel = qualityIndicatorLabels[entry[0]];
 | 
				
			||||||
      })}
 | 
					        if (!indicatorLabel || entry[1] === null) return;
 | 
				
			||||||
      {Object.entries(formatQualityIndicators(question.qualityIndicators)).map(
 | 
					        const indicator = entry[0] as UsedIndicatorName; // guaranteed by the previous line
 | 
				
			||||||
        (entry, i) => {
 | 
					        const value = entry[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <div className="col-span-1 row-span-1">
 | 
					          <div key={indicator}>
 | 
				
			||||||
              <span>${entry[0]}:</span> 
 | 
					            <span>{indicatorLabel}:</span> 
 | 
				
			||||||
            <span className="font-bold">
 | 
					            <span className="font-bold">
 | 
				
			||||||
              {`${getCurrencySymbolIfNeeded({
 | 
					              {`${getCurrencySymbolIfNeeded({
 | 
				
			||||||
                  indicator: entry[0],
 | 
					                indicator,
 | 
				
			||||||
                platform: question.platform.id,
 | 
					                platform: question.platform.id,
 | 
				
			||||||
                })}${formatNumber(entry[1])}${getPercentageSymbolIfNeeded({
 | 
					              })}${formatNumber(value)}${getPercentageSymbolIfNeeded({
 | 
				
			||||||
                  indicator: entry[0],
 | 
					                indicator,
 | 
				
			||||||
                platform: question.platform.id,
 | 
					                platform: question.platform.id,
 | 
				
			||||||
              })}`}
 | 
					              })}`}
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        }
 | 
					      })}
 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -235,16 +184,13 @@ function getStarsColor(numstars: number) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Props {
 | 
					interface Props {
 | 
				
			||||||
  question: QuestionFragment;
 | 
					  question: QuestionFragment;
 | 
				
			||||||
  showTimeStamp: boolean;
 | 
					 | 
				
			||||||
  expandFooterToFullWidth: boolean;
 | 
					  expandFooterToFullWidth: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const QuestionFooter: React.FC<Props> = ({
 | 
					export const QuestionFooter: React.FC<Props> = ({
 | 
				
			||||||
  question,
 | 
					  question,
 | 
				
			||||||
  showTimeStamp,
 | 
					 | 
				
			||||||
  expandFooterToFullWidth,
 | 
					  expandFooterToFullWidth,
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  let debuggingWithBackground = false;
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      className={`grid grid-cols-3 ${
 | 
					      className={`grid grid-cols-3 ${
 | 
				
			||||||
| 
						 | 
					@ -254,14 +200,14 @@ export const QuestionFooter: React.FC<Props> = ({
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={`self-center col-span-1 ${getStarsColor(
 | 
					        className={`self-center col-span-1 ${getStarsColor(
 | 
				
			||||||
          question.qualityIndicators.stars
 | 
					          question.qualityIndicators.stars
 | 
				
			||||||
        )} ${debuggingWithBackground ? "bg-red-200" : ""}`}
 | 
					        )}`}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {getstars(question.qualityIndicators.stars)}
 | 
					        {getstars(question.qualityIndicators.stars)}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={`${
 | 
					        className={`${
 | 
				
			||||||
          expandFooterToFullWidth ? "place-self-center" : "self-center"
 | 
					          expandFooterToFullWidth ? "place-self-center" : "self-center"
 | 
				
			||||||
        }  col-span-1 font-bold ${debuggingWithBackground ? "bg-red-100" : ""}`}
 | 
					        }  col-span-1 font-bold`}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {question.platform.label
 | 
					        {question.platform.label
 | 
				
			||||||
          .replace("Good Judgment Open", "GJOpen")
 | 
					          .replace("Good Judgment Open", "GJOpen")
 | 
				
			||||||
| 
						 | 
					@ -272,12 +218,9 @@ export const QuestionFooter: React.FC<Props> = ({
 | 
				
			||||||
          expandFooterToFullWidth
 | 
					          expandFooterToFullWidth
 | 
				
			||||||
            ? "justify-self-end mr-4"
 | 
					            ? "justify-self-end mr-4"
 | 
				
			||||||
            : "justify-self-center"
 | 
					            : "justify-self-center"
 | 
				
			||||||
        } col-span-1 ${debuggingWithBackground ? "bg-red-100" : ""}`}
 | 
					        } col-span-1`}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {displayQualityIndicators({
 | 
					        <QualityIndicatorsList question={question} />
 | 
				
			||||||
          question,
 | 
					 | 
				
			||||||
          showTimeStamp,
 | 
					 | 
				
			||||||
        })}
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ import ReactMarkdown from "react-markdown";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CopyText } from "../../common/CopyText";
 | 
					import { CopyText } from "../../common/CopyText";
 | 
				
			||||||
import { QuestionOptions } from "../../questions/components/QuestionOptions";
 | 
					import { QuestionOptions } from "../../questions/components/QuestionOptions";
 | 
				
			||||||
import { formatProbability } from "../../questions/utils";
 | 
					 | 
				
			||||||
import { QuestionFragment } from "../../search/queries.generated";
 | 
					import { QuestionFragment } from "../../search/queries.generated";
 | 
				
			||||||
import { Card } from "../Card";
 | 
					import { Card } from "../Card";
 | 
				
			||||||
import { QuestionFooter } from "./QuestionFooter";
 | 
					import { QuestionFooter } from "./QuestionFooter";
 | 
				
			||||||
| 
						 | 
					@ -87,99 +86,12 @@ const cleanText = (text: string): string => {
 | 
				
			||||||
  return 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
 | 
					// Auxiliary components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DisplayMarkdown: React.FC<{ description: string }> = ({
 | 
					const DisplayMarkdown: React.FC<{ description: string }> = ({
 | 
				
			||||||
  description,
 | 
					  description,
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  let formatted = truncateText(250, cleanText(description));
 | 
					  const formatted = truncateText(250, cleanText(description));
 | 
				
			||||||
  // overflow-hidden overflow-ellipsis h-24
 | 
					  // overflow-hidden overflow-ellipsis h-24
 | 
				
			||||||
  return formatted === "" ? null : (
 | 
					  return formatted === "" ? null : (
 | 
				
			||||||
    <div className="overflow-clip">
 | 
					    <div className="overflow-clip">
 | 
				
			||||||
| 
						 | 
					@ -217,19 +129,10 @@ export const DisplayQuestion: React.FC<Props> = ({
 | 
				
			||||||
  expandFooterToFullWidth,
 | 
					  expandFooterToFullWidth,
 | 
				
			||||||
  showIdToggle,
 | 
					  showIdToggle,
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const {
 | 
					  const { options } = question;
 | 
				
			||||||
    platform,
 | 
					  const lastUpdated = new Date(question.timestamp * 1000);
 | 
				
			||||||
    description,
 | 
					 | 
				
			||||||
    options,
 | 
					 | 
				
			||||||
    qualityIndicators,
 | 
					 | 
				
			||||||
    timestamp,
 | 
					 | 
				
			||||||
    visualization,
 | 
					 | 
				
			||||||
  } = question;
 | 
					 | 
				
			||||||
  const lastUpdated = new Date(timestamp * 1000);
 | 
					 | 
				
			||||||
  const displayTimestampAtBottom =
 | 
					 | 
				
			||||||
    checkIfDisplayTimeStampAtBottom(qualityIndicators);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const yesNoOptions =
 | 
					  const isBinary =
 | 
				
			||||||
    options.length === 2 &&
 | 
					    options.length === 2 &&
 | 
				
			||||||
    (options[0].name === "Yes" || options[0].name === "No");
 | 
					    (options[0].name === "Yes" || options[0].name === "No");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -243,7 +146,7 @@ export const DisplayQuestion: React.FC<Props> = ({
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          ) : null}
 | 
					          ) : null}
 | 
				
			||||||
          <div>
 | 
					          <div>
 | 
				
			||||||
            <Link href={`/questions/${question.id}`}>
 | 
					            <Link href={`/questions/${question.id}`} passHref>
 | 
				
			||||||
              <a className="float-right block ml-2 mt-1.5">
 | 
					              <a className="float-right block ml-2 mt-1.5">
 | 
				
			||||||
                <FaExpand
 | 
					                <FaExpand
 | 
				
			||||||
                  size="18"
 | 
					                  size="18"
 | 
				
			||||||
| 
						 | 
					@ -261,41 +164,17 @@ export const DisplayQuestion: React.FC<Props> = ({
 | 
				
			||||||
              </a>
 | 
					              </a>
 | 
				
			||||||
            </Card.Title>
 | 
					            </Card.Title>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          {yesNoOptions && (
 | 
					          {isBinary ? (
 | 
				
			||||||
            <div className="flex justify-between">
 | 
					            <div className="flex justify-between">
 | 
				
			||||||
              <div className="space-x-2">
 | 
					              <QuestionOptions options={options} />
 | 
				
			||||||
                <span
 | 
					              <div className={`hidden ${showTimeStamp ? "sm:block" : ""}`}>
 | 
				
			||||||
                  className={`${primaryForecastColor(
 | 
					 | 
				
			||||||
                    options[0].probability
 | 
					 | 
				
			||||||
                  )} text-white w-16 rounded-md px-1.5 py-0.5 font-bold`}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                  {formatProbability(options[0].probability)}
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
                <span
 | 
					 | 
				
			||||||
                  className={`${textColor(
 | 
					 | 
				
			||||||
                    options[0].probability
 | 
					 | 
				
			||||||
                  )} text-gray-500 inline-block`}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                  {primaryEstimateAsText(options[0].probability)}
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div
 | 
					 | 
				
			||||||
                className={`hidden ${
 | 
					 | 
				
			||||||
                  showTimeStamp && !displayTimestampAtBottom ? "sm:block" : ""
 | 
					 | 
				
			||||||
                }`}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <LastUpdated timestamp={lastUpdated} />
 | 
					                <LastUpdated timestamp={lastUpdated} />
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          )}
 | 
					          ) : (
 | 
				
			||||||
          {!yesNoOptions && (
 | 
					 | 
				
			||||||
            <div className="space-y-2">
 | 
					            <div className="space-y-2">
 | 
				
			||||||
              <QuestionOptions options={options} />
 | 
					              <QuestionOptions options={options} />
 | 
				
			||||||
              <div
 | 
					              <div className={`hidden ${showTimeStamp ? "sm:block" : ""} ml-6`}>
 | 
				
			||||||
                className={`hidden ${
 | 
					 | 
				
			||||||
                  showTimeStamp && !displayTimestampAtBottom ? "sm:block" : ""
 | 
					 | 
				
			||||||
                } ml-6`}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <LastUpdated timestamp={lastUpdated} />
 | 
					                <LastUpdated timestamp={lastUpdated} />
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
| 
						 | 
					@ -303,14 +182,14 @@ export const DisplayQuestion: React.FC<Props> = ({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {question.platform.id !== "guesstimate" && options.length < 3 && (
 | 
					          {question.platform.id !== "guesstimate" && options.length < 3 && (
 | 
				
			||||||
            <div className="text-gray-500">
 | 
					            <div className="text-gray-500">
 | 
				
			||||||
              <DisplayMarkdown description={description} />
 | 
					              <DisplayMarkdown description={question.description} />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {question.platform.id === "guesstimate" && (
 | 
					          {question.platform.id === "guesstimate" && (
 | 
				
			||||||
            <img
 | 
					            <img
 | 
				
			||||||
              className="rounded-sm"
 | 
					              className="rounded-sm"
 | 
				
			||||||
              src={visualization}
 | 
					              src={question.visualization}
 | 
				
			||||||
              alt="Guesstimate Screenshot"
 | 
					              alt="Guesstimate Screenshot"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
| 
						 | 
					@ -324,7 +203,6 @@ export const DisplayQuestion: React.FC<Props> = ({
 | 
				
			||||||
        <div className="w-full">
 | 
					        <div className="w-full">
 | 
				
			||||||
          <QuestionFooter
 | 
					          <QuestionFooter
 | 
				
			||||||
            question={question}
 | 
					            question={question}
 | 
				
			||||||
            showTimeStamp={showTimeStamp && displayTimestampAtBottom}
 | 
					 | 
				
			||||||
            expandFooterToFullWidth={expandFooterToFullWidth}
 | 
					            expandFooterToFullWidth={expandFooterToFullWidth}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,82 @@
 | 
				
			||||||
 | 
					import { QuestionFragment } from "../../search/queries.generated";
 | 
				
			||||||
import { formatProbability } from "../utils";
 | 
					import { formatProbability } from "../utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const OptionRow: React.FC<{ option: any }> = ({ option }) => {
 | 
					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) => {
 | 
					const chooseColor = (probability: number) => {
 | 
				
			||||||
  if (probability < 0.1) {
 | 
					  if (probability < 0.1) {
 | 
				
			||||||
    return "bg-blue-50 text-blue-500";
 | 
					    return "bg-blue-50 text-blue-500";
 | 
				
			||||||
| 
						 | 
					@ -13,6 +89,7 @@ const OptionRow: React.FC<{ option: any }> = ({ option }) => {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const OptionRow: React.FC<{ option: Option }> = ({ option }) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="flex items-center">
 | 
					    <div className="flex items-center">
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
| 
						 | 
					@ -29,9 +106,36 @@ const OptionRow: React.FC<{ option: any }> = ({ 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 optionsSorted = options.sort((a, b) => b.probability - a.probability);
 | 
				
			||||||
  const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options.
 | 
					  const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isBinary) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div className="space-x-2">
 | 
				
			||||||
 | 
					        <span
 | 
				
			||||||
 | 
					          className={`${primaryForecastColor(
 | 
				
			||||||
 | 
					            options[0].probability
 | 
				
			||||||
 | 
					          )} text-white w-16 rounded-md px-1.5 py-0.5 font-bold`}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {formatProbability(options[0].probability)}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					        <span
 | 
				
			||||||
 | 
					          className={`${textColor(
 | 
				
			||||||
 | 
					            options[0].probability
 | 
				
			||||||
 | 
					          )} text-gray-500 inline-block`}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {primaryEstimateAsText(options[0].probability)}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="space-y-2">
 | 
					      <div className="space-y-2">
 | 
				
			||||||
        {optionsMax5.map((option, i) => (
 | 
					        {optionsMax5.map((option, i) => (
 | 
				
			||||||
| 
						 | 
					@ -39,4 +143,5 @@ export const QuestionOptions: React.FC<{ options: any[] }> = ({ options }) => {
 | 
				
			||||||
        ))}
 | 
					        ))}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Query } from "../../common/Query";
 | 
					import { Query } from "../../common/Query";
 | 
				
			||||||
import { Card } from "../../display/Card";
 | 
					import { Card } from "../../display/Card";
 | 
				
			||||||
 | 
					import { QuestionFooter } from "../../display/DisplayQuestion/QuestionFooter";
 | 
				
			||||||
import { Layout } from "../../display/Layout";
 | 
					import { Layout } from "../../display/Layout";
 | 
				
			||||||
import { QuestionFragment } from "../../search/queries.generated";
 | 
					import { QuestionFragment } from "../../search/queries.generated";
 | 
				
			||||||
import { ssrUrql } from "../../urql";
 | 
					import { ssrUrql } from "../../urql";
 | 
				
			||||||
| 
						 | 
					@ -39,8 +40,18 @@ const QuestionCardContents: React.FC<{ question: QuestionFragment }> = ({
 | 
				
			||||||
  question,
 | 
					  question,
 | 
				
			||||||
}) => (
 | 
					}) => (
 | 
				
			||||||
  <div className="space-y-4">
 | 
					  <div className="space-y-4">
 | 
				
			||||||
    <h1>{question.title}</h1>
 | 
					    <h1>
 | 
				
			||||||
 | 
					      <a
 | 
				
			||||||
 | 
					        className="text-black no-underline"
 | 
				
			||||||
 | 
					        href={question.url}
 | 
				
			||||||
 | 
					        target="_blank"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {question.title}
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    </h1>
 | 
				
			||||||
 | 
					    <QuestionFooter question={question} expandFooterToFullWidth={true} />
 | 
				
			||||||
    <QuestionOptions options={question.options} />
 | 
					    <QuestionOptions options={question.options} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ReactMarkdown linkTarget="_blank" className="font-normal">
 | 
					    <ReactMarkdown linkTarget="_blank" className="font-normal">
 | 
				
			||||||
      {question.description}
 | 
					      {question.description}
 | 
				
			||||||
    </ReactMarkdown>
 | 
					    </ReactMarkdown>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user