fix: add liquidity flow polish (#2017)

* addressed feedback

* set initial, min and max zoom levels

* better handle 0

* avoid formatting range selector

* polish `not created` state

* remove unused import
This commit is contained in:
Justin Domingue 2021-07-12 10:19:12 -07:00 committed by GitHub
parent 77fbccd3f1
commit 8d567e4d4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 111 deletions

@ -75,7 +75,7 @@ export const Brush = ({
const previousBrushExtent = usePrevious(brushExtent)
const brushed = useCallback(
({ mode, type, selection }: D3BrushEvent<unknown>) => {
({ type, selection }: D3BrushEvent<unknown>) => {
if (!selection) {
setLocalBrushExtent(null)
return
@ -83,15 +83,14 @@ export const Brush = ({
const scaled = (selection as [number, number]).map(xScale.invert) as [number, number]
// undefined `mode` means brush was programatically moved
// skip calling the handler to avoid a loop
if (type === 'end' && mode !== undefined) {
// avoid infinite render loop by checking for change
if (type === 'end' && (brushExtent[0] !== scaled[0] || brushExtent[1] !== scaled[1])) {
setBrushExtent(scaled)
}
setLocalBrushExtent(scaled)
},
[xScale.invert, setBrushExtent]
[xScale.invert, brushExtent, setBrushExtent]
)
// keep local and external brush extent in sync

@ -20,7 +20,7 @@ export function Chart({
brushDomain,
brushLabels,
onBrushDomainChange,
initialZoom,
zoomLevels,
}: LiquidityChartRangeInputProps) {
const svgRef = useRef<SVGSVGElement | null>(null)
@ -34,7 +34,7 @@ export function Chart({
const { xScale, yScale } = useMemo(() => {
const scales = {
xScale: scaleLinear()
.domain([(1 - initialZoom) * current, (1 + initialZoom) * current] as number[])
.domain([(1 - zoomLevels.initial) * current, (1 + zoomLevels.initial) * current] as number[])
.range([0, innerWidth]),
yScale: scaleLinear()
.domain([0, max(series, yAccessor)] as number[])
@ -47,7 +47,7 @@ export function Chart({
}
return scales
}, [initialZoom, current, innerWidth, series, innerHeight, zoom])
}, [zoomLevels.initial, current, innerWidth, series, innerHeight, zoom])
useEffect(() => {
if (!brushDomain) {
@ -67,6 +67,7 @@ export function Chart({
innerWidth={innerWidth}
innerHeight={innerHeight}
showClear={Boolean(zoom && zoom.k !== 1)}
zoomLevels={zoomLevels}
/>
<svg ref={svgRef} width="100%" height="100%" viewBox={`0 0 ${width} ${height}`} style={{ overflow: 'visible' }}>
<defs>

@ -3,6 +3,7 @@ import { ButtonGray } from 'components/Button'
import styled from 'styled-components/macro'
import { ScaleLinear, select, ZoomBehavior, zoom, ZoomTransform } from 'd3'
import { RefreshCcw, ZoomIn, ZoomOut } from 'react-feather'
import { ZoomLevels } from './types'
const Wrapper = styled.div<{ count: number }>`
display: grid;
@ -32,6 +33,7 @@ export default function Zoom({
innerWidth,
innerHeight,
showClear,
zoomLevels,
}: {
svg: SVGSVGElement | null
xScale: ScaleLinear<number, number>
@ -39,6 +41,7 @@ export default function Zoom({
innerWidth: number
innerHeight: number
showClear: boolean
zoomLevels: ZoomLevels
}) {
const zoomBehavior = useRef<ZoomBehavior<Element, unknown>>()
@ -71,7 +74,7 @@ export default function Zoom({
// zoom
zoomBehavior.current = zoom()
.scaleExtent([0.3, 10])
.scaleExtent([zoomLevels.min, zoomLevels.max])
.translateExtent([
[0, 0],
[innerWidth, innerHeight],
@ -85,7 +88,7 @@ export default function Zoom({
select(svg as Element)
.call(zoomBehavior.current)
.on('mousedown.zoom', null)
}, [innerHeight, innerWidth, setZoom, svg, xScale, zoomBehavior])
}, [innerHeight, innerWidth, setZoom, svg, xScale, zoomBehavior, zoomLevels.max, zoomLevels.min])
return (
<Wrapper count={showClear ? 3 : 2}>

@ -36,7 +36,9 @@ export function useDensityChartData({
price0: parseFloat(t.price0),
}
newData.push(chartEntry)
if (chartEntry.activeLiquidity > 0) {
newData.push(chartEntry)
}
}
return newData

@ -16,6 +16,25 @@ import { format } from 'd3'
import { Bound } from 'state/mint/v3/actions'
import { FeeAmount } from '@uniswap/v3-sdk'
import ReactGA from 'react-ga'
import { ZoomLevels } from './types'
const ZOOM_LEVELS: Record<FeeAmount, ZoomLevels> = {
[FeeAmount.LOW]: {
initial: 0.002,
min: 0.001,
max: 2,
},
[FeeAmount.MEDIUM]: {
initial: 0.3,
min: 0.01,
max: 20,
},
[FeeAmount.HIGH]: {
initial: 0.3,
min: 0.01,
max: 20,
},
}
const ChartWrapper = styled.div`
position: relative;
@ -51,7 +70,7 @@ export default function LiquidityChartRangeInput({
}: {
currencyA: Currency | undefined
currencyB: Currency | undefined
feeAmount?: number
feeAmount?: FeeAmount
ticksAtLimit: { [bound in Bound]?: boolean | undefined }
price: number | undefined
priceLower?: Price<Token, Token>
@ -73,7 +92,7 @@ export default function LiquidityChartRangeInput({
const onBrushDomainChangeEnded = useCallback(
(domain) => {
const leftRangeValue = Number(domain[0])
let leftRangeValue = Number(domain[0])
const rightRangeValue = Number(domain[1])
ReactGA.event({
@ -81,6 +100,10 @@ export default function LiquidityChartRangeInput({
action: 'Chart brushed',
})
if (leftRangeValue <= 0) {
leftRangeValue = 1 / 10 ** 6
}
batch(() => {
// simulate user input for auto-formatting and other validations
leftRangeValue > 0 && onLeftRangeInput(leftRangeValue.toFixed(6))
@ -165,7 +188,7 @@ export default function LiquidityChartRangeInput({
brushLabels={brushLabelValue}
brushDomain={brushDomain}
onBrushDomainChange={onBrushDomainChangeEnded}
initialZoom={feeAmount === FeeAmount.LOW ? 0.02 : 0.3}
zoomLevels={ZOOM_LEVELS[feeAmount ?? FeeAmount.MEDIUM]}
/>
</ChartWrapper>
)}

@ -15,6 +15,12 @@ export interface Margins {
left: number
}
export interface ZoomLevels {
initial: number
min: number
max: number
}
export interface LiquidityChartRangeInputProps {
// to distringuish between multiple charts in the DOM
id?: string
@ -47,5 +53,5 @@ export interface LiquidityChartRangeInputProps {
brushDomain: [number, number] | undefined
onBrushDomainChange: (domain: [number, number]) => void
initialZoom: number
zoomLevels: ZoomLevels
}

@ -4,7 +4,6 @@ import StepCounter from 'components/InputStepCounter/InputStepCounter'
import { RowBetween } from 'components/Row'
import { AutoColumn } from 'components/Column'
import { Bound } from 'state/mint/v3/actions'
import { formatTickPrice } from 'utils/formatTickPrice'
// currencyA is the base token
export default function RangeSelector({
@ -45,7 +44,7 @@ export default function RangeSelector({
<AutoColumn gap="md">
<RowBetween>
<StepCounter
value={formatTickPrice(leftPrice, ticksAtLimit, Bound.LOWER, '')}
value={ticksAtLimit[Bound.LOWER] ? '0' : leftPrice?.toSignificant(5) ?? ''}
onUserInput={onLeftRangeInput}
width="48%"
decrement={isSorted ? getDecrementLower : getIncrementUpper}
@ -59,7 +58,7 @@ export default function RangeSelector({
tokenB={currencyB?.symbol}
/>
<StepCounter
value={formatTickPrice(rightPrice, ticksAtLimit, Bound.UPPER, '')}
value={ticksAtLimit[Bound.UPPER] ? '∞' : rightPrice?.toSignificant(5) ?? ''}
onUserInput={onRightRangeInput}
width="48%"
decrement={isSorted ? getDecrementUpper : getIncrementLower}

@ -68,7 +68,6 @@ import { BigNumber } from '@ethersproject/bignumber'
import { AddRemoveTabs } from 'components/NavigationTabs'
import HoverInlineText from 'components/HoverInlineText'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import PresetsButtons from 'components/RangeSelector/PresetsButtons'
import LiquidityChartRangeInput from 'components/LiquidityChartRangeInput'
import { SupportedChainId } from 'constants/chains'
import OptimismDowntimeWarning from 'components/OptimismDowntimeWarning'
@ -451,7 +450,7 @@ export default function AddLiquidity({
const { [Bound.LOWER]: tickLower, [Bound.UPPER]: tickUpper } = ticks
const { [Bound.LOWER]: priceLower, [Bound.UPPER]: priceUpper } = pricesAtTicks
const { getDecrementLower, getIncrementLower, getDecrementUpper, getIncrementUpper, getSetRange, getSetFullRange } =
const { getDecrementLower, getIncrementLower, getDecrementUpper, getIncrementUpper, getSetFullRange } =
useRangeHopCallbacks(baseCurrency ?? undefined, quoteCurrency ?? undefined, feeAmount, tickLower, tickUpper, pool)
// we need an existence check on parsed amounts for single-asset deposits
@ -656,32 +655,6 @@ export default function AddLiquidity({
token0={currencyA?.wrapped}
token1={currencyB?.wrapped}
/>
{noLiquidity && (
<BlueCard
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
padding: '1rem 1rem',
}}
>
<div style={{ marginRight: '12px', width: '30px', height: '30px' }}>
<AlertCircle color={theme.primaryText1} size={30} />
</div>
<TYPE.body
fontSize={14}
style={{ marginBottom: 8, fontWeight: 500 }}
textAlign="center"
color={theme.primaryText1}
>
<Trans>
You are the first liquidity provider for this Uniswap V3 pool.The transaction cost will be
much higher as it includes the gas to create the pool.
</Trans>
</TYPE.body>
</BlueCard>
)}
</AutoColumn>{' '}
</>
)}
@ -743,65 +716,54 @@ export default function AddLiquidity({
</HideMedium>
<RightContainer gap="lg">
<DynamicSection gap="md" disabled={!feeAmount || invalidPool}>
<RowBetween>
<TYPE.label>
<Trans>Set your Price Range</Trans>
</TYPE.label>
</RowBetween>
{!noLiquidity ? (
<>
<RowBetween>
<TYPE.label>
<Trans>Set Price Range</Trans>
</TYPE.label>
</RowBetween>
{price && baseCurrency && quoteCurrency && !noLiquidity && (
<AutoRow gap="4px" justify="center" style={{ marginTop: '0.5rem' }}>
<Trans>
<TYPE.main fontWeight={500} textAlign="center" fontSize={12} color="text1">
Current Price:
</TYPE.main>
<TYPE.body fontWeight={500} textAlign="center" fontSize={12} color="text1">
<HoverInlineText
maxCharacters={20}
text={invertPrice ? price.invert().toSignificant(6) : price.toSignificant(6)}
/>
</TYPE.body>
<TYPE.body color="text2" fontSize={12}>
{quoteCurrency?.symbol} per {baseCurrency.symbol}
</TYPE.body>
</Trans>
</AutoRow>
)}
{price && baseCurrency && quoteCurrency && !noLiquidity && (
<AutoRow gap="4px" justify="center" style={{ marginTop: '0.5rem' }}>
<Trans>
<TYPE.main fontWeight={500} textAlign="center" fontSize={12} color="text1">
Current Price:
</TYPE.main>
<TYPE.body fontWeight={500} textAlign="center" fontSize={12} color="text1">
<HoverInlineText
maxCharacters={20}
text={invertPrice ? price.invert().toSignificant(6) : price.toSignificant(6)}
/>
</TYPE.body>
<TYPE.body color="text2" fontSize={12}>
{quoteCurrency?.symbol} per {baseCurrency.symbol}
</TYPE.body>
</Trans>
</AutoRow>
)}
<LiquidityChartRangeInput
currencyA={baseCurrency ?? undefined}
currencyB={quoteCurrency ?? undefined}
feeAmount={feeAmount}
ticksAtLimit={ticksAtLimit}
price={price ? parseFloat((invertPrice ? price.invert() : price).toSignificant(8)) : undefined}
priceLower={priceLower}
priceUpper={priceUpper}
onLeftRangeInput={onLeftRangeInput}
onRightRangeInput={onRightRangeInput}
interactive={!hasExistingPosition}
/>
{noLiquidity && (
<LiquidityChartRangeInput
currencyA={baseCurrency ?? undefined}
currencyB={quoteCurrency ?? undefined}
feeAmount={feeAmount}
ticksAtLimit={ticksAtLimit}
price={
price ? parseFloat((invertPrice ? price.invert() : price).toSignificant(8)) : undefined
}
priceLower={priceLower}
priceUpper={priceUpper}
onLeftRangeInput={onLeftRangeInput}
onRightRangeInput={onRightRangeInput}
interactive={!hasExistingPosition}
/>
</>
) : (
<AutoColumn gap="md">
<RowBetween>
<TYPE.label>
<Trans>Set Starting Price</Trans>
</TYPE.label>
{baseCurrency && quoteCurrency ? (
<RateToggle
currencyA={baseCurrency}
currencyB={quoteCurrency}
handleRateToggle={() => {
onLeftRangeInput('')
onRightRangeInput('')
history.push(
`/add/${currencyIdB as string}/${currencyIdA as string}${
feeAmount ? '/' + feeAmount : ''
}`
)
}}
/>
) : null}
</RowBetween>
<OutlineCard padding="12px">
@ -842,19 +804,6 @@ export default function AddLiquidity({
<StackedContainer>
<StackedItem style={{ opacity: showCapitalEfficiencyWarning ? '0.05' : 1 }}>
<AutoColumn gap="md">
{!noLiquidity && (
<PresetsButtons
feeAmount={feeAmount}
setRange={(numTicks: number) => {
const [range1, range2] = getSetRange(numTicks)
onLeftRangeInput(invertPrice ? range2 : range1)
onRightRangeInput(invertPrice ? range1 : range2)
}}
setFullRange={() => {
setShowCapitalEfficiencyWarning(true)
}}
/>
)}
<RangeSelector
priceLower={priceLower}
priceUpper={priceUpper}
@ -954,6 +903,32 @@ export default function AddLiquidity({
) : null}
</DynamicSection>
{noLiquidity && (
<BlueCard
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
padding: '1rem 1rem',
}}
>
<div style={{ marginRight: '12px', width: '30px', height: '30px' }}>
<AlertCircle color={theme.primaryText1} size={30} />
</div>
<TYPE.body
fontSize={14}
style={{ marginBottom: 8, fontWeight: 500 }}
textAlign="center"
color={theme.primaryText1}
>
<Trans>
You are the first liquidity provider for this Uniswap V3 pool.The transaction cost will be
much higher as it includes the gas to create the pool.
</Trans>
</TYPE.body>
</BlueCard>
)}
<MediumOnly>
<Buttons />
</MediumOnly>