1 //= require ./history/changesets-layer
2 /* global ChangesetsLayer */
4 export default function (map) {
8 .on("click", ".changeset_more a", loadMoreChangesets)
9 .on("mouseover", "[data-changeset]", function () {
10 toggleChangesetHighlight($(this).data("changeset").id, true);
12 .on("mouseout", "[data-changeset]", function () {
13 toggleChangesetHighlight($(this).data("changeset").id, false);
17 map.on("zoomstart", () => inZoom = true);
18 map.on("zoomend", () => inZoom = false);
20 const changesetsLayer = new ChangesetsLayer()
21 .on("mouseover", function (e) {
23 toggleChangesetHighlight(e.layer.id, true);
25 .on("mouseout", function (e) {
27 toggleChangesetHighlight(e.layer.id, false);
29 .on("requestscrolltochangeset", function (e) {
30 const [item] = $(`#changeset_${e.id}`);
31 item?.scrollIntoView({ block: "nearest", behavior: "smooth" });
34 let changesetIntersectionObserver;
36 function disableChangesetIntersectionObserver() {
37 if (changesetIntersectionObserver) {
38 changesetIntersectionObserver.disconnect();
39 changesetIntersectionObserver = null;
43 function enableChangesetIntersectionObserver() {
44 disableChangesetIntersectionObserver();
45 if (!window.IntersectionObserver) return;
47 let keepInitialLocation = true;
48 let itemsInViewport = $();
50 changesetIntersectionObserver = new IntersectionObserver((entries) => {
51 let closestTargetToTop,
52 closestDistanceToTop = Infinity,
53 closestTargetToBottom,
54 closestDistanceToBottom = Infinity;
56 for (const entry of entries) {
57 const id = $(entry.target).data("changeset")?.id;
59 if (entry.isIntersecting) {
60 itemsInViewport = itemsInViewport.add(entry.target);
61 if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, 0);
64 itemsInViewport = itemsInViewport.not(entry.target);
67 const distanceToTop = entry.rootBounds.top - entry.boundingClientRect.bottom;
68 const distanceToBottom = entry.boundingClientRect.top - entry.rootBounds.bottom;
70 if (distanceToTop >= 0 && distanceToTop < closestDistanceToTop) {
71 closestDistanceToTop = distanceToTop;
72 closestTargetToTop = entry.target;
74 if (distanceToBottom >= 0 && distanceToBottom <= closestDistanceToBottom) {
75 closestDistanceToBottom = distanceToBottom;
76 closestTargetToBottom = entry.target;
80 itemsInViewport.first().prevAll().each(function () {
81 const id = $(this).data("changeset")?.id;
82 if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, 1);
84 itemsInViewport.last().nextAll().each(function () {
85 const id = $(this).data("changeset")?.id;
86 if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, -1);
89 changesetsLayer.updateChangesetsOrder();
91 if (keepInitialLocation) {
92 keepInitialLocation = false;
96 if (closestTargetToTop && closestDistanceToTop < closestDistanceToBottom) {
97 const id = $(closestTargetToTop).data("changeset")?.id;
99 OSM.router.replace(location.pathname + "?" + new URLSearchParams({ before: id }) + location.hash);
101 } else if (closestTargetToBottom) {
102 const id = $(closestTargetToBottom).data("changeset")?.id;
104 OSM.router.replace(location.pathname + "?" + new URLSearchParams({ after: id }) + location.hash);
107 }, { root: $("#sidebar")[0] });
109 $("#sidebar_content .changesets ol").children().each(function () {
110 changesetIntersectionObserver.observe(this);
114 function toggleChangesetHighlight(id, state) {
115 changesetsLayer.toggleChangesetHighlight(id, state);
116 $("#sidebar_content .changesets ol li").removeClass("selected");
118 $("#changeset_" + id).addClass("selected");
122 function displayFirstChangesets(html) {
123 $("#sidebar_content .changesets").html(html);
125 $("#sidebar_content .changesets ol")
126 .before($("<div class='changeset-color-hint-bar opacity-75 sticky-top changeset-above-sidebar-viewport'>"))
127 .after($("<div class='changeset-color-hint-bar opacity-75 sticky-bottom changeset-below-sidebar-viewport'>"));
129 if (location.pathname === "/history") {
130 setPaginationMapHashes();
134 function displayMoreChangesets(div, html) {
135 const sidebar = $("#sidebar")[0];
136 const previousScrollHeightMinusTop = sidebar.scrollHeight - sidebar.scrollTop;
138 const oldList = $("#sidebar_content .changesets ol");
140 div.replaceWith(html);
142 const prevNewList = oldList.prevAll("ol");
143 if (prevNewList.length) {
144 prevNewList.next(".changeset_more").remove();
145 prevNewList.children().prependTo(oldList);
146 prevNewList.remove();
148 // restore scroll position only if prepending
149 sidebar.scrollTop = sidebar.scrollHeight - previousScrollHeightMinusTop;
152 const nextNewList = oldList.nextAll("ol");
153 if (nextNewList.length) {
154 nextNewList.prev(".changeset_more").remove();
155 nextNewList.children().appendTo(oldList);
156 nextNewList.remove();
159 if (location.pathname === "/history") {
160 setPaginationMapHashes();
164 function setPaginationMapHashes() {
165 $("#sidebar .pagination a").each(function () {
166 $(this).prop("hash", OSM.formatHash(map));
170 function loadFirstChangesets() {
171 const data = new URLSearchParams();
172 const isHistory = location.pathname === "/history";
174 disableChangesetIntersectionObserver();
177 setBboxFetchData(data);
178 const feedLink = $("link[type=\"application/atom+xml\"]"),
179 feedHref = feedLink.attr("href").split("?")[0];
180 feedLink.attr("href", feedHref + "?" + data);
183 setListFetchData(data, location);
185 fetch(location.pathname + "?" + data)
186 .then(response => response.text())
187 .then(function (html) {
188 displayFirstChangesets(html);
189 enableChangesetIntersectionObserver();
191 if (data.has("before")) {
192 const [firstItem] = $("#sidebar_content .changesets ol").children().first();
193 firstItem?.scrollIntoView();
194 } else if (data.has("after")) {
195 const [lastItem] = $("#sidebar_content .changesets ol").children().last();
196 lastItem?.scrollIntoView(false);
198 const [sidebar] = $("#sidebar");
199 sidebar.scrollTop = 0;
202 updateMap(isHistory);
206 function loadMoreChangesets(e) {
210 const div = $(this).parents(".changeset_more");
211 const isHistory = location.pathname === "/history";
213 div.find(".pagination").addClass("invisible");
214 div.find("[hidden]").prop("hidden", false);
216 const data = new URLSearchParams();
218 if (location.pathname === "/history") {
219 setBboxFetchData(data);
222 const url = new URL($(this).attr("href"), location);
223 setListFetchData(data, url);
225 fetch(url.pathname + "?" + data)
226 .then(response => response.text())
227 .then(function (html) {
228 displayMoreChangesets(div, html);
229 enableChangesetIntersectionObserver();
231 updateMap(isHistory);
235 function setBboxFetchData(data) {
236 const crs = map.options.crs;
237 const sw = map.getBounds().getSouthWest();
238 const ne = map.getBounds().getNorthEast();
239 const swClamped = crs.unproject(crs.project(sw));
240 const neClamped = crs.unproject(crs.project(ne));
242 if (sw.lat >= swClamped.lat || ne.lat <= neClamped.lat || ne.lng - sw.lng < 360) {
243 data.set("bbox", map.getBounds().toBBoxString());
247 function setListFetchData(data, url) {
248 const params = new URLSearchParams(url.search);
250 data.set("list", "1");
252 if (params.has("before")) {
253 data.set("before", params.get("before"));
255 if (params.has("after")) {
256 data.set("after", params.get("after"));
260 function moveEndListener() {
261 if (location.pathname === "/history") {
262 OSM.router.replace("/history" + window.location.hash);
263 loadFirstChangesets();
265 $("#sidebar_content .changesets ol li").removeClass("selected");
266 changesetsLayer.updateChangesetsGeometry(map);
270 function zoomEndListener() {
271 $("#sidebar_content .changesets ol li").removeClass("selected");
272 changesetsLayer.updateChangesetsGeometry(map);
275 function updateMap(isHistory) {
276 const changesets = $("[data-changeset]").map(function (index, element) {
277 return $(element).data("changeset");
278 }).get().filter(function (changeset) {
279 return changeset.bbox;
282 changesetsLayer.updateChangesets(map, changesets);
285 const bounds = changesetsLayer.getBounds();
286 if (bounds.isValid()) map.fitBounds(bounds);
290 page.pushstate = page.popstate = function (path) {
291 OSM.loadSidebarContent(path, page.load);
294 page.load = function () {
295 map.addLayer(changesetsLayer);
296 map.on("moveend", moveEndListener);
297 map.on("zoomend", zoomEndListener);
298 loadFirstChangesets();
301 page.unload = function () {
302 map.removeLayer(changesetsLayer);
303 map.off("moveend", moveEndListener);
304 map.off("zoomend", zoomEndListener);
305 disableChangesetIntersectionObserver();