From 2c29fd322ee171aa57cd949c5c2070e511700f93 Mon Sep 17 00:00:00 2001 From: Olivier Maury <Olivier.Maury@inrae.fr> Date: Thu, 7 Mar 2024 18:09:10 +0100 Subject: [PATCH] Ajouter les boutons d'export. fixes #25 --- .../www/client/i18n/AppConstants.java | 6 ++ .../www/client/ui/map/ControlSuppliers.java | 32 ++++---- .../agrometinfo/www/client/util/UiUtils.java | 13 +++ .../www/client/view/RightPanelView.java | 30 +++++-- .../client/i18n/AppConstants_fr.properties | 1 + .../www/client/public/ol-downloadbutton.js | 79 +++++++++++++++++++ .../agrometinfo/www/client/public/style.css | 44 ++++++----- .../public/vendors/ol/ol-layerswitcher.css | 7 -- www-server/src/main/webapp/index.html | 29 +++---- 9 files changed, 180 insertions(+), 61 deletions(-) create mode 100644 www-client/src/main/resources/fr/agrometinfo/www/client/public/ol-downloadbutton.js diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java index 1bdffb0..d914d2d 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java @@ -141,6 +141,12 @@ public interface AppConstants extends com.google.gwt.i18n.client.ConstantsWithLo @DefaultStringValue("Daily values") String dailyValues(); + /** + * @return translation + */ + @DefaultStringValue("Download chart") + String downloadChart(); + /** * @return translation */ diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/ui/map/ControlSuppliers.java b/www-client/src/main/java/fr/agrometinfo/www/client/ui/map/ControlSuppliers.java index 859a799..6d201fc 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/ui/map/ControlSuppliers.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/ui/map/ControlSuppliers.java @@ -5,8 +5,6 @@ import org.dominokit.domino.ui.icons.MdiIcon; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.ButtonElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.LinkElement; import com.google.gwt.dom.client.Node; import com.google.gwt.user.client.DOM; @@ -14,7 +12,6 @@ import fr.agrometinfo.www.client.i18n.MapConstants; import ol.Collection; import ol.Extent; import ol.control.Control; -import ol.control.ControlOptions; import ol.control.FullScreen; import ol.control.FullScreenOptions; import ol.control.Zoom; @@ -40,12 +37,24 @@ public abstract class ControlSuppliers { public static Collection<Control> createAllControls() { final Collection<Control> controls = new Collection<>(); controls.insertAt(0, ControlSuppliers.createZoom()); - controls.insertAt(0, ControlSuppliers.createInraeLogo()); + controls.insertAt(0, ControlSuppliers.createDownloadButton()); controls.insertAt(0, ControlSuppliers.createFullScreen()); controls.insertAt(0, ControlSuppliers.createLayerSwitcher()); return controls; } + /** + * A custom control with button to download the map canvas. + * + * @return control + */ + public static native Control createDownloadButton() /*-{ + return new $wnd.ol.control.DownloadButton({ + downloadFileName : 'agrometinfo.png', + tipLabel : 'Télécharger la carte' // Optional label for button + }); + }-*/; + /** * @return internationalized control to toggle full screen */ @@ -56,20 +65,15 @@ public abstract class ControlSuppliers { } /** - * Create a MapBox logo. + * OpenLayers LayerSwitcher Control, displays a list of layers and groups + * associated with a map which have a `title` property. * - * @return MapBox logo + * @return control */ - public static Control createInraeLogo() { - final ControlOptions controlOptions = new ControlOptions(); - final LinkElement mapboxLogo = Document.get().createLinkElement(); - mapboxLogo.setClassName(".mapLogo"); - controlOptions.setElement(mapboxLogo); - return new Control(controlOptions); - } - public static native Control createLayerSwitcher() /*-{ return new $wnd.ol.control.LayerSwitcher({ + collapseLabel : '', + collapseTipLabel : 'Couches géographiques et fonds de carte', tipLabel : 'Légende' // Optional label for button }); }-*/; diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java index db182cb..1b7d608 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java @@ -6,6 +6,19 @@ package fr.agrometinfo.www.client.util; * @author Olivier Maury */ public final class UiUtils { + /** + * Trigger download from an URL with the file name. + * + * @param url URL to download + * @param downloadFileName file name + */ + public static native void downloadUrl(String url, String downloadFileName) /*-{ + var fileLink = document.createElement('a'); + fileLink.href = url; + fileLink.download = downloadFileName; + fileLink.click(); + }-*/; + /** * @return the name of the used browser. */ diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/view/RightPanelView.java b/www-client/src/main/java/fr/agrometinfo/www/client/view/RightPanelView.java index 4b12c3a..b166ddc 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/view/RightPanelView.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/view/RightPanelView.java @@ -51,6 +51,7 @@ import fr.agrometinfo.www.client.presenter.RightPanelPresenter; import fr.agrometinfo.www.client.ui.CardDetails; import fr.agrometinfo.www.client.util.ApplicationUtils; import fr.agrometinfo.www.client.util.DateUtils; +import fr.agrometinfo.www.client.util.UiUtils; import fr.agrometinfo.www.shared.dto.IndicatorDTO; import fr.agrometinfo.www.shared.dto.SummaryDTO; @@ -143,6 +144,11 @@ public final class RightPanelView implements RightPanelPresenter.View { */ private EventListener backBtnClickListener = null; + /** + * Chart with daily values. + */ + private TimeSeriesLineChart chart; + /** * Card for the daily comparison value for the user choice. */ @@ -153,6 +159,13 @@ public final class RightPanelView implements RightPanelPresenter.View { */ private final DominoElement<HTMLElement> container; + /** + * Button to download the chart. + */ + private final Button downloadBtn = Button.create(Icons.MDI_ICONS.download_mdi()).linkify() // + .setContent(CSTS.downloadChart()) // + .addClickListener(e -> UiUtils.downloadUrl(chart.getCanvas().toDataURL(), "graph.png")); + /** * Panel header. */ @@ -168,19 +181,19 @@ public final class RightPanelView implements RightPanelPresenter.View { */ private final DominoElement<HTMLDivElement> lineChartContainer = DominoElement.div(); + /** + * Name of chosen period. + */ + private String periodName; + /** * Related presenter. */ private RightPanelPresenter presenter; - /** * Values according to user's choice. */ private SummaryDTO summary; - /** - * Name of chosen period. - */ - private String periodName; /** * Constructor. @@ -197,14 +210,14 @@ public final class RightPanelView implements RightPanelPresenter.View { GWT.log("RightPanelView.createBarChart() " + values.size()); final DateTimeFormat dtf = DateTimeFormat.getFormat(PredefinedFormat.DATE_LONG); final String subtitle = MSGS.chartSubtitle(summary.getDate(), summary.getIndicator().getUnit()); - final TimeSeriesLineChart chart = new TimeSeriesLineChart(); + chart = new TimeSeriesLineChart(); setChartOptions(chart, CSTS.dailyValues(), subtitle); chart.getOptions().getTooltips().getCallbacks().setTitleCallback(new AbstractTooltipTitleCallback() { @Override - public List<String> onTitle(final IsChart chart, final List<TooltipItem> items) { + public List<String> onTitle(final IsChart aChart, final List<TooltipItem> items) { final TooltipItem item = items.iterator().next(); - final LineDataset ds = (LineDataset) chart.getData().getDatasets().get(0); + final LineDataset ds = (LineDataset) aChart.getData().getDatasets().get(0); final DataPoint dp = ds.getDataPoints().get(item.getDataIndex()); return Arrays.asList(dtf.format(dp.getXAsDate())); } @@ -277,6 +290,7 @@ public final class RightPanelView implements RightPanelPresenter.View { .addColumn(Column.span6().add(averageCard)) // .addColumn(Column.span6().add(comparisonCard))); container.add(lineChartContainer); + container.add(downloadBtn); } @Override diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties index 6f2b438..cabef82 100644 --- a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties +++ b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties @@ -21,6 +21,7 @@ contactSeeFAQ = Avez-vous pris connaissance de notre FAQ ? contactSendMessage= Envoyer contactUs = Contactez-nous dailyValues = Valeurs journalières +downloadChart = Télécharger le graphique failureBody = Corps : failureHeaders = Entêtes HTTP : failureStatusText = Texte d’état HTTP : diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/public/ol-downloadbutton.js b/www-client/src/main/resources/fr/agrometinfo/www/client/public/ol-downloadbutton.js new file mode 100644 index 0000000..9eb2a56 --- /dev/null +++ b/www-client/src/main/resources/fr/agrometinfo/www/client/public/ol-downloadbutton.js @@ -0,0 +1,79 @@ +(function(global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('ol/control/Control')) : + typeof define === 'function' && define.amd ? define(['ol/control/Control'], factory) : + (global.DownloadButton = factory(global.ol.control.Control)); +}(this, (function(Control) { + 'use strict'; + + Control = 'default' in Control ? Control['default'] : Control; + + /** + * A custom control with button to download the map canvas. + * @constructor + * @extends {ol/control/Control~Control} + * @param opt_options DownloadButton options, see [DownloadButton Options](#options) for more details. + */ + class DownloadButton extends Control { + constructor(opt_options) { + const options = Object.assign({}, opt_options); + const element = document.createElement('div'); + element.className = 'ol-control ol-download-button'; + super({ element: element, target: options.target }); + this.downloadFileName = options.downloadFileName ? options.downloadFileName : 'map.png'; + this.tipLabel = options.tipLabel ? options.tipLabel : 'Download'; + var icon = document.createElement('i'); + icon.className = 'mdi mdi-download mdi-24px'; + this.button = document.createElement('button'); + this.button.setAttribute('title', this.tipLabel); + this.button.setAttribute('aria-label', this.tipLabel); + this.button.onclick = (e) => { + const evt = e || window.event; + DownloadButton.export(this.getMap(), this.downloadFileName); + evt.preventDefault(); + }; + this.button.appendChild(icon); + element.appendChild(this.button); + } + /** + * **_[static]_** - Export the canvas + * @param map The OpenLayers Map instance to render layers for + * @param downloadFileName The file name for the exported file + */ + static export(map, downloadFileName) { + // simplified from https://openlayers.org/en/latest/examples/export-map.html + var mapCanvas = document.createElement('canvas'); + var size = map.getSize(); + mapCanvas.width = size[0]; + mapCanvas.height = size[1]; + var mapContext = mapCanvas.getContext('2d'); + Array.prototype.forEach.call(map.getViewport().querySelectorAll('canvas'), function(canvas) { + mapContext.drawImage(canvas, 0, 0); + }); + mapContext.globalAlpha = 1; + mapContext.setTransform(1, 0, 0, 1, 0, 0); + var link = document.createElement('a'); + link.href = mapCanvas.toDataURL(); + link.download = downloadFileName; + link.click(); + } + /** + * **_[static]_** - Generate a UUID + * Adapted from http://stackoverflow.com/a/2117523/526860 + * @returns {String} UUID + */ + static uuid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); + } + } + // Expose DownloadButton as ol.control.DownloadButton if using a full build of + // OpenLayers + if (window['ol'] && window['ol']['control']) { + window['ol']['control']['DownloadButton'] = DownloadButton; + } + + return DownloadButton; + +}))); diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/public/style.css b/www-client/src/main/resources/fr/agrometinfo/www/client/public/style.css index ade2d54..973afa9 100644 --- a/www-client/src/main/resources/fr/agrometinfo/www/client/public/style.css +++ b/www-client/src/main/resources/fr/agrometinfo/www/client/public/style.css @@ -123,23 +123,23 @@ select { /* card-details */ details.card-details { - background: #fff; - border-radius: 5px; - box-shadow: 0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12); - margin-bottom: 10px; + background: #fff; + border-radius: 5px; + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12); + margin-bottom: 10px; } details.card-details > div, details.card-details > summary { - padding: 10px 15px; + padding: 10px 15px; } details.card-details > summary { - color: #30353b; - list-style: none; - display: flex; - justify-content: space-between; - align-items: center; + color: #30353b; + list-style: none; + display: flex; + justify-content: space-between; + align-items: center; } details.card-details > summary::-webkit-details-marker { display: none; @@ -193,29 +193,37 @@ details.card-details[open] > summary i { height: 38px; width: 38px; } +#mapContainer .ol-download-button, +#mapContainer .ol-full-screen, +#mapContainer .layer-switcher, +#mapContainer .ol-zoom-extent, +#mapContainer .ol-zoom { + left: auto; + right: .5em; + z-index: 1; +} #mapContainer .ol-full-screen { top: .5em; - right: .5em; bottom: auto; - left: auto; } #mapContainer .layer-switcher { top: 3.5em; - right: .5em; bottom: auto; - left: auto; +} +#mapContainer .layer-switcher .panel { + z-index: 2; +} +#mapContainer .ol-download-button { + top: 6.5em; + bottom: auto; } #mapContainer .ol-zoom-extent { top: auto; bottom: 8em; - right: .5em; - left: auto; } #mapContainer .ol-zoom { top: auto; bottom: 2em; - right: .5em; - left: auto; } :root { --logo-height: 50px; diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/public/vendors/ol/ol-layerswitcher.css b/www-client/src/main/resources/fr/agrometinfo/www/client/public/vendors/ol/ol-layerswitcher.css index 56f57ec..f63e295 100644 --- a/www-client/src/main/resources/fr/agrometinfo/www/client/public/vendors/ol/ol-layerswitcher.css +++ b/www-client/src/main/resources/fr/agrometinfo/www/client/public/vendors/ol/ol-layerswitcher.css @@ -37,7 +37,6 @@ .layer-switcher.shown { overflow-y: hidden; - display: flex; flex-direction: column; max-height: calc(100% - 5.5em); } @@ -53,10 +52,6 @@ display: block; } -.layer-switcher.shown button { - display: none; -} - .layer-switcher.shown.layer-switcher-activation-mode-click > button { display: block; background-image: unset; @@ -83,8 +78,6 @@ .layer-switcher li.group + li.group { margin-top: 0.4em; } -.layer-switcher li.group + li.layer-switcher-base-group { -} .layer-switcher li.group > label { font-weight: bold; diff --git a/www-server/src/main/webapp/index.html b/www-server/src/main/webapp/index.html index 96663b4..5498ba6 100644 --- a/www-server/src/main/webapp/index.html +++ b/www-server/src/main/webapp/index.html @@ -43,20 +43,21 @@ <script src="app/app.nocache.js"></script> <script src="app/vendors/ol/ol.js"></script> <script src="app/vendors/ol/ol-layerswitcher.js"></script> - <!-- Matomo --> - <script> - var _paq = window._paq = window._paq || []; - /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ - _paq.push(['trackPageView']); - _paq.push(['enableLinkTracking']); - (function() { - var u="//agroclim.inrae.fr/matomo/"; - _paq.push(['setTrackerUrl', u+'matomo.php']); - _paq.push(['setSiteId', '${matomo.site.id}']); - var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; - g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); - })(); - </script> + <script src="app/ol-downloadbutton.js"></script> + <!-- Matomo --> + <script> + var _paq = window._paq = window._paq || []; + /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ + _paq.push(['trackPageView']); + _paq.push(['enableLinkTracking']); + (function() { + var u="//agroclim.inrae.fr/matomo/"; + _paq.push(['setTrackerUrl', u+'matomo.php']); + _paq.push(['setSiteId', '${matomo.site.id}']); + var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; + g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); + })(); + </script> </head> <!-- --> -- GitLab