sequenceserver 3.1.1 → 3.1.3
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 +24 -0
- data/lib/sequenceserver/blast/tasks.rb +1 -1
- data/lib/sequenceserver/blast.rb +6 -0
- data/lib/sequenceserver/database.rb +13 -0
- data/lib/sequenceserver/routes.rb +1 -1
- data/lib/sequenceserver/sequence.rb +1 -2
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver.rb +1 -1
- data/public/css/app.min.css +1 -1
- data/public/css/sequenceserver.css +0 -18
- data/public/css/sequenceserver.min.css +2 -2
- data/public/js/alignment_exporter.js +16 -28
- data/public/js/cloud_share_modal.js +42 -42
- data/public/js/form.js +12 -10
- data/public/js/grapher.js +4 -4
- data/public/js/hit.js +3 -3
- data/public/js/hits.js +276 -0
- data/public/js/jquery_world.js +1 -1
- data/public/js/mailto.js +1 -3
- data/public/js/null_plugins/report_plugins.js +1 -0
- data/public/js/options.js +2 -6
- data/public/js/query.js +1 -1
- data/public/js/report.js +68 -252
- data/public/js/report_root.js +7 -5
- data/public/js/search.js +28 -11
- data/public/js/sequence.js +158 -158
- data/public/js/sequence_modal.js +28 -36
- data/public/js/sidebar.js +7 -6
- 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/report.spec.js +37 -15
- 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 +300 -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 +292 -0
- data/public/sequenceserver-search.min.js.map +1 -0
- data/views/layout.erb +3 -7
- data/views/search.erb +1 -1
- data/views/search_layout.erb +1 -1
- metadata +11 -5
- data/public/config.js +0 -147
- data/public/packages/jquery-ui@1.11.4.js +0 -16624
@@ -1,5 +1,6 @@
|
|
1
1
|
import * as Exporter from './exporter';
|
2
2
|
import _ from 'underscore';
|
3
|
+
|
3
4
|
export default class AlignmentExporter {
|
4
5
|
constructor() {
|
5
6
|
this.prepare_alignments_for_export = this.prepare_alignments_for_export.bind(this);
|
@@ -7,49 +8,36 @@ export default class AlignmentExporter {
|
|
7
8
|
}
|
8
9
|
|
9
10
|
wrap_string(str, width) {
|
10
|
-
|
11
|
-
var wrapped = '';
|
12
|
-
while(true) {
|
13
|
-
wrapped += str.substring(idx, idx + width);
|
14
|
-
idx += width;
|
15
|
-
if(idx < str.length) {
|
16
|
-
wrapped += '\n';
|
17
|
-
} else {
|
18
|
-
break;
|
19
|
-
}
|
20
|
-
}
|
21
|
-
return wrapped;
|
11
|
+
return str.match(new RegExp(`.{1,${width}}`, 'g')).join('\n');
|
22
12
|
}
|
23
13
|
|
24
14
|
generate_fasta(hsps) {
|
15
|
+
let fasta = '';
|
25
16
|
|
26
|
-
|
17
|
+
hsps.map(hsp => {
|
18
|
+
fasta += `>${hsp.query_id}:${hsp.qstart}-${hsp.qend}\n`;
|
19
|
+
fasta += `${hsp.qseq}\n`;
|
20
|
+
fasta += `>${hsp.query_id}:${hsp.qstart}-${hsp.qend}_alignment_${hsp.hit_id}:${hsp.sstart}-${hsp.send}\n`;
|
21
|
+
fasta += `${hsp.midline}\n`;
|
22
|
+
fasta += `>${hsp.hit_id}:${hsp.sstart}-${hsp.send}\n`;
|
23
|
+
fasta += `${hsp.sseq}\n`;
|
24
|
+
});
|
27
25
|
|
28
|
-
_.each(hsps, _.bind(function (hsp) {
|
29
|
-
fasta += '>'+hsp.query_id+':'+hsp.qstart+'-'+hsp.qend+'\n';
|
30
|
-
fasta += hsp.qseq+'\n';
|
31
|
-
fasta += '>'+hsp.query_id+':'+hsp.qstart+'-'+hsp.qend+'_alignment_'+hsp.hit_id+':'+hsp.sstart+'-'+hsp.send+'\n';
|
32
|
-
fasta += hsp.midline+'\n';
|
33
|
-
fasta += '>'+hsp.hit_id+':'+hsp.sstart+'-'+hsp.send+'\n';
|
34
|
-
fasta += hsp.sseq+'\n';
|
35
|
-
}, this));
|
36
26
|
return fasta;
|
37
27
|
}
|
38
28
|
|
39
29
|
get_alignments_download_metadata(hsps, filename_prefix){
|
40
|
-
|
41
|
-
|
42
|
-
|
30
|
+
const fasta = this.generate_fasta(hsps);
|
31
|
+
const blob = new Blob([fasta], { type: 'text/fasta' });
|
32
|
+
const filename = Exporter.sanitize_filename(filename_prefix) + '.txt';
|
43
33
|
return {filename, blob};
|
44
34
|
}
|
45
35
|
|
46
|
-
|
47
36
|
prepare_alignments_for_export(hsps, filename_prefix) {
|
48
37
|
const { filename, blob } = this.get_alignments_download_metadata(hsps, filename_prefix);
|
49
|
-
|
50
|
-
return blob_url;
|
38
|
+
return Exporter.generate_blob_url(blob, filename);
|
51
39
|
}
|
52
|
-
|
40
|
+
|
53
41
|
export_alignments(hsps, filename_prefix) {
|
54
42
|
const { filename, blob } = this.get_alignments_download_metadata(hsps, filename_prefix);
|
55
43
|
Exporter.download_blob(blob, filename);
|
@@ -28,13 +28,11 @@ export default class CloudShareModal extends React.Component {
|
|
28
28
|
this.setState({ [name]: inputValue });
|
29
29
|
}
|
30
30
|
|
31
|
-
handleSubmit = (e) => {
|
31
|
+
handleSubmit = async (e) => {
|
32
32
|
e.preventDefault();
|
33
33
|
|
34
34
|
const { email } = this.state;
|
35
|
-
const
|
36
|
-
const match = window.location.pathname.match(regex);
|
37
|
-
const jobId = match[1];
|
35
|
+
const jobId = this.getJobIdFromPath();
|
38
36
|
|
39
37
|
this.setState({ formState: 'loading' });
|
40
38
|
|
@@ -43,33 +41,40 @@ export default class CloudShareModal extends React.Component {
|
|
43
41
|
sender_email: email
|
44
42
|
};
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
.then(response => response.json())
|
54
|
-
.then(data => {
|
55
|
-
if (data.shareable_url) {
|
56
|
-
// Successful response
|
57
|
-
this.setState({ formState: 'results', shareableurl: data.shareable_url });
|
58
|
-
} else if (data.errors) {
|
59
|
-
// Error response with specific error messages
|
60
|
-
const errorMessages = data.errors;
|
61
|
-
this.setState({ formState: 'error', errorMessages });
|
62
|
-
} else {
|
63
|
-
// Generic error message
|
64
|
-
throw new Error('Unknown error submitting form');
|
65
|
-
}
|
66
|
-
})
|
67
|
-
.catch(error => {
|
68
|
-
this.setState({
|
69
|
-
formState: 'error',
|
70
|
-
errorMessages: [error.message]
|
71
|
-
});
|
44
|
+
try {
|
45
|
+
const response = await fetch('/cloud_share', {
|
46
|
+
method: 'POST',
|
47
|
+
headers: {
|
48
|
+
'Content-Type': 'application/json'
|
49
|
+
},
|
50
|
+
body: JSON.stringify(requestData)
|
72
51
|
});
|
52
|
+
|
53
|
+
if (!response.ok) {
|
54
|
+
throw new Error('Network response was not ok');
|
55
|
+
}
|
56
|
+
|
57
|
+
const data = await response.json();
|
58
|
+
|
59
|
+
if (data.shareable_url) {
|
60
|
+
this.setState({ formState: 'results', shareableurl: data.shareable_url });
|
61
|
+
} else if (data.errors) {
|
62
|
+
this.setState({ formState: 'error', errorMessages: data.errors });
|
63
|
+
} else {
|
64
|
+
throw new Error('Unknown error submitting form');
|
65
|
+
}
|
66
|
+
} catch (error) {
|
67
|
+
this.setState({
|
68
|
+
formState: 'error',
|
69
|
+
errorMessages: [error.message]
|
70
|
+
});
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
getJobIdFromPath = () => {
|
75
|
+
const regex = /\/([^/]+)(?:\/|#|\?|$)/;
|
76
|
+
const match = window.location.pathname.match(regex);
|
77
|
+
return match ? match[1] : match;
|
73
78
|
}
|
74
79
|
|
75
80
|
renderLoading() {
|
@@ -93,18 +98,13 @@ export default class CloudShareModal extends React.Component {
|
|
93
98
|
return (
|
94
99
|
<>
|
95
100
|
{
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
</div>
|
104
|
-
</div>
|
105
|
-
);
|
106
|
-
}, this)
|
107
|
-
)
|
101
|
+
errorMessages.map((errorMessage, index) => (
|
102
|
+
<div key={`fastan-${index}`} className="fastan">
|
103
|
+
<div className="section-content">
|
104
|
+
<div className="modal-error">{errorMessage}</div>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
))
|
108
108
|
}
|
109
109
|
{this.renderForm()}
|
110
110
|
</>
|
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
|
|
@@ -65,7 +67,7 @@ export class Form extends Component {
|
|
65
67
|
* (if any).
|
66
68
|
*/
|
67
69
|
if (data['query']) {
|
68
|
-
this.
|
70
|
+
this.query.current.value(data['query']);
|
69
71
|
}
|
70
72
|
|
71
73
|
setTimeout(function () {
|
@@ -96,7 +98,7 @@ export class Form extends Component {
|
|
96
98
|
evt.preventDefault();
|
97
99
|
const form = this.formRef.current;
|
98
100
|
const formData = new FormData(form);
|
99
|
-
formData.append('method', this.
|
101
|
+
formData.append('method', this.button.current.state.methods[0]);
|
100
102
|
fetch(window.location.href, {
|
101
103
|
method: 'POST',
|
102
104
|
body: formData
|
@@ -118,7 +120,7 @@ export class Form extends Component {
|
|
118
120
|
var database_type = this.databaseType;
|
119
121
|
var sequence_type = this.sequenceType;
|
120
122
|
|
121
|
-
if (this.
|
123
|
+
if (this.query.current.isEmpty()) {
|
122
124
|
return [];
|
123
125
|
}
|
124
126
|
|
@@ -165,8 +167,8 @@ export class Form extends Component {
|
|
165
167
|
}
|
166
168
|
|
167
169
|
setButtonState() {
|
168
|
-
this.
|
169
|
-
hasQuery: !this.
|
170
|
+
this.button.current.setState({
|
171
|
+
hasQuery: !this.query.current.isEmpty(),
|
170
172
|
hasDatabases: !!this.databaseType,
|
171
173
|
methods: this.determineBlastMethods()
|
172
174
|
});
|
@@ -205,16 +207,16 @@ export class Form extends Component {
|
|
205
207
|
<form id="blast" ref={this.formRef} onSubmit={this.handleFormSubmission}>
|
206
208
|
<input type="hidden" name="_csrf" value={document.querySelector('meta[name="_csrf"]').content} />
|
207
209
|
<div className="px-4">
|
208
|
-
<SearchQueryWidget ref=
|
210
|
+
<SearchQueryWidget ref={this.query} onSequenceTypeChanged={this.handleSequenceTypeChanged} onSequenceChanged={this.handleSequenceChanged}/>
|
209
211
|
|
210
212
|
{this.useTreeWidget() ?
|
211
|
-
<DatabasesTree
|
213
|
+
<DatabasesTree
|
212
214
|
databases={this.state.databases} tree={this.state.tree}
|
213
215
|
preSelectedDbs={this.state.preSelectedDbs}
|
214
216
|
onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
|
215
217
|
onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
|
216
218
|
:
|
217
|
-
<Databases
|
219
|
+
<Databases databases={this.state.databases}
|
218
220
|
preSelectedDbs={this.state.preSelectedDbs}
|
219
221
|
onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
|
220
222
|
onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
|
@@ -234,7 +236,7 @@ export class Form extends Component {
|
|
234
236
|
<label className="block my-4 md:my-2">
|
235
237
|
<input type="checkbox" id="toggleNewTab" /> Open results in new tab
|
236
238
|
</label>
|
237
|
-
<SearchButton ref=
|
239
|
+
<SearchButton ref={this.button} onAlgoChanged={this.handleAlgoChanged} />
|
238
240
|
</div>
|
239
241
|
|
240
242
|
</form>
|
@@ -305,4 +307,4 @@ class MixedNotification extends Component {
|
|
305
307
|
</div>
|
306
308
|
);
|
307
309
|
}
|
308
|
-
}
|
310
|
+
}
|
data/public/js/grapher.js
CHANGED
@@ -17,7 +17,7 @@ export default function Grapher(Graph) {
|
|
17
17
|
return class extends React.Component {
|
18
18
|
constructor(props) {
|
19
19
|
super(props);
|
20
|
-
this.name = Graph.name();
|
20
|
+
this.name = Graph.name(this.props);
|
21
21
|
this.collapsePreferences = new CollapsePreferences(this);
|
22
22
|
let isCollapsed = this.collapsePreferences.preferenceStoredAsCollapsed();
|
23
23
|
this.state = { collapsed: Graph.canCollapse() && (this.props.collapsed || isCollapsed) };
|
@@ -30,12 +30,12 @@ export default function Grapher(Graph) {
|
|
30
30
|
|
31
31
|
render() {
|
32
32
|
// Do not render when Graph.name() is null
|
33
|
-
if (Graph.name() === null) {
|
33
|
+
if (Graph.name(this.props) === null) {
|
34
34
|
return null;
|
35
35
|
} else {
|
36
36
|
var cssClasses = Graph.className() + ' grapher';
|
37
37
|
return (
|
38
|
-
<div
|
38
|
+
<div className={cssClasses}>
|
39
39
|
{this.header()}
|
40
40
|
{this.svgContainerJSX()}
|
41
41
|
</div>
|
@@ -51,7 +51,7 @@ export default function Grapher(Graph) {
|
|
51
51
|
onClick={() => this.collapsePreferences.toggleCollapse()}
|
52
52
|
>
|
53
53
|
{this.collapsePreferences.renderCollapseIcon()}
|
54
|
-
{Graph.name()}
|
54
|
+
{Graph.name(this.props)}
|
55
55
|
</h4>
|
56
56
|
{!this.state.collapsed && this.graphLinksJSX()}
|
57
57
|
</div>;
|
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;
|
@@ -246,4 +246,4 @@ export default class extends Component {
|
|
246
246
|
</div>
|
247
247
|
);
|
248
248
|
}
|
249
|
-
}
|
249
|
+
}
|
data/public/js/hits.js
ADDED
@@ -0,0 +1,276 @@
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
2
|
+
import { Component } from 'react';
|
3
|
+
import _ from 'underscore';
|
4
|
+
|
5
|
+
import { ReportQuery } from './query';
|
6
|
+
import Hit from './hit';
|
7
|
+
import HSP from './hsp';
|
8
|
+
import AlignmentExporter from './alignment_exporter';
|
9
|
+
/* eslint-enable no-unused-vars */
|
10
|
+
|
11
|
+
class Hits extends Component {
|
12
|
+
constructor(props) {
|
13
|
+
super(props);
|
14
|
+
this.numUpdates = 0;
|
15
|
+
this.nextQuery = 0;
|
16
|
+
this.nextHit = 0;
|
17
|
+
this.nextHSP = 0;
|
18
|
+
this.maxHSPs = 3; // max HSPs to render in a cycle
|
19
|
+
this.state = props.state;
|
20
|
+
this.prepareAlignmentOfSelectedHits = this.prepareAlignmentOfSelectedHits.bind(this);
|
21
|
+
}
|
22
|
+
|
23
|
+
componentDidMount() {
|
24
|
+
this.componentDidUpdate(this.props, this.state);
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Called for the first time after as BLAST results have been retrieved from
|
29
|
+
* the server and added to this.state by fetchResults. Only summary overview
|
30
|
+
* and circos would have been rendered at this point. At this stage we kick
|
31
|
+
* start iteratively adding 1 HSP to the page every 25 milli-seconds.
|
32
|
+
*/
|
33
|
+
componentDidUpdate(prevProps, prevState) {
|
34
|
+
// Log to console how long the last update take?
|
35
|
+
// console.log((Date.now() - this.lastTimeStamp) / 1000);
|
36
|
+
|
37
|
+
// Lock sidebar in its position on the first update.
|
38
|
+
if (this.nextQuery == 0 && this.nextHit == 0 && this.nextHSP == 0) {
|
39
|
+
this.affixSidebar();
|
40
|
+
}
|
41
|
+
|
42
|
+
// Queue next update if we have not rendered all results yet.
|
43
|
+
if (this.nextQuery < this.state.queries.length) {
|
44
|
+
// setTimeout is used to clear call stack and space out
|
45
|
+
// the updates giving the browser a chance to respond
|
46
|
+
// to user interactions.
|
47
|
+
setTimeout(() => this.updateState(), 25);
|
48
|
+
} else {
|
49
|
+
this.props.componentFinishedUpdating();
|
50
|
+
}
|
51
|
+
|
52
|
+
this.props.plugins.componentDidUpdate(prevProps, prevState);
|
53
|
+
}
|
54
|
+
|
55
|
+
/* eslint complexity: ["error", 6] */
|
56
|
+
/* ---------------------
|
57
|
+
* Push next slice of results to React for rendering.
|
58
|
+
*/
|
59
|
+
updateState() {
|
60
|
+
var results = { items: [], numHSPsProcessed: 0 };
|
61
|
+
this.processQueries(results);
|
62
|
+
|
63
|
+
// Push the components to react for rendering.
|
64
|
+
this.numUpdates++;
|
65
|
+
this.lastTimeStamp = Date.now();
|
66
|
+
this.setState({
|
67
|
+
results: this.state.results.concat(results.items),
|
68
|
+
veryBig: this.numUpdates >= 250,
|
69
|
+
});
|
70
|
+
}
|
71
|
+
|
72
|
+
processQueries(results) {
|
73
|
+
while (this.nextQuery < this.state.queries.length) {
|
74
|
+
var query = this.state.queries[this.nextQuery];
|
75
|
+
|
76
|
+
// We may see a query multiple times during rendering because only
|
77
|
+
// 3 hsps are rendered in each cycle, but we want to create the
|
78
|
+
// corresponding Query component only the first time we see it.
|
79
|
+
if (this.nextHit == 0 && this.nextHSP == 0) {
|
80
|
+
results.items.push(this.renderReportQuery(query));
|
81
|
+
results.items.push(...this.props.plugins.queryResults(query));
|
82
|
+
}
|
83
|
+
|
84
|
+
this.processHits(results, query);
|
85
|
+
this.itterateLoops(['nextQuery', 'nextHit'], query.hits.length);
|
86
|
+
if (results.numHSPsProcessed == this.maxHSPs) break;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
processHits(results, query) {
|
91
|
+
while (this.nextHit < query.hits.length) {
|
92
|
+
var hit = query.hits[this.nextHit];
|
93
|
+
// We may see a hit multiple times during rendering because only
|
94
|
+
// 10 hsps are rendered in each cycle, but we want to create the
|
95
|
+
// corresponding Hit component only the first time we see it.
|
96
|
+
if (this.nextHSP == 0) results.items.push(this.renderHit(query, hit));
|
97
|
+
|
98
|
+
this.processHSPS(results, query, hit);
|
99
|
+
this.itterateLoops(['nextHit', 'nextHSP'], hit.hsps.length);
|
100
|
+
if (results.numHSPsProcessed == this.maxHSPs) break;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
processHSPS(results, query, hit) {
|
105
|
+
while (this.nextHSP < hit.hsps.length) {
|
106
|
+
// Get nextHSP and increment the counter.
|
107
|
+
var hsp = hit.hsps[this.nextHSP++];
|
108
|
+
results.items.push(
|
109
|
+
this.renderHsp(query, hit, hsp)
|
110
|
+
);
|
111
|
+
results.numHSPsProcessed++;
|
112
|
+
if (results.numHSPsProcessed == this.maxHSPs) break;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
/*
|
117
|
+
* this function check if 2nd argument is reach end of it
|
118
|
+
*/
|
119
|
+
itterateLoops(args, length) {
|
120
|
+
if (this[args[1]] != length) return;
|
121
|
+
|
122
|
+
this[args[0]]++;
|
123
|
+
this[args[1]] = 0;
|
124
|
+
}
|
125
|
+
|
126
|
+
renderHsp(query, hit, hsp) {
|
127
|
+
return (
|
128
|
+
<HSP
|
129
|
+
key={
|
130
|
+
'Query_' +
|
131
|
+
query.number +
|
132
|
+
'_Hit_' +
|
133
|
+
hit.number +
|
134
|
+
'_HSP_' +
|
135
|
+
hsp.number
|
136
|
+
}
|
137
|
+
query={query}
|
138
|
+
hit={hit}
|
139
|
+
hsp={hsp}
|
140
|
+
algorithm={this.state.program}
|
141
|
+
showHSPNumbers={hit.hsps.length > 1}
|
142
|
+
{...this.props}
|
143
|
+
/>
|
144
|
+
);
|
145
|
+
}
|
146
|
+
|
147
|
+
renderHit(query, hit) {
|
148
|
+
return (
|
149
|
+
<Hit
|
150
|
+
key={'Query_' + query.number + '_Hit_' + hit.number}
|
151
|
+
query={query}
|
152
|
+
hit={hit}
|
153
|
+
algorithm={this.state.program}
|
154
|
+
querydb={this.state.querydb}
|
155
|
+
selectHit={this.selectHit}
|
156
|
+
imported_xml={this.state.imported_xml}
|
157
|
+
non_parse_seqids={this.state.non_parse_seqids}
|
158
|
+
showQueryCrumbs={this.state.queries.length > 1}
|
159
|
+
showHitCrumbs={query.hits.length > 1}
|
160
|
+
veryBig={this.state.veryBig}
|
161
|
+
onChange={this.prepareAlignmentOfSelectedHits}
|
162
|
+
{...this.props}
|
163
|
+
/>
|
164
|
+
);
|
165
|
+
}
|
166
|
+
|
167
|
+
renderReportQuery(query) {
|
168
|
+
return (
|
169
|
+
<ReportQuery
|
170
|
+
key={'Query_' + query.id}
|
171
|
+
query={query}
|
172
|
+
program={this.state.program}
|
173
|
+
querydb={this.state.querydb}
|
174
|
+
showQueryCrumbs={this.state.queries.length > 1}
|
175
|
+
non_parse_seqids={this.state.non_parse_seqids}
|
176
|
+
imported_xml={this.state.imported_xml}
|
177
|
+
veryBig={this.state.veryBig}
|
178
|
+
/>
|
179
|
+
);
|
180
|
+
}
|
181
|
+
|
182
|
+
/**
|
183
|
+
* Affixes the sidebar.
|
184
|
+
*/
|
185
|
+
affixSidebar() {
|
186
|
+
var $sidebar = $('.sidebar');
|
187
|
+
var sidebarOffset = $sidebar.offset();
|
188
|
+
if (sidebarOffset) {
|
189
|
+
$sidebar.affix({
|
190
|
+
offset: {
|
191
|
+
top: sidebarOffset.top,
|
192
|
+
},
|
193
|
+
});
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
/* eslint complexity: ["error", 6] */
|
198
|
+
/* -----------------------------------
|
199
|
+
* Event-handler when hit is selected
|
200
|
+
* Adds glow to hit component.
|
201
|
+
* Updates number of Fasta that can be downloaded
|
202
|
+
*/
|
203
|
+
selectHit(id) {
|
204
|
+
var checkbox = $('#' + id);
|
205
|
+
var num_checked = $('.hit-links :checkbox:checked').length;
|
206
|
+
|
207
|
+
if (!checkbox || !checkbox.val()) return;
|
208
|
+
|
209
|
+
var $hit = $(checkbox.data('target'));
|
210
|
+
|
211
|
+
// Highlight selected hit and enable 'Download FASTA/Alignment of
|
212
|
+
// selected' links.
|
213
|
+
if (checkbox.is(':checked')) {
|
214
|
+
$hit.addClass('glow');
|
215
|
+
$hit.next('.hsp').addClass('glow');
|
216
|
+
$('.download-fasta-of-selected').enable();
|
217
|
+
$('.download-alignment-of-selected').enable();
|
218
|
+
} else {
|
219
|
+
$hit.removeClass('glow');
|
220
|
+
$hit.next('.hsp').removeClass('glow');
|
221
|
+
$('.download-fasta-of-selected').attr('href', '#').removeAttr('download');
|
222
|
+
}
|
223
|
+
|
224
|
+
var $a = $('.download-fasta-of-selected');
|
225
|
+
var $b = $('.download-alignment-of-selected');
|
226
|
+
|
227
|
+
if (num_checked >= 1) {
|
228
|
+
$a.find('.text-bold').html(num_checked);
|
229
|
+
$b.find('.text-bold').html(num_checked);
|
230
|
+
}
|
231
|
+
|
232
|
+
if (num_checked == 0) {
|
233
|
+
$a.addClass('disabled').find('.text-bold').html('');
|
234
|
+
$b.addClass('disabled').find('.text-bold').html('');
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
prepareAlignmentOfSelectedHits() {
|
239
|
+
var sequence_ids = $('.hit-links :checkbox:checked').map(function () {
|
240
|
+
return this.value;
|
241
|
+
}).get();
|
242
|
+
|
243
|
+
if(!sequence_ids.length){
|
244
|
+
// remove attributes from link if sequence_ids array is empty
|
245
|
+
$('.download-alignment-of-selected').attr('href', '#').removeAttr('download');
|
246
|
+
return;
|
247
|
+
|
248
|
+
}
|
249
|
+
if(this.state.alignment_blob_url){
|
250
|
+
// always revoke existing url if any because this method will always create a new url
|
251
|
+
window.URL.revokeObjectURL(this.state.alignment_blob_url);
|
252
|
+
}
|
253
|
+
var hsps_arr = [];
|
254
|
+
var aln_exporter = new AlignmentExporter();
|
255
|
+
const self = this;
|
256
|
+
_.each(this.state.queries, _.bind(function (query) {
|
257
|
+
_.each(query.hits, function (hit) {
|
258
|
+
if (_.indexOf(sequence_ids, hit.id) != -1) {
|
259
|
+
hsps_arr = hsps_arr.concat(self.props.populate_hsp_array(hit, query.id));
|
260
|
+
}
|
261
|
+
});
|
262
|
+
}, this));
|
263
|
+
const filename = 'alignment-' + sequence_ids.length + '_hits.txt';
|
264
|
+
const blob_url = aln_exporter.prepare_alignments_for_export(hsps_arr, filename);
|
265
|
+
// set required download attributes for link
|
266
|
+
$('.download-alignment-of-selected').attr('href', blob_url).attr('download', filename);
|
267
|
+
// track new url for future removal
|
268
|
+
this.setState({alignment_blob_url: blob_url});
|
269
|
+
}
|
270
|
+
|
271
|
+
render() {
|
272
|
+
return this.state.results;
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
export default Hits;
|
data/public/js/jquery_world.js
CHANGED
data/public/js/mailto.js
CHANGED
@@ -5,9 +5,7 @@ export default function asMailtoHref(querydb, program, numQueries, url, isOpenAc
|
|
5
5
|
}
|
6
6
|
|
7
7
|
function formatDatabases(querydb) {
|
8
|
-
return querydb
|
9
|
-
.slice(0, 15)
|
10
|
-
.map(db => ' ' + db.title);
|
8
|
+
return querydb ? querydb.slice(0, 15).map(db => ' ' + db.title) : "";
|
11
9
|
}
|
12
10
|
|
13
11
|
function composeEmail(dbsArr, program, numQueries, url, isOpenAccess) {
|
data/public/js/options.js
CHANGED
@@ -110,18 +110,14 @@ export class Options extends Component {
|
|
110
110
|
}
|
111
111
|
|
112
112
|
advancedParamsJSX() {
|
113
|
-
|
114
|
-
return null;
|
115
|
-
}
|
116
|
-
|
117
|
-
let classNames = 'flex-grow block px-4 py-1 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 text-base';
|
113
|
+
let classNames = 'flex-grow block px-4 py-1 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 text-base font-mono';
|
118
114
|
|
119
115
|
if (this.state.textValue) {
|
120
116
|
classNames += ' bg-yellow-100';
|
121
117
|
}
|
122
118
|
|
123
119
|
return(
|
124
|
-
|
120
|
+
<div className={this.state.paramsMode !== 'advanced' ? 'w-full hidden' : 'w-full'}>
|
125
121
|
<div className="flex items-end">
|
126
122
|
<label className="flex items-center" htmlFor="advanced">
|
127
123
|
Advanced parameters
|
data/public/js/query.js
CHANGED
@@ -348,7 +348,7 @@ export class SearchQueryWidget extends Component {
|
|
348
348
|
className="sequence">
|
349
349
|
<textarea
|
350
350
|
id="sequence" ref={this.textareaRef}
|
351
|
-
className="block w-full p-4 text-gray-900 border border-gray-300 rounded-l-lg rounded-tr-lg bg-gray-50 text-base
|
351
|
+
className="block w-full p-4 text-gray-900 border border-gray-300 rounded-l-lg rounded-tr-lg bg-gray-50 text-base font-mono"
|
352
352
|
name="sequence" value={this.state.value}
|
353
353
|
rows="6"
|
354
354
|
required="required"
|