Revert "Merge branch 'main' of https://github.com/manifoldmarkets/manifold"
This reverts commit603201a00f
, reversing changes made tob517817ee3
.
This commit is contained in:
parent
603201a00f
commit
3fb43c16c4
139
web/components/analytics/charts.tsx
Normal file
139
web/components/analytics/charts.tsx
Normal file
|
@ -0,0 +1,139 @@
|
|||
import { Point, ResponsiveLine } from '@nivo/line'
|
||||
import clsx from 'clsx'
|
||||
import { formatPercent } from 'common/util/format'
|
||||
import dayjs from 'dayjs'
|
||||
import { zip } from 'lodash'
|
||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||
import { Col } from '../layout/col'
|
||||
|
||||
export function DailyCountChart(props: {
|
||||
startDate: number
|
||||
dailyCounts: number[]
|
||||
small?: boolean
|
||||
}) {
|
||||
const { dailyCounts, startDate, small } = props
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const dates = dailyCounts.map((_, i) =>
|
||||
dayjs(startDate).add(i, 'day').toDate()
|
||||
)
|
||||
|
||||
const points = zip(dates, dailyCounts).map(([date, betCount]) => ({
|
||||
x: date,
|
||||
y: betCount,
|
||||
}))
|
||||
const data = [{ id: 'Count', data: points, color: '#11b981' }]
|
||||
|
||||
const bottomAxisTicks = width && width < 600 ? 6 : undefined
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'h-[250px] w-full overflow-hidden',
|
||||
!small && 'md:h-[400px]'
|
||||
)}
|
||||
>
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
yScale={{ type: 'linear', stacked: false }}
|
||||
xScale={{
|
||||
type: 'time',
|
||||
}}
|
||||
axisBottom={{
|
||||
tickValues: bottomAxisTicks,
|
||||
format: (date) => dayjs(date).format('MMM DD'),
|
||||
}}
|
||||
colors={{ datum: 'color' }}
|
||||
pointSize={0}
|
||||
pointBorderWidth={1}
|
||||
pointBorderColor="#fff"
|
||||
enableSlices="x"
|
||||
enableGridX={!!width && width >= 800}
|
||||
enableArea
|
||||
margin={{ top: 20, right: 28, bottom: 22, left: 40 }}
|
||||
sliceTooltip={({ slice }) => {
|
||||
const point = slice.points[0]
|
||||
return <Tooltip point={point} />
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DailyPercentChart(props: {
|
||||
startDate: number
|
||||
dailyPercent: number[]
|
||||
small?: boolean
|
||||
excludeFirstDays?: number
|
||||
}) {
|
||||
const { dailyPercent, startDate, small, excludeFirstDays } = props
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const dates = dailyPercent.map((_, i) =>
|
||||
dayjs(startDate).add(i, 'day').toDate()
|
||||
)
|
||||
|
||||
const points = zip(dates, dailyPercent)
|
||||
.map(([date, percent]) => ({
|
||||
x: date,
|
||||
y: percent,
|
||||
}))
|
||||
.slice(excludeFirstDays ?? 0)
|
||||
const data = [{ id: 'Percent', data: points, color: '#11b981' }]
|
||||
|
||||
const bottomAxisTicks = width && width < 600 ? 6 : undefined
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'h-[250px] w-full overflow-hidden',
|
||||
!small && 'md:h-[400px]'
|
||||
)}
|
||||
>
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
yScale={{ type: 'linear', stacked: false }}
|
||||
xScale={{
|
||||
type: 'time',
|
||||
}}
|
||||
axisLeft={{
|
||||
format: formatPercent,
|
||||
}}
|
||||
axisBottom={{
|
||||
tickValues: bottomAxisTicks,
|
||||
format: (date) => dayjs(date).format('MMM DD'),
|
||||
}}
|
||||
colors={{ datum: 'color' }}
|
||||
pointSize={0}
|
||||
pointBorderWidth={1}
|
||||
pointBorderColor="#fff"
|
||||
enableSlices="x"
|
||||
enableGridX={!!width && width >= 800}
|
||||
enableArea
|
||||
margin={{ top: 20, right: 28, bottom: 22, left: 40 }}
|
||||
sliceTooltip={({ slice }) => {
|
||||
const point = slice.points[0]
|
||||
return <Tooltip point={point} isPercent />
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Tooltip(props: { point: Point; isPercent?: boolean }) {
|
||||
const { point, isPercent } = props
|
||||
return (
|
||||
<Col className="border border-gray-300 bg-white py-2 px-3">
|
||||
<div
|
||||
className="pb-1"
|
||||
style={{
|
||||
color: point.serieColor,
|
||||
}}
|
||||
>
|
||||
<strong>{point.serieId}</strong>{' '}
|
||||
{isPercent ? formatPercent(+point.data.y) : Math.round(+point.data.y)}
|
||||
</div>
|
||||
<div>{dayjs(point.data.x).format('MMM DD')}</div>
|
||||
</Col>
|
||||
)
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { useMemo } from 'react'
|
||||
import { last, sortBy } from 'lodash'
|
||||
import { scaleTime, scaleLinear } from 'd3-scale'
|
||||
import { curveStepAfter } from 'd3-shape'
|
||||
|
||||
import { Bet } from 'common/bet'
|
||||
import { getProbability, getInitialProbability } from 'common/calculate'
|
||||
|
@ -77,7 +76,6 @@ export const BinaryContractChart = (props: {
|
|||
yScale={yScale}
|
||||
data={data}
|
||||
color="#11b981"
|
||||
curve={curveStepAfter}
|
||||
onMouseOver={onMouseOver}
|
||||
Tooltip={BinaryChartTooltip}
|
||||
pct
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useMemo } from 'react'
|
||||
import { last, sum, sortBy, groupBy } from 'lodash'
|
||||
import { scaleTime, scaleLinear } from 'd3-scale'
|
||||
import { curveStepAfter } from 'd3-shape'
|
||||
|
||||
import { Bet } from 'common/bet'
|
||||
import { Answer } from 'common/answer'
|
||||
|
@ -215,7 +214,6 @@ export const ChoiceContractChart = (props: {
|
|||
yScale={yScale}
|
||||
data={data}
|
||||
colors={CATEGORY_COLORS}
|
||||
curve={curveStepAfter}
|
||||
onMouseOver={onMouseOver}
|
||||
Tooltip={ChoiceTooltip}
|
||||
pct
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useMemo } from 'react'
|
||||
import { last, sortBy } from 'lodash'
|
||||
import { scaleTime, scaleLog, scaleLinear } from 'd3-scale'
|
||||
import { curveStepAfter } from 'd3-shape'
|
||||
|
||||
import { Bet } from 'common/bet'
|
||||
import { DAY_MS } from 'common/util/time'
|
||||
|
@ -86,11 +85,11 @@ export const PseudoNumericContractChart = (props: {
|
|||
Date.now()
|
||||
)
|
||||
const visibleRange = [start, rightmostDate]
|
||||
const xScale = scaleTime(visibleRange, [0, width - MARGIN_X])
|
||||
const xScale = scaleTime(visibleRange, [0, width ?? 0 - MARGIN_X])
|
||||
// clamp log scale to make sure zeroes go to the bottom
|
||||
const yScale = isLogScale
|
||||
? scaleLog([Math.max(min, 1), max], [height - MARGIN_Y, 0]).clamp(true)
|
||||
: scaleLinear([min, max], [height - MARGIN_Y, 0])
|
||||
? scaleLog([Math.max(min, 1), max], [height ?? 0 - MARGIN_Y, 0]).clamp(true)
|
||||
: scaleLinear([min, max], [height ?? 0 - MARGIN_Y, 0])
|
||||
return (
|
||||
<SingleValueHistoryChart
|
||||
w={width}
|
||||
|
@ -98,7 +97,6 @@ export const PseudoNumericContractChart = (props: {
|
|||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
data={data}
|
||||
curve={curveStepAfter}
|
||||
onMouseOver={onMouseOver}
|
||||
Tooltip={PseudoNumericChartTooltip}
|
||||
color={NUMERIC_GRAPH_COLOR}
|
||||
|
|
|
@ -4,11 +4,11 @@ import { axisBottom, axisLeft } from 'd3-axis'
|
|||
import { D3BrushEvent } from 'd3-brush'
|
||||
import { ScaleTime, ScaleContinuousNumeric } from 'd3-scale'
|
||||
import {
|
||||
CurveFactory,
|
||||
SeriesPoint,
|
||||
curveLinear,
|
||||
curveStepAfter,
|
||||
stack,
|
||||
stackOrderReverse,
|
||||
SeriesPoint,
|
||||
} from 'd3-shape'
|
||||
import { range } from 'lodash'
|
||||
|
||||
|
@ -52,11 +52,10 @@ export const DistributionChart = <P extends DistributionPoint>(props: {
|
|||
color: string
|
||||
xScale: ScaleContinuousNumeric<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
curve?: CurveFactory
|
||||
onMouseOver?: (p: P | undefined) => void
|
||||
Tooltip?: TooltipComponent<number, P>
|
||||
}) => {
|
||||
const { color, data, yScale, w, h, curve, Tooltip } = props
|
||||
const { color, data, yScale, w, h, Tooltip } = props
|
||||
|
||||
const [viewXScale, setViewXScale] =
|
||||
useState<ScaleContinuousNumeric<number, number>>()
|
||||
|
@ -101,7 +100,7 @@ export const DistributionChart = <P extends DistributionPoint>(props: {
|
|||
px={px}
|
||||
py0={py0}
|
||||
py1={py1}
|
||||
curve={curve ?? curveLinear}
|
||||
curve={curveLinear}
|
||||
/>
|
||||
</SVGChart>
|
||||
)
|
||||
|
@ -114,12 +113,11 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
|||
colors: readonly string[]
|
||||
xScale: ScaleTime<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
curve?: CurveFactory
|
||||
onMouseOver?: (p: P | undefined) => void
|
||||
Tooltip?: TooltipComponent<Date, P>
|
||||
pct?: boolean
|
||||
}) => {
|
||||
const { colors, data, yScale, w, h, curve, Tooltip, pct } = props
|
||||
const { colors, data, yScale, w, h, Tooltip, pct } = props
|
||||
|
||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||
const xScale = viewXScale ?? props.xScale
|
||||
|
@ -179,7 +177,7 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
|||
px={px}
|
||||
py0={py0}
|
||||
py1={py1}
|
||||
curve={curve ?? curveLinear}
|
||||
curve={curveStepAfter}
|
||||
fill={colors[i]}
|
||||
/>
|
||||
))}
|
||||
|
@ -194,12 +192,11 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
|||
color: string
|
||||
xScale: ScaleTime<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
curve?: CurveFactory
|
||||
onMouseOver?: (p: P | undefined) => void
|
||||
Tooltip?: TooltipComponent<Date, P>
|
||||
pct?: boolean
|
||||
}) => {
|
||||
const { color, data, yScale, w, h, curve, Tooltip, pct } = props
|
||||
const { color, data, yScale, w, h, Tooltip, pct } = props
|
||||
|
||||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||
const xScale = viewXScale ?? props.xScale
|
||||
|
@ -249,7 +246,7 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
|||
px={px}
|
||||
py0={py0}
|
||||
py1={py1}
|
||||
curve={curve ?? curveLinear}
|
||||
curve={curveStepAfter}
|
||||
/>
|
||||
</SVGChart>
|
||||
)
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
import { pointer, select } from 'd3-selection'
|
||||
import { Axis, AxisScale } from 'd3-axis'
|
||||
import { brushX, D3BrushEvent } from 'd3-brush'
|
||||
import { area, line, CurveFactory } from 'd3-shape'
|
||||
import { area, line, curveStepAfter, CurveFactory } from 'd3-shape'
|
||||
import { nanoid } from 'nanoid'
|
||||
import dayjs from 'dayjs'
|
||||
import clsx from 'clsx'
|
||||
|
@ -73,11 +73,11 @@ const LinePathInternal = <P,>(
|
|||
data: P[]
|
||||
px: number | ((p: P) => number)
|
||||
py: number | ((p: P) => number)
|
||||
curve: CurveFactory
|
||||
curve?: CurveFactory
|
||||
} & SVGProps<SVGPathElement>
|
||||
) => {
|
||||
const { data, px, py, curve, ...rest } = props
|
||||
const d3Line = line<P>(px, py).curve(curve)
|
||||
const d3Line = line<P>(px, py).curve(curve ?? curveStepAfter)
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return <path {...rest} fill="none" d={d3Line(data)!} />
|
||||
}
|
||||
|
@ -89,11 +89,11 @@ const AreaPathInternal = <P,>(
|
|||
px: number | ((p: P) => number)
|
||||
py0: number | ((p: P) => number)
|
||||
py1: number | ((p: P) => number)
|
||||
curve: CurveFactory
|
||||
curve?: CurveFactory
|
||||
} & SVGProps<SVGPathElement>
|
||||
) => {
|
||||
const { data, px, py0, py1, curve, ...rest } = props
|
||||
const d3Area = area<P>(px, py0, py1).curve(curve)
|
||||
const d3Area = area<P>(px, py0, py1).curve(curve ?? curveStepAfter)
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return <path {...rest} d={d3Area(data)!} />
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ export const AreaWithTopStroke = <P,>(props: {
|
|||
px: number | ((p: P) => number)
|
||||
py0: number | ((p: P) => number)
|
||||
py1: number | ((p: P) => number)
|
||||
curve: CurveFactory
|
||||
curve?: CurveFactory
|
||||
}) => {
|
||||
const { color, data, px, py0, py1, curve } = props
|
||||
return (
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
import { useMemo } from 'react'
|
||||
import { scaleTime, scaleLinear } from 'd3-scale'
|
||||
import { min, max } from 'lodash'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { formatPercent } from 'common/util/format'
|
||||
import { Row } from '../layout/row'
|
||||
import { HistoryPoint, SingleValueHistoryChart } from './generic-charts'
|
||||
import { TooltipProps, MARGIN_X, MARGIN_Y } from './helpers'
|
||||
import { SizedContainer } from 'web/components/sized-container'
|
||||
|
||||
const getPoints = (startDate: number, dailyValues: number[]) => {
|
||||
const startDateDayJs = dayjs(startDate)
|
||||
return dailyValues.map((y, i) => ({
|
||||
x: startDateDayJs.add(i, 'day').toDate(),
|
||||
y: y,
|
||||
}))
|
||||
}
|
||||
|
||||
const DailyCountTooltip = (props: TooltipProps<Date, HistoryPoint>) => {
|
||||
const { data, mouseX, xScale } = props
|
||||
const d = xScale.invert(mouseX)
|
||||
return (
|
||||
<Row className="items-center gap-2">
|
||||
<span className="font-semibold">{dayjs(d).format('MMM DD')}</span>
|
||||
<span className="text-greyscale-6">{data.y}</span>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
const DailyPercentTooltip = (props: TooltipProps<Date, HistoryPoint>) => {
|
||||
const { data, mouseX, xScale } = props
|
||||
const d = xScale.invert(mouseX)
|
||||
return (
|
||||
<Row className="items-center gap-2">
|
||||
<span className="font-semibold">{dayjs(d).format('MMM DD')}</span>
|
||||
<span className="text-greyscale-6">{formatPercent(data.y)}</span>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export function DailyChart(props: {
|
||||
startDate: number
|
||||
dailyValues: number[]
|
||||
excludeFirstDays?: number
|
||||
pct?: boolean
|
||||
}) {
|
||||
const { dailyValues, startDate, excludeFirstDays, pct } = props
|
||||
|
||||
const data = useMemo(
|
||||
() => getPoints(startDate, dailyValues).slice(excludeFirstDays ?? 0),
|
||||
[startDate, dailyValues, excludeFirstDays]
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const minDate = min(data.map((d) => d.x))!
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const maxDate = max(data.map((d) => d.x))!
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const maxValue = max(data.map((d) => d.y))!
|
||||
return (
|
||||
<SizedContainer fullHeight={250} mobileHeight={250}>
|
||||
{(width, height) => (
|
||||
<SingleValueHistoryChart
|
||||
w={width}
|
||||
h={height}
|
||||
xScale={scaleTime([minDate, maxDate], [0, width - MARGIN_X])}
|
||||
yScale={scaleLinear([0, maxValue], [height - MARGIN_Y, 0])}
|
||||
data={data}
|
||||
Tooltip={pct ? DailyPercentTooltip : DailyCountTooltip}
|
||||
color="#11b981"
|
||||
pct={pct}
|
||||
/>
|
||||
)}
|
||||
</SizedContainer>
|
||||
)
|
||||
}
|
|
@ -393,9 +393,7 @@ export function ContractCardProbChange(props: {
|
|||
noLinkAvatar?: boolean
|
||||
className?: string
|
||||
}) {
|
||||
const { noLinkAvatar, className } = props
|
||||
const contract = useContractWithPreload(props.contract) as CPMMBinaryContract
|
||||
|
||||
const { contract, noLinkAvatar, className } = props
|
||||
return (
|
||||
<Col
|
||||
className={clsx(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import React, { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
||||
import { Col } from '../layout/col'
|
||||
import { ContractChart } from 'web/components/charts/contract'
|
||||
|
@ -22,7 +24,6 @@ import {
|
|||
BinaryContract,
|
||||
} from 'common/contract'
|
||||
import { ContractDetails } from './contract-details'
|
||||
import { SizedContainer } from 'web/components/sized-container'
|
||||
|
||||
const OverviewQuestion = (props: { text: string }) => (
|
||||
<Linkify className="text-lg text-indigo-700 sm:text-2xl" text={props.text} />
|
||||
|
@ -48,18 +49,32 @@ const SizedContractChart = (props: {
|
|||
fullHeight: number
|
||||
mobileHeight: number
|
||||
}) => {
|
||||
const { fullHeight, mobileHeight, contract, bets } = props
|
||||
const { contract, bets, fullHeight, mobileHeight } = props
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [chartWidth, setChartWidth] = useState<number>()
|
||||
const [chartHeight, setChartHeight] = useState<number>()
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setChartHeight(window.innerWidth < 800 ? mobileHeight : fullHeight)
|
||||
setChartWidth(containerRef.current?.clientWidth)
|
||||
}
|
||||
handleResize()
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [fullHeight, mobileHeight])
|
||||
return (
|
||||
<SizedContainer fullHeight={fullHeight} mobileHeight={mobileHeight}>
|
||||
{(width, height) => (
|
||||
<div ref={containerRef}>
|
||||
{chartWidth != null && chartHeight != null && (
|
||||
<ContractChart
|
||||
width={width}
|
||||
height={height}
|
||||
contract={contract}
|
||||
bets={bets}
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
/>
|
||||
)}
|
||||
</SizedContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -99,11 +114,7 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => {
|
|||
<ContractDetails contract={contract} />
|
||||
<Row className="justify-between gap-4">
|
||||
<OverviewQuestion text={contract.question} />
|
||||
<BinaryResolutionOrChance
|
||||
className="flex items-end"
|
||||
contract={contract}
|
||||
large
|
||||
/>
|
||||
<BinaryResolutionOrChance contract={contract} large />
|
||||
</Row>
|
||||
</Col>
|
||||
<SizedContractChart
|
||||
|
|
|
@ -77,34 +77,13 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
|
|||
const comments = useComments(contract.id) ?? props.comments
|
||||
const [sort, setSort] = useState<'Newest' | 'Best'>('Newest')
|
||||
const me = useUser()
|
||||
|
||||
if (comments == null) {
|
||||
return <LoadingIndicator />
|
||||
}
|
||||
|
||||
const tipsOrBountiesAwarded =
|
||||
Object.keys(tips).length > 0 || comments.some((c) => c.bountiesAwarded)
|
||||
|
||||
const sortedComments = sortBy(comments, (c) =>
|
||||
sort === 'Newest'
|
||||
? c.createdTime
|
||||
: // Is this too magic? If there are tips/bounties, 'Best' shows your own comments made within the last 10 minutes first, then sorts by score
|
||||
tipsOrBountiesAwarded &&
|
||||
c.createdTime > Date.now() - 10 * MINUTE_MS &&
|
||||
c.userId === me?.id
|
||||
? -Infinity
|
||||
: -((c.bountiesAwarded ?? 0) + sum(Object.values(tips[c.id] ?? [])))
|
||||
)
|
||||
|
||||
const commentsByParent = groupBy(
|
||||
sortedComments,
|
||||
(c) => c.replyToCommentId ?? '_'
|
||||
)
|
||||
const topLevelComments = commentsByParent['_'] ?? []
|
||||
// Top level comments are reverse-chronological, while replies are chronological
|
||||
if (sort === 'Newest') topLevelComments.reverse()
|
||||
|
||||
if (contract.outcomeType === 'FREE_RESPONSE') {
|
||||
const generalComments = comments.filter(
|
||||
(c) => c.answerOutcome === undefined && c.betId === undefined
|
||||
)
|
||||
const sortedAnswers = sortBy(
|
||||
contract.answers,
|
||||
(a) => -getOutcomeProbability(contract, a.id)
|
||||
|
@ -113,9 +92,6 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
|
|||
comments,
|
||||
(c) => c.answerOutcome ?? c.betOutcome ?? '_'
|
||||
)
|
||||
const generalTopLevelComments = topLevelComments.filter(
|
||||
(c) => c.answerOutcome === undefined && c.betId === undefined
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{sortedAnswers.map((answer) => (
|
||||
|
@ -139,12 +115,12 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
|
|||
<div className="text-md mt-8 mb-2 text-left">General Comments</div>
|
||||
<div className="mb-4 w-full border-b border-gray-200" />
|
||||
<ContractCommentInput className="mb-5" contract={contract} />
|
||||
{generalTopLevelComments.map((comment) => (
|
||||
{generalComments.map((comment) => (
|
||||
<FeedCommentThread
|
||||
key={comment.id}
|
||||
contract={contract}
|
||||
parentComment={comment}
|
||||
threadComments={commentsByParent[comment.id] ?? []}
|
||||
threadComments={[]}
|
||||
tips={tips}
|
||||
/>
|
||||
))}
|
||||
|
@ -152,6 +128,24 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
|
|||
</>
|
||||
)
|
||||
} else {
|
||||
const tipsOrBountiesAwarded =
|
||||
Object.keys(tips).length > 0 || comments.some((c) => c.bountiesAwarded)
|
||||
|
||||
const commentsByParent = groupBy(
|
||||
sortBy(comments, (c) =>
|
||||
sort === 'Newest'
|
||||
? -c.createdTime
|
||||
: // Is this too magic? If there are tips/bounties, 'Best' shows your own comments made within the last 10 minutes first, then sorts by score
|
||||
tipsOrBountiesAwarded &&
|
||||
c.createdTime > Date.now() - 10 * MINUTE_MS &&
|
||||
c.userId === me?.id
|
||||
? -Infinity
|
||||
: -((c.bountiesAwarded ?? 0) + sum(Object.values(tips[c.id] ?? [])))
|
||||
),
|
||||
(c) => c.replyToCommentId ?? '_'
|
||||
)
|
||||
|
||||
const topLevelComments = commentsByParent['_'] ?? []
|
||||
return (
|
||||
<>
|
||||
<ContractCommentInput className="mb-5" contract={contract} />
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import { ReactNode, useEffect, useRef, useState } from 'react'
|
||||
|
||||
export const SizedContainer = (props: {
|
||||
fullHeight: number
|
||||
mobileHeight: number
|
||||
mobileThreshold?: number
|
||||
children: (width: number, height: number) => ReactNode
|
||||
}) => {
|
||||
const { children, fullHeight, mobileHeight } = props
|
||||
const threshold = props.mobileThreshold ?? 800
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [width, setWidth] = useState<number>()
|
||||
const [height, setHeight] = useState<number>()
|
||||
useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
const handleResize = () => {
|
||||
setHeight(window.innerWidth <= threshold ? mobileHeight : fullHeight)
|
||||
setWidth(containerRef.current?.clientWidth)
|
||||
}
|
||||
handleResize()
|
||||
const resizeObserver = new ResizeObserver(handleResize)
|
||||
resizeObserver.observe(containerRef.current)
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}
|
||||
}, [threshold, fullHeight, mobileHeight])
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
{width != null && height != null && children(width, height)}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -11,7 +11,7 @@ const App = () => {
|
|||
url="/cowp"
|
||||
/>
|
||||
<Link href="https://www.youtube.com/watch?v=FavUpD_IjVY">
|
||||
<img src="https://i.imgur.com/Lt54IiU.png" className="cursor-pointer" />
|
||||
<img src="https://i.imgur.com/Lt54IiU.png" />
|
||||
</Link>
|
||||
</Page>
|
||||
)
|
||||
|
|
|
@ -16,7 +16,7 @@ export default function LabsPage() {
|
|||
url="/labs"
|
||||
/>
|
||||
<Col className="px-4">
|
||||
<Title className="sm:!mt-0" text="🧪 Manifold Labs" />
|
||||
<Title className="sm:!mt-0" text="Manifold Labs" />
|
||||
|
||||
<Masonry
|
||||
breakpointCols={{ default: 2, 768: 1 }}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { DailyChart } from 'web/components/charts/stats'
|
||||
import {
|
||||
DailyCountChart,
|
||||
DailyPercentChart,
|
||||
} from 'web/components/analytics/charts'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { Tabs } from 'web/components/layout/tabs'
|
||||
|
@ -93,36 +96,40 @@ export function CustomAnalytics(props: Stats) {
|
|||
{
|
||||
title: 'Daily',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={dailyActiveUsers}
|
||||
<DailyCountChart
|
||||
dailyCounts={dailyActiveUsers}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Daily (7d avg)',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={dailyActiveUsersWeeklyAvg}
|
||||
<DailyCountChart
|
||||
dailyCounts={dailyActiveUsersWeeklyAvg}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Weekly',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={weeklyActiveUsers}
|
||||
<DailyCountChart
|
||||
dailyCounts={weeklyActiveUsers}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Monthly',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={monthlyActiveUsers}
|
||||
<DailyCountChart
|
||||
dailyCounts={monthlyActiveUsers}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -142,44 +149,44 @@ export function CustomAnalytics(props: Stats) {
|
|||
{
|
||||
title: 'D1',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={d1}
|
||||
<DailyPercentChart
|
||||
dailyPercent={d1}
|
||||
startDate={startDate}
|
||||
small
|
||||
excludeFirstDays={1}
|
||||
pct
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'D1 (7d avg)',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={d1WeeklyAvg}
|
||||
<DailyPercentChart
|
||||
dailyPercent={d1WeeklyAvg}
|
||||
startDate={startDate}
|
||||
small
|
||||
excludeFirstDays={7}
|
||||
pct
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'W1',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={weekOnWeekRetention}
|
||||
<DailyPercentChart
|
||||
dailyPercent={weekOnWeekRetention}
|
||||
startDate={startDate}
|
||||
small
|
||||
excludeFirstDays={14}
|
||||
pct
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'M1',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={monthlyRetention}
|
||||
<DailyPercentChart
|
||||
dailyPercent={monthlyRetention}
|
||||
startDate={startDate}
|
||||
small
|
||||
excludeFirstDays={60}
|
||||
pct
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -200,33 +207,33 @@ export function CustomAnalytics(props: Stats) {
|
|||
{
|
||||
title: 'ND1',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={nd1}
|
||||
<DailyPercentChart
|
||||
dailyPercent={nd1}
|
||||
startDate={startDate}
|
||||
excludeFirstDays={1}
|
||||
pct
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'ND1 (7d avg)',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={nd1WeeklyAvg}
|
||||
<DailyPercentChart
|
||||
dailyPercent={nd1WeeklyAvg}
|
||||
startDate={startDate}
|
||||
excludeFirstDays={7}
|
||||
pct
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'NW1',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={nw1}
|
||||
<DailyPercentChart
|
||||
dailyPercent={nw1}
|
||||
startDate={startDate}
|
||||
excludeFirstDays={14}
|
||||
pct
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -242,31 +249,41 @@ export function CustomAnalytics(props: Stats) {
|
|||
{
|
||||
title: capitalize(PAST_BETS),
|
||||
content: (
|
||||
<DailyChart dailyValues={dailyBetCounts} startDate={startDate} />
|
||||
<DailyCountChart
|
||||
dailyCounts={dailyBetCounts}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Markets created',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={dailyContractCounts}
|
||||
<DailyCountChart
|
||||
dailyCounts={dailyContractCounts}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Comments',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={dailyCommentCounts}
|
||||
<DailyCountChart
|
||||
dailyCounts={dailyCommentCounts}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Signups',
|
||||
content: (
|
||||
<DailyChart dailyValues={dailySignups} startDate={startDate} />
|
||||
<DailyCountChart
|
||||
dailyCounts={dailySignups}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
|
@ -287,22 +304,22 @@ export function CustomAnalytics(props: Stats) {
|
|||
{
|
||||
title: 'Daily',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={dailyActivationRate}
|
||||
<DailyPercentChart
|
||||
dailyPercent={dailyActivationRate}
|
||||
startDate={startDate}
|
||||
excludeFirstDays={1}
|
||||
pct
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Daily (7d avg)',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={dailyActivationRateWeeklyAvg}
|
||||
<DailyPercentChart
|
||||
dailyPercent={dailyActivationRateWeeklyAvg}
|
||||
startDate={startDate}
|
||||
excludeFirstDays={7}
|
||||
pct
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -318,33 +335,33 @@ export function CustomAnalytics(props: Stats) {
|
|||
{
|
||||
title: 'Daily / Weekly',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={dailyDividedByWeekly}
|
||||
<DailyPercentChart
|
||||
dailyPercent={dailyDividedByWeekly}
|
||||
startDate={startDate}
|
||||
small
|
||||
excludeFirstDays={7}
|
||||
pct
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Daily / Monthly',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={dailyDividedByMonthly}
|
||||
<DailyPercentChart
|
||||
dailyPercent={dailyDividedByMonthly}
|
||||
startDate={startDate}
|
||||
small
|
||||
excludeFirstDays={30}
|
||||
pct
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Weekly / Monthly',
|
||||
content: (
|
||||
<DailyChart
|
||||
dailyValues={weeklyDividedByMonthly}
|
||||
<DailyPercentChart
|
||||
dailyPercent={weeklyDividedByMonthly}
|
||||
startDate={startDate}
|
||||
small
|
||||
excludeFirstDays={30}
|
||||
pct
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -363,19 +380,31 @@ export function CustomAnalytics(props: Stats) {
|
|||
{
|
||||
title: 'Daily',
|
||||
content: (
|
||||
<DailyChart dailyValues={manaBet.daily} startDate={startDate} />
|
||||
<DailyCountChart
|
||||
dailyCounts={manaBet.daily}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Weekly',
|
||||
content: (
|
||||
<DailyChart dailyValues={manaBet.weekly} startDate={startDate} />
|
||||
<DailyCountChart
|
||||
dailyCounts={manaBet.weekly}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Monthly',
|
||||
content: (
|
||||
<DailyChart dailyValues={manaBet.monthly} startDate={startDate} />
|
||||
<DailyCountChart
|
||||
dailyCounts={manaBet.monthly}
|
||||
startDate={startDate}
|
||||
small
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
|
|
Loading…
Reference in New Issue
Block a user