Changeset 6081


Ignore:
Timestamp:
03/05/15 18:24:55 (9 years ago)
Author:
emanuel.dima@uni-tuebingen.de
Message:
  1. alpha 26: zoom in result, other UI changes
Location:
SRUAggregator/trunk
Files:
16 edited

Legend:

Unmodified
Added
Removed
  • SRUAggregator/trunk/aggregator.yml

    r6065 r6081  
    3636      - fr
    3737      - it
    38       - sp
     38      - es
    3939      - pl
    4040
  • SRUAggregator/trunk/pom.xml

    r6065 r6081  
    88        <groupId>eu.clarin.sru.fcs</groupId>
    99        <artifactId>Aggregator2</artifactId>
    10         <version>2.0.0-alpha-25</version>
     10        <version>2.0.0-alpha-26</version>
    1111        <name>FCS Aggregator</name>
    1212
     
    5454                        <groupId>eu.clarin.sru</groupId>
    5555                        <artifactId>sru-client</artifactId>
    56                         <version>0.9.5-DEBUG</version>
     56                        <version>0.9.5</version>
    5757                        <exclusions>
    5858                                <exclusion>
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java

    r6065 r6081  
    8686 * @author edima
    8787 *
    88  * TODO: ?use weblicht only to show up in zoomed mode
    89  * - send only tcf with only a text layer and language (from the list in params)
    90  *
    9188 * TODO: add the modes described above (except live)
    9289 *
    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
    10491 *
    10592 * TODO: websockets
     
    155142                params = config.aggregatorParams;
    156143                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;
    157150
    158151                System.out.println("Using parameters: ");
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/rest/RestService.java

    r6065 r6081  
    88import eu.clarin.sru.fcs.aggregator.app.AggregatorConfiguration;
    99import eu.clarin.sru.fcs.aggregator.app.AggregatorConfiguration.Params.WeblichtConfig;
     10import eu.clarin.sru.fcs.aggregator.scan.Corpora;
    1011import eu.clarin.sru.fcs.aggregator.scan.Corpus;
    1112import eu.clarin.sru.fcs.aggregator.scan.Statistics;
     
    7071
    7172        @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
    72234        @Path("statistics")
    73235        public Response getStatistics() throws IOException {
     
    103265        }
    104266
    105         @GET
    106         @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         @POST
    119         @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         @GET
    171         @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         @GET
    183         @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         @GET
    223         @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 == null
    251                                 ? Response.status(503).entity("error while exporting to weblicht").build()
    252                                 : Response.seeOther(weblichtUri).entity(weblichtUri).build();
    253         }
    254267}
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawler.java

    r6057 r6081  
    8585                final Endpoint endpoint;
    8686                final Corpora corpora;
    87                 String fullRequestUrl;
    8887
    8988                ExplainTask(final Institution institution, final Endpoint endpoint, final Corpora corpora) {
     
    9998                                explainRequest.setExtraRequestData(SRUCQL.EXPLAIN_ASK_FOR_RESOURCES_PARAM, "true");
    10099                                explainRequest.setParseRecordDataEnabled(true);
    101                                 fullRequestUrl = explainRequest.makeURI(SRUVersion.VERSION_1_2).toString();
    102100                        } catch (Throwable ex) {
    103101                                log.error("Exception creating explain request for {}: {}", endpoint.getUrl(), ex.getMessage());
     
    135133                                                SRUExplainRequest request = response.getRequest();
    136134                                                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);
    139137                                        }
    140138                                }
     
    144142                                log.error("{} Exception in explain callback {}", latch.get(), endpoint.getUrl());
    145143                                log.error("--> ", xc);
    146                                 statistics.addErrorDatapoint(institution, endpoint, xc, fullRequestUrl);
     144                                statistics.addErrorDatapoint(institution, endpoint, xc, response.getRequest().getRequestedURI().toString());
    147145                        } finally {
    148146                                if (endpoint.getProtocol().equals(FCSProtocolVersion.LEGACY)) {
    149147                                        new ScanTask(institution, endpoint, null, corpora, 0).start();
    150148                                        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());
    152150                                }
    153151
     
    161159                                log.error("{} Error while explaining {}: {}", latch.get(), endpoint.getUrl(), error.getMessage());
    162160                                statistics.addEndpointDatapoint(institution, endpoint, stats.getQueueTime(), stats.getExecutionTime());
    163                                 statistics.addErrorDatapoint(institution, endpoint, error, fullRequestUrl);
     161                                statistics.addErrorDatapoint(institution, endpoint, error, request.getRequestedURI().toString());
    164162                                if (Throw.isCausedBy(error, SocketTimeoutException.class)) {
    165163                                        return;
     
    250248                                scanRequest.setExtraRequestData(SRUCQL.SCAN_RESOURCE_INFO_PARAMETER,
    251249                                                SRUCQL.SCAN_RESOURCE_INFO_PARAMETER_DEFAULT_VALUE);
    252                                 fullRequestUrl = scanRequest.makeURI(SRUVersion.VERSION_1_2).toString();
     250                                fullRequestUrl = scanRequest.getRequestedURI().toString();
    253251                        } catch (Throwable ex) {
    254252                                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  
    2828        private static final Logger LOGGER = Logger.getLogger(Exports.class.getName());
    2929
    30         public static String getExportCSV(List<Result> resultsProcessed, String separator) {
    31 
     30        public static String getExportCSV(List<Result> resultsProcessed, String filterLanguage, String separator) {
    3231                boolean noResult = true;
    3332                StringBuilder csv = new StringBuilder();
     
    4544                        for (Result result : resultsProcessed) {
    4645                                for (Kwic kwic : result.getKwics()) {
     46                                        if (filterLanguage != null && !filterLanguage.equals(kwic.getLanguage())) {
     47                                                continue;
     48                                        }
    4749                                        csv.append("\"");
    4850                                        csv.append(escapeQuotes(kwic.getLeft()));
     
    9294        }
    9395
    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 {
    9797                SXSSFWorkbook workbook = null;
    9898                ByteArrayOutputStream excelStream = new ByteArrayOutputStream();
     99                int rownum = 0;
    99100                if (resultsProcessed != null && !resultsProcessed.isEmpty()) {
    100101                        try {
     
    112113                                headerStyle.setFont(boldFont);
    113114
    114                                 Row row = sheet.createRow(0);
     115                                Row row = sheet.createRow(rownum++);
    115116
    116117                                for (int j = 0; j < headers.length; ++j) {
     
    122123                                // Body
    123124                                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++);
    130131                                                cell = row.createCell(0, Cell.CELL_TYPE_STRING);
    131132                                                cell.setCellValue(kwic.getLeft());
     
    142143                                                        cell.setCellValue(kwic.getReference());
    143144                                                }
    144                                                 noResult = false;
    145145                                        }
    146146                                }
     
    155155                        }
    156156                }
    157                 if (noResult) {
     157                if (rownum <= 1) {
    158158                        return null;
    159159                } else {
    160160                        return excelStream.toByteArray();
    161161                }
    162 
    163162        }
    164163
    165164        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);
    168167                if (text == null || text.isEmpty()) {
    169168                        return null;
     
    171170                        WLData data;
    172171                        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);
    175177                        tc.createTextLayer().addText(text);
    176178                        data = new WLData(md, tc);
     
    186188        }
    187189
    188         public static String getExportText(List<Result> resultsProcessed) {
     190        public static String getExportText(List<Result> resultsProcessed, String filterLanguage) {
    189191                StringBuilder text = new StringBuilder();
    190192                if (resultsProcessed != null && !resultsProcessed.isEmpty()) {
    191193                        for (Result result : resultsProcessed) {
    192194                                for (Kwic kwic : result.getKwics()) {
     195                                        if (filterLanguage != null && !filterLanguage.equals(kwic.getLanguage())) {
     196                                                continue;
     197                                        }
    193198                                        int i = kwic.getFragments().size() - 1;
    194199                                        for (Kwic.TextFragment tf : kwic.getFragments()) {
    195200                                                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)) {
    197203                                                        text.append(" ");
    198204                                                }
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java

    r6043 r6081  
    5454        }
    5555
    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) {
    5759                final Request request = new Request(corpus, searchString, startRecord, startRecord + maxRecords - 1);
    5860                log.info("Executing search in '{}' query='{}' maxRecords='{}'", corpus, searchString, maxRecords);
     
    7577                requests.add(request);
    7678
    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 
    8479                try {
    8580                        searchClient.searchRetrieve(searchRequest, new ThrottledClient.SearchCallback() {
     
    9388                                                List<Diagnostic> diagnostics = result.getDiagnostics();
    9489                                                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());
    9691                                                        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());
    9894                                                        }
    9995                                                }
     
    107103                                        try {
    108104                                                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());
    110106                                                results.add(new Result(request, null, xc));
    111107                                                requests.remove(request);
     
    134130        }
    135131
    136         public List<Result> getResults() {
     132        public List<Result> getResults(String corpusId) {
    137133                List<Result> copy = new ArrayList<>();
    138134                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                        }
    140144                }
    141145                return copy;
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/util/LanguagesISO693.java

    r6065 r6081  
    1010import java.util.HashMap;
    1111import java.util.Map;
     12import java.util.Set;
    1213import org.slf4j.LoggerFactory;
    1314
     
    129130                return l.name;
    130131        }
     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        }
    131141}
  • SRUAggregator/trunk/src/main/resources/assets/base.css

    r6050 r6081  
    4444.float-right {
    4545    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;
    4663}
    4764
     
    270287}
    271288
    272 div.panel-title .institutionName{
     289.zoomResultButton {
     290        border: 1px solid transparent;
     291}
     292div.panel:hover .zoomResultButton {
     293        color: white;
     294        background-color: #00406F;
     295        border: 1px solid black;
     296}
     297
     298div.panel-title span.institutionName{
    273299        font-size: 12px;
     300        font-weight:300;
     301        color: #888;
     302}
     303
     304.modal-title span.institutionName{
     305        font-size: 14px;
    274306        font-weight:300;
    275307        color: #888;
     
    313345        margin-top: 0;
    314346        margin-bottom: 5px;
     347        padding: 10px 15px 4px;
    315348}
    316349.bs-callout .panel-title {
     
    352385        max-width:552px;
    353386}
     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
    354404
    355405/*** transitions css ***/
  • SRUAggregator/trunk/src/main/resources/assets/index.html

    r5959 r6081  
    4141<body>
    4242        <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>
    4444        </noscript>
    4545
     
    5656        <script src="js/search.js"></script>
    5757        <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 -->
    5874</body>
    5975</html>
  • SRUAggregator/trunk/src/main/resources/assets/js/components.js

    r6050 r6081  
    7070
    7171
     72window.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
    72108window.MyReact.Modal = React.createClass({displayName: 'Modal',
    73109        propTypes: {
    74                 title: PT.string.isRequired,
     110                title: PT.object.isRequired,
    75111        },
    76112        componentDidMount: function() {
     
    203239        render: function() {
    204240                var chevron = "glyphicon glyphicon-chevron-" + (this.state.open ? "down":"right");
    205                 var chevronStyle = {fontSize:12};
    206                 var right = {float:"right"};
    207241                return  React.createElement("div", {className: "bs-callout bs-callout-info"},
    208242                                        React.createElement("div", {className: "panel"},
    209243                                                React.createElement("div", {className: "panel-heading unselectable row", onClick: this.toggleState},
    210244                                                        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}}), " ",
    212246                                                                this.props.title
    213247                                                        ),
    214                                                         React.createElement("div", {style: right},
     248                                                        React.createElement("div", {className: "float-right"},
    215249                                                                this.props.info
    216250                                                        )
  • SRUAggregator/trunk/src/main/resources/assets/js/components.jsx

    r6049 r6081  
    7070
    7171
     72window.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">&times;</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
    72108window.MyReact.Modal = React.createClass({
    73109        propTypes: {
    74                 title: PT.string.isRequired,
     110                title: PT.object.isRequired,
    75111        },
    76112        componentDidMount: function() {
     
    203239        render: function() {
    204240                var chevron = "glyphicon glyphicon-chevron-" + (this.state.open ? "down":"right");
    205                 var chevronStyle = {fontSize:12};
    206                 var right = {float:"right"};
    207241                return  <div className="bs-callout bs-callout-info">
    208242                                        <div className="panel">
    209243                                                <div className="panel-heading unselectable row" onClick={this.toggleState}>
    210244                                                        <div className="panel-title unselectable col-sm-11">
    211                                                                 <span className={chevron} style={chevronStyle} />&nbsp;
     245                                                                <span className={chevron} style={{fontSize:12}} />&nbsp;
    212246                                                                {this.props.title}
    213247                                                        </div>
    214                                                         <div style={right}>
     248                                                        <div className='float-right'>
    215249                                                                {this.props.info}
    216250                                                        </div>
  • SRUAggregator/trunk/src/main/resources/assets/js/main.js

    r6065 r6081  
    33"use strict";
    44
    5 var VERSION = "VERSION 2.0.0.α25";
     5var VERSION = "VERSION 2.0.0.α26";
    66var URLROOT = "/Aggregator-testing";
    77
  • SRUAggregator/trunk/src/main/resources/assets/js/main.jsx

    r6065 r6081  
    33"use strict";
    44
    5 var VERSION = "VERSION 2.0.0.α25";
     5var VERSION = "VERSION 2.0.0.α26";
    66var URLROOT = "/Aggregator-testing";
    77
  • SRUAggregator/trunk/src/main/resources/assets/js/search.js

    r6057 r6081  
    1515var InfoPopover = window.MyReact.InfoPopover;
    1616var Panel = window.MyReact.Panel;
     17var ModalMixin = window.MyReact.ModalMixin;
    1718var Modal = window.MyReact.Modal;
    1819
     
    6465                corpus.priority = 1; // used for ordering search results in corpus view
    6566                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                }
    6672        });
    6773}
     
    158164};
    159165
     166function 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
    160175
    161176var AggregatorPage = window.MyAggregator.AggregatorPage = React.createClass({displayName: 'AggregatorPage',
     
    174189                        corpora: new Corpora([], this.updateCorpora),
    175190                        languageMap: {},
     191                        weblichtLanguages: [],
    176192                        query: "",
    177193                        language: this.anyLanguage,
     
    182198                        searchId: null,
    183199                        timeout: 0,
    184                         hits: this.nohits,                     
     200                        hits: this.nohits,
     201
     202                        zoomedCorpusHit: null,
    185203                };
    186204        },
    187205
    188206        componentDidMount: function() {
    189                 this.refreshCorpora();
    190                 this.refreshLanguages();
    191         },
    192 
    193         refreshCorpora: function() {
    194207                this.props.ajax({
    195                         url: 'rest/corpora',
     208                        url: 'rest/init',
    196209                        success: function(json, textStatus, jqXHR) {
    197210                                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                                        });
    210216                                }
    211217                        }.bind(this),
     
    266272        },
    267273
    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);
    274290        },
    275291
     
    301317
    302318        filterResults: function() {
     319                var noLangFiltering = this.state.languageFilter === 'byMeta';
    303320                var langCode = this.state.language[0];
    304321                return this.state.hits.results.map(function(corpusHit) {
     
    310327                                diagnostics: corpusHit.diagnostics,
    311328                                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                                        }),
    315333                        };
    316334                });
     
    329347        },
    330348
     349        toggleResultModal: function(e, corpusHit) {
     350                $(this.refs.resultModal.getDOMNode()).modal();
     351                this.setState({zoomedCorpusHit: corpusHit});
     352                e.preventDefault();
     353                e.stopPropagation();
     354        },
     355
    331356        handleChange: function(event) {
    332         this.setState({query: event.target.value});
     357                this.setState({query: event.target.value});
    333358        },
    334359
    335360        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                                );
    339377        },
    340378
     
    415453                                ),
    416454
    417                     React.createElement(Modal, {ref: "corporaModal", title: "Collections"},
     455                                React.createElement(Modal, {ref: "corporaModal", title: React.createElement("span", null, "Collections")},
    418456                                        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")},
    422460                                        React.createElement(LanguageSelector, {anyLanguage: this.anyLanguage,
    423461                                                                          languageMap: this.state.languageMap,
     
    425463                                                                          languageFilter: this.state.languageFilter,
    426464                                                                          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                                ),
    428475
    429476                                React.createElement("div", {className: "top-gap"},
    430477                                        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})
    435483                                )
    436484                        )
     
    483531        render: function() {
    484532                var languages = _.pairs(this.props.languageMap)
    485                                 .sort(function(l1, l2){return l1[1].localeCompare(l2[1]); });
     533                                                .sort(function(l1, l2){return l1[1].localeCompare(l2[1]); });
    486534                languages.unshift(this.props.anyLanguage);
    487535                languages = languages.map(this.renderLanguageObject);
     
    503551                                                        React.createElement("label", {style: {color:'black'}},
    504552                                                                 this.renderRadio('byMeta'), " ",
    505                                                                 "Use the collections", "'", " specified language to filter results"
     553                                                                "Use the collections", "'", " specified language to filter results"
    506554                                                        )
    507555                                                        ),
     
    509557                                                        React.createElement("label", {style: {color:'black'}},
    510558                                                                 this.renderRadio('byGuess'), " ",
    511                                                                 "Filter results by using a language detector"
     559                                                                "Filter results by using a language detector"
    512560                                                        )
    513561                                                        ),
     
    515563                                                        React.createElement("label", {style: {color:'black'}},
    516564                                                                 this.renderRadio('byMetaAndGuess'), " ",
    517                                                                 "First use the collections", "'", " specified language then also use a language detector"
     565                                                                "First use the collections", "'", " specified language then also use a language detector"
    518566                                                        )
    519567                                                        )
     
    526574/////////////////////////////////
    527575
    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 
     576var ResultMixin = window.MyReact.ResultMixin = {
     577        // getDefaultProps: function(){
     578        //      return {hasPopover: true};
     579        // },
     580 
    537581        getInitialState: function () {
    538582                return {
     
    545589        },
    546590
    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                                );
    549596        },
    550597
     
    572619                                        React.createElement("td", {style: scenter, className: "keyword"}, hit.keyword),
    573620                                        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                                         )
    611621                                );
    612622        },
     
    658668        },
    659669
    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) {
    700684                return (
    701685                        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"},
    703687                                        React.createElement("span", {className: "glyphicon glyphicon-download-alt", 'aria-hidden': "true"}),
    704688                                        " ", " Download ", " ",
     
    706690                                ),
    707691                                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")},
    709693                                                        " ", " 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")},
    711695                                                        " ", " 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")},
    713697                                                        " ", " 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")},
    715699                                                        " ", " As Plain Text file"))
    716700                                )
     
    719703        },
    720704
    721         renderToWeblichtLinks: function() {
     705        renderToWeblichtLinks: function(corpusId, error) {
    722706                return (
    723707                        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"}),
    726710                                        " ", " Use Weblicht ", " ",
    727711                                        React.createElement("span", {className: "caret"})
    728712                                ),
    729713                                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                                        )
    732721                                )
    733722                        )
     
    735724        },
    736725
    737         renderToolbox: function(hits) {
    738                 if (hits <= 0) {
     726};
     727
     728var 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) {
    739749                        return false;
    740750                }
    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"
    757781                                                        )
    758782                                                )
     783
    759784                                        )
    760785                                );
    761786        },
     787});
     788
     789var 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        },
    762847
    763848        render: function() {
     849                if (this.props.results.length + this.props.requests.length === 0) {
     850                        return false;
     851                }
    764852                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;
    769854                return  React.createElement("div", null,
    770855                                        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)
    776875                                        )
    777876                                );
  • SRUAggregator/trunk/src/main/resources/assets/js/search.jsx

    r6057 r6081  
    1515var InfoPopover = window.MyReact.InfoPopover;
    1616var Panel = window.MyReact.Panel;
     17var ModalMixin = window.MyReact.ModalMixin;
    1718var Modal = window.MyReact.Modal;
    1819
     
    6465                corpus.priority = 1; // used for ordering search results in corpus view
    6566                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                }
    6672        });
    6773}
     
    158164};
    159165
     166function 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
    160175
    161176var AggregatorPage = window.MyAggregator.AggregatorPage = React.createClass({
     
    174189                        corpora: new Corpora([], this.updateCorpora),
    175190                        languageMap: {},
     191                        weblichtLanguages: [],
    176192                        query: "",
    177193                        language: this.anyLanguage,
     
    182198                        searchId: null,
    183199                        timeout: 0,
    184                         hits: this.nohits,                     
     200                        hits: this.nohits,
     201
     202                        zoomedCorpusHit: null,
    185203                };
    186204        },
    187205
    188206        componentDidMount: function() {
    189                 this.refreshCorpora();
    190                 this.refreshLanguages();
    191         },
    192 
    193         refreshCorpora: function() {
    194207                this.props.ajax({
    195                         url: 'rest/corpora',
     208                        url: 'rest/init',
    196209                        success: function(json, textStatus, jqXHR) {
    197210                                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                                        });
    210216                                }
    211217                        }.bind(this),
     
    266272        },
    267273
    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);
    274290        },
    275291
     
    301317
    302318        filterResults: function() {
     319                var noLangFiltering = this.state.languageFilter === 'byMeta';
    303320                var langCode = this.state.language[0];
    304321                return this.state.hits.results.map(function(corpusHit) {
     
    310327                                diagnostics: corpusHit.diagnostics,
    311328                                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                                        }),
    315333                        };
    316334                });
     
    329347        },
    330348
     349        toggleResultModal: function(e, corpusHit) {
     350                $(this.refs.resultModal.getDOMNode()).modal();
     351                this.setState({zoomedCorpusHit: corpusHit});
     352                e.preventDefault();
     353                e.stopPropagation();
     354        },
     355
    331356        handleChange: function(event) {
    332         this.setState({query: event.target.value});
     357                this.setState({query: event.target.value});
    333358        },
    334359
    335360        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>;
    339377        },
    340378
     
    415453                                </div>
    416454
    417                     <Modal ref="corporaModal" title="Collections">
     455                                <Modal ref="corporaModal" title={<span>Collections</span>}>
    418456                                        <CorpusView corpora={this.state.corpora} languageMap={this.state.languageMap} />
    419                     </Modal>
    420 
    421                     <Modal ref="languageModal" title="Select Language">
     457                                </Modal>
     458
     459                                <Modal ref="languageModal" title={<span>Select Language</span>}>
    422460                                        <LanguageSelector anyLanguage={this.anyLanguage}
    423461                                                                          languageMap={this.state.languageMap}
     
    425463                                                                          languageFilter={this.state.languageFilter}
    426464                                                                          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>
    428475
    429476                                <div className="top-gap">
    430477                                        <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}/>
    435483                                </div>
    436484                        </div>
     
    483531        render: function() {
    484532                var languages = _.pairs(this.props.languageMap)
    485                                 .sort(function(l1, l2){return l1[1].localeCompare(l2[1]); });
     533                                                .sort(function(l1, l2){return l1[1].localeCompare(l2[1]); });
    486534                languages.unshift(this.props.anyLanguage);
    487535                languages = languages.map(this.renderLanguageObject);
     
    503551                                                        <label style={{color:'black'}}>
    504552                                                                { this.renderRadio('byMeta') }{" "}
    505                                                                 Use the collections{"'"} specified language to filter results
     553                                                                Use the collections{"'"} specified language to filter results
    506554                                                        </label>
    507555                                                        </div>
     
    509557                                                        <label style={{color:'black'}}>
    510558                                                                { this.renderRadio('byGuess') }{" "}
    511                                                                 Filter results by using a language detector
     559                                                                Filter results by using a language detector
    512560                                                        </label>
    513561                                                        </div>
     
    515563                                                        <label style={{color:'black'}}>
    516564                                                                { this.renderRadio('byMetaAndGuess') }{" "}
    517                                                                 First use the collections{"'"} specified language then also use a language detector
     565                                                                First use the collections{"'"} specified language then also use a language detector
    518566                                                        </label>
    519567                                                        </div>
     
    526574/////////////////////////////////
    527575
    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 
     576var ResultMixin = window.MyReact.ResultMixin = {
     577        // getDefaultProps: function(){
     578        //      return {hasPopover: true};
     579        // },
     580 
    537581        getInitialState: function () {
    538582                return {
     
    545589        },
    546590
    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>;
    549596        },
    550597
     
    573620                                        <td style={sleft}>{hit.right}</td>
    574621                                </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>;
    612622        },
    613623
     
    658668        },
    659669
    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                                                &nbsp;
     678                                                Display as Key Word In Context
     679                                        </label>
     680                                </div>;
     681        },
     682
     683        renderDownloadLinks: function(corpusId) {
    700684                return (
    701685                        <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">
    703687                                        <span className="glyphicon glyphicon-download-alt" aria-hidden="true"/>
    704688                                        {" "} Download {" "}
     
    706690                                </button>
    707691                                <ul className="dropdown-menu">
    708                                         <li> <a href={this.props.getDownloadLink("csv")}>
     692                                        <li> <a href={this.props.getDownloadLink(corpusId, "csv")}>
    709693                                                        {" "} As CSV file</a></li>
    710                                         <li> <a href={this.props.getDownloadLink("excel")}>
     694                                        <li> <a href={this.props.getDownloadLink(corpusId, "excel")}>
    711695                                                        {" "} As Excel file</a></li>
    712                                         <li> <a href={this.props.getDownloadLink("tcf")}>
     696                                        <li> <a href={this.props.getDownloadLink(corpusId, "tcf")}>
    713697                                                        {" "} As TCF file</a></li>
    714                                         <li> <a href={this.props.getDownloadLink("text")}>
     698                                        <li> <a href={this.props.getDownloadLink(corpusId, "text")}>
    715699                                                        {" "} As Plain Text file</a></li>
    716700                                </ul>
     
    719703        },
    720704
    721         renderToWeblichtLinks: function() {
     705        renderToWeblichtLinks: function(corpusId, error) {
    722706                return (
    723707                        <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"/>
    726710                                        {" "} Use Weblicht {" "}
    727711                                        <span className="caret"/>
    728712                                </button>
    729713                                <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>
    732721                                </ul>
    733722                        </div>
     
    735724        },
    736725
    737         renderToolbox: function(hits) {
    738                 if (hits <= 0) {
     726};
     727
     728var 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) {
    739749                        return false;
    740750                }
    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                                                                 &nbsp;
    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>
    758763                                                </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
     789var 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>
    759810                                        </div>
    760811                                </div>;
    761812        },
    762813
     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
    763848        render: function() {
     849                if (this.props.results.length + this.props.requests.length === 0) {
     850                        return false;
     851                }
    764852                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;
    769854                return  <div>
    770855                                        <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)}
    776875                                        </ReactCSSTransitionGroup>
    777876                                </div>;
Note: See TracChangeset for help on using the changeset viewer.