]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/user.js
Migrate user location edit page to MapLibre
[rails.git] / app / assets / javascripts / user.js
1 //= require maplibre.map
2 //= require maplibre.i18n
3 //= require maplibre.combinedcontrolgroup
4 //= require ./home_location_name-endpoint
5
6 $(function () {
7   const defaultHomeZoom = 11;
8   let map, marker, deleted_lat, deleted_lon, deleted_home_name, homeLocationNameGeocoder, savedLat, savedLon;
9
10   if ($("#social_links").length) {
11     $("#add-social-link").on("click", function () {
12       const newIndex = -Date.now();
13
14       $("#social_links template").contents().clone().appendTo("#social_links")
15         .find("input").attr("name", `user[social_links_attributes][${newIndex}][url]`).trigger("focus");
16
17       renumberSocialLinks();
18     });
19
20     $("#social_links").on("click", "button", function () {
21       const row = $(this).closest(".row");
22       const [destroyCheckbox] = row.find(".social_link_destroy input[type='checkbox']");
23
24       if (destroyCheckbox) {
25         destroyCheckbox.checked = true;
26         row.addClass("d-none");
27       } else {
28         row.remove();
29       }
30
31       renumberSocialLinks();
32     });
33
34     $(".social_link_destroy input[type='checkbox']:checked").each(function () {
35       $(this).closest(".row").addClass("d-none");
36     });
37
38     renumberSocialLinks();
39   }
40
41   function renumberSocialLinks() {
42     $("#social_links .row:not(.d-none)").each(function (i) {
43       const inputLabel = OSM.i18n.t("javascripts.profile.social_link_n", { n: i + 1 });
44       const removeButtonLabel = OSM.i18n.t("javascripts.profile.remove_social_link_n", { n: i + 1 });
45
46       $(this).find("input[type='text']")
47         .attr("placeholder", inputLabel)
48         .attr("aria-label", inputLabel);
49       $(this).find("button")
50         .attr("title", removeButtonLabel);
51     });
52   }
53
54   if ($("#map").length) {
55     map = new maplibregl.Map({
56       container: "map",
57       style: OSM.MapLibre.Styles.Mapnik,
58       attributionControl: false,
59       locale: OSM.MapLibre.Locale,
60       rollEnabled: false,
61       dragRotate: false,
62       pitchWithRotate: false,
63       bearingSnap: 180,
64       maxPitch: 0,
65       center: OSM.home ? [OSM.home.lon, OSM.home.lat] : [0, 0],
66       zoom: OSM.home ? defaultHomeZoom : 0
67     });
68
69     savedLat = $("#home_lat").val();
70     savedLon = $("#home_lon").val();
71     homeLocationNameGeocoder = OSM.HomeLocationNameGeocoder($("#home_lat"), $("#home_lon"), $("#home_location_name"));
72
73     const position = $("html").attr("dir") === "rtl" ? "top-left" : "top-right";
74     const navigationControl = new maplibregl.NavigationControl({ showCompass: false });
75     const geolocateControl = new maplibregl.GeolocateControl({
76       positionOptions: {
77         enableHighAccuracy: true
78       },
79       trackUserLocation: true
80     });
81     map.addControl(new OSM.MapLibre.CombinedControlGroup([navigationControl, geolocateControl]), position);
82     map.touchZoomRotate.disableRotation();
83     map.keyboard.disableRotation();
84
85     marker = OSM.MapLibre.getMarker({});
86
87     if (OSM.home) {
88       marker.setLngLat([OSM.home.lon, OSM.home.lat]);
89       marker.addTo(map);
90     }
91
92     map.on("click", function (e) {
93       if (!$("#updatehome").is(":checked")) return;
94
95       const [lat, lon] = OSM.cropLocation(e.lngLat, map.getZoom() + 1);
96
97       $("#home_lat").val(lat);
98       $("#home_lon").val(lon);
99
100       clearDeletedText();
101       respondToHomeLatLonUpdate();
102     });
103     map.on("moveend", function () {
104       const lat = $("#home_lat").val().trim(),
105             lon = $("#home_lon").val().trim();
106       let location;
107
108       try {
109         if (lat && lon) {
110           location = new maplibregl.LngLat(lon, lat);
111         }
112       } catch (error) {
113         // keep location undefined
114       }
115
116       $("#home_show").prop("disabled", !location || isCloseEnoughToMapCenter(location));
117     });
118
119     $("#home_lat, #home_lon").on("input", function () {
120       clearDeletedText();
121       respondToHomeLatLonUpdate();
122     });
123
124     $("#home_location_name").on("input", function () {
125       homeLocationNameGeocoder.autofill = false;
126       clearDeletedText();
127
128       respondToHomeLatLonUpdate(false);
129     });
130
131     $("#home_show").click(function () {
132       const lat = $("#home_lat").val(),
133             lon = $("#home_lon").val();
134
135       map.flyTo({ center: [lon, lat], zoom: defaultHomeZoom });
136     });
137
138     $("#home_delete").click(function () {
139       const lat = $("#home_lat").val(),
140             lon = $("#home_lon").val(),
141             locationName = $("#home_location_name").val();
142
143       $("#home_lat, #home_lon, #home_location_name").val("");
144       deleted_lat = lat;
145       deleted_lon = lon;
146       deleted_home_name = locationName;
147
148       respondToHomeLatLonUpdate(false);
149       $("#home_undelete").trigger("focus");
150     });
151
152     $("#home_undelete").click(function () {
153       $("#home_lat").val(deleted_lat);
154       $("#home_lon").val(deleted_lon);
155       $("#home_location_name").val(deleted_home_name);
156       clearDeletedText();
157
158       respondToHomeLatLonUpdate(false);
159       $("#home_delete").trigger("focus");
160     });
161   }
162
163   function respondToHomeLatLonUpdate(updateLocationName = true) {
164     const lat = $("#home_lat").val().trim(),
165           lon = $("#home_lon").val().trim(),
166           locationName = $("#home_location_name").val().trim();
167     let location;
168
169     try {
170       if (lat && lon) {
171         location = new maplibregl.LngLat(lon, lat);
172         if (updateLocationName) {
173           if (savedLat && savedLon && $("#home_location_name").val().trim()) {
174             homeLocationNameGeocoder.updateHomeLocationName(false, savedLat, savedLon, () => {
175               savedLat = savedLon = null;
176               homeLocationNameGeocoder.updateHomeLocationName();
177             });
178           } else {
179             savedLat = savedLon = null;
180             homeLocationNameGeocoder.updateHomeLocationName();
181           }
182         }
183       }
184       $("#home_lat, #home_lon").removeClass("is-invalid");
185     } catch (error) {
186       if (lat && isNaN(lat)) $("#home_lat").addClass("is-invalid");
187       if (lon && isNaN(lon)) $("#home_lon").addClass("is-invalid");
188     }
189
190     $("#home_message").toggleClass("invisible", Boolean(location));
191     $("#home_show").prop("hidden", !location);
192     $("#home_delete").prop("hidden", !location && !locationName);
193     $("#home_undelete").prop("hidden", !(
194       (!location || !locationName) &&
195       ((deleted_lat && deleted_lon) || deleted_home_name)
196     ));
197     if (location) {
198       marker.setLngLat([lon, lat]);
199       marker.addTo(map);
200       map.panTo([lon, lat]);
201     } else {
202       marker.remove();
203     }
204   }
205
206   function isCloseEnoughToMapCenter(location) {
207     const inputPt = map.project(location),
208           centerPt = map.project(map.getCenter());
209
210     return Math.sqrt(Math.pow(centerPt.x - inputPt.x, 2) + Math.pow(centerPt.y - inputPt.y, 2)) < 10;
211   }
212
213   function clearDeletedText() {
214     deleted_lat = null;
215     deleted_lon = null;
216     deleted_home_name = null;
217   }
218
219   $("input#user_avatar").on("change", function () {
220     $("#user_avatar_action_new").prop("checked", true);
221   });
222
223   $("#content.user_confirm").each(function () {
224     $(this).hide();
225     $(this).find("#confirm").submit();
226   });
227
228   $("input[name=legale]").change(function () {
229     $("#contributorTerms").html("<div class='spinner-border' role='status'><span class='visually-hidden'>" + OSM.i18n.t("browse.start_rjs.loading") + "</span></div>");
230     fetch(this.dataset.url, { headers: { "x-requested-with": "XMLHttpRequest" } })
231       .then(r => r.text())
232       .then(html => { $("#contributorTerms").html(html); });
233   });
234
235   $("#read_ct").on("click", function () {
236     $("#continue").prop("disabled", !($(this).prop("checked") && $("#read_tou").prop("checked")));
237   });
238
239   $("#read_tou").on("click", function () {
240     $("#continue").prop("disabled", !($(this).prop("checked") && $("#read_ct").prop("checked")));
241   });
242 });