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/sequence_modal.js
CHANGED
|
@@ -13,6 +13,7 @@ export default class SequenceModal extends React.Component {
|
|
|
13
13
|
error_msgs: [],
|
|
14
14
|
sequences: [],
|
|
15
15
|
requestCompleted: false,
|
|
16
|
+
isModalVisible: false,
|
|
16
17
|
};
|
|
17
18
|
this.modalRef = createRef();
|
|
18
19
|
}
|
|
@@ -20,98 +21,88 @@ export default class SequenceModal extends React.Component {
|
|
|
20
21
|
// Lifecycle methods. //
|
|
21
22
|
|
|
22
23
|
render() {
|
|
24
|
+
const { isModalVisible, requestCompleted } = this.state;
|
|
25
|
+
|
|
23
26
|
return (
|
|
24
|
-
<div className="
|
|
25
|
-
<
|
|
26
|
-
<div className="
|
|
27
|
-
<div className="
|
|
28
|
-
<h3
|
|
27
|
+
<div className="relative sequence-viewer-wrap">
|
|
28
|
+
<dialog ref={this.modalRef} className="sequence-viewer fixed p-4 w-full max-w-2xl bg-transparent focus:outline-none overflow-visible z-50">
|
|
29
|
+
<div className="relative flex flex-col rounded-lg bg-white shadow">
|
|
30
|
+
<div className="flex items-start justify-between rounded-t border-b p-5">
|
|
31
|
+
<h3 className="text-xl font-medium text-gray-900">
|
|
32
|
+
View sequence
|
|
33
|
+
</h3>
|
|
34
|
+
<button className="sequence-viewer-close ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-gray-400 hover:bg-gray-200" onClick={this.hide}>
|
|
35
|
+
<i className="fa-solid fa-xmark hover:text-black"></i>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="sequence-viewer-content max-h-[80vh] overflow-y-scroll">
|
|
39
|
+
{(requestCompleted && this.resultsJSX()) || this.loadingJSX()}
|
|
29
40
|
</div>
|
|
30
|
-
|
|
31
|
-
{(this.state.requestCompleted && this.resultsJSX()) ||
|
|
32
|
-
this.loadingJSX()}
|
|
33
41
|
</div>
|
|
34
|
-
</
|
|
42
|
+
</dialog>
|
|
35
43
|
</div>
|
|
36
44
|
);
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
/*
|
|
40
|
-
* Returns jQuery reference to the main modal container.
|
|
41
|
-
*/
|
|
42
|
-
modal() {
|
|
43
|
-
return $(this.modalRef.current);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
47
|
/**
|
|
47
48
|
* Shows sequence viewer.
|
|
48
49
|
*/
|
|
49
|
-
show(url) {
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
50
|
+
show = (url) => {
|
|
51
|
+
this.modalRef.current?.showModal();
|
|
52
|
+
this.setState({ requestCompleted: false });
|
|
53
|
+
this.loadJSON(url);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Hide sequence viewer.
|
|
58
58
|
*/
|
|
59
|
-
hide() {
|
|
60
|
-
this.
|
|
59
|
+
hide = () => {
|
|
60
|
+
this.modalRef.current?.close();
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Loads sequence using AJAX and updates modal state.
|
|
65
65
|
*/
|
|
66
|
-
loadJSON(url) {
|
|
66
|
+
async loadJSON(url) {
|
|
67
67
|
// Fetch sequence and update state.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
76
|
-
}, this)
|
|
77
|
-
)
|
|
78
|
-
.fail((jqXHR, status, error) => {
|
|
79
|
-
this.hide();
|
|
80
|
-
this.props.showErrorModal(jqXHR.responseJSON);
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(url);
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
this.setState({
|
|
72
|
+
sequences: data.sequences,
|
|
73
|
+
error_msgs: data.error_msgs,
|
|
74
|
+
requestCompleted: true,
|
|
81
75
|
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.log('Error fetching sequence:', error);
|
|
78
|
+
this.hide();
|
|
79
|
+
this.props.showErrorModal(error);
|
|
80
|
+
}
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
resultsJSX() {
|
|
85
84
|
return (
|
|
86
|
-
<div className="
|
|
87
|
-
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}, this)
|
|
101
|
-
)}
|
|
102
|
-
{_.map(
|
|
103
|
-
this.state.sequences,
|
|
104
|
-
_.bind(function (sequence) {
|
|
105
|
-
return <SequenceViewer sequence={sequence} />;
|
|
106
|
-
}, this)
|
|
107
|
-
)}
|
|
85
|
+
<div className="pt-2 px-6 pb-6 mt-2">
|
|
86
|
+
{this.state.error_msgs.map((error_msg, index) => (
|
|
87
|
+
<div key={`error-message-${index}`} className="fastan">
|
|
88
|
+
<div className="section-header border-b border-seqorange pl-px table mb-0 w-full pb-2">
|
|
89
|
+
<h4 className="text-sm table-cell">{error_msg[0]}</h4>
|
|
90
|
+
</div>
|
|
91
|
+
<div className="pt-0 px-0 pb-px">
|
|
92
|
+
<pre className="m-0 p-0 rounded-none border-0 bg-inherit whitespace-pre-wrap break-keep">{error_msg[1]}</pre>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
))}
|
|
96
|
+
{this.state.sequences.map((sequence, index) => (
|
|
97
|
+
<SequenceViewer key={`sequence-viewer-${index}`} sequence={sequence} />
|
|
98
|
+
))}
|
|
108
99
|
</div>
|
|
109
100
|
);
|
|
110
101
|
}
|
|
111
102
|
|
|
112
103
|
loadingJSX() {
|
|
113
104
|
return (
|
|
114
|
-
<div className="
|
|
105
|
+
<div className="my-4 text-center">
|
|
115
106
|
<i className="fa fa-spinner fa-3x fa-spin"></i>
|
|
116
107
|
</div>
|
|
117
108
|
);
|
|
@@ -132,13 +123,13 @@ class SequenceViewer extends React.Component {
|
|
|
132
123
|
|
|
133
124
|
return (
|
|
134
125
|
<div className="fastan">
|
|
135
|
-
<div className="section-header">
|
|
136
|
-
<h4>
|
|
126
|
+
<div className="section-header border-b border-seqorange pl-px table mb-0 w-full pb-2">
|
|
127
|
+
<h4 className="text-sm table-cell">
|
|
137
128
|
{this.props.sequence.id}
|
|
138
|
-
<small> {this.props.sequence.title}</small>
|
|
129
|
+
<small className="text-inherit"> {this.props.sequence.title}</small>
|
|
139
130
|
</h4>
|
|
140
131
|
</div>
|
|
141
|
-
<div className="
|
|
132
|
+
<div className="fastan-content relative pt-0 px-0 pb-px">
|
|
142
133
|
<div className={this.widgetClass} id={this.widgetID}></div>
|
|
143
134
|
</div>
|
|
144
135
|
</div>
|
|
@@ -160,6 +151,8 @@ class SequenceViewer extends React.Component {
|
|
|
160
151
|
footer: false,
|
|
161
152
|
},
|
|
162
153
|
});
|
|
163
|
-
|
|
154
|
+
setTimeout(function() {
|
|
155
|
+
requestAnimationFrame(() => { widget.hideFormatSelector() }); // ensure React is done painting the DOM of the element before calling a function on it.
|
|
156
|
+
});
|
|
164
157
|
}
|
|
165
158
|
}
|
data/public/js/share_url.js
CHANGED
|
@@ -10,14 +10,14 @@ const ShareURLComponent = ({ querydb, program, queryLength, url }) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
|
-
<div className="share-url-component">
|
|
14
|
-
<input name="shareableUrl" type="text" value={url} readOnly />
|
|
15
|
-
<div className="
|
|
16
|
-
<button className="
|
|
13
|
+
<div className="px-6 share-url-component">
|
|
14
|
+
<input className="w-full mb-3 pt-3" name="shareableUrl" type="text" value={url} readOnly />
|
|
15
|
+
<div className="py-4 flex justify-between items-center">
|
|
16
|
+
<button className="py-2 px-3 border border-transparent rounded-md shadow-sm text-white bg-seqblue hover:bg-seqorange focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-seqorange" onClick={copyToClipboard}>{copied ? 'Copied!' : 'Copy to Clipboard'}</button>
|
|
17
17
|
<a href={asMailtoHref(querydb, program, queryLength, url, true)}>Share via email</a>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
20
|
);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export default ShareURLComponent;
|
|
23
|
+
export default ShareURLComponent;
|
data/public/js/sidebar.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component } from 'react';
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
2
|
import _ from 'underscore';
|
|
3
3
|
|
|
4
4
|
import downloadFASTA from './download_fasta';
|
|
@@ -32,6 +32,7 @@ export default class extends Component {
|
|
|
32
32
|
this.copyURL = this.copyURL.bind(this);
|
|
33
33
|
this.shareCloudInit = this.shareCloudInit.bind(this);
|
|
34
34
|
this.sharingPanelJSX = this.sharingPanelJSX.bind(this);
|
|
35
|
+
this.cloudShareModal = React.createRef();
|
|
35
36
|
this.timeout = null;
|
|
36
37
|
this.queryElems = [];
|
|
37
38
|
this.state = {
|
|
@@ -40,14 +41,6 @@ export default class extends Component {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
componentDidMount() {
|
|
43
|
-
/**
|
|
44
|
-
* Fixes tooltips in the sidebar, allows tooltip display on click
|
|
45
|
-
*/
|
|
46
|
-
$(function () {
|
|
47
|
-
$('.sidebar [data-toggle="tooltip"]').tooltip({ placement: 'right' });
|
|
48
|
-
$('#copyURL').tooltip({ title: 'Copied!', trigger: 'click', placement: 'right', delay: 0 });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
44
|
//keep track of the current queryIndex so it doesn't get lost on page reload
|
|
52
45
|
const urlMatch = window.location.href.match(/#Query_(\d+)/);
|
|
53
46
|
if (urlMatch && urlMatch.length > 1) {
|
|
@@ -180,30 +173,30 @@ export default class extends Component {
|
|
|
180
173
|
return false;
|
|
181
174
|
}
|
|
182
175
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
176
|
/**
|
|
187
177
|
* Handles copying the URL into the user's clipboard. Modified from: https://stackoverflow.com/a/49618964/18117380
|
|
188
178
|
* Hides the 'Copied!' tooltip after 3 seconds
|
|
189
179
|
*/
|
|
190
180
|
|
|
191
181
|
copyURL() {
|
|
192
|
-
|
|
193
|
-
|
|
182
|
+
const element = document.createElement('input');
|
|
183
|
+
const url = window.location.href;
|
|
194
184
|
document.body.appendChild(element);
|
|
195
185
|
element.value = url;
|
|
196
186
|
element.select();
|
|
197
187
|
document.execCommand('copy');
|
|
198
188
|
document.body.removeChild(element);
|
|
199
189
|
|
|
200
|
-
|
|
201
|
-
|
|
190
|
+
const tooltip = document.getElementById('tooltip');
|
|
191
|
+
tooltip.classList.remove('hidden');
|
|
192
|
+
|
|
193
|
+
setTimeout(() => {
|
|
194
|
+
tooltip.classList.add('hidden');
|
|
202
195
|
}, 3000);
|
|
203
196
|
}
|
|
204
197
|
|
|
205
198
|
shareCloudInit() {
|
|
206
|
-
this.
|
|
199
|
+
this.cloudShareModal.current.show();
|
|
207
200
|
}
|
|
208
201
|
|
|
209
202
|
topPanelJSX() {
|
|
@@ -215,20 +208,20 @@ export default class extends Component {
|
|
|
215
208
|
var rootURL = path.join('/');
|
|
216
209
|
return (
|
|
217
210
|
<div className="sidebar-top-panel">
|
|
218
|
-
<div className="
|
|
219
|
-
<h4>
|
|
211
|
+
<div className="pl-px table mb-0 w-full">
|
|
212
|
+
<h4 className="text-sm font-bold mb-0 mt-0.5">
|
|
220
213
|
{this.summaryString()}
|
|
221
214
|
</h4>
|
|
222
215
|
</div>
|
|
223
216
|
{this.props.data.queries.length > 12 && this.queryIndexButtons()}
|
|
224
217
|
<div>
|
|
225
|
-
<a href={`${rootURL}/?job_id=${job_id}`}>
|
|
218
|
+
<a href={`${rootURL}/?job_id=${job_id}`} className="text-sm text-seqblue hover:text-seqorange cursor-pointer">
|
|
226
219
|
<i className="fa fa-pencil"></i> Edit search
|
|
227
220
|
</a>
|
|
228
|
-
<span className="
|
|
221
|
+
<span className="text-seqorange px-1">|</span>
|
|
229
222
|
<a href={`${rootURL}/`}
|
|
230
|
-
onClick={this.clearSession}>
|
|
231
|
-
<i className="fa fa-file
|
|
223
|
+
onClick={this.clearSession} className="text-sm text-seqblue hover:text-seqorange cursor-pointer">
|
|
224
|
+
<i className="fa-regular fa-file"></i> New search
|
|
232
225
|
</a>
|
|
233
226
|
</div>
|
|
234
227
|
{this.props.shouldShowIndex && this.indexJSX()}
|
|
@@ -252,7 +245,7 @@ export default class extends Component {
|
|
|
252
245
|
const buttonStyle = {
|
|
253
246
|
outline: 'none', border: 'none', background: 'none'
|
|
254
247
|
};
|
|
255
|
-
const buttonClasses = '
|
|
248
|
+
const buttonClasses = 'text-sm text-seqblue hover:text-seqorange hover:bg-gray-200';
|
|
256
249
|
|
|
257
250
|
const handlePreviousBtnClick = () => this.handleQueryIndexChange(this.state.queryIndex - 1);
|
|
258
251
|
const handleNextBtnClick = () => this.handleQueryIndexChange(this.state.queryIndex + 1);
|
|
@@ -263,15 +256,15 @@ export default class extends Component {
|
|
|
263
256
|
);
|
|
264
257
|
return <div style={{ display: 'flex', width: '100%', margin: '7px 0' }}>
|
|
265
258
|
{this.state.queryIndex > 1 && <NavButton text="Previous Query" onClick={handlePreviousBtnClick} />}
|
|
266
|
-
{this.state.queryIndex > 1 && this.state.queryIndex < this.props.data.queries.length && <span className="
|
|
259
|
+
{this.state.queryIndex > 1 && this.state.queryIndex < this.props.data.queries.length && <span className="text-seqorange px-1">|</span>}
|
|
267
260
|
{this.state.queryIndex < this.props.data.queries.length && <NavButton onClick={handleNextBtnClick} text="Next Query" />}
|
|
268
261
|
</div>;
|
|
269
262
|
}
|
|
270
263
|
indexJSX() {
|
|
271
|
-
return <ul className="
|
|
264
|
+
return <ul className="w-full"> {
|
|
272
265
|
_.map(this.props.data.queries, (query) => {
|
|
273
266
|
return <li key={'Side_bar_' + query.id}>
|
|
274
|
-
<a className="
|
|
267
|
+
<a className="side-nav text-sm text-seqblue hover:text-seqorange focus:text-seqorange active:text-seqorange cursor-pointer hover-bold line-clamp-1 mb-1.5"
|
|
275
268
|
title={'Query= ' + query.id + ' ' + query.title}
|
|
276
269
|
href={'#Query_' + query.number}>
|
|
277
270
|
{'Query= ' + query.id}
|
|
@@ -285,77 +278,113 @@ export default class extends Component {
|
|
|
285
278
|
downloadsPanelJSX() {
|
|
286
279
|
return (
|
|
287
280
|
<div className="downloads">
|
|
288
|
-
<div className="
|
|
289
|
-
<h4>
|
|
281
|
+
<div className="pl-px table mb-0 w-full">
|
|
282
|
+
<h4 className="text-sm font-bold mb-0 mt-2.5">
|
|
290
283
|
Download FASTA, XML, TSV
|
|
291
284
|
</h4>
|
|
292
285
|
</div>
|
|
293
|
-
<ul
|
|
286
|
+
<ul>
|
|
294
287
|
{
|
|
295
|
-
!(this.props.data.imported_xml || this.props.data.non_parse_seqids) && <li>
|
|
296
|
-
<a
|
|
297
|
-
|
|
298
|
-
|
|
288
|
+
!(this.props.data.imported_xml || this.props.data.non_parse_seqids) && <li className="hover:bg-gray-200 mb-1">
|
|
289
|
+
<a
|
|
290
|
+
href="#"
|
|
291
|
+
className={`text-sm text-seqblue download-fasta-of-all hover:text-seqorange cursor-pointer py-0.5 px-0.5 ${!this.props.atLeastOneHit && 'disabled'}`}
|
|
292
|
+
onClick={this.props.atLeastOneHit ? this.downloadFastaOfAll : (e) => e.preventDefault()}>
|
|
293
|
+
FASTA of all hits
|
|
299
294
|
</a>
|
|
300
295
|
</li>
|
|
301
296
|
}
|
|
302
297
|
{
|
|
303
|
-
!(this.props.data.imported_xml || this.props.data.non_parse_seqids) && <li>
|
|
304
|
-
<a
|
|
298
|
+
!(this.props.data.imported_xml || this.props.data.non_parse_seqids) && <li className="mb-1">
|
|
299
|
+
<a
|
|
300
|
+
href="#"
|
|
301
|
+
className="flex text-sm download-fasta-of-selected text-seqblue disabled py-0.5 px-0.5"
|
|
305
302
|
onClick={this.downloadFastaOfSelected}>
|
|
306
|
-
FASTA of <span className="
|
|
303
|
+
FASTA of <span className="font-bold px-0.5"></span> selected hit(s)
|
|
307
304
|
</a>
|
|
308
305
|
</li>
|
|
309
306
|
}
|
|
310
|
-
<li>
|
|
311
|
-
<a href="#" className={`
|
|
307
|
+
<li className="hover:bg-gray-200 mb-1">
|
|
308
|
+
<a href="#" className={`text-sm text-seqblue download-alignment-of-all hover:text-seqorange cursor-pointer py-0.5 px-0.5 ${!this.props.atLeastOneHit && 'disabled'}`}>
|
|
312
309
|
Alignment of all hits
|
|
313
310
|
</a>
|
|
314
311
|
</li>
|
|
315
|
-
<li>
|
|
316
|
-
<a href="#" className="
|
|
317
|
-
Alignment of <span className="
|
|
312
|
+
<li className="mb-1">
|
|
313
|
+
<a href="#" className="flex text-sm download-alignment-of-selected text-seqblue disabled py-0.5 px-0.5">
|
|
314
|
+
Alignment of <span className="font-bold px-0.5"></span> selected hit(s)
|
|
318
315
|
</a>
|
|
319
316
|
</li>
|
|
320
317
|
{
|
|
321
|
-
!this.props.data.imported_xml && <li>
|
|
322
|
-
<a
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
318
|
+
!this.props.data.imported_xml && <li className="hover:bg-gray-200 mb-1">
|
|
319
|
+
<a href={'download/' + this.props.data.search_id + '.std_tsv'}>
|
|
320
|
+
<div className="relative flex flex-col items-center group">
|
|
321
|
+
<div className="flex items-center w-full">
|
|
322
|
+
<span className="w-full text-sm text-seqblue hover:text-seqorange download cursor-pointer py-0.5 px-0.5">Standard tabular report</span>
|
|
323
|
+
<div className="absolute hidden left-full ml-2 items-center group-hover:flex tooltip-wrap">
|
|
324
|
+
<div className="w-0 h-0 border-y-8 border-r-8 border-t-transparent border-b-transparent border-r-black -mr-px"></div>
|
|
325
|
+
<span className="relative z-10 p-2 side-tooltip-text leading-4 text-center text-white whitespace-no-wrap bg-black shadow-lg rounded-md">
|
|
326
|
+
15 columns: query and subject ID; scientific
|
|
327
|
+
name, alignment length, mismatches, gaps, identity,
|
|
328
|
+
start and end coordinates, e value, bitscore, query
|
|
329
|
+
coverage per subject and per HSP.
|
|
330
|
+
</span>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
329
334
|
</a>
|
|
330
335
|
</li>
|
|
331
336
|
}
|
|
332
337
|
{
|
|
333
|
-
!this.props.data.imported_xml && <li>
|
|
334
|
-
<a
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
338
|
+
!this.props.data.imported_xml && <li className="hover:bg-gray-200 mb-1">
|
|
339
|
+
<a href={'download/' + this.props.data.search_id + '.full_tsv'}>
|
|
340
|
+
<div className="relative flex flex-col items-center group">
|
|
341
|
+
<div className="flex items-center w-full">
|
|
342
|
+
<span className="w-full text-sm text-seqblue hover:text-seqorange download cursor-pointer py-0.5 px-0.5">Full tabular report</span>
|
|
343
|
+
<div className="absolute hidden left-full ml-2 items-center group-hover:flex tooltip-wrap">
|
|
344
|
+
<div className="w-0 h-0 border-y-8 border-r-8 border-t-transparent border-b-transparent border-r-black -mr-px"></div>
|
|
345
|
+
<span className="relative z-10 p-2 side-tooltip-text leading-4 text-center text-white whitespace-no-wrap bg-black shadow-lg rounded-md">
|
|
346
|
+
44 columns: query and subject ID, GI,
|
|
347
|
+
accessions, and length; alignment details;
|
|
348
|
+
taxonomy details of subject sequence(s) and
|
|
349
|
+
query coverage per subject and per HSP.
|
|
350
|
+
</span>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
341
354
|
</a>
|
|
342
355
|
</li>
|
|
343
356
|
}
|
|
344
357
|
{
|
|
345
|
-
!this.props.data.imported_xml && <li>
|
|
346
|
-
<a
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
358
|
+
!this.props.data.imported_xml && <li className="hover:bg-gray-200 mb-1">
|
|
359
|
+
<a href={'download/' + this.props.data.search_id + '.xml'}>
|
|
360
|
+
<div className="relative flex flex-col items-center group">
|
|
361
|
+
<div className="flex items-center w-full">
|
|
362
|
+
<span className="w-full text-sm text-seqblue hover:text-seqorange download cursor-pointer py-0.5 px-0.5">Full XML report</span>
|
|
363
|
+
<div className="absolute hidden left-full ml-2 items-center group-hover:flex tooltip-wrap">
|
|
364
|
+
<div className="w-0 h-0 border-y-8 border-r-8 border-t-transparent border-b-transparent border-r-black -mr-px"></div>
|
|
365
|
+
<span className="relative z-10 p-2 side-tooltip-text leading-4 text-center text-white whitespace-no-wrap bg-black shadow-lg rounded-md">
|
|
366
|
+
Results in XML format.
|
|
367
|
+
</span>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
350
371
|
</a>
|
|
351
372
|
</li>
|
|
352
373
|
}
|
|
353
374
|
{
|
|
354
|
-
!this.props.data.imported_xml && <li>
|
|
355
|
-
<a
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
375
|
+
!this.props.data.imported_xml && <li className="hover:bg-gray-200 mb-1">
|
|
376
|
+
<a href={'download/' + this.props.data.search_id + '.pairwise'}>
|
|
377
|
+
<div className="relative flex flex-col items-center group">
|
|
378
|
+
<div className="flex items-center w-full">
|
|
379
|
+
<span className="w-full text-sm text-seqblue hover:text-seqorange download cursor-pointer py-0.5 px-0.5">Full Text report</span>
|
|
380
|
+
<div className="absolute hidden left-full ml-2 items-center group-hover:flex tooltip-wrap">
|
|
381
|
+
<div className="w-0 h-0 border-y-8 border-r-8 border-t-transparent border-b-transparent border-r-black -mr-px"></div>
|
|
382
|
+
<span className="relative z-10 p-2 side-tooltip-text leading-4 text-center text-white whitespace-no-wrap bg-black shadow-lg rounded-md">
|
|
383
|
+
Results in text format.
|
|
384
|
+
</span>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
359
388
|
</a>
|
|
360
389
|
</li>
|
|
361
390
|
}
|
|
@@ -368,42 +397,76 @@ export default class extends Component {
|
|
|
368
397
|
sharingPanelJSX() {
|
|
369
398
|
return (
|
|
370
399
|
<div className="sharing-panel">
|
|
371
|
-
<div className="
|
|
372
|
-
<h4>
|
|
400
|
+
<div className="pl-px table mb-0 w-full">
|
|
401
|
+
<h4 className="text-sm font-bold mb-0 mt-2.5">
|
|
373
402
|
Share results
|
|
374
403
|
</h4>
|
|
375
404
|
</div>
|
|
376
|
-
<ul
|
|
405
|
+
<ul>
|
|
377
406
|
{!this.props.cloudSharingEnabled &&
|
|
378
|
-
<li>
|
|
379
|
-
<a id="copyURL" className="
|
|
380
|
-
|
|
381
|
-
|
|
407
|
+
<li className="hover:text-seqorange hover:bg-gray-200">
|
|
408
|
+
<a id="copyURL" className="flex text-sm text-seqblue hover:text-seqorange copy-URL cursor-pointer py-0.5 px-0.5 w-full" onClick={this.copyURL}>
|
|
409
|
+
<div className="relative flex gap-2 items-center group w-full">
|
|
410
|
+
<i className="fa fa-copy"></i>
|
|
411
|
+
<div className="flex items-center">
|
|
412
|
+
<span className="w-full">Copy URL to clipboard</span>
|
|
413
|
+
<div id="tooltip" className="absolute hidden left-full ml-2 items-center">
|
|
414
|
+
<div className="flex items-center">
|
|
415
|
+
<div className="w-0 h-0 border-y-8 border-r-8 border-t-transparent border-b-transparent border-r-black -mr-px"></div>
|
|
416
|
+
<span className="relative z-10 p-2 side-tooltip-text leading-4 text-center text-white whitespace-no-wrap bg-black shadow-lg rounded-md">
|
|
417
|
+
Copied!
|
|
418
|
+
</span>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
382
423
|
</a>
|
|
383
424
|
</li>
|
|
384
425
|
}
|
|
385
426
|
{!this.props.cloudSharingEnabled &&
|
|
386
|
-
<li>
|
|
387
|
-
<a id="sendEmail" className="
|
|
388
|
-
|
|
427
|
+
<li className="hover:text-seqorange hover:bg-gray-200">
|
|
428
|
+
<a id="sendEmail" className="flex text-sm text-seqblue hover:text-seqorange email-URL cursor-pointer py-0.5 px-0.5 w-full"
|
|
429
|
+
href={asMailtoHref(this.props.data.querydb, this.props.data.program, this.props.data.queries.length, window.location.href)}
|
|
389
430
|
target="_blank" rel="noopener noreferrer">
|
|
390
|
-
<
|
|
431
|
+
<div className="relative flex gap-2 items-center group w-full">
|
|
432
|
+
<i className="fa fa-envelope"></i>
|
|
433
|
+
<div className="flex items-center w-full">
|
|
434
|
+
<span className="w-full">Send by email</span>
|
|
435
|
+
<div className="absolute hidden left-full ml-2 items-center group-hover:flex tooltip-wrap">
|
|
436
|
+
<div className="w-0 h-0 border-y-8 border-r-8 border-t-transparent border-b-transparent border-r-black -mr-px"></div>
|
|
437
|
+
<span className="relative z-10 p-2 side-tooltip-text leading-4 text-center text-white whitespace-no-wrap bg-black shadow-lg rounded-md">
|
|
438
|
+
Send by email
|
|
439
|
+
</span>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
391
443
|
</a>
|
|
392
444
|
</li>
|
|
393
445
|
}
|
|
394
446
|
{this.props.cloudSharingEnabled &&
|
|
395
|
-
<li>
|
|
396
|
-
<button className="
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
447
|
+
<li className="hover:text-seqorange hover:bg-gray-200">
|
|
448
|
+
<button className="flex text-sm text-seqblue hover:text-seqorange cloud-Post cursor-pointer py-0.5 px-0.5 w-full" onClick={this.shareCloudInit}>
|
|
449
|
+
<div className="relative flex gap-2 items-center group w-full">
|
|
450
|
+
<i className="fa fa-cloud"></i>
|
|
451
|
+
<div className="flex items-center">
|
|
452
|
+
<span className="w-full">Share to cloud</span>
|
|
453
|
+
<div className="absolute hidden left-full ml-2 items-center group-hover:flex tooltip-wrap">
|
|
454
|
+
<div className="w-0 h-0 border-y-8 border-r-8 border-t-transparent border-b-transparent border-r-black -mr-px"></div>
|
|
455
|
+
<span className="relative z-10 p-2 side-tooltip-text leading-4 text-center text-white whitespace-no-wrap bg-black shadow-lg rounded-md">
|
|
456
|
+
Results in pairwise format
|
|
457
|
+
Upload results to SequenceServer Cloud where it will become accessable
|
|
458
|
+
to everyone who has a link.
|
|
459
|
+
</span>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
400
463
|
</button>
|
|
401
464
|
</li>
|
|
402
465
|
}
|
|
403
466
|
</ul>
|
|
404
467
|
{
|
|
405
468
|
<CloudShareModal
|
|
406
|
-
ref=
|
|
469
|
+
ref={this.cloudShareModal}
|
|
407
470
|
querydb={this.props.data.querydb}
|
|
408
471
|
program={this.props.data.program}
|
|
409
472
|
queryLength={this.props.data.queries.length}
|
|
@@ -415,14 +478,14 @@ export default class extends Component {
|
|
|
415
478
|
|
|
416
479
|
render() {
|
|
417
480
|
return (
|
|
418
|
-
<div className="sidebar">
|
|
481
|
+
<div className="sidebar sticky top-0 print:hidden">
|
|
419
482
|
{this.topPanelJSX()}
|
|
420
483
|
{this.downloadsPanelJSX()}
|
|
421
484
|
{this.sharingPanelJSX()}
|
|
422
485
|
<div className="referral-panel">
|
|
423
|
-
<div className="
|
|
424
|
-
<h4>Recommend SequenceServer</h4>
|
|
425
|
-
<p><a href="https://sequenceserver.com/referral-program" target="_blank">Earn up to $
|
|
486
|
+
<div className="pl-px table mb-0 w-full text-sm">
|
|
487
|
+
<h4 className="font-bold mb-0 mt-2.5">Recommend SequenceServer</h4>
|
|
488
|
+
<p><a href="https://sequenceserver.com/referral-program" target="_blank" className="text-seqblue hover:text-seqorange">Earn up to $400 per signup</a></p>
|
|
426
489
|
</div>
|
|
427
490
|
</div>
|
|
428
491
|
</div>
|
data/public/js/svgExporter.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
*/
|
|
10
10
|
import * as Exporter from './exporter';
|
|
11
|
+
import * as d3 from 'd3';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Exports the given <svg> DOM node as a .svg file.
|
|
@@ -44,7 +45,11 @@ var export_as_png = function (svg, filename) {
|
|
|
44
45
|
Exporter.download_url(canvas.toDataURL('image/png'), filename);
|
|
45
46
|
};
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
var svgString = serialize_svg(svg);
|
|
49
|
+
var encodedSvg = encodeURIComponent(svgString).replace(/%([0-9A-F]{2})/gi, function(match, p1) {
|
|
50
|
+
return String.fromCharCode('0x' + p1);
|
|
51
|
+
});
|
|
52
|
+
img.src = 'data:image/svg+xml;base64,' + window.btoa(encodedSvg);
|
|
48
53
|
};
|
|
49
54
|
|
|
50
55
|
var serialize_svg = function(svg) {
|
|
@@ -61,8 +66,8 @@ var serialize_svg = function(svg) {
|
|
|
61
66
|
|
|
62
67
|
svg.removeAttribute('xmlns');
|
|
63
68
|
svg.removeAttribute('xlink');
|
|
64
|
-
svg.setAttributeNS(d3.
|
|
65
|
-
svg.setAttributeNS(d3.
|
|
69
|
+
svg.setAttributeNS(d3.namespaces.xmlns, 'xmlns', d3.namespaces.svg);
|
|
70
|
+
svg.setAttributeNS(d3.namespaces.xmlns, 'xmlns:xlink', d3.namespaces.xlink);
|
|
66
71
|
|
|
67
72
|
var source = (new XMLSerializer()).serializeToString(svg);
|
|
68
73
|
var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC ' +
|