Merge pull request #90 from quantified-uncertainty/embed-question-page-2
Embed question page 2
This commit is contained in:
		
						commit
						c051f0dc7d
					
				
							
								
								
									
										16
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							|  | @ -61,6 +61,7 @@ | ||||||
|         "postcss-preset-env": "^7.3.2", |         "postcss-preset-env": "^7.3.2", | ||||||
|         "prisma": "^3.11.1", |         "prisma": "^3.11.1", | ||||||
|         "query-string": "^7.1.1", |         "query-string": "^7.1.1", | ||||||
|  |         "re-resizable": "^6.9.9", | ||||||
|         "react": "^17.0.2", |         "react": "^17.0.2", | ||||||
|         "react-component-export-image": "^1.0.6", |         "react-component-export-image": "^1.0.6", | ||||||
|         "react-compound-slider": "^3.3.1", |         "react-compound-slider": "^3.3.1", | ||||||
|  | @ -37219,6 +37220,15 @@ | ||||||
|         "rc": "cli.js" |         "rc": "cli.js" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/re-resizable": { | ||||||
|  |       "version": "6.9.9", | ||||||
|  |       "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.9.tgz", | ||||||
|  |       "integrity": "sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==", | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "react": "^16.13.1 || ^17.0.0 || ^18.0.0", | ||||||
|  |         "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/react": { |     "node_modules/react": { | ||||||
|       "version": "17.0.2", |       "version": "17.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", |       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", | ||||||
|  | @ -67696,6 +67706,12 @@ | ||||||
|         "strip-json-comments": "~2.0.1" |         "strip-json-comments": "~2.0.1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "re-resizable": { | ||||||
|  |       "version": "6.9.9", | ||||||
|  |       "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.9.tgz", | ||||||
|  |       "integrity": "sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==", | ||||||
|  |       "requires": {} | ||||||
|  |     }, | ||||||
|     "react": { |     "react": { | ||||||
|       "version": "17.0.2", |       "version": "17.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", |       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", | ||||||
|  |  | ||||||
|  | @ -79,6 +79,7 @@ | ||||||
|     "postcss-preset-env": "^7.3.2", |     "postcss-preset-env": "^7.3.2", | ||||||
|     "prisma": "^3.11.1", |     "prisma": "^3.11.1", | ||||||
|     "query-string": "^7.1.1", |     "query-string": "^7.1.1", | ||||||
|  |     "re-resizable": "^6.9.9", | ||||||
|     "react": "^17.0.2", |     "react": "^17.0.2", | ||||||
|     "react-component-export-image": "^1.0.6", |     "react-component-export-image": "^1.0.6", | ||||||
|     "react-compound-slider": "^3.3.1", |     "react-compound-slider": "^3.3.1", | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								src/web/common/BoxedLink.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/web/common/BoxedLink.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | import { FaExternalLinkAlt } from "react-icons/fa"; | ||||||
|  | 
 | ||||||
|  | type Props = { | ||||||
|  |   url: string; | ||||||
|  |   size?: "normal" | "small"; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const BoxedLink: React.FC<Props> = ({ | ||||||
|  |   url, | ||||||
|  |   size = "normal", | ||||||
|  |   children, | ||||||
|  | }) => ( | ||||||
|  |   <a | ||||||
|  |     className={`px-2 py-1 border-2 border-gray-400 rounded-lg text-black no-underline text-normal hover:bg-gray-100 inline-flex flex-nowrap space-x-1 items-center ${ | ||||||
|  |       size === "small" ? "text-sm" : "" | ||||||
|  |     }`}
 | ||||||
|  |     href={url} | ||||||
|  |     target="_blank" | ||||||
|  |   > | ||||||
|  |     <span>{children}</span> | ||||||
|  |     <FaExternalLinkAlt className="text-gray-400 inline" /> | ||||||
|  |   </a> | ||||||
|  | ); | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import domtoimage from "dom-to-image"; // https://github.com/tsayen/dom-to-image
 | import domtoimage from "dom-to-image"; // https://github.com/tsayen/dom-to-image
 | ||||||
|  | import { Resizable } from "re-resizable"; | ||||||
| import { useEffect, useRef, useState } from "react"; | import { useEffect, useRef, useState } from "react"; | ||||||
| 
 |  | ||||||
| import { Button } from "../../common/Button"; | import { Button } from "../../common/Button"; | ||||||
| import { CopyParagraph } from "../../common/CopyParagraph"; | import { CopyParagraph } from "../../common/CopyParagraph"; | ||||||
| import { QuestionFragment } from "../../fragments.generated"; | import { QuestionFragment } from "../../fragments.generated"; | ||||||
|  | @ -79,6 +79,15 @@ const MetaculusSource: React.FC<{ | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | const GrayContainer: React.FC<{ title: string }> = ({ title, children }) => ( | ||||||
|  |   <div className="bg-gray-100 p-2 space-y-1"> | ||||||
|  |     <div className="uppercase text-xs font-bold tracking-wide text-gray-600"> | ||||||
|  |       {title}: | ||||||
|  |     </div> | ||||||
|  |     <div>{children}</div> | ||||||
|  |   </div> | ||||||
|  | ); | ||||||
|  | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   question: QuestionFragment; |   question: QuestionFragment; | ||||||
| } | } | ||||||
|  | @ -118,27 +127,19 @@ export const CaptureQuestion: React.FC<Props> = ({ question }) => { | ||||||
|     await exportAsPictureAndCode(); |     await exportAsPictureAndCode(); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   if (imgSrc) { | ||||||
|     return ( |     return ( | ||||||
|     <div className="grid grid-cols-1 md:grid-cols-2 gap-4 place-items-center"> |       <div className="space-y-4"> | ||||||
|       <div ref={containerRef}> |         <GrayContainer title="Generated image"> | ||||||
|         <QuestionCard |           <a href={imgSrc} target="_blank"> | ||||||
|           question={question} |  | ||||||
|           showTimeStamp={true} |  | ||||||
|           showExpandButton={false} |  | ||||||
|           expandFooterToFullWidth={true} |  | ||||||
|         /> |  | ||||||
|       </div> |  | ||||||
|       <div> |  | ||||||
|         <Button onClick={onCaptureButtonClick}>{mainButtonText}</Button> |  | ||||||
|       </div> |  | ||||||
|       {imgSrc ? ( |  | ||||||
|         <> |  | ||||||
|           <div> |  | ||||||
|             <img src={imgSrc} /> |             <img src={imgSrc} /> | ||||||
|           </div> |           </a> | ||||||
|  |         </GrayContainer> | ||||||
|         <div> |         <div> | ||||||
|           <ImageSource question={question} imgSrc={imgSrc} /> |           <ImageSource question={question} imgSrc={imgSrc} /> | ||||||
|         </div> |         </div> | ||||||
|  |         {question.platform.id === "metaculus" ? ( | ||||||
|  |           <> | ||||||
|             <div className="justify-self-stretch"> |             <div className="justify-self-stretch"> | ||||||
|               <MetaculusEmbed question={question} /> |               <MetaculusEmbed question={question} /> | ||||||
|             </div> |             </div> | ||||||
|  | @ -149,6 +150,34 @@ export const CaptureQuestion: React.FC<Props> = ({ question }) => { | ||||||
|         ) : null} |         ) : null} | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <div className="space-y-2 flex flex-col"> | ||||||
|  |       <GrayContainer title="Resizable preview"> | ||||||
|  |         <Resizable | ||||||
|  |           minWidth={320} | ||||||
|  |           bounds="window" | ||||||
|  |           enable={{ right: true, left: true }} | ||||||
|  |         > | ||||||
|  |           <div ref={containerRef}> | ||||||
|  |             <QuestionCard | ||||||
|  |               container={(children) => ( | ||||||
|  |                 <div className="px-4 py-3 bg-white">{children}</div> | ||||||
|  |               )} | ||||||
|  |               question={question} | ||||||
|  |               showTimeStamp={true} | ||||||
|  |               showExpandButton={false} | ||||||
|  |               expandFooterToFullWidth={true} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |         </Resizable> | ||||||
|  |       </GrayContainer> | ||||||
|  |       <Button onClick={onCaptureButtonClick} size="small"> | ||||||
|  |         {mainButtonText} | ||||||
|  |       </Button> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Note: https://stackoverflow.com/questions/66016033/can-no-longer-upload-images-to-imgur-from-localhost
 | // Note: https://stackoverflow.com/questions/66016033/can-no-longer-upload-images-to-imgur-from-localhost
 | ||||||
|  |  | ||||||
|  | @ -1,16 +1,6 @@ | ||||||
| import { FaExternalLinkAlt } from "react-icons/fa"; | import { BoxedLink } from "../../common/BoxedLink"; | ||||||
| 
 |  | ||||||
| import { QuestionFragment } from "../../fragments.generated"; | import { QuestionFragment } from "../../fragments.generated"; | ||||||
| 
 | 
 | ||||||
| export const PlatformLink: React.FC<{ question: QuestionFragment }> = ({ | export const PlatformLink: React.FC<{ question: QuestionFragment }> = ({ | ||||||
|   question, |   question, | ||||||
| }) => ( | }) => <BoxedLink url={question.url}>{question.platform.label}</BoxedLink>; | ||||||
|   <a |  | ||||||
|     className="px-2 py-1 border-2 border-gray-400 rounded-lg text-black no-underline text-normal hover:bg-gray-100 flex flex-nowrap space-x-1 items-center" |  | ||||||
|     href={question.url} |  | ||||||
|     target="_blank" |  | ||||||
|   > |  | ||||||
|     <span>{question.platform.label}</span> |  | ||||||
|     <FaExternalLinkAlt className="text-gray-400 inline sm:text-md text-md" /> |  | ||||||
|   </a> |  | ||||||
| ); |  | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import Link from "next/link"; | import Link from "next/link"; | ||||||
|  | import { ReactElement, ReactNode } from "react"; | ||||||
| import { FaExpand } from "react-icons/fa"; | import { FaExpand } from "react-icons/fa"; | ||||||
| import ReactMarkdown from "react-markdown"; | import ReactMarkdown from "react-markdown"; | ||||||
| 
 |  | ||||||
| import { Card } from "../../../common/Card"; | import { Card } from "../../../common/Card"; | ||||||
| import { CopyText } from "../../../common/CopyText"; | import { CopyText } from "../../../common/CopyText"; | ||||||
| import { QuestionFragment } from "../../../fragments.generated"; | import { QuestionFragment } from "../../../fragments.generated"; | ||||||
|  | @ -63,6 +63,7 @@ const LastUpdated: React.FC<{ timestamp: Date }> = ({ timestamp }) => ( | ||||||
| // Main component
 | // Main component
 | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|  |   container?: (children: ReactNode) => ReactElement; | ||||||
|   question: QuestionFragment; |   question: QuestionFragment; | ||||||
|   showTimeStamp: boolean; |   showTimeStamp: boolean; | ||||||
|   expandFooterToFullWidth: boolean; |   expandFooterToFullWidth: boolean; | ||||||
|  | @ -71,6 +72,7 @@ interface Props { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const QuestionCard: React.FC<Props> = ({ | export const QuestionCard: React.FC<Props> = ({ | ||||||
|  |   container = (children) => <Card>{children}</Card>, | ||||||
|   question, |   question, | ||||||
|   showTimeStamp, |   showTimeStamp, | ||||||
|   expandFooterToFullWidth, |   expandFooterToFullWidth, | ||||||
|  | @ -82,8 +84,7 @@ export const QuestionCard: React.FC<Props> = ({ | ||||||
| 
 | 
 | ||||||
|   const isBinary = isQuestionBinary(question); |   const isBinary = isQuestionBinary(question); | ||||||
| 
 | 
 | ||||||
|   return ( |   return container( | ||||||
|     <Card> |  | ||||||
|     <div className="h-full flex flex-col space-y-4"> |     <div className="h-full flex flex-col space-y-4"> | ||||||
|       <div className="flex-grow space-y-4"> |       <div className="flex-grow space-y-4"> | ||||||
|         {showIdToggle ? ( |         {showIdToggle ? ( | ||||||
|  | @ -148,6 +149,5 @@ export const QuestionCard: React.FC<Props> = ({ | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     </Card> |  | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,11 +1,10 @@ | ||||||
| import { GetServerSideProps, NextPage } from "next"; | import { GetServerSideProps, NextPage } from "next"; | ||||||
| import NextError from "next/error"; | import NextError from "next/error"; | ||||||
| import ReactMarkdown from "react-markdown"; | import ReactMarkdown from "react-markdown"; | ||||||
| 
 | import { BoxedLink } from "../../common/BoxedLink"; | ||||||
| import { Card } from "../../common/Card"; | import { Card } from "../../common/Card"; | ||||||
| import { CopyParagraph } from "../../common/CopyParagraph"; | import { CopyParagraph } from "../../common/CopyParagraph"; | ||||||
| import { Layout } from "../../common/Layout"; | import { Layout } from "../../common/Layout"; | ||||||
| import { LineHeader } from "../../common/LineHeader"; |  | ||||||
| import { Query } from "../../common/Query"; | import { Query } from "../../common/Query"; | ||||||
| import { QuestionWithHistoryFragment } from "../../fragments.generated"; | import { QuestionWithHistoryFragment } from "../../fragments.generated"; | ||||||
| import { ssrUrql } from "../../urql"; | import { ssrUrql } from "../../urql"; | ||||||
|  | @ -43,13 +42,51 @@ export const getServerSideProps: GetServerSideProps<Props> = async ( | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const Section: React.FC<{ title: string }> = ({ title, children }) => ( | const Section: React.FC<{ title: string; id?: string }> = ({ | ||||||
|   <div className="space-y-2 flex flex-col items-start"> |   title, | ||||||
|     <h2 className="text-xl text-gray-900">{title}</h2> |   children, | ||||||
|  |   id, | ||||||
|  | }) => ( | ||||||
|  |   <div className="space-y-4 flex flex-col items-start" id={id}> | ||||||
|  |     <div className="border-b-2 border-gray-200 w-full group"> | ||||||
|  |       <h2 className="text-xl leading-3 text-gray-900"> | ||||||
|  |         <span>{title}</span> | ||||||
|  |         {id ? ( | ||||||
|  |           <> | ||||||
|  |             {" "} | ||||||
|  |             <a | ||||||
|  |               className="text-gray-300 no-underline hidden group-hover:inline" | ||||||
|  |               href={`#${id}`} | ||||||
|  |             > | ||||||
|  |               # | ||||||
|  |             </a> | ||||||
|  |           </> | ||||||
|  |         ) : null} | ||||||
|  |       </h2> | ||||||
|  |     </div> | ||||||
|     <div>{children}</div> |     <div>{children}</div> | ||||||
|   </div> |   </div> | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | const EmbedSection: React.FC<{ question: QuestionWithHistoryFragment }> = ({ | ||||||
|  |   question, | ||||||
|  | }) => { | ||||||
|  |   const url = getBasePath() + `/questions/embed/${question.id}`; | ||||||
|  |   return ( | ||||||
|  |     <Section title="Embed" id="embed"> | ||||||
|  |       <div className="mb-2"> | ||||||
|  |         <BoxedLink url={url} size="small"> | ||||||
|  |           Preview | ||||||
|  |         </BoxedLink> | ||||||
|  |       </div> | ||||||
|  |       <CopyParagraph | ||||||
|  |         text={`<iframe src="${url}" height="600" width="600" frameborder="0" />`} | ||||||
|  |         buttonText="Copy HTML" | ||||||
|  |       /> | ||||||
|  |     </Section> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| const LargeQuestionCard: React.FC<{ | const LargeQuestionCard: React.FC<{ | ||||||
|   question: QuestionWithHistoryFragment; |   question: QuestionWithHistoryFragment; | ||||||
| }> = ({ question }) => { | }> = ({ question }) => { | ||||||
|  | @ -65,8 +102,8 @@ const LargeQuestionCard: React.FC<{ | ||||||
|         <QuestionChartOrVisualization question={question} /> |         <QuestionChartOrVisualization question={question} /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div className="mx-auto max-w-prose"> |       <div className="mx-auto max-w-prose space-y-8"> | ||||||
|         <Section title="Question description"> |         <Section title="Question description" id="description"> | ||||||
|           <ReactMarkdown |           <ReactMarkdown | ||||||
|             linkTarget="_blank" |             linkTarget="_blank" | ||||||
|             className="font-normal text-gray-900" |             className="font-normal text-gray-900" | ||||||
|  | @ -74,39 +111,17 @@ const LargeQuestionCard: React.FC<{ | ||||||
|             {question.description.replaceAll("---", "")} |             {question.description.replaceAll("---", "")} | ||||||
|           </ReactMarkdown> |           </ReactMarkdown> | ||||||
|         </Section> |         </Section> | ||||||
|         <div className="mt-5"> |         <Section title="Indicators" id="indicators"> | ||||||
|           <Section title="Indicators"> |  | ||||||
|           <IndicatorsTable question={question} /> |           <IndicatorsTable question={question} /> | ||||||
|         </Section> |         </Section> | ||||||
|         </div> |         <Section title="Capture" id="capture"> | ||||||
|  |           <CaptureQuestion question={question} /> | ||||||
|  |         </Section> | ||||||
|  |         <EmbedSection question={question} /> | ||||||
|       </div> |       </div> | ||||||
|     </Card> |     </Card> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| const QuestionScreen: React.FC<{ question: QuestionWithHistoryFragment }> = ({ |  | ||||||
|   question, |  | ||||||
| }) => ( |  | ||||||
|   <div className="space-y-8"> |  | ||||||
|     <LargeQuestionCard question={question} /> |  | ||||||
|     <div className="space-y-4"> |  | ||||||
|       <LineHeader> |  | ||||||
|         <h1>Capture</h1> |  | ||||||
|       </LineHeader> |  | ||||||
|       <CaptureQuestion question={question} /> |  | ||||||
|       <LineHeader> |  | ||||||
|         <h1>Embed</h1> |  | ||||||
|       </LineHeader> |  | ||||||
|       <div className="max-w-md mx-auto"> |  | ||||||
|         <CopyParagraph |  | ||||||
|           text={`<iframe src="${ |  | ||||||
|             getBasePath() + `/questions/embed/${question.id}` |  | ||||||
|           }" height="600" width="600" frameborder="0" />`}
 |  | ||||||
|           buttonText="Copy HTML" |  | ||||||
|         /> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const QuestionPage: NextPage<Props> = ({ id }) => { | const QuestionPage: NextPage<Props> = ({ id }) => { | ||||||
|   return ( |   return ( | ||||||
|  | @ -115,7 +130,7 @@ const QuestionPage: NextPage<Props> = ({ id }) => { | ||||||
|         <Query document={QuestionPageDocument} variables={{ id }}> |         <Query document={QuestionPageDocument} variables={{ id }}> | ||||||
|           {({ data }) => |           {({ data }) => | ||||||
|             data.result ? ( |             data.result ? ( | ||||||
|               <QuestionScreen question={data.result} /> |               <LargeQuestionCard question={data.result} /> | ||||||
|             ) : ( |             ) : ( | ||||||
|               <NextError statusCode={404} /> |               <NextError statusCode={404} /> | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user