parent
							
								
									d6bb27f97c
								
							
						
					
					
						commit
						c115b5cca7
					
				|  | @ -20,11 +20,11 @@ import { AnswerBetPanel } from 'web/components/answers/answer-bet-panel' | ||||||
| import { Row } from 'web/components/layout/row' | import { Row } from 'web/components/layout/row' | ||||||
| import { Avatar } from 'web/components/avatar' | import { Avatar } from 'web/components/avatar' | ||||||
| import { Linkify } from 'web/components/linkify' | import { Linkify } from 'web/components/linkify' | ||||||
| import { BuyButton } from 'web/components/yes-no-selector' |  | ||||||
| import { UserLink } from 'web/components/user-link' |  | ||||||
| import { Button } from 'web/components/button' | import { Button } from 'web/components/button' | ||||||
| import { useAdmin } from 'web/hooks/use-admin' | import { useAdmin } from 'web/hooks/use-admin' | ||||||
| import { needsAdminToResolve } from 'web/pages/[username]/[contractSlug]' | import { needsAdminToResolve } from 'web/pages/[username]/[contractSlug]' | ||||||
|  | import { CATEGORY_COLORS } from '../charts/contract/choice' | ||||||
|  | import { useChartAnswers } from '../charts/contract/choice' | ||||||
| 
 | 
 | ||||||
| export function AnswersPanel(props: { | export function AnswersPanel(props: { | ||||||
|   contract: FreeResponseContract | MultipleChoiceContract |   contract: FreeResponseContract | MultipleChoiceContract | ||||||
|  | @ -38,6 +38,7 @@ export function AnswersPanel(props: { | ||||||
|   const answers = (useAnswers(contract.id) ?? contract.answers).filter( |   const answers = (useAnswers(contract.id) ?? contract.answers).filter( | ||||||
|     (a) => a.number != 0 || contract.outcomeType === 'MULTIPLE_CHOICE' |     (a) => a.number != 0 || contract.outcomeType === 'MULTIPLE_CHOICE' | ||||||
|   ) |   ) | ||||||
|  | 
 | ||||||
|   const hasZeroBetAnswers = answers.some((answer) => totalBets[answer.id] < 1) |   const hasZeroBetAnswers = answers.some((answer) => totalBets[answer.id] < 1) | ||||||
| 
 | 
 | ||||||
|   const [winningAnswers, losingAnswers] = partition( |   const [winningAnswers, losingAnswers] = partition( | ||||||
|  | @ -104,6 +105,10 @@ export function AnswersPanel(props: { | ||||||
|     ? 'checkbox' |     ? 'checkbox' | ||||||
|     : undefined |     : undefined | ||||||
| 
 | 
 | ||||||
|  |   const colorSortedAnswer = useChartAnswers(contract).map( | ||||||
|  |     (value, _index) => value.text | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Col className="gap-3"> |     <Col className="gap-3"> | ||||||
|       {(resolveOption || resolution) && |       {(resolveOption || resolution) && | ||||||
|  | @ -128,7 +133,12 @@ export function AnswersPanel(props: { | ||||||
|           )} |           )} | ||||||
|         > |         > | ||||||
|           {answerItems.map((item) => ( |           {answerItems.map((item) => ( | ||||||
|             <OpenAnswer key={item.id} answer={item} contract={contract} /> |             <OpenAnswer | ||||||
|  |               key={item.id} | ||||||
|  |               answer={item} | ||||||
|  |               contract={contract} | ||||||
|  |               colorIndex={colorSortedAnswer.indexOf(item.text)} | ||||||
|  |             /> | ||||||
|           ))} |           ))} | ||||||
|           {hasZeroBetAnswers && !showAllAnswers && ( |           {hasZeroBetAnswers && !showAllAnswers && ( | ||||||
|             <Button |             <Button | ||||||
|  | @ -174,15 +184,18 @@ export function AnswersPanel(props: { | ||||||
| function OpenAnswer(props: { | function OpenAnswer(props: { | ||||||
|   contract: FreeResponseContract | MultipleChoiceContract |   contract: FreeResponseContract | MultipleChoiceContract | ||||||
|   answer: Answer |   answer: Answer | ||||||
|  |   colorIndex: number | undefined | ||||||
| }) { | }) { | ||||||
|   const { answer, contract } = props |   const { answer, contract, colorIndex } = props | ||||||
|   const { username, avatarUrl, name, text } = answer |   const { username, avatarUrl, text } = answer | ||||||
|   const prob = getDpmOutcomeProbability(contract.totalShares, answer.id) |   const prob = getDpmOutcomeProbability(contract.totalShares, answer.id) | ||||||
|   const probPercent = formatPercent(prob) |   const probPercent = formatPercent(prob) | ||||||
|   const [open, setOpen] = useState(false) |   const [open, setOpen] = useState(false) | ||||||
|  |   const color = | ||||||
|  |     colorIndex != undefined ? CATEGORY_COLORS[colorIndex] : '#B1B1C7' | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Col className="border-base-200 bg-base-200 relative flex-1 rounded-md px-2"> |     <Col className="my-1 px-2"> | ||||||
|       <Modal open={open} setOpen={setOpen} position="center"> |       <Modal open={open} setOpen={setOpen} position="center"> | ||||||
|         <AnswerBetPanel |         <AnswerBetPanel | ||||||
|           answer={answer} |           answer={answer} | ||||||
|  | @ -193,40 +206,44 @@ function OpenAnswer(props: { | ||||||
|         /> |         /> | ||||||
|       </Modal> |       </Modal> | ||||||
| 
 | 
 | ||||||
|       <div |       <Col | ||||||
|         className="pointer-events-none absolute -mx-2 h-full rounded-tl-md bg-green-600 bg-opacity-10" |  | ||||||
|         style={{ width: `${100 * Math.max(prob, 0.01)}%` }} |  | ||||||
|       /> |  | ||||||
| 
 |  | ||||||
|       <Row className="my-4 gap-3"> |  | ||||||
|         <Avatar className="mx-1" username={username} avatarUrl={avatarUrl} /> |  | ||||||
|         <Col className="min-w-0 flex-1 lg:gap-1"> |  | ||||||
|           <div className="text-sm text-gray-500"> |  | ||||||
|             <UserLink username={username} name={name} /> answered |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <Col className="align-items justify-between gap-4 sm:flex-row"> |  | ||||||
|             <Linkify className="whitespace-pre-line text-lg" text={text} /> |  | ||||||
|             <Row className="align-items items-center justify-end gap-4"> |  | ||||||
|               <span |  | ||||||
|         className={clsx( |         className={clsx( | ||||||
|                   'text-2xl', |           'bg-greyscale-1 relative w-full rounded-lg transition-all', | ||||||
|                   tradingAllowed(contract) ? 'text-primary' : 'text-gray-500' |           tradingAllowed(contract) ? 'text-greyscale-7' : 'text-greyscale-5' | ||||||
|         )} |         )} | ||||||
|       > |       > | ||||||
|                 {probPercent} |         <Row className="z-20 -mb-1 justify-between gap-2 py-2 px-3"> | ||||||
|               </span> |           <Row> | ||||||
|               <BuyButton |             <Avatar | ||||||
|                 className={clsx( |               className="mt-0.5 mr-2 inline h-5 w-5 border border-transparent transition-transform hover:border-none" | ||||||
|                   'btn-sm flex-initial !px-6 sm:flex', |               username={username} | ||||||
|                   tradingAllowed(contract) ? '' : '!hidden' |               avatarUrl={avatarUrl} | ||||||
|                 )} |             /> | ||||||
|                 onClick={() => setOpen(true)} |             <Linkify | ||||||
|  |               className="text-md cursor-pointer whitespace-pre-line" | ||||||
|  |               text={text} | ||||||
|             /> |             /> | ||||||
|           </Row> |           </Row> | ||||||
|           </Col> |           <Row className="gap-2"> | ||||||
|         </Col> |             <div className="my-auto text-xl">{probPercent}</div> | ||||||
|  |             {tradingAllowed(contract) && ( | ||||||
|  |               <Button | ||||||
|  |                 size="2xs" | ||||||
|  |                 color="gray-outline" | ||||||
|  |                 onClick={() => setOpen(true)} | ||||||
|  |                 className="my-auto" | ||||||
|  |               > | ||||||
|  |                 BUY | ||||||
|  |               </Button> | ||||||
|  |             )} | ||||||
|           </Row> |           </Row> | ||||||
|  |         </Row> | ||||||
|  |         <hr | ||||||
|  |           color={color} | ||||||
|  |           className="absolute z-0 h-full w-full rounded-l-lg border-none opacity-30" | ||||||
|  |           style={{ width: `${100 * Math.max(prob, 0.01)}%` }} | ||||||
|  |         /> | ||||||
|  |       </Col> | ||||||
|     </Col> |     </Col> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ export type ColorType = | ||||||
|   | 'indigo' |   | 'indigo' | ||||||
|   | 'yellow' |   | 'yellow' | ||||||
|   | 'gray' |   | 'gray' | ||||||
|  |   | 'gray-outline' | ||||||
|   | 'gradient' |   | 'gradient' | ||||||
|   | 'gray-white' |   | 'gray-white' | ||||||
|   | 'highlight-blue' |   | 'highlight-blue' | ||||||
|  | @ -63,6 +64,8 @@ export function Button(props: { | ||||||
|           'disabled:bg-greyscale-2 bg-indigo-500 text-white hover:bg-indigo-600', |           'disabled:bg-greyscale-2 bg-indigo-500 text-white hover:bg-indigo-600', | ||||||
|         color === 'gray' && |         color === 'gray' && | ||||||
|           'bg-greyscale-1 text-greyscale-6 hover:bg-greyscale-2 disabled:opacity-50', |           'bg-greyscale-1 text-greyscale-6 hover:bg-greyscale-2 disabled:opacity-50', | ||||||
|  |         color === 'gray-outline' && | ||||||
|  |           'border-greyscale-4 text-greyscale-4 hover:bg-greyscale-4 border-2 hover:text-white disabled:opacity-50', | ||||||
|         color === 'gradient' && |         color === 'gradient' && | ||||||
|           'disabled:bg-greyscale-2 border-none bg-gradient-to-r from-indigo-500 to-blue-500 text-white hover:from-indigo-700 hover:to-blue-700', |           'disabled:bg-greyscale-2 border-none bg-gradient-to-r from-indigo-500 to-blue-500 text-white hover:from-indigo-700 hover:to-blue-700', | ||||||
|         color === 'gray-white' && |         color === 'gray-white' && | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ import { Row } from 'web/components/layout/row' | ||||||
| import { Avatar } from 'web/components/avatar' | import { Avatar } from 'web/components/avatar' | ||||||
| 
 | 
 | ||||||
| // thanks to https://observablehq.com/@jonhelfman/optimal-orders-for-choosing-categorical-colors
 | // thanks to https://observablehq.com/@jonhelfman/optimal-orders-for-choosing-categorical-colors
 | ||||||
| const CATEGORY_COLORS = [ | export const CATEGORY_COLORS = [ | ||||||
|   '#00b8dd', |   '#00b8dd', | ||||||
|   '#eecafe', |   '#eecafe', | ||||||
|   '#874c62', |   '#874c62', | ||||||
|  | @ -144,6 +144,15 @@ const Legend = (props: { className?: string; items: LegendItem[] }) => { | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function useChartAnswers( | ||||||
|  |   contract: FreeResponseContract | MultipleChoiceContract | ||||||
|  | ) { | ||||||
|  |   return useMemo( | ||||||
|  |     () => getTrackedAnswers(contract, CATEGORY_COLORS.length), | ||||||
|  |     [contract] | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export const ChoiceContractChart = (props: { | export const ChoiceContractChart = (props: { | ||||||
|   contract: FreeResponseContract | MultipleChoiceContract |   contract: FreeResponseContract | MultipleChoiceContract | ||||||
|   bets: Bet[] |   bets: Bet[] | ||||||
|  | @ -153,10 +162,7 @@ export const ChoiceContractChart = (props: { | ||||||
| }) => { | }) => { | ||||||
|   const { contract, bets, width, height, onMouseOver } = props |   const { contract, bets, width, height, onMouseOver } = props | ||||||
|   const [start, end] = getDateRange(contract) |   const [start, end] = getDateRange(contract) | ||||||
|   const answers = useMemo( |   const answers = useChartAnswers(contract) | ||||||
|     () => getTrackedAnswers(contract, CATEGORY_COLORS.length), |  | ||||||
|     [contract] |  | ||||||
|   ) |  | ||||||
|   const betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets]) |   const betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets]) | ||||||
|   const data = useMemo( |   const data = useMemo( | ||||||
|     () => [ |     () => [ | ||||||
|  |  | ||||||
|  | @ -244,7 +244,7 @@ function Button(props: { | ||||||
|       type="button" |       type="button" | ||||||
|       className={clsx( |       className={clsx( | ||||||
|         'inline-flex flex-1 items-center justify-center rounded-md border border-transparent px-8 py-3 font-medium shadow-sm', |         'inline-flex flex-1 items-center justify-center rounded-md border border-transparent px-8 py-3 font-medium shadow-sm', | ||||||
|         color === 'green' && 'bg-teal-500 bg-teal-600 text-white', |         color === 'green' && 'bg-teal-500 text-white hover:bg-teal-600', | ||||||
|         color === 'red' && 'bg-red-400 text-white hover:bg-red-500', |         color === 'red' && 'bg-red-400 text-white hover:bg-red-500', | ||||||
|         color === 'yellow' && 'bg-yellow-400 text-white hover:bg-yellow-500', |         color === 'yellow' && 'bg-yellow-400 text-white hover:bg-yellow-500', | ||||||
|         color === 'blue' && 'bg-blue-400 text-white hover:bg-blue-500', |         color === 'blue' && 'bg-blue-400 text-white hover:bg-blue-500', | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ module.exports = { | ||||||
|       colors: { |       colors: { | ||||||
|         'red-25': '#FDF7F6', |         'red-25': '#FDF7F6', | ||||||
|         'greyscale-1': '#FBFBFF', |         'greyscale-1': '#FBFBFF', | ||||||
|  |         'greyscale-1.5': '#F4F4FB', | ||||||
|         'greyscale-2': '#E7E7F4', |         'greyscale-2': '#E7E7F4', | ||||||
|         'greyscale-3': '#D8D8EB', |         'greyscale-3': '#D8D8EB', | ||||||
|         'greyscale-4': '#B1B1C7', |         'greyscale-4': '#B1B1C7', | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user