aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorhellekin <hellekin@cepheide.org>2021-01-22 05:32:15 +0100
committerhellekin <hellekin@cepheide.org>2021-01-22 05:32:15 +0100
commitb54a8458d5029b3494165b7430e21b3ae34ecc0c (patch)
tree32ea6fc6b8774f672325fec52f0ffc97229a9568 /app
parent1c1aead78192982e221179de6688b944e5b01bf6 (diff)
downloadincommon-map-b54a8458d5029b3494165b7430e21b3ae34ecc0c.tar.gz
Upgrade Rails and add StimulusJS support
Diffstat (limited to 'app')
-rw-r--r--app/assets/stylesheets/application.scss (renamed from app/assets/stylesheets/application.css)8
-rw-r--r--app/assets/stylesheets/sections.scss3
-rw-r--r--app/assets/stylesheets/taxonomies.scss2
-rw-r--r--app/controllers/categories_controller.rb5
-rw-r--r--app/controllers/sections_controller.rb16
-rw-r--r--app/controllers/users_controller.rb13
-rw-r--r--app/helpers/application_helper.rb19
-rw-r--r--app/helpers/leaflet_helper.rb11
-rw-r--r--app/helpers/map_helper.rb19
-rw-r--r--app/helpers/sections_helper.rb19
-rw-r--r--app/helpers/taxonomies/filter_helper.rb2
-rw-r--r--app/javascript/controllers/map_controller.js59
-rw-r--r--app/javascript/controllers/taxonomy_controller.js112
-rw-r--r--app/javascript/packs/application.js6
-rw-r--r--app/models/agency.rb28
-rw-r--r--app/models/resource.rb20
-rw-r--r--app/views/agents/_edit.html.erb6
-rw-r--r--app/views/agents/_form.html.erb22
-rw-r--r--app/views/agents/index.html.erb5
-rw-r--r--app/views/agents/new.html.erb2
-rw-r--r--app/views/agents/show.html.erb3
-rw-r--r--app/views/categories/_category.html.erb17
-rw-r--r--app/views/categories/_edit.html.erb6
-rw-r--r--app/views/categories/_form.html.erb25
-rw-r--r--app/views/categories/edit.html.erb1
-rw-r--r--app/views/categories/show.html.erb2
-rw-r--r--app/views/layouts/application.html.erb55
-rw-r--r--app/views/resources/_resource.json.erb1
-rw-r--r--app/views/resources/show.json.erb1
-rw-r--r--app/views/sections/show.html.erb11
-rw-r--r--app/views/sections/show.json.erb1
-rw-r--r--app/views/taxonomies/_taxonomy.html.erb1
-rw-r--r--app/views/taxonomies/index.html.erb2
-rw-r--r--app/views/users/_user.html.erb3
-rw-r--r--app/views/users/index.html.erb6
-rw-r--r--app/views/welcome/authenticate.html.erb2
-rw-r--r--app/views/welcome/index.html.erb5
37 files changed, 401 insertions, 118 deletions
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.scss
index d05ea0f..94d00ec 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.scss
@@ -10,6 +10,12 @@
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
- *= require_tree .
+ *= require normalize.css/normalize.css
*= require_self
+ *= require leaflet
+ *= require leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css
+ *= require leaflet.markercluster/dist/MarkerCluster.css
+ *= require leaflet.markercluster/dist/MarkerCluster.Default.css
+ *= require_tree .
+ *
*/
diff --git a/app/assets/stylesheets/sections.scss b/app/assets/stylesheets/sections.scss
new file mode 100644
index 0000000..d540c09
--- /dev/null
+++ b/app/assets/stylesheets/sections.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the sections controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: https://sass-lang.com/
diff --git a/app/assets/stylesheets/taxonomies.scss b/app/assets/stylesheets/taxonomies.scss
index 4e227da..b1fabcd 100644
--- a/app/assets/stylesheets/taxonomies.scss
+++ b/app/assets/stylesheets/taxonomies.scss
@@ -68,9 +68,11 @@ button[data-action="taxonomy#toggle"] {
font-weight: bold;
ol {
display: block;
+ z-index: 1002;
}
li.active {
text-align: right;
+ z-index: 1002;
}
}
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index 40f1ff5..da8d837 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -10,6 +10,11 @@ class CategoriesController < ApplicationController
def show
@category = Category.find(params[:id])
+
+ respond_to do |format|
+ format.html
+ format.js
+ end
end
def edit
diff --git a/app/controllers/sections_controller.rb b/app/controllers/sections_controller.rb
new file mode 100644
index 0000000..fed1d57
--- /dev/null
+++ b/app/controllers/sections_controller.rb
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: 2020 IN COMMON Collective <collective@incommon.cc>
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+class SectionsController < ApplicationController
+ skip_before_action :verify_authenticity_token, only: :show
+
+ def show
+ @section = Section.find(params[:id])
+ respond_to do |format|
+ format.html
+ format.js
+ format.json
+ end
+ end
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 9a3cf74..f0479d1 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -3,18 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
class UsersController < ApplicationController
- # GET /my/users
- # If you're a leader, you will see a list of Agent members
+ # GET /my/peers
def index
- begin
- return 403 unless current_user.agencies.find_by(name: current_agent).leader?
- rescue Exception => e
- Rails.logger.info("Exception %s: %s" % [e.class, e.message])
- flash[:notice] = "Talk to your leader!"
- redirect_to root_url and return
- end
-
- @users = Agent.where(name: current_agent).members
+ @users = current_agent.members
end
# GET /my/account
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 15d5082..e39da5f 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -6,25 +6,6 @@ module ApplicationHelper
def current_agency
current_user.agencies.where(agent: current_agent).first
end
- %w(observer editor maintainer leader).each do |role|
- define_method :"current_user_#{role}?" do
- current_agency.send(:"#{role}?")
- end
- end
-
- def map_container(map = Map.first)
- raw tag.div(
- tag.div(id: 'map',
- data: {
- target: 'map.container'
- }),
- data: {
- controller: 'map',
- 'map-latitude': map.latitude,
- 'map-longitude': map.longitude,
- 'map-zoom': map.zoom
- })
- end
# Markdown helper
# Always use all extensions. Additional parser and render options may be
diff --git a/app/helpers/leaflet_helper.rb b/app/helpers/leaflet_helper.rb
new file mode 100644
index 0000000..6ca03f3
--- /dev/null
+++ b/app/helpers/leaflet_helper.rb
@@ -0,0 +1,11 @@
+module LeafletHelper
+ def marker_for(resource, options = {})
+ coords = [resource.latitude, resource.longitude]
+
+ "L.marker(#{coords}, #{options}).bindPopup(%s)" % popup_for(resource)
+ end
+
+ def popup_for(resource)
+ render partial: 'resource/popup', locals: { resource: resource }
+ end
+end
diff --git a/app/helpers/map_helper.rb b/app/helpers/map_helper.rb
new file mode 100644
index 0000000..3403801
--- /dev/null
+++ b/app/helpers/map_helper.rb
@@ -0,0 +1,19 @@
+# SPDX-FileCopyrightText: 2020 IN COMMON Collective <collective@incommon.cc>
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+module MapHelper
+ def map_container(map = Map.first)
+ raw tag.div(
+ tag.div(id: 'map',
+ data: {
+ target: 'map.container'
+ }),
+ data: {
+ controller: 'map',
+ 'map-latitude': map.latitude,
+ 'map-longitude': map.longitude,
+ 'map-zoom': map.zoom
+ })
+ end
+end
diff --git a/app/helpers/sections_helper.rb b/app/helpers/sections_helper.rb
new file mode 100644
index 0000000..8180fee
--- /dev/null
+++ b/app/helpers/sections_helper.rb
@@ -0,0 +1,19 @@
+module SectionsHelper
+ # Render a section as a GeoJSON FeatureCollection
+ def geojson_feature_collection(section)
+ out = []
+ class_name = { baseVal: "cat#{section.category_id} sec#{section.id}" }
+ style = { fill: section.category.color }
+
+ section.resources.each do |marker|
+ # Add styling
+ marker = marker.to_geojson
+ marker['className'] = class_name
+ marker['style'] = style
+
+ out << marker.to_json
+ end
+
+ raw("{ \"type\": \"FeatureCollection\", \"features\": [ #{out.join(',')} ] }")
+ end
+end
diff --git a/app/helpers/taxonomies/filter_helper.rb b/app/helpers/taxonomies/filter_helper.rb
index 97fe271..92a501f 100644
--- a/app/helpers/taxonomies/filter_helper.rb
+++ b/app/helpers/taxonomies/filter_helper.rb
@@ -11,7 +11,7 @@ module Taxonomies::FilterHelper
@taxonomy.categories.each do |cat|
list = []
cat.sections.each do |sec|
- list << tag.li(h("#{sec.rank}. #{sec.name}"), id: "section-#{sec.id}", data: { action: "taxonomy#section", target: 'taxonomy.section', 'taxonomy-section-id': sec.id })
+ list << tag.li(h("#{sec.rank}. #{sec.name}"), id: "section-#{sec.id}", data: { action: "click->taxonomy#section", target: 'taxonomy.section', 'taxonomy-section-id': sec.id })
end
html << tag.li(h("#{cat.rank}. #{cat.name}") << tag.ol(list.join.html_safe),
id: "category-#{cat.id}",
diff --git a/app/javascript/controllers/map_controller.js b/app/javascript/controllers/map_controller.js
index 30c1361..1704da8 100644
--- a/app/javascript/controllers/map_controller.js
+++ b/app/javascript/controllers/map_controller.js
@@ -7,32 +7,65 @@
//
import { Controller } from "stimulus"
-import "leaflet/dist/leaflet.css"
-import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css'; // Re-uses images from ~leaflet package
-import L from "leaflet"
+import 'leaflet/dist/leaflet.css'
+// Re-uses images from ~leaflet package
+import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css'
+
+import * as L from 'leaflet'
+import 'leaflet-defaulticon-compatibility'
+import "leaflet-providers"
+import "leaflet.markercluster/dist/leaflet.markercluster.js"
+
+import 'leaflet-makimarkers'
export default class extends Controller {
static targets = [ "container" ]
initialize() {
-
+ console.log("Map controller initialized.")
+ this.mapBoxAPIToken = 'pk.eyJ1IjoibmVtYWVsIiwiYSI6ImNrZzBrYjBudTB3bnMyenFmNWtrN3h3bmMifQ.Rkeyhm-9iIQOV7NAMA5LaA'
}
connect() {
- this.map = L.map(this.containerTarget, {
- zoomDelta: 0.5,
- zoomSnap: 0.5,
- }).setView(this._coordinates(), this._zoom());
+ L.MakiMarkers.accessToken = this.mapBoxAPIToken
- L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
+ var mapbox = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
tileSize: 512,
zoomOffset: -1,
- accessToken: 'pk.eyJ1IjoibmVtYWVsIiwiYSI6ImNrZzBrYjBudTB3bnMyenFmNWtrN3h3bmMifQ.Rkeyhm-9iIQOV7NAMA5LaA'
- }).addTo(this.map);
+ accessToken: this.mapBoxAPIToken
+ })
+
+ var stamen_tr = L.tileLayer.provider('Stamen.Toner')
+ var stamen_wc = L.tileLayer.provider('Stamen.Watercolor')
+ var tiles_osm = L.tileLayer.provider('OpenStreetMap.Mapnik')
+ var tiles_sat = L.tileLayer.provider('Esri.WorldImagery')
+
+ const tilemaps = {
+ 'osm': tiles_osm,
+ 'mapbox': mapbox,
+ 'watercolor': stamen_wc,
+ 'greyscale': stamen_tr,
+ 'satellite': tiles_sat
+ }
+
+ const overlays = {}
+
+ this.map = L.map(this.containerTarget, {
+ zoomDelta: 0.5,
+ zoomSnap: 0.5,
+ layers: [ tiles_osm ],
+ }).setView(this._coordinates(), this._zoom());
+
+ L.control.layers(tilemaps, overlays).addTo(this.map)
+
+ L.DomEvent.on(window, 'hashchange', this.doSomethingCool);
+
+ // Allow calling the mapController from elsewhere.
+ this.element[this.identifier] = this
}
disconnect() {
@@ -40,6 +73,10 @@ export default class extends Controller {
console.log('Map controller disconnected.')
}
+ doSomethingCool() {
+ console.log(window.location.hash)
+ }
+
_coordinates() {
return [this.data.get('latitude'), this.data.get('longitude')];
}
diff --git a/app/javascript/controllers/taxonomy_controller.js b/app/javascript/controllers/taxonomy_controller.js
index 16d57fa..02b1483 100644
--- a/app/javascript/controllers/taxonomy_controller.js
+++ b/app/javascript/controllers/taxonomy_controller.js
@@ -9,15 +9,18 @@
import { Controller } from "stimulus"
export default class extends Controller {
- static targets = [ "category", "deploy", "filter", "section", "toggle" ]
+ static targets = [ "category", "deploy", "filter", "layers", "section", "toggle" ]
initialize() {
console.log("Taxonomy controller initialized.")
+
+ this.overlays = {}
+ this.sectionsOnMap = []
}
connect() {
console.log("Taxonomy controller connected.")
-
+ this.taxonomy_uuid = this.data.get('uuid')
if (this.hasFilterTarget) {
let url = `/taxonomies/${this.data.get('uuid')}/filter.js`
console.log(`loading url = ${url}`)
@@ -29,10 +32,19 @@ export default class extends Controller {
} else {
console.log("Taxonomy filter is missing")
}
+
+ // Access the map
+ if (this.map = document.querySelector('#map').parentElement.map.map) {
+ console.log("Taxonomy Controller connected to this.map")
+ } else {
+ console.log("Taxonomy Controller could not load map!")
+ }
}
+
deploy() {
console.log(`deploying taxonomy ${this.data.get('uuid')}`);
+
fetch(`/taxonomies/${this.data.get('uuid')}.js`)
.then(response => response.text())
.then(html => this.deployTarget.innerHTML = html);
@@ -45,12 +57,100 @@ export default class extends Controller {
}
category(event) {
- let catId = event.target.dataset.taxonomyCategoryId
- event.target.classList.toggle('active')
+ let catId = (event.target.dataset.taxonomyCategoryId || event.target.parentNode.parentNode.dataset.CategoryId)
+ let secId = event.target.dataset.taxonomySectionId
+ console.log(`Category: ${catId}/${secId}`)
+ var active = event.target.classList.toggle('active')
+ if (active) {
+ console.log('activated')
+ } else {
+ console.log('deactivated')
+ }
+ }
+
+ loadCategory(catId) {
+ document.querySelector(`#category-${catId}`).lastChild.childNodes.forEach(function(li) {
+ this._mapLayerToggleSection(li.dataset.taxonomySectionId)
+ })
}
section(event) {
- let catId = event.target.dataset.taxonomySectionId
- event.target.classList.toggle('active')
+ let secId = event.target.dataset.taxonomySectionId
+ console.log(`Section: ${secId} to be loaded`)
+ this._loadMarkers(secId)
+ this._mapLayerToggleSection(secId)
+ }
+
+ _sectionIconName(secId) {
+ const names = {
+ 215: 'campsite',
+ 216: 'hospital',
+ 217: 'landmark',
+ 218: 'shelter',
+ 219: 'lodging',
+ 220: 'playground',
+ 221: 'residential-community',
+ 222: 'home',
+ 223: 'residential-community',
+ 224: 'residential-community',
+ 225: 'home'
+
+ }
+ return names[secId] || 'circle'
+ }
+
+ _loadMarkers(secId) {
+ if (this.overlays[secId] == undefined) {
+ console.log(`loading markers for section ${secId} [${this._sectionIconName(secId)}]...`)
+ let overlay = L.layerGroup();
+ let markers = L.markerClusterGroup();
+ let iconName = this._sectionIconName(secId);
+
+ fetch(`/sections/${secId}.json`, {
+ headers: { 'X-CSRF-Token': this._csrfToken() }
+ })
+ .then(response => response.json())
+ .then(data => {
+ L.geoJSON(data, {
+ pointToLayer: function (feature, latlng) {
+ return L.marker(latlng, {
+ attribution: feature.source,
+ icon: L.MakiMarkers.icon({
+ icon: iconName,
+ className: feature.className.baseVal,
+ color: feature.style.fill,
+ size: 'm'
+ })
+ });
+ },
+ onEachFeature: (feature, layer) => {
+ layer.on('click', () => this.onClick(layer))
+ layer.addTo(markers)
+ }
+ })
+ })
+ console.log(`cluster counts ${markers.length} markers`)
+ this.overlays[secId] = markers
+ }
+ }
+
+ onClick(layer) {
+ console.log(layer)
+ }
+
+ _mapLayerToggleSection(secId) {
+ if (this.sectionsOnMap.includes(secId)) {
+ console.log(`removing section ${secId} from map`)
+ this.map.removeLayer(this.overlays[secId])
+ this.sectionsOnMap.pop(secId)
+ } else {
+ console.log(`adding section ${secId} to the map`)
+ this.sectionsOnMap.push(secId)
+ this.overlays[secId].addTo(this.map)
+ }
+ }
+
+ _csrfToken() {
+ return document.querySelector('[name=csrf-token]').content
}
}
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 5e3eec0..a4b51a8 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -15,4 +15,10 @@ require("@rails/activestorage").start()
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
+//= require leaflet
+//= require leaflet-defaulticon-compatibility
+//= require leaflet.markercluster
+
import "controllers"
+
+import 'stylesheets/application'
diff --git a/app/models/agency.rb b/app/models/agency.rb
index 7ffe489..842e8bd 100644
--- a/app/models/agency.rb
+++ b/app/models/agency.rb
@@ -3,34 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
class Agency < ApplicationRecord
- include Bitfields
-
belongs_to :agent
belongs_to :user
-
- bitfield :roles, :observer, :editor, :maintainer, :leader
-
- class << self
- # Grant role in agent to user
- def grant(agent, user, role)
- r = find_or_create_by(agent: agent, user: user)
- r&.public_send("#{role}=", true) && r&.save
- end
-
- # Revoke role in agent from user
- def revoke(agent, user, role)
- r = find_by(agent: agent, user: user)
- r&.public_send("#{role}=", false) && r&.save
- end
- end
-
- # Grant role to current user in current agent
- def grant(role)
- self.class.grant(agent, user, role)
- end
-
- # Revoke role from current user in current agent
- def revoke(role)
- self.class.revoke(agent, user, role)
- end
end
diff --git a/app/models/resource.rb b/app/models/resource.rb
index 8df106c..bde0c56 100644
--- a/app/models/resource.rb
+++ b/app/models/resource.rb
@@ -44,17 +44,17 @@ class Resource < ApplicationRecord
# You can use, e.g.: res.longitude = 0.123
def longitude
- feature['geometry']['coordinates'][0]
+ format('%<lon>3.7f', lon: feature['geometry']['coordinates'][0]).to_f
end
def longitude=(value)
- feature['geometry']['coordinates'][0] = value
+ feature['geometry']['coordinates'][0] = format('%<lon>3.7f', lon: value).to_f
end
# You can use, e.g.: res.latitude = 0.123
def latitude
- feature['geometry']['coordinates'][1]
+ format('%<lat>2.7f', lat: feature['geometry']['coordinates'][1]).to_f
end
def latitude=(value)
- feature['geometry']['coordinates'][1] = value
+ feature['geometry']['coordinates'][1] = format('%<lat>2.7f', lat: value).to_f
end
# Properties
@@ -68,4 +68,16 @@ class Resource < ApplicationRecord
feature['properties'][prop.to_s] = v
end
end
+
+ # Poor man's GeoJSON output
+ def to_geojson
+ out = feature.dup
+ # Convert original Dewey IDs with local Section IDs
+ out['properties']['categories'] = Section.where(dewey_id: out['properties']['categories']).pluck(:id)
+ # Add IN COMMON Resource UUID property
+ out['properties']['uuid'] = uuid
+ # Add IN COMMON Agent UUID property
+ out['properties']['agent_uuid'] = agent.uuid
+ out
+ end
end
diff --git a/app/views/agents/_edit.html.erb b/app/views/agents/_edit.html.erb
new file mode 100644
index 0000000..72ce56d
--- /dev/null
+++ b/app/views/agents/_edit.html.erb
@@ -0,0 +1,6 @@
+<h3>Edit <%= @agent.presence&.name || 'new agent' %></h3>
+<h2>Context: <%= current_agent %></h2>
+
+<%= form_with model: @agent, url: controller.action_name == 'new' ? agents_path : agent_path(@agent) do |f| %>
+ <%= render partial: 'form', locals: { agent: @agent, f: f } %>
+<% end %>
diff --git a/app/views/agents/_form.html.erb b/app/views/agents/_form.html.erb
new file mode 100644
index 0000000..563e8ec
--- /dev/null
+++ b/app/views/agents/_form.html.erb
@@ -0,0 +1,22 @@
+<fieldset>
+ <%= tag.legend "Propriétés de l'Agent" %>
+
+ <dl>
+ <dt><%= f.label :name %></dt>
+ <dd><%= f.text_field :name, maxlength: 64, placeholder: 'incommon' %>
+ <br>hint: this must match a group name on talk.incommon.cc</dd>
+
+ <dt><%= f.label :summary %></dt>
+ <dd><%= f.text_field :summary, maxlength: 136, placeholder: 'Default Agent' %></dd>
+
+ <dt><%= f.label :description %></dt>
+ <dd><%= f.text_area :description, cols: 72, rows: 10, spellcheck: true,
+placeholder: '## IN COMMON Default Agent
+
+La description _peut_ comporter du [Markdown].
+
+[Markdown]: https://www.markdownguide.org/getting-started/' %></dd>
+ </dl>
+</fieldset>
+
+<p><%= f.submit 'Save' %></p>
diff --git a/app/views/agents/index.html.erb b/app/views/agents/index.html.erb
new file mode 100644
index 0000000..6cb9fb3
--- /dev/null
+++ b/app/views/agents/index.html.erb
@@ -0,0 +1,5 @@
+<article id="agents">
+ <h1>Agents</h1>
+
+ <%= render partial: @agents %>
+</article>
diff --git a/app/views/agents/new.html.erb b/app/views/agents/new.html.erb
new file mode 100644
index 0000000..3794662
--- /dev/null
+++ b/app/views/agents/new.html.erb
@@ -0,0 +1,2 @@
+<%= render partial: 'edit', locals: { agent: @agent } %>
+
diff --git a/app/views/agents/show.html.erb b/app/views/agents/show.html.erb
new file mode 100644
index 0000000..0652dc8
--- /dev/null
+++ b/app/views/agents/show.html.erb
@@ -0,0 +1,3 @@
+<article class="agent" id="agent-<%= @agent.to_param %>">
+ <%= render partial: @agent %>
+</article>
diff --git a/app/views/categories/_category.html.erb b/app/views/categories/_category.html.erb
new file mode 100644
index 0000000..d301fe6
--- /dev/null
+++ b/app/views/categories/_category.html.erb
@@ -0,0 +1,17 @@
+<article class="category" id="category-<%= category.id %>">
+ <header style="background-color: <%= category.color %>">
+ <h1><%= category.taxonomy.name %></h1>
+ <h2><%= category.name %></h2>
+ </header>
+ <p><%= h category.summary %></p>
+ <div class="markdown"><%= h category.description %></div>
+ <section id="stats"><h3>Info</h3>
+ <p><%= pluralize(category.sections_count, 'section') %></p>
+ <p><%= pluralize(category.sections.map { |s| s.resources }.sum(&:count) || 0, 'resources') %></p>
+ </section>
+ <section id="actions">
+ <ul>
+ <li><%= link_to 'edit', edit_category_path(category) %></li>
+ </ul>
+ </section>
+</article>
diff --git a/app/views/categories/_edit.html.erb b/app/views/categories/_edit.html.erb
new file mode 100644
index 0000000..4a2da94
--- /dev/null
+++ b/app/views/categories/_edit.html.erb
@@ -0,0 +1,6 @@
+<h3>Edit <%= @category.presence&.name || 'new category' %></h3>
+<h2>Context: <%= @taxonomy %></h2>
+
+<%= form_with model: [@taxonomy,@category], url: controller.action_name == 'new' ? taxonomy_categories_path(taxonomy: @taxonomy) : category_path(@category) do |f| %>
+ <%= render partial: 'form', locals: { category: category, f: f } %>
+<% end %>
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb
new file mode 100644
index 0000000..c4c77c6
--- /dev/null
+++ b/app/views/categories/_form.html.erb
@@ -0,0 +1,25 @@
+<fieldset>
+ <%= tag.legend "Categorie..." %>
+
+ <dl>
+ <dt><%= f.label :name %></dt>
+ <dd><%= f.text_field :name, maxlength: 64, placeholder: 'Se loger' %></dd>
+
+ <dt><%= f.label :summary %></dt>
+ <dd><%= f.text_field :summary, maxlength: 136, placeholder: 'Une ferme locale' %></dd>
+
+ <dt><%= f.label :description %></dt>
+ <dd><%= f.text_area :description, cols: 72, rows: 10, placeholder: '## Un choix pertinent
+
+La description _peut_ comporter du [Markdown].
+
+[Markdown]: https://www.markdownguide.org/getting-started/' %></dd>
+
+ <dt><%= f.label :color %></dt>
+ <dd><%= f.color_field :color, placeholder: '#cc0077 (or: rgba(255 0 0 0.5)' %></dd>
+ </dl>
+</fieldset>
+
+<%= f.hidden_field :taxonomy_id, value: @category.taxonomy_id %>
+
+<p><%= f.submit 'Save' %></p>
diff --git a/app/views/categories/edit.html.erb b/app/views/categories/edit.html.erb
new file mode 100644
index 0000000..054bed3
--- /dev/null
+++ b/app/views/categories/edit.html.erb
@@ -0,0 +1 @@
+<%= render partial: 'edit', locals: { category: @category } %>
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
new file mode 100644
index 0000000..ed548c6
--- /dev/null
+++ b/app/views/categories/show.html.erb
@@ -0,0 +1,2 @@
+<%= @category.inspect %>
+<%= render @category %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 210ace8..bc02585 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -4,33 +4,34 @@
<meta charset="UTF-8">
<title>Carte IN COMMON</title>
<meta name="viewport" content="initial-width: device-width initial-scale=1.0 viewport-fit=cover">
- <%= csrf_meta_tags %>
- <%= csp_meta_tag %>
+ <%= csrf_meta_tags %>
+ <%= csp_meta_tag %>
+ <%= display_meta_tags site: 'IN COMMON Map' %>
- <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
- <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
- <%= yield :head %>
- </head>
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+ <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
+ <%= yield :head %>
+ </head>
- <body>
- <header>
- <h1><%= link_to image_tag("https://talk.incommon.cc/uploads/disc_6_incommon/original/1X/92f926e8210ea7a5412d6e2bdabfa825233e808b.png", alt: "IN COMMON", size: "x4em"), '/' %></h1>
- <%= render 'application/user_info' %>
- <%= yield :header %>
- </header>
- <main>
- <article>
- <%= yield %>
- </article>
- </main>
- <aside>
- <%= yield :aside %>
- </aside>
- <footer>
- <%= yield :footer %>
- </footer>
- <%= render partial: 'debug' %>
- <%= render partial: 'flash' %>
- <%= yield :body_end %>
- </body>
+ <body>
+ <header>
+ <h1><%= link_to image_tag("https://talk.incommon.cc/uploads/disc_6_incommon/original/1X/92f926e8210ea7a5412d6e2bdabfa825233e808b.png", alt: "IN COMMON", size: "x4em"), '/' %></h1>
+ <%= render 'application/user_info' %>
+ <%= yield :header %>
+ </header>
+ <main>
+ <article>
+ <%= yield %>
+ </article>
+ </main>
+ <aside>
+ <%= yield :aside %>
+ </aside>
+ <footer>
+ <%= yield :footer %>
+ </footer>
+ <%= render partial: 'debug' %>
+ <%= render partial: 'flash' %>
+ <%= yield :body_end %>
+ </body>
</html>
diff --git a/app/views/resources/_resource.json.erb b/app/views/resources/_resource.json.erb
new file mode 100644
index 0000000..a3769d6
--- /dev/null
+++ b/app/views/resources/_resource.json.erb
@@ -0,0 +1 @@
+<%= resource.as_json %>
diff --git a/app/views/resources/show.json.erb b/app/views/resources/show.json.erb
new file mode 100644
index 0000000..5855d72
--- /dev/null
+++ b/app/views/resources/show.json.erb
@@ -0,0 +1 @@
+<%= render @resource %>
diff --git a/app/views/sections/show.html.erb b/app/views/sections/show.html.erb
new file mode 100644
index 0000000..9a69891
--- /dev/null
+++ b/app/views/sections/show.html.erb
@@ -0,0 +1,11 @@
+<pre><code lang="ecmascript">
+// First, create a section layer on the map, then assign all resource markers to it.
+sec<%= @section.id %> = L.layerGroup
+
+<% @section.resources.each_with_index do |res, i| %>
+mk_<%= i %> = L.marker([<%= res.latitude %>, <%= res.longitude %>]).addTo(sec<%= @section.id %>)
+<% end %>
+
+sec<%= @section.id %>.addTo(map)
+
+</code></pre>
diff --git a/app/views/sections/show.json.erb b/app/views/sections/show.json.erb
new file mode 100644
index 0000000..9520404
--- /dev/null
+++ b/app/views/sections/show.json.erb
@@ -0,0 +1 @@
+<%= geojson_feature_collection(@section) %>
diff --git a/app/views/taxonomies/_taxonomy.html.erb b/app/views/taxonomies/_taxonomy.html.erb
index eff6931..eb2dd79 100644
--- a/app/views/taxonomies/_taxonomy.html.erb
+++ b/app/views/taxonomies/_taxonomy.html.erb
@@ -5,4 +5,3 @@
<button data-action="taxonomy#deploy">Preview</button>
<div class="deploy" data-target="taxonomy.deploy"></div>
</section>
-
diff --git a/app/views/taxonomies/index.html.erb b/app/views/taxonomies/index.html.erb
index d060f6c..705b0d4 100644
--- a/app/views/taxonomies/index.html.erb
+++ b/app/views/taxonomies/index.html.erb
@@ -1,5 +1,5 @@
<section id="taxonomies">
<h2>Available Taxonomies</h2>
- <%= render collection: @taxonomies: %>
+ <%= render collection: @taxonomies %>
</section>
diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb
new file mode 100644
index 0000000..2da5239
--- /dev/null
+++ b/app/views/users/_user.html.erb
@@ -0,0 +1,3 @@
+<div class="member">
+ <span class="name"><%= user.name %></span>
+</div>
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb
index 0d53728..3451981 100644
--- a/app/views/users/index.html.erb
+++ b/app/views/users/index.html.erb
@@ -1,4 +1,4 @@
-<h1>Users#index</h1>
-<p>Find me in app/views/users/index.html.erb</p>
+<h1>Agent <%= current_agent %> Members</h1>
+<p>This agent counts (<%= pluralize(@users.count, "member") %>)</p>
-<p>Show a table with Agent <%= current_agent %> members (<%= pluralize(@users.count, "user") %>), their roles, and ways to change them.</p>
+<%= render partial: 'user', collection: @users %>
diff --git a/app/views/welcome/authenticate.html.erb b/app/views/welcome/authenticate.html.erb
index 548101b..ba9f1c5 100644
--- a/app/views/welcome/authenticate.html.erb
+++ b/app/views/welcome/authenticate.html.erb
@@ -5,7 +5,7 @@
<p>Your Agents:
<ul>
<% @current_user&.agencies&.each do |a| %>
- <li><%= a.name %> (<%= a.roles %>)</li>
+ <li><%= a.name %></li>
<% end %>
</ul>
</p>
diff --git a/app/views/welcome/index.html.erb b/app/views/welcome/index.html.erb
index 2a154ae..d9fd0f6 100644
--- a/app/views/welcome/index.html.erb
+++ b/app/views/welcome/index.html.erb
@@ -8,14 +8,11 @@
<button data-action="taxonomy#toggle" data-target="taxonomy.toggle"></button>
</div>
<div data-target="taxonomy.filter"></div>
+ <div data-target="taxonomy.layers"></div>
</div>
<% end %>
<% content_for :debug do %>
<% if current_user.present? %>
- <p>Premier élément de la liste:</p>
- <% res = @resources.first %>
- <p><%= res.feature["properties"]["name"] %></p>
- <p><%= res.feature["geometry"]["coordinates"] %></p>
<% end %><%# ensure it does not break when we don't have a record... %>
<% end %>