Changeset 5900 for SRUAggregator
- Timestamp:
- 12/10/14 14:33:08 (9 years ago)
- Location:
- SRUAggregator/trunk
- Files:
-
- 22 edited
Legend:
- Unmodified
- Added
- Removed
-
SRUAggregator/trunk/aggregator.yml
r5893 r5900 15 15 # use the simple server factory, run on a single port 16 16 server: 17 applicationContextPath: / 17 applicationContextPath: /Aggregator-testing/ 18 18 type: simple 19 19 connector: 20 20 type: http 21 port: 8080 22 23 #server: 24 # softNofileLimit: 1000 25 # hardNofileLimit: 1000 26 # applicationConnectors: 27 # - type: http 28 # port: 8080 29 # - type: https 30 # port: 8443 31 # keyStorePath: example.keystore 32 # keyStorePassword: example 33 # validateCerts: false 34 # this requires the npn-boot library on the JVM's boot classpath 35 # - type: spdy3 36 # port: 8445 37 # keyStorePath: example.keystore 38 # keyStorePassword: example 39 # validateCerts: false 40 # adminConnectors: 41 # - type: http 42 # port: 8081 43 # - type: https 44 # port: 8444 45 # keyStorePath: example.keystore 46 # keyStorePassword: example 47 # validateCerts: false 21 port: 4019 48 22 49 23 # Logging settings. -
SRUAggregator/trunk/aggregator_development.yml
r5893 r5900 9 9 SCAN_TASK_TIME_UNIT: HOURS 10 10 11 ENDPOINTS_SCAN_TIMEOUT_MS: 1500011 ENDPOINTS_SCAN_TIMEOUT_MS: 60000 12 12 ENDPOINTS_SEARCH_TIMEOUT_MS: 5000 13 13 EXECUTOR_SHUTDOWN_TIMEOUT_MS: 100 … … 15 15 # use the simple server factory, run on a single port 16 16 server: 17 applicationContextPath: / 17 applicationContextPath: /Aggregator-testing 18 18 type: simple 19 19 connector: 20 20 type: http 21 port: 8080 22 23 #server: 24 # softNofileLimit: 1000 25 # hardNofileLimit: 1000 26 # applicationConnectors: 27 # - type: http 28 # port: 8080 29 # - type: https 30 # port: 8443 31 # keyStorePath: example.keystore 32 # keyStorePassword: example 33 # validateCerts: false 34 # this requires the npn-boot library on the JVM's boot classpath 35 # - type: spdy3 36 # port: 8445 37 # keyStorePath: example.keystore 38 # keyStorePassword: example 39 # validateCerts: false 40 # adminConnectors: 41 # - type: http 42 # port: 8081 43 # - type: https 44 # port: 8444 45 # keyStorePath: example.keystore 46 # keyStorePassword: example 47 # validateCerts: false 21 port: 4019 48 22 49 23 # Logging settings. -
SRUAggregator/trunk/pom.xml
r5897 r5900 8 8 <groupId>eu.clarin.sru.fcs</groupId> 9 9 <artifactId>Aggregator2</artifactId> 10 <version>2.0.0-alpha- 8</version>10 <version>2.0.0-alpha-9</version> 11 11 <name>FCS Aggregator</name> 12 12 … … 62 62 <groupId>eu.clarin.sru</groupId> 63 63 <artifactId>sru-client</artifactId> 64 <version>0.9. 5-DEBUG</version>64 <version>0.9.4</version> 65 65 </dependency> 66 66 -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java
r5897 r5900 80 80 * @author edima 81 81 * 82 * TODO: make language show nicely in the UI 83 * 84 * TODO: use selected visible corpus for search 85 * 86 * TODO: use language selection to hide corpora 87 * 88 * TODO: support new spec-compatible centres, see Oliver's mail ............... 82 * TODO: push footer down 83 * 84 * TODO: 1. support new spec-compatible centres, see Oliver's mail 85 * (use SRUClient's extraResponseData POJOs) 86 * 87 * TODO: tri-state for parent collections; search + message implications 89 88 * 90 89 * TODO: disable popups easily 91 90 * 92 * TODO: zoom into the results from a corpus, allow functionality only for the93 * view (search for next set of results)91 * TODO: 2. zoom into the results from a corpus, allow functionality only for 92 * the view (search for next set of results) 94 93 * 95 94 * TODO: Fix activeSearch memory leak (gc searches older than...) 96 95 * 97 * TODO: Use weblicht with results96 * TODO: 3. Use weblicht with results 98 97 * 99 98 * TODO: Export to personal workspace as csv, excel, tcf, plain text … … 101 100 * TODO: Download to personal workspace as csv, excel, tcf, plain text 102 101 * 103 * TODO: use SRUClient's extraResponseData POJOs102 * TODO: 4. use a language guesser ? 104 103 * 105 104 * TODO: websockets … … 108 107 * 109 108 * TODO: show multiple hits on the same result in multiple rows, linked visually 109 * 110 * TODO: improve help page text 110 111 * 111 112 */ … … 281 282 return model; 282 283 } 283 284 // filter = new EndpointUrlFilterAllow("lindat");285 // filter = new EndpointUrlFilterDeny("leipzig");286 // filter = new EndpointUrlFilterAllow("leipzig", "mpi.nl");287 // filter = new EndpointUrlFilterAllow("lindat");288 284 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/AggregatorConfiguration.java
r5894 r5900 1 1 package eu.clarin.sru.fcs.aggregator.app; 2 2 3 import com.fasterxml.jackson.annotation.JsonIgnore; 3 4 import com.fasterxml.jackson.annotation.JsonProperty; 4 5 import io.dropwizard.Configuration; … … 51 52 long EXECUTOR_SHUTDOWN_TIMEOUT_MS; 52 53 54 @JsonIgnore 53 55 public TimeUnit getScanTaskTimeUnit() { 54 56 return TimeUnit.valueOf(SCAN_TASK_TIME_UNIT); 55 57 } 56 58 59 @JsonIgnore 57 60 public int getENDPOINTS_SCAN_TIMEOUT_MS() { 58 61 return ENDPOINTS_SCAN_TIMEOUT_MS; 59 62 } 60 63 64 @JsonIgnore 61 65 public int getENDPOINTS_SEARCH_TIMEOUT_MS() { 62 66 return ENDPOINTS_SEARCH_TIMEOUT_MS; -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/lang/LanguagesISO693_3.java
r5897 r5900 9 9 import java.io.InputStreamReader; 10 10 import java.util.HashMap; 11 import java.util.HashSet;12 11 import java.util.Map; 13 12 import java.util.Set; … … 22 21 23 22 private static final org.slf4j.Logger log = LoggerFactory.getLogger(LanguagesISO693_3.class); 24 public static final String LANGUAGES_FILE_PATH = "/lang/ ISO-639-3_20140320.tab";23 public static final String LANGUAGES_FILE_PATH = "/lang/iso-639-3_20140320.tab"; 25 24 public static final String LANGUAGES_FILE_ENCODING = "UTF-8"; 26 25 -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/rest/RestService.java
r5897 r5900 18 18 import java.net.URI; 19 19 import java.util.HashMap; 20 import java.util.HashSet; 20 21 import java.util.List; 21 22 import java.util.Map; … … 23 24 import javax.servlet.ServletContext; 24 25 import javax.servlet.http.HttpServletRequest; 26 import javax.ws.rs.Consumes; 25 27 import javax.ws.rs.FormParam; 26 28 import javax.ws.rs.GET; … … 32 34 import javax.ws.rs.core.MediaType; 33 35 import javax.ws.rs.core.Response; 36 import org.slf4j.LoggerFactory; 34 37 35 38 /** … … 40 43 @Path("/") 41 44 public class RestService { 45 46 private static final org.slf4j.Logger log = LoggerFactory.getLogger(RestService.class); 42 47 43 48 ObjectWriter ow = new ObjectMapper().writerWithDefaultPrettyPrinter(); … … 92 97 Map<String, String> languages = new HashMap<String, String>(); 93 98 Set<String> codes = Aggregator.getInstance().getCorpora().getLanguages(); 99 log.info("get language codes", codes); 94 100 for (String code : codes) { 95 101 String name = LanguagesISO693_3.getInstance().nameForCode(code); … … 110 116 public Response postSearch( 111 117 @FormParam("query") String query, 112 @FormParam("corporaIds") Set<String> corporaIds,113 118 @FormParam("numberOfResults") Integer numberOfResults, 114 @FormParam("language") String language) throws Exception { 119 @FormParam("language") String language, 120 @FormParam("corporaIds[]") List<String> corporaIds) throws Exception { 115 121 if (query == null || query.isEmpty()) { 116 122 return Response.status(400).entity("'query' parameter expected").build(); 117 123 } 124 // log.info("POST /search corporaIds: " + corporaIds); 118 125 if (corporaIds == null || corporaIds.isEmpty()) { 119 126 return Response.status(400).entity("'corporaIds' parameter expected").build(); 120 127 } 121 List<Corpus> corpora = Aggregator.getInstance().getCorpora().getCorporaByIds( corporaIds);128 List<Corpus> corpora = Aggregator.getInstance().getCorpora().getCorporaByIds(new HashSet<String>(corporaIds)); 122 129 if (corpora == null || corpora.isEmpty()) { 123 130 return Response.status(503).entity("No corpora, please wait for the server to finish scanning").build(); -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Corpora.java
r5897 r5900 1 1 package eu.clarin.sru.fcs.aggregator.scan; 2 2 3 import com.fasterxml.jackson.annotation.JsonProperty; 3 4 import java.util.ArrayList; 4 5 import java.util.Collections; 5 6 import java.util.HashMap; 6 7 import java.util.HashSet; 7 import java.util.LinkedHashSet;8 8 import java.util.List; 9 9 import java.util.Map; … … 22 22 private static final org.slf4j.Logger log = LoggerFactory.getLogger(Corpora.class); 23 23 24 private Map<String, Set<Corpus>> langToRootCorpora = new HashMap<String, Set<Corpus>>(); 25 private Map<String, Set<Corpus>> langToTopUniqueCorpora = new HashMap<String, Set<Corpus>>(); 24 @JsonProperty 26 25 private Map<String, Diagnostic> endpointDiagnostics = Collections.synchronizedMap(new HashMap<String, Diagnostic>()); 26 @JsonProperty 27 27 private List<Institution> institutions = Collections.synchronizedList(new ArrayList<Institution>()); 28 @JsonProperty 28 29 private List<Corpus> corpora = new ArrayList<Corpus>(); 29 30 … … 50 51 if (parentCorpus == null) { //i.e it's a root corpus 51 52 corpora.add(c); 52 for (String lang : c.getLanguages()) {53 if (!langToRootCorpora.containsKey(lang)) {54 langToRootCorpora.put(lang, new HashSet<Corpus>());55 }56 langToRootCorpora.get(lang).add(c);57 }58 53 } else { 59 54 parentCorpus.addCorpus(c); 60 }61 62 // index top corpora with unique language as for their languages63 if (c.getLanguages().size() == 164 && (parentCorpus == null || parentCorpus.getLanguages().size() > 0)) {65 String lang = c.getLanguages().iterator().next();66 if (!langToTopUniqueCorpora.containsKey(lang)) {67 langToTopUniqueCorpora.put(lang, new LinkedHashSet<Corpus>());68 }69 langToTopUniqueCorpora.get(lang).add(c);70 55 } 71 56 return true; … … 77 62 78 63 public Set<String> getLanguages() { 79 Set<String> languages = new HashSet<String>(this.langToRootCorpora.size()); 80 languages.addAll(this.langToRootCorpora.keySet()); 64 final Set<String> languages = new HashSet<String>(); 65 visit(corpora, new CallCorpus() { 66 @Override 67 public void call(Corpus c) { 68 languages.addAll(c.getLanguages()); 69 } 70 }); 81 71 return languages; 82 72 } 83 73 84 public List<Corpus> getRootCorporaForLang(String lang) { 85 List<Corpus> ret = new ArrayList<Corpus>(); 86 for (Corpus c : corpora) { 87 if (c.getLanguages().contains(lang)) { 88 ret.add(c); 74 public List<Corpus> getCorporaByIds(final Set<String> corporaIds) { 75 final List<Corpus> found = new ArrayList<Corpus>(); 76 visit(corpora, new CallCorpus() { 77 @Override 78 public void call(Corpus c) { 79 if (corporaIds.contains(c.getId())) { 80 found.add(c); 81 } 89 82 } 90 } 91 return ret; 92 } 93 94 public List<Corpus> getTopUniqueLanguageCorpora(String lang) { 95 ArrayList<Corpus> corpora = new ArrayList<Corpus>(); 96 corpora.addAll(langToTopUniqueCorpora.get(lang)); 97 return corpora; 98 } 99 100 @Override 101 public String toString() { 102 return "corpora{\n" + "institutions=" + institutions + "\n" 103 + "\n corpora=" + corpora + "\n}"; 83 }); 84 return found; 104 85 } 105 86 … … 130 111 } 131 112 132 public List<Corpus> getCorporaByIds(final Set<String> corporaIds) { 133 final List<Corpus> found = new ArrayList<Corpus>(); 134 visit(corpora, new CallCorpus() { 135 @Override 136 public void call(Corpus c) { 137 if (corporaIds.contains(c.getId())) { 138 found.add(c); 139 } 140 } 141 }); 142 return found; 143 } 113 public static interface CallCorpus { 144 114 145 public static interface CallCorpus {146 115 void call(Corpus c); 147 116 } … … 153 122 } 154 123 } 124 125 @Override 126 public String toString() { 127 return "corpora{\n" + "institutions=" + institutions + "\n" 128 + "\n corpora=" + corpora + "\n}"; 129 } 130 155 131 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Corpus.java
r5897 r5900 1 1 package eu.clarin.sru.fcs.aggregator.scan; 2 2 3 import com.fasterxml.jackson.annotation.JsonIgnore;4 3 import eu.clarin.sru.fcs.aggregator.lang.LanguagesISO693_3; 5 4 import java.util.ArrayList; … … 9 8 import java.util.Set; 10 9 import java.util.regex.Pattern; 10 import org.slf4j.LoggerFactory; 11 11 12 12 /** … … 18 18 */ 19 19 public class Corpus { 20 private static final org.slf4j.Logger log = LoggerFactory.getLogger(Corpus.class); 20 21 21 22 public static final String ROOT_HANDLE = "root"; … … 41 42 } 42 43 43 @JsonIgnore44 44 public String getId() { 45 45 return endpointUrl + "#" + handle; 46 } 47 48 public void setId(String id) { // dumb setter for JsonDeserialization 46 49 } 47 50 … … 112 115 } else { 113 116 String code = LanguagesISO693_3.getInstance().codeForName(language); 114 if (code != null) { 115 this.languages.add(code); 116 } else { 117 this.languages.add(language); 118 } 117 this.languages.add(code == null ? language : code); 119 118 } 120 119 } … … 126 125 public void setLandingPage(String landingPage) { 127 126 this.landingPage = landingPage; 127 } 128 129 public String getTitle() { 130 return title; 131 } 132 133 void setTitle(String title) { 134 this.title = title; 128 135 } 129 136 -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Diagnostic.java
r5893 r5900 28 28 return context; 29 29 } 30 30 31 public String getMessage() { 31 32 return message; … … 51 52 this.diagnostic = diagnostic; 52 53 } 54 55 @Override 56 public int hashCode() { 57 https://primes.utm.edu/lists/small/1000.txt 58 return uri.hashCode() * 967 + context.hashCode() * 797 59 + message.hashCode() * 1669 + diagnostic.hashCode(); 60 } 61 62 @Override 63 public boolean equals(Object obj) { 64 if (!(obj instanceof Diagnostic)) { 65 return false; 66 } 67 Diagnostic d = (Diagnostic) obj; 68 return uri.equals(d.uri) && message.equals(d.message) 69 && context.equals(d.context) && diagnostic.equals(d.diagnostic); 70 } 53 71 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Institution.java
r5893 r5900 51 51 } 52 52 53 @Override 54 public int hashCode() { 55 https://primes.utm.edu/lists/small/1000.txt 56 return name.hashCode() * 2953; 57 } 58 59 @Override 60 public boolean equals(Object obj) { 61 if (!(obj instanceof Institution)) { 62 return false; 63 } 64 Institution i = (Institution) obj; 65 return i.name.equals(name); 66 } 53 67 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawler.java
r5894 r5900 162 162 return; 163 163 } 164 try { 165 log.error("--> " + request.makeURI(SRUVersion.VERSION_1_2) + " --> ", error); 166 } catch (SRUClientException nestedxc) { 167 log.error(" xc on xc ", nestedxc); 168 } 164 log.error("--> " + request.getBaseURI() + "?" + request.getScanClause() + " --> ", error); 169 165 } 170 166 } … … 197 193 } 198 194 199 // TODO: ask Oliver to add API support for the extra info in the200 // SRU client/server libraries, so that it's not necessary to work201 // with DocumentFragment202 195 private static void addExtraInfo(Corpus c, SRUTerm term) { 203 196 DocumentFragment extraInfo = term.getExtraTermData(); 204 String enDescription = null ;197 String enDescription = null, enTitle = null; 205 198 if (extraInfo != null) { 206 199 NodeList infoNodes = extraInfo.getChildNodes().item(0).getChildNodes(); … … 215 208 && languageNodes.item(j).getLocalName().equals("Language")) { 216 209 Element languageNode = (Element) languageNodes.item(j); 217 String languageText = languageNode.getTextContent() .trim();218 if ( !languageText.isEmpty()) {210 String languageText = languageNode.getTextContent(); 211 if (languageText != null && !languageText.trim().isEmpty()) { 219 212 c.addLanguage(languageText.trim()); 220 213 } 221 214 } 215 } 216 } else if (infoNode.getNodeType() == Node.ELEMENT_NODE && infoNode.getLocalName().equals("Title")) { 217 Element element = (Element) infoNode; 218 String descr = infoNode.getTextContent().replaceAll("<br/>", " "); 219 descr = descr.replaceAll("<br/>", " "); 220 descr = descr.replaceAll("[\t\n\r ]+", " "); 221 c.setTitle(descr.trim()); 222 //String lang = element.getAttributeNS("http://clarin.eu/fcs/1.0/resource-info", "lang"); 223 //System.out.println("ATTRIBUTE LANG: " + lang); 224 if ("en".equals(element.getAttribute("xml:lang"))) { 225 enTitle = c.getDescription(); 222 226 } 223 227 } else if (infoNode.getNodeType() == Node.ELEMENT_NODE && infoNode.getLocalName().equals("Description")) { … … 234 238 } 235 239 } 240 // title in Engish has priority 241 if (enTitle != null && !enTitle.isEmpty()) { 242 c.setTitle(enTitle); 243 } 236 244 // description in Engish has priority 237 245 if (enDescription != null && !enDescription.isEmpty()) { -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Request.java
r5893 r5900 39 39 } 40 40 41 public boolean hasCorpusHandle r() {41 public boolean hasCorpusHandle() { 42 42 return corpus != null && corpus.getHandle() != null; 43 43 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java
r5893 r5900 62 62 searchRequest.setQuery("\"" + searchString + "\""); 63 63 searchRequest.setStartRecord(startRecord); 64 if (request.hasCorpusHandle r()) {64 if (request.hasCorpusHandle()) { 65 65 searchRequest.setExtraRequestData(SRUCQL.SEARCH_CORPUS_HANDLE_PARAMETER, corpus.getHandle()); 66 66 } -
SRUAggregator/trunk/src/main/resources/assets/base.css
r5897 r5900 176 176 } 177 177 div.corpus-container:hover { 178 b ackground-color: #F5F5F5;178 box-shadow: 0 0 10px rgb(179,216,253) !important; 179 179 } 180 180 div.corpus-container.dimmed { 181 font-weight: 300;182 opacity: 0.4;181 /*font-weight: 300;*/ 182 /*opacity: 0.8;*/ 183 183 } 184 184 -
SRUAggregator/trunk/src/main/resources/assets/index.html
r5897 r5900 24 24 <a title="about" id="aboutlink"> 25 25 <span class="glyphicon glyphicon-info-sign"></span> 26 <span>VERSION 2.0.0.α 8</span>26 <span>VERSION 2.0.0.α9</span> 27 27 </a> 28 28 </div> -
SRUAggregator/trunk/src/main/resources/assets/js/corpora.js
r5897 r5900 46 46 propTypes: { 47 47 corpora: PT.object.isRequired, 48 languageMap: PT.object.isRequired, 48 49 }, 49 50 … … 65 66 66 67 searchCorpus: function(query) { 68 // sort fn: descending priority, stable sort 69 var sortFn = function(a, b){ 70 if (b.priority === a.priority) { 71 return b.index - a.index; // stable sort 72 } 73 return b.priority - a.priority; 74 }; 75 76 this.props.corpora.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); }); 77 this.props.corpora.corpora.sort(sortFn); 78 67 79 query = query.toLowerCase(); 68 80 var querytokens = query.split(" "); … … 80 92 // find priority for each corpus 81 93 this.props.corpora.recurse(function(corpus){ 94 var title = corpus.title ? corpus.title : corpus.displayName; 82 95 querytokens.forEach(function(qtoken){ 83 if ( corpus.displayName && corpus.displayName.toLowerCase().indexOf(qtoken) >= 0) {96 if (title && title.toLowerCase().indexOf(qtoken) >= 0) { 84 97 corpus.priority ++; 85 // console.log(corpus.displayName, "name ++");86 98 } 87 99 if (corpus.description && corpus.description.toLowerCase().indexOf(qtoken) >= 0) { 88 100 corpus.priority ++; 89 // console.log(corpus.displayName, "desc ++");90 101 } 91 102 if (corpus.institution && corpus.institution.name && 92 103 corpus.institution.name.toLowerCase().indexOf(qtoken) >= 0) { 93 104 corpus.priority ++; 94 // console.log(corpus.displayName, "inst ++");95 105 } 96 106 if (corpus.languages){ … … 98 108 if (lang.toLowerCase().indexOf(qtoken) >= 0){ 99 109 corpus.priority ++; 100 // console.log(corpus.displayName, "lang ++");101 110 } 102 111 }); 103 } 104 }); 105 }); 112 corpus.languages.forEach(function(lang){ 113 if (this.props.languageMap[lang].toLowerCase().indexOf(qtoken) >= 0){ 114 corpus.priority ++; 115 } 116 }.bind(this)); 117 } 118 }.bind(this)); 119 }.bind(this)); 106 120 107 121 // ensure root corpora have nonnull priority … … 115 129 }); 116 130 117 // order (descending priority) 118 var sortFn = function(a, b){ 119 if (b.priority === a.priority) { 120 return b.index - a.index; // make it a stable sort 121 } 122 return b.priority - a.priority; 123 }; 124 125 this.props.corpora.recurse(function(corpus){ 126 if (corpus.subCorpora) 127 corpus.subCorpora.sort(sortFn); 131 this.props.corpora.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); }); 132 this.props.corpora.corpora.sort(sortFn); 133 134 // display 135 this.props.corpora.update(); 136 // console.log("corpus search done", query); 137 }, 138 139 getMinMaxPriority: function() { 140 var min = 1, max = 0; 141 this.props.corpora.recurse(function(c) { 142 if (c.priority < min) min = c.priority; 143 if (max < c.priority) max = c.priority; 128 144 }); 129 this.props.corpora.corpora.sort(sortFn); 130 131 // display 132 this.props.corpora.update(); 133 // console.log("corpus search done", query); 145 return [min, max]; 134 146 }, 135 147 … … 157 169 }, 158 170 159 renderCorpus: function(level, corpus) { 171 renderLanguages: function(languages) { 172 return languages 173 .map(function(l) { return this.props.languageMap[l]; }.bind(this)) 174 .sort() 175 .join(" "); 176 }, 177 178 renderCorpus: function(level, minmaxp, corpus) { 179 if (!corpus.visible) { 180 return false; 181 } 182 160 183 var indent = {marginLeft:level*50}; 161 184 var corpusContainerClass = "corpus-container "+(corpus.priority>0?"":"dimmed"); 185 186 var hue = 80 * corpus.priority / minmaxp[1]; 187 if (corpus.priority > 0) { hue += 40; } 188 var color = minmaxp[0] === minmaxp[1] ? 'transparent' : 'hsl('+hue+', 50%, 50%)'; 189 var priorityStyle = {paddingBottom: 4, paddingLeft: 2, borderBottom: '2px solid '+color }; 162 190 return React.createElement("div", {className: corpusContainerClass, key: corpus.displayName}, 163 191 React.createElement("div", {className: "row corpus"}, 164 192 React.createElement("div", {className: "col-sm-1 vcenter", onClick: this.toggleSelection.bind(this,corpus)}, 165 this.renderCheckbox(corpus) 193 React.createElement("div", {style: priorityStyle}, 194 this.renderCheckbox(corpus) 195 ) 166 196 ), 167 197 React.createElement("div", {className: "col-sm-8 vcenter"}, 168 198 React.createElement("div", {style: indent}, 169 React.createElement("h3", null, corpus. displayName, " :", corpus.priority),199 React.createElement("h3", null, corpus.title ? corpus.title : corpus.displayName, " "), 170 200 React.createElement("p", null, corpus.description), 171 201 this.renderExpansion(corpus) … … 174 204 React.createElement("div", {className: "col-sm-3 vcenter"}, 175 205 React.createElement("p", null, React.createElement("i", {className: "fa fa-institution"}), " ", corpus.institution.name), 176 React.createElement("p", null, React.createElement("i", {className: "fa fa-language"}), " ", corpus.languages.join(" ")) 206 React.createElement("p", null, React.createElement("i", {className: "fa fa-language"}), " ", this.renderLanguages(corpus.languages)), 207 corpus.landingPage ? 208 React.createElement("p", null, React.createElement("i", {className: "fa fa-home"}), " ", React.createElement("a", {href: corpus.landingPage}, corpus.landingPage)) : 209 false 177 210 ) 178 211 ), 179 corpus.expanded ? corpus.subCorpora.map(this.renderCorpus.bind(this, level+1)) : false212 corpus.expanded ? corpus.subCorpora.map(this.renderCorpus.bind(this, level+1, minmaxp)) : false 180 213 ); 181 214 }, 182 215 183 216 render: function() { 217 var minmaxp = this.getMinMaxPriority(); 184 218 return React.createElement("div", {style: {margin: "0 30px"}}, 185 219 React.createElement("div", {className: "row"}, 186 React.createElement("div", {className: "float-right"}, 220 React.createElement("div", {className: "float-left inline"}, 221 React.createElement("h3", {style: {marginTop:10}}, 222 this.props.corpora.getSelectedMessage() 223 ) 224 ), 225 React.createElement("div", {className: "float-right inline"}, 187 226 React.createElement("div", {className: "inline", style: { marginRight: 20}}, 188 227 React.createElement(SearchCorpusBox, {search: this.searchCorpus}) … … 194 233 ) 195 234 ), 196 this.props.corpora.corpora.map(this.renderCorpus.bind(this, 0))235 this.props.corpora.corpora.map(this.renderCorpus.bind(this, 0, minmaxp)) 197 236 ); 198 237 } -
SRUAggregator/trunk/src/main/resources/assets/js/corpora.jsx
r5897 r5900 46 46 propTypes: { 47 47 corpora: PT.object.isRequired, 48 languageMap: PT.object.isRequired, 48 49 }, 49 50 … … 65 66 66 67 searchCorpus: function(query) { 68 // sort fn: descending priority, stable sort 69 var sortFn = function(a, b){ 70 if (b.priority === a.priority) { 71 return b.index - a.index; // stable sort 72 } 73 return b.priority - a.priority; 74 }; 75 76 this.props.corpora.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); }); 77 this.props.corpora.corpora.sort(sortFn); 78 67 79 query = query.toLowerCase(); 68 80 var querytokens = query.split(" "); … … 80 92 // find priority for each corpus 81 93 this.props.corpora.recurse(function(corpus){ 94 var title = corpus.title ? corpus.title : corpus.displayName; 82 95 querytokens.forEach(function(qtoken){ 83 if ( corpus.displayName && corpus.displayName.toLowerCase().indexOf(qtoken) >= 0) {96 if (title && title.toLowerCase().indexOf(qtoken) >= 0) { 84 97 corpus.priority ++; 85 // console.log(corpus.displayName, "name ++");86 98 } 87 99 if (corpus.description && corpus.description.toLowerCase().indexOf(qtoken) >= 0) { 88 100 corpus.priority ++; 89 // console.log(corpus.displayName, "desc ++");90 101 } 91 102 if (corpus.institution && corpus.institution.name && 92 103 corpus.institution.name.toLowerCase().indexOf(qtoken) >= 0) { 93 104 corpus.priority ++; 94 // console.log(corpus.displayName, "inst ++");95 105 } 96 106 if (corpus.languages){ … … 98 108 if (lang.toLowerCase().indexOf(qtoken) >= 0){ 99 109 corpus.priority ++; 100 // console.log(corpus.displayName, "lang ++");101 110 } 102 111 }); 103 } 104 }); 105 }); 112 corpus.languages.forEach(function(lang){ 113 if (this.props.languageMap[lang].toLowerCase().indexOf(qtoken) >= 0){ 114 corpus.priority ++; 115 } 116 }.bind(this)); 117 } 118 }.bind(this)); 119 }.bind(this)); 106 120 107 121 // ensure root corpora have nonnull priority … … 115 129 }); 116 130 117 // order (descending priority) 118 var sortFn = function(a, b){ 119 if (b.priority === a.priority) { 120 return b.index - a.index; // make it a stable sort 121 } 122 return b.priority - a.priority; 123 }; 124 125 this.props.corpora.recurse(function(corpus){ 126 if (corpus.subCorpora) 127 corpus.subCorpora.sort(sortFn); 131 this.props.corpora.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); }); 132 this.props.corpora.corpora.sort(sortFn); 133 134 // display 135 this.props.corpora.update(); 136 // console.log("corpus search done", query); 137 }, 138 139 getMinMaxPriority: function() { 140 var min = 1, max = 0; 141 this.props.corpora.recurse(function(c) { 142 if (c.priority < min) min = c.priority; 143 if (max < c.priority) max = c.priority; 128 144 }); 129 this.props.corpora.corpora.sort(sortFn); 130 131 // display 132 this.props.corpora.update(); 133 // console.log("corpus search done", query); 145 return [min, max]; 134 146 }, 135 147 … … 157 169 }, 158 170 159 renderCorpus: function(level, corpus) { 171 renderLanguages: function(languages) { 172 return languages 173 .map(function(l) { return this.props.languageMap[l]; }.bind(this)) 174 .sort() 175 .join(" "); 176 }, 177 178 renderCorpus: function(level, minmaxp, corpus) { 179 if (!corpus.visible) { 180 return false; 181 } 182 160 183 var indent = {marginLeft:level*50}; 161 184 var corpusContainerClass = "corpus-container "+(corpus.priority>0?"":"dimmed"); 185 186 var hue = 80 * corpus.priority / minmaxp[1]; 187 if (corpus.priority > 0) { hue += 40; } 188 var color = minmaxp[0] === minmaxp[1] ? 'transparent' : 'hsl('+hue+', 50%, 50%)'; 189 var priorityStyle = {paddingBottom: 4, paddingLeft: 2, borderBottom: '2px solid '+color }; 162 190 return <div className={corpusContainerClass} key={corpus.displayName}> 163 191 <div className="row corpus"> 164 192 <div className="col-sm-1 vcenter" onClick={this.toggleSelection.bind(this,corpus)}> 165 {this.renderCheckbox(corpus)} 193 <div style={priorityStyle}> 194 {this.renderCheckbox(corpus)} 195 </div> 166 196 </div> 167 197 <div className="col-sm-8 vcenter"> 168 198 <div style={indent}> 169 <h3>{corpus. displayName} :{corpus.priority}</h3>199 <h3>{corpus.title ? corpus.title : corpus.displayName} </h3> 170 200 <p>{corpus.description}</p> 171 201 {this.renderExpansion(corpus)} … … 174 204 <div className="col-sm-3 vcenter"> 175 205 <p><i className="fa fa-institution"/> {corpus.institution.name}</p> 176 <p><i className="fa fa-language"/> {corpus.languages.join(" ")}</p> 206 <p><i className="fa fa-language"/> {this.renderLanguages(corpus.languages)}</p> 207 { corpus.landingPage ? 208 <p><i className="fa fa-home"/> <a href={corpus.landingPage}>{corpus.landingPage}</a></p> : 209 false } 177 210 </div> 178 211 </div> 179 {corpus.expanded ? corpus.subCorpora.map(this.renderCorpus.bind(this, level+1)) : false}212 {corpus.expanded ? corpus.subCorpora.map(this.renderCorpus.bind(this, level+1, minmaxp)) : false} 180 213 </div>; 181 214 }, 182 215 183 216 render: function() { 217 var minmaxp = this.getMinMaxPriority(); 184 218 return <div style={{margin: "0 30px"}}> 185 219 <div className="row"> 186 <div className="float-right"> 220 <div className="float-left inline"> 221 <h3 style={{marginTop:10}}> 222 {this.props.corpora.getSelectedMessage()} 223 </h3> 224 </div> 225 <div className="float-right inline"> 187 226 <div className="inline" style={{ marginRight: 20 }} > 188 227 <SearchCorpusBox search={this.searchCorpus}/> … … 194 233 </div> 195 234 </div> 196 {this.props.corpora.corpora.map(this.renderCorpus.bind(this, 0))}235 {this.props.corpora.corpora.map(this.renderCorpus.bind(this, 0, minmaxp))} 197 236 </div>; 198 237 } -
SRUAggregator/trunk/src/main/resources/assets/js/main.js
r5897 r5900 13 13 var ErrorPane = window.MyReact.ErrorPane; 14 14 15 var multipleLanguageCode = "mul"; // see ISO-693-3 16 15 17 var layers = [ 16 18 { … … 20 22 searchLabel: "SAMPA query", 21 23 searchLabelBkColor: "#eef", 22 allCollections: "All collections",23 24 }, 24 25 { … … 28 29 searchLabel: "Search text", 29 30 searchLabelBkColor: "#fed", 30 allCollections: "All collections",31 31 }, 32 32 ]; … … 39 39 var that = this; 40 40 this.corpora = corpora; 41 this.recurse(function(corpus, index){ 42 corpus.visible = true; // selected in the corpus view 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 43 60 corpus.selected = true; // selected in the corpus view 44 corpus.expanded = false; // expanded in the corpus view61 corpus.expanded = false; // not expanded in the corpus view 45 62 corpus.priority = 1; // priority in corpus view 46 63 corpus.index = index; 47 64 }); 48 this.update = function() {49 updateFn(that);50 };51 65 } 52 66 53 67 Corpora.prototype.recurseCorpus = function(corpus, fn) { 54 fn(corpus); 55 if (corpus.subCorpora) 68 if (false === fn(corpus)) { 69 // no recursion 70 } else { 56 71 this.recurseCorpora(corpus.subCorpora, fn); 72 } 57 73 }; 58 74 59 75 Corpora.prototype.recurseCorpora = function(corpora, fn) { 60 76 var recfn = function(corpus, index){ 61 fn(corpus, index); 62 corpus.subCorpora.forEach(recfn); 77 if (false === fn(corpus)) { 78 // no recursion 79 } else { 80 corpus.subCorpora.forEach(recfn); 81 } 63 82 }; 64 83 corpora.forEach(recfn); … … 75 94 languages[lang] = true; 76 95 }); 96 return true; 77 97 }); 78 98 return languages; 79 99 }; 80 100 81 82 var Main = React.createClass({ 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 158 159 var Main = React.createClass({displayName: 'Main', 83 160 getInitialState: function () { 84 161 return { … … 160 237 161 238 renderAggregator: function() { 162 return <AggregatorPage ajax={this.ajax} corpora={this.state.corpora} languageMap={this.state.languageMap} />;239 return React.createElement(AggregatorPage, {ajax: this.ajax, corpora: this.state.corpora, languageMap: this.state.languageMap}); 163 240 }, 164 241 165 242 renderStatistics: function() { 166 return <StatisticsPage ajax={this.ajax} />;243 return React.createElement(StatisticsPage, {ajax: this.ajax}); 167 244 }, 168 245 169 246 renderHelp: function() { 170 return <HelpPage />;247 return React.createElement(HelpPage, null); 171 248 }, 172 249 … … 182 259 var classname = "navbar-collapse collapse " + (this.state.navbarCollapse?"in":""); 183 260 return ( 184 <div className={classname}>185 <ul className="nav navbar-nav">186 <li className={this.state.navbarPageFn === this.renderAggregator ? "active":""}>187 <a className="link" tabIndex="-1"188 onClick ={this.setNavbarPageFn.bind(this, this.renderAggregator)}>Aggregator</a>189 </li>190 <li className={this.state.navbarPageFn === this.renderStatistics ? "active":""}>191 <a className="link" tabIndex="-1"192 onClick ={this.setNavbarPageFn.bind(this, this.renderStatistics)}>Statistics</a>193 </li>194 <li className={this.state.navbarPageFn === this.renderHelp ? "active":""}>195 <a className="link" tabIndex="-1"196 onClick ={this.setNavbarPageFn.bind(this, this.renderHelp)}>Help</a>197 </li>198 </ul>199 <ul id="CLARIN_header_right" className="nav navbar-nav navbar-right">200 <li className="unauthenticated">201 <a href="login" tabIndex="-1"><span className="glyphicon glyphicon-log-in"></span> LOGIN</a>202 </li>203 </ul>204 </div>261 React.createElement("div", {className: classname}, 262 React.createElement("ul", {className: "nav navbar-nav"}, 263 React.createElement("li", {className: this.state.navbarPageFn === this.renderAggregator ? "active":""}, 264 React.createElement("a", {className: "link", tabIndex: "-1", 265 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 ), 271 React.createElement("li", {className: this.state.navbarPageFn === this.renderHelp ? "active":""}, 272 React.createElement("a", {className: "link", tabIndex: "-1", 273 onClick: this.setNavbarPageFn.bind(this, this.renderHelp)}, "Help") 274 ) 275 ), 276 React.createElement("ul", {id: "CLARIN_header_right", className: "nav navbar-nav navbar-right"}, 277 React.createElement("li", {className: "unauthenticated"}, 278 React.createElement("a", {href: "login", tabIndex: "-1"}, React.createElement("span", {className: "glyphicon glyphicon-log-in"}), " LOGIN") 279 ) 280 ) 281 ) 205 282 ); 206 283 }, … … 208 285 render: function() { 209 286 return ( 210 <div>211 <div className="container">212 <div className="beta-tag">213 <span>BETA</span>214 </div>215 </div>287 React.createElement("div", null, 288 React.createElement("div", {className: "container"}, 289 React.createElement("div", {className: "beta-tag"}, 290 React.createElement("span", null, "BETA") 291 ) 292 ), 216 293 217 <div className="navbar navbar-default navbar-static-top" role="navigation">218 <div className="container">219 <div className="navbar-header">220 <button type="button" className="navbar-toggle" onClick={this.toggleCollapse}>221 <span className="sr-only">Toggle navigation</span>222 <span className="icon-bar"></span>223 <span className="icon-bar"></span>224 <span className="icon-bar"></span>225 </button>226 <a className="navbar-brand" href="#" tabIndex="-1"><header>Federated Content Search</header></a>227 </div>228 {this.renderCollapsible()}229 </div>230 </div>231 232 <ErrorPane errorMessages={this.state.errorMessages} />233 234 <div id="push">235 <div className="container">236 {this.state.navbarPageFn()}237 </div>238 <div className="top-gap" />239 </div>240 </div>294 React.createElement("div", {className: "navbar navbar-default navbar-static-top", role: "navigation"}, 295 React.createElement("div", {className: "container"}, 296 React.createElement("div", {className: "navbar-header"}, 297 React.createElement("button", {type: "button", className: "navbar-toggle", onClick: this.toggleCollapse}, 298 React.createElement("span", {className: "sr-only"}, "Toggle navigation"), 299 React.createElement("span", {className: "icon-bar"}), 300 React.createElement("span", {className: "icon-bar"}), 301 React.createElement("span", {className: "icon-bar"}) 302 ), 303 React.createElement("a", {className: "navbar-brand", href: "#", tabIndex: "-1"}, React.createElement("header", null, "Federated Content Search")) 304 ), 305 this.renderCollapsible() 306 ) 307 ), 308 309 React.createElement(ErrorPane, {errorMessages: this.state.errorMessages}), 310 311 React.createElement("div", {id: "push"}, 312 React.createElement("div", {className: "container"}, 313 this.state.navbarPageFn() 314 ), 315 React.createElement("div", {className: "top-gap"}) 316 ) 317 ) 241 318 ); 242 319 } 243 320 }); 244 321 245 var AggregatorPage = React.createClass({ 322 var AggregatorPage = React.createClass({displayName: 'AggregatorPage', 246 323 propTypes: { 247 324 ajax: PT.func.isRequired, … … 256 333 results: [], 257 334 }, 258 anyLanguage: [ "ANY", "Any Language"],335 anyLanguage: [multipleLanguageCode, "Any Language"], 259 336 260 337 getInitialState: function () { … … 270 347 271 348 search: function(query) { 272 console.log(query);349 // console.log(query); 273 350 if (!query) { 274 351 this.setState({ hits: this.nohits, searchId: null }); … … 280 357 data: { 281 358 layer: this.state.searchLayerId, 359 language: this.state.language[0], 282 360 query: query, 283 361 numberOfResults: this.state.numberOfResults, 362 corporaIds: this.props.corpora.getSelectedIds(), 284 363 }, 285 364 success: function(searchId, textStatus, jqXHR) { 286 console.log("search ["+query+"] ok: ", searchId, jqXHR);365 // console.log("search ["+query+"] ok: ", searchId, jqXHR); 287 366 this.setState({searchId : searchId}); 288 367 this.timeout = 250; … … 309 388 } 310 389 this.setState({hits:json}); 311 console.log("hits:", json);390 // console.log("hits:", json); 312 391 }.bind(this), 313 392 }); 314 393 }, 315 394 316 setAState: function(id, value) { 317 var v = {}; 318 v[id] = value; 319 this.setState(v); 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}); 320 405 }, 321 406 … … 343 428 var layer = layerMap[this.state.searchLayerId]; 344 429 return ( 345 <div className="top-gap">346 <div className="row">347 <div className="aligncenter" style={{marginLeft:16, marginRight:16}}>348 <div className="input-group">349 <span className="input-group-addon" style={{backgroundColor:layer.searchLabelBkColor}}>350 {layer.searchLabel}351 </span>352 353 <SearchBox search={this.search} placeholder={layer.searchPlaceholder} />354 <div className="input-group-btn">355 <button className="btn btn-default input-lg" type="button" onClick={this.search}>356 <i className="glyphicon glyphicon-search"></i>357 </button>358 </div>359 </div>360 </div>361 </div>362 363 <div className="wel" style={{marginTop:20}}>364 <div className="aligncenter" >365 <form className="form-inline" role="form">366 367 <div className="input-group" style={{marginRight:10}}>368 <span className="input-group-addon nobkg">Search in</span>369 <button type="button" className="btn btn-default" onClick={this.toggleCorpusSelection}>370 {layer.allCollections} <span className="caret"/>371 </button>372 </div>373 374 <div className="input-group" style={{marginRight:10}}>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}}, 375 460 376 <span className="input-group-addon nobkg" >of</span>461 React.createElement("span", {className: "input-group-addon nobkg"}, "of"), 377 462 378 <div className="input-group-btn"> 379 <button className="form-control btn btn-default" 380 aria-expanded="false" data-toggle="dropdown"> 381 {this.state.language[1]} <span className="caret"/> 382 </button> 383 <ul ref="languageDropdownMenu" className="dropdown-menu"> 384 <li key={this.anyLanguage[0]}> <a tabIndex="-1" href="#" 385 onClick={this.setAState.bind(this, "language", this.anyLanguage)}> 386 {this.anyLanguage[1]}</a> 387 </li> 388 { _.pairs(this.props.languageMap).map(function(l) { 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) { 389 476 var desc = l[1] + " [" + l[0] + "]"; 390 return <li key={l[0]}> <a tabIndex="-1" href="#"391 onClick ={this.setAState.bind(this, "language", l)}>{desc}</a></li>;477 return React.createElement("li", {key: l[0]}, " ", React.createElement("a", {tabIndex: "-1", href: "#", 478 onClick: this.setLanguage.bind(this, l)}, desc)); 392 479 }.bind(this)) 393 }394 </ul>395 </div>396 397 <div className="input-group-btn">398 <ul ref="layerDropdownMenu" className="dropdown-menu">399 {layers.map(function(l) {400 return <li key={l.id}> <a tabIndex="-1" href="#"401 onClick ={this.setAState.bind(this, "searchLayerId", l.id)}> {l.name} </a></li>;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, " ")); 402 489 }.bind(this)) 403 }404 </ul>405 <button className="form-control btn btn-default"406 aria-expanded="false" data-toggle="dropdown" >407 {layer.name} <span className="caret"/>408 </button>409 </div>410 411 </div>412 413 <div className="input-group">414 <span className="input-group-addon nobkg">and show up to</span>415 <div className="input-group-btn">416 <input type="number" className="form-control input" min="10" max="250" step="5"417 onChange ={this.setNumberOfResults} value={this.state.numberOfResults}418 onKeyPress ={this.stop}/>419 </div>420 <span className="input-group-addon nobkg">hits</span>421 </div>422 </form>423 </div>424 </div>425 426 <Modal ref="corporaModal" title="Collections">427 <CorpusView ref="corpusView" corpora={this.props.corpora} />428 </Modal>429 430 <div className="top-gap">431 <Results requests={this.state.hits.requests} results={this.state.hits.results} />432 </div>433 </div>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 ) 434 521 ); 435 522 }, … … 439 526 }); 440 527 441 var StatisticsPage = React.createClass({ 528 var StatisticsPage = React.createClass({displayName: 'StatisticsPage', 442 529 propTypes: { 443 530 ajax: PT.func.isRequired, … … 469 556 470 557 listItem: function(it) { 471 return <li> {it[0]}:472 {typeof(it[1]) === "object" ?473 <ul>{_.pairs(it[1]).map(this.listItem)}</ul>:558 return React.createElement("li", null, " ", it[0], ":", 559 typeof(it[1]) === "object" ? 560 React.createElement("ul", null, _.pairs(it[1]).map(this.listItem)) : 474 561 it[1] 475 }476 </li>;562 563 ); 477 564 }, 478 565 … … 506 593 507 594 renderStatistics: function(stats) { 508 return <ul>{_.pairs(stats).map(this.listItem)}</ul>;595 return React.createElement("ul", null, _.pairs(stats).map(this.listItem)); 509 596 }, 510 597 511 598 render: function() { 512 599 return ( 513 <div>514 <div className="top-gap">515 <h1>Statistics</h1>516 <h2>Last Scan</h2>517 {this.renderStatistics(this.state.lastScanStats)}518 <h2>Search</h2>519 {this.renderStatistics(this.state.searchStats)}520 </div>521 </div>600 React.createElement("div", null, 601 React.createElement("div", {className: "top-gap"}, 602 React.createElement("h1", null, "Statistics"), 603 React.createElement("h2", null, "Last Scan"), 604 this.renderStatistics(this.state.lastScanStats), 605 React.createElement("h2", null, "Search"), 606 this.renderStatistics(this.state.searchStats) 607 ) 608 ) 522 609 ); 523 610 }, 524 611 }); 525 612 526 var HelpPage = React.createClass({ 613 var HelpPage = React.createClass({displayName: 'HelpPage', 527 614 openHelpDesk: function() { 528 615 window.open('http://support.clarin-d.de/mail/form.php?queue=Aggregator', … … 532 619 render: function() { 533 620 return ( 534 <div>535 <div className="top-gap">536 <h3>Performing search in FCS corpora</h3>537 <p>To perform simple keyword search in all CLARIN-D Federated Content Search centers538 and their corpora, go to the search field at the top of the page,539 enter your query, and click 'search' button or press the 'Enter' key.</p>621 React.createElement("div", null, 622 React.createElement("div", {className: "top-gap"}, 623 React.createElement("h3", null, "Performing search in FCS corpora"), 624 React.createElement("p", null, "To perform simple keyword search in all CLARIN-D Federated Content Search centers" + ' ' + 625 "and their corpora, go to the search field at the top of the page," + ' ' + 626 "enter your query, and click 'search' button or press the 'Enter' key."), 540 627 541 <h3>Search Options - adjusting search criteria</h3>542 <p>To select specific corpora based on their name or language and to specify543 number of search results (hits) per corpus per page, click on the 'Search options'544 link. Here, you can filter resources based on the language, select specific resources,545 set the maximum number of hits.</p>546 547 <h3>Search Results - inspecting search results</h3>548 <p>When the search starts, the 'Search results' page is displayed549 and its content starts to get filled with the corpora responses.550 To save or process the displayed search result, in the 'Search results' page,551 go to the menu and select either 'Export to Personal Workspace',552 'Download' or 'Use WebLicht' menu item. This menu appears only after553 all the results on the page have been loaded. To get the next hits from each corpus,554 click the 'next' arrow at the bottom of 'Search results' page.</p>555 556 557 <h3>More help</h3>558 <p>More detailed information on using FCS Aggregator is available559 at the Aggegator wiki page. If you still cannot find an answer to your question,560 or if want to send a feedback, you can write to Clarin-D helpdesk: </p>561 <button type="button" className="btn btn-default btn-lg" onClick={this.openHelpDesk} >562 <span className="glyphicon glyphicon-question-sign" aria-hidden="true"></span>563 HelpDesk564 </button>565 </div>566 </div>628 React.createElement("h3", null, "Search Options - adjusting search criteria"), 629 React.createElement("p", null, "To select specific corpora based on their name or language and to specify" + ' ' + 630 "number of search results (hits) per corpus per page, click on the 'Search options'" + ' ' + 631 "link. Here, you can filter resources based on the language, select specific resources," + ' ' + 632 "set the maximum number of hits."), 633 634 React.createElement("h3", null, "Search Results - inspecting search results"), 635 React.createElement("p", null, "When the search starts, the 'Search results' page is displayed" + ' ' + 636 "and its content starts to get filled with the corpora responses." + ' ' + 637 "To save or process the displayed search result, in the 'Search results' page," + ' ' + 638 "go to the menu and select either 'Export to Personal Workspace'," + ' ' + 639 "'Download' or 'Use WebLicht' menu item. This menu appears only after" + ' ' + 640 "all the results on the page have been loaded. To get the next hits from each corpus," + ' ' + 641 "click the 'next' arrow at the bottom of 'Search results' page."), 642 643 644 React.createElement("h3", null, "More help"), 645 React.createElement("p", null, "More detailed information on using FCS Aggregator is available" + ' ' + 646 "at the Aggegator wiki page. If you still cannot find an answer to your question," + ' ' + 647 "or if want to send a feedback, you can write to Clarin-D helpdesk: "), 648 React.createElement("button", {type: "button", className: "btn btn-default btn-lg", onClick: this.openHelpDesk}, 649 React.createElement("span", {className: "glyphicon glyphicon-question-sign", 'aria-hidden': "true"}), 650 "Â HelpDesk" 651 ) 652 ) 653 ) 567 654 ); 568 655 } … … 592 679 593 680 594 React.render( <Main />, document.getElementById('reactMain') );681 React.render(React.createElement(Main, null), document.getElementById('reactMain') ); 595 682 })(); -
SRUAggregator/trunk/src/main/resources/assets/js/main.jsx
r5897 r5900 13 13 var ErrorPane = window.MyReact.ErrorPane; 14 14 15 var multipleLanguageCode = "mul"; // see ISO-693-3 16 15 17 var layers = [ 16 18 { … … 20 22 searchLabel: "SAMPA query", 21 23 searchLabelBkColor: "#eef", 22 allCollections: "All collections",23 24 }, 24 25 { … … 28 29 searchLabel: "Search text", 29 30 searchLabelBkColor: "#fed", 30 allCollections: "All collections",31 31 }, 32 32 ]; … … 39 39 var that = this; 40 40 this.corpora = corpora; 41 this.recurse(function(corpus, index){ 42 corpus.visible = true; // selected in the corpus view 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 43 60 corpus.selected = true; // selected in the corpus view 44 corpus.expanded = false; // expanded in the corpus view61 corpus.expanded = false; // not expanded in the corpus view 45 62 corpus.priority = 1; // priority in corpus view 46 63 corpus.index = index; 47 64 }); 48 this.update = function() {49 updateFn(that);50 };51 65 } 52 66 53 67 Corpora.prototype.recurseCorpus = function(corpus, fn) { 54 fn(corpus); 55 if (corpus.subCorpora) 68 if (false === fn(corpus)) { 69 // no recursion 70 } else { 56 71 this.recurseCorpora(corpus.subCorpora, fn); 72 } 57 73 }; 58 74 59 75 Corpora.prototype.recurseCorpora = function(corpora, fn) { 60 76 var recfn = function(corpus, index){ 61 fn(corpus, index); 62 corpus.subCorpora.forEach(recfn); 77 if (false === fn(corpus)) { 78 // no recursion 79 } else { 80 corpus.subCorpora.forEach(recfn); 81 } 63 82 }; 64 83 corpora.forEach(recfn); … … 75 94 languages[lang] = true; 76 95 }); 96 return true; 77 97 }); 78 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"; 79 156 }; 80 157 … … 256 333 results: [], 257 334 }, 258 anyLanguage: [ "ANY", "Any Language"],335 anyLanguage: [multipleLanguageCode, "Any Language"], 259 336 260 337 getInitialState: function () { … … 270 347 271 348 search: function(query) { 272 console.log(query);349 // console.log(query); 273 350 if (!query) { 274 351 this.setState({ hits: this.nohits, searchId: null }); … … 280 357 data: { 281 358 layer: this.state.searchLayerId, 359 language: this.state.language[0], 282 360 query: query, 283 361 numberOfResults: this.state.numberOfResults, 362 corporaIds: this.props.corpora.getSelectedIds(), 284 363 }, 285 364 success: function(searchId, textStatus, jqXHR) { 286 console.log("search ["+query+"] ok: ", searchId, jqXHR);365 // console.log("search ["+query+"] ok: ", searchId, jqXHR); 287 366 this.setState({searchId : searchId}); 288 367 this.timeout = 250; … … 309 388 } 310 389 this.setState({hits:json}); 311 console.log("hits:", json);390 // console.log("hits:", json); 312 391 }.bind(this), 313 392 }); 314 393 }, 315 394 316 setAState: function(id, value) { 317 var v = {}; 318 v[id] = value; 319 this.setState(v); 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}); 320 405 }, 321 406 … … 368 453 <span className="input-group-addon nobkg">Search in</span> 369 454 <button type="button" className="btn btn-default" onClick={this.toggleCorpusSelection}> 370 { layer.allCollections} <span className="caret"/>455 {this.props.corpora.getSelectedMessage()} <span className="caret"/> 371 456 </button> 372 457 </div> … … 383 468 <ul ref="languageDropdownMenu" className="dropdown-menu"> 384 469 <li key={this.anyLanguage[0]}> <a tabIndex="-1" href="#" 385 onClick={this.set AState.bind(this, "language", this.anyLanguage)}>470 onClick={this.setLanguage.bind(this, this.anyLanguage)}> 386 471 {this.anyLanguage[1]}</a> 387 472 </li> 388 { _.pairs(this.props.languageMap).map(function(l) { 473 { _.pairs(this.props.languageMap).sort(function(l1, l2){ 474 return l1[1].localeCompare(l2[1]); 475 }).map(function(l) { 389 476 var desc = l[1] + " [" + l[0] + "]"; 390 477 return <li key={l[0]}> <a tabIndex="-1" href="#" 391 onClick={this.set AState.bind(this, "language", l)}>{desc}</a></li>;478 onClick={this.setLanguage.bind(this, l)}>{desc}</a></li>; 392 479 }.bind(this)) 393 480 } … … 399 486 { layers.map(function(l) { 400 487 return <li key={l.id}> <a tabIndex="-1" href="#" 401 onClick={this.set AState.bind(this, "searchLayerId", l.id)}> {l.name} </a></li>;488 onClick={this.setLayer.bind(this, l.id)}> {l.name} </a></li>; 402 489 }.bind(this)) 403 490 } … … 425 512 426 513 <Modal ref="corporaModal" title="Collections"> 427 <CorpusView ref="corpusView" corpora={this.props.corpora} />514 <CorpusView corpora={this.props.corpora} languageMap={this.props.languageMap} /> 428 515 </Modal> 429 516 -
SRUAggregator/trunk/src/main/resources/assets/js/search.js
r5897 r5900 90 90 var inline = {display:"inline-block"}; 91 91 return React.createElement("div", {style: inline}, 92 React.createElement("span", {className: "corpusName"}, " ", corpus. displayName),92 React.createElement("span", {className: "corpusName"}, " ", corpus.title ? corpus.title : corpus.displayName), 93 93 React.createElement("span", {className: "institutionName"}, " â ", corpus.institution.name) 94 94 ); … … 98 98 var inline = {display:"inline-block"}; 99 99 return React.createElement("div", null, 100 React.createElement(InfoPopover, {placement: "left", title: corpus.displayName}, 100 React.createElement(InfoPopover, {placement: "left", 101 title: corpus.title ? corpus.title : corpus.displayName}, 101 102 React.createElement("dl", {className: "dl-horizontal"}, 102 103 React.createElement("dt", null, "Institution"), … … 165 166 }, 166 167 167 renderFoundMessage: function( ) {168 renderFoundMessage: function(hits) { 168 169 if (this.props.results.length === 0) 169 170 return false; 170 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length;171 171 var total = this.props.results.length; 172 172 return hits + " collections with results found in " + total + " searched collections"; … … 191 191 192 192 render: function() { 193 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length; 193 194 var margintop = {marginTop:"10px"}; 194 195 var margin = {marginTop:"0", padding:"20px"}; … … 198 199 React.createElement(ReactCSSTransitionGroup, {transitionName: "fade"}, 199 200 React.createElement("div", {key: "-searching-message-", style: margintop}, this.renderSearchingMessage(), " "), 200 React.createElement("div", {key: "-found-message-", style: margintop}, this.renderFoundMessage( ), " "),201 React.createElement("div", {key: "-found-message-", style: margintop}, this.renderFoundMessage(hits), " "), 201 202 React.createElement("div", {key: "-progress-", style: margintop}, this.renderProgressBar()), 202 this.props.results.length> 0 ? this.renderKwicCheckbox() : false,203 hits > 0 ? this.renderKwicCheckbox() : false, 203 204 this.props.results.map(this.renderResultPanels) 204 205 ) -
SRUAggregator/trunk/src/main/resources/assets/js/search.jsx
r5897 r5900 90 90 var inline = {display:"inline-block"}; 91 91 return <div style={inline}> 92 <span className="corpusName"> {corpus. displayName}</span>92 <span className="corpusName"> {corpus.title ? corpus.title : corpus.displayName}</span> 93 93 <span className="institutionName"> â {corpus.institution.name}</span> 94 94 </div>; … … 98 98 var inline = {display:"inline-block"}; 99 99 return <div> 100 <InfoPopover placement="left" title={corpus.displayName}> 100 <InfoPopover placement="left" 101 title={corpus.title ? corpus.title : corpus.displayName}> 101 102 <dl className="dl-horizontal"> 102 103 <dt>Institution</dt> … … 165 166 }, 166 167 167 renderFoundMessage: function( ) {168 renderFoundMessage: function(hits) { 168 169 if (this.props.results.length === 0) 169 170 return false; 170 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length;171 171 var total = this.props.results.length; 172 172 return hits + " collections with results found in " + total + " searched collections"; … … 191 191 192 192 render: function() { 193 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length; 193 194 var margintop = {marginTop:"10px"}; 194 195 var margin = {marginTop:"0", padding:"20px"}; … … 198 199 <ReactCSSTransitionGroup transitionName="fade"> 199 200 <div key="-searching-message-" style={margintop}>{this.renderSearchingMessage()} </div> 200 <div key="-found-message-" style={margintop}>{this.renderFoundMessage( )} </div>201 <div key="-found-message-" style={margintop}>{this.renderFoundMessage(hits)} </div> 201 202 <div key="-progress-" style={margintop}>{this.renderProgressBar()}</div> 202 { this.props.results.length> 0 ? this.renderKwicCheckbox() : false}203 {hits > 0 ? this.renderKwicCheckbox() : false} 203 204 {this.props.results.map(this.renderResultPanels)} 204 205 </ReactCSSTransitionGroup>
Note: See TracChangeset
for help on using the changeset viewer.