sequenceserver 3.1.2 → 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/blast/tasks.rb +1 -1
- data/lib/sequenceserver/routes.rb +1 -1
- 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"
         |