]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/leaflet.share.js
Merge remote-tracking branch 'upstream/pull/4699'
[rails.git] / app / assets / javascripts / leaflet.share.js
1 L.OSM.share = function (options) {
2   var control = L.OSM.sidebarPane(options, "share", "javascripts.share.title", "javascripts.share.title"),
3       marker = L.marker([0, 0], { draggable: true }),
4       locationFilter = new L.LocationFilter({
5         enableButton: false,
6         adjustButton: false
7       });
8
9   control.onAddPane = function (map, button, $ui) {
10     // Link / Embed
11
12     var $linkSection = $("<div>")
13       .attr("class", "share-link p-3 border-bottom border-secondary-subtle")
14       .appendTo($ui);
15
16     $("<h4>")
17       .text(I18n.t("javascripts.share.link"))
18       .appendTo($linkSection);
19
20     var $form = $("<form>")
21       .appendTo($linkSection);
22
23     $("<div>")
24       .attr("class", "form-check mb-3")
25       .appendTo($form)
26       .append($("<label>")
27         .attr("for", "link_marker")
28         .attr("class", "form-check-label")
29         .text(I18n.t("javascripts.share.include_marker")))
30       .append($("<input>")
31         .attr("id", "link_marker")
32         .attr("type", "checkbox")
33         .attr("class", "form-check-input")
34         .bind("change", toggleMarker));
35
36     $("<div class='btn-group btn-group-sm mb-2'>")
37       .appendTo($form)
38       .append($("<a class='btn btn-primary'>")
39         .addClass("active")
40         .attr("for", "long_input")
41         .attr("id", "long_link")
42         .text(I18n.t("javascripts.share.long_link")))
43       .append($("<a class='btn btn-primary'>")
44         .attr("for", "short_input")
45         .attr("id", "short_link")
46         .text(I18n.t("javascripts.share.short_link")))
47       .append($("<a class='btn btn-primary'>")
48         .attr("for", "embed_html")
49         .attr("id", "embed_link")
50         .attr("data-bs-title", I18n.t("javascripts.site.embed_html_disabled"))
51         .attr("href", "#")
52         .text(I18n.t("javascripts.share.embed")))
53       .on("click", "a", function (e) {
54         e.preventDefault();
55         if (!$(this).hasClass("btn-primary")) return;
56         var id = "#" + $(this).attr("for");
57         $(this).siblings("a")
58           .removeClass("active");
59         $(this).addClass("active");
60         $linkSection.find(".share-tab")
61           .hide();
62         $linkSection.find(".share-tab:has(" + id + ")")
63           .show()
64           .find("input, textarea")
65           .select();
66       });
67
68     $("<div>")
69       .attr("class", "share-tab")
70       .appendTo($form)
71       .append($("<input>")
72         .attr("id", "long_input")
73         .attr("type", "text")
74         .attr("class", "form-control form-control-sm font-monospace")
75         .attr("readonly", true)
76         .on("click", select));
77
78     $("<div>")
79       .attr("class", "share-tab")
80       .hide()
81       .appendTo($form)
82       .append($("<input>")
83         .attr("id", "short_input")
84         .attr("type", "text")
85         .attr("class", "form-control form-control-sm font-monospace")
86         .attr("readonly", true)
87         .on("click", select));
88
89     $("<div>")
90       .attr("class", "share-tab")
91       .hide()
92       .appendTo($form)
93       .append(
94         $("<textarea>")
95           .attr("id", "embed_html")
96           .attr("class", "form-control form-control-sm font-monospace")
97           .attr("readonly", true)
98           .on("click", select))
99       .append(
100         $("<p>")
101           .attr("class", "text-muted")
102           .text(I18n.t("javascripts.share.paste_html")));
103
104     // Geo URI
105
106     var $geoUriSection = $("<div>")
107       .attr("class", "share-geo-uri p-3 border-bottom border-secondary-subtle")
108       .appendTo($ui);
109
110     $("<h4>")
111       .text(I18n.t("javascripts.share.geo_uri"))
112       .appendTo($geoUriSection);
113
114     $("<div>")
115       .appendTo($geoUriSection)
116       .append($("<a>")
117         .attr("id", "geo_uri"));
118
119     // Image
120
121     var $imageSection = $("<div>")
122       .attr("class", "share-image p-3")
123       .appendTo($ui);
124
125     $("<h4>")
126       .text(I18n.t("javascripts.share.image"))
127       .appendTo($imageSection);
128
129     $("<div>")
130       .attr("id", "export-warning")
131       .attr("class", "text-muted")
132       .text(I18n.t("javascripts.share.only_standard_layer"))
133       .appendTo($imageSection);
134
135     $form = $("<form>")
136       .attr("id", "export-image")
137       .attr("action", "/export/finish")
138       .attr("method", "post")
139       .appendTo($imageSection);
140
141     $("<div>")
142       .appendTo($form)
143       .attr("class", "row mb-3")
144       .append($("<label>")
145         .attr("for", "mapnik_format")
146         .attr("class", "col-auto col-form-label")
147         .text(I18n.t("javascripts.share.format")))
148       .append($("<div>")
149         .attr("class", "col-auto")
150         .append($("<select>")
151           .attr("name", "mapnik_format")
152           .attr("id", "mapnik_format")
153           .attr("class", "form-select w-auto")
154           .append($("<option>").val("png").text("PNG").prop("selected", true))
155           .append($("<option>").val("jpeg").text("JPEG"))
156           .append($("<option>").val("svg").text("SVG"))
157           .append($("<option>").val("pdf").text("PDF"))));
158
159     $("<div>")
160       .appendTo($form)
161       .attr("class", "row mb-3")
162       .append($("<label>")
163         .attr("for", "mapnik_scale")
164         .attr("class", "col-auto col-form-label")
165         .text(I18n.t("javascripts.share.scale")))
166       .append($("<div>")
167         .attr("class", "col-auto")
168         .append($("<div>")
169           .attr("class", "input-group flex-nowrap")
170           .append($("<span>")
171             .attr("class", "input-group-text")
172             .text("1 : "))
173           .append($("<input>")
174             .attr("name", "mapnik_scale")
175             .attr("id", "mapnik_scale")
176             .attr("type", "text")
177             .attr("class", "form-control")
178             .on("change", update))));
179
180     $("<div>")
181       .attr("class", "row mb-3")
182       .appendTo($form)
183       .append($("<div>")
184         .attr("class", "col-auto")
185         .append($("<div>")
186           .attr("class", "form-check")
187           .append($("<label>")
188             .attr("for", "image_filter")
189             .attr("class", "form-check-label")
190             .text(I18n.t("javascripts.share.custom_dimensions")))
191           .append($("<input>")
192             .attr("id", "image_filter")
193             .attr("type", "checkbox")
194             .attr("class", "form-check-input")
195             .bind("change", toggleFilter))));
196
197     ["minlon", "minlat", "maxlon", "maxlat"].forEach(function (name) {
198       $("<input>")
199         .attr("id", "mapnik_" + name)
200         .attr("name", name)
201         .attr("type", "hidden")
202         .appendTo($form);
203     });
204
205     $("<input>")
206       .attr("name", "format")
207       .attr("value", "mapnik")
208       .attr("type", "hidden")
209       .appendTo($form);
210
211     var csrf_param = $("meta[name=csrf-param]").attr("content"),
212         csrf_token = $("meta[name=csrf-token]").attr("content");
213
214     $("<input>")
215       .attr("name", csrf_param)
216       .attr("value", csrf_token)
217       .attr("type", "hidden")
218       .appendTo($form);
219
220     var args = {
221       width: "<span id=\"mapnik_image_width\"></span>",
222       height: "<span id=\"mapnik_image_height\"></span>"
223     };
224
225     $("<p>")
226       .attr("class", "text-muted")
227       .html(I18n.t("javascripts.share.image_dimensions", args))
228       .appendTo($form);
229
230     $("<input>")
231       .attr("type", "submit")
232       .attr("class", "btn btn-primary")
233       .attr("value", I18n.t("javascripts.share.download"))
234       .appendTo($form);
235
236     locationFilter
237       .on("change", update)
238       .addTo(map);
239
240     marker.on("dragend", movedMarker);
241     map.on("move", movedMap);
242     map.on("moveend layeradd layerremove", update);
243
244     $ui
245       .on("show", shown)
246       .on("hide", hidden);
247
248     function shown() {
249       $("#mapnik_scale").val(getScale());
250       update();
251     }
252
253     function hidden() {
254       map.removeLayer(marker);
255       map.options.scrollWheelZoom = map.options.doubleClickZoom = true;
256       locationFilter.disable();
257       update();
258     }
259
260     function toggleMarker() {
261       if ($(this).is(":checked")) {
262         marker.setLatLng(map.getCenter());
263         map.addLayer(marker);
264         map.options.scrollWheelZoom = map.options.doubleClickZoom = "center";
265       } else {
266         map.removeLayer(marker);
267         map.options.scrollWheelZoom = map.options.doubleClickZoom = true;
268       }
269       update();
270     }
271
272     function toggleFilter() {
273       if ($(this).is(":checked")) {
274         locationFilter.setBounds(map.getBounds().pad(-0.2));
275         locationFilter.enable();
276       } else {
277         locationFilter.disable();
278       }
279       update();
280     }
281
282     function movedMap() {
283       marker.setLatLng(map.getCenter());
284       update();
285     }
286
287     function movedMarker() {
288       if (map.hasLayer(marker)) {
289         map.off("move", movedMap);
290         map.on("moveend", updateOnce);
291         map.panTo(marker.getLatLng());
292       }
293     }
294
295     function updateOnce() {
296       map.off("moveend", updateOnce);
297       map.on("move", movedMap);
298       update();
299     }
300
301     function escapeHTML(string) {
302       var htmlEscapes = {
303         "&": "&amp;",
304         "<": "&lt;",
305         ">": "&gt;",
306         "\"": "&quot;",
307         "'": "&#x27;"
308       };
309       return string === null ? "" : String(string).replace(/[&<>"']/g, function (match) {
310         return htmlEscapes[match];
311       });
312     }
313
314     function update() {
315       var canEmbed = map.getMapBaseLayerId() !== "tracestracktopo";
316       var bounds = map.getBounds();
317
318       $("#link_marker")
319         .prop("checked", map.hasLayer(marker));
320
321       $("#image_filter")
322         .prop("checked", locationFilter.isEnabled());
323
324       // Link / Embed
325
326       $("#short_input").val(map.getShortUrl(marker));
327       $("#long_input").val(map.getUrl(marker));
328       $("#short_link").attr("href", map.getShortUrl(marker));
329       $("#long_link").attr("href", map.getUrl(marker));
330
331       var params = {
332         bbox: bounds.toBBoxString(),
333         layer: map.getMapBaseLayerId()
334       };
335
336       if (map.hasLayer(marker)) {
337         var latLng = marker.getLatLng().wrap();
338         params.marker = latLng.lat + "," + latLng.lng;
339       }
340
341       $("#embed_link")
342         .toggleClass("btn-primary", canEmbed)
343         .toggleClass("btn-secondary", !canEmbed)
344         .tooltip(canEmbed ? "disable" : "enable");
345       if (!canEmbed && $("#embed_link").hasClass("active")) {
346         $("#long_link").click();
347       }
348
349       $("#embed_html").val(
350         "<iframe width=\"425\" height=\"350\" src=\"" +
351           escapeHTML(OSM.SERVER_PROTOCOL + "://" + OSM.SERVER_URL + "/export/embed.html?" + $.param(params)) +
352           "\" style=\"border: 1px solid black\"></iframe><br/>" +
353           "<small><a href=\"" + escapeHTML(map.getUrl(marker)) + "\">" +
354           escapeHTML(I18n.t("javascripts.share.view_larger_map")) + "</a></small>");
355
356       // Geo URI
357
358       $("#geo_uri")
359         .attr("href", map.getGeoUri(marker))
360         .html(map.getGeoUri(marker));
361
362       // Image
363
364       if (locationFilter.isEnabled()) {
365         bounds = locationFilter.getBounds();
366       }
367
368       var scale = $("#mapnik_scale").val(),
369           size = L.bounds(L.CRS.EPSG3857.project(bounds.getSouthWest()),
370                           L.CRS.EPSG3857.project(bounds.getNorthEast())).getSize(),
371           maxScale = Math.floor(Math.sqrt(size.x * size.y / 0.3136));
372
373       $("#mapnik_minlon").val(bounds.getWest());
374       $("#mapnik_minlat").val(bounds.getSouth());
375       $("#mapnik_maxlon").val(bounds.getEast());
376       $("#mapnik_maxlat").val(bounds.getNorth());
377
378       if (scale < maxScale) {
379         scale = roundScale(maxScale);
380         $("#mapnik_scale").val(scale);
381       }
382
383       $("#mapnik_image_width").text(Math.round(size.x / scale / 0.00028));
384       $("#mapnik_image_height").text(Math.round(size.y / scale / 0.00028));
385
386       if (map.getMapBaseLayerId() === "mapnik") {
387         $("#export-image").show();
388         $("#export-warning").hide();
389       } else {
390         $("#export-image").hide();
391         $("#export-warning").show();
392       }
393     }
394
395     function select() {
396       $(this).select();
397     }
398
399     function getScale() {
400       var bounds = map.getBounds(),
401           centerLat = bounds.getCenter().lat,
402           halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
403           meters = halfWorldMeters * (bounds.getEast() - bounds.getWest()) / 180,
404           pixelsPerMeter = map.getSize().x / meters,
405           metersPerPixel = 1 / (92 * 39.3701);
406       return Math.round(1 / (pixelsPerMeter * metersPerPixel));
407     }
408
409     function roundScale(scale) {
410       var precision = 5 * Math.pow(10, Math.floor(Math.LOG10E * Math.log(scale)) - 2);
411       return precision * Math.ceil(scale / precision);
412     }
413   };
414
415   return control;
416 };