OSM.HistoryChangesetsLayer = L.FeatureGroup.extend({
- _changesets: [],
+ _changesets: new Map,
+
+ _getChangesetStyle: function ({ isHighlighted, sidebarRelativePosition }) {
+ let className;
+
+ if (sidebarRelativePosition > 0) {
+ className = "changeset-above-sidebar-viewport";
+ } else if (sidebarRelativePosition < 0) {
+ className = "changeset-below-sidebar-viewport";
+ } else {
+ className = "changeset-in-sidebar-viewport";
+ }
+ if (isHighlighted) {
+ className += " changeset-highlighted";
+ }
+
+ return {
+ weight: isHighlighted ? 3 : 2,
+ color: "var(--changeset-border-color)",
+ fillColor: "var(--changeset-fill-color)",
+ fillOpacity: isHighlighted ? 0.3 : 0,
+ className
+ };
+ },
+
+ _updateChangesetStyle: function (changeset) {
+ const rect = this.getLayer(changeset.id);
+ if (!rect) return;
+
+ const style = this._getChangesetStyle(changeset);
+ rect.setStyle(style);
+ // setStyle doesn't update css classes: https://github.com/leaflet/leaflet/issues/2662
+ rect._path.classList.value = style.className;
+ rect._path.classList.add("leaflet-interactive");
+ },
updateChangesets: function (map, changesets) {
- this._changesets = changesets;
+ this._changesets = new Map(changesets.map(changeset => [changeset.id, changeset]));
this.updateChangesetShapes(map);
},
updateChangesetShapes: function (map) {
- this.clearLayers();
-
- for (const changeset of this._changesets) {
+ for (const changeset of this._changesets.values()) {
const bottomLeft = map.project(L.latLng(changeset.bbox.minlat, changeset.bbox.minlon)),
topRight = map.project(L.latLng(changeset.bbox.maxlat, changeset.bbox.maxlon)),
width = topRight.x - bottomLeft.x,
map.unproject(topRight));
}
- this._changesets.sort(function (a, b) {
- return b.bounds.getSize() - a.bounds.getSize();
- });
-
this.updateChangesetLocations(map);
-
- for (const changeset of this._changesets) {
- const rect = L.rectangle(changeset.bounds,
- { weight: 2, color: "#FF9500", opacity: 1, fillColor: "#FFFFAF", fillOpacity: 0 });
- rect.id = changeset.id;
- rect.addTo(this);
- }
+ this.reorderChangesets();
},
updateChangesetLocations: function (map) {
const mapCenterLng = map.getCenter().lng;
- for (const changeset of this._changesets) {
+ for (const changeset of this._changesets.values()) {
const changesetSouthWest = changeset.bounds.getSouthWest();
const changesetNorthEast = changeset.bounds.getNorthEast();
const changesetCenterLng = (changesetSouthWest.lng + changesetNorthEast.lng) / 2;
}
},
- highlightChangeset: function (id) {
- this.getLayer(id)?.setStyle({ fillOpacity: 0.3, color: "#FF6600", weight: 3 });
+ reorderChangesets: function () {
+ const changesetEntries = [...this._changesets];
+ changesetEntries.sort(([, a], [, b]) => {
+ const aInViewport = !a.sidebarRelativePosition;
+ const bInViewport = !b.sidebarRelativePosition;
+ if (aInViewport !== bInViewport) return aInViewport - bInViewport;
+ return b.bounds.getSize() - a.bounds.getSize();
+ });
+ this._changesets = new Map(changesetEntries);
+
+ this.clearLayers();
+
+ for (const changeset of this._changesets.values()) {
+ delete changeset.isHighlighted;
+ const rect = L.rectangle(changeset.bounds, this._getChangesetStyle(changeset));
+ rect.id = changeset.id;
+ rect.addTo(this);
+ }
},
- unHighlightChangeset: function (id) {
- this.getLayer(id)?.setStyle({ fillOpacity: 0, color: "#FF9500", weight: 2 });
+ toggleChangesetHighlight: function (id, state) {
+ const changeset = this._changesets.get(id);
+ if (!changeset) return;
+
+ changeset.isHighlighted = state;
+ this._updateChangesetStyle(changeset);
+ },
+
+ setChangesetSidebarRelativePosition: function (id, state) {
+ const changeset = this._changesets.get(id);
+ if (!changeset) return;
+
+ changeset.sidebarRelativePosition = state;
+ this._updateChangesetStyle(changeset);
},
getLayerId: function (layer) {
$("#sidebar_content")
.on("click", ".changeset_more a", loadMoreChangesets)
.on("mouseover", "[data-changeset]", function () {
- highlightChangeset($(this).data("changeset").id);
+ toggleChangesetHighlight($(this).data("changeset").id, true);
})
.on("mouseout", "[data-changeset]", function () {
- unHighlightChangeset($(this).data("changeset").id);
+ toggleChangesetHighlight($(this).data("changeset").id, false);
});
const changesetsLayer = new OSM.HistoryChangesetsLayer()
.on("mouseover", function (e) {
- highlightChangeset(e.layer.id);
+ toggleChangesetHighlight(e.layer.id, true);
})
.on("mouseout", function (e) {
- unHighlightChangeset(e.layer.id);
+ toggleChangesetHighlight(e.layer.id, false);
})
.on("click", function (e) {
clickChangeset(e.layer.id, e.originalEvent);
disableChangesetIntersectionObserver();
if (!window.IntersectionObserver) return;
- let ignoreIntersectionEvents = true;
+ let keepInitialLocation = 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 id = $(entry.target).data("changeset")?.id;
+
+ if (entry.isIntersecting) {
+ if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, 0);
+ continue;
+ }
const distanceToTop = entry.rootBounds.top - entry.boundingClientRect.bottom;
const distanceToBottom = entry.boundingClientRect.top - entry.rootBounds.bottom;
+
+ if (id) changesetsLayer.setChangesetSidebarRelativePosition(id, distanceToTop >= 0 ? 1 : -1);
+
if (distanceToTop >= 0 && distanceToTop < closestDistanceToTop) {
closestDistanceToTop = distanceToTop;
closestTargetToTop = entry.target;
}
}
+ changesetsLayer.reorderChangesets();
+
+ if (keepInitialLocation) {
+ keepInitialLocation = false;
+ return;
+ }
+
if (closestTargetToTop && closestDistanceToTop < closestDistanceToBottom) {
const id = $(closestTargetToTop).data("changeset")?.id;
if (id) {
});
}
- function highlightChangeset(id) {
- changesetsLayer.highlightChangeset(id);
- $("#changeset_" + id).addClass("selected");
- }
-
- function unHighlightChangeset(id) {
- changesetsLayer.unHighlightChangeset(id);
- $("#changeset_" + id).removeClass("selected");
+ function toggleChangesetHighlight(id, state) {
+ changesetsLayer.toggleChangesetHighlight(id, state);
+ $("#changeset_" + id).toggleClass("selected", state);
}
function clickChangeset(id, e) {
function displayFirstChangesets(html) {
$("#sidebar_content .changesets").html(html);
+ $("#sidebar_content .changesets ol")
+ .before($("<div class='changeset-color-hint-bar opacity-75 sticky-top changeset-above-sidebar-viewport'>"))
+ .after($("<div class='changeset-color-hint-bar opacity-75 sticky-bottom changeset-below-sidebar-viewport'>"));
+
if (location.pathname === "/history") {
setPaginationMapHashes();
}