source: SRUAggregator/trunk/src/main/resources/assets/js/search.js @ 6093

Last change on this file since 6093 was 6093, checked in by emanuel.dima@uni-tuebingen.de, 9 years ago
  1. beta-28: disabling sampa for now
File size: 31.8 KB
Line 
1/** @jsx React.DOM */
2(function() {
3"use strict";
4
5var NO_MORE_RECORDS_DIAGNOSTIC_URI = "info:srw/diagnostic/1/61";
6
7window.MyAggregator = window.MyAggregator || {};
8
9var React = window.React;
10var PT = React.PropTypes;
11var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
12
13var CorpusSelection = window.MyAggregator.CorpusSelection;
14var HitNumber = window.MyAggregator.HitNumber;
15var CorpusView = window.MyAggregator.CorpusView;
16var Popover = window.MyReact.Popover;
17var InfoPopover = window.MyReact.InfoPopover;
18var Panel = window.MyReact.Panel;
19var ModalMixin = window.MyReact.ModalMixin;
20var Modal = window.MyReact.Modal;
21
22var multipleLanguageCode = "mul"; // see ISO-693-3
23
24var layers = [
25        {
26                id: "text",
27                name: "Text Resources",
28                searchPlaceholder: "Elephant",
29                searchLabel: "Search text",
30                searchLabelBkColor: "#fed",
31                className: '',
32        },
33        {
34                id: "sampa",
35                name: "Phonetic Transcriptions",
36                searchPlaceholder: "stA:z",
37                searchLabel: "SAMPA query",
38                searchLabelBkColor: "#eef",
39                disabled: true,
40        },
41];
42var layerMap = {
43        text: layers[0],
44        sampa: layers[1], 
45};
46
47function Corpora(corpora, updateFn) {
48        var that = this;
49        this.corpora = corpora;
50        this.update = function() { 
51                updateFn(that); 
52        };
53       
54        var sortFn = function(x, y) {
55                var r = x.institution.name.localeCompare(y.institution.name);
56                if (r !== 0) {
57                        return r;
58                }
59                return x.title.toLowerCase().localeCompare(y.title.toLowerCase()); 
60        };
61
62        this.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); });
63        this.corpora.sort(sortFn);
64
65        this.recurse(function(corpus, index) {
66                corpus.visible = true; // visible in the corpus view
67                corpus.selected = true; // selected in the corpus view
68                corpus.expanded = false; // not expanded in the corpus view
69                corpus.priority = 1; // used for ordering search results in corpus view
70                corpus.index = index; // original order, used for stable sort
71        });
72}
73
74Corpora.prototype.recurseCorpus = function(corpus, fn) {
75        if (false === fn(corpus)) {             
76                // no recursion
77        } else {
78                this.recurseCorpora(corpus.subCorpora, fn);
79        }
80};
81
82Corpora.prototype.recurseCorpora = function(corpora, fn) {
83        var recfn = function(corpus, index){
84                if (false === fn(corpus, index)) {
85                        // no recursion
86                } else {
87                        corpus.subCorpora.forEach(recfn);
88                }
89        };
90        corpora.forEach(recfn);
91};
92
93Corpora.prototype.recurse = function(fn) {
94        this.recurseCorpora(this.corpora, fn);
95};
96
97Corpora.prototype.getLanguageCodes = function() {
98        var languages = {};
99        this.recurse(function(corpus) {
100                corpus.languages.forEach(function(lang) {
101                        languages[lang] = true;
102                });
103                return true;
104        });
105        return languages;
106};
107
108Corpora.prototype.isCorpusVisible = function(corpus, layerId, languageCode) {
109        if (layerId !== "text") {
110                return false;
111        }
112        // yes for any language
113        if (languageCode === multipleLanguageCode) {
114                return true;
115        }
116        // yes if the corpus is in only that language
117        if (corpus.languages && corpus.languages.length === 1 && corpus.languages[0] === languageCode) {
118                return true;
119        }       
120
121        // ? yes if the corpus also contains that language
122        if (corpus.languages && corpus.languages.indexOf(languageCode) >=0) {
123                return true;
124        }
125
126        // ? yes if the corpus has no language
127        // if (!corpus.languages || corpus.languages.length === 0) {
128        //      return true;
129        // }
130        return false;
131};
132
133Corpora.prototype.setVisibility = function(layerId, languageCode) {
134        // top level
135        this.corpora.forEach(function(corpus) {
136                corpus.visible = this.isCorpusVisible(corpus, layerId, languageCode);
137                this.recurseCorpora(corpus.subCorpora, function(c) { c.visible = corpus.visible; });
138        }.bind(this));
139};
140
141Corpora.prototype.getSelectedIds = function() {
142        var ids = [];
143        this.recurse(function(corpus) {
144                if (corpus.visible && corpus.selected) {
145                        ids.push(corpus.id);
146                        return false; // top-most collection in tree, don't delve deeper
147                }
148                return true;
149        });
150
151        // console.log("ids: ", ids.length, {ids:ids});
152        return ids;
153};
154
155Corpora.prototype.getSelectedMessage = function() {
156        var selected = this.getSelectedIds().length;
157        if (this.corpora.length === selected) {
158                return "All available collections";
159        } else if (selected === 1) {
160                return "1 selected collection";
161        }
162        return selected+" selected collections";
163};
164
165function encodeQueryData(data)
166{
167        var ret = [];
168        for (var d in data) {
169                ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
170        }
171        return ret.join("&");
172}
173
174
175var AggregatorPage = window.MyAggregator.AggregatorPage = React.createClass({displayName: 'AggregatorPage',
176        propTypes: {
177                ajax: PT.func.isRequired
178        },
179
180        nohits: { 
181                results: null,
182        },
183        anyLanguage: [multipleLanguageCode, "Any Language"],
184
185        getInitialState: function () {
186                return {
187                        corpora: new Corpora([], this.updateCorpora),
188                        languageMap: {},
189                        weblichtLanguages: [],
190                        query: "",
191                        language: this.anyLanguage,
192                        languageFilter: 'byMeta',
193                        searchLayerId: "text",
194                        numberOfResults: 10,
195
196                        searchId: null,
197                        timeout: 0,
198                        hits: this.nohits,
199
200                        zoomedCorpusHit: null,
201                };
202        },
203
204        componentDidMount: function() {
205                this.props.ajax({
206                        url: 'rest/init',
207                        success: function(json, textStatus, jqXHR) {
208                                if (this.isMounted()) {
209                                        this.setState({
210                                                corpora : new Corpora(json.corpora, this.updateCorpora),
211                                                languageMap: json.languages,
212                                                weblichtLanguages: json.weblichtLanguages,
213                                        });
214                                }
215                        }.bind(this),
216                });
217        },
218
219        updateCorpora: function(corpora) {
220                this.setState({corpora:corpora});
221        },
222
223        search: function() {
224                var query = this.state.query;
225                if (!query) {
226                        this.setState({ hits: this.nohits, searchId: null });
227                        return;                 
228                }
229                var selectedIds = this.state.corpora.getSelectedIds();
230                // console.log("searching in the following corpora:", selectedIds);
231                this.props.ajax({
232                        url: 'rest/search',
233                        type: "POST",
234                        data: {
235                                layer: this.state.searchLayerId,
236                                language: this.state.language[0],
237                                query: query,
238                                numberOfResults: this.state.numberOfResults,
239                                corporaIds: selectedIds,
240                        },
241                        success: function(searchId, textStatus, jqXHR) {
242                                // console.log("search ["+query+"] ok: ", searchId, jqXHR);
243                                var timeout = 250;
244                                setTimeout(this.refreshSearchResults, timeout);
245                                this.setState({ searchId: searchId, timeout: timeout });
246                        }.bind(this),
247                });
248        },
249        nextResults: function(corpusId) {
250                // console.log("searching next results in corpus:", corpusId);
251                this.props.ajax({
252                        url: 'rest/search/'+this.state.searchId,
253                        type: "POST",
254                        data: {
255                                corpusId: corpusId,
256                                numberOfResults: this.state.numberOfResults,
257                        },
258                        success: function(searchId, textStatus, jqXHR) {
259                                // console.log("search ["+query+"] ok: ", searchId, jqXHR);
260                                var timeout = 250;
261                                setTimeout(this.refreshSearchResults, timeout);
262                                this.setState({ searchId: searchId, timeout: timeout });
263                        }.bind(this),
264                });
265        },
266
267        refreshSearchResults: function() {
268                if (!this.state.searchId || !this.isMounted()) {
269                        return;
270                }
271                this.props.ajax({
272                        url: 'rest/search/'+this.state.searchId,
273                        success: function(json, textStatus, jqXHR) {
274                                var timeout = this.state.timeout;
275                                if (json.inProgress) {
276                                        if (timeout < 10000) {
277                                                timeout = 1.5 * timeout;
278                                        }
279                                        setTimeout(this.refreshSearchResults, timeout);
280                                        // console.log("new search in: " + this.timeout + "ms");
281                                } else {
282                                        console.log("search ended; hits:", json);
283                                }
284                                var corpusHit = this.state.zoomedCorpusHit;
285                                if (corpusHit) {
286                                        for (var resi = 0; resi < json.results.length; resi++) {
287                                                var res = json.results[resi];
288                                                if (res.corpus.id === corpusHit.corpus.id) {
289                                                        corpusHit = res;
290                                                        break;
291                                                }
292                                        }
293                                }
294                                this.setState({ hits: json, timeout: timeout, zoomedCorpusHit: corpusHit});
295                        }.bind(this),
296                });
297        },
298
299        getExportParams: function(corpusId, format) {
300                var params = corpusId ? {corpusId:corpusId}:{};
301                if (format) params.format = format;
302                if (this.state.languageFilter === 'byGuess' || this.state.languageFilter === 'byMetaAndGuess') {
303                        params.filterLanguage = this.state.language[0];
304                }
305                return encodeQueryData(params);
306        },
307
308        getDownloadLink: function(corpusId, format) {
309                return 'rest/search/'+this.state.searchId+'/download?' + this.getExportParams(corpusId, format);
310        },
311
312        getToWeblichtLink: function(corpusId) {
313                var params = corpusId ? {corpusId:corpusId}:{};
314                return 'rest/search/'+this.state.searchId+'/toWeblicht?' + this.getExportParams(corpusId);
315        },
316
317        setLanguageAndFilter: function(languageObj, languageFilter) {
318                this.state.corpora.setVisibility(this.state.searchLayerId, 
319                        languageFilter === 'byGuess' ? multipleLanguageCode : languageObj[0]);
320                this.setState({
321                        language: languageObj, 
322                        languageFilter: languageFilter, 
323                        corpora: this.state.corpora, // === this.state.corpora.update();
324                });
325        },
326
327        setLayer: function(layerId) {
328                this.state.corpora.setVisibility(layerId, this.state.language[0]);
329                this.setState({
330                        searchLayerId: layerId, 
331                        hits: this.nohits, 
332                        searchId: null,
333                        corpora: this.state.corpora, // === this.state.corpora.update();
334                });
335        },
336
337        setNumberOfResults: function(e) {
338                var n = e.target.value;
339                if (n < 10) n = 10;
340                if (n > 250) n = 250;
341                this.setState({numberOfResults: n});
342                e.preventDefault();
343                e.stopPropagation();
344        },
345
346        stop: function(e) {
347                e.stopPropagation();
348        },
349
350        filterResults: function() {
351                var noLangFiltering = this.state.languageFilter === 'byMeta';
352                var langCode = this.state.language[0];
353                var results = null, inProgress = 0, hits = 0;
354                if (this.state.hits.results) {
355                        results = this.state.hits.results.map(function(corpusHit) { 
356                                return {
357                                        corpus: corpusHit.corpus,
358                                        inProgress: corpusHit.inProgress,
359                                        exception: corpusHit.exception,
360                                        diagnostics: corpusHit.diagnostics,
361                                        kwics: noLangFiltering ? corpusHit.kwics :
362                                                corpusHit.kwics.filter(function(kwic) {
363                                                        return kwic.language === langCode || 
364                                                               langCode === multipleLanguageCode || 
365                                                               langCode === null; 
366                                                }),
367                                };
368                        });
369                        for (var i = 0; i < results.length; i++) {
370                                var result = results[i];
371                                if (result.inProgress) {
372                                        inProgress++;
373                                }
374                                if (result.kwics.length > 0) {
375                                        hits ++;
376                                }
377                        }
378                }
379                return {
380                        results: results,
381                        hits: hits,
382                        inProgress: inProgress,
383                };
384        },
385
386        toggleLanguageSelection: function(e) {
387                $(this.refs.languageModal.getDOMNode()).modal();
388                e.preventDefault();
389                e.stopPropagation();
390        },
391
392        toggleCorpusSelection: function(e) {
393                $(this.refs.corporaModal.getDOMNode()).modal();
394                e.preventDefault();
395                e.stopPropagation();
396        },
397
398        toggleResultModal: function(e, corpusHit) {
399                $(this.refs.resultModal.getDOMNode()).modal();
400                this.setState({zoomedCorpusHit: corpusHit});
401                e.preventDefault();
402                e.stopPropagation();
403        },
404
405        onQuery: function(event) {
406                this.setState({query: event.target.value});
407        },
408
409        handleKey: function(event) {
410                if (event.keyCode==13) {
411                        this.search();
412                }
413        },
414
415        renderZoomedResultTitle: function(corpusHit) {
416                if (!corpusHit) return React.createElement("span", null);
417                var corpus = corpusHit.corpus;
418                return React.createElement("h3", {style: {fontSize:'1em'}}, 
419                                        corpus.title, 
420                                         corpus.landingPage ? 
421                                                React.createElement("a", {href: corpus.landingPage, onClick: this.stop, style: {fontSize:12}}, 
422                                                        React.createElement("span", null, " – Homepage "), 
423                                                        React.createElement("i", {className: "glyphicon glyphicon-home"})
424                                                ): false
425                                );
426        },
427
428        render: function() {
429                var layer = layerMap[this.state.searchLayerId];
430                return  (
431                        React.createElement("div", {className: "top-gap"}, 
432                                React.createElement("div", {className: "row"}, 
433                                        React.createElement("div", {className: "aligncenter", style: {marginLeft:16, marginRight:16}}, 
434                                                React.createElement("div", {className: "input-group"}, 
435                                                        React.createElement("span", {className: "input-group-addon", style: {backgroundColor:layer.searchLabelBkColor}}, 
436                                                                layer.searchLabel
437                                                        ), 
438
439                                                        React.createElement("input", {className: "form-control input-lg search", name: "query", type: "text", 
440                                                                value: this.state.query, placeholder: this.props.placeholder, 
441                                                                tabIndex: "1", onChange: this.onQuery, onKeyDown: this.handleKey}), 
442                                                        React.createElement("div", {className: "input-group-btn"}, 
443                                                                React.createElement("button", {className: "btn btn-default input-lg", type: "button", onClick: this.search}, 
444                                                                        React.createElement("i", {className: "glyphicon glyphicon-search"})
445                                                                )
446                                                        )
447                                                )
448                                        )
449                                ), 
450
451                                React.createElement("div", {className: "wel", style: {marginTop:20}}, 
452                                        React.createElement("div", {className: "aligncenter"}, 
453                                                React.createElement("form", {className: "form-inline", role: "form"}, 
454
455                                                        React.createElement("div", {className: "input-group"}, 
456                                                               
457                                                                React.createElement("span", {className: "input-group-addon nobkg"}, "Search for"), 
458                                                               
459                                                                React.createElement("div", {className: "input-group-btn"}, 
460                                                                        React.createElement("button", {className: "form-control btn btn-default", 
461                                                                                        onClick: this.toggleLanguageSelection}, 
462                                                                                this.state.language[1], " ", React.createElement("span", {className: "caret"})
463                                                                        ), 
464                                                                        React.createElement("span", null)
465                                                                ), 
466
467                                                                React.createElement("div", {className: "input-group-btn"}, 
468                                                                        React.createElement("ul", {ref: "layerDropdownMenu", className: "dropdown-menu"}, 
469                                                                                        layers.map(function(l) { 
470                                                                                                var cls = l.disabled ? 'disabled':'';
471                                                                                                var handler = function() { if (!l.disabled) this.setLayer(l.id); };
472                                                                                                return React.createElement("li", {key: l.id, className: cls}, " ", React.createElement("a", {tabIndex: "-1", href: "#", 
473                                                                                                        onClick: handler}, " ", l.name, " "));
474                                                                                        }.bind(this))
475                                                                               
476                                                                        ),                                                             
477                                                                        React.createElement("button", {className: "form-control btn btn-default", 
478                                                                                        'aria-expanded': "false", 'data-toggle': "dropdown"}, 
479                                                                                layer.name, " ", React.createElement("span", {className: "caret"})
480                                                                        )
481                                                                )
482
483                                                        ), 
484
485                                                        React.createElement("div", {className: "input-group"}, 
486                                                                React.createElement("span", {className: "input-group-addon nobkg"}, "in"), 
487                                                                React.createElement("button", {type: "button", className: "btn btn-default", onClick: this.toggleCorpusSelection}, 
488                                                                        this.state.corpora.getSelectedMessage(), " ", React.createElement("span", {className: "caret"})
489                                                                )
490                                                        ),                                                     
491
492                                                        React.createElement("div", {className: "input-group"}, 
493                                                                React.createElement("span", {className: "input-group-addon nobkg"}, "and show up to"), 
494                                                                React.createElement("div", {className: "input-group-btn"}, 
495                                                                        React.createElement("input", {type: "number", className: "form-control input", min: "10", max: "250", 
496                                                                                style: {width:60}, 
497                                                                                onChange: this.setNumberOfResults, value: this.state.numberOfResults, 
498                                                                                onKeyPress: this.stop})
499                                                                ), 
500                                                                React.createElement("span", {className: "input-group-addon nobkg"}, "hits")
501                                                        )
502                                                )
503                                        )
504                                ), 
505
506                                React.createElement(Modal, {ref: "corporaModal", title: React.createElement("span", null, "Collections")}, 
507                                        React.createElement(CorpusView, {corpora: this.state.corpora, languageMap: this.state.languageMap})
508                                ), 
509
510                                React.createElement(Modal, {ref: "languageModal", title: React.createElement("span", null, "Select Language")}, 
511                                        React.createElement(LanguageSelector, {anyLanguage: this.anyLanguage, 
512                                                                          languageMap: this.state.languageMap, 
513                                                                          selectedLanguage: this.state.language, 
514                                                                          languageFilter: this.state.languageFilter, 
515                                                                          languageChangeHandler: this.setLanguageAndFilter})
516                                ), 
517
518                                React.createElement(Modal, {ref: "resultModal", title: this.renderZoomedResultTitle(this.state.zoomedCorpusHit)}, 
519                                        React.createElement(ZoomedResult, {corpusHit: this.state.zoomedCorpusHit, 
520                                                                  nextResults: this.nextResults, 
521                                                                  getDownloadLink: this.getDownloadLink, 
522                                                                  getToWeblichtLink: this.getToWeblichtLink, 
523                                                                  searchedLanguage: this.state.language, 
524                                                                  weblichtLanguages: this.state.weblichtLanguages, 
525                                                                  languageMap: this.state.languageMap})
526                                ), 
527
528                                React.createElement("div", {className: "top-gap"}, 
529                                        React.createElement(Results, {collhits: this.filterResults(), 
530                                                         toggleResultModal: this.toggleResultModal, 
531                                                         getDownloadLink: this.getDownloadLink, 
532                                                         getToWeblichtLink: this.getToWeblichtLink, 
533                                                         searchedLanguage: this.state.language})
534                                )
535                        )
536                        );
537        },
538});
539
540
541
542/////////////////////////////////
543
544var LanguageSelector = React.createClass({displayName: 'LanguageSelector',
545        propTypes: {
546                anyLanguage: PT.array.isRequired,
547                languageMap: PT.object.isRequired,
548                selectedLanguage: PT.array.isRequired,
549                languageFilter: PT.string.isRequired,
550                languageChangeHandler: PT.func.isRequired,
551        },
552        mixins: [React.addons.LinkedStateMixin],
553
554        selectLang: function(language) {
555                this.props.languageChangeHandler(language, this.props.languageFilter);
556        },
557
558        setFilter: function(filter) {
559                this.props.languageChangeHandler(this.props.selectedLanguage, filter);
560        },
561
562        renderLanguageObject: function(lang) {
563                var desc = lang[1] + " [" + lang[0] + "]";
564                var style = {
565                        whiteSpace: "nowrap",
566                        fontWeight: lang[0] === this.props.selectedLanguage[0] ? "bold":"normal",
567                };
568                return  React.createElement("div", {key: lang[0]}, 
569                                        React.createElement("a", {tabIndex: "-1", href: "#", style: style, onClick: this.selectLang.bind(this, lang)}, desc)
570                                );
571        },
572
573        renderRadio: function(option) {
574                return  this.props.languageFilter === option ? 
575                                React.createElement("input", {type: "radio", name: "filterOpts", value: option, checked: true, onChange: this.setFilter.bind(this, option)})
576                                : React.createElement("input", {type: "radio", name: "filterOpts", value: option, onChange: this.setFilter.bind(this, option)});
577        },
578
579        render: function() {
580                var languages = _.pairs(this.props.languageMap)
581                                                 .sort(function(l1, l2){return l1[1].localeCompare(l2[1]); });
582                languages.unshift(this.props.anyLanguage);
583                languages = languages.map(this.renderLanguageObject);
584                var third = Math.round(languages.length/3);
585                var l1 = languages.slice(0, third);
586                var l2 = languages.slice(third, 2*third);
587                var l3 = languages.slice(2*third, languages.length);
588
589                return  React.createElement("div", null, 
590                                        React.createElement("div", {className: "row"}, 
591                                                React.createElement("div", {className: "col-sm-4"}, l1), 
592                                                React.createElement("div", {className: "col-sm-4"}, l2), 
593                                                React.createElement("div", {className: "col-sm-4"}, l3), 
594                                                React.createElement("div", {className: "col-sm-12", style: {marginTop:10, marginBottom:10, borderBottom:"1px solid #eee"}})
595                                        ), 
596                                        React.createElement("form", {className: "form", role: "form"}, 
597                                                React.createElement("div", {className: "input-group"}, 
598                                                        React.createElement("div", null, 
599                                                        React.createElement("label", {style: {color:'black'}}, 
600                                                                 this.renderRadio('byMeta'), " ", 
601                                                                "Use the collections", "'", " specified language to filter results" 
602                                                        )
603                                                        ), 
604                                                        React.createElement("div", null, 
605                                                        React.createElement("label", {style: {color:'black'}}, 
606                                                                 this.renderRadio('byGuess'), " ", 
607                                                                "Filter results by using a language detector" 
608                                                        )
609                                                        ), 
610                                                        React.createElement("div", null, 
611                                                        React.createElement("label", {style: {color:'black'}}, 
612                                                                 this.renderRadio('byMetaAndGuess'), " ", 
613                                                                "First use the collections", "'", " specified language then also use a language detector"
614                                                        )
615                                                        )
616                                                )
617                                        )
618                                );
619        }
620});
621
622/////////////////////////////////
623
624var ResultMixin = window.MyReact.ResultMixin = {
625        // getDefaultProps: function(){
626        //      return {hasPopover: true};
627        // },
628 
629        getInitialState: function () {
630                return { 
631                        displayKwic: false,
632                };
633        },
634
635        toggleKwic: function() {
636                this.setState({displayKwic:!this.state.displayKwic});
637        },
638
639        renderPanelTitle: function(corpus) {
640                return  React.createElement("div", {className: "inline"}, 
641                                        React.createElement("span", {className: "corpusName"}, " ", corpus.title), 
642                                        React.createElement("span", {className: "institutionName"}, " — ", corpus.institution.name)
643                                );
644        },
645
646        renderRowLanguage: function(hit) {
647                return false; //<span style={{fontFace:"Courier",color:"black"}}>{hit.language} </span> ;
648        },
649
650        renderRowsAsHits: function(hit,i) {
651                function renderTextFragments(tf, idx) {
652                        return React.createElement("span", {key: idx, className: tf.hit?"keyword":""}, tf.text);
653                }
654                return  React.createElement("p", {key: i, className: "hitrow"}, 
655                                        this.renderRowLanguage(hit), 
656                                        hit.fragments.map(renderTextFragments)
657                                );
658        },
659
660        renderRowsAsKwic: function(hit,i) {
661                var sleft={textAlign:"left", verticalAlign:"top", width:"50%"};
662                var scenter={textAlign:"center", verticalAlign:"top", maxWidth:"50%"};
663                var sright={textAlign:"right", verticalAlign:"top", maxWidth:"50%"};
664                return  React.createElement("tr", {key: i, className: "hitrow"}, 
665                                        React.createElement("td", null, this.renderRowLanguage(hit)), 
666                                        React.createElement("td", {style: sright}, hit.left), 
667                                        React.createElement("td", {style: scenter, className: "keyword"}, hit.keyword), 
668                                        React.createElement("td", {style: sleft}, hit.right)
669                                );
670        },
671
672        renderDiagnostic: function(d, key) {
673                if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) {
674                        return false;
675                }
676                return  React.createElement("div", {className: "alert alert-warning", key: key}, 
677                                        React.createElement("div", null, "Diagnostic: ", d.message)
678                                ); 
679        },
680
681        renderDiagnostics: function(corpusHit) {
682                if (!corpusHit.diagnostics || corpusHit.diagnostics.length === 0) {
683                        return false;
684                }
685                return corpusHit.diagnostics.map(this.renderDiagnostic);
686        },
687
688        renderErrors: function(corpusHit) {
689                var xc = corpusHit.exception;
690                if (!xc) {
691                        return false;
692                }
693                return  (
694                        React.createElement("div", {className: "alert alert-danger", role: "alert"}, 
695                                React.createElement("div", null, "Exception: ", xc.message), 
696                                 xc.cause ? React.createElement("div", null, "Caused by: ", xc.cause) : false
697                        )
698                );
699        },
700
701        renderPanelBody: function(corpusHit) {
702                var fulllength = {width:"100%"};
703                if (this.state.displayKwic) {
704                        return  React.createElement("div", null, 
705                                                this.renderErrors(corpusHit), 
706                                                this.renderDiagnostics(corpusHit), 
707                                                React.createElement("table", {className: "table table-condensed table-hover", style: fulllength}, 
708                                                        React.createElement("tbody", null, corpusHit.kwics.map(this.renderRowsAsKwic))
709                                                )
710                                        );
711                } else {
712                        return  React.createElement("div", null, 
713                                                this.renderErrors(corpusHit), 
714                                                this.renderDiagnostics(corpusHit), 
715                                                corpusHit.kwics.map(this.renderRowsAsHits)
716                                        );
717                }
718        },
719
720        renderDisplayKWIC: function() {
721                return  React.createElement("div", {className: "inline btn-group", style: {display:"inline-block"}}, 
722                                        React.createElement("label", {forHtml: "inputKwic", className: "btn btn-flat"}, 
723                                                 this.state.displayKwic ? 
724                                                        React.createElement("input", {id: "inputKwic", type: "checkbox", value: "kwic", checked: true, onChange: this.toggleKwic}) :
725                                                        React.createElement("input", {id: "inputKwic", type: "checkbox", value: "kwic", onChange: this.toggleKwic}), 
726                                               
727                                                " " + ' ' +
728                                                "Display as Key Word In Context"
729                                        )
730                                );
731        },
732
733        renderDownloadLinks: function(corpusId) {
734                return (
735                        React.createElement("div", {className: "dropdown"}, 
736                                React.createElement("button", {className: "btn btn-flat", 'aria-expanded': "false", 'data-toggle': "dropdown"}, 
737                                        React.createElement("span", {className: "glyphicon glyphicon-download-alt", 'aria-hidden': "true"}), 
738                                        " ", " Download ", " ", 
739                                        React.createElement("span", {className: "caret"})
740                                ), 
741                                React.createElement("ul", {className: "dropdown-menu"}, 
742                                        React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink(corpusId, "csv")}, 
743                                                        " ", " As CSV file")), 
744                                        React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink(corpusId, "excel")}, 
745                                                        " ", " As Excel file")), 
746                                        React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink(corpusId, "tcf")}, 
747                                                        " ", " As TCF file")), 
748                                        React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink(corpusId, "text")}, 
749                                                        " ", " As Plain Text file"))
750                                )
751                        )
752                );
753        },
754
755        renderToWeblichtLinks: function(corpusId, error) {
756                return (
757                        React.createElement("div", {className: "dropdown"}, 
758                                React.createElement("button", {className: "btn btn-flat", 'aria-expanded': "false", 'data-toggle': "dropdown"}, 
759                                        React.createElement("span", {className: "glyphicon glyphicon-export", 'aria-hidden': "true"}), 
760                                        " ", " Use Weblicht ", " ", 
761                                        React.createElement("span", {className: "caret"})
762                                ), 
763                                React.createElement("ul", {className: "dropdown-menu"}, 
764                                        React.createElement("li", null, 
765                                                error ? 
766                                                        React.createElement("div", {className: "alert alert-danger", style: {margin:10, width:200}}, error) :
767                                                        React.createElement("a", {href: this.props.getToWeblichtLink(corpusId), target: "_blank"}, " ", 
768                                                                "Send to Weblicht")
769                                               
770                                        )
771                                )
772                        )
773                );
774        },
775
776};
777
778var ZoomedResult = React.createClass({displayName: 'ZoomedResult',
779        propTypes: {
780                corpusHit: PT.object,
781                nextResults: PT.func.isRequired,
782                languageMap: PT.object.isRequired,
783                weblichtLanguages: PT.array.isRequired,
784                searchedLanguage: PT.array.isRequired,
785                getDownloadLink: PT.func.isRequired,
786                getToWeblichtLink: PT.func.isRequired,
787        },
788        mixins: [ResultMixin],
789
790        getInitialState: function() {
791                return {
792                        inProgress: false,
793                };
794        },
795
796        componentWillReceiveProps: function() {
797                this.setState({inProgress: false});
798        },
799
800        nextResults: function(e) {
801                this.setState({inProgress: true});
802                this.props.nextResults(this.props.corpusHit.corpus.id);
803        },
804
805        renderLanguages: function(languages) {
806                return languages
807                                .map(function(l) { return this.props.languageMap[l]; }.bind(this))
808                                .sort()
809                                .join(", ");
810        },
811
812        renderMoreResults:function(){
813                if (this.state.inProgress || this.props.corpusHit.inProgress) 
814                        return React.createElement("span", {style: {fontStyle:'italic'}}, "Retrieving results, please wait...");
815
816                var moreResults = true;
817                for (var i = 0; i < this.props.corpusHit.diagnostics.length; i++) {
818                        var d = this.props.corpusHit.diagnostics[i];
819                        if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) {
820                                moreResults = false;
821                                break;
822                        }
823                }
824                if (!moreResults)
825                        return React.createElement("span", {style: {fontStyle:'italic'}}, "No other results available for this query");
826                return  React.createElement("button", {className: "btn btn-default", onClick: this.nextResults}, 
827                                        React.createElement("span", {className: "glyphicon glyphicon-option-horizontal", 'aria-hidden': "true"}), " More Results"
828                                );
829        },
830
831        render: function() {
832                var corpusHit = this.props.corpusHit;
833                if (!corpusHit) {
834                        return false;
835                }
836                var wlerror = null;
837                if (this.props.weblichtLanguages.indexOf(this.props.searchedLanguage[0]) < 0) {
838                        wlerror = "Cannot use WebLicht: unsupported language";
839                }
840                var corpus = corpusHit.corpus;
841                return  React.createElement("div", null, 
842                                        React.createElement(ReactCSSTransitionGroup, {transitionName: "fade"}, 
843                                                React.createElement("div", {className: "corpusDescription"}, 
844                                                        React.createElement("p", null, React.createElement("i", {className: "fa fa-institution"}), " ", corpus.institution.name), 
845                                                        corpus.description ? 
846                                                                React.createElement("p", null, React.createElement("i", {className: "glyphicon glyphicon-info-sign"}), " ", corpus.description): false, 
847                                                        React.createElement("p", null, React.createElement("i", {className: "fa fa-language"}), " ", this.renderLanguages(corpus.languages))
848                                                ), 
849                                                React.createElement("div", {style: {marginBottom:2}}, 
850                                                        React.createElement("div", {className: "float-right"}, 
851                                                                React.createElement("div", null, 
852                                                                         this.renderDisplayKWIC(), 
853                                                                        React.createElement("div", {className: "inline"}, " ", this.renderDownloadLinks(corpusHit.corpus.id), " "), 
854                                                                        React.createElement("div", {className: "inline"}, " ", this.renderToWeblichtLinks(corpus.id, wlerror), " ")
855                                                                )
856                                                        ), 
857                                                        React.createElement("div", {style: {clear:'both'}})
858                                                ), 
859                                                React.createElement("div", {className: "panel"}, 
860                                                        React.createElement("div", {className: "panel-body corpusResults"}, this.renderPanelBody(corpusHit))
861                                                ), 
862
863                                                React.createElement("div", {style: {textAlign:'center', marginTop:10}}, 
864                                                         this.renderMoreResults() 
865                                                )
866
867                                        )
868                                );
869        },
870});
871
872var Results = React.createClass({displayName: 'Results',
873        propTypes: {
874                collhits: PT.object.isRequired,
875                searchedLanguage: PT.array.isRequired,
876                toggleResultModal: PT.func.isRequired,
877                getDownloadLink: PT.func.isRequired,
878                getToWeblichtLink: PT.func.isRequired,
879        },
880        mixins: [ResultMixin],
881
882        renderPanelInfo: function(corpusHit) {
883                var corpus = corpusHit.corpus;
884                var inline = {display:"inline-block"};
885                return  React.createElement("div", null, 
886                                        " ", 
887                                        React.createElement("div", {style: inline}, 
888                                                React.createElement("button", {className: "btn btn-default zoomResultButton", 
889                                                                onClick: function(e){this.props.toggleResultModal(e,corpusHit)}.bind(this)}, 
890                                                                React.createElement("span", {className: "glyphicon glyphicon-eye-open"}), " View"
891                                                )
892                                        )
893                                );
894        },
895
896        renderResultPanel: function(corpusHit) {
897                if (corpusHit.kwics.length === 0 && 
898                        !corpusHit.exception &&
899                        corpusHit.diagnostics.length === 0) {
900                                return false;
901                }
902                return  React.createElement(Panel, {key: corpusHit.corpus.id, 
903                                                title: this.renderPanelTitle(corpusHit.corpus), 
904                                                info: this.renderPanelInfo(corpusHit)}, 
905                                        this.renderPanelBody(corpusHit)
906                                );
907        },
908
909        renderProgressMessage: function() {
910                var collhits = this.props.collhits;
911                var done = collhits.results.length - collhits.inProgress;
912                var msg = collhits.hits + " matching collections found in " + done + " searched collections";
913                var percents = Math.round(100 * collhits.hits / collhits.results.length);
914                var styleperc = {width: percents+"%"};
915                return  React.createElement("div", {style: {marginTop:10}}, 
916                                        React.createElement("div", null, msg), 
917                                        collhits.inProgress > 0 ? 
918                                                React.createElement("div", {className: "progress", style: {marginBottom:10}}, 
919                                                        React.createElement("div", {className: "progress-bar progress-bar-striped active", role: "progressbar", 
920                                                                'aria-valuenow': percents, 'aria-valuemin': "0", 'aria-valuemax': "100", style: styleperc}), 
921                                                        percents > 2 ? false :
922                                                                React.createElement("div", {className: "progress-bar progress-bar-striped active", role: "progressbar", 
923                                                                        'aria-valuenow': "100", 'aria-valuemin': "0", 'aria-valuemax': "100", 
924                                                                        style: {width: '100%', backgroundColor:'#888'}})
925                                                       
926                                                ) : 
927                                                false
928                                );
929        },
930
931        render: function() {
932                var collhits = this.props.collhits;
933                if (!collhits.results) {
934                        return false;
935                }
936                var showprogress = collhits.inProgress > 0;
937                return  React.createElement("div", null, 
938                                        React.createElement(ReactCSSTransitionGroup, {transitionName: "fade"}, 
939                                                 showprogress ? this.renderProgressMessage() : React.createElement("div", {style: {height:20}}), 
940                                                React.createElement("div", {style: {marginBottom:2}}, 
941                                                         showprogress ? false : 
942                                                                React.createElement("div", {className: "float-left"}, " ", collhits.hits + " matching collections found", " "), 
943                                                       
944                                                         collhits.hits === 0 ? false : 
945                                                                React.createElement("div", {className: "float-right"}, 
946                                                                        React.createElement("div", null, 
947                                                                                 this.renderDisplayKWIC(), 
948                                                                                 collhits.inProgress === 0 ? 
949                                                                                        React.createElement("div", {className: "inline"}, " ", this.renderDownloadLinks(), " ")
950                                                                                        :false
951                                                                               
952                                                                        )
953                                                                ), 
954                                                       
955                                                        React.createElement("div", {style: {clear:'both'}})
956                                                ), 
957                                                collhits.results.map(this.renderResultPanel)
958                                        )
959                                );
960        }
961});
962
963var _ = window._ = window._ || {
964        keys: function() {
965                var ret = [];
966                for (var x in o) {
967                        if (o.hasOwnProperty(x)) {
968                                ret.push(x);
969                        }
970                }
971                return ret;
972        },
973
974        pairs: function(o){
975                var ret = [];
976                for (var x in o) {
977                        if (o.hasOwnProperty(x)) {
978                                ret.push([x, o[x]]);
979                        }
980                }
981                return ret;
982        },
983
984        values: function(o){
985                var ret = [];
986                for (var x in o) {
987                        if (o.hasOwnProperty(x)) {
988                                ret.push(o[x]);
989                        }
990                }
991                return ret;
992        },
993};
994
995})();
Note: See TracBrowser for help on using the repository browser.