Changeset 5919 for SRUAggregator
- Timestamp:
- 01/15/15 16:01:26 (9 years ago)
- Location:
- SRUAggregator/trunk
- Files:
-
- 1 added
- 13 edited
Legend:
- Unmodified
- Added
- Removed
-
SRUAggregator/trunk/aggregator_development.yml
r5901 r5919 5 5 AGGREGATOR_FILE_PATH: /Users/edima/fcsAggregatorCorpora.json 6 6 SCAN_MAX_DEPTH: 3 7 SCAN_TASK_INITIAL_DELAY: 07 SCAN_TASK_INITIAL_DELAY: 12 8 8 SCAN_TASK_INTERVAL: 24 9 9 SCAN_TASK_TIME_UNIT: HOURS -
SRUAggregator/trunk/build.sh
r5893 r5919 3 3 ASSETDIR=src/main/resources/assets 4 4 LIBDIR=$ASSETDIR/lib 5 FONTDIR=$ASSETDIR/fonts 6 JSDIR=$ASSETDIR/js 5 7 6 8 if [ ! -e bower_components ] 7 9 then 10 mkdir -p $LIBDIR 11 mkdir -p $FONTDIR 12 mkdir -p $JSDIR 13 8 14 npm install bower react-tools 9 node_modules/bower/bin/bower install jquery bootstrap react react-addons font-awesome15 node_modules/bower/bin/bower install jquery bootstrap react react-addons react-router font-awesome 10 16 11 mkdir -p src/main/webapp/lib12 cp bower_components/bootstrap/dist/ css/bootstrap.min.css $LIBDIR13 cp bower_components/ bootstrap/dist/js/bootstrap.min.js $LIBDIR14 cp bower_components/ jquery/dist/jquery.min.js $LIBDIR15 cp bower_components/react/react-with-addons. js $LIBDIR16 cp bower_components/react /react-with-addons.min.js $LIBDIR17 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/ 18 24 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/ 22 27 fi 23 28 24 JSDIR=$ASSETDIR/js25 29 for f in $JSDIR/*.jsx; do 26 30 cp -v $f $JSDIR/`basename $f .jsx`.js; … … 28 32 node_modules/react-tools/bin/jsx --no-cache-dir $JSDIR $JSDIR 29 33 30 # mvn -q clean package34 # mvn -q clean package 31 35 32 36 # Run in production: … … 34 38 35 39 # 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.yml40 # 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 8 8 <groupId>eu.clarin.sru.fcs</groupId> 9 9 <artifactId>Aggregator2</artifactId> 10 <version>2.0.0-alpha-1 0</version>10 <version>2.0.0-alpha-11</version> 11 11 <name>FCS Aggregator</name> 12 12 … … 86 86 <version>1.5.3</version> 87 87 </dependency> 88 <dependency> 89 <groupId>com.optimaize.languagedetector</groupId> 90 <artifactId>language-detector</artifactId> 91 <version>0.4</version> 92 </dependency> 93 88 94 <dependency> 89 95 <groupId>com.googlecode.sardine</groupId> -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java
r5901 r5919 2 2 3 3 import com.fasterxml.jackson.databind.ObjectMapper; 4 import com.optimaize.langdetect.LanguageDetector; 5 import com.optimaize.langdetect.LanguageDetectorBuilder; 6 import com.optimaize.langdetect.ngram.NgramExtractors; 7 import com.optimaize.langdetect.profiles.LanguageProfile; 8 import com.optimaize.langdetect.profiles.LanguageProfileReader; 9 import com.optimaize.langdetect.text.*; 4 10 import eu.clarin.sru.fcs.aggregator.search.Search; 5 11 import eu.clarin.sru.fcs.aggregator.scan.ScanCrawlTask; … … 80 86 * @author edima 81 87 * 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 * 82 103 * TODO: version page: credits, open source, see vcr/version page 83 104 * 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 106 106 * 107 107 * TODO: 1. support new spec-compatible centres, see Oliver's mail 108 108 * (use SRUClient's extraResponseData POJOs) 109 109 * 110 * TODO: tri-state for parent collections; search + message implications111 *112 * TODO: disable popups easily113 *114 110 * TODO: 2. zoom into the results from a corpus, allow functionality only for 115 111 * the view (search for next set of results) 116 112 * 113 * TODO: disable popups easily 114 * 117 115 * TODO: Fix activeSearch memory leak (gc searches older than...) 118 116 * 119 * TODO: 3.Use weblicht with results117 * TODO: Use weblicht with results 120 118 * 121 119 * TODO: Export to personal workspace as csv, excel, tcf, plain text 122 120 * 123 121 * TODO: Download to personal workspace as csv, excel, tcf, plain text 124 *125 * TODO: 4. use a language guesser ?126 122 * 127 123 * TODO: websockets … … 150 146 private AtomicReference<Statistics> searchStatsAtom = new AtomicReference<Statistics>(new Statistics()); 151 147 152 private TokenizerModel model; 148 private TokenizerModel tokenizerModel; 149 private LanguageDetector languageDetector; 150 private TextObjectFactory textObjectFactory; 151 153 152 private ThrottledClient sruScanClient = null; 154 153 private ThrottledClient sruSearchClient = null; … … 170 169 171 170 @Override 172 public void run(AggregatorConfiguration config, Environment environment) {171 public void run(AggregatorConfiguration config, Environment environment) throws Exception { 173 172 params = config.aggregatorParams; 174 173 instance = this; … … 212 211 } 213 212 214 public void init() {213 public void init() throws IOException { 215 214 log.info("Aggregator initialization started."); 216 215 sruScanClient = new ThrottledClient( 217 new ClarinFCSClientBuilder()216 new ClarinFCSClientBuilder() 218 217 .setConnectTimeout(params.ENDPOINTS_SCAN_TIMEOUT_MS) 219 218 .setSocketTimeout(params.ENDPOINTS_SCAN_TIMEOUT_MS) … … 245 244 246 245 LanguagesISO693_3.getInstance(); // force init 247 model = setUpTokenizers(); 246 initTokenizer(); 247 initLanguageDetector(); 248 248 249 249 ScanCrawlTask task = new ScanCrawlTask(sruScanClient, … … 302 302 } 303 303 304 private static TokenizerModel setUpTokenizers() {304 private void initTokenizer() { 305 305 TokenizerModel model = null; 306 306 try { … … 311 311 log.error("Failed to load tokenizer model", ex); 312 312 } 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(); 314 328 } 315 329 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Kwic.java
r5758 r5919 2 2 3 3 import eu.clarin.sru.client.fcs.DataViewHits; 4 import eu.clarin.sru.fcs.aggregator.app.Aggregator; 4 5 import java.util.ArrayList; 5 6 import java.util.List; … … 39 40 private String pid; 40 41 private String reference; 42 private String language; 41 43 private List<TextFragment> fragments = new ArrayList<TextFragment>(); 42 44 … … 60 62 fragments.add(new TextFragment(text.substring(lastOffset, text.length()), false)); 61 63 } 64 65 language = Aggregator.getInstance().detectLanguage(hits.getText()); 62 66 } 63 67 … … 72 76 public String getReference() { 73 77 return reference; 78 } 79 80 public String getLanguage() { 81 return language; 74 82 } 75 83 -
SRUAggregator/trunk/src/main/resources/assets/base.css
r5900 r5919 109 109 } 110 110 111 /* large spinner */ 112 input[type=number]::-webkit-inner-spin-button, 113 input[type=number]::-webkit-outer-spin-button { 114 margin-left: 2px; 115 font-size:24px; 116 } 117 118 111 119 input, select, textarea { 112 113 114 115 116 117 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; 118 126 } 119 127 input:focus, select:focus, textarea:focus, button:focus, a:focus { -
SRUAggregator/trunk/src/main/resources/assets/index.html
r5901 r5919 15 15 </noscript> 16 16 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> 20 38 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> 39 62 </div> 40 63 </div> 41 64 </div> 42 65 43 <script src="lib/jquery.min.js"></script>44 66 <!--[if lt IE 9]> 45 67 <script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-shim.js"></script> 46 68 <script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-sham.js"></script> 47 69 <![endif]--> 70 <script src="lib/jquery.min.js"></script> 48 71 <script src="lib/react-with-addons.js"></script> 49 72 <script src="lib/bootstrap.min.js"></script> 50 73 <script src="js/components.js"></script> 74 <script src="js/corpora.js"></script> 51 75 <script src="js/search.js"></script> 52 <script src="js/corpora.js"></script>53 76 <script src="js/main.js"></script> 54 77 </body> -
SRUAggregator/trunk/src/main/resources/assets/js/corpora.js
r5900 r5919 2 2 (function() { 3 3 "use strict"; 4 5 window.MyAggregator = window.MyAggregator || {}; 4 6 5 7 var PT = React.PropTypes; … … 43 45 }); 44 46 45 var CorpusView = React.createClass({displayName: 'CorpusView',47 var CorpusView = window.MyAggregator.CorpusView = React.createClass({displayName: 'CorpusView', 46 48 propTypes: { 47 49 corpora: PT.object.isRequired, … … 238 240 }); 239 241 240 /////////////////////////////////241 242 if (!window.MyAggregator) {243 window.MyAggregator = {};244 }245 window.MyAggregator.CorpusView = CorpusView;246 242 })(); -
SRUAggregator/trunk/src/main/resources/assets/js/corpora.jsx
r5900 r5919 2 2 (function() { 3 3 "use strict"; 4 5 window.MyAggregator = window.MyAggregator || {}; 4 6 5 7 var PT = React.PropTypes; … … 43 45 }); 44 46 45 var CorpusView = React.createClass({47 var CorpusView = window.MyAggregator.CorpusView = React.createClass({ 46 48 propTypes: { 47 49 corpora: PT.object.isRequired, … … 238 240 }); 239 241 240 /////////////////////////////////241 242 if (!window.MyAggregator) {243 window.MyAggregator = {};244 }245 window.MyAggregator.CorpusView = CorpusView;246 242 })(); -
SRUAggregator/trunk/src/main/resources/assets/js/main.js
r5900 r5919 5 5 var PT = React.PropTypes; 6 6 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;13 7 var 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 8 var AggregatorPage = window.MyAggregator.AggregatorPage; 158 9 159 10 var Main = React.createClass({displayName: 'Main', … … 163 14 navbarPageFn: this.renderAggregator, 164 15 errorMessages: [], 165 166 corpora: new Corpora([], this.updateCorpora),167 languageMap: {},168 16 }; 169 },170 171 componentDidMount: function() {172 this.refreshCorpora();173 this.refreshLanguages();174 17 }, 175 18 … … 214 57 }, 215 58 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 238 59 renderAggregator: function() { 239 60 return React.createElement(AggregatorPage, {ajax: this.ajax, corpora: this.state.corpora, languageMap: this.state.languageMap}); … … 264 85 React.createElement("a", {className: "link", tabIndex: "-1", 265 86 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")270 87 ), 271 88 React.createElement("li", {className: this.state.navbarPageFn === this.renderHelp ? "active":""}, … … 320 137 }); 321 138 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 527 140 528 141 var StatisticsPage = React.createClass({displayName: 'StatisticsPage', … … 613 226 var HelpPage = React.createClass({displayName: 'HelpPage', 614 227 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', 616 229 '_blank', 'height=560,width=370'); 617 230 }, … … 656 269 }); 657 270 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 681 271 React.render(React.createElement(Main, null), document.getElementById('reactMain') ); 682 272 })(); -
SRUAggregator/trunk/src/main/resources/assets/js/main.jsx
r5900 r5919 5 5 var PT = React.PropTypes; 6 6 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;13 7 var 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 8 var AggregatorPage = window.MyAggregator.AggregatorPage; 158 9 159 10 var Main = React.createClass({ … … 163 14 navbarPageFn: this.renderAggregator, 164 15 errorMessages: [], 165 166 corpora: new Corpora([], this.updateCorpora),167 languageMap: {},168 16 }; 169 },170 171 componentDidMount: function() {172 this.refreshCorpora();173 this.refreshLanguages();174 17 }, 175 18 … … 214 57 }, 215 58 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 238 59 renderAggregator: function() { 239 60 return <AggregatorPage ajax={this.ajax} corpora={this.state.corpora} languageMap={this.state.languageMap} />; … … 264 85 <a className="link" tabIndex="-1" 265 86 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>270 87 </li> 271 88 <li className={this.state.navbarPageFn === this.renderHelp ? "active":""}> … … 320 137 }); 321 138 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 527 140 528 141 var StatisticsPage = React.createClass({ … … 613 226 var HelpPage = React.createClass({ 614 227 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', 616 229 '_blank', 'height=560,width=370'); 617 230 }, … … 656 269 }); 657 270 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 681 271 React.render(<Main />, document.getElementById('reactMain') ); 682 272 })(); -
SRUAggregator/trunk/src/main/resources/assets/js/search.js
r5900 r5919 3 3 "use strict"; 4 4 5 window.MyAggregator = window.MyAggregator || {}; 6 5 7 var React = window.React; 6 8 var PT = React.PropTypes; 7 var ReactCSSTransitionGroup = window.React.addons.CSSTransitionGroup; 8 // own components 9 var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; 10 11 var CorpusSelection = window.MyAggregator.CorpusSelection; 12 var HitNumber = window.MyAggregator.HitNumber; 13 var CorpusView = window.MyAggregator.CorpusView; 9 14 var InfoPopover = window.MyReact.InfoPopover; 10 15 var Panel = window.MyReact.Panel; 16 var Modal = window.MyReact.Modal; 17 18 var multipleLanguageCode = "mul"; // see ISO-693-3 19 20 var 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 ]; 36 var layerMap = { 37 sampa: layers[0], 38 text: layers[1], 39 }; 40 41 function 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 70 Corpora.prototype.recurseCorpus = function(corpus, fn) { 71 if (false === fn(corpus)) { 72 // no recursion 73 } else { 74 this.recurseCorpora(corpus.subCorpora, fn); 75 } 76 }; 77 78 Corpora.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 89 Corpora.prototype.recurse = function(fn) { 90 this.recurseCorpora(this.corpora, fn); 91 }; 92 93 Corpora.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 104 Corpora.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 129 Corpora.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 137 Corpora.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 151 Corpora.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 162 var 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 11 396 12 397 … … 67 452 }, 68 453 454 renderRowLanguage: function(hit) { 455 return React.createElement("span", {style: {fontFace:"Courier",color:"black"}}, hit.language); 456 }, 457 69 458 renderRowsAsHits: function(hit,i) { 70 459 function renderTextFragments(tf, idx) { … … 72 461 } 73 462 return React.createElement("p", {key: i, className: "hitrow"}, 463 this.renderRowLanguage(hit), 74 464 hit.fragments.map(renderTextFragments) 75 465 ); … … 81 471 var sright={textAlign:"right", verticalAlign:"middle", maxWidth:"50%"}; 82 472 return React.createElement("tr", {key: i, className: "hitrow"}, 473 React.createElement("td", null, this.renderRowLanguage(hit)), 83 474 React.createElement("td", {style: sright}, hit.left), 84 475 React.createElement("td", {style: scenter, className: "keyword"}, hit.keyword), … … 208 599 }); 209 600 210 if (!window.MyAggregator) { 211 window.MyAggregator = {}; 212 } 213 window.MyAggregator.SearchBox = SearchBox; 214 window.MyAggregator.Results = Results; 601 var _ = 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 215 623 })(); -
SRUAggregator/trunk/src/main/resources/assets/js/search.jsx
r5900 r5919 3 3 "use strict"; 4 4 5 window.MyAggregator = window.MyAggregator || {}; 6 5 7 var React = window.React; 6 8 var PT = React.PropTypes; 7 var ReactCSSTransitionGroup = window.React.addons.CSSTransitionGroup; 8 // own components 9 var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; 10 11 var CorpusSelection = window.MyAggregator.CorpusSelection; 12 var HitNumber = window.MyAggregator.HitNumber; 13 var CorpusView = window.MyAggregator.CorpusView; 9 14 var InfoPopover = window.MyReact.InfoPopover; 10 15 var Panel = window.MyReact.Panel; 16 var Modal = window.MyReact.Modal; 17 18 var multipleLanguageCode = "mul"; // see ISO-693-3 19 20 var 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 ]; 36 var layerMap = { 37 sampa: layers[0], 38 text: layers[1], 39 }; 40 41 function 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 70 Corpora.prototype.recurseCorpus = function(corpus, fn) { 71 if (false === fn(corpus)) { 72 // no recursion 73 } else { 74 this.recurseCorpora(corpus.subCorpora, fn); 75 } 76 }; 77 78 Corpora.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 89 Corpora.prototype.recurse = function(fn) { 90 this.recurseCorpora(this.corpora, fn); 91 }; 92 93 Corpora.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 104 Corpora.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 129 Corpora.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 137 Corpora.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 151 Corpora.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 162 var 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 11 396 12 397 … … 67 452 }, 68 453 454 renderRowLanguage: function(hit) { 455 return <span style={{fontFace:"Courier",color:"black"}}>{hit.language}</span>; 456 }, 457 69 458 renderRowsAsHits: function(hit,i) { 70 459 function renderTextFragments(tf, idx) { … … 72 461 } 73 462 return <p key={i} className="hitrow"> 463 {this.renderRowLanguage(hit)} 74 464 {hit.fragments.map(renderTextFragments)} 75 465 </p>; … … 81 471 var sright={textAlign:"right", verticalAlign:"middle", maxWidth:"50%"}; 82 472 return <tr key={i} className="hitrow"> 473 <td>{this.renderRowLanguage(hit)}</td> 83 474 <td style={sright}>{hit.left}</td> 84 475 <td style={scenter} className="keyword">{hit.keyword}</td> … … 208 599 }); 209 600 210 if (!window.MyAggregator) { 211 window.MyAggregator = {}; 212 } 213 window.MyAggregator.SearchBox = SearchBox; 214 window.MyAggregator.Results = Results; 601 var _ = 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 215 623 })();
Note: See TracChangeset
for help on using the changeset viewer.