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/form.js CHANGED
@@ -34,6 +34,8 @@ export class Form extends Component {
34
34
  this.handleAlgoChanged = this.handleAlgoChanged.bind(this);
35
35
  this.handleFormSubmission = this.handleFormSubmission.bind(this);
36
36
  this.formRef = createRef();
37
+ this.query = createRef();
38
+ this.button = createRef();
37
39
  this.setButtonState = this.setButtonState.bind(this);
38
40
  }
39
41
 
@@ -58,16 +60,26 @@ export class Form extends Component {
58
60
  databases: data['database'],
59
61
  preSelectedDbs: data['preSelectedDbs'],
60
62
  preDefinedOpts: data['options'],
61
- blastTaskMap: data['blastTaskMap']
63
+ blastTaskMap: data['blastTaskMap'],
64
+ currentlySelectedDbs: data['preSelectedDbs'] || [],
65
+ }, () => {
66
+ this.handleDatabaseSelectionChanged(this.state.currentlySelectedDbs);
67
+ this.handleFirstSelectionDB();
68
+ this.setBlastMethods();
62
69
  });
63
70
 
64
71
  /* Pre-populate the form with server sent query sequences
65
72
  * (if any).
66
73
  */
67
74
  if (data['query']) {
68
- this.refs.query.value(data['query']);
75
+ this.query.current.value(data['query']);
69
76
  }
70
77
 
78
+ // DB selection
79
+ setTimeout(() => {
80
+ this.preselectDatabases(this.state.preSelectedDbs);
81
+ }, 0);
82
+
71
83
  setTimeout(function () {
72
84
  $('.jstree_div').click();
73
85
  }, 1000);
@@ -81,11 +93,32 @@ export class Form extends Component {
81
93
  $button.trigger('click');
82
94
  }
83
95
  });
96
+ }
84
97
 
85
- // show overlay to create visual feedback on button click
86
- $('#method').on('click', () => {
87
- $('#overlay').css('display', 'block');
88
- });
98
+ handleFirstSelectionDB() {
99
+ const firstSelectedDb = this.state.preSelectedDbs != null ? this.state.preSelectedDbs[0].type : null;
100
+ if (firstSelectedDb) {
101
+ this.handleDatabaseTypeChanged(firstSelectedDb);
102
+ }
103
+ }
104
+
105
+ setBlastMethods() {
106
+ const methods = this.determineBlastMethods();
107
+ if (methods.length > 0) {
108
+ this.setState({ blastMethod: methods[0] });
109
+ }
110
+ }
111
+
112
+ preselectDatabases(preSelectedDbs) {
113
+ if (preSelectedDbs) {
114
+ preSelectedDbs.forEach(db => {
115
+ const matchCheckbox = this.formRef.current.querySelector(`input.checkbox-database[value="${db.id}"]`);
116
+ if (matchCheckbox) {
117
+ matchCheckbox.click();
118
+ matchCheckbox.checked = true;
119
+ }
120
+ });
121
+ }
89
122
  }
90
123
 
91
124
  useTreeWidget() {
@@ -95,14 +128,18 @@ export class Form extends Component {
95
128
  handleFormSubmission(evt) {
96
129
  evt.preventDefault();
97
130
  const form = this.formRef.current;
131
+
132
+ document.getElementById('overlay').style.display = 'block';
133
+
98
134
  const formData = new FormData(form);
99
- formData.append('method', this.refs.button.state.methods[0]);
135
+ formData.append('method', this.button.current.state.methods[0]);
136
+
100
137
  fetch(window.location.href, {
101
138
  method: 'POST',
102
139
  body: formData
103
140
  }).then(res => {
104
141
  //remove overlay when form is submitted
105
- $('#overlay').css('display', 'none');
142
+ document.getElementById('overlay').style.display = 'none';
106
143
  // redirect
107
144
  if (res.redirected && res.url) {
108
145
  // setTimeout is needed here as a workaround because safari doesnt allow async calling of window.open
@@ -118,7 +155,7 @@ export class Form extends Component {
118
155
  var database_type = this.databaseType;
119
156
  var sequence_type = this.sequenceType;
120
157
 
121
- if (this.refs.query.isEmpty()) {
158
+ if (this.query.current.isEmpty()) {
122
159
  return [];
123
160
  }
124
161
 
@@ -165,8 +202,8 @@ export class Form extends Component {
165
202
  }
166
203
 
167
204
  setButtonState() {
168
- this.refs.button.setState({
169
- hasQuery: !this.refs.query.isEmpty(),
205
+ this.button.current.setState({
206
+ hasQuery: !this.query.current.isEmpty(),
170
207
  hasDatabases: !!this.databaseType,
171
208
  methods: this.determineBlastMethods()
172
209
  });
@@ -205,22 +242,23 @@ export class Form extends Component {
205
242
  <form id="blast" ref={this.formRef} onSubmit={this.handleFormSubmission}>
206
243
  <input type="hidden" name="_csrf" value={document.querySelector('meta[name="_csrf"]').content} />
207
244
  <div className="px-4">
208
- <SearchQueryWidget ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged} onSequenceChanged={this.handleSequenceChanged}/>
245
+ <SearchQueryWidget ref={this.query} onSequenceTypeChanged={this.handleSequenceTypeChanged} onSequenceChanged={this.handleSequenceChanged}/>
209
246
 
210
247
  {this.useTreeWidget() ?
211
- <DatabasesTree ref="databases"
248
+ <DatabasesTree
212
249
  databases={this.state.databases} tree={this.state.tree}
213
250
  preSelectedDbs={this.state.preSelectedDbs}
214
251
  onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
215
252
  onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
216
253
  :
217
- <Databases ref="databases" databases={this.state.databases}
254
+ <Databases databases={this.state.databases}
218
255
  preSelectedDbs={this.state.preSelectedDbs}
219
256
  onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
220
257
  onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
221
258
  }
222
259
 
223
260
  <Options blastMethod={this.state.blastMethod} predefinedOptions={this.state.preDefinedOpts[this.state.blastMethod] || {}} blastTasks={(this.state.blastTaskMap || {})[this.state.blastMethod]} />
261
+
224
262
  </div>
225
263
 
226
264
  <div className="py-6"></div> {/* add a spacer so that the sticky action bar does not hide any contents */}
@@ -231,10 +269,10 @@ export class Form extends Component {
231
269
  currentBlastMethod={this.state.blastMethod}
232
270
  />
233
271
 
234
- <label className="block my-4 md:my-2">
272
+ <label className="text-seqblue hover:seqorange cursor-pointer block my-4 md:my-2">
235
273
  <input type="checkbox" id="toggleNewTab" /> Open results in new tab
236
274
  </label>
237
- <SearchButton ref="button" onAlgoChanged={this.handleAlgoChanged} />
275
+ <SearchButton ref={this.button} onAlgoChanged={this.handleAlgoChanged} />
238
276
  </div>
239
277
 
240
278
  </form>
@@ -305,4 +343,4 @@ class MixedNotification extends Component {
305
343
  </div>
306
344
  );
307
345
  }
308
- }
346
+ }
data/public/js/grapher.js CHANGED
@@ -1,131 +1,133 @@
1
1
  import _ from 'underscore';
2
- import React, { createRef } from 'react';
2
+ import React, { createRef, useState, useEffect, useCallback } from 'react';
3
3
 
4
4
  import './svgExporter'; // create handlers for SVG and PNG download buttons
5
5
  import CollapsePreferences from './collapse_preferences';
6
-
7
- // Each instance of Grapher is added to this object once the component has been
8
- // mounted. This is so that grapher can be iterated over and redrawn on window
9
- // resize event.
10
- var Graphers = {};
6
+ import useDetectPrint from "react-detect-print";
11
7
 
12
8
  // Grapher is a function that takes a Graph class and returns a React component.
13
9
  // This React component provides HTML boilerplate to add heading, to make the
14
10
  // graphs collapsible, to redraw graphs when window is resized, and SVG and PNG
15
11
  // export buttons and functionality.
16
12
  export default function Grapher(Graph) {
17
- return class extends React.Component {
18
- constructor(props) {
19
- super(props);
20
- this.name = Graph.name();
21
- this.collapsePreferences = new CollapsePreferences(this);
22
- let isCollapsed = this.collapsePreferences.preferenceStoredAsCollapsed();
23
- this.state = { collapsed: Graph.canCollapse() && (this.props.collapsed || isCollapsed) };
24
- this.svgContainerRef = createRef();
25
- }
13
+ return function Component(props) {
14
+ const alwaysShowName = Graph.alwaysShowName === undefined ? false : Graph.alwaysShowName();
15
+ const printing = useDetectPrint();
16
+ const name = Graph.name(props);
17
+ const [width, setWidth] = useState(window.innerWidth);
18
+ const [collapsed, setCollapsed] = useState(false);
19
+ const svgContainerRef = createRef();
20
+ let graph = null;
26
21
 
27
- graphId() {
28
- return Graph.graphId(this.props);
29
- }
22
+ const graphId = () => Graph.graphId(props)
30
23
 
31
- render() {
32
- // Do not render when Graph.name() is null
33
- if (Graph.name() === null) {
34
- return null;
35
- } else {
36
- var cssClasses = Graph.className() + ' grapher';
37
- return (
38
- <div ref="grapher" className={cssClasses}>
39
- {this.header()}
40
- {this.svgContainerJSX()}
41
- </div>
42
- );
43
- }
24
+ const graphLinksJSX = () => {
25
+ return (
26
+ <div className="hit-links float-right text-right text-blue-300 h-4 print:hidden">
27
+ <a href="#" className="btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer export-to-svg">
28
+ <i className="fa fa-download" /> SVG
29
+ </a>
30
+ <span className="line px-1">|</span>
31
+ <a href="#" className="btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer export-to-png">
32
+ <i className="fa fa-download" /> PNG
33
+ </a>
34
+ </div>
35
+ );
44
36
  }
45
37
 
46
- header() {
38
+ const header = () => {
47
39
  if(Graph.canCollapse()) {
48
- return <div className="grapher-header">
40
+ return <div className="grapher-header pr-px">
49
41
  <h4
50
- className="caption"
51
- onClick={() => this.collapsePreferences.toggleCollapse()}
42
+ className="inline-block pl-px m-0 caption cursor-pointer text-sm"
43
+ onClick={() => collapsePreferences.toggleCollapse()}
52
44
  >
53
- {this.collapsePreferences.renderCollapseIcon()}
54
- &nbsp;{Graph.name()}
45
+ {collapsePreferences.renderCollapseIcon()}
46
+ <span className="print:hidden">&nbsp;</span>{Graph.name(props)}
55
47
  </h4>
56
- {!this.state.collapsed && this.graphLinksJSX()}
48
+ {!collapsed && graphLinksJSX()}
49
+ </div>;
50
+ } else if (alwaysShowName) {
51
+ return <div className="grapher-histogram-header" style={{ position: 'relative' }}>
52
+ <h4 className="caption">&nbsp;{Graph.name(props)}</h4>
53
+ <div className="pull-right" style={{ position: 'absolute', top: 0, right: 0 }}>
54
+ {!collapsed && graphLinksJSX()}
55
+ </div>
57
56
  </div>;
58
57
  } else {
59
- return <div className="grapher-header">
60
- {!this.state.collapsed && this.graphLinksJSX()}
58
+ return <div className="pr-px">
59
+ {!collapsed && graphLinksJSX()}
61
60
  </div>;
62
61
  }
63
62
  }
64
63
 
65
- graphLinksJSX() {
66
- return (
67
- <div className="hit-links graph-links">
68
- <a href="#" className="btn-link export-to-svg">
69
- <i className="fa fa-download" /> SVG
70
- </a>
71
- <span className="line">|</span>
72
- <a href="#" className="btn-link export-to-png">
73
- <i className="fa fa-download" /> PNG
74
- </a>
75
- </div>
76
- );
77
- }
78
-
79
- svgContainerJSX() {
80
- var cssClasses = Graph.className() + ' svg-container collapse';
81
- if (!this.state.collapsed) cssClasses += ' in';
64
+ const svgContainerJSX = () => {
65
+ var cssClasses = Graph.className() + ' svg-container hidden';
66
+ if (!collapsed) cssClasses += ' !block';
82
67
  return (
83
68
  <div
84
- ref={this.svgContainerRef}
85
- id={this.graphId()}
69
+ ref={svgContainerRef}
70
+ id={graphId()}
86
71
  className={cssClasses}
87
72
  ></div>
88
73
  );
89
74
  }
90
75
 
91
- componentDidMount() {
92
- Graphers[this.graphId()] = this;
93
-
94
- // Draw visualisation for the first time. Visualisations are
95
- // redrawn when browser window is resized.
96
- this.draw();
76
+ const svgContainer = () => {
77
+ return $(svgContainerRef.current);
97
78
  }
98
79
 
99
- componentDidUpdate() {
100
- // Re-draw visualisation when the component change state.
101
- this.draw();
102
- }
103
- svgContainer() {
104
- return $(this.svgContainerRef.current);
105
- }
106
-
107
- draw() {
80
+ const draw = (printing = false) => {
81
+ let graphWidth = 'auto';
82
+ if (printing) graphWidth = '900';
108
83
  // Clean slate.
109
- this.svgContainer().empty();
110
- this.graph = null;
84
+ svgContainer().empty();
85
+ graph = null;
111
86
 
112
87
  // Draw if uncollapsed.
113
- if (this.state.collapsed) {
114
- return;
115
- }
116
- this.graph = new Graph(this.svgContainer(), this.props);
117
- this.svgContainer()
88
+ if (collapsed) return;
89
+
90
+ svgContainer().width(graphWidth);
91
+ graph = new Graph(svgContainer(), props);
92
+ svgContainer()
118
93
  .find('svg')
119
- .attr('data-name', Graph.dataName(this.props));
94
+ .attr('data-name', Graph.dataName(props));
120
95
  }
121
- };
122
- }
123
96
 
124
- // Redraw if window resized.
125
- $(window).resize(
126
- _.debounce(function () {
127
- _.each(Graphers, (grapher) => {
128
- grapher.draw();
129
- });
130
- }, 125)
131
- );
97
+ useEffect(() => {
98
+ // Attach a debounced listener to handle window resize events
99
+ // Updates the width state with the current window width, throttled to run at most once every 125ms
100
+ const handleResize = _.debounce(() => setWidth(window.innerWidth), 125);
101
+ window.addEventListener("resize", handleResize);
102
+
103
+ const isCollapsed = collapsePreferences.preferenceStoredAsCollapsed();
104
+ setCollapsed(Graph.canCollapse() && (props.collapsed || isCollapsed))
105
+ draw();
106
+
107
+ return () => window.removeEventListener("resize", handleResize)
108
+ }, [])
109
+
110
+ useEffect(() => {
111
+ draw(printing);
112
+ }, [printing, width])
113
+
114
+ const setState = (state) => {
115
+ setCollapsed(state.collapsed)
116
+ }
117
+
118
+ const collapsePreferences = new CollapsePreferences({name: name, state: { collapsed: collapsed }, setState: setState});
119
+
120
+ if (Graph.name(props) === null) {
121
+ return(null);
122
+ } else {
123
+ const printCss = collapsed ? 'print:hidden' : '';
124
+ const cssClasses = Graph.className() + ' grapher' + printCss;
125
+ return (
126
+ <div className={cssClasses}>
127
+ {header()}
128
+ {svgContainerJSX()}
129
+ </div>
130
+ )
131
+ }
132
+ }
133
+ }
data/public/js/hit.js CHANGED
@@ -80,14 +80,14 @@ export default class extends Component {
80
80
  return `get_sequence/?sequence_ids=${sequenceIDs}&database_ids=${databaseIDs}`;
81
81
  }
82
82
 
83
- downloadFASTA(event) {
83
+ downloadFASTA(_event) {
84
84
  var sequenceIDs = [this.sequenceID()];
85
85
  downloadFASTA(sequenceIDs, this.databaseIDs());
86
86
  }
87
87
 
88
88
  // Event-handler for exporting alignments.
89
89
  // Calls relevant method on AlignmentExporter defined in alignment_exporter.js.
90
- downloadAlignment(event) {
90
+ downloadAlignment(_event) {
91
91
  var hsps = _.map(this.props.hit.hsps, _.bind(function (hsp) {
92
92
  hsp.query_id = this.props.query.id;
93
93
  hsp.hit_id = this.props.hit.id;
@@ -99,33 +99,35 @@ export default class extends Component {
99
99
  }
100
100
 
101
101
  headerJSX() {
102
- var meta = `length: ${this.hitLength().toLocaleString()}`;
102
+ var meta = `Length: ${this.hitLength().toLocaleString()}`;
103
103
 
104
104
  if (this.props.showQueryCrumbs && this.props.showHitCrumbs) {
105
105
  // Multiper queries, multiple hits
106
- meta = `hit ${this.props.hit.number} of query ${this.props.query.number}, ` + meta;
106
+ meta = `hit ${this.props.hit.number} of query ${this.props.query.number}. ${meta}`;
107
107
  }
108
108
  else if (this.props.showQueryCrumbs && !this.props.showHitCrumbs) {
109
109
  // Multiple queries, single hit
110
- meta = `the only hit of query ${this.props.query.number}, ` + meta;
110
+ meta = `the only hit of query ${this.props.query.number}. ${meta}`;
111
111
  }
112
112
  else if (!this.props.showQueryCrumbs && this.props.showHitCrumbs) {
113
113
  // Single query, multiple hits
114
- meta = `hit ${this.props.hit.number}, ` + meta;
114
+ meta = `hit ${this.props.hit.number}. ${meta}`;
115
115
  }
116
116
 
117
- return <div className="section-header">
118
- <h4>
119
- <i className="fa fa-minus-square-o"></i>&nbsp;
120
- <strong>{this.props.hit.id}</strong>&nbsp;
121
- {this.props.hit.title}
117
+ return <div className="section-header border-b border-seqorange flex flex-col sm:flex-row sm:justify-between w-full">
118
+ <h4 className="text-sm cursor-pointer flex flex-col sm:flex-row items-start sm:items-start" data-parent-id={`#${this.domID()}`}>
119
+ <div className="flex items-start">
120
+ <i className="fa-regular fa-square-minus print:!hidden"></i>
121
+ <strong className="cursor-text ml-1 print:ml-0"> {this.props.hit.id}</strong>
122
+ </div>
123
+ <span className="ml-1">{this.props.hit.title}</span>
122
124
  </h4>
123
- <span className="label label-reset pos-label">{meta}</span>
125
+ <span className="label first-letter:capitalize text-sm font-normal text-inherit cursor-text">{meta}</span>
124
126
  </div>;
125
127
  }
126
128
 
127
129
  contentJSX() {
128
- return <div className="section-content" data-parent-hit={this.domID()}>
130
+ return <div className="pt-0 px-0 pb-px" data-parent-hit={this.domID()}>
129
131
  {this.hitLinks()}
130
132
  <HSPOverview key={'kablammo' + this.props.query.id} query={this.props.query}
131
133
  hit={this.props.hit} algorithm={this.props.algorithm}
@@ -149,8 +151,8 @@ export default class extends Component {
149
151
  });
150
152
 
151
153
  return (
152
- <div className="hit-links">
153
- <label>
154
+ <div className="hit-links h-4 print:hidden">
155
+ <label className="text-sm text-seqblue hover:seqorange cursor-pointer mb-0">
154
156
  <input type="checkbox" id={this.domID() + '_checkbox'}
155
157
  value={this.sequenceID()} onChange={function () {
156
158
  this.props.selectHit(this.domID() + '_checkbox');
@@ -160,12 +162,12 @@ export default class extends Component {
160
162
  </label>
161
163
  {
162
164
  btns.map((btn, index) => {
163
- return [<span className="line" key={`btn-${index}`}>|</span>, this.button(Object.assign(btn, { key: index }))];
165
+ return [<span className="text-seqorange mt-0 mr-1 ml-0 mb-1 px-1" key={`btn-${index}`}>|</span>, this.button(Object.assign(btn, { key: index }))];
164
166
  })
165
167
  }
166
168
  {
167
169
  this.props.hit.links.map((link, index) => {
168
- return [<span className="line" key={`link-${index}`}>|</span>, this.a(link, index)];
170
+ return [<span className="text-seqorange mt-0 mr-1 ml-0 mb-1 px-1" key={`link-${index}`}>|</span>, this.a(link, index)];
169
171
  })
170
172
  }
171
173
  </div>
@@ -213,12 +215,12 @@ export default class extends Component {
213
215
 
214
216
  button({ text, icon, title, className, onClick, key }) {
215
217
  if (onClick) {
216
- return <button key={key} className={`btn-link ${className}`}
218
+ return <button key={key} className={`btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer ${className}`}
217
219
  title={title} onClick={onClick}><i className={`fa ${icon}`}></i> {text}
218
220
  </button>;
219
221
  }
220
222
  else {
221
- return <button key={key} className="btn-link view-sequence disabled"
223
+ return <button key={key} className="text-sm view-sequence disabled"
222
224
  title={title} disabled={true}>
223
225
  <i className={`fa ${icon}`}></i> {text}
224
226
  </button>;
@@ -231,7 +233,7 @@ export default class extends Component {
231
233
  a(link, key) {
232
234
  if (!link.title || !link.url) return;
233
235
 
234
- let className = 'btn btn-link';
236
+ let className = 'btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer';
235
237
  if (link.class) className = `${className} ${link.class}`;
236
238
  return <a href={link.url} key={`${link.url}-${key}`} className={className} target='_blank'>
237
239
  {link.icon && <i className={'fa ' + link.icon}></i>}
@@ -240,10 +242,10 @@ export default class extends Component {
240
242
  }
241
243
  render() {
242
244
  return (
243
- <div className="hit" id={this.domID()} data-hit-def={this.props.hit.id}
245
+ <div className="hit mt-1 border-l-2 border-transparent pl-1 -ml-1" id={this.domID()} data-hit-def={this.props.hit.id}
244
246
  data-hit-len={this.props.hit.length} data-hit-evalue={this.props.hit.evalue}>
245
247
  {this.headerJSX()} {this.contentJSX()}
246
248
  </div>
247
249
  );
248
250
  }
249
- }
251
+ }