diff --git a/src/App.js b/src/App.js index b7329ff..ea8a41d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,613 +1,23 @@ import React, { useState, useEffect, - useRef, - useMemo, - useLayoutEffect, - useCallback, } from "react"; -import SingleRow from "./components/SingleRow"; -import SettingsPanel from "./components/SettingsPanel"; -import { Dialog } from "@headlessui/react"; + + import "./App.css"; import ClipLoader from "react-spinners/ClipLoader"; -import { genbankToJson } from "bio-parsers"; -import { useMeasure } from "react-use"; // or just 'react-use-measure' -import { FaSearch, FaTimes, FaZoo } from "react-icons/fa"; -import { DebounceInput } from "react-debounce-input"; -import { useWindowVirtualizer } from "@tanstack/react-virtual"; -// settings icon -import { MdSettings } from "react-icons/md"; -import { GiDna1 } from "react-icons/gi"; -import {BsArrowRightCircleFill, BsArrowLeftCircleFill} from "react-icons/bs"; -import { ToastContainer, toast } from "react-toastify"; + +import GensploreView from "./components/GensploreView"; import { useDebounce, useQueryState } from "./hooks"; import "react-toastify/dist/ReactToastify.css"; -import Tooltip from "./components/Tooltip"; -import { getReverseComplement, filterFeatures } from "./utils"; +import { GiDna1 } from "react-icons/gi"; // import github icon import { FaGithub } from "react-icons/fa"; -function SearchPanel({ - searchPanelOpen, - setSearchPanelOpen, - searchInput, - setSearchInput, - searchType, - setSearchType, - curSeqHitIndex, - sequenceHits, - setCurSeqHitIndex, - includeRC, - setIncludeRC -}) { - const handleInputChange = (event) => { - setCurSeqHitIndex(0); - setSearchInput(event.target.value); - // if event.target.value has only ACGT characters, then set searchType to sequence - // if event.target.value has non-numeric characters, then set searchType to annot - // if event.target.value has only numeric characters, then set searchType to nuc - if (/^[0-9]+$/.test(event.target.value)) { - setSearchType("nuc"); - } - else if (/^[ACGTacgt]+$/.test(event.target.value)) { - setSearchType("sequence"); - } - else if (/^[^0-9]+$/.test(event.target.value)) { - setSearchType("annot"); - } - - }; - - const searchOption = [ - { value: "nuc", label: "nuc. index" }, - { value: "annot", label: "annotation" }, - { value: "sequence", label: "sequence"} - ]; - - return ( -
-
- {searchPanelOpen ? ( - <> - - - - - - ) : ( - - )} -
- { - searchType === "sequence" && searchInput && searchInput.length>0&& ( -
- - - {sequenceHits.length > 0 && ( - <> - - - - - - Hit {curSeqHitIndex + 1} of {sequenceHits.length} - - - - - )} - {sequenceHits.length === 0 && ( - <> - No hits found - - )} -
- ) - - } -
- ); -} - - -function GensploreView({ genbankString, searchInput, setSearchInput }) { - const [searchPanelOpen, setSearchPanelOpen] = useState(false); - const [zoomLevel, setRawZoomLevel] = useState(0); - const [whereMouseWentDown, setWhereMouseWentDown] = useState(null); - const [whereMouseWentUp, setWhereMouseWentUp] = useState(null); - const [whereMouseCurrentlyIs, setWhereMouseCurrentlyIs] = useState(null); - const [searchType, setSearchType] = useState("nuc"); - - const [ref, { width }] = useMeasure(); - - const [hoveredInfo, setHoveredInfo] = useState(null); - const [genbankData, setGenbankData] = useState(null); - const [sequenceHits, setSequenceHits] = useState([]); - const [curSeqHitIndex, setCurSeqHitIndex] = useState(0); - const [includeRC, setIncludeRC] = useState(false); - - // safely convert searchInput to int - const intSearchInput = searchType === "nuc" ? parseInt(searchInput) : null; - const annotSearchInput = searchType === "annot" ? searchInput : null; - const sequenceSearchInput = searchType === "sequence" && searchInput ? searchInput.toUpperCase() : null; - - const [whereOnPage, setWhereOnPage] = useState(0); - - // listen to scroll - function getDocHeight() { - var D = document; - return Math.max( - Math.max(D.body.scrollHeight, D.documentElement.scrollHeight), - Math.max(D.body.offsetHeight, D.documentElement.offsetHeight), - Math.max(D.body.clientHeight, D.documentElement.clientHeight) - ); - } - useEffect(() => { - // capture how far down the page we are as a percentage - const handleScroll = () => { - const scrollTop = window.pageYOffset; - const winHeight = window.innerHeight; - const docHeight = getDocHeight(); - const totalDocScrollLength = docHeight - winHeight; - const scrollPosition = scrollTop / totalDocScrollLength; - // if difference is more than 1%, update - if (Math.abs(scrollPosition - whereOnPage) > 0.05) { - // debounce - setWhereOnPage(scrollPosition); - } - }; - window.addEventListener("scroll", handleScroll); - return () => { - window.removeEventListener("scroll", handleScroll); - }; - }, [whereOnPage]); - - useEffect(() => { - const loadGenbankString = async () => { - try { - const genbankObject = await genbankToJson(genbankString); - console.log("GenBank file loaded:", genbankObject); - // to uppercase - genbankObject[0].parsedSequence.sequence = - genbankObject[0].parsedSequence.sequence.toUpperCase(); - setGenbankData(genbankObject[0]); - } catch (error) { - console.error("Error loading GenBank file:", error); - } - }; - loadGenbankString(); - }, []); - - // detect ctrl-F and open search panel - useEffect(() => { - const handleKeyDown = (e) => { - if ((e.ctrlKey || e.metaKey) && e.keyCode === 70) { - e.preventDefault(); - setSearchPanelOpen(true); - // focus on search input - setTimeout(() => { - document.getElementById("search-input").focus(); - // select all text - document.getElementById("search-input").select(); - }, 100); - } - }; - window.addEventListener("keydown", handleKeyDown); - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; - }, []); - - // detect ctrl-F and open search panel - useEffect(() => { - const handleKeyDown = (e) => { - // ctrl-C - if ((e.ctrlKey || e.metaKey) && e.keyCode === 67) { - const selStart = Math.min(whereMouseWentDown, whereMouseWentUp); - const selEnd = Math.max(whereMouseWentDown, whereMouseWentUp); - //console.log(selStart,selEnd); - let selectedText = genbankData.parsedSequence.sequence.substring( - selStart, - selEnd - ); - if (selectedText) { - if (e.shiftKey) { - selectedText = getReverseComplement(selectedText); - } - console.log(selectedText); - navigator.clipboard.writeText(selectedText); - toast.success( - `Copied ${e.shiftKey ? "reverse complement" : ""} to clipboard` - ); - e.preventDefault(); - } - } - }; - window.addEventListener("keydown", handleKeyDown); - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; - }, [genbankData, whereMouseWentDown, whereMouseWentUp]); - - let rowWidth = Math.floor((width * 0.0965) / 2 ** zoomLevel); - // rowWidth minimum 50 - if (rowWidth < 30) { - rowWidth = 30; - } - //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: () => 90, - scrollMargin: parentOffsetRef.current, - }); - - const virtualItems = rowVirtualizer.getVirtualItems(); - const [centeredNucleotide, setCenteredNucleotide] = useState(null); - - const setZoomLevel = (x) => { - const middleRow = virtualItems[Math.floor(virtualItems.length / 2)].index; - const middleRowStart = rowData[middleRow].rowStart; - const middleRowEnd = rowData[middleRow].rowEnd; - const middleRowMiddle = Math.floor((middleRowStart + middleRowEnd) / 2); - setCenteredNucleotide(middleRowMiddle); - console.log("middleRowMiddle", middleRowMiddle); - setRawZoomLevel(x); - }; - - useEffect(() => { - if (!centeredNucleotide) return; - // if there is a selection, use that instead - if (whereMouseWentDown && whereMouseWentUp) { - const midPoint = Math.floor((whereMouseWentDown + whereMouseWentUp) / 2); - rowVirtualizer.scrollToIndex(3 + Math.floor(midPoint / rowWidth), { - align: "center", - smoothScroll: false, - }); - setCenteredNucleotide(null); - return; - } - const row = Math.floor(centeredNucleotide / rowWidth); - rowVirtualizer.scrollToIndex(row, { - align: "center", - smoothScroll: false, - }); - setCenteredNucleotide(null); - console.log("scrolling to", centeredNucleotide); - }, [centeredNucleotide, zoomLevel]); - - const [lastSearch, setLastSearch] = useState(null); - const [enableRC, setEnableRC] = useState(false); - const [configModalOpen, setConfigModalOpen] = useState(false); - - useEffect(() => { - if (!intSearchInput) return; - const row = Math.floor(intSearchInput / rowWidth); - if (intSearchInput === lastSearch) { - return; - } - // checkrow is valid - if (row > rowData.length) { - return; - } - - rowVirtualizer.scrollToIndex(row + 1, { align: "center" }); - - setLastSearch(intSearchInput); - }, [intSearchInput, rowWidth]); - useEffect(() => { - if (!annotSearchInput) return; - const strippedAnnotInput = annotSearchInput.replace(/\s/g, ""); - if (strippedAnnotInput === "") return; - // search the features for one that matches - const matchingFeatures = filterFeatures( - genbankData.parsedSequence.features, - strippedAnnotInput - ); - if (matchingFeatures.length === 0) { - toast.error("No matching features found"); - return; - } - const firstMatchingFeature = matchingFeatures[0]; - const row = Math.floor(firstMatchingFeature.start / rowWidth); - rowVirtualizer.scrollToIndex(row + 1, { align: "center" }); - setLastSearch(annotSearchInput); - }, [annotSearchInput]); - - - useEffect(() => { - if(!sequenceSearchInput) { - setSequenceHits([]); - return; - } - const strippedSequenceInput = sequenceSearchInput.replace(/\s/g, ""); - if (strippedSequenceInput === ""){ - setSequenceHits([]); - return; - } - console.log("strippedSequenceInput", strippedSequenceInput); - const matchingSequence = fullSequence.indexOf(strippedSequenceInput); - - if(matchingSequence === -1) { - //toast.error("No matching sequence found"); - setSequenceHits([]); - - return; - } - // we want to find all locations that match and store them with setSequenceHits as [start,end] - const seqHits = []; - let start = 0; - const rc = getReverseComplement(strippedSequenceInput); - console.log("rc", rc); - while (true) { - const hit1 = fullSequence.indexOf(strippedSequenceInput, start); - const hit2 = includeRC ? fullSequence.indexOf(rc, start) : -1; - const hit = hit1 === -1 ? hit2 : (hit2 === -1 ? hit1 : Math.min(hit1, hit2)); - - if (hit === -1) break; - seqHits.push([hit, hit + strippedSequenceInput.length]); - start = hit + 1; - } - setSequenceHits(seqHits); - - const row = Math.floor(seqHits[curSeqHitIndex][0] / rowWidth); - console.log("row", row); - rowVirtualizer.scrollToIndex(row + 1, { align: "center" }); - setLastSearch(sequenceSearchInput); - }, [sequenceSearchInput, curSeqHitIndex,includeRC]); - - - //console.log("virtualItems", virtualItems); - - if (!genbankData) { - return
Loading...
; - } - - if (!width) { - return ( -
-
-
- ); - } - return (<> - setConfigModalOpen(false)} - className="fixed z-50 max-w-2xl px-4 py-6 bg-white rounded-lg shadow-xl sm:px-6 sm:py-8 sm:pb-4 sm:pt-6" -> - - -
- - Settings - - - - Customize appearance - - -

- -

- -
- -
-
-
-
- - -
- - {true && ( -
- -
- )} - -
- -
- -
- - {genbankData && ( -
- { - // small logo on left, name and definition on right - } -
- -
-
-

{genbankData.parsedSequence.name}

-
-
- {genbankData.parsedSequence.definition} -
-
-
-
-
-
- {virtualItems.map((virtualitem) => { - const row = rowData[virtualitem.index]; - //return (
{genbankData.parsedSequence.sequence.slice(row.start,row.end)}
) - return ( -
- -
- ); - })} -
-
-
-
- )} -
-
- - ); -} const App = () => { const addlExamples = [ diff --git a/src/SearchPanel.js b/src/SearchPanel.js new file mode 100644 index 0000000..526e017 --- /dev/null +++ b/src/SearchPanel.js @@ -0,0 +1,144 @@ +import React, { + useState, + useEffect, + } from "react"; + +import { FaSearch, FaTimes, FaZoo } from "react-icons/fa"; +import { DebounceInput } from "react-debounce-input"; + +// settings icon + + +import {BsArrowRightCircleFill, BsArrowLeftCircleFill} from "react-icons/bs"; + + +function SearchPanel({ + searchPanelOpen, + setSearchPanelOpen, + searchInput, + setSearchInput, + searchType, + setSearchType, + curSeqHitIndex, + sequenceHits, + setCurSeqHitIndex, + includeRC, + setIncludeRC + }) { + const handleInputChange = (event) => { + setCurSeqHitIndex(0); + setSearchInput(event.target.value); + // if event.target.value has only ACGT characters, then set searchType to sequence + // if event.target.value has non-numeric characters, then set searchType to annot + // if event.target.value has only numeric characters, then set searchType to nuc + if (/^[0-9]+$/.test(event.target.value)) { + setSearchType("nuc"); + } + else if (/^[ACGTacgt]+$/.test(event.target.value)) { + setSearchType("sequence"); + } + else if (/^[^0-9]+$/.test(event.target.value)) { + setSearchType("annot"); + } + + }; + + const searchOption = [ + { value: "nuc", label: "nuc. index" }, + { value: "annot", label: "annotation" }, + { value: "sequence", label: "sequence"} + ]; + + return ( +
+
+ {searchPanelOpen ? ( + <> + + + + + + ) : ( + + )} +
+ { + searchType === "sequence" && searchInput && searchInput.length>0&& ( +
+ + + {sequenceHits.length > 0 && ( + <> + + + + + + Hit {curSeqHitIndex + 1} of {sequenceHits.length} + + + + + )} + {sequenceHits.length === 0 && ( + <> + No hits found + + )} +
+ ) + + } +
+ ); + } + + export default SearchPanel; + \ No newline at end of file diff --git a/src/components/GensploreView.js b/src/components/GensploreView.js new file mode 100644 index 0000000..994c4a6 --- /dev/null +++ b/src/components/GensploreView.js @@ -0,0 +1,472 @@ +import React, { + useState, + useEffect, + useRef, + useMemo, + useLayoutEffect, + useCallback, + } from "react"; + +import Tooltip from "./Tooltip"; +import { getReverseComplement, filterFeatures } from "../utils"; +import SingleRow from "./SingleRow"; +import SettingsPanel from "./SettingsPanel"; +import { Dialog } from "@headlessui/react"; +import { genbankToJson } from "bio-parsers"; +import { useMeasure } from "react-use"; // or just 'react-use-measure' +import { useWindowVirtualizer } from "@tanstack/react-virtual"; +import { ToastContainer, toast } from "react-toastify"; +import SearchPanel from "../SearchPanel"; +import { GiDna1 } from "react-icons/gi"; +import { MdSettings } from "react-icons/md"; + +function GensploreView({ genbankString, searchInput, setSearchInput }) { + const [searchPanelOpen, setSearchPanelOpen] = useState(false); + const [zoomLevel, setRawZoomLevel] = useState(0); + const [whereMouseWentDown, setWhereMouseWentDown] = useState(null); + const [whereMouseWentUp, setWhereMouseWentUp] = useState(null); + const [whereMouseCurrentlyIs, setWhereMouseCurrentlyIs] = useState(null); + const [searchType, setSearchType] = useState("nuc"); + + const [ref, { width }] = useMeasure(); + + const [hoveredInfo, setHoveredInfo] = useState(null); + const [genbankData, setGenbankData] = useState(null); + const [sequenceHits, setSequenceHits] = useState([]); + const [curSeqHitIndex, setCurSeqHitIndex] = useState(0); + const [includeRC, setIncludeRC] = useState(false); + + // safely convert searchInput to int + const intSearchInput = searchType === "nuc" ? parseInt(searchInput) : null; + const annotSearchInput = searchType === "annot" ? searchInput : null; + const sequenceSearchInput = searchType === "sequence" && searchInput ? searchInput.toUpperCase() : null; + + const [whereOnPage, setWhereOnPage] = useState(0); + + // listen to scroll + function getDocHeight() { + var D = document; + return Math.max( + Math.max(D.body.scrollHeight, D.documentElement.scrollHeight), + Math.max(D.body.offsetHeight, D.documentElement.offsetHeight), + Math.max(D.body.clientHeight, D.documentElement.clientHeight) + ); + } + useEffect(() => { + // capture how far down the page we are as a percentage + const handleScroll = () => { + const scrollTop = window.pageYOffset; + const winHeight = window.innerHeight; + const docHeight = getDocHeight(); + const totalDocScrollLength = docHeight - winHeight; + const scrollPosition = scrollTop / totalDocScrollLength; + // if difference is more than 1%, update + if (Math.abs(scrollPosition - whereOnPage) > 0.05) { + // debounce + setWhereOnPage(scrollPosition); + } + }; + window.addEventListener("scroll", handleScroll); + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, [whereOnPage]); + + useEffect(() => { + const loadGenbankString = async () => { + try { + const genbankObject = await genbankToJson(genbankString); + console.log("GenBank file loaded:", genbankObject); + // to uppercase + genbankObject[0].parsedSequence.sequence = + genbankObject[0].parsedSequence.sequence.toUpperCase(); + setGenbankData(genbankObject[0]); + } catch (error) { + console.error("Error loading GenBank file:", error); + } + }; + loadGenbankString(); + }, []); + + // detect ctrl-F and open search panel + useEffect(() => { + const handleKeyDown = (e) => { + if ((e.ctrlKey || e.metaKey) && e.keyCode === 70) { + e.preventDefault(); + setSearchPanelOpen(true); + // focus on search input + setTimeout(() => { + document.getElementById("search-input").focus(); + // select all text + document.getElementById("search-input").select(); + }, 100); + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + // detect ctrl-F and open search panel + useEffect(() => { + const handleKeyDown = (e) => { + // ctrl-C + if ((e.ctrlKey || e.metaKey) && e.keyCode === 67) { + const selStart = Math.min(whereMouseWentDown, whereMouseWentUp); + const selEnd = Math.max(whereMouseWentDown, whereMouseWentUp); + //console.log(selStart,selEnd); + let selectedText = genbankData.parsedSequence.sequence.substring( + selStart, + selEnd + ); + if (selectedText) { + if (e.shiftKey) { + selectedText = getReverseComplement(selectedText); + } + console.log(selectedText); + navigator.clipboard.writeText(selectedText); + toast.success( + `Copied ${e.shiftKey ? "reverse complement" : ""} to clipboard` + ); + e.preventDefault(); + } + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [genbankData, whereMouseWentDown, whereMouseWentUp]); + + let rowWidth = Math.floor((width * 0.0965) / 2 ** zoomLevel); + // rowWidth minimum 50 + if (rowWidth < 30) { + rowWidth = 30; + } + //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: () => 90, + scrollMargin: parentOffsetRef.current, + }); + + const virtualItems = rowVirtualizer.getVirtualItems(); + const [centeredNucleotide, setCenteredNucleotide] = useState(null); + + const setZoomLevel = (x) => { + const middleRow = virtualItems[Math.floor(virtualItems.length / 2)].index; + const middleRowStart = rowData[middleRow].rowStart; + const middleRowEnd = rowData[middleRow].rowEnd; + const middleRowMiddle = Math.floor((middleRowStart + middleRowEnd) / 2); + setCenteredNucleotide(middleRowMiddle); + console.log("middleRowMiddle", middleRowMiddle); + setRawZoomLevel(x); + }; + + useEffect(() => { + if (!centeredNucleotide) return; + // if there is a selection, use that instead + if (whereMouseWentDown && whereMouseWentUp) { + const midPoint = Math.floor((whereMouseWentDown + whereMouseWentUp) / 2); + rowVirtualizer.scrollToIndex(3 + Math.floor(midPoint / rowWidth), { + align: "center", + smoothScroll: false, + }); + setCenteredNucleotide(null); + return; + } + const row = Math.floor(centeredNucleotide / rowWidth); + rowVirtualizer.scrollToIndex(row, { + align: "center", + smoothScroll: false, + }); + setCenteredNucleotide(null); + console.log("scrolling to", centeredNucleotide); + }, [centeredNucleotide, zoomLevel]); + + const [lastSearch, setLastSearch] = useState(null); + const [enableRC, setEnableRC] = useState(false); + const [configModalOpen, setConfigModalOpen] = useState(false); + + useEffect(() => { + if (!intSearchInput) return; + const row = Math.floor(intSearchInput / rowWidth); + if (intSearchInput === lastSearch) { + return; + } + // checkrow is valid + if (row > rowData.length) { + return; + } + + rowVirtualizer.scrollToIndex(row + 1, { align: "center" }); + + setLastSearch(intSearchInput); + }, [intSearchInput, rowWidth]); + useEffect(() => { + if (!annotSearchInput) return; + const strippedAnnotInput = annotSearchInput.replace(/\s/g, ""); + if (strippedAnnotInput === "") return; + // search the features for one that matches + const matchingFeatures = filterFeatures( + genbankData.parsedSequence.features, + strippedAnnotInput + ); + if (matchingFeatures.length === 0) { + toast.error("No matching features found"); + return; + } + const firstMatchingFeature = matchingFeatures[0]; + const row = Math.floor(firstMatchingFeature.start / rowWidth); + rowVirtualizer.scrollToIndex(row + 1, { align: "center" }); + setLastSearch(annotSearchInput); + }, [annotSearchInput]); + + + useEffect(() => { + if(!sequenceSearchInput) { + setSequenceHits([]); + return; + } + const strippedSequenceInput = sequenceSearchInput.replace(/\s/g, ""); + if (strippedSequenceInput === ""){ + setSequenceHits([]); + return; + } + console.log("strippedSequenceInput", strippedSequenceInput); + const matchingSequence = fullSequence.indexOf(strippedSequenceInput); + + if(matchingSequence === -1) { + //toast.error("No matching sequence found"); + setSequenceHits([]); + + return; + } + // we want to find all locations that match and store them with setSequenceHits as [start,end] + const seqHits = []; + let start = 0; + const rc = getReverseComplement(strippedSequenceInput); + console.log("rc", rc); + while (true) { + const hit1 = fullSequence.indexOf(strippedSequenceInput, start); + const hit2 = includeRC ? fullSequence.indexOf(rc, start) : -1; + const hit = hit1 === -1 ? hit2 : (hit2 === -1 ? hit1 : Math.min(hit1, hit2)); + + if (hit === -1) break; + seqHits.push([hit, hit + strippedSequenceInput.length]); + start = hit + 1; + } + setSequenceHits(seqHits); + + const row = Math.floor(seqHits[curSeqHitIndex][0] / rowWidth); + console.log("row", row); + rowVirtualizer.scrollToIndex(row + 1, { align: "center" }); + setLastSearch(sequenceSearchInput); + }, [sequenceSearchInput, curSeqHitIndex,includeRC]); + + + //console.log("virtualItems", virtualItems); + + if (!genbankData) { + return
Loading...
; + } + + if (!width) { + return ( +
+
+
+ ); + } + + return (<> + setConfigModalOpen(false)} + className="fixed z-50 max-w-2xl px-4 py-6 bg-white rounded-lg shadow-xl sm:px-6 sm:py-8 sm:pb-4 sm:pt-6" + > + + +
+ + Settings + + + + Customize appearance + + +

+ +

+ +
+ +
+
+
+
+ + +
+ + {true && ( +
+ +
+ )} + +
+ +
+ +
+ + {genbankData && ( +
+ { + // small logo on left, name and definition on right + } +
+ +
+
+

{genbankData.parsedSequence.name}

+
+
+ {genbankData.parsedSequence.definition} +
+
+
+
+
+
+ {virtualItems.map((virtualitem) => { + const row = rowData[virtualitem.index]; + //return (
{genbankData.parsedSequence.sequence.slice(row.start,row.end)}
) + return ( +
+ +
+ ); + })} +
+
+
+
+ )} +
+
+ + ); + } + + export default GensploreView; \ No newline at end of file