1 OSM.initializations.push(function (map) {
2 const $contextMenu = $("#map-context-menu");
3 map.osm_contextmenu = new OSM.ContextMenu(map, $contextMenu);
5 const toggleMenuItem = ($element, enable) => {
6 $element.toggleClass("disabled", !enable)
7 .attr("aria-disabled", enable ? null : "true");
10 const updateContextMenuState = () => {
11 const zoom = map.getZoom();
12 toggleMenuItem($("#menu-action-add-note"), zoom >= 12);
13 toggleMenuItem($("#menu-action-query-features"), zoom >= 14);
16 const getDirectionsCoordinates = ($input) => {
17 const lat = $input.attr("data-lat");
18 const lon = $input.attr("data-lon");
19 if (lat && lon) return `${lat},${lon}`;
23 const latLngFromContext = () =>
24 L.latLng($contextMenu.data("lat"), $contextMenu.data("lng"));
26 const croppedLatLon = () =>
27 OSM.cropLocation(latLngFromContext(), map.getZoom());
29 const routeWithLatLon = (path, extraParams = {}) => {
30 const [lat, lon] = croppedLatLon();
31 OSM.router.route(`${path}?` + new URLSearchParams({ lat, lon, ...extraParams }));
34 const contextmenuItems = [
36 id: "menu-action-directions-from",
38 text: OSM.i18n.t("javascripts.context.directions_from"),
40 const params = new URLSearchParams({
41 from: croppedLatLon().join(","),
42 to: getDirectionsCoordinates($("#route_to"))
44 OSM.router.route(`/directions?${params}`);
48 id: "menu-action-directions-to",
50 text: OSM.i18n.t("javascripts.context.directions_to"),
52 const params = new URLSearchParams({
53 from: getDirectionsCoordinates($("#route_from")),
54 to: croppedLatLon().join(",")
56 OSM.router.route(`/directions?${params}`);
63 id: "menu-action-add-note",
64 icon: "bi-chat-square-text",
65 text: OSM.i18n.t("javascripts.context.add_note"),
66 callback: () => routeWithLatLon("/note/new")
72 id: "menu-action-show-address",
74 text: OSM.i18n.t("javascripts.context.show_address"),
75 callback: () => routeWithLatLon("/search", { zoom: map.getZoom() })
78 id: "menu-action-query-features",
79 icon: "bi-question-lg",
80 text: OSM.i18n.t("javascripts.context.query_features"),
81 callback: () => routeWithLatLon("/query")
84 id: "menu-action-centre-map",
86 text: OSM.i18n.t("javascripts.context.centre_map"),
87 callback: () => map.panTo(latLngFromContext())
91 map.on("contextmenu", function (e) {
92 map.osm_contextmenu.show(e, contextmenuItems);
93 updateContextMenuState();
96 map.on("show-contextmenu", function (data) {
97 map.osm_contextmenu.show(data.event, data.items);
100 map.on("zoomend", updateContextMenuState);
104 constructor(map, $element) {
106 this._$element = $element;
107 this._popperInstance = null;
109 this._map.on("click movestart", this.hide, this);
110 $(document).on("click", (e) => {
111 if (!$(e.target).closest(this._$element).length) {
118 e.originalEvent.preventDefault();
119 e.originalEvent.stopPropagation();
122 this._$element.removeClass("d-none");
123 this._updatePopper(e);
125 this._$element.data("lat", e.latlng.lat);
126 this._$element.data("lng", e.latlng.lng);
130 this._$element.addClass("d-none");
131 if (this._popperInstance) {
132 this._popperInstance.destroy();
133 this._popperInstance = null;
138 const getVirtualReference = (x, y) => ({
139 getBoundingClientRect: () => ({
140 width: 0, height: 0, top: y, left: x, right: x, bottom: y
144 if (this._popperInstance) {
145 this._popperInstance.destroy();
146 this._popperInstance = null;
149 const virtualReference = getVirtualReference(
150 e.originalEvent.clientX,
151 e.originalEvent.clientY
154 this._popperInstance = Popper.createPopper(virtualReference, this._$element.find(".dropdown-menu")[0], {
155 placement: "bottom-start",
156 strategy: "absolute",
165 name: "preventOverflow",
166 options: { boundary: document.getElementById("map") }
171 fallbackPlacements: ["top-start", "bottom-end", "top-end"]
179 const $menuList = $("<ul>").addClass("dropdown-menu show shadow cm_dropdown_menu");
181 items.forEach((item) => {
182 const $menuItem = item.separator ?
183 this._createSeparator() :
184 this._createMenuItem(item);
185 $menuList.append($menuItem);
188 this._$element.empty().append($menuList);
191 _createMenuItem(item) {
192 const $icon = $("<i>").addClass(`bi ${item.icon}`).prop("ariaHidden", true);
193 const $label = $("<span>").text(item.text);
195 const $link = $("<a>")
196 .addClass("dropdown-item d-flex align-items-center gap-3")
197 .attr({ href: "#", id: item.id })
198 .append($icon, $label)
199 .on("click", (e) => {
205 return $("<li>").append($link);
209 return $("<li>").append($("<hr>").addClass("dropdown-divider"));
213 OSM.ContextMenu = ContextMenu;