]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/index/history-changesets-layer.js
Update community forum icon
[rails.git] / app / assets / javascripts / index / history-changesets-layer.js
1 OSM.HistoryChangesetBboxLayer = L.FeatureGroup.extend({
2   getLayerId: function (layer) {
3     return layer.id;
4   },
5
6   addChangesetLayer: function (changeset) {
7     const style = this._getChangesetStyle(changeset);
8     const anchor = $(L.SVG.create("a")).attr("href", $(`#changeset_${changeset.id} a.changeset_id`).attr("href"));
9     const rectangle = L.rectangle(changeset.bounds, {
10       ...style,
11       contextmenu: true,
12       contextmenuItems: [{
13         text: OSM.i18n.t("javascripts.context.scroll_to_changeset"),
14         callback: () => {
15           this.fire("requestscrolltochangeset", { id: changeset.id }, true);
16         },
17         index: 0
18       }, {
19         separator: true,
20         index: 1
21       }]
22     });
23     rectangle.id = changeset.id;
24     rectangle.on("add", function () {
25       $(this.getElement()).replaceWith(anchor).appendTo(anchor);
26     });
27     rectangle.on("remove", function () {
28       anchor.remove();
29     });
30     return this.addLayer(rectangle);
31   },
32
33   updateChangesetLayerBounds: function (changeset) {
34     this.getLayer(changeset.id)?.setBounds(changeset.bounds);
35   },
36
37   _getSidebarRelativeClassName: function ({ sidebarRelativePosition }) {
38     if (sidebarRelativePosition > 0) {
39       return "changeset-above-sidebar-viewport";
40     } else if (sidebarRelativePosition < 0) {
41       return "changeset-below-sidebar-viewport";
42     } else {
43       return "changeset-in-sidebar-viewport";
44     }
45   }
46 });
47
48 OSM.HistoryChangesetBboxAreaLayer = OSM.HistoryChangesetBboxLayer.extend({
49   _getChangesetStyle: function (changeset) {
50     return {
51       stroke: false,
52       fillOpacity: 0,
53       className: this._getSidebarRelativeClassName(changeset)
54     };
55   }
56 });
57
58 OSM.HistoryChangesetBboxOutlineLayer = OSM.HistoryChangesetBboxLayer.extend({
59   _getChangesetStyle: function (changeset) {
60     return {
61       weight: 4,
62       color: "var(--changeset-outline-color)",
63       fill: false,
64       className: this._getSidebarRelativeClassName(changeset)
65     };
66   }
67 });
68
69 OSM.HistoryChangesetBboxBorderLayer = OSM.HistoryChangesetBboxLayer.extend({
70   _getChangesetStyle: function (changeset) {
71     return {
72       weight: 2,
73       color: "var(--changeset-border-color)",
74       fill: false,
75       className: this._getSidebarRelativeClassName(changeset)
76     };
77   }
78 });
79
80 OSM.HistoryChangesetBboxHighlightAreaLayer = OSM.HistoryChangesetBboxLayer.extend({
81   _getChangesetStyle: function (changeset) {
82     return {
83       interactive: false,
84       stroke: false,
85       fillColor: "var(--changeset-fill-color)",
86       fillOpacity: 0.3,
87       className: this._getSidebarRelativeClassName(changeset)
88     };
89   }
90 });
91
92 OSM.HistoryChangesetBboxHighlightOutlineLayer = OSM.HistoryChangesetBboxLayer.extend({
93   _getChangesetStyle: function (changeset) {
94     return {
95       interactive: false,
96       weight: changeset.sidebarRelativePosition === 0 ? 8 : 6,
97       color: "var(--changeset-outline-color)",
98       fill: false,
99       className: this._getSidebarRelativeClassName(changeset) + " changeset-highlight-outline"
100     };
101   }
102 });
103
104 OSM.HistoryChangesetBboxHighlightBorderLayer = OSM.HistoryChangesetBboxLayer.extend({
105   _getChangesetStyle: function (changeset) {
106     return {
107       interactive: false,
108       weight: 4,
109       color: "var(--changeset-border-color)",
110       fill: false,
111       className: this._getSidebarRelativeClassName(changeset)
112     };
113   }
114 });
115
116 OSM.HistoryChangesetsLayer = L.FeatureGroup.extend({
117   updateChangesets: function (map, changesets) {
118     this._changesets = new Map(changesets.map(changeset => [changeset.id, changeset]));
119     this.updateChangesetsGeometry(map);
120   },
121
122   updateChangesetsGeometry: function (map) {
123     const changesetSizeLowerBound = 20, // Min width/height of changeset in pixels
124           mapViewExpansion = 2; // Half of bbox border+outline width in pixels
125
126     const mapViewCenterLng = map.getCenter().lng,
127           mapViewPixelBounds = map.getPixelBounds();
128
129     mapViewPixelBounds.min.x -= mapViewExpansion;
130     mapViewPixelBounds.min.y -= mapViewExpansion;
131     mapViewPixelBounds.max.x += mapViewExpansion;
132     mapViewPixelBounds.max.y += mapViewExpansion;
133
134     for (const changeset of this._changesets.values()) {
135       const changesetNorthWestLatLng = L.latLng(changeset.bbox.maxlat, changeset.bbox.minlon),
136             changesetSouthEastLatLng = L.latLng(changeset.bbox.minlat, changeset.bbox.maxlon),
137             changesetCenterLng = (changesetNorthWestLatLng.lng + changesetSouthEastLatLng.lng) / 2,
138             shiftInWorldCircumferences = Math.round((changesetCenterLng - mapViewCenterLng) / 360);
139
140       if (shiftInWorldCircumferences) {
141         changesetNorthWestLatLng.lng -= shiftInWorldCircumferences * 360;
142         changesetSouthEastLatLng.lng -= shiftInWorldCircumferences * 360;
143       }
144
145       const changesetMinCorner = map.project(changesetNorthWestLatLng),
146             changesetMaxCorner = map.project(changesetSouthEastLatLng),
147             changesetSizeX = changesetMaxCorner.x - changesetMinCorner.x,
148             changesetSizeY = changesetMaxCorner.y - changesetMinCorner.y;
149
150       if (changesetSizeX < changesetSizeLowerBound) {
151         changesetMinCorner.x -= (changesetSizeLowerBound - changesetSizeX) / 2;
152         changesetMaxCorner.x += (changesetSizeLowerBound - changesetSizeX) / 2;
153       }
154
155       if (changesetSizeY < changesetSizeLowerBound) {
156         changesetMinCorner.y -= (changesetSizeLowerBound - changesetSizeY) / 2;
157         changesetMaxCorner.y += (changesetSizeLowerBound - changesetSizeY) / 2;
158       }
159
160       changeset.bounds = L.latLngBounds(map.unproject(changesetMinCorner),
161                                         map.unproject(changesetMaxCorner));
162
163       const changesetPixelBounds = L.bounds(changesetMinCorner, changesetMaxCorner);
164
165       changeset.hasEdgesInMapView = changesetPixelBounds.overlaps(mapViewPixelBounds) &&
166                                     !changesetPixelBounds.contains(mapViewPixelBounds);
167     }
168
169     this.updateChangesetsOrder();
170   },
171
172   updateChangesetsOrder: function () {
173     const changesetEntries = [...this._changesets];
174     changesetEntries.sort(([, a], [, b]) => b.bounds.getSize() - a.bounds.getSize());
175     this._changesets = new Map(changesetEntries);
176
177     for (const layer of this._bboxLayers) {
178       layer.clearLayers();
179     }
180
181     for (const changeset of this._changesets.values()) {
182       if (changeset.sidebarRelativePosition !== 0 && changeset.hasEdgesInMapView) {
183         this._areaLayer.addChangesetLayer(changeset);
184       }
185     }
186
187     for (const changeset of this._changesets.values()) {
188       if (changeset.sidebarRelativePosition === 0 && changeset.hasEdgesInMapView) {
189         this._areaLayer.addChangesetLayer(changeset);
190       }
191     }
192
193     for (const changeset of this._changesets.values()) {
194       if (changeset.sidebarRelativePosition !== 0) {
195         this._borderLayer.addChangesetLayer(changeset);
196       }
197     }
198
199     for (const changeset of this._changesets.values()) {
200       if (changeset.sidebarRelativePosition === 0) {
201         this._outlineLayer.addChangesetLayer(changeset);
202       }
203     }
204
205     for (const changeset of this._changesets.values()) {
206       if (changeset.sidebarRelativePosition === 0) {
207         this._borderLayer.addChangesetLayer(changeset);
208       }
209     }
210   },
211
212   toggleChangesetHighlight: function (id, state) {
213     const changeset = this._changesets.get(id);
214     if (!changeset) return;
215
216     this._highlightAreaLayer.clearLayers();
217     this._highlightOutlineLayer.clearLayers();
218     this._highlightBorderLayer.clearLayers();
219
220     if (state) {
221       this._highlightAreaLayer.addChangesetLayer(changeset);
222       this._highlightOutlineLayer.addChangesetLayer(changeset);
223       this._highlightBorderLayer.addChangesetLayer(changeset);
224     }
225   },
226
227   setChangesetSidebarRelativePosition: function (id, state) {
228     const changeset = this._changesets.get(id);
229     if (!changeset) return;
230     changeset.sidebarRelativePosition = state;
231   }
232 });
233
234 OSM.HistoryChangesetsLayer.addInitHook(function () {
235   this._changesets = new Map;
236
237   this._bboxLayers = [
238     this._areaLayer = new OSM.HistoryChangesetBboxAreaLayer().addTo(this),
239     this._outlineLayer = new OSM.HistoryChangesetBboxOutlineLayer().addTo(this),
240     this._borderLayer = new OSM.HistoryChangesetBboxBorderLayer().addTo(this),
241     this._highlightAreaLayer = new OSM.HistoryChangesetBboxHighlightAreaLayer().addTo(this),
242     this._highlightOutlineLayer = new OSM.HistoryChangesetBboxHighlightOutlineLayer().addTo(this),
243     this._highlightBorderLayer = new OSM.HistoryChangesetBboxHighlightBorderLayer().addTo(this)
244   ];
245 });