1 | "use strict"; |
---|
2 | import classNames from "classnames"; |
---|
3 | import CorpusView from "../components/corpusview.jsx"; |
---|
4 | import LanguageSelector from "../components/languageselector.jsx" |
---|
5 | import Modal from "../components/modal.jsx"; |
---|
6 | import Results from "../components/results.jsx"; |
---|
7 | import QueryInput from "../components/queryinput.jsx"; |
---|
8 | import ZoomedResult from "../components/zoomedresult.jsx"; |
---|
9 | import PropTypes from "prop-types"; |
---|
10 | import createReactClass from "create-react-class"; |
---|
11 | |
---|
12 | var PT = PropTypes; |
---|
13 | |
---|
14 | window.MyAggregator = window.MyAggregator || {}; |
---|
15 | var multipleLanguageCode = window.MyAggregator.multipleLanguageCode = "mul"; // see ISO-693-3 |
---|
16 | |
---|
17 | var AggregatorPage = createReactClass({ |
---|
18 | // fixme! - class AggregatorPage extends React.Component { |
---|
19 | propTypes: { |
---|
20 | ajax: PT.func.isRequired, |
---|
21 | error: PT.func.isRequired, |
---|
22 | embedded: PT.bool.isRequired |
---|
23 | }, |
---|
24 | |
---|
25 | nohits: { |
---|
26 | results: null, |
---|
27 | }, |
---|
28 | anyLanguage: [multipleLanguageCode, "Any Language"], |
---|
29 | |
---|
30 | getInitialState: function () { |
---|
31 | var aggrContext = getQueryVariable('x-aggregation-context'); |
---|
32 | aggrContext = aggrContext && JSON.parse(aggrContext); |
---|
33 | |
---|
34 | return { |
---|
35 | corpora: new Corpora([], this.updateCorpora), |
---|
36 | languageMap: {}, |
---|
37 | weblichtLanguages: [], |
---|
38 | queryTypeId: getQueryVariable('queryType') || 'cql', |
---|
39 | cqlQuery: ((getQueryVariable('queryType') || 'cql') === 'cql') && getQueryVariable('query') || '', |
---|
40 | fcsQuery: ((getQueryVariable('queryType') || 'cql') === 'fcs') && getQueryVariable('query') || '', |
---|
41 | aggregationContext: aggrContext || null, |
---|
42 | language: this.anyLanguage, |
---|
43 | languageFilter: 'byMeta', |
---|
44 | numberOfResults: 10, |
---|
45 | |
---|
46 | searchId: null, |
---|
47 | timeout: 0, |
---|
48 | hits: this.nohits, |
---|
49 | |
---|
50 | zoomedCorpusHit: null, |
---|
51 | }; |
---|
52 | }, |
---|
53 | |
---|
54 | componentDidMount: function() { |
---|
55 | this._isMounted = true; |
---|
56 | |
---|
57 | this.props.ajax({ |
---|
58 | url: 'rest/init', |
---|
59 | success: function(json, textStatus, jqXHR) { |
---|
60 | if (this._isMounted) { |
---|
61 | var corpora = new Corpora(json.corpora, this.updateCorpora); |
---|
62 | |
---|
63 | // // for testing aggregation context |
---|
64 | // json['x-aggregation-context'] = { |
---|
65 | // 'EKUT': ["http://hdl.handle.net/11858/00-1778-0000-0001-DDAF-D"] |
---|
66 | // }; |
---|
67 | |
---|
68 | var aggregationContext = json['x-aggregation-context'] || this.state.aggregationContext; |
---|
69 | |
---|
70 | window.MyAggregator.mode = getQueryVariable('mode') || json.mode; |
---|
71 | window.MyAggregator.corpora = json.corpora; |
---|
72 | window.MyAggregator.xAggregationContext = aggregationContext; |
---|
73 | |
---|
74 | // Setting visibility, e.g. only corpora |
---|
75 | // from v2.0 endpoints for fcs v2.0 |
---|
76 | corpora.setVisibility(this.state.queryTypeId, this.state.language[0]); |
---|
77 | |
---|
78 | if (aggregationContext) { |
---|
79 | const contextCorporaInfo = corpora.setAggregationContext(aggregationContext); |
---|
80 | const unavailableCorporaHandles = contextCorporaInfo.unavailable; // list of unavailable aggregationContext |
---|
81 | if (unavailableCorporaHandles.length > 0) { |
---|
82 | this.props.error("Could not find requested collection handles:\n" + unavailableCorporaHandles.join('\n')); |
---|
83 | } |
---|
84 | |
---|
85 | const actuallySelectedCorpora = corpora.getSelectedIds(); |
---|
86 | |
---|
87 | if (contextCorporaInfo.selected.length !== actuallySelectedCorpora.length) { |
---|
88 | if (actuallySelectedCorpora.length === 0) { |
---|
89 | this.props.error("This search does not support the required collection(s), will search all collections instead"); // TODO give detailed reason its not supported. |
---|
90 | corpora.recurse(function(corpus) { corpus.selected = true; }); |
---|
91 | } else { |
---|
92 | var err = "Some required context collections are not supported for this search:\n" |
---|
93 | err = err + contextCorpora.filter((c) => { |
---|
94 | if (actuallySelectedCorpora.indexOf(c) === -1) { |
---|
95 | console.warn("Requested corpus but not available for selection", c); |
---|
96 | return true; |
---|
97 | } |
---|
98 | return false; |
---|
99 | }).map((c) => c.title).join('\n') |
---|
100 | this.props.error(err); |
---|
101 | } |
---|
102 | } |
---|
103 | } |
---|
104 | else { |
---|
105 | // no context set all visibl to selected as default. |
---|
106 | console.log("no context set, selecting all available"); |
---|
107 | corpora.recurse(c => {c.visible ? c.selected=true : null}) |
---|
108 | } |
---|
109 | |
---|
110 | this.setState({ |
---|
111 | corpora : corpora, |
---|
112 | languageMap: json.languages, |
---|
113 | weblichtLanguages: json.weblichtLanguages, |
---|
114 | aggregationContext: aggregationContext, |
---|
115 | }, this.postInit); |
---|
116 | } |
---|
117 | else { |
---|
118 | console.warn("Got Aggregator init response, but not mounted!"); |
---|
119 | } |
---|
120 | }.bind(this), |
---|
121 | }); |
---|
122 | }, |
---|
123 | |
---|
124 | postInit() { |
---|
125 | if (window.MyAggregator.mode === 'search') { |
---|
126 | this.search(); |
---|
127 | } |
---|
128 | }, |
---|
129 | |
---|
130 | updateCorpora: function(corpora) { |
---|
131 | this.setState({corpora:corpora}); |
---|
132 | }, |
---|
133 | |
---|
134 | getCurrentQuery() { |
---|
135 | return this.state.queryTypeId === 'fcs' ? this.state.fcsQuery : this.state.cqlQuery; |
---|
136 | }, |
---|
137 | |
---|
138 | search() { |
---|
139 | var query = this.getCurrentQuery(); |
---|
140 | var queryTypeId = this.state.queryTypeId; |
---|
141 | if (!query || (this.props.embedded && window.MyAggregator.mode !== 'search')) { |
---|
142 | this.setState({ hits: this.nohits, searchId: null }); |
---|
143 | return; |
---|
144 | } |
---|
145 | var selectedIds = this.state.corpora.getSelectedIds(); |
---|
146 | if (!selectedIds.length) { |
---|
147 | this.props.error("Please select a collection to search into"); |
---|
148 | return; |
---|
149 | } |
---|
150 | |
---|
151 | // console.log("searching in the following corpora:", selectedIds); |
---|
152 | // console.log("searching with queryType:", queryTypeId); |
---|
153 | this.props.ajax({ |
---|
154 | url: 'rest/search', |
---|
155 | type: "POST", |
---|
156 | data: { |
---|
157 | query: query, |
---|
158 | queryType: queryTypeId, |
---|
159 | language: this.state.language[0], |
---|
160 | numberOfResults: this.state.numberOfResults, |
---|
161 | corporaIds: selectedIds, |
---|
162 | }, |
---|
163 | success: function(searchId, textStatus, jqXHR) { |
---|
164 | // console.log("search ["+query+"] ok: ", searchId, jqXHR); |
---|
165 | //Piwik.getAsyncTracker().trackSiteSearch(query, queryTypeId); |
---|
166 | // automatic inclusion of piwik in prod |
---|
167 | //console.log("location.hostname: " + location.hostname); |
---|
168 | if (location.hostname !== "localhost") { |
---|
169 | //console.log("location.host: " + location.host); |
---|
170 | _paq.push(['trackSiteSearch', query, queryTypeId, false]); |
---|
171 | } |
---|
172 | |
---|
173 | var timeout = 250; |
---|
174 | setTimeout(this.refreshSearchResults, timeout); |
---|
175 | this.setState({ searchId: searchId, timeout: timeout }); |
---|
176 | }.bind(this), |
---|
177 | }); |
---|
178 | }, |
---|
179 | |
---|
180 | nextResults: function(corpusId) { |
---|
181 | // console.log("searching next results in corpus:", corpusId); |
---|
182 | this.props.ajax({ |
---|
183 | url: 'rest/search/'+this.state.searchId, |
---|
184 | type: "POST", |
---|
185 | data: { |
---|
186 | corpusId: corpusId, |
---|
187 | numberOfResults: this.state.numberOfResults, |
---|
188 | }, |
---|
189 | success: function(searchId, textStatus, jqXHR) { |
---|
190 | // console.log("search ["+query+"] ok: ", searchId, jqXHR); |
---|
191 | var timeout = 250; |
---|
192 | setTimeout(this.refreshSearchResults, timeout); |
---|
193 | this.setState({ searchId: searchId, timeout: timeout }); |
---|
194 | }.bind(this), |
---|
195 | }); |
---|
196 | }, |
---|
197 | |
---|
198 | refreshSearchResults: function() { |
---|
199 | if (!this.state.searchId || !this._isMounted) { |
---|
200 | return; |
---|
201 | } |
---|
202 | this.props.ajax({ |
---|
203 | url: 'rest/search/'+this.state.searchId, |
---|
204 | success: function(json, textStatus, jqXHR) { |
---|
205 | var timeout = this.state.timeout; |
---|
206 | if (json.inProgress) { |
---|
207 | if (timeout < 10000) { |
---|
208 | timeout = 1.5 * timeout; |
---|
209 | } |
---|
210 | setTimeout(this.refreshSearchResults, timeout); |
---|
211 | // console.log("new search in: " + this.timeout + "ms"); |
---|
212 | } else { |
---|
213 | console.log("search ended; hits:", json); |
---|
214 | } |
---|
215 | var corpusHit = this.state.zoomedCorpusHit; |
---|
216 | if (corpusHit) { |
---|
217 | for (var resi = 0; resi < json.results.length; resi++) { |
---|
218 | var res = json.results[resi]; |
---|
219 | if (res.corpus.id === corpusHit.corpus.id) { |
---|
220 | corpusHit = res; |
---|
221 | break; |
---|
222 | } |
---|
223 | } |
---|
224 | } |
---|
225 | this.setState({ hits: json, timeout: timeout, zoomedCorpusHit: corpusHit}); |
---|
226 | }.bind(this), |
---|
227 | }); |
---|
228 | }, |
---|
229 | |
---|
230 | getExportParams: function(corpusId, format, filterLanguage) { |
---|
231 | var params = corpusId ? {corpusId:corpusId}:{}; |
---|
232 | if (format) params.format = format; |
---|
233 | if (filterLanguage) { |
---|
234 | params.filterLanguage = filterLanguage; |
---|
235 | } else if (this.state.languageFilter === 'byGuess' || this.state.languageFilter === 'byMetaAndGuess') { |
---|
236 | params.filterLanguage = this.state.language[0]; |
---|
237 | } |
---|
238 | return encodeQueryData(params); |
---|
239 | }, |
---|
240 | |
---|
241 | getDownloadLink: function(corpusId, format) { |
---|
242 | return 'rest/search/'+this.state.searchId+'/download?' + |
---|
243 | this.getExportParams(corpusId, format); |
---|
244 | }, |
---|
245 | |
---|
246 | getToWeblichtLink: function(corpusId, forceLanguage) { |
---|
247 | return 'rest/search/'+this.state.searchId+'/toWeblicht?' + |
---|
248 | this.getExportParams(corpusId, null, forceLanguage); |
---|
249 | }, |
---|
250 | |
---|
251 | setLanguageAndFilter: function(languageObj, languageFilter) { |
---|
252 | this.state.corpora.setVisibility(this.state.queryTypeId, |
---|
253 | languageFilter === 'byGuess' ? multipleLanguageCode : languageObj[0]); |
---|
254 | this.setState({ |
---|
255 | language: languageObj, |
---|
256 | languageFilter: languageFilter, |
---|
257 | corpora: this.state.corpora, // === this.state.corpora.update(); |
---|
258 | }); |
---|
259 | }, |
---|
260 | |
---|
261 | setQueryType: function(queryTypeId) { |
---|
262 | this.state.corpora.setVisibility(queryTypeId, this.state.language[0]); |
---|
263 | setQueryVariable('queryType', queryTypeId); |
---|
264 | setQueryVariable('query', queryTypeId === 'cql' ? this.state.cqlQuery : this.state.fcsQuery) |
---|
265 | this.setState({ |
---|
266 | queryTypeId: queryTypeId, |
---|
267 | hits: this.nohits, |
---|
268 | searchId: null, |
---|
269 | }); |
---|
270 | }, |
---|
271 | |
---|
272 | setNumberOfResults: function(e) { |
---|
273 | var n = e.target.value; |
---|
274 | if (n < 10) n = 10; |
---|
275 | if (n > 250) n = 250; |
---|
276 | this.setState({numberOfResults: n}); |
---|
277 | e.preventDefault(); |
---|
278 | e.stopPropagation(); |
---|
279 | }, |
---|
280 | |
---|
281 | stop: function(e) { |
---|
282 | e.stopPropagation(); |
---|
283 | }, |
---|
284 | |
---|
285 | filterResults: function() { |
---|
286 | var noLangFiltering = this.state.languageFilter === 'byMeta'; |
---|
287 | var langCode = this.state.language[0]; |
---|
288 | var results = null, inProgress = 0, hits = 0; |
---|
289 | if (this.state.hits.results) { |
---|
290 | results = this.state.hits.results.map(function(corpusHit) { |
---|
291 | return { |
---|
292 | corpus: corpusHit.corpus, |
---|
293 | inProgress: corpusHit.inProgress, |
---|
294 | exception: corpusHit.exception, |
---|
295 | diagnostics: corpusHit.diagnostics, |
---|
296 | kwics: noLangFiltering ? corpusHit.kwics : |
---|
297 | corpusHit.kwics.filter(function(kwic) { |
---|
298 | return kwic.language === langCode || |
---|
299 | langCode === multipleLanguageCode || |
---|
300 | langCode === null; |
---|
301 | }), |
---|
302 | advancedLayers: noLangFiltering ? corpusHit.advancedLayers : |
---|
303 | corpusHit.advancedLayers.filter(function(layer) { |
---|
304 | return layer.language === langCode || |
---|
305 | langCode === multipleLanguageCode || |
---|
306 | langCode === null; |
---|
307 | }), |
---|
308 | }; |
---|
309 | }); |
---|
310 | for (var i = 0; i < results.length; i++) { |
---|
311 | var result = results[i]; |
---|
312 | if (result.inProgress) { |
---|
313 | inProgress++; |
---|
314 | } |
---|
315 | if (result.kwics.length > 0) { |
---|
316 | hits ++; |
---|
317 | } |
---|
318 | } |
---|
319 | } |
---|
320 | return { |
---|
321 | results: results, |
---|
322 | hits: hits, |
---|
323 | inProgress: inProgress, |
---|
324 | }; |
---|
325 | }, |
---|
326 | |
---|
327 | toggleLanguageSelection: function(e) { |
---|
328 | $(ReactDOM.findDOMNode(this.refs.languageModal)).modal(); |
---|
329 | e.preventDefault(); |
---|
330 | e.stopPropagation(); |
---|
331 | }, |
---|
332 | |
---|
333 | toggleCorpusSelection: function(e) { |
---|
334 | $(ReactDOM.findDOMNode(this.refs.corporaModal)).modal(); |
---|
335 | e.preventDefault(); |
---|
336 | e.stopPropagation(); |
---|
337 | }, |
---|
338 | |
---|
339 | toggleResultModal: function(e, corpusHit) { |
---|
340 | $(ReactDOM.findDOMNode(this.refs.resultModal)).modal(); |
---|
341 | this.setState({zoomedCorpusHit: corpusHit}); |
---|
342 | e.preventDefault(); |
---|
343 | e.stopPropagation(); |
---|
344 | }, |
---|
345 | |
---|
346 | onQueryChange: function(queryStr) { |
---|
347 | if (this.state.queryTypeId === 'cql') { |
---|
348 | this.setState({ |
---|
349 | cqlQuery: queryStr || '', |
---|
350 | }); |
---|
351 | } else { |
---|
352 | this.setState({ |
---|
353 | fcsQuery: queryStr || '', |
---|
354 | }); |
---|
355 | } |
---|
356 | setQueryVariable('query', queryStr); |
---|
357 | }, |
---|
358 | |
---|
359 | handleKey: function(event) { |
---|
360 | if (event.keyCode==13) { |
---|
361 | this.search(); |
---|
362 | } |
---|
363 | }, |
---|
364 | |
---|
365 | renderZoomedResultTitle: function(corpusHit) { |
---|
366 | if (!corpusHit) return (<span/>); |
---|
367 | var corpus = corpusHit.corpus; |
---|
368 | return (<h3 style={{fontSize:'1em'}}> |
---|
369 | {corpus.title} |
---|
370 | { corpus.landingPage ? |
---|
371 | <a href={corpus.landingPage} onClick={this.stop} style={{fontSize:12}}> |
---|
372 | <span> â Homepage </span> |
---|
373 | <i className="glyphicon glyphicon-home"/> |
---|
374 | </a>: false} |
---|
375 | </h3>); |
---|
376 | }, |
---|
377 | |
---|
378 | renderSearchButtonOrLink: function() { |
---|
379 | if (this.props.embedded) { |
---|
380 | var query = this.getCurrentQuery(); |
---|
381 | var queryTypeId = this.state.queryTypeId; |
---|
382 | var btnClass = classNames({ |
---|
383 | 'btn': true, |
---|
384 | 'btn-default': queryTypeId === 'cql', |
---|
385 | 'btn-primary': true, |
---|
386 | 'input-lg': true |
---|
387 | }); |
---|
388 | var newurl = !query ? "#" : |
---|
389 | (window.MyAggregator.URLROOT + (this.props.embedded ? "/embed" : "/") + "?" + encodeQueryData({queryType:queryTypeId, query:query, mode:'search'})); |
---|
390 | return ( <a className={btnClass} style={{paddingTop:13}} |
---|
391 | type="button" target="_blank" href={newurl}> |
---|
392 | <i className="glyphicon glyphicon-search"></i> |
---|
393 | </a> |
---|
394 | ); |
---|
395 | } |
---|
396 | return ( |
---|
397 | <button className="btn btn-default input-lg image_button" type="button" onClick={this.search}> |
---|
398 | <i className="glyphicon glyphicon-search"></i> |
---|
399 | </button> |
---|
400 | ); |
---|
401 | }, |
---|
402 | |
---|
403 | |
---|
404 | renderQueryInput() { |
---|
405 | var queryType = queryTypeMap[this.state.queryTypeId]; |
---|
406 | return ( |
---|
407 | <QueryInput |
---|
408 | searchedLanguages={this.state.searchedLanguages || [multipleLanguageCode]} |
---|
409 | corpora={this.props.corpora} |
---|
410 | queryTypeId={this.state.queryTypeId} |
---|
411 | query={this.getCurrentQuery()===undefined ? queryType.searchPlaceholder : this.getCurrentQuery()} |
---|
412 | embedded={this.props.embedded} |
---|
413 | placeholder={queryType.searchPlaceholder} |
---|
414 | onQueryChange={this.onQueryChange} |
---|
415 | onKeyDown={this.handleKey} /> |
---|
416 | ); |
---|
417 | }, |
---|
418 | |
---|
419 | renderEmbed () { |
---|
420 | var queryType = queryTypeMap[this.state.queryTypeId]; |
---|
421 | |
---|
422 | return <div className="aligncenter" style={{marginLeft:16, marginRight:16}}> |
---|
423 | <div className={"input-group"}> |
---|
424 | <span className="input-group-addon" style={{backgroundColor:queryType.searchLabelBkColor}}> |
---|
425 | {queryType.searchLabel} |
---|
426 | </span> |
---|
427 | |
---|
428 | { this.renderQueryInput() } |
---|
429 | |
---|
430 | <div className="input-group-btn"> |
---|
431 | {this.renderSearchButtonOrLink()} |
---|
432 | </div> |
---|
433 | </div> |
---|
434 | </div> |
---|
435 | }, |
---|
436 | |
---|
437 | renderGQB () { |
---|
438 | var queryType = queryTypeMap[this.state.queryTypeId]; |
---|
439 | |
---|
440 | return <div style={{marginLeft:16, marginRight:16}}> |
---|
441 | <div className="panel panel-default"> |
---|
442 | <div className="panel-heading" style={{backgroundColor:queryType.searchLabelBkColor, fontSize: "120%"}}> |
---|
443 | {queryType.searchLabel} |
---|
444 | </div> |
---|
445 | |
---|
446 | <div className="panel-body"> |
---|
447 | { this.renderQueryInput() } |
---|
448 | </div> |
---|
449 | |
---|
450 | <div className="panel-footer"> |
---|
451 | <div className="input-group"> |
---|
452 | |
---|
453 | <pre className="adv-query-preview aligncenter input-control input-lg">{this.getCurrentQuery()}</pre> |
---|
454 | |
---|
455 | <div className="input-group-btn"> |
---|
456 | {this.renderSearchButtonOrLink()} |
---|
457 | </div> |
---|
458 | </div> |
---|
459 | </div> |
---|
460 | </div> |
---|
461 | </div> |
---|
462 | }, |
---|
463 | |
---|
464 | renderUnavailableCorporaMessage() { |
---|
465 | if (!this.state.corpora) { |
---|
466 | return; |
---|
467 | } |
---|
468 | const unavailable = []; |
---|
469 | this.state.corpora.recurse((c) => { |
---|
470 | if (c.selected && ! c.visible) { |
---|
471 | unavailable.push(c); |
---|
472 | } |
---|
473 | if (c.selected) { |
---|
474 | // apparently a selected corpus |
---|
475 | } |
---|
476 | }); |
---|
477 | |
---|
478 | if (unavailable.length) { |
---|
479 | return <div id="unavailable-corpora-message" className="text-muted"> |
---|
480 | <div id="unavailable-corpora-message-message"> |
---|
481 | <a role="button" data-toggle="dropdown">{unavailable.length} selected collection{unavailable.length > 1 ? 's are' : ' is'} disabled in this search mode.</a> |
---|
482 | </div> |
---|
483 | <ul id="unavailable-corpora-message-list" className="dropdown-menu"> |
---|
484 | { |
---|
485 | unavailable.map((c) => <li className="unavailable-corpora-message-item">{c.name}</li>) |
---|
486 | } |
---|
487 | </ul> |
---|
488 | </div> |
---|
489 | } |
---|
490 | }, |
---|
491 | |
---|
492 | render: function() { |
---|
493 | |
---|
494 | var queryType = queryTypeMap[this.state.queryTypeId]; |
---|
495 | return ( |
---|
496 | <div className="top-gap"> |
---|
497 | <div className="row"> |
---|
498 | { (!this.props.embedded && this.state.queryTypeId == "fcs") ? this.renderGQB() : this.renderEmbed() } |
---|
499 | </div> |
---|
500 | |
---|
501 | <div className="well" style={{marginTop:20}}> |
---|
502 | <div className="aligncenter" > |
---|
503 | { |
---|
504 | //this.renderUnavailableCorporaMessage() |
---|
505 | } |
---|
506 | <form className="form-inline" role="form"> |
---|
507 | |
---|
508 | <div className="input-group"> |
---|
509 | |
---|
510 | <span className="input-group-addon nobkg" >Search for</span> |
---|
511 | |
---|
512 | <div className="input-group-btn"> |
---|
513 | <button className="form-control btn btn-default" |
---|
514 | onClick={this.toggleLanguageSelection}> |
---|
515 | {this.state.language[1]} <span className="caret"/> |
---|
516 | </button> |
---|
517 | <span/> |
---|
518 | </div> |
---|
519 | <div className="input-group-btn hidden-xxs"> |
---|
520 | <ul ref="queryTypeDropdownMenu" className="dropdown-menu"> |
---|
521 | { queryTypes.map(function(l) { |
---|
522 | var cls = l.disabled ? 'disabled':''; |
---|
523 | var handler = function() { if (!l.disabled) this.setQueryType(l.id); }.bind(this); |
---|
524 | return (<li key={l.id} className={cls}> <a tabIndex="-1" href="#" |
---|
525 | onClick={handler}> {l.name} </a></li>); |
---|
526 | }.bind(this)) |
---|
527 | } |
---|
528 | </ul> |
---|
529 | |
---|
530 | <button className="form-control btn btn-default" |
---|
531 | aria-expanded="false" data-toggle="dropdown" > |
---|
532 | {queryType.name} <span className="caret"/> |
---|
533 | </button> |
---|
534 | </div> |
---|
535 | |
---|
536 | </div> |
---|
537 | |
---|
538 | <div className="input-group hidden-xs"> |
---|
539 | <span className="input-group-addon nobkg">in</span> |
---|
540 | <button type="button" className="btn btn-default" onClick={this.toggleCorpusSelection}> |
---|
541 | {this.state.corpora.getSelectedMessage()} <span className="caret"/> |
---|
542 | </button> |
---|
543 | </div> |
---|
544 | |
---|
545 | <div className="input-group hidden-xs hidden-sm"> |
---|
546 | <span className="input-group-addon nobkg">and show up to</span> |
---|
547 | <div className="input-group-btn"> |
---|
548 | <input type="number" className="form-control input" min="10" max="250" |
---|
549 | style={{width:60}} |
---|
550 | onChange={this.setNumberOfResults} value={this.state.numberOfResults} |
---|
551 | onKeyPress={this.stop}/> |
---|
552 | </div> |
---|
553 | <span className="input-group-addon nobkg">hits per endpoint</span> |
---|
554 | </div> |
---|
555 | |
---|
556 | </form> |
---|
557 | </div> |
---|
558 | </div> |
---|
559 | |
---|
560 | <Modal ref="corporaModal" title={<span>Collections <small className="text-muted">{this.props.corpora && this.props.corpora.getSelectedMessage()}</small></span>}> |
---|
561 | <CorpusView corpora={this.state.corpora} languageMap={this.state.languageMap} /> |
---|
562 | </Modal> |
---|
563 | |
---|
564 | <Modal ref="languageModal" title={<span>Select Language</span>}> |
---|
565 | <LanguageSelector anyLanguage={this.anyLanguage} |
---|
566 | languageMap={this.state.languageMap} |
---|
567 | selectedLanguage={this.state.language} |
---|
568 | languageFilter={this.state.languageFilter} |
---|
569 | languageChangeHandler={this.setLanguageAndFilter} /> |
---|
570 | </Modal> |
---|
571 | |
---|
572 | <Modal ref="resultModal" title={this.renderZoomedResultTitle(this.state.zoomedCorpusHit)}> |
---|
573 | <ZoomedResult corpusHit={this.state.zoomedCorpusHit} |
---|
574 | nextResults={this.nextResults} |
---|
575 | getDownloadLink={this.getDownloadLink} |
---|
576 | getToWeblichtLink={this.getToWeblichtLink} |
---|
577 | searchedLanguage={this.state.language} |
---|
578 | weblichtLanguages={this.state.weblichtLanguages} |
---|
579 | languageMap={this.state.languageMap} |
---|
580 | queryTypeId={this.state.queryTypeId} /> |
---|
581 | </Modal> |
---|
582 | |
---|
583 | <div className="top-gap"> |
---|
584 | <Results collhits={this.filterResults()} |
---|
585 | toggleResultModal={this.toggleResultModal} |
---|
586 | getDownloadLink={this.getDownloadLink} |
---|
587 | getToWeblichtLink={this.getToWeblichtLink} |
---|
588 | searchedLanguage={this.state.language} |
---|
589 | queryTypeId={this.state.queryTypeId}/> |
---|
590 | </div> |
---|
591 | </div> |
---|
592 | ); |
---|
593 | }, |
---|
594 | }); |
---|
595 | |
---|
596 | function Corpora(corpora, updateFn) { |
---|
597 | var that = this; |
---|
598 | this.corpora = corpora; |
---|
599 | this.update = function() { |
---|
600 | updateFn(that); |
---|
601 | }; |
---|
602 | |
---|
603 | var sortFn = function(x, y) { |
---|
604 | var r = x.institution.name.localeCompare(y.institution.name); |
---|
605 | if (r !== 0) { |
---|
606 | return r; |
---|
607 | } |
---|
608 | return x.title.toLowerCase().localeCompare(y.title.toLowerCase()); |
---|
609 | }; |
---|
610 | |
---|
611 | this.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); }); |
---|
612 | this.corpora.sort(sortFn); |
---|
613 | |
---|
614 | this.recurse(function(corpus, index) { |
---|
615 | corpus.visible = true; // visible in the corpus view |
---|
616 | corpus.selected = false; // not selected in the corpus view, assign later |
---|
617 | corpus.expanded = false; // not expanded in the corpus view |
---|
618 | corpus.priority = 1; // used for ordering search results in corpus view |
---|
619 | corpus.index = index; // original order, used for stable sort |
---|
620 | }); |
---|
621 | } |
---|
622 | |
---|
623 | Corpora.prototype.recurseCorpus = function(corpus, fn) { |
---|
624 | if (false === fn(corpus)) { |
---|
625 | // no recursion |
---|
626 | } else { |
---|
627 | this.recurseCorpora(corpus.subCorpora, fn); |
---|
628 | } |
---|
629 | }; |
---|
630 | |
---|
631 | Corpora.prototype.recurseCorpora = function(corpora, fn) { |
---|
632 | var recfn = function(corpus, index){ |
---|
633 | if (false === fn(corpus, index)) { |
---|
634 | // no recursion |
---|
635 | } else { |
---|
636 | corpus.subCorpora.forEach(recfn); |
---|
637 | } |
---|
638 | }; |
---|
639 | corpora.forEach(recfn); |
---|
640 | }; |
---|
641 | |
---|
642 | Corpora.prototype.recurse = function(fn) { |
---|
643 | this.recurseCorpora(this.corpora, fn); |
---|
644 | }; |
---|
645 | |
---|
646 | Corpora.prototype.getLanguageCodes = function() { |
---|
647 | var languages = {}; |
---|
648 | this.recurse(function(corpus) { |
---|
649 | corpus.languages.forEach(function(lang) { |
---|
650 | languages[lang] = true; |
---|
651 | }); |
---|
652 | return true; |
---|
653 | }); |
---|
654 | return languages; |
---|
655 | }; |
---|
656 | |
---|
657 | Corpora.prototype.isCorpusVisible = function(corpus, queryTypeId, languageCode) { |
---|
658 | if (queryTypeId === "fcs" && (corpus.endpoint.protocol === "LEGACY" || corpus.endpoint.protocol === "VERSION_1")) { |
---|
659 | return false; |
---|
660 | } |
---|
661 | // yes for any language |
---|
662 | if (languageCode === multipleLanguageCode) { |
---|
663 | return true; |
---|
664 | } |
---|
665 | // yes if the corpus is in only that language |
---|
666 | if (corpus.languages && corpus.languages.length === 1 && corpus.languages[0] === languageCode) { |
---|
667 | return true; |
---|
668 | } |
---|
669 | |
---|
670 | // ? yes if the corpus also contains that language |
---|
671 | if (corpus.languages && corpus.languages.indexOf(languageCode) >=0) { |
---|
672 | return true; |
---|
673 | } |
---|
674 | |
---|
675 | // ? yes if the corpus has no language |
---|
676 | // if (!corpus.languages || corpus.languages.length === 0) { |
---|
677 | // return true; |
---|
678 | // } |
---|
679 | return false; |
---|
680 | }; |
---|
681 | |
---|
682 | Corpora.prototype.setVisibility = function(queryTypeId, languageCode) { |
---|
683 | // top level |
---|
684 | this.corpora.forEach(function(corpus) { |
---|
685 | corpus.visible = this.isCorpusVisible(corpus, queryTypeId, languageCode); |
---|
686 | this.recurseCorpora(corpus.subCorpora, function(c) { c.visible = corpus.visible; }); |
---|
687 | }.bind(this)); |
---|
688 | }; |
---|
689 | |
---|
690 | Corpora.prototype.setAggregationContext = function(endpoints2handles) { |
---|
691 | var selectSubTree = function(select, corpus) { |
---|
692 | corpus.selected = select; |
---|
693 | this.recurseCorpora(corpus.subCorpora, function(c) { c.selected = corpus.selected; }); |
---|
694 | }; |
---|
695 | |
---|
696 | this.corpora.forEach(selectSubTree.bind(this, false)); |
---|
697 | |
---|
698 | var handlesNotFound = []; |
---|
699 | var corporaToSelect = []; |
---|
700 | _.pairs(endpoints2handles).forEach((endp) => { |
---|
701 | var endpoint = endp[0]; |
---|
702 | var handles = endp[1]; |
---|
703 | console.log('setAggregationContext: endpoint', endpoint); |
---|
704 | console.log('setAggregationContext: handles', handles); |
---|
705 | handles.forEach((handle) => { |
---|
706 | var found = false; |
---|
707 | this.recurse((corpus) => { |
---|
708 | if (corpus.handle === handle) { |
---|
709 | found = true; |
---|
710 | corporaToSelect.push(corpus); |
---|
711 | } |
---|
712 | }) |
---|
713 | if (!found) { |
---|
714 | console.warn("Handle not found in corpora", handle); |
---|
715 | handlesNotFound.push(handle); |
---|
716 | } |
---|
717 | }) |
---|
718 | }) |
---|
719 | |
---|
720 | corporaToSelect.forEach(selectSubTree.bind(this, true)); |
---|
721 | return {'selected': corporaToSelect, 'unavailable': handlesNotFound}; |
---|
722 | }; |
---|
723 | |
---|
724 | Corpora.prototype.getSelectedIds = function() { |
---|
725 | var ids = []; |
---|
726 | this.recurse(function(corpus) { |
---|
727 | if (corpus.visible && corpus.selected) { |
---|
728 | ids.push(corpus.id); |
---|
729 | //eturn false; // top-most collection in tree, don't delve deeper |
---|
730 | // But subcollections are also selectable on their own?... |
---|
731 | } |
---|
732 | return true; |
---|
733 | }); |
---|
734 | |
---|
735 | // console.log("ids: ", ids.length, {ids:ids}); |
---|
736 | return ids; |
---|
737 | }; |
---|
738 | |
---|
739 | Corpora.prototype.getSelectedMessage = function() { |
---|
740 | var selected = this.getSelectedIds().length; |
---|
741 | if (this.corpora.length === selected) { |
---|
742 | return "All available collections (" + selected + ")"; |
---|
743 | } else if (selected === 1) { |
---|
744 | return "1 selected collection"; |
---|
745 | } |
---|
746 | return selected + " selected collections"; |
---|
747 | }; |
---|
748 | |
---|
749 | function getQueryVariable(variable) { |
---|
750 | var query = window.location.search.substring(1); |
---|
751 | var vars = query.split('&'); |
---|
752 | for (var i = 0; i < vars.length; i++) { |
---|
753 | var pair = vars[i].split('='); |
---|
754 | if (decodeURIComponent(pair[0]) == variable) { |
---|
755 | console.log("variable found: (", variable, ") = ", decodeURIComponent(pair[1])); |
---|
756 | return decodeURIComponent(pair[1]); |
---|
757 | } |
---|
758 | } |
---|
759 | return null; |
---|
760 | } |
---|
761 | |
---|
762 | /* setter opposite of getQueryVariable*/ |
---|
763 | function setQueryVariable(qvar, value) { |
---|
764 | var query = window.location.search.substring(1); |
---|
765 | var vars = query.split('&'); |
---|
766 | var d = {}; |
---|
767 | d[qvar] = value; |
---|
768 | var found = false; |
---|
769 | for (var i = 0; i < vars.length; i++) { |
---|
770 | var pair = vars[i].split('='); |
---|
771 | if (decodeURIComponent(pair[0]) === qvar) { |
---|
772 | |
---|
773 | vars[i] = encodeQueryData(d); |
---|
774 | found=true; |
---|
775 | break; |
---|
776 | } |
---|
777 | } |
---|
778 | |
---|
779 | if (!found) { |
---|
780 | // add to end of url |
---|
781 | vars.push(encodeQueryData(d)); |
---|
782 | } |
---|
783 | |
---|
784 | var searchPart = vars.join('&'); |
---|
785 | var newUrl = window.location.origin + window.location.pathname+'?'+searchPart; |
---|
786 | console.log("set url", newUrl); |
---|
787 | window.history.replaceState(window.history.state, null, newUrl); |
---|
788 | } |
---|
789 | |
---|
790 | |
---|
791 | function encodeQueryData(data) |
---|
792 | { |
---|
793 | var ret = []; |
---|
794 | for (var d in data) { |
---|
795 | ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d])); |
---|
796 | } |
---|
797 | return ret.join("&"); |
---|
798 | } |
---|
799 | |
---|
800 | var queryTypes = [ |
---|
801 | { |
---|
802 | id: "cql", |
---|
803 | name: "Text layer Contextual Query Language (CQL)", |
---|
804 | searchPlaceholder: "Elephant", |
---|
805 | searchLabel: "Text layer CQL query", |
---|
806 | searchLabelBkColor: "#fed", |
---|
807 | className: '', |
---|
808 | }, |
---|
809 | { |
---|
810 | id: "fcs", |
---|
811 | name: "Multi-layer Federated Content Search Query Language (FCS-QL)", |
---|
812 | searchPlaceholder: "[word = 'annotation'][word = 'focused']", |
---|
813 | searchLabel: "Multi-layer FCS query", |
---|
814 | searchLabelBkColor: "#efd", |
---|
815 | disabled: false, |
---|
816 | }, |
---|
817 | ]; |
---|
818 | |
---|
819 | var queryTypeMap = { |
---|
820 | cql: queryTypes[0], |
---|
821 | fcs: queryTypes[1], |
---|
822 | }; |
---|
823 | |
---|
824 | module.exports = AggregatorPage; |
---|