Merge branch 'questions-page-2' after solving conflicts
This commit is contained in:
commit
07a7bb0c55
6
.eslintrc
Normal file
6
.eslintrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": ["next", "prettier"],
|
||||
"rules": {
|
||||
"next/no-document-import-in-page": "off"
|
||||
}
|
||||
}
|
1001
package-lock.json
generated
1001
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -89,7 +89,8 @@
|
|||
"ts-node": "^10.7.0",
|
||||
"tunnel": "^0.0.6",
|
||||
"urql": "^2.2.0",
|
||||
"urql-custom-scalars-exchange": "^0.1.5"
|
||||
"urql-custom-scalars-exchange": "^0.1.5",
|
||||
"victory": "^36.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
|
|
|
@ -5,7 +5,8 @@ import React from "react";
|
|||
|
||||
import { platforms } from "../backend/platforms";
|
||||
import { DisplayQuestion } from "../web/display/DisplayQuestion";
|
||||
import { QuestionFragment, SearchDocument } from "../web/search/queries.generated";
|
||||
import { QuestionFragment } from "../web/fragments.generated";
|
||||
import { SearchDocument } from "../web/search/queries.generated";
|
||||
import { ssrUrql } from "../web/urql";
|
||||
|
||||
interface Props {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as Types from '../../graphql/types.generated';
|
||||
|
||||
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
import { QuestionFragmentDoc } from '../search/queries.generated';
|
||||
import { QuestionFragmentDoc } from '../fragments.generated';
|
||||
export type DashboardFragment = { __typename?: 'Dashboard', id: string, title: string, description: string, creator: string, questions: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } }> };
|
||||
|
||||
export type DashboardByIdQueryVariables = Types.Exact<{
|
||||
|
|
|
@ -2,7 +2,7 @@ import domtoimage from "dom-to-image"; // https://github.com/tsayen/dom-to-image
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
|
||||
import { QuestionFragment } from "../search/queries.generated";
|
||||
import { QuestionFragment } from "../fragments.generated";
|
||||
import { uploadToImgur } from "../worker/uploadToImgur";
|
||||
import { DisplayQuestion } from "./DisplayQuestion";
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { QuestionFragment } from "../../search/queries.generated";
|
||||
import { QuestionFragment } from "../../fragments.generated";
|
||||
|
||||
type QualityIndicator = QuestionFragment["qualityIndicators"];
|
||||
type IndicatorName = keyof QualityIndicator;
|
||||
|
|
|
@ -3,8 +3,9 @@ import { FaExpand } from "react-icons/fa";
|
|||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
import { CopyText } from "../../common/CopyText";
|
||||
import { QuestionFragment } from "../../fragments.generated";
|
||||
import { QuestionOptions } from "../../questions/components/QuestionOptions";
|
||||
import { QuestionFragment } from "../../search/queries.generated";
|
||||
import { cleanText } from "../../utils";
|
||||
import { Card } from "../Card";
|
||||
import { QuestionFooter } from "./QuestionFooter";
|
||||
|
||||
|
@ -68,24 +69,6 @@ if (!String.prototype.replaceAll) {
|
|||
};
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// Auxiliary components
|
||||
|
||||
const DisplayMarkdown: React.FC<{ description: string }> = ({
|
||||
|
@ -146,16 +129,14 @@ export const DisplayQuestion: React.FC<Props> = ({
|
|||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
{process.env.NEXT_PUBLIC_ENABLE_QUESTION_PAGES ? (
|
||||
<Link href={`/questions/${question.id}`} passHref>
|
||||
<a className="float-right block ml-2 mt-1.5">
|
||||
<FaExpand
|
||||
size="18"
|
||||
className="text-gray-400 hover:text-gray-700"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
) : null}
|
||||
<Link href={`/questions/${question.id}`} passHref>
|
||||
<a className="float-right block ml-2 mt-1.5">
|
||||
<FaExpand
|
||||
size="18"
|
||||
className="text-gray-400 hover:text-gray-700"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<Card.Title>
|
||||
<a
|
||||
className="text-black no-underline"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
import { QuestionFragment } from "../search/queries.generated";
|
||||
import { QuestionFragment } from "../fragments.generated";
|
||||
import { DisplayQuestion } from "./DisplayQuestion";
|
||||
|
||||
interface Props {
|
||||
|
|
9
src/web/fragments.generated.tsx
Normal file
9
src/web/fragments.generated.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import * as Types from '../graphql/types.generated';
|
||||
|
||||
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
export type QuestionFragment = { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } };
|
||||
|
||||
export type QuestionWithHistoryFragment = { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, history: Array<{ __typename?: 'History', timestamp: number, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }> }>, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } };
|
||||
|
||||
export const QuestionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Question"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Question"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"probability"}}]}},{"kind":"Field","name":{"kind":"Name","value":"platform"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}},{"kind":"Field","name":{"kind":"Name","value":"qualityIndicators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stars"}},{"kind":"Field","name":{"kind":"Name","value":"numForecasts"}},{"kind":"Field","name":{"kind":"Name","value":"numForecasters"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"spread"}},{"kind":"Field","name":{"kind":"Name","value":"sharesVolume"}},{"kind":"Field","name":{"kind":"Name","value":"openInterest"}},{"kind":"Field","name":{"kind":"Name","value":"liquidity"}},{"kind":"Field","name":{"kind":"Name","value":"tradeVolume"}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualization"}}]}}]} as unknown as DocumentNode<QuestionFragment, unknown>;
|
||||
export const QuestionWithHistoryFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"QuestionWithHistory"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Question"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Question"}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"probability"}}]}}]}}]}},...QuestionFragmentDoc.definitions]} as unknown as DocumentNode<QuestionWithHistoryFragment, unknown>;
|
38
src/web/fragments.graphql
Normal file
38
src/web/fragments.graphql
Normal file
|
@ -0,0 +1,38 @@
|
|||
fragment Question on Question {
|
||||
id
|
||||
url
|
||||
title
|
||||
description
|
||||
timestamp
|
||||
options {
|
||||
name
|
||||
probability
|
||||
}
|
||||
platform {
|
||||
id
|
||||
label
|
||||
}
|
||||
qualityIndicators {
|
||||
stars
|
||||
numForecasts
|
||||
numForecasters
|
||||
volume
|
||||
spread
|
||||
sharesVolume
|
||||
openInterest
|
||||
liquidity
|
||||
tradeVolume
|
||||
}
|
||||
visualization
|
||||
}
|
||||
|
||||
fragment QuestionWithHistory on Question {
|
||||
...Question
|
||||
history {
|
||||
timestamp
|
||||
options {
|
||||
name
|
||||
probability
|
||||
}
|
||||
}
|
||||
}
|
253
src/web/questions/components/HistoryChart.tsx
Normal file
253
src/web/questions/components/HistoryChart.tsx
Normal file
|
@ -0,0 +1,253 @@
|
|||
import React from "react";
|
||||
import {
|
||||
VictoryAxis,
|
||||
VictoryChart,
|
||||
VictoryGroup,
|
||||
VictoryLabel,
|
||||
VictoryLegend,
|
||||
VictoryScatter,
|
||||
VictoryLine,
|
||||
VictoryTheme,
|
||||
VictoryTooltip,
|
||||
VictoryVoronoiContainer,
|
||||
} from "victory";
|
||||
|
||||
import { QuestionWithHistoryFragment } from "../../fragments.generated";
|
||||
|
||||
interface Props {
|
||||
question: QuestionWithHistoryFragment;
|
||||
}
|
||||
|
||||
const buildDataset = (n, fn) => {
|
||||
return Array.from(Array(n).keys()).map((x) => ({
|
||||
date: x,
|
||||
probability: fn(x),
|
||||
}));
|
||||
};
|
||||
|
||||
let getDate0 = (x) => {
|
||||
// for fake data
|
||||
let date = new Date(x);
|
||||
return date.toISOString().slice(5, 10).replaceAll("-", "/");
|
||||
};
|
||||
|
||||
let formatOptionName = (name) => {
|
||||
return name.length > 10 ? name.slice(0, 8) + "..." : name;
|
||||
};
|
||||
|
||||
let getLength = (str) => {
|
||||
let capitalLetterLengthMultiplier = 1.25;
|
||||
let smallLetterMultiplier = 0.8;
|
||||
let numUpper = (str.match(/[A-Z]/g) || []).length;
|
||||
let numSmallLetters = (str.match(/[fijlrt]/g) || []).length;
|
||||
let numSpaces = (str.match(/[\s]/g) || []).length;
|
||||
let length =
|
||||
str.length +
|
||||
-numUpper -
|
||||
numSmallLetters +
|
||||
numUpper * capitalLetterLengthMultiplier +
|
||||
(numSmallLetters + numSpaces) * smallLetterMultiplier;
|
||||
return length;
|
||||
};
|
||||
|
||||
let timestampToString = (x) => {
|
||||
// for real timestamps
|
||||
console.log(x);
|
||||
let date = new Date(Date.parse(x));
|
||||
let dayOfMonth = date.getDate();
|
||||
let month = date.getMonth() + 1;
|
||||
let year = date.getFullYear();
|
||||
let dateString = `${("0" + dayOfMonth).slice(-2)}/${("0" + month).slice(
|
||||
-2
|
||||
)}/${year.toString().slice(-2)}`;
|
||||
console.log(dateString);
|
||||
return dateString;
|
||||
};
|
||||
|
||||
let dataAsXy = (data) =>
|
||||
data.map((datum) => ({
|
||||
x: timestampToString(datum.date), //getDate(datum.date * (1000 * 60 * 60 * 24)),
|
||||
y: datum.probability,
|
||||
name: datum.name,
|
||||
}));
|
||||
|
||||
const colors = ["dodgerblue", "crimson", "seagreen", "darkviolet", "turquoise"];
|
||||
const getVictoryGroup = (data, i) => {
|
||||
return (
|
||||
<VictoryGroup color={colors[i] || "darkgray"} data={dataAsXy(data)}>
|
||||
<VictoryScatter
|
||||
name={`scatter-${i}`}
|
||||
//style={{ labels: { display: "none" } }}
|
||||
size={({ active }) => (active ? 3.75 : 3)}
|
||||
//labels={() => null}
|
||||
//labelComponent={<span></span>}
|
||||
/>
|
||||
|
||||
<VictoryLine
|
||||
name={`line-${i}`}
|
||||
//style={{ labels: { display: "none" } }}
|
||||
//labels={() => null}
|
||||
//labelComponent={<span></span>}
|
||||
/>
|
||||
</VictoryGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const HistoryChart: React.FC<Props> = ({ question }) => {
|
||||
let dataSetsNames = [];
|
||||
question.history.forEach((item) => {
|
||||
let optionNames = item.options.map((option) => option.name);
|
||||
dataSetsNames.push(...optionNames);
|
||||
});
|
||||
dataSetsNames = [...new Set(dataSetsNames)].slice(0, 5); // take the first 5
|
||||
let dataSets = [];
|
||||
let maxProbability = 0;
|
||||
let longestNameLength = 0;
|
||||
|
||||
for (let name of dataSetsNames) {
|
||||
let newDataset = [];
|
||||
let previousDate = -Infinity;
|
||||
for (let item of question.history) {
|
||||
let relevantItemsArray = item.options.filter((x) => x.name == name);
|
||||
let date = new Date(item.timestamp * 1000);
|
||||
if (
|
||||
relevantItemsArray.length == 1 &&
|
||||
item.timestamp - previousDate > 12 * 60 * 60
|
||||
) {
|
||||
let relevantItem = relevantItemsArray[0];
|
||||
// if (relevantItem.type == "PROBABILITY") {
|
||||
let result = {
|
||||
date,
|
||||
probability: relevantItem.probability,
|
||||
name: relevantItem.name,
|
||||
};
|
||||
maxProbability =
|
||||
relevantItem.probability > maxProbability
|
||||
? relevantItem.probability
|
||||
: maxProbability;
|
||||
let length = getLength(relevantItem.name);
|
||||
longestNameLength =
|
||||
length > longestNameLength ? length : longestNameLength;
|
||||
newDataset.push(result);
|
||||
// }
|
||||
previousDate = item.timestamp;
|
||||
}
|
||||
}
|
||||
dataSets.push(newDataset);
|
||||
}
|
||||
let letterLength = 7;
|
||||
let labelLegendStart = 45;
|
||||
|
||||
let domainMax =
|
||||
maxProbability < 0.5 ? Math.round(10 * (maxProbability + 0.05)) / 10 : 1;
|
||||
let dataSetsLength = dataSets.length;
|
||||
let goldenRatio = (1 + Math.sqrt(5)) / 2;
|
||||
let width = 750;
|
||||
let height = width / goldenRatio;
|
||||
let padding = {
|
||||
top: 20,
|
||||
bottom: 50,
|
||||
left: 0,
|
||||
right: labelLegendStart + letterLength * longestNameLength,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center w-full">
|
||||
<div className="w-10/12">
|
||||
<a
|
||||
className="text‑inherit no-underline"
|
||||
href={question.url}
|
||||
target="_blank"
|
||||
></a>
|
||||
<VictoryChart
|
||||
domainPadding={20}
|
||||
padding={padding}
|
||||
theme={VictoryTheme.material}
|
||||
height={height}
|
||||
width={width}
|
||||
containerComponent={
|
||||
<VictoryVoronoiContainer
|
||||
labels={({ datum }) => `Not shown`}
|
||||
labelComponent={
|
||||
<VictoryTooltip
|
||||
pointerLength={0}
|
||||
dy={-12}
|
||||
text={({ datum }) =>
|
||||
`${datum.name}: ${Math.round(datum.y * 100)}%`
|
||||
}
|
||||
style={{
|
||||
fontSize: 15,
|
||||
fill: "black",
|
||||
strokeWidth: 0.05,
|
||||
}}
|
||||
flyoutStyle={{
|
||||
stroke: "black",
|
||||
fill: "white",
|
||||
}}
|
||||
cornerRadius={0}
|
||||
flyoutPadding={7}
|
||||
/>
|
||||
}
|
||||
voronoiBlacklist={
|
||||
["line-0", "line-1", "line-2", "line-3", "line-4"]
|
||||
//Array.from(Array(5).keys()).map((x, i) => `line${i}`)
|
||||
// see: https://github.com/FormidableLabs/victory/issues/545
|
||||
}
|
||||
/>
|
||||
}
|
||||
domain={{
|
||||
y: [0, domainMax],
|
||||
}}
|
||||
>
|
||||
<VictoryLegend
|
||||
x={width - labelLegendStart - letterLength * longestNameLength}
|
||||
y={height / 2 - 18 - (dataSetsLength - 1) * 13}
|
||||
orientation="vertical"
|
||||
gutter={20}
|
||||
style={{ border: { stroke: "black" }, labels: { fontSize: 15 } }}
|
||||
data={Array.from(Array(dataSetsLength).keys()).map((i) => ({
|
||||
name: dataSetsNames[i],
|
||||
symbol: { fill: colors[i] },
|
||||
}))}
|
||||
/>
|
||||
|
||||
{dataSets
|
||||
.slice(0, 5)
|
||||
.map((dataset, i) => getVictoryGroup(dataset, i))}
|
||||
<VictoryAxis
|
||||
// tickValues specifies both the number of ticks and where
|
||||
// they are placed on the axis
|
||||
// tickValues={dataAsXy.map((datum) => datum.x)}
|
||||
// tickFormat={dataAsXy.map((datum) => datum.x)}
|
||||
tickCount={7}
|
||||
style={{
|
||||
grid: { stroke: null, strokeWidth: 0.5 },
|
||||
}}
|
||||
//axisLabelComponent={
|
||||
// <VictoryLabel dy={40} style={{ fontSize: 10, fill: "gray" }} />
|
||||
//}
|
||||
// label="Date (dd/mm/yy)"
|
||||
tickLabelComponent={
|
||||
<VictoryLabel
|
||||
dy={10}
|
||||
angle={-30}
|
||||
style={{ fontSize: 15, fill: "gray" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<VictoryAxis
|
||||
dependentAxis
|
||||
// tickFormat specifies how ticks should be displayed
|
||||
tickFormat={(x) => `${x * 100}%`}
|
||||
style={{
|
||||
grid: { stroke: "#D3D3D3", strokeWidth: 0.5 },
|
||||
}}
|
||||
tickLabelComponent={
|
||||
<VictoryLabel dy={0} style={{ fontSize: 15, fill: "gray" }} />
|
||||
}
|
||||
/>
|
||||
</VictoryChart>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
227
src/web/questions/components/QuestionIndicators.tsx
Normal file
227
src/web/questions/components/QuestionIndicators.tsx
Normal file
|
@ -0,0 +1,227 @@
|
|||
import { QuestionFragment } from "../../fragments.generated";
|
||||
|
||||
type QualityIndicator = QuestionFragment["qualityIndicators"];
|
||||
type IndicatorName = keyof QualityIndicator;
|
||||
|
||||
// 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";
|
||||
|
||||
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) => {
|
||||
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";
|
||||
}
|
||||
};
|
||||
|
||||
/* Display functions*/
|
||||
|
||||
const getPercentageSymbolIfNeeded = ({
|
||||
indicator,
|
||||
platform,
|
||||
}: {
|
||||
indicator: UsedIndicatorName;
|
||||
platform: string;
|
||||
}) => {
|
||||
let indicatorsWhichNeedPercentageSymbol: IndicatorName[] = ["spread"];
|
||||
if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) {
|
||||
return "%";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const getCurrencySymbolIfNeeded = ({
|
||||
indicator,
|
||||
platform,
|
||||
}: {
|
||||
indicator: UsedIndicatorName;
|
||||
platform: string;
|
||||
}) => {
|
||||
const indicatorsWhichNeedCurrencySymbol: IndicatorName[] = [
|
||||
"volume",
|
||||
"tradeVolume",
|
||||
"openInterest",
|
||||
"liquidity",
|
||||
];
|
||||
let dollarPlatforms = ["predictit", "kalshi", "polymarket"];
|
||||
if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) {
|
||||
if (dollarPlatforms.includes(platform)) {
|
||||
return "$";
|
||||
} else {
|
||||
return "£";
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const FirstQualityIndicator: React.FC<{
|
||||
question: QuestionFragment;
|
||||
}> = ({ question }) => {
|
||||
if (question.qualityIndicators.numForecasts) {
|
||||
return (
|
||||
<div className="flex">
|
||||
<span>Forecasts:</span>
|
||||
<span className="font-bold">
|
||||
{Number(question.qualityIndicators.numForecasts).toFixed(0)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const QualityIndicatorsList: React.FC<{
|
||||
question: QuestionFragment;
|
||||
}> = ({ question }) => {
|
||||
return (
|
||||
<div className="text-sm">
|
||||
<FirstQualityIndicator question={question} />
|
||||
{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 (
|
||||
<div key={indicator}>
|
||||
<span>{indicatorLabel}:</span>
|
||||
<span className="font-bold">
|
||||
{`${getCurrencySymbolIfNeeded({
|
||||
indicator,
|
||||
platform: question.platform.id,
|
||||
})}${formatNumber(value)}${getPercentageSymbolIfNeeded({
|
||||
indicator,
|
||||
platform: question.platform.id,
|
||||
})}`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 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 {
|
||||
question: QuestionFragment;
|
||||
expandFooterToFullWidth: boolean;
|
||||
}
|
||||
|
||||
export const QuestionFooter: React.FC<Props> = ({
|
||||
question,
|
||||
expandFooterToFullWidth,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`grid grid-cols-3 ${
|
||||
expandFooterToFullWidth ? "justify-between" : ""
|
||||
} text-gray-500 mb-2 mt-1`}
|
||||
>
|
||||
<div
|
||||
className={`self-center col-span-1 ${getStarsColor(
|
||||
question.qualityIndicators.stars
|
||||
)}`}
|
||||
>
|
||||
{getstars(question.qualityIndicators.stars)}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
expandFooterToFullWidth ? "place-self-center" : "self-center"
|
||||
} col-span-1 font-bold`}
|
||||
>
|
||||
{question.platform.label
|
||||
.replace("Good Judgment Open", "GJOpen")
|
||||
.replace(/ /g, "\u00a0")}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
expandFooterToFullWidth
|
||||
? "justify-self-end mr-4"
|
||||
: "justify-self-center"
|
||||
} col-span-1`}
|
||||
>
|
||||
<QualityIndicatorsList question={question} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { QuestionFragment } from "../../search/queries.generated";
|
||||
import { QuestionFragment } from "../../fragments.generated";
|
||||
import { formatProbability } from "../utils";
|
||||
|
||||
type Option = QuestionFragment["options"][0];
|
||||
|
|
|
@ -5,10 +5,11 @@ 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 { QuestionWithHistoryFragment } from "../../fragments.generated";
|
||||
import { ssrUrql } from "../../urql";
|
||||
import { HistoryChart } from "../components/HistoryChart";
|
||||
import { QuestionOptions } from "../components/QuestionOptions";
|
||||
import { QuestionByIdDocument } from "../queries.generated";
|
||||
import { QuestionPageDocument } from "../queries.generated";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
@ -21,7 +22,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
|||
const id = context.query.id as string;
|
||||
|
||||
const question =
|
||||
(await client.query(QuestionByIdDocument, { id }).toPromise()).data
|
||||
(await client.query(QuestionPageDocument, { id }).toPromise()).data
|
||||
?.result || null;
|
||||
|
||||
if (!question) {
|
||||
|
@ -36,11 +37,11 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
|||
};
|
||||
};
|
||||
|
||||
const QuestionCardContents: React.FC<{ question: QuestionFragment }> = ({
|
||||
question,
|
||||
}) => (
|
||||
<div className="space-y-4">
|
||||
<h1>
|
||||
const QuestionCardContents: React.FC<{
|
||||
question: QuestionWithHistoryFragment;
|
||||
}> = ({ question }) => (
|
||||
<div className="grid grid-cols-1 space-y-4 place-items-center">
|
||||
<h1 className="text-4xl place-self-center w-full text-center mt-10">
|
||||
<a
|
||||
className="text-black no-underline"
|
||||
href={question.url}
|
||||
|
@ -49,10 +50,22 @@ const QuestionCardContents: React.FC<{ question: QuestionFragment }> = ({
|
|||
{question.title}
|
||||
</a>
|
||||
</h1>
|
||||
<QuestionFooter question={question} expandFooterToFullWidth={true} />
|
||||
<HistoryChart question={question} />
|
||||
{/*
|
||||
<div className="flex justify-center items-center w-full">
|
||||
<div className="w-6/12">
|
||||
<QuestionFooter question={question} expandFooterToFullWidth={true} />
|
||||
</div>
|
||||
</div>
|
||||
<QuestionOptions options={question.options} />
|
||||
|
||||
<ReactMarkdown linkTarget="_blank" className="font-normal">
|
||||
*/}
|
||||
|
||||
<h1 className="pt-10 text-xl place-self-center w-full text-center ">
|
||||
{"Question description"}
|
||||
</h1>
|
||||
|
||||
<ReactMarkdown linkTarget="_blank" className="font-normal w-9/12">
|
||||
{question.description}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
@ -61,9 +74,9 @@ const QuestionCardContents: React.FC<{ question: QuestionFragment }> = ({
|
|||
const QuestionPage: NextPage<Props> = ({ id }) => {
|
||||
return (
|
||||
<Layout page="question">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<Card highlightOnHover={false}>
|
||||
<Query document={QuestionByIdDocument} variables={{ id }}>
|
||||
<Query document={QuestionPageDocument} variables={{ id }}>
|
||||
{({ data }) => <QuestionCardContents question={data.result} />}
|
||||
</Query>
|
||||
</Card>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import * as Types from '../../graphql/types.generated';
|
||||
|
||||
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
import { QuestionFragmentDoc } from '../search/queries.generated';
|
||||
export type QuestionByIdQueryVariables = Types.Exact<{
|
||||
import { QuestionWithHistoryFragmentDoc } from '../fragments.generated';
|
||||
export type QuestionPageQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type QuestionByIdQuery = { __typename?: 'Query', result: { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } } };
|
||||
export type QuestionPageQuery = { __typename?: 'Query', result: { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, history: Array<{ __typename?: 'History', timestamp: number, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }> }>, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } } };
|
||||
|
||||
|
||||
export const QuestionByIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"QuestionById"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"result"},"name":{"kind":"Name","value":"question"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Question"}}]}}]}},...QuestionFragmentDoc.definitions]} as unknown as DocumentNode<QuestionByIdQuery, QuestionByIdQueryVariables>;
|
||||
export const QuestionPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"QuestionPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"result"},"name":{"kind":"Name","value":"question"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"QuestionWithHistory"}}]}}]}},...QuestionWithHistoryFragmentDoc.definitions]} as unknown as DocumentNode<QuestionPageQuery, QuestionPageQueryVariables>;
|
|
@ -1,5 +1,5 @@
|
|||
query QuestionById($id: ID!) {
|
||||
query QuestionPage($id: ID!) {
|
||||
result: question(id: $id) {
|
||||
...Question
|
||||
...QuestionWithHistory
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ import { ButtonsForStars } from "../display/ButtonsForStars";
|
|||
import { MultiSelectPlatform } from "../display/MultiSelectPlatform";
|
||||
import { QueryForm } from "../display/QueryForm";
|
||||
import { SliderElement } from "../display/SliderElement";
|
||||
import { QuestionFragment } from "../fragments.generated";
|
||||
import { useIsFirstRender, useNoInitialEffect } from "../hooks";
|
||||
import { Props as AnySearchPageProps, QueryParameters } from "./anySearchPage";
|
||||
import { QuestionFragment, SearchDocument } from "./queries.generated";
|
||||
import { SearchDocument } from "./queries.generated";
|
||||
|
||||
interface Props extends AnySearchPageProps {
|
||||
hasSearchbar: boolean;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { GetServerSideProps } from "next";
|
||||
|
||||
import { getPlatformsConfig, PlatformConfig, platforms } from "../../backend/platforms";
|
||||
import { QuestionFragment } from "../fragments.generated";
|
||||
import { ssrUrql } from "../urql";
|
||||
import { FrontpageDocument, QuestionFragment, SearchDocument } from "./queries.generated";
|
||||
import { FrontpageDocument, SearchDocument } from "./queries.generated";
|
||||
|
||||
/* Common code for / and /capture */
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as Types from '../../graphql/types.generated';
|
||||
|
||||
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
export type QuestionFragment = { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } };
|
||||
|
||||
import { QuestionFragmentDoc } from '../fragments.generated';
|
||||
export type FrontpageQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
|
@ -15,6 +14,6 @@ export type SearchQueryVariables = Types.Exact<{
|
|||
|
||||
export type SearchQuery = { __typename?: 'Query', result: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } }> };
|
||||
|
||||
export const QuestionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Question"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Question"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"probability"}}]}},{"kind":"Field","name":{"kind":"Name","value":"platform"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}},{"kind":"Field","name":{"kind":"Name","value":"qualityIndicators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stars"}},{"kind":"Field","name":{"kind":"Name","value":"numForecasts"}},{"kind":"Field","name":{"kind":"Name","value":"numForecasters"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"spread"}},{"kind":"Field","name":{"kind":"Name","value":"sharesVolume"}},{"kind":"Field","name":{"kind":"Name","value":"openInterest"}},{"kind":"Field","name":{"kind":"Name","value":"liquidity"}},{"kind":"Field","name":{"kind":"Name","value":"tradeVolume"}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualization"}}]}}]} as unknown as DocumentNode<QuestionFragment, unknown>;
|
||||
|
||||
export const FrontpageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Frontpage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"result"},"name":{"kind":"Name","value":"frontpage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Question"}}]}}]}},...QuestionFragmentDoc.definitions]} as unknown as DocumentNode<FrontpageQuery, FrontpageQueryVariables>;
|
||||
export const SearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Search"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"result"},"name":{"kind":"Name","value":"searchQuestions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Question"}}]}}]}},...QuestionFragmentDoc.definitions]} as unknown as DocumentNode<SearchQuery, SearchQueryVariables>;
|
|
@ -1,31 +1,3 @@
|
|||
fragment Question on Question {
|
||||
id
|
||||
url
|
||||
title
|
||||
description
|
||||
timestamp
|
||||
options {
|
||||
name
|
||||
probability
|
||||
}
|
||||
platform {
|
||||
id
|
||||
label
|
||||
}
|
||||
qualityIndicators {
|
||||
stars
|
||||
numForecasts
|
||||
numForecasters
|
||||
volume
|
||||
spread
|
||||
sharesVolume
|
||||
openInterest
|
||||
liquidity
|
||||
tradeVolume
|
||||
}
|
||||
visualization
|
||||
}
|
||||
|
||||
query Frontpage {
|
||||
result: frontpage {
|
||||
...Question
|
||||
|
|
|
@ -10,3 +10,21 @@ export const getBasePath = () => {
|
|||
|
||||
return "http://localhost:3000";
|
||||
};
|
||||
|
||||
export 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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user