Changeset 5919 for SRUAggregator


Ignore:
Timestamp:
01/15/15 16:01:26 (9 years ago)
Author:
emanuel.dima@uni-tuebingen.de
Message:
  1. alpha11: UI refactor
Location:
SRUAggregator/trunk
Files:
1 added
13 edited

Legend:

Unmodified
Added
Removed
  • SRUAggregator/trunk/aggregator_development.yml

    r5901 r5919  
    55  AGGREGATOR_FILE_PATH: /Users/edima/fcsAggregatorCorpora.json
    66  SCAN_MAX_DEPTH: 3
    7   SCAN_TASK_INITIAL_DELAY: 0
     7  SCAN_TASK_INITIAL_DELAY: 12
    88  SCAN_TASK_INTERVAL: 24
    99  SCAN_TASK_TIME_UNIT: HOURS
  • SRUAggregator/trunk/build.sh

    r5893 r5919  
    33ASSETDIR=src/main/resources/assets
    44LIBDIR=$ASSETDIR/lib
     5FONTDIR=$ASSETDIR/fonts
     6JSDIR=$ASSETDIR/js
    57
    68if [ ! -e bower_components ]
    79then
     10        mkdir -p $LIBDIR
     11        mkdir -p $FONTDIR
     12        mkdir -p $JSDIR
     13
    814        npm install bower react-tools
    9         node_modules/bower/bin/bower install jquery bootstrap react react-addons font-awesome
     15        node_modules/bower/bin/bower install jquery bootstrap react react-addons react-router font-awesome
    1016
    11         mkdir -p src/main/webapp/lib
    12         cp bower_components/bootstrap/dist/css/bootstrap.min.css $LIBDIR
    13         cp bower_components/bootstrap/dist/js/bootstrap.min.js $LIBDIR
    14         cp bower_components/jquery/dist/jquery.min.js $LIBDIR
    15         cp bower_components/react/react-with-addons.js $LIBDIR
    16         cp bower_components/react/react-with-addons.min.js $LIBDIR
    17         cp bower_components/font-awesome/css/font-awesome.min.css $LIBDIR
     17        cp bower_components/bootstrap/dist/css/bootstrap.min.css $LIBDIR/
     18        cp bower_components/bootstrap/dist/js/bootstrap.min.js $LIBDIR/
     19        cp bower_components/jquery/dist/jquery.min.js $LIBDIR/
     20        cp bower_components/react/react-with-addons.js $LIBDIR/
     21        cp bower_components/react/react-with-addons.min.js $LIBDIR/
     22        cp bower_components/react-router/dist/react-router.min.js $LIBDIR/
     23        cp bower_components/font-awesome/css/font-awesome.min.css $LIBDIR/
    1824
    19         mkdir -p src/main/webapp/fonts
    20         cp bower_components/bootstrap/fonts/*  $ASSETDIR/fonts/
    21         cp bower_components/font-awesome/fonts/* $ASSETDIR/fonts/
     25        cp bower_components/bootstrap/fonts/*  $FONTDIR/
     26        cp bower_components/font-awesome/fonts/* $FONTDIR/
    2227fi
    2328
    24 JSDIR=$ASSETDIR/js
    2529for f in $JSDIR/*.jsx; do
    2630        cp -v $f $JSDIR/`basename $f .jsx`.js;
     
    2832node_modules/react-tools/bin/jsx --no-cache-dir $JSDIR $JSDIR
    2933
    30 #mvn -q clean package
     34# mvn -q clean package
    3135
    3236# Run in production:
     
    3438
    3539# Run for development:
    36 #java -cp src/main/resources:target/Aggregator2-2.0.0-alpha-6.jar eu.clarin.sru.fcs.aggregator.app.Aggregator server aggregator_development.yml
     40# java -cp src/main/resources:target/Aggregator2-2.0.0-alpha-10.jar eu.clarin.sru.fcs.aggregator.app.Aggregator server aggregator_development.yml
  • SRUAggregator/trunk/pom.xml

    r5901 r5919  
    88        <groupId>eu.clarin.sru.fcs</groupId>
    99        <artifactId>Aggregator2</artifactId>
    10         <version>2.0.0-alpha-10</version>
     10        <version>2.0.0-alpha-11</version>
    1111        <name>FCS Aggregator</name>
    1212
     
    8686                        <version>1.5.3</version>
    8787                </dependency>
     88                <dependency>
     89                    <groupId>com.optimaize.languagedetector</groupId>
     90                    <artifactId>language-detector</artifactId>
     91                    <version>0.4</version>
     92                </dependency>
     93               
    8894                <dependency>
    8995                        <groupId>com.googlecode.sardine</groupId>
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java

    r5901 r5919  
    22
    33import com.fasterxml.jackson.databind.ObjectMapper;
     4import com.optimaize.langdetect.LanguageDetector;
     5import com.optimaize.langdetect.LanguageDetectorBuilder;
     6import com.optimaize.langdetect.ngram.NgramExtractors;
     7import com.optimaize.langdetect.profiles.LanguageProfile;
     8import com.optimaize.langdetect.profiles.LanguageProfileReader;
     9import com.optimaize.langdetect.text.*;
    410import eu.clarin.sru.fcs.aggregator.search.Search;
    511import eu.clarin.sru.fcs.aggregator.scan.ScanCrawlTask;
     
    8086 * @author edima
    8187 *
     88 * TODO: try to refine results by language using a language library, with UI
     89 * element
     90 *
     91 * TODO: condensed list of corpora
     92 *
     93 * TODO: group the list of corpora by institution?
     94 *
     95 * TODO: fix ordering of corpora in corpora view
     96 *
     97 * TODO: corpora search should not indicate the ones that don't match
     98 *
     99 * TODO: Collections view: home link (make a single consistent text for it)
     100 *
     101 * TODO: tri-state for parent collections; search + message implications
     102 *
    82103 * TODO: version page: credits, open source, see vcr/version page
    83104 *
    84  * TODO: change the order in the GUI: selected collections after the
    85  * language/layer
    86  *
    87  * TODO: condensed list of corpora
    88  *
    89  * TODO: group the list of corpora by institution?
    90  *
    91  * TODO: fix ordering of corpora in corpora view
    92  *
    93  * TODO: corpora search should not indicate the ones that don't match
    94  *
    95  * TODO: try to refine by language using a language library, with UI element
    96  *
    97  * TODO: helpdesk: switch to english (parameter of the form)
    98  *
    99  * TODO: label: "phonetic transcriptions" (ask the BAS guys)
    100  *
    101  * TODO: number of results control: make the buttons larger
    102  *
    103  * TODO: Collections view: home link (make a single consistent text for it)
    104  *
    105  * TODO: push footer down
     105 * TODO: statistics page liked from version page
    106106 *
    107107 * TODO: 1. support new spec-compatible centres, see Oliver's mail
    108108 * (use SRUClient's extraResponseData POJOs)
    109109 *
    110  * TODO: tri-state for parent collections; search + message implications
    111  *
    112  * TODO: disable popups easily
    113  *
    114110 * TODO: 2. zoom into the results from a corpus, allow functionality only for
    115111 * the view (search for next set of results)
    116112 *
     113 * TODO: disable popups easily
     114 *
    117115 * TODO: Fix activeSearch memory leak (gc searches older than...)
    118116 *
    119  * TODO: 3. Use weblicht with results
     117 * TODO: Use weblicht with results
    120118 *
    121119 * TODO: Export to personal workspace as csv, excel, tcf, plain text
    122120 *
    123121 * TODO: Download to personal workspace as csv, excel, tcf, plain text
    124  *
    125  * TODO: 4. use a language guesser ?
    126122 *
    127123 * TODO: websockets
     
    150146        private AtomicReference<Statistics> searchStatsAtom = new AtomicReference<Statistics>(new Statistics());
    151147
    152         private TokenizerModel model;
     148        private TokenizerModel tokenizerModel;
     149        private LanguageDetector languageDetector;
     150        private TextObjectFactory textObjectFactory;
     151
    153152        private ThrottledClient sruScanClient = null;
    154153        private ThrottledClient sruSearchClient = null;
     
    170169
    171170        @Override
    172         public void run(AggregatorConfiguration config, Environment environment) {
     171        public void run(AggregatorConfiguration config, Environment environment) throws Exception {
    173172                params = config.aggregatorParams;
    174173                instance = this;
     
    212211        }
    213212
    214         public void init() {
     213        public void init() throws IOException {
    215214                log.info("Aggregator initialization started.");
    216215                sruScanClient = new ThrottledClient(
    217                         new ClarinFCSClientBuilder()
     216                                new ClarinFCSClientBuilder()
    218217                                .setConnectTimeout(params.ENDPOINTS_SCAN_TIMEOUT_MS)
    219218                                .setSocketTimeout(params.ENDPOINTS_SCAN_TIMEOUT_MS)
     
    245244
    246245                LanguagesISO693_3.getInstance(); // force init
    247                 model = setUpTokenizers();
     246                initTokenizer();
     247                initLanguageDetector();
    248248
    249249                ScanCrawlTask task = new ScanCrawlTask(sruScanClient,
     
    302302        }
    303303
    304         private static TokenizerModel setUpTokenizers() {
     304        private void initTokenizer() {
    305305                TokenizerModel model = null;
    306306                try {
     
    311311                        log.error("Failed to load tokenizer model", ex);
    312312                }
    313                 return model;
     313                tokenizerModel = model;
     314        }
     315
     316        public void initLanguageDetector() throws IOException {
     317                List<LanguageProfile> languageProfiles = new LanguageProfileReader().readAll();
     318                languageDetector = LanguageDetectorBuilder
     319                                .create(NgramExtractors.standard())
     320                                .withProfiles(languageProfiles)
     321                                .build();
     322
     323                textObjectFactory = CommonTextObjectFactories.forDetectingOnLargeText();
     324        }
     325
     326        public String detectLanguage(String text) {
     327                return languageDetector.detect(textObjectFactory.forText(text)).orNull();
    314328        }
    315329}
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Kwic.java

    r5758 r5919  
    22
    33import eu.clarin.sru.client.fcs.DataViewHits;
     4import eu.clarin.sru.fcs.aggregator.app.Aggregator;
    45import java.util.ArrayList;
    56import java.util.List;
     
    3940        private String pid;
    4041        private String reference;
     42        private String language;
    4143        private List<TextFragment> fragments = new ArrayList<TextFragment>();
    4244
     
    6062                        fragments.add(new TextFragment(text.substring(lastOffset, text.length()), false));
    6163                }
     64
     65                language = Aggregator.getInstance().detectLanguage(hits.getText());
    6266        }
    6367
     
    7276        public String getReference() {
    7377                return reference;
     78        }
     79
     80        public String getLanguage() {
     81                return language;
    7482        }
    7583
  • SRUAggregator/trunk/src/main/resources/assets/base.css

    r5900 r5919  
    109109}
    110110
     111/* large spinner */
     112input[type=number]::-webkit-inner-spin-button,
     113input[type=number]::-webkit-outer-spin-button {
     114        margin-left: 2px;
     115        font-size:24px;
     116}
     117
     118
    111119input, select, textarea {
    112   -webkit-transition: all 0.30s ease-in-out;
    113   -moz-transition: all 0.30s ease-in-out;
    114   -ms-transition: all 0.30s ease-in-out;
    115   -o-transition: all 0.30s ease-in-out;
    116   outline: none;
    117   border: 1px solid #ccc;
     120        -webkit-transition: all 0.30s ease-in-out;
     121        -moz-transition: all 0.30s ease-in-out;
     122        -ms-transition: all 0.30s ease-in-out;
     123        -o-transition: all 0.30s ease-in-out;
     124        outline: none;
     125        border: 1px solid #ccc;
    118126}
    119127input:focus, select:focus, textarea:focus, button:focus, a:focus {
  • SRUAggregator/trunk/src/main/resources/assets/index.html

    r5901 r5919  
    1515        </noscript>
    1616
    17         <div id="body">
    18                 <div id="reactMain">
    19         </div>
     17        <style>
     18                html,
     19                body {
     20                        margin:0;
     21                        padding:0;
     22                        height:100%;
     23                }
     24                #container {
     25                        min-height:100%;
     26                        position:relative;
     27                }
     28                #body {
     29                        padding-bottom:100px;   /* Height of the footer */
     30                }
     31                #footer {
     32                        position:absolute;
     33                        bottom:0;
     34                        width:100%;
     35                        height:100px;   /* Height of the footer */
     36                }
     37        </style>
    2038
    21         <div id="footer">
    22                 <div class="container">
    23                         <div id="CLARIN_footer_left">
    24                                 <a title="about" id="aboutlink">
    25                                         <span class="glyphicon glyphicon-info-sign"></span>
    26                                         <span>VERSION 2.0.0.α10</span>
    27                                 </a>
    28                         </div>
    29                         <div id="CLARIN_footer_middle">
    30                                 <a title="CLARIN ERIC" href="https://www.clarin.eu/">
    31                                 <img src="img/clarindLogo.png" alt="CLARIN ERIC logo" style="height:80px"/>
    32                                 </a>
    33                         </div>
    34                         <div id="CLARIN_footer_right">
    35                                 <a title="contact" href="mailto:fcs@clarin.eu">
    36                                         <span class="glyphicon glyphicon-envelope"></span>
    37                                         <span> CONTACT</span>
    38                                 </a>
     39        <div id="container">
     40                <div id="body">
     41                        <div id="reactMain">
     42                </div>
     43                <div id="footer">
     44                        <div class="container">
     45                                <div id="CLARIN_footer_left">
     46                                                <a title="about" id="aboutlink" href="about">
     47                                                <span class="glyphicon glyphicon-info-sign"></span>
     48                                                <span>VERSION 2.0.0.α11</span>
     49                                        </a>
     50                                </div>
     51                                <div id="CLARIN_footer_middle">
     52                                        <a title="CLARIN ERIC" href="https://www.clarin.eu/">
     53                                        <img src="img/clarindLogo.png" alt="CLARIN ERIC logo" style="height:80px"/>
     54                                        </a>
     55                                </div>
     56                                <div id="CLARIN_footer_right">
     57                                        <a title="contact" href="mailto:fcs@clarin.eu">
     58                                                <span class="glyphicon glyphicon-envelope"></span>
     59                                                <span> CONTACT</span>
     60                                        </a>
     61                                </div>
    3962                        </div>
    4063                </div>
    4164        </div>
    4265
    43         <script src="lib/jquery.min.js"></script>
    4466        <!--[if lt IE 9]>
    4567                <script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-shim.js"></script>
    4668                <script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-sham.js"></script>
    4769        <![endif]-->   
     70        <script src="lib/jquery.min.js"></script>
    4871        <script src="lib/react-with-addons.js"></script>
    4972        <script src="lib/bootstrap.min.js"></script>
    5073        <script src="js/components.js"></script>
     74        <script src="js/corpora.js"></script>
    5175        <script src="js/search.js"></script>
    52         <script src="js/corpora.js"></script>
    5376        <script src="js/main.js"></script>
    5477</body>
  • SRUAggregator/trunk/src/main/resources/assets/js/corpora.js

    r5900 r5919  
    22(function() {
    33"use strict";
     4
     5window.MyAggregator = window.MyAggregator || {};
    46
    57var PT = React.PropTypes;
     
    4345});
    4446
    45 var CorpusView = React.createClass({displayName: 'CorpusView',
     47var CorpusView = window.MyAggregator.CorpusView = React.createClass({displayName: 'CorpusView',
    4648        propTypes: {
    4749                corpora: PT.object.isRequired,
     
    238240});
    239241
    240 /////////////////////////////////
    241 
    242 if (!window.MyAggregator) {
    243         window.MyAggregator = {};
    244 }
    245 window.MyAggregator.CorpusView = CorpusView;
    246242})();
  • SRUAggregator/trunk/src/main/resources/assets/js/corpora.jsx

    r5900 r5919  
    22(function() {
    33"use strict";
     4
     5window.MyAggregator = window.MyAggregator || {};
    46
    57var PT = React.PropTypes;
     
    4345});
    4446
    45 var CorpusView = React.createClass({
     47var CorpusView = window.MyAggregator.CorpusView = React.createClass({
    4648        propTypes: {
    4749                corpora: PT.object.isRequired,
     
    238240});
    239241
    240 /////////////////////////////////
    241 
    242 if (!window.MyAggregator) {
    243         window.MyAggregator = {};
    244 }
    245 window.MyAggregator.CorpusView = CorpusView;
    246242})();
  • SRUAggregator/trunk/src/main/resources/assets/js/main.js

    r5900 r5919  
    55var PT = React.PropTypes;
    66
    7 var SearchBox = window.MyAggregator.SearchBox;
    8 var CorpusSelection = window.MyAggregator.CorpusSelection;
    9 var HitNumber = window.MyAggregator.HitNumber;
    10 var Results = window.MyAggregator.Results;
    11 var CorpusView = window.MyAggregator.CorpusView;
    12 var Modal = window.MyReact.Modal;
    137var ErrorPane = window.MyReact.ErrorPane;
    14 
    15 var multipleLanguageCode = "mul"; // see ISO-693-3
    16 
    17 var layers = [
    18         {
    19                 id: "sampa",
    20                 name: "Phonetics Resources",
    21                 searchPlaceholder: "stA:z",
    22                 searchLabel: "SAMPA query",
    23                 searchLabelBkColor: "#eef",
    24         },
    25         {
    26                 id: "text",
    27                 name: "Text Resources",
    28                 searchPlaceholder: "Elephant",
    29                 searchLabel: "Search text",
    30                 searchLabelBkColor: "#fed",
    31         },
    32 ];
    33 var layerMap = {
    34         sampa: layers[0],
    35         text: layers[1],
    36 };
    37 
    38 function Corpora(corpora, updateFn) {
    39         var that = this;
    40         this.corpora = corpora;
    41         this.update = function() {
    42                 updateFn(that);
    43         };
    44        
    45         var sortFn = function(x, y) {
    46                 var r = x.institution.name.localeCompare(y.institution.name);
    47                 if (r !== 0) {
    48                         return r;
    49                 }
    50                 var t1 = x.title ? x.title : x.displayName;
    51                 var t2 = y.title ? y.title : y.displayName;
    52                 return t1.toLowerCase().localeCompare(t2.toLowerCase());
    53         };
    54 
    55         this.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); });
    56         this.corpora.sort(sortFn);
    57 
    58         this.recurse(function(corpus, index) {
    59                 corpus.visible = true; // visible in the corpus view
    60                 corpus.selected = true; // selected in the corpus view
    61                 corpus.expanded = false; // not expanded in the corpus view
    62                 corpus.priority = 1; // priority in corpus view
    63                 corpus.index = index;
    64         });
    65 }
    66 
    67 Corpora.prototype.recurseCorpus = function(corpus, fn) {
    68         if (false === fn(corpus)) {             
    69                 // no recursion
    70         } else {
    71                 this.recurseCorpora(corpus.subCorpora, fn);
    72         }
    73 };
    74 
    75 Corpora.prototype.recurseCorpora = function(corpora, fn) {
    76         var recfn = function(corpus, index){
    77                 if (false === fn(corpus)) {
    78                         // no recursion
    79                 } else {
    80                         corpus.subCorpora.forEach(recfn);
    81                 }
    82         };
    83         corpora.forEach(recfn);
    84 };
    85 
    86 Corpora.prototype.recurse = function(fn) {
    87         this.recurseCorpora(this.corpora, fn);
    88 };
    89 
    90 Corpora.prototype.getLanguageCodes = function() {
    91         var languages = {};
    92         this.recurse(function(corpus) {
    93                 corpus.languages.forEach(function(lang) {
    94                         languages[lang] = true;
    95                 });
    96                 return true;
    97         });
    98         return languages;
    99 };
    100 
    101 Corpora.prototype.isCorpusVisible = function(corpus, layerId, languageCode) {
    102         if (layerId !== "text") {
    103                 return false;
    104         }
    105         // yes for any language
    106         if (languageCode === multipleLanguageCode) {
    107                 return true;
    108         }
    109         // yes if the corpus is in only that language
    110         if (corpus.languages && corpus.languages.length === 1 && corpus.languages[0] === languageCode) {
    111                 return true;
    112         }       
    113 
    114         // ? yes if the corpus also contains that language
    115         if (corpus.languages && corpus.languages.indexOf(languageCode) >=0) {
    116                 return true;
    117         }
    118 
    119         // ? yes if the corpus has no language
    120         // if (!corpus.languages || corpus.languages.length === 0) {
    121         //      return true;
    122         // }
    123         return false;
    124 };
    125 
    126 Corpora.prototype.setVisibility = function(layerId, languageCode) {
    127         // top level
    128         this.corpora.forEach(function(corpus) {
    129                 corpus.visible = this.isCorpusVisible(corpus, layerId, languageCode);
    130                 this.recurseCorpora(corpus.subCorpora, function(c) { c.visible = corpus.visible; });
    131         }.bind(this));
    132 };
    133 
    134 Corpora.prototype.getSelectedIds = function() {
    135         var ids = [];
    136         this.recurse(function(corpus) {
    137                 if (corpus.visible && corpus.selected) {
    138                         ids.push(corpus.id);
    139                         return false; // top-most collection in tree, don't delve deeper
    140                 }
    141                 return true;
    142         });
    143 
    144         // console.log("ids: ", ids.length, {ids:ids});
    145         return ids;
    146 };
    147 
    148 Corpora.prototype.getSelectedMessage = function() {
    149         var selected = this.getSelectedIds().length;
    150         if (this.corpora.length === selected) {
    151                 return "All available collections";
    152         } else if (selected === 1) {
    153                 return "1 selected collection";
    154         }
    155         return selected+" selected collections";
    156 };
    157 
     8var AggregatorPage = window.MyAggregator.AggregatorPage;
    1589
    15910var Main = React.createClass({displayName: 'Main',
     
    16314                        navbarPageFn: this.renderAggregator,
    16415                        errorMessages: [],
    165 
    166                         corpora: new Corpora([], this.updateCorpora),
    167                         languageMap: {},
    16816                };
    169         },
    170 
    171         componentDidMount: function() {
    172                 this.refreshCorpora();
    173                 this.refreshLanguages();
    17417        },
    17518
     
    21457        },
    21558
    216         refreshCorpora: function() {
    217                 this.ajax({
    218                         url: 'rest/corpora',
    219                         success: function(json, textStatus, jqXHR) {
    220                                 this.setState({corpora : new Corpora(json, this.updateCorpora)});
    221                         }.bind(this),
    222                 });
    223         },
    224 
    225         refreshLanguages: function() {
    226                 this.ajax({
    227                         url: 'rest/languages',
    228                         success: function(json, textStatus, jqXHR) {
    229                                 this.setState({languageMap : json});
    230                         }.bind(this),
    231                 });
    232         },
    233 
    234         updateCorpora: function(corpora) {
    235                 this.setState({corpora:corpora});
    236         },
    237 
    23859        renderAggregator: function() {
    23960                return React.createElement(AggregatorPage, {ajax: this.ajax, corpora: this.state.corpora, languageMap: this.state.languageMap});
     
    26485                                                React.createElement("a", {className: "link", tabIndex: "-1",
    26586                                                        onClick: this.setNavbarPageFn.bind(this, this.renderAggregator)}, "Aggregator")
    266                                         ),
    267                                         React.createElement("li", {className: this.state.navbarPageFn === this.renderStatistics ? "active":""},
    268                                                 React.createElement("a", {className: "link", tabIndex: "-1",
    269                                                         onClick: this.setNavbarPageFn.bind(this, this.renderStatistics)}, "Statistics")
    27087                                        ),
    27188                                        React.createElement("li", {className: this.state.navbarPageFn === this.renderHelp ? "active":""},
     
    320137});
    321138
    322 var AggregatorPage = React.createClass({displayName: 'AggregatorPage',
    323         propTypes: {
    324                 ajax: PT.func.isRequired,
    325                 corpora: PT.object.isRequired,
    326                 languageMap: PT.object.isRequired,
    327         },
    328 
    329         mixins: [React.addons.LinkedStateMixin],
    330         timeout: 0,
    331         nohits: {
    332                 requests: [],
    333                 results: [],
    334         },
    335         anyLanguage: [multipleLanguageCode, "Any Language"],
    336 
    337         getInitialState: function () {
    338                 return {
    339                         searchLayerId: "text",
    340                         language: this.anyLanguage,
    341                         numberOfResults: 10,
    342 
    343                         searchId: null,
    344                         hits: this.nohits,
    345                 };
    346         },
    347 
    348         search: function(query) {
    349                 // console.log(query);
    350                 if (!query) {
    351                         this.setState({ hits: this.nohits, searchId: null });
    352                         return;                 
    353                 }
    354                 this.props.ajax({
    355                         url: 'rest/search',
    356                         type: "POST",
    357                         data: {
    358                                 layer: this.state.searchLayerId,
    359                                 language: this.state.language[0],
    360                                 query: query,
    361                                 numberOfResults: this.state.numberOfResults,
    362                                 corporaIds: this.props.corpora.getSelectedIds(),
    363                         },
    364                         success: function(searchId, textStatus, jqXHR) {
    365                                 // console.log("search ["+query+"] ok: ", searchId, jqXHR);
    366                                 this.setState({searchId : searchId});
    367                                 this.timeout = 250;
    368                                 setTimeout(this.refreshSearchResults, this.timeout);
    369                         }.bind(this),
    370                 });
    371         },
    372 
    373         refreshSearchResults: function() {
    374                 if (!this.state.searchId) {
    375                         return;
    376                 }
    377                 this.props.ajax({
    378                         url: 'rest/search/'+this.state.searchId,
    379                         success: function(json, textStatus, jqXHR) {
    380                                 if (json.requests.length > 0) {
    381                                         if (this.timeout < 10000) {
    382                                                 this.timeout = 1.5 * this.timeout;
    383                                         }
    384                                         setTimeout(this.refreshSearchResults, this.timeout);
    385                                         // console.log("new search in: " + this.timeout+ "ms");
    386                                 } else {
    387                                         // console.log("search ended");
    388                                 }
    389                                 this.setState({hits:json});
    390                                 // console.log("hits:", json);
    391                         }.bind(this),
    392                 });
    393         },
    394 
    395         setLanguage: function(languageObj) {
    396                 this.props.corpora.setVisibility(this.state.searchLayerId, languageObj[0]);
    397                 this.setState({language: languageObj});
    398                 this.props.corpora.update();
    399         },
    400 
    401         setLayer: function(layerId) {
    402                 this.props.corpora.setVisibility(layerId, this.state.language[0]);
    403                 this.props.corpora.update();
    404                 this.setState({searchLayerId: layerId});
    405         },
    406 
    407         setNumberOfResults: function(e) {
    408                 var n = e.target.value;
    409                 if (n < 10) n = 10;
    410                 if (n > 250) n = 250;
    411                 this.setState({numberOfResults: n});
    412                 e.preventDefault();
    413                 e.stopPropagation();
    414         },
    415 
    416         stop: function(e) {
    417                 e.preventDefault();
    418                 e.stopPropagation();
    419         },
    420 
    421         toggleCorpusSelection: function(e) {
    422                 $(this.refs.corporaModal.getDOMNode()).modal();
    423                 e.preventDefault();
    424                 e.stopPropagation();
    425         },
    426 
    427         renderAggregator: function() {
    428                 var layer = layerMap[this.state.searchLayerId];
    429                 return  (
    430                         React.createElement("div", {className: "top-gap"},
    431                                 React.createElement("div", {className: "row"},
    432                                         React.createElement("div", {className: "aligncenter", style: {marginLeft:16, marginRight:16}},
    433                                                 React.createElement("div", {className: "input-group"},
    434                                                         React.createElement("span", {className: "input-group-addon", style: {backgroundColor:layer.searchLabelBkColor}},
    435                                                                 layer.searchLabel
    436                                                         ),
    437 
    438                                                         React.createElement(SearchBox, {search: this.search, placeholder: layer.searchPlaceholder}),
    439                                                         React.createElement("div", {className: "input-group-btn"},
    440                                                                 React.createElement("button", {className: "btn btn-default input-lg", type: "button", onClick: this.search},
    441                                                                         React.createElement("i", {className: "glyphicon glyphicon-search"})
    442                                                                 )
    443                                                         )
    444                                                 )
    445                                         )
    446                                 ),
    447 
    448                                 React.createElement("div", {className: "wel", style: {marginTop:20}},
    449                                         React.createElement("div", {className: "aligncenter"},
    450                                                 React.createElement("form", {className: "form-inline", role: "form"},
    451 
    452                                                         React.createElement("div", {className: "input-group", style: {marginRight:10}},
    453                                                                 React.createElement("span", {className: "input-group-addon nobkg"}, "Search in"),
    454                                                                         React.createElement("button", {type: "button", className: "btn btn-default", onClick: this.toggleCorpusSelection},
    455                                                                                 this.props.corpora.getSelectedMessage(), " ", React.createElement("span", {className: "caret"})
    456                                                                         )
    457                                                         ),
    458 
    459                                                         React.createElement("div", {className: "input-group", style: {marginRight:10}},
    460                                                                
    461                                                                 React.createElement("span", {className: "input-group-addon nobkg"}, "of"),
    462                                                                
    463                                                                 React.createElement("div", {className: "input-group-btn"},
    464                                                                         React.createElement("button", {className: "form-control btn btn-default",
    465                                                                                         'aria-expanded': "false", 'data-toggle': "dropdown"},
    466                                                                                 this.state.language[1], " ", React.createElement("span", {className: "caret"})
    467                                                                         ),
    468                                                                         React.createElement("ul", {ref: "languageDropdownMenu", className: "dropdown-menu"},
    469                                                                                 React.createElement("li", {key: this.anyLanguage[0]}, " ", React.createElement("a", {tabIndex: "-1", href: "#",
    470                                                                                                 onClick: this.setLanguage.bind(this, this.anyLanguage)},
    471                                                                                         this.anyLanguage[1])
    472                                                                                 ),
    473                                                                                         _.pairs(this.props.languageMap).sort(function(l1, l2){
    474                                                                                                 return l1[1].localeCompare(l2[1]);
    475                                                                                         }).map(function(l) {
    476                                                                                                 var desc = l[1] + " [" + l[0] + "]";
    477                                                                                                 return React.createElement("li", {key: l[0]}, " ", React.createElement("a", {tabIndex: "-1", href: "#",
    478                                                                                                         onClick: this.setLanguage.bind(this, l)}, desc));
    479                                                                                         }.bind(this))
    480                                                                                
    481                                                                         )
    482                                                                 ),
    483 
    484                                                                 React.createElement("div", {className: "input-group-btn"},
    485                                                                         React.createElement("ul", {ref: "layerDropdownMenu", className: "dropdown-menu"},
    486                                                                                         layers.map(function(l) {
    487                                                                                                 return React.createElement("li", {key: l.id}, " ", React.createElement("a", {tabIndex: "-1", href: "#",
    488                                                                                                         onClick: this.setLayer.bind(this, l.id)}, " ", l.name, " "));
    489                                                                                         }.bind(this))
    490                                                                                
    491                                                                         ),                                                             
    492                                                                         React.createElement("button", {className: "form-control btn btn-default",
    493                                                                                         'aria-expanded': "false", 'data-toggle': "dropdown"},
    494                                                                                 layer.name, " ", React.createElement("span", {className: "caret"})
    495                                                                         )
    496                                                                 )
    497 
    498                                                         ),
    499 
    500                                                         React.createElement("div", {className: "input-group"},
    501                                                                 React.createElement("span", {className: "input-group-addon nobkg"}, "and show up to"),
    502                                                                 React.createElement("div", {className: "input-group-btn"},
    503                                                                         React.createElement("input", {type: "number", className: "form-control input", min: "10", max: "250", step: "5",
    504                                                                                 onChange: this.setNumberOfResults, value: this.state.numberOfResults,
    505                                                                                 onKeyPress: this.stop})
    506                                                                 ),
    507                                                                 React.createElement("span", {className: "input-group-addon nobkg"}, "hits")
    508                                                         )
    509                                                 )
    510                                         )
    511                                 ),
    512 
    513                     React.createElement(Modal, {ref: "corporaModal", title: "Collections"},
    514                                         React.createElement(CorpusView, {corpora: this.props.corpora, languageMap: this.props.languageMap})
    515                     ),
    516 
    517                                 React.createElement("div", {className: "top-gap"},
    518                                         React.createElement(Results, {requests: this.state.hits.requests, results: this.state.hits.results})
    519                                 )
    520                         )
    521                         );
    522         },
    523         render: function() {
    524                 return this.renderAggregator();
    525         }
    526 });
     139
    527140
    528141var StatisticsPage = React.createClass({displayName: 'StatisticsPage',
     
    613226var HelpPage = React.createClass({displayName: 'HelpPage',
    614227        openHelpDesk: function() {
    615                 window.open('http://support.clarin-d.de/mail/form.php?queue=Aggregator',
     228                window.open('http://support.clarin-d.de/mail/form.php?queue=Aggregator&lang=en',
    616229                        '_blank', 'height=560,width=370');
    617230        },
     
    656269});
    657270
    658 var _ = _ || {
    659         keys: function() {
    660                 var ret = [];
    661                 for (var x in o) {
    662                         if (o.hasOwnProperty(x)) {
    663                                 ret.push(x);
    664                         }
    665                 }
    666                 return ret;
    667         },
    668 
    669         pairs: function(o){
    670                 var ret = [];
    671                 for (var x in o) {
    672                         if (o.hasOwnProperty(x)) {
    673                                 ret.push([x, o[x]]);
    674                         }
    675                 }
    676                 return ret;
    677         },
    678 };
    679 
    680 
    681271React.render(React.createElement(Main, null), document.getElementById('reactMain') );
    682272})();
  • SRUAggregator/trunk/src/main/resources/assets/js/main.jsx

    r5900 r5919  
    55var PT = React.PropTypes;
    66
    7 var SearchBox = window.MyAggregator.SearchBox;
    8 var CorpusSelection = window.MyAggregator.CorpusSelection;
    9 var HitNumber = window.MyAggregator.HitNumber;
    10 var Results = window.MyAggregator.Results;
    11 var CorpusView = window.MyAggregator.CorpusView;
    12 var Modal = window.MyReact.Modal;
    137var ErrorPane = window.MyReact.ErrorPane;
    14 
    15 var multipleLanguageCode = "mul"; // see ISO-693-3
    16 
    17 var layers = [
    18         {
    19                 id: "sampa",
    20                 name: "Phonetics Resources",
    21                 searchPlaceholder: "stA:z",
    22                 searchLabel: "SAMPA query",
    23                 searchLabelBkColor: "#eef",
    24         },
    25         {
    26                 id: "text",
    27                 name: "Text Resources",
    28                 searchPlaceholder: "Elephant",
    29                 searchLabel: "Search text",
    30                 searchLabelBkColor: "#fed",
    31         },
    32 ];
    33 var layerMap = {
    34         sampa: layers[0],
    35         text: layers[1],
    36 };
    37 
    38 function Corpora(corpora, updateFn) {
    39         var that = this;
    40         this.corpora = corpora;
    41         this.update = function() {
    42                 updateFn(that);
    43         };
    44        
    45         var sortFn = function(x, y) {
    46                 var r = x.institution.name.localeCompare(y.institution.name);
    47                 if (r !== 0) {
    48                         return r;
    49                 }
    50                 var t1 = x.title ? x.title : x.displayName;
    51                 var t2 = y.title ? y.title : y.displayName;
    52                 return t1.toLowerCase().localeCompare(t2.toLowerCase());
    53         };
    54 
    55         this.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); });
    56         this.corpora.sort(sortFn);
    57 
    58         this.recurse(function(corpus, index) {
    59                 corpus.visible = true; // visible in the corpus view
    60                 corpus.selected = true; // selected in the corpus view
    61                 corpus.expanded = false; // not expanded in the corpus view
    62                 corpus.priority = 1; // priority in corpus view
    63                 corpus.index = index;
    64         });
    65 }
    66 
    67 Corpora.prototype.recurseCorpus = function(corpus, fn) {
    68         if (false === fn(corpus)) {             
    69                 // no recursion
    70         } else {
    71                 this.recurseCorpora(corpus.subCorpora, fn);
    72         }
    73 };
    74 
    75 Corpora.prototype.recurseCorpora = function(corpora, fn) {
    76         var recfn = function(corpus, index){
    77                 if (false === fn(corpus)) {
    78                         // no recursion
    79                 } else {
    80                         corpus.subCorpora.forEach(recfn);
    81                 }
    82         };
    83         corpora.forEach(recfn);
    84 };
    85 
    86 Corpora.prototype.recurse = function(fn) {
    87         this.recurseCorpora(this.corpora, fn);
    88 };
    89 
    90 Corpora.prototype.getLanguageCodes = function() {
    91         var languages = {};
    92         this.recurse(function(corpus) {
    93                 corpus.languages.forEach(function(lang) {
    94                         languages[lang] = true;
    95                 });
    96                 return true;
    97         });
    98         return languages;
    99 };
    100 
    101 Corpora.prototype.isCorpusVisible = function(corpus, layerId, languageCode) {
    102         if (layerId !== "text") {
    103                 return false;
    104         }
    105         // yes for any language
    106         if (languageCode === multipleLanguageCode) {
    107                 return true;
    108         }
    109         // yes if the corpus is in only that language
    110         if (corpus.languages && corpus.languages.length === 1 && corpus.languages[0] === languageCode) {
    111                 return true;
    112         }       
    113 
    114         // ? yes if the corpus also contains that language
    115         if (corpus.languages && corpus.languages.indexOf(languageCode) >=0) {
    116                 return true;
    117         }
    118 
    119         // ? yes if the corpus has no language
    120         // if (!corpus.languages || corpus.languages.length === 0) {
    121         //      return true;
    122         // }
    123         return false;
    124 };
    125 
    126 Corpora.prototype.setVisibility = function(layerId, languageCode) {
    127         // top level
    128         this.corpora.forEach(function(corpus) {
    129                 corpus.visible = this.isCorpusVisible(corpus, layerId, languageCode);
    130                 this.recurseCorpora(corpus.subCorpora, function(c) { c.visible = corpus.visible; });
    131         }.bind(this));
    132 };
    133 
    134 Corpora.prototype.getSelectedIds = function() {
    135         var ids = [];
    136         this.recurse(function(corpus) {
    137                 if (corpus.visible && corpus.selected) {
    138                         ids.push(corpus.id);
    139                         return false; // top-most collection in tree, don't delve deeper
    140                 }
    141                 return true;
    142         });
    143 
    144         // console.log("ids: ", ids.length, {ids:ids});
    145         return ids;
    146 };
    147 
    148 Corpora.prototype.getSelectedMessage = function() {
    149         var selected = this.getSelectedIds().length;
    150         if (this.corpora.length === selected) {
    151                 return "All available collections";
    152         } else if (selected === 1) {
    153                 return "1 selected collection";
    154         }
    155         return selected+" selected collections";
    156 };
    157 
     8var AggregatorPage = window.MyAggregator.AggregatorPage;
    1589
    15910var Main = React.createClass({
     
    16314                        navbarPageFn: this.renderAggregator,
    16415                        errorMessages: [],
    165 
    166                         corpora: new Corpora([], this.updateCorpora),
    167                         languageMap: {},
    16816                };
    169         },
    170 
    171         componentDidMount: function() {
    172                 this.refreshCorpora();
    173                 this.refreshLanguages();
    17417        },
    17518
     
    21457        },
    21558
    216         refreshCorpora: function() {
    217                 this.ajax({
    218                         url: 'rest/corpora',
    219                         success: function(json, textStatus, jqXHR) {
    220                                 this.setState({corpora : new Corpora(json, this.updateCorpora)});
    221                         }.bind(this),
    222                 });
    223         },
    224 
    225         refreshLanguages: function() {
    226                 this.ajax({
    227                         url: 'rest/languages',
    228                         success: function(json, textStatus, jqXHR) {
    229                                 this.setState({languageMap : json});
    230                         }.bind(this),
    231                 });
    232         },
    233 
    234         updateCorpora: function(corpora) {
    235                 this.setState({corpora:corpora});
    236         },
    237 
    23859        renderAggregator: function() {
    23960                return <AggregatorPage ajax={this.ajax} corpora={this.state.corpora} languageMap={this.state.languageMap} />;
     
    26485                                                <a className="link" tabIndex="-1"
    26586                                                        onClick={this.setNavbarPageFn.bind(this, this.renderAggregator)}>Aggregator</a>
    266                                         </li>
    267                                         <li className={this.state.navbarPageFn === this.renderStatistics ? "active":""}>
    268                                                 <a className="link" tabIndex="-1"
    269                                                         onClick={this.setNavbarPageFn.bind(this, this.renderStatistics)}>Statistics</a>
    27087                                        </li>
    27188                                        <li className={this.state.navbarPageFn === this.renderHelp ? "active":""}>
     
    320137});
    321138
    322 var AggregatorPage = React.createClass({
    323         propTypes: {
    324                 ajax: PT.func.isRequired,
    325                 corpora: PT.object.isRequired,
    326                 languageMap: PT.object.isRequired,
    327         },
    328 
    329         mixins: [React.addons.LinkedStateMixin],
    330         timeout: 0,
    331         nohits: {
    332                 requests: [],
    333                 results: [],
    334         },
    335         anyLanguage: [multipleLanguageCode, "Any Language"],
    336 
    337         getInitialState: function () {
    338                 return {
    339                         searchLayerId: "text",
    340                         language: this.anyLanguage,
    341                         numberOfResults: 10,
    342 
    343                         searchId: null,
    344                         hits: this.nohits,
    345                 };
    346         },
    347 
    348         search: function(query) {
    349                 // console.log(query);
    350                 if (!query) {
    351                         this.setState({ hits: this.nohits, searchId: null });
    352                         return;                 
    353                 }
    354                 this.props.ajax({
    355                         url: 'rest/search',
    356                         type: "POST",
    357                         data: {
    358                                 layer: this.state.searchLayerId,
    359                                 language: this.state.language[0],
    360                                 query: query,
    361                                 numberOfResults: this.state.numberOfResults,
    362                                 corporaIds: this.props.corpora.getSelectedIds(),
    363                         },
    364                         success: function(searchId, textStatus, jqXHR) {
    365                                 // console.log("search ["+query+"] ok: ", searchId, jqXHR);
    366                                 this.setState({searchId : searchId});
    367                                 this.timeout = 250;
    368                                 setTimeout(this.refreshSearchResults, this.timeout);
    369                         }.bind(this),
    370                 });
    371         },
    372 
    373         refreshSearchResults: function() {
    374                 if (!this.state.searchId) {
    375                         return;
    376                 }
    377                 this.props.ajax({
    378                         url: 'rest/search/'+this.state.searchId,
    379                         success: function(json, textStatus, jqXHR) {
    380                                 if (json.requests.length > 0) {
    381                                         if (this.timeout < 10000) {
    382                                                 this.timeout = 1.5 * this.timeout;
    383                                         }
    384                                         setTimeout(this.refreshSearchResults, this.timeout);
    385                                         // console.log("new search in: " + this.timeout+ "ms");
    386                                 } else {
    387                                         // console.log("search ended");
    388                                 }
    389                                 this.setState({hits:json});
    390                                 // console.log("hits:", json);
    391                         }.bind(this),
    392                 });
    393         },
    394 
    395         setLanguage: function(languageObj) {
    396                 this.props.corpora.setVisibility(this.state.searchLayerId, languageObj[0]);
    397                 this.setState({language: languageObj});
    398                 this.props.corpora.update();
    399         },
    400 
    401         setLayer: function(layerId) {
    402                 this.props.corpora.setVisibility(layerId, this.state.language[0]);
    403                 this.props.corpora.update();
    404                 this.setState({searchLayerId: layerId});
    405         },
    406 
    407         setNumberOfResults: function(e) {
    408                 var n = e.target.value;
    409                 if (n < 10) n = 10;
    410                 if (n > 250) n = 250;
    411                 this.setState({numberOfResults: n});
    412                 e.preventDefault();
    413                 e.stopPropagation();
    414         },
    415 
    416         stop: function(e) {
    417                 e.preventDefault();
    418                 e.stopPropagation();
    419         },
    420 
    421         toggleCorpusSelection: function(e) {
    422                 $(this.refs.corporaModal.getDOMNode()).modal();
    423                 e.preventDefault();
    424                 e.stopPropagation();
    425         },
    426 
    427         renderAggregator: function() {
    428                 var layer = layerMap[this.state.searchLayerId];
    429                 return  (
    430                         <div className="top-gap">
    431                                 <div className="row">
    432                                         <div className="aligncenter" style={{marginLeft:16, marginRight:16}}>
    433                                                 <div className="input-group">
    434                                                         <span className="input-group-addon" style={{backgroundColor:layer.searchLabelBkColor}}>
    435                                                                 {layer.searchLabel}
    436                                                         </span>
    437 
    438                                                         <SearchBox search={this.search} placeholder={layer.searchPlaceholder} />
    439                                                         <div className="input-group-btn">
    440                                                                 <button className="btn btn-default input-lg" type="button" onClick={this.search}>
    441                                                                         <i className="glyphicon glyphicon-search"></i>
    442                                                                 </button>
    443                                                         </div>
    444                                                 </div>
    445                                         </div>
    446                                 </div>
    447 
    448                                 <div className="wel" style={{marginTop:20}}>
    449                                         <div className="aligncenter" >
    450                                                 <form className="form-inline" role="form">
    451 
    452                                                         <div className="input-group" style={{marginRight:10}}>
    453                                                                 <span className="input-group-addon nobkg">Search in</span>
    454                                                                         <button type="button" className="btn btn-default" onClick={this.toggleCorpusSelection}>
    455                                                                                 {this.props.corpora.getSelectedMessage()} <span className="caret"/>
    456                                                                         </button>
    457                                                         </div>
    458 
    459                                                         <div className="input-group" style={{marginRight:10}}>
    460                                                                
    461                                                                 <span className="input-group-addon nobkg" >of</span>
    462                                                                
    463                                                                 <div className="input-group-btn">
    464                                                                         <button className="form-control btn btn-default"
    465                                                                                         aria-expanded="false" data-toggle="dropdown">
    466                                                                                 {this.state.language[1]} <span className="caret"/>
    467                                                                         </button>
    468                                                                         <ul ref="languageDropdownMenu" className="dropdown-menu">
    469                                                                                 <li key={this.anyLanguage[0]}> <a tabIndex="-1" href="#"
    470                                                                                                 onClick={this.setLanguage.bind(this, this.anyLanguage)}>
    471                                                                                         {this.anyLanguage[1]}</a>
    472                                                                                 </li>
    473                                                                                 {       _.pairs(this.props.languageMap).sort(function(l1, l2){
    474                                                                                                 return l1[1].localeCompare(l2[1]);
    475                                                                                         }).map(function(l) {
    476                                                                                                 var desc = l[1] + " [" + l[0] + "]";
    477                                                                                                 return <li key={l[0]}> <a tabIndex="-1" href="#"
    478                                                                                                         onClick={this.setLanguage.bind(this, l)}>{desc}</a></li>;
    479                                                                                         }.bind(this))
    480                                                                                 }
    481                                                                         </ul>
    482                                                                 </div>
    483 
    484                                                                 <div className="input-group-btn">
    485                                                                         <ul ref="layerDropdownMenu" className="dropdown-menu">
    486                                                                                 {       layers.map(function(l) {
    487                                                                                                 return <li key={l.id}> <a tabIndex="-1" href="#"
    488                                                                                                         onClick={this.setLayer.bind(this, l.id)}> {l.name} </a></li>;
    489                                                                                         }.bind(this))
    490                                                                                 }
    491                                                                         </ul>                                                           
    492                                                                         <button className="form-control btn btn-default"
    493                                                                                         aria-expanded="false" data-toggle="dropdown" >
    494                                                                                 {layer.name} <span className="caret"/>
    495                                                                         </button>
    496                                                                 </div>
    497 
    498                                                         </div>
    499 
    500                                                         <div className="input-group">
    501                                                                 <span className="input-group-addon nobkg">and show up to</span>
    502                                                                 <div className="input-group-btn">
    503                                                                         <input type="number" className="form-control input" min="10" max="250" step="5"
    504                                                                                 onChange={this.setNumberOfResults} value={this.state.numberOfResults}
    505                                                                                 onKeyPress={this.stop}/>
    506                                                                 </div>
    507                                                                 <span className="input-group-addon nobkg">hits</span>
    508                                                         </div>
    509                                                 </form>
    510                                         </div>
    511                                 </div>
    512 
    513                     <Modal ref="corporaModal" title="Collections">
    514                                         <CorpusView corpora={this.props.corpora} languageMap={this.props.languageMap} />
    515                     </Modal>
    516 
    517                                 <div className="top-gap">
    518                                         <Results requests={this.state.hits.requests} results={this.state.hits.results} />
    519                                 </div>
    520                         </div>
    521                         );
    522         },
    523         render: function() {
    524                 return this.renderAggregator();
    525         }
    526 });
     139
    527140
    528141var StatisticsPage = React.createClass({
     
    613226var HelpPage = React.createClass({
    614227        openHelpDesk: function() {
    615                 window.open('http://support.clarin-d.de/mail/form.php?queue=Aggregator',
     228                window.open('http://support.clarin-d.de/mail/form.php?queue=Aggregator&lang=en',
    616229                        '_blank', 'height=560,width=370');
    617230        },
     
    656269});
    657270
    658 var _ = _ || {
    659         keys: function() {
    660                 var ret = [];
    661                 for (var x in o) {
    662                         if (o.hasOwnProperty(x)) {
    663                                 ret.push(x);
    664                         }
    665                 }
    666                 return ret;
    667         },
    668 
    669         pairs: function(o){
    670                 var ret = [];
    671                 for (var x in o) {
    672                         if (o.hasOwnProperty(x)) {
    673                                 ret.push([x, o[x]]);
    674                         }
    675                 }
    676                 return ret;
    677         },
    678 };
    679 
    680 
    681271React.render(<Main />, document.getElementById('reactMain') );
    682272})();
  • SRUAggregator/trunk/src/main/resources/assets/js/search.js

    r5900 r5919  
    33"use strict";
    44
     5window.MyAggregator = window.MyAggregator || {};
     6
    57var React = window.React;
    68var PT = React.PropTypes;
    7 var ReactCSSTransitionGroup = window.React.addons.CSSTransitionGroup;
    8 // own components
     9var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
     10
     11var CorpusSelection = window.MyAggregator.CorpusSelection;
     12var HitNumber = window.MyAggregator.HitNumber;
     13var CorpusView = window.MyAggregator.CorpusView;
    914var InfoPopover = window.MyReact.InfoPopover;
    1015var Panel = window.MyReact.Panel;
     16var Modal = window.MyReact.Modal;
     17
     18var multipleLanguageCode = "mul"; // see ISO-693-3
     19
     20var layers = [
     21        {
     22                id: "sampa",
     23                name: "Phonetic Transcriptions",
     24                searchPlaceholder: "stA:z",
     25                searchLabel: "SAMPA query",
     26                searchLabelBkColor: "#eef",
     27        },
     28        {
     29                id: "text",
     30                name: "Text Resources",
     31                searchPlaceholder: "Elephant",
     32                searchLabel: "Search text",
     33                searchLabelBkColor: "#fed",
     34        },
     35];
     36var layerMap = {
     37        sampa: layers[0],
     38        text: layers[1],
     39};
     40
     41function Corpora(corpora, updateFn) {
     42        var that = this;
     43        this.corpora = corpora;
     44        this.update = function() {
     45                updateFn(that);
     46        };
     47       
     48        var sortFn = function(x, y) {
     49                var r = x.institution.name.localeCompare(y.institution.name);
     50                if (r !== 0) {
     51                        return r;
     52                }
     53                var t1 = x.title ? x.title : x.displayName;
     54                var t2 = y.title ? y.title : y.displayName;
     55                return t1.toLowerCase().localeCompare(t2.toLowerCase());
     56        };
     57
     58        this.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); });
     59        this.corpora.sort(sortFn);
     60
     61        this.recurse(function(corpus, index) {
     62                corpus.visible = true; // visible in the corpus view
     63                corpus.selected = true; // selected in the corpus view
     64                corpus.expanded = false; // not expanded in the corpus view
     65                corpus.priority = 1; // priority in corpus view
     66                corpus.index = index;
     67        });
     68}
     69
     70Corpora.prototype.recurseCorpus = function(corpus, fn) {
     71        if (false === fn(corpus)) {             
     72                // no recursion
     73        } else {
     74                this.recurseCorpora(corpus.subCorpora, fn);
     75        }
     76};
     77
     78Corpora.prototype.recurseCorpora = function(corpora, fn) {
     79        var recfn = function(corpus, index){
     80                if (false === fn(corpus)) {
     81                        // no recursion
     82                } else {
     83                        corpus.subCorpora.forEach(recfn);
     84                }
     85        };
     86        corpora.forEach(recfn);
     87};
     88
     89Corpora.prototype.recurse = function(fn) {
     90        this.recurseCorpora(this.corpora, fn);
     91};
     92
     93Corpora.prototype.getLanguageCodes = function() {
     94        var languages = {};
     95        this.recurse(function(corpus) {
     96                corpus.languages.forEach(function(lang) {
     97                        languages[lang] = true;
     98                });
     99                return true;
     100        });
     101        return languages;
     102};
     103
     104Corpora.prototype.isCorpusVisible = function(corpus, layerId, languageCode) {
     105        if (layerId !== "text") {
     106                return false;
     107        }
     108        // yes for any language
     109        if (languageCode === multipleLanguageCode) {
     110                return true;
     111        }
     112        // yes if the corpus is in only that language
     113        if (corpus.languages && corpus.languages.length === 1 && corpus.languages[0] === languageCode) {
     114                return true;
     115        }       
     116
     117        // ? yes if the corpus also contains that language
     118        if (corpus.languages && corpus.languages.indexOf(languageCode) >=0) {
     119                return true;
     120        }
     121
     122        // ? yes if the corpus has no language
     123        // if (!corpus.languages || corpus.languages.length === 0) {
     124        //      return true;
     125        // }
     126        return false;
     127};
     128
     129Corpora.prototype.setVisibility = function(layerId, languageCode) {
     130        // top level
     131        this.corpora.forEach(function(corpus) {
     132                corpus.visible = this.isCorpusVisible(corpus, layerId, languageCode);
     133                this.recurseCorpora(corpus.subCorpora, function(c) { c.visible = corpus.visible; });
     134        }.bind(this));
     135};
     136
     137Corpora.prototype.getSelectedIds = function() {
     138        var ids = [];
     139        this.recurse(function(corpus) {
     140                if (corpus.visible && corpus.selected) {
     141                        ids.push(corpus.id);
     142                        return false; // top-most collection in tree, don't delve deeper
     143                }
     144                return true;
     145        });
     146
     147        // console.log("ids: ", ids.length, {ids:ids});
     148        return ids;
     149};
     150
     151Corpora.prototype.getSelectedMessage = function() {
     152        var selected = this.getSelectedIds().length;
     153        if (this.corpora.length === selected) {
     154                return "All available collections";
     155        } else if (selected === 1) {
     156                return "1 selected collection";
     157        }
     158        return selected+" selected collections";
     159};
     160
     161
     162var AggregatorPage = window.MyAggregator.AggregatorPage = React.createClass({displayName: 'AggregatorPage',
     163        propTypes: {
     164                ajax: PT.func.isRequired
     165        },
     166
     167        mixins: [React.addons.LinkedStateMixin],
     168        timeout: 0,
     169        nohits: {
     170                requests: [],
     171                results: [],
     172        },
     173        anyLanguage: [multipleLanguageCode, "Any Language"],
     174
     175        getInitialState: function () {
     176                return {
     177                        corpora: new Corpora([], this.updateCorpora),
     178                        languageMap: {},
     179                        language: this.anyLanguage,
     180                        searchLayerId: "text",
     181                        numberOfResults: 10,
     182
     183                        searchId: null,
     184                        hits: this.nohits,
     185                };
     186        },
     187
     188        componentDidMount: function() {
     189                this.refreshCorpora();
     190                this.refreshLanguages();
     191        },
     192
     193        refreshCorpora: function() {
     194                this.props.ajax({
     195                        url: 'rest/corpora',
     196                        success: function(json, textStatus, jqXHR) {
     197                                this.setState({corpora : new Corpora(json, this.updateCorpora)});
     198                        }.bind(this),
     199                });
     200        },
     201
     202        refreshLanguages: function() {
     203                this.props.ajax({
     204                        url: 'rest/languages',
     205                        success: function(json, textStatus, jqXHR) {
     206                                this.setState({languageMap : json});
     207                        }.bind(this),
     208                });
     209        },
     210
     211        updateCorpora: function(corpora) {
     212                this.setState({corpora:corpora});
     213        },
     214
     215        search: function(query) {
     216                // console.log(query);
     217                if (!query) {
     218                        this.setState({ hits: this.nohits, searchId: null });
     219                        return;                 
     220                }
     221                this.props.ajax({
     222                        url: 'rest/search',
     223                        type: "POST",
     224                        data: {
     225                                layer: this.state.searchLayerId,
     226                                language: this.state.language[0],
     227                                query: query,
     228                                numberOfResults: this.state.numberOfResults,
     229                                corporaIds: this.state.corpora.getSelectedIds(),
     230                        },
     231                        success: function(searchId, textStatus, jqXHR) {
     232                                // console.log("search ["+query+"] ok: ", searchId, jqXHR);
     233                                this.setState({searchId : searchId});
     234                                this.timeout = 250;
     235                                setTimeout(this.refreshSearchResults, this.timeout);
     236                        }.bind(this),
     237                });
     238        },
     239
     240        refreshSearchResults: function() {
     241                if (!this.state.searchId) {
     242                        return;
     243                }
     244                this.props.ajax({
     245                        url: 'rest/search/'+this.state.searchId,
     246                        success: function(json, textStatus, jqXHR) {
     247                                if (json.requests.length > 0) {
     248                                        if (this.timeout < 10000) {
     249                                                this.timeout = 1.5 * this.timeout;
     250                                        }
     251                                        setTimeout(this.refreshSearchResults, this.timeout);
     252                                        // console.log("new search in: " + this.timeout+ "ms");
     253                                } else {
     254                                        // console.log("search ended");
     255                                }
     256                                this.setState({hits:json});
     257                                // console.log("hits:", json);
     258                        }.bind(this),
     259                });
     260        },
     261
     262        setLanguage: function(languageObj) {
     263                this.state.corpora.setVisibility(this.state.searchLayerId, languageObj[0]);
     264                this.setState({language: languageObj});
     265                this.state.corpora.update();
     266        },
     267
     268        setLayer: function(layerId) {
     269                this.state.corpora.setVisibility(layerId, this.state.language[0]);
     270                this.state.corpora.update();
     271                this.setState({searchLayerId: layerId});
     272        },
     273
     274        setNumberOfResults: function(e) {
     275                var n = e.target.value;
     276                if (n < 10) n = 10;
     277                if (n > 250) n = 250;
     278                this.setState({numberOfResults: n});
     279                e.preventDefault();
     280                e.stopPropagation();
     281        },
     282
     283        stop: function(e) {
     284                e.preventDefault();
     285                e.stopPropagation();
     286        },
     287
     288        toggleCorpusSelection: function(e) {
     289                $(this.refs.corporaModal.getDOMNode()).modal();
     290                e.preventDefault();
     291                e.stopPropagation();
     292        },
     293
     294        renderAggregator: function() {
     295                var layer = layerMap[this.state.searchLayerId];
     296                return  (
     297                        React.createElement("div", {className: "top-gap"},
     298                                React.createElement("div", {className: "row"},
     299                                        React.createElement("div", {className: "aligncenter", style: {marginLeft:16, marginRight:16}},
     300                                                React.createElement("div", {className: "input-group"},
     301                                                        React.createElement("span", {className: "input-group-addon", style: {backgroundColor:layer.searchLabelBkColor}},
     302                                                                layer.searchLabel
     303                                                        ),
     304
     305                                                        React.createElement(SearchBox, {search: this.search, placeholder: layer.searchPlaceholder}),
     306                                                        React.createElement("div", {className: "input-group-btn"},
     307                                                                React.createElement("button", {className: "btn btn-default input-lg", type: "button", onClick: this.search},
     308                                                                        React.createElement("i", {className: "glyphicon glyphicon-search"})
     309                                                                )
     310                                                        )
     311                                                )
     312                                        )
     313                                ),
     314
     315                                React.createElement("div", {className: "wel", style: {marginTop:20}},
     316                                        React.createElement("div", {className: "aligncenter"},
     317                                                React.createElement("form", {className: "form-inline", role: "form"},
     318
     319                                                        React.createElement("div", {className: "input-group"},
     320                                                               
     321                                                                React.createElement("span", {className: "input-group-addon nobkg"}, "Search for"),
     322                                                               
     323                                                                React.createElement("div", {className: "input-group-btn"},
     324                                                                        React.createElement("button", {className: "form-control btn btn-default",
     325                                                                                        'aria-expanded': "false", 'data-toggle': "dropdown"},
     326                                                                                this.state.language[1], " ", React.createElement("span", {className: "caret"})
     327                                                                        ),
     328                                                                        React.createElement("ul", {ref: "languageDropdownMenu", className: "dropdown-menu"},
     329                                                                                React.createElement("li", {key: this.anyLanguage[0]}, " ", React.createElement("a", {tabIndex: "-1", href: "#",
     330                                                                                                onClick: this.setLanguage.bind(this, this.anyLanguage)},
     331                                                                                        this.anyLanguage[1])
     332                                                                                ),
     333                                                                                        _.pairs(this.state.languageMap).sort(function(l1, l2){
     334                                                                                                return l1[1].localeCompare(l2[1]);
     335                                                                                        }).map(function(l) {
     336                                                                                                var desc = l[1] + " [" + l[0] + "]";
     337                                                                                                return React.createElement("li", {key: l[0]}, " ", React.createElement("a", {tabIndex: "-1", href: "#",
     338                                                                                                        onClick: this.setLanguage.bind(this, l)}, desc));
     339                                                                                        }.bind(this))
     340                                                                               
     341                                                                        )
     342                                                                ),
     343
     344                                                                React.createElement("div", {className: "input-group-btn"},
     345                                                                        React.createElement("ul", {ref: "layerDropdownMenu", className: "dropdown-menu"},
     346                                                                                        layers.map(function(l) {
     347                                                                                                return React.createElement("li", {key: l.id}, " ", React.createElement("a", {tabIndex: "-1", href: "#",
     348                                                                                                        onClick: this.setLayer.bind(this, l.id)}, " ", l.name, " "));
     349                                                                                        }.bind(this))
     350                                                                               
     351                                                                        ),                                                             
     352                                                                        React.createElement("button", {className: "form-control btn btn-default",
     353                                                                                        'aria-expanded': "false", 'data-toggle': "dropdown"},
     354                                                                                layer.name, " ", React.createElement("span", {className: "caret"})
     355                                                                        )
     356                                                                )
     357
     358                                                        ),
     359
     360                                                        React.createElement("div", {className: "input-group"},
     361                                                                React.createElement("span", {className: "input-group-addon nobkg"}, "in"),
     362                                                                        React.createElement("button", {type: "button", className: "btn btn-default", onClick: this.toggleCorpusSelection},
     363                                                                                this.state.corpora.getSelectedMessage(), " ", React.createElement("span", {className: "caret"})
     364                                                                        )
     365                                                        ),                                                     
     366
     367                                                        React.createElement("div", {className: "input-group"},
     368                                                                React.createElement("span", {className: "input-group-addon nobkg"}, "and show up to"),
     369                                                                React.createElement("div", {className: "input-group-btn"},
     370                                                                        React.createElement("input", {type: "number", className: "form-control input", min: "10", max: "250", step: "5",
     371                                                                                style: {width:54},
     372                                                                                onChange: this.setNumberOfResults, value: this.state.numberOfResults,
     373                                                                                onKeyPress: this.stop})
     374                                                                ),
     375                                                                React.createElement("span", {className: "input-group-addon nobkg"}, "hits")
     376                                                        )
     377                                                )
     378                                        )
     379                                ),
     380
     381                    React.createElement(Modal, {ref: "corporaModal", title: "Collections"},
     382                                        React.createElement(CorpusView, {corpora: this.state.corpora, languageMap: this.state.languageMap})
     383                    ),
     384
     385                                React.createElement("div", {className: "top-gap"},
     386                                        React.createElement(Results, {requests: this.state.hits.requests, results: this.state.hits.results})
     387                                )
     388                        )
     389                        );
     390        },
     391        render: function() {
     392                return this.renderAggregator();
     393        }
     394});
     395
    11396
    12397
     
    67452        },
    68453
     454        renderRowLanguage: function(hit) {
     455                return React.createElement("span", {style: {fontFace:"Courier",color:"black"}}, hit.language);
     456        },
     457
    69458        renderRowsAsHits: function(hit,i) {
    70459                function renderTextFragments(tf, idx) {
     
    72461                }
    73462                return  React.createElement("p", {key: i, className: "hitrow"},
     463                                        this.renderRowLanguage(hit),
    74464                                        hit.fragments.map(renderTextFragments)
    75465                                );
     
    81471                var sright={textAlign:"right", verticalAlign:"middle", maxWidth:"50%"};
    82472                return  React.createElement("tr", {key: i, className: "hitrow"},
     473                                        React.createElement("td", null, this.renderRowLanguage(hit)),
    83474                                        React.createElement("td", {style: sright}, hit.left),
    84475                                        React.createElement("td", {style: scenter, className: "keyword"}, hit.keyword),
     
    208599});
    209600
    210 if (!window.MyAggregator) {
    211         window.MyAggregator = {};
    212 }
    213 window.MyAggregator.SearchBox = SearchBox;
    214 window.MyAggregator.Results = Results;
     601var _ = window._ = window._ || {
     602        keys: function() {
     603                var ret = [];
     604                for (var x in o) {
     605                        if (o.hasOwnProperty(x)) {
     606                                ret.push(x);
     607                        }
     608                }
     609                return ret;
     610        },
     611
     612        pairs: function(o){
     613                var ret = [];
     614                for (var x in o) {
     615                        if (o.hasOwnProperty(x)) {
     616                                ret.push([x, o[x]]);
     617                        }
     618                }
     619                return ret;
     620        },
     621};
     622
    215623})();
  • SRUAggregator/trunk/src/main/resources/assets/js/search.jsx

    r5900 r5919  
    33"use strict";
    44
     5window.MyAggregator = window.MyAggregator || {};
     6
    57var React = window.React;
    68var PT = React.PropTypes;
    7 var ReactCSSTransitionGroup = window.React.addons.CSSTransitionGroup;
    8 // own components
     9var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
     10
     11var CorpusSelection = window.MyAggregator.CorpusSelection;
     12var HitNumber = window.MyAggregator.HitNumber;
     13var CorpusView = window.MyAggregator.CorpusView;
    914var InfoPopover = window.MyReact.InfoPopover;
    1015var Panel = window.MyReact.Panel;
     16var Modal = window.MyReact.Modal;
     17
     18var multipleLanguageCode = "mul"; // see ISO-693-3
     19
     20var layers = [
     21        {
     22                id: "sampa",
     23                name: "Phonetic Transcriptions",
     24                searchPlaceholder: "stA:z",
     25                searchLabel: "SAMPA query",
     26                searchLabelBkColor: "#eef",
     27        },
     28        {
     29                id: "text",
     30                name: "Text Resources",
     31                searchPlaceholder: "Elephant",
     32                searchLabel: "Search text",
     33                searchLabelBkColor: "#fed",
     34        },
     35];
     36var layerMap = {
     37        sampa: layers[0],
     38        text: layers[1],
     39};
     40
     41function Corpora(corpora, updateFn) {
     42        var that = this;
     43        this.corpora = corpora;
     44        this.update = function() {
     45                updateFn(that);
     46        };
     47       
     48        var sortFn = function(x, y) {
     49                var r = x.institution.name.localeCompare(y.institution.name);
     50                if (r !== 0) {
     51                        return r;
     52                }
     53                var t1 = x.title ? x.title : x.displayName;
     54                var t2 = y.title ? y.title : y.displayName;
     55                return t1.toLowerCase().localeCompare(t2.toLowerCase());
     56        };
     57
     58        this.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); });
     59        this.corpora.sort(sortFn);
     60
     61        this.recurse(function(corpus, index) {
     62                corpus.visible = true; // visible in the corpus view
     63                corpus.selected = true; // selected in the corpus view
     64                corpus.expanded = false; // not expanded in the corpus view
     65                corpus.priority = 1; // priority in corpus view
     66                corpus.index = index;
     67        });
     68}
     69
     70Corpora.prototype.recurseCorpus = function(corpus, fn) {
     71        if (false === fn(corpus)) {             
     72                // no recursion
     73        } else {
     74                this.recurseCorpora(corpus.subCorpora, fn);
     75        }
     76};
     77
     78Corpora.prototype.recurseCorpora = function(corpora, fn) {
     79        var recfn = function(corpus, index){
     80                if (false === fn(corpus)) {
     81                        // no recursion
     82                } else {
     83                        corpus.subCorpora.forEach(recfn);
     84                }
     85        };
     86        corpora.forEach(recfn);
     87};
     88
     89Corpora.prototype.recurse = function(fn) {
     90        this.recurseCorpora(this.corpora, fn);
     91};
     92
     93Corpora.prototype.getLanguageCodes = function() {
     94        var languages = {};
     95        this.recurse(function(corpus) {
     96                corpus.languages.forEach(function(lang) {
     97                        languages[lang] = true;
     98                });
     99                return true;
     100        });
     101        return languages;
     102};
     103
     104Corpora.prototype.isCorpusVisible = function(corpus, layerId, languageCode) {
     105        if (layerId !== "text") {
     106                return false;
     107        }
     108        // yes for any language
     109        if (languageCode === multipleLanguageCode) {
     110                return true;
     111        }
     112        // yes if the corpus is in only that language
     113        if (corpus.languages && corpus.languages.length === 1 && corpus.languages[0] === languageCode) {
     114                return true;
     115        }       
     116
     117        // ? yes if the corpus also contains that language
     118        if (corpus.languages && corpus.languages.indexOf(languageCode) >=0) {
     119                return true;
     120        }
     121
     122        // ? yes if the corpus has no language
     123        // if (!corpus.languages || corpus.languages.length === 0) {
     124        //      return true;
     125        // }
     126        return false;
     127};
     128
     129Corpora.prototype.setVisibility = function(layerId, languageCode) {
     130        // top level
     131        this.corpora.forEach(function(corpus) {
     132                corpus.visible = this.isCorpusVisible(corpus, layerId, languageCode);
     133                this.recurseCorpora(corpus.subCorpora, function(c) { c.visible = corpus.visible; });
     134        }.bind(this));
     135};
     136
     137Corpora.prototype.getSelectedIds = function() {
     138        var ids = [];
     139        this.recurse(function(corpus) {
     140                if (corpus.visible && corpus.selected) {
     141                        ids.push(corpus.id);
     142                        return false; // top-most collection in tree, don't delve deeper
     143                }
     144                return true;
     145        });
     146
     147        // console.log("ids: ", ids.length, {ids:ids});
     148        return ids;
     149};
     150
     151Corpora.prototype.getSelectedMessage = function() {
     152        var selected = this.getSelectedIds().length;
     153        if (this.corpora.length === selected) {
     154                return "All available collections";
     155        } else if (selected === 1) {
     156                return "1 selected collection";
     157        }
     158        return selected+" selected collections";
     159};
     160
     161
     162var AggregatorPage = window.MyAggregator.AggregatorPage = React.createClass({
     163        propTypes: {
     164                ajax: PT.func.isRequired
     165        },
     166
     167        mixins: [React.addons.LinkedStateMixin],
     168        timeout: 0,
     169        nohits: {
     170                requests: [],
     171                results: [],
     172        },
     173        anyLanguage: [multipleLanguageCode, "Any Language"],
     174
     175        getInitialState: function () {
     176                return {
     177                        corpora: new Corpora([], this.updateCorpora),
     178                        languageMap: {},
     179                        language: this.anyLanguage,
     180                        searchLayerId: "text",
     181                        numberOfResults: 10,
     182
     183                        searchId: null,
     184                        hits: this.nohits,
     185                };
     186        },
     187
     188        componentDidMount: function() {
     189                this.refreshCorpora();
     190                this.refreshLanguages();
     191        },
     192
     193        refreshCorpora: function() {
     194                this.props.ajax({
     195                        url: 'rest/corpora',
     196                        success: function(json, textStatus, jqXHR) {
     197                                this.setState({corpora : new Corpora(json, this.updateCorpora)});
     198                        }.bind(this),
     199                });
     200        },
     201
     202        refreshLanguages: function() {
     203                this.props.ajax({
     204                        url: 'rest/languages',
     205                        success: function(json, textStatus, jqXHR) {
     206                                this.setState({languageMap : json});
     207                        }.bind(this),
     208                });
     209        },
     210
     211        updateCorpora: function(corpora) {
     212                this.setState({corpora:corpora});
     213        },
     214
     215        search: function(query) {
     216                // console.log(query);
     217                if (!query) {
     218                        this.setState({ hits: this.nohits, searchId: null });
     219                        return;                 
     220                }
     221                this.props.ajax({
     222                        url: 'rest/search',
     223                        type: "POST",
     224                        data: {
     225                                layer: this.state.searchLayerId,
     226                                language: this.state.language[0],
     227                                query: query,
     228                                numberOfResults: this.state.numberOfResults,
     229                                corporaIds: this.state.corpora.getSelectedIds(),
     230                        },
     231                        success: function(searchId, textStatus, jqXHR) {
     232                                // console.log("search ["+query+"] ok: ", searchId, jqXHR);
     233                                this.setState({searchId : searchId});
     234                                this.timeout = 250;
     235                                setTimeout(this.refreshSearchResults, this.timeout);
     236                        }.bind(this),
     237                });
     238        },
     239
     240        refreshSearchResults: function() {
     241                if (!this.state.searchId) {
     242                        return;
     243                }
     244                this.props.ajax({
     245                        url: 'rest/search/'+this.state.searchId,
     246                        success: function(json, textStatus, jqXHR) {
     247                                if (json.requests.length > 0) {
     248                                        if (this.timeout < 10000) {
     249                                                this.timeout = 1.5 * this.timeout;
     250                                        }
     251                                        setTimeout(this.refreshSearchResults, this.timeout);
     252                                        // console.log("new search in: " + this.timeout+ "ms");
     253                                } else {
     254                                        // console.log("search ended");
     255                                }
     256                                this.setState({hits:json});
     257                                // console.log("hits:", json);
     258                        }.bind(this),
     259                });
     260        },
     261
     262        setLanguage: function(languageObj) {
     263                this.state.corpora.setVisibility(this.state.searchLayerId, languageObj[0]);
     264                this.setState({language: languageObj});
     265                this.state.corpora.update();
     266        },
     267
     268        setLayer: function(layerId) {
     269                this.state.corpora.setVisibility(layerId, this.state.language[0]);
     270                this.state.corpora.update();
     271                this.setState({searchLayerId: layerId});
     272        },
     273
     274        setNumberOfResults: function(e) {
     275                var n = e.target.value;
     276                if (n < 10) n = 10;
     277                if (n > 250) n = 250;
     278                this.setState({numberOfResults: n});
     279                e.preventDefault();
     280                e.stopPropagation();
     281        },
     282
     283        stop: function(e) {
     284                e.preventDefault();
     285                e.stopPropagation();
     286        },
     287
     288        toggleCorpusSelection: function(e) {
     289                $(this.refs.corporaModal.getDOMNode()).modal();
     290                e.preventDefault();
     291                e.stopPropagation();
     292        },
     293
     294        renderAggregator: function() {
     295                var layer = layerMap[this.state.searchLayerId];
     296                return  (
     297                        <div className="top-gap">
     298                                <div className="row">
     299                                        <div className="aligncenter" style={{marginLeft:16, marginRight:16}}>
     300                                                <div className="input-group">
     301                                                        <span className="input-group-addon" style={{backgroundColor:layer.searchLabelBkColor}}>
     302                                                                {layer.searchLabel}
     303                                                        </span>
     304
     305                                                        <SearchBox search={this.search} placeholder={layer.searchPlaceholder} />
     306                                                        <div className="input-group-btn">
     307                                                                <button className="btn btn-default input-lg" type="button" onClick={this.search}>
     308                                                                        <i className="glyphicon glyphicon-search"></i>
     309                                                                </button>
     310                                                        </div>
     311                                                </div>
     312                                        </div>
     313                                </div>
     314
     315                                <div className="wel" style={{marginTop:20}}>
     316                                        <div className="aligncenter" >
     317                                                <form className="form-inline" role="form">
     318
     319                                                        <div className="input-group">
     320                                                               
     321                                                                <span className="input-group-addon nobkg" >Search for</span>
     322                                                               
     323                                                                <div className="input-group-btn">
     324                                                                        <button className="form-control btn btn-default"
     325                                                                                        aria-expanded="false" data-toggle="dropdown">
     326                                                                                {this.state.language[1]} <span className="caret"/>
     327                                                                        </button>
     328                                                                        <ul ref="languageDropdownMenu" className="dropdown-menu">
     329                                                                                <li key={this.anyLanguage[0]}> <a tabIndex="-1" href="#"
     330                                                                                                onClick={this.setLanguage.bind(this, this.anyLanguage)}>
     331                                                                                        {this.anyLanguage[1]}</a>
     332                                                                                </li>
     333                                                                                {       _.pairs(this.state.languageMap).sort(function(l1, l2){
     334                                                                                                return l1[1].localeCompare(l2[1]);
     335                                                                                        }).map(function(l) {
     336                                                                                                var desc = l[1] + " [" + l[0] + "]";
     337                                                                                                return <li key={l[0]}> <a tabIndex="-1" href="#"
     338                                                                                                        onClick={this.setLanguage.bind(this, l)}>{desc}</a></li>;
     339                                                                                        }.bind(this))
     340                                                                                }
     341                                                                        </ul>
     342                                                                </div>
     343
     344                                                                <div className="input-group-btn">
     345                                                                        <ul ref="layerDropdownMenu" className="dropdown-menu">
     346                                                                                {       layers.map(function(l) {
     347                                                                                                return <li key={l.id}> <a tabIndex="-1" href="#"
     348                                                                                                        onClick={this.setLayer.bind(this, l.id)}> {l.name} </a></li>;
     349                                                                                        }.bind(this))
     350                                                                                }
     351                                                                        </ul>                                                           
     352                                                                        <button className="form-control btn btn-default"
     353                                                                                        aria-expanded="false" data-toggle="dropdown" >
     354                                                                                {layer.name} <span className="caret"/>
     355                                                                        </button>
     356                                                                </div>
     357
     358                                                        </div>
     359
     360                                                        <div className="input-group">
     361                                                                <span className="input-group-addon nobkg">in</span>
     362                                                                        <button type="button" className="btn btn-default" onClick={this.toggleCorpusSelection}>
     363                                                                                {this.state.corpora.getSelectedMessage()} <span className="caret"/>
     364                                                                        </button>
     365                                                        </div>                                                 
     366
     367                                                        <div className="input-group">
     368                                                                <span className="input-group-addon nobkg">and show up to</span>
     369                                                                <div className="input-group-btn">
     370                                                                        <input type="number" className="form-control input" min="10" max="250" step="5"
     371                                                                                style={{width:54}}
     372                                                                                onChange={this.setNumberOfResults} value={this.state.numberOfResults}
     373                                                                                onKeyPress={this.stop}/>
     374                                                                </div>
     375                                                                <span className="input-group-addon nobkg">hits</span>
     376                                                        </div>
     377                                                </form>
     378                                        </div>
     379                                </div>
     380
     381                    <Modal ref="corporaModal" title="Collections">
     382                                        <CorpusView corpora={this.state.corpora} languageMap={this.state.languageMap} />
     383                    </Modal>
     384
     385                                <div className="top-gap">
     386                                        <Results requests={this.state.hits.requests} results={this.state.hits.results} />
     387                                </div>
     388                        </div>
     389                        );
     390        },
     391        render: function() {
     392                return this.renderAggregator();
     393        }
     394});
     395
    11396
    12397
     
    67452        },
    68453
     454        renderRowLanguage: function(hit) {
     455                return <span style={{fontFace:"Courier",color:"black"}}>{hit.language}</span>;
     456        },
     457
    69458        renderRowsAsHits: function(hit,i) {
    70459                function renderTextFragments(tf, idx) {
     
    72461                }
    73462                return  <p key={i} className="hitrow">
     463                                        {this.renderRowLanguage(hit)}
    74464                                        {hit.fragments.map(renderTextFragments)}
    75465                                </p>;
     
    81471                var sright={textAlign:"right", verticalAlign:"middle", maxWidth:"50%"};
    82472                return  <tr key={i} className="hitrow">
     473                                        <td>{this.renderRowLanguage(hit)}</td>
    83474                                        <td style={sright}>{hit.left}</td>
    84475                                        <td style={scenter} className="keyword">{hit.keyword}</td>
     
    208599});
    209600
    210 if (!window.MyAggregator) {
    211         window.MyAggregator = {};
    212 }
    213 window.MyAggregator.SearchBox = SearchBox;
    214 window.MyAggregator.Results = Results;
     601var _ = window._ = window._ || {
     602        keys: function() {
     603                var ret = [];
     604                for (var x in o) {
     605                        if (o.hasOwnProperty(x)) {
     606                                ret.push(x);
     607                        }
     608                }
     609                return ret;
     610        },
     611
     612        pairs: function(o){
     613                var ret = [];
     614                for (var x in o) {
     615                        if (o.hasOwnProperty(x)) {
     616                                ret.push([x, o[x]]);
     617                        }
     618                }
     619                return ret;
     620        },
     621};
     622
    215623})();
Note: See TracChangeset for help on using the changeset viewer.