diff --git a/app/assets/javascripts/darkswarm/controllers/registration/registration_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/registration/registration_controller.js.coffee index 5145c05ea1..7d38de19da 100644 --- a/app/assets/javascripts/darkswarm/controllers/registration/registration_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/registration/registration_controller.js.coffee @@ -42,8 +42,17 @@ angular.module('Darkswarm').controller "RegistrationCtrl", ($scope, Registration $scope.toggleAddressConfirmed = -> $scope.addressConfirmed = !$scope.addressConfirmed if $scope.addressConfirmed + $scope.setLatLongIfUsingOpenStreetMap() $scope.enterprise.address.latitude = $scope.latLong.latitude $scope.enterprise.address.longitude = $scope.latLong.longitude else $scope.enterprise.address.latitude = null $scope.enterprise.address.longitude = null + + # When OpenStreetMaps is enabled the latitude and longitude are calculated via a Stimulus + # controller, so they need to be read from data properties to be accessible here. + $scope.setLatLongIfUsingOpenStreetMap = -> + openStreetMap = document.getElementById("open-street-map") + if !$scope.latLong && openStreetMap && openStreetMap.dataset.latitude && openStreetMap.dataset.longitude + $scope.latLong = { latitude: openStreetMap.dataset.latitude, longitude: openStreetMap.dataset.longitude } + diff --git a/app/helpers/map_helper.rb b/app/helpers/map_helper.rb index bc3b66c49c..5952187077 100644 --- a/app/helpers/map_helper.rb +++ b/app/helpers/map_helper.rb @@ -2,7 +2,9 @@ module MapHelper def using_google_maps? - ENV["GOOGLE_MAPS_API_KEY"].present? || google_maps_configured_with_geocoder_api_key? + !ContentConfig.open_street_map_enabled && ( + ENV["GOOGLE_MAPS_API_KEY"].present? || google_maps_configured_with_geocoder_api_key? + ) end private diff --git a/app/views/registration/steps/_details.html.haml b/app/views/registration/steps/_details.html.haml index 45fb0e56b7..eee82d4ee3 100644 --- a/app/views/registration/steps/_details.html.haml +++ b/app/views/registration/steps/_details.html.haml @@ -57,7 +57,10 @@ %span.error{ "ng-show": "details.state.$error.required && submitted" } = t(".state_field_error") - = render 'registration/steps/location_map' if using_google_maps? + - if using_google_maps? + = render 'registration/steps/location_map_google' + - elsif ContentConfig.open_street_map_enabled + = render 'registration/steps/location_map_osm' .row.buttons .small-12.columns diff --git a/app/views/registration/steps/_location_map.html.haml b/app/views/registration/steps/_location_map_google.html.haml similarity index 100% rename from app/views/registration/steps/_location_map.html.haml rename to app/views/registration/steps/_location_map_google.html.haml diff --git a/app/views/registration/steps/_location_map_osm.html.haml b/app/views/registration/steps/_location_map_osm.html.haml new file mode 100644 index 0000000000..c60ffa74f7 --- /dev/null +++ b/app/views/registration/steps/_location_map_osm.html.haml @@ -0,0 +1,18 @@ +%div{ data: { + controller: "open-street-map", + "open-street-map-default-latitude-value": ContentConfig.open_street_map_default_latitude, + "open-street-map-default-longitude-value": ContentConfig.open_street_map_default_longitude, + "open-street-map-provider-name-value": ContentConfig.open_street_map_provider_name, + "open-street-map-provider-options-value": ContentConfig.open_street_map_provider_options + } + } + .center + %input.button.primary{ data: { action: "click->open-street-map#locateAddress" }, type: "button", value: "{{'registration.steps.details.locate_address' | t}}" } + .center{ data: { "open-street-map-target": "dragPinNote" }, style: "display: none" } + %strong {{'registration.steps.details.drag_pin' | t}} + #open-street-map.map-container--registration + + .center{ data: { "open-street-map-target": "confirmAddressField" }, style: "display: none" } + .field + %input{ type: 'checkbox', id: 'confirm_address', name: 'confirm_address', "ng-click": 'toggleAddressConfirmed()' } + %label{ for: 'confirm_address' } {{'registration.steps.details.confirm_address' | t}} diff --git a/app/webpacker/controllers/open_street_map_controller.js b/app/webpacker/controllers/open_street_map_controller.js new file mode 100644 index 0000000000..0c5f983158 --- /dev/null +++ b/app/webpacker/controllers/open_street_map_controller.js @@ -0,0 +1,103 @@ +import { Controller } from "stimulus"; +import L from "leaflet"; +import LeafetProviders from "leaflet-providers"; +import { OpenStreetMapProvider } from 'leaflet-geosearch'; + +export default class extends Controller { + static targets = ["confirmAddressField", "dragPinNote"]; + static values = { + defaultLatitude: Number, + defaultLongitude: Number, + providerName: String, + providerOptions: Object + }; + + connect() { + this.zoomLevel = 6; + this.#displayMapWhenAtRegistrationDetailsStep(); + } + + disconnect() { + this.map.remove(); + } + + async locateAddress() { + const results = await this.provider.search({ query: this.#addressQuery() }); + if(results.length > 0) { + const result = results[0]; + this.#setLatitudeLongitude(result.y, result.x); + this.#addMarker(result.y, result.x); + this.map.setView([result.y, result.x], this.zoomLevel); + this.confirmAddressFieldTarget.style.display = "block"; + this.dragPinNoteTarget.style.display = "block"; + } + } + + #addressQuery() { + const stateField = document.getElementById("enterprise_state"); + const state = stateField.options[stateField.selectedIndex]?.label; + const countryField = document.getElementById("enterprise_country"); + const country = countryField.options[countryField.selectedIndex]?.label; + const city = document.getElementById("enterprise_city")?.value; + const zipcode = document.getElementById("enterprise_zipcode")?.value; + const addressLine1 = document.getElementById("enterprise_address")?.value; + const addressLine2 = document.getElementById("enterprise_address2")?.value; + + // If someone clicks the locate address on map button without filling in their address the + // geocoded address will not be very accurate so don't zoom in too close so it's easier for + // people to see where the marker is. + if(!addressLine1 && !city && !zipcode) { + this.zoomLevel = 6; + } else { + this.zoomLevel = 14; + } + + return [addressLine1, addressLine2, city, state, zipcode, country].filter((value) => !!value).join(", ") + } + + #addMarker(latitude, longitude) { + const icon = L.icon({ iconUrl: "/map_icons/map_003-producer-shop.svg" }); + this.marker = L.marker([latitude, longitude], { + draggable: true, + icon: icon, + }); + + this.marker.on("dragend", (event) => { + const position = event.target.getLatLng(); + this.#setLatitudeLongitude(position.lat, position.lng); + }); + + this.marker.addTo(this.map); + } + + #displayMap() { + this.map = L.map('open-street-map') + L.tileLayer.provider(this.providerNameValue, this.providerOptionsValue).addTo(this.map) + this.map.setView([this.defaultLatitudeValue, this.defaultLongitudeValue], this.zoomLevel) + this.provider = new OpenStreetMapProvider(); + } + + // The connect() method is called before the registration details step is visible, this + // causes the map tiles to render incorrectly. To fix this only display the map when the + // registration details step has been reached. + #displayMapWhenAtRegistrationDetailsStep() { + const observer = new IntersectionObserver( + ([intersectionObserverEntry]) => { + if(intersectionObserverEntry.target.offsetParent !== null) { + this.#displayMap(); + observer.disconnect() + } + }, + { threshold: [0] } + ); + observer.observe(document.getElementById("registration-details")); + } + + // The registration process uses Angular, set latitude and longitude data properties so the + // Angular RegistrationCtrl controller can read and add them to the parameters it uses to create + // new enterprises. + #setLatitudeLongitude(latitude, longitude) { + document.getElementById("open-street-map").dataset.latitude = latitude; + document.getElementById("open-street-map").dataset.longitude = longitude; + } +} diff --git a/package.json b/package.json index 6b92211887..9230d1faac 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,9 @@ "hotkeys-js": "^3.13.7", "jquery-ui": "1.13.3", "js-big-decimal": "^2.0.7", + "leaflet": "1.9.4", + "leaflet-geosearch": "3.11.1", + "leaflet-providers": "2.0.0", "moment": "^2.30.1", "mrujs": "^1.0.0", "select2": "^4.0.13", diff --git a/yarn.lock b/yarn.lock index 4500b9918a..25d86faf9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1112,6 +1112,13 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== +"@googlemaps/js-api-loader@^1.16.6": + version "1.16.6" + resolved "https://registry.yarnpkg.com/@googlemaps/js-api-loader/-/js-api-loader-1.16.6.tgz#c89970c94b55796d51746c092f0e52953994a171" + integrity sha512-V8p5W9DbPQx74jWUmyYJOerhiB4C+MHekaO0ZRmc6lrOYrvY7+syLhzOWpp55kqSPeNb+qbC2h8i69aLIX6krQ== + dependencies: + fast-deep-equal "^3.1.3" + "@hotwired/stimulus-webpack-helpers@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@hotwired/stimulus-webpack-helpers/-/stimulus-webpack-helpers-1.0.1.tgz#4cd74487adeca576c9865ac2b9fe5cb20cef16dd" @@ -3911,7 +3918,7 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -5707,6 +5714,24 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" +leaflet-geosearch@3.11.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/leaflet-geosearch/-/leaflet-geosearch-3.11.1.tgz#e35b1e156afef388bbb42543eb56502e9a633192" + integrity sha512-cILLFLmn8C3MtGhaafGqY7CuhUG2+bGyUgfPHgNgW6fs4EvYyiIO88nO2ZqR7Hy6Ba089haLkhODpZR+/fRrPQ== + optionalDependencies: + "@googlemaps/js-api-loader" "^1.16.6" + leaflet "^1.6.0" + +leaflet-providers@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/leaflet-providers/-/leaflet-providers-2.0.0.tgz#dfdab9ca2dccc57c79e1462bc3dd20f78910afcb" + integrity sha512-CWwKEnHd66Qsx0m4o5q5ZOa60s00B91pMxnlr4Y22msubfs7dhbZhdMIz8bvZQkrZqi67ppI1fsZRS6vtrLcOA== + +leaflet@1.9.4, leaflet@^1.6.0: + version "1.9.4" + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d" + integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"