]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/index/history.js
Localisation updates from https://translatewiki.net.
[rails.git] / app / assets / javascripts / index / history.js
1 //= require jquery-simulate/jquery.simulate
2 //= require ./history-changesets-layer
3
4 OSM.History = function (map) {
5   const page = {};
6
7   $("#sidebar_content")
8     .on("click", ".changeset_more a", loadMoreChangesets)
9     .on("mouseover", "[data-changeset]", function () {
10       toggleChangesetHighlight($(this).data("changeset").id, true);
11     })
12     .on("mouseout", "[data-changeset]", function () {
13       toggleChangesetHighlight($(this).data("changeset").id, false);
14     });
15
16   let inZoom = false;
17   map.on("zoomstart", () => inZoom = true);
18   map.on("zoomend", () => inZoom = false);
19
20   const changesetsLayer = new OSM.HistoryChangesetsLayer()
21     .on("mouseover", function (e) {
22       if (inZoom) return;
23       toggleChangesetHighlight(e.layer.id, true);
24     })
25     .on("mouseout", function (e) {
26       if (inZoom) return;
27       toggleChangesetHighlight(e.layer.id, false);
28     })
29     .on("click", function (e) {
30       clickChangeset(e.layer.id, e.originalEvent);
31     })
32     .on("requestscrolltochangeset", function (e) {
33       const [item] = $(`#changeset_${e.id}`);
34       item?.scrollIntoView({ block: "nearest", behavior: "smooth" });
35     });
36
37   let changesetIntersectionObserver;
38
39   function disableChangesetIntersectionObserver() {
40     if (changesetIntersectionObserver) {
41       changesetIntersectionObserver.disconnect();
42       changesetIntersectionObserver = null;
43     }
44   }
45
46   function enableChangesetIntersectionObserver() {
47     disableChangesetIntersectionObserver();
48     if (!window.IntersectionObserver) return;
49
50     let keepInitialLocation = true;
51     let itemsInViewport = $();
52
53     changesetIntersectionObserver = new IntersectionObserver((entries) => {
54       let closestTargetToTop,
55           closestDistanceToTop = Infinity,
56           closestTargetToBottom,
57           closestDistanceToBottom = Infinity;
58
59       for (const entry of entries) {
60         const id = $(entry.target).data("changeset")?.id;
61
62         if (entry.isIntersecting) {
63           itemsInViewport = itemsInViewport.add(entry.target);
64           if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, 0);
65           continue;
66         } else {
67           itemsInViewport = itemsInViewport.not(entry.target);
68         }
69
70         const distanceToTop = entry.rootBounds.top - entry.boundingClientRect.bottom;
71         const distanceToBottom = entry.boundingClientRect.top - entry.rootBounds.bottom;
72
73         if (distanceToTop >= 0 && distanceToTop < closestDistanceToTop) {
74           closestDistanceToTop = distanceToTop;
75           closestTargetToTop = entry.target;
76         }
77         if (distanceToBottom >= 0 && distanceToBottom <= closestDistanceToBottom) {
78           closestDistanceToBottom = distanceToBottom;
79           closestTargetToBottom = entry.target;
80         }
81       }
82
83       itemsInViewport.first().prevAll().each(function () {
84         const id = $(this).data("changeset")?.id;
85         if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, 1);
86       });
87       itemsInViewport.last().nextAll().each(function () {
88         const id = $(this).data("changeset")?.id;
89         if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, -1);
90       });
91
92       changesetsLayer.updateChangesetsOrder();
93
94       if (keepInitialLocation) {
95         keepInitialLocation = false;
96         return;
97       }
98
99       if (closestTargetToTop && closestDistanceToTop < closestDistanceToBottom) {
100         const id = $(closestTargetToTop).data("changeset")?.id;
101         if (id) {
102           OSM.router.replace(location.pathname + "?" + new URLSearchParams({ before: id }) + location.hash);
103         }
104       } else if (closestTargetToBottom) {
105         const id = $(closestTargetToBottom).data("changeset")?.id;
106         if (id) {
107           OSM.router.replace(location.pathname + "?" + new URLSearchParams({ after: id }) + location.hash);
108         }
109       }
110     }, { root: $("#sidebar")[0] });
111
112     $("#sidebar_content .changesets ol").children().each(function () {
113       changesetIntersectionObserver.observe(this);
114     });
115   }
116
117   function toggleChangesetHighlight(id, state) {
118     changesetsLayer.toggleChangesetHighlight(id, state);
119     $("#sidebar_content .changesets ol li").removeClass("selected");
120     if (state) {
121       $("#changeset_" + id).addClass("selected");
122     }
123   }
124
125   function clickChangeset(id, e) {
126     $("#changeset_" + id).find("a.changeset_id").simulate("click", e);
127   }
128
129   function displayFirstChangesets(html) {
130     $("#sidebar_content .changesets").html(html);
131
132     $("#sidebar_content .changesets ol")
133       .before($("<div class='changeset-color-hint-bar opacity-75 sticky-top changeset-above-sidebar-viewport'>"))
134       .after($("<div class='changeset-color-hint-bar opacity-75 sticky-bottom changeset-below-sidebar-viewport'>"));
135
136     if (location.pathname === "/history") {
137       setPaginationMapHashes();
138     }
139   }
140
141   function displayMoreChangesets(div, html) {
142     const sidebar = $("#sidebar")[0];
143     const previousScrollHeightMinusTop = sidebar.scrollHeight - sidebar.scrollTop;
144
145     const oldList = $("#sidebar_content .changesets ol");
146
147     div.replaceWith(html);
148
149     const prevNewList = oldList.prevAll("ol");
150     if (prevNewList.length) {
151       prevNewList.next(".changeset_more").remove();
152       prevNewList.children().prependTo(oldList);
153       prevNewList.remove();
154
155       // restore scroll position only if prepending
156       sidebar.scrollTop = sidebar.scrollHeight - previousScrollHeightMinusTop;
157     }
158
159     const nextNewList = oldList.nextAll("ol");
160     if (nextNewList.length) {
161       nextNewList.prev(".changeset_more").remove();
162       nextNewList.children().appendTo(oldList);
163       nextNewList.remove();
164     }
165
166     if (location.pathname === "/history") {
167       setPaginationMapHashes();
168     }
169   }
170
171   function setPaginationMapHashes() {
172     $("#sidebar .pagination a").each(function () {
173       $(this).prop("hash", OSM.formatHash(map));
174     });
175   }
176
177   function loadFirstChangesets() {
178     const data = new URLSearchParams();
179
180     disableChangesetIntersectionObserver();
181
182     if (location.pathname === "/history") {
183       setBboxFetchData(data);
184       const feedLink = $("link[type=\"application/atom+xml\"]"),
185             feedHref = feedLink.attr("href").split("?")[0];
186       feedLink.attr("href", feedHref + "?" + data);
187     }
188
189     setListFetchData(data, location);
190
191     fetch(location.pathname + "?" + data)
192       .then(response => response.text())
193       .then(function (html) {
194         displayFirstChangesets(html);
195         enableChangesetIntersectionObserver();
196
197         if (data.has("before")) {
198           const [firstItem] = $("#sidebar_content .changesets ol").children().first();
199           firstItem?.scrollIntoView();
200         } else if (data.has("after")) {
201           const [lastItem] = $("#sidebar_content .changesets ol").children().last();
202           lastItem?.scrollIntoView(false);
203         } else {
204           const [sidebar] = $("#sidebar");
205           sidebar.scrollTop = 0;
206         }
207
208         updateMap();
209       });
210   }
211
212   function loadMoreChangesets(e) {
213     e.preventDefault();
214     e.stopPropagation();
215
216     const div = $(this).parents(".changeset_more");
217
218     div.find(".pagination").addClass("invisible");
219     div.find("[hidden]").prop("hidden", false);
220
221     const data = new URLSearchParams();
222
223     if (location.pathname === "/history") {
224       setBboxFetchData(data);
225     }
226
227     const url = new URL($(this).attr("href"), location);
228     setListFetchData(data, url);
229
230     fetch(url.pathname + "?" + data)
231       .then(response => response.text())
232       .then(function (html) {
233         displayMoreChangesets(div, html);
234         enableChangesetIntersectionObserver();
235
236         updateMap();
237       });
238   }
239
240   function setBboxFetchData(data) {
241     const crs = map.options.crs;
242     const sw = map.getBounds().getSouthWest();
243     const ne = map.getBounds().getNorthEast();
244     const swClamped = crs.unproject(crs.project(sw));
245     const neClamped = crs.unproject(crs.project(ne));
246
247     if (sw.lat >= swClamped.lat || ne.lat <= neClamped.lat || ne.lng - sw.lng < 360) {
248       data.set("bbox", map.getBounds().toBBoxString());
249     }
250   }
251
252   function setListFetchData(data, url) {
253     const params = new URLSearchParams(url.search);
254
255     data.set("list", "1");
256
257     if (params.has("before")) {
258       data.set("before", params.get("before"));
259     }
260     if (params.has("after")) {
261       data.set("after", params.get("after"));
262     }
263   }
264
265   function moveEndListener() {
266     if (location.pathname === "/history") {
267       OSM.router.replace("/history" + window.location.hash);
268       loadFirstChangesets();
269     } else {
270       $("#sidebar_content .changesets ol li").removeClass("selected");
271       changesetsLayer.updateChangesetsGeometry(map);
272     }
273   }
274
275   function zoomEndListener() {
276     $("#sidebar_content .changesets ol li").removeClass("selected");
277     changesetsLayer.updateChangesetsGeometry(map);
278   }
279
280   function updateMap() {
281     const changesets = $("[data-changeset]").map(function (index, element) {
282       return $(element).data("changeset");
283     }).get().filter(function (changeset) {
284       return changeset.bbox;
285     });
286
287     changesetsLayer.updateChangesets(map, changesets);
288
289     if (location.pathname !== "/history") {
290       const bounds = changesetsLayer.getBounds();
291       if (bounds.isValid()) map.fitBounds(bounds);
292     }
293   }
294
295   page.pushstate = page.popstate = function (path) {
296     OSM.loadSidebarContent(path, page.load);
297   };
298
299   page.load = function () {
300     map.addLayer(changesetsLayer);
301     map.on("moveend", moveEndListener);
302     map.on("zoomend", zoomEndListener);
303     loadFirstChangesets();
304   };
305
306   page.unload = function () {
307     map.removeLayer(changesetsLayer);
308     map.off("moveend", moveEndListener);
309     map.off("zoomend", zoomEndListener);
310     disableChangesetIntersectionObserver();
311   };
312
313   return page;
314 };