From 46b025ac748f261b6e9a6367634cab9693b87576 Mon Sep 17 00:00:00 2001 From: jinoosss Date: Thu, 22 Aug 2024 18:36:19 +0900 Subject: [PATCH 1/2] fix: refactor graph tooltip architecture --- .../common/bar-area-graph/BarAreaGraph.tsx | 18 +- .../common/pool-graph/PoolGraph.styles.ts | 136 +--- .../common/pool-graph/PoolGraph.tsx | 764 +++++------------- .../common/pool-graph/PoolGraph.types.ts | 36 + .../pool-graph-svg/PoolGraphSVG.styles.ts | 8 + .../pool-graph-svg/PoolGraphSVG.tsx | 298 +++++++ .../PoolGraphTooltip.styles.ts | 141 ++++ .../pool-graph-tooltip/PoolGraphTooltip.tsx | 239 ++++++ .../IncentivizedPoolCard.tsx | 6 +- .../PoolInfoLazyChart.tsx | 2 +- .../MyDetailedPositionCard.tsx | 4 +- .../PoolPairInfoContent.tsx | 30 +- 12 files changed, 952 insertions(+), 730 deletions(-) create mode 100644 packages/web/src/components/common/pool-graph/PoolGraph.types.ts create mode 100644 packages/web/src/components/common/pool-graph/pool-graph-svg/PoolGraphSVG.styles.ts create mode 100644 packages/web/src/components/common/pool-graph/pool-graph-svg/PoolGraphSVG.tsx create mode 100644 packages/web/src/components/common/pool-graph/pool-graph-tooltip/PoolGraphTooltip.styles.ts create mode 100644 packages/web/src/components/common/pool-graph/pool-graph-tooltip/PoolGraphTooltip.tsx diff --git a/packages/web/src/components/common/bar-area-graph/BarAreaGraph.tsx b/packages/web/src/components/common/bar-area-graph/BarAreaGraph.tsx index 5fc46ff8c..cd5e76570 100644 --- a/packages/web/src/components/common/bar-area-graph/BarAreaGraph.tsx +++ b/packages/web/src/components/common/bar-area-graph/BarAreaGraph.tsx @@ -52,18 +52,20 @@ const BarAreaGraph: React.FC = ({ poolBins, }) => { const isHideBar = useMemo(() => { - const isAllReserveZeroBin40 = poolBins.every(item => Number(item.reserveTokenA) === 0 && Number(item.reserveTokenB) === 0); - const isAllReserveZeroBin = positionBins.every(item => Number(item.reserveTokenA) === 0 && Number(item.reserveTokenB) === 0); + const isAllReserveZeroBin40 = poolBins.every( + item => + Number(item.reserveTokenA) === 0 && Number(item.reserveTokenB) === 0, + ); + const isAllReserveZeroBin = positionBins.every( + item => + Number(item.reserveTokenA) === 0 && Number(item.reserveTokenB) === 0, + ); return isAllReserveZeroBin40 && isAllReserveZeroBin; }, [poolBins, positionBins]); return ( - + = ({ poolPrice={pool?.price || 1} isPosition binsMyAmount={positionBins} - showBar={!isHideBar} + disabled={isHideBar} /> ); diff --git a/packages/web/src/components/common/pool-graph/PoolGraph.styles.ts b/packages/web/src/components/common/pool-graph/PoolGraph.styles.ts index 684dc94e9..e54ab8058 100644 --- a/packages/web/src/components/common/pool-graph/PoolGraph.styles.ts +++ b/packages/web/src/components/common/pool-graph/PoolGraph.styles.ts @@ -1,6 +1,4 @@ import styled from "@emotion/styled"; -import { fonts } from "@constants/font.constant"; -import { media } from "@styles/media"; export const PoolGraphWrapper = styled.div` position: relative; @@ -22,142 +20,10 @@ export const PoolGraphWrapper = styled.div` stroke: ${({ theme }) => theme.color.background06}; } &:hover { - .bin-inner { + .bin-inner { opacity: 0.4; } } } } `; - -export const PoolGraphTooltipWrapper = styled.div` - padding: 0; - pointer-events: none; - border-radius: 8px; - - &.disabled { - display: none; - } - - .tooltip-wrapper { - display: flex; - flex-direction: column; - background-color: ${({ theme }) => theme.color.background02}; - align-items: flex-start; - border-radius: 8px; - gap: 8px; - ${fonts.body12}; - line-height: 1em; - - .row { - display: flex; - flex-direction: row; - width: 100%; - gap: 8px; - & > span { - display: flex; - flex-direction: row; - align-items: center; - &.price-range { - justify-content: flex-end; - } - &.token-title { - ${media.mobile} { - display: none; - } - } - } - } - - .header { - display: flex; - flex-direction: column; - color: ${({ theme }) => theme.color.text04}; - justify-content: space-between; - width: 100%; - &.mt-8 { - margin-top: 8px; - } - .row { - padding: 3px 0; - } - } - - .content { - display: flex; - flex-direction: column; - color: ${({ theme }) => theme.color.text02}; - gap: 8px; - width: 100%; - .row { - padding: 4px 0; - } - &:last-of-type { - .token { - ${media.mobile} { - display: none; - } - } - } - } - - .token { - flex-shrink: 0; - min-width: 80px; - gap: 8px; - - img { - width: 20px; - height: 20px; - } - ${media.mobile} { - display: none; - } - } - .amount { - flex-shrink: 0; - min-width: 76px; - & .hidden { - display: inline; - overflow: hidden; - text-overflow: clip; - white-space: nowrap; - word-break: break-all; - } - & .small-font { - font-size: 10px; - } - &.w-100 { - min-width: 108px; - } - img { - width: 20px; - height: 20px; - display: none; - } - - ${media.mobile} { - &.total-amount { - width: 85px; - gap: 8px; - } - img { - display: block; - } - } - } - - .price-range { - width: 100%; - } - .small-font { - font-size: 12px; - } - ${media.mobile} { - max-width: max-content; - } - @media (max-width: 360px){ - max-width: 336px; - } - } -`; diff --git a/packages/web/src/components/common/pool-graph/PoolGraph.tsx b/packages/web/src/components/common/pool-graph/PoolGraph.tsx index f98cb4cb4..f6fe647a4 100644 --- a/packages/web/src/components/common/pool-graph/PoolGraph.tsx +++ b/packages/web/src/components/common/pool-graph/PoolGraph.tsx @@ -5,17 +5,20 @@ import React, { useRef, useState, } from "react"; -import { PoolGraphTooltipWrapper, PoolGraphWrapper } from "./PoolGraph.styles"; import * as d3 from "d3"; +import * as uuid from "uuid"; + +import { FloatingPosition } from "@hooks/common/use-floating-tooltip"; import { PoolBinModel } from "@models/pool/pool-bin-model"; import { TokenModel } from "@models/token/token-model"; -import { useColorGraph } from "@hooks/common/use-color-graph"; +import { formatTokenExchangeRate } from "@utils/stake-position-utils"; import { tickToPriceStr } from "@utils/swap-utils"; import FloatingTooltip from "../tooltip/FloatingTooltip"; -import { FloatingPosition } from "@hooks/common/use-floating-tooltip"; -import MissingLogo from "../missing-logo/MissingLogo"; -import { formatTokenExchangeRate } from "@utils/stake-position-utils"; -import { useTranslation } from "react-i18next"; + +import PoolGraphSVG from "./pool-graph-svg/PoolGraphSVG"; +import PoolGraphTooltip from "./pool-graph-tooltip/PoolGraphTooltip"; +import { PoolGraphWrapper } from "./PoolGraph.styles"; +import { ReservedBin, TooltipInfo } from "./PoolGraph.types"; export interface PoolGraphProps { tokenA: TokenModel; @@ -42,28 +45,8 @@ export interface PoolGraphProps { poolPrice: number; isPosition?: boolean; binsMyAmount?: PoolBinModel[]; - isSwap?: boolean; - showBar?: boolean; -} - -interface TooltipInfo { - tokenA: TokenModel; - tokenB: TokenModel; - tokenAAmount: string | null; - tokenBAmount: string | null; - myTokenAAmount: string | null; - myTokenBAmount: string | null; - tokenARange: { - min: string | null; - max: string | null; - }; - tokenBRange: { - min: string | null; - max: string | null; - }; - tokenAPrice: string; - tokenBPrice: string; - isBlackBar: boolean; + isReversed?: boolean; + disabled?: boolean; } const PoolGraph: React.FC = ({ @@ -89,37 +72,37 @@ const PoolGraph: React.FC = ({ poolPrice, isPosition = false, binsMyAmount = [], - isSwap = false, - showBar = true, + isReversed = false, + disabled = true, }) => { - const graphIdRef = useRef(Math.random()); - const defaultMinX = Math.min(...bins.map(bin => bin.minTick)); - const svgRef = useRef(null); - const chartRef = useRef(null); + const graphIdRef = useRef(uuid.v4()); + const graphId = graphIdRef.current.toString(); + const getBinId = useCallback( + (index: number) => `pool-graph-bin-${graphId}-${index}`, + [graphId], + ); + + const svgRef = useRef(null); const tooltipRef = useRef(null); const lastHoverBinIndexRef = useRef(); - const { redColor, greenColor } = useColorGraph(); - const boundsWidth = width - margin.right - margin.left; const boundsHeight = height - margin.top - margin.bottom; - const getBinId = useCallback( - (index: number) => `pool-graph-bin-${graphIdRef.current}-${index}`, - [], - ); - // D3 - Dimension Definition + const defaultMinX = Math.min(...bins.map(bin => bin.minTick)); const minX = d3.min(bins, bin => bin.minTick - defaultMinX) || 0; const maxX = d3.max(bins, bin => bin.maxTick - defaultMinX) || 0; - const resolvedBins = useMemo(() => { + const reservedBins: ReservedBin[] = useMemo(() => { const length = bins.length / 2; const fullLength = length * 2; + const convertReserveBins = bins.map((item, index) => { const reserveTokenAMap = Number(item.reserveTokenB) / (Number(poolPrice) || 1); const reserveTokenBMap = Number(item.reserveTokenA); + return { ...item, reserveTokenAMyAmount: binsMyAmount?.[index]?.reserveTokenA || 0, @@ -131,7 +114,8 @@ const PoolGraph: React.FC = ({ const maxHeight = d3.max(convertReserveBins, bin => bin.reserveTokenAMap) || 0; - const temp = convertReserveBins + + const reserveBins = convertReserveBins .sort((b1, b2) => b1.minTick - b2.minTick) .map(bin => { return { @@ -143,17 +127,22 @@ const PoolGraph: React.FC = ({ maxTickSwap: bin.maxTick - defaultMinX, }; }); - const revereTemp = temp.map((item, i) => ({ - ...temp[length * 2 - i - 1], + + if (!isReversed) { + return reserveBins; + } + + const reverseReserveBins = reserveBins.map((item, i) => ({ + ...reserveBins[length * 2 - i - 1], minTick: item.minTick, maxTick: item.maxTick, - minTickSwap: temp[fullLength - i - 1].minTick, - maxTickSwap: temp[fullLength - i - 1].maxTick, + minTickSwap: reserveBins[fullLength - i - 1].minTick, + maxTickSwap: reserveBins[fullLength - i - 1].maxTick, })); - return !isSwap ? temp : revereTemp; - }, [bins, boundsHeight, defaultMinX, poolPrice, isSwap, binsMyAmount]); + return reverseReserveBins; + }, [bins, isReversed, poolPrice, binsMyAmount.length]); - const maxHeight = d3.max(resolvedBins, bin => bin.reserveTokenMap) || 0; + const maxHeight = d3.max(reservedBins, bin => bin.reserveTokenMap) || 0; // D3 - Scale Definition const defaultScaleX = d3 @@ -169,7 +158,6 @@ const PoolGraph: React.FC = ({ .domain([0, maxHeight || 0]) .range([boundsHeight, 0]); }, [boundsHeight, maxHeight]); - const centerX = currentTick ?? (minX && maxX ? (minX + maxX) / 2 : 0); const [tickOfPrices, setTickOfPrices] = useState<{ [key in number]: string }>( {}, @@ -178,20 +166,20 @@ const PoolGraph: React.FC = ({ const [positionX, setPositionX] = useState(null); const [positionY, setPositionY] = useState(null); - function getTickSpacing() { - if (resolvedBins.length < 1) { + const binSpacing = useMemo(() => { + if (reservedBins.length < 1) { return 0; } - if (resolvedBins.length === 2) { + if (reservedBins.length === 2) { return 20; } const spacing = - scaleX(resolvedBins[1].minTick) - scaleX(resolvedBins[0].minTick); + scaleX(reservedBins[1].minTick) - scaleX(reservedBins[0].minTick); if (spacing < 2) { return spacing; } return spacing; - } + }, [reservedBins, scaleX]); const tooltipPosition = useMemo((): FloatingPosition => { if (position) { @@ -207,291 +195,170 @@ const PoolGraph: React.FC = ({ } return `${isStart ? "right" : "left"}`; }, [width, height, positionX, positionY, position]); - const random = Math.random().toString(); - - /** Update Chart by data */ - function updateChart() { - const tickSpacing = getTickSpacing(); - const centerPosition = scaleX(centerX - defaultMinX) - tickSpacing / 2; - // Retrieves the colour of the chart bar at the current tick. - function fillByBin(bin: PoolBinModel) { - let isBlackBar = !!( - maxTickPosition && - minTickPosition && - (scaleX(bin.minTick) < minTickPosition - tickSpacing || - scaleX(bin.minTick) > maxTickPosition) - ); - - if (isSwap) { - isBlackBar = !!( - maxTickPosition && - minTickPosition && - (scaleX(bin.minTick) < scaleX(maxX) - maxTickPosition - tickSpacing || - scaleX(bin.minTick) > scaleX(maxX) - minTickPosition) - ); - } - if (isBlackBar) return themeKey === "dark" ? "#1C2230" : "#E0E8F4"; - if (currentTick && bin.minTick < Number(currentTick - defaultMinX)) { - return `url(#gradient-bar-green-${random})`; + const onMouseMoveChartBin = useCallback( + (event: MouseEvent) => { + if (!mouseover || Object.keys(tickOfPrices).length === 0) { + return; } - return `url(#gradient-bar-red-${random})`; - } - - // Clean child elements. - d3.select(chartRef.current).selectChildren().remove(); - - // Create a chart bar. - const rects = d3.select(chartRef.current); - - rects.attr("clip-path", "url(#clip)"); - - // D3 - Draw Current tick (middle line) - if (currentTick) { - rects - .append("line") - .attr("x1", centerPosition + tickSpacing / 2) - .attr("x2", centerPosition + tickSpacing / 2) - .attr("y1", 0) - .attr("y2", boundsHeight) - .attr("stroke-dasharray", 3) - .attr("stroke", `${themeKey === "dark" ? "#E0E8F4" : "#596782"}`) - .attr("stroke-width", 1); - } + const mouseX = event.offsetX; + const mouseY = event.offsetY; + const currentBin = reservedBins.find(bin => { + if (mouseY < 0.000001 || mouseY > height) { + return false; + } + if (bin.reserveTokenMap < 0 || !bin.reserveTokenMap) { + return false; + } + const isHoveringCurrentBin = document + .getElementById(getBinId(bin.index)) + ?.matches(":hover"); - // D3 - Draw bins as bars - if (showBar) { - rects - .selectAll("rects") - .data(resolvedBins) - .enter() - .append("g") - .attr("class", "bin-wrapper") - .attr("id", bin => getBinId(bin.index)) - .each(function (bin) { - d3.select(this) - .append("rect") - .style("fill", "transparent") - .attr("class", "bin-inner") - .style("stroke-width", "0") - .attr("x", scaleX(bin.minTick)) - .attr("width", scaleX(bin.maxTick - bin.minTick)) - .attr("y", () => { - const scaleYComputation = scaleY(bin.reserveTokenMap) ?? 0; - return ( - scaleYComputation - - (scaleYComputation > height - 3 && scaleYComputation !== height - ? 3 - : 0) - ); - }) - .attr("height", () => { - const scaleYComputation = scaleY(bin.reserveTokenMap) ?? 0; - return ( - boundsHeight - - scaleYComputation + - (scaleYComputation > height - 3 && scaleYComputation !== height - ? 3 - : 0) - ); - }); - d3.select(this) - .append("rect") - .style("fill", fillByBin(bin)) - .attr("class", "bin-inner") - .style("stroke-width", "0") - .attr("x", scaleX(bin.minTick) + 1) - .attr("width", scaleX(bin.maxTick - bin.minTick) - 0.5) - .attr("y", () => { - const scaleYComputation = scaleY(bin.reserveTokenMap) ?? 0; - return ( - scaleYComputation - - (scaleYComputation > height - 3 && scaleYComputation !== height - ? 3 - : 0) - ); - }) - .attr("height", () => { - const scaleYComputation = scaleY(bin.reserveTokenMap) ?? 0; - return ( - boundsHeight - - scaleYComputation + - (scaleYComputation > height - 3 && scaleYComputation !== height - ? 3 - : 0) - ); - }); - }); - } - } + const isHoveringPreviousBin = document + .getElementById(getBinId(bin.index - 1)) + ?.matches(":hover"); - function onMouseoverChartBin(event: MouseEvent) { - if (!mouseover) { - return; - } - const mouseX = event.offsetX; - const mouseY = event.offsetY; - const currentBin = resolvedBins.find(bin => { - if (mouseY < 0.000001 || mouseY > height) { - return false; - } - if (bin.reserveTokenMap < 0 || !bin.reserveTokenMap) { - return false; - } - const isHoveringCurrentBin = document - .getElementById(getBinId(bin.index)) - ?.matches(":hover"); + const isHoveringNextBin = document + .getElementById(getBinId(bin.index + 1)) + ?.matches(":hover"); - const isHoveringPreviousBin = document - .getElementById(getBinId(bin.index - 1)) - ?.matches(":hover"); + const hoveredBinIndex = (() => { + if (isHoveringCurrentBin) return bin.index; - const isHoveringNextBin = document - .getElementById(getBinId(bin.index + 1)) - ?.matches(":hover"); + if (isHoveringPreviousBin) return bin.index - 1; - const isHoveringIndex = (() => { - if (isHoveringCurrentBin) return bin.index; + if (isHoveringNextBin) return bin.index + 1; + })(); - if (isHoveringPreviousBin) return bin.index - 1; + if (hoveredBinIndex !== 0 && !hoveredBinIndex) return false; - if (isHoveringNextBin) return bin.index + 1; - })(); + return bin.index === hoveredBinIndex; + }); - if (isHoveringIndex !== 0 && !isHoveringIndex) return false; + if (currentBin?.index) { + if (currentBin.index !== lastHoverBinIndexRef.current) { + lastHoverBinIndexRef.current = currentBin.index; + } else { + setPositionX(mouseX); + setPositionY(mouseY); + return; + } + } - return bin.index === isHoveringIndex; - }); - if ( - currentBin?.index && - currentBin?.index !== lastHoverBinIndexRef.current - ) { - lastHoverBinIndexRef.current = currentBin?.index; - } + if (!currentBin) { + setPositionX(null); + setPositionY(null); + if (!nextSpacing) { + setTooltipInfo(null); + } + return; + } - if (!currentBin) { - setPositionX(null); - setPositionY(null); - if (!nextSpacing) { + if ( + Math.abs(height - mouseY - 0.0001) > + boundsHeight - + scaleY(currentBin.reserveTokenMap) + + (scaleY(currentBin.reserveTokenMap) > height - 3 && + scaleY(currentBin.reserveTokenMap) !== height + ? 3 + : 0) + ) { + setPositionX(null); + setPositionY(null); setTooltipInfo(null); + return; } - return; - } + const minTick = currentBin.minTick + defaultMinX; + const maxTick = currentBin.maxTick + defaultMinX; - if ( - Math.abs(height - mouseY - 0.0001) > - boundsHeight - - scaleY(currentBin.reserveTokenMap) + - (scaleY(currentBin.reserveTokenMap) > height - 3 && - scaleY(currentBin.reserveTokenMap) !== height - ? 3 - : 0) - ) { - setPositionX(null); - setPositionY(null); - setTooltipInfo(null); - return; - } - const minTick = currentBin.minTick + defaultMinX; - const maxTick = currentBin.maxTick + defaultMinX; - - const minTickSwap = currentBin.minTickSwap + defaultMinX; - const maxTickSwap = currentBin.maxTickSwap + defaultMinX; - - const tokenARange = { - min: tickOfPrices[!isSwap ? minTick : minTickSwap] || null, - max: tickOfPrices[!isSwap ? maxTick : maxTickSwap] || null, - }; - const tokenBRange = { - min: tickOfPrices[!isSwap ? -minTick : -minTickSwap] || null, - max: tickOfPrices[!isSwap ? -maxTick : -maxTickSwap] || null, - }; - const index = currentBin.index; - - const tokenAAmountStr = currentBin.reserveTokenA; - const tokenBAmountStr = currentBin.reserveTokenB; - const myTokenAAmountStr = currentBin?.reserveTokenAMyAmount; - const myTokenBAmountStr = currentBin?.reserveTokenBMyAmount; - const tickSpacing = getTickSpacing(); - let isBlackBar = !!( - maxTickPosition && - minTickPosition && - (scaleX(currentBin.minTick) < minTickPosition - tickSpacing || - scaleX(currentBin.minTick) > maxTickPosition) - ); - if (isSwap) { - isBlackBar = !!( + const minTickSwap = currentBin.minTickSwap + defaultMinX; + const maxTickSwap = currentBin.maxTickSwap + defaultMinX; + + const tokenARange = { + min: tickOfPrices[!isReversed ? minTick : minTickSwap] || null, + max: tickOfPrices[!isReversed ? maxTick : maxTickSwap] || null, + }; + const tokenBRange = { + min: tickOfPrices[!isReversed ? -minTick : -minTickSwap] || null, + max: tickOfPrices[!isReversed ? -maxTick : -maxTickSwap] || null, + }; + const index = currentBin.index; + + const tokenAAmountStr = currentBin.reserveTokenA; + const tokenBAmountStr = currentBin.reserveTokenB; + const depositTokenAAmountStr = currentBin?.reserveTokenAMyAmount; + const depositTokenBAmountStr = currentBin?.reserveTokenBMyAmount; + let disabledBin = !!( maxTickPosition && minTickPosition && - (scaleX(currentBin.minTick) < - scaleX(maxX) - maxTickPosition - tickSpacing || - scaleX(currentBin.minTick) > scaleX(maxX) - minTickPosition) + (scaleX(currentBin.minTick) < minTickPosition - binSpacing || + scaleX(currentBin.minTick) > maxTickPosition) ); - } - setTooltipInfo({ - tokenA: tokenA, - tokenB: tokenB, - tokenAAmount: tokenAAmountStr + if (isReversed) { + disabledBin = !!( + maxTickPosition && + minTickPosition && + (scaleX(currentBin.minTick) < + scaleX(maxX) - maxTickPosition - binSpacing || + scaleX(currentBin.minTick) > scaleX(maxX) - minTickPosition) + ); + } + + const tokenAAmount = tokenAAmountStr ? formatTokenExchangeRate(tokenAAmountStr.toString(), { maxSignificantDigits: 6, minLimit: 0.000001, }) - : "-", - tokenBAmount: tokenBAmountStr + : "-"; + const tokenBAmount = tokenBAmountStr ? formatTokenExchangeRate(tokenBAmountStr.toString(), { maxSignificantDigits: 6, minLimit: 0.000001, }) - : "-", - myTokenAAmount: + : "-"; + const depositTokenAAmount = index < 20 ? "-" : index > 19 && `${currentBin.reserveTokenAMyAmount}` === "0" ? "<0.000001" - : formatTokenExchangeRate(myTokenAAmountStr.toString(), { + : formatTokenExchangeRate(depositTokenAAmountStr.toString(), { maxSignificantDigits: 6, minLimit: 0.000001, - }) || "-", - myTokenBAmount: + }) || "-"; + const depositTokenBAmount = index > 19 ? "-" : index < 20 && `${currentBin.reserveTokenBMyAmount}` === "0" ? "<0.000001" - : formatTokenExchangeRate(myTokenBAmountStr.toString(), { + : formatTokenExchangeRate(depositTokenBAmountStr.toString(), { maxSignificantDigits: 6, minLimit: 0.000001, - }) || "-", - tokenARange: tokenARange, - tokenBRange: tokenBRange, - tokenAPrice: tickOfPrices[currentTick || 0], - tokenBPrice: tickOfPrices[-(currentTick || 0)], - isBlackBar, - }); - setPositionX(mouseX); - setPositionY(mouseY); - } + }) || "-"; + + setTooltipInfo({ + tokenA, + tokenB, + tokenAAmount, + tokenBAmount, + depositTokenAAmount, + depositTokenBAmount, + tokenARange: tokenARange, + tokenBRange: tokenBRange, + tokenAPrice: tickOfPrices[currentTick || 0], + tokenBPrice: tickOfPrices[-(currentTick || 0)], + disabled: disabledBin, + }); + setPositionX(mouseX); + setPositionY(mouseY); + }, + [mouseover, tickOfPrices, reservedBins, binSpacing, currentTick], + ); - function onMouseoutChartBin() { + function onMouseOutChartBin() { setPositionX(null); setPositionY(null); } - function onMouseoverClear(event: MouseEvent) { - const { clientX, clientY } = event; - if (!svgRef.current?.getClientRects()[0]) { - setTooltipInfo(null); - return; - } - const { left, right, top, bottom } = svgRef.current?.getClientRects()[0]; - if ( - clientX < left || - clientX > right || - clientY < top || - clientY > bottom - ) { - setTooltipInfo(null); - } - } useEffect(() => { if (bins.length > 0) { new Promise<{ [key in number]: string }>(resolve => { @@ -521,43 +388,10 @@ const PoolGraph: React.FC = ({ } }, [bins]); - useEffect(() => { - // D3 - Draw bin and define interaction - const svgElement = d3 - .select(svgRef.current) - .attr("width", width) - .attr("height", height) - .attr("viewBox", [0, 0, width, height]) - .attr("style", "max-width: 100%; height: auto;") - .on("mousemove", onMouseoverChartBin) - .on("mouseout", onMouseoutChartBin); - - if (svgElement.select("defs").empty()) { - svgElement - .append("defs") - .append("clipPath") - .attr("id", "clip") - .append("rect") - .attr("width", width) - .attr("height", height); - } - - if (!!width && !!height && !!scaleX && !!scaleY) { - updateChart(); - } - }, [width, height, scaleX, scaleY]); - - useEffect(() => { - if (tooltipInfo) { - window.addEventListener("scroll", onMouseoutChartBin); - return () => window.removeEventListener("scroll", onMouseoutChartBin); - } - }, [tooltipInfo]); - useEffect(() => { if (tooltipInfo) { - window.addEventListener("mousemove", onMouseoverClear); - return () => window.removeEventListener("mousemove", onMouseoverClear); + window.addEventListener("scroll", onMouseOutChartBin); + return () => window.removeEventListener("scroll", onMouseOutChartBin); } }, [tooltipInfo]); @@ -569,251 +403,47 @@ const PoolGraph: React.FC = ({ position={tooltipPosition} offset={offset} content={ - showBar && tooltipInfo ? ( - - - + ) : null } > - - - - - - - - - - - - - + ); }; export default React.memo(PoolGraph); - -interface PoolGraphBinTooptipProps { - tooltipInfo: TooltipInfo | null; - isPosition: boolean; -} - -export const PoolGraphBinTooptip: React.FC = ({ - tooltipInfo, - isPosition, -}) => { - const { t } = useTranslation(); - - const tokenAPriceString = useMemo(() => { - if (tooltipInfo === null) { - return "-"; - } - const { tokenAPrice, tokenB } = tooltipInfo; - if (!tokenAPrice) { - return "-"; - } - return `${tokenAPrice} ${tokenB.symbol}`; - }, [tooltipInfo?.tokenB, tooltipInfo?.tokenAPrice]); - - const tokenBPriceString = useMemo(() => { - if (tooltipInfo === null) { - return "-"; - } - const { tokenBPrice, tokenA } = tooltipInfo; - if (!tokenBPrice) { - return "-"; - } - return `${tokenBPrice} ${tokenA.symbol}`; - }, [tooltipInfo?.tokenA, tooltipInfo?.tokenBPrice]); - - const tokenAPriceRangeStr = useMemo(() => { - if (tooltipInfo === null) { - return "-"; - } - const { tokenARange, tokenB } = tooltipInfo; - if (tokenARange?.min === null || tokenARange?.max === null) { - return "-"; - } - return `${tokenARange.min} - ${tokenARange.max} ${tokenB.symbol}`; - }, [tooltipInfo?.tokenB, tooltipInfo?.tokenARange]); - - const tokenBPriceRangeStr = useMemo(() => { - if (tooltipInfo === null) { - return "-"; - } - const { tokenBRange, tokenA } = tooltipInfo; - if (tokenBRange?.min === null || tokenBRange?.max === null) { - return "-"; - } - return `${tokenBRange.max} - ${tokenBRange.min} ${tokenA.symbol}`; - }, [tooltipInfo?.tokenA, tooltipInfo?.tokenBRange]); - - return tooltipInfo ? ( -
-
-
- {t("common:poolGraph.tooltip.quote")} - {t("business:currentPrice")} -
-
-
-
- - - - {tooltipInfo.tokenA.symbol} {t("common:price")} - - - {tokenAPriceString} -
-
- - - - {tooltipInfo.tokenB.symbol} {t("common:price")} - - - {tokenBPriceString} -
-
-
-
- {t("business:token")} - - {t("common:poolGraph.tooltip.totalAmt")} - - {isPosition && !tooltipInfo.isBlackBar ? ( - - {t("common:poolGraph.tooltip.positionAmt")} - - ) : ( - "" - )} - - {t("common:poolGraph.tooltip.priceRange")} - -
-
-
-
- - - {tooltipInfo.tokenA.symbol} - - - - - {isPosition && !tooltipInfo.isBlackBar ? ( - - - {tooltipInfo.myTokenAAmount || "0"} - - - ) : ( - "" - )} - 21 ? "small-font" : "" - }`} - > - {tokenAPriceRangeStr} - -
-
- - - {tooltipInfo.tokenB.symbol} - - - - - {isPosition && !tooltipInfo.isBlackBar ? ( - - - {tooltipInfo.myTokenBAmount || "0"} - - - ) : ( - "" - )} - 21 ? "small-font" : "" - }`} - > - {tokenBPriceRangeStr} - -
-
-
- ) : ( - <> - ); -}; diff --git a/packages/web/src/components/common/pool-graph/PoolGraph.types.ts b/packages/web/src/components/common/pool-graph/PoolGraph.types.ts new file mode 100644 index 000000000..2c37b29fc --- /dev/null +++ b/packages/web/src/components/common/pool-graph/PoolGraph.types.ts @@ -0,0 +1,36 @@ +import { TokenModel } from "@models/token/token-model"; + +export interface TooltipInfo { + tokenA: TokenModel; + tokenB: TokenModel; + tokenAAmount: string | null; + tokenBAmount: string | null; + depositTokenAAmount: string | null; + depositTokenBAmount: string | null; + tokenARange: { + min: string | null; + max: string | null; + }; + tokenBRange: { + min: string | null; + max: string | null; + }; + tokenAPrice: string; + tokenBPrice: string; + disabled?: boolean; +} + +export interface ReservedBin { + minTick: number; + maxTick: number; + reserveTokenMap: number; + minTickSwap: number; + maxTickSwap: number; + reserveTokenAMyAmount: number; + reserveTokenBMyAmount: number; + reserveTokenAMap: number; + index: number; + liquidity: number; + reserveTokenA: number; + reserveTokenB: number; +} diff --git a/packages/web/src/components/common/pool-graph/pool-graph-svg/PoolGraphSVG.styles.ts b/packages/web/src/components/common/pool-graph/pool-graph-svg/PoolGraphSVG.styles.ts new file mode 100644 index 000000000..35ec94aff --- /dev/null +++ b/packages/web/src/components/common/pool-graph/pool-graph-svg/PoolGraphSVG.styles.ts @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; + +export const PoolGraphSVGContainer = styled.svg` + display: flex; + flex-direction: column; + width: 100%; + height: auto; +`; diff --git a/packages/web/src/components/common/pool-graph/pool-graph-svg/PoolGraphSVG.tsx b/packages/web/src/components/common/pool-graph/pool-graph-svg/PoolGraphSVG.tsx new file mode 100644 index 000000000..6da4ca524 --- /dev/null +++ b/packages/web/src/components/common/pool-graph/pool-graph-svg/PoolGraphSVG.tsx @@ -0,0 +1,298 @@ +import React, { + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useRef, +} from "react"; +import * as d3 from "d3"; + +import { useColorGraph } from "@hooks/common/use-color-graph"; +import { ThemeKeys } from "@styles/ThemeTypes"; + +import { ReservedBin } from "../PoolGraph.types"; +import { PoolGraphSVGContainer } from "./PoolGraphSVG.styles"; + +interface PoolGraphSVGProps { + graphId: string; + width: number; + height: number; + margin?: { + left: number; + right: number; + top: number; + bottom: number; + }; + currentTick?: number | null; + reservedBins: ReservedBin[]; + maxTickPosition?: number | null; + minTickPosition?: number | null; + binSpacing: number; + isReversed: boolean; + disabled?: boolean; + themeKey: ThemeKeys; + scaleX: d3.ScaleLinear; + scaleY: d3.ScaleLinear; + d3Position: { + defaultMinX: number; + minX: number; + maxX: number; + }; + onMouseEnter?: (event: React.MouseEvent | React.TouchEvent) => void; + onMouseLeave?: (event: React.MouseEvent) => void; + onTouchStart?: (event: React.TouchEvent) => void; + onMouseMove: (event: MouseEvent) => void; + onMouseOut: () => void; +} + +const PoolGraphSVG = forwardRef( + ( + { + graphId, + width, + height, + margin = { + left: 0, + right: 0, + top: 0, + bottom: 0, + }, + currentTick, + binSpacing, + maxTickPosition = 0, + minTickPosition = 0, + reservedBins, + themeKey, + isReversed, + disabled, + scaleX, + scaleY, + d3Position, + onMouseEnter, + onMouseLeave, + onTouchStart, + onMouseMove, + onMouseOut, + }, + forwardedRef, + ) => { + const svgRef = useRef(null); + useImperativeHandle(forwardedRef, () => svgRef.current as SVGSVGElement); + + const chartRef = useRef(null); + const { redColor, greenColor } = useColorGraph(); + + const boundsWidth = width - margin.right - margin.left; + const boundsHeight = height - margin.top - margin.bottom; + + // D3 - Dimension Definition + const { defaultMinX, minX, maxX } = d3Position; + const centerX = currentTick ?? (minX && maxX ? (minX + maxX) / 2 : 0); + + /** Update Chart by data */ + function updateChart() { + const centerPosition = scaleX(centerX - defaultMinX) - binSpacing / 2; + + // Retrieves the colour of the chart bar at the current tick. + function fillByBin(bin: ReservedBin) { + let isBlackBar = !!( + maxTickPosition && + minTickPosition && + (scaleX(bin.minTick) < minTickPosition - binSpacing || + scaleX(bin.minTick) > maxTickPosition) + ); + + if (isReversed) { + isBlackBar = !!( + maxTickPosition && + minTickPosition && + (scaleX(bin.minTick) < + scaleX(maxX) - maxTickPosition - binSpacing || + scaleX(bin.minTick) > scaleX(maxX) - minTickPosition) + ); + } + if (isBlackBar) return themeKey === "dark" ? "#1C2230" : "#E0E8F4"; + if (currentTick && bin.minTick < Number(currentTick - defaultMinX)) { + return `url(#gradient-bar-green-${graphId})`; + } + return `url(#gradient-bar-red-${graphId})`; + } + + // Create a chart bar. + const rects = d3.select(chartRef.current); + rects.attr("clip-path", "url(#clip)"); + + // D3 - Draw Current tick (middle line) + rects.select("line").remove(); + if (currentTick && rects.select("line").empty()) { + rects + .append("line") + .attr("x1", centerPosition + binSpacing / 2) + .attr("x2", centerPosition + binSpacing / 2) + .attr("y1", 0) + .attr("y2", boundsHeight) + .attr("stroke-dasharray", 3) + .attr("stroke", `${themeKey === "dark" ? "#E0E8F4" : "#596782"}`) + .attr("stroke-width", 1); + } + + // D3 - Draw reservedBins as bars + rects.selectAll("g").remove(); + if (!disabled && rects.selectAll("g").size() === 0) { + rects + .selectAll("rects") + .data(reservedBins) + .enter() + .append("g") + .attr("class", "bin-wrapper") + .attr("id", bin => getBinId(bin.index)) + .each(function (bin) { + d3.select(this) + .append("rect") + .style("fill", "transparent") + .attr("class", "bin-inner") + .style("stroke-width", "0") + .attr("x", scaleX(bin.minTick)) + .attr("width", scaleX(bin.maxTick - bin.minTick)) + .attr("y", () => { + const scaleYComputation = scaleY(bin.reserveTokenMap) ?? 0; + return ( + scaleYComputation - + (scaleYComputation > height - 3 && + scaleYComputation !== height + ? 3 + : 0) + ); + }) + .attr("height", () => { + const scaleYComputation = scaleY(bin.reserveTokenMap) ?? 0; + return ( + boundsHeight - + scaleYComputation + + (scaleYComputation > height - 3 && + scaleYComputation !== height + ? 3 + : 0) + ); + }); + + const heightPadding = 3; + d3.select(this) + .append("rect") + .style("fill", fillByBin(bin)) + .attr("class", "bin-inner") + .style("stroke-width", "0") + .attr("x", scaleX(bin.minTick) + 1) + .attr("width", scaleX(bin.maxTick - bin.minTick) - 0.5) + .attr("y", () => { + const scaleYComputation = scaleY(bin.reserveTokenMap) ?? 0; + return ( + scaleYComputation - + (scaleYComputation > height - 3 && + scaleYComputation !== height + ? 3 + : 0) + ); + }) + .attr("height", () => { + const scaleYComputation = scaleY(bin.reserveTokenMap) ?? 0; + return ( + boundsHeight - + scaleYComputation + + (scaleYComputation > height - heightPadding && + scaleYComputation !== height + ? heightPadding + : 0) + ); + }); + }); + } + } + + const getBinId = useCallback( + (index: number) => `pool-graph-bin-${graphId}-${index}`, + [graphId], + ); + + useEffect(() => { + // D3 - Draw bin and define interaction + if (!svgRef?.current) { + return; + } + + const svgElement = d3 + .select(svgRef?.current) + .attr("width", width) + .attr("height", height) + .attr("viewBox", [0, 0, width, height]) + .attr("style", "max-width: 100%; height: auto;") + .on("mousemove", onMouseMove) + .on("mouseout", onMouseOut); + + if (svgElement.select("defs").empty()) { + svgElement.selectAll("defs").remove(); + } + + svgElement + .append("defs") + .append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + if (!!width && !!height && !!chartRef.current) { + updateChart(); + } + }, [ + width, + height, + reservedBins, + svgRef?.current, + chartRef?.current, + onMouseMove, + ]); + + return ( + + + + + + + + + + + + + + + ); + }, +); + +PoolGraphSVG.displayName = "PoolGraphSVG"; +export default PoolGraphSVG; diff --git a/packages/web/src/components/common/pool-graph/pool-graph-tooltip/PoolGraphTooltip.styles.ts b/packages/web/src/components/common/pool-graph/pool-graph-tooltip/PoolGraphTooltip.styles.ts new file mode 100644 index 000000000..88f439d6f --- /dev/null +++ b/packages/web/src/components/common/pool-graph/pool-graph-tooltip/PoolGraphTooltip.styles.ts @@ -0,0 +1,141 @@ +import { fonts } from "@constants/font.constant"; +import styled from "@emotion/styled"; +import { media } from "@styles/media"; + +export const PoolGraphTooltipContainer = styled.div` + & { + display: flex; + flex-direction: column; + background-color: ${({ theme }) => theme.color.background02}; + align-items: flex-start; + border-radius: 8px; + gap: 8px; + ${fonts.body12}; + line-height: 1em; + + .row { + display: flex; + flex-direction: row; + width: 100%; + gap: 8px; + + & > span { + display: flex; + flex-direction: row; + align-items: center; + + &.price-range { + justify-content: flex-end; + } + + &.token-title { + ${media.mobile} { + display: none; + } + } + } + } + + .header { + display: flex; + flex-direction: column; + color: ${({ theme }) => theme.color.text04}; + justify-content: space-between; + width: 100%; + + &.mt-8 { + margin-top: 8px; + } + + .row { + padding: 3px 0; + } + } + + .content { + display: flex; + flex-direction: column; + color: ${({ theme }) => theme.color.text02}; + gap: 8px; + width: 100%; + + .row { + padding: 4px 0; + } + + &:last-of-type { + .token { + ${media.mobile} { + display: none; + } + } + } + } + + .token { + flex-shrink: 0; + min-width: 80px; + gap: 8px; + + img { + width: 20px; + height: 20px; + } + ${media.mobile} { + display: none; + } + } + .amount { + flex-shrink: 0; + min-width: 76px; + + & .token-amount-value { + display: inline; + overflow: hidden; + text-overflow: clip; + white-space: nowrap; + word-break: break-all; + } + + & .small-font { + font-size: 10px; + } + + &.w-100 { + min-width: 108px; + } + + img { + width: 20px; + height: 20px; + display: none; + } + + ${media.mobile} { + &.total-amount { + width: 85px; + gap: 8px; + } + + img { + display: block; + } + } + } + + .price-range { + width: 100%; + } + .small-font { + font-size: 12px; + } + + ${media.mobile} { + max-width: max-content; + } + + @media (max-width: 360px) { + max-width: 336px; + } + } +`; diff --git a/packages/web/src/components/common/pool-graph/pool-graph-tooltip/PoolGraphTooltip.tsx b/packages/web/src/components/common/pool-graph/pool-graph-tooltip/PoolGraphTooltip.tsx new file mode 100644 index 000000000..3ec5e9493 --- /dev/null +++ b/packages/web/src/components/common/pool-graph/pool-graph-tooltip/PoolGraphTooltip.tsx @@ -0,0 +1,239 @@ +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; + +import MissingLogo from "@components/common/missing-logo/MissingLogo"; + +import { TooltipInfo } from "../PoolGraph.types"; +import { PoolGraphTooltipContainer } from "./PoolGraphTooltip.styles"; + +function makeClassNameWithSmallFont( + className: string, + target: string, + limitLength = 21, +) { + const additionalClassName = "small-font"; + if (target.length > limitLength) { + return `${className} ${additionalClassName}`; + } + return className; +} + +export interface PoolGraphTooltipProps { + tooltipInfo: TooltipInfo | null; + isPosition: boolean; + disabled?: boolean; +} + +const PoolGraphTooltip: React.FC> = ({ + tooltipInfo, + isPosition, + disabled, +}) => { + const { t } = useTranslation(); + + const displayTooltipInfo: { + tokenAPrice: string; + tokenBPrice: string; + tokenAPriceRange: string; + tokenBPriceRange: string; + totalTokenAAmount: string; + totalTokenBAmount: string; + depositTokenAAmount: string; + depositTokenBAmount: string; + } = useMemo(() => { + if (!tooltipInfo) { + return { + tokenAPrice: "-", + tokenBPrice: "-", + tokenAPriceRange: "-", + tokenBPriceRange: "-", + totalTokenAAmount: "-", + totalTokenBAmount: "-", + depositTokenAAmount: "-", + depositTokenBAmount: "-", + }; + } + + const { + tokenA, + tokenB, + tokenAPrice, + tokenBPrice, + tokenARange, + tokenBRange, + tokenAAmount, + tokenBAmount, + depositTokenAAmount, + depositTokenBAmount, + } = tooltipInfo; + + return { + tokenAPrice: `${tokenAPrice} ${tokenB.symbol}`, + tokenBPrice: `${tokenBPrice} ${tokenA.symbol}`, + tokenAPriceRange: `${tokenARange.min} - ${tokenARange.max} ${tokenB.symbol}`, + tokenBPriceRange: `${tokenBRange.max} - ${tokenBRange.min} ${tokenA.symbol}`, + totalTokenAAmount: tokenAAmount || "0", + totalTokenBAmount: tokenBAmount || "0", + depositTokenAAmount: depositTokenAAmount || "0", + depositTokenBAmount: depositTokenBAmount || "0", + }; + }, [tooltipInfo]); + + const isDisplayPositionAmount = useMemo(() => { + if (!!tooltipInfo?.disabled) { + return false; + } + return isPosition; + }, [isPosition, tooltipInfo?.disabled]); + + if (!tooltipInfo || disabled) { + return ; + } + + return ( + +
+
+ {t("common:poolGraph.tooltip.quote")} + {t("business:currentPrice")} +
+
+ +
+
+ + + + {tooltipInfo.tokenA.symbol} {t("common:price")} + + + + {displayTooltipInfo.tokenAPriceRange} + +
+ +
+ + + + {tooltipInfo.tokenB.symbol} {t("common:price")} + + + + {displayTooltipInfo.tokenBPriceRange} + +
+
+ +
+
+ {t("business:token")} + + {t("common:poolGraph.tooltip.totalAmt")} + + {isDisplayPositionAmount && ( + + {t("common:poolGraph.tooltip.positionAmt")} + + )} + + {t("common:poolGraph.tooltip.priceRange")} + +
+
+ +
+
+ + + {tooltipInfo.tokenA.symbol} + + + + {displayTooltipInfo.totalTokenAAmount} + + + + {isDisplayPositionAmount && ( + + + {displayTooltipInfo.depositTokenAAmount} + + + )} + + {displayTooltipInfo.tokenAPriceRange} + +
+ +
+ + + {tooltipInfo.tokenB.symbol} + + + + {displayTooltipInfo.totalTokenBAmount} + + + {isDisplayPositionAmount && ( + + + {displayTooltipInfo.depositTokenBAmount} + + + )} + + {displayTooltipInfo.tokenBPriceRange} + +
+
+
+ ); +}; + +export default PoolGraphTooltip; diff --git a/packages/web/src/views/earn/components/incentivized-pool-card-list/incentivized-pool-card/IncentivizedPoolCard.tsx b/packages/web/src/views/earn/components/incentivized-pool-card-list/incentivized-pool-card/IncentivizedPoolCard.tsx index 32b8438f5..69dcb7b10 100644 --- a/packages/web/src/views/earn/components/incentivized-pool-card-list/incentivized-pool-card/IncentivizedPoolCard.tsx +++ b/packages/web/src/views/earn/components/incentivized-pool-card-list/incentivized-pool-card/IncentivizedPoolCard.tsx @@ -8,7 +8,7 @@ import OverlapTokenLogo from "@components/common/overlap-token-logo/OverlapToken import PoolGraph from "@components/common/pool-graph/PoolGraph"; import { INCENTIVE_TYPE_MAPPER, - SwapFeeTierInfoMap + SwapFeeTierInfoMap, } from "@constants/option.constant"; import { useGnotToGnot } from "@hooks/token/use-gnot-wugnot"; import { IncentivizePoolCardInfo } from "@models/pool/info/pool-card-info"; @@ -17,7 +17,7 @@ import { numberToFormat } from "@utils/string-utils"; import { PoolCardWrapper, - PoolCardWrapperWrapperBorder + PoolCardWrapperWrapperBorder, } from "./IncentivizedPoolCard.styles"; export interface IncentivizedPoolCardProps { @@ -173,7 +173,7 @@ const IncentivizedPoolCard: React.FC = ({ position="top" offset={40} poolPrice={pool?.price || 1} - showBar={!isHideBar} + disabled={isHideBar} />
{"Current Price"} diff --git a/packages/web/src/views/earn/components/pool-list/pool-list-table/pool-info/pool-info-lazy-chart/PoolInfoLazyChart.tsx b/packages/web/src/views/earn/components/pool-list/pool-list-table/pool-info/pool-info-lazy-chart/PoolInfoLazyChart.tsx index 0ba653b0d..d489fc1b0 100644 --- a/packages/web/src/views/earn/components/pool-list/pool-list-table/pool-info/pool-info-lazy-chart/PoolInfoLazyChart.tsx +++ b/packages/web/src/views/earn/components/pool-list/pool-list-table/pool-info/pool-info-lazy-chart/PoolInfoLazyChart.tsx @@ -91,7 +91,7 @@ const PoolInfoLazyChart: React.FC = ({ currentTick={currentTick} bins={bins ?? []} mouseover - showBar={!isHideBar} + disabled={isHideBar} themeKey={themeKey} position="top" nextSpacing={false} diff --git a/packages/web/src/views/pool/pool-detail/components/my-liquidity/my-detailed-position-card/MyDetailedPositionCard.tsx b/packages/web/src/views/pool/pool-detail/components/my-liquidity/my-detailed-position-card/MyDetailedPositionCard.tsx index 45b44bac6..051865ac0 100644 --- a/packages/web/src/views/pool/pool-detail/components/my-liquidity/my-detailed-position-card/MyDetailedPositionCard.tsx +++ b/packages/web/src/views/pool/pool-detail/components/my-liquidity/my-detailed-position-card/MyDetailedPositionCard.tsx @@ -1049,8 +1049,8 @@ const MyDetailedPositionCard: React.FC = ({ minTickPosition={minTickPosition} maxTickPosition={maxTickPosition} binsMyAmount={positionBin} - isSwap={isSwap} - showBar={!isHideBar} + isReversed={isSwap} + disabled={isHideBar} /> )} {loading && ( diff --git a/packages/web/src/views/pool/pool-detail/components/pool-pair-information/pool-pair-info-content/PoolPairInfoContent.tsx b/packages/web/src/views/pool/pool-detail/components/pool-pair-information/pool-pair-info-content/PoolPairInfoContent.tsx index 8c7bc2732..a20c54994 100644 --- a/packages/web/src/views/pool/pool-detail/components/pool-pair-information/pool-pair-info-content/PoolPairInfoContent.tsx +++ b/packages/web/src/views/pool/pool-detail/components/pool-pair-information/pool-pair-info-content/PoolPairInfoContent.tsx @@ -34,7 +34,7 @@ import { PoolPairInfoContentWrapper, TokenAmountTooltipContentWrapper, TvlSectionWrapper, - VolumeSectionWrapper + VolumeSectionWrapper, } from "./PoolPairInfoContent.styles"; import TooltipAPR from "./TooltipAPR"; @@ -233,18 +233,20 @@ const PoolPairInfoContent: React.FC = ({ }, [getGnotPath, pool.tokenA, pool.tokenB]); const stakeLogo = useMemo(() => { - return pool?.rewardTokens?.reduce((accum : TokenModel[], current:TokenModel) => { - const index = accum.findIndex( - inserted => getGnotPath(inserted).path === getGnotPath(current).path, - ); - if (index === -1) { - accum.push(current); - } - return accum; - }, []).map(item => ({ - ...item, - ...getGnotPath(item), - })); + return pool?.rewardTokens + ?.reduce((accum: TokenModel[], current: TokenModel) => { + const index = accum.findIndex( + inserted => getGnotPath(inserted).path === getGnotPath(current).path, + ); + if (index === -1) { + accum.push(current); + } + return accum; + }, []) + .map(item => ({ + ...item, + ...getGnotPath(item), + })); }, [getGnotPath, pool?.rewardTokens]); const isHideBar = useMemo(() => { @@ -526,7 +528,7 @@ const PoolPairInfoContent: React.FC = ({ position="top" offset={40} poolPrice={tickToPrice(pool.currentTick) || 1} - showBar={!isHideBar} + disabled={isHideBar} /> )} {loadingBins && ( From e512870aa79b9c000d374229e08bf15c98f71def Mon Sep 17 00:00:00 2001 From: jinoosss Date: Thu, 22 Aug 2024 21:53:05 +0900 Subject: [PATCH 2/2] fix: modify the tooltip update conditions --- .../common/pool-graph/PoolGraph.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/web/src/components/common/pool-graph/PoolGraph.tsx b/packages/web/src/components/common/pool-graph/PoolGraph.tsx index f6fe647a4..54475df68 100644 --- a/packages/web/src/components/common/pool-graph/PoolGraph.tsx +++ b/packages/web/src/components/common/pool-graph/PoolGraph.tsx @@ -207,9 +207,6 @@ const PoolGraph: React.FC = ({ if (mouseY < 0.000001 || mouseY > height) { return false; } - if (bin.reserveTokenMap < 0 || !bin.reserveTokenMap) { - return false; - } const isHoveringCurrentBin = document .getElementById(getBinId(bin.index)) ?.matches(":hover"); @@ -235,25 +232,28 @@ const PoolGraph: React.FC = ({ return bin.index === hoveredBinIndex; }); - if (currentBin?.index) { - if (currentBin.index !== lastHoverBinIndexRef.current) { - lastHoverBinIndexRef.current = currentBin.index; - } else { - setPositionX(mouseX); - setPositionY(mouseY); - return; - } - } - if (!currentBin) { + lastHoverBinIndexRef.current = -1; setPositionX(null); setPositionY(null); + if (!nextSpacing) { setTooltipInfo(null); } return; } + // Only updates the position when the hovered area is the same bar and has tooltip information. + if (currentBin.index === lastHoverBinIndexRef.current) { + if (tooltipInfo) { + setPositionX(mouseX); + setPositionY(mouseY); + return; + } + } + + lastHoverBinIndexRef.current = currentBin.index; + if ( Math.abs(height - mouseY - 0.0001) > boundsHeight -