refactor: more typescript
This commit is contained in:
parent
439a9045da
commit
f6e2e8cfa1
23
package-lock.json
generated
23
package-lock.json
generated
|
@ -5,7 +5,6 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "metaforecast",
|
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -17,11 +16,13 @@
|
||||||
"@prisma/client": "^3.11.1",
|
"@prisma/client": "^3.11.1",
|
||||||
"@tailwindcss/forms": "^0.4.0",
|
"@tailwindcss/forms": "^0.4.0",
|
||||||
"@tailwindcss/typography": "^0.5.1",
|
"@tailwindcss/typography": "^0.5.1",
|
||||||
|
"@types/chroma-js": "^2.1.3",
|
||||||
"@types/dom-to-image": "^2.6.4",
|
"@types/dom-to-image": "^2.6.4",
|
||||||
"@types/jsdom": "^16.2.14",
|
"@types/jsdom": "^16.2.14",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-copy-to-clipboard": "^5.0.2",
|
"@types/react-copy-to-clipboard": "^5.0.2",
|
||||||
|
"@types/textversionjs": "^1.1.1",
|
||||||
"airtable": "^0.11.1",
|
"airtable": "^0.11.1",
|
||||||
"algoliasearch": "^4.10.3",
|
"algoliasearch": "^4.10.3",
|
||||||
"autoprefixer": "^10.1.0",
|
"autoprefixer": "^10.1.0",
|
||||||
|
@ -3170,6 +3171,11 @@
|
||||||
"@types/responselike": "*"
|
"@types/responselike": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/chroma-js": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
|
||||||
|
},
|
||||||
"node_modules/@types/cross-spawn": {
|
"node_modules/@types/cross-spawn": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz",
|
||||||
|
@ -3354,6 +3360,11 @@
|
||||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/textversionjs": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/textversionjs/-/textversionjs-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xXa08oZ76+J2rS36guKd6zJFAbkRtUnuDb0R6OFtY93VTuXdi94k0ycsIBrsq/Av8DmiQVfTYE3k7KCEUVYsag=="
|
||||||
|
},
|
||||||
"node_modules/@types/tough-cookie": {
|
"node_modules/@types/tough-cookie": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz",
|
||||||
|
@ -42405,6 +42416,11 @@
|
||||||
"@types/responselike": "*"
|
"@types/responselike": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/chroma-js": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
|
||||||
|
},
|
||||||
"@types/cross-spawn": {
|
"@types/cross-spawn": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz",
|
||||||
|
@ -42577,6 +42593,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types%2fscheduler/-/scheduler-0.16.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types%2fscheduler/-/scheduler-0.16.2.tgz",
|
||||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
|
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
|
||||||
},
|
},
|
||||||
|
"@types/textversionjs": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/textversionjs/-/textversionjs-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xXa08oZ76+J2rS36guKd6zJFAbkRtUnuDb0R6OFtY93VTuXdi94k0ycsIBrsq/Av8DmiQVfTYE3k7KCEUVYsag=="
|
||||||
|
},
|
||||||
"@types/tough-cookie": {
|
"@types/tough-cookie": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz",
|
||||||
|
|
|
@ -35,11 +35,13 @@
|
||||||
"@prisma/client": "^3.11.1",
|
"@prisma/client": "^3.11.1",
|
||||||
"@tailwindcss/forms": "^0.4.0",
|
"@tailwindcss/forms": "^0.4.0",
|
||||||
"@tailwindcss/typography": "^0.5.1",
|
"@tailwindcss/typography": "^0.5.1",
|
||||||
|
"@types/chroma-js": "^2.1.3",
|
||||||
"@types/dom-to-image": "^2.6.4",
|
"@types/dom-to-image": "^2.6.4",
|
||||||
"@types/jsdom": "^16.2.14",
|
"@types/jsdom": "^16.2.14",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-copy-to-clipboard": "^5.0.2",
|
"@types/react-copy-to-clipboard": "^5.0.2",
|
||||||
|
"@types/textversionjs": "^1.1.1",
|
||||||
"airtable": "^0.11.1",
|
"airtable": "^0.11.1",
|
||||||
"algoliasearch": "^4.10.3",
|
"algoliasearch": "^4.10.3",
|
||||||
"autoprefixer": "^10.1.0",
|
"autoprefixer": "^10.1.0",
|
||||||
|
|
|
@ -42,7 +42,7 @@ async function fetchPredictions() {
|
||||||
const agent = new https.Agent({
|
const agent = new https.Agent({
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
});
|
});
|
||||||
let response = await axios({
|
const response = await axios({
|
||||||
url: endpoint,
|
url: endpoint,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -115,18 +115,19 @@ async function processPredictions(data) {
|
||||||
if (rules == undefined) {
|
if (rules == undefined) {
|
||||||
// console.log(prediction.description)
|
// console.log(prediction.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = rules.split("? ")[0] + "?";
|
let title = rules.split("? ")[0] + "?";
|
||||||
let description = rules.split("? ")[1].trim();
|
let description = rules.split("? ")[1].trim();
|
||||||
if (title.includes("of the named")) {
|
if (title.includes("of the named")) {
|
||||||
title = prediction.marketName + ": " + title;
|
title = prediction.marketName + ": " + title;
|
||||||
}
|
}
|
||||||
let result = {
|
const result = {
|
||||||
id: id,
|
id,
|
||||||
title: title,
|
title,
|
||||||
url: `https://www.betfair.com/exchange/plus/politics/market/${prediction.marketId}`,
|
url: `https://www.betfair.com/exchange/plus/politics/market/${prediction.marketId}`,
|
||||||
platform: platformName,
|
platform: platformName,
|
||||||
description: description,
|
description,
|
||||||
options: options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
stars: calculateStars(platformName, {
|
stars: calculateStars(platformName, {
|
||||||
volume: prediction.totalMatched,
|
volume: prediction.totalMatched,
|
||||||
|
|
|
@ -9,10 +9,7 @@ import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "goodjudgment";
|
const platformName = "goodjudgment";
|
||||||
let endpoint = "https://goodjudgment.io/superforecasts/";
|
const endpoint = "https://goodjudgment.io/superforecasts/";
|
||||||
String.prototype.replaceAll = function replaceAll(search, replace) {
|
|
||||||
return this.split(search).join(replace);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
export const goodjudgment: Platform = {
|
export const goodjudgment: Platform = {
|
||||||
|
|
|
@ -9,10 +9,7 @@ import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "infer";
|
const platformName = "infer";
|
||||||
let htmlEndPoint = "https://www.infer-pub.com/questions";
|
const htmlEndPoint = "https://www.infer-pub.com/questions";
|
||||||
String.prototype.replaceAll = function replaceAll(search, replace) {
|
|
||||||
return this.split(search).join(replace);
|
|
||||||
};
|
|
||||||
const DEBUG_MODE: "on" | "off" = "off"; // "off"
|
const DEBUG_MODE: "on" | "off" = "off"; // "off"
|
||||||
const SLEEP_TIME_RANDOM = 7000; // miliseconds
|
const SLEEP_TIME_RANDOM = 7000; // miliseconds
|
||||||
const SLEEP_TIME_EXTRA = 2000;
|
const SLEEP_TIME_EXTRA = 2000;
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
export function roughSizeOfObject(object) {
|
|
||||||
var objectList = [];
|
|
||||||
var stack = [object];
|
|
||||||
var bytes = 0;
|
|
||||||
|
|
||||||
while (stack.length) {
|
|
||||||
var value = stack.pop();
|
|
||||||
if (typeof value === "boolean") {
|
|
||||||
bytes += 4;
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
bytes += value.length * 2;
|
|
||||||
} else if (typeof value === "number") {
|
|
||||||
bytes += 8;
|
|
||||||
} else if (typeof value === "object" && objectList.indexOf(value) === -1) {
|
|
||||||
objectList.push(value);
|
|
||||||
|
|
||||||
for (var i in value) {
|
|
||||||
stack.push(value[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let megaBytes = bytes / 1024 ** 2;
|
|
||||||
let megaBytesRounded = Math.round(megaBytes * 10) / 10;
|
|
||||||
return megaBytesRounded;
|
|
||||||
}
|
|
|
@ -1,31 +1,5 @@
|
||||||
export function getStarSymbols(numstars) {
|
let average = (array: number[]) =>
|
||||||
let stars = "★★☆☆☆";
|
array.reduce((a, b) => a + b, 0) / array.length;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let average = (array) => array.reduce((a, b) => a + b, 0) / array.length;
|
|
||||||
|
|
||||||
function calculateStarsAstralCodexTen(data) {
|
function calculateStarsAstralCodexTen(data) {
|
||||||
let nuno = (data) => 3;
|
let nuno = (data) => 3;
|
||||||
|
|
|
@ -1,26 +1,16 @@
|
||||||
/* Imports */
|
|
||||||
import textVersion from "textversionjs";
|
import textVersion from "textversionjs";
|
||||||
|
|
||||||
/* Definitions */
|
export default function toMarkdown(htmlText: string) {
|
||||||
String.prototype.replaceAll = function replaceAll(search, replace) {
|
|
||||||
return this.split(search).join(replace);
|
|
||||||
};
|
|
||||||
|
|
||||||
var styleConfig = {
|
|
||||||
linkProcess: function (href, linkText) {
|
|
||||||
let newHref = href ? href.replace(/\(/g, "%28").replace(/\)/g, "%29") : "";
|
|
||||||
// Deal corretly in markdown with links that contain parenthesis
|
|
||||||
return `[${linkText}](${newHref})`;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Support functions */
|
|
||||||
|
|
||||||
/* Body */
|
|
||||||
|
|
||||||
export default function toMarkdown(htmlText) {
|
|
||||||
let html2 = htmlText.replaceAll(`='`, `="`).replaceAll(`'>`, `">`);
|
let html2 = htmlText.replaceAll(`='`, `="`).replaceAll(`'>`, `">`);
|
||||||
return textVersion(html2, styleConfig);
|
return textVersion(html2, {
|
||||||
|
linkProcess: (href, linkText) => {
|
||||||
|
let newHref = href
|
||||||
|
? href.replace(/\(/g, "%28").replace(/\)/g, "%29")
|
||||||
|
: "";
|
||||||
|
// Deal correctly in markdown with links that contain parenthesis
|
||||||
|
return `[${linkText}](${newHref})`;
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// toMarkdown()
|
// toMarkdown()
|
||||||
|
|
|
@ -36,6 +36,7 @@ const DashboardObj = builder.objectRef<Dashboard>("Dashboard").implement({
|
||||||
builder.queryField("dashboard", (t) =>
|
builder.queryField("dashboard", (t) =>
|
||||||
t.field({
|
t.field({
|
||||||
type: DashboardObj,
|
type: DashboardObj,
|
||||||
|
nullable: true,
|
||||||
description: "Look up a single dashboard by its id",
|
description: "Look up a single dashboard by its id",
|
||||||
args: {
|
args: {
|
||||||
id: t.arg({ type: "ID", required: true }),
|
id: t.arg({ type: "ID", required: true }),
|
||||||
|
|
|
@ -140,6 +140,7 @@ builder.queryField("questions", (t) =>
|
||||||
builder.queryField("question", (t) =>
|
builder.queryField("question", (t) =>
|
||||||
t.field({
|
t.field({
|
||||||
type: QuestionObj,
|
type: QuestionObj,
|
||||||
|
nullable: true,
|
||||||
description: "Look up a single question by its id",
|
description: "Look up a single question by its id",
|
||||||
args: {
|
args: {
|
||||||
id: t.arg({ type: "ID", required: true }),
|
id: t.arg({ type: "ID", required: true }),
|
||||||
|
@ -149,7 +150,6 @@ builder.queryField("question", (t) =>
|
||||||
const [platform, id] = [parts[0], parts.slice(1).join("-")];
|
const [platform, id] = [parts[0], parts.slice(1).join("-")];
|
||||||
if (platform === "guesstimate") {
|
if (platform === "guesstimate") {
|
||||||
const q = await guesstimate.fetchQuestion(Number(id));
|
const q = await guesstimate.fetchQuestion(Number(id));
|
||||||
console.log(q);
|
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
return await prisma.question.findUnique({
|
return await prisma.question.findUnique({
|
||||||
|
|
|
@ -32,19 +32,19 @@ builder.queryField("searchQuestions", (t) =>
|
||||||
// defs
|
// defs
|
||||||
const query = input.query === undefined ? "" : input.query;
|
const query = input.query === undefined ? "" : input.query;
|
||||||
if (query === "") return [];
|
if (query === "") return [];
|
||||||
const forecastsThreshold = input.forecastsThreshold;
|
const { forecastsThreshold, starsThreshold } = input;
|
||||||
const starsThreshold = input.starsThreshold;
|
|
||||||
const platformsIncludeGuesstimate =
|
const platformsIncludeGuesstimate =
|
||||||
input.forecastingPlatforms?.includes("guesstimate") &&
|
input.forecastingPlatforms?.includes("guesstimate") &&
|
||||||
starsThreshold <= 1;
|
(!starsThreshold || starsThreshold <= 1);
|
||||||
|
|
||||||
// preparation
|
// preparation
|
||||||
const unawaitedAlgoliaResponse = searchWithAlgolia({
|
const unawaitedAlgoliaResponse = searchWithAlgolia({
|
||||||
queryString: query,
|
queryString: query,
|
||||||
hitsPerPage: input.limit + 50,
|
hitsPerPage: input.limit ?? 50,
|
||||||
starsThreshold,
|
starsThreshold: starsThreshold ?? undefined,
|
||||||
filterByPlatforms: input.forecastingPlatforms,
|
filterByPlatforms: input.forecastingPlatforms ?? undefined,
|
||||||
forecastsThreshold,
|
forecastsThreshold: forecastsThreshold ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
let results: AlgoliaQuestion[] = [];
|
let results: AlgoliaQuestion[] = [];
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { GetServerSideProps, NextPage } from "next";
|
import { GetServerSideProps, NextPage } from "next";
|
||||||
import Error from "next/error";
|
import NextError from "next/error";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DashboardByIdDocument, DashboardFragment
|
DashboardByIdDocument, DashboardFragment
|
||||||
|
@ -19,9 +19,13 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
const dashboardId = context.query.id as string;
|
const dashboardId = context.query.id as string;
|
||||||
const numCols = Number(context.query.numCols);
|
const numCols = Number(context.query.numCols);
|
||||||
|
|
||||||
const dashboard = (
|
const response = await client
|
||||||
await client.query(DashboardByIdDocument, { id: dashboardId }).toPromise()
|
.query(DashboardByIdDocument, { id: dashboardId })
|
||||||
).data?.result;
|
.toPromise();
|
||||||
|
if (!response.data) {
|
||||||
|
throw new Error(`GraphQL query failed: ${response.error}`);
|
||||||
|
}
|
||||||
|
const dashboard = response.data.result;
|
||||||
|
|
||||||
if (!dashboard) {
|
if (!dashboard) {
|
||||||
context.res.statusCode = 404;
|
context.res.statusCode = 404;
|
||||||
|
@ -32,14 +36,14 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
// reduntant: page component doesn't do graphql requests, but it's still nice/more consistent to have data in cache
|
// reduntant: page component doesn't do graphql requests, but it's still nice/more consistent to have data in cache
|
||||||
urqlState: ssrCache.extractData(),
|
urqlState: ssrCache.extractData(),
|
||||||
dashboard,
|
dashboard,
|
||||||
numCols: !numCols ? null : numCols < 5 ? numCols : 4,
|
numCols: !numCols ? undefined : numCols < 5 ? numCols : 4,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmbedDashboardPage: NextPage<Props> = ({ dashboard, numCols }) => {
|
const EmbedDashboardPage: NextPage<Props> = ({ dashboard, numCols }) => {
|
||||||
if (!dashboard) {
|
if (!dashboard) {
|
||||||
return <Error statusCode={404} />;
|
return <NextError statusCode={404} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -45,8 +45,11 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
const defaultNumDisplay = 21;
|
const defaultNumDisplay = 21;
|
||||||
const initialNumDisplay = Number(urlQuery.numDisplay) || defaultNumDisplay;
|
const initialNumDisplay = Number(urlQuery.numDisplay) || defaultNumDisplay;
|
||||||
|
|
||||||
const defaultResults = (await client.query(FrontpageDocument).toPromise())
|
const response = await client.query(FrontpageDocument).toPromise();
|
||||||
.data.result;
|
if (!response.data) {
|
||||||
|
throw new Error(`GraphQL query failed: ${response.error}`);
|
||||||
|
}
|
||||||
|
const defaultResults = response.data.result;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!!initialQueryParameters &&
|
!!initialQueryParameters &&
|
||||||
|
@ -58,7 +61,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
.query(SearchDocument, {
|
.query(SearchDocument, {
|
||||||
input: {
|
input: {
|
||||||
...initialQueryParameters,
|
...initialQueryParameters,
|
||||||
limit: initialNumDisplay,
|
limit: initialNumDisplay + 50,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
|
@ -29,16 +29,19 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
|
|
||||||
let results: QuestionFragment[] = [];
|
let results: QuestionFragment[] = [];
|
||||||
if (initialQueryParameters.query !== "") {
|
if (initialQueryParameters.query !== "") {
|
||||||
results = (
|
const response = await client
|
||||||
await client
|
.query(SearchDocument, {
|
||||||
.query(SearchDocument, {
|
input: {
|
||||||
input: {
|
...initialQueryParameters,
|
||||||
...initialQueryParameters,
|
limit: 1,
|
||||||
limit: 1,
|
},
|
||||||
},
|
})
|
||||||
})
|
.toPromise();
|
||||||
.toPromise()
|
if (response.data?.result) {
|
||||||
).data.result;
|
results = response.data.result;
|
||||||
|
} else {
|
||||||
|
throw new Error("GraphQL request failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React, { ErrorInfo } from "react";
|
import React, { ErrorInfo } from "react";
|
||||||
|
|
||||||
import { Logo2 } from "../icons/index";
|
import { Logo2 } from "../icons";
|
||||||
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
page: string;
|
page: string;
|
||||||
|
|
|
@ -87,7 +87,7 @@ export const MultiSelectPlatform: React.FC<Props> = ({
|
||||||
|
|
||||||
const selectValue = value.map((v) => id2option[v]).filter((v) => v);
|
const selectValue = value.map((v) => id2option[v]).filter((v) => v);
|
||||||
|
|
||||||
const onSelectChange = (newValue: Option[]) => {
|
const onSelectChange = (newValue: readonly Option[]) => {
|
||||||
onChange(newValue.map((o) => o.value));
|
onChange(newValue.map((o) => o.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,9 @@ export const DashboardCreator: React.FC<Props> = ({ handleSubmit }) => {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setActing(false);
|
setActing(false);
|
||||||
const substituteText = `Error: ${error.message}
|
const substituteText = `Error: ${
|
||||||
|
error instanceof Error ? error.message : "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
Try something like:
|
Try something like:
|
||||||
${exampleInput}
|
${exampleInput}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import * as React from "react";
|
export const Favicon: React.FC<React.SVGAttributes<SVGElement>> = (props) => {
|
||||||
|
|
||||||
function SvgFavicon(props) {
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -11,6 +9,4 @@ function SvgFavicon(props) {
|
||||||
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm198.4-588.1a32 32 0 00-24.5.5L414.9 415 296.4 686c-3.6 8.2-3.6 17.5 0 25.7 3.4 7.8 9.7 13.9 17.7 17 3.8 1.5 7.7 2.2 11.7 2.2 4.4 0 8.7-.9 12.8-2.7l271-118.6 118.5-271a32.06 32.06 0 00-17.7-42.7zM576.8 534.4l26.2 26.2-42.4 42.4-26.2-26.2L380 644.4 447.5 490 422 464.4l42.4-42.4 25.5 25.5L644.4 380l-67.6 154.4zM464.4 422L422 464.4l25.5 25.6 86.9 86.8 26.2 26.2 42.4-42.4-26.2-26.2-86.8-86.9z" />
|
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm198.4-588.1a32 32 0 00-24.5.5L414.9 415 296.4 686c-3.6 8.2-3.6 17.5 0 25.7 3.4 7.8 9.7 13.9 17.7 17 3.8 1.5 7.7 2.2 11.7 2.2 4.4 0 8.7-.9 12.8-2.7l271-118.6 118.5-271a32.06 32.06 0 00-17.7-42.7zM576.8 534.4l26.2 26.2-42.4 42.4-26.2-26.2L380 644.4 447.5 490 422 464.4l42.4-42.4 25.5 25.5L644.4 380l-67.6 154.4zM464.4 422L422 464.4l25.5 25.6 86.9 86.8 26.2 26.2 42.4-42.4-26.2-26.2-86.8-86.9z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SvgFavicon;
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import * as React from "react";
|
export const Logo: React.FC<React.SVGAttributes<SVGElement>> = (props) => {
|
||||||
|
|
||||||
function SvgLogo(props) {
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width={1333.333}
|
width={1333.333}
|
||||||
|
@ -76,6 +74,4 @@ function SvgLogo(props) {
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SvgLogo;
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import * as React from "react";
|
export const Logo2: React.FC<React.SVGAttributes<SVGElement>> = (props) => {
|
||||||
|
|
||||||
function SvgLogo2(props) {
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -69,6 +67,4 @@ function SvgLogo2(props) {
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SvgLogo2;
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export { default as Favicon } from "./Favicon";
|
export { Favicon } from "./Favicon";
|
||||||
export { default as Logo } from "./Logo";
|
export { Logo } from "./Logo";
|
||||||
export { default as Logo2 } from "./Logo2";
|
export { Logo2 } from "./Logo2";
|
||||||
|
|
|
@ -34,10 +34,12 @@ const getVictoryGroup = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InnerChart: React.FC<{
|
export type Props = {
|
||||||
data: ChartData;
|
data: ChartData;
|
||||||
highlight: number | undefined;
|
highlight: number | undefined;
|
||||||
}> = ({
|
};
|
||||||
|
|
||||||
|
export const InnerChart: React.FC<Props> = ({
|
||||||
data: { maxProbability, seriesList, minDate, maxDate },
|
data: { maxProbability, seriesList, minDate, maxDate },
|
||||||
highlight,
|
highlight,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -120,7 +122,7 @@ export const InnerChart: React.FC<{
|
||||||
<VictoryAxis
|
<VictoryAxis
|
||||||
tickCount={Math.min(7, differenceInDays(maxDate, minDate) + 1)}
|
tickCount={Math.min(7, differenceInDays(maxDate, minDate) + 1)}
|
||||||
style={{
|
style={{
|
||||||
grid: { stroke: null, strokeWidth: 0.5 },
|
grid: { strokeWidth: 0.5 },
|
||||||
}}
|
}}
|
||||||
tickLabelComponent={
|
tickLabelComponent={
|
||||||
<VictoryLabel
|
<VictoryLabel
|
||||||
|
|
|
@ -12,15 +12,17 @@ const LegendItem: React.FC<{ item: Item; onHighlight: () => void }> = ({
|
||||||
onHighlight,
|
onHighlight,
|
||||||
}) => {
|
}) => {
|
||||||
const { x, y, reference, floating, strategy } = useFloating({
|
const { x, y, reference, floating, strategy } = useFloating({
|
||||||
// placement: "right",
|
|
||||||
middleware: [shift()],
|
middleware: [shift()],
|
||||||
});
|
});
|
||||||
|
|
||||||
const [showTooltip, setShowTooltip] = useState(false);
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
const textRef = useRef<HTMLDivElement>();
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const onHover = () => {
|
const onHover = () => {
|
||||||
if (textRef.current.scrollWidth > textRef.current.clientWidth) {
|
if (
|
||||||
|
textRef.current &&
|
||||||
|
textRef.current.scrollWidth > textRef.current.clientWidth
|
||||||
|
) {
|
||||||
setShowTooltip(true);
|
setShowTooltip(true);
|
||||||
}
|
}
|
||||||
onHighlight();
|
onHighlight();
|
||||||
|
|
|
@ -2,11 +2,12 @@ import dynamic from "next/dynamic";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
|
|
||||||
import { QuestionWithHistoryFragment } from "../../../fragments.generated";
|
import { QuestionWithHistoryFragment } from "../../../fragments.generated";
|
||||||
|
import { Props as InnerChartProps } from "./InnerChart"; // hopefully doesn't import code, just types - need to check
|
||||||
import { InnerChartPlaceholder } from "./InnerChartPlaceholder";
|
import { InnerChartPlaceholder } from "./InnerChartPlaceholder";
|
||||||
import { Legend } from "./Legend";
|
import { Legend } from "./Legend";
|
||||||
import { buildChartData, chartColors } from "./utils";
|
import { buildChartData, chartColors } from "./utils";
|
||||||
|
|
||||||
const InnerChart = dynamic(
|
const InnerChart = dynamic<InnerChartProps>(
|
||||||
() => import("./InnerChart").then((mod) => mod.InnerChart),
|
() => import("./InnerChart").then((mod) => mod.InnerChart),
|
||||||
{ ssr: false, loading: () => <InnerChartPlaceholder /> }
|
{ ssr: false, loading: () => <InnerChartPlaceholder /> }
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { addDays, startOfDay, startOfToday, startOfTomorrow } from "date-fns";
|
import { addDays, startOfDay, startOfToday, startOfTomorrow } from "date-fns";
|
||||||
|
|
||||||
import { QuestionWithHistoryFragment } from "../../../fragments.generated";
|
import { QuestionWithHistoryFragment } from "../../../fragments.generated";
|
||||||
|
import { isQuestionBinary } from "../../../utils";
|
||||||
|
import { isFullQuestionOption } from "../../utils";
|
||||||
|
|
||||||
export type ChartSeries = { x: Date; y: number; name: string }[];
|
export type ChartSeries = { x: Date; y: number; name: string }[];
|
||||||
|
|
||||||
|
@ -33,6 +35,7 @@ export const buildChartData = (
|
||||||
question: QuestionWithHistoryFragment
|
question: QuestionWithHistoryFragment
|
||||||
): ChartData => {
|
): ChartData => {
|
||||||
let seriesNames = question.options
|
let seriesNames = question.options
|
||||||
|
.filter(isFullQuestionOption)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (a.probability > b.probability) {
|
if (a.probability > b.probability) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -44,9 +47,7 @@ export const buildChartData = (
|
||||||
.map((o) => o.name)
|
.map((o) => o.name)
|
||||||
.slice(0, MAX_LINES);
|
.slice(0, MAX_LINES);
|
||||||
|
|
||||||
const isBinary =
|
const isBinary = isQuestionBinary(question);
|
||||||
(seriesNames[0] === "Yes" && seriesNames[1] === "No") ||
|
|
||||||
(seriesNames[0] === "No" && seriesNames[1] === "Yes");
|
|
||||||
if (isBinary) {
|
if (isBinary) {
|
||||||
seriesNames = ["Yes"];
|
seriesNames = ["Yes"];
|
||||||
}
|
}
|
||||||
|
@ -69,6 +70,9 @@ export const buildChartData = (
|
||||||
const date = new Date(item.timestamp * 1000);
|
const date = new Date(item.timestamp * 1000);
|
||||||
|
|
||||||
for (const option of item.options) {
|
for (const option of item.options) {
|
||||||
|
if (option.name == null || option.probability == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const idx = nameToIndex[option.name];
|
const idx = nameToIndex[option.name];
|
||||||
if (idx === undefined) {
|
if (idx === undefined) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -45,18 +45,18 @@ export const IndicatorsTable: React.FC<Props> = ({ question }) => (
|
||||||
) : null}
|
) : null}
|
||||||
{Object.keys(question.qualityIndicators)
|
{Object.keys(question.qualityIndicators)
|
||||||
.filter(
|
.filter(
|
||||||
(indicator) =>
|
(indicator): indicator is UsedIndicatorName =>
|
||||||
question.qualityIndicators[indicator] != null &&
|
(question.qualityIndicators as any)[indicator] != null &&
|
||||||
!!qualityIndicatorLabels[indicator]
|
indicator in qualityIndicatorLabels
|
||||||
)
|
)
|
||||||
.map((indicator: UsedIndicatorName) => {
|
.map((indicator) => {
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
title={qualityIndicatorLabels[indicator]}
|
title={qualityIndicatorLabels[indicator]}
|
||||||
key={indicator}
|
key={indicator}
|
||||||
>
|
>
|
||||||
{formatIndicatorValue(
|
{formatIndicatorValue(
|
||||||
question.qualityIndicators[indicator],
|
Number(question.qualityIndicators[indicator]), // must be non-null due to former check
|
||||||
indicator,
|
indicator,
|
||||||
question.platform.id
|
question.platform.id
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -30,13 +30,17 @@ export const qualityIndicatorLabels: { [k in UsedIndicatorName]: string } = {
|
||||||
openInterest: "Interest",
|
openInterest: "Interest",
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatNumber = (num) => {
|
const isUsedIndicatorName = (name: string): name is UsedIndicatorName => {
|
||||||
if (Number(num) < 1000) {
|
return name in qualityIndicatorLabels;
|
||||||
return Number(num).toFixed(0);
|
};
|
||||||
|
|
||||||
|
const formatNumber = (num: number) => {
|
||||||
|
if (num < 1000) {
|
||||||
|
return num.toFixed(0);
|
||||||
} else if (num < 10000) {
|
} else if (num < 10000) {
|
||||||
return (Number(num) / 1000).toFixed(1) + "k";
|
return (num / 1000).toFixed(1) + "k";
|
||||||
} else {
|
} else {
|
||||||
return (Number(num) / 1000).toFixed(0) + "k";
|
return (num / 1000).toFixed(0) + "k";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,7 +104,7 @@ const FirstQualityIndicator: React.FC<{
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatIndicatorValue = (
|
export const formatIndicatorValue = (
|
||||||
value: any,
|
value: number,
|
||||||
indicator: UsedIndicatorName,
|
indicator: UsedIndicatorName,
|
||||||
platform: string
|
platform: string
|
||||||
): string => {
|
): string => {
|
||||||
|
@ -119,21 +123,26 @@ const QualityIndicatorsList: React.FC<{
|
||||||
return (
|
return (
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<FirstQualityIndicator question={question} />
|
<FirstQualityIndicator question={question} />
|
||||||
{Object.entries(question.qualityIndicators).map((entry, i) => {
|
{Object.entries(question.qualityIndicators).map(
|
||||||
const indicatorLabel = qualityIndicatorLabels[entry[0]];
|
([indicator, value], i) => {
|
||||||
if (!indicatorLabel || entry[1] === null) return;
|
if (!isUsedIndicatorName(indicator)) return;
|
||||||
const indicator = entry[0] as UsedIndicatorName; // guaranteed by the previous line
|
const indicatorLabel = qualityIndicatorLabels[indicator];
|
||||||
const value = entry[1];
|
if (!indicatorLabel || value === null) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={indicator}>
|
<div key={indicator}>
|
||||||
<span>{indicatorLabel}:</span>
|
<span>{indicatorLabel}:</span>
|
||||||
<span className="font-bold">
|
<span className="font-bold">
|
||||||
{formatIndicatorValue(value, indicator, question.platform.id)}
|
{formatIndicatorValue(
|
||||||
</span>
|
Number(value),
|
||||||
</div>
|
indicator,
|
||||||
);
|
question.platform.id
|
||||||
})}
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,62 +13,25 @@ const truncateText = (length: number, text: string): string => {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (!!text && text.length <= length) {
|
if (text.length <= length) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
const breakpoints = " .!?";
|
const breakpoints = " .!?";
|
||||||
let lastLetter = null;
|
let lastLetter: string | undefined = undefined;
|
||||||
let lastIndex = null;
|
let lastIndex: number | undefined = undefined;
|
||||||
for (let index = length; index > 0; index--) {
|
for (let index = length; index > 0; index--) {
|
||||||
let letter = text[index];
|
const letter = text[index];
|
||||||
if (breakpoints.includes(letter)) {
|
if (breakpoints.includes(letter)) {
|
||||||
lastLetter = letter;
|
lastLetter = letter;
|
||||||
lastIndex = index;
|
lastIndex = index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let truncatedText = !!text.slice
|
let truncatedText =
|
||||||
? text.slice(0, lastIndex) + (lastLetter != "." ? "..." : "..")
|
text.slice(0, lastIndex) + (lastLetter != "." ? "..." : "..");
|
||||||
: "";
|
|
||||||
return truncatedText;
|
return truncatedText;
|
||||||
};
|
};
|
||||||
|
|
||||||
// replaceAll polyfill
|
|
||||||
function escapeRegExp(string) {
|
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceAll(
|
|
||||||
originalString: string,
|
|
||||||
pattern: string | RegExp,
|
|
||||||
substitute
|
|
||||||
) {
|
|
||||||
return originalString.replace(
|
|
||||||
new RegExp(escapeRegExp(pattern), "g"),
|
|
||||||
substitute
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!String.prototype.replaceAll) {
|
|
||||||
String.prototype.replaceAll = function (
|
|
||||||
pattern: string | RegExp,
|
|
||||||
substitute
|
|
||||||
) {
|
|
||||||
let originalString = this;
|
|
||||||
|
|
||||||
// If a regex pattern
|
|
||||||
if (
|
|
||||||
Object.prototype.toString.call(pattern).toLowerCase() ===
|
|
||||||
"[object regexp]"
|
|
||||||
) {
|
|
||||||
return originalString.replace(pattern, substitute);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a string
|
|
||||||
return replaceAll(originalString, pattern, substitute);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auxiliary components
|
// Auxiliary components
|
||||||
|
|
||||||
const DisplayMarkdown: React.FC<{ description: string }> = ({
|
const DisplayMarkdown: React.FC<{ description: string }> = ({
|
||||||
|
@ -153,14 +116,14 @@ export const QuestionCard: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
{isBinary ? (
|
{isBinary ? (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<QuestionOptions options={options} />
|
<QuestionOptions question={question} />
|
||||||
<div className={`hidden ${showTimeStamp ? "sm:block" : ""}`}>
|
<div className={`hidden ${showTimeStamp ? "sm:block" : ""}`}>
|
||||||
<LastUpdated timestamp={lastUpdated} />
|
<LastUpdated timestamp={lastUpdated} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<QuestionOptions options={options} />
|
<QuestionOptions question={question} />
|
||||||
<div className={`hidden ${showTimeStamp ? "sm:block" : ""} ml-6`}>
|
<div className={`hidden ${showTimeStamp ? "sm:block" : ""} ml-6`}>
|
||||||
<LastUpdated timestamp={lastUpdated} />
|
<LastUpdated timestamp={lastUpdated} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,227 +0,0 @@
|
||||||
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,7 +1,6 @@
|
||||||
import { QuestionFragment } from "../../fragments.generated";
|
import { QuestionFragment } from "../../fragments.generated";
|
||||||
import { formatProbability } from "../utils";
|
import { isQuestionBinary } from "../../utils";
|
||||||
|
import { formatProbability, FullQuestionOption, isFullQuestionOption } from "../utils";
|
||||||
type Option = QuestionFragment["options"][0];
|
|
||||||
|
|
||||||
const textColor = (probability: number) => {
|
const textColor = (probability: number) => {
|
||||||
if (probability < 0.03) {
|
if (probability < 0.03) {
|
||||||
|
@ -89,7 +88,7 @@ const chooseColor = (probability: number) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const OptionRow: React.FC<{ option: Option }> = ({ option }) => {
|
const OptionRow: React.FC<{ option: FullQuestionOption }> = ({ option }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div
|
<div
|
||||||
|
@ -106,15 +105,19 @@ const OptionRow: React.FC<{ option: Option }> = ({ option }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QuestionOptions: React.FC<{ options: Option[] }> = ({
|
export const QuestionOptions: React.FC<{ question: QuestionFragment }> = ({
|
||||||
options,
|
question,
|
||||||
}) => {
|
}) => {
|
||||||
const isBinary =
|
const isBinary = isQuestionBinary(question);
|
||||||
options.length === 2 &&
|
|
||||||
(options[0].name === "Yes" || options[0].name === "No");
|
|
||||||
|
|
||||||
if (isBinary) {
|
if (isBinary) {
|
||||||
const yesOption = options.find((o) => o.name === "Yes");
|
const yesOption = question.options.find((o) => o.name === "Yes");
|
||||||
|
if (!yesOption) {
|
||||||
|
return null; // shouldn't happen
|
||||||
|
}
|
||||||
|
if (!isFullQuestionOption(yesOption)) {
|
||||||
|
return null; // missing data
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<span
|
<span
|
||||||
|
@ -134,8 +137,11 @@ export const QuestionOptions: React.FC<{ options: Option[] }> = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const optionsSorted = options.sort((a, b) => b.probability - a.probability);
|
const optionsSorted = question.options
|
||||||
const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options.
|
.filter(isFullQuestionOption)
|
||||||
|
.sort((a, b) => b.probability - a.probability);
|
||||||
|
|
||||||
|
const optionsMax5 = optionsSorted.slice(0, 5); // display max 5 options.
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
|
@ -8,3 +8,20 @@ export const formatProbability = (probability: number) => {
|
||||||
: percentage.toFixed(0) + "%";
|
: percentage.toFixed(0) + "%";
|
||||||
return percentageCapped;
|
return percentageCapped;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
import { QuestionFragment } from "../fragments.generated";
|
||||||
|
|
||||||
|
export type QuestionOption = QuestionFragment["options"][0];
|
||||||
|
export type FullQuestionOption = Exclude<
|
||||||
|
QuestionOption,
|
||||||
|
"name" | "probability"
|
||||||
|
> & {
|
||||||
|
name: NonNullable<QuestionOption["name"]>;
|
||||||
|
probability: NonNullable<QuestionOption["probability"]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isFullQuestionOption = (
|
||||||
|
option: QuestionOption
|
||||||
|
): option is FullQuestionOption => {
|
||||||
|
return option.name != null && option.probability != null;
|
||||||
|
};
|
||||||
|
|
|
@ -61,7 +61,7 @@ export const SearchScreen: React.FC<Props> = ({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
...queryParameters,
|
...queryParameters,
|
||||||
limit: numDisplay,
|
limit: numDisplay + 50,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pause: !isFirstRender,
|
pause: !isFirstRender,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { QuestionFragment } from "./fragments.generated";
|
||||||
|
|
||||||
export const getBasePath = () => {
|
export const getBasePath = () => {
|
||||||
if (process.env.NEXT_PUBLIC_VERCEL_URL) {
|
if (process.env.NEXT_PUBLIC_VERCEL_URL) {
|
||||||
return `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`;
|
return `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`;
|
||||||
|
@ -29,3 +31,12 @@ export const cleanText = (text: string): string => {
|
||||||
//console.log(textString)
|
//console.log(textString)
|
||||||
return textString;
|
return textString;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isQuestionBinary = (question: QuestionFragment): boolean => {
|
||||||
|
const { options } = question;
|
||||||
|
return (
|
||||||
|
options.length === 2 &&
|
||||||
|
((options[0].name === "Yes" && options[1].name === "No") ||
|
||||||
|
(options[0].name === "No" && options[1].name === "Yes"))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -13,9 +13,9 @@ const index = client.initIndex("metaforecast");
|
||||||
interface SearchOpts {
|
interface SearchOpts {
|
||||||
queryString: string;
|
queryString: string;
|
||||||
hitsPerPage?: number;
|
hitsPerPage?: number;
|
||||||
starsThreshold: number;
|
starsThreshold?: number;
|
||||||
filterByPlatforms: string[];
|
filterByPlatforms?: string[];
|
||||||
forecastsThreshold: number;
|
forecastsThreshold?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildFilter = ({
|
const buildFilter = ({
|
||||||
|
@ -33,7 +33,7 @@ const buildFilter = ({
|
||||||
? filterByPlatforms.map((platform) => `platform:"${platform}"`).join(" OR ")
|
? filterByPlatforms.map((platform) => `platform:"${platform}"`).join(" OR ")
|
||||||
: null;
|
: null;
|
||||||
const numForecastsFilter =
|
const numForecastsFilter =
|
||||||
forecastsThreshold > 0
|
forecastsThreshold && forecastsThreshold > 0
|
||||||
? `qualityindicators.numforecasts >= ${forecastsThreshold}`
|
? `qualityindicators.numforecasts >= ${forecastsThreshold}`
|
||||||
: null;
|
: null;
|
||||||
const finalFilter = [starsFilter, platformsFilter, numForecastsFilter]
|
const finalFilter = [starsFilter, platformsFilter, numForecastsFilter]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user