source: SRUAggregator/trunk/src/main/resources/assets/js/main.jsx @ 6254

Last change on this file since 6254 was 6254, checked in by emanuel.dima@uni-tuebingen.de, 9 years ago
  1. beta-50: moved to https for the centres registry URL
File size: 19.0 KB
Line 
1/** @jsx React.DOM */
2(function() {
3"use strict";
4
5var VERSION = window.MyAggregator.VERSION = "v.2.0.0-beta-50";
6
7var URLROOT = window.MyAggregator.URLROOT =
8        window.location.pathname.substring(0, window.location.pathname.indexOf("/",2)) ||
9        "/Aggregator";
10
11var PT = React.PropTypes;
12
13var ErrorPane = window.MyReact.ErrorPane;
14var AggregatorPage = window.MyAggregator.AggregatorPage;
15
16/**
17The FCS Aggregator UI is based on reactjs.
18- index.html: describes the general page structure, with a push-down footer;
19  on that structure the Main and Footer components are plugged.
20- main.jsx: defines the simple top components (Main, HelpPage, AboutPage, StatisticsPage)
21- search.jsx: defines
22        - the Corpora store of collections
23        - the AggregatorPage component which deals with search and displays the search results
24- corpora.jsx: defines the CorpusView, rendered when the user views the available collections
25- components.jsx: various general usage React components
26
27The top-most component, Main, tracks of the window's location URL and, depending on the value,
28  renders various components inside its frame:
29        - AggregatorPage is the view corresponding to the normal search UI (search bar and all)
30          This is the most complex component.
31        - HelpPage renders the help page
32        - About renders the about page
33        - Statistics renders the stats page
34        - another URL, /Aggregator/embed, determines Main and AggregatorPage to render just the search bar.
35          The embedded view is supposed to work like a YouTube embedded clip.
36*/
37
38var Main = React.createClass({
39        componentWillMount: function() {
40                routeFromLocation.bind(this)();
41        },
42
43        getInitialState: function () {
44                return {
45                        navbarCollapse: false,
46                        navbarPageFn: this.renderAggregator,
47                        errorMessages: [],
48                };
49        },
50
51        error: function(errObj) {
52                var err = "";
53                if (typeof errObj === 'string' || errObj instanceof String) {
54                        err = errObj;
55                } else if (typeof errObj === 'object' && errObj.statusText) {
56                        console.log("ERROR: jqXHR = ", errObj);
57                        err = errObj.statusText;
58                } else {
59                        return;
60                }
61
62                var that = this;
63                var errs = this.state.errorMessages.slice();
64                errs.push(err);
65                this.setState({errorMessages: errs});
66
67                setTimeout(function() {
68                        var errs = that.state.errorMessages.slice();
69                        errs.shift();
70                        that.setState({errorMessages: errs});
71                }, 10000);
72        },
73
74        ajax: function(ajaxObject) {
75                var that = this;
76                if (!ajaxObject.error) {
77                        ajaxObject.error = function(jqXHR, textStatus, error) {
78                                if (jqXHR.readyState === 0) {
79                                        that.error("Network error, please check your internet connection");
80                                } else if (jqXHR.responseText) {
81                                        that.error(jqXHR.responseText + " ("+error+")");
82                                } else  {
83                                        that.error(error + " ("+textStatus+")");
84                                }
85                                console.log("ajax error, jqXHR: ", jqXHR);
86                        };
87                }
88                // console.log("ajax", ajaxObject);
89                jQuery.ajax(ajaxObject);
90        },
91
92        toggleCollapse: function() {
93                this.setState({navbarCollapse: !this.state.navbarCollapse});
94        },
95
96        renderAggregator: function() {
97                return <AggregatorPage ajax={this.ajax} error={this.error} />;
98        },
99
100        renderHelp: function() {
101                return <HelpPage />;
102        },
103
104        renderAbout: function() {
105                return <AboutPage/>;
106        },
107
108        renderStatistics: function() {
109                return <StatisticsPage ajax={this.ajax} />;
110        },
111
112        renderEmbedded: function() {
113                return <AggregatorPage ajax={this.ajax} error={this.error} embedded={true}/>;
114        },
115
116        getPageFns: function() {
117                return {
118                        '': this.renderAggregator,
119                        'help': this.renderHelp,
120                        'about': this.renderAbout,
121                        'stats': this.renderStatistics,
122                        'embed': this.renderEmbedded,
123                };
124        },
125
126        gotoPage: function(doPushHistory, pageFnName) {
127                var pageFn = this.getPageFns()[pageFnName];
128                if (this.state.navbarPageFn !== pageFn) {
129                        if (doPushHistory) {
130                                window.history.pushState({page:pageFnName}, '', URLROOT+"/"+pageFnName);
131                        }
132                        this.setState({navbarPageFn: pageFn});
133                        // console.log("new page: " + document.location + ", name: " + pageFnName);
134                }
135        },
136
137        toAggregator: function(doPushHistory) { this.gotoPage(doPushHistory, ''); },
138        toHelp: function(doPushHistory) { this.gotoPage(doPushHistory, 'help'); },
139        toAbout: function(doPushHistory) { this.gotoPage(doPushHistory, 'about'); },
140        toStatistics: function(doPushHistory) { this.gotoPage(doPushHistory, 'stats'); },
141        toEmbedded: function(doPushHistory) { this.gotoPage(doPushHistory, 'embed'); },
142
143        renderLogin: function() {
144                return false;
145                // return  <li className="unauthenticated">
146                //                      <a href="login" tabIndex="-1"><span className="glyphicon glyphicon-log-in"></span> LOGIN</a>
147                //              </li>;
148        },
149
150        renderCollapsible: function() {
151                var classname = "navbar-collapse collapse " + (this.state.navbarCollapse?"in":"");
152                return (
153                        <div className={classname}>
154                                <ul className="nav navbar-nav">
155                                        <li className={this.state.navbarPageFn === this.renderAggregator ? "active":""}>
156                                                <a className="link" tabIndex="-1" onClick={this.toAggregator.bind(this, true)}>Aggregator</a>
157                                        </li>
158                                        <li className={this.state.navbarPageFn === this.renderHelp ? "active":""}>
159                                                <a className="link" tabIndex="-1" onClick={this.toHelp.bind(this, true)}>Help</a>
160                                        </li>
161                                </ul>
162                                <ul className="nav navbar-nav navbar-right">
163                                        <li> <div id="clarinservices" style={{padding:4}}/> </li>
164                                        {this.renderLogin()}
165                                </ul>
166                        </div>
167                );
168        },
169
170        renderTop: function() {
171                if (this.state.navbarPageFn === this.renderEmbedded) {
172                        return false;
173                }
174                return  (
175                        <div>
176                                <div className="navbar navbar-default navbar-static-top" role="navigation">
177                                        <div className="container">
178                                                <div className="navbar-header">
179                                                        <button type="button" className="navbar-toggle" onClick={this.toggleCollapse}>
180                                                                <span className="sr-only">Toggle navigation</span>
181                                                                <span className="icon-bar"></span>
182                                                                <span className="icon-bar"></span>
183                                                                <span className="icon-bar"></span>
184                                                        </button>
185                                                        <a className="navbar-brand" href={URLROOT} tabIndex="-1">
186                                                                <img width="28px" height="28px" src="img/magglass1.png"/>
187                                                                <header className="inline"> Content Search </header>
188                                                        </a>
189                                                </div>
190                                                {this.renderCollapsible()}
191                                        </div>
192                                </div>
193
194                                <ErrorPane errorMessages={this.state.errorMessages} />
195
196                        </div>
197                );
198        },
199
200        render: function() {
201                return  (
202                        <div>
203                                <div> { this.renderTop() } </div>
204
205                                <div id="push">
206                                        <div className="container">
207                                                {this.state.navbarPageFn()}
208                                        </div>
209                                        <div className="top-gap" />
210                                </div>
211                        </div>
212                );
213        }
214});
215
216
217var StatisticsPage = React.createClass({
218        propTypes: {
219                ajax: PT.func.isRequired,
220        },
221
222        getInitialState: function () {
223                return {
224                        stats: {},
225                        activeTab: 0,
226                        // searchStats: {},
227                        // lastScanStats: {},
228                };
229        },
230
231        componentDidMount: function() {
232                this.refreshStats();
233        },
234
235        refreshStats: function() {
236                this.props.ajax({
237                        url: 'rest/statistics',
238                        success: function(json, textStatus, jqXHR) {
239                                this.setState({stats: json});
240                                // console.log("stats:", json);
241                        }.bind(this),
242                });
243        },
244
245        renderWaitTimeSecs: function(t) {
246                var hue = t * 4;
247                if (hue > 120) {
248                        hue = 120;
249                }
250                var a = hue/120;
251                hue = 120 - hue;
252                var shue = "hsla("+hue+",100%,80%,"+a+")";
253                return  <span className="badge" style={{backgroundColor:shue, color:"black"}}>
254                                        {t.toFixed(3)}s
255                                </span>;
256        },
257
258        renderCollections: function(colls) {
259                return  <div style={{marginLeft:40}}>
260                                        { colls.length === 0 ?
261                                                <div style={{color:"#a94442"}}>NO collections found</div>
262                                                :
263                                                <div>
264                                                        {colls.length} root collection(s):
265                                                        <ul className='list-unstyled' style={{marginLeft:40}}>
266                                                                { colls.map(function(name, i) { return <div key={i}>{name}</div>; }) }
267                                                        </ul>
268                                                </div>
269                                        }
270                                </div>;
271        },
272
273        renderDiagnostic: function(d) {
274                var classes = "inline alert alert-warning " + (d.diagnostic.uri === 'LEGACY' ? "legacy" : "");
275                return  <div key={d.diagnostic.uri}>
276                                        <div className={classes} >
277                                                <div>
278                                                        { d.counter <= 1 ? false :
279                                                                <div className="inline" style={{margin:"5px 5px 5px 5px"}}>
280                                                                        <span className="badge" style={{backgroundColor:'#ae7241'}}>x {d.counter}</span>
281                                                                </div>
282                                                        }
283                                                        Diagnostic: {d.diagnostic.message}: {d.diagnostic.diagnostic}
284                                                </div>
285                                                <div>Context: <a href={d.context}>{d.context}</a></div>
286                                        </div>
287                                </div>;
288        },
289
290        renderError: function(e) {
291                var xc = e.exception;
292                return  <div key={xc.message}>
293                                        <div className="inline alert alert-danger" role="alert">
294                                                <div>
295                                                        { e.counter <= 1 ? false :
296                                                                <div className="inline" style={{margin:"5px 5px 5px 5px"}}>
297                                                                        <span className="badge" style={{backgroundColor:'#c94442'}}>x {e.counter} </span>
298                                                                </div>
299                                                        }
300                                                        Exception: {xc.message}
301                                                </div>
302                                                <div>Context: <a href={e.context}>{e.context}</a></div>
303                                                { xc.cause ? <div>Caused by: {xc.cause}</div> : false}
304                                        </div>
305                                </div>;
306        },
307
308        renderEndpoint: function(isScan, endpoint) {
309                var stat = endpoint[1];
310                var errors = _.values(stat.errors);
311                var diagnostics = _.values(stat.diagnostics);
312                return <div style={{marginTop:10}} key={endpoint[0]}>
313                                        <ul className='list-inline list-unstyled' style={{marginBottom:0}}>
314                                                <li>
315                                                        { stat.version == "LEGACY" ?
316                                                                <span style={{color:'#a94442'}}>legacy <i className="glyphicon glyphicon-thumbs-down"></i> </span>
317                                                                : <span style={{color:'#3c763d'}}><i className="glyphicon glyphicon-thumbs-up"></i> </span>
318                                                        }
319                                                        { " "+endpoint[0] }
320                                                </li>
321                                        </ul>
322                                        <div style={{marginLeft:40}}>
323                                        { isScan ?
324                                                <div>Max concurrent scan requests:{" "} {stat.maxConcurrentRequests} </div> :
325                                                <div>Max concurrent search requests:{" "} {stat.maxConcurrentRequests} </div>
326                                        }
327                                        </div>
328                                        <div style={{marginLeft:40}}>
329                                                <span>{stat.numberOfRequests}</span> request(s),
330                                                average:{this.renderWaitTimeSecs(stat.avgExecutionTime)},
331                                                max: {this.renderWaitTimeSecs(stat.maxExecutionTime)}
332                                        </div>
333                                        { isScan ? this.renderCollections(stat.rootCollections) : false }
334                                        {       (errors && errors.length) ?
335                                                <div className='inline' style={{marginLeft:40}}>
336                                                        { errors.map(this.renderError) }
337                                                </div> : false
338                                        }
339                                        {       (diagnostics && diagnostics.length) ?
340                                                <div className='inline' style={{marginLeft:40}}>
341                                                        { diagnostics.map(this.renderDiagnostic) }
342                                                </div> : false
343                                        }
344                                </div>;
345        },
346
347        renderInstitution: function(isScan, inst) {
348                return  <div style={{marginTop:30}} key={inst[0]}>
349                                        <h4>{inst[0]}</h4>
350                                        <div style={{marginLeft:20}}> {_.pairs(inst[1]).map(this.renderEndpoint.bind(this, isScan)) }</div>
351                                </div>;
352        },
353
354        renderStatistics: function(stats) {
355                return  <div className="container statistics" style={{marginTop:20}}>
356                                        <div>
357                                                <div>Start date: {new Date(stats.date).toLocaleString()}</div>
358                                                <div>Timeout: {" "}<kbd>{stats.timeout} seconds</kbd></div>
359                                        </div>
360                                        <div> { _.pairs(stats.institutions).map(this.renderInstitution.bind(this, stats.isScan)) } </div>
361                                </div>
362                                 ;
363        },
364
365        setTab: function(idx) {
366                this.setState({activeTab:idx});
367        },
368
369        render: function() {
370                return  (
371                        <div>
372                                <div className="top-gap">
373                                        <h1>Statistics</h1>
374                                        <p/>
375                                        <div role="tabpanel">
376                                                <ul className="nav nav-tabs" role="tablist">
377                                                        { _.pairs(this.state.stats).map(function(st, idx){
378                                                                        var classname = idx === this.state.activeTab ? "active":"";
379                                                                        return  <li role="presentation" className={classname} key={st[0]}>
380                                                                                                <a href="#" role="tab" onClick={this.setTab.bind(this, idx)}>{st[0]}</a>
381                                                                                        </li>;
382                                                                }.bind(this))
383                                                        }
384                                                </ul>
385
386                                                <div className="tab-content">
387                                                        { _.pairs(this.state.stats).map(function(st, idx){
388                                                                        var classname = idx === this.state.activeTab ? "tab-pane active" : "tab-pane";
389                                                                        return  <div role="tabpanel" className={classname} key={st[0]}>
390                                                                                                {this.renderStatistics(st[1])}
391                                                                                        </div>;
392                                                                }.bind(this))
393                                                        }
394                                                </div>
395                                        </div>
396                                </div>
397                        </div>
398                        );
399        },
400});
401
402var HelpPage = React.createClass({
403        openHelpDesk: function() {
404                window.open('http://support.clarin-d.de/mail/form.php?queue=Aggregator&lang=en',
405                        '_blank', 'height=560,width=370');
406        },
407
408        render: function() {
409                return  (
410                        <div>
411                                <div className="top-gap">
412                                        <h1>Help</h1>
413                                        <h3>Performing search in FCS corpora</h3>
414                                        <p>To perform simple keyword search in all CLARIN-D Federated Content Search centres
415                                        and their corpora, go to the search field at the top of the page,
416                                        enter your query, and click 'search' button or press the 'Enter' key.</p>
417
418                                        <p>When the search starts, the page will start filling in with the corpora responses.
419                                        After the entire search process has ended you have the option to download the results
420                                        in various formats.
421                                        </p>
422
423                                        <p>If you are particularly interested in the results returned by a corpus, you have
424                                        the option to focus only on the results of that corpus, by clicking on the 'Watch' button.
425                                        In this view mode you can also download the results of use the WebLicht processing services
426                                        to further analyse the results.</p>
427
428
429                                        <h3>Adjusting search criteria</h3>
430                                        <p>The FCS Aggregator makes possible to select specific corpora based on their name
431                                        or language and to specify the number of search results (hits) per corpus per page.
432                                        The user interface controls that allows to change these options are located
433                                        right below the search fiels on the main page. The current options are
434                                        to filter resources based on their language, to select specific resources, and
435                                        to set the maximum number of hits.</p>
436
437
438                                        <h3>More help</h3>
439                                        <p>More detailed information on using FCS Aggregator is available at the &nbsp;
440                                        <a href="http://weblicht.sfs.uni-tuebingen.de/weblichtwiki/index.php/FCS_Aggregator">
441                                                Aggregator wiki page
442                                        </a>.
443                                        If you still cannot find an answer to your question,
444                                        or if want to send a feedback, you can write to Clarin-D helpdesk: </p>
445                                        <button type="button" className="btn btn-default btn-lg" onClick={this.openHelpDesk} >
446                                                <span className="glyphicon glyphicon-question-sign" aria-hidden="true"></span>
447                                                &nbsp;HelpDesk
448                                        </button>
449                                </div>
450                        </div>
451                );
452        }
453});
454
455var AboutPage = React.createClass({
456        render: function() {
457                return  <div>
458                                        <div className="top-gap">
459                                                <h1 style={{padding:15}}>About</h1>
460
461                                                <div className="col-md-6">
462                                                <h3>People</h3>
463
464                                                <ul>
465                                                        <li>Emanuel Dima</li>
466                                                        <li>Yana Panchenko</li>
467                                                        <li>Oliver Schonefeld</li>
468                                                        <li>Dieter Van Uytvanck</li>
469                                                </ul>
470
471                                                <h3>Statistics</h3>
472                                                <button type="button" className="btn btn-default btn-lg" onClick={function() {main.toStatistics(true);}} >
473                                                        <span className="glyphicon glyphicon-cog" aria-hidden="true"> </span>
474                                                        View server log
475                                                </button>
476                                                </div>
477
478                                                <div className="col-md-6">
479                                                <h3>Technology</h3>
480
481                                                <p>The Aggregator uses the following software components:</p>
482
483                                                <ul>
484                                                        <li>
485                                                                <a href="http://dropwizard.io/">Dropwizard</a>{" "}
486                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
487                                                        </li>
488                                                        <li>
489                                                                <a href="http://eclipse.org/jetty/">Jetty</a>{" "}
490                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
491                                                        </li>
492                                                        <li>
493                                                                <a href="http://jackson.codehaus.org/">Jackson</a>{" "}
494                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
495                                                        </li>
496                                                        <li>
497                                                                <a href="https://jersey.java.net/">Jersey</a>{" "}
498                                                                (<a href="https://jersey.java.net/license.html#/cddl">CCDL 1.1</a>)
499                                                        </li>
500                                                        <li>
501                                                                <a href="https://github.com/optimaize/language-detector">Optimaize Language Detector</a>{" "}
502                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
503                                                        </li>
504                                                        <li>
505                                                                <a href="http://poi.apache.org/">Apache POI</a>{" "}
506                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
507                                                        </li>
508                                                </ul>
509
510                                                <ul>
511                                                        <li>
512                                                                <a href="http://facebook.github.io/react/">React</a>{" "}
513                                                                (<a href="https://github.com/facebook/react/blob/master/LICENSE">BSD license</a>)
514                                                        </li>
515                                                        <li>
516                                                                <a href="http://getbootstrap.com/">Bootstrap</a>{" "}
517                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT license</a>)
518                                                        </li>
519                                                        <li>
520                                                                <a href="http://jquery.com/">jQuery</a>{" "}
521                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT license</a>)
522                                                        </li>
523                                                        <li>
524                                                                <a href="http://glyphicons.com/">GLYPHICONS free</a>{" "}
525                                                                (<a href="https://creativecommons.org/licenses/by/3.0/">CC-BY 3.0</a>)
526                                                        </li>
527                                                        <li>
528                                                                <a href="http://fortawesome.github.io/Font-Awesome/">FontAwesome</a>{" "}
529                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT</a>, <a href="http://scripts.sil.org/OFL">SIL Open Font License</a>)
530                                                        </li>
531                                                </ul>
532
533                                                <p>The content search icon is made by
534                                                        <a href="http://www.freepik.com" title="Freepik"> Freepik </a>
535                                                        from
536                                                        <a href="http://www.flaticon.com" title="Flaticon"> www.flaticon.com </a>
537                                                        and licensed under
538                                                        <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0"> CC BY 3.0 </a>
539                                                </p>
540                                                </div>
541
542                                        </div>
543                                </div>;
544        }
545});
546
547var Footer = React.createClass({
548        toAbout: function(e) {
549                main.toAbout(true);
550                e.preventDefault();
551                e.stopPropagation();
552        },
553
554        render: function() {
555                return (
556                        <div className="container" style={{textAlign:'center'}}>
557                                <div className="row">
558                                        <div style={{position:'relative', float:'left'}}>
559                                                <div className="leftist" style={{position:'absolute'}}>
560                                                        <div>
561                                                                <a title="about" href="about" onClick={this.toAbout}>About</a>
562                                                        </div>
563                                                        <div style={{color:'#777'}}>{VERSION}</div>
564                                                </div>
565                                        </div>
566                                        <a title="CLARIN ERIC" href="https://www.clarin.eu/">
567                                                <img src="img/clarindLogo.png" alt="CLARIN ERIC logo" style={{height:60}}/>
568                                        </a>
569                                        <div style={{position:'relative', float:'right'}}>
570                                                <div className="rightist" style={{position:'absolute', right:'0'}}>
571                                                        <a title="contact" href="mailto:fcs@clarin.eu">Contact</a>
572                                                </div>
573                                        </div>
574                                </div>
575                        </div>
576                );
577        }
578});
579
580function isEmbeddedView() {
581        var path = window.location.pathname.split('/');
582        return (path.length >= 3 && path[2] === 'embed');
583}
584
585function endsWith(str, suffix) {
586    return str.indexOf(suffix, str.length - suffix.length) !== -1;
587}
588
589var routeFromLocation = function() {
590        // console.log("routeFromLocation: " + document.location);
591        if (!this) throw "routeFromLocation must be bound to main";
592        var path = window.location.pathname.split('/');
593        if (path.length === 3) {
594                var p = path[2];
595                if (p === 'help') {
596                        this.toHelp(false);
597                } else if (p === 'about') {
598                        this.toAbout(false);
599                } else if (p === 'stats') {
600                        this.toStatistics(false);
601                } else if (p === 'embed') {
602                        this.toEmbedded(false);
603                } else {
604                        this.toAggregator(false);
605                }
606        } else {
607                this.toAggregator(false);
608        }
609};
610
611var main = React.render(<Main />,  document.getElementById('body'));
612if (!isEmbeddedView()) {
613        React.render(<Footer />, document.getElementById('footer') );
614} else if (jQuery) {
615        jQuery("#footer").remove();
616}
617
618window.onpopstate = routeFromLocation.bind(main);
619window.MyAggregator.main = main;
620
621})();
Note: See TracBrowser for help on using the repository browser.