Merge pull request #5108 from consul/map_refactoring

Simplify the code rendering the map
This commit is contained in:
Javi Martín
2023-05-05 15:21:49 +02:00
committed by GitHub
26 changed files with 337 additions and 274 deletions

View File

@@ -7,9 +7,6 @@
App.Map.initializeMap(this);
});
},
attributionPrefix: function() {
return '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">Leaflet</a>';
},
destroy: function() {
App.Map.maps.forEach(function(map) {
map.off();
@@ -18,47 +15,11 @@
App.Map.maps = [];
},
initializeMap: function(element) {
var addMarkerInvestments, clearFormfields, createMarker, dataCoordinates, editable, formCoordinates,
getPopupContent, latitudeInputSelector, longitudeInputSelector, map, mapAttribution, mapCenterLatLng,
mapCenterLatitude, mapCenterLongitude, mapTilesProvider, marker, markerIcon, markerLatitude,
markerLongitude, moveOrPlaceMarker, openMarkerPopup, removeMarker, removeMarkerSelector,
updateFormfields, zoom, zoomInputSelector;
var createMarker, editable, investmentsMarkers, markerData, map, marker,
markerIcon, moveOrPlaceMarker, removeMarker, removeMarkerSelector;
App.Map.cleanInvestmentCoordinates(element);
mapTilesProvider = $(element).data("map-tiles-provider");
mapAttribution = $(element).data("map-tiles-provider-attribution");
latitudeInputSelector = $(element).data("latitude-input-selector");
longitudeInputSelector = $(element).data("longitude-input-selector");
zoomInputSelector = $(element).data("zoom-input-selector");
formCoordinates = {
lat: $(latitudeInputSelector).val(),
long: $(longitudeInputSelector).val(),
zoom: $(zoomInputSelector).val()
};
dataCoordinates = {
lat: $(element).data("marker-latitude"),
long: $(element).data("marker-longitude")
};
if (App.Map.validCoordinates(formCoordinates)) {
markerLatitude = formCoordinates.lat;
markerLongitude = formCoordinates.long;
mapCenterLatitude = formCoordinates.lat;
mapCenterLongitude = formCoordinates.long;
} else if (App.Map.validCoordinates(dataCoordinates)) {
markerLatitude = dataCoordinates.lat;
markerLongitude = dataCoordinates.long;
mapCenterLatitude = dataCoordinates.lat;
mapCenterLongitude = dataCoordinates.long;
} else {
mapCenterLatitude = $(element).data("map-center-latitude");
mapCenterLongitude = $(element).data("map-center-longitude");
}
if (App.Map.validZoom(formCoordinates.zoom)) {
zoom = formCoordinates.zoom;
} else {
zoom = $(element).data("map-zoom");
}
removeMarkerSelector = $(element).data("marker-remove-selector");
addMarkerInvestments = $(element).data("marker-investments-coordinates");
investmentsMarkers = $(element).data("marker-investments-coordinates");
editable = $(element).data("marker-editable");
marker = null;
markerIcon = L.divIcon({
@@ -68,17 +29,19 @@
html: '<div class="map-icon"></div>'
});
createMarker = function(latitude, longitude) {
var markerLatLng;
var newMarker, markerLatLng;
markerLatLng = new L.LatLng(latitude, longitude);
marker = L.marker(markerLatLng, {
newMarker = L.marker(markerLatLng, {
icon: markerIcon,
draggable: editable
});
if (editable) {
marker.on("dragend", updateFormfields);
newMarker.on("dragend", function() {
App.Map.updateFormfields(map, newMarker);
});
}
marker.addTo(map);
return marker;
newMarker.addTo(map);
return newMarker;
};
removeMarker = function(e) {
e.preventDefault();
@@ -86,7 +49,7 @@
map.removeLayer(marker);
marker = null;
}
clearFormfields();
App.Map.clearFormfields(element);
};
moveOrPlaceMarker = function(e) {
if (marker) {
@@ -94,56 +57,122 @@
} else {
marker = createMarker(e.latlng.lat, e.latlng.lng);
}
updateFormfields();
App.Map.updateFormfields(map, marker);
};
updateFormfields = function() {
$(latitudeInputSelector).val(marker.getLatLng().lat);
$(longitudeInputSelector).val(marker.getLatLng().lng);
$(zoomInputSelector).val(map.getZoom());
};
clearFormfields = function() {
$(latitudeInputSelector).val("");
$(longitudeInputSelector).val("");
$(zoomInputSelector).val("");
};
openMarkerPopup = function(e) {
marker = e.target;
$.ajax("/investments/" + marker.options.id + "/json_data", {
type: "GET",
dataType: "json",
success: function(data) {
e.target.bindPopup(getPopupContent(data)).openPopup();
}
});
};
getPopupContent = function(data) {
return "<a href='/budgets/" + data.budget_id + "/investments/" + data.investment_id + "'>" + data.investment_title + "</a>";
};
mapCenterLatLng = new L.LatLng(mapCenterLatitude, mapCenterLongitude);
map = L.map(element.id, { scrollWheelZoom: false }).setView(mapCenterLatLng, zoom);
map.attributionControl.setPrefix(App.Map.attributionPrefix());
map = App.Map.leafletMap(element);
App.Map.maps.push(map);
L.tileLayer(mapTilesProvider, {
attribution: mapAttribution
}).addTo(map);
if (markerLatitude && markerLongitude && !addMarkerInvestments) {
marker = createMarker(markerLatitude, markerLongitude);
App.Map.addAttribution(map);
markerData = App.Map.markerData(element);
if (markerData.lat && markerData.long && !investmentsMarkers) {
marker = createMarker(markerData.lat, markerData.long);
}
if (editable) {
$(removeMarkerSelector).on("click", removeMarker);
map.on("zoomend", function() {
if (marker) {
updateFormfields();
App.Map.updateFormfields(map, marker);
}
});
map.on("click", moveOrPlaceMarker);
}
if (addMarkerInvestments) {
addMarkerInvestments.forEach(function(coordinates) {
App.Map.addInvestmentsMarkers(investmentsMarkers, createMarker);
},
leafletMap: function(element) {
var centerData, mapCenterLatLng;
centerData = App.Map.centerData(element);
mapCenterLatLng = new L.LatLng(centerData.lat, centerData.long);
return L.map(element.id, { scrollWheelZoom: false }).setView(mapCenterLatLng, centerData.zoom);
},
attributionPrefix: function() {
return '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">Leaflet</a>';
},
markerData: function(element) {
var dataCoordinates, formCoordinates, inputs, latitude, longitude;
inputs = App.Map.coordinatesInputs(element);
dataCoordinates = {
lat: $(element).data("marker-latitude"),
long: $(element).data("marker-longitude")
};
formCoordinates = {
lat: inputs.lat.val(),
long: inputs.long.val(),
zoom: inputs.zoom.val()
};
if (App.Map.validCoordinates(formCoordinates)) {
latitude = formCoordinates.lat;
longitude = formCoordinates.long;
} else if (App.Map.validCoordinates(dataCoordinates)) {
latitude = dataCoordinates.lat;
longitude = dataCoordinates.long;
}
return {
lat: latitude,
long: longitude,
zoom: formCoordinates.zoom
};
},
centerData: function(element) {
var markerCoordinates, latitude, longitude, zoom;
markerCoordinates = App.Map.markerData(element);
if (App.Map.validCoordinates(markerCoordinates)) {
latitude = markerCoordinates.lat;
longitude = markerCoordinates.long;
} else {
latitude = $(element).data("map-center-latitude");
longitude = $(element).data("map-center-longitude");
}
if (App.Map.validZoom(markerCoordinates.zoom)) {
zoom = markerCoordinates.zoom;
} else {
zoom = $(element).data("map-zoom");
}
return {
lat: latitude,
long: longitude,
zoom: zoom
};
},
coordinatesInputs: function(element) {
return {
lat: $($(element).data("latitude-input-selector")),
long: $($(element).data("longitude-input-selector")),
zoom: $($(element).data("zoom-input-selector"))
};
},
updateFormfields: function(map, marker) {
var inputs = App.Map.coordinatesInputs(map._container);
inputs.lat.val(marker.getLatLng().lat);
inputs.long.val(marker.getLatLng().lng);
inputs.zoom.val(map.getZoom());
},
clearFormfields: function(element) {
var inputs = App.Map.coordinatesInputs(element);
inputs.lat.val("");
inputs.long.val("");
inputs.zoom.val("");
},
addInvestmentsMarkers: function(markers, createMarker) {
if (markers) {
markers.forEach(function(coordinates) {
var marker;
if (App.Map.validCoordinates(coordinates)) {
marker = createMarker(coordinates.lat, coordinates.long);
marker.options.id = coordinates.investment_id;
marker.on("click", openMarkerPopup);
marker.on("click", App.Map.openMarkerPopup);
}
});
}
@@ -156,6 +185,30 @@
$(element).attr("data-marker-investments-coordinates", clean_markers);
}
},
addAttribution: function(map) {
var element, mapAttribution, mapTilesProvider;
element = map._container;
mapTilesProvider = $(element).data("map-tiles-provider");
mapAttribution = $(element).data("map-tiles-provider-attribution");
map.attributionControl.setPrefix(App.Map.attributionPrefix());
L.tileLayer(mapTilesProvider, { attribution: mapAttribution }).addTo(map);
},
openMarkerPopup: function(e) {
var marker = e.target;
$.ajax("/investments/" + marker.options.id + "/json_data", {
type: "GET",
dataType: "json",
success: function(data) {
e.target.bindPopup(App.Map.getPopupContent(data)).openPopup();
}
});
},
getPopupContent: function(data) {
return "<a href='/budgets/" + data.budget_id + "/investments/" + data.investment_id + "'>" +
data.investment_title + "</a>";
},
validZoom: function(zoom) {
return App.Map.isNumeric(zoom);
},

View File

@@ -34,6 +34,7 @@
@import "legislation";
@import "legislation_process";
@import "legislation_process_form";
@import "map_location";
@import "moderation_actions";
@import "notification_item";
@import "community";

View File

@@ -19,7 +19,6 @@
// 20. Documents
// 21. Related content
// 22. Images
// 23. Maps
// 24. Homepage
// 25. LocalCensusRecords
//
@@ -2152,48 +2151,6 @@ table {
}
}
// 23. Maps
// -----------------
.location-map-remove-marker {
border-bottom: 1px dotted #cf2a0e;
color: $delete;
display: inline-block;
margin-top: $line-height / 2;
&:hover,
&:active,
&:focus {
border-bottom-style: solid;
color: #cf2a0e;
text-decoration: none;
}
}
.leaflet-bar a {
&.leaflet-disabled {
color: #525252;
}
}
.leaflet-container {
.leaflet-control-attribution {
background: rgba(255, 255, 255, 0.9);
}
a {
@include link;
}
}
.leaflet-bottom,
.leaflet-pane,
.leaflet-top {
z-index: 4;
}
// 24. Homepage
// ------------

View File

@@ -0,0 +1,38 @@
.location-map-remove-marker {
border-bottom: 1px dotted #cf2a0e;
color: $delete;
display: inline-block;
margin-top: $line-height / 2;
&:hover,
&:active,
&:focus {
border-bottom-style: solid;
color: #cf2a0e;
text-decoration: none;
}
}
.leaflet-bar a {
&.leaflet-disabled {
color: #525252;
}
}
.leaflet-container {
.leaflet-control-attribution {
background: rgba(255, 255, 255, 0.9);
}
a {
@include link;
}
}
.leaflet-bottom,
.leaflet-pane,
.leaflet-top {
z-index: 4;
}

View File

@@ -55,8 +55,6 @@
map_location: investment.map_location || MapLocation.new,
label: t("budgets.investments.form.map_location"),
help: t("budgets.investments.form.map_location_instructions"),
remove_marker_label: t("budgets.investments.form.map_remove_marker"),
parent_class: "budget_investment",
i18n_namespace: "budgets.investments" %>
</div>
<% end %>

View File

@@ -1,4 +1,4 @@
<div class="map inline">
<h2><%= t("budgets.index.map") %></h2>
<%= render_map(nil, "budgets", false, nil, coordinates) %>
<%= render_map(nil, investments_coordinates: coordinates) %>
</div>

View File

@@ -61,8 +61,6 @@
map_location: proposal.map_location || MapLocation.new,
label: t("proposals.form.map_location"),
help: t("proposals.form.map_location_instructions"),
remove_marker_label: t("proposals.form.map_remove_marker"),
parent_class: "proposal",
i18n_namespace: "proposals" %>
</div>
<% end %>

View File

@@ -0,0 +1,5 @@
<%= tag.div(id: dom_id(map_location), class: "map_location map", data: data) %>
<% if editable? %>
<%= remove_marker %>
<% end %>

View File

@@ -0,0 +1,79 @@
class Shared::MapLocationComponent < ApplicationComponent
attr_reader :investments_coordinates, :form
def initialize(map_location, investments_coordinates: nil, form: nil)
@map_location = map_location
@investments_coordinates = investments_coordinates
@form = form
end
def map_location
@map_location ||= MapLocation.new
end
private
def editable?
form.present?
end
def latitude
map_location.latitude.presence || Setting["map.latitude"]
end
def longitude
map_location.longitude.presence || Setting["map.longitude"]
end
def zoom
map_location.zoom.presence || Setting["map.zoom"]
end
def remove_marker_label
t("proposals.form.map_remove_marker")
end
def remove_marker_link_id
"remove-marker-link-#{dom_id(map_location)}"
end
def remove_marker
tag.div class: "margin-bottom" do
link_to remove_marker_label, "#",
id: remove_marker_link_id,
class: "js-location-map-remove-marker location-map-remove-marker"
end
end
def data
{
map: "",
map_center_latitude: latitude,
map_center_longitude: longitude,
map_zoom: zoom,
map_tiles_provider: Rails.application.secrets.map_tiles_provider,
map_tiles_provider_attribution: Rails.application.secrets.map_tiles_provider_attribution,
marker_editable: editable?,
marker_remove_selector: "##{remove_marker_link_id}",
marker_investments_coordinates: investments_coordinates,
marker_latitude: map_location.latitude.presence,
marker_longitude: map_location.longitude.presence
}.merge(input_selectors)
end
def input_selectors
if form
{
latitude_input_selector: "##{input_id(:latitude)}",
longitude_input_selector: "##{input_id(:longitude)}",
zoom_input_selector: "##{input_id(:zoom)}"
}
else
{}
end
end
def input_id(attribute)
form.hidden_field(attribute).match(/ id="([^"]+)"/)[1]
end
end

View File

@@ -181,7 +181,7 @@ module Budgets
end
def load_map
@map_location = MapLocation.load_from_heading(@heading) if @heading.present?
@map_location = MapLocation.from_heading(@heading) if @heading.present?
end
end
end

View File

@@ -68,11 +68,6 @@ module CommentableActions
end
end
def map
@resource = resource_model.new
@tag_cloud = tag_cloud
end
private
def track_event

View File

@@ -3,10 +3,10 @@ class Legislation::ProposalsController < Legislation::BaseController
include FlagActions
include ImageAttributes
before_action :load_categories, only: [:new, :create, :edit, :map, :summary]
before_action :load_geozones, only: [:edit, :map, :summary]
before_action :load_categories, only: [:new, :create, :edit, :summary]
before_action :load_geozones, only: [:edit, :summary]
before_action :authenticate_user!, except: [:show, :map, :summary]
before_action :authenticate_user!, except: [:show, :summary]
load_and_authorize_resource :process, class: "Legislation::Process"
load_and_authorize_resource :proposal, class: "Legislation::Proposal", through: :process

View File

@@ -77,6 +77,11 @@ class ProposalsController < ApplicationController
@tag_cloud = tag_cloud
end
def map
@proposal = Proposal.new
@tag_cloud = tag_cloud
end
def disable_recommendations
if current_user.update(recommended_proposals: false)
redirect_to proposals_path, notice: t("proposals.index.recommendations.actions.success")

View File

@@ -3,62 +3,7 @@ module MapLocationsHelper
map_location.present? && map_location.available?
end
def map_location_latitude(map_location)
map_location.present? && map_location.latitude.present? ? map_location.latitude : Setting["map.latitude"]
end
def map_location_longitude(map_location)
map_location.present? && map_location.longitude.present? ? map_location.longitude : Setting["map.longitude"]
end
def map_location_zoom(map_location)
map_location.present? && map_location.zoom.present? ? map_location.zoom : Setting["map.zoom"]
end
def map_location_input_id(prefix, attribute)
"#{prefix}_map_location_attributes_#{attribute}"
end
def map_location_remove_marker_link_id(map_location)
"remove-marker-link-#{dom_id(map_location)}"
end
def render_map(map_location, parent_class, editable, remove_marker_label, investments_coordinates = nil)
map_location = MapLocation.new if map_location.nil?
map = tag.div id: dom_id(map_location),
class: "map_location map",
data: prepare_map_settings(map_location, editable, parent_class, investments_coordinates)
map += map_location_remove_marker(map_location, remove_marker_label) if editable
map
end
def map_location_remove_marker(map_location, text)
tag.div class: "margin-bottom" do
link_to text, "#",
id: map_location_remove_marker_link_id(map_location),
class: "js-location-map-remove-marker location-map-remove-marker"
end
end
private
def prepare_map_settings(map_location, editable, parent_class, investments_coordinates = nil)
options = {
map: "",
map_center_latitude: map_location_latitude(map_location),
map_center_longitude: map_location_longitude(map_location),
map_zoom: map_location_zoom(map_location),
map_tiles_provider: Rails.application.secrets.map_tiles_provider,
map_tiles_provider_attribution: Rails.application.secrets.map_tiles_provider_attribution,
marker_editable: editable,
marker_remove_selector: "##{map_location_remove_marker_link_id(map_location)}",
latitude_input_selector: "##{map_location_input_id(parent_class, "latitude")}",
longitude_input_selector: "##{map_location_input_id(parent_class, "longitude")}",
zoom_input_selector: "##{map_location_input_id(parent_class, "zoom")}",
marker_investments_coordinates: investments_coordinates
}
options[:marker_latitude] = map_location.latitude if map_location.latitude.present?
options[:marker_longitude] = map_location.longitude if map_location.longitude.present?
options
def render_map(...)
render Shared::MapLocationComponent.new(...)
end
end

View File

@@ -25,7 +25,7 @@ module Abilities
id: Legislation::Process.past.published.where(result_publication_enabled: true).ids
can [:read, :changes, :go_to_version], Legislation::DraftVersion
can [:read], Legislation::Question
can [:read, :map, :share], Legislation::Proposal
can [:read, :share], Legislation::Proposal
can [:search, :comments, :read, :create, :new_comment], Legislation::Annotation
can [:read, :help], ::SDG::Goal

View File

@@ -17,11 +17,11 @@ class MapLocation < ApplicationRecord
}
end
def self.load_from_heading(heading)
map = new
map.zoom = Budget::Heading::OSM_DISTRICT_LEVEL_ZOOM
map.latitude = heading.latitude.to_f if heading.latitude.present?
map.longitude = heading.longitude.to_f if heading.longitude.present?
map
def self.from_heading(heading)
new(
zoom: Budget::Heading::OSM_DISTRICT_LEVEL_ZOOM,
latitude: (heading.latitude.to_f if heading.latitude.present?),
longitude: (heading.longitude.to_f if heading.longitude.present?)
)
end
end

View File

@@ -28,7 +28,7 @@
<% if feature?(:map) && map_location_available?(@investment.map_location) %>
<div class="margin">
<%= render_map(investment.map_location, "budget_investment", false, nil) %>
<%= render_map(investment.map_location) %>
</div>
<% end %>

View File

@@ -1,3 +1,3 @@
<div class="map">
<%= render_map(@map_location, "budgets", false, nil, @investments_map_coordinates) %>
<%= render_map(@map_location, investments_coordinates: @investments_map_coordinates) %>
</div>

View File

@@ -1 +0,0 @@
<%= render "shared/map", new_url_path: new_proposal_path %>

View File

@@ -1,16 +1,10 @@
<%= form.label :map_location, label %>
<p class="help-text" id="tag-list-help-text"><%= help %></p>
<%= render_map(map_location, parent_class, editable = true, remove_marker_label) %>
<%= form.fields_for :map_location, map_location do |m_l_fields| %>
<%= m_l_fields.hidden_field :latitude,
value: map_location.latitude,
id: map_location_input_id(parent_class, "latitude") %>
<%= m_l_fields.hidden_field :longitude,
value: map_location.longitude,
id: map_location_input_id(parent_class, "longitude") %>
<%= m_l_fields.hidden_field :zoom,
value: map_location.zoom,
id: map_location_input_id(parent_class, "zoom") %>
<%= render_map(map_location, form: m_l_fields) %>
<%= m_l_fields.hidden_field :latitude %>
<%= m_l_fields.hidden_field :longitude %>
<%= m_l_fields.hidden_field :zoom %>
<% end %>

View File

@@ -42,7 +42,7 @@
<% if feature?(:map) && map_location_available?(@proposal.map_location) %>
<div class="margin">
<%= render_map(@proposal.map_location, "proposal", false, nil) %>
<%= render_map(@proposal.map_location) %>
</div>
<% end %>

View File

@@ -1 +1,50 @@
<%= render "shared/map", new_url_path: new_proposal_path %>
<div class="row">
<div class="small-12 medium-9 column margin-top">
<h1><%= t("map.title") %></h1>
<div class="row">
<div class="small-12 medium-3 column">
<ul id="geozones" class="no-bullet">
<% @geozones.each do |geozone| %>
<li><%= link_to geozone.name, proposals_path(search: geozone.name) %></li>
<% end %>
</ul>
</div>
<div class="show-for-medium medium-9 column text-center">
<%= image_tag(image_path_for("map.jpg"), usemap: "#map") %>
</div>
<map name="map" id="html_map">
<% @geozones.each do |geozone| %>
<area shape="poly"
coords="<%= geozone.html_map_coordinates %>"
href="<%= polymorphic_path(@proposal, search: geozone.name) %>"
title="<%= geozone.name %>"
alt="<%= geozone.name %>">
<% end %>
</map>
</div>
<h2><%= t("map.proposal_for_district") %></h2>
<%= form_for(@proposal, url: new_proposal_path, method: :get) do |f| %>
<div class="small-12 medium-4">
<%= f.select :geozone_id, geozone_select_options, include_blank: t("geozones.none") %>
</div>
<div class="actions small-12">
<%= f.submit(class: "button radius", value: t("map.start_proposal")) %>
</div>
<% end %>
</div>
<div class="small-12 medium-3 column">
<aside class="sidebar">
<%= link_to t("map.start_proposal"),
new_proposal_path, class: "button radius expand" %>
<%= render "shared/tag_cloud", taggable: "Proposal" %>
</aside>
</div>
</div>

View File

@@ -1,50 +0,0 @@
<div class="row">
<div class="small-12 medium-9 column margin-top">
<h1><%= t("map.title") %></h1>
<div class="row">
<div class="small-12 medium-3 column">
<ul id="geozones" class="no-bullet">
<% @geozones.each do |geozone| %>
<li><%= link_to geozone.name, proposals_path(search: geozone.name) %></li>
<% end %>
</ul>
</div>
<div class="show-for-medium medium-9 column text-center">
<%= image_tag(image_path_for("map.jpg"), usemap: "#map") %>
</div>
<map name="map" id="html_map">
<% @geozones.each do |geozone| %>
<area shape="poly"
coords="<%= geozone.html_map_coordinates %>"
href="<%= polymorphic_path(@resource, search: geozone.name) %>"
title="<%= geozone.name %>"
alt="<%= geozone.name %>">
<% end %>
</map>
</div>
<h2><%= t("map.proposal_for_district") %></h2>
<%= form_for(@resource, url: new_url_path, method: :get) do |f| %>
<div class="small-12 medium-4">
<%= f.select :geozone_id, geozone_select_options, include_blank: t("geozones.none") %>
</div>
<div class="actions small-12">
<%= f.submit(class: "button radius", value: t("map.start_proposal")) %>
</div>
<% end %>
</div>
<div class="small-12 medium-3 column">
<aside class="sidebar">
<%= link_to t("map.start_proposal"),
new_proposal_path, class: "button radius expand" %>
<%= render "shared/tag_cloud", taggable: "Proposal" %>
</aside>
</div>
</div>

View File

@@ -82,7 +82,6 @@ en:
tags_placeholder: "Enter the tags you would like to use, separated by commas (',')"
map_location: "Map location"
map_location_instructions: "Navigate the map to the location and place the marker."
map_remove_marker: "Remove map marker"
index:
title: Participatory budgeting
unfeasible: Unfeasible investment projects

View File

@@ -82,7 +82,6 @@ es:
tags_placeholder: "Escribe las etiquetas que desees separadas por coma (',')"
map_location: "Ubicación en el mapa"
map_location_instructions: "Navega por el mapa hasta la ubicación y coloca el marcador."
map_remove_marker: "Eliminar el marcador"
index:
title: Presupuestos participativos
unfeasible: Proyectos de gasto no viables

View File

@@ -21,7 +21,6 @@ namespace :legislation do
put :unflag
end
collection do
get :map
get :suggest
end
end