diff --git a/README.md b/README.md index 028a48c73e8654e923592c7be5a691cf9ffb35ca..274b721aad68b3a870978d0dc091a7b44afff8e8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Look at the [contribution guide](CONTRIBUTING.md). ## Install development environment -- Install `node` and `npm` +- Install `node` and `yarn` Installation via `nvm` is recommended for easier control of installed version: https://github.com/creationix/nvm @@ -19,24 +19,18 @@ nvm install 10.13.0 nvm use v10.13.0 ``` -- Install Angular CLI - -```sh -npm install -g @angular/cli@7.0.6 -``` - - Install JS dependencies ```sh -cd frontend -npm install +cd web +yarn ``` - Install latest Java JDK8 See latest instructions for your operating system. -- (Optional) Install `docker` and `docker-compose` +- (Optional) Install `docker` If you want to run an Elasticsearch and Kibana instance on your machine. You can use your favorite package manager for that @@ -49,7 +43,7 @@ First make sure you have access to an Elasticsearch HTTP API server on `http://1 If you want to run an Elasticsearch server on your development machine you can use the `docker`/`docker-compose` configuration like so: ```sh -docker-compose up +docker compose up ``` > This will launch an Elasticsearch server (with port forwarding `9200`) and a Kibana server (with port forwarding `5601`) @@ -57,13 +51,20 @@ docker-compose up > **Warning**: This repository does not automatically index data into Elasticsearch, you need to prepare your indices beforehand. -If you just need access to API (to run the `ng serve` on top of it), you can run: +If you just need access to API, you can run: ```sh ./gradlew bootRun ``` -Otherwise, for the complete server (backend APIs + frontend interface), you can run: +If you are developing and need to work on the `web` assets (scripts, styles, etc), +you'll need to run the application with the `dev` profile: + +```sh +./gradlew bootRun --args='--spring.profiles.active=dev' +``` + +Otherwise, for the complete server (backend APIs + web interface), you can run: ```sh ./gradlew assemble && java -jar backend/build/libs/faidare.jar @@ -71,18 +72,26 @@ Otherwise, for the complete server (backend APIs + frontend interface), you can The server should then be accessible at http://localhost:8380/faidare-dev -## Run frontend development server +## Build the JS/CSS assets for the Web module -The frontend requests are redirected to the local backend API server (see instructions above to launch) via the -Angular proxy. +The `web` directory contains the scripts and styles used by the thymeleaf templates +when a Germplasm, Study or Site page is rendered. -You can run the development server with the following command: +The build process for these assets can be run with the following command: ```sh -cd frontend -ng serve +cd web +yarn watch ``` +`yarn watch` automatically picks up the changes in any files, +and rebuild the resulting assets (thanks to Webpack). +Make sure the backend is running with the `dev` profile if you do so (see above), +otherwise the changes won't be shown in the browser. + +`yarn watch:prod` is also available to use production settings, +while `yarn build` and `yarn build:prod` do the same but without watching the changes. + ## Harvest Before all, take care to get data locally before running any indexing script. diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 64acb331b41fea7957dcb7ea7c0d7713e561dd43..6c4f0926f442b6e83b9230f7cfdf2f6ee27c1447 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -11,7 +11,7 @@ plugins { java jacoco id("org.springframework.boot") version "2.1.2.RELEASE" - id("com.gorylenko.gradle-git-properties") version "1.5.2" + id("com.gorylenko.gradle-git-properties") version "2.3.1" id("io.spring.dependency-management") version "1.0.6.RELEASE" id("org.sonarqube") id("org.owasp.dependencycheck") version "6.0.3" diff --git a/backend/src/main/resources/templates/fragments/institute.html b/backend/src/main/resources/templates/fragments/institute.html index a79ff6cc547696db1bff202ab5ca8bb481602809..85871d5238adda8d38610de218a4b1654c4dce1a 100644 --- a/backend/src/main/resources/templates/fragments/institute.html +++ b/backend/src/main/resources/templates/fragments/institute.html @@ -1,34 +1,48 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> - -<body> - -<!-- + <body> + <!-- Reusable fragment displaying the content of an institute popover. Its unique argument (institute) is an InstituteVO --> -<th:block th:fragment="institute(institute)"> - <div class="text-center py-2" th:if="${institute.logo}"> - <img class="img-fluid" th:src="${institute.logo}" th:alt="${institute.instituteName}"/> - </div> - <div th:replace="fragments/row::text-row(label='Code', text=${institute.instituteCode})"></div> - <div th:replace="fragments/row::text-row(label='Acronym', text=${institute.acronym})"></div> - <div th:replace="fragments/row::text-row(label='Organization', text=${institute.organisation})"></div> - <div th:replace="fragments/row::text-row(label='Type', text=${institute.instituteType})"></div> - <div th:replace="fragments/row::text-row(label='Address', text=${institute.address})"></div> - - <th:block th:if="${institute.webSite}"> - <div th:replace="fragments/row::row(label='Website', content=~{::.institute-website})"> - <a class="institute-website" - target="_blank" - th:href="${institute.webSite}" - th:text="${#strings.abbreviate(institute.webSite, 25)}"></a> - </div> - </th:block> -</th:block> - -</body> + <th:block th:fragment="institute(institute)"> + <div class="text-center py-2" th:if="${institute.logo}"> + <img + class="img-fluid" + th:src="${institute.logo}" + th:alt="${institute.instituteName}" + /> + </div> + <div + th:replace="fragments/row::text-row(label='Code', text=${institute.instituteCode})" + ></div> + <div + th:replace="fragments/row::text-row(label='Acronym', text=${institute.acronym})" + ></div> + <div + th:replace="fragments/row::text-row(label='Organization', text=${institute.organisation})" + ></div> + <div + th:replace="fragments/row::text-row(label='Type', text=${institute.instituteType})" + ></div> + <div + th:replace="fragments/row::text-row(label='Address', text=${institute.address})" + ></div> + <th:block th:if="${institute.webSite}"> + <div + th:replace="fragments/row::row(label='Website', content=~{::.institute-website})" + > + <a + class="institute-website" + target="_blank" + th:href="${institute.webSite}" + th:text="${#strings.abbreviate(institute.webSite, 25)}" + ></a> + </div> + </th:block> + </th:block> + </body> </html> diff --git a/backend/src/main/resources/templates/fragments/link.html b/backend/src/main/resources/templates/fragments/link.html index d7c43bbdb422c31c5bce647d302c803270672669..d82f0bd5c03a81844046044b6702e8e3e361d561 100644 --- a/backend/src/main/resources/templates/fragments/link.html +++ b/backend/src/main/resources/templates/fragments/link.html @@ -1,20 +1,19 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> - -<body> -<!-- + <body> + <!-- Reusable fragment displaying a link with a label if the provided url is not empty, or a span with the label if the provided url is empty. Both arguments are strings. --> -<th:block th:fragment="link(label, url)"> - <a th:unless="${#strings.isEmpty(url)}" - th:href="${url}" - th:text="${label}"></a> - <span th:if="${#strings.isEmpty(url)}" th:text="${label}"></span> -</th:block> - -</body> - + <th:block th:fragment="link(label, url)"> + <a + th:unless="${#strings.isEmpty(url)}" + th:href="${url}" + th:text="${label}" + ></a> + <span th:if="${#strings.isEmpty(url)}" th:text="${label}"></span> + </th:block> + </body> </html> diff --git a/backend/src/main/resources/templates/fragments/map.html b/backend/src/main/resources/templates/fragments/map.html index 35ebed333c5c5eaf55160f400e5ebde01313cf24..cad10ae8e4027e704be8bdc78e8308b82aa9cc59 100644 --- a/backend/src/main/resources/templates/fragments/map.html +++ b/backend/src/main/resources/templates/fragments/map.html @@ -1,23 +1,24 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> - -<body> -<!-- + <body> + <!-- Reusable fragment displaying a map and its legend. The map is initially hidden. The JavaScript displays it if there are locations to display --> -<div th:fragment="map" id="map-container" class="d-none"> - <div id="map" class="border rounded"></div> - <div class="map-legend mt-1 small"> - <img th:src="@{/assets/images/marker-icon-red.png}" id="red"/> - <label for="red" class="me-2">Origin site</label> - <img th:src="@{/assets/images/marker-icon-blue.png}" id="blue"/> - <label for="blue" class="me-2">Collecting site</label> - <img th:src="@{/assets/images/marker-icon-green.png}" id="green"/> - <label for="green" class="me-2">Evaluation site</label> - <img th:src="@{/assets/images/marker-icon-purple.png}" id="purple"/> - <label for="purple">Multi-purpose site</label> - </div> -</div> + <div th:fragment="map" id="map-container" class="d-none"> + <div id="map" class="border rounded"></div> + <div class="map-legend mt-1 small"> + <img th:src="@{/assets/images/marker-icon-red.png}" id="red" /> + <label for="red" class="me-2">Origin site</label> + <img th:src="@{/assets/images/marker-icon-blue.png}" id="blue" /> + <label for="blue" class="me-2">Collecting site</label> + <img th:src="@{/assets/images/marker-icon-green.png}" id="green" /> + <label for="green" class="me-2">Evaluation site</label> + <img th:src="@{/assets/images/marker-icon-purple.png}" id="purple" /> + <label for="purple">Multi-purpose site</label> + </div> + </div> + </body> +</html> diff --git a/backend/src/main/resources/templates/fragments/row.html b/backend/src/main/resources/templates/fragments/row.html index 8676b5906b77ae97d6e8c2ca826839450ce28842..aebf2476a74cbb9d5cd3e1fb57fcdde5d331a462 100644 --- a/backend/src/main/resources/templates/fragments/row.html +++ b/backend/src/main/resources/templates/fragments/row.html @@ -1,10 +1,8 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> - -<body> - -<!-- + <body> + <!-- Reusable fragment displaying a responsive row containing a label and a content. The label argument is a string. The content argument is a fragment which is displayed at the right of the label. @@ -19,14 +17,14 @@ into a block with the condition: </th:block> --> -<div th:fragment="row(label, content)" class="row f-row"> - <div class="col-md-4 label pb-1 pb-md-0" th:text="${label}"></div> - <div class="col"> - <th:block th:replace="${content}" /> - </div> -</div> + <div th:fragment="row(label, content)" class="row f-row"> + <div class="col-md-4 label pb-1 pb-md-0" th:text="${label}"></div> + <div class="col"> + <th:block th:replace="${content}" /> + </div> + </div> -<!-- + <!-- Reusable fragment displaying a responsive row containing a label and a textual content. The label argument is a string. The text argument is a string which is displayed at the right of the label. @@ -40,11 +38,13 @@ into a block with the condition: <div th:replace="fragments/row::text-row(label='Some label', text=${someTextExpression})"></div> </th:block> --> -<div th:fragment="text-row(label, text)" th:unless="${#strings.isEmpty(text)}" class="row f-row"> - <div class="col-md-4 label pb-1 pb-md-0" th:text="${label}"></div> - <div class="col" th:text="${text}"></div> -</div> - -</body> - + <div + th:fragment="text-row(label, text)" + th:unless="${#strings.isEmpty(text)}" + class="row f-row" + > + <div class="col-md-4 label pb-1 pb-md-0" th:text="${label}"></div> + <div class="col" th:text="${text}"></div> + </div> + </body> </html> diff --git a/backend/src/main/resources/templates/fragments/source.html b/backend/src/main/resources/templates/fragments/source.html index b09638810885559be62d0c5448adecd617dbe2fb..ad5e588c3d53e3ce096774e742d008f5110f44ec 100644 --- a/backend/src/main/resources/templates/fragments/source.html +++ b/backend/src/main/resources/templates/fragments/source.html @@ -1,10 +1,8 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> - -<body> - -<!-- + <body> + <!-- Reusable fragment displaying the source and the data links of an entity (site, study or germplasm). The source argument is a DataSource. The url argument is a string, which is the URL of the entity. @@ -12,24 +10,32 @@ The entityType argument is a string, which is used in the message "Link to this <entityType>". --> -<th:block th:fragment="source(source, url, entityType)"> - <th:block th:if="${source != null}"> - <div th:replace="fragments/row::row(label='Source', content=~{::.source})"> - <a class="source" target="_blank" th:href="${source.url}"> - <img class="img-fluid" style="max-height: 60px;" th:src="${source.image}" th:alt="${source.name} + ' logo'" /> - </a> - </div> - </th:block> - - <th:block th:if="${url != null && source != null}"> - <div th:replace="fragments/row::row(label='Data link', content=~{::.source-url})"> - <a class="source-url" target="_blank" th:href="${url}"> - Link to this <span th:text="${entityType}"></span> on <th:block th:text="${source.name}" /> - </a> - </div> - </th:block> -</th:block> - -</body> + <th:block th:fragment="source(source, url, entityType)"> + <th:block th:if="${source != null}"> + <div + th:replace="fragments/row::row(label='Source', content=~{::.source})" + > + <a class="source" target="_blank" th:href="${source.url}"> + <img + class="img-fluid" + style="max-height: 60px" + th:src="${source.image}" + th:alt="${source.name} + ' logo'" + /> + </a> + </div> + </th:block> + <th:block th:if="${url != null && source != null}"> + <div + th:replace="fragments/row::row(label='Data link', content=~{::.source-url})" + > + <a class="source-url" target="_blank" th:href="${url}"> + Link to this <span th:text="${entityType}"></span> on + <th:block th:text="${source.name}" /> + </a> + </div> + </th:block> + </th:block> + </body> </html> diff --git a/backend/src/main/resources/templates/fragments/xrefs.html b/backend/src/main/resources/templates/fragments/xrefs.html index c60310b69c5d852b7a636d9ab7392d0a52d2b553..938d3ea6e96aeabb26d19290b4fe0fa132645e2e 100644 --- a/backend/src/main/resources/templates/fragments/xrefs.html +++ b/backend/src/main/resources/templates/fragments/xrefs.html @@ -1,40 +1,53 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> - -<body> - -<!-- + <body> + <!-- Reusable fragment displaying a cross references section, with its title. The unique argument (crossReferences) is a List<XRefDocumentVO> --> -<div class="f-card" th:fragment="xrefs(crossReferences)" th:if="${!#lists.isEmpty(crossReferences)}"> - <h2>Cross references</h2> - <div class="f-card-body"> - <div class="scroll-table-container scroll-table-container-big"> - <table class="table table-sm table-striped table-sticky table-responsive-sm"> - <thead> - <tr> - <th scope="col">Name</th> - <th scope="col">Source</th> - <th scope="col">Type</th> - <th scope="col">Description</th> - </tr> - </thead> - <tbody> - <tr th:each="crossRef : ${crossReferences}"> - <td><a th:href="${crossRef.url}" target="_blank" th:text="${crossRef.name}"></a></td> - <td th:text="${crossRef.databaseName}"></td> - <td th:text="${crossRef.entryType}"></td> - <td style="min-width: 30rem;" th:text="${#strings.abbreviate(crossRef.description, 120)}"></td> - </tr> - </tbody> - </table> + <div + class="f-card" + th:fragment="xrefs(crossReferences)" + th:if="${!#lists.isEmpty(crossReferences)}" + > + <h2>Cross references</h2> + <div class="f-card-body"> + <div class="scroll-table-container scroll-table-container-big"> + <table + class=" + table table-sm table-striped table-sticky table-responsive-sm + " + > + <thead> + <tr> + <th scope="col">Name</th> + <th scope="col">Source</th> + <th scope="col">Type</th> + <th scope="col">Description</th> + </tr> + </thead> + <tbody> + <tr th:each="crossRef : ${crossReferences}"> + <td> + <a + th:href="${crossRef.url}" + target="_blank" + th:text="${crossRef.name}" + ></a> + </td> + <td th:text="${crossRef.databaseName}"></td> + <td th:text="${crossRef.entryType}"></td> + <td + style="min-width: 30rem" + th:text="${#strings.abbreviate(crossRef.description, 120)}" + ></td> + </tr> + </tbody> + </table> + </div> + </div> </div> - </div> -</div> - -</body> - + </body> </html> diff --git a/backend/src/main/resources/templates/germplasm.html b/backend/src/main/resources/templates/germplasm.html index 26c1a85bb55893c61dc237858e1f3a1a88f66be9..b18b67bbc2e6c6e52660165403442dbb735d015e 100644 --- a/backend/src/main/resources/templates/germplasm.html +++ b/backend/src/main/resources/templates/germplasm.html @@ -4,449 +4,710 @@ xmlns:th="http://www.thymeleaf.org" th:replace="~{layout/main :: layout(title=~{::title}, content=~{::main}, script=~{::script})}" > -<head> - <title>Germplasm: <th:block th:text="${model.germplasm.germplasmName}" /></title> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -</head> - -<body> -<main> - <div class="d-flex"> - <h1 class="flex-grow-1">Germplasm: <th:block th:text="${model.germplasm.germplasmName}" /></h1> - <div th:if="${model.germplasm.holdingGenbank != null && model.germplasm.holdingGenbank.logo != null}"> - <img th:src="${model.germplasm.holdingGenbank.logo}" th:alt="${model.germplasm.holdingGenbank.instituteName}" /> - </div> - </div> - - <div th:replace="fragments/map::map"></div> - - <div class="row align-items-center justify-content-center mt-4"> - <div class="col-auto field" th:if="${model.germplasm.photo != null && model.germplasm.photo.thumbnailFile != null}"> - <template id="photo-popover"> - <div class="card"> - <img th:src="${model.germplasm.photo.file}" class="card-img-top" alt=""> - <div class="card-body"> - <div th:replace="fragments/row::text-row(label='Accession name', text=${model.germplasm.germplasmName})"></div> - <div th:replace="fragments/row::text-row(label='Photo name', text=${model.germplasm.photo.photoName})"></div> - <div th:replace="fragments/row::text-row(label='Description', text=${model.germplasm.photo.description})"></div> - <div th:replace="fragments/row::text-row(label='Copyright', text=${model.germplasm.photo.copyright})"></div> + <head> + <title> + Germplasm: <th:block th:text="${model.germplasm.germplasmName}" /> + </title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + </head> + + <body> + <main> + <div class="d-flex"> + <h1 class="flex-grow-1"> + Germplasm: <th:block th:text="${model.germplasm.germplasmName}" /> + </h1> + <div + th:if="${model.germplasm.holdingGenbank != null && model.germplasm.holdingGenbank.logo != null}" + > + <img + th:src="${model.germplasm.holdingGenbank.logo}" + th:alt="${model.germplasm.holdingGenbank.instituteName}" + /> + </div> + </div> + + <div th:replace="fragments/map::map"></div> + + <div class="row align-items-center justify-content-center mt-4"> + <div + class="col-auto field" + th:if="${model.germplasm.photo != null && model.germplasm.photo.thumbnailFile != null}" + > + <template id="photo-popover"> + <div class="card"> + <img + th:src="${model.germplasm.photo.file}" + class="card-img-top" + alt="" + /> + <div class="card-body"> + <div + th:replace="fragments/row::text-row(label='Accession name', text=${model.germplasm.germplasmName})" + ></div> + <div + th:replace="fragments/row::text-row(label='Photo name', text=${model.germplasm.photo.photoName})" + ></div> + <div + th:replace="fragments/row::text-row(label='Description', text=${model.germplasm.photo.description})" + ></div> + <div + th:replace="fragments/row::text-row(label='Copyright', text=${model.germplasm.photo.copyright})" + ></div> + </div> + </div> + </template> + + <a + role="button" + class="d-flex flex-column align-items-center" + data-bs-toggle="popover" + tabindex="0" + th:data-bs-title="${model.germplasm.photo.photoName}" + data-bs-element="#photo-popover" + data-bs-container="body" + data-bs-trigger="focus" + > + <img + th:src="${model.germplasm.photo.thumbnailFile}" + class="img-fluid" + /> + + <figcaption class="figure-caption"> + © <span th:text="${model.germplasm.photo.copyright}"></span> + </figcaption> + </a> + </div> + + <div class="col-12 col-lg"> + <div class="f-card"> + <h2>Identification</h2> + + <div class="f-card-body"> + <div + th:replace="fragments/row::text-row(label='Germplasm name', text=${model.germplasm.germplasmName})" + ></div> + <div + th:replace="fragments/row::text-row(label='Accession number', text=${model.germplasm.accessionNumber})" + ></div> + + <div + th:replace="fragments/source::source(source=${model.source}, url=${model.germplasm.url}, entityType='germplasm')" + ></div> + + <th:block th:unless="${#lists.isEmpty(model.germplasm.synonyms)}"> + <div + th:replace="fragments/row::row(label='Accession synonyms', content=~{::#accession-synonyms})" + > + <div + id="accession-synonyms" + class="content-overflow" + th:text="${#strings.listJoin(model.germplasm.synonyms, ', ')}" + ></div> + </div> + </th:block> + + <th:block th:unless="${#strings.isEmpty(model.taxon)}"> + <div + th:replace="fragments/row::row(label='Taxon', content=~{::#taxon})" + > + <div id="taxon"> + <template id="taxon-popover"> + <th:block + th:unless="${#strings.isEmpty(model.germplasm.genus)}" + > + <div + th:replace="fragments/row::row(label='Genus', content=~{::#taxon-genus})" + > + <em + id="taxon-genus" + th:text="${model.germplasm.genus}" + ></em> + </div> + </th:block> + <th:block + th:unless="${#strings.isEmpty(model.germplasm.species)}" + > + <div + th:replace="fragments/row::row(label='Species', content=~{::#taxon-species})" + > + <span id="taxon-species"> + <em th:text="${model.germplasm.species}"></em> + <span + th:unless="${#strings.isEmpty(model.germplasm.speciesAuthority)}" + th:text="${'(' + model.germplasm.speciesAuthority + ')'}" + ></span> + </span> + </div> + </th:block> + <th:block + th:unless="${#strings.isEmpty(model.germplasm.subtaxa)}" + > + <div + th:replace="fragments/row::row(label='Subtaxa', content=~{::#taxon-subtaxa})" + > + <span id="taxon-subtaxa"> + <em th:text="${model.germplasm.subtaxa}"></em> + <span + th:unless="${#strings.isEmpty(model.germplasm.subtaxaAuthority)}" + th:text="${'(' + model.germplasm.subtaxaAuthority + ')'}" + ></span> + </span> + </div> + </th:block> + + <div + th:replace="fragments/row::text-row(label='Authority', text=${model.taxonAuthor})" + ></div> + + <th:block + th:unless="${#lists.isEmpty(model.germplasm.taxonIds)}" + > + <div + th:replace="fragments/row::row(label='Taxon IDs', content=~{::#taxon-ids})" + > + <div id="taxon-ids"> + <div + th:each="taxonId : ${model.germplasm.taxonIds}" + class="row" + > + <div + class="col-6 text-nowrap" + th:text="${taxonId.sourceName}" + ></div> + <div class="col-6"> + <span + class="taxon-id" + th:replace="fragments/link::link(label=${taxonId.taxonId}, url=${#faidare.taxonIdUrl(taxonId)})" + ></span> + </div> + </div> + </div> + </div> + </th:block> + + <div + th:replace="fragments/row::text-row(label='Comment', text=${model.germplasm.taxonComment})" + ></div> + <th:block + th:unless="${#lists.isEmpty(model.germplasm.taxonCommonNames)}" + > + <div + th:replace="fragments/row::row(label='Taxon common names', content=~{::#taxon-common-names})" + > + <div + id="taxon-common-names" + class="content-overflow" + th:text="${#strings.listJoin(model.germplasm.taxonCommonNames, ', ')}" + ></div> + </div> + </th:block> + <th:block + th:unless="${#lists.isEmpty(model.germplasm.taxonSynonyms)}" + > + <div + th:replace="fragments/row::row(label='Taxon synonyms', content=~{::#taxon-synonyms})" + > + <div + id="taxon-synonyms" + class="content-overflow" + th:text="${#strings.listJoin(model.germplasm.taxonSynonyms, ', ')}" + ></div> + </div> + </th:block> + </template> + <a + role="button" + tabindex="0" + data-bs-toggle="popover" + th:data-bs-title="${model.taxon}" + data-bs-element="#taxon-popover" + data-bs-container="body" + data-bs-trigger="focus" + > + <em th:text="${model.taxon}"></em> + <th:block + th:unless="${#strings.isEmpty(model.taxonAuthor)}" + >(<span th:text="${model.taxonAuthor}"></span + >)</th:block + > + </a> + </div> + </div> + </th:block> + + <div + th:replace="fragments/row::text-row(label='Biological status', text=${model.germplasm.biologicalStatusOfAccessionCode})" + ></div> + <div + th:replace="fragments/row::text-row(label='Genetic nature', text=${model.germplasm.geneticNature})" + ></div> + <div + th:replace="fragments/row::text-row(label='Seed source', text=${model.germplasm.seedSource})" + ></div> + <div + th:replace="fragments/row::text-row(label='Pedigree', text=${model.germplasm.pedigree})" + ></div> + <div + th:replace="fragments/row::text-row(label='Comments', text=${model.germplasm.comment})" + ></div> + + <th:block + th:if="${model.germplasm.originSite != null && !#strings.isEmpty(model.germplasm.originSite.siteName)}" + > + <div + th:replace="fragments/row::row(label='Origin site', content=~{::#origin-site})" + > + <a + id="origin-site" + th:href="@{/sites/{siteId}(siteId=${#faidare.toSiteParam(model.germplasm.originSite.siteId)})}" + th:text="${model.germplasm.originSite.siteName}" + ></a> + </div> + </th:block> + </div> </div> </div> - </template> - - <a role="button" - class="d-flex flex-column align-items-center" - data-bs-toggle="popover" - tabindex="0" - th:data-bs-title="${model.germplasm.photo.photoName}" - data-bs-element="#photo-popover" - data-bs-container="body" - data-bs-trigger="focus"> - <img th:src="${model.germplasm.photo.thumbnailFile}" class="img-fluid" /> - - <figcaption class="figure-caption"> - © <span th:text="${model.germplasm.photo.copyright}"></span> - </figcaption> - </a> - </div> - - <div class="col-12 col-lg"> - <div class="f-card"> - <h2>Identification</h2> + </div> + <div class="f-card" th:if="${model.germplasm.holdingInstitute}"> + <h2>Depositary</h2> <div class="f-card-body"> - <div th:replace="fragments/row::text-row(label='Germplasm name', text=${model.germplasm.germplasmName})"></div> - <div th:replace="fragments/row::text-row(label='Accession number', text=${model.germplasm.accessionNumber})"></div> - - <div th:replace="fragments/source::source(source=${model.source}, url=${model.germplasm.url}, entityType='germplasm')"></div> + <template id="holding-institute-popover"> + <div + th:replace="fragments/institute::institute(institute=${model.germplasm.holdingInstitute})" + ></div> + </template> + <div + th:replace="fragments/row::row(label='Institution', content=~{::#institution})" + > + <a + id="institution" + role="button" + tabindex="0" + data-bs-toggle="popover" + th:data-bs-title="${model.germplasm.holdingInstitute.instituteName}" + data-bs-element="#holding-institute-popover" + data-bs-container="body" + data-bs-trigger="focus" + th:text="${model.germplasm.holdingInstitute.instituteName}" + ></a> + </div> - <th:block th:unless="${#lists.isEmpty(model.germplasm.synonyms)}"> - <div th:replace="fragments/row::row(label='Accession synonyms', content=~{::#accession-synonyms})"> - <div id="accession-synonyms" class="content-overflow" th:text="${#strings.listJoin(model.germplasm.synonyms, ', ')}"></div> + <th:block + th:if="${model.germplasm.holdingGenbank != null && !#strings.isEmpty(model.germplasm.holdingGenbank.instituteName) && !#strings.isEmpty(model.germplasm.holdingGenbank.webSite)}" + > + <div + th:replace="fragments/row::row(label='Stock center name', content=~{::#stock-center-name})" + > + <a + id="stock-center-name" + target="_blank" + th:href="${model.germplasm.holdingGenbank.webSite}" + th:text="${model.germplasm.holdingGenbank.instituteName}" + ></a> </div> </th:block> - <th:block th:unless="${#strings.isEmpty(model.taxon)}"> - <div th:replace="fragments/row::row(label='Taxon', content=~{::#taxon})"> - <div id="taxon"> - <template id="taxon-popover"> - <th:block th:unless="${#strings.isEmpty(model.germplasm.genus)}"> - <div th:replace="fragments/row::row(label='Genus', content=~{::#taxon-genus})"> - <em id="taxon-genus" th:text="${model.germplasm.genus}"></em> - </div> - </th:block> - <th:block th:unless="${#strings.isEmpty(model.germplasm.species)}"> - <div th:replace="fragments/row::row(label='Species', content=~{::#taxon-species})"> - <span id="taxon-species"> - <em th:text="${model.germplasm.species}"></em> - <span th:unless="${#strings.isEmpty(model.germplasm.speciesAuthority)}" - th:text="${'(' + model.germplasm.speciesAuthority + ')'}"></span> - </span> - </div> - </th:block> - <th:block th:unless="${#strings.isEmpty(model.germplasm.subtaxa)}"> - <div th:replace="fragments/row::row(label='Subtaxa', content=~{::#taxon-subtaxa})"> - <span id="taxon-subtaxa"> - <em th:text="${model.germplasm.subtaxa}"></em> - <span th:unless="${#strings.isEmpty(model.germplasm.subtaxaAuthority)}" - th:text="${'(' + model.germplasm.subtaxaAuthority + ')'}"></span> - </span> - </div> - </th:block> + <div + th:replace="fragments/row::text-row(label='Presence status', text=${model.germplasm.presenceStatus})" + ></div> + </div> + </div> - <div th:replace="fragments/row::text-row(label='Authority', text=${model.taxonAuthor})"></div> + <div class="f-card" th:if="${model.collecting}"> + <h2>Collector</h2> + <div class="f-card-body"> + <th:block + th:if="${model.germplasm.collectingSite != null && !#strings.isEmpty(model.germplasm.collectingSite.siteName)}" + > + <div + th:replace="fragments/row::row(label='Collecting site', content=~{::#collecting-site})" + > + <a + id="collecting-site" + th:href="@{/sites/{siteId}(siteId=${#faidare.toSiteParam(model.germplasm.collectingSite.siteId)})}" + th:text="${model.germplasm.collectingSite.siteName}" + ></a> + </div> + </th:block> - <th:block th:unless="${#lists.isEmpty(model.germplasm.taxonIds)}"> - <div th:replace="fragments/row::row(label='Taxon IDs', content=~{::#taxon-ids})"> - <div id="taxon-ids"> - <div th:each="taxonId : ${model.germplasm.taxonIds}" class="row"> - <div class="col-6 text-nowrap" th:text="${taxonId.sourceName}"></div> - <div class="col-6"> - <span class="taxon-id" - th:replace="fragments/link::link(label=${taxonId.taxonId}, url=${#faidare.taxonIdUrl(taxonId)})"></span> - </div> - </div> - </div> - </div> - </th:block> + <div + th:replace="fragments/row::text-row(label='Material type', text=${model.germplasm.collector.materialType})" + ></div> + <div + th:replace="fragments/row::text-row(label='Collectors', text=${model.germplasm.collector.collectors})" + ></div> + + <th:block + th:if="${!#strings.isEmpty(model.germplasm.acquisitionDate) && model.germplasm.collector.accessionCreationDate == null}" + > + <div + th:replace="fragments/row::text-row(label='Acquisition / Creation date', text=${model.germplasm.acquisitionDate})" + ></div> + </th:block> - <div th:replace="fragments/row::text-row(label='Comment', text=${model.germplasm.taxonComment})"></div> - <th:block th:unless="${#lists.isEmpty(model.germplasm.taxonCommonNames)}"> - <div th:replace="fragments/row::row(label='Taxon common names', content=~{::#taxon-common-names})"> - <div id="taxon-common-names" class="content-overflow" th:text="${#strings.listJoin(model.germplasm.taxonCommonNames, ', ')}"></div> - </div> - </th:block> - <th:block th:unless="${#lists.isEmpty(model.germplasm.taxonSynonyms)}"> - <div th:replace="fragments/row::row(label='Taxon synonyms', content=~{::#taxon-synonyms})"> - <div id="taxon-synonyms" class="content-overflow" th:text="${#strings.listJoin(model.germplasm.taxonSynonyms, ', ')}"></div> - </div> - </th:block> - </template> - <a role="button" - tabindex="0" - data-bs-toggle="popover" - th:data-bs-title="${model.taxon}" - data-bs-element="#taxon-popover" - data-bs-container="body" - data-bs-trigger="focus"> - <em th:text="${model.taxon}"></em> - <th:block th:unless="${#strings.isEmpty(model.taxonAuthor)}">(<span th:text="${model.taxonAuthor}"></span>)</th:block> - </a> - </div> + <th:block + th:if="${model.germplasm.collector.institute != null && !#strings.isEmpty(model.germplasm.collector.institute.instituteName)}" + > + <template id="collector-institute-popover"> + <div + th:replace="fragments/institute::institute(institute=${model.germplasm.collector.institute})" + ></div> + </template> + <div + th:replace="fragments/row::row(label='Institution', content=~{::#collecting-institution})" + > + <a + id="collecting-institution" + role="button" + tabindex="0" + data-bs-toggle="popover" + th:data-bs-title="${model.germplasm.collector.institute.instituteName}" + data-bs-element="#collector-institute-popover" + data-bs-container="body" + data-bs-trigger="focus" + th:text="${model.germplasm.collector.institute.instituteName}" + ></a> </div> </th:block> - <div th:replace="fragments/row::text-row(label='Biological status', text=${model.germplasm.biologicalStatusOfAccessionCode})"></div> - <div th:replace="fragments/row::text-row(label='Genetic nature', text=${model.germplasm.geneticNature})"></div> - <div th:replace="fragments/row::text-row(label='Seed source', text=${model.germplasm.seedSource})"></div> - <div th:replace="fragments/row::text-row(label='Pedigree', text=${model.germplasm.pedigree})"></div> - <div th:replace="fragments/row::text-row(label='Comments', text=${model.germplasm.comment})"></div> + <div + th:replace="fragments/row::text-row(label='Accession number', text=${model.germplasm.collector.accessionNumber})" + ></div> + </div> + </div> - <th:block th:if="${model.germplasm.originSite != null && !#strings.isEmpty(model.germplasm.originSite.siteName)}"> - <div th:replace="fragments/row::row(label='Origin site', content=~{::#origin-site})"> - <a id="origin-site" th:href="@{/sites/{siteId}(siteId=${#faidare.toSiteParam(model.germplasm.originSite.siteId)})}" th:text="${model.germplasm.originSite.siteName}"></a> + <div class="f-card" th:if="${model.breeding}"> + <h2>Breeder</h2> + <div class="f-card-body"> + <th:block + th:if="${model.germplasm.breeder.institute != null && !#strings.isEmpty(model.germplasm.breeder.institute.instituteName)}" + > + <template id="breeder-institute-popover"> + <div + th:replace="fragments/institute::institute(institute=${model.germplasm.breeder.institute})" + ></div> + </template> + <div + th:replace="fragments/row::row(label='Institute', content=~{::#breeding-institution})" + > + <a + id="breeding-institution" + role="button" + tabindex="0" + data-bs-toggle="popover" + th:data-bs-title="${model.germplasm.breeder.institute.instituteName}" + data-bs-element="#breeder-institute-popover" + data-bs-container="body" + data-bs-trigger="focus" + th:text="${model.germplasm.breeder.institute.instituteName}" + ></a> </div> </th:block> + + <div + th:replace="fragments/row::text-row(label='Accession creation year', text=${model.germplasm.breeder.accessionCreationDate})" + ></div> + <div + th:replace="fragments/row::text-row(label='Accession number', text=${model.germplasm.breeder.accessionNumber})" + ></div> + <div + th:replace="fragments/row::text-row(label='Catalog registration year', text=${model.germplasm.breeder.registrationYear})" + ></div> + <div + th:replace="fragments/row::text-row(label='Catalog deregistration year', text=${model.germplasm.breeder.deregistrationYear})" + ></div> </div> </div> - </div> - </div> - - <div class="f-card" th:if="${model.germplasm.holdingInstitute}"> - <h2>Depositary</h2> - <div class="f-card-body"> - <template id="holding-institute-popover"> - <div th:replace="fragments/institute::institute(institute=${model.germplasm.holdingInstitute})"></div> - </template> - <div th:replace="fragments/row::row(label='Institution', content=~{::#institution})"> - <a id="institution" - role="button" - tabindex="0" - data-bs-toggle="popover" - th:data-bs-title="${model.germplasm.holdingInstitute.instituteName}" - data-bs-element="#holding-institute-popover" - data-bs-container="body" - data-bs-trigger="focus" - th:text="${model.germplasm.holdingInstitute.instituteName}"></a> - </div> - <th:block th:if="${model.germplasm.holdingGenbank != null && !#strings.isEmpty(model.germplasm.holdingGenbank.instituteName) && !#strings.isEmpty(model.germplasm.holdingGenbank.webSite)}"> - <div th:replace="fragments/row::row(label='Stock center name', content=~{::#stock-center-name})"> - <a id="stock-center-name" - target="_blank" - th:href="${model.germplasm.holdingGenbank.webSite}" - th:text="${model.germplasm.holdingGenbank.instituteName}"></a> - </div> - </th:block> - - <div th:replace="fragments/row::text-row(label='Presence status', text=${model.germplasm.presenceStatus})"></div> - </div> - </div> - - <div class="f-card" th:if="${model.collecting}"> - <h2>Collector</h2> - <div class="f-card-body"> - <th:block th:if="${model.germplasm.collectingSite != null && !#strings.isEmpty(model.germplasm.collectingSite.siteName)}"> - <div th:replace="fragments/row::row(label='Collecting site', content=~{::#collecting-site})"> - <a id="collecting-site" - th:href="@{/sites/{siteId}(siteId=${#faidare.toSiteParam(model.germplasm.collectingSite.siteId)})}" - th:text="${model.germplasm.collectingSite.siteName}" - ></a> - </div> - </th:block> - - <div th:replace="fragments/row::text-row(label='Material type', text=${model.germplasm.collector.materialType})"></div> - <div th:replace="fragments/row::text-row(label='Collectors', text=${model.germplasm.collector.collectors})"></div> - - <th:block th:if="${!#strings.isEmpty(model.germplasm.acquisitionDate) && model.germplasm.collector.accessionCreationDate == null}"> - <div th:replace="fragments/row::text-row(label='Acquisition / Creation date', text=${model.germplasm.acquisitionDate})"></div> - </th:block> - - <th:block th:if="${model.germplasm.collector.institute != null && !#strings.isEmpty(model.germplasm.collector.institute.instituteName)}"> - <template id="collector-institute-popover"> - <div th:replace="fragments/institute::institute(institute=${model.germplasm.collector.institute})"></div> - </template> - <div th:replace="fragments/row::row(label='Institution', content=~{::#collecting-institution})"> - <a id="collecting-institution" - role="button" - tabindex="0" - data-bs-toggle="popover" - th:data-bs-title="${model.germplasm.collector.institute.instituteName}" - data-bs-element="#collector-institute-popover" - data-bs-container="body" - data-bs-trigger="focus" - th:text="${model.germplasm.collector.institute.instituteName}"></a> + <div class="f-card" th:unless="${#lists.isEmpty(model.germplasm.donors)}"> + <h2>Donors</h2> + <div class="f-card-body"> + <div class="scroll-table-container"> + <table + class=" + table table-sm table-striped table-sticky table-responsive-sm + " + > + <thead> + <tr> + <th scope="col">Institute name</th> + <th scope="col">Institute code</th> + <th scope="col">Donation date</th> + <th scope="col">Accession number</th> + <th scope="col">Accession PUI</th> + </tr> + </thead> + <tbody> + <tr th:each="row, donorIterStat : ${model.germplasm.donors}"> + <td> + <template + th:id="${'donor-institute-popover-' + donorIterStat.index}" + > + <div + th:replace="fragments/institute::institute(institute=${row.donorInstitute})" + ></div> + </template> + <a + role="button" + tabindex="0" + data-bs-toggle="popover" + th:data-bs-title="${row.donorInstitute.instituteName}" + th:data-bs-element="${'#donor-institute-popover-' + donorIterStat.index}" + data-bs-container="body" + data-bs-trigger="focus" + th:text="${row.donorInstitute.instituteName}" + ></a> + </td> + <td th:text="${row.donorInstituteCode}"></td> + <td th:text="${row.donationDate}"></td> + <td th:text="${row.donorAccessionNumber}"></td> + <td th:text="${row.donorGermplasmPUI}"></td> + </tr> + </tbody> + </table> + </div> </div> - </th:block> - - <div th:replace="fragments/row::text-row(label='Accession number', text=${model.germplasm.collector.accessionNumber})"></div> - </div> - </div> - - <div class="f-card" th:if="${model.breeding}"> - <h2>Breeder</h2> - <div class="f-card-body"> - <th:block th:if="${model.germplasm.breeder.institute != null && !#strings.isEmpty(model.germplasm.breeder.institute.instituteName)}"> - <template id="breeder-institute-popover"> - <div th:replace="fragments/institute::institute(institute=${model.germplasm.breeder.institute})"></div> - </template> - <div th:replace="fragments/row::row(label='Institute', content=~{::#breeding-institution})"> - <a id="breeding-institution" - role="button" - tabindex="0" - data-bs-toggle="popover" - th:data-bs-title="${model.germplasm.breeder.institute.instituteName}" - data-bs-element="#breeder-institute-popover" - data-bs-container="body" - data-bs-trigger="focus" - th:text="${model.germplasm.breeder.institute.instituteName}"></a> + </div> + + <div + class="f-card" + th:unless="${#lists.isEmpty(model.germplasm.distributors)}" + > + <h2>Distributors</h2> + <div class="f-card-body"> + <div class="scroll-table-container"> + <table + class=" + table table-sm table-striped table-sticky table-responsive-sm + " + > + <thead> + <tr> + <th scope="col">Institute</th> + <th scope="col">Accession number</th> + <th scope="col">Distribution status</th> + </tr> + </thead> + <tbody> + <tr + th:each="row, distributorIterStat : ${model.germplasm.distributors}" + > + <td> + <template + th:id="${'distributor-institute-popover-' + distributorIterStat.index}" + > + <div + th:replace="fragments/institute::institute(institute=${row.institute})" + ></div> + </template> + <a + role="button" + tabindex="0" + th:data-bs-title="${row.institute.instituteName}" + th:data-bs-element="${'#distributor-institute-popover-' + distributorIterStat.index}" + data-bs-container="body" + data-bs-trigger="focus" + th:text="${row.institute.instituteName}" + ></a> + </td> + <td th:text="${row.accessionNumber}"></td> + <td th:text="${row.distributionStatus}"></td> + </tr> + </tbody> + </table> + </div> </div> - </th:block> - - <div th:replace="fragments/row::text-row(label='Accession creation year', text=${model.germplasm.breeder.accessionCreationDate})"></div> - <div th:replace="fragments/row::text-row(label='Accession number', text=${model.germplasm.breeder.accessionNumber})"></div> - <div th:replace="fragments/row::text-row(label='Catalog registration year', text=${model.germplasm.breeder.registrationYear})"></div> - <div th:replace="fragments/row::text-row(label='Catalog deregistration year', text=${model.germplasm.breeder.deregistrationYear})"></div> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.germplasm.donors)}"> - <h2>Donors</h2> - <div class="f-card-body"> - <div class="scroll-table-container"> - <table class="table table-sm table-striped table-sticky table-responsive-sm"> - <thead> - <tr> - <th scope="col">Institute name</th> - <th scope="col">Institute code</th> - <th scope="col">Donation date</th> - <th scope="col">Accession number</th> - <th scope="col">Accession PUI</th> - </tr> - </thead> - <tbody> - <tr th:each="row, donorIterStat : ${model.germplasm.donors}"> - <td> - <template th:id="${'donor-institute-popover-' + donorIterStat.index}"> - <div th:replace="fragments/institute::institute(institute=${row.donorInstitute})"></div> - </template> - <a role="button" - tabindex="0" - data-bs-toggle="popover" - th:data-bs-title="${row.donorInstitute.instituteName}" - th:data-bs-element="${'#donor-institute-popover-' + donorIterStat.index}" - data-bs-container="body" - data-bs-trigger="focus" - th:text="${row.donorInstitute.instituteName}"></a> - </td> - <td th:text="${row.donorInstituteCode}"></td> - <td th:text="${row.donationDate}"></td> - <td th:text="${row.donorAccessionNumber}"></td> - <td th:text="${row.donorGermplasmPUI}"></td> - </tr> - </tbody> - </table> </div> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.germplasm.distributors)}"> - <h2>Distributors</h2> - <div class="f-card-body"> - <div class="scroll-table-container"> - <table class="table table-sm table-striped table-sticky table-responsive-sm"> - <thead> - <tr> - <th scope="col">Institute</th> - <th scope="col">Accession number</th> - <th scope="col">Distribution status</th> - </tr> - </thead> - <tbody> - <tr th:each="row, distributorIterStat : ${model.germplasm.distributors}"> - <td> - <template th:id="${'distributor-institute-popover-' + distributorIterStat.index}"> - <div th:replace="fragments/institute::institute(institute=${row.institute})"></div> - </template> - <a role="button" - tabindex="0" - th:data-bs-title="${row.institute.instituteName}" - th:data-bs-element="${'#distributor-institute-popover-' + distributorIterStat.index}" - data-bs-container="body" - data-bs-trigger="focus" - th:text="${row.institute.instituteName}"></a> - </td> - <td th:text="${row.accessionNumber}"></td> - <td th:text="${row.distributionStatus}"></td> - </tr> - </tbody> - </table> + + <div class="f-card" th:unless="${#lists.isEmpty(model.attributes)}"> + <h2>Evaluation Data</h2> + <div class="f-card-body"> + <th:block th:each="descriptor : ${model.attributes}"> + <div + th:replace="fragments/row::text-row(label=${descriptor.attributeName}, text=${descriptor.value})" + ></div> + </th:block> + </div> </div> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.attributes)}"> - <h2>Evaluation Data</h2> - <div class="f-card-body"> - <th:block th:each="descriptor : ${model.attributes}"> - <div th:replace="fragments/row::text-row(label=${descriptor.attributeName}, text=${descriptor.value})"></div> - </th:block> - </div> - </div> - - <div class="f-card" th:if="${model.genealogyPresent}"> - <h2>Genealogy</h2> - <div class="f-card-body"> - <th:block th:if="${model.pedigree != null}"> - <div th:replace="fragments/row::text-row(label='Crossing plan', text=${model.pedigree.crossingPlan})"></div> - <div th:replace="fragments/row::text-row(label='Crossing year', text=${model.pedigree.crossingYear})"></div> - <div th:replace="fragments/row::text-row(label='Family code', text=${model.pedigree.familyCode})"></div> - <th:block th:unless="${#strings.isEmpty(model.pedigree.parent1Name)}"> - <div th:replace="fragments/row::row(label='Parent accessions', content=~{::#parent-accessions})"> - <div id="parent-accessions"> - <th:block th:if="${model.pedigree.parent1DbId}"> - <div th:replace="fragments/row::row(label=${model.pedigree.parent1Type}, content=~{::#parent1-link})"> - <a id="parent1-link" th:href="@{/germplasms/{germplasmId}(germplasmId=${model.pedigree.parent1DbId})}" th:text="${model.pedigree.parent1Name}"></a> - </div> - </th:block> - <th:block th:if="${model.pedigree.parent2DbId}"> - <div th:replace="fragments/row::row(label=${model.pedigree.parent2Type}, content=~{::#parent2-link})"> - <a id="parent2-link" th:href="@{/germplasms/{germplasmId}(germplasmId=${model.pedigree.parent2DbId})}" th:text="${model.pedigree.parent2Name}"></a> - </div> - </th:block> - </div> - </div> - </th:block> - - <th:block th:unless="${#lists.isEmpty(model.pedigree.siblings)}"> - <div th:replace="fragments/row::row(label='Sibling accessions', content=~{::#sibling-accessions})"> - <div id="sibling-accessions" class="content-overflow"> - <a th:each="sibling : ${model.pedigree.siblings}" - th:href="@{/germplasms/{germplasmId}(germplasmId=${sibling.germplasmDbId})}" - th:text="${sibling.defaultDisplayName}"></a> - </div> - </div> - </th:block> - </th:block> - - <th:block th:unless="${#lists.isEmpty(model.germplasm.children)}"> - <div th:replace="fragments/row::row(label='Descendants', content=~{::#descendants})"> - <div id="descendants" class="content-overflow content-overflow-big"> - <th:block th:each="child : ${model.germplasm.children}"> - <div th:replace="fragments/row::row(label=${#strings.isEmpty(child.secondParentName) ? ('children of ' + child.firstParentName) : ('children of ' + child.firstParentName + ' x ' + child.secondParentName) }, content=~{::.descendant-child})"> - <div class="descendant-child"> - <th:block th:each="sibling, siblingIterStat : ${child.sibblings}"> - <a th:href="@{/germplasms(pui=${sibling.pui})}" - th:text="${sibling.name}"></a><th:block th:unless="${siblingIterStat.last}">, </th:block> + <div class="f-card" th:if="${model.genealogyPresent}"> + <h2>Genealogy</h2> + <div class="f-card-body"> + <th:block th:if="${model.pedigree != null}"> + <div + th:replace="fragments/row::text-row(label='Crossing plan', text=${model.pedigree.crossingPlan})" + ></div> + <div + th:replace="fragments/row::text-row(label='Crossing year', text=${model.pedigree.crossingYear})" + ></div> + <div + th:replace="fragments/row::text-row(label='Family code', text=${model.pedigree.familyCode})" + ></div> + <th:block + th:unless="${#strings.isEmpty(model.pedigree.parent1Name)}" + > + <div + th:replace="fragments/row::row(label='Parent accessions', content=~{::#parent-accessions})" + > + <div id="parent-accessions"> + <th:block th:if="${model.pedigree.parent1DbId}"> + <div + th:replace="fragments/row::row(label=${model.pedigree.parent1Type}, content=~{::#parent1-link})" + > + <a + id="parent1-link" + th:href="@{/germplasms/{germplasmId}(germplasmId=${model.pedigree.parent1DbId})}" + th:text="${model.pedigree.parent1Name}" + ></a> + </div> + </th:block> + + <th:block th:if="${model.pedigree.parent2DbId}"> + <div + th:replace="fragments/row::row(label=${model.pedigree.parent2Type}, content=~{::#parent2-link})" + > + <a + id="parent2-link" + th:href="@{/germplasms/{germplasmId}(germplasmId=${model.pedigree.parent2DbId})}" + th:text="${model.pedigree.parent2Name}" + ></a> + </div> </th:block> </div> </div> </th:block> - </div> - </div> - </th:block> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.germplasm.population)}"> - <h2>Population</h2> - <div class="f-card-body"> - <th:block th:each="population : ${model.germplasm.population}"> - - <th:block th:if="${population.germplasmRef != null}"> - <th:block th:unless="${#strings.isEmpty(population.germplasmRef.pui)}"> - <div th:replace="fragments/row::row(label=${#faidare.collPopTitle(population)}, content=~{::.population-1})"> - <div class="population-1"> - <a th:if="${population.germplasmRef.pui != model.germplasm.germplasmPUI}" - th:href="@{/germplasms(pui=${population.germplasmRef.pui})}" - th:text="${population.germplasmRef.name}"></a> - <span th:if="${population.germplasmRef.pui == model.germplasm.germplasmPUI}" - th:text="${population.germplasmRef.name}"></span> - is composed by <span th:text="${population.germplasmCount}"></span> accession(s) - <!-- TODO there was a link pointing at a search here --> + + <th:block th:unless="${#lists.isEmpty(model.pedigree.siblings)}"> + <div + th:replace="fragments/row::row(label='Sibling accessions', content=~{::#sibling-accessions})" + > + <div id="sibling-accessions" class="content-overflow"> + <a + th:each="sibling : ${model.pedigree.siblings}" + th:href="@{/germplasms/{germplasmId}(germplasmId=${sibling.germplasmDbId})}" + th:text="${sibling.defaultDisplayName}" + ></a> + </div> + </div> + </th:block> + </th:block> + + <th:block th:unless="${#lists.isEmpty(model.germplasm.children)}"> + <div + th:replace="fragments/row::row(label='Descendants', content=~{::#descendants})" + > + <div + id="descendants" + class="content-overflow content-overflow-big" + > + <th:block th:each="child : ${model.germplasm.children}"> + <div + th:replace="fragments/row::row(label=${#strings.isEmpty(child.secondParentName) ? ('children of ' + child.firstParentName) : ('children of ' + child.firstParentName + ' x ' + child.secondParentName) }, content=~{::.descendant-child})" + > + <div class="descendant-child"> + <th:block + th:each="sibling, siblingIterStat : ${child.sibblings}" + > + <a + th:href="@{/germplasms(pui=${sibling.pui})}" + th:text="${sibling.name}" + ></a + ><th:block th:unless="${siblingIterStat.last}" + >, + </th:block> + </th:block> + </div> + </div> + </th:block> </div> </div> </th:block> - </th:block> - - <th:block th:if="${population.germplasmRef == null}"> - <div th:replace="fragments/row::text-row(label=${#faidare.collPopTitle(population)}, text=${population.germplasmCount + ' accession(s)'})"></div> - <!-- TODO there was a link pointing at a search here --> - </th:block> - </th:block> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.germplasm.collection)}"> - <h2>Collection</h2> - <div class="f-card-body"> - <th:block th:each="collection : ${model.germplasm.collection}"> - <div th:replace="fragments/row::text-row(label=${#faidare.collPopTitle(collection)}, text=${collection.germplasmCount + ' accession(s)'})"></div> - <!-- TODO there was a link pointing at a search here --> - </th:block> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.germplasm.panel)}"> - <h2>Panel</h2> - <div class="f-card-body"> - <th:block th:each="panel : ${model.germplasm.panel}"> - <div th:replace="fragments/row::text-row(label=${#faidare.collPopTitleWithoutUnderscores(panel)}, text=${panel.germplasmCount + ' accession(s)'})"></div> - <!-- TODO there was a link pointing at a search here --> - </th:block> - </div> - </div> - - <div th:replace="fragments/xrefs::xrefs(crossReferences=${model.crossReferences})"></div> -</main> - -<script th:inline="javascript"> - faidare.initializePopovers(); - faidare.initializeMap({ - contextPath: [[${#request.getContextPath()}]], - locations: /* TODO [[${model.mapLocations}]]*/ [] - }); -</script> -</body> + </div> + </div> + + <div + class="f-card" + th:unless="${#lists.isEmpty(model.germplasm.population)}" + > + <h2>Population</h2> + <div class="f-card-body"> + <th:block th:each="population : ${model.germplasm.population}"> + <th:block th:if="${population.germplasmRef != null}"> + <th:block + th:unless="${#strings.isEmpty(population.germplasmRef.pui)}" + > + <div + th:replace="fragments/row::row(label=${#faidare.collPopTitle(population)}, content=~{::.population-1})" + > + <div class="population-1"> + <a + th:if="${population.germplasmRef.pui != model.germplasm.germplasmPUI}" + th:href="@{/germplasms(pui=${population.germplasmRef.pui})}" + th:text="${population.germplasmRef.name}" + ></a> + <span + th:if="${population.germplasmRef.pui == model.germplasm.germplasmPUI}" + th:text="${population.germplasmRef.name}" + ></span> + is composed by + <span th:text="${population.germplasmCount}"></span> + accession(s) + <!-- TODO there was a link pointing at a search here --> + </div> + </div> + </th:block> + </th:block> + + <th:block th:if="${population.germplasmRef == null}"> + <div + th:replace="fragments/row::text-row(label=${#faidare.collPopTitle(population)}, text=${population.germplasmCount + ' accession(s)'})" + ></div> + <!-- TODO there was a link pointing at a search here --> + </th:block> + </th:block> + </div> + </div> + + <div + class="f-card" + th:unless="${#lists.isEmpty(model.germplasm.collection)}" + > + <h2>Collection</h2> + <div class="f-card-body"> + <th:block th:each="collection : ${model.germplasm.collection}"> + <div + th:replace="fragments/row::text-row(label=${#faidare.collPopTitle(collection)}, text=${collection.germplasmCount + ' accession(s)'})" + ></div> + <!-- TODO there was a link pointing at a search here --> + </th:block> + </div> + </div> + + <div class="f-card" th:unless="${#lists.isEmpty(model.germplasm.panel)}"> + <h2>Panel</h2> + <div class="f-card-body"> + <th:block th:each="panel : ${model.germplasm.panel}"> + <div + th:replace="fragments/row::text-row(label=${#faidare.collPopTitleWithoutUnderscores(panel)}, text=${panel.germplasmCount + ' accession(s)'})" + ></div> + <!-- TODO there was a link pointing at a search here --> + </th:block> + </div> + </div> + + <div + th:replace="fragments/xrefs::xrefs(crossReferences=${model.crossReferences})" + ></div> + </main> + + <script th:inline="javascript"> + faidare.initializePopovers(); + faidare.initializeMap({ + contextPath: [[${#request.getContextPath()}]], + locations: /* TODO [[${model.mapLocations}]]*/ [] + }); + </script> + </body> </html> diff --git a/backend/src/main/resources/templates/index.html b/backend/src/main/resources/templates/index.html index 49f401b0284647d171f51ec5643987e27d836a05..2130bf93d95928eeb7711d61e3cecfbe42a37742 100644 --- a/backend/src/main/resources/templates/index.html +++ b/backend/src/main/resources/templates/index.html @@ -2,16 +2,16 @@ <html xmlns:th="http://www.thymeleaf.org" - th:replace="~{layout/main :: layout(title=~{::title}, content=~{::main})}" + th:replace="~{layout/main :: layout(title=~{::title}, content=~{::main}, script=~{})}" > -<head> - <title>Faidare</title> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -</head> + <head> + <title>Faidare</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + </head> -<body> -<main> - <h1>Welcome to Faidare</h1> -</main> -</body> + <body> + <main> + <h1>Welcome to Faidare</h1> + </main> + </body> </html> diff --git a/backend/src/main/resources/templates/layout/main.html b/backend/src/main/resources/templates/layout/main.html index 22c36a5efb6dfe860a20b41f520ba826d160e96b..8097134fddb189f6c038ba29aceb29f14eee972f 100644 --- a/backend/src/main/resources/templates/layout/main.html +++ b/backend/src/main/resources/templates/layout/main.html @@ -1,21 +1,29 @@ <!DOCTYPE html> -<html lang="fr" th:fragment="layout (title, content, script)" xmlns:th="http://www.thymeleaf.org"> +<html + lang="fr" + th:fragment="layout (title, content, script)" + xmlns:th="http://www.thymeleaf.org" +> <head> <title th:replace="${title}">Layout Title</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta content="width=device-width, initial-scale=1" name="viewport" /> - <link th:href="@{/assets/style.css}" rel="stylesheet"> + <link th:href="@{/assets/style.css}" rel="stylesheet" /> - <link rel="shortcut icon" th:href="@{/static/assets/images/favicon.ico}" type="image/x-icon" /> + <link + rel="shortcut icon" + th:href="@{/static/assets/images/favicon.ico}" + type="image/x-icon" + /> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container"> <span class="navbar-brand py-0"> - <img th:src="@{/assets/images/logo.png}" style="height: 40px"/> + <img th:src="@{/assets/images/logo.png}" style="height: 40px" /> </span> </div> </nav> diff --git a/backend/src/main/resources/templates/site.html b/backend/src/main/resources/templates/site.html index c3bd01b26333e960cd766f7ad3418605e2057483..b923a2e7346b9a9718b0c8bea31781c23b0bc78e 100644 --- a/backend/src/main/resources/templates/site.html +++ b/backend/src/main/resources/templates/site.html @@ -4,75 +4,130 @@ xmlns:th="http://www.thymeleaf.org" th:replace="~{layout/main :: layout(title=~{::title}, content=~{::main}, script=~{::script})}" > -<head> - <title>Site <th:block th:text="${model.site.locationName}" /></title> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -</head> + <head> + <title>Site <th:block th:text="${model.site.locationName}" /></title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + </head> -<body> -<main> - <h1>Site <th:block th:text="${model.site.locationName}" /></h1> + <body> + <main> + <h1>Site <th:block th:text="${model.site.locationName}" /></h1> - <div th:replace="fragments/map::map"></div> + <div th:replace="fragments/map::map"></div> - <div class="f-card mt-4"> - <h2>Details</h2> - <div class="f-card-body"> - <th:block th:if="${model.site.uri != null && !model.site.uri.startsWith('urn:')}"> - <div th:replace="fragments/row::text-row(label='Permanent unique identifier', text=${model.site.uri})"></div> - </th:block> + <div class="f-card mt-4"> + <h2>Details</h2> + <div class="f-card-body"> + <th:block + th:if="${model.site.uri != null && !model.site.uri.startsWith('urn:')}" + > + <div + th:replace="fragments/row::text-row(label='Permanent unique identifier', text=${model.site.uri})" + ></div> + </th:block> - <div th:replace="fragments/source::source(source=${model.source}, url=${model.site.url}, entityType='site')"></div> + <div + th:replace="fragments/source::source(source=${model.source}, url=${model.site.url}, entityType='site')" + ></div> - <div th:replace="fragments/row::text-row(label='Abbreviation', text=${model.site.abbreviation})"></div> - <div th:replace="fragments/row::text-row(label='Type', text=${model.site.locationType})"></div> - <div th:replace="fragments/row::text-row(label='Status', text=${model.siteStatus})"></div> - <div th:replace="fragments/row::text-row(label='Institution/Landowner', text=${model.site.instituteName})"></div> - <div th:replace="fragments/row::text-row(label='Institution address', text=${model.site.instituteAddress})"></div> - <div th:replace="fragments/row::text-row(label='Coordinates precision', text=${model.coordinatesPrecision})"></div> - <th:block th:if="${model.site.latitude}"> - <div th:replace="fragments/row::text-row(label='Latitude', text=${#coordinates.formatLatitude(model.site.latitude)})"></div> - </th:block> - <th:block th:if="${model.site.longitude}"> - <div th:replace="fragments/row::text-row(label='Longitude', text=${#coordinates.formatLongitude(model.site.longitude)})"></div> - </th:block> - <div th:replace="fragments/row::text-row(label='Geographical location', text=${model.geographicalLocation})"></div> - <th:block th:if="${model.site.countryName != null && model.geographicalLocation == null}"> - <div th:replace="fragments/row::text-row(label='Country name', text=${model.site.countryName})"></div> - </th:block> + <div + th:replace="fragments/row::text-row(label='Abbreviation', text=${model.site.abbreviation})" + ></div> + <div + th:replace="fragments/row::text-row(label='Type', text=${model.site.locationType})" + ></div> + <div + th:replace="fragments/row::text-row(label='Status', text=${model.siteStatus})" + ></div> + <div + th:replace="fragments/row::text-row(label='Institution/Landowner', text=${model.site.instituteName})" + ></div> + <div + th:replace="fragments/row::text-row(label='Institution address', text=${model.site.instituteAddress})" + ></div> + <div + th:replace="fragments/row::text-row(label='Coordinates precision', text=${model.coordinatesPrecision})" + ></div> + <th:block th:if="${model.site.latitude}"> + <div + th:replace="fragments/row::text-row(label='Latitude', text=${#coordinates.formatLatitude(model.site.latitude)})" + ></div> + </th:block> + <th:block th:if="${model.site.longitude}"> + <div + th:replace="fragments/row::text-row(label='Longitude', text=${#coordinates.formatLongitude(model.site.longitude)})" + ></div> + </th:block> + <div + th:replace="fragments/row::text-row(label='Geographical location', text=${model.geographicalLocation})" + ></div> + <th:block + th:if="${model.site.countryName != null && model.geographicalLocation == null}" + > + <div + th:replace="fragments/row::text-row(label='Country name', text=${model.site.countryName})" + ></div> + </th:block> - <th:block th:if="${model.site.countryCode != null && model.geographicalLocation == null}"> - <div th:replace="fragments/row::text-row(label='Country code', text=${model.site.countryName})"></div> - </th:block> + <th:block + th:if="${model.site.countryCode != null && model.geographicalLocation == null}" + > + <div + th:replace="fragments/row::text-row(label='Country code', text=${model.site.countryName})" + ></div> + </th:block> - <div th:replace="fragments/row::text-row(label='Altitude', text=${model.site.altitude})"></div> - <div th:replace="fragments/row::text-row(label='Slope', text=${model.slope})"></div> - <div th:replace="fragments/row::text-row(label='Exposure', text=${model.exposure})"></div> - <div th:replace="fragments/row::text-row(label='Topography', text=${model.topography})"></div> - <div th:replace="fragments/row::text-row(label='Environment type', text=${model.environmentType})"></div> - <div th:replace="fragments/row::text-row(label='Distance to city', text=${model.distanceToCity})"></div> - <div th:replace="fragments/row::text-row(label='Direction from city', text=${model.directionFromCity})"></div> - <div th:replace="fragments/row::text-row(label='Comment', text=${model.comment})"></div> - </div> - </div> + <div + th:replace="fragments/row::text-row(label='Altitude', text=${model.site.altitude})" + ></div> + <div + th:replace="fragments/row::text-row(label='Slope', text=${model.slope})" + ></div> + <div + th:replace="fragments/row::text-row(label='Exposure', text=${model.exposure})" + ></div> + <div + th:replace="fragments/row::text-row(label='Topography', text=${model.topography})" + ></div> + <div + th:replace="fragments/row::text-row(label='Environment type', text=${model.environmentType})" + ></div> + <div + th:replace="fragments/row::text-row(label='Distance to city', text=${model.distanceToCity})" + ></div> + <div + th:replace="fragments/row::text-row(label='Direction from city', text=${model.directionFromCity})" + ></div> + <div + th:replace="fragments/row::text-row(label='Comment', text=${model.comment})" + ></div> + </div> + </div> - <div class="f-card" th:unless="${#lists.isEmpty(model.additionalInfoProperties)}"> - <h2>Additional info</h2> - <div class="f-card-body"> - <th:block th:each="prop : ${model.additionalInfoProperties}"> - <div th:replace="fragments/row::text-row(label=${prop.key}, text=${prop.value})"></div> - </th:block> - </div> - </div> + <div + class="f-card" + th:unless="${#lists.isEmpty(model.additionalInfoProperties)}" + > + <h2>Additional info</h2> + <div class="f-card-body"> + <th:block th:each="prop : ${model.additionalInfoProperties}"> + <div + th:replace="fragments/row::text-row(label=${prop.key}, text=${prop.value})" + ></div> + </th:block> + </div> + </div> - <div th:replace="fragments/xrefs::xrefs(crossReferences=${model.crossReferences})"></div> -</main> + <div + th:replace="fragments/xrefs::xrefs(crossReferences=${model.crossReferences})" + ></div> + </main> -<script th:inline="javascript"> - faidare.initializeMap({ - contextPath: [[${#request.getContextPath()}]], - locations: /* TODO [[${model.mapLocations}]]*/ [] - }); -</script> -</body> + <script th:inline="javascript"> + faidare.initializeMap({ + contextPath: [[${#request.getContextPath()}]], + locations: /* TODO [[${model.mapLocations}]]*/ [] + }); + </script> + </body> </html> diff --git a/backend/src/main/resources/templates/study.html b/backend/src/main/resources/templates/study.html index 225b6cc8a6911b3c934157e91aab55b462e7a756..415e21edb6f466863d1de5c7efaba35d458e8855 100644 --- a/backend/src/main/resources/templates/study.html +++ b/backend/src/main/resources/templates/study.html @@ -4,200 +4,294 @@ xmlns:th="http://www.thymeleaf.org" th:replace="~{layout/main :: layout(title=~{::title}, content=~{::main}, script=~{::script})}" > -<head> - <title>Study <th:block th:text="${model.study.studyType}" />: <th:block th:text="${model.study.studyName}" /></title> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -</head> - -<body> -<main> - <h1>Study <th:block th:text="${model.study.studyType}" />: <th:block th:text="${model.study.studyName}" /></h1> - - <div th:replace="fragments/map::map"></div> - - <div class="f-card mt-4"> - <h2>Identification</h2> - <div class="f-card-body"> - <div th:replace="fragments/row::text-row(label='Name', text=${model.study.studyName})"></div> - <div th:replace="fragments/row::text-row(label='Identifier', text=${model.study.studyDbId})"></div> - - <div th:replace="fragments/source::source(source=${model.source}, url=${model.study.url}, entityType='study')"></div> - - <div th:replace="fragments/row::text-row(label='Project name', text=${model.study.programName})"></div> - <div th:replace="fragments/row::text-row(label='Description', text=${model.study.studyDescription})"></div> - <th:block th:if="${model.study.active != null}"> - <div th:replace="fragments/row::text-row(label='Active', text=${model.study.active ? 'Yes' : 'No'})"></div> - </th:block> - - <th:block th:unless="${#lists.isEmpty(model.study.seasons)}"> - <div th:replace="fragments/row::text-row(label='Seasons', text=${#strings.listJoin(model.study.seasons, ',')})"></div> - </th:block> - <th:block th:if="${model.study.startDate != null && model.study.endDate != null}"> - <div th:replace="fragments/row::text-row(label='Date', text=${'From ' + #dates.format(model.study.startDate, 'yyyy-MM-dd') + ' to ' + #dates.format(model.study.endDate, 'yyyy-MM-dd') })"></div> - </th:block> - <th:block th:if="${model.study.startDate != null && model.study.endDate == null}"> - <div th:replace="fragments/row::text-row(label='Date', text=${'Started on ' + #dates.format(model.study.startDate, 'yyyy-MM-dd')})"></div> - </th:block> - - <th:block th:if="${model.study.locationDbId}"> - <div th:replace="fragments/row::row(label='Location name', content=~{::#location})"> - <a id="location" th:href="@{/sites/{siteId}(siteId=${model.study.locationDbId})}" th:text="${model.study.locationName}"></a> + <head> + <title> + Study <th:block th:text="${model.study.studyType}" />: <th:block + th:text="${model.study.studyName}" /> + </title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + </head> + + <body> + <main> + <h1> + Study <th:block th:text="${model.study.studyType}" />: + <th:block th:text="${model.study.studyName}" /> + </h1> + + <div th:replace="fragments/map::map"></div> + + <div class="f-card mt-4"> + <h2>Identification</h2> + <div class="f-card-body"> + <div + th:replace="fragments/row::text-row(label='Name', text=${model.study.studyName})" + ></div> + <div + th:replace="fragments/row::text-row(label='Identifier', text=${model.study.studyDbId})" + ></div> + + <div + th:replace="fragments/source::source(source=${model.source}, url=${model.study.url}, entityType='study')" + ></div> + + <div + th:replace="fragments/row::text-row(label='Project name', text=${model.study.programName})" + ></div> + <div + th:replace="fragments/row::text-row(label='Description', text=${model.study.studyDescription})" + ></div> + <th:block th:if="${model.study.active != null}"> + <div + th:replace="fragments/row::text-row(label='Active', text=${model.study.active ? 'Yes' : 'No'})" + ></div> + </th:block> + + <th:block th:unless="${#lists.isEmpty(model.study.seasons)}"> + <div + th:replace="fragments/row::text-row(label='Seasons', text=${#strings.listJoin(model.study.seasons, ',')})" + ></div> + </th:block> + <th:block + th:if="${model.study.startDate != null && model.study.endDate != null}" + > + <div + th:replace="fragments/row::text-row(label='Date', text=${'From ' + #dates.format(model.study.startDate, 'yyyy-MM-dd') + ' to ' + #dates.format(model.study.endDate, 'yyyy-MM-dd') })" + ></div> + </th:block> + <th:block + th:if="${model.study.startDate != null && model.study.endDate == null}" + > + <div + th:replace="fragments/row::text-row(label='Date', text=${'Started on ' + #dates.format(model.study.startDate, 'yyyy-MM-dd')})" + ></div> + </th:block> + + <th:block th:if="${model.study.locationDbId}"> + <div + th:replace="fragments/row::row(label='Location name', content=~{::#location})" + > + <a + id="location" + th:href="@{/sites/{siteId}(siteId=${model.study.locationDbId})}" + th:text="${model.study.locationName}" + ></a> + </div> + </th:block> + + <th:block th:unless="${#lists.isEmpty(model.study.dataLinks)}"> + <div + th:replace="fragments/row::row(label='Data files', content=~{::#data-files})" + > + <ul id="data-files" class="list-unstyled"> + <li th:each="dataLink : ${model.study.dataLinks}"> + <a + target="_blank" + th:href="${dataLink.url}" + th:text="${dataLink.name}" + ></a> + </li> + </ul> + </div> + </th:block> </div> - </th:block> - - <th:block th:unless="${#lists.isEmpty(model.study.dataLinks)}"> - <div th:replace="fragments/row::row(label='Data files', content=~{::#data-files})"> - <ul id="data-files" class="list-unstyled"> - <li th:each="dataLink : ${model.study.dataLinks}"> - <a target="_blank" th:href="${dataLink.url}" th:text="${dataLink.name}"></a> - </li> - </ul> + </div> + + <div class="f-card" th:unles="${#lists.isEmpty(model.germplasms)}"> + <h2>Genotype</h2> + <div class="f-card-body"> + <div class="scroll-table-container scroll-table-container-big"> + <table + class=" + table table-sm table-striped table-sticky table-responsive-sm + " + > + <thead> + <tr> + <th scope="col">Accession number</th> + <th scope="col">Name</th> + <th scope="col">Taxon</th> + </tr> + </thead> + <tbody> + <tr th:each="row : ${model.germplasms}"> + <td> + <a + th:href="@{/germplasms/{germplasmId}(germplasmId=${row.germplasmDbId})}" + th:text="${row.accessionNumber}" + ></a> + </td> + <td th:text="${row.germplasmName}"></td> + <td + th:text="${(row.genus == null ? '' : row.genus) + ' ' + (row.species == null ? '' : row.species)+ ' ' + (row.subtaxa == null ? '' : row.subtaxa) }" + ></td> + </tr> + </tbody> + </table> + </div> </div> - </th:block> - </div> - </div> - - <div class="f-card" th:unles="${#lists.isEmpty(model.germplasms)}"> - <h2>Genotype</h2> - <div class="f-card-body"> - <div class="scroll-table-container scroll-table-container-big"> - <table class="table table-sm table-striped table-sticky table-responsive-sm"> - <thead> - <tr> - <th scope="col">Accession number</th> - <th scope="col">Name</th> - <th scope="col">Taxon</th> - </tr> - </thead> - <tbody> - <tr th:each="row : ${model.germplasms}"> - <td> - <a th:href="@{/germplasms/{germplasmId}(germplasmId=${row.germplasmDbId})}" th:text="${row.accessionNumber}"></a> - </td> - <td th:text="${row.germplasmName}"></td> - <td th:text="${(row.genus == null ? '' : row.genus) + ' ' + (row.species == null ? '' : row.species)+ ' ' + (row.subtaxa == null ? '' : row.subtaxa) }"></td> - </tr> - </tbody> - </table> </div> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.variables)}"> - <h2>Variables</h2> - <div class="f-card-body"> - <div class="scroll-table-container"> - <table class="table table-sm table-striped table-sticky table-responsive-sm"> - <thead> - <tr> - <th scope="col">Variable ID</th> - <th scope="col">Variable short name</th> - <th scope="col">Variable long name</th> - <th scope="col">Ontology name</th> - <th scope="col">Trait description</th> - </tr> - </thead> - <tbody> - <tr th:each="row : ${model.variables}"> - <td> - <a th:unless="${#strings.isEmpty(row.documentationURL)}" th:href="${row.documentationURL}" th:text="${row.observationVariableDbId}" target="_blank" ></a> - <span th:if="${#strings.isEmpty(row.documentationURL)}" th:text="${row.observationVariableDbId}"></span> - </td> - <td th:text="${row.name}"></td> - <td th:text="${#lists.isEmpty(row.synonyms) ? '' : row.synonyms[0]}"></td> - <td th:text="${row.ontologyName}"></td> - <td th:text="${row.trait.description}"></td> - </tr> - </tbody> - </table> + + <div class="f-card" th:unless="${#lists.isEmpty(model.variables)}"> + <h2>Variables</h2> + <div class="f-card-body"> + <div class="scroll-table-container"> + <table + class=" + table table-sm table-striped table-sticky table-responsive-sm + " + > + <thead> + <tr> + <th scope="col">Variable ID</th> + <th scope="col">Variable short name</th> + <th scope="col">Variable long name</th> + <th scope="col">Ontology name</th> + <th scope="col">Trait description</th> + </tr> + </thead> + <tbody> + <tr th:each="row : ${model.variables}"> + <td> + <a + th:unless="${#strings.isEmpty(row.documentationURL)}" + th:href="${row.documentationURL}" + th:text="${row.observationVariableDbId}" + target="_blank" + ></a> + <span + th:if="${#strings.isEmpty(row.documentationURL)}" + th:text="${row.observationVariableDbId}" + ></span> + </td> + <td th:text="${row.name}"></td> + <td + th:text="${#lists.isEmpty(row.synonyms) ? '' : row.synonyms[0]}" + ></td> + <td th:text="${row.ontologyName}"></td> + <td th:text="${row.trait.description}"></td> + </tr> + </tbody> + </table> + </div> + </div> </div> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.trials)}"> - <h2>Data Set</h2> - <div class="f-card-body"> - <div class="scroll-table-container scroll-table-container-big"> - <table class="table table-sm table-striped table-sticky table-responsive-sm"> - <thead> - <tr> - <th scope="col">Name</th> - <th scope="col">Type</th> - <th scope="col">Linked studies identifier</th> - </tr> - </thead> - <tbody> - <tr th:each="row : ${model.trials}"> - <td> - <a th:unless="${#strings.isEmpty(row.documentationURL)}" th:href="${row.documentationURL}" th:text="${row.trialName}" target="_blank" ></a> - <span th:if="${#strings.isEmpty(row.documentationURL)}" th:text="${row.trialName}"></span> - </td> - <td th:text="${row.trialType}"></td> - <td style="width: 60%"> - <th:block th:each="trialStudy, iterStat : ${row.studies}" - th:if="${trialStudy.studyDbId != model.study.studyDbId}"> - <a th:href="@{/studies/{studyId}(studyId=${trialStudy.studyDbId})}" - th:text="${trialStudy.studyName.trim()}"> - </a><th:block th:if="${iterStat.last}">; </th:block> - </th:block> - </td> - </tr> - </tbody> - </table> + + <div class="f-card" th:unless="${#lists.isEmpty(model.trials)}"> + <h2>Data Set</h2> + <div class="f-card-body"> + <div class="scroll-table-container scroll-table-container-big"> + <table + class=" + table table-sm table-striped table-sticky table-responsive-sm + " + > + <thead> + <tr> + <th scope="col">Name</th> + <th scope="col">Type</th> + <th scope="col">Linked studies identifier</th> + </tr> + </thead> + <tbody> + <tr th:each="row : ${model.trials}"> + <td> + <a + th:unless="${#strings.isEmpty(row.documentationURL)}" + th:href="${row.documentationURL}" + th:text="${row.trialName}" + target="_blank" + ></a> + <span + th:if="${#strings.isEmpty(row.documentationURL)}" + th:text="${row.trialName}" + ></span> + </td> + <td th:text="${row.trialType}"></td> + <td style="width: 60%"> + <th:block + th:each="trialStudy, iterStat : ${row.studies}" + th:if="${trialStudy.studyDbId != model.study.studyDbId}" + > + <a + th:href="@{/studies/{studyId}(studyId=${trialStudy.studyDbId})}" + th:text="${trialStudy.studyName.trim()}" + > + </a + ><th:block th:if="${iterStat.last}">; </th:block> + </th:block> + </td> + </tr> + </tbody> + </table> + </div> + </div> </div> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.study.contacts)}"> - <h2>Contact</h2> - <div class="f-card-body"> - <div class="scroll-table-container"> - <table class="table table-sm table-striped table-sticky table-responsive-sm"> - <thead> - <tr> - <th scope="col">Role</th> - <th scope="col">Name</th> - <th scope="col">Email</th> - <th scope="col">Institution</th> - </tr> - </thead> - <tbody> - <tr th:each="row : ${model.study.contacts}"> - <td th:text="${row.type}"></td> - <td th:text="${row.name}"></td> - <td th:text="${row.email}"></td> - <td th:text="${row.institutionName}"></td> - </tr> - </tbody> - </table> + + <div class="f-card" th:unless="${#lists.isEmpty(model.study.contacts)}"> + <h2>Contact</h2> + <div class="f-card-body"> + <div class="scroll-table-container"> + <table + class=" + table table-sm table-striped table-sticky table-responsive-sm + " + > + <thead> + <tr> + <th scope="col">Role</th> + <th scope="col">Name</th> + <th scope="col">Email</th> + <th scope="col">Institution</th> + </tr> + </thead> + <tbody> + <tr th:each="row : ${model.study.contacts}"> + <td th:text="${row.type}"></td> + <td th:text="${row.name}"></td> + <td th:text="${row.email}"></td> + <td th:text="${row.institutionName}"></td> + </tr> + </tbody> + </table> + </div> + </div> </div> - </div> - </div> - - <div class="f-card" th:unless="${#lists.isEmpty(model.additionalInfoProperties)}"> - <h2>Additional information</h2> - <div class="f-card-body"> - <div class="scroll-table-container"> - <table class="table table-sm"> - <tbody> - <tr th:each="row : ${model.additionalInfoProperties}"> - <th class="label" style="width: 33.33%" th:text="${row.key}" scope="row"></th> - <td th:text="${row.value}"></td> - </tr> - </tbody> - </table> + + <div + class="f-card" + th:unless="${#lists.isEmpty(model.additionalInfoProperties)}" + > + <h2>Additional information</h2> + <div class="f-card-body"> + <div class="scroll-table-container"> + <table class="table table-sm"> + <tbody> + <tr th:each="row : ${model.additionalInfoProperties}"> + <th + class="label" + style="width: 33.33%" + th:text="${row.key}" + scope="row" + ></th> + <td th:text="${row.value}"></td> + </tr> + </tbody> + </table> + </div> + </div> </div> - </div> - </div> - - <div th:replace="fragments/xrefs::xrefs(crossReferences=${model.crossReferences})"></div> -</main> - -<script th:inline="javascript"> - faidare.initializeMap({ - contextPath: [[${#request.getContextPath()}]], - locations: /* TODO [[${model.mapLocations}]]*/ [] - }); -</script> -</body> + + <div + th:replace="fragments/xrefs::xrefs(crossReferences=${model.crossReferences})" + ></div> + </main> + + <script th:inline="javascript"> + faidare.initializeMap({ + contextPath: [[${#request.getContextPath()}]], + locations: /* TODO [[${model.mapLocations}]]*/ [] + }); + </script> + </body> </html> diff --git a/web/package.json b/web/package.json index 175c1ad3659043ec411679d5763c2827dfd2d158..3efc776bb3c7da2b2537fa6e7aaaebd295d57509 100644 --- a/web/package.json +++ b/web/package.json @@ -8,7 +8,9 @@ "build:prod": "webpack --mode production", "watch": "webpack --mode development --watch", "watch:prod": "webpack --mode production --watch", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "format": "prettier --write \"src/**/*.+(ts|js|json|html|scss)\"", + "format:backend-templates": "prettier --write ../backend/src/main/resources/templates" }, "author": "", "license": "MIT", @@ -20,16 +22,29 @@ "devDependencies": { "@types/bootstrap": "5.1.2", "@types/leaflet.markercluster": "1.4.5", + "autoprefixer": "10.3.3", "clean-webpack-plugin": "4.0.0", "css-loader": "6.2.0", "css-minimizer-webpack-plugin": "3.0.2", "leaflet.markercluster": "1.5.1", "mini-css-extract-plugin": "2.2.2", + "postcss": "8.3.6", + "postcss-loader": "6.1.1", + "prettier": "2.3.2", "sass": "1.39.0", "sass-loader": "12.1.0", "ts-loader": "9.2.5", "typescript": "4.4.2", "webpack": "5.51.1", "webpack-cli": "4.8.0" + }, + "browserslist": [ + "defaults" + ], + "prettier": { + "printWidth": 140, + "singleQuote": true, + "arrowParens": "avoid", + "trailingComma": "none" } } diff --git a/web/src/bootstrap/popovers.ts b/web/src/bootstrap/popovers.ts index 83b703de77469e545f992f1ad2396706db3621bd..b19e5e75dedc37274b3a863bd1f18e448af43bdf 100644 --- a/web/src/bootstrap/popovers.ts +++ b/web/src/bootstrap/popovers.ts @@ -1,7 +1,7 @@ import { Popover } from 'bootstrap'; export function initializePopovers() { - const popoverTriggerList: Array<HTMLElement> = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')) + const popoverTriggerList: Array<HTMLElement> = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); popoverTriggerList.forEach(popoverTriggerEl => { const options: Partial<Popover.Options> = {}; const contentSelector = popoverTriggerEl.dataset.bsElement; diff --git a/web/src/index.ts b/web/src/index.ts index c906b28eb7d5cb4d78785bd6ea5bd01ff4b92fd2..060cf75d6a996a175e6d4b29c8fa1b067cf0610e 100644 --- a/web/src/index.ts +++ b/web/src/index.ts @@ -4,4 +4,4 @@ import { initializeMap } from './map/map'; (window as any).faidare = { initializePopovers, initializeMap -} +}; diff --git a/web/src/map/map.ts b/web/src/map/map.ts index 9fc2a96004bca2ea53eaf342371595c7ea3f5b29..a61002a445bc900a1aa60a97125df9a67c98c676 100644 --- a/web/src/map/map.ts +++ b/web/src/map/map.ts @@ -36,11 +36,12 @@ export function initializeMap(options: MapOptions) { } const mapContainerElement = document.querySelector('#map-container'); - mapContainerElement!.classList.remove("d-none"); + mapContainerElement!.classList.remove('d-none'); const mapElement = document.querySelector('#map') as HTMLElement; const map = L.map(mapElement); L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', { - attribution: 'Tiles © Esri — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, ' + + attribution: + 'Tiles © Esri — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, ' + 'Esri China (Hong Kong), Esri (Thailand), TomTom, 2012' }).addTo(map); @@ -52,7 +53,7 @@ export function initializeMap(options: MapOptions) { for (const location of options.locations) { const icon = L.icon({ iconUrl: markerIconUrl(options.contextPath, location), - iconAnchor: [12, 41], // point of the icon which will correspond to marker's location + iconAnchor: [12, 41] // point of the icon which will correspond to marker's location }); const popupElement = document.createElement('div'); @@ -74,10 +75,7 @@ export function initializeMap(options: MapOptions) { linkElement.href = `${options.contextPath}/sites/${location.locationDbId}`; popupElement.appendChild(linkElement); - const marker = L.marker( - [location.latitude, location.longitude], - { icon: icon } - ); + const marker = L.marker([location.latitude, location.longitude], { icon: icon }); markers.addLayer(marker.bindPopup(popupElement)); mapMarkers.push(marker); } diff --git a/web/src/style/style.scss b/web/src/style/style.scss index 42aa2bb3a883a5991772d6b9cbe4eddb0eb7d267..806a0d4bec25eee291369e323e27f2c77d0d3fe1 100644 --- a/web/src/style/style.scss +++ b/web/src/style/style.scss @@ -12,7 +12,7 @@ $table-group-separator-color: $table-border-color; @import '~leaflet.markercluster/dist/MarkerCluster.css'; @import '~leaflet.markercluster/dist/MarkerCluster.Default.css'; -a[role=button] { +a[role='button'] { color: $link-color !important; } diff --git a/web/webpack.config.js b/web/webpack.config.js index 1c7b808029f88e4107f429df70a5fb93e383a29b..a8ffae0a633e83b0de4095643e2e4ece795023c5 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -40,7 +40,15 @@ module.exports = (env, argv) => ({ use: [ MiniCssExtractPlugin.loader, 'css-loader', - 'sass-loader' + 'sass-loader', + { + loader: "postcss-loader", + options: { + postcssOptions: { + plugins: ["autoprefixer",], + }, + }, + } ], }, ], diff --git a/web/yarn.lock b/web/yarn.lock index 290a3152e5858a22535126431a6e723c3bfd0602..006da818ecb29a7986970a0c18274d4648db5611 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2,6 +2,27 @@ # yarn lockfile v1 +"@babel/code-frame@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + +"@babel/helper-validator-identifier@^7.14.5": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" + integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== + +"@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@discoveryjs/json-ext@^0.5.0": version "0.5.3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" @@ -102,6 +123,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.2.tgz#0465a39b5456b61a04d98bd5545f8b34be340cb7" integrity sha512-TbG4TOx9hng8FKxaVrCisdaxKxqEwJ3zwHoCWXZ0Jw6mnvTInpaB99/2Cy4+XxpXtjNv9/TgfGSvZFyfV/t8Fw== +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + "@types/sizzle@*": version "2.3.3" resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" @@ -285,6 +311,13 @@ alphanum-sort@^1.0.2: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -312,6 +345,18 @@ array-uniq@^1.0.1: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= +autoprefixer@10.3.3: + version "10.3.3" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.3.3.tgz#4bac89c74ef98e6a40fe1c5b76c0d1c91db153ce" + integrity sha512-yRzjxfnggrP/+qVHlUuZz5FZzEbkT+Yt0/Df6ScEMnbbZBLzYB2W0KLxoQCW+THm1SpOsM1ZPcTHAwuvmibIsQ== + dependencies: + browserslist "^4.16.8" + caniuse-lite "^1.0.30001252" + colorette "^1.3.0" + fraction.js "^4.1.1" + normalize-range "^0.1.2" + postcss-value-parser "^4.1.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -347,7 +392,7 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6: +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.16.8: version "4.16.8" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.8.tgz#cb868b0b554f137ba6e33de0ecff2eda403c4fb0" integrity sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ== @@ -363,6 +408,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -373,11 +423,20 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001251: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001252: version "1.0.30001252" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz#cb16e4e3dafe948fc4a9bb3307aea054b912019a" integrity sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw== +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -430,6 +489,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -437,6 +503,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -479,6 +550,17 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -717,6 +799,13 @@ envinfo@^7.7.3: resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-module-lexer@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.7.1.tgz#c2c8e0f46f2df06274cdaf0dd3f3b33e0a0b267d" @@ -727,6 +816,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -807,6 +901,11 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" +fraction.js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff" + integrity sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -867,6 +966,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -900,6 +1004,14 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-local@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" @@ -931,6 +1043,11 @@ is-absolute-url@^3.0.3: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -1022,6 +1139,11 @@ jest-worker@^27.0.2: merge-stream "^2.0.0" supports-color "^8.0.0" +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + jshint@^2.13.1: version "2.13.1" resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.13.1.tgz#16bbbecdbb4564d3758d9de4f24926f8c7f8f835" @@ -1041,6 +1163,11 @@ json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -1074,6 +1201,11 @@ lilconfig@^2.0.3: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + loader-runner@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" @@ -1177,6 +1309,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + normalize-url@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" @@ -1251,6 +1388,23 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -1276,6 +1430,11 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -1355,6 +1514,15 @@ postcss-discard-overridden@^5.0.1: resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== +postcss-loader@6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.1.1.tgz#58dd0a3accd9bc87cc52eff75244db578d11301a" + integrity sha512-lBmJMvRh1D40dqpWKr9Rpygwxn8M74U9uaCSeYGNKLGInbk9mXBt1ultHf2dH9Ghk6Ue4UXlXWwGMH9QdUJ5ug== + dependencies: + cosmiconfig "^7.0.0" + klona "^2.0.4" + semver "^7.3.5" + postcss-merge-longhand@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.2.tgz#277ada51d9a7958e8ef8cf263103c9384b322a41" @@ -1559,7 +1727,7 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@^8.2.15, postcss@^8.3.5: +postcss@8.3.6, postcss@^8.2.15, postcss@^8.3.5: version "8.3.6" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== @@ -1568,6 +1736,11 @@ postcss@^8.2.15, postcss@^8.3.5: nanoid "^3.1.23" source-map-js "^0.6.2" +prettier@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" + integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== + punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -1611,6 +1784,11 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -1754,6 +1932,13 @@ stylehacks@^5.0.1: browserslist "^4.16.0" postcss-selector-parser "^6.0.4" +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -1953,7 +2138,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.2: +yaml@^1.10.0, yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==