diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 319397e5..9ed6887b 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -25,3 +25,4 @@ @import "components/profile_header"; @import "components/copyable_input"; @import "components/device_map"; +@import "components/map_location_picker"; diff --git a/app/assets/stylesheets/components/map_location_picker.scss b/app/assets/stylesheets/components/map_location_picker.scss new file mode 100644 index 00000000..6ab4f345 --- /dev/null +++ b/app/assets/stylesheets/components/map_location_picker.scss @@ -0,0 +1,4 @@ +.map-location-picker { + width: 100%; + aspect-ratio: 1.66; +} diff --git a/app/javascript/application.js b/app/javascript/application.js index eef4fb3e..2918fdf0 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,10 +1,12 @@ import * as $ from "jquery"; import {setupCopyableInputs} from "components/copyable_input"; import {setupDeviceMaps} from "components/device_map"; +import {setupMapLocationPickers} from "components/map_location_picker"; export default function setupApplication() { $(function() { setupCopyableInputs(); setupDeviceMaps(); + setupMapLocationPickers(); }); } diff --git a/app/javascript/components/device_map.js b/app/javascript/components/device_map.js index bd16c691..4b8b276d 100644 --- a/app/javascript/components/device_map.js +++ b/app/javascript/components/device_map.js @@ -19,7 +19,7 @@ export function setupDeviceMaps() { }); const map = L.map(element, { center: [latitude, longitude], - zoom: 13, + zoom: 7, attributionControl: false, zoomControl: false, scrollWheelZoom: false, diff --git a/app/javascript/components/map_location_picker.js b/app/javascript/components/map_location_picker.js new file mode 100644 index 00000000..be9577cc --- /dev/null +++ b/app/javascript/components/map_location_picker.js @@ -0,0 +1,95 @@ +import * as $ from "jquery"; +import L from 'leaflet'; +import 'leaflet-defaulticon-compatibility'; + +const DEFAULT_LATITUDE = 41.396767038690285; +const DEFAULT_LONGITUDE = 2.1943382543588137; + +class MapLocationPicker { + constructor(element) { + this.element = element + this.latitudeInput = $("#" + element.dataset["latitudeInputId"]); + this.longitudeInput = $("#" + element.dataset["longitudeInputId"]); + const markerUrl = element.dataset["markerUrl"]; + const markerShadowUrl = element.dataset["markerShadowUrl"]; + this.icon = L.icon({ + iconUrl: markerUrl, + shadowUrl: markerShadowUrl, + iconSize: [32, 40], + shadowSize: [51, 59], + iconAnchor: [16, 40], + shadowAnchor: [10, 59] + }); + this.map = L.map(this.element, { + center: this.defaultCenterLatLng(), + zoom: this.defaultZoom(), + attributionControl: false, + }); + L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.{ext}', { + minZoom: 0, + maxZoom: 20, + attribution: '© Stadia Maps © Stamen Design © OpenMapTiles © OpenStreetMap contributors', + ext: 'png' + }).addTo(this.map); + + if(this.getLatLng()) { + this.createMarker(); + } + + this.map.on('click', function(e) { + const latLng= e.latlng; + this.setLatLng(latLng.lat, latLng.lng); + if(this.marker) { + this.updateMarkerPosition(); + } else { + this.createMarker(); + } + }.bind(this)); + } + + defaultZoom() { + return this.getLatLng() ? 15 : 3; + } + + defaultCenterLatLng() { + return this.getLatLng() || [DEFAULT_LATITUDE, DEFAULT_LONGITUDE] + } + + getLatLng() { + const lat = this.latitudeInput.val(); + const lng = this.longitudeInput.val(); + if(lat && lng) { + return { "lat": lat, "lng": lng } + } + } + + setLatLng(lat, lng) { + this.latitudeInput.val(lat); + this.longitudeInput.val(lng); + } + + createMarker() { + const position = this.getLatLng(); + this.marker = L.marker([position.lat, position.lng], { icon: this.icon, draggable: true }).addTo(this.map); + this.marker.on('dragend', function(event) { + var position = event.target.getLatLng(); + this.setLatLng(position.lat, position.lng); + this.updateMarkerPosition(); + }.bind(this)); + this.map.panTo(new L.LatLng(position.lat, position.lng)); + } + + updateMarkerPosition() { + const position = this.getLatLng(); + this.marker.setLatLng(new L.LatLng(position.lat, position.lng),{draggable:'true'}); + this.map.panTo(new L.LatLng(position.lat, position.lng)) + } +} + + + +export function setupMapLocationPickers() { + $(".map-location-picker").each(function(ix, element) { + new MapLocationPicker(element); + }); +} diff --git a/app/views/ui/devices/edit.html.erb b/app/views/ui/devices/edit.html.erb index fa821c65..69e7a193 100644 --- a/app/views/ui/devices/edit.html.erb +++ b/app/views/ui/devices/edit.html.erb @@ -8,7 +8,9 @@

<%= t(:edit_device_location_subhead) %>

<%= t(:edit_device_location_blurb) %>

-

TODO implement map

+ <%= f.hidden_field :latitude %> + <%= f.hidden_field :longitude %> + <%= render partial: "ui/shared/map_location_picker", locals: { latitude_input_id: "device_latitude", longitude_input_id: "device_longitude"} %>

<%= t(:edit_device_open_data_subhead) %>

diff --git a/app/views/ui/shared/_map_location_picker.html.erb b/app/views/ui/shared/_map_location_picker.html.erb new file mode 100644 index 00000000..bc03c6e5 --- /dev/null +++ b/app/views/ui/shared/_map_location_picker.html.erb @@ -0,0 +1 @@ +
" data-marker-shadow-url="<%= asset_url("map-pin-shadow.png") %>">