From ad465cf57f635f7f640bbe913156c1ff2b78cf19 Mon Sep 17 00:00:00 2001 From: Tadas Tamosauskas Date: Tue, 30 Jan 2024 14:07:14 +0000 Subject: [PATCH] Modify the loading screen for cloud SequenceServer Cloud users can get email notifications when long running jobs complete, tweak the results loading page to indicated that. --- package.json | 4 ++-- public/js/report.js | 3 +++ public/sequenceserver-report.min.js | 2 +- webpack.config.js | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 23d5c769d..76ccb57bd 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ }, "scripts": { "watch": "webpack --watch", - "watch-cloud": "webpack --watch --env=reportPluginsPath='../sequence_server_js_extensions/report_plugins.js' --env=downloadLinks='../sequence_server_js_extensions/conserved_domains/download_links.js' --env=hitButtons='../sequence_server_js_extensions/hit_buttons.js'", + "watch-cloud": "webpack --watch --env=reportPluginsPath='../sequence_server_js_extensions/report_plugins.js' --env=downloadLinks='../sequence_server_js_extensions/conserved_domains/download_links.js' --env=hitButtons='../sequence_server_js_extensions/hit_buttons.js' --env=targetEnv='cloud'", "test": "jest", "minify:css": "cleancss public/css/sequenceserver.css > public/css/sequenceserver.min.css", "build": "webpack && npm run minify:css", - "build-cloud": "webpack --env=reportPluginsPath='../sequence_server_js_extensions/report_plugins.js' --env=downloadLinks='../sequence_server_js_extensions/conserved_domains/download_links.js' --env=hitButtons='../sequence_server_js_extensions/hit_buttons.js' && npm run minify:css" + "build-cloud": "webpack --env=reportPluginsPath='../sequence_server_js_extensions/report_plugins.js' --env=downloadLinks='../sequence_server_js_extensions/conserved_domains/download_links.js' --env=hitButtons='../sequence_server_js_extensions/hit_buttons.js' --env=targetEnv='cloud' && npm run minify:css" }, "dependencies": { "babel-plugin-transform-export-extensions": "^6.22.0", diff --git a/public/js/report.js b/public/js/report.js index 6637dfa97..2417829c9 100644 --- a/public/js/report.js +++ b/public/js/report.js @@ -273,6 +273,9 @@ class Report extends Component {
You can bookmark the page and come back to it later or share the link with someone. +
+
+ { process.env.targetEnv === 'cloud' && If the job takes more than 10 minutes to complete, we will send you an email upon completion. }

diff --git a/public/sequenceserver-report.min.js b/public/sequenceserver-report.min.js index f85603df9..9c0498651 100644 --- a/public/sequenceserver-report.min.js +++ b/public/sequenceserver-report.min.js @@ -225,7 +225,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _jquery_world__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./jquery_world */ \"./public/js/jquery_world.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var underscore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! underscore */ \"./node_modules/underscore/modules/index-all.js\");\n/* harmony import */ var _sidebar__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sidebar */ \"./public/js/sidebar.js\");\n/* harmony import */ var _circos__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./circos */ \"./public/js/circos.js\");\n/* harmony import */ var _query__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./query */ \"./public/js/query.js\");\n/* harmony import */ var _hit__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./hit */ \"./public/js/hit.js\");\n/* harmony import */ var _hsp__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./hsp */ \"./public/js/hsp.js\");\n/* harmony import */ var _alignment_exporter__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./alignment_exporter */ \"./public/js/alignment_exporter.js\");\n/* harmony import */ var report_plugins__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! report_plugins */ \"./public/js/null_plugins/report_plugins.js\");\n/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! react/jsx-runtime */ \"./node_modules/react/jsx-runtime.js\");\n/* provided dependency */ var $ = __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nfunction ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\nfunction _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\nfunction _iterableToArray(iter) { if (typeof Symbol !== \"undefined\" && iter[Symbol.iterator] != null || iter[\"@@iterator\"] != null) return Array.from(iter); }\nfunction _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, \"prototype\", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } else if (call !== void 0) { throw new TypeError(\"Derived constructors may only return object or undefined\"); } return _assertThisInitialized(self); }\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n // for custom $.tooltip function\n\n\n\n\n\n\n\n\n\n\n/**\n * Renders entire report.\n *\n * Composed of Query and Sidebar components.\n */\n\n\nvar Report = /*#__PURE__*/function (_Component) {\n _inherits(Report, _Component);\n var _super = _createSuper(Report);\n function Report(props) {\n var _this;\n _classCallCheck(this, Report);\n _this = _super.call(this, props);\n // Properties below are internal state used to render results in small\n // slices (see updateState).\n _this.numUpdates = 0;\n _this.nextQuery = 0;\n _this.nextHit = 0;\n _this.nextHSP = 0;\n _this.maxHSPs = 3; // max HSPs to render in a cycle\n _this.state = {\n user_warning: null,\n download_links: [],\n search_id: '',\n seqserv_version: '',\n program: '',\n program_version: '',\n submitted_at: '',\n queries: [],\n results: [],\n querydb: [],\n params: [],\n stats: [],\n alignment_blob_url: '',\n allQueriesLoaded: false,\n cloud_sharing_enabled: false\n };\n _this.prepareAlignmentOfSelectedHits = _this.prepareAlignmentOfSelectedHits.bind(_assertThisInitialized(_this));\n _this.prepareAlignmentOfAllHits = _this.prepareAlignmentOfAllHits.bind(_assertThisInitialized(_this));\n _this.setStateFromJSON = _this.setStateFromJSON.bind(_assertThisInitialized(_this));\n _this.plugins = new report_plugins__WEBPACK_IMPORTED_MODULE_9__[\"default\"](_assertThisInitialized(_this));\n return _this;\n }\n\n /**\n * Fetch results.\n */\n _createClass(Report, [{\n key: \"fetchResults\",\n value: function fetchResults() {\n var path = location.pathname + '.json' + location.search;\n this.pollPeriodically(path, this.setStateFromJSON, this.props.showErrorModal);\n }\n }, {\n key: \"pollPeriodically\",\n value: function pollPeriodically(path, callback, errCallback) {\n var intervals = [200, 400, 800, 1200, 2000, 3000, 5000];\n function poll() {\n $.getJSON(path).complete(function (jqXHR) {\n switch (jqXHR.status) {\n case 202:\n var interval;\n if (intervals.length === 1) {\n interval = intervals[0];\n } else {\n interval = intervals.shift();\n }\n setTimeout(poll, interval);\n break;\n case 200:\n callback(jqXHR.responseJSON);\n break;\n case 400:\n case 422:\n case 500:\n errCallback(jqXHR.responseJSON);\n break;\n }\n });\n }\n poll();\n }\n\n /**\n * Calls setState after any required modification to responseJSON.\n */\n }, {\n key: \"setStateFromJSON\",\n value: function setStateFromJSON(responseJSON) {\n this.lastTimeStamp = Date.now();\n // the callback prepares the download link for all alignments\n if (responseJSON.user_warning == 'LARGE_RESULT') {\n this.setState({\n user_warning: responseJSON.user_warning,\n download_links: responseJSON.download_links\n });\n } else {\n this.setState(responseJSON, this.prepareAlignmentOfAllHits);\n }\n }\n\n /**\n * Called as soon as the page has loaded and the user sees the loading spinner.\n * We use this opportunity to setup services that make use of delegated events\n * bound to the window, document, or body.\n */\n }, {\n key: \"componentDidMount\",\n value: function componentDidMount() {\n this.fetchResults();\n this.plugins.init();\n // This sets up an event handler which enables users to select text from\n // hit header without collapsing the hit.\n this.preventCollapseOnSelection();\n this.toggleTable();\n }\n\n /**\n * Called for the first time after as BLAST results have been retrieved from\n * the server and added to this.state by fetchResults. Only summary overview\n * and circos would have been rendered at this point. At this stage we kick\n * start iteratively adding 1 HSP to the page every 25 milli-seconds.\n */\n }, {\n key: \"componentDidUpdate\",\n value: function componentDidUpdate(prevProps, prevState) {\n var _this2 = this;\n // Log to console how long the last update take?\n // console.log((Date.now() - this.lastTimeStamp) / 1000);\n\n // Lock sidebar in its position on the first update.\n if (this.nextQuery == 0 && this.nextHit == 0 && this.nextHSP == 0) {\n this.affixSidebar();\n }\n\n // Queue next update if we have not rendered all results yet.\n if (this.nextQuery < this.state.queries.length) {\n // setTimeout is used to clear call stack and space out\n // the updates giving the browser a chance to respond\n // to user interactions.\n setTimeout(function () {\n return _this2.updateState();\n }, 25);\n } else {\n this.componentFinishedUpdating();\n }\n this.plugins.componentDidUpdate(prevProps, prevState);\n }\n\n /**\n * Push next slice of results to React for rendering.\n */\n }, {\n key: \"updateState\",\n value: function updateState() {\n var results = [];\n var numHSPsProcessed = 0;\n while (this.nextQuery < this.state.queries.length) {\n var query = this.state.queries[this.nextQuery];\n\n // We may see a query multiple times during rendering because only\n // 3 hsps are rendered in each cycle, but we want to create the\n // corresponding Query component only the first time we see it.\n if (this.nextHit == 0 && this.nextHSP == 0) {\n results.push( /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_query__WEBPACK_IMPORTED_MODULE_5__.ReportQuery, {\n query: query,\n program: this.state.program,\n querydb: this.state.querydb,\n showQueryCrumbs: this.state.queries.length > 1,\n non_parse_seqids: this.state.non_parse_seqids,\n imported_xml: this.state.imported_xml,\n veryBig: this.state.veryBig\n }, 'Query_' + query.id));\n results.push.apply(results, _toConsumableArray(this.plugins.queryResults(query)));\n }\n while (this.nextHit < query.hits.length) {\n var hit = query.hits[this.nextHit];\n // We may see a hit multiple times during rendering because only\n // 10 hsps are rendered in each cycle, but we want to create the\n // corresponding Hit component only the first time we see it.\n if (this.nextHSP == 0) {\n results.push( /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_hit__WEBPACK_IMPORTED_MODULE_6__[\"default\"], _objectSpread({\n query: query,\n hit: hit,\n algorithm: this.state.program,\n querydb: this.state.querydb,\n selectHit: this.selectHit,\n imported_xml: this.state.imported_xml,\n non_parse_seqids: this.state.non_parse_seqids,\n showQueryCrumbs: this.state.queries.length > 1,\n showHitCrumbs: query.hits.length > 1,\n veryBig: this.state.veryBig,\n onChange: this.prepareAlignmentOfSelectedHits\n }, this.props), 'Query_' + query.number + '_Hit_' + hit.number));\n }\n while (this.nextHSP < hit.hsps.length) {\n // Get nextHSP and increment the counter.\n var hsp = hit.hsps[this.nextHSP++];\n results.push( /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_hsp__WEBPACK_IMPORTED_MODULE_7__[\"default\"], _objectSpread({\n query: query,\n hit: hit,\n hsp: hsp,\n algorithm: this.state.program,\n showHSPNumbers: hit.hsps.length > 1\n }, this.props), 'Query_' + query.number + '_Hit_' + hit.number + '_HSP_' + hsp.number));\n numHSPsProcessed++;\n if (numHSPsProcessed == this.maxHSPs) break;\n }\n // Are we here because we have iterated over all hsps of a hit,\n // or because of the break clause in the inner loop?\n if (this.nextHSP == hit.hsps.length) {\n this.nextHit = this.nextHit + 1;\n this.nextHSP = 0;\n }\n if (numHSPsProcessed == this.maxHSPs) break;\n }\n\n // Are we here because we have iterated over all hits of a query,\n // or because of the break clause in the inner loop?\n if (this.nextHit == query.hits.length) {\n this.nextQuery = this.nextQuery + 1;\n this.nextHit = 0;\n }\n if (numHSPsProcessed == this.maxHSPs) break;\n }\n\n // Push the components to react for rendering.\n this.numUpdates++;\n this.lastTimeStamp = Date.now();\n this.setState({\n results: this.state.results.concat(results),\n veryBig: this.numUpdates >= 250\n });\n }\n\n /**\n * Called after all results have been rendered.\n */\n }, {\n key: \"componentFinishedUpdating\",\n value: function componentFinishedUpdating() {\n if (this.state.allQueriesLoaded) return;\n this.shouldShowIndex() && this.setupScrollSpy();\n this.setState({\n allQueriesLoaded: true\n });\n }\n\n /**\n * Returns loading message\n */\n }, {\n key: \"loadingJSX\",\n value: function loadingJSX() {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"div\", {\n className: \"row\",\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"col-md-6 col-md-offset-3 text-center\",\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"h1\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"i\", {\n className: \"fa fa-cog fa-spin\"\n }), \"\\xA0 BLAST-ing\"]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), \"This can take some time depending on the size of your query and database(s). The page will update automatically when BLAST is done.\", /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), \"You can bookmark the page and come back to it later or share the link with someone.\"]\n })]\n })\n });\n }\n\n /**\n * Return results JSX.\n */\n }, {\n key: \"resultsJSX\",\n value: function resultsJSX() {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"row\",\n id: \"results\",\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"div\", {\n className: \"col-md-3 hidden-sm hidden-xs\",\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_sidebar__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n data: this.state,\n atLeastOneHit: this.atLeastOneHit(),\n shouldShowIndex: this.shouldShowIndex(),\n allQueriesLoaded: this.state.allQueriesLoaded,\n cloudSharingEnabled: this.state.cloud_sharing_enabled\n })\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"col-md-9\",\n children: [this.overviewJSX(), this.circosJSX(), this.state.results]\n })]\n });\n }\n }, {\n key: \"warningJSX\",\n value: function warningJSX() {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"div\", {\n className: \"container\",\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"div\", {\n className: \"row\",\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"col-md-6 col-md-offset-3 text-center\",\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"h1\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"i\", {\n className: \"fa fa-exclamation-triangle\"\n }), \"\\xA0 Warning\"]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), \"The BLAST result might be too large to load in the browser. If you have a powerful machine you can try loading the results anyway. Otherwise, you can download the results and view them locally.\"]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"p\", {\n children: this.state.download_links.map(function (link, index) {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"a\", {\n href: link.url,\n className: \"btn btn-secondary\",\n children: link.name\n }, 'download_link_' + index);\n })\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"p\", {\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"a\", {\n href: location.pathname + '?bypass_file_size_warning=true',\n className: \"btn btn-primary\",\n children: \"View results in browser anyway\"\n })\n })]\n })\n })\n });\n }\n /**\n * Renders report overview.\n */\n }, {\n key: \"overviewJSX\",\n value: function overviewJSX() {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"overview\",\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"strong\", {\n children: [\"SequenceServer \", this.state.seqserv_version]\n }), \" using\", ' ', /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"strong\", {\n children: this.state.program_version\n }), this.state.submitted_at && \", query submitted on \".concat(this.state.submitted_at)]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"strong\", {\n children: \" Databases: \"\n }), this.state.querydb.map(function (db) {\n return db.title;\n }).join(', '), ' ', \"(\", this.state.stats.nsequences, \" sequences,\\xA0\", this.state.stats.ncharacters, \" characters)\"]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"strong\", {\n children: \"Parameters: \"\n }), ' ', underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].map(this.state.params, function (val, key) {\n return key + ' ' + val;\n }).join(', ')]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [\"Please cite:\", ' ', /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"a\", {\n href: \"https://doi.org/10.1093/molbev/msz185\",\n children: \"https://doi.org/10.1093/molbev/msz185\"\n })]\n })]\n });\n }\n\n /**\n * Return JSX for circos if we have at least one hit.\n */\n }, {\n key: \"circosJSX\",\n value: function circosJSX() {\n return this.atLeastTwoHits() ? /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_circos__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n queries: this.state.queries,\n program: this.state.program,\n collapsed: \"true\"\n }) : /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"span\", {});\n }\n\n // Controller //\n\n /**\n * Returns true if results have been fetched.\n *\n * A holding message is shown till results are fetched.\n */\n }, {\n key: \"isResultAvailable\",\n value: function isResultAvailable() {\n return this.state.queries.length >= 1;\n }\n\n /**\n * Indicates the response contains a warning message for the user\n * in which case we should not render the results and render the\n * warning instead.\n **/\n }, {\n key: \"isUserWarningPresent\",\n value: function isUserWarningPresent() {\n return this.state.user_warning;\n }\n\n /**\n * Returns true if we have at least one hit.\n */\n }, {\n key: \"atLeastOneHit\",\n value: function atLeastOneHit() {\n return this.state.queries.some(function (query) {\n return query.hits.length > 0;\n });\n }\n\n /**\n * Does the report have at least two hits? This is used to determine\n * whether Circos should be enabled or not.\n */\n }, {\n key: \"atLeastTwoHits\",\n value: function atLeastTwoHits() {\n var hit_num = 0;\n return this.state.queries.some(function (query) {\n hit_num += query.hits.length;\n return hit_num > 1;\n });\n }\n\n /**\n * Returns true if index should be shown in the sidebar. Index is shown\n * only for 2 and 8 queries.\n */\n }, {\n key: \"shouldShowIndex\",\n value: function shouldShowIndex() {\n var num_queries = this.state.queries.length;\n return num_queries >= 2 && num_queries <= 12;\n }\n\n /**\n * Prevents folding of hits during text-selection.\n */\n }, {\n key: \"preventCollapseOnSelection\",\n value: function preventCollapseOnSelection() {\n $('body').on('mousedown', '.hit > .section-header > h4', function (event) {\n var $this = $(this);\n $this.on('mouseup mousemove', function handler(event) {\n if (event.type === 'mouseup') {\n // user wants to toggle\n var hitID = $this.parents('.hit').attr('id');\n $(\"div[data-parent-hit=\".concat(hitID, \"]\")).toggle();\n $this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');\n } else {\n // user wants to select\n $this.attr('data-toggle', '');\n }\n $this.off('mouseup mousemove', handler);\n });\n });\n }\n\n /* Handling the fa icon when Hit Table is collapsed */\n }, {\n key: \"toggleTable\",\n value: function toggleTable() {\n $('body').on('mousedown', '.resultn .caption[data-toggle=\"collapse\"]', function (event) {\n var $this = $(this);\n $this.on('mouseup mousemove', function handler(event) {\n $this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');\n $this.off('mouseup mousemove', handler);\n });\n });\n }\n\n /**\n * Affixes the sidebar.\n */\n }, {\n key: \"affixSidebar\",\n value: function affixSidebar() {\n var $sidebar = $('.sidebar');\n var sidebarOffset = $sidebar.offset();\n if (sidebarOffset) {\n $sidebar.affix({\n offset: {\n top: sidebarOffset.top\n }\n });\n }\n }\n\n /**\n * For the query in viewport, highlights corresponding entry in the index.\n */\n }, {\n key: \"setupScrollSpy\",\n value: function setupScrollSpy() {\n $('body').scrollspy({\n target: '.sidebar'\n });\n }\n\n /**\n * Event-handler when hit is selected\n * Adds glow to hit component.\n * Updates number of Fasta that can be downloaded\n */\n }, {\n key: \"selectHit\",\n value: function selectHit(id) {\n var checkbox = $('#' + id);\n var num_checked = $('.hit-links :checkbox:checked').length;\n if (!checkbox || !checkbox.val()) {\n return;\n }\n var $hit = $(checkbox.data('target'));\n\n // Highlight selected hit and enable 'Download FASTA/Alignment of\n // selected' links.\n if (checkbox.is(':checked')) {\n $hit.addClass('glow');\n $hit.next('.hsp').addClass('glow');\n $('.download-fasta-of-selected').enable();\n $('.download-alignment-of-selected').enable();\n } else {\n $hit.removeClass('glow');\n $hit.next('.hsp').removeClass('glow');\n }\n var $a = $('.download-fasta-of-selected');\n var $b = $('.download-alignment-of-selected');\n if (num_checked >= 1) {\n $a.find('.text-bold').html(num_checked);\n $b.find('.text-bold').html(num_checked);\n }\n if (num_checked == 0) {\n $a.addClass('disabled').find('.text-bold').html('');\n $b.addClass('disabled').find('.text-bold').html('');\n }\n }\n }, {\n key: \"populate_hsp_array\",\n value: function populate_hsp_array(hit, query_id) {\n return hit.hsps.map(function (hsp) {\n return Object.assign(hsp, {\n hit_id: hit.id,\n query_id: query_id\n });\n });\n }\n }, {\n key: \"prepareAlignmentOfSelectedHits\",\n value: function prepareAlignmentOfSelectedHits() {\n var sequence_ids = $('.hit-links :checkbox:checked').map(function () {\n return this.value;\n }).get();\n if (!sequence_ids.length) {\n // remove attributes from link if sequence_ids array is empty\n $('.download-alignment-of-selected').attr('href', '#').removeAttr('download');\n return;\n }\n if (this.state.alignment_blob_url) {\n // always revoke existing url if any because this method will always create a new url\n window.URL.revokeObjectURL(this.state.alignment_blob_url);\n }\n var hsps_arr = [];\n var aln_exporter = new _alignment_exporter__WEBPACK_IMPORTED_MODULE_8__[\"default\"]();\n var self = this;\n underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].each(this.state.queries, underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].bind(function (query) {\n underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].each(query.hits, function (hit) {\n if (underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].indexOf(sequence_ids, hit.id) != -1) {\n hsps_arr = hsps_arr.concat(self.populate_hsp_array(hit, query.id));\n }\n });\n }, this));\n var filename = 'alignment-' + sequence_ids.length + '_hits.txt';\n var blob_url = aln_exporter.prepare_alignments_for_export(hsps_arr, filename);\n // set required download attributes for link\n $('.download-alignment-of-selected').attr('href', blob_url).attr('download', filename);\n // track new url for future removal\n this.setState({\n alignment_blob_url: blob_url\n });\n }\n }, {\n key: \"prepareAlignmentOfAllHits\",\n value: function prepareAlignmentOfAllHits() {\n var _this3 = this;\n // Get number of hits and array of all hsps.\n var num_hits = 0;\n var hsps_arr = [];\n if (!this.state.queries.length) {\n return;\n }\n this.state.queries.forEach(function (query) {\n return query.hits.forEach(function (hit) {\n num_hits++;\n hsps_arr = hsps_arr.concat(_this3.populate_hsp_array(hit, query.id));\n });\n });\n var aln_exporter = new _alignment_exporter__WEBPACK_IMPORTED_MODULE_8__[\"default\"]();\n var file_name = \"alignment-\".concat(num_hits, \"_hits.txt\");\n var blob_url = aln_exporter.prepare_alignments_for_export(hsps_arr, file_name);\n $('.download-alignment-of-all').attr('href', blob_url).attr('download', file_name);\n return false;\n }\n }, {\n key: \"render\",\n value: function render() {\n if (this.isUserWarningPresent()) {\n return this.warningJSX();\n } else if (this.isResultAvailable()) {\n return this.resultsJSX();\n } else {\n return this.loadingJSX();\n }\n }\n }]);\n return Report;\n}(react__WEBPACK_IMPORTED_MODULE_1__.Component);\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Report);\n\n//# sourceURL=webpack://SequenceServer/./public/js/report.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _jquery_world__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./jquery_world */ \"./public/js/jquery_world.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var underscore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! underscore */ \"./node_modules/underscore/modules/index-all.js\");\n/* harmony import */ var _sidebar__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sidebar */ \"./public/js/sidebar.js\");\n/* harmony import */ var _circos__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./circos */ \"./public/js/circos.js\");\n/* harmony import */ var _query__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./query */ \"./public/js/query.js\");\n/* harmony import */ var _hit__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./hit */ \"./public/js/hit.js\");\n/* harmony import */ var _hsp__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./hsp */ \"./public/js/hsp.js\");\n/* harmony import */ var _alignment_exporter__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./alignment_exporter */ \"./public/js/alignment_exporter.js\");\n/* harmony import */ var report_plugins__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! report_plugins */ \"./public/js/null_plugins/report_plugins.js\");\n/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! react/jsx-runtime */ \"./node_modules/react/jsx-runtime.js\");\n/* provided dependency */ var $ = __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nfunction ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\nfunction _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\nfunction _iterableToArray(iter) { if (typeof Symbol !== \"undefined\" && iter[Symbol.iterator] != null || iter[\"@@iterator\"] != null) return Array.from(iter); }\nfunction _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, \"prototype\", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } else if (call !== void 0) { throw new TypeError(\"Derived constructors may only return object or undefined\"); } return _assertThisInitialized(self); }\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n // for custom $.tooltip function\n\n\n\n\n\n\n\n\n\n\n/**\n * Renders entire report.\n *\n * Composed of Query and Sidebar components.\n */\n\n\nvar Report = /*#__PURE__*/function (_Component) {\n _inherits(Report, _Component);\n var _super = _createSuper(Report);\n function Report(props) {\n var _this;\n _classCallCheck(this, Report);\n _this = _super.call(this, props);\n // Properties below are internal state used to render results in small\n // slices (see updateState).\n _this.numUpdates = 0;\n _this.nextQuery = 0;\n _this.nextHit = 0;\n _this.nextHSP = 0;\n _this.maxHSPs = 3; // max HSPs to render in a cycle\n _this.state = {\n user_warning: null,\n download_links: [],\n search_id: '',\n seqserv_version: '',\n program: '',\n program_version: '',\n submitted_at: '',\n queries: [],\n results: [],\n querydb: [],\n params: [],\n stats: [],\n alignment_blob_url: '',\n allQueriesLoaded: false,\n cloud_sharing_enabled: false\n };\n _this.prepareAlignmentOfSelectedHits = _this.prepareAlignmentOfSelectedHits.bind(_assertThisInitialized(_this));\n _this.prepareAlignmentOfAllHits = _this.prepareAlignmentOfAllHits.bind(_assertThisInitialized(_this));\n _this.setStateFromJSON = _this.setStateFromJSON.bind(_assertThisInitialized(_this));\n _this.plugins = new report_plugins__WEBPACK_IMPORTED_MODULE_9__[\"default\"](_assertThisInitialized(_this));\n return _this;\n }\n\n /**\n * Fetch results.\n */\n _createClass(Report, [{\n key: \"fetchResults\",\n value: function fetchResults() {\n var path = location.pathname + '.json' + location.search;\n this.pollPeriodically(path, this.setStateFromJSON, this.props.showErrorModal);\n }\n }, {\n key: \"pollPeriodically\",\n value: function pollPeriodically(path, callback, errCallback) {\n var intervals = [200, 400, 800, 1200, 2000, 3000, 5000];\n function poll() {\n $.getJSON(path).complete(function (jqXHR) {\n switch (jqXHR.status) {\n case 202:\n var interval;\n if (intervals.length === 1) {\n interval = intervals[0];\n } else {\n interval = intervals.shift();\n }\n setTimeout(poll, interval);\n break;\n case 200:\n callback(jqXHR.responseJSON);\n break;\n case 400:\n case 422:\n case 500:\n errCallback(jqXHR.responseJSON);\n break;\n }\n });\n }\n poll();\n }\n\n /**\n * Calls setState after any required modification to responseJSON.\n */\n }, {\n key: \"setStateFromJSON\",\n value: function setStateFromJSON(responseJSON) {\n this.lastTimeStamp = Date.now();\n // the callback prepares the download link for all alignments\n if (responseJSON.user_warning == 'LARGE_RESULT') {\n this.setState({\n user_warning: responseJSON.user_warning,\n download_links: responseJSON.download_links\n });\n } else {\n this.setState(responseJSON, this.prepareAlignmentOfAllHits);\n }\n }\n\n /**\n * Called as soon as the page has loaded and the user sees the loading spinner.\n * We use this opportunity to setup services that make use of delegated events\n * bound to the window, document, or body.\n */\n }, {\n key: \"componentDidMount\",\n value: function componentDidMount() {\n this.fetchResults();\n this.plugins.init();\n // This sets up an event handler which enables users to select text from\n // hit header without collapsing the hit.\n this.preventCollapseOnSelection();\n this.toggleTable();\n }\n\n /**\n * Called for the first time after as BLAST results have been retrieved from\n * the server and added to this.state by fetchResults. Only summary overview\n * and circos would have been rendered at this point. At this stage we kick\n * start iteratively adding 1 HSP to the page every 25 milli-seconds.\n */\n }, {\n key: \"componentDidUpdate\",\n value: function componentDidUpdate(prevProps, prevState) {\n var _this2 = this;\n // Log to console how long the last update take?\n // console.log((Date.now() - this.lastTimeStamp) / 1000);\n\n // Lock sidebar in its position on the first update.\n if (this.nextQuery == 0 && this.nextHit == 0 && this.nextHSP == 0) {\n this.affixSidebar();\n }\n\n // Queue next update if we have not rendered all results yet.\n if (this.nextQuery < this.state.queries.length) {\n // setTimeout is used to clear call stack and space out\n // the updates giving the browser a chance to respond\n // to user interactions.\n setTimeout(function () {\n return _this2.updateState();\n }, 25);\n } else {\n this.componentFinishedUpdating();\n }\n this.plugins.componentDidUpdate(prevProps, prevState);\n }\n\n /**\n * Push next slice of results to React for rendering.\n */\n }, {\n key: \"updateState\",\n value: function updateState() {\n var results = [];\n var numHSPsProcessed = 0;\n while (this.nextQuery < this.state.queries.length) {\n var query = this.state.queries[this.nextQuery];\n\n // We may see a query multiple times during rendering because only\n // 3 hsps are rendered in each cycle, but we want to create the\n // corresponding Query component only the first time we see it.\n if (this.nextHit == 0 && this.nextHSP == 0) {\n results.push( /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_query__WEBPACK_IMPORTED_MODULE_5__.ReportQuery, {\n query: query,\n program: this.state.program,\n querydb: this.state.querydb,\n showQueryCrumbs: this.state.queries.length > 1,\n non_parse_seqids: this.state.non_parse_seqids,\n imported_xml: this.state.imported_xml,\n veryBig: this.state.veryBig\n }, 'Query_' + query.id));\n results.push.apply(results, _toConsumableArray(this.plugins.queryResults(query)));\n }\n while (this.nextHit < query.hits.length) {\n var hit = query.hits[this.nextHit];\n // We may see a hit multiple times during rendering because only\n // 10 hsps are rendered in each cycle, but we want to create the\n // corresponding Hit component only the first time we see it.\n if (this.nextHSP == 0) {\n results.push( /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_hit__WEBPACK_IMPORTED_MODULE_6__[\"default\"], _objectSpread({\n query: query,\n hit: hit,\n algorithm: this.state.program,\n querydb: this.state.querydb,\n selectHit: this.selectHit,\n imported_xml: this.state.imported_xml,\n non_parse_seqids: this.state.non_parse_seqids,\n showQueryCrumbs: this.state.queries.length > 1,\n showHitCrumbs: query.hits.length > 1,\n veryBig: this.state.veryBig,\n onChange: this.prepareAlignmentOfSelectedHits\n }, this.props), 'Query_' + query.number + '_Hit_' + hit.number));\n }\n while (this.nextHSP < hit.hsps.length) {\n // Get nextHSP and increment the counter.\n var hsp = hit.hsps[this.nextHSP++];\n results.push( /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_hsp__WEBPACK_IMPORTED_MODULE_7__[\"default\"], _objectSpread({\n query: query,\n hit: hit,\n hsp: hsp,\n algorithm: this.state.program,\n showHSPNumbers: hit.hsps.length > 1\n }, this.props), 'Query_' + query.number + '_Hit_' + hit.number + '_HSP_' + hsp.number));\n numHSPsProcessed++;\n if (numHSPsProcessed == this.maxHSPs) break;\n }\n // Are we here because we have iterated over all hsps of a hit,\n // or because of the break clause in the inner loop?\n if (this.nextHSP == hit.hsps.length) {\n this.nextHit = this.nextHit + 1;\n this.nextHSP = 0;\n }\n if (numHSPsProcessed == this.maxHSPs) break;\n }\n\n // Are we here because we have iterated over all hits of a query,\n // or because of the break clause in the inner loop?\n if (this.nextHit == query.hits.length) {\n this.nextQuery = this.nextQuery + 1;\n this.nextHit = 0;\n }\n if (numHSPsProcessed == this.maxHSPs) break;\n }\n\n // Push the components to react for rendering.\n this.numUpdates++;\n this.lastTimeStamp = Date.now();\n this.setState({\n results: this.state.results.concat(results),\n veryBig: this.numUpdates >= 250\n });\n }\n\n /**\n * Called after all results have been rendered.\n */\n }, {\n key: \"componentFinishedUpdating\",\n value: function componentFinishedUpdating() {\n if (this.state.allQueriesLoaded) return;\n this.shouldShowIndex() && this.setupScrollSpy();\n this.setState({\n allQueriesLoaded: true\n });\n }\n\n /**\n * Returns loading message\n */\n }, {\n key: \"loadingJSX\",\n value: function loadingJSX() {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"div\", {\n className: \"row\",\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"col-md-6 col-md-offset-3 text-center\",\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"h1\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"i\", {\n className: \"fa fa-cog fa-spin\"\n }), \"\\xA0 BLAST-ing\"]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), \"This can take some time depending on the size of your query and database(s). The page will update automatically when BLAST is done.\", /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), \"You can bookmark the page and come back to it later or share the link with someone.\", /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), false && /*#__PURE__*/0]\n })]\n })\n });\n }\n\n /**\n * Return results JSX.\n */\n }, {\n key: \"resultsJSX\",\n value: function resultsJSX() {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"row\",\n id: \"results\",\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"div\", {\n className: \"col-md-3 hidden-sm hidden-xs\",\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_sidebar__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n data: this.state,\n atLeastOneHit: this.atLeastOneHit(),\n shouldShowIndex: this.shouldShowIndex(),\n allQueriesLoaded: this.state.allQueriesLoaded,\n cloudSharingEnabled: this.state.cloud_sharing_enabled\n })\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"col-md-9\",\n children: [this.overviewJSX(), this.circosJSX(), this.state.results]\n })]\n });\n }\n }, {\n key: \"warningJSX\",\n value: function warningJSX() {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"div\", {\n className: \"container\",\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"div\", {\n className: \"row\",\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"col-md-6 col-md-offset-3 text-center\",\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"h1\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"i\", {\n className: \"fa fa-exclamation-triangle\"\n }), \"\\xA0 Warning\"]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), \"The BLAST result might be too large to load in the browser. If you have a powerful machine you can try loading the results anyway. Otherwise, you can download the results and view them locally.\"]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"p\", {\n children: this.state.download_links.map(function (link, index) {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"a\", {\n href: link.url,\n className: \"btn btn-secondary\",\n children: link.name\n }, 'download_link_' + index);\n })\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"br\", {}), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"p\", {\n children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"a\", {\n href: location.pathname + '?bypass_file_size_warning=true',\n className: \"btn btn-primary\",\n children: \"View results in browser anyway\"\n })\n })]\n })\n })\n });\n }\n /**\n * Renders report overview.\n */\n }, {\n key: \"overviewJSX\",\n value: function overviewJSX() {\n return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"div\", {\n className: \"overview\",\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"strong\", {\n children: [\"SequenceServer \", this.state.seqserv_version]\n }), \" using\", ' ', /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"strong\", {\n children: this.state.program_version\n }), this.state.submitted_at && \", query submitted on \".concat(this.state.submitted_at)]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"strong\", {\n children: \" Databases: \"\n }), this.state.querydb.map(function (db) {\n return db.title;\n }).join(', '), ' ', \"(\", this.state.stats.nsequences, \" sequences,\\xA0\", this.state.stats.ncharacters, \" characters)\"]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"strong\", {\n children: \"Parameters: \"\n }), ' ', underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].map(this.state.params, function (val, key) {\n return key + ' ' + val;\n }).join(', ')]\n }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsxs)(\"p\", {\n children: [\"Please cite:\", ' ', /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"a\", {\n href: \"https://doi.org/10.1093/molbev/msz185\",\n children: \"https://doi.org/10.1093/molbev/msz185\"\n })]\n })]\n });\n }\n\n /**\n * Return JSX for circos if we have at least one hit.\n */\n }, {\n key: \"circosJSX\",\n value: function circosJSX() {\n return this.atLeastTwoHits() ? /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(_circos__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n queries: this.state.queries,\n program: this.state.program,\n collapsed: \"true\"\n }) : /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_10__.jsx)(\"span\", {});\n }\n\n // Controller //\n\n /**\n * Returns true if results have been fetched.\n *\n * A holding message is shown till results are fetched.\n */\n }, {\n key: \"isResultAvailable\",\n value: function isResultAvailable() {\n return this.state.queries.length >= 1;\n }\n\n /**\n * Indicates the response contains a warning message for the user\n * in which case we should not render the results and render the\n * warning instead.\n **/\n }, {\n key: \"isUserWarningPresent\",\n value: function isUserWarningPresent() {\n return this.state.user_warning;\n }\n\n /**\n * Returns true if we have at least one hit.\n */\n }, {\n key: \"atLeastOneHit\",\n value: function atLeastOneHit() {\n return this.state.queries.some(function (query) {\n return query.hits.length > 0;\n });\n }\n\n /**\n * Does the report have at least two hits? This is used to determine\n * whether Circos should be enabled or not.\n */\n }, {\n key: \"atLeastTwoHits\",\n value: function atLeastTwoHits() {\n var hit_num = 0;\n return this.state.queries.some(function (query) {\n hit_num += query.hits.length;\n return hit_num > 1;\n });\n }\n\n /**\n * Returns true if index should be shown in the sidebar. Index is shown\n * only for 2 and 8 queries.\n */\n }, {\n key: \"shouldShowIndex\",\n value: function shouldShowIndex() {\n var num_queries = this.state.queries.length;\n return num_queries >= 2 && num_queries <= 12;\n }\n\n /**\n * Prevents folding of hits during text-selection.\n */\n }, {\n key: \"preventCollapseOnSelection\",\n value: function preventCollapseOnSelection() {\n $('body').on('mousedown', '.hit > .section-header > h4', function (event) {\n var $this = $(this);\n $this.on('mouseup mousemove', function handler(event) {\n if (event.type === 'mouseup') {\n // user wants to toggle\n var hitID = $this.parents('.hit').attr('id');\n $(\"div[data-parent-hit=\".concat(hitID, \"]\")).toggle();\n $this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');\n } else {\n // user wants to select\n $this.attr('data-toggle', '');\n }\n $this.off('mouseup mousemove', handler);\n });\n });\n }\n\n /* Handling the fa icon when Hit Table is collapsed */\n }, {\n key: \"toggleTable\",\n value: function toggleTable() {\n $('body').on('mousedown', '.resultn .caption[data-toggle=\"collapse\"]', function (event) {\n var $this = $(this);\n $this.on('mouseup mousemove', function handler(event) {\n $this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');\n $this.off('mouseup mousemove', handler);\n });\n });\n }\n\n /**\n * Affixes the sidebar.\n */\n }, {\n key: \"affixSidebar\",\n value: function affixSidebar() {\n var $sidebar = $('.sidebar');\n var sidebarOffset = $sidebar.offset();\n if (sidebarOffset) {\n $sidebar.affix({\n offset: {\n top: sidebarOffset.top\n }\n });\n }\n }\n\n /**\n * For the query in viewport, highlights corresponding entry in the index.\n */\n }, {\n key: \"setupScrollSpy\",\n value: function setupScrollSpy() {\n $('body').scrollspy({\n target: '.sidebar'\n });\n }\n\n /**\n * Event-handler when hit is selected\n * Adds glow to hit component.\n * Updates number of Fasta that can be downloaded\n */\n }, {\n key: \"selectHit\",\n value: function selectHit(id) {\n var checkbox = $('#' + id);\n var num_checked = $('.hit-links :checkbox:checked').length;\n if (!checkbox || !checkbox.val()) {\n return;\n }\n var $hit = $(checkbox.data('target'));\n\n // Highlight selected hit and enable 'Download FASTA/Alignment of\n // selected' links.\n if (checkbox.is(':checked')) {\n $hit.addClass('glow');\n $hit.next('.hsp').addClass('glow');\n $('.download-fasta-of-selected').enable();\n $('.download-alignment-of-selected').enable();\n } else {\n $hit.removeClass('glow');\n $hit.next('.hsp').removeClass('glow');\n }\n var $a = $('.download-fasta-of-selected');\n var $b = $('.download-alignment-of-selected');\n if (num_checked >= 1) {\n $a.find('.text-bold').html(num_checked);\n $b.find('.text-bold').html(num_checked);\n }\n if (num_checked == 0) {\n $a.addClass('disabled').find('.text-bold').html('');\n $b.addClass('disabled').find('.text-bold').html('');\n }\n }\n }, {\n key: \"populate_hsp_array\",\n value: function populate_hsp_array(hit, query_id) {\n return hit.hsps.map(function (hsp) {\n return Object.assign(hsp, {\n hit_id: hit.id,\n query_id: query_id\n });\n });\n }\n }, {\n key: \"prepareAlignmentOfSelectedHits\",\n value: function prepareAlignmentOfSelectedHits() {\n var sequence_ids = $('.hit-links :checkbox:checked').map(function () {\n return this.value;\n }).get();\n if (!sequence_ids.length) {\n // remove attributes from link if sequence_ids array is empty\n $('.download-alignment-of-selected').attr('href', '#').removeAttr('download');\n return;\n }\n if (this.state.alignment_blob_url) {\n // always revoke existing url if any because this method will always create a new url\n window.URL.revokeObjectURL(this.state.alignment_blob_url);\n }\n var hsps_arr = [];\n var aln_exporter = new _alignment_exporter__WEBPACK_IMPORTED_MODULE_8__[\"default\"]();\n var self = this;\n underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].each(this.state.queries, underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].bind(function (query) {\n underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].each(query.hits, function (hit) {\n if (underscore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].indexOf(sequence_ids, hit.id) != -1) {\n hsps_arr = hsps_arr.concat(self.populate_hsp_array(hit, query.id));\n }\n });\n }, this));\n var filename = 'alignment-' + sequence_ids.length + '_hits.txt';\n var blob_url = aln_exporter.prepare_alignments_for_export(hsps_arr, filename);\n // set required download attributes for link\n $('.download-alignment-of-selected').attr('href', blob_url).attr('download', filename);\n // track new url for future removal\n this.setState({\n alignment_blob_url: blob_url\n });\n }\n }, {\n key: \"prepareAlignmentOfAllHits\",\n value: function prepareAlignmentOfAllHits() {\n var _this3 = this;\n // Get number of hits and array of all hsps.\n var num_hits = 0;\n var hsps_arr = [];\n if (!this.state.queries.length) {\n return;\n }\n this.state.queries.forEach(function (query) {\n return query.hits.forEach(function (hit) {\n num_hits++;\n hsps_arr = hsps_arr.concat(_this3.populate_hsp_array(hit, query.id));\n });\n });\n var aln_exporter = new _alignment_exporter__WEBPACK_IMPORTED_MODULE_8__[\"default\"]();\n var file_name = \"alignment-\".concat(num_hits, \"_hits.txt\");\n var blob_url = aln_exporter.prepare_alignments_for_export(hsps_arr, file_name);\n $('.download-alignment-of-all').attr('href', blob_url).attr('download', file_name);\n return false;\n }\n }, {\n key: \"render\",\n value: function render() {\n if (this.isUserWarningPresent()) {\n return this.warningJSX();\n } else if (this.isResultAvailable()) {\n return this.resultsJSX();\n } else {\n return this.loadingJSX();\n }\n }\n }]);\n return Report;\n}(react__WEBPACK_IMPORTED_MODULE_1__.Component);\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Report);\n\n//# sourceURL=webpack://SequenceServer/./public/js/report.js?"); /***/ }), diff --git a/webpack.config.js b/webpack.config.js index 361535466..d7036b0ca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -37,7 +37,7 @@ module.exports = (env, argv) => { $: 'jquery', jQuery: 'jquery', }), - new webpack.EnvironmentPlugin({ NODE_ENV: 'development' }), + new webpack.EnvironmentPlugin({ NODE_ENV: 'development', targetEnv: env.targetEnv || 'standalone'}), new webpack.DefinePlugin({ process: { env: {} }, })