sequenceserver 3.0.1 → 3.1.1
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 +32 -2
- 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 +66 -25
- data/lib/sequenceserver/sequence.rb +35 -7
- data/lib/sequenceserver/server.rb +1 -1
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver.rb +1 -1
- data/public/404.html +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/download_fasta.js +1 -0
- data/public/js/form.js +79 -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 +10 -1
- data/public/js/tests/database.spec.js +5 -5
- data/public/js/tests/form.spec.js +98 -0
- 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 +16 -6
- data/public/sequenceserver-report.min.js +46 -24
- 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 +61 -3
- data/views/search.erb +33 -38
- data/views/search_layout.erb +153 -0
- data/views/tblastn_options.erb +57 -57
- data/views/tblastx_options.erb +64 -64
- metadata +51 -22
- data/lib/sequenceserver/makeblastdb-modified-with-cache.rb +0 -345
- data/public/SequenceServer_logo.png +0 -0
- data/public/js/tests/advanced_parameters.spec.js +0 -36
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/download_fasta.js
CHANGED
@@ -7,6 +7,7 @@ export default function downloadFASTA(sequence_ids, database_ids) {
|
|
7
7
|
var form = $('<form/>').attr('method', 'post').attr('action', 'get_sequence');
|
8
8
|
addField('sequence_ids', sequence_ids);
|
9
9
|
addField('database_ids', database_ids);
|
10
|
+
addField('_csrf', document.querySelector('meta[name="_csrf"]').content);
|
10
11
|
form.appendTo('body').submit().remove();
|
11
12
|
|
12
13
|
function addField(name, val) {
|
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,94 @@ 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
|
-
<
|
188
|
-
|
205
|
+
<form id="blast" ref={this.formRef} onSubmit={this.handleFormSubmission}>
|
206
|
+
<input type="hidden" name="_csrf" value={document.querySelector('meta[name="_csrf"]').content} />
|
207
|
+
<div className="px-4">
|
208
|
+
<SearchQueryWidget ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged} onSequenceChanged={this.handleSequenceChanged}/>
|
209
|
+
|
210
|
+
{this.useTreeWidget() ?
|
211
|
+
<DatabasesTree ref="databases"
|
212
|
+
databases={this.state.databases} tree={this.state.tree}
|
213
|
+
preSelectedDbs={this.state.preSelectedDbs}
|
214
|
+
onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
|
215
|
+
onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
|
216
|
+
:
|
217
|
+
<Databases ref="databases" databases={this.state.databases}
|
218
|
+
preSelectedDbs={this.state.preSelectedDbs}
|
219
|
+
onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
|
220
|
+
onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
|
221
|
+
}
|
222
|
+
|
223
|
+
<Options blastMethod={this.state.blastMethod} predefinedOptions={this.state.preDefinedOpts[this.state.blastMethod] || {}} blastTasks={(this.state.blastTaskMap || {})[this.state.blastMethod]} />
|
189
224
|
</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>
|
225
|
+
|
226
|
+
<div className="py-6"></div> {/* add a spacer so that the sticky action bar does not hide any contents */}
|
227
|
+
|
228
|
+
<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">
|
229
|
+
<QueryStats
|
230
|
+
residuesInQuerySequence={this.state.residuesInQuerySequence} numberOfDatabasesSelected={this.state.currentlySelectedDbs.length} residuesInSelectedDbs={this.residuesInSelectedDbs()}
|
231
|
+
currentBlastMethod={this.state.blastMethod}
|
232
|
+
/>
|
233
|
+
|
234
|
+
<label className="block my-4 md:my-2">
|
235
|
+
<input type="checkbox" id="toggleNewTab" /> Open results in new tab
|
236
|
+
</label>
|
209
237
|
<SearchButton ref="button" onAlgoChanged={this.handleAlgoChanged} />
|
210
238
|
</div>
|
239
|
+
|
211
240
|
</form>
|
212
241
|
</div>
|
213
242
|
);
|
@@ -220,11 +249,11 @@ class ProteinNotification extends Component {
|
|
220
249
|
render() {
|
221
250
|
return (
|
222
251
|
<div
|
223
|
-
|
252
|
+
data-role="notification"
|
224
253
|
id="protein-sequence-notification"
|
225
254
|
style={{ display: 'none' }}>
|
226
255
|
<div
|
227
|
-
className="
|
256
|
+
className="bg-blue-100 border rounded border-blue-800 px-4 py-2 my-2">
|
228
257
|
Detected: amino-acid sequence(s).
|
229
258
|
</div>
|
230
259
|
</div>
|
@@ -235,11 +264,11 @@ class ProteinNotification extends Component {
|
|
235
264
|
class NucleotideNotification extends Component {
|
236
265
|
render() {
|
237
266
|
return (<div
|
238
|
-
|
267
|
+
data-role="notification"
|
239
268
|
id="nucleotide-sequence-notification"
|
240
269
|
style={{ display: 'none' }}>
|
241
270
|
<div
|
242
|
-
className="
|
271
|
+
className="bg-blue-100 border rounded border-blue-800 px-4 py-2 my-2">
|
243
272
|
Detected: nucleotide sequence(s).
|
244
273
|
</div>
|
245
274
|
</div>
|
@@ -250,11 +279,11 @@ class NucleotideNotification extends Component {
|
|
250
279
|
class FastqNotification extends Component {
|
251
280
|
render() {
|
252
281
|
return (<div
|
253
|
-
|
282
|
+
data-role="notification"
|
254
283
|
id="fastq-sequence-notification"
|
255
284
|
style={{ display: 'none' }}>
|
256
285
|
<div
|
257
|
-
className="
|
286
|
+
className="bg-blue-100 border rounded border-blue-800 px-4 py-2 my-2">
|
258
287
|
Detected FASTQ and automatically converted to FASTA.
|
259
288
|
</div>
|
260
289
|
</div>
|
@@ -266,7 +295,7 @@ class MixedNotification extends Component {
|
|
266
295
|
render() {
|
267
296
|
return (
|
268
297
|
<div
|
269
|
-
|
298
|
+
data-role="notification"
|
270
299
|
id="mixed-sequence-notification"
|
271
300
|
style={{ display: 'none' }}>
|
272
301
|
<div
|