feat: capture on question pages, fixes, new frontpage condition
This commit is contained in:
parent
3ae7a68cb2
commit
3b85c32c9d
62
package-lock.json
generated
62
package-lock.json
generated
|
@ -16,15 +16,18 @@
|
||||||
"@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/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",
|
||||||
"airtable": "^0.11.1",
|
"airtable": "^0.11.1",
|
||||||
"algoliasearch": "^4.10.3",
|
"algoliasearch": "^4.10.3",
|
||||||
"autoprefixer": "^10.1.0",
|
"autoprefixer": "^10.1.0",
|
||||||
"axios": "^0.25.0",
|
"axios": "^0.25.0",
|
||||||
"chroma-js": "^2.4.2",
|
"chroma-js": "^2.4.2",
|
||||||
"critters": "^0.0.16",
|
"critters": "^0.0.16",
|
||||||
|
"date-fns": "^2.28.0",
|
||||||
"dom-to-image": "^2.6.0",
|
"dom-to-image": "^2.6.0",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
|
@ -3157,6 +3160,11 @@
|
||||||
"@types/ms": "*"
|
"@types/ms": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/dom-to-image": {
|
||||||
|
"version": "2.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.4.tgz",
|
||||||
|
"integrity": "sha512-UddUdGF1qulrSDulkz3K2Ypq527MR6ixlgAzqLbxSiQ0icx0XDlIV+h4+edmjq/1dqn0KgN0xGSe1kI9t+vGuw=="
|
||||||
|
},
|
||||||
"node_modules/@types/hast": {
|
"node_modules/@types/hast": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types%2fhast/-/hast-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types%2fhast/-/hast-2.3.4.tgz",
|
||||||
|
@ -3288,6 +3296,14 @@
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-copy-to-clipboard": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-transition-group": {
|
"node_modules/@types/react-transition-group": {
|
||||||
"version": "4.4.4",
|
"version": "4.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types%2freact-transition-group/-/react-transition-group-4.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types%2freact-transition-group/-/react-transition-group-4.4.4.tgz",
|
||||||
|
@ -5250,10 +5266,16 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/date-fns": {
|
"node_modules/date-fns": {
|
||||||
"version": "1.30.1",
|
"version": "2.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
|
||||||
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
|
"integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==",
|
||||||
"dev": true
|
"engines": {
|
||||||
|
"node": ">=0.11"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/date-fns"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/debounce": {
|
"node_modules/debounce": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
|
@ -8055,6 +8077,12 @@
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/listr-verbose-renderer/node_modules/date-fns": {
|
||||||
|
"version": "1.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
|
||||||
|
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/listr-verbose-renderer/node_modules/escape-string-regexp": {
|
"node_modules/listr-verbose-renderer/node_modules/escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
|
@ -42331,6 +42359,11 @@
|
||||||
"@types/ms": "*"
|
"@types/ms": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/dom-to-image": {
|
||||||
|
"version": "2.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.4.tgz",
|
||||||
|
"integrity": "sha512-UddUdGF1qulrSDulkz3K2Ypq527MR6ixlgAzqLbxSiQ0icx0XDlIV+h4+edmjq/1dqn0KgN0xGSe1kI9t+vGuw=="
|
||||||
|
},
|
||||||
"@types/hast": {
|
"@types/hast": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types%2fhast/-/hast-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types%2fhast/-/hast-2.3.4.tgz",
|
||||||
|
@ -42453,6 +42486,14 @@
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-copy-to-clipboard": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-transition-group": {
|
"@types/react-transition-group": {
|
||||||
"version": "4.4.4",
|
"version": "4.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types%2freact-transition-group/-/react-transition-group-4.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types%2freact-transition-group/-/react-transition-group-4.4.4.tgz",
|
||||||
|
@ -43924,10 +43965,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"date-fns": {
|
"date-fns": {
|
||||||
"version": "1.30.1",
|
"version": "2.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
|
||||||
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
|
"integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"debounce": {
|
"debounce": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
|
@ -45991,6 +46031,12 @@
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"version": "1.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
|
||||||
|
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"escape-string-regexp": {
|
"escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
|
|
|
@ -34,15 +34,18 @@
|
||||||
"@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/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",
|
||||||
"airtable": "^0.11.1",
|
"airtable": "^0.11.1",
|
||||||
"algoliasearch": "^4.10.3",
|
"algoliasearch": "^4.10.3",
|
||||||
"autoprefixer": "^10.1.0",
|
"autoprefixer": "^10.1.0",
|
||||||
"axios": "^0.25.0",
|
"axios": "^0.25.0",
|
||||||
"chroma-js": "^2.4.2",
|
"chroma-js": "^2.4.2",
|
||||||
"critters": "^0.0.16",
|
"critters": "^0.0.16",
|
||||||
|
"date-fns": "^2.28.0",
|
||||||
"dom-to-image": "^2.6.0",
|
"dom-to-image": "^2.6.0",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
|
|
|
@ -19,11 +19,14 @@ export async function getFrontpage(): Promise<Question[]> {
|
||||||
export async function rebuildFrontpage() {
|
export async function rebuildFrontpage() {
|
||||||
await measureTime(async () => {
|
await measureTime(async () => {
|
||||||
const rows = await prisma.$queryRaw<{ id: string }[]>`
|
const rows = await prisma.$queryRaw<{ id: string }[]>`
|
||||||
SELECT id FROM questions
|
SELECT questions.id FROM questions, history
|
||||||
WHERE
|
WHERE
|
||||||
(qualityindicators->>'stars')::int >= 3
|
questions.id = history.id
|
||||||
AND description != ''
|
AND (questions.qualityindicators->>'stars')::int >= 3
|
||||||
AND JSONB_ARRAY_LENGTH(options) > 0
|
AND questions.description != ''
|
||||||
|
AND JSONB_ARRAY_LENGTH(questions.options) > 0
|
||||||
|
GROUP BY questions.id
|
||||||
|
HAVING COUNT(DISTINCT history.timestamp) >= 7
|
||||||
ORDER BY RANDOM() LIMIT 50
|
ORDER BY RANDOM() LIMIT 50
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,16 @@
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { displayQuestionsWrapperForSearch } from "../web/display/displayQuestionsWrappers";
|
|
||||||
import { Layout } from "../web/display/Layout";
|
import { Layout } from "../web/display/Layout";
|
||||||
import { Props } from "../web/search/anySearchPage";
|
import { Props } from "../web/search/anySearchPage";
|
||||||
import CommonDisplay from "../web/search/CommonDisplay";
|
import { CommonDisplay } from "../web/search/CommonDisplay";
|
||||||
|
|
||||||
export { getServerSideProps } from "../web/search/anySearchPage";
|
export { getServerSideProps } from "../web/search/anySearchPage";
|
||||||
|
|
||||||
const IndexPage: NextPage<Props> = (props) => {
|
const IndexPage: NextPage<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<Layout page="search">
|
<Layout page="search">
|
||||||
<CommonDisplay
|
<CommonDisplay {...props} />
|
||||||
{...props}
|
|
||||||
hasSearchbar={true}
|
|
||||||
hasCapture={false}
|
|
||||||
hasAdvancedOptions={true}
|
|
||||||
placeholder={"Find forecasts about..."}
|
|
||||||
displaySeeMoreHint={true}
|
|
||||||
displayQuestionsWrapper={displayQuestionsWrapperForSearch}
|
|
||||||
/>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
35
src/web/common/CopyParagraph.tsx
Normal file
35
src/web/common/CopyParagraph.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||||
|
|
||||||
|
import { Button } from "../display/Button";
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/39501289/in-reactjs-how-to-copy-text-to-clipboard
|
||||||
|
|
||||||
|
export const CopyParagraph: React.FC<{ text: string; buttonText: string }> = ({
|
||||||
|
text,
|
||||||
|
buttonText: initialButtonText,
|
||||||
|
}) => {
|
||||||
|
const [buttonText, setButtonText] = useState(initialButtonText);
|
||||||
|
const handleButton = () => {
|
||||||
|
setButtonText("Copied");
|
||||||
|
setTimeout(async () => {
|
||||||
|
setButtonText(initialButtonText);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-stretch">
|
||||||
|
<p
|
||||||
|
className="bg-gray-100 cursor-pointer px-3 py-2 rounded-md shadow text-gray-700 font-mono text-sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
<CopyToClipboard text={text} onCopy={handleButton}>
|
||||||
|
<Button size="small">{buttonText}</Button>
|
||||||
|
</CopyToClipboard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -5,6 +5,8 @@ interface Props {
|
||||||
displayText: string;
|
displayText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/39501289/in-reactjs-how-to-copy-text-to-clipboard
|
||||||
|
|
||||||
export const CopyText: React.FC<Props> = ({ text, displayText }) => (
|
export const CopyText: React.FC<Props> = ({ text, displayText }) => (
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-center p-4 space-x-3 border rounded border-blue-400 hover:border-transparent bg-transparent hover:bg-blue-300 text-sm font-medium text-blue-400 hover:text-white cursor-pointer"
|
className="flex items-center justify-center p-4 space-x-3 border rounded border-blue-400 hover:border-transparent bg-transparent hover:bg-blue-300 text-sm font-medium text-blue-400 hover:text-white cursor-pointer"
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
size?: "small" | "normal";
|
||||||
|
}
|
||||||
|
|
||||||
export const Button: React.FC<Props> = ({ children, ...rest }) => (
|
export const Button: React.FC<Props> = ({
|
||||||
|
children,
|
||||||
|
size = "normal",
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const padding = size === "normal" ? "px-5 py-4" : "px-3 py-2";
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
{...rest}
|
{...rest}
|
||||||
className="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded text-center"
|
className={`bg-blue-500 cursor-pointer rounded-md shadow text-white hover:bg-blue-600 active:bg-gray-700 ${padding}`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -1,253 +0,0 @@
|
||||||
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 "../fragments.generated";
|
|
||||||
import { uploadToImgur } from "../worker/uploadToImgur";
|
|
||||||
import { DisplayQuestion } from "./DisplayQuestion";
|
|
||||||
|
|
||||||
function displayOneQuestionInner(result: QuestionFragment, containerRef) {
|
|
||||||
return (
|
|
||||||
<div ref={containerRef}>
|
|
||||||
{result ? (
|
|
||||||
<DisplayQuestion
|
|
||||||
question={result}
|
|
||||||
showTimeStamp={true}
|
|
||||||
expandFooterToFullWidth={true}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let domToImageWrapper = (reactRef) => {
|
|
||||||
let node = reactRef.current;
|
|
||||||
const scale = 3; // Increase for better quality
|
|
||||||
const style = {
|
|
||||||
transform: "scale(" + scale + ")",
|
|
||||||
transformOrigin: "top left",
|
|
||||||
width: node.offsetWidth + "px",
|
|
||||||
height: node.offsetHeight + "px",
|
|
||||||
};
|
|
||||||
const param = {
|
|
||||||
height: node.offsetHeight * scale,
|
|
||||||
width: node.offsetWidth * scale,
|
|
||||||
quality: 1,
|
|
||||||
style,
|
|
||||||
};
|
|
||||||
let image = domtoimage.toPng(node, param);
|
|
||||||
return image;
|
|
||||||
};
|
|
||||||
|
|
||||||
let generateHtml = (result, imgSrc) => {
|
|
||||||
let html = `<a href="${result.url} target="_blank""><img src="${imgSrc}" alt="Metaforecast.org snapshot of ''${result.title}'', from ${result.platform}"></a>`;
|
|
||||||
return html;
|
|
||||||
};
|
|
||||||
|
|
||||||
let generateMarkdown = (result, imgSrc) => {
|
|
||||||
let markdown = `[![](${imgSrc})](${result.url})`;
|
|
||||||
return markdown;
|
|
||||||
};
|
|
||||||
|
|
||||||
let generateSource = (result, imgSrc, hasDisplayBeenCaptured) => {
|
|
||||||
const [htmlButtonStatus, setHtmlButtonStatus] = useState("Copy HTML");
|
|
||||||
const [markdownButtonStatus, setMarkdownButtonStatus] =
|
|
||||||
useState("Copy markdown");
|
|
||||||
let handleHtmlButton = () => {
|
|
||||||
setHtmlButtonStatus("Copied");
|
|
||||||
let newtimeoutId = setTimeout(async () => {
|
|
||||||
setHtmlButtonStatus("Copy HTML");
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
let handleMarkdownButton = () => {
|
|
||||||
setMarkdownButtonStatus("Copied");
|
|
||||||
let newtimeoutId = setTimeout(async () => {
|
|
||||||
setMarkdownButtonStatus("Copy markdown");
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (result && imgSrc && hasDisplayBeenCaptured) {
|
|
||||||
return (
|
|
||||||
<div className="grid">
|
|
||||||
<p className="bg-gray-100 cursor-pointer px-3 py-2 rounded-md shadow text-grey-7000 font-mono text-sm">
|
|
||||||
{generateMarkdown(result, imgSrc)}
|
|
||||||
</p>
|
|
||||||
<CopyToClipboard
|
|
||||||
text={generateMarkdown(result, imgSrc)}
|
|
||||||
onCopy={() => handleMarkdownButton()}
|
|
||||||
>
|
|
||||||
<button className="bg-blue-500 cursor-pointer px-3 py-2 rounded-md shadow text-white hover:bg-blue-600 active:scale-120">
|
|
||||||
{markdownButtonStatus}
|
|
||||||
</button>
|
|
||||||
</CopyToClipboard>
|
|
||||||
<p className="bg-gray-100 cursor-pointer px-3 py-2 rounded-md shadow text-grey-7000 font-mono text-sm">
|
|
||||||
{generateHtml(result, imgSrc)}
|
|
||||||
</p>
|
|
||||||
<CopyToClipboard
|
|
||||||
text={generateHtml(result, imgSrc)}
|
|
||||||
onCopy={() => handleHtmlButton()}
|
|
||||||
>
|
|
||||||
<button className="bg-blue-500 cursor-pointer px-3 py-2 rounded-md shadow text-white mb-4 hover:bg-blue-600">
|
|
||||||
{htmlButtonStatus}
|
|
||||||
</button>
|
|
||||||
</CopyToClipboard>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let generateIframeURL = (result) => {
|
|
||||||
let iframeURL = "";
|
|
||||||
if (result) {
|
|
||||||
// if check not strictly necessary today
|
|
||||||
let parts = result.url.replace("questions", "questions/embed").split("/");
|
|
||||||
parts.pop();
|
|
||||||
parts.pop();
|
|
||||||
iframeURL = parts.join("/");
|
|
||||||
}
|
|
||||||
return iframeURL;
|
|
||||||
};
|
|
||||||
|
|
||||||
let metaculusEmbed = (result) => {
|
|
||||||
let platform = "";
|
|
||||||
let iframeURL = "";
|
|
||||||
if (result) {
|
|
||||||
iframeURL = generateIframeURL(result);
|
|
||||||
platform = result.platform;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
className={`${
|
|
||||||
platform == "Metaculus" ? "" : "hidden"
|
|
||||||
} flex h-80 w-full justify-self-center self-center`}
|
|
||||||
src={iframeURL}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let generateMetaculusIframeHTML = (result) => {
|
|
||||||
if (result) {
|
|
||||||
let iframeURL = generateIframeURL(result);
|
|
||||||
return `<iframe src="${iframeURL}" height="400" width="600"/>`;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let generateMetaculusSource = (result, hasDisplayBeenCaptured) => {
|
|
||||||
const [htmlButtonStatus, setHtmlButtonStatus] = useState("Copy HTML");
|
|
||||||
let handleHtmlButton = () => {
|
|
||||||
setHtmlButtonStatus("Copied");
|
|
||||||
let newtimeoutId = setTimeout(async () => {
|
|
||||||
setHtmlButtonStatus("Copy HTML");
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (result && hasDisplayBeenCaptured && result.platform == "Metaculus") {
|
|
||||||
return (
|
|
||||||
<div className="grid">
|
|
||||||
<p className="bg-gray-100 cursor-pointer px-3 py-2 rounded-md shadow text-grey-7000 font-mono text-sm">
|
|
||||||
{generateMetaculusIframeHTML(result)}
|
|
||||||
</p>
|
|
||||||
<CopyToClipboard
|
|
||||||
text={generateMetaculusIframeHTML(result)}
|
|
||||||
onCopy={() => handleHtmlButton()}
|
|
||||||
>
|
|
||||||
<button className="bg-blue-500 cursor-pointer px-3 py-2 rounded-md shadow text-white mb-4 hover:bg-blue-600">
|
|
||||||
{htmlButtonStatus}
|
|
||||||
</button>
|
|
||||||
</CopyToClipboard>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
result: QuestionFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DisplayOneQuestionForCapture: React.FC<Props> = ({ result }) => {
|
|
||||||
const [hasDisplayBeenCaptured, setHasDisplayBeenCaptured] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setHasDisplayBeenCaptured(false);
|
|
||||||
}, [result]);
|
|
||||||
|
|
||||||
const containerRef = useRef(null);
|
|
||||||
const [imgSrc, setImgSrc] = useState("");
|
|
||||||
const [mainButtonStatus, setMainButtonStatus] = useState(
|
|
||||||
"Capture image and generate code"
|
|
||||||
);
|
|
||||||
|
|
||||||
let exportAsPictureAndCode = () => {
|
|
||||||
let handleGettingImgurlImage = (imgurUrl) => {
|
|
||||||
setImgSrc(imgurUrl);
|
|
||||||
setMainButtonStatus("Done!");
|
|
||||||
let newtimeoutId = setTimeout(async () => {
|
|
||||||
setMainButtonStatus("Capture image and generate code");
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
domToImageWrapper(containerRef)
|
|
||||||
.then(async function (dataUrl) {
|
|
||||||
if (dataUrl) {
|
|
||||||
uploadToImgur(dataUrl, handleGettingImgurlImage);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
console.error("oops, something went wrong!", error);
|
|
||||||
});
|
|
||||||
}; //
|
|
||||||
|
|
||||||
let onCaptureButtonClick = () => {
|
|
||||||
exportAsPictureAndCode();
|
|
||||||
setMainButtonStatus("Processing...");
|
|
||||||
setHasDisplayBeenCaptured(true);
|
|
||||||
setImgSrc("");
|
|
||||||
};
|
|
||||||
|
|
||||||
function generateCaptureButton(result, onCaptureButtonClick) {
|
|
||||||
if (result) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={() => onCaptureButtonClick()}
|
|
||||||
className="bg-blue-500 cursor-pointer px-5 py-4 rounded-md shadow text-white hover:bg-blue-600 active:bg-gray-700"
|
|
||||||
>
|
|
||||||
{mainButtonStatus}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-center">
|
|
||||||
<div className="flex col-span-1 items-center justify-center">
|
|
||||||
{displayOneQuestionInner(result, containerRef)}
|
|
||||||
</div>
|
|
||||||
<div className="flex col-span-1 items-center justify-center">
|
|
||||||
{generateCaptureButton(result, onCaptureButtonClick)}
|
|
||||||
</div>
|
|
||||||
<div className="flex col-span-1 items-center justify-center">
|
|
||||||
<img src={imgSrc} className={hasDisplayBeenCaptured ? "" : "hidden"} />
|
|
||||||
</div>
|
|
||||||
<div className="flex col-span-1 items-center justify-center">
|
|
||||||
<div>{generateSource(result, imgSrc, hasDisplayBeenCaptured)}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex col-span-1 items-center justify-center mb-8">
|
|
||||||
{metaculusEmbed(result)}
|
|
||||||
</div>
|
|
||||||
<div className="flex col-span-1 items-center justify-center">
|
|
||||||
<div>{generateMetaculusSource(result, hasDisplayBeenCaptured)}</div>
|
|
||||||
</div>
|
|
||||||
<br></br>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/39501289/in-reactjs-how-to-copy-text-to-clipboard
|
|
||||||
// Note: https://stackoverflow.com/questions/66016033/can-no-longer-upload-images-to-imgur-from-localhost
|
|
||||||
// Use: http://imgurtester:3000/embed for testing.
|
|
|
@ -104,6 +104,7 @@ interface Props {
|
||||||
showTimeStamp: boolean;
|
showTimeStamp: boolean;
|
||||||
expandFooterToFullWidth: boolean;
|
expandFooterToFullWidth: boolean;
|
||||||
showIdToggle?: boolean;
|
showIdToggle?: boolean;
|
||||||
|
showExpandButton?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DisplayQuestion: React.FC<Props> = ({
|
export const DisplayQuestion: React.FC<Props> = ({
|
||||||
|
@ -111,6 +112,7 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
showTimeStamp,
|
showTimeStamp,
|
||||||
expandFooterToFullWidth,
|
expandFooterToFullWidth,
|
||||||
showIdToggle,
|
showIdToggle,
|
||||||
|
showExpandButton = true,
|
||||||
}) => {
|
}) => {
|
||||||
const { options } = question;
|
const { options } = question;
|
||||||
const lastUpdated = new Date(question.timestamp * 1000);
|
const lastUpdated = new Date(question.timestamp * 1000);
|
||||||
|
@ -129,6 +131,7 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div>
|
<div>
|
||||||
|
{showExpandButton ? (
|
||||||
<Link href={`/questions/${question.id}`} passHref>
|
<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
|
||||||
|
@ -137,6 +140,7 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
) : null}
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<a
|
<a
|
||||||
className="text-black no-underline"
|
className="text-black no-underline"
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { DisplayQuestions } from "./DisplayQuestions";
|
|
||||||
|
|
||||||
export function displayQuestionsWrapperForSearch({
|
|
||||||
results,
|
|
||||||
numDisplay,
|
|
||||||
showIdToggle,
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
||||||
<DisplayQuestions
|
|
||||||
results={results || []}
|
|
||||||
numDisplay={numDisplay}
|
|
||||||
showIdToggle={showIdToggle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
155
src/web/questions/components/CaptureQuestion.tsx
Normal file
155
src/web/questions/components/CaptureQuestion.tsx
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import domtoimage from "dom-to-image"; // https://github.com/tsayen/dom-to-image
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { CopyParagraph } from "../../common/CopyParagraph";
|
||||||
|
import { Button } from "../../display/Button";
|
||||||
|
import { DisplayQuestion } from "../../display/DisplayQuestion";
|
||||||
|
import { QuestionFragment } from "../../fragments.generated";
|
||||||
|
import { uploadToImgur } from "../../worker/uploadToImgur";
|
||||||
|
|
||||||
|
const domToImageWrapper = async (node: HTMLDivElement) => {
|
||||||
|
const scale = 3; // Increase for better quality
|
||||||
|
const style = {
|
||||||
|
transform: "scale(" + scale + ")",
|
||||||
|
transformOrigin: "top left",
|
||||||
|
width: node.offsetWidth + "px",
|
||||||
|
height: node.offsetHeight + "px",
|
||||||
|
};
|
||||||
|
const param = {
|
||||||
|
height: node.offsetHeight * scale,
|
||||||
|
width: node.offsetWidth * scale,
|
||||||
|
quality: 1,
|
||||||
|
style,
|
||||||
|
};
|
||||||
|
const image = await domtoimage.toPng(node, param);
|
||||||
|
return image;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImageSource: React.FC<{ question: QuestionFragment; imgSrc: string }> = ({
|
||||||
|
question,
|
||||||
|
imgSrc,
|
||||||
|
}) => {
|
||||||
|
if (!imgSrc) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = `<a href="${question.url}" target="_blank"><img src="${imgSrc}" alt="Metaforecast.org snapshot of ''${question.title}'', from ${question.platform.label}"></a>`;
|
||||||
|
const markdown = `[![](${imgSrc})](${question.url})`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<CopyParagraph text={markdown} buttonText="Copy markdown" />
|
||||||
|
<CopyParagraph text={html} buttonText="Copy HTML" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateMetaculusIframeURL = (question: QuestionFragment) => {
|
||||||
|
let parts = question.url.replace("questions", "questions/embed").split("/");
|
||||||
|
parts.pop();
|
||||||
|
parts.pop();
|
||||||
|
const iframeURL = parts.join("/");
|
||||||
|
return iframeURL;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateMetaculusIframeHTML = (question: QuestionFragment) => {
|
||||||
|
const iframeURL = generateMetaculusIframeURL(question);
|
||||||
|
return `<iframe src="${iframeURL}" height="400" width="600"/>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MetaculusEmbed: React.FC<{ question: QuestionFragment }> = ({
|
||||||
|
question,
|
||||||
|
}) => {
|
||||||
|
if (question.platform.id !== "metaculus") return null;
|
||||||
|
|
||||||
|
const iframeURL = generateMetaculusIframeURL(question);
|
||||||
|
return <iframe className="w-full h-80" src={iframeURL} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MetaculusSource: React.FC<{
|
||||||
|
question: QuestionFragment;
|
||||||
|
}> = ({ question }) => {
|
||||||
|
if (question.platform.id !== "metaculus") return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CopyParagraph
|
||||||
|
text={generateMetaculusIframeHTML(question)}
|
||||||
|
buttonText="Copy HTML"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
question: QuestionFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CaptureQuestion: React.FC<Props> = ({ question }) => {
|
||||||
|
const [imgSrc, setImgSrc] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImgSrc(null);
|
||||||
|
}, [question]);
|
||||||
|
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const initialMainButtonText = "Capture image and generate code";
|
||||||
|
const [mainButtonText, setMainButtonText] = useState(initialMainButtonText);
|
||||||
|
|
||||||
|
const exportAsPictureAndCode = async () => {
|
||||||
|
if (!containerRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const dataUrl = await domToImageWrapper(containerRef.current);
|
||||||
|
const imgurUrl = await uploadToImgur(dataUrl);
|
||||||
|
setImgSrc(imgurUrl);
|
||||||
|
setMainButtonText("Done!");
|
||||||
|
setTimeout(async () => {
|
||||||
|
setMainButtonText(initialMainButtonText);
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("oops, something went wrong!", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCaptureButtonClick = async () => {
|
||||||
|
setMainButtonText("Processing...");
|
||||||
|
setImgSrc(null);
|
||||||
|
await exportAsPictureAndCode();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 place-items-center">
|
||||||
|
<div ref={containerRef}>
|
||||||
|
<DisplayQuestion
|
||||||
|
question={question}
|
||||||
|
showTimeStamp={true}
|
||||||
|
showExpandButton={false}
|
||||||
|
expandFooterToFullWidth={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button onClick={onCaptureButtonClick}>{mainButtonText}</Button>
|
||||||
|
</div>
|
||||||
|
{imgSrc ? (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<img src={imgSrc} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ImageSource question={question} imgSrc={imgSrc} />
|
||||||
|
</div>
|
||||||
|
<div className="justify-self-stretch">
|
||||||
|
<MetaculusEmbed question={question} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MetaculusSource question={question} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: https://stackoverflow.com/questions/66016033/can-no-longer-upload-images-to-imgur-from-localhost
|
||||||
|
// Use: http://imgurtester:3000/embed for testing.
|
|
@ -41,7 +41,7 @@ const dataAsXy = (data: DataSet) =>
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const colors = ["dodgerblue", "crimson", "seagreen", "darkviolet", "turquoise"];
|
const colors = ["dodgerblue", "crimson", "seagreen", "darkviolet", "turquoise"];
|
||||||
// can't be replaced with React component, VictoryChar requires VictoryGroup elements to be immediate children
|
// can't be replaced with React component, VictoryChart requires VictoryGroup elements to be immediate children
|
||||||
const getVictoryGroup = ({ data, i }: { data: DataSet; i: number }) => {
|
const getVictoryGroup = ({ data, i }: { data: DataSet; i: number }) => {
|
||||||
return (
|
return (
|
||||||
<VictoryGroup color={colors[i] || "darkgray"} data={dataAsXy(data)} key={i}>
|
<VictoryGroup color={colors[i] || "darkgray"} data={dataAsXy(data)} key={i}>
|
||||||
|
|
|
@ -4,11 +4,11 @@ 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 { DisplayOneQuestionForCapture } from "../../display/DisplayOneQuestionForCapture";
|
|
||||||
import { Layout } from "../../display/Layout";
|
import { Layout } from "../../display/Layout";
|
||||||
import { LineHeader } from "../../display/LineHeader";
|
import { LineHeader } from "../../display/LineHeader";
|
||||||
import { QuestionWithHistoryFragment } from "../../fragments.generated";
|
import { QuestionWithHistoryFragment } from "../../fragments.generated";
|
||||||
import { ssrUrql } from "../../urql";
|
import { ssrUrql } from "../../urql";
|
||||||
|
import { CaptureQuestion } from "../components/CaptureQuestion";
|
||||||
import { HistoryChart } from "../components/HistoryChart";
|
import { HistoryChart } from "../components/HistoryChart";
|
||||||
import { IndicatorsTable } from "../components/IndicatorsTable";
|
import { IndicatorsTable } from "../components/IndicatorsTable";
|
||||||
import { QuestionPageDocument } from "../queries.generated";
|
import { QuestionPageDocument } from "../queries.generated";
|
||||||
|
@ -93,7 +93,7 @@ const QuestionPage: NextPage<Props> = ({ id }) => {
|
||||||
<LineHeader>
|
<LineHeader>
|
||||||
<h1>Capture</h1>
|
<h1>Capture</h1>
|
||||||
</LineHeader>
|
</LineHeader>
|
||||||
<DisplayOneQuestionForCapture result={data.result} />
|
<CaptureQuestion question={data.result} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React, { Fragment, useMemo, useState } from "react";
|
||||||
import { useQuery } from "urql";
|
import { useQuery } from "urql";
|
||||||
|
|
||||||
import { ButtonsForStars } from "../display/ButtonsForStars";
|
import { ButtonsForStars } from "../display/ButtonsForStars";
|
||||||
|
import { DisplayQuestions } from "../display/DisplayQuestions";
|
||||||
import { MultiSelectPlatform } from "../display/MultiSelectPlatform";
|
import { MultiSelectPlatform } from "../display/MultiSelectPlatform";
|
||||||
import { QueryForm } from "../display/QueryForm";
|
import { QueryForm } from "../display/QueryForm";
|
||||||
import { SliderElement } from "../display/SliderElement";
|
import { SliderElement } from "../display/SliderElement";
|
||||||
|
@ -11,34 +12,16 @@ import { useIsFirstRender, useNoInitialEffect } from "../hooks";
|
||||||
import { Props as AnySearchPageProps, QueryParameters } from "./anySearchPage";
|
import { Props as AnySearchPageProps, QueryParameters } from "./anySearchPage";
|
||||||
import { SearchDocument } from "./queries.generated";
|
import { SearchDocument } from "./queries.generated";
|
||||||
|
|
||||||
interface Props extends AnySearchPageProps {
|
interface Props extends AnySearchPageProps {}
|
||||||
hasSearchbar: boolean;
|
|
||||||
hasCapture: boolean;
|
|
||||||
hasAdvancedOptions: boolean;
|
|
||||||
placeholder: string;
|
|
||||||
displaySeeMoreHint: boolean;
|
|
||||||
displayQuestionsWrapper: (opts: {
|
|
||||||
results: QuestionFragment[];
|
|
||||||
numDisplay: number;
|
|
||||||
whichResultToDisplayAndCapture: number;
|
|
||||||
showIdToggle: boolean;
|
|
||||||
}) => React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
const CommonDisplay: React.FC<Props> = ({
|
export const CommonDisplay: React.FC<Props> = ({
|
||||||
defaultResults,
|
defaultResults,
|
||||||
initialQueryParameters,
|
initialQueryParameters,
|
||||||
defaultQueryParameters,
|
defaultQueryParameters,
|
||||||
initialNumDisplay,
|
initialNumDisplay,
|
||||||
defaultNumDisplay,
|
defaultNumDisplay,
|
||||||
platformsConfig,
|
platformsConfig,
|
||||||
hasSearchbar,
|
|
||||||
hasCapture,
|
|
||||||
hasAdvancedOptions,
|
|
||||||
placeholder,
|
|
||||||
displaySeeMoreHint,
|
|
||||||
displayQuestionsWrapper,
|
|
||||||
}) => {
|
}) => {
|
||||||
/* States */
|
/* States */
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -119,12 +102,15 @@ const CommonDisplay: React.FC<Props> = ({
|
||||||
numDisplay % 3 != 0
|
numDisplay % 3 != 0
|
||||||
? numDisplay + (3 - (Math.round(numDisplay) % 3))
|
? numDisplay + (3 - (Math.round(numDisplay) % 3))
|
||||||
: numDisplay;
|
: numDisplay;
|
||||||
return displayQuestionsWrapper({
|
return (
|
||||||
results,
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
numDisplay: numDisplayRounded,
|
<DisplayQuestions
|
||||||
whichResultToDisplayAndCapture,
|
results={results}
|
||||||
showIdToggle,
|
numDisplay={numDisplayRounded}
|
||||||
});
|
showIdToggle={showIdToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRoute = () => {
|
const updateRoute = () => {
|
||||||
|
@ -230,32 +216,18 @@ const CommonDisplay: React.FC<Props> = ({
|
||||||
setShowIdToggle(!showIdToggle);
|
setShowIdToggle(!showIdToggle);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Capture functionality
|
|
||||||
const onClickBack = () => {
|
|
||||||
const decreaseUntil0 = (num: number) => (num - 1 > 0 ? num - 1 : 0);
|
|
||||||
setWhichResultToDisplayAndCapture(
|
|
||||||
decreaseUntil0(whichResultToDisplayAndCapture)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const onClickForward = (whichResultToDisplayAndCapture: number) => {
|
|
||||||
setWhichResultToDisplayAndCapture(whichResultToDisplayAndCapture + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Final return */
|
/* Final return */
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<label className="mb-4 mt-4 flex flex-row justify-center items-center">
|
<label className="mb-4 mt-4 flex flex-row justify-center items-center">
|
||||||
{hasSearchbar ? (
|
|
||||||
<div className="w-10/12 mb-2">
|
<div className="w-10/12 mb-2">
|
||||||
<QueryForm
|
<QueryForm
|
||||||
value={queryParameters.query}
|
value={queryParameters.query}
|
||||||
onChange={onChangeSearchBar}
|
onChange={onChangeSearchBar}
|
||||||
placeholder={placeholder}
|
placeholder="Find forecasts about..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
|
||||||
|
|
||||||
{hasAdvancedOptions ? (
|
|
||||||
<div className="w-2/12 flex justify-center ml-4 md:ml-2 lg:ml-0">
|
<div className="w-2/12 flex justify-center ml-4 md:ml-2 lg:ml-0">
|
||||||
<button
|
<button
|
||||||
className="text-gray-500 text-sm mb-2"
|
className="text-gray-500 text-sm mb-2"
|
||||||
|
@ -264,27 +236,9 @@ const CommonDisplay: React.FC<Props> = ({
|
||||||
Advanced options ▼
|
Advanced options ▼
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
|
||||||
|
|
||||||
{hasCapture ? (
|
|
||||||
<div className="w-2/12 flex justify-center ml-4 md:ml-2 gap-1 lg:ml-0">
|
|
||||||
<button
|
|
||||||
className="text-blue-500 cursor-pointer text-xl mb-3 pr-3 hover:text-blue-600"
|
|
||||||
onClick={() => onClickBack()}
|
|
||||||
>
|
|
||||||
◀
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="text-blue-500 cursor-pointer text-xl mb-3 pl-3 hover:text-blue-600"
|
|
||||||
onClick={() => onClickForward(whichResultToDisplayAndCapture)}
|
|
||||||
>
|
|
||||||
▶
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{hasAdvancedOptions && advancedOptions ? (
|
{advancedOptions ? (
|
||||||
<div className="flex-1 flex-col mx-auto justify-center items-center w-full">
|
<div className="flex-1 flex-col mx-auto justify-center items-center w-full">
|
||||||
<div className="grid sm:grid-rows-4 sm:grid-cols-1 md:grid-rows-2 lg:grid-rows-2 grid-cols-1 md:grid-cols-3 lg:grid-cols-3 items-center content-center bg-gray-50 rounded-md px-8 pt-4 pb-1 shadow mb-4">
|
<div className="grid sm:grid-rows-4 sm:grid-cols-1 md:grid-rows-2 lg:grid-rows-2 grid-cols-1 md:grid-cols-3 lg:grid-cols-3 items-center content-center bg-gray-50 rounded-md px-8 pt-4 pb-1 shadow mb-4">
|
||||||
<div className="flex row-start-1 row-end-1 col-start-1 col-end-4 md:row-span-1 md:col-start-1 md:col-end-1 md:row-start-1 md:row-end-1 lg:row-span-1 lg:col-start-1 lg:col-end-1 lg:row-start-1 lg:row-end-1 items-center justify-center mb-4">
|
<div className="flex row-start-1 row-end-1 col-start-1 col-end-4 md:row-span-1 md:col-start-1 md:col-end-1 md:row-start-1 md:row-end-1 lg:row-span-1 lg:col-start-1 lg:col-end-1 lg:row-start-1 lg:row-end-1 items-center justify-center mb-4">
|
||||||
|
@ -326,8 +280,7 @@ const CommonDisplay: React.FC<Props> = ({
|
||||||
|
|
||||||
<div>{getInfoToDisplayQuestionsFunction()}</div>
|
<div>{getInfoToDisplayQuestionsFunction()}</div>
|
||||||
|
|
||||||
{displaySeeMoreHint &&
|
{!results || (results.length !== 0 && numDisplay < results.length) ? (
|
||||||
(!results || (results.length !== 0 && numDisplay < results.length)) ? (
|
|
||||||
<div>
|
<div>
|
||||||
<p className="mt-4 mb-4">
|
<p className="mt-4 mb-4">
|
||||||
{"Can't find what you were looking for?"}
|
{"Can't find what you were looking for?"}
|
||||||
|
@ -357,5 +310,3 @@ const CommonDisplay: React.FC<Props> = ({
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CommonDisplay;
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { QuestionFragment } from "../fragments.generated";
|
||||||
import { ssrUrql } from "../urql";
|
import { ssrUrql } from "../urql";
|
||||||
import { FrontpageDocument, SearchDocument } from "./queries.generated";
|
import { FrontpageDocument, SearchDocument } from "./queries.generated";
|
||||||
|
|
||||||
/* Common code for / and /capture */
|
/* Common code for / and /capture (/capture is deprecated, TODO - refactor) */
|
||||||
|
|
||||||
export interface QueryParameters {
|
export interface QueryParameters {
|
||||||
query: string;
|
query: string;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
// import fetch from "fetch"
|
|
||||||
import axios, { AxiosRequestConfig } from "axios";
|
import axios, { AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
export async function uploadToImgur(dataURL, handleGettingImgurlImage) {
|
export async function uploadToImgur(dataURL: string): Promise<string> {
|
||||||
let request: AxiosRequestConfig = {
|
const request: AxiosRequestConfig = {
|
||||||
method: "post",
|
method: "post",
|
||||||
url: "https://api.imgur.com/3/image",
|
url: "https://api.imgur.com/3/image",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -12,18 +11,15 @@ export async function uploadToImgur(dataURL, handleGettingImgurlImage) {
|
||||||
type: "base64",
|
type: "base64",
|
||||||
image: dataURL.split(",")[1],
|
image: dataURL.split(",")[1],
|
||||||
},
|
},
|
||||||
// redirect: "follow",
|
|
||||||
};
|
};
|
||||||
let url;
|
|
||||||
|
let url = "https://i.imgur.com/qcThRRz.gif"; // Error image
|
||||||
try {
|
try {
|
||||||
let response = await axios(request).then((response) => response.data);
|
const response = await axios(request).then((response) => response.data);
|
||||||
// console.log(dataURL)
|
|
||||||
// console.log(response)
|
|
||||||
url = `https://i.imgur.com/${response.data.id}.png`;
|
url = `https://i.imgur.com/${response.data.id}.png`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
console.log("error", error);
|
||||||
}
|
}
|
||||||
let errorImageURL = "https://i.imgur.com/qcThRRz.gif"; // Error image
|
|
||||||
url = url || errorImageURL;
|
return url;
|
||||||
handleGettingImgurlImage(url);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user