sequenceserver 3.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sequenceserver might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bin/sequenceserver +2 -2
- data/lib/sequenceserver/api_errors.rb +1 -1
- data/lib/sequenceserver/blast/job.rb +20 -3
- data/lib/sequenceserver/blast/report.rb +74 -86
- data/lib/sequenceserver/blast/tasks.rb +38 -0
- data/lib/sequenceserver/config.rb +54 -20
- data/lib/sequenceserver/makeblastdb.rb +16 -2
- data/lib/sequenceserver/report.rb +0 -6
- data/lib/sequenceserver/routes.rb +32 -21
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver.rb +1 -1
- data/public/css/app.css +121 -0
- data/public/css/app.min.css +1 -0
- data/public/css/sequenceserver.css +0 -148
- data/public/css/sequenceserver.min.css +3 -3
- data/public/js/circos.js +2 -2
- data/public/js/collapse_preferences.js +37 -0
- data/public/js/databases.js +65 -37
- data/public/js/databases_tree.js +2 -1
- data/public/js/dnd.js +37 -50
- data/public/js/form.js +78 -50
- data/public/js/grapher.js +23 -37
- data/public/js/hits_overview.js +2 -2
- data/public/js/kablammo.js +2 -2
- data/public/js/length_distribution.js +3 -3
- data/public/js/null_plugins/grapher/histogram.js +25 -0
- data/public/js/null_plugins/options.js +3 -0
- data/public/js/null_plugins/query_stats.js +11 -0
- data/public/js/null_plugins/report_plugins.js +6 -1
- data/public/js/null_plugins/search_header_plugin.js +4 -0
- data/public/js/options.js +161 -56
- data/public/js/query.js +85 -59
- data/public/js/report.js +1 -1
- data/public/js/search.js +2 -0
- data/public/js/search_button.js +67 -56
- data/public/js/sidebar.js +1 -1
- data/public/js/tests/database.spec.js +5 -5
- data/public/js/tests/{advanced_parameters.spec.js → form.spec.js} +35 -1
- data/public/js/tests/mock_data/databases.json +5 -5
- data/public/js/tests/mocks/circos.js +6 -0
- data/public/js/tests/report.spec.js +4 -3
- data/public/js/tests/search_query.spec.js +5 -6
- data/public/sequenceserver-report.min.js +45 -23
- data/public/sequenceserver-search.min.js +57 -13
- data/public/sequenceserver_logo.webp +0 -0
- data/views/blastn_options.erb +66 -66
- data/views/blastp_options.erb +59 -59
- data/views/blastx_options.erb +68 -68
- data/views/layout.erb +60 -3
- data/views/search.erb +33 -38
- data/views/search_layout.erb +152 -0
- data/views/tblastn_options.erb +57 -57
- data/views/tblastx_options.erb +64 -64
- metadata +31 -22
- data/lib/sequenceserver/makeblastdb-modified-with-cache.rb +0 -345
- data/public/SequenceServer_logo.png +0 -0
data/public/js/circos.js
CHANGED
@@ -2,7 +2,7 @@ import d3 from 'd3';
|
|
2
2
|
import Circos from '../packages/circosJS@1.7.0';
|
3
3
|
import _ from 'underscore';
|
4
4
|
|
5
|
-
import Grapher from '
|
5
|
+
import Grapher from 'grapher';
|
6
6
|
import * as Helpers from './visualisation_helpers';
|
7
7
|
import Utils from './utils';
|
8
8
|
|
@@ -19,7 +19,7 @@ class Graph {
|
|
19
19
|
return 'circos';
|
20
20
|
}
|
21
21
|
|
22
|
-
static
|
22
|
+
static graphId(props) {
|
23
23
|
return 'circos-collapse';
|
24
24
|
}
|
25
25
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
export default class CollapsePreferences {
|
2
|
+
constructor(component) {
|
3
|
+
this.component = component;
|
4
|
+
this.collapsePreferences = JSON.parse(localStorage.getItem('collapsePreferences')) || [];
|
5
|
+
}
|
6
|
+
|
7
|
+
toggleCollapse() {
|
8
|
+
let currentlyCollapsed = this.component.state.collapsed;
|
9
|
+
|
10
|
+
this.component.setState({ collapsed: !currentlyCollapsed });
|
11
|
+
|
12
|
+
let collapsePreferences = JSON.parse(localStorage.getItem('collapsePreferences')) || [];
|
13
|
+
|
14
|
+
if (currentlyCollapsed) {
|
15
|
+
localStorage.setItem('collapsePreferences', JSON.stringify(collapsePreferences.filter((name) => name !== this.component.name)));
|
16
|
+
} else {
|
17
|
+
let uniqueCollapsePreferences = [... new Set(collapsePreferences.concat([this.component.name]))];
|
18
|
+
localStorage.setItem('collapsePreferences', JSON.stringify(uniqueCollapsePreferences));
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
preferenceStoredAsCollapsed() {
|
23
|
+
return this.collapsePreferences.includes(this.component.name);
|
24
|
+
}
|
25
|
+
|
26
|
+
renderCollapseIcon() {
|
27
|
+
return this.component.state.collapsed ? this.plusIcon() : this.minusIcon();
|
28
|
+
}
|
29
|
+
|
30
|
+
minusIcon() {
|
31
|
+
return <i className="fa fa-minus-square-o"></i>;
|
32
|
+
}
|
33
|
+
|
34
|
+
plusIcon() {
|
35
|
+
return <i className="fa fa-plus-square-o"></i>;
|
36
|
+
}
|
37
|
+
}
|
data/public/js/databases.js
CHANGED
@@ -4,30 +4,43 @@ import _ from 'underscore';
|
|
4
4
|
export class Databases extends Component {
|
5
5
|
constructor(props) {
|
6
6
|
super(props);
|
7
|
-
this.state = {
|
7
|
+
this.state = {
|
8
|
+
type: '',
|
9
|
+
currentlySelectedDatabases: [],
|
10
|
+
};
|
11
|
+
|
8
12
|
this.preSelectedDbs = this.props.preSelectedDbs;
|
9
13
|
this.databases = this.databases.bind(this);
|
10
14
|
this.nselected = this.nselected.bind(this);
|
11
15
|
this.categories = this.categories.bind(this);
|
12
|
-
this.handleClick = this.handleClick.bind(this);
|
13
16
|
this.handleToggle = this.handleToggle.bind(this);
|
14
17
|
this.renderDatabases = this.renderDatabases.bind(this);
|
15
18
|
this.renderDatabase = this.renderDatabase.bind(this);
|
16
19
|
}
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
|
21
|
+
componentDidUpdate(_prevProps, prevState) {
|
22
|
+
// If there's only one database, select it.
|
23
|
+
if (this.databases() && this.databases().length === 1 && this.state.currentlySelectedDatabases.length === 0) {
|
24
|
+
this.setState({currentlySelectedDatabases: this.databases()});
|
21
25
|
}
|
22
26
|
|
23
|
-
if (this.preSelectedDbs) {
|
24
|
-
|
25
|
-
$(selectors.join(',')).prop('checked', true);
|
26
|
-
this.handleClick(this.preSelectedDbs[0]);
|
27
|
+
if (this.preSelectedDbs && this.preSelectedDbs.length !== 0) {
|
28
|
+
this.setState({currentlySelectedDatabases: this.preSelectedDbs});
|
27
29
|
this.preSelectedDbs = null;
|
28
30
|
}
|
29
|
-
this.
|
31
|
+
const type = this.state.currentlySelectedDatabases[0] ? this.state.currentlySelectedDatabases[0].type : '';
|
32
|
+
if (type != this.state.type) {
|
33
|
+
this.setState({ type: type });
|
34
|
+
this.props.onDatabaseTypeChanged(type);
|
35
|
+
}
|
36
|
+
|
37
|
+
if (prevState.currentlySelectedDatabases !== this.state.currentlySelectedDatabases) {
|
38
|
+
// Call the prop function with the new state
|
39
|
+
this.props.onDatabaseSelectionChanged(this.state.currentlySelectedDatabases);
|
40
|
+
}
|
41
|
+
|
30
42
|
}
|
43
|
+
|
31
44
|
databases(category) {
|
32
45
|
var databases = this.props.databases;
|
33
46
|
if (category) {
|
@@ -38,40 +51,39 @@ export class Databases extends Component {
|
|
38
51
|
}
|
39
52
|
|
40
53
|
nselected() {
|
41
|
-
return
|
54
|
+
return this.state.currentlySelectedDatabases.length;
|
42
55
|
}
|
43
56
|
|
44
57
|
categories() {
|
45
58
|
return _.uniq(_.map(this.props.databases, _.iteratee('type'))).sort();
|
46
59
|
}
|
47
60
|
|
48
|
-
handleClick(database) {
|
49
|
-
var type = this.nselected() ? database.type : '';
|
50
|
-
if (type != this.state.type) this.setState({ type: type });
|
51
|
-
}
|
52
|
-
|
53
61
|
handleToggle(toggleState, type) {
|
54
62
|
switch (toggleState) {
|
55
63
|
case '[Select all]':
|
56
|
-
|
64
|
+
this.setState({ currentlySelectedDatabases: this.databases(type) });
|
57
65
|
break;
|
58
66
|
case '[Deselect all]':
|
59
|
-
|
67
|
+
this.setState({ currentlySelectedDatabases: [] });
|
60
68
|
break;
|
61
69
|
}
|
62
|
-
this.forceUpdate();
|
63
70
|
}
|
71
|
+
|
64
72
|
renderDatabases(category) {
|
65
73
|
// Panel name and column width.
|
66
74
|
var panelTitle = category[0].toUpperCase() + category.substring(1).toLowerCase() + ' databases';
|
67
|
-
var columnClass = this.categories().length === 1 ? 'col-
|
75
|
+
var columnClass = this.categories().length === 1 ? 'col-span-2' : '';
|
68
76
|
|
69
77
|
// Toggle button.
|
70
78
|
var toggleState = '[Select all]';
|
71
|
-
var toggleClass = '
|
79
|
+
var toggleClass = 'px-2 text-sm';
|
72
80
|
var toggleShown = this.databases(category).length > 1;
|
73
81
|
var toggleDisabled = this.state.type && this.state.type !== category;
|
74
|
-
if (toggleShown && toggleDisabled)
|
82
|
+
if (toggleShown && toggleDisabled) {
|
83
|
+
toggleClass += ' text-gray-400';
|
84
|
+
} else {
|
85
|
+
toggleClass += ' text-seqblue';
|
86
|
+
}
|
75
87
|
if (!toggleShown) toggleClass += ' hidden';
|
76
88
|
if (this.nselected() === this.databases(category).length) {
|
77
89
|
toggleState = '[Deselect all]';
|
@@ -80,9 +92,11 @@ export class Databases extends Component {
|
|
80
92
|
// JSX.
|
81
93
|
return (
|
82
94
|
<div className={columnClass} key={'DB_' + category}>
|
83
|
-
<div
|
84
|
-
<div className="
|
85
|
-
<h4 style={{ display: 'inline' }}>
|
95
|
+
<div>
|
96
|
+
<div className="border-b border-seqorange mb-2">
|
97
|
+
<h4 style={{ display: 'inline' }} className="font-medium">
|
98
|
+
{panelTitle}
|
99
|
+
</h4>
|
86
100
|
<button
|
87
101
|
type="button"
|
88
102
|
className={toggleClass}
|
@@ -94,15 +108,12 @@ export class Databases extends Component {
|
|
94
108
|
{toggleState}
|
95
109
|
</button>
|
96
110
|
</div>
|
97
|
-
<ul className={'
|
111
|
+
<ul className={'databases ' + category}>
|
98
112
|
{_.map(
|
99
113
|
this.databases(category),
|
100
114
|
_.bind(function (database, index) {
|
101
115
|
return (
|
102
|
-
<li
|
103
|
-
className="list-group-item"
|
104
|
-
key={'DB_' + category + index}
|
105
|
-
>
|
116
|
+
<li key={'DB_' + category + index}>
|
106
117
|
{this.renderDatabase(database)}
|
107
118
|
</li>
|
108
119
|
);
|
@@ -114,20 +125,37 @@ export class Databases extends Component {
|
|
114
125
|
);
|
115
126
|
}
|
116
127
|
|
128
|
+
handleDatabaseSelectionClick(database) {
|
129
|
+
const isSelected = this.state.currentlySelectedDatabases.some(db => db.id === database.id);
|
130
|
+
|
131
|
+
if (isSelected) {
|
132
|
+
this.setState(prevState => ({
|
133
|
+
currentlySelectedDatabases: prevState.currentlySelectedDatabases.filter(db => db.id !== database.id)
|
134
|
+
}));
|
135
|
+
} else {
|
136
|
+
this.setState(prevState => ({
|
137
|
+
currentlySelectedDatabases: [...prevState.currentlySelectedDatabases, database]
|
138
|
+
}));
|
139
|
+
}
|
140
|
+
}
|
141
|
+
|
117
142
|
renderDatabase(database) {
|
118
|
-
|
143
|
+
const isDisabled = this.state.type && this.state.type !== database.type;
|
144
|
+
const isChecked = this.state.currentlySelectedDatabases.some(db => db.id === database.id);
|
119
145
|
|
120
146
|
return (
|
121
|
-
<label className={(
|
147
|
+
<label className={(isDisabled && 'database text-gray-400') || 'database text-seqblue'}>
|
122
148
|
<input
|
123
149
|
type="checkbox"
|
124
150
|
name="databases[]"
|
125
151
|
value={database.id}
|
126
152
|
data-type={database.type}
|
127
|
-
disabled={
|
153
|
+
disabled={isDisabled}
|
154
|
+
checked={isChecked}
|
128
155
|
onChange={_.bind(function () {
|
129
|
-
this.
|
156
|
+
this.handleDatabaseSelectionClick(database);
|
130
157
|
}, this)}
|
158
|
+
|
131
159
|
/>
|
132
160
|
{' ' + (database.title || database.name)}
|
133
161
|
</label>
|
@@ -136,9 +164,9 @@ export class Databases extends Component {
|
|
136
164
|
|
137
165
|
render() {
|
138
166
|
return (
|
139
|
-
<div className="
|
167
|
+
<div className="my-6 grid md:grid-cols-2 gap-4">
|
140
168
|
{_.map(this.categories(), this.renderDatabases)}
|
141
169
|
</div>
|
142
170
|
);
|
143
171
|
}
|
144
|
-
}
|
172
|
+
}
|
data/public/js/databases_tree.js
CHANGED
@@ -102,6 +102,7 @@ export default class extends Databases {
|
|
102
102
|
{
|
103
103
|
this.renderDatabaseTree(category)
|
104
104
|
}
|
105
|
+
<link rel="stylesheet" media="screen,print" type="text/css" href="vendor/github/vakata/jstree@3.3.8/dist/themes/default/style.min.css"/>
|
105
106
|
</div>
|
106
107
|
);
|
107
108
|
}
|
@@ -111,7 +112,7 @@ export default class extends Databases {
|
|
111
112
|
var search_id = tree_id + '_search';
|
112
113
|
|
113
114
|
return (
|
114
|
-
<input type='text' id={search_id}
|
115
|
+
<input type='text' id={search_id} className='border rounded px-1' placeholder='Search...'
|
115
116
|
onKeyUp=
|
116
117
|
{
|
117
118
|
_.bind(function () {
|
data/public/js/dnd.js
CHANGED
@@ -102,59 +102,46 @@ export class DnD extends Component {
|
|
102
102
|
render() {
|
103
103
|
return (
|
104
104
|
<div
|
105
|
-
className="dnd-overlay"
|
105
|
+
className="dnd-overlay absolute left-0 top-0 w-full h-full bg-gray-200 bg-opacity-75 z-40"
|
106
106
|
style={{ display: 'none' }}>
|
107
|
-
<div
|
108
|
-
|
107
|
+
<div className="flex flex-col space-y-4 h-full items-center justify-center dnd-overlay-container text-2xl">
|
108
|
+
<p
|
109
|
+
className="dnd-overlay-drop flex items-center space-x-4"
|
110
|
+
style={{ display: 'none' }}>
|
111
|
+
<i className="fa fa-2x fa-file"></i>
|
112
|
+
Drop query sequence file here
|
113
|
+
</p>
|
114
|
+
<p
|
115
|
+
className="dnd-overlay-overwrite flex items-center space-x-4"
|
116
|
+
style={{ display: 'none' }}>
|
117
|
+
<i className="fa fa-2x fa-file"></i>
|
118
|
+
<span className="text-red-800">Overwrite</span>&nbps;query sequence file
|
119
|
+
</p>
|
120
|
+
|
109
121
|
<div
|
110
|
-
className="
|
122
|
+
className="dnd-errors text-red-800">
|
123
|
+
<div
|
124
|
+
className="dnd-error row"
|
125
|
+
id="dnd-multi-notification"
|
126
|
+
style={{ display: 'none' }}>
|
127
|
+
|
128
|
+
One file at a time please.
|
129
|
+
</div>
|
130
|
+
|
111
131
|
<div
|
112
|
-
className="
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
<div
|
127
|
-
className="dnd-errors">
|
128
|
-
<div
|
129
|
-
className="dnd-error row"
|
130
|
-
id="dnd-multi-notification"
|
131
|
-
style={{ display: 'none' }}>
|
132
|
-
<div
|
133
|
-
className="col-md-6 col-md-offset-3">
|
134
|
-
One file at a time please.
|
135
|
-
</div>
|
136
|
-
</div>
|
137
|
-
|
138
|
-
<div
|
139
|
-
className="dnd-error row"
|
140
|
-
id="dnd-large-file-notification"
|
141
|
-
style={{ display: 'none' }}>
|
142
|
-
<div
|
143
|
-
className="col-md-6 col-md-offset-3">
|
144
|
-
Too big a file. Can only do less than 250 MB. >_<
|
145
|
-
</div>
|
146
|
-
</div>
|
147
|
-
|
148
|
-
<div
|
149
|
-
className="dnd-error row"
|
150
|
-
id="dnd-format-notification"
|
151
|
-
style={{ display: 'none' }}>
|
152
|
-
<div
|
153
|
-
className="col-md-6 col-md-offset-3">
|
154
|
-
Only FASTA files please.
|
155
|
-
</div>
|
156
|
-
</div>
|
157
|
-
</div>
|
132
|
+
className="dnd-error row"
|
133
|
+
id="dnd-large-file-notification"
|
134
|
+
style={{ display: 'none' }}>
|
135
|
+
|
136
|
+
Too big a file. Can only do less than 250 MB. >_<
|
137
|
+
</div>
|
138
|
+
|
139
|
+
<div
|
140
|
+
className="dnd-error row"
|
141
|
+
id="dnd-format-notification"
|
142
|
+
style={{ display: 'none' }}>
|
143
|
+
|
144
|
+
Only FASTA files please.
|
158
145
|
</div>
|
159
146
|
</div>
|
160
147
|
</div>
|
data/public/js/form.js
CHANGED
@@ -4,7 +4,8 @@ import { SearchQueryWidget } from './query';
|
|
4
4
|
import DatabasesTree from './databases_tree';
|
5
5
|
import { Databases } from './databases';
|
6
6
|
import _ from 'underscore';
|
7
|
-
import { Options } from '
|
7
|
+
import { Options } from 'options';
|
8
|
+
import QueryStats from 'query_stats';
|
8
9
|
|
9
10
|
/**
|
10
11
|
* Search form.
|
@@ -16,15 +17,24 @@ export class Form extends Component {
|
|
16
17
|
constructor(props) {
|
17
18
|
super(props);
|
18
19
|
this.state = {
|
19
|
-
databases: [],
|
20
|
+
databases: [],
|
21
|
+
preSelectedDbs: [],
|
22
|
+
currentlySelectedDbs: [],
|
23
|
+
preDefinedOpts: {},
|
24
|
+
tree: {},
|
25
|
+
residuesInQuerySequence: 0,
|
26
|
+
blastMethod: ''
|
20
27
|
};
|
21
28
|
this.useTreeWidget = this.useTreeWidget.bind(this);
|
22
|
-
this.
|
29
|
+
this.determineBlastMethods = this.determineBlastMethods.bind(this);
|
23
30
|
this.handleSequenceTypeChanged = this.handleSequenceTypeChanged.bind(this);
|
31
|
+
this.handleSequenceChanged = this.handleSequenceChanged.bind(this);
|
24
32
|
this.handleDatabaseTypeChanged = this.handleDatabaseTypeChanged.bind(this);
|
33
|
+
this.handleDatabaseSelectionChanged = this.handleDatabaseSelectionChanged.bind(this);
|
25
34
|
this.handleAlgoChanged = this.handleAlgoChanged.bind(this);
|
26
35
|
this.handleFormSubmission = this.handleFormSubmission.bind(this);
|
27
36
|
this.formRef = createRef();
|
37
|
+
this.setButtonState = this.setButtonState.bind(this);
|
28
38
|
}
|
29
39
|
|
30
40
|
componentDidMount() {
|
@@ -47,7 +57,8 @@ export class Form extends Component {
|
|
47
57
|
tree: data['tree'],
|
48
58
|
databases: data['database'],
|
49
59
|
preSelectedDbs: data['preSelectedDbs'],
|
50
|
-
preDefinedOpts: data['options']
|
60
|
+
preDefinedOpts: data['options'],
|
61
|
+
blastTaskMap: data['blastTaskMap']
|
51
62
|
});
|
52
63
|
|
53
64
|
/* Pre-populate the form with server sent query sequences
|
@@ -103,7 +114,7 @@ export class Form extends Component {
|
|
103
114
|
});
|
104
115
|
}
|
105
116
|
|
106
|
-
|
117
|
+
determineBlastMethods() {
|
107
118
|
var database_type = this.databaseType;
|
108
119
|
var sequence_type = this.sequenceType;
|
109
120
|
|
@@ -138,76 +149,93 @@ export class Form extends Component {
|
|
138
149
|
return [];
|
139
150
|
}
|
140
151
|
|
152
|
+
handleSequenceChanged(residuesInQuerySequence) {
|
153
|
+
if(residuesInQuerySequence !== this.state.residuesInQuerySequence)
|
154
|
+
this.setState({ residuesInQuerySequence: residuesInQuerySequence});
|
155
|
+
}
|
156
|
+
|
141
157
|
handleSequenceTypeChanged(type) {
|
142
158
|
this.sequenceType = type;
|
143
|
-
this.
|
144
|
-
hasQuery: !this.refs.query.isEmpty(),
|
145
|
-
hasDatabases: !!this.databaseType,
|
146
|
-
methods: this.determineBlastMethod()
|
147
|
-
});
|
159
|
+
this.setButtonState();
|
148
160
|
}
|
149
161
|
|
150
162
|
handleDatabaseTypeChanged(type) {
|
151
163
|
this.databaseType = type;
|
164
|
+
this.setButtonState();
|
165
|
+
}
|
166
|
+
|
167
|
+
setButtonState() {
|
152
168
|
this.refs.button.setState({
|
153
169
|
hasQuery: !this.refs.query.isEmpty(),
|
154
170
|
hasDatabases: !!this.databaseType,
|
155
|
-
methods: this.
|
171
|
+
methods: this.determineBlastMethods()
|
156
172
|
});
|
157
173
|
}
|
158
174
|
|
175
|
+
handleDatabaseSelectionChanged(selectedDbs) {
|
176
|
+
if (!_.isEqual(selectedDbs, this.state.currentlySelectedDbs))
|
177
|
+
this.setState({ currentlySelectedDbs: selectedDbs });
|
178
|
+
}
|
179
|
+
|
159
180
|
handleAlgoChanged(algo) {
|
160
181
|
if (algo in this.state.preDefinedOpts) {
|
161
|
-
|
162
|
-
this.refs.opts.setState({
|
163
|
-
method: algo,
|
164
|
-
preOpts: preDefinedOpts,
|
165
|
-
value: (preDefinedOpts['last search'] ||
|
166
|
-
preDefinedOpts['default']).join(' ')
|
167
|
-
});
|
182
|
+
this.setState({ blastMethod: algo });
|
168
183
|
}
|
169
184
|
else {
|
170
|
-
this.
|
185
|
+
this.setState({ blastMethod: ''});
|
171
186
|
}
|
172
187
|
}
|
173
188
|
|
189
|
+
residuesInSelectedDbs() {
|
190
|
+
return this.state.currentlySelectedDbs.reduce((sum, db) => sum + parseInt(db.ncharacters, 10), 0);
|
191
|
+
}
|
192
|
+
|
174
193
|
render() {
|
175
194
|
return (
|
176
|
-
<div
|
195
|
+
<div>
|
177
196
|
<div id="overlay" style={{ position: 'absolute', top: 0, left: 0, width: '100vw', height: '100vw', background: 'rgba(0, 0, 0, 0.2)', display: 'none', zIndex: 99 }} />
|
178
197
|
|
179
|
-
<div className="
|
198
|
+
<div className="fixed top-0 left-0 w-full max-h-8 px-8" data-notifications id="notifications">
|
180
199
|
<FastqNotification />
|
181
200
|
<NucleotideNotification />
|
182
201
|
<ProteinNotification />
|
183
202
|
<MixedNotification />
|
184
203
|
</div>
|
185
204
|
|
186
|
-
<form id="blast" ref={this.formRef} onSubmit={this.handleFormSubmission}
|
187
|
-
<div className="
|
188
|
-
<SearchQueryWidget ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged} />
|
205
|
+
<form id="blast" ref={this.formRef} onSubmit={this.handleFormSubmission}>
|
206
|
+
<div className="px-4">
|
207
|
+
<SearchQueryWidget ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged} onSequenceChanged={this.handleSequenceChanged}/>
|
208
|
+
|
209
|
+
{this.useTreeWidget() ?
|
210
|
+
<DatabasesTree ref="databases"
|
211
|
+
databases={this.state.databases} tree={this.state.tree}
|
212
|
+
preSelectedDbs={this.state.preSelectedDbs}
|
213
|
+
onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
|
214
|
+
onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
|
215
|
+
:
|
216
|
+
<Databases ref="databases" databases={this.state.databases}
|
217
|
+
preSelectedDbs={this.state.preSelectedDbs}
|
218
|
+
onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
|
219
|
+
onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
|
220
|
+
}
|
221
|
+
|
222
|
+
<Options blastMethod={this.state.blastMethod} predefinedOptions={this.state.preDefinedOpts[this.state.blastMethod] || {}} blastTasks={(this.state.blastTaskMap || {})[this.state.blastMethod]} />
|
189
223
|
</div>
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
<div className="col-md-2">
|
203
|
-
<div className="form-group" style={{ 'textAlign': 'center', 'padding': '7px 0' }}>
|
204
|
-
<label>
|
205
|
-
<input type="checkbox" id="toggleNewTab" /> Open results in new tab
|
206
|
-
</label>
|
207
|
-
</div>
|
208
|
-
</div>
|
224
|
+
|
225
|
+
<div className="py-6"></div> {/* add a spacer so that the sticky action bar does not hide any contents */}
|
226
|
+
|
227
|
+
<div className="pb-4 pt-2 px-4 sticky bottom-0 md:flex flex-row md:space-x-4 items-center justify-end bg-gradient-to-t to-gray-100/90 from-white/90">
|
228
|
+
<QueryStats
|
229
|
+
residuesInQuerySequence={this.state.residuesInQuerySequence} numberOfDatabasesSelected={this.state.currentlySelectedDbs.length} residuesInSelectedDbs={this.residuesInSelectedDbs()}
|
230
|
+
currentBlastMethod={this.state.blastMethod}
|
231
|
+
/>
|
232
|
+
|
233
|
+
<label className="block my-4 md:my-2">
|
234
|
+
<input type="checkbox" id="toggleNewTab" /> Open results in new tab
|
235
|
+
</label>
|
209
236
|
<SearchButton ref="button" onAlgoChanged={this.handleAlgoChanged} />
|
210
237
|
</div>
|
238
|
+
|
211
239
|
</form>
|
212
240
|
</div>
|
213
241
|
);
|
@@ -220,11 +248,11 @@ class ProteinNotification extends Component {
|
|
220
248
|
render() {
|
221
249
|
return (
|
222
250
|
<div
|
223
|
-
|
251
|
+
data-role="notification"
|
224
252
|
id="protein-sequence-notification"
|
225
253
|
style={{ display: 'none' }}>
|
226
254
|
<div
|
227
|
-
className="
|
255
|
+
className="bg-blue-100 border rounded border-blue-800 px-4 py-2 my-2">
|
228
256
|
Detected: amino-acid sequence(s).
|
229
257
|
</div>
|
230
258
|
</div>
|
@@ -235,11 +263,11 @@ class ProteinNotification extends Component {
|
|
235
263
|
class NucleotideNotification extends Component {
|
236
264
|
render() {
|
237
265
|
return (<div
|
238
|
-
|
266
|
+
data-role="notification"
|
239
267
|
id="nucleotide-sequence-notification"
|
240
268
|
style={{ display: 'none' }}>
|
241
269
|
<div
|
242
|
-
className="
|
270
|
+
className="bg-blue-100 border rounded border-blue-800 px-4 py-2 my-2">
|
243
271
|
Detected: nucleotide sequence(s).
|
244
272
|
</div>
|
245
273
|
</div>
|
@@ -250,11 +278,11 @@ class NucleotideNotification extends Component {
|
|
250
278
|
class FastqNotification extends Component {
|
251
279
|
render() {
|
252
280
|
return (<div
|
253
|
-
|
281
|
+
data-role="notification"
|
254
282
|
id="fastq-sequence-notification"
|
255
283
|
style={{ display: 'none' }}>
|
256
284
|
<div
|
257
|
-
className="
|
285
|
+
className="bg-blue-100 border rounded border-blue-800 px-4 py-2 my-2">
|
258
286
|
Detected FASTQ and automatically converted to FASTA.
|
259
287
|
</div>
|
260
288
|
</div>
|
@@ -266,7 +294,7 @@ class MixedNotification extends Component {
|
|
266
294
|
render() {
|
267
295
|
return (
|
268
296
|
<div
|
269
|
-
|
297
|
+
data-role="notification"
|
270
298
|
id="mixed-sequence-notification"
|
271
299
|
style={{ display: 'none' }}>
|
272
300
|
<div
|