1 //= require jquery-simulate/jquery.simulate
2 //= require ./history-changesets-layer
4 OSM.History = 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);
16 const changesetsLayer = new OSM.HistoryChangesetsLayer()
17 .on("mouseover", function (e) {
18 toggleChangesetHighlight(e.layer.id, true);
20 .on("mouseout", function (e) {
21 toggleChangesetHighlight(e.layer.id, false);
23 .on("click", function (e) {
24 clickChangeset(e.layer.id, e.originalEvent);
27 let changesetIntersectionObserver;
29 function disableChangesetIntersectionObserver() {
30 if (changesetIntersectionObserver) {
31 changesetIntersectionObserver.disconnect();
32 changesetIntersectionObserver = null;
36 function enableChangesetIntersectionObserver() {
37 disableChangesetIntersectionObserver();
38 if (!window.IntersectionObserver) return;
40 let keepInitialLocation = true;
42 changesetIntersectionObserver = new IntersectionObserver((entries) => {
43 let closestTargetToTop,
44 closestDistanceToTop = Infinity,
45 closestTargetToBottom,
46 closestDistanceToBottom = Infinity;
48 for (const entry of entries) {
49 const id = $(entry.target).data("changeset")?.id;
51 if (entry.isIntersecting) {
52 if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, 0);
56 const distanceToTop = entry.rootBounds.top - entry.boundingClientRect.bottom;
57 const distanceToBottom = entry.boundingClientRect.top - entry.rootBounds.bottom;
59 if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, distanceToTop >= 0 ? 1 : -1);
61 if (distanceToTop >= 0 && distanceToTop < closestDistanceToTop) {
62 closestDistanceToTop = distanceToTop;
63 closestTargetToTop = entry.target;
65 if (distanceToBottom >= 0 && distanceToBottom <= closestDistanceToBottom) {
66 closestDistanceToBottom = distanceToBottom;
67 closestTargetToBottom = entry.target;
71 if (keepInitialLocation) {
72 keepInitialLocation = false;
76 if (closestTargetToTop && closestDistanceToTop < closestDistanceToBottom) {
77 const id = $(closestTargetToTop).data("changeset")?.id;
79 OSM.router.replace(location.pathname + "?" + new URLSearchParams({ before: id }) + location.hash);
81 } else if (closestTargetToBottom) {
82 const id = $(closestTargetToBottom).data("changeset")?.id;
84 OSM.router.replace(location.pathname + "?" + new URLSearchParams({ after: id }) + location.hash);
87 }, { root: $("#sidebar")[0] });
89 $("#sidebar_content .changesets ol").children().each(function () {
90 changesetIntersectionObserver.observe(this);
94 function toggleChangesetHighlight(id, state) {
95 changesetsLayer.toggleChangesetHighlight(id, state);
96 $("#changeset_" + id).toggleClass("selected", state);
99 function clickChangeset(id, e) {
100 $("#changeset_" + id).find("a.changeset_id").simulate("click", e);
103 function displayFirstChangesets(html) {
104 $("#sidebar_content .changesets").html(html);
106 if (location.pathname === "/history") {
107 setPaginationMapHashes();
111 function displayMoreChangesets(div, html) {
112 const sidebar = $("#sidebar")[0];
113 const previousScrollHeightMinusTop = sidebar.scrollHeight - sidebar.scrollTop;
115 const oldList = $("#sidebar_content .changesets ol");
117 div.replaceWith(html);
119 const prevNewList = oldList.prevAll("ol");
120 if (prevNewList.length) {
121 prevNewList.next(".changeset_more").remove();
122 prevNewList.children().prependTo(oldList);
123 prevNewList.remove();
125 // restore scroll position only if prepending
126 sidebar.scrollTop = sidebar.scrollHeight - previousScrollHeightMinusTop;
129 const nextNewList = oldList.nextAll("ol");
130 if (nextNewList.length) {
131 nextNewList.prev(".changeset_more").remove();
132 nextNewList.children().appendTo(oldList);
133 nextNewList.remove();
136 if (location.pathname === "/history") {
137 setPaginationMapHashes();
141 function setPaginationMapHashes() {
142 $("#sidebar .pagination a").each(function () {
143 $(this).prop("hash", OSM.formatHash({
144 center: map.getCenter(),
150 function loadFirstChangesets() {
151 const data = new URLSearchParams();
153 disableChangesetIntersectionObserver();
155 if (location.pathname === "/history") {
156 setBboxFetchData(data);
157 const feedLink = $("link[type=\"application/atom+xml\"]"),
158 feedHref = feedLink.attr("href").split("?")[0];
159 feedLink.attr("href", feedHref + "?" + data);
162 setListFetchData(data, location);
164 fetch(location.pathname + "?" + data)
165 .then(response => response.text())
166 .then(function (html) {
167 displayFirstChangesets(html);
168 enableChangesetIntersectionObserver();
170 if (data.has("before")) {
171 const [firstItem] = $("#sidebar_content .changesets ol").children().first();
172 firstItem?.scrollIntoView();
173 } else if (data.has("after")) {
174 const [lastItem] = $("#sidebar_content .changesets ol").children().last();
175 lastItem?.scrollIntoView(false);
177 const [sidebar] = $("#sidebar");
178 sidebar.scrollTop = 0;
185 function loadMoreChangesets(e) {
189 const div = $(this).parents(".changeset_more");
191 div.find(".pagination").addClass("invisible");
192 div.find("[hidden]").prop("hidden", false);
194 const data = new URLSearchParams();
196 if (location.pathname === "/history") {
197 setBboxFetchData(data);
200 const url = new URL($(this).attr("href"), location);
201 setListFetchData(data, url);
203 fetch(url.pathname + "?" + data)
204 .then(response => response.text())
205 .then(function (html) {
206 displayMoreChangesets(div, html);
207 enableChangesetIntersectionObserver();
213 function setBboxFetchData(data) {
214 const crs = map.options.crs;
215 const sw = map.getBounds().getSouthWest();
216 const ne = map.getBounds().getNorthEast();
217 const swClamped = crs.unproject(crs.project(sw));
218 const neClamped = crs.unproject(crs.project(ne));
220 if (sw.lat >= swClamped.lat || ne.lat <= neClamped.lat || ne.lng - sw.lng < 360) {
221 data.set("bbox", map.getBounds().toBBoxString());
225 function setListFetchData(data, url) {
226 const params = new URLSearchParams(url.search);
228 data.set("list", "1");
230 if (params.has("before")) {
231 data.set("before", params.get("before"));
233 if (params.has("after")) {
234 data.set("after", params.get("after"));
238 function moveEndListener() {
239 if (location.pathname === "/history") {
240 OSM.router.replace("/history" + window.location.hash);
241 loadFirstChangesets();
243 changesetsLayer.updateChangesetLocations(map);
247 function zoomEndListener() {
248 changesetsLayer.updateChangesetShapes(map);
251 function updateMap() {
252 const changesets = $("[data-changeset]").map(function (index, element) {
253 return $(element).data("changeset");
254 }).get().filter(function (changeset) {
255 return changeset.bbox;
258 changesetsLayer.updateChangesets(map, changesets);
260 if (location.pathname !== "/history") {
261 const bounds = changesetsLayer.getBounds();
262 if (bounds.isValid()) map.fitBounds(bounds);
266 page.pushstate = page.popstate = function (path) {
267 OSM.loadSidebarContent(path, page.load);
270 page.load = function () {
271 map.addLayer(changesetsLayer);
272 map.on("moveend", moveEndListener);
273 map.on("zoomend", zoomEndListener);
274 loadFirstChangesets();
277 page.unload = function () {
278 map.removeLayer(changesetsLayer);
279 map.off("moveend", moveEndListener);
280 map.off("zoomend", zoomEndListener);
281 disableChangesetIntersectionObserver();