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);
 
  17   map.on("zoomstart", () => inZoom = true);
 
  18   map.on("zoomend", () => inZoom = false);
 
  20   const changesetsLayer = new OSM.HistoryChangesetsLayer()
 
  21     .on("mouseover", function (e) {
 
  23       toggleChangesetHighlight(e.layer.id, true);
 
  25     .on("mouseout", function (e) {
 
  27       toggleChangesetHighlight(e.layer.id, false);
 
  29     .on("click", function (e) {
 
  30       clickChangeset(e.layer.id, e.originalEvent);
 
  32     .on("requestscrolltochangeset", function (e) {
 
  33       const [item] = $(`#changeset_${e.id}`);
 
  34       item?.scrollIntoView({ block: "nearest", behavior: "smooth" });
 
  37   let changesetIntersectionObserver;
 
  39   function disableChangesetIntersectionObserver() {
 
  40     if (changesetIntersectionObserver) {
 
  41       changesetIntersectionObserver.disconnect();
 
  42       changesetIntersectionObserver = null;
 
  46   function enableChangesetIntersectionObserver() {
 
  47     disableChangesetIntersectionObserver();
 
  48     if (!window.IntersectionObserver) return;
 
  50     let keepInitialLocation = true;
 
  51     let itemsInViewport = $();
 
  53     changesetIntersectionObserver = new IntersectionObserver((entries) => {
 
  54       let closestTargetToTop,
 
  55           closestDistanceToTop = Infinity,
 
  56           closestTargetToBottom,
 
  57           closestDistanceToBottom = Infinity;
 
  59       for (const entry of entries) {
 
  60         const id = $(entry.target).data("changeset")?.id;
 
  62         if (entry.isIntersecting) {
 
  63           itemsInViewport = itemsInViewport.add(entry.target);
 
  64           if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, 0);
 
  67           itemsInViewport = itemsInViewport.not(entry.target);
 
  70         const distanceToTop = entry.rootBounds.top - entry.boundingClientRect.bottom;
 
  71         const distanceToBottom = entry.boundingClientRect.top - entry.rootBounds.bottom;
 
  73         if (distanceToTop >= 0 && distanceToTop < closestDistanceToTop) {
 
  74           closestDistanceToTop = distanceToTop;
 
  75           closestTargetToTop = entry.target;
 
  77         if (distanceToBottom >= 0 && distanceToBottom <= closestDistanceToBottom) {
 
  78           closestDistanceToBottom = distanceToBottom;
 
  79           closestTargetToBottom = entry.target;
 
  83       itemsInViewport.first().prevAll().each(function () {
 
  84         const id = $(this).data("changeset")?.id;
 
  85         if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, 1);
 
  87       itemsInViewport.last().nextAll().each(function () {
 
  88         const id = $(this).data("changeset")?.id;
 
  89         if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, -1);
 
  92       changesetsLayer.updateChangesetsOrder();
 
  94       if (keepInitialLocation) {
 
  95         keepInitialLocation = false;
 
  99       if (closestTargetToTop && closestDistanceToTop < closestDistanceToBottom) {
 
 100         const id = $(closestTargetToTop).data("changeset")?.id;
 
 102           OSM.router.replace(location.pathname + "?" + new URLSearchParams({ before: id }) + location.hash);
 
 104       } else if (closestTargetToBottom) {
 
 105         const id = $(closestTargetToBottom).data("changeset")?.id;
 
 107           OSM.router.replace(location.pathname + "?" + new URLSearchParams({ after: id }) + location.hash);
 
 110     }, { root: $("#sidebar")[0] });
 
 112     $("#sidebar_content .changesets ol").children().each(function () {
 
 113       changesetIntersectionObserver.observe(this);
 
 117   function toggleChangesetHighlight(id, state) {
 
 118     changesetsLayer.toggleChangesetHighlight(id, state);
 
 119     $("#sidebar_content .changesets ol li").removeClass("selected");
 
 121       $("#changeset_" + id).addClass("selected");
 
 125   function clickChangeset(id, e) {
 
 126     $("#changeset_" + id).find("a.changeset_id").simulate("click", e);
 
 129   function displayFirstChangesets(html) {
 
 130     $("#sidebar_content .changesets").html(html);
 
 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'>"));
 
 136     if (location.pathname === "/history") {
 
 137       setPaginationMapHashes();
 
 141   function displayMoreChangesets(div, html) {
 
 142     const sidebar = $("#sidebar")[0];
 
 143     const previousScrollHeightMinusTop = sidebar.scrollHeight - sidebar.scrollTop;
 
 145     const oldList = $("#sidebar_content .changesets ol");
 
 147     div.replaceWith(html);
 
 149     const prevNewList = oldList.prevAll("ol");
 
 150     if (prevNewList.length) {
 
 151       prevNewList.next(".changeset_more").remove();
 
 152       prevNewList.children().prependTo(oldList);
 
 153       prevNewList.remove();
 
 155       // restore scroll position only if prepending
 
 156       sidebar.scrollTop = sidebar.scrollHeight - previousScrollHeightMinusTop;
 
 159     const nextNewList = oldList.nextAll("ol");
 
 160     if (nextNewList.length) {
 
 161       nextNewList.prev(".changeset_more").remove();
 
 162       nextNewList.children().appendTo(oldList);
 
 163       nextNewList.remove();
 
 166     if (location.pathname === "/history") {
 
 167       setPaginationMapHashes();
 
 171   function setPaginationMapHashes() {
 
 172     $("#sidebar .pagination a").each(function () {
 
 173       $(this).prop("hash", OSM.formatHash(map));
 
 177   function loadFirstChangesets() {
 
 178     const data = new URLSearchParams();
 
 180     disableChangesetIntersectionObserver();
 
 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);
 
 189     setListFetchData(data, location);
 
 191     fetch(location.pathname + "?" + data)
 
 192       .then(response => response.text())
 
 193       .then(function (html) {
 
 194         displayFirstChangesets(html);
 
 195         enableChangesetIntersectionObserver();
 
 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);
 
 204           const [sidebar] = $("#sidebar");
 
 205           sidebar.scrollTop = 0;
 
 212   function loadMoreChangesets(e) {
 
 216     const div = $(this).parents(".changeset_more");
 
 218     div.find(".pagination").addClass("invisible");
 
 219     div.find("[hidden]").prop("hidden", false);
 
 221     const data = new URLSearchParams();
 
 223     if (location.pathname === "/history") {
 
 224       setBboxFetchData(data);
 
 227     const url = new URL($(this).attr("href"), location);
 
 228     setListFetchData(data, url);
 
 230     fetch(url.pathname + "?" + data)
 
 231       .then(response => response.text())
 
 232       .then(function (html) {
 
 233         displayMoreChangesets(div, html);
 
 234         enableChangesetIntersectionObserver();
 
 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));
 
 247     if (sw.lat >= swClamped.lat || ne.lat <= neClamped.lat || ne.lng - sw.lng < 360) {
 
 248       data.set("bbox", map.getBounds().toBBoxString());
 
 252   function setListFetchData(data, url) {
 
 253     const params = new URLSearchParams(url.search);
 
 255     data.set("list", "1");
 
 257     if (params.has("before")) {
 
 258       data.set("before", params.get("before"));
 
 260     if (params.has("after")) {
 
 261       data.set("after", params.get("after"));
 
 265   function moveEndListener() {
 
 266     if (location.pathname === "/history") {
 
 267       OSM.router.replace("/history" + window.location.hash);
 
 268       loadFirstChangesets();
 
 270       $("#sidebar_content .changesets ol li").removeClass("selected");
 
 271       changesetsLayer.updateChangesetsGeometry(map);
 
 275   function zoomEndListener() {
 
 276     $("#sidebar_content .changesets ol li").removeClass("selected");
 
 277     changesetsLayer.updateChangesetsGeometry(map);
 
 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;
 
 287     changesetsLayer.updateChangesets(map, changesets);
 
 289     if (location.pathname !== "/history") {
 
 290       const bounds = changesetsLayer.getBounds();
 
 291       if (bounds.isValid()) map.fitBounds(bounds);
 
 295   page.pushstate = page.popstate = function (path) {
 
 296     OSM.loadSidebarContent(path, page.load);
 
 299   page.load = function () {
 
 300     map.addLayer(changesetsLayer);
 
 301     map.on("moveend", moveEndListener);
 
 302     map.on("zoomend", zoomEndListener);
 
 303     loadFirstChangesets();
 
 306   page.unload = function () {
 
 307     map.removeLayer(changesetsLayer);
 
 308     map.off("moveend", moveEndListener);
 
 309     map.off("zoomend", zoomEndListener);
 
 310     disableChangesetIntersectionObserver();