diff --git a/backend/src/main/java/fr/inra/urgi/faidare/utils/Sites.java b/backend/src/main/java/fr/inra/urgi/faidare/utils/Sites.java new file mode 100644 index 0000000000000000000000000000000000000000..67fde44b8117296f13defc599adc6dcd0cd970a3 --- /dev/null +++ b/backend/src/main/java/fr/inra/urgi/faidare/utils/Sites.java @@ -0,0 +1,14 @@ +package fr.inra.urgi.faidare.utils; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * Utilities for sites + * @author JB Nizet + */ +public class Sites { + public static String siteIdToLocationId(String siteId) { + return Base64.getUrlEncoder().encodeToString(("urn:URGI/location/" + siteId).getBytes(StandardCharsets.US_ASCII)); + } +} diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/germplasm/GermplasmController.java b/backend/src/main/java/fr/inra/urgi/faidare/web/germplasm/GermplasmController.java index cf3430490eb3b9cba9803c41ab775b7a2558ce14..cd24d06dc61698b217db1422c9585ed4b7b97b05 100644 --- a/backend/src/main/java/fr/inra/urgi/faidare/web/germplasm/GermplasmController.java +++ b/backend/src/main/java/fr/inra/urgi/faidare/web/germplasm/GermplasmController.java @@ -1,5 +1,6 @@ package fr.inra.urgi.faidare.web.germplasm; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -105,7 +106,6 @@ public class GermplasmController { createXref("bazbing") ); - sortDonors(germplasm); sortPopulations(germplasm); sortCollections(germplasm); @@ -117,8 +117,7 @@ public class GermplasmController { faidareProperties.getByUri(germplasm.getSourceUri()), attributes, pedigree, - crossReferences - ) + crossReferences) ); } @@ -205,8 +204,23 @@ public class GermplasmController { SiteVO originSite = new SiteVO(); originSite.setSiteId("1234"); originSite.setSiteName("Le Moulon"); + originSite.setSiteType("Origin site"); + originSite.setLatitude(47.0F); + originSite.setLongitude(12.0F); result.setOriginSite(originSite); + List<SiteVO> evaluationSites = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + SiteVO evaluationSite = new SiteVO(); + evaluationSite.setSiteId(Integer.toString(12347 + i)); + evaluationSite.setSiteType("Evaluation site"); + evaluationSite.setSiteName("Site " + i); + evaluationSite.setLatitude(46.0F + i); + evaluationSite.setLongitude(13.0F + i); + evaluationSites.add(evaluationSite); + } + result.setEvaluationSites(evaluationSites); + result.setGenus("Genus 1"); result.setSpecies("Species 1"); result.setSpeciesAuthority("Species Auth"); @@ -241,7 +255,13 @@ public class GermplasmController { collector.setAccessionNumber("567"); result.setCollector(collector); - result.setCollectingSite(originSite); + SiteVO collectingSite = new SiteVO(); + collectingSite.setSiteId("1235"); + collectingSite.setSiteName("St Just"); + collectingSite.setSiteType("Collecting site"); + collectingSite.setLatitude(48.0F); + collectingSite.setLongitude(13.0F); + result.setCollectingSite(collectingSite); result.setAcquisitionDate("In the summer"); GermplasmInstituteVO breeder = new GermplasmInstituteVO(); diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/germplasm/GermplasmModel.java b/backend/src/main/java/fr/inra/urgi/faidare/web/germplasm/GermplasmModel.java index 8acdf78fc5f36d5427dba77b1abf57463f388f74..ffa24336f395b2d00817babacc09b38981318178 100644 --- a/backend/src/main/java/fr/inra/urgi/faidare/web/germplasm/GermplasmModel.java +++ b/backend/src/main/java/fr/inra/urgi/faidare/web/germplasm/GermplasmModel.java @@ -1,13 +1,16 @@ package fr.inra.urgi.faidare.web.germplasm; +import java.util.ArrayList; import java.util.List; import fr.inra.urgi.faidare.domain.brapi.v1.data.BrapiGermplasmAttributeValue; import fr.inra.urgi.faidare.domain.data.germplasm.GermplasmInstituteVO; import fr.inra.urgi.faidare.domain.data.germplasm.GermplasmVO; import fr.inra.urgi.faidare.domain.data.germplasm.PedigreeVO; +import fr.inra.urgi.faidare.domain.data.germplasm.SiteVO; import fr.inra.urgi.faidare.domain.datadiscovery.data.DataSource; import fr.inra.urgi.faidare.domain.xref.XRefDocumentVO; +import fr.inra.urgi.faidare.web.site.MapLocation; import org.apache.logging.log4j.util.Strings; /** @@ -136,4 +139,19 @@ public final class GermplasmModel { || Strings.isNotBlank(this.pedigree.getCrossingYear()) || Strings.isNotBlank(this.pedigree.getFamilyCode())); } + + public List<MapLocation> getMapLocations() { + List<SiteVO> sites = new ArrayList<>(); + if (germplasm.getCollectingSite() != null) { + sites.add(germplasm.getCollectingSite()); + } + if (germplasm.getOriginSite() != null) { + sites.add(germplasm.getOriginSite()); + } + if (germplasm.getEvaluationSites() != null) { + sites.addAll(germplasm.getEvaluationSites()); + } + + return MapLocation.sitesToDisplayableMapLocations(sites); + } } diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/site/MapLocation.java b/backend/src/main/java/fr/inra/urgi/faidare/web/site/MapLocation.java new file mode 100644 index 0000000000000000000000000000000000000000..3b096853cfff4a467d7a277f9fb4b41e65b2ad01 --- /dev/null +++ b/backend/src/main/java/fr/inra/urgi/faidare/web/site/MapLocation.java @@ -0,0 +1,82 @@ +package fr.inra.urgi.faidare.web.site; + +import java.util.List; +import java.util.stream.Collectors; + +import fr.inra.urgi.faidare.domain.data.LocationVO; +import fr.inra.urgi.faidare.domain.data.germplasm.SiteVO; +import fr.inra.urgi.faidare.utils.Sites; + +/** + * An object that can be serialized to JSON to serve as a map marker. + * @author JB Nizet + */ +public final class MapLocation { + private final String locationDbId; + private final String locationType; + private final String locationName; + private final double latitude; + private final double longitude; + + public MapLocation(String locationDbId, + String locationType, + String locationName, + double latitude, + double longitude) { + this.locationDbId = locationDbId; + this.locationType = locationType; + this.locationName = locationName; + this.latitude = latitude; + this.longitude = longitude; + } + + public MapLocation(LocationVO site) { + this(site.getLocationDbId(), + site.getLocationType(), + site.getLocationName(), + site.getLatitude(), + site.getLongitude()); + } + + public MapLocation(SiteVO site) { + this(Sites.siteIdToLocationId(site.getSiteId()), + site.getSiteType(), + site.getSiteName(), + site.getLatitude(), + site.getLongitude()); + } + + public static List<MapLocation> locationsToDisplayableMapLocations(List<LocationVO> locations) { + return locations.stream() + .filter(location -> location.getLatitude() != null && location.getLongitude() != null) + .map(MapLocation::new) + .collect(Collectors.toList()); + } + + public static List<MapLocation> sitesToDisplayableMapLocations(List<SiteVO> sites) { + return sites.stream() + .filter(site -> site.getLatitude() != null && site.getLongitude() != null) + .map(MapLocation::new) + .collect(Collectors.toList()); + } + + public String getLocationDbId() { + return locationDbId; + } + + public String getLocationType() { + return locationType; + } + + public String getLocationName() { + return locationName; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } +} diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/site/SiteModel.java b/backend/src/main/java/fr/inra/urgi/faidare/web/site/SiteModel.java index 61102ce0cc0af9ed2b8394cfd5df9ee3ac1deb58..cd8f7bb80bbb6cad8d29a9c76c3b6e1a39dce06f 100644 --- a/backend/src/main/java/fr/inra/urgi/faidare/web/site/SiteModel.java +++ b/backend/src/main/java/fr/inra/urgi/faidare/web/site/SiteModel.java @@ -111,4 +111,8 @@ public final class SiteModel { public List<XRefDocumentVO> getCrossReferences() { return crossReferences; } + + public List<MapLocation> getMapLocations() { + return MapLocation.locationsToDisplayableMapLocations(Collections.singletonList(this.site)); + } } diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java b/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java index ff6aee3bfb5e322b4385cbd491ad9ee9f336b5cd..833c45471061a5ef02d7f5fc57319fc9afc8a12b 100644 --- a/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java +++ b/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java @@ -12,16 +12,20 @@ import com.google.common.collect.Lists; import fr.inra.urgi.faidare.api.NotFoundException; import fr.inra.urgi.faidare.config.FaidareProperties; import fr.inra.urgi.faidare.domain.criteria.GermplasmPOSTSearchCriteria; +import fr.inra.urgi.faidare.domain.data.LocationVO; import fr.inra.urgi.faidare.domain.data.TrialVO; import fr.inra.urgi.faidare.domain.data.germplasm.GermplasmVO; import fr.inra.urgi.faidare.domain.data.study.StudyDetailVO; import fr.inra.urgi.faidare.domain.data.variable.ObservationVariableVO; import fr.inra.urgi.faidare.domain.xref.XRefDocumentVO; import fr.inra.urgi.faidare.repository.es.GermplasmRepository; +import fr.inra.urgi.faidare.repository.es.LocationRepository; import fr.inra.urgi.faidare.repository.es.StudyRepository; import fr.inra.urgi.faidare.repository.es.TrialRepository; import fr.inra.urgi.faidare.repository.es.XRefDocumentRepository; import fr.inra.urgi.faidare.repository.file.CropOntologyRepository; +import fr.inra.urgi.faidare.web.site.MapLocation; +import org.apache.logging.log4j.util.Strings; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -42,19 +46,22 @@ public class StudyController { private final GermplasmRepository germplasmRepository; private final CropOntologyRepository cropOntologyRepository; private final TrialRepository trialRepository; + private final LocationRepository locationRepository; public StudyController(StudyRepository studyRepository, FaidareProperties faidareProperties, XRefDocumentRepository xRefDocumentRepository, GermplasmRepository germplasmRepository, CropOntologyRepository cropOntologyRepository, - TrialRepository trialRepository) { + TrialRepository trialRepository, + LocationRepository locationRepository) { this.studyRepository = studyRepository; this.faidareProperties = faidareProperties; this.xRefDocumentRepository = xRefDocumentRepository; this.germplasmRepository = germplasmRepository; this.cropOntologyRepository = cropOntologyRepository; this.trialRepository = trialRepository; + this.locationRepository = locationRepository; } @GetMapping("/{studyId}") @@ -77,6 +84,11 @@ public class StudyController { List<GermplasmVO> germplasms = getGermplasms(study); List<ObservationVariableVO>variables = getVariables(study); List<TrialVO> trials = getTrials(study); + LocationVO location = getLocation(study); + + // TODO remove this + location.setLatitude(34.0); + location.setLongitude(14.0); return new ModelAndView("study", "model", @@ -86,11 +98,19 @@ public class StudyController { germplasms, variables, trials, - crossReferences + crossReferences, + location ) ); } + private LocationVO getLocation(StudyDetailVO study) { + if (Strings.isBlank(study.getLocationDbId())) { + return null; + } + return locationRepository.getById(study.getLocationDbId()); + } + private List<GermplasmVO> getGermplasms(StudyDetailVO study) { if (study.getGermplasmDbIds() == null || study.getGermplasmDbIds().isEmpty()) { return Collections.emptyList(); @@ -125,6 +145,8 @@ public class StudyController { .collect(Collectors.toList()); } + + private XRefDocumentVO createXref(String name) { XRefDocumentVO xref = new XRefDocumentVO(); xref.setName(name); diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyModel.java b/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyModel.java index 980c78d43d3dcae233c2371de2f80add8f11d421..bc77dfc3d9b9fdb7da63dd7b47afb15ab1e3bff6 100644 --- a/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyModel.java +++ b/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyModel.java @@ -1,11 +1,8 @@ package fr.inra.urgi.faidare.web.study; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import fr.inra.urgi.faidare.domain.data.LocationVO; @@ -15,6 +12,7 @@ import fr.inra.urgi.faidare.domain.data.study.StudyDetailVO; import fr.inra.urgi.faidare.domain.data.variable.ObservationVariableVO; import fr.inra.urgi.faidare.domain.datadiscovery.data.DataSource; import fr.inra.urgi.faidare.domain.xref.XRefDocumentVO; +import fr.inra.urgi.faidare.web.site.MapLocation; /** * The model used by the study page @@ -27,6 +25,7 @@ public final class StudyModel { private final List<ObservationVariableVO> variables; private final List<TrialVO> trials; private final List<XRefDocumentVO> crossReferences; + private final LocationVO location; private final List<Map.Entry<String, Object>> additionalInfoProperties; public StudyModel(StudyDetailVO study, @@ -34,13 +33,15 @@ public final class StudyModel { List<GermplasmVO> germplasms, List<ObservationVariableVO> variables, List<TrialVO> trials, - List<XRefDocumentVO> crossReferences) { + List<XRefDocumentVO> crossReferences, + LocationVO location) { this.study = study; this.source = source; this.germplasms = germplasms; this.variables = variables; this.trials = trials; this.crossReferences = crossReferences; + this.location = location; Map<String, Object> additionalInfo = study.getAdditionalInfo() == null ? Collections.emptyMap() : study.getAdditionalInfo().getProperties(); @@ -79,4 +80,11 @@ public final class StudyModel { public List<Map.Entry<String, Object>> getAdditionalInfoProperties() { return additionalInfoProperties; } + + public List<MapLocation> getMapLocations() { + if (this.location == null) { + return Collections.emptyList(); + } + return MapLocation.locationsToDisplayableMapLocations(Collections.singletonList(this.location)); + } } diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/thymeleaf/FaidareExpressions.java b/backend/src/main/java/fr/inra/urgi/faidare/web/thymeleaf/FaidareExpressions.java index a9f699de3dc80e7bc1b3bc4a7af0bc7bbb5d5da3..9ba16d342d94e9097e720c5b3ad2e370d564fe6d 100644 --- a/backend/src/main/java/fr/inra/urgi/faidare/web/thymeleaf/FaidareExpressions.java +++ b/backend/src/main/java/fr/inra/urgi/faidare/web/thymeleaf/FaidareExpressions.java @@ -9,8 +9,11 @@ import java.util.Locale; import java.util.Map; import java.util.function.Function; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import fr.inra.urgi.faidare.domain.data.germplasm.CollPopVO; import fr.inra.urgi.faidare.domain.data.germplasm.TaxonSourceVO; +import fr.inra.urgi.faidare.utils.Sites; import org.apache.logging.log4j.util.Strings; /** @@ -21,6 +24,7 @@ public class FaidareExpressions { private static final Map<String, Function<String, String>> TAXON_ID_URL_FACTORIES_BY_SOURCE_NAME = createTaxonIdUrlFactories(); + private static final ObjectMapper objectMapper = new ObjectMapper(); private static Map<String, Function<String, String>> createTaxonIdUrlFactories() { Map<String, Function<String, String>> result = new HashMap<>(); @@ -38,7 +42,7 @@ public class FaidareExpressions { } public String toSiteParam(String siteId) { - return Base64.getUrlEncoder().encodeToString(("urn:URGI/location/" + siteId).getBytes(StandardCharsets.US_ASCII)); + return Sites.siteIdToLocationId(siteId); } public String collPopTitle(CollPopVO collPopVO) { @@ -55,6 +59,15 @@ public class FaidareExpressions { return urlFactory != null ? urlFactory.apply(taxonSource.getTaxonId()) : null; } + public String toJson(Object value) { + try { + return objectMapper.writeValueAsString(value); + } + catch (JsonProcessingException e) { + throw new IllegalArgumentException(e); + } + } + private String collPopTitle(CollPopVO collPopVO, Function<String, String> nameTransformer) { if (Strings.isBlank(collPopVO.getType())) { return nameTransformer.apply(collPopVO.getName()); diff --git a/backend/src/main/resources/static/assets/images/marker-icon-blue.png b/backend/src/main/resources/static/assets/images/marker-icon-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..e2e9f757f515ded172e6f72c3ce55bbe15579649 Binary files /dev/null and b/backend/src/main/resources/static/assets/images/marker-icon-blue.png differ diff --git a/backend/src/main/resources/static/assets/images/marker-icon-green.png b/backend/src/main/resources/static/assets/images/marker-icon-green.png new file mode 100644 index 0000000000000000000000000000000000000000..6b4fb278f611f802d0c4e9cf88edad80e1e1c975 Binary files /dev/null and b/backend/src/main/resources/static/assets/images/marker-icon-green.png differ diff --git a/backend/src/main/resources/static/assets/images/marker-icon-purple.png b/backend/src/main/resources/static/assets/images/marker-icon-purple.png new file mode 100644 index 0000000000000000000000000000000000000000..63e423d250842ad5c9666507a4dd843a8f6a2b93 Binary files /dev/null and b/backend/src/main/resources/static/assets/images/marker-icon-purple.png differ diff --git a/backend/src/main/resources/static/assets/images/marker-icon-red.png b/backend/src/main/resources/static/assets/images/marker-icon-red.png new file mode 100644 index 0000000000000000000000000000000000000000..e3c0026ef246271f89aad81b6cf47b2ff63596d9 Binary files /dev/null and b/backend/src/main/resources/static/assets/images/marker-icon-red.png differ diff --git a/backend/src/main/resources/static/assets/script.js b/backend/src/main/resources/static/assets/script.js new file mode 100644 index 0000000000000000000000000000000000000000..a01603cc5ae086a8d60178594fae420dd5fe6580 --- /dev/null +++ b/backend/src/main/resources/static/assets/script.js @@ -0,0 +1,100 @@ +const faidare = (() => { + function initializePopovers() { + const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')) + popoverTriggerList.forEach(popoverTriggerEl => { + const options = {}; + const contentSelector = popoverTriggerEl.dataset.bsElement; + if (contentSelector) { + const content = document.querySelector(contentSelector); + if (content) { + options.content = () => { + const element = document.createElement('div'); + element.innerHTML = content.innerHTML; + return element; + }; + options.html = true; + } else { + throw new Error('element with selector ' + contentSelector + ' not found'); + } + } + return new bootstrap.Popover(popoverTriggerEl, options); + }); + } + + function markerColor(location) { + switch (location.locationType) { + case 'Origin site': + return 'red'; + case 'Collecting site': + return 'blue'; + case 'Evaluation site': + return 'green'; + } + return 'purple'; + } + + function markerIconUrl(contextPath, location) { + return `${contextPath}/assets/images/marker-icon-${markerColor(location)}.png`; + } + + function initializeMap(options) { + if (!options.locations.length) { + return; + } + + const mapContainerElement = document.querySelector('#map-container'); + mapContainerElement.classList.remove("d-none"); + const mapElement = document.querySelector('#map'); + 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, ' + + 'Esri China (Hong Kong), Esri (Thailand), TomTom, 2012' + }).addTo(map); + + const firstLocation = options.locations[0]; + map.setView([firstLocation.latitude, firstLocation.longitude], 5); + + const markers = L.markerClusterGroup(); + const mapMarkers = []; + 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 + }); + const popupElement = document.createElement('div'); + const titleElement = document.createElement('strong'); + titleElement.innerText = location.locationName; + const typeElement = document.createElement('div'); + typeElement.innerText = location.locationType; + const linkElement = document.createElement('a'); + linkElement.innerText = 'Details'; + linkElement.href = `${options.contextPath}/sites/${location.locationDbId}`; + popupElement.appendChild(titleElement); + popupElement.appendChild(typeElement); + popupElement.appendChild(linkElement); + + const marker = L.marker( + [location.latitude, location.longitude], + { icon: icon } + ); + markers.addLayer(marker.bindPopup(popupElement)); + mapMarkers.push(marker); + } + const initialZoom = map.getZoom(); + + map.fitBounds(L.featureGroup(mapMarkers).getBounds()); + const markerZoom = map.getZoom(); + + setTimeout(() => { + map.setZoom(Math.min(initialZoom, markerZoom)); + map.addLayer(markers); + }, 100); + } + + return { + initializePopovers, + initializeMap + }; +})(); + + diff --git a/backend/src/main/resources/static/assets/style.css b/backend/src/main/resources/static/assets/style.css index 340b22eae82d8f58d76250fdb37ded071695b6c5..87c396caa7c37cfe7a8fe274146bb9ce1a3f070f 100644 --- a/backend/src/main/resources/static/assets/style.css +++ b/backend/src/main/resources/static/assets/style.css @@ -5,3 +5,11 @@ .popover { max-width: min(80vw, 600px); } + +#map { + height: min(400px, 60vh); +} + +.map-legend img { + height: 1.5rem; +} diff --git a/backend/src/main/resources/templates/fragments/map.html b/backend/src/main/resources/templates/fragments/map.html new file mode 100644 index 0000000000000000000000000000000000000000..7fce9d1ec4155e167d3faf5572ee1c9968613750 --- /dev/null +++ b/backend/src/main/resources/templates/fragments/map.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> + +<html xmlns:th="http://www.thymeleaf.org"> + +<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"> + <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> diff --git a/backend/src/main/resources/templates/germplasm.html b/backend/src/main/resources/templates/germplasm.html index 1c51d43887a55f7e125dc46a85ae961de9d5b7bd..15ccd615a62c962ec5047510bef07a4cdd6a2652 100644 --- a/backend/src/main/resources/templates/germplasm.html +++ b/backend/src/main/resources/templates/germplasm.html @@ -2,7 +2,7 @@ <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=~{::script})}" > <head> <title>Germplasm: <th:block th:text="${model.germplasm.germplasmName}" /></title> @@ -18,6 +18,8 @@ </div> </div> + <div th:replace="fragments/map::map"></div> + <div class="row align-items-center justify-content-center"> <div class="col-auto field" th:if="${model.germplasm.photo != null && model.germplasm.photo.thumbnailFile != null}"> <template id="photo-popover"> @@ -414,5 +416,13 @@ <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/layout/main.html b/backend/src/main/resources/templates/layout/main.html index 4cd33f7022d4752767011dba6f59cf6391bd8c4c..b428faaf870ff160e99a1db5c296c19e4240cf16 100644 --- a/backend/src/main/resources/templates/layout/main.html +++ b/backend/src/main/resources/templates/layout/main.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -<html lang="fr" th:fragment="layout (title, content)" 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> @@ -8,6 +8,11 @@ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous"> <link th:href="@{/assets/style.css}" rel="stylesheet"> + <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" + integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" + crossorigin=""/> + <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.1.0/dist/MarkerCluster.css" /> + <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.1.0/dist/MarkerCluster.Default.css" /> <link rel="shortcut icon" th:href="@{/static/assets/images/favicon.ico}" type="image/x-icon" /> </head> @@ -27,26 +32,11 @@ </footer> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script> - <script type="text/javascript"> - const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')) - popoverTriggerList.forEach(popoverTriggerEl => { - const options = {}; - const contentSelector = popoverTriggerEl.dataset.bsElement; - if (contentSelector) { - const content = document.querySelector(contentSelector); - if (content) { - options.content = () => { - const element = document.createElement('div'); - element.innerHTML = content.innerHTML; - return element; - }; - options.html = true; - } else { - throw new Error('element with selector ' + contentSelector + ' not found'); - } - } - return new bootstrap.Popover(popoverTriggerEl, options); - }); - </script> + <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" + integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" + crossorigin=""></script> + <script src="https://unpkg.com/leaflet.markercluster@1.1.0/dist/leaflet.markercluster.js"></script> + <script type="text/javascript" th:src="@{/assets/script.js}"></script> + <script type="text/javascript" th:replace="${script}"></script> </body> </html> diff --git a/backend/src/main/resources/templates/site.html b/backend/src/main/resources/templates/site.html index d5d65e5c598f80106ef4bc910b7184114ec40d5c..d859f4f29e5d6ec3a2f0803235d2922a286ee2bd 100644 --- a/backend/src/main/resources/templates/site.html +++ b/backend/src/main/resources/templates/site.html @@ -2,7 +2,7 @@ <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=~{::script})}" > <head> <title>Site <th:block th:text="${model.site.locationName}" /></title> @@ -13,6 +13,8 @@ <main> <h1>Site <th:block th:text="${model.site.locationName}" /></h1> + <div th:replace="fragments/map::map"></div> + <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> @@ -58,5 +60,12 @@ <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/backend/src/main/resources/templates/study.html b/backend/src/main/resources/templates/study.html index 7bd5bbbd64fb18a2bff615b3f76e23e5153b3031..c2ee9a3ba0ec73b15de54a5861f15a288b4b174a 100644 --- a/backend/src/main/resources/templates/study.html +++ b/backend/src/main/resources/templates/study.html @@ -2,7 +2,7 @@ <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=~{::script})}" > <head> <title>Study <th:block th:text="${model.study.studyType}" />: <th:block th:text="${model.study.studyName}" /></title> @@ -13,6 +13,8 @@ <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> + <h2>Identification</h2> <div th:replace="fragments/row::text-row(label='Name', text=${model.study.studyName})"></div> @@ -188,5 +190,12 @@ <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>