From e26f8397c114e76dd2404a73ed1e17bad8cb9c5f Mon Sep 17 00:00:00 2001 From: Niranjan Ramadas Date: Sat, 18 Jun 2022 07:25:30 -0400 Subject: [PATCH] Add a proposal "exploration" page similar to the realms-explorer proposal page (#767) * Add a proposal exploration page * adjust styling --- components/ProposalRemainingVotingTime.tsx | 141 +++++ components/ProposalSignatories.tsx | 43 ++ components/ProposalTopVotersBubbleChart.tsx | 152 ++++++ components/ProposalTopVotersList.tsx | 251 +++++++++ components/ProposalVoteResults.tsx | 170 ++++++ hooks/useSignatories.ts | 48 ++ hooks/useVoteRecords.ts | 90 +++ models/accounts/getAccountsByFilter.ts | 49 ++ models/accounts/index.ts | 1 + models/proposal/buildTopVoters.ts | 107 ++++ models/proposal/calculateMintMaxVoteWeight.ts | 21 + models/proposal/calulateMaxVoteScore.ts | 27 + models/proposal/getSignatories.ts | 36 ++ models/proposal/getTokenOwnerRecords.ts | 39 ++ models/proposal/getVoteRecords.ts | 36 ++ models/proposal/index.ts | 6 + package.json | 5 + pages/dao/[symbol]/proposal/[pk]/explore.tsx | 102 ++++ .../proposal/{[pk].tsx => [pk]/index.tsx} | 23 +- utils/formatPercentage.ts | 11 + utils/ntext.ts | 7 + yarn.lock | 511 +++++++++++++++++- 22 files changed, 1874 insertions(+), 2 deletions(-) create mode 100644 components/ProposalRemainingVotingTime.tsx create mode 100644 components/ProposalSignatories.tsx create mode 100644 components/ProposalTopVotersBubbleChart.tsx create mode 100644 components/ProposalTopVotersList.tsx create mode 100644 components/ProposalVoteResults.tsx create mode 100644 hooks/useSignatories.ts create mode 100644 hooks/useVoteRecords.ts create mode 100644 models/accounts/getAccountsByFilter.ts create mode 100644 models/accounts/index.ts create mode 100644 models/proposal/buildTopVoters.ts create mode 100644 models/proposal/calculateMintMaxVoteWeight.ts create mode 100644 models/proposal/calulateMaxVoteScore.ts create mode 100644 models/proposal/getSignatories.ts create mode 100644 models/proposal/getTokenOwnerRecords.ts create mode 100644 models/proposal/getVoteRecords.ts create mode 100644 models/proposal/index.ts create mode 100644 pages/dao/[symbol]/proposal/[pk]/explore.tsx rename pages/dao/[symbol]/proposal/{[pk].tsx => [pk]/index.tsx} (86%) create mode 100644 utils/formatPercentage.ts create mode 100644 utils/ntext.ts diff --git a/components/ProposalRemainingVotingTime.tsx b/components/ProposalRemainingVotingTime.tsx new file mode 100644 index 0000000000..f409498492 --- /dev/null +++ b/components/ProposalRemainingVotingTime.tsx @@ -0,0 +1,141 @@ +import { Governance, ProgramAccount, Proposal } from '@solana/spl-governance' +import classNames from 'classnames' +import dayjs from 'dayjs' +import { useEffect, useState, useRef } from 'react' + +import { ntext } from '@utils/ntext' + +const diffTime = (ended: boolean, now: dayjs.Dayjs, end: dayjs.Dayjs) => { + if (ended) { + return { + days: 0, + hours: 0, + minutes: 0, + seconds: 0, + } + } + + const days = end.diff(now, 'day') + const withoutDays = end.subtract(days, 'day') + const hours = withoutDays.diff(now, 'hour') + const withoutHours = withoutDays.subtract(hours, 'hour') + const minutes = withoutHours.diff(now, 'minute') + const withoutMinutes = withoutHours.subtract(minutes, 'minute') + const seconds = withoutMinutes.diff(now, 'second') + + return { days, hours, minutes, seconds } +} + +const Cell = ({ + count, + hideLeadingZeros, + label, +}: { + count: number + hideLeadingZeros?: boolean + label: string +}) => ( +
+
+ {count < 10 && + (hideLeadingZeros ? 0 : '0')} + {count} +
+
{ntext(count, label)}
+
+) + +const Divider = () => ( +
+ : +
+) + +interface Props { + className?: string + align?: 'left' | 'right' + governance: ProgramAccount + proposal: ProgramAccount +} + +export default function ProposalRemainingVotingTime(props: Props) { + const voteTime = props.proposal.account.getTimeToVoteEnd( + props.governance.account + ) + const votingEnded = props.proposal.account.hasVoteTimeEnded( + props.governance.account + ) + + const [now, setNow] = useState(dayjs()) + const end = useRef(dayjs(1000 * (dayjs().unix() + voteTime))) + + useEffect(() => { + const interval = setInterval(() => { + setNow(dayjs()) + }, 1000) + + return () => clearInterval(interval) + }, []) + + const { days, hours, minutes, seconds } = diffTime( + votingEnded, + now, + end.current + ) + + return ( +
+

+ Voting Time Remaining +

+ {votingEnded ? ( +
+ Voting has ended +
+ ) : ( +
+ + + + + + + +
+ )} +
+ ) +} diff --git a/components/ProposalSignatories.tsx b/components/ProposalSignatories.tsx new file mode 100644 index 0000000000..5b94e23066 --- /dev/null +++ b/components/ProposalSignatories.tsx @@ -0,0 +1,43 @@ +import { + ProgramAccount, + Proposal, + SignatoryRecord, +} from '@solana/spl-governance' +import { ExternalLinkIcon } from '@heroicons/react/outline' + +import { getExplorerUrl } from '@components/explorer/tools' +import { abbreviateAddress } from '@utils/formatting' + +interface Props { + className?: string + endpoint: string + proposal: ProgramAccount + signatories: ProgramAccount[] +} + +export default function ProposalSignatories(props: Props) { + return ( +
+

+ Signatories - {props.proposal.account.signatoriesCount} /{' '} + {props.proposal.account.signatoriesSignedOffCount} +

+
+ {props.signatories + .filter((s) => s.account.signedOff) + .map((s) => ( + + {abbreviateAddress(s.pubkey)} + + + ))} +
+
+ ) +} diff --git a/components/ProposalTopVotersBubbleChart.tsx b/components/ProposalTopVotersBubbleChart.tsx new file mode 100644 index 0000000000..c8fe019b93 --- /dev/null +++ b/components/ProposalTopVotersBubbleChart.tsx @@ -0,0 +1,152 @@ +import { useEffect, useRef, useState } from 'react' +import AutoSizer from 'react-virtualized-auto-sizer' +import type { HierarchyCircularNode } from 'd3' + +import { VoterDisplayData, VoteType } from '@models/proposal' +import { abbreviateAddress } from '@utils/formatting' +import { getExplorerUrl } from '@components/explorer/tools' + +const voteTypeDomain = (type: VoteType) => { + switch (type) { + case VoteType.No: + return 'Nay' + case VoteType.Undecided: + return 'Undecided' + case VoteType.Yes: + return 'Yay' + } +} + +const loadD3 = () => import('d3') + +type Unpromise

= P extends Promise ? T : never +type D3 = Unpromise> + +interface Props { + className?: string + data: VoterDisplayData[] + endpoint: string + height: number + maxNumBubbles: number + highlighted?: string + width: number + onHighlight?(key?: string): void +} + +function Content(props: Props) { + const container = useRef(null) + const [d3, setD3] = useState(null) + + useEffect(() => { + loadD3().then(setD3) + }, []) + + useEffect(() => { + if (container.current && d3 && props.data.length) { + container.current.innerHTML = '' + + const color = d3 + .scaleOrdinal() + .domain([ + voteTypeDomain(VoteType.Undecided), + voteTypeDomain(VoteType.Yes), + voteTypeDomain(VoteType.No), + ]) + .range(['rgb(82, 82, 82)', 'rgb(101, 163, 13)', 'rgb(159, 18, 57)']) + + const hierarchy = d3 + .hierarchy({ children: props.data }) + .sum((d: any) => (d.votesCast ? d.votesCast.toNumber() : 0)) + .sort((a, b) => (b.value || 0) - (a.value || 0)) + + const pack = d3.pack().size([props.width, props.height]).padding(3) + + const root = pack(hierarchy) + + const parent = d3 + .select(container.current) + .append('svg') + .attr('viewBox', [0, 0, props.width, props.height]) + .attr('height', props.height) + .attr('width', props.width) + .attr('font-size', 10) + .attr('font-family', 'sans-serif') + .attr('text-anchor', 'middle') + + const data = root + .descendants() + .slice( + 1, + props.maxNumBubbles + ) as HierarchyCircularNode[] + + const group = parent + .selectAll('g') + .data(data) + .join('g') + .attr('transform', (d) => `translate(${d.x + 1},${d.y + 1})`) + .style('opacity', (d) => (d.data.key === props.highlighted ? 1 : 0.5)) + .style('cursor', 'pointer') + .on('mouseenter', function () { + const node = d3 + .select(this) + .datum() as HierarchyCircularNode + props.onHighlight?.(node.data.key) + }) + .on('mouseleave', () => { + props.onHighlight?.() + }) + .on('click', function () { + const node = d3 + .select(this) + .datum() as HierarchyCircularNode + + window.open(getExplorerUrl(props.endpoint, node.data.name), '_blank') + }) + + // draw circles + group + .append('circle') + .attr('r', (d) => d.r) + .attr('fill', (d) => color(voteTypeDomain(d.data.voteType)) as string) + + // add labels + group + .append('svg:text') + .attr('fill', 'white') + .style('pointer-events', 'none') + .style('opacity', (d) => (d.data.key === props.highlighted ? 1 : 0.2)) + .style('transform', (d) => + d.data.key === props.highlighted ? 'scale(1.5)' : 'scale(1)' + ) + .attr('y', '0.5em') + .text((d) => abbreviateAddress(d.data.name)) + } + }, [ + container, + props.data, + props.maxNumBubbles, + d3, + props.height, + props.width, + props.highlighted, + ]) + + return ( +

+ ) +} + +export default function ProposalTopVotersBubbleChart( + props: Omit +) { + return ( +
+ {(sizing) => } +
+ ) +} + +ProposalTopVotersBubbleChart.defaultProps = { + maxNumBubbles: 50, +} diff --git a/components/ProposalTopVotersList.tsx b/components/ProposalTopVotersList.tsx new file mode 100644 index 0000000000..e5d4a18144 --- /dev/null +++ b/components/ProposalTopVotersList.tsx @@ -0,0 +1,251 @@ +import { FixedSizeList } from 'react-window' +import AutoSizer from 'react-virtualized-auto-sizer' +import classNames from 'classnames' +import BN from 'bn.js' +import { BigNumber } from 'bignumber.js' +import { useEffect, useMemo, useRef, useState } from 'react' + +import { formatPercentage } from '@utils/formatPercentage' +import { VoterDisplayData, VoteType } from '@models/proposal' +import { abbreviateAddress } from '@utils/formatting' +import { getExplorerUrl } from '@components/explorer/tools' + +const ROW_STYLES = + 'absolute top-0 bottom-0 text-ellipsis overflow-hidden leading-[35px] px-2' + +const COL_1_STYLES = classNames(ROW_STYLES, 'left-0', 'w-[25%]') + +const COL_2_STYLES = classNames( + ROW_STYLES, + 'left-[30%]', + 'w-[15%]', + 'text-right' +) + +const COL_3_STYLES = classNames( + ROW_STYLES, + 'left-[45%]', + 'w-[30%]', + 'text-right' +) + +const COL_4_STYLES = classNames( + ROW_STYLES, + 'left-[75%]', + 'right-0', + 'text-right' +) + +const formatNumber = (value: BN, decimals: number) => { + const num = new BigNumber(value.toString()).shiftedBy(-decimals) + + if (typeof Intl === 'undefined' || typeof navigator === 'undefined') { + return num.toFormat() + } + + const formatter = new Intl.NumberFormat(navigator.language, { + minimumFractionDigits: decimals, + }) + return formatter.format(num.toNumber()) +} + +const voteTypeText = (type: VoteType) => { + switch (type) { + case VoteType.No: + return 'Nay' + case VoteType.Undecided: + return '' + case VoteType.Yes: + return 'Yay' + } +} + +const Filter = ({ + defaultChecked, + label, + onChange, +}: { + defaultChecked?: boolean + label: string + onChange?(value: boolean): void +}) => { + return ( + + ) +} + +const voteTypeBg = (type: VoteType) => { + switch (type) { + case VoteType.No: + return 'bg-rose-800' + case VoteType.Undecided: + return 'bg-neutral-600' + case VoteType.Yes: + return 'bg-lime-800' + } +} + +interface Props { + className?: string + data: VoterDisplayData[] + endpoint: string + highlighted?: string + onHighlight?(key?: string): void +} + +export default function ProposalTopVotersList(props: Props) { + const ref = useRef>(null) + const [showYays, setShowYays] = useState(true) + const [showNays, setShowNays] = useState(true) + const [showUndecideds, setShowUndecideds] = useState(true) + + const records = useMemo( + () => + props.data.filter((data) => { + if (data.voteType === VoteType.No && !showNays) { + return false + } + + if (data.voteType === VoteType.Undecided && !showUndecideds) { + return false + } + + if (data.voteType === VoteType.Yes && !showYays) { + return false + } + + return true + }), + [props.data, showYays, showNays, showUndecideds] + ) + + useEffect(() => { + if (ref.current && props.highlighted) { + const index = records.findIndex((data) => data.key === props.highlighted) + + if (index >= 0) { + ref.current.scrollToItem(index, 'smart') + } + } + }, [ref, props.highlighted, records]) + + return ( +
+
+
Account
+
Vote
+
Vote Weight
+
Percentage
+
+
props.onHighlight?.(undefined)} + > + + {({ height, width }) => ( + + {({ data, style, index }) => { + const rowData = data[index] + + return ( + props.onHighlight?.(rowData.key)} + > + +
+ Show: + + + +
+
+ ) +} diff --git a/components/ProposalVoteResults.tsx b/components/ProposalVoteResults.tsx new file mode 100644 index 0000000000..df2218b573 --- /dev/null +++ b/components/ProposalVoteResults.tsx @@ -0,0 +1,170 @@ +import { Governance, ProgramAccount, Proposal } from '@solana/spl-governance' +import classNames from 'classnames' + +import { VoterDisplayData, VoteType } from '@models/proposal' +import { formatPercentage } from '@utils/formatPercentage' + +interface Props { + className?: string + data: VoterDisplayData[] + governance: ProgramAccount + proposal: ProgramAccount +} + +export default function ProposalVoteResult(props: Props) { + const yesPercent = props.data.reduce((acc, cur) => { + if (cur.voteType === VoteType.Yes) { + return acc + cur.votePercentage + } + + return acc + }, 0) + + const noPercent = props.data.reduce((acc, cur) => { + if (cur.voteType === VoteType.No) { + return acc + cur.votePercentage + } + + return acc + }, 0) + + const threshold = + props.proposal.account.voteThresholdPercentage?.value || + props.governance.account.config.voteThresholdPercentage.value + + return ( +
+

Vote Result

+
+
+
+
+
+
+
+
+
Yay
+
+ {formatPercentage(yesPercent)} +
+
+
+
Nay
+
+ {formatPercentage(noPercent)} +
+
+ {threshold && ( + <> +
+
+
Threshold
+
+ {formatPercentage(threshold)} +
+
+ + )} +
+
+
+ ) +} diff --git a/hooks/useSignatories.ts b/hooks/useSignatories.ts new file mode 100644 index 0000000000..594db98f2f --- /dev/null +++ b/hooks/useSignatories.ts @@ -0,0 +1,48 @@ +import { useEffect, useState } from 'react' +import { pipe } from 'fp-ts/function' +import { matchW } from 'fp-ts/TaskEither' +import { + Proposal, + ProgramAccount, + RpcContext, + SignatoryRecord, +} from '@solana/spl-governance' + +import useRpcContext from '@hooks/useRpcContext' +import { getSignatories } from '@models/proposal' +import useRealm from '@hooks/useRealm' +import { fromOption } from 'fp-ts/Either' + +export default function useSignatories(proposal?: ProgramAccount) { + const { getRpcContext } = useRpcContext() + const [signatories, setSignatories] = useState< + ProgramAccount[] + >([]) + const [context, setContext] = useState(null) + const { realm } = useRealm() + + useEffect(() => { + if (realm) { + setContext(getRpcContext()) + } + }, [realm]) + + useEffect(() => { + if (context && proposal) { + pipe( + () => + getSignatories({ + connection: context.connection, + programId: context.programId, + proposalPk: proposal.pubkey, + }).then(fromOption(() => new Error('Could not fetch signatories'))), + matchW((reason) => { + console.log(reason) + setSignatories([]) + }, setSignatories) + )() + } + }, [context, proposal]) + + return signatories +} diff --git a/hooks/useVoteRecords.ts b/hooks/useVoteRecords.ts new file mode 100644 index 0000000000..bb6baa1aa8 --- /dev/null +++ b/hooks/useVoteRecords.ts @@ -0,0 +1,90 @@ +import { useEffect, useMemo, useState } from 'react' +import { pipe } from 'fp-ts/function' +import { matchW, fromTaskOption } from 'fp-ts/TaskEither' +import { + Proposal, + ProgramAccount, + VoteRecord, + TokenOwnerRecord, + RpcContext, +} from '@solana/spl-governance' + +import useRpcContext from '@hooks/useRpcContext' +import { getVoteRecords, getTokenOwnerRecords } from '@models/proposal' +import useRealm from '@hooks/useRealm' +import { buildTopVoters, VoteType } from '@models/proposal' + +export { VoteType } + +export default function useVoteRecords(proposal?: ProgramAccount) { + const { getRpcContext } = useRpcContext() + const [voteRecords, setVoteRecords] = useState[]>( + [] + ) + const [tokenOwnerRecords, setTokenOwnerRecords] = useState< + ProgramAccount[] + >([]) + const [context, setContext] = useState(null) + const { mint, realm } = useRealm() + + const governingTokenMintPk = proposal?.account.isVoteFinalized() + ? undefined + : proposal?.account.governingTokenMint + + useEffect(() => { + if (context && proposal && realm) { + // fetch vote records + pipe( + () => + getVoteRecords({ + connection: context.connection, + programId: context.programId, + proposalPk: proposal.pubkey, + }), + fromTaskOption(() => new Error('Could not fetch vote records')), + matchW((reason) => { + console.log(reason) + setVoteRecords([]) + }, setVoteRecords) + )() + + // fetch token records + pipe( + () => + getTokenOwnerRecords({ + governingTokenMint: governingTokenMintPk, + connection: context.connection, + programId: context.programId, + realm: realm.pubkey, + }), + fromTaskOption(() => new Error('Could not fetch token records')), + matchW((reason) => { + console.log(reason) + setTokenOwnerRecords([]) + }, setTokenOwnerRecords) + )() + } + }, [context, governingTokenMintPk, proposal, realm]) + + useEffect(() => { + if (realm) { + setContext(getRpcContext()) + } + }, [realm]) + + const topVoters = useMemo(() => { + if (realm && proposal && mint) { + return buildTopVoters( + voteRecords, + tokenOwnerRecords, + realm, + proposal, + mint + ) + } + + return [] + }, [voteRecords, tokenOwnerRecords, realm, proposal, mint]) + + return topVoters +} diff --git a/models/accounts/getAccountsByFilter.ts b/models/accounts/getAccountsByFilter.ts new file mode 100644 index 0000000000..f9b17580cd --- /dev/null +++ b/models/accounts/getAccountsByFilter.ts @@ -0,0 +1,49 @@ +import { + getGovernanceAccounts, + GovernanceAccount, + GovernanceAccountClass, + ProgramAccount, + MemcmpFilter, +} from '@solana/spl-governance' +import { Connection, PublicKey } from '@solana/web3.js' +import { pipe } from 'fp-ts/function' +import { tryCatch, map, match } from 'fp-ts/TaskEither' +import { some, none } from 'fp-ts/Option' + +interface Args { + accountClass: GovernanceAccountClass + connection: Connection + filters: MemcmpFilter[] + programId: PublicKey +} + +export function getAccountsByFilters({ + accountClass, + connection, + filters, + programId, +}: Args) { + return pipe( + tryCatch( + () => + getGovernanceAccounts( + connection, + programId, + (accountClass as unknown) as new (args: any) => AccountType, + filters + ), + (error) => + error instanceof Error ? error : new Error('Could not fetch accounts') + ), + map((accounts) => + accounts.reduce((acc, account) => { + acc[account.pubkey.toBase58()] = account + return acc + }, {} as { [address: string]: ProgramAccount }) + ), + match((error) => { + console.error(error) + return none + }, some) + )() +} diff --git a/models/accounts/index.ts b/models/accounts/index.ts new file mode 100644 index 0000000000..a54d5a9fe8 --- /dev/null +++ b/models/accounts/index.ts @@ -0,0 +1 @@ +export * from './getAccountsByFilter' diff --git a/models/proposal/buildTopVoters.ts b/models/proposal/buildTopVoters.ts new file mode 100644 index 0000000000..b9cca91737 --- /dev/null +++ b/models/proposal/buildTopVoters.ts @@ -0,0 +1,107 @@ +import { + ProgramAccount, + VoteRecord, + TokenOwnerRecord, + Realm, + Proposal, +} from '@solana/spl-governance' +import { MintInfo } from '@solana/spl-token' +import BN from 'bn.js' +import { PublicKey } from '@solana/web3.js' +import { BigNumber } from 'bignumber.js' + +import { calculateMaxVoteScore } from '@models/proposal/calulateMaxVoteScore' + +export enum VoteType { + No, + Undecided, + Yes, +} + +export interface VoterDisplayData { + decimals: number + name: PublicKey + voteType: VoteType + votesCast: BN + votePercentage: number + key: string +} + +const buildResults = ( + key: PublicKey, + amount: BN, + label: VoteType, + total: BN, + decimals: number +) => ({ + decimals, + name: key, + voteType: label, + votesCast: amount, + key: key.toBase58(), + votePercentage: new BigNumber(amount.toString()) + .shiftedBy(2) + .dividedBy(new BigNumber(total.toString())) + .toNumber(), +}) + +const ZERO = new BN(0) + +export function buildTopVoters( + voteRecords: ProgramAccount[], + tokenOwnerRecords: ProgramAccount[], + realm: ProgramAccount, + proposal: ProgramAccount, + governingTokenMint: MintInfo +): VoterDisplayData[] { + const maxVote = calculateMaxVoteScore(realm, proposal, governingTokenMint) + + const undecidedData = tokenOwnerRecords + .filter( + (tokenOwnerRecord) => + !tokenOwnerRecord.account.governingTokenDepositAmount.isZero() && + !voteRecords.some( + (voteRecord) => + voteRecord.account.governingTokenOwner.toBase58() === + tokenOwnerRecord.account.governingTokenOwner.toBase58() + ) + ) + .map((record) => + buildResults( + record.account.governingTokenOwner, + record.account.governingTokenDepositAmount, + VoteType.Undecided, + maxVote, + governingTokenMint.decimals + ) + ) + + const noVoteData = voteRecords + .filter((record) => record.account.getNoVoteWeight()?.gt(ZERO)) + .map((record) => + buildResults( + record.account.governingTokenOwner, + record.account.getNoVoteWeight()!, + VoteType.No, + maxVote, + governingTokenMint.decimals + ) + ) + + const yesVoteData = voteRecords + .filter((record) => record.account.getYesVoteWeight()?.gt(ZERO)) + .map((record) => + buildResults( + record.account.governingTokenOwner, + record.account.getYesVoteWeight()!, + VoteType.Yes, + maxVote, + governingTokenMint.decimals + ) + ) + + return undecidedData + .concat(yesVoteData) + .concat(noVoteData) + .sort((a, b) => b.votesCast.cmp(a.votesCast)) +} diff --git a/models/proposal/calculateMintMaxVoteWeight.ts b/models/proposal/calculateMintMaxVoteWeight.ts new file mode 100644 index 0000000000..2e4ebf406f --- /dev/null +++ b/models/proposal/calculateMintMaxVoteWeight.ts @@ -0,0 +1,21 @@ +import { MintInfo } from '@solana/spl-token' +import BN from 'bn.js' +import { BigNumber } from 'bignumber.js' +import { MintMaxVoteWeightSource } from '@solana/spl-governance' + +export function calculateMintMaxVoteWeight( + mint: MintInfo, + maxVoteWeightSource: MintMaxVoteWeightSource +) { + if (maxVoteWeightSource.isFullSupply()) { + return mint.supply as BN + } + + const supplyFraction = maxVoteWeightSource.getSupplyFraction() + + const maxVoteWeight = new BigNumber(supplyFraction.toString()) + .multipliedBy(mint.supply.toString()) + .shiftedBy(-MintMaxVoteWeightSource.SUPPLY_FRACTION_DECIMALS) + + return new BN(maxVoteWeight.dp(0, BigNumber.ROUND_DOWN).toString()) +} diff --git a/models/proposal/calulateMaxVoteScore.ts b/models/proposal/calulateMaxVoteScore.ts new file mode 100644 index 0000000000..77b04a1df0 --- /dev/null +++ b/models/proposal/calulateMaxVoteScore.ts @@ -0,0 +1,27 @@ +import { ProgramAccount, Realm, Proposal } from '@solana/spl-governance' +import { MintInfo } from '@solana/spl-token' +import BN from 'bn.js' + +import { calculateMintMaxVoteWeight } from '@models/proposal' + +export function calculateMaxVoteScore( + realm: ProgramAccount, + proposal: ProgramAccount, + governingTokenMint: MintInfo +) { + if (proposal.account.isVoteFinalized() && proposal.account.maxVoteWeight) { + return proposal.account.maxVoteWeight + } + + if ( + proposal.account.governingTokenMint.toBase58() === + realm.account.config.councilMint?.toBase58() + ) { + return governingTokenMint.supply as BN + } + + return calculateMintMaxVoteWeight( + governingTokenMint, + realm.account.config.communityMintMaxVoteWeightSource + ) +} diff --git a/models/proposal/getSignatories.ts b/models/proposal/getSignatories.ts new file mode 100644 index 0000000000..c075fed7c4 --- /dev/null +++ b/models/proposal/getSignatories.ts @@ -0,0 +1,36 @@ +import { Connection, PublicKey } from '@solana/web3.js' +import { pubkeyFilter, SignatoryRecord } from '@solana/spl-governance' +import { none, map } from 'fp-ts/Option' +import { pipe } from 'fp-ts/function' + +import { getAccountsByFilters } from '@models/accounts' + +interface Args { + connection: Connection + proposalPk: PublicKey + programId: PublicKey +} + +export async function getSignatories({ + connection, + programId, + proposalPk, +}: Args) { + const filter = pubkeyFilter(1, proposalPk) + + if (!filter) { + return none + } + + const accounts = await getAccountsByFilters({ + connection, + programId, + accountClass: SignatoryRecord, + filters: [filter], + }) + + return pipe( + accounts, + map((accounts) => Object.values(accounts)) + ) +} diff --git a/models/proposal/getTokenOwnerRecords.ts b/models/proposal/getTokenOwnerRecords.ts new file mode 100644 index 0000000000..7791239e8c --- /dev/null +++ b/models/proposal/getTokenOwnerRecords.ts @@ -0,0 +1,39 @@ +import { Connection, PublicKey } from '@solana/web3.js' +import { pubkeyFilter, TokenOwnerRecord } from '@solana/spl-governance' +import { none, map } from 'fp-ts/Option' +import { pipe } from 'fp-ts/function' + +import { getAccountsByFilters } from '@models/accounts' + +interface Args { + connection: Connection + governingTokenMint?: PublicKey + programId: PublicKey + realm: PublicKey +} + +export async function getTokenOwnerRecords({ + connection, + governingTokenMint, + programId, + realm, +}: Args) { + const filter1 = pubkeyFilter(1, realm) + const filter2 = pubkeyFilter(1 + 32, governingTokenMint) + + if (!(filter1 && filter2)) { + return none + } + + const accounts = await getAccountsByFilters({ + connection, + programId, + accountClass: TokenOwnerRecord, + filters: [filter1, filter2], + }) + + return pipe( + accounts, + map((accounts) => Object.values(accounts)) + ) +} diff --git a/models/proposal/getVoteRecords.ts b/models/proposal/getVoteRecords.ts new file mode 100644 index 0000000000..5aa7d737fc --- /dev/null +++ b/models/proposal/getVoteRecords.ts @@ -0,0 +1,36 @@ +import { Connection, PublicKey } from '@solana/web3.js' +import { pubkeyFilter, VoteRecord } from '@solana/spl-governance' +import { none, map } from 'fp-ts/Option' +import { pipe } from 'fp-ts/function' + +import { getAccountsByFilters } from '@models/accounts' + +interface Args { + connection: Connection + proposalPk: PublicKey + programId: PublicKey +} + +export async function getVoteRecords({ + connection, + programId, + proposalPk, +}: Args) { + const filter = pubkeyFilter(1, proposalPk) + + if (!filter) { + return none + } + + const accounts = await getAccountsByFilters({ + connection, + programId, + accountClass: VoteRecord, + filters: [filter], + }) + + return pipe( + accounts, + map((accounts) => Object.values(accounts)) + ) +} diff --git a/models/proposal/index.ts b/models/proposal/index.ts new file mode 100644 index 0000000000..8990056623 --- /dev/null +++ b/models/proposal/index.ts @@ -0,0 +1,6 @@ +export * from './buildTopVoters' +export * from './calculateMintMaxVoteWeight' +export * from './calulateMaxVoteScore' +export * from './getSignatories' +export * from './getTokenOwnerRecords' +export * from './getVoteRecords' diff --git a/package.json b/package.json index 14737dea91..252059ceaf 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,9 @@ "bignumber.js": "^9.0.2", "buffer-layout": "^1.2.2", "classnames": "^2.3.1", + "d3": "^7.4.4", "dayjs": "^1.11.1", + "fp-ts": "^2.12.1", "goblingold-sdk": "^1.2.35", "immer": "^9.0.12", "next": "^12.1.5", @@ -85,6 +87,8 @@ "react-hook-form": "^7.31.3", "react-markdown": "^7.0.0", "react-portal": "^4.2.2", + "react-virtualized-auto-sizer": "^1.0.6", + "react-window": "^1.8.7", "remark-gfm": "^3.0.1", "superstruct": "^0.15.4", "ts-node": "^10.7.0", @@ -96,6 +100,7 @@ "@notifi-network/notifi-core": "^0.12.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^11.2.5", + "@types/d3": "^7.4.0", "@types/jest": "^27.4.1", "@types/node": "^14.14.25", "@types/react": "^17.0.44", diff --git a/pages/dao/[symbol]/proposal/[pk]/explore.tsx b/pages/dao/[symbol]/proposal/[pk]/explore.tsx new file mode 100644 index 0000000000..d20074da60 --- /dev/null +++ b/pages/dao/[symbol]/proposal/[pk]/explore.tsx @@ -0,0 +1,102 @@ +import { useState } from 'react' +import { useRouter } from 'next/router' +import classNames from 'classnames' +import { ChevronLeftIcon } from '@heroicons/react/solid' + +import useProposal from '@hooks/useProposal' +import useVoteRecords from '@hooks/useVoteRecords' +import ProposalStateBadge from '@components/ProposalStatusBadge' +import ProposalTopVotersList from '@components/ProposalTopVotersList' +import ProposalTopVotersBubbleChart from '@components/ProposalTopVotersBubbleChart' +import useWalletStore from 'stores/useWalletStore' +import useSignatories from '@hooks/useSignatories' +import ProposalSignatories from '@components/ProposalSignatories' +import ProposalVoteResult from '@components/ProposalVoteResults' +import ProposalRemainingVotingTime from '@components/ProposalRemainingVotingTime' + +export default function Explore() { + const { proposal, governance } = useProposal() + const [highlighted, setHighlighted] = useState() + const connection = useWalletStore((s) => s.connection) + const records = useVoteRecords(proposal) + const signatories = useSignatories(proposal) + const router = useRouter() + + const endpoint = connection.endpoint + + return ( +
+ + {proposal && governance ? ( +
+
+

{proposal?.account.name}

+ +
+

Top Voters

+
setHighlighted(undefined)} + > + + +
+
+ + + +
+
+ ) : ( +
+
+
+
+
+ )} +
+ ) +} diff --git a/pages/dao/[symbol]/proposal/[pk].tsx b/pages/dao/[symbol]/proposal/[pk]/index.tsx similarity index 86% rename from pages/dao/[symbol]/proposal/[pk].tsx rename to pages/dao/[symbol]/proposal/[pk]/index.tsx index bfd7774990..ec3a8a43d8 100644 --- a/pages/dao/[symbol]/proposal/[pk].tsx +++ b/pages/dao/[symbol]/proposal/[pk]/index.tsx @@ -20,9 +20,12 @@ import VoteResultStatus from '@components/VoteResultStatus' import VoteResults from '@components/VoteResults' import { resolveProposalDescription } from '@utils/helpers' import PreviousRouteBtn from '@components/PreviousRouteBtn' +import Link from 'next/link' +import useQueryContext from '@hooks/useQueryContext' +import { ChevronRightIcon } from '@heroicons/react/solid' const Proposal = () => { - const { realmInfo } = useRealm() + const { realmInfo, symbol } = useRealm() const { proposal, descriptionLink } = useProposal() const [description, setDescription] = useState('') const { yesVoteProgress, yesVotesRequired } = useProposalVotes( @@ -51,6 +54,9 @@ const Proposal = () => { handleResolveDescription() } }, [descriptionLink]) + + const { fmtUrlWithCluster } = useQueryContext() + return (
@@ -140,6 +146,21 @@ const Proposal = () => {
)} + {proposal && ( + + )}
) : null} diff --git a/utils/formatPercentage.ts b/utils/formatPercentage.ts new file mode 100644 index 0000000000..f8685dc8ea --- /dev/null +++ b/utils/formatPercentage.ts @@ -0,0 +1,11 @@ +export function formatPercentage(percentage: number) { + if (percentage === 0 || percentage === Infinity) { + return '0%' + } + + if (percentage < 0.01) { + return '<0.01%' + } + + return `${+percentage.toFixed(2)}%` +} diff --git a/utils/ntext.ts b/utils/ntext.ts new file mode 100644 index 0000000000..c9dcd50690 --- /dev/null +++ b/utils/ntext.ts @@ -0,0 +1,7 @@ +export function ntext(count: number, singular: string, plural?: string) { + if (count === 1) { + return singular + } + + return plural || `${singular}s` +} diff --git a/yarn.lock b/yarn.lock index 74947117ad..7dcaa59072 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3245,6 +3245,216 @@ dependencies: "@types/node" "*" +"@types/d3-array@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.3.tgz#87d990bf504d14ad6b16766979d04e943c046dac" + integrity sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ== + +"@types/d3-axis@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.1.tgz#6afc20744fa5cc0cbc3e2bd367b140a79ed3e7a8" + integrity sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.1.tgz#ae5f17ce391935ca88b29000e60ee20452c6357c" + integrity sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.1.tgz#54c8856c19c8e4ab36a53f73ba737de4768ad248" + integrity sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw== + +"@types/d3-color@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.0.tgz#6594da178ded6c7c3842f3cc0ac84b156f12f2d4" + integrity sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA== + +"@types/d3-contour@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.1.tgz#9ff4e2fd2a3910de9c5097270a7da8a6ef240017" + integrity sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz#006b7bd838baec1511270cb900bf4fc377bbbf41" + integrity sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ== + +"@types/d3-dispatch@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz#a1b18ae5fa055a6734cb3bd3cbc6260ef19676e3" + integrity sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw== + +"@types/d3-drag@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.1.tgz#fb1e3d5cceeee4d913caa59dedf55c94cb66e80f" + integrity sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.0.tgz#f3c61fb117bd493ec0e814856feb804a14cfc311" + integrity sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A== + +"@types/d3-ease@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.0.tgz#c29926f8b596f9dadaeca062a32a45365681eae0" + integrity sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA== + +"@types/d3-fetch@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.1.tgz#f9fa88b81aa2eea5814f11aec82ecfddbd0b8fe0" + integrity sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.3.tgz#76cb20d04ae798afede1ea6e41750763ff5a9c82" + integrity sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA== + +"@types/d3-format@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.1.tgz#194f1317a499edd7e58766f96735bdc0216bb89d" + integrity sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg== + +"@types/d3-geo@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.0.2.tgz#e7ec5f484c159b2c404c42d260e6d99d99f45d9a" + integrity sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz#4561bb7ace038f247e108295ef77b6a82193ac25" + integrity sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ== + +"@types/d3-interpolate@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz#e7d17fa4a5830ad56fe22ce3b4fac8541a9572dc" + integrity sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.0.tgz#939e3a784ae4f80b1fde8098b91af1776ff1312b" + integrity sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg== + +"@types/d3-polygon@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.0.tgz#5200a3fa793d7736fa104285fa19b0dbc2424b93" + integrity sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw== + +"@types/d3-quadtree@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz#433112a178eb7df123aab2ce11c67f51cafe8ff5" + integrity sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw== + +"@types/d3-random@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.1.tgz#5c8d42b36cd4c80b92e5626a252f994ca6bfc953" + integrity sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ== + +"@types/d3-scale-chromatic@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#103124777e8cdec85b20b51fd3397c682ee1e954" + integrity sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw== + +"@types/d3-scale@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.2.tgz#41be241126af4630524ead9cb1008ab2f0f26e69" + integrity sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.2.tgz#23e48a285b24063630bbe312cc0cfe2276de4a59" + integrity sha512-d29EDd0iUBrRoKhPndhDY6U/PYxOWqgIZwKTooy2UkBfU7TNZNpRho0yLWPxlatQrFWk2mnTu71IZQ4+LRgKlQ== + +"@types/d3-shape@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.0.tgz#1d87a6ddcf28285ef1e5c278ca4bdbc0658f3505" + integrity sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.0.tgz#ee7b6e798f8deb2d9640675f8811d0253aaa1946" + integrity sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw== + +"@types/d3-time@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.0.tgz#e1ac0f3e9e195135361fa1a1d62f795d87e6e819" + integrity sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg== + +"@types/d3-timer@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.0.tgz#e2505f1c21ec08bda8915238e397fb71d2fc54ce" + integrity sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g== + +"@types/d3-transition@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.1.tgz#c9a96125567173d6163a6985b874f79154f4cc3d" + integrity sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.1.tgz#4bfc7e29625c4f79df38e2c36de52ec3e9faf826" + integrity sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.0.tgz#fc5cac5b1756fc592a3cf1f3dc881bf08225f515" + integrity sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + "@types/debug@^4.0.0": version "4.1.7" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz" @@ -3261,6 +3471,11 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/geojson@*": + version "7946.0.8" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" + integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== + "@types/glob@^7.1.1": version "7.2.0" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" @@ -4954,6 +5169,11 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz" integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg== +commander@7: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" @@ -5277,21 +5497,130 @@ d3-array@2, d3-array@^2.3.0: dependencies: internmap "^1.0.0" +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.1.6.tgz#0342c835925826f49b4d16eb7027aec334ffc97d" + integrity sha512-DCbBBNuKOeiR9h04ySRBMW52TFVc91O9wJziuyXw6Ztmy8D3oZbmCkOO3UHKC7ceNJsN2Mavo9+vwV8EAEUXzA== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + "d3-color@1 - 2", d3-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-3.0.1.tgz#2c64255d43059599cd0dba8fe4cc3d51ccdd9bbd" + integrity sha512-0Oc4D0KyhwhM7ZL0RMnfGycLN7hxHB8CMmwZ3+H26PWAG0ozNuYG5hXSDNgmP1SgJkQMrlG6cP20HoaSbvcJTQ== + dependencies: + d3-array "2 - 3" + +d3-delaunay@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92" + integrity sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + "d3-format@1 - 2": version "2.0.0" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767" integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + d3-format@^1.4.4: version "1.4.5" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== +d3-geo@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.0.1.tgz#4f92362fd8685d93e3b1fae0fd97dc8980b1ed7e" + integrity sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + "d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" @@ -5299,11 +5628,46 @@ d3-format@^1.4.4: dependencies: d3-color "1 - 2" +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + d3-path@1: version "1.0.9" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== +"d3-path@1 - 3", d3-path@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" + integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-scale-chromatic@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" + integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + d3-scale-chromatic@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz#c13f3af86685ff91323dc2f0ebd2dabbd72d8bab" @@ -5312,6 +5676,17 @@ d3-scale-chromatic@^2.0.0: d3-color "1 - 2" d3-interpolate "1 - 2" +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + d3-scale@^3.2.3: version "3.3.0" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3" @@ -5323,6 +5698,18 @@ d3-scale@^3.2.3: d3-time "^2.1.1" d3-time-format "2 - 3" +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556" + integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ== + dependencies: + d3-path "1 - 3" + d3-shape@^1.2.2, d3-shape@^1.3.5: version "1.3.7" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" @@ -5337,6 +5724,13 @@ d3-shape@^1.2.2, d3-shape@^1.3.5: dependencies: d3-time "1 - 2" +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + "d3-time@1 - 2", d3-time@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682" @@ -5344,11 +5738,81 @@ d3-shape@^1.2.2, d3-shape@^1.3.5: dependencies: d3-array "2" +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" + integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ== + dependencies: + d3-array "2 - 3" + d3-time@^1.0.11: version "1.1.0" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.4.4: + version "7.4.4" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.4.4.tgz#bfbf87487c37d3196efebd5a63e3a0ed8299d8ff" + integrity sha512-97FE+MYdAlV3R9P74+R3Uar7wUKkIFu89UWMjEaDhiJ9VxKvqaMxauImy8PC2DdBkdM2BxJOIoLxPrcZUyrKoQ== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "3" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -5542,6 +6006,13 @@ del@^4.1.1: pify "^4.0.1" rimraf "^2.6.3" +delaunator@5: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" + integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw== + dependencies: + robust-predicates "^3.0.0" + delay@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz" @@ -6599,6 +7070,11 @@ forwarded@0.2.0: resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +fp-ts@^2.12.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.12.1.tgz#e389488bfd1507af06bd5965e97367edcd4fabad" + integrity sha512-oxvgqUYR6O9VkKXrxkJ0NOyU0FrE705MeqgBUMEPWyTu6Pwn768cJbHChw2XOBlgFLKfIHxjr2OOBFpv2mUGZw== + fraction.js@^4.0.13: version "4.2.0" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" @@ -7251,7 +7727,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: +iconv-lite@0.6, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -7386,6 +7862,11 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + internmap@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" @@ -9184,6 +9665,11 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz" @@ -11609,6 +12095,19 @@ react-use-gesture@^9.1.3: resolved "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.1.3.tgz" integrity sha512-CdqA2SmS/fj3kkS2W8ZU8wjTbVBAIwDWaRprX7OKaj7HlGwBasGEFggmk5qNklknqk9zK/h8D355bEJFTpqEMg== +react-virtualized-auto-sizer@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.6.tgz#66c5b1c9278064c5ef1699ed40a29c11518f97ca" + integrity sha512-7tQ0BmZqfVF6YYEWcIGuoR3OdYe8I/ZFbNclFlGOC3pMqunkYF/oL30NCjSGl9sMEb17AnzixDz98Kqc3N76HQ== + +react-window@^1.8.7: + version "1.8.7" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.7.tgz#5e9fd0d23f48f432d7022cdb327219353a15f0d4" + integrity sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@^17.0.0, react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -11971,6 +12470,11 @@ rlp@^2.2.4: dependencies: bn.js "^5.2.0" +robust-predicates@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" + integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== + rpc-websockets@^7.4.12: version "7.4.17" resolved "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.4.17.tgz" @@ -12006,6 +12510,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + rxjs@6, rxjs@^6.6.7: version "6.6.7" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz"