sequenceserver 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequenceserver/api_errors.rb +18 -0
  3. data/lib/sequenceserver/blast/hit.rb +1 -2
  4. data/lib/sequenceserver/blast/job.rb +15 -5
  5. data/lib/sequenceserver/blast/report.rb +5 -1
  6. data/lib/sequenceserver/blast/tasks.rb +1 -1
  7. data/lib/sequenceserver/database.rb +5 -1
  8. data/lib/sequenceserver/job.rb +26 -10
  9. data/lib/sequenceserver/makeblastdb.rb +2 -1
  10. data/lib/sequenceserver/routes.rb +38 -13
  11. data/lib/sequenceserver/version.rb +1 -1
  12. data/lib/sequenceserver/zip_file_generator.rb +1 -1
  13. data/lib/sequenceserver.rb +1 -1
  14. data/public/404.html +8 -16
  15. data/public/css/app.css +794 -111
  16. data/public/css/app.min.css +1 -1
  17. data/public/css/sequenceserver.min.css +1 -8
  18. data/public/js/alignment_exporter.js +16 -28
  19. data/public/js/circos.js +103 -96
  20. data/public/{packages/circosJS@1.7.0.js → js/circosjs.js} +156 -778
  21. data/public/js/cloud_share_modal.js +95 -98
  22. data/public/js/collapse_preferences.js +13 -15
  23. data/public/js/databases.js +4 -3
  24. data/public/js/databases_tree.js +51 -30
  25. data/public/js/error_modal.js +35 -22
  26. data/public/js/exporter.js +2 -0
  27. data/public/js/form.js +55 -17
  28. data/public/js/grapher.js +94 -92
  29. data/public/js/hit.js +24 -22
  30. data/public/js/hits.js +283 -0
  31. data/public/js/hits_overview.js +8 -27
  32. data/public/js/hsp.js +140 -144
  33. data/public/js/jquery_world.js +25 -16
  34. data/public/js/kablammo.js +14 -10
  35. data/public/js/length_distribution.js +23 -37
  36. data/public/js/mailto.js +1 -3
  37. data/public/js/null_plugins/report_plugins.js +4 -5
  38. data/public/js/options.js +19 -25
  39. data/public/js/query.js +36 -35
  40. data/public/js/report/alignment_results.js +18 -0
  41. data/public/js/report/graphical_overview.js +38 -0
  42. data/public/js/report/report_header.js +28 -0
  43. data/public/js/report/run_summary.js +43 -0
  44. data/public/js/report.js +124 -348
  45. data/public/js/report_root.js +10 -8
  46. data/public/js/search.js +31 -11
  47. data/public/js/search_button.js +85 -78
  48. data/public/js/sequence.js +169 -180
  49. data/public/js/sequence_modal.js +58 -65
  50. data/public/js/share_url.js +5 -5
  51. data/public/js/sidebar.js +152 -89
  52. data/public/js/svgExporter.js +8 -3
  53. data/public/js/tests/alignment_exporter.spec.js +38 -0
  54. data/public/js/tests/cloud_share_modal.spec.js +75 -0
  55. data/public/js/tests/form.spec.js +1 -1
  56. data/public/js/tests/report.spec.js +37 -15
  57. data/public/js/tests/visualisation_helpers.spec.js +28 -0
  58. data/public/js/visualisation_helpers.js +24 -27
  59. data/public/packages/jquery-ui@1.13.3.js +19070 -0
  60. data/public/sequenceserver-report.min.js +3 -2481
  61. data/public/sequenceserver-report.min.js.LICENSE.txt +213 -0
  62. data/public/sequenceserver-report.min.js.map +1 -0
  63. data/public/sequenceserver-search.min.js +3 -2382
  64. data/public/sequenceserver-search.min.js.LICENSE.txt +262 -0
  65. data/public/sequenceserver-search.min.js.map +1 -0
  66. data/views/error.erb +1 -1
  67. data/views/layout.erb +122 -133
  68. data/views/report.erb +3 -2
  69. data/views/search.erb +10 -14
  70. metadata +21 -28
  71. data/public/config.js +0 -147
  72. data/public/css/fonts.css +0 -31
  73. data/public/css/grapher.css +0 -670
  74. data/public/css/sequenceserver.css +0 -527
  75. data/public/packages/jquery-ui@1.11.4.js +0 -16624
  76. data/public/vendor/github/mbostock/d3@3.5.6/d3.js +0 -9506
  77. data/public/vendor/github/mbostock/d3@3.5.6.js +0 -1
  78. data/views/search_layout.erb +0 -153
data/public/js/report.js CHANGED
@@ -1,14 +1,12 @@
1
1
  import './jquery_world'; // for custom $.tooltip function
2
2
  import React, { Component } from 'react';
3
- import _ from 'underscore';
4
3
 
5
4
  import Sidebar from './sidebar';
6
- import Circos from './circos';
7
- import { ReportQuery } from './query';
8
- import Hit from './hit';
9
- import HSP from './hsp';
10
5
  import AlignmentExporter from './alignment_exporter';
11
6
  import ReportPlugins from 'report_plugins';
7
+ import RunSummary from './report/run_summary';
8
+ import GraphicalOverview from './report/graphical_overview';
9
+ import AlignmentResults from './report/alignment_results';
12
10
 
13
11
  /**
14
12
  * Renders entire report.
@@ -21,11 +19,6 @@ class Report extends Component {
21
19
  super(props);
22
20
  // Properties below are internal state used to render results in small
23
21
  // slices (see updateState).
24
- this.numUpdates = 0;
25
- this.nextQuery = 0;
26
- this.nextHit = 0;
27
- this.nextHSP = 0;
28
- this.maxHSPs = 3; // max HSPs to render in a cycle
29
22
  this.state = {
30
23
  user_warning: null,
31
24
  download_links: [],
@@ -34,8 +27,8 @@ class Report extends Component {
34
27
  program: '',
35
28
  program_version: '',
36
29
  submitted_at: '',
37
- queries: [],
38
30
  results: [],
31
+ queries: [],
39
32
  querydb: [],
40
33
  params: [],
41
34
  stats: [],
@@ -43,7 +36,6 @@ class Report extends Component {
43
36
  allQueriesLoaded: false,
44
37
  cloud_sharing_enabled: false,
45
38
  };
46
- this.prepareAlignmentOfSelectedHits = this.prepareAlignmentOfSelectedHits.bind(this);
47
39
  this.prepareAlignmentOfAllHits = this.prepareAlignmentOfAllHits.bind(this);
48
40
  this.setStateFromJSON = this.setStateFromJSON.bind(this);
49
41
  this.plugins = new ReportPlugins(this);
@@ -58,31 +50,66 @@ class Report extends Component {
58
50
  }
59
51
 
60
52
  pollPeriodically(path, callback, errCallback) {
61
- var intervals = [200, 400, 800, 1200, 2000, 3000, 5000];
53
+ var intervals = [200, 400, 800, 1200, 2000, 3000, 5000];
62
54
  function poll() {
63
- $.getJSON(path).complete(function (jqXHR) {
64
- switch (jqXHR.status) {
65
- case 202:
66
- var interval;
67
- if (intervals.length === 1) {
68
- interval = intervals[0];
55
+ fetch(path)
56
+ .then(response => {
57
+ // Handle HTTP status codes
58
+ if (!response.ok) throw response;
59
+
60
+ return response.text().then(data => {
61
+ if (data) {
62
+ data = parseJSON(data);
63
+ };
64
+ return { status: response.status, data }
65
+ });
66
+ })
67
+ .then(({ status, data }) => {
68
+ switch (status) {
69
+ case 202:
70
+ var interval;
71
+ if (intervals.length === 1) {
72
+ interval = intervals[0];
73
+ } else {
74
+ interval = intervals.shift();
75
+ }
76
+ setTimeout(poll, interval);
77
+ break;
78
+ case 200:
79
+ callback(data);
80
+ break;
81
+ }
82
+ })
83
+ .catch(error => {
84
+ if (error.text) {
85
+ error.text().then(errData => {
86
+ errData = parseJSON(errData);
87
+ switch (error.status) {
88
+ case 400:
89
+ case 422:
90
+ case 500:
91
+ errCallback(errData);
92
+ break;
93
+ default:
94
+ console.error("Unhandled error:", error.status);
95
+ }
96
+ });
69
97
  } else {
70
- interval = intervals.shift();
98
+ console.error("Network error:", error);
71
99
  }
72
- setTimeout(poll, interval);
73
- break;
74
- case 200:
75
- callback(jqXHR.responseJSON);
76
- break;
77
- case 400:
78
- case 422:
79
- case 500:
80
- errCallback(jqXHR.responseJSON);
81
- break;
82
- }
83
- });
100
+ });
84
101
  }
85
102
 
103
+ function parseJSON(str) {
104
+ let parsedJson = str;
105
+ try {
106
+ parsedJson = JSON.parse(str);
107
+ } catch (e) {
108
+ console.error("Error parsing JSON:", e);
109
+ }
110
+
111
+ return parsedJson;
112
+ }
86
113
  poll();
87
114
  }
88
115
 
@@ -106,146 +133,12 @@ class Report extends Component {
106
133
  */
107
134
  componentDidMount() {
108
135
  this.fetchResults();
109
- this.plugins.init();
110
136
  // This sets up an event handler which enables users to select text from
111
137
  // hit header without collapsing the hit.
112
138
  this.preventCollapseOnSelection();
113
139
  this.toggleTable();
114
140
  }
115
141
 
116
- /**
117
- * Called for the first time after as BLAST results have been retrieved from
118
- * the server and added to this.state by fetchResults. Only summary overview
119
- * and circos would have been rendered at this point. At this stage we kick
120
- * start iteratively adding 1 HSP to the page every 25 milli-seconds.
121
- */
122
- componentDidUpdate(prevProps, prevState) {
123
- // Log to console how long the last update take?
124
- // console.log((Date.now() - this.lastTimeStamp) / 1000);
125
-
126
- // Lock sidebar in its position on the first update.
127
- if (this.nextQuery == 0 && this.nextHit == 0 && this.nextHSP == 0) {
128
- this.affixSidebar();
129
- }
130
-
131
- // Queue next update if we have not rendered all results yet.
132
- if (this.nextQuery < this.state.queries.length) {
133
- // setTimeout is used to clear call stack and space out
134
- // the updates giving the browser a chance to respond
135
- // to user interactions.
136
- setTimeout(() => this.updateState(), 25);
137
- } else {
138
- this.componentFinishedUpdating();
139
- }
140
-
141
- this.plugins.componentDidUpdate(prevProps, prevState);
142
- }
143
-
144
- /**
145
- * Push next slice of results to React for rendering.
146
- */
147
- updateState() {
148
- var results = [];
149
- var numHSPsProcessed = 0;
150
- while (this.nextQuery < this.state.queries.length) {
151
- var query = this.state.queries[this.nextQuery];
152
-
153
- // We may see a query multiple times during rendering because only
154
- // 3 hsps are rendered in each cycle, but we want to create the
155
- // corresponding Query component only the first time we see it.
156
- if (this.nextHit == 0 && this.nextHSP == 0) {
157
- results.push(
158
- <ReportQuery
159
- key={'Query_' + query.id}
160
- query={query}
161
- program={this.state.program}
162
- querydb={this.state.querydb}
163
- showQueryCrumbs={this.state.queries.length > 1}
164
- non_parse_seqids={this.state.non_parse_seqids}
165
- imported_xml={this.state.imported_xml}
166
- veryBig={this.state.veryBig}
167
- />
168
- );
169
-
170
- results.push(...this.plugins.queryResults(query));
171
- }
172
-
173
- while (this.nextHit < query.hits.length) {
174
- var hit = query.hits[this.nextHit];
175
- // We may see a hit multiple times during rendering because only
176
- // 10 hsps are rendered in each cycle, but we want to create the
177
- // corresponding Hit component only the first time we see it.
178
- if (this.nextHSP == 0) {
179
- results.push(
180
- <Hit
181
- key={'Query_' + query.number + '_Hit_' + hit.number}
182
- query={query}
183
- hit={hit}
184
- algorithm={this.state.program}
185
- querydb={this.state.querydb}
186
- selectHit={this.selectHit}
187
- imported_xml={this.state.imported_xml}
188
- non_parse_seqids={this.state.non_parse_seqids}
189
- showQueryCrumbs={this.state.queries.length > 1}
190
- showHitCrumbs={query.hits.length > 1}
191
- veryBig={this.state.veryBig}
192
- onChange={this.prepareAlignmentOfSelectedHits}
193
- {...this.props}
194
- />
195
- );
196
- }
197
-
198
- while (this.nextHSP < hit.hsps.length) {
199
- // Get nextHSP and increment the counter.
200
- var hsp = hit.hsps[this.nextHSP++];
201
- results.push(
202
- <HSP
203
- key={
204
- 'Query_' +
205
- query.number +
206
- '_Hit_' +
207
- hit.number +
208
- '_HSP_' +
209
- hsp.number
210
- }
211
- query={query}
212
- hit={hit}
213
- hsp={hsp}
214
- algorithm={this.state.program}
215
- showHSPNumbers={hit.hsps.length > 1}
216
- {...this.props}
217
- />
218
- );
219
- numHSPsProcessed++;
220
- if (numHSPsProcessed == this.maxHSPs) break;
221
- }
222
- // Are we here because we have iterated over all hsps of a hit,
223
- // or because of the break clause in the inner loop?
224
- if (this.nextHSP == hit.hsps.length) {
225
- this.nextHit = this.nextHit + 1;
226
- this.nextHSP = 0;
227
- }
228
- if (numHSPsProcessed == this.maxHSPs) break;
229
- }
230
-
231
- // Are we here because we have iterated over all hits of a query,
232
- // or because of the break clause in the inner loop?
233
- if (this.nextHit == query.hits.length) {
234
- this.nextQuery = this.nextQuery + 1;
235
- this.nextHit = 0;
236
- }
237
- if (numHSPsProcessed == this.maxHSPs) break;
238
- }
239
-
240
- // Push the components to react for rendering.
241
- this.numUpdates++;
242
- this.lastTimeStamp = Date.now();
243
- this.setState({
244
- results: this.state.results.concat(results),
245
- veryBig: this.numUpdates >= 250,
246
- });
247
- }
248
-
249
142
  /**
250
143
  * Called after all results have been rendered.
251
144
  */
@@ -260,21 +153,20 @@ class Report extends Component {
260
153
  */
261
154
  loadingJSX() {
262
155
  return (
263
- <div className="row">
264
- <div className="col-md-6 col-md-offset-3 text-center">
265
- <h1>
156
+ <div className="grid grid-cols-6 gap-4">
157
+ <div className="col-start-1 col-end-7 text-center pt-3">
158
+ <h1 className="mb-8 text-4xl">
266
159
  <i className="fa fa-cog fa-spin"></i>&nbsp; BLAST-ing
267
160
  </h1>
268
- <p>
269
- <br />
270
- This can take some time depending on the size of your query and
271
- database(s). The page will update automatically when BLAST is done.
272
- <br />
273
- <br />
274
- You can bookmark the page and come back to it later or share the
275
- link with someone.
276
- <br />
277
- <br />
161
+ <div className="mb-5 w-full">
162
+ <p className="m-auto w-full md:w-6/12 text-sm">This can take some time depending on the size of your query and
163
+ database(s). The page will update automatically when BLAST is done.</p>
164
+ </div>
165
+ <p className="mb-9 text-sm">
166
+ You can bookmark the page and come back to it later or share the
167
+ link with someone.
168
+ </p>
169
+ <p className="text-sm">
278
170
  { process.env.targetEnv === 'cloud' && <b>If the job takes more than 10 minutes to complete, we will send you an email upon completion.</b> }
279
171
  </p>
280
172
  </div>
@@ -282,13 +174,14 @@ class Report extends Component {
282
174
  );
283
175
  }
284
176
 
177
+ /* eslint-disable */
285
178
  /**
286
179
  * Return results JSX.
287
180
  */
288
181
  resultsJSX() {
289
182
  return (
290
- <div className="row" id="results">
291
- <div className="col-md-3 hidden-sm hidden-xs">
183
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4 print:grid-cols-1" id="results">
184
+ <div className="hidden md:col-span-1 md:block print:hidden">
292
185
  <Sidebar
293
186
  data={this.state}
294
187
  atLeastOneHit={this.atLeastOneHit()}
@@ -297,31 +190,46 @@ class Report extends Component {
297
190
  cloudSharingEnabled={this.state.cloud_sharing_enabled}
298
191
  />
299
192
  </div>
300
- <div className="col-md-9">
301
- {this.overviewJSX()}
302
- {this.circosJSX()}
303
- {this.plugins.generateStats()}
304
- {this.state.results}
193
+ <div className="col-span-1 md:col-span-3 print:col-span-1">
194
+ <RunSummary
195
+ seqserv_version={this.state.seqserv_version}
196
+ program_version={this.state.program_version}
197
+ submitted_at={this.state.submitted_at}
198
+ querydb={this.state.querydb}
199
+ stats={this.state.stats}
200
+ params={this.state.params}
201
+ />
202
+ <GraphicalOverview
203
+ queries={this.state.queries}
204
+ prorgam={this.state.program}
205
+ plugins={this.plugins}
206
+ />
207
+ <AlignmentResults
208
+ state={this.state}
209
+ populate_hsp_array={this.populate_hsp_array.bind(this)}
210
+ componentFinishedUpdating={(_) => this.componentFinishedUpdating(_)}
211
+ plugins={this.plugins}
212
+ {...this.props}
213
+ />
305
214
  </div>
306
215
  </div>
307
216
  );
308
217
  }
218
+ /* eslint-enable */
309
219
 
310
220
 
311
221
  warningJSX() {
312
222
  return(
313
- <div className="container">
314
- <div className="row">
315
- <div className="col-md-6 col-md-offset-3 text-center">
316
- <h1>
223
+ <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
224
+ <div className="grid grid-cols-6 gap-4">
225
+ <div className="col-start-1 col-end-7 text-center">
226
+ <h1 className="mb-4 text-4xl">
317
227
  <i className="fa fa-exclamation-triangle"></i>&nbsp; Warning
318
228
  </h1>
319
- <p>
320
- <br />
229
+ <p className="mb-2">
321
230
  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.
322
231
  </p>
323
- <br />
324
- <p>
232
+ <p className="mb-2">
325
233
  {this.state.download_links.map((link, index) => {
326
234
  return (
327
235
  <a href={link.url} className="btn btn-secondary" key={'download_link_' + index} >
@@ -330,9 +238,8 @@ class Report extends Component {
330
238
  );
331
239
  })}
332
240
  </p>
333
- <br />
334
241
  <p>
335
- <a href={location.pathname + '?bypass_file_size_warning=true'} className="btn btn-primary">
242
+ <a href={location.pathname + '?bypass_file_size_warning=true'} className="py-2 px-3 border border-transparent rounded-md shadow-sm text-white bg-seqblue hover:bg-seqorange focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-seqorange">
336
243
  View results in browser anyway
337
244
  </a>
338
245
  </p>
@@ -341,57 +248,6 @@ class Report extends Component {
341
248
  </div>
342
249
  );
343
250
  }
344
- /**
345
- * Renders report overview.
346
- */
347
- overviewJSX() {
348
- return (
349
- <div className="overview">
350
- <p>
351
- <strong>SequenceServer {this.state.seqserv_version}</strong> using{' '}
352
- <strong>{this.state.program_version}</strong>
353
- {this.state.submitted_at &&
354
- `, query submitted on ${this.state.submitted_at}`}
355
- </p>
356
- <p>
357
- <strong> Databases: </strong>
358
- {this.state.querydb
359
- .map((db) => {
360
- return db.title;
361
- })
362
- .join(', ')}{' '}
363
- ({this.state.stats.nsequences} sequences,&nbsp;
364
- {this.state.stats.ncharacters} characters)
365
- </p>
366
- <p>
367
- <strong>Parameters: </strong>{' '}
368
- {_.map(this.state.params, function (val, key) {
369
- return key + ' ' + val;
370
- }).join(', ')}
371
- </p>
372
- <p>
373
- Please cite:{' '}
374
- <a href="https://doi.org/10.1093/molbev/msz185">
375
- https://doi.org/10.1093/molbev/msz185
376
- </a>
377
- </p>
378
- </div>
379
- );
380
- }
381
-
382
- /**
383
- * Return JSX for circos if we have at least one hit.
384
- */
385
- circosJSX() {
386
- return this.atLeastTwoHits() ? (
387
- <Circos
388
- queries={this.state.queries}
389
- program={this.state.program}
390
- />
391
- ) : (
392
- <span></span>
393
- );
394
- }
395
251
 
396
252
  // Controller //
397
253
 
@@ -420,18 +276,6 @@ class Report extends Component {
420
276
  return this.state.queries.some((query) => query.hits.length > 0);
421
277
  }
422
278
 
423
- /**
424
- * Does the report have at least two hits? This is used to determine
425
- * whether Circos should be enabled or not.
426
- */
427
- atLeastTwoHits() {
428
- var hit_num = 0;
429
- return this.state.queries.some((query) => {
430
- hit_num += query.hits.length;
431
- return hit_num > 1;
432
- });
433
- }
434
-
435
279
  /**
436
280
  * Returns true if index should be shown in the sidebar. Index is shown
437
281
  * only for 2 and 8 queries.
@@ -452,7 +296,8 @@ class Report extends Component {
452
296
  // user wants to toggle
453
297
  var hitID = $this.parents('.hit').attr('id');
454
298
  $(`div[data-parent-hit=${hitID}]`).toggle();
455
- $this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');
299
+ $this.find('i').toggleClass('fa-square-minus fa-square-plus');
300
+ $($this.data('parent-id')).toggleClass('print:hidden');
456
301
  } else {
457
302
  // user wants to select
458
303
  $this.attr('data-toggle', '');
@@ -463,6 +308,7 @@ class Report extends Component {
463
308
  }
464
309
 
465
310
  /* Handling the fa icon when Hit Table is collapsed */
311
+ /* TODO:JOKO check if this method still being used? */
466
312
  toggleTable() {
467
313
  $('body').on(
468
314
  'mousedown',
@@ -470,113 +316,43 @@ class Report extends Component {
470
316
  function (event) {
471
317
  var $this = $(this);
472
318
  $this.on('mouseup mousemove', function handler(event) {
473
- $this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');
319
+ $this.find('i').toggleClass('fa-square-minus fa-square-plus');
474
320
  $this.off('mouseup mousemove', handler);
475
321
  });
476
322
  }
477
323
  );
478
324
  }
479
325
 
480
- /**
481
- * Affixes the sidebar.
482
- */
483
- affixSidebar() {
484
- var $sidebar = $('.sidebar');
485
- var sidebarOffset = $sidebar.offset();
486
- if (sidebarOffset) {
487
- $sidebar.affix({
488
- offset: {
489
- top: sidebarOffset.top,
490
- },
491
- });
492
- }
493
- }
326
+
494
327
 
495
328
  /**
496
329
  * For the query in viewport, highlights corresponding entry in the index.
497
330
  */
498
331
  setupScrollSpy() {
499
- $('body').scrollspy({ target: '.sidebar' });
500
- }
501
-
502
- /**
503
- * Event-handler when hit is selected
504
- * Adds glow to hit component.
505
- * Updates number of Fasta that can be downloaded
506
- */
507
- selectHit(id) {
508
- var checkbox = $('#' + id);
509
- var num_checked = $('.hit-links :checkbox:checked').length;
332
+ var sectionIds = $('a.side-nav');
510
333
 
511
- if (!checkbox || !checkbox.val()) {
512
- return;
513
- }
334
+ $(document).scroll(function(){
335
+ sectionIds.each(function(){
514
336
 
515
- var $hit = $(checkbox.data('target'));
337
+ var container = $(this).attr('href');
338
+ var containerOffset = $(container).offset().top;
339
+ var containerHeight = $(container).outerHeight();
340
+ var containerBottom = containerOffset + containerHeight;
341
+ var scrollPosition = $(document).scrollTop();
516
342
 
517
- // Highlight selected hit and enable 'Download FASTA/Alignment of
518
- // selected' links.
519
- if (checkbox.is(':checked')) {
520
- $hit.addClass('glow');
521
- $hit.next('.hsp').addClass('glow');
522
- $('.download-fasta-of-selected').enable();
523
- $('.download-alignment-of-selected').enable();
524
- } else {
525
- $hit.removeClass('glow');
526
- $hit.next('.hsp').removeClass('glow');
527
- $('.download-fasta-of-selected').attr('href', '#').removeAttr('download');
528
- }
529
-
530
- var $a = $('.download-fasta-of-selected');
531
- var $b = $('.download-alignment-of-selected');
532
-
533
- if (num_checked >= 1) {
534
- $a.find('.text-bold').html(num_checked);
535
- $b.find('.text-bold').html(num_checked);
536
- }
537
-
538
- if (num_checked == 0) {
539
- $a.addClass('disabled').find('.text-bold').html('');
540
- $b.addClass('disabled').find('.text-bold').html('');
541
- }
343
+ if(scrollPosition < containerBottom - 20 && scrollPosition >= containerOffset - 20){
344
+ $(this).addClass('active');
345
+ } else {
346
+ $(this).removeClass('active');
347
+ }
348
+ });
349
+ });
542
350
  }
351
+
543
352
  populate_hsp_array(hit, query_id){
544
353
  return hit.hsps.map(hsp => Object.assign(hsp, {hit_id: hit.id, query_id}));
545
354
  }
546
355
 
547
- prepareAlignmentOfSelectedHits() {
548
- var sequence_ids = $('.hit-links :checkbox:checked').map(function () {
549
- return this.value;
550
- }).get();
551
-
552
- if(!sequence_ids.length){
553
- // remove attributes from link if sequence_ids array is empty
554
- $('.download-alignment-of-selected').attr('href', '#').removeAttr('download');
555
- return;
556
-
557
- }
558
- if(this.state.alignment_blob_url){
559
- // always revoke existing url if any because this method will always create a new url
560
- window.URL.revokeObjectURL(this.state.alignment_blob_url);
561
- }
562
- var hsps_arr = [];
563
- var aln_exporter = new AlignmentExporter();
564
- const self = this;
565
- _.each(this.state.queries, _.bind(function (query) {
566
- _.each(query.hits, function (hit) {
567
- if (_.indexOf(sequence_ids, hit.id) != -1) {
568
- hsps_arr = hsps_arr.concat(self.populate_hsp_array(hit, query.id));
569
- }
570
- });
571
- }, this));
572
- const filename = 'alignment-' + sequence_ids.length + '_hits.txt';
573
- const blob_url = aln_exporter.prepare_alignments_for_export(hsps_arr, filename);
574
- // set required download attributes for link
575
- $('.download-alignment-of-selected').attr('href', blob_url).attr('download', filename);
576
- // track new url for future removal
577
- this.setState({alignment_blob_url: blob_url});
578
- }
579
-
580
356
  prepareAlignmentOfAllHits() {
581
357
  // Get number of hits and array of all hsps.
582
358
  var num_hits = 0;