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 = () => L.latLng($contextMenu.data("lat"), $contextMenu.data("lng"));
25 const croppedLatLng = () => OSM.cropLocation(latLngFromContext(), map.getZoom());
27 const routeWithLatLon = (path, extraParams = {}) => {
28 const { lat, lng } = croppedLatLng();
29 OSM.router.route(`${path}?` + new URLSearchParams({ lat, lon: lng, ...extraParams }));
32 const contextmenuItems = [
34 id: "menu-action-directions-from",
36 text: OSM.i18n.t("javascripts.context.directions_from"),
38 const { lat, lng } = croppedLatLng();
39 const params = new URLSearchParams({
40 from: `${lat},${lng}`,
41 to: getDirectionsCoordinates($("#route_to"))
43 OSM.router.route(`/directions?${params}`);
47 id: "menu-action-directions-to",
49 text: OSM.i18n.t("javascripts.context.directions_to"),
51 const { lat, lng } = croppedLatLng();
52 const params = new URLSearchParams({
53 from: getDirectionsCoordinates($("#route_from")),
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);
103 OSM.ContextMenu = class {
104 constructor(map, $element) {
106 this._$element = $element;
107 this._popperInstance = null;
109 const hide = this.hide.bind(this);
110 this._map.on("click", hide);
111 this._map.on("movestart", hide);
112 $(document).on("click", (e) => {
113 if (!$(e.target).closest(this._$element).length) {
120 e.originalEvent.preventDefault();
121 e.originalEvent.stopPropagation();
124 this._$element.removeClass("d-none");
125 this._updatePopper(e);
127 this._$element.data("lat", e.latlng.lat);
128 this._$element.data("lng", e.latlng.lng);
132 this._$element.addClass("d-none");
133 if (this._popperInstance) {
134 this._popperInstance.destroy();
135 this._popperInstance = null;
140 const getVirtualReference = (x, y) => ({
141 getBoundingClientRect: () => ({
142 width: 0, height: 0, top: y, left: x, right: x, bottom: y
146 if (this._popperInstance) {
147 this._popperInstance.destroy();
148 this._popperInstance = null;
151 const virtualReference = getVirtualReference(
152 e.originalEvent.clientX,
153 e.originalEvent.clientY
156 this._popperInstance = Popper.createPopper(virtualReference, this._$element.find(".dropdown-menu")[0], {
157 placement: "bottom-start",
158 strategy: "absolute",
167 name: "preventOverflow",
168 options: { boundary: document.getElementById("map") }
173 fallbackPlacements: ["top-start", "bottom-end", "top-end"]
181 const $menuList = $("<ul>").addClass("dropdown-menu show shadow cm_dropdown_menu");
183 items.forEach((item) => {
184 const $menuItem = item.separator ?
185 this._createSeparator() :
186 this._createMenuItem(item);
187 $menuList.append($menuItem);
190 this._$element.empty().append($menuList);
193 _createMenuItem(item) {
194 const $icon = $("<i>").addClass(`bi ${item.icon}`).prop("ariaHidden", true);
195 const $label = $("<span>").text(item.text);
197 const $link = $("<a>")
198 .addClass("dropdown-item d-flex align-items-center gap-3")
199 .attr({ href: "#", id: item.id })
200 .append($icon, $label)
201 .on("click", (e) => {
207 return $("<li>").append($link);
211 return $("<li>").append($("<hr>").addClass("dropdown-divider"));