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 (<>
-
-
-
-
-
- {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 (<>
+
+
+
+
+
+ {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