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.
- checksums.yaml +4 -4
- data/lib/sequenceserver/api_errors.rb +18 -0
- data/lib/sequenceserver/blast/hit.rb +1 -2
- data/lib/sequenceserver/blast/job.rb +15 -5
- data/lib/sequenceserver/blast/report.rb +5 -1
- data/lib/sequenceserver/blast/tasks.rb +1 -1
- data/lib/sequenceserver/database.rb +5 -1
- data/lib/sequenceserver/job.rb +26 -10
- data/lib/sequenceserver/makeblastdb.rb +2 -1
- data/lib/sequenceserver/routes.rb +38 -13
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver/zip_file_generator.rb +1 -1
- data/lib/sequenceserver.rb +1 -1
- data/public/404.html +8 -16
- data/public/css/app.css +794 -111
- data/public/css/app.min.css +1 -1
- data/public/css/sequenceserver.min.css +1 -8
- data/public/js/alignment_exporter.js +16 -28
- data/public/js/circos.js +103 -96
- data/public/{packages/circosJS@1.7.0.js → js/circosjs.js} +156 -778
- data/public/js/cloud_share_modal.js +95 -98
- data/public/js/collapse_preferences.js +13 -15
- data/public/js/databases.js +4 -3
- data/public/js/databases_tree.js +51 -30
- data/public/js/error_modal.js +35 -22
- data/public/js/exporter.js +2 -0
- data/public/js/form.js +55 -17
- data/public/js/grapher.js +94 -92
- data/public/js/hit.js +24 -22
- data/public/js/hits.js +283 -0
- data/public/js/hits_overview.js +8 -27
- data/public/js/hsp.js +140 -144
- data/public/js/jquery_world.js +25 -16
- data/public/js/kablammo.js +14 -10
- data/public/js/length_distribution.js +23 -37
- data/public/js/mailto.js +1 -3
- data/public/js/null_plugins/report_plugins.js +4 -5
- data/public/js/options.js +19 -25
- data/public/js/query.js +36 -35
- data/public/js/report/alignment_results.js +18 -0
- data/public/js/report/graphical_overview.js +38 -0
- data/public/js/report/report_header.js +28 -0
- data/public/js/report/run_summary.js +43 -0
- data/public/js/report.js +124 -348
- data/public/js/report_root.js +10 -8
- data/public/js/search.js +31 -11
- data/public/js/search_button.js +85 -78
- data/public/js/sequence.js +169 -180
- data/public/js/sequence_modal.js +58 -65
- data/public/js/share_url.js +5 -5
- data/public/js/sidebar.js +152 -89
- data/public/js/svgExporter.js +8 -3
- data/public/js/tests/alignment_exporter.spec.js +38 -0
- data/public/js/tests/cloud_share_modal.spec.js +75 -0
- data/public/js/tests/form.spec.js +1 -1
- data/public/js/tests/report.spec.js +37 -15
- data/public/js/tests/visualisation_helpers.spec.js +28 -0
- data/public/js/visualisation_helpers.js +24 -27
- data/public/packages/jquery-ui@1.13.3.js +19070 -0
- data/public/sequenceserver-report.min.js +3 -2481
- data/public/sequenceserver-report.min.js.LICENSE.txt +213 -0
- data/public/sequenceserver-report.min.js.map +1 -0
- data/public/sequenceserver-search.min.js +3 -2382
- data/public/sequenceserver-search.min.js.LICENSE.txt +262 -0
- data/public/sequenceserver-search.min.js.map +1 -0
- data/views/error.erb +1 -1
- data/views/layout.erb +122 -133
- data/views/report.erb +3 -2
- data/views/search.erb +10 -14
- metadata +21 -28
- data/public/config.js +0 -147
- data/public/css/fonts.css +0 -31
- data/public/css/grapher.css +0 -670
- data/public/css/sequenceserver.css +0 -527
- data/public/packages/jquery-ui@1.11.4.js +0 -16624
- data/public/vendor/github/mbostock/d3@3.5.6/d3.js +0 -9506
- data/public/vendor/github/mbostock/d3@3.5.6.js +0 -1
- 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.
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
169
|
-
hasQuery: !this.
|
|
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=
|
|
245
|
+
<SearchQueryWidget ref={this.query} onSequenceTypeChanged={this.handleSequenceTypeChanged} onSequenceChanged={this.handleSequenceChanged}/>
|
|
209
246
|
|
|
210
247
|
{this.useTreeWidget() ?
|
|
211
|
-
<DatabasesTree
|
|
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
|
|
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=
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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={() =>
|
|
42
|
+
className="inline-block pl-px m-0 caption cursor-pointer text-sm"
|
|
43
|
+
onClick={() => collapsePreferences.toggleCollapse()}
|
|
52
44
|
>
|
|
53
|
-
{
|
|
54
|
-
|
|
45
|
+
{collapsePreferences.renderCollapseIcon()}
|
|
46
|
+
<span className="print:hidden"> </span>{Graph.name(props)}
|
|
55
47
|
</h4>
|
|
56
|
-
{!
|
|
48
|
+
{!collapsed && graphLinksJSX()}
|
|
49
|
+
</div>;
|
|
50
|
+
} else if (alwaysShowName) {
|
|
51
|
+
return <div className="grapher-histogram-header" style={{ position: 'relative' }}>
|
|
52
|
+
<h4 className="caption"> {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="
|
|
60
|
-
{!
|
|
58
|
+
return <div className="pr-px">
|
|
59
|
+
{!collapsed && graphLinksJSX()}
|
|
61
60
|
</div>;
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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={
|
|
85
|
-
id={
|
|
69
|
+
ref={svgContainerRef}
|
|
70
|
+
id={graphId()}
|
|
86
71
|
className={cssClasses}
|
|
87
72
|
></div>
|
|
88
73
|
);
|
|
89
74
|
}
|
|
90
75
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
110
|
-
|
|
84
|
+
svgContainer().empty();
|
|
85
|
+
graph = null;
|
|
111
86
|
|
|
112
87
|
// Draw if uncollapsed.
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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(
|
|
94
|
+
.attr('data-name', Graph.dataName(props));
|
|
120
95
|
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
96
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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(
|
|
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(
|
|
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 = `
|
|
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}
|
|
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}
|
|
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}
|
|
114
|
+
meta = `hit ${this.props.hit.number}. ${meta}`;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
return <div className="section-header">
|
|
118
|
-
<h4>
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
|
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
|
+
}
|