From b9e33480855757834348382efc9afede09ba62ca Mon Sep 17 00:00:00 2001 From: mmd-osm Date: Sun, 13 Jul 2025 20:28:48 +0200 Subject: [PATCH] Image download with user feedback --- app/assets/javascripts/leaflet.share.js | 59 ++++++++++++++++++++++ app/controllers/application_controller.rb | 3 +- app/controllers/export_controller.rb | 3 +- app/views/share_panes/show.html.erb | 4 +- config/locales/en.yml | 3 ++ test/controllers/export_controller_test.rb | 2 +- 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/leaflet.share.js b/app/assets/javascripts/leaflet.share.js index ea6c97b4c..ccae590c0 100644 --- a/app/assets/javascripts/leaflet.share.js +++ b/app/assets/javascripts/leaflet.share.js @@ -28,6 +28,65 @@ L.OSM.share = function (options) { const csrfInput = $ui.find("#csrf_export")[0]; [[csrfInput.name, csrfInput.value]] = Object.entries(OSM.csrf); + function downloadBlob(blob, filename) { + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + async function handleExportSuccess(fetchResponse) { + try { + const blob = await fetchResponse.response.blob(); + const filename = OSM.i18n.t("javascripts.share.filename"); + downloadBlob(blob, filename); + } catch (err) { + // eslint-disable-next-line no-alert + alert(OSM.i18n.t("javascripts.share.export_failed", { reason: "(blob error)" })); + } + } + + async function handleExportError(event) { + let detailMessage; + try { + detailMessage = event?.detail?.error?.message; + if (!detailMessage) { + const responseText = await event.detail.fetchResponse.responseText; + const parser = new DOMParser(); + const doc = parser.parseFromString(responseText, "text/html"); + detailMessage = doc.body ? doc.body.textContent.trim() : "(unknown)"; + } + } catch (err) { + detailMessage = "(unknown)"; + } + // eslint-disable-next-line no-alert + alert(OSM.i18n.t("javascripts.share.export_failed", { reason: detailMessage })); + } + + document.getElementById("export-image").addEventListener("turbo:submit-end", function (event) { + if (event.detail.success) { + handleExportSuccess(event.detail.fetchResponse); + } else { + handleExportError(event); + } + }); + + document.getElementById("export-image").addEventListener("turbo:before-fetch-response", function (event) { + const response = event.detail.fetchResponse.response; + const contentType = response.headers.get("content-type"); + + if (!response.ok && contentType?.includes("text/html")) { + // Prevent Turbo from replacing the current page with an error HTML response + // from the image export endpoint + event.preventDefault(); + event.stopPropagation(); + } + }); + locationFilter .on("change", update) .addTo(map); diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b72206c76..644689dc2 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -246,7 +246,8 @@ class ApplicationController < ActionController::Base def map_layout policy = request.content_security_policy.clone policy.connect_src(*policy.connect_src, "http://127.0.0.1:8111", "https://vector.openstreetmap.org", "https://api.maptiler.com", - Settings.nominatim_url, Settings.overpass_url, Settings.fossgis_osrm_url, Settings.graphhopper_url, Settings.fossgis_valhalla_url, Settings.wikidata_api_url) + "https://tile.thunderforest.com", "https://render.openstreetmap.org", Settings.nominatim_url, Settings.overpass_url, + Settings.fossgis_osrm_url, Settings.graphhopper_url, Settings.fossgis_valhalla_url, Settings.wikidata_api_url) policy.form_action(*policy.form_action, "render.openstreetmap.org", "tile.thunderforest.com") policy.img_src(*policy.img_src, Settings.wikimedia_commons_url, "upload.wikimedia.org") policy.style_src(*policy.style_src, :unsafe_inline) diff --git a/app/controllers/export_controller.rb b/app/controllers/export_controller.rb index fdae75b9a..fea8155a6 100644 --- a/app/controllers/export_controller.rb +++ b/app/controllers/export_controller.rb @@ -24,8 +24,9 @@ class ExportController < ApplicationController when "mapnik" # redirect to a special 'export' cgi script scale = params[:mapnik_scale] + token = ROTP::TOTP.new(Settings.totp_key, :interval => 3600).now if Settings.totp_key - redirect_to "https://render.openstreetmap.org/cgi-bin/export?bbox=#{bbox}&scale=#{scale}&format=#{format}", :allow_other_host => true + redirect_to "https://render.openstreetmap.org/cgi-bin/export?bbox=#{bbox}&scale=#{scale}&format=#{format}&token=#{token}", :allow_other_host => true when "cyclemap", "transportmap" zoom = params[:zoom] lat = params[:lat] diff --git a/app/views/share_panes/show.html.erb b/app/views/share_panes/show.html.erb index 04b4e1532..0f06dfc33 100644 --- a/app/views/share_panes/show.html.erb +++ b/app/views/share_panes/show.html.erb @@ -40,7 +40,7 @@ <% end %> -
+
@@ -82,6 +82,6 @@ **%w[layer width height] .to_h { |key| [key.to_sym, content_tag(:span, "", :id => "mapnik_image_#{key}")] } %>

- " /> + " data-turbo-submits-with="<%= t ".downloading" %>" />
diff --git a/config/locales/en.yml b/config/locales/en.yml index 999b78450..4dcadcdc1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2781,6 +2781,7 @@ en: custom_dimensions: "Set custom dimensions" image_dimensions_html: "Image will show the %{layer} layer at %{width} x %{height}" download: "Download" + downloading: "Downloading..." traces: visibility: private: "Private (only shared as anonymous, unordered points)" @@ -3400,6 +3401,8 @@ en: share: title: "Share" view_larger_map: "View Larger Map" + export_failed: "Map export failed: %{reason}" + filename: "map" embed: report_problem: "Report a problem" legend: diff --git a/test/controllers/export_controller_test.rb b/test/controllers/export_controller_test.rb index 7f7b4ee8c..e4c8bb1f9 100644 --- a/test/controllers/export_controller_test.rb +++ b/test/controllers/export_controller_test.rb @@ -25,7 +25,7 @@ class ExportControllerTest < ActionDispatch::IntegrationTest # test the finish action for mapnik images def test_finish_mapnik post export_finish_path(:minlon => 0, :minlat => 50, :maxlon => 1, :maxlat => 51, :format => "mapnik", :mapnik_format => "test", :mapnik_scale => "12") - assert_redirected_to "https://render.openstreetmap.org/cgi-bin/export?bbox=0.0,50.0,1.0,51.0&scale=12&format=test" + assert_redirected_to "https://render.openstreetmap.org/cgi-bin/export?bbox=0.0,50.0,1.0,51.0&scale=12&format=test&token=" end ### -- 2.39.5