diff --git a/Gemfile.lock b/Gemfile.lock
index c3b1cffdd..435fd292b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -493,7 +493,7 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
- scout_apm (2.6.3)
+ scout_apm (2.6.4)
parser
searchkick (4.1.0)
activemodel (>= 5)
diff --git a/app/assets/images/spade-marker.svg b/app/assets/images/spade-marker.svg
new file mode 100644
index 000000000..791a3709e
--- /dev/null
+++ b/app/assets/images/spade-marker.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/crops.js.erb b/app/assets/javascripts/cropmap.js.erb
similarity index 91%
rename from app/assets/javascripts/crops.js.erb
rename to app/assets/javascripts/cropmap.js.erb
index 78a3a2222..756530666 100644
--- a/app/assets/javascripts/crops.js.erb
+++ b/app/assets/javascripts/cropmap.js.erb
@@ -9,12 +9,20 @@ function showCropMap(cropmap) {
}).addTo(cropmap);
var markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 });
+ var crop_icon = L.icon({
+ iconUrl: location.pathname + '.svg',
+ iconSize: [24, 24],
+ iconAnchor: [12, 12],
+ popupAnchor: [0, -12],
+ });
+
var things_to_map = location.pathname + '.json';
$.getJSON(things_to_map, function(crop) {
$.each(crop.plantings, function(i, planting) {
var owner = planting.owner;
if (owner.latitude && owner.longitude) {
- var marker = new L.Marker(new L.LatLng(owner.latitude, owner.longitude));
+ var marker = new L.Marker(new L.LatLng(owner.latitude, owner.longitude),
+ {'icon': crop_icon});
var planting_url = "/plantings/" + planting.id;
var planting_link = "" + owner.login_name + "'s " + crop.name + "";
@@ -44,7 +52,6 @@ function showCropMap(cropmap) {
$(document).ready(function() {
if (document.getElementById("cropmap") !== null) {
-
L.Icon.Default.imagePath = '/assets';
var cropmap = L.map('cropmap').setView([0.0, -0.0], 2);
diff --git a/app/assets/javascripts/members.js.erb b/app/assets/javascripts/members.js.erb
index 51a662758..42fafe6b5 100644
--- a/app/assets/javascripts/members.js.erb
+++ b/app/assets/javascripts/members.js.erb
@@ -7,6 +7,13 @@ if (document.getElementById("membermap") !== null) {
L.Icon.Default.imagePath = '/assets';
+ var default_marker_icon = L.icon({
+ iconUrl: "<%=image_url('spade-marker.svg')%>",
+ iconSize: [48, 48],
+ iconAnchor: [24, 48],
+ popupAnchor: [0, -46],
+ });
+
$.getJSON(location.pathname + '.json', function(member) {
if (member.latitude && member.longitude) {
var membermap = L.map('membermap').setView([member.latitude, member.longitude], 4);
@@ -15,7 +22,8 @@ if (document.getElementById("membermap") !== null) {
attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox',
maxZoom: 18
}).addTo(membermap);
- var marker = new L.Marker(new L.LatLng(member.latitude, member.longitude));
+ var marker = new L.Marker(new L.LatLng(member.latitude, member.longitude),
+ {'icon': default_marker_icon});
var member_url = "/members/" + member.slug;
var member_link = "" + member.login_name + "";
diff --git a/app/assets/javascripts/places.js.erb b/app/assets/javascripts/places.js.erb
index f0322b38b..b5917f614 100644
--- a/app/assets/javascripts/places.js.erb
+++ b/app/assets/javascripts/places.js.erb
@@ -1,20 +1,23 @@
if (document.getElementById("placesmap") !== null) {
- places_base_path = "/places";
- mapbox_map_id = "<%= Rails.env == 'test' ? 0 : Rails.application.config.mapbox_map_id %>";
- mapbox_access_token = "<%= Rails.env == 'test' ? 0 : Rails.application.config.mapbox_access_token %>";
- mapbox_base_url = "https://a.tiles.mapbox.com/v4/" + mapbox_map_id + "/{z}/{x}/{y}.png?access_token=" + mapbox_access_token;
- nominatim_base_url = 'https://nominatim.openstreetmap.org/search/';
- nominatim_user_agent_email = "<%= Rails.env == 'test' ? 0 : Rails.application.config.user_agent_email %>";
+ var places_base_path = "/places";
+ var mapbox_map_id = "<%= Rails.env == 'test' ? 0 : Rails.application.config.mapbox_map_id %>";
+ var mapbox_access_token = "<%= Rails.env == 'test' ? 0 : Rails.application.config.mapbox_access_token %>";
+ var mapbox_base_url = "https://a.tiles.mapbox.com/v4/" + mapbox_map_id + "/{z}/{x}/{y}.png?access_token=" + mapbox_access_token;
+ var nominatim_base_url = 'https://nominatim.openstreetmap.org/search/';
+ var nominatim_user_agent_email = "<%= Rails.env == 'test' ? 0 : Rails.application.config.user_agent_email %>";
L.Icon.Default.imagePath = '/assets'
- if (location.pathname === places_base_path) { //places index page
+ var placesmap;
+
+ if (location.pathname === places_base_path || location.pathname === '/') { //places index page
placesmap = L.map('placesmap').setView([0.0, -0.0], 2);
showMap(placesmap);
- } else { // specific place page
- place = location.pathname.replace(places_base_path + "/", '');
- nominatim_query_url = nominatim_base_url + place;
- nominatim_options = {
+ }
+ else { // specific place page
+ var place = location.pathname.replace(places_base_path + "/", '');
+ var nominatim_query_url = nominatim_base_url + place;
+ var nominatim_options = {
format: "json",
callback: "placeholder",
limit: 1,
@@ -32,20 +35,58 @@ function showMap(placesmap) {
attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox',
maxZoom: 18
}).addTo(placesmap);
- markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 });
- things_to_map = location.pathname + '.json';
- $.getJSON(things_to_map, function(members) {
+ var markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 });
+
+ var members_url = location.pathname + '.json';
+
+ // Icon documentation: https://leafletjs.com/reference-1.5.0.html#icon
+ var default_marker_icon = L.icon({
+ iconUrl: "<%=image_url('spade-marker.svg')%>",
+ iconSize: [48, 48],
+ iconAnchor: [24, 48],
+ popupAnchor: [0, -46],
+ });
+
+ $.getJSON(members_url, function(members) {
$.each(members, function(i, m) {
if (m.latitude && m.longitude) {
- marker = new L.Marker(new L.LatLng(m.latitude, m.longitude));
- link = "
" + m.login_name + "
";
- where = "" + m.location + "
";
+ var marker = new L.Marker(new L.LatLng(m.latitude, m.longitude), {icon: default_marker_icon});
+ var link = "" + m.login_name + "
";
+ var where = "" + m.location + "
";
marker.bindPopup(link + where).openPopup();
markers.addLayer(marker);
}
});
});
+ function fetchCropsForMap(url) {
+ $.getJSON(url, function(crop_data) {
+ $.each(crop_data['data'], function(i, p) {
+ if (p.attributes.latitude && p.attributes.longitude) {
+ var crop_marker = L.icon({
+ iconUrl: "/crops/" + p.attributes['crop-slug'] + '.svg',
+ iconSize: [48, 48],
+ iconAnchor: [24, 48],
+ popupAnchor: [0, -46],
+ });
+ var marker = new L.Marker(new L.LatLng(p.attributes.latitude, p.attributes.longitude),
+ {icon: crop_marker});
+ var link = "" + p.attributes['crop-name'] + "
";
+ var where = "" + p.attributes.location + "
";
+ if (p.attributes['thumbnail']) {
+ where += '
';
+ }
+ marker.bindPopup(link + where).openPopup();
+ markers.addLayer(marker);
+ }
+ });
+ if (crop_data['links']['next']) {
+ fetchCropsForMap(crop_data['links']['next'])
+ }
+ return crop_data;
+ });
+ }
+ fetchCropsForMap('/api/v1/plantings.json?filter[finished]=false&sort=-planted-at');
placesmap.addLayer(markers);
}
diff --git a/app/assets/stylesheets/leaflet_overrides.scss b/app/assets/stylesheets/leaflet_overrides.scss
index aa69bb297..c89f27303 100644
--- a/app/assets/stylesheets/leaflet_overrides.scss
+++ b/app/assets/stylesheets/leaflet_overrides.scss
@@ -1,13 +1,13 @@
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
- border: none;
+ border: 0;
}
.thumbnail {
- background: #fff !important;
- border: solid 1px whitesmoke;
-}
+ background: $white;
+ border: solid 1px $white;
-.thumbnail .crop-thumbnail .cropinfo {
- padding-top: 14px;
+ .crop-thumbnail .cropinfo {
+ padding-top: 14px;
+ }
}
diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb
index 2dd6a948c..7aefd98af 100644
--- a/app/controllers/crops_controller.rb
+++ b/app/controllers/crops_controller.rb
@@ -66,7 +66,10 @@ class CropsController < ApplicationController
respond_to do |format|
format.html
- format.svg { send_data(@crop.svg_icon, type: "image/svg+xml", disposition: "inline") }
+ format.svg do
+ icon_data = @crop.svg_icon.presence || File.read(Rails.root.join('app', 'assets', 'images', 'icons', 'sprout.svg'))
+ send_data(icon_data, type: "image/svg+xml", disposition: "inline")
+ end
format.json { render json: @crop.to_json(crop_json_fields) }
end
end
diff --git a/app/models/concerns/open_farm_data.rb b/app/models/concerns/open_farm_data.rb
index f56c0fa66..077971574 100644
--- a/app/models/concerns/open_farm_data.rb
+++ b/app/models/concerns/open_farm_data.rb
@@ -19,7 +19,10 @@ module OpenFarmData
end
def svg_icon
- fetch_attr('svg_icon')
+ icon = fetch_attr('svg_icon')
+ return icon if icon.present?
+
+ parent.svg_icon if parent.present?
end
def tags_array
diff --git a/app/models/planting.rb b/app/models/planting.rb
index 868f36735..686153b64 100644
--- a/app/models/planting.rb
+++ b/app/models/planting.rb
@@ -45,10 +45,12 @@ class Planting < ApplicationRecord
##
## Delegations
- delegate :name, :en_wikipedia_url, :default_scientific_name, :plantings_count,
+ delegate :name, :slug, :en_wikipedia_url, :default_scientific_name, :plantings_count,
to: :crop, prefix: true
- delegate :annual?, to: :crop
+ delegate :annual?, :svg_icon, to: :crop
+ delegate :location, :longitude, :latitude, to: :garden
+
##
## Validations
validates :garden, presence: true
@@ -77,11 +79,6 @@ class Planting < ApplicationRecord
].join('-').tr(' ', '-').downcase
end
- # location = garden owner + garden name, i.e. "Skud's backyard"
- def location
- I18n.t("gardens.location", garden: garden.name, owner: garden.owner.login_name)
- end
-
# stringify as "beet in Skud's backyard" or similar
def to_s
I18n.t('plantings.string', crop: crop.name, garden: garden.name, owner: owner)
diff --git a/app/resources/planting_resource.rb b/app/resources/planting_resource.rb
index 1c641c8e7..629e8e9a6 100644
--- a/app/resources/planting_resource.rb
+++ b/app/resources/planting_resource.rb
@@ -3,17 +3,26 @@ class PlantingResource < BaseResource
has_one :garden
has_one :crop
- # has_one :owner, class_name: 'Member'
+ has_one :owner, class_name: 'Member'
has_many :photos
has_many :harvests
- attribute :slug, :planted_at, :finished, :finished_at, :quantity, :description, :sunniness, :planted_from
+ attribute :slug
+ attribute :planted_at
+ attribute :finished
+ attribute :finished_at
+ attribute :quantity
+ attribute :description
+ attribute :sunniness
+ attribute :planted_from
# Predictions
- # attribute :expected_lifespan
- # attribute :finish_predicted_at
- # attribute :first_harvest_date
- # attribute :last_harvest_date
+ attribute :expected_lifespan
+ attribute :finish_predicted_at
+ attribute :first_harvest_date
+ attribute :last_harvest_date
+
+ attributes :longitude, :latitude, :location
filter :slug
filter :crop
@@ -22,18 +31,16 @@ class PlantingResource < BaseResource
filter :owner
filter :finished
- # attribute :percentage_grown
- # def percentage_grown
- # @model.percentage_grown
- # end
+ attribute :percentage_grown
+ def percentage_grown
+ @model.percentage_grown
+ end
- # attribute :crop_name
- # def crop_name
- # @model.crop.name
- # end
+ attribute :crop_name
+ attribute :crop_slug
- # attribute :thumbnail
- # def thumbnail
- # @model.default_photo&.thumbnail_url
- # end
+ attribute :thumbnail
+ def thumbnail
+ @model.default_photo&.thumbnail_url
+ end
end
diff --git a/app/views/places/index.html.haml b/app/views/places/index.html.haml
index ebc180a81..f915e67db 100644
--- a/app/views/places/index.html.haml
+++ b/app/views/places/index.html.haml
@@ -1,3 +1,6 @@
- content_for :title, t(".title", site_name: ENV['GROWSTUFF_SITE_NAME'])
+
+%h2=t(".title", site_name: ENV['GROWSTUFF_SITE_NAME'])
+
= render partial: 'search_form'
#placesmap.map
diff --git a/spec/controllers/api/v1/plantings_controller_spec.rb b/spec/controllers/api/v1/plantings_controller_spec.rb
index c187e1213..b807c724e 100644
--- a/spec/controllers/api/v1/plantings_controller_spec.rb
+++ b/spec/controllers/api/v1/plantings_controller_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe Api::V1::PlantingsController, type: :controller do
let(:expected_attributes) do
{
'crop-name' => my_planting.crop.name,
+ 'crop-slug' => my_planting.crop.slug,
'description' => my_planting.description,
'expected-lifespan' => nil,
'finish-predicted-at' => nil,
@@ -23,6 +24,9 @@ RSpec.describe Api::V1::PlantingsController, type: :controller do
'finished-at' => my_planting.finished_at,
'first-harvest-date' => nil,
'last-harvest-date' => nil,
+ 'latitude' => my_planting.garden.latitude,
+ 'longitude' => my_planting.garden.longitude,
+ 'location' => my_planting.garden.location,
'percentage-grown' => nil,
'planted-at' => '2000-01-01',
'planted-from' => my_planting.planted_from,
@@ -46,6 +50,7 @@ RSpec.describe Api::V1::PlantingsController, type: :controller do
let(:expected_attributes) do
{
'crop-name' => my_planting.crop.name,
+ 'crop-slug' => my_planting.crop.slug,
'description' => my_planting.description,
'expected-lifespan' => nil,
'finish-predicted-at' => nil,
@@ -53,6 +58,9 @@ RSpec.describe Api::V1::PlantingsController, type: :controller do
'finished-at' => my_planting.finished_at,
'first-harvest-date' => nil,
'last-harvest-date' => nil,
+ 'latitude' => my_planting.garden.latitude,
+ 'longitude' => my_planting.garden.longitude,
+ 'location' => my_planting.garden.location,
'percentage-grown' => nil,
'planted-at' => '2000-01-01',
'planted-from' => my_planting.planted_from,
diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb
index 56e477f4f..3bbd0a255 100644
--- a/spec/models/planting_spec.rb
+++ b/spec/models/planting_spec.rb
@@ -212,7 +212,7 @@ describe Planting do
end
it "generates a location" do
- planting.location.should eq "#{garden_owner.login_name}'s #{garden.name}"
+ planting.location.should eq garden.location
end
it "has a slug" do
diff --git a/spec/requests/api/v1/plantings_request_spec.rb b/spec/requests/api/v1/plantings_request_spec.rb
index cfd60fcf7..705c23c6f 100644
--- a/spec/requests/api/v1/plantings_request_spec.rb
+++ b/spec/requests/api/v1/plantings_request_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe 'Plantings', type: :request do
subject { JSON.parse response.body }
let(:headers) { { 'Accept' => 'application/vnd.api+json' } }
- let!(:planting) { FactoryBot.create :planting }
+ let!(:planting) { FactoryBot.create :planting }
let(:planting_encoded_as_json_api) do
{ "id" => planting.id.to_s,
"type" => "plantings",
@@ -60,6 +60,7 @@ RSpec.describe 'Plantings', type: :request do
"quantity" => 33,
"description" => planting.description,
"crop-name" => planting.crop.name,
+ "crop-slug" => planting.crop.slug,
"sunniness" => nil,
"planted-from" => nil,
"expected-lifespan" => nil,
@@ -67,12 +68,17 @@ RSpec.describe 'Plantings', type: :request do
"percentage-grown" => nil,
"first-harvest-date" => nil,
"last-harvest-date" => nil,
- "thumbnail" => nil
+ "thumbnail" => nil,
+ "location" => planting.garden.location,
+ "longitude" => planting.garden.longitude,
+ "latitude" => planting.garden.latitude
}
end
it '#index' do
get '/api/v1/plantings', params: {}, headers: headers
+ expect(subject['data'][0].keys).to eq(planting_encoded_as_json_api.keys)
+ expect(subject['data'][0]['attributes'].keys.sort!).to eq(planting_encoded_as_json_api['attributes'].keys.sort!)
expect(subject['data']).to include(planting_encoded_as_json_api)
end