]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/index.js
Merge remote-tracking branch 'upstream/pull/7190'
[rails.git] / app / assets / javascripts / index.js
1 //= require_self
2 //= require numbered_pagination
3 //= require leaflet.sidebar
4 //= require leaflet.sidebar-pane
5 //= require leaflet.locate
6 //= require leaflet.layers
7 //= require leaflet.legend
8 //= require leaflet.note
9 //= require leaflet.share
10 //= require leaflet.query
11 //= require index/contextmenu
12 //= require index/initializations
13 //= require index/layers/data
14 //= require index/layers/notes
15 //= require router
16
17 OSM.initializations = [];
18
19 $(function () {
20   const map = new L.OSM.Map("map", {
21     zoomControl: false,
22     layerControl: false,
23     contextmenu: true,
24     worldCopyJump: true
25   });
26
27   OSM.loadSidebarContent = function (path, callback) {
28     let content_path = path;
29
30     map.setSidebarOverlaid(false);
31
32     $("#sidebar_loader").prop("hidden", false).addClass("delayed-fade-in");
33
34     // Prevent caching the XHR response as a full-page URL
35     // https://github.com/openstreetmap/openstreetmap-website/issues/5663
36     if (content_path.indexOf("?") >= 0) {
37       content_path += "&xhr=1";
38     } else {
39       content_path += "?xhr=1";
40     }
41
42     $("#sidebar_content")
43       .empty();
44
45     fetch(content_path, { headers: { "accept": "text/html", "x-requested-with": "XMLHttpRequest" } })
46       .then(response => {
47         $("#flash").empty();
48         $("#sidebar_loader").removeClass("delayed-fade-in").prop("hidden", true);
49
50         const title = response.headers.get("X-Page-Title");
51         if (title) document.title = decodeURIComponent(title);
52
53         return response.text();
54       })
55       .then(html => {
56         const content = $(html);
57
58         $("head")
59           .find("link[type=\"application/atom+xml\"]")
60           .remove();
61
62         $("head")
63           .append(content.filter("link[type=\"application/atom+xml\"]"));
64
65         $("#sidebar_content").html(content.not("link[type=\"application/atom+xml\"]"));
66
67         if (callback) {
68           callback();
69         }
70       });
71   };
72
73   const token = $("head").data("oauthToken");
74   if (token) OSM.oauth = { authorization: "Bearer " + token };
75
76   const params = OSM.mapParams();
77
78   map.attributionControl.setPrefix("");
79
80   map.updateLayers(params.layers);
81
82   map.on("baselayerchange", function (e) {
83     if (map.getZoom() > e.layer.options.maxZoom) {
84       map.setView(map.getCenter(), e.layer.options.maxZoom, { reset: true });
85     }
86   });
87
88   const sidebar = L.OSM.sidebar("#map-ui")
89     .addTo(map);
90
91   const position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
92
93   function addControlGroup(controls) {
94     for (const control of controls) control.addTo(map);
95
96     const firstContainer = controls[0].getContainer();
97     $(firstContainer).find(".control-button").first()
98       .addClass("control-button-first");
99
100     const lastContainer = controls[controls.length - 1].getContainer();
101     $(lastContainer).find(".control-button").last()
102       .addClass("control-button-last");
103   }
104
105   addControlGroup([
106     L.OSM.zoom({ position }),
107     L.OSM.locate({ position })
108   ]);
109
110   addControlGroup([
111     L.OSM.layers({
112       position,
113       sidebar,
114       layers: map.baseLayers
115     }),
116     L.OSM.legend({ position, sidebar }),
117     L.OSM.share({
118       position,
119       sidebar,
120       "short": true
121     })
122   ]);
123
124   addControlGroup([
125     L.OSM.note({ position, sidebar })
126   ]);
127
128   addControlGroup([
129     L.OSM.query({ position, sidebar })
130   ]);
131
132   L.control.scale()
133     .addTo(map);
134
135   OSM.initializations.forEach(func => func(map));
136
137   if (OSM.STATUS !== "api_offline" && OSM.STATUS !== "database_offline") {
138     OSM.initializeNotesLayer(map);
139     if (params.layers.indexOf(map.noteLayer.options.code) >= 0) {
140       map.addLayer(map.noteLayer);
141     }
142
143     OSM.initializeDataLayer(map);
144     if (params.layers.indexOf(map.dataLayer.options.code) >= 0) {
145       map.addLayer(map.dataLayer);
146     }
147
148     if (params.layers.indexOf(map.gpsLayer.options.code) >= 0) {
149       map.addLayer(map.gpsLayer);
150     }
151   }
152
153   $(".leaflet-control .control-button").tooltip({ placement: "left", container: "body" });
154
155   const expires = new Date();
156   const thisYear = expires.getFullYear();
157   expires.setFullYear(thisYear + 10);
158
159   const updateCookieAndLinks = function () {
160     updateLinks(
161       map.getCenter().wrap(),
162       map.getZoom(),
163       map.getLayersCode(),
164       map._object);
165
166     OSM.cookies.set("_osm_location", OSM.locationCookie(map), { expires });
167   };
168   for (const e of ["moveend", "baselayerchange", "overlayadd", "overlayremove"]) {
169     map.on(e, updateCookieAndLinks);
170   }
171
172   if (OSM.cookies.get("_osm_welcome") !== "hide") {
173     $(".welcome").addClass("d-md-block");
174   }
175
176   $(".welcome .btn-close").on("click", function () {
177     $(".welcome").removeClass("d-md-block");
178     OSM.cookies.set("_osm_welcome", "hide", { expires });
179   });
180
181   expires.setFullYear(thisYear + 1);
182
183   $("#banner .btn-close").on("click", function (e) {
184     const cookieId = e.target.id;
185     $("#banner").removeClass("d-md-block");
186     e.preventDefault();
187     if (cookieId) {
188       OSM.cookies.set(cookieId, "hide", { expires });
189     }
190   });
191
192   if (OSM.MATOMO) {
193     const matomoLayerHandler = function (e) {
194       if (e.layer.options) {
195         const goal = OSM.MATOMO.goals[e.layer.options.layerId];
196
197         if (goal) {
198           $("body").trigger("matomogoal", goal);
199         }
200       }
201     };
202     map.on("baselayerchange", matomoLayerHandler);
203     map.on("overlayadd", matomoLayerHandler);
204   }
205
206   if (params.bounds) {
207     map.fitBounds(params.bounds);
208   } else {
209     map.setView([params.lat, params.lon], params.zoom);
210   }
211
212   if (params.marker && params.mrad) {
213     L.circle([params.mlat, params.mlon], { radius: params.mrad }).addTo(map);
214   } else if (params.marker) {
215     L.marker([params.mlat, params.mlon], { icon: OSM.getMarker({ color: "var(--marker-blue)" }) }).addTo(map);
216   }
217
218   function remoteEditHandler(bbox, object) {
219     const remoteEditHost = "http://127.0.0.1:8111",
220           osmHost = location.protocol + "//" + location.host,
221           query = new URLSearchParams({
222             left: bbox.getWest() - 0.0001,
223             top: bbox.getNorth() + 0.0001,
224             right: bbox.getEast() + 0.0001,
225             bottom: bbox.getSouth() - 0.0001
226           });
227
228     if (object && object.type !== "note") query.set("select", object.type + object.id); // can't select notes
229     sendRemoteEditCommand(remoteEditHost + "/load_and_zoom?" + query)
230       .then(() => {
231         if (object && object.type === "note") {
232           const noteQuery = new URLSearchParams({ url: osmHost + OSM.apiUrl(object) });
233           sendRemoteEditCommand(remoteEditHost + "/import?" + noteQuery);
234         }
235       })
236       .catch(() => {
237         OSM.showAlert(OSM.i18n.t("javascripts.remote_edit.failed.title"),
238                       OSM.i18n.t("javascripts.remote_edit.failed.body"));
239       });
240
241     function sendRemoteEditCommand(url) {
242       return fetch(url, { mode: "no-cors", signal: AbortSignal.timeout(5000) });
243     }
244
245     return false;
246   }
247
248   $("a[data-editor=remote]").click(function (e) {
249     const params = OSM.mapParams(this.search);
250     remoteEditHandler(map.getBounds(), params.object);
251     e.preventDefault();
252   });
253
254   if (new URLSearchParams(location.search).get("edit_help")) {
255     $("#editanchor")
256       .removeAttr("title")
257       .tooltip({
258         placement: "bottom",
259         title: OSM.i18n.t("javascripts.edit_help")
260       })
261       .tooltip("show");
262
263     $("body").one("click", function () {
264       $("#editanchor").tooltip("hide");
265     });
266   }
267
268   OSM.router = OSM.Router(map, {
269     "/": "index",
270     "/search": "search",
271     "/directions": "directions",
272     "/export": "export",
273     "/note/new": "new_note",
274     "/history/friends": "history",
275     "/history/nearby": "history",
276     "/history": "history",
277     "/user/:display_name/history": "history",
278     "/note/:id": "note",
279     "/node/:id(/history)": { module: "index_element", part: m => m.mappedElement("node") },
280     "/node/:id/history/:version": { module: "index_element", part: m => m.mappedElement("node") },
281     "/way/:id(/history)": { module: "index_element", part: m => m.mappedElement("way") },
282     "/way/:id/history/:version": { module: "index_element", part: m => m.element("way") },
283     "/relation/:id(/history)": { module: "index_element", part: m => m.mappedElement("relation") },
284     "/relation/:id/history/:version": { module: "index_element", part: m => m.element("relation") },
285     "/changeset/:id": "changeset",
286     "/query": "query",
287     "/account/home": "home"
288   });
289
290   if (OSM.preferred_editor === "remote" && location.pathname === "/edit") {
291     remoteEditHandler(map.getBounds(), params.object);
292     OSM.router.setCurrentPath("/");
293   }
294
295   OSM.router.load();
296
297   $(document).on("click", "a", function (e) {
298     if (e.isDefaultPrevented() || e.isPropagationStopped() || $(e.target).data("turbo")) {
299       return;
300     }
301
302     // Open links in a new tab as normal.
303     if (e.which > 1 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
304       return;
305     }
306
307     // Open local anchor links as normal.
308     if ($(this).attr("href")?.startsWith("#")) {
309       return;
310     }
311
312     // Ignore cross-protocol and cross-origin links.
313     const url = new URL($(this).attr("href"), location);
314     if (location.protocol !== url.protocol || location.host !== url.host) {
315       return;
316     }
317
318     if (OSM.router.route(url.pathname + url.search + url.hash)) {
319       e.preventDefault();
320       if (url.pathname !== "/directions") {
321         $("header").addClass("closed");
322       }
323     }
324   });
325
326   $(document).on("click", "#sidebar .sidebar-close-controls button", function () {
327     $(".search_form input[name=query]").val("");
328     OSM.router.route("/" + OSM.formatHash(map));
329   });
330 });