diff --git a/package.json b/package.json index fb55b85..31aa010 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@tanstack/react-virtual": "^3.0.0-beta.48", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/src/App.js b/src/App.js index c52a2fe..e762ef8 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef, useMemo, useLayoutEffect } from "react"; import './App.css'; @@ -6,7 +6,7 @@ import { genbankToJson } from "bio-parsers"; import { useMeasure } from 'react-use'; // or just 'react-use-measure' import {FaSearch,FaTimes} from 'react-icons/fa'; import {DebounceInput} from 'react-debounce-input'; - +import { useVirtualizer, useWindowVirtualizer} from '@tanstack/react-virtual'; const Tooltip = ({ hoveredInfo }) => { const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); @@ -193,12 +193,8 @@ const codonToAminoAcid = (codon) => { } }; -const SingleRow = ({ parsedSequence, rowStart, rowEnd, setHoveredInfo, rowId, searchInput, renderProperly }) => { - if (!renderProperly) - { - return
- } +const SingleRow = ({ parsedSequence, rowStart, rowEnd, setHoveredInfo, rowId, searchInput }) => { + const isSelected = searchInput>=rowStart && searchInput<=rowEnd; if(isSelected){ @@ -209,6 +205,7 @@ const SingleRow = ({ parsedSequence, rowStart, rowEnd, setHoveredInfo, rowId, se const fullSequence = parsedSequence.sequence; const rowSequence = fullSequence.slice(rowStart, rowEnd); + const relevantFeatures = parsedSequence.features.filter( (feature) => ( feature.type !== "source" && @@ -393,7 +390,7 @@ const SingleRow = ({ parsedSequence, rowStart, rowEnd, setHoveredInfo, rowId, se > {codon.aminoAcid} - + {codon.codonIndex+1} @@ -547,7 +544,7 @@ function App() { }; }, []); const [ref, { width }] = useMeasure(); - //console.log("width", width); + const [hoveredInfo, setHoveredInfo] = useState(null); const [genbankData, setGenbankData] = useState(null); const [searchInput, setSearchInput] = useState(null); @@ -614,41 +611,76 @@ function App() { if (rowWidth < 40) { rowWidth = 40; } + //console.log("rowWidth", rowWidth); + + + + let fullSequence, sequenceLength; + if(genbankData){ + fullSequence = genbankData.parsedSequence.sequence; + sequenceLength = fullSequence.length; + } + + + const rowData = useMemo(() => { + if (!fullSequence) return []; + const rowData = []; + + + + for (let i = 0; i < sequenceLength; i += rowWidth) { + + rowData.push({ + rowStart: i, + rowEnd: (i + rowWidth > sequenceLength ? sequenceLength : i + rowWidth) + }); + } + return rowData; +} +, [fullSequence, rowWidth,sequenceLength]); + + + const parentRef = useRef(null); + const parentOffsetRef = useRef(0); + + useLayoutEffect(() => { + parentOffsetRef.current = parentRef.current?.offsetTop ?? 0 + }, []) + + + + + const rowVirtualizer = useWindowVirtualizer({ + count: rowData.length, + estimateSize: () => 60, + scrollMargin: parentOffsetRef.current, + }) + + + + const virtualItems = rowVirtualizer.getVirtualItems(); - // useEffect useEffect(() => { if(!intSearchInput) return; const row = Math.floor(intSearchInput / rowWidth); - console.log("row", row); + rowVirtualizer.scrollToIndex(row, {align:"center", + smoothScroll:true}); - setTimeout(() => { - const rowElement = document.getElementById(`row-${row}`); - if (!rowElement) return; - rowElement.scrollIntoView({ behavior: "smooth", block: "center" }); - }, 100); - setTimeout(() => { - const rowElement = document.getElementById(`row-${row}`); - if (!rowElement) return; - rowElement.scrollIntoView({ behavior: "smooth", block: "center" }); - }, 1000); }, [intSearchInput]); + + //console.log("virtualItems", virtualItems); + if (!genbankData ) { return
Loading...
; } - const rowData = []; - const fullSequence = genbankData.parsedSequence.sequence; - const sequenceLength = fullSequence.length; - for (let i = 0; i < sequenceLength; i += rowWidth) { - rowData.push({ - rowStart: i, - rowEnd: (i + rowWidth > sequenceLength ? sequenceLength : i + rowWidth) - }); - } + + + if(!width){ return ( @@ -676,10 +708,10 @@ function App() { -
+
{genbankData && ( -
+

{genbankData.parsedSequence.name}

@@ -689,18 +721,47 @@ function App() {
-
- {rowData.map((row, index) => ( - +
+
+ + {virtualItems.map((virtualitem) => { + const row = rowData[virtualitem.index]; + //return (
{genbankData.parsedSequence.sequence.slice(row.start,row.end)}
) + return ( +
+ =intSearchInput && row.rowEnd<=intSearchInput)} + renderProperly={true} /> - ))} +
) + } + ) +} +
+
diff --git a/yarn.lock b/yarn.lock index 1c880a8..d9ca71f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1771,6 +1771,18 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@tanstack/react-virtual@^3.0.0-beta.48": + version "3.0.0-beta.48" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.48.tgz#c5055f78b84bf5cb37ee3ca612edf3b19ddf7e10" + integrity sha512-zNNEtCF5clBLhqvUZPfmGaOiWrqdgIc7IzrT/HiAKFkFTF4R30b/mfY4+wCJ9tW95Tvs1Ml4THUA3s/vIHMHQg== + dependencies: + "@tanstack/virtual-core" "3.0.0-beta.48" + +"@tanstack/virtual-core@3.0.0-beta.48": + version "3.0.0-beta.48" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.48.tgz#bcc5737dc0b9bd3e3c61e75b319c1b5edf30c621" + integrity sha512-W57cC4J1rd3fvWfiio1EteZiokZL8/CvDK/wo+C7xkgYrFw4BXr2P3mvo0HFxCDb15KeqyuF2pTpGWN+D1OXIg== + "@testing-library/dom@^8.5.0": version "8.20.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.0.tgz#914aa862cef0f5e89b98cc48e3445c4c921010f6"