import { Component } from 'react'; import _ from 'underscore'; import downloadFASTA from './download_fasta'; import asMailtoHref from './mailto'; import CloudShareModal from './cloud_share_modal'; import DownloadLinks from 'download_links'; /** * checks whether code is being run by jest */ // eslint-disable-next-line no-undef const isTestMode = () => process.env.JEST_WORKER_ID !== undefined || process.env.NODE_ENV === 'test'; /** * Renders links for downloading hit information in different formats. * Renders links for navigating to each query. */ export default class extends Component { constructor(props) { super(props); this.downloadFastaOfAll = this.downloadFastaOfAll.bind(this); this.downloadFastaOfSelected = this.downloadFastaOfSelected.bind(this); this.topPanelJSX = this.topPanelJSX.bind(this); this.summaryString = this.summaryString.bind(this); this.indexJSX = this.indexJSX.bind(this); this.downloadsPanelJSX = this.downloadsPanelJSX.bind(this); this.handleQueryIndexChange = this.handleQueryIndexChange.bind(this); this.isElementInViewPort = this.isElementInViewPort.bind(this); this.setVisibleQueryIndex = this.setVisibleQueryIndex.bind(this); this.debounceScrolling = this.debounceScrolling.bind(this); this.scrollListener = this.scrollListener.bind(this); this.copyURL = this.copyURL.bind(this); this.shareCloudInit = this.shareCloudInit.bind(this); this.sharingPanelJSX = this.sharingPanelJSX.bind(this); this.timeout = null; this.queryElems = []; this.state = { queryIndex: 1 }; } componentDidMount() { /** * Fixes tooltips in the sidebar, allows tooltip display on click */ $(function () { $('.sidebar [data-toggle="tooltip"]').tooltip({ placement: 'right' }); $('#copyURL').tooltip({ title: 'Copied!', trigger: 'click', placement: 'right', delay: 0 }); }); //keep track of the current queryIndex so it doesn't get lost on page reload const urlMatch = window.location.href.match(/#Query_(\d+)/); if (urlMatch && urlMatch.length > 1) { const queryNumber = +urlMatch[1]; const index = this.props.data.queries.findIndex(query => query.number === queryNumber); this.setState({ queryIndex: index + 1 }); } window.addEventListener('scroll', this.scrollListener); $('a[href^="#Query_"]').on('click', this.animateAnchorElements); } componentWillUnmount() { window.removeEventListener('scroll', this.scrollListener); } componentDidUpdate(prevProps) { if (this.props.allQueriesLoaded && !prevProps.allQueriesLoaded) { /** * storing all query elements in this variable once they all become available so we don't have to fetch them all over again */ this.queryElems = Array.from(document.querySelectorAll('.resultn')); } } /** * to avoid unnecessary computations, we debounce the scroll listener so it only fires after user has stopped scrolling for some milliseconds */ scrollListener() { this.debounceScrolling(this.setVisibleQueryIndex, 500); } debounceScrolling(callback, timer) { if (this.timeout) { clearTimeout(this.timeout); } this.timeout = setTimeout(callback, timer); } /** * This method makes the page aware of what query is visible so that clicking previous / next button at any point * navigates to the proper query */ setVisibleQueryIndex() { const queryElems = this.queryElems.length ? this.queryElems : Array.from(document.querySelectorAll('.resultn')); const hits = Array.from(document.querySelectorAll('.hit[id^=Query_]')); // get the first visible element and marks it as the current query const topmostEl = queryElems.find(this.isElementInViewPort) || hits.find(this.isElementInViewPort); if (topmostEl) { const queryIndex = Number(topmostEl.id.match(/Query_(\d+)/)[1]); let hash = `#Query_${queryIndex}`; // if we can guarantee that the browser can handle change in url hash without the page jumping, // then we update the url hash after scroll. else, hash is only updated on click of next or prev button if (window.history.pushState) { window.history.pushState(null, null, hash); } this.setState({ queryIndex }); } } animateAnchorElements(e) { // allow normal behavior in test mode to prevent warnings or errors from jquery if (isTestMode()) return; e.preventDefault(); $('html, body').animate({ scrollTop: $(this.hash).offset().top }, 300); if (window.history.pushState) { window.history.pushState(null, null, this.hash); } else { window.location.hash = this.hash; } } isElementInViewPort(elem) { const { top, left, right, bottom } = elem.getBoundingClientRect(); return ( top >= 0 && left >= 0 && bottom <= (window.innerHeight || document.documentElement.clientHeight) && right <= (window.innerWidth || document.documentElement.clientWidth) ); } /** * Clear sessionStorage - useful to initiate a new search in the same tab. * Passing sessionStorage.clear directly as onclick callback didn't work * (on macOS Chrome). */ clearSession() { sessionStorage.clear(); } /** * * handle next and previous query button clicks */ handleQueryIndexChange(nextQuery) { if (nextQuery < 1 || nextQuery > this.props.data.queries.length) return; const anchorEl = document.createElement('a'); //indexing at [nextQuery - 1] because array is 0-indexed anchorEl.setAttribute('href', '#Query_' + this.props.data.queries[nextQuery - 1].number); anchorEl.setAttribute('hidden', true); document.body.appendChild(anchorEl); // add smooth scrolling animation with jquery $(anchorEl).on('click', this.animateAnchorElements); anchorEl.click(); document.body.removeChild(anchorEl); this.setState({ queryIndex: nextQuery }); } /** * Event-handler for downloading fasta of all hits. */ downloadFastaOfAll() { var sequence_ids = []; this.props.data.queries.forEach( (query) => query.hits.forEach( (hit) => sequence_ids.push(hit.id))); var database_ids = this.props.data.querydb.map((querydb) => querydb.id); downloadFASTA(sequence_ids, database_ids); return false; } /** * Handles downloading fasta of selected hits. */ downloadFastaOfSelected() { var sequence_ids = $('.hit-links :checkbox:checked').map(function () { return this.value; }).get(); if (sequence_ids.length === 0) { return false; } var database_ids = _.map(this.props.data.querydb, _.iteratee('id')); downloadFASTA(sequence_ids, database_ids); return false; } /** * Handles copying the URL into the user's clipboard. Modified from: https://stackoverflow.com/a/49618964/18117380 * Hides the 'Copied!' tooltip after 3 seconds */ copyURL() { var element = document.createElement('input'); var url = window.location.href; document.body.appendChild(element); element.value = url; element.select(); document.execCommand('copy'); document.body.removeChild(element); setTimeout(function () { $('#copyURL')._tooltip('hide'); }, 3000); } shareCloudInit() { this.refs.cloudShareModal.show(); } topPanelJSX() { var path = location.pathname.split('/'); // Get job id. var job_id = path.pop(); // Deriving rootURL this way is required for subURI deployments // - we cannot just send to '/'. var rootURL = path.join('/'); return (

{this.summaryString()}

{this.props.data.queries.length > 12 && this.queryIndexButtons()}
Edit search | New search
{this.props.shouldShowIndex && this.indexJSX()}
); } summaryString() { var program = this.props.data.program; var numqueries = this.props.data.queries.length; var numquerydb = this.props.data.querydb.length; return ( program.toUpperCase() + ': ' + numqueries + ' ' + (numqueries > 1 ? 'queries' : 'query') + ', ' + numquerydb + ' ' + (numquerydb > 1 ? 'databases' : 'database') ); } queryIndexButtons() { const buttonStyle = { outline: 'none', border: 'none', background: 'none' }; const buttonClasses = 'btn-link nowrap-ellipsis hover-bold'; const handlePreviousBtnClick = () => this.handleQueryIndexChange(this.state.queryIndex - 1); const handleNextBtnClick = () => this.handleQueryIndexChange(this.state.queryIndex + 1); // eslint-disable-next-line no-unused-vars const NavButton = ({ text, onClick }) => ( ); return
{this.state.queryIndex > 1 && } {this.state.queryIndex > 1 && this.state.queryIndex < this.props.data.queries.length && |} {this.state.queryIndex < this.props.data.queries.length && }
; } indexJSX() { return ; } downloadsPanelJSX() { return (

Download FASTA, XML, TSV

); } sharingPanelJSX() { return (

Share results

{ }
); } render() { return (
{this.topPanelJSX()} {this.downloadsPanelJSX()} {this.sharingPanelJSX()}

Recommend SequenceServer

Earn up to $100 per signup

); } }