sequenceserver 3.1.3 → 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 (68) 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/database.rb +5 -1
  7. data/lib/sequenceserver/job.rb +26 -10
  8. data/lib/sequenceserver/makeblastdb.rb +2 -1
  9. data/lib/sequenceserver/routes.rb +37 -12
  10. data/lib/sequenceserver/version.rb +1 -1
  11. data/lib/sequenceserver/zip_file_generator.rb +1 -1
  12. data/public/404.html +8 -16
  13. data/public/css/app.css +794 -111
  14. data/public/css/app.min.css +1 -1
  15. data/public/css/sequenceserver.min.css +1 -8
  16. data/public/js/circos.js +103 -96
  17. data/public/{packages/circosJS@1.7.0.js → js/circosjs.js} +156 -778
  18. data/public/js/cloud_share_modal.js +55 -58
  19. data/public/js/collapse_preferences.js +13 -15
  20. data/public/js/databases.js +4 -3
  21. data/public/js/databases_tree.js +51 -30
  22. data/public/js/error_modal.js +35 -22
  23. data/public/js/exporter.js +2 -0
  24. data/public/js/form.js +43 -7
  25. data/public/js/grapher.js +94 -92
  26. data/public/js/hit.js +21 -19
  27. data/public/js/hits.js +45 -38
  28. data/public/js/hits_overview.js +8 -27
  29. data/public/js/hsp.js +140 -144
  30. data/public/js/jquery_world.js +24 -15
  31. data/public/js/kablammo.js +14 -10
  32. data/public/js/length_distribution.js +23 -37
  33. data/public/js/null_plugins/report_plugins.js +3 -5
  34. data/public/js/options.js +17 -19
  35. data/public/js/query.js +36 -35
  36. data/public/js/report/alignment_results.js +18 -0
  37. data/public/js/report/graphical_overview.js +38 -0
  38. data/public/js/report/report_header.js +28 -0
  39. data/public/js/report/run_summary.js +43 -0
  40. data/public/js/report.js +62 -102
  41. data/public/js/report_root.js +3 -3
  42. data/public/js/search.js +5 -2
  43. data/public/js/search_button.js +85 -78
  44. data/public/js/sequence.js +11 -22
  45. data/public/js/sequence_modal.js +38 -37
  46. data/public/js/share_url.js +5 -5
  47. data/public/js/sidebar.js +148 -86
  48. data/public/js/svgExporter.js +8 -3
  49. data/public/js/tests/form.spec.js +1 -1
  50. data/public/js/tests/visualisation_helpers.spec.js +28 -0
  51. data/public/js/visualisation_helpers.js +24 -27
  52. data/public/sequenceserver-report.min.js +1 -1
  53. data/public/sequenceserver-report.min.js.LICENSE.txt +1 -88
  54. data/public/sequenceserver-report.min.js.map +1 -1
  55. data/public/sequenceserver-search.min.js +1 -1
  56. data/public/sequenceserver-search.min.js.LICENSE.txt +0 -30
  57. data/public/sequenceserver-search.min.js.map +1 -1
  58. data/views/error.erb +1 -1
  59. data/views/layout.erb +123 -130
  60. data/views/report.erb +3 -2
  61. data/views/search.erb +10 -14
  62. metadata +13 -26
  63. data/public/css/fonts.css +0 -31
  64. data/public/css/grapher.css +0 -670
  65. data/public/css/sequenceserver.css +0 -509
  66. data/public/vendor/github/mbostock/d3@3.5.6/d3.js +0 -9506
  67. data/public/vendor/github/mbostock/d3@3.5.6.js +0 -1
  68. data/views/search_layout.erb +0 -153
data/public/js/report.js CHANGED
@@ -1,12 +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 Hits from './hits';
7
- import Circos from './circos';
8
5
  import AlignmentExporter from './alignment_exporter';
9
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';
10
10
 
11
11
  /**
12
12
  * Renders entire report.
@@ -133,7 +133,6 @@ class Report extends Component {
133
133
  */
134
134
  componentDidMount() {
135
135
  this.fetchResults();
136
- this.plugins.init();
137
136
  // This sets up an event handler which enables users to select text from
138
137
  // hit header without collapsing the hit.
139
138
  this.preventCollapseOnSelection();
@@ -154,21 +153,20 @@ class Report extends Component {
154
153
  */
155
154
  loadingJSX() {
156
155
  return (
157
- <div className="row">
158
- <div className="col-md-6 col-md-offset-3 text-center">
159
- <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">
160
159
  <i className="fa fa-cog fa-spin"></i>&nbsp; BLAST-ing
161
160
  </h1>
162
- <p>
163
- <br />
164
- This can take some time depending on the size of your query and
165
- database(s). The page will update automatically when BLAST is done.
166
- <br />
167
- <br />
168
- You can bookmark the page and come back to it later or share the
169
- link with someone.
170
- <br />
171
- <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">
172
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> }
173
171
  </p>
174
172
  </div>
@@ -182,8 +180,8 @@ class Report extends Component {
182
180
  */
183
181
  resultsJSX() {
184
182
  return (
185
- <div className="row" id="results">
186
- <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">
187
185
  <Sidebar
188
186
  data={this.state}
189
187
  atLeastOneHit={this.atLeastOneHit()}
@@ -192,15 +190,24 @@ class Report extends Component {
192
190
  cloudSharingEnabled={this.state.cloud_sharing_enabled}
193
191
  />
194
192
  </div>
195
- <div className="col-md-9">
196
- {this.overviewJSX()}
197
- {this.circosJSX()}
198
- {this.plugins.generateStats(this.state.queries)}
199
- {this.state.results}
200
- <Hits
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
201
208
  state={this.state}
202
- componentFinishedUpdating={(_) => this.componentFinishedUpdating(_)}
203
209
  populate_hsp_array={this.populate_hsp_array.bind(this)}
210
+ componentFinishedUpdating={(_) => this.componentFinishedUpdating(_)}
204
211
  plugins={this.plugins}
205
212
  {...this.props}
206
213
  />
@@ -213,18 +220,16 @@ class Report extends Component {
213
220
 
214
221
  warningJSX() {
215
222
  return(
216
- <div className="container">
217
- <div className="row">
218
- <div className="col-md-6 col-md-offset-3 text-center">
219
- <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">
220
227
  <i className="fa fa-exclamation-triangle"></i>&nbsp; Warning
221
228
  </h1>
222
- <p>
223
- <br />
229
+ <p className="mb-2">
224
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.
225
231
  </p>
226
- <br />
227
- <p>
232
+ <p className="mb-2">
228
233
  {this.state.download_links.map((link, index) => {
229
234
  return (
230
235
  <a href={link.url} className="btn btn-secondary" key={'download_link_' + index} >
@@ -233,9 +238,8 @@ class Report extends Component {
233
238
  );
234
239
  })}
235
240
  </p>
236
- <br />
237
241
  <p>
238
- <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">
239
243
  View results in browser anyway
240
244
  </a>
241
245
  </p>
@@ -244,57 +248,6 @@ class Report extends Component {
244
248
  </div>
245
249
  );
246
250
  }
247
- /**
248
- * Renders report overview.
249
- */
250
- overviewJSX() {
251
- return (
252
- <div className="overview">
253
- <p>
254
- <strong>SequenceServer {this.state.seqserv_version}</strong> using{' '}
255
- <strong>{this.state.program_version}</strong>
256
- {this.state.submitted_at &&
257
- `, query submitted on ${this.state.submitted_at}`}
258
- </p>
259
- <p>
260
- <strong> Databases: </strong>
261
- {this.state.querydb
262
- .map((db) => {
263
- return db.title;
264
- })
265
- .join(', ')}{' '}
266
- ({this.state.stats.nsequences} sequences,&nbsp;
267
- {this.state.stats.ncharacters} characters)
268
- </p>
269
- <p>
270
- <strong>Parameters: </strong>{' '}
271
- {_.map(this.state.params, function (val, key) {
272
- return key + ' ' + val;
273
- }).join(', ')}
274
- </p>
275
- <p>
276
- Please cite:{' '}
277
- <a href="https://doi.org/10.1093/molbev/msz185">
278
- https://doi.org/10.1093/molbev/msz185
279
- </a>
280
- </p>
281
- </div>
282
- );
283
- }
284
-
285
- /**
286
- * Return JSX for circos if we have at least one hit.
287
- */
288
- circosJSX() {
289
- return this.atLeastTwoHits() ? (
290
- <Circos
291
- queries={this.state.queries}
292
- program={this.state.program}
293
- />
294
- ) : (
295
- <span></span>
296
- );
297
- }
298
251
 
299
252
  // Controller //
300
253
 
@@ -323,18 +276,6 @@ class Report extends Component {
323
276
  return this.state.queries.some((query) => query.hits.length > 0);
324
277
  }
325
278
 
326
- /**
327
- * Does the report have at least two hits? This is used to determine
328
- * whether Circos should be enabled or not.
329
- */
330
- atLeastTwoHits() {
331
- var hit_num = 0;
332
- return this.state.queries.some((query) => {
333
- hit_num += query.hits.length;
334
- return hit_num > 1;
335
- });
336
- }
337
-
338
279
  /**
339
280
  * Returns true if index should be shown in the sidebar. Index is shown
340
281
  * only for 2 and 8 queries.
@@ -355,7 +296,8 @@ class Report extends Component {
355
296
  // user wants to toggle
356
297
  var hitID = $this.parents('.hit').attr('id');
357
298
  $(`div[data-parent-hit=${hitID}]`).toggle();
358
- $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');
359
301
  } else {
360
302
  // user wants to select
361
303
  $this.attr('data-toggle', '');
@@ -366,6 +308,7 @@ class Report extends Component {
366
308
  }
367
309
 
368
310
  /* Handling the fa icon when Hit Table is collapsed */
311
+ /* TODO:JOKO check if this method still being used? */
369
312
  toggleTable() {
370
313
  $('body').on(
371
314
  'mousedown',
@@ -373,7 +316,7 @@ class Report extends Component {
373
316
  function (event) {
374
317
  var $this = $(this);
375
318
  $this.on('mouseup mousemove', function handler(event) {
376
- $this.find('i').toggleClass('fa-minus-square-o fa-plus-square-o');
319
+ $this.find('i').toggleClass('fa-square-minus fa-square-plus');
377
320
  $this.off('mouseup mousemove', handler);
378
321
  });
379
322
  }
@@ -386,7 +329,24 @@ class Report extends Component {
386
329
  * For the query in viewport, highlights corresponding entry in the index.
387
330
  */
388
331
  setupScrollSpy() {
389
- $('body').scrollspy({ target: '.sidebar' });
332
+ var sectionIds = $('a.side-nav');
333
+
334
+ $(document).scroll(function(){
335
+ sectionIds.each(function(){
336
+
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();
342
+
343
+ if(scrollPosition < containerBottom - 20 && scrollPosition >= containerOffset - 20){
344
+ $(this).addClass('active');
345
+ } else {
346
+ $(this).removeClass('active');
347
+ }
348
+ });
349
+ });
390
350
  }
391
351
 
392
352
  populate_hsp_array(hit, query_id){
@@ -43,9 +43,9 @@ class Page extends Component {
43
43
  render() {
44
44
  return (
45
45
  <div>
46
- {/* Provide bootstrap .container element inside the #view for
46
+ {/* Provide tailwind element inside the #view for
47
47
  the Report component to render itself in. */}
48
- <div className="container">
48
+ <div className="mx-auto">
49
49
  <Report
50
50
  showSequenceModal={(_) => this.showSequenceModal(_)}
51
51
  getCharacterWidth={() => this.getCharacterWidth()}
@@ -54,7 +54,7 @@ class Page extends Component {
54
54
  </div>
55
55
 
56
56
  {/* Add a hidden span tag containing chars used in HSPs */}
57
- <pre className="pre-reset hsp-lines" ref={this.hspChars} hidden>
57
+ <pre className="m-0 p-0 rounded-none border-0 bg-inherit whitespace-pre-wrap break-keep mt-1 pre-text tracking-wider" ref={this.hspChars} hidden>
58
58
  ABCDEFGHIJKLMNOPQRSTUVWXYZ +-
59
59
  </pre>
60
60
 
data/public/js/search.js CHANGED
@@ -40,10 +40,13 @@ const root = createRoot(document.getElementById('view'));
40
40
  root.render(<Page />);
41
41
 
42
42
  document.addEventListener('DOMContentLoaded', () => {
43
- const closeButton = document.querySelector('.js--close-help');
43
+ const closeButton = document.querySelector('button.advanced-modal-close');
44
+ const modal = document.querySelector('dialog.advanced-modal');
45
+ modal.addEventListener('close', () => { document.body.classList.remove("overflow-hidden") });
46
+
44
47
  if (closeButton) {
45
48
  closeButton.addEventListener('click', function() {
46
- document.querySelector('[data-help-modal]').classList.add('hidden');
49
+ modal.close();
47
50
  });
48
51
  }
49
52
  });
@@ -1,6 +1,5 @@
1
1
  import React, { Component, createRef } from 'react';
2
2
  import _ from 'underscore';
3
- import './jquery_world';
4
3
 
5
4
  /**
6
5
  * SearchButton widget.
@@ -13,23 +12,31 @@ export class SearchButton extends Component {
13
12
  hasQuery: false,
14
13
  hasDatabases: false,
15
14
  dropdownVisible: false,
15
+ messageTooltip: "",
16
+ titleTooltip: "",
17
+ hideMessageTooltip: false
16
18
  };
17
- this.inputGroup = this.inputGroup.bind(this);
18
- this.submitButton = this.submitButton.bind(this);
19
- this.initTooltip = this.initTooltip.bind(this);
20
- this.showTooltip = this.showTooltip.bind(this);
21
- this.hideTooltip = this.hideTooltip.bind(this);
22
19
  this.changeAlgorithm = this.changeAlgorithm.bind(this);
23
20
  this.decorate = this.decorate.bind(this);
21
+ this.setMessageTooltipButton = this.setMessageTooltipButton.bind(this);
22
+ this.setTitleTooltipButton = this.setTitleTooltipButton.bind(this);
23
+ this.renderButton = this.renderButton.bind(this);
24
+ this.inputGroup = this.inputGroup.bind(this);
25
+ this.handleMouseOver = this.handleMouseOver.bind(this)
26
+ this.handeMouseOut = this.handeMouseOut.bind(this)
24
27
  this.inputGroupRef = createRef();
25
- this.submitButtonRef = createRef();
26
28
  }
29
+
27
30
  componentDidMount() {
28
- this.initTooltip();
31
+ this.timeout = setTimeout(() => this.setState({ hideMessageTooltip: true}), 3000)
29
32
  }
30
33
 
31
34
  shouldComponentUpdate(props, state) {
32
- return !_.isEqual(state.methods, this.state.methods) || state.dropdownVisible !== this.state.dropdownVisible;
35
+ return !_.isEqual(state.methods, this.state.methods) ||
36
+ state.dropdownVisible !== this.state.dropdownVisible ||
37
+ state.messageTooltip !== this.state.messageTooltip ||
38
+ state.titleTooltip !== this.state.titleTooltip ||
39
+ state.hideMessageTooltip !== this.state.hideMessageTooltip;
33
40
  }
34
41
 
35
42
  componentDidUpdate(_prevProps, prevState) {
@@ -42,66 +49,11 @@ export class SearchButton extends Component {
42
49
  }
43
50
  }
44
51
  }
45
- // Internal helpers. //
46
52
 
47
- /**
48
- * Returns jquery wrapped input group.
49
- */
50
53
  inputGroup() {
51
54
  return $(this.inputGroupRef.current);
52
55
  }
53
56
 
54
- /**
55
- * Returns jquery wrapped submit button.
56
- */
57
- submitButton() {
58
- return $(this.submitButtonRef.current);
59
- }
60
-
61
- /**
62
- * Initialise tooltip on input group and submit button.
63
- */
64
- initTooltip() {
65
- this.inputGroup().tooltip({
66
- trigger: 'manual',
67
- title: _.bind(function () {
68
- if (!this.state.hasQuery && !this.state.hasDatabases) {
69
- return 'You must enter a query sequence and select one or more databases above before you can run a search!';
70
- } else if (this.state.hasQuery && !this.state.hasDatabases) {
71
- return 'You must select one or more databases above before you can run a search!';
72
- } else if (!this.state.hasQuery && this.state.hasDatabases) {
73
- return 'You must enter a query sequence above before you can run a search!';
74
- }
75
- }, this),
76
- });
77
-
78
- this.submitButton().tooltip({
79
- title: _.bind(function () {
80
- var title = 'Click to BLAST or press Ctrl+Enter.';
81
- if (this.state.methods.length > 1) {
82
- title +=
83
- ' Click dropdown button on the right for other' +
84
- ' BLAST algorithms that can be used.';
85
- }
86
- return title;
87
- }, this),
88
- });
89
- }
90
-
91
- /**
92
- * Show tooltip on input group.
93
- */
94
- showTooltip() {
95
- this.inputGroup()._tooltip('show');
96
- }
97
-
98
- /**
99
- * Hide tooltip on input group.
100
- */
101
- hideTooltip() {
102
- this.inputGroup()._tooltip('hide');
103
- }
104
-
105
57
  /**
106
58
  * Change selected algorithm.
107
59
  *
@@ -140,26 +92,49 @@ export class SearchButton extends Component {
140
92
  dropdownVisible: !prevState.dropdownVisible
141
93
  }));
142
94
  }
95
+
96
+ setMessageTooltipButton() {
97
+ if (!this.state.hasQuery && !this.state.hasDatabases) {
98
+ this.setState({ messageTooltip: 'You must enter a query sequence and select one or more databases above before you can run a search!' });
99
+ } else if (this.state.hasQuery && !this.state.hasDatabases) {
100
+ this.setState({ messageTooltip: 'You must select one or more databases above before you can run a search!' });
101
+ } else if (!this.state.hasQuery && this.state.hasDatabases) {
102
+ this.setState({ messageTooltip: 'You must enter a query sequence above before you can run a search!' });
103
+ }
104
+ }
143
105
 
144
- render() {
145
- var methods = this.state.methods;
146
- var method = methods[0];
147
- var multi = methods.length > 1;
106
+ setTitleTooltipButton() {
107
+ var title = 'Click to BLAST or press Ctrl+Enter.';
108
+
109
+ this.setState({ titleTooltip: title });
110
+
111
+ if (this.state.methods.length > 1) {
112
+ this.setState({
113
+ titleTooltip: title +=
114
+ ' Click dropdown button on the right for other' +
115
+ ' BLAST algorithms that can be used.'
116
+ });
117
+ }
118
+ }
119
+
120
+ handeMouseOut() {
121
+ clearTimeout(this.timeout);
122
+ this.setState({ hideMessageTooltip: false });
123
+ }
148
124
 
125
+ handleMouseOver() {
126
+ this.setMessageTooltipButton();
127
+ this.setTitleTooltipButton();
128
+ this.timeout = setTimeout(() => this.setState({ hideMessageTooltip: true}), 3000)
129
+ }
130
+
131
+ renderButton(method, multi, methods) {
149
132
  return (
150
- <div
151
- // className={multi ? 'flex' : 'flex'}
152
- className="my-4 md:my-2 flex justify-end w-full md:w-auto relative"
153
- id="methods"
154
- ref={this.inputGroupRef}
155
- onMouseOver={this.showTooltip}
156
- onMouseOut={this.hideTooltip}
157
- >
133
+ <div className="flex">
158
134
  <button
159
135
  type="submit"
160
- className="uppercase w-full md:w-auto flex text-xl justify-center py-2 px-16 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"
136
+ className={`${method ? 'hover:bg-seqorange focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-seqorange ' : 'opacity-50 cursor-not-allowed '}uppercase w-full md:w-auto flex text-xl justify-center py-2 px-16 border border-transparent rounded-md shadow-sm text-white bg-seqblue`}
161
137
  id="method"
162
- ref={this.submitButtonRef}
163
138
  name="method"
164
139
  value={method}
165
140
  disabled={!method}
@@ -201,6 +176,38 @@ export class SearchButton extends Component {
201
176
  </div>
202
177
  )}
203
178
  </div>
179
+ )
180
+ }
181
+
182
+ render() {
183
+ var methods = this.state.methods;
184
+ var method = methods[0];
185
+ var multi = methods.length > 1;
186
+
187
+ return (
188
+ <div
189
+ className="my-4 md:my-2 flex justify-end w-full md:w-auto relative"
190
+ id="methods"
191
+ ref={this.inputGroupRef}
192
+ onMouseOver={this.handleMouseOver}
193
+ onMouseOut={this.handeMouseOut}
194
+ >
195
+ <div className="relative flex flex-col items-center group">
196
+ <div className="flex items-center w-full">
197
+ {this.renderButton(method, multi, methods)}
198
+ { !this.state.dropdownVisible &&
199
+ <div className="absolute hidden bottom-11 items-center flex-col-reverse group-hover:flex w-full">
200
+ <div className="w-0 h-0 border-y-8 border-r-8 border-t-transparent border-b-transparent border-r-black -mt-1 -mr-px tooltip-arrow-rotate"></div>
201
+ <span className="relative z-10 p-2 search-button-text leading-4 text-center text-white whitespace-no-wrap bg-black shadow-lg rounded-md">
202
+ { !this.state.hasQuery || !this.state.hasDatabases ? (
203
+ this.state.hideMessageTooltip ? <>{this.state.titleTooltip}</> : this.state.messageTooltip
204
+ ) : <> {this.state.titleTooltip}</>}
205
+ </span>
206
+ </div>
207
+ }
208
+ </div>
209
+ </div>
210
+ </div>
204
211
  );
205
212
  }
206
213
  }
@@ -293,17 +293,9 @@ require = (function e(t, n, r) { function s(o, u) { if (!n[o]) { if (!t[o]) { va
293
293
  //Initialize tooltip
294
294
  var tooltip = 'sequenceTip' + this.opt.target;
295
295
  jQuery('<div id="' + tooltip + '"></div>')
296
- .css({
297
- 'position': 'absolute',
298
- 'z-index': '999999',
299
- 'color': '#fff',
300
- 'font-size': '12px',
301
- 'width': 'auto',
302
- 'display': 'none'
303
- })
304
- .addClass('tooltip')
305
- .appendTo('body')
306
- .hide();
296
+ .appendTo('.fastan-content')
297
+ .addClass('absolute top-0 left-0')
298
+ .show()
307
299
  this.opt._tooltip = document.getElementById(tooltip);
308
300
 
309
301
  if ((this.opt.sequence)) {
@@ -1188,20 +1180,13 @@ require = (function e(t, n, r) { function s(o, u) { if (!n[o]) { if (!t[o]) { va
1188
1180
  if (opt.numLeft) {
1189
1181
  $('<pre/>')
1190
1182
  .html(indL)
1191
- .addClass('indL')
1192
- .css({
1193
- color: '#aaa',
1194
- display: 'inline-block'
1195
- })
1183
+ .addClass('indL hidden sm:inline-block')
1196
1184
  .appendTo(this._contentDiv);
1197
1185
  }
1198
1186
 
1199
1187
  $('<pre/>')
1200
1188
  .html(str)
1201
- .addClass('seqF')
1202
- .css({
1203
- display: 'inline-block'
1204
- })
1189
+ .addClass('seqF inline-block')
1205
1190
  .appendTo(this._contentDiv);
1206
1191
 
1207
1192
  if (opt.numRight) {
@@ -1328,14 +1313,18 @@ require = (function e(t, n, r) { function s(o, u) { if (!n[o]) { if (!t[o]) { va
1328
1313
  jQuery(target).mouseover(function (e) {
1329
1314
 
1330
1315
  var offset = jQuery(e.target).offset();
1316
+ var containerOffset = jQuery(e.target).closest('.seqF').offset();
1331
1317
 
1332
1318
  if (!jQuery(tipId).is(':visible')) {
1333
1319
  jQuery(tipId)
1334
1320
  .css({
1335
1321
  'background-color': '#000',
1336
1322
  'padding': '3px 10px 3px 10px',
1337
- 'top': offset.top + jQuery(e.target).height() + 'px',
1338
- 'left': offset.left + jQuery(e.target).width() + 'px'
1323
+ 'top': offset.top - containerOffset.top + jQuery(e.target).height() + 15 + 'px',
1324
+ 'left': offset.left - containerOffset.left + jQuery(e.target).width() + 70 + 'px',
1325
+ 'color': '#fff',
1326
+ 'font-size': '12px',
1327
+ 'position': 'absolute'
1339
1328
  })
1340
1329
  .animate({ opacity: '0.85' }, 10)
1341
1330
  .html(cbGetMessageFunction.call(target))