]> git.openstreetmap.org Git - rails.git/blobdiff - app/assets/javascripts/index/history.js
Add function to set changeset list bbox param
[rails.git] / app / assets / javascripts / index / history.js
index 6e4e73a23fa7e5564bcad943a6b78f4111a96680..85b5e96eb6ea4ca48ef109bec0b604aaf0974218 100644 (file)
@@ -4,7 +4,7 @@ OSM.History = function (map) {
   const page = {};
 
   $("#sidebar_content")
-    .on("click", ".changeset_more a", loadMore)
+    .on("click", ".changeset_more a", loadMoreChangesets)
     .on("mouseover", "[data-changeset]", function () {
       highlightChangeset($(this).data("changeset").id);
     })
@@ -27,6 +27,65 @@ OSM.History = function (map) {
     return layer.id;
   };
 
+  let changesetIntersectionObserver;
+
+  function disableChangesetIntersectionObserver() {
+    if (changesetIntersectionObserver) {
+      changesetIntersectionObserver.disconnect();
+      changesetIntersectionObserver = null;
+    }
+  }
+
+  function enableChangesetIntersectionObserver() {
+    disableChangesetIntersectionObserver();
+    if (!window.IntersectionObserver) return;
+
+    let ignoreIntersectionEvents = true;
+
+    changesetIntersectionObserver = new IntersectionObserver((entries) => {
+      if (ignoreIntersectionEvents) {
+        ignoreIntersectionEvents = false;
+        return;
+      }
+
+      let closestTargetToTop,
+          closestDistanceToTop = Infinity,
+          closestTargetToBottom,
+          closestDistanceToBottom = Infinity;
+
+      for (const entry of entries) {
+        if (entry.isIntersecting) continue;
+
+        const distanceToTop = entry.rootBounds.top - entry.boundingClientRect.bottom;
+        const distanceToBottom = entry.boundingClientRect.top - entry.rootBounds.bottom;
+        if (distanceToTop >= 0 && distanceToTop < closestDistanceToTop) {
+          closestDistanceToTop = distanceToTop;
+          closestTargetToTop = entry.target;
+        }
+        if (distanceToBottom >= 0 && distanceToBottom <= closestDistanceToBottom) {
+          closestDistanceToBottom = distanceToBottom;
+          closestTargetToBottom = entry.target;
+        }
+      }
+
+      if (closestTargetToTop && closestDistanceToTop < closestDistanceToBottom) {
+        const id = $(closestTargetToTop).data("changeset")?.id;
+        if (id) {
+          OSM.router.replace(location.pathname + "?" + new URLSearchParams({ before: id }) + location.hash);
+        }
+      } else if (closestTargetToBottom) {
+        const id = $(closestTargetToBottom).data("changeset")?.id;
+        if (id) {
+          OSM.router.replace(location.pathname + "?" + new URLSearchParams({ after: id }) + location.hash);
+        }
+      }
+    }, { root: $("#sidebar")[0] });
+
+    $("#sidebar_content .changesets ol").children().each(function () {
+      changesetIntersectionObserver.observe(this);
+    });
+  }
+
   function highlightChangeset(id) {
     const layer = group.getLayer(id);
     if (layer) layer.setStyle({ fillOpacity: 0.3, color: "#FF6600", weight: 3 });
@@ -45,49 +104,134 @@ OSM.History = function (map) {
 
   function displayFirstChangesets(html) {
     $("#sidebar_content .changesets").html(html);
+
+    if (location.pathname === "/history") {
+      setPaginationMapHashes();
+    }
   }
 
-  function displayMoreChangesets(html) {
-    $("#sidebar_content .changeset_more").replaceWith(html);
-    const oldList = $("#sidebar_content .changesets ol").first();
-    const newList = oldList.next("ol");
-    newList.children().appendTo(oldList);
-    newList.remove();
+  function displayMoreChangesets(div, html) {
+    const sidebar = $("#sidebar")[0];
+    const previousScrollHeightMinusTop = sidebar.scrollHeight - sidebar.scrollTop;
+
+    const oldList = $("#sidebar_content .changesets ol");
+
+    div.replaceWith(html);
+
+    const prevNewList = oldList.prevAll("ol");
+    if (prevNewList.length) {
+      prevNewList.next(".changeset_more").remove();
+      prevNewList.children().prependTo(oldList);
+      prevNewList.remove();
+
+      // restore scroll position only if prepending
+      sidebar.scrollTop = sidebar.scrollHeight - previousScrollHeightMinusTop;
+    }
+
+    const nextNewList = oldList.nextAll("ol");
+    if (nextNewList.length) {
+      nextNewList.prev(".changeset_more").remove();
+      nextNewList.children().appendTo(oldList);
+      nextNewList.remove();
+    }
+
+    if (location.pathname === "/history") {
+      setPaginationMapHashes();
+    }
   }
 
-  function update() {
+  function setPaginationMapHashes() {
+    $("#sidebar .pagination a").each(function () {
+      $(this).prop("hash", OSM.formatHash({
+        center: map.getCenter(),
+        zoom: map.getZoom()
+      }));
+    });
+  }
+
+  function loadFirstChangesets() {
     const data = new URLSearchParams();
 
+    disableChangesetIntersectionObserver();
+
     if (location.pathname === "/history") {
-      data.set("bbox", map.getBounds().wrap().toBBoxString());
+      setBboxFetchData(data);
       const feedLink = $("link[type=\"application/atom+xml\"]"),
             feedHref = feedLink.attr("href").split("?")[0];
       feedLink.attr("href", feedHref + "?" + data);
     }
 
-    data.set("list", "1");
+    setListFetchData(data, location);
 
     fetch(location.pathname + "?" + data)
       .then(response => response.text())
       .then(function (html) {
         displayFirstChangesets(html);
+        enableChangesetIntersectionObserver();
+
+        if (data.has("before")) {
+          const [firstItem] = $("#sidebar_content .changesets ol").children().first();
+          firstItem?.scrollIntoView();
+        } else if (data.has("after")) {
+          const [lastItem] = $("#sidebar_content .changesets ol").children().last();
+          lastItem?.scrollIntoView(false);
+        } else {
+          const [sidebar] = $("#sidebar");
+          sidebar.scrollTop = 0;
+        }
+
         updateMap();
       });
   }
 
-  function loadMore(e) {
+  function loadMoreChangesets(e) {
     e.preventDefault();
     e.stopPropagation();
 
     const div = $(this).parents(".changeset_more");
 
-    $(this).hide();
-    div.find(".loader").show();
+    div.find(".pagination").addClass("invisible");
+    div.find("[hidden]").prop("hidden", false);
 
-    $.get($(this).attr("href"), function (html) {
-      displayMoreChangesets(html);
-      updateMap();
-    });
+    const data = new URLSearchParams();
+
+    if (location.pathname === "/history") {
+      setBboxFetchData(data);
+    }
+
+    const url = new URL($(this).attr("href"), location);
+    setListFetchData(data, url);
+
+    fetch(url.pathname + "?" + data)
+      .then(response => response.text())
+      .then(function (html) {
+        displayMoreChangesets(div, html);
+        enableChangesetIntersectionObserver();
+
+        updateMap();
+      });
+  }
+
+  function setBboxFetchData(data) {
+    data.set("bbox", map.getBounds().wrap().toBBoxString());
+  }
+
+  function setListFetchData(data, url) {
+    const params = new URLSearchParams(url.search);
+
+    data.set("list", "1");
+
+    if (params.has("before")) {
+      data.set("before", params.get("before"));
+    }
+    if (params.has("after")) {
+      data.set("after", params.get("after"));
+    }
+  }
+
+  function reloadChangesetsBecauseOfMapMovement() {
+    OSM.router.replace("/history" + window.location.hash);
+    loadFirstChangesets();
   }
 
   let changesets = [];
@@ -151,18 +295,19 @@ OSM.History = function (map) {
     map.addLayer(group);
 
     if (location.pathname === "/history") {
-      map.on("moveend", update);
+      map.on("moveend", reloadChangesetsBecauseOfMapMovement);
     }
 
     map.on("zoomend", updateBounds);
 
-    update();
+    loadFirstChangesets();
   };
 
   page.unload = function () {
     map.removeLayer(group);
-    map.off("moveend", update);
+    map.off("moveend", reloadChangesetsBecauseOfMapMovement);
     map.off("zoomend", updateBounds);
+    disableChangesetIntersectionObserver();
   };
 
   return page;