Changeset 6081
- Timestamp:
- 03/05/15 18:24:55 (9 years ago)
- Location:
- SRUAggregator/trunk
- Files:
-
- 16 edited
Legend:
- Unmodified
- Added
- Removed
-
SRUAggregator/trunk/aggregator.yml
r6065 r6081 36 36 - fr 37 37 - it 38 - sp38 - es 39 39 - pl 40 40 -
SRUAggregator/trunk/pom.xml
r6065 r6081 8 8 <groupId>eu.clarin.sru.fcs</groupId> 9 9 <artifactId>Aggregator2</artifactId> 10 <version>2.0.0-alpha-2 5</version>10 <version>2.0.0-alpha-26</version> 11 11 <name>FCS Aggregator</name> 12 12 … … 54 54 <groupId>eu.clarin.sru</groupId> 55 55 <artifactId>sru-client</artifactId> 56 <version>0.9.5 -DEBUG</version>56 <version>0.9.5</version> 57 57 <exclusions> 58 58 <exclusion> -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java
r6065 r6081 86 86 * @author edima 87 87 * 88 * TODO: ?use weblicht only to show up in zoomed mode89 * - send only tcf with only a text layer and language (from the list in params)90 *91 88 * TODO: add the modes described above (except live) 92 89 * 93 * TODO: zoom into the results from a corpus, allow functionality only for 94 * the view (search for next set of results) 95 * 96 * TODO: Export search results to personal workspace as csv, excel, tcf, plain 97 * text: ask Marie/Wei about oauth ways to do that ndg oauth; ask Menzo, Willem, 98 * Twan (they did a test, it worked) 99 * 100 * TODO: add PiWik support, tracking the following: 101 * - visits, searches, search per corpus 102 * 103 * TODO: BUG: language detection is immediate, in UI; export implications 90 * TODO: Export search results to personal workspace using oauth 104 91 * 105 92 * TODO: websockets … … 155 142 params = config.aggregatorParams; 156 143 instance = this; 144 145 List<String> wll = new ArrayList<String>(); 146 for (String l : config.aggregatorParams.weblichtConfig.getAcceptedTcfLanguages()) { 147 wll.add(LanguagesISO693.getInstance().code_3ForCode(l)); 148 } 149 config.aggregatorParams.weblichtConfig.acceptedTcfLanguages = wll; 157 150 158 151 System.out.println("Using parameters: "); -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/rest/RestService.java
r6065 r6081 8 8 import eu.clarin.sru.fcs.aggregator.app.AggregatorConfiguration; 9 9 import eu.clarin.sru.fcs.aggregator.app.AggregatorConfiguration.Params.WeblichtConfig; 10 import eu.clarin.sru.fcs.aggregator.scan.Corpora; 10 11 import eu.clarin.sru.fcs.aggregator.scan.Corpus; 11 12 import eu.clarin.sru.fcs.aggregator.scan.Statistics; … … 70 71 71 72 @GET 73 @Path("languages") 74 public Response getLanguages() throws IOException { 75 Set<String> codes = Aggregator.getInstance().getCorpora().getLanguages(); 76 log.info("get language codes", codes); 77 Map<String, String> languages = LanguagesISO693.getInstance().getLanguageMap(codes); 78 return Response.ok(toJson(languages)).build(); 79 } 80 81 @GET 82 @Path("init") 83 public Response getInit() throws IOException { 84 log.info("get initial data"); 85 final Corpora corpora = Aggregator.getInstance().getCorpora(); 86 Object j = new HashMap<String, Object>() { 87 { 88 put("corpora", corpora.getCorpora()); 89 put("languages", LanguagesISO693.getInstance().getLanguageMap(corpora.getLanguages())); 90 put("weblichtLanguages", Aggregator.getInstance().getParams().getWeblichtConfig().getAcceptedTcfLanguages()); 91 } 92 }; 93 return Response.ok(toJson(j)).build(); 94 } 95 96 @POST 97 @Path("search") 98 public Response postSearch( 99 @FormParam("query") String query, 100 @FormParam("numberOfResults") Integer numberOfResults, 101 @FormParam("language") String language, 102 @FormParam("useLanguageGuesser") boolean useLanguageGuesser, 103 @FormParam("corporaIds[]") List<String> corporaIds) throws Exception { 104 if (query == null || query.isEmpty()) { 105 return Response.status(400).entity("'query' parameter expected").build(); 106 } 107 // log.info("POST /search corporaIds: " + corporaIds); 108 if (corporaIds == null || corporaIds.isEmpty()) { 109 return Response.status(400).entity("'corporaIds' parameter expected").build(); 110 } 111 List<Corpus> corpora = Aggregator.getInstance().getCorpora().getCorporaByIds(new HashSet<String>(corporaIds)); 112 if (corpora == null || corpora.isEmpty()) { 113 return Response.status(503).entity("No corpora, please wait for the server to finish scanning").build(); 114 } 115 if (numberOfResults == null || numberOfResults < 10) { 116 numberOfResults = 10; 117 } 118 if (numberOfResults > 250) { 119 numberOfResults = 250; 120 } 121 Search search = Aggregator.getInstance().startSearch(SRUVersion.VERSION_1_2, corpora, query, language, numberOfResults); 122 if (search == null) { 123 return Response.status(500).entity("Initiating search failed").build(); 124 } 125 URI uri = URI.create("" + search.getId()); 126 return Response.created(uri).entity(uri).build(); 127 } 128 129 public static class JsonSearch { 130 131 List<Request> requests; 132 List<Result> results; 133 134 public JsonSearch(List<Request> requests, List<Result> results) { 135 this.requests = requests; 136 this.results = results; 137 } 138 139 public List<Request> getRequests() { 140 return requests; 141 } 142 143 public List<Result> getResults() { 144 return results; 145 } 146 } 147 148 @GET 149 @Path("search/{id}") 150 public Response getSearch(@PathParam("id") Long searchId, 151 @QueryParam("corpusId") String corpusId) throws Exception { 152 Search search = Aggregator.getInstance().getSearchById(searchId); 153 if (search == null) { 154 return Response.status(Response.Status.NOT_FOUND).entity("Search job not found").build(); 155 } 156 157 JsonSearch js = new JsonSearch(search.getRequests(), search.getResults(corpusId)); 158 return Response.ok(js).build(); 159 } 160 161 @GET 162 @Path("search/{id}/download") 163 public Response downloadSearchResults(@PathParam("id") Long searchId, 164 @QueryParam("corpusId") String corpusId, 165 @QueryParam("filterLanguage") String filterLanguage, 166 @QueryParam("format") String format) throws Exception { 167 Search search = Aggregator.getInstance().getSearchById(searchId); 168 if (search == null) { 169 return Response.status(Response.Status.NOT_FOUND).entity("Search job not found").build(); 170 } 171 if (filterLanguage == null || filterLanguage.isEmpty()) { 172 filterLanguage = null; 173 } 174 175 if (format == null || format.trim().isEmpty() || format.trim().equals("text")) { 176 String text = Exports.getExportText(search.getResults(corpusId), filterLanguage); 177 return download(text, MediaType.TEXT_PLAIN, search.getQuery() + ".txt"); 178 } else if (format.equals("tcf")) { 179 byte[] bytes = Exports.getExportTCF( 180 search.getResults(corpusId), search.getSearchLanguage(), filterLanguage); 181 return download(bytes, TCF_MEDIA_TYPE, search.getQuery() + ".xml"); 182 } else if (format.equals("excel")) { 183 byte[] bytes = Exports.getExportExcel(search.getResults(corpusId), filterLanguage); 184 return download(bytes, EXCEL_MEDIA_TYPE, search.getQuery() + ".xls"); 185 } else if (format.equals("csv")) { 186 String csv = Exports.getExportCSV(search.getResults(corpusId), filterLanguage, ";"); 187 return download(csv, MediaType.TEXT_PLAIN, search.getQuery() + ".csv"); 188 } 189 190 return Response.status(Response.Status.BAD_REQUEST) 191 .entity("format parameter must be one of: text, tcf, excel, csv") 192 .build(); 193 } 194 195 Response download(Object entity, String mediaType, String filesuffix) { 196 if (entity == null) { 197 return Response.status(Response.Status.INTERNAL_SERVER_ERROR) 198 .entity("error while converting to the export format").build(); 199 } 200 String filename = EXPORT_FILENAME_PREFIX + filesuffix; 201 return Response.ok(entity, mediaType) 202 .header("Content-Disposition", "attachment; filename=\"" + filename + "\"") 203 .build(); 204 } 205 206 @GET 207 @Path("search/{id}/toWeblicht") 208 public Response sendSearchResultsToWeblicht(@PathParam("id") Long searchId, 209 @QueryParam("filterLanguage") String filterLanguage, 210 @QueryParam("corpusId") String corpusId) throws Exception { 211 Search search = Aggregator.getInstance().getSearchById(searchId); 212 if (search == null) { 213 return Response.status(Response.Status.NOT_FOUND).entity("Search job not found").build(); 214 } 215 if (filterLanguage == null || filterLanguage.isEmpty()) { 216 filterLanguage = null; 217 } 218 219 String url = null; 220 byte[] bytes = Exports.getExportTCF( 221 search.getResults(corpusId), search.getSearchLanguage(), filterLanguage); 222 if (bytes != null) { 223 url = DataTransfer.uploadToDropOff(bytes, "text/tcf+xml", ".tcf"); 224 } 225 226 WeblichtConfig weblicht = Aggregator.getInstance().getParams().getWeblichtConfig(); 227 URI weblichtUri = new URI(weblicht.getUrl() + url); 228 return url == null 229 ? Response.status(503).entity("error while exporting to weblicht").build() 230 : Response.seeOther(weblichtUri).entity(weblichtUri).build(); 231 } 232 233 @GET 72 234 @Path("statistics") 73 235 public Response getStatistics() throws IOException { … … 103 265 } 104 266 105 @GET106 @Path("languages")107 public Response getLanguages() throws IOException {108 Map<String, String> languages = new HashMap<String, String>();109 Set<String> codes = Aggregator.getInstance().getCorpora().getLanguages();110 log.info("get language codes", codes);111 for (String code : codes) {112 String name = LanguagesISO693.getInstance().nameForCode(code);113 languages.put(code, name != null ? name : code);114 }115 return Response.ok(toJson(languages)).build();116 }117 118 @POST119 @Path("search")120 public Response postSearch(121 @FormParam("query") String query,122 @FormParam("numberOfResults") Integer numberOfResults,123 @FormParam("language") String language,124 @FormParam("useLanguageGuesser") boolean useLanguageGuesser,125 @FormParam("corporaIds[]") List<String> corporaIds) throws Exception {126 if (query == null || query.isEmpty()) {127 return Response.status(400).entity("'query' parameter expected").build();128 }129 // log.info("POST /search corporaIds: " + corporaIds);130 if (corporaIds == null || corporaIds.isEmpty()) {131 return Response.status(400).entity("'corporaIds' parameter expected").build();132 }133 List<Corpus> corpora = Aggregator.getInstance().getCorpora().getCorporaByIds(new HashSet<String>(corporaIds));134 if (corpora == null || corpora.isEmpty()) {135 return Response.status(503).entity("No corpora, please wait for the server to finish scanning").build();136 }137 if (numberOfResults == null || numberOfResults < 10) {138 numberOfResults = 10;139 }140 if (numberOfResults > 250) {141 numberOfResults = 250;142 }143 Search search = Aggregator.getInstance().startSearch(SRUVersion.VERSION_1_2, corpora, query, language, numberOfResults);144 if (search == null) {145 return Response.status(500).entity("Initiating search failed").build();146 }147 URI uri = URI.create("" + search.getId());148 return Response.created(uri).entity(uri).build();149 }150 151 public static class JsonSearch {152 153 List<Request> requests;154 List<Result> results;155 156 public JsonSearch(List<Request> requests, List<Result> results) {157 this.requests = requests;158 this.results = results;159 }160 161 public List<Request> getRequests() {162 return requests;163 }164 165 public List<Result> getResults() {166 return results;167 }168 }169 170 @GET171 @Path("search/{id}")172 public Response getSearch(@PathParam("id") Long searchId) throws Exception {173 Search search = Aggregator.getInstance().getSearchById(searchId);174 if (search == null) {175 return Response.status(Response.Status.NOT_FOUND).entity("Search job not found").build();176 }177 178 JsonSearch js = new JsonSearch(search.getRequests(), search.getResults());179 return Response.ok(js).build();180 }181 182 @GET183 @Path("search/{id}/download")184 public Response downloadSearchResults(@PathParam("id") Long searchId,185 @QueryParam("format") String format) throws Exception {186 Search search = Aggregator.getInstance().getSearchById(searchId);187 if (search == null) {188 return Response.status(Response.Status.NOT_FOUND).entity("Search job not found").build();189 }190 191 if (format == null || format.trim().isEmpty() || format.trim().equals("text")) {192 String text = Exports.getExportText(search.getResults());193 return download(text, MediaType.TEXT_PLAIN, search.getQuery() + ".txt");194 } else if (format.equals("tcf")) {195 byte[] bytes = Exports.getExportTCF(196 search.getResults(), search.getSearchLanguage());197 return download(bytes, TCF_MEDIA_TYPE, search.getQuery() + ".xml");198 } else if (format.equals("excel")) {199 byte[] bytes = Exports.getExportExcel(search.getResults());200 return download(bytes, EXCEL_MEDIA_TYPE, search.getQuery() + ".xls");201 } else if (format.equals("csv")) {202 String csv = Exports.getExportCSV(search.getResults(), ";");203 return download(csv, MediaType.TEXT_PLAIN, search.getQuery() + ".csv");204 }205 206 return Response.status(Response.Status.BAD_REQUEST)207 .entity("format parameter must be one of: text, tcf, excel, csv")208 .build();209 }210 211 Response download(Object entity, String mediaType, String filesuffix) {212 if (entity == null) {213 return Response.status(Response.Status.INTERNAL_SERVER_ERROR)214 .entity("error while converting to the export format").build();215 }216 String filename = EXPORT_FILENAME_PREFIX + filesuffix;217 return Response.ok(entity, mediaType)218 .header("Content-Disposition", "attachment; filename=\"" + filename + "\"")219 .build();220 }221 222 @GET223 @Path("search/{id}/toWeblicht")224 public Response sendSearchResultsToWeblicht(@PathParam("id") Long searchId,225 @QueryParam("format") String format) throws Exception {226 Search search = Aggregator.getInstance().getSearchById(searchId);227 if (search == null) {228 return Response.status(Response.Status.NOT_FOUND).entity("Search job not found").build();229 }230 231 String url = null;232 if (format == null || format.isEmpty() || format.trim().equals("text")) {233 String text = Exports.getExportText(search.getResults());234 if (text != null) {235 byte[] bytes = text.getBytes(SEARCH_RESULTS_ENCODING);236 url = DataTransfer.uploadToDropOff(bytes, "text/plan", ".txt");237 }238 } else if (format.equals("tcf")) {239 byte[] bytes = Exports.getExportTCF(240 search.getResults(), search.getSearchLanguage());241 if (bytes != null) {242 url = DataTransfer.uploadToDropOff(bytes, "text/tcf+xml", ".tcf");243 }244 } else {245 return Response.status(400).entity("incorrect format parameter").build();246 }247 248 WeblichtConfig weblicht = Aggregator.getInstance().getParams().getWeblichtConfig();249 URI weblichtUri = new URI(weblicht.getUrl() + url);250 return url == null251 ? Response.status(503).entity("error while exporting to weblicht").build()252 : Response.seeOther(weblichtUri).entity(weblichtUri).build();253 }254 267 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawler.java
r6057 r6081 85 85 final Endpoint endpoint; 86 86 final Corpora corpora; 87 String fullRequestUrl;88 87 89 88 ExplainTask(final Institution institution, final Endpoint endpoint, final Corpora corpora) { … … 99 98 explainRequest.setExtraRequestData(SRUCQL.EXPLAIN_ASK_FOR_RESOURCES_PARAM, "true"); 100 99 explainRequest.setParseRecordDataEnabled(true); 101 fullRequestUrl = explainRequest.makeURI(SRUVersion.VERSION_1_2).toString();102 100 } catch (Throwable ex) { 103 101 log.error("Exception creating explain request for {}: {}", endpoint.getUrl(), ex.getMessage()); … … 135 133 SRUExplainRequest request = response.getRequest(); 136 134 Diagnostic diag = new Diagnostic(d.getURI(), d.getMessage(), d.getDetails()); 137 statistics.addEndpointDiagnostic(institution, endpoint, diag, fullRequestUrl);138 log.info("Diagnostic: {}: {}", fullRequestUrl, diag.message);135 statistics.addEndpointDiagnostic(institution, endpoint, diag, response.getRequest().getRequestedURI().toString()); 136 log.info("Diagnostic: {}: {}", response.getRequest().getRequestedURI().toString(), diag.message); 139 137 } 140 138 } … … 144 142 log.error("{} Exception in explain callback {}", latch.get(), endpoint.getUrl()); 145 143 log.error("--> ", xc); 146 statistics.addErrorDatapoint(institution, endpoint, xc, fullRequestUrl);144 statistics.addErrorDatapoint(institution, endpoint, xc, response.getRequest().getRequestedURI().toString()); 147 145 } finally { 148 146 if (endpoint.getProtocol().equals(FCSProtocolVersion.LEGACY)) { 149 147 new ScanTask(institution, endpoint, null, corpora, 0).start(); 150 148 Diagnostic diag = new Diagnostic("LEGACY", "Endpoint didn't return any resource on EXPLAIN, presuming legacy support", ""); 151 statistics.addEndpointDiagnostic(institution, endpoint, diag, fullRequestUrl);149 statistics.addEndpointDiagnostic(institution, endpoint, diag, response.getRequest().getRequestedURI().toString()); 152 150 } 153 151 … … 161 159 log.error("{} Error while explaining {}: {}", latch.get(), endpoint.getUrl(), error.getMessage()); 162 160 statistics.addEndpointDatapoint(institution, endpoint, stats.getQueueTime(), stats.getExecutionTime()); 163 statistics.addErrorDatapoint(institution, endpoint, error, fullRequestUrl);161 statistics.addErrorDatapoint(institution, endpoint, error, request.getRequestedURI().toString()); 164 162 if (Throw.isCausedBy(error, SocketTimeoutException.class)) { 165 163 return; … … 250 248 scanRequest.setExtraRequestData(SRUCQL.SCAN_RESOURCE_INFO_PARAMETER, 251 249 SRUCQL.SCAN_RESOURCE_INFO_PARAMETER_DEFAULT_VALUE); 252 fullRequestUrl = scanRequest. makeURI(SRUVersion.VERSION_1_2).toString();250 fullRequestUrl = scanRequest.getRequestedURI().toString(); 253 251 } catch (Throwable ex) { 254 252 log.error("Exception creating scan request for {}: {}", endpoint.getUrl(), ex.getMessage()); -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Exports.java
r6065 r6081 28 28 private static final Logger LOGGER = Logger.getLogger(Exports.class.getName()); 29 29 30 public static String getExportCSV(List<Result> resultsProcessed, String separator) { 31 30 public static String getExportCSV(List<Result> resultsProcessed, String filterLanguage, String separator) { 32 31 boolean noResult = true; 33 32 StringBuilder csv = new StringBuilder(); … … 45 44 for (Result result : resultsProcessed) { 46 45 for (Kwic kwic : result.getKwics()) { 46 if (filterLanguage != null && !filterLanguage.equals(kwic.getLanguage())) { 47 continue; 48 } 47 49 csv.append("\""); 48 50 csv.append(escapeQuotes(kwic.getLeft())); … … 92 94 } 93 95 94 public static byte[] getExportExcel(List<Result> resultsProcessed) throws ExportException { 95 96 boolean noResult = true; 96 public static byte[] getExportExcel(List<Result> resultsProcessed, String filterLanguage) throws ExportException { 97 97 SXSSFWorkbook workbook = null; 98 98 ByteArrayOutputStream excelStream = new ByteArrayOutputStream(); 99 int rownum = 0; 99 100 if (resultsProcessed != null && !resultsProcessed.isEmpty()) { 100 101 try { … … 112 113 headerStyle.setFont(boldFont); 113 114 114 Row row = sheet.createRow( 0);115 Row row = sheet.createRow(rownum++); 115 116 116 117 for (int j = 0; j < headers.length; ++j) { … … 122 123 // Body 123 124 Cell cell; 124 for ( int k = 0; k < resultsProcessed.size(); k++) {125 Result result = resultsProcessed.get(k);126 List<Kwic> kwics = result.getKwics();127 for (int i = 0; i < kwics.size(); i++) {128 Kwic kwic = kwics.get(i);129 row = sheet.createRow( k + i + 1);125 for (Result result : resultsProcessed) { 126 for (Kwic kwic : result.getKwics()) { 127 if (filterLanguage != null && !filterLanguage.equals(kwic.getLanguage())) { 128 continue; 129 } 130 row = sheet.createRow(rownum++); 130 131 cell = row.createCell(0, Cell.CELL_TYPE_STRING); 131 132 cell.setCellValue(kwic.getLeft()); … … 142 143 cell.setCellValue(kwic.getReference()); 143 144 } 144 noResult = false;145 145 } 146 146 } … … 155 155 } 156 156 } 157 if ( noResult) {157 if (rownum <= 1) { 158 158 return null; 159 159 } else { 160 160 return excelStream.toByteArray(); 161 161 } 162 163 162 } 164 163 165 164 public static byte[] getExportTCF(List<Result> resultsProcessed, 166 String searchLanguage ) throws ExportException {167 String text = getExportText(resultsProcessed );165 String searchLanguage, String filterLanguage) throws ExportException { 166 String text = getExportText(resultsProcessed, filterLanguage); 168 167 if (text == null || text.isEmpty()) { 169 168 return null; … … 171 170 WLData data; 172 171 MetaData md = new MetaData(); 173 String languageCode = LanguagesISO693.getInstance().code_1ForCode_3(searchLanguage); 174 TextCorpusStored tc = new TextCorpusStored(languageCode); 172 String lang = LanguagesISO693.getInstance().code_1ForCode_3(searchLanguage); 173 if (filterLanguage != null) { 174 lang = LanguagesISO693.getInstance().code_1ForCode_3(filterLanguage); 175 } 176 TextCorpusStored tc = new TextCorpusStored(lang); 175 177 tc.createTextLayer().addText(text); 176 178 data = new WLData(md, tc); … … 186 188 } 187 189 188 public static String getExportText(List<Result> resultsProcessed ) {190 public static String getExportText(List<Result> resultsProcessed, String filterLanguage) { 189 191 StringBuilder text = new StringBuilder(); 190 192 if (resultsProcessed != null && !resultsProcessed.isEmpty()) { 191 193 for (Result result : resultsProcessed) { 192 194 for (Kwic kwic : result.getKwics()) { 195 if (filterLanguage != null && !filterLanguage.equals(kwic.getLanguage())) { 196 continue; 197 } 193 198 int i = kwic.getFragments().size() - 1; 194 199 for (Kwic.TextFragment tf : kwic.getFragments()) { 195 200 text.append(tf.text); 196 if (i > 0) { 201 char last = text.length() > 0 ? text.charAt(text.length() - 1) : ' '; 202 if (i > 0 && !Character.isWhitespace(last)) { 197 203 text.append(" "); 198 204 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java
r6043 r6081 54 54 } 55 55 56 private Request executeSearch(ThrottledClient searchClient, SRUVersion version, final Corpus corpus, String searchString, int startRecord, int maxRecords) { 56 private Request executeSearch(ThrottledClient searchClient, SRUVersion version, 57 final Corpus corpus, String searchString, 58 int startRecord, int maxRecords) { 57 59 final Request request = new Request(corpus, searchString, startRecord, startRecord + maxRecords - 1); 58 60 log.info("Executing search in '{}' query='{}' maxRecords='{}'", corpus, searchString, maxRecords); … … 75 77 requests.add(request); 76 78 77 String url = null;78 try {79 url = searchRequest.makeURI(SRUVersion.VERSION_1_2).toString();80 } catch (SRUClientException ex) {81 }82 final String fullRequestUrl = url;83 84 79 try { 85 80 searchClient.searchRetrieve(searchRequest, new ThrottledClient.SearchCallback() { … … 93 88 List<Diagnostic> diagnostics = result.getDiagnostics(); 94 89 if (diagnostics != null && !diagnostics.isEmpty()) { 95 log.error("diagnostic for url: " + response.getRequest(). makeURI(SRUVersion.VERSION_1_2));90 log.error("diagnostic for url: " + response.getRequest().getRequestedURI().toString()); 96 91 for (Diagnostic diagnostic : diagnostics) { 97 statistics.addEndpointDiagnostic(corpus.getInstitution(), corpus.getEndpoint(), diagnostic, fullRequestUrl); 92 statistics.addEndpointDiagnostic(corpus.getInstitution(), corpus.getEndpoint(), 93 diagnostic, response.getRequest().getRequestedURI().toString()); 98 94 } 99 95 } … … 107 103 try { 108 104 statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpoint(), stats.getQueueTime(), stats.getExecutionTime()); 109 statistics.addErrorDatapoint(corpus.getInstitution(), corpus.getEndpoint(), xc, fullRequestUrl);105 statistics.addErrorDatapoint(corpus.getInstitution(), corpus.getEndpoint(), xc, srureq.getRequestedURI().toString()); 110 106 results.add(new Result(request, null, xc)); 111 107 requests.remove(request); … … 134 130 } 135 131 136 public List<Result> getResults( ) {132 public List<Result> getResults(String corpusId) { 137 133 List<Result> copy = new ArrayList<>(); 138 134 synchronized (results) { 139 copy.addAll(results); 135 if (corpusId == null || corpusId.isEmpty()) { 136 copy.addAll(results); 137 } else { 138 for (Result r : results) { 139 if (corpusId.equals(r.getCorpus().getId())) { 140 copy.add(r); 141 } 142 } 143 } 140 144 } 141 145 return copy; -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/util/LanguagesISO693.java
r6065 r6081 10 10 import java.util.HashMap; 11 11 import java.util.Map; 12 import java.util.Set; 12 13 import org.slf4j.LoggerFactory; 13 14 … … 129 130 return l.name; 130 131 } 132 133 public Map<String, String> getLanguageMap(Set<String> codes) { 134 Map<String, String> languages = new HashMap<String, String>(); 135 for (String code : codes) { 136 String name = LanguagesISO693.getInstance().nameForCode(code); 137 languages.put(code, name != null ? name : code); 138 } 139 return languages; 140 } 131 141 } -
SRUAggregator/trunk/src/main/resources/assets/base.css
r6050 r6081 44 44 .float-right { 45 45 float: right; 46 } 47 48 .float-left { 49 float: left; 50 } 51 52 .btn-flat { 53 color: #333; 54 margin: 2px; 55 background-color: #fff; 56 border-color: transparent; 57 } 58 .btn-flat:hover { 59 color: #333; 60 margin: 2px; 61 background-color: #e6e6e6; 62 border-color: #adadad; 46 63 } 47 64 … … 270 287 } 271 288 272 div.panel-title .institutionName{ 289 .zoomResultButton { 290 border: 1px solid transparent; 291 } 292 div.panel:hover .zoomResultButton { 293 color: white; 294 background-color: #00406F; 295 border: 1px solid black; 296 } 297 298 div.panel-title span.institutionName{ 273 299 font-size: 12px; 300 font-weight:300; 301 color: #888; 302 } 303 304 .modal-title span.institutionName{ 305 font-size: 14px; 274 306 font-weight:300; 275 307 color: #888; … … 313 345 margin-top: 0; 314 346 margin-bottom: 5px; 347 padding: 10px 15px 4px; 315 348 } 316 349 .bs-callout .panel-title { … … 352 385 max-width:552px; 353 386 } 387 388 /*** zoomed result ***/ 389 390 .modal-body .corpusDescription { 391 padding-left: 15px; 392 } 393 .modal-body .corpusDescription p { 394 margin: 4px 0; 395 } 396 397 .modal-body .corpusResults { 398 padding: 15px; 399 border: 1px solid #ddd; 400 border-left: 5px solid #00406F; /*blue*/ 401 border-radius: 4px; 402 } 403 354 404 355 405 /*** transitions css ***/ -
SRUAggregator/trunk/src/main/resources/assets/index.html
r5959 r6081 41 41 <body> 42 42 <noscript> 43 < div>Federated Content Search Aggregator requires JavaScript to be enabled!</div>43 <p>Federated Content Search Aggregator requires JavaScript to be enabled!</p> 44 44 </noscript> 45 45 … … 56 56 <script src="js/search.js"></script> 57 57 <script src="js/main.js"></script> 58 59 <!-- Piwik --> 60 <script type="text/javascript"> 61 var _paq = _paq || []; 62 _paq.push(['trackPageView']); 63 _paq.push(['enableLinkTracking']); 64 (function() { 65 var u="https://stats.clarin.eu/"; 66 _paq.push(['setTrackerUrl', u+'piwik.php']); 67 _paq.push(['setSiteId', 10]); 68 var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; 69 g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s); 70 })(); 71 </script> 72 <noscript><p><img src="https://stats.clarin.eu/piwik.php?idsite=10" style="border:0;" alt="" /></p></noscript> 73 <!-- End Piwik Code --> 58 74 </body> 59 75 </html> -
SRUAggregator/trunk/src/main/resources/assets/js/components.js
r6050 r6081 70 70 71 71 72 window.MyReact.ModalMixin = { 73 componentDidMount: function() { 74 $(this.getDOMNode()).modal({background: true, keyboard: true, show: false}); 75 }, 76 componentWillUnmount: function() { 77 $(this.getDOMNode()).off('hidden'); 78 }, 79 handleClick: function(e) { 80 e.stopPropagation(); 81 }, 82 renderModal: function(title, content) { 83 return ( 84 React.createElement("div", {onClick: this.handleClick, className: "modal fade", role: "dialog", 'aria-hidden': "true"}, 85 React.createElement("div", {className: "modal-dialog"}, 86 React.createElement("div", {className: "modal-content"}, 87 React.createElement("div", {className: "modal-header"}, 88 React.createElement("button", {type: "button", className: "close", 'data-dismiss': "modal"}, 89 React.createElement("span", {'aria-hidden': "true"}, "Ã"), 90 React.createElement("span", {className: "sr-only"}, "Close") 91 ), 92 React.createElement("h2", {className: "modal-title"}, title) 93 ), 94 React.createElement("div", {className: "modal-body"}, 95 content 96 ), 97 React.createElement("div", {className: "modal-footer"}, 98 React.createElement("button", {type: "button", className: "btn btn-default", 'data-dismiss': "modal"}, "Close") 99 ) 100 ) 101 ) 102 ) 103 ); 104 } 105 }; 106 107 72 108 window.MyReact.Modal = React.createClass({displayName: 'Modal', 73 109 propTypes: { 74 title: PT. string.isRequired,110 title: PT.object.isRequired, 75 111 }, 76 112 componentDidMount: function() { … … 203 239 render: function() { 204 240 var chevron = "glyphicon glyphicon-chevron-" + (this.state.open ? "down":"right"); 205 var chevronStyle = {fontSize:12};206 var right = {float:"right"};207 241 return React.createElement("div", {className: "bs-callout bs-callout-info"}, 208 242 React.createElement("div", {className: "panel"}, 209 243 React.createElement("div", {className: "panel-heading unselectable row", onClick: this.toggleState}, 210 244 React.createElement("div", {className: "panel-title unselectable col-sm-11"}, 211 React.createElement("span", {className: chevron, style: chevronStyle}), "Â ",245 React.createElement("span", {className: chevron, style: {fontSize:12}}), "Â ", 212 246 this.props.title 213 247 ), 214 React.createElement("div", { style: right},248 React.createElement("div", {className: "float-right"}, 215 249 this.props.info 216 250 ) -
SRUAggregator/trunk/src/main/resources/assets/js/components.jsx
r6049 r6081 70 70 71 71 72 window.MyReact.ModalMixin = { 73 componentDidMount: function() { 74 $(this.getDOMNode()).modal({background: true, keyboard: true, show: false}); 75 }, 76 componentWillUnmount: function() { 77 $(this.getDOMNode()).off('hidden'); 78 }, 79 handleClick: function(e) { 80 e.stopPropagation(); 81 }, 82 renderModal: function(title, content) { 83 return ( 84 <div onClick={this.handleClick} className="modal fade" role="dialog" aria-hidden="true"> 85 <div className="modal-dialog"> 86 <div className="modal-content"> 87 <div className="modal-header"> 88 <button type="button" className="close" data-dismiss="modal"> 89 <span aria-hidden="true">×</span> 90 <span className="sr-only">Close</span> 91 </button> 92 <h2 className="modal-title">{title}</h2> 93 </div> 94 <div className="modal-body"> 95 {content} 96 </div> 97 <div className="modal-footer"> 98 <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> 99 </div> 100 </div> 101 </div> 102 </div> 103 ); 104 } 105 }; 106 107 72 108 window.MyReact.Modal = React.createClass({ 73 109 propTypes: { 74 title: PT. string.isRequired,110 title: PT.object.isRequired, 75 111 }, 76 112 componentDidMount: function() { … … 203 239 render: function() { 204 240 var chevron = "glyphicon glyphicon-chevron-" + (this.state.open ? "down":"right"); 205 var chevronStyle = {fontSize:12};206 var right = {float:"right"};207 241 return <div className="bs-callout bs-callout-info"> 208 242 <div className="panel"> 209 243 <div className="panel-heading unselectable row" onClick={this.toggleState}> 210 244 <div className="panel-title unselectable col-sm-11"> 211 <span className={chevron} style={ chevronStyle} /> 245 <span className={chevron} style={{fontSize:12}} /> 212 246 {this.props.title} 213 247 </div> 214 <div style={right}>248 <div className='float-right'> 215 249 {this.props.info} 216 250 </div> -
SRUAggregator/trunk/src/main/resources/assets/js/main.js
r6065 r6081 3 3 "use strict"; 4 4 5 var VERSION = "VERSION 2.0.0.α2 5";5 var VERSION = "VERSION 2.0.0.α26"; 6 6 var URLROOT = "/Aggregator-testing"; 7 7 -
SRUAggregator/trunk/src/main/resources/assets/js/main.jsx
r6065 r6081 3 3 "use strict"; 4 4 5 var VERSION = "VERSION 2.0.0.α2 5";5 var VERSION = "VERSION 2.0.0.α26"; 6 6 var URLROOT = "/Aggregator-testing"; 7 7 -
SRUAggregator/trunk/src/main/resources/assets/js/search.js
r6057 r6081 15 15 var InfoPopover = window.MyReact.InfoPopover; 16 16 var Panel = window.MyReact.Panel; 17 var ModalMixin = window.MyReact.ModalMixin; 17 18 var Modal = window.MyReact.Modal; 18 19 … … 64 65 corpus.priority = 1; // used for ordering search results in corpus view 65 66 corpus.index = index; // original order, used for stable sort 67 }); 68 this.recurse(function(corpus, index) { 69 if (corpus.institution.link > 1) { 70 corpus.selected = false; 71 } 66 72 }); 67 73 } … … 158 164 }; 159 165 166 function encodeQueryData(data) 167 { 168 var ret = []; 169 for (var d in data) { 170 ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d])); 171 } 172 return ret.join("&"); 173 } 174 160 175 161 176 var AggregatorPage = window.MyAggregator.AggregatorPage = React.createClass({displayName: 'AggregatorPage', … … 174 189 corpora: new Corpora([], this.updateCorpora), 175 190 languageMap: {}, 191 weblichtLanguages: [], 176 192 query: "", 177 193 language: this.anyLanguage, … … 182 198 searchId: null, 183 199 timeout: 0, 184 hits: this.nohits, 200 hits: this.nohits, 201 202 zoomedCorpusHit: null, 185 203 }; 186 204 }, 187 205 188 206 componentDidMount: function() { 189 this.refreshCorpora();190 this.refreshLanguages();191 },192 193 refreshCorpora: function() {194 207 this.props.ajax({ 195 url: 'rest/ corpora',208 url: 'rest/init', 196 209 success: function(json, textStatus, jqXHR) { 197 210 if (this.isMounted()) { 198 this.setState({corpora : new Corpora(json, this.updateCorpora)}); 199 } 200 }.bind(this), 201 }); 202 }, 203 204 refreshLanguages: function() { 205 this.props.ajax({ 206 url: 'rest/languages', 207 success: function(json, textStatus, jqXHR) { 208 if (this.isMounted()) { 209 this.setState({languageMap : json}); 211 this.setState({ 212 corpora : new Corpora(json.corpora, this.updateCorpora), 213 languageMap: json.languages, 214 weblichtLanguages: json.weblichtLanguages, 215 }); 210 216 } 211 217 }.bind(this), … … 266 272 }, 267 273 268 getDownloadLink: function(format) { 269 return 'rest/search/'+this.state.searchId+'/download?format='+format; 270 }, 271 272 getToWeblichtLink: function(format) { 273 return 'rest/search/'+this.state.searchId+'/toWeblicht?format='+format; 274 getExportParams: function(corpusId, format) { 275 var params = corpusId ? {corpusId:corpusId}:{}; 276 if (format) params.format = format; 277 if (this.state.languageFilter === 'byGuess' || this.state.languageFilter === 'byMetaAndGuess') { 278 params.filterLanguage = this.state.language[0]; 279 } 280 return encodeQueryData(params); 281 }, 282 283 getDownloadLink: function(corpusId, format) { 284 return 'rest/search/'+this.state.searchId+'/download?' + this.getExportParams(corpusId, format); 285 }, 286 287 getToWeblichtLink: function(corpusId) { 288 var params = corpusId ? {corpusId:corpusId}:{}; 289 return 'rest/search/'+this.state.searchId+'/toWeblicht?' + this.getExportParams(corpusId); 274 290 }, 275 291 … … 301 317 302 318 filterResults: function() { 319 var noLangFiltering = this.state.languageFilter === 'byMeta'; 303 320 var langCode = this.state.language[0]; 304 321 return this.state.hits.results.map(function(corpusHit) { … … 310 327 diagnostics: corpusHit.diagnostics, 311 328 searchString: corpusHit.searchString, 312 kwics: corpusHit.kwics.filter(function(kwic){ 313 return kwic.language === langCode || langCode === multipleLanguageCode || langCode === null; 314 }), 329 kwics: noLangFiltering ? corpusHit.kwics : 330 corpusHit.kwics.filter(function(kwic) { 331 return kwic.language === langCode || langCode === multipleLanguageCode || langCode === null; 332 }), 315 333 }; 316 334 }); … … 329 347 }, 330 348 349 toggleResultModal: function(e, corpusHit) { 350 $(this.refs.resultModal.getDOMNode()).modal(); 351 this.setState({zoomedCorpusHit: corpusHit}); 352 e.preventDefault(); 353 e.stopPropagation(); 354 }, 355 331 356 handleChange: function(event) { 332 357 this.setState({query: event.target.value}); 333 358 }, 334 359 335 360 handleKey: function(event) { 336 if (event.keyCode==13) { 337 this.search(); 338 } 361 if (event.keyCode==13) { 362 this.search(); 363 } 364 }, 365 366 renderZoomedResultTitle: function(corpusHit) { 367 if (!corpusHit) return React.createElement("span", null); 368 var corpus = corpusHit.corpus; 369 return React.createElement("h3", {style: {fontSize:'1em'}}, 370 corpus.title, 371 corpus.landingPage ? 372 React.createElement("a", {href: corpus.landingPage, onClick: this.stop, style: {fontSize:12}}, 373 React.createElement("span", null, " â Homepage "), 374 React.createElement("i", {className: "glyphicon glyphicon-home"}) 375 ): false 376 ); 339 377 }, 340 378 … … 415 453 ), 416 454 417 React.createElement(Modal, {ref: "corporaModal", title: "Collections"},455 React.createElement(Modal, {ref: "corporaModal", title: React.createElement("span", null, "Collections")}, 418 456 React.createElement(CorpusView, {corpora: this.state.corpora, languageMap: this.state.languageMap}) 419 420 421 React.createElement(Modal, {ref: "languageModal", title: "Select Language"},457 ), 458 459 React.createElement(Modal, {ref: "languageModal", title: React.createElement("span", null, "Select Language")}, 422 460 React.createElement(LanguageSelector, {anyLanguage: this.anyLanguage, 423 461 languageMap: this.state.languageMap, … … 425 463 languageFilter: this.state.languageFilter, 426 464 languageChangeHandler: this.setLanguageAndFilter}) 427 ), 465 ), 466 467 React.createElement(Modal, {ref: "resultModal", title: this.renderZoomedResultTitle(this.state.zoomedCorpusHit)}, 468 React.createElement(ZoomedResult, {corpusHit: this.state.zoomedCorpusHit, 469 getDownloadLink: this.getDownloadLink, 470 getToWeblichtLink: this.getToWeblichtLink, 471 searchedLanguage: this.state.language, 472 weblichtLanguages: this.state.weblichtLanguages, 473 languageMap: this.state.languageMap}) 474 ), 428 475 429 476 React.createElement("div", {className: "top-gap"}, 430 477 React.createElement(Results, {requests: this.state.hits.requests, 431 results: this.filterResults(), 432 getDownloadLink: this.getDownloadLink, 433 getToWeblichtLink: this.getToWeblichtLink, 434 searchedLanguage: this.state.language}) 478 results: this.filterResults(), 479 toggleResultModal: this.toggleResultModal, 480 getDownloadLink: this.getDownloadLink, 481 getToWeblichtLink: this.getToWeblichtLink, 482 searchedLanguage: this.state.language}) 435 483 ) 436 484 ) … … 483 531 render: function() { 484 532 var languages = _.pairs(this.props.languageMap) 485 533 .sort(function(l1, l2){return l1[1].localeCompare(l2[1]); }); 486 534 languages.unshift(this.props.anyLanguage); 487 535 languages = languages.map(this.renderLanguageObject); … … 503 551 React.createElement("label", {style: {color:'black'}}, 504 552 this.renderRadio('byMeta'), " ", 505 553 "Use the collections", "'", " specified language to filter results" 506 554 ) 507 555 ), … … 509 557 React.createElement("label", {style: {color:'black'}}, 510 558 this.renderRadio('byGuess'), " ", 511 559 "Filter results by using a language detector" 512 560 ) 513 561 ), … … 515 563 React.createElement("label", {style: {color:'black'}}, 516 564 this.renderRadio('byMetaAndGuess'), " ", 517 565 "First use the collections", "'", " specified language then also use a language detector" 518 566 ) 519 567 ) … … 526 574 ///////////////////////////////// 527 575 528 var Results = React.createClass({displayName: 'Results', 529 propTypes: { 530 requests: PT.array.isRequired, 531 results: PT.array.isRequired, 532 searchedLanguage: PT.array.isRequired, 533 getDownloadLink: PT.func.isRequired, 534 getToWeblichtLink: PT.func.isRequired, 535 }, 536 576 var ResultMixin = window.MyReact.ResultMixin = { 577 // getDefaultProps: function(){ 578 // return {hasPopover: true}; 579 // }, 580 537 581 getInitialState: function () { 538 582 return { … … 545 589 }, 546 590 547 zoom: function(e) { 548 e.stopPropagation(); 591 renderPanelTitle: function(corpus) { 592 return React.createElement("div", {className: "inline"}, 593 React.createElement("span", {className: "corpusName"}, " ", corpus.title), 594 React.createElement("span", {className: "institutionName"}, " â ", corpus.institution.name) 595 ); 549 596 }, 550 597 … … 572 619 React.createElement("td", {style: scenter, className: "keyword"}, hit.keyword), 573 620 React.createElement("td", {style: sleft}, hit.right) 574 );575 },576 577 renderPanelTitle: function(corpus) {578 var inline = {display:"inline-block"};579 return React.createElement("div", {style: inline},580 React.createElement("span", {className: "corpusName"}, " ", corpus.title),581 React.createElement("span", {className: "institutionName"}, " â ", corpus.institution.name)582 );583 },584 585 renderPanelInfo: function(corpus) {586 var inline = {display:"inline-block"};587 return React.createElement("div", null,588 React.createElement(InfoPopover, {placement: "left", title: corpus.title},589 React.createElement("dl", {className: "dl-horizontal"},590 React.createElement("dt", null, "Institution"),591 React.createElement("dd", null, corpus.institution.name),592 593 corpus.description ? React.createElement("dt", null, "Description"):false,594 corpus.description ? React.createElement("dd", null, corpus.description): false,595 596 corpus.landingPage ? React.createElement("dt", null, "Landing Page") : false,597 corpus.landingPage ?598 React.createElement("dd", null, React.createElement("a", {href: corpus.landingPage}, corpus.landingPage)):599 false,600 601 React.createElement("dt", null, "Languages"),602 React.createElement("dd", null, corpus.languages.join(", "))603 )604 ),605 " ",606 React.createElement("div", {style: inline},607 React.createElement("button", {className: "btn btn-default btn-xs", onClick: this.zoom},608 React.createElement("span", {className: "glyphicon glyphicon-fullscreen"})609 )610 )611 621 ); 612 622 }, … … 658 668 }, 659 669 660 renderResultPanels: function(corpusHit) { 661 if (corpusHit.kwics.length === 0 && 662 !corpusHit.exception && 663 corpusHit.diagnostics.length === 0) { 664 return false; 665 } 666 return React.createElement(Panel, {key: corpusHit.corpus.id, 667 title: this.renderPanelTitle(corpusHit.corpus), 668 info: this.renderPanelInfo(corpusHit.corpus)}, 669 this.renderPanelBody(corpusHit) 670 ); 671 }, 672 673 renderProgressBar: function() { 674 var percents = 100 * this.props.results.length / (this.props.requests.length + this.props.results.length); 675 var sperc = Math.round(percents); 676 var styleperc = {width: sperc+"%"}; 677 return this.props.requests.length > 0 ? 678 React.createElement("div", {className: "progress", style: {marginBottom:10}}, 679 React.createElement("div", {className: "progress-bar progress-bar-striped active", role: "progressbar", 680 'aria-valuenow': sperc, 'aria-valuemin': "0", 'aria-valuemax': "100", style: styleperc}) 681 ) : 682 false; 683 }, 684 685 renderSearchingMessage: function() { 686 return false; 687 // if (this.props.requests.length === 0) 688 // return false; 689 // return "Searching in " + this.props.requests.length + " collections..."; 690 }, 691 692 renderFoundMessage: function(hits) { 693 if (this.props.results.length === 0) 694 return false; 695 var total = this.props.results.length; 696 return hits + " collections with results found in " + total + " searched collections"; 697 }, 698 699 renderDownloadLinks: function() { 670 renderDisplayKWIC: function() { 671 return React.createElement("div", {className: "inline btn-group", style: {display:"inline-block"}}, 672 React.createElement("label", {forHtml: "inputKwic", className: "btn btn-flat"}, 673 this.state.displayKwic ? 674 React.createElement("input", {id: "inputKwic", type: "checkbox", value: "kwic", checked: true, onChange: this.toggleKwic}) : 675 React.createElement("input", {id: "inputKwic", type: "checkbox", value: "kwic", onChange: this.toggleKwic}), 676 677 "Â " + ' ' + 678 "Display as Key Word In Context" 679 ) 680 ); 681 }, 682 683 renderDownloadLinks: function(corpusId) { 700 684 return ( 701 685 React.createElement("div", {className: "dropdown"}, 702 React.createElement("button", {className: "btn btn- default", 'aria-expanded': "false", 'data-toggle': "dropdown"},686 React.createElement("button", {className: "btn btn-flat", 'aria-expanded': "false", 'data-toggle': "dropdown"}, 703 687 React.createElement("span", {className: "glyphicon glyphicon-download-alt", 'aria-hidden': "true"}), 704 688 " ", " Download ", " ", … … 706 690 ), 707 691 React.createElement("ul", {className: "dropdown-menu"}, 708 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink( "csv")},692 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink(corpusId, "csv")}, 709 693 " ", " As CSV file")), 710 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink( "excel")},694 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink(corpusId, "excel")}, 711 695 " ", " As Excel file")), 712 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink( "tcf")},696 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink(corpusId, "tcf")}, 713 697 " ", " As TCF file")), 714 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink( "text")},698 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getDownloadLink(corpusId, "text")}, 715 699 " ", " As Plain Text file")) 716 700 ) … … 719 703 }, 720 704 721 renderToWeblichtLinks: function( ) {705 renderToWeblichtLinks: function(corpusId, error) { 722 706 return ( 723 707 React.createElement("div", {className: "dropdown"}, 724 React.createElement("button", {className: "btn btn- default", 'aria-expanded': "false", 'data-toggle': "dropdown"},725 React.createElement("span", {className: "glyphicon glyphicon- download-alt", 'aria-hidden': "true"}),708 React.createElement("button", {className: "btn btn-flat", 'aria-expanded': "false", 'data-toggle': "dropdown"}, 709 React.createElement("span", {className: "glyphicon glyphicon-export", 'aria-hidden': "true"}), 726 710 " ", " Use Weblicht ", " ", 727 711 React.createElement("span", {className: "caret"}) 728 712 ), 729 713 React.createElement("ul", {className: "dropdown-menu"}, 730 React.createElement("li", null, " ", React.createElement("a", {href: this.props.getToWeblichtLink("text")}, 731 " ", " As Plain Text file")) 714 React.createElement("li", null, 715 error ? 716 React.createElement("div", {className: "alert alert-danger", style: {margin:10}}, error) : 717 React.createElement("a", {href: this.props.getToWeblichtLink(corpusId), target: "_blank"}, " ", 718 "Send to Weblicht") 719 720 ) 732 721 ) 733 722 ) … … 735 724 }, 736 725 737 renderToolbox: function(hits) { 738 if (hits <= 0) { 726 }; 727 728 var ZoomedResult = React.createClass({displayName: 'ZoomedResult', 729 propTypes: { 730 corpusHit: PT.object, 731 languageMap: PT.object.isRequired, 732 weblichtLanguages: PT.array.isRequired, 733 searchedLanguage: PT.array.isRequired, 734 getDownloadLink: PT.func.isRequired, 735 getToWeblichtLink: PT.func.isRequired, 736 }, 737 mixins: [ResultMixin], 738 739 renderLanguages: function(languages) { 740 return languages 741 .map(function(l) { return this.props.languageMap[l]; }.bind(this)) 742 .sort() 743 .join(", "); 744 }, 745 746 render: function() { 747 var corpusHit = this.props.corpusHit; 748 if (!corpusHit) { 739 749 return false; 740 750 } 741 return React.createElement("div", {key: "-toolbox-", style: {marginBottom:10}}, 742 React.createElement("div", {className: "toolbox float-left inline"}, 743 this.renderDownloadLinks() 744 ), 745 React.createElement("div", {className: "toolbox float-left inline"}, 746 this.renderToWeblichtLinks() 747 ), 748 React.createElement("div", {className: "float-right inline", style: {marginTop:15}}, 749 React.createElement("div", {className: "btn-group", style: {display:"inline-block"}}, 750 React.createElement("label", {forHtml: "inputKwic", className: "btn-default"}, 751 this.state.displayKwic ? 752 React.createElement("input", {id: "inputKwic", type: "checkbox", value: "kwic", checked: true, onChange: this.toggleKwic}) : 753 React.createElement("input", {id: "inputKwic", type: "checkbox", value: "kwic", onChange: this.toggleKwic}), 754 755 "Â " + ' ' + 756 "Display as Key Word In Context" 751 var wlerror = null; 752 if (this.props.weblichtLanguages.indexOf(this.props.searchedLanguage[0]) < 0) { 753 wlerror = "Cannot use WebLicht: unsupported language"; 754 } 755 var corpus = corpusHit.corpus; 756 return React.createElement("div", null, 757 React.createElement(ReactCSSTransitionGroup, {transitionName: "fade"}, 758 React.createElement("div", {className: "corpusDescription"}, 759 React.createElement("p", null, React.createElement("i", {className: "fa fa-institution"}), " ", corpus.institution.name), 760 corpus.description ? 761 React.createElement("p", null, React.createElement("i", {className: "glyphicon glyphicon-info-sign"}), " ", corpus.description): false, 762 React.createElement("p", null, React.createElement("i", {className: "fa fa-language"}), " ", this.renderLanguages(corpus.languages)) 763 ), 764 React.createElement("div", {style: {marginBottom:2}}, 765 React.createElement("div", {className: "float-right"}, 766 React.createElement("div", null, 767 this.renderDisplayKWIC(), 768 React.createElement("div", {className: "inline"}, " ", this.renderDownloadLinks(corpusHit.corpus.id), " "), 769 React.createElement("div", {className: "inline"}, " ", this.renderToWeblichtLinks(corpus.id, wlerror), " ") 770 ) 771 ), 772 React.createElement("div", {style: {clear:'both'}}) 773 ), 774 React.createElement("div", {className: "panel"}, 775 React.createElement("div", {className: "panel-body corpusResults"}, this.renderPanelBody(corpusHit)) 776 ), 777 778 React.createElement("div", {style: {textAlign:'center', marginTop:10}}, 779 React.createElement("button", {className: "btn btn-default"}, 780 React.createElement("span", {className: "glyphicon glyphicon-option-horizontal", 'aria-hidden': "true"}), " More Results" 757 781 ) 758 782 ) 783 759 784 ) 760 785 ); 761 786 }, 787 }); 788 789 var Results = React.createClass({displayName: 'Results', 790 propTypes: { 791 requests: PT.array.isRequired, 792 results: PT.array.isRequired, 793 searchedLanguage: PT.array.isRequired, 794 toggleResultModal: PT.func.isRequired, 795 getDownloadLink: PT.func.isRequired, 796 getToWeblichtLink: PT.func.isRequired, 797 }, 798 mixins: [ResultMixin], 799 800 renderPanelInfo: function(corpusHit) { 801 var corpus = corpusHit.corpus; 802 var inline = {display:"inline-block"}; 803 return React.createElement("div", null, 804 " ", 805 React.createElement("div", {style: inline}, 806 React.createElement("button", {className: "btn btn-default zoomResultButton", 807 onClick: function(e){this.props.toggleResultModal(e,corpusHit)}.bind(this)}, 808 React.createElement("span", {className: "glyphicon glyphicon-eye-open"}), " View" 809 ) 810 ) 811 ); 812 }, 813 814 renderResultPanel: function(corpusHit) { 815 if (corpusHit.kwics.length === 0 && 816 !corpusHit.exception && 817 corpusHit.diagnostics.length === 0) { 818 return false; 819 } 820 return React.createElement(Panel, {key: corpusHit.corpus.id, 821 title: this.renderPanelTitle(corpusHit.corpus), 822 info: this.renderPanelInfo(corpusHit)}, 823 this.renderPanelBody(corpusHit) 824 ); 825 }, 826 827 renderProgressMessage: function(hits) { 828 var total = this.props.requests.length + this.props.results.length; 829 var msg = hits + " matching collections found in " + this.props.results.length + " searched collections"; 830 var percents = Math.round(100 * this.props.results.length / total); 831 var styleperc = {width: percents+"%"}; 832 return React.createElement("div", {style: {marginTop:10}}, 833 React.createElement("div", null, msg), 834 this.props.requests.length > 0 ? 835 React.createElement("div", {className: "progress", style: {marginBottom:10}}, 836 React.createElement("div", {className: "progress-bar progress-bar-striped active", role: "progressbar", 837 'aria-valuenow': percents, 'aria-valuemin': "0", 'aria-valuemax': "100", style: styleperc}), 838 percents > 2 ? false : 839 React.createElement("div", {className: "progress-bar progress-bar-striped active", role: "progressbar", 840 'aria-valuenow': "100", 'aria-valuemin': "0", 'aria-valuemax': "100", 841 style: {width: '100%', backgroundColor:'#888'}}) 842 843 ) : 844 false 845 ); 846 }, 762 847 763 848 render: function() { 849 if (this.props.results.length + this.props.requests.length === 0) { 850 return false; 851 } 764 852 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length; 765 var margintop = {marginTop:"10px"}; 766 var margin = {marginTop:"0", padding:"20px"}; 767 var inlinew = {display:"inline-block", margin:"0 5px 0 0", width:"240px;"}; 768 var right= {float:"right"}; 853 var showprogress = this.props.requests.length > 0; 769 854 return React.createElement("div", null, 770 855 React.createElement(ReactCSSTransitionGroup, {transitionName: "fade"}, 771 React.createElement("div", {key: "-searching-message-", style: margintop}, this.renderSearchingMessage(), " "), 772 React.createElement("div", {key: "-found-message-", style: margintop}, this.renderFoundMessage(hits), " "), 773 React.createElement("div", {key: "-progress-", style: margintop}, this.renderProgressBar()), 774 this.renderToolbox(hits), 775 this.props.results.map(this.renderResultPanels) 856 showprogress ? this.renderProgressMessage(hits) : React.createElement("div", {style: {height:20}}), 857 React.createElement("div", {style: {marginBottom:2}}, 858 showprogress ? false : 859 React.createElement("div", {className: "float-left"}, " ", hits + " matching collections found", " "), 860 861 hits === 0 ? false : 862 React.createElement("div", {className: "float-right"}, 863 React.createElement("div", null, 864 this.renderDisplayKWIC(), 865 this.props.requests.length === 0 ? 866 React.createElement("div", {className: "inline"}, " ", this.renderDownloadLinks(), " ") 867 :false 868 869 ) 870 ), 871 872 React.createElement("div", {style: {clear:'both'}}) 873 ), 874 this.props.results.map(this.renderResultPanel) 776 875 ) 777 876 ); -
SRUAggregator/trunk/src/main/resources/assets/js/search.jsx
r6057 r6081 15 15 var InfoPopover = window.MyReact.InfoPopover; 16 16 var Panel = window.MyReact.Panel; 17 var ModalMixin = window.MyReact.ModalMixin; 17 18 var Modal = window.MyReact.Modal; 18 19 … … 64 65 corpus.priority = 1; // used for ordering search results in corpus view 65 66 corpus.index = index; // original order, used for stable sort 67 }); 68 this.recurse(function(corpus, index) { 69 if (corpus.institution.link > 1) { 70 corpus.selected = false; 71 } 66 72 }); 67 73 } … … 158 164 }; 159 165 166 function encodeQueryData(data) 167 { 168 var ret = []; 169 for (var d in data) { 170 ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d])); 171 } 172 return ret.join("&"); 173 } 174 160 175 161 176 var AggregatorPage = window.MyAggregator.AggregatorPage = React.createClass({ … … 174 189 corpora: new Corpora([], this.updateCorpora), 175 190 languageMap: {}, 191 weblichtLanguages: [], 176 192 query: "", 177 193 language: this.anyLanguage, … … 182 198 searchId: null, 183 199 timeout: 0, 184 hits: this.nohits, 200 hits: this.nohits, 201 202 zoomedCorpusHit: null, 185 203 }; 186 204 }, 187 205 188 206 componentDidMount: function() { 189 this.refreshCorpora();190 this.refreshLanguages();191 },192 193 refreshCorpora: function() {194 207 this.props.ajax({ 195 url: 'rest/ corpora',208 url: 'rest/init', 196 209 success: function(json, textStatus, jqXHR) { 197 210 if (this.isMounted()) { 198 this.setState({corpora : new Corpora(json, this.updateCorpora)}); 199 } 200 }.bind(this), 201 }); 202 }, 203 204 refreshLanguages: function() { 205 this.props.ajax({ 206 url: 'rest/languages', 207 success: function(json, textStatus, jqXHR) { 208 if (this.isMounted()) { 209 this.setState({languageMap : json}); 211 this.setState({ 212 corpora : new Corpora(json.corpora, this.updateCorpora), 213 languageMap: json.languages, 214 weblichtLanguages: json.weblichtLanguages, 215 }); 210 216 } 211 217 }.bind(this), … … 266 272 }, 267 273 268 getDownloadLink: function(format) { 269 return 'rest/search/'+this.state.searchId+'/download?format='+format; 270 }, 271 272 getToWeblichtLink: function(format) { 273 return 'rest/search/'+this.state.searchId+'/toWeblicht?format='+format; 274 getExportParams: function(corpusId, format) { 275 var params = corpusId ? {corpusId:corpusId}:{}; 276 if (format) params.format = format; 277 if (this.state.languageFilter === 'byGuess' || this.state.languageFilter === 'byMetaAndGuess') { 278 params.filterLanguage = this.state.language[0]; 279 } 280 return encodeQueryData(params); 281 }, 282 283 getDownloadLink: function(corpusId, format) { 284 return 'rest/search/'+this.state.searchId+'/download?' + this.getExportParams(corpusId, format); 285 }, 286 287 getToWeblichtLink: function(corpusId) { 288 var params = corpusId ? {corpusId:corpusId}:{}; 289 return 'rest/search/'+this.state.searchId+'/toWeblicht?' + this.getExportParams(corpusId); 274 290 }, 275 291 … … 301 317 302 318 filterResults: function() { 319 var noLangFiltering = this.state.languageFilter === 'byMeta'; 303 320 var langCode = this.state.language[0]; 304 321 return this.state.hits.results.map(function(corpusHit) { … … 310 327 diagnostics: corpusHit.diagnostics, 311 328 searchString: corpusHit.searchString, 312 kwics: corpusHit.kwics.filter(function(kwic){ 313 return kwic.language === langCode || langCode === multipleLanguageCode || langCode === null; 314 }), 329 kwics: noLangFiltering ? corpusHit.kwics : 330 corpusHit.kwics.filter(function(kwic) { 331 return kwic.language === langCode || langCode === multipleLanguageCode || langCode === null; 332 }), 315 333 }; 316 334 }); … … 329 347 }, 330 348 349 toggleResultModal: function(e, corpusHit) { 350 $(this.refs.resultModal.getDOMNode()).modal(); 351 this.setState({zoomedCorpusHit: corpusHit}); 352 e.preventDefault(); 353 e.stopPropagation(); 354 }, 355 331 356 handleChange: function(event) { 332 357 this.setState({query: event.target.value}); 333 358 }, 334 359 335 360 handleKey: function(event) { 336 if (event.keyCode==13) { 337 this.search(); 338 } 361 if (event.keyCode==13) { 362 this.search(); 363 } 364 }, 365 366 renderZoomedResultTitle: function(corpusHit) { 367 if (!corpusHit) return <span/>; 368 var corpus = corpusHit.corpus; 369 return <h3 style={{fontSize:'1em'}}> 370 {corpus.title} 371 { corpus.landingPage ? 372 <a href={corpus.landingPage} onClick={this.stop} style={{fontSize:12}}> 373 <span> â Homepage </span> 374 <i className="glyphicon glyphicon-home"/> 375 </a>: false} 376 </h3>; 339 377 }, 340 378 … … 415 453 </div> 416 454 417 <Modal ref="corporaModal" title="Collections">455 <Modal ref="corporaModal" title={<span>Collections</span>}> 418 456 <CorpusView corpora={this.state.corpora} languageMap={this.state.languageMap} /> 419 420 421 <Modal ref="languageModal" title="Select Language">457 </Modal> 458 459 <Modal ref="languageModal" title={<span>Select Language</span>}> 422 460 <LanguageSelector anyLanguage={this.anyLanguage} 423 461 languageMap={this.state.languageMap} … … 425 463 languageFilter={this.state.languageFilter} 426 464 languageChangeHandler={this.setLanguageAndFilter} /> 427 </Modal> 465 </Modal> 466 467 <Modal ref="resultModal" title={this.renderZoomedResultTitle(this.state.zoomedCorpusHit)}> 468 <ZoomedResult corpusHit={this.state.zoomedCorpusHit} 469 getDownloadLink={this.getDownloadLink} 470 getToWeblichtLink={this.getToWeblichtLink} 471 searchedLanguage={this.state.language} 472 weblichtLanguages={this.state.weblichtLanguages} 473 languageMap={this.state.languageMap} /> 474 </Modal> 428 475 429 476 <div className="top-gap"> 430 477 <Results requests={this.state.hits.requests} 431 results={this.filterResults()} 432 getDownloadLink={this.getDownloadLink} 433 getToWeblichtLink={this.getToWeblichtLink} 434 searchedLanguage={this.state.language}/> 478 results={this.filterResults()} 479 toggleResultModal={this.toggleResultModal} 480 getDownloadLink={this.getDownloadLink} 481 getToWeblichtLink={this.getToWeblichtLink} 482 searchedLanguage={this.state.language}/> 435 483 </div> 436 484 </div> … … 483 531 render: function() { 484 532 var languages = _.pairs(this.props.languageMap) 485 533 .sort(function(l1, l2){return l1[1].localeCompare(l2[1]); }); 486 534 languages.unshift(this.props.anyLanguage); 487 535 languages = languages.map(this.renderLanguageObject); … … 503 551 <label style={{color:'black'}}> 504 552 { this.renderRadio('byMeta') }{" "} 505 553 Use the collections{"'"} specified language to filter results 506 554 </label> 507 555 </div> … … 509 557 <label style={{color:'black'}}> 510 558 { this.renderRadio('byGuess') }{" "} 511 559 Filter results by using a language detector 512 560 </label> 513 561 </div> … … 515 563 <label style={{color:'black'}}> 516 564 { this.renderRadio('byMetaAndGuess') }{" "} 517 565 First use the collections{"'"} specified language then also use a language detector 518 566 </label> 519 567 </div> … … 526 574 ///////////////////////////////// 527 575 528 var Results = React.createClass({ 529 propTypes: { 530 requests: PT.array.isRequired, 531 results: PT.array.isRequired, 532 searchedLanguage: PT.array.isRequired, 533 getDownloadLink: PT.func.isRequired, 534 getToWeblichtLink: PT.func.isRequired, 535 }, 536 576 var ResultMixin = window.MyReact.ResultMixin = { 577 // getDefaultProps: function(){ 578 // return {hasPopover: true}; 579 // }, 580 537 581 getInitialState: function () { 538 582 return { … … 545 589 }, 546 590 547 zoom: function(e) { 548 e.stopPropagation(); 591 renderPanelTitle: function(corpus) { 592 return <div className='inline'> 593 <span className="corpusName"> {corpus.title}</span> 594 <span className="institutionName"> â {corpus.institution.name}</span> 595 </div>; 549 596 }, 550 597 … … 573 620 <td style={sleft}>{hit.right}</td> 574 621 </tr>; 575 },576 577 renderPanelTitle: function(corpus) {578 var inline = {display:"inline-block"};579 return <div style={inline}>580 <span className="corpusName"> {corpus.title}</span>581 <span className="institutionName"> â {corpus.institution.name}</span>582 </div>;583 },584 585 renderPanelInfo: function(corpus) {586 var inline = {display:"inline-block"};587 return <div>588 <InfoPopover placement="left" title={corpus.title}>589 <dl className="dl-horizontal">590 <dt>Institution</dt>591 <dd>{corpus.institution.name}</dd>592 593 {corpus.description ? <dt>Description</dt>:false}594 {corpus.description ? <dd>{corpus.description}</dd>: false}595 596 {corpus.landingPage ? <dt>Landing Page</dt> : false }597 {corpus.landingPage ?598 <dd><a href={corpus.landingPage}>{corpus.landingPage}</a></dd>:599 false}600 601 <dt>Languages</dt>602 <dd>{corpus.languages.join(", ")}</dd>603 </dl>604 </InfoPopover>605 {" "}606 <div style={inline}>607 <button className="btn btn-default btn-xs" onClick={this.zoom}>608 <span className="glyphicon glyphicon-fullscreen"/>609 </button>610 </div>611 </div>;612 622 }, 613 623 … … 658 668 }, 659 669 660 renderResultPanels: function(corpusHit) { 661 if (corpusHit.kwics.length === 0 && 662 !corpusHit.exception && 663 corpusHit.diagnostics.length === 0) { 664 return false; 665 } 666 return <Panel key={corpusHit.corpus.id} 667 title={this.renderPanelTitle(corpusHit.corpus)} 668 info={this.renderPanelInfo(corpusHit.corpus)}> 669 {this.renderPanelBody(corpusHit)} 670 </Panel>; 671 }, 672 673 renderProgressBar: function() { 674 var percents = 100 * this.props.results.length / (this.props.requests.length + this.props.results.length); 675 var sperc = Math.round(percents); 676 var styleperc = {width: sperc+"%"}; 677 return this.props.requests.length > 0 ? 678 <div className="progress" style={{marginBottom:10}}> 679 <div className="progress-bar progress-bar-striped active" role="progressbar" 680 aria-valuenow={sperc} aria-valuemin="0" aria-valuemax="100" style={styleperc} /> 681 </div> : 682 false; 683 }, 684 685 renderSearchingMessage: function() { 686 return false; 687 // if (this.props.requests.length === 0) 688 // return false; 689 // return "Searching in " + this.props.requests.length + " collections..."; 690 }, 691 692 renderFoundMessage: function(hits) { 693 if (this.props.results.length === 0) 694 return false; 695 var total = this.props.results.length; 696 return hits + " collections with results found in " + total + " searched collections"; 697 }, 698 699 renderDownloadLinks: function() { 670 renderDisplayKWIC: function() { 671 return <div className="inline btn-group" style={{display:"inline-block"}}> 672 <label forHtml="inputKwic" className="btn btn-flat"> 673 { this.state.displayKwic ? 674 <input id="inputKwic" type="checkbox" value="kwic" checked onChange={this.toggleKwic} /> : 675 <input id="inputKwic" type="checkbox" value="kwic" onChange={this.toggleKwic} /> 676 } 677 678 Display as Key Word In Context 679 </label> 680 </div>; 681 }, 682 683 renderDownloadLinks: function(corpusId) { 700 684 return ( 701 685 <div className="dropdown"> 702 <button className="btn btn- default" aria-expanded="false" data-toggle="dropdown">686 <button className="btn btn-flat" aria-expanded="false" data-toggle="dropdown"> 703 687 <span className="glyphicon glyphicon-download-alt" aria-hidden="true"/> 704 688 {" "} Download {" "} … … 706 690 </button> 707 691 <ul className="dropdown-menu"> 708 <li> <a href={this.props.getDownloadLink( "csv")}>692 <li> <a href={this.props.getDownloadLink(corpusId, "csv")}> 709 693 {" "} As CSV file</a></li> 710 <li> <a href={this.props.getDownloadLink( "excel")}>694 <li> <a href={this.props.getDownloadLink(corpusId, "excel")}> 711 695 {" "} As Excel file</a></li> 712 <li> <a href={this.props.getDownloadLink( "tcf")}>696 <li> <a href={this.props.getDownloadLink(corpusId, "tcf")}> 713 697 {" "} As TCF file</a></li> 714 <li> <a href={this.props.getDownloadLink( "text")}>698 <li> <a href={this.props.getDownloadLink(corpusId, "text")}> 715 699 {" "} As Plain Text file</a></li> 716 700 </ul> … … 719 703 }, 720 704 721 renderToWeblichtLinks: function( ) {705 renderToWeblichtLinks: function(corpusId, error) { 722 706 return ( 723 707 <div className="dropdown"> 724 <button className="btn btn- default" aria-expanded="false" data-toggle="dropdown">725 <span className="glyphicon glyphicon- download-alt" aria-hidden="true"/>708 <button className="btn btn-flat" aria-expanded="false" data-toggle="dropdown"> 709 <span className="glyphicon glyphicon-export" aria-hidden="true"/> 726 710 {" "} Use Weblicht {" "} 727 711 <span className="caret"/> 728 712 </button> 729 713 <ul className="dropdown-menu"> 730 <li> <a href={this.props.getToWeblichtLink("text")}> 731 {" "} As Plain Text file</a></li> 714 <li> 715 {error ? 716 <div className="alert alert-danger" style={{margin:10}}>{error}</div> : 717 <a href={this.props.getToWeblichtLink(corpusId)} target="_blank">{" "} 718 Send to Weblicht</a> 719 } 720 </li> 732 721 </ul> 733 722 </div> … … 735 724 }, 736 725 737 renderToolbox: function(hits) { 738 if (hits <= 0) { 726 }; 727 728 var ZoomedResult = React.createClass({ 729 propTypes: { 730 corpusHit: PT.object, 731 languageMap: PT.object.isRequired, 732 weblichtLanguages: PT.array.isRequired, 733 searchedLanguage: PT.array.isRequired, 734 getDownloadLink: PT.func.isRequired, 735 getToWeblichtLink: PT.func.isRequired, 736 }, 737 mixins: [ResultMixin], 738 739 renderLanguages: function(languages) { 740 return languages 741 .map(function(l) { return this.props.languageMap[l]; }.bind(this)) 742 .sort() 743 .join(", "); 744 }, 745 746 render: function() { 747 var corpusHit = this.props.corpusHit; 748 if (!corpusHit) { 739 749 return false; 740 750 } 741 return <div key="-toolbox-" style={{marginBottom:10}}> 742 <div className="toolbox float-left inline"> 743 {this.renderDownloadLinks()} 744 </div> 745 <div className="toolbox float-left inline"> 746 {this.renderToWeblichtLinks()} 747 </div> 748 <div className="float-right inline" style={{marginTop:15}}> 749 <div className="btn-group" style={{display:"inline-block"}}> 750 <label forHtml="inputKwic" className="btn-default"> 751 { this.state.displayKwic ? 752 <input id="inputKwic" type="checkbox" value="kwic" checked onChange={this.toggleKwic} /> : 753 <input id="inputKwic" type="checkbox" value="kwic" onChange={this.toggleKwic} /> 754 } 755 756 Display as Key Word In Context 757 </label> 751 var wlerror = null; 752 if (this.props.weblichtLanguages.indexOf(this.props.searchedLanguage[0]) < 0) { 753 wlerror = "Cannot use WebLicht: unsupported language"; 754 } 755 var corpus = corpusHit.corpus; 756 return <div> 757 <ReactCSSTransitionGroup transitionName="fade"> 758 <div className='corpusDescription'> 759 <p><i className="fa fa-institution"/> {corpus.institution.name}</p> 760 {corpus.description ? 761 <p><i className="glyphicon glyphicon-info-sign"/> {corpus.description}</p>: false} 762 <p><i className="fa fa-language"/> {this.renderLanguages(corpus.languages)}</p> 758 763 </div> 764 <div style={{marginBottom:2}}> 765 <div className="float-right"> 766 <div> 767 { this.renderDisplayKWIC() } 768 <div className="inline"> {this.renderDownloadLinks(corpusHit.corpus.id)} </div> 769 <div className="inline"> {this.renderToWeblichtLinks(corpus.id, wlerror)} </div> 770 </div> 771 </div> 772 <div style={{clear:'both'}}/> 773 </div> 774 <div className="panel"> 775 <div className="panel-body corpusResults">{this.renderPanelBody(corpusHit)}</div> 776 </div> 777 778 <div style={{textAlign:'center', marginTop:10}}> 779 <button className="btn btn-default"> 780 <span className="glyphicon glyphicon-option-horizontal" aria-hidden="true"/> More Results 781 </button> 782 </div> 783 784 </ReactCSSTransitionGroup> 785 </div>; 786 }, 787 }); 788 789 var Results = React.createClass({ 790 propTypes: { 791 requests: PT.array.isRequired, 792 results: PT.array.isRequired, 793 searchedLanguage: PT.array.isRequired, 794 toggleResultModal: PT.func.isRequired, 795 getDownloadLink: PT.func.isRequired, 796 getToWeblichtLink: PT.func.isRequired, 797 }, 798 mixins: [ResultMixin], 799 800 renderPanelInfo: function(corpusHit) { 801 var corpus = corpusHit.corpus; 802 var inline = {display:"inline-block"}; 803 return <div> 804 {" "} 805 <div style={inline}> 806 <button className="btn btn-default zoomResultButton" 807 onClick={function(e){this.props.toggleResultModal(e,corpusHit)}.bind(this)}> 808 <span className="glyphicon glyphicon-eye-open"/> View 809 </button> 759 810 </div> 760 811 </div>; 761 812 }, 762 813 814 renderResultPanel: function(corpusHit) { 815 if (corpusHit.kwics.length === 0 && 816 !corpusHit.exception && 817 corpusHit.diagnostics.length === 0) { 818 return false; 819 } 820 return <Panel key={corpusHit.corpus.id} 821 title={this.renderPanelTitle(corpusHit.corpus)} 822 info={this.renderPanelInfo(corpusHit)}> 823 {this.renderPanelBody(corpusHit)} 824 </Panel>; 825 }, 826 827 renderProgressMessage: function(hits) { 828 var total = this.props.requests.length + this.props.results.length; 829 var msg = hits + " matching collections found in " + this.props.results.length + " searched collections"; 830 var percents = Math.round(100 * this.props.results.length / total); 831 var styleperc = {width: percents+"%"}; 832 return <div style={{marginTop:10}}> 833 <div>{msg}</div> 834 {this.props.requests.length > 0 ? 835 <div className="progress" style={{marginBottom:10}}> 836 <div className="progress-bar progress-bar-striped active" role="progressbar" 837 aria-valuenow={percents} aria-valuemin="0" aria-valuemax="100" style={styleperc} /> 838 {percents > 2 ? false : 839 <div className="progress-bar progress-bar-striped active" role="progressbar" 840 aria-valuenow='100' aria-valuemin="0" aria-valuemax="100" 841 style={{width: '100%', backgroundColor:'#888'}} /> 842 } 843 </div> : 844 false} 845 </div>; 846 }, 847 763 848 render: function() { 849 if (this.props.results.length + this.props.requests.length === 0) { 850 return false; 851 } 764 852 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length; 765 var margintop = {marginTop:"10px"}; 766 var margin = {marginTop:"0", padding:"20px"}; 767 var inlinew = {display:"inline-block", margin:"0 5px 0 0", width:"240px;"}; 768 var right= {float:"right"}; 853 var showprogress = this.props.requests.length > 0; 769 854 return <div> 770 855 <ReactCSSTransitionGroup transitionName="fade"> 771 <div key="-searching-message-" style={margintop}>{this.renderSearchingMessage()} </div> 772 <div key="-found-message-" style={margintop}>{this.renderFoundMessage(hits)} </div> 773 <div key="-progress-" style={margintop}>{this.renderProgressBar()}</div> 774 {this.renderToolbox(hits)} 775 {this.props.results.map(this.renderResultPanels)} 856 { showprogress ? this.renderProgressMessage(hits) : <div style={{height:20}} />} 857 <div style={{marginBottom:2}}> 858 { showprogress ? false : 859 <div className="float-left"> {hits + " matching collections found"} </div> 860 } 861 { hits === 0 ? false : 862 <div className="float-right"> 863 <div> 864 { this.renderDisplayKWIC() } 865 { this.props.requests.length === 0 ? 866 <div className="inline"> {this.renderDownloadLinks()} </div> 867 :false 868 } 869 </div> 870 </div> 871 } 872 <div style={{clear:'both'}}/> 873 </div> 874 {this.props.results.map(this.renderResultPanel)} 776 875 </ReactCSSTransitionGroup> 777 876 </div>;
Note: See TracChangeset
for help on using the changeset viewer.