geo_labels 0.3.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +1 -0
- data/app/assets/javascripts/geo_labels/application.coffee +1 -1
- data/app/controllers/geo_labels/application_controller.rb +5 -0
- data/app/controllers/geo_labels/contacts_controller.rb +1 -1
- data/app/controllers/geo_labels/dashboard_controller.rb +30 -8
- data/app/helpers/geo_labels/application_helper.rb +1 -0
- data/app/models/geo_labels/contact.rb +16 -2
- data/app/models/geo_labels/contact_label.rb +1 -0
- data/app/models/geo_labels/label.rb +14 -0
- data/app/views/geo_labels/contacts/_form.html.slim +9 -2
- data/app/views/geo_labels/contacts/index.html.slim +6 -0
- data/app/views/geo_labels/contacts/show.html.slim +5 -1
- data/app/views/geo_labels/dashboard/about.html.slim +3 -0
- data/app/views/geo_labels/dashboard/export.html.slim +7 -3
- data/app/views/geo_labels/dashboard/main.html.slim +8 -1
- data/app/views/geo_labels/menu/_navigation.html.slim +23 -0
- data/app/views/geo_labels/menu/_right_menu_general.html.slim +11 -0
- data/app/views/geo_labels/users/_abilities.html.slim +20 -0
- data/app/views/layouts/geo_labels/application.html.slim +1 -1
- data/config/locales/geo_labels.en.yml +17 -0
- data/config/locales/geo_labels.es.yml +20 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20231206183544_add_url_to_geo_labels_contacts.rb +5 -0
- data/lib/geo_labels/exporter.rb +5 -2
- data/lib/geo_labels/importer/csv_data.rb +43 -0
- data/lib/geo_labels/importer/yaml_data.rb +116 -0
- data/lib/geo_labels/importer.rb +33 -112
- data/lib/geo_labels/version.rb +1 -1
- data/lib/geo_labels.rb +2 -0
- metadata +12 -6
- data/app/views/geo_labels/application/_navigation.html.slim +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30768188b560ce1db38ddd61a8f06c37eb44334bba0ea8309efbf1f0b6b809fe
|
4
|
+
data.tar.gz: b295252da7f1b305a7a7029884b3e50c6c239ba54b3914727b51469d38021f4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d145b7d63a43d47d771b1a76c06b17afa032d035d385bc64f89fb0d565b1809b3021f6d970fb018e1ba9983c8a270443d41b5600261046cb612a6cb2e14ef209
|
7
|
+
data.tar.gz: fe7b5b251a2a7e51f76f4a39a0aafa8ac308f830c0ad63544cb7fad9d469a96162ea212c07cc41bdc3f48c3dcde8c49d744656a7c20420c33188c6267a33d34f
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,35 @@
|
|
1
1
|
CHANGELOG of geo\_labels
|
2
2
|
============================
|
3
3
|
|
4
|
+
2023-12-23 v0.4.3
|
5
|
+
-------------------------
|
6
|
+
* Optimize nested label representation to sort of N+1 query strategy
|
7
|
+
* Show labels on main query result page of contacts
|
8
|
+
|
9
|
+
2023-10-25 v0.4.2
|
10
|
+
-------------------------
|
11
|
+
* Truncate label names to prevent ugly outputting
|
12
|
+
|
13
|
+
2023-10-25 v0.4.1
|
14
|
+
-------------------------
|
15
|
+
* Add CSV import options
|
16
|
+
* Add authorization options
|
17
|
+
* Allow unsecure access by explicit authorization in `app/models/ability.rb`
|
18
|
+
<code>
|
19
|
+
can :manage, GeoLabels::Engine
|
20
|
+
can :manage, GeoLabels::Contact
|
21
|
+
can :manage, GeoLabels::Label
|
22
|
+
</code>
|
23
|
+
|
24
|
+
2023-10-24 v0.4.0
|
25
|
+
-------------------------
|
26
|
+
* Set default map zoom level to 13
|
27
|
+
* Add CSV export option
|
28
|
+
|
29
|
+
2023-09-07 v0.3.3
|
30
|
+
-------------------------
|
31
|
+
* OpenStreetMap gives coordinates in the url after double click. Support copy paste of that format
|
32
|
+
|
4
33
|
2023-09-07 v0.3.2
|
5
34
|
-------------------------
|
6
35
|
* Add manual Google maps copy-paste string input option for complex locations
|
data/README.md
CHANGED
@@ -35,6 +35,7 @@ In your `config/routes.rb` file add:
|
|
35
35
|
```
|
36
36
|
|
37
37
|
### Add the authorizations
|
38
|
+
Authorization is handled by [cancan](https://github.com/CanCanCommunity/cancancan).
|
38
39
|
In your `app/models/ability.rb` file add the authorizations.
|
39
40
|
This is a custom operation that you have to adjust to your needs.
|
40
41
|
To allow all users full controll to the contracts add:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GeoLabels
|
3
3
|
class ApplicationController < ::ApplicationController
|
4
|
+
before_action :authorize_engine!
|
4
5
|
|
5
6
|
helper_method :query
|
6
7
|
|
@@ -13,5 +14,9 @@ module GeoLabels
|
|
13
14
|
def query
|
14
15
|
params[:q]
|
15
16
|
end
|
17
|
+
|
18
|
+
def authorize_engine!
|
19
|
+
authorize! :read, GeoLabels::Engine
|
20
|
+
end
|
16
21
|
end
|
17
22
|
end
|
@@ -9,7 +9,7 @@ module GeoLabels
|
|
9
9
|
authorize! :index, record_class
|
10
10
|
@q = record_class.ransack query
|
11
11
|
@q.sorts = 'name asc' if @q.sorts.empty?
|
12
|
-
@records = @q.result.page(params[:page]).per(100)
|
12
|
+
@records = @q.result.includes(:labels).page(params[:page]).per(100)
|
13
13
|
respond_with(@records)
|
14
14
|
end
|
15
15
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GeoLabels
|
3
4
|
class DashboardController < ApplicationController
|
4
5
|
|
@@ -9,27 +10,48 @@ module GeoLabels
|
|
9
10
|
# fomantic-ui uses comma separated id values
|
10
11
|
label_ids = label_ids.to_s.split(',') unless label_ids.is_a?(Array)
|
11
12
|
states_array = (params[:states].presence || 'approved').split(',')
|
12
|
-
@contacts = Contact.geocoded.for_label_ids(label_ids, predication: params[:predication], states: states_array)
|
13
|
+
@contacts = Contact.geocoded.includes(:labels).for_label_ids(label_ids, predication: params[:predication], states: states_array)
|
13
14
|
end
|
14
15
|
|
16
|
+
def about; end
|
17
|
+
|
18
|
+
# SHOW action for the view to call the export_file action
|
15
19
|
def export
|
16
|
-
|
20
|
+
authorize! :read, GeoLabels::Contact
|
21
|
+
authorize! :read, GeoLabels::Label
|
17
22
|
end
|
18
23
|
|
19
24
|
def export_file
|
25
|
+
authorize! :read, GeoLabels::Contact
|
26
|
+
authorize! :read, GeoLabels::Label
|
27
|
+
|
20
28
|
timestamp = Time.now.utc.iso8601.first(19).gsub(/-|:/, '')
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
29
|
+
respond_to do |format|
|
30
|
+
format.yaml do
|
31
|
+
send_data GeoLabels::Exporter.export_str,
|
32
|
+
filename: "GeoLabels-export-#{timestamp}.yml",
|
33
|
+
type: :text,
|
34
|
+
disposition: :attachment
|
35
|
+
end
|
36
|
+
format.csv do
|
37
|
+
send_data GeoLabels::Contact.to_csv,
|
38
|
+
filename: "GeoLabels-export-#{timestamp}.csv",
|
39
|
+
type: :text,
|
40
|
+
disposition: :attachment
|
41
|
+
end
|
42
|
+
end
|
25
43
|
end
|
26
44
|
|
27
45
|
def import
|
28
|
-
|
46
|
+
authorize! :update, GeoLabels::Contact
|
47
|
+
authorize! :update, GeoLabels::Label
|
29
48
|
end
|
30
49
|
|
31
50
|
def import_file
|
32
|
-
|
51
|
+
authorize! :update, GeoLabels::Contact
|
52
|
+
authorize! :update, GeoLabels::Label
|
53
|
+
|
54
|
+
GeoLabels::Importer.import params[:file]
|
33
55
|
redirect_to root_path, notice: 'File imported'
|
34
56
|
rescue => e
|
35
57
|
redirect_to import_path, alert: e.message
|
@@ -13,6 +13,7 @@ module GeoLabels
|
|
13
13
|
label_name = link_to(item[:name], label_path(item[:id]))
|
14
14
|
edit_link = table_edit_link(item, geo_labels.edit_label_path(item[:id]))
|
15
15
|
add_child_link = link_to(tag.i(class: 'plus icon'), geo_labels.new_label_path(parent_id: item[:id]))
|
16
|
+
add_child_link = edit_link = '' unless can? :edit, GeoLabels::Label
|
16
17
|
output += "#{lead_space}#{tag.li(label_name + add_child_link + edit_link)}\n".html_safe
|
17
18
|
next unless item[:children].present?
|
18
19
|
|
@@ -7,7 +7,7 @@ module GeoLabels
|
|
7
7
|
include RatingsSupport
|
8
8
|
|
9
9
|
RANSACKABLE_ATTRIBUTES = %w[
|
10
|
-
city country created_at department description
|
10
|
+
city country created_at department url description
|
11
11
|
id latitude longitude name state street
|
12
12
|
subsection updated_at
|
13
13
|
].freeze
|
@@ -42,6 +42,20 @@ module GeoLabels
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def self.to_csv
|
46
|
+
require 'csv'
|
47
|
+
contacts = includes(:labels).all
|
48
|
+
additional_attributes = %w[description state street subsection city department country url latitude longitude]
|
49
|
+
CSV.generate do |csv|
|
50
|
+
csv << %w[name labels] + additional_attributes
|
51
|
+
contacts.each do |contact|
|
52
|
+
csv << [contact.name, contact.labels.map(&:name).compact.sort.join('|')] +
|
53
|
+
contact.attributes.fetch_values(*additional_attributes)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
45
59
|
def label_ids=(array_or_string)
|
46
60
|
super array_or_string.is_a?(Array) ? array_or_string : array_or_string.split(',')
|
47
61
|
end
|
@@ -98,7 +112,7 @@ module GeoLabels
|
|
98
112
|
|
99
113
|
def lat_lng=(value)
|
100
114
|
case value
|
101
|
-
when String then self.latitude, self.longitude = value.split(/,\s?/)
|
115
|
+
when String then self.latitude, self.longitude = value.sub('/', ',').split(/,\s?/)
|
102
116
|
when Array then self.latitude, self.longitude = value
|
103
117
|
end
|
104
118
|
end
|
@@ -9,6 +9,7 @@ module GeoLabels
|
|
9
9
|
|
10
10
|
acts_as_nested_set
|
11
11
|
|
12
|
+
normalizes :name, with: ->(name) { name.strip }
|
12
13
|
validates :name, presence: true
|
13
14
|
|
14
15
|
def self.descendants_for_ids(ids)
|
@@ -20,9 +21,22 @@ module GeoLabels
|
|
20
21
|
descendants_scope
|
21
22
|
end
|
22
23
|
|
24
|
+
def self.each_h_with_level(&blk)
|
25
|
+
returner = proc do |label, level|
|
26
|
+
blk.call(label, level)
|
27
|
+
label[:children].try :each do |child|
|
28
|
+
returner.call(child, level + 1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
Exporter.labels_tree_h.each do |label| # {name: 'Food', id: 3, children: [{name: 'Italian'...}]}
|
32
|
+
returner.call(label, 0)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
23
36
|
def associated_contacts
|
24
37
|
ids = ContactLabel.joins(:label).merge(self_and_descendants).pluck(:contact_id).uniq
|
25
38
|
Contact.order(:name).find(ids)
|
26
39
|
end
|
40
|
+
|
27
41
|
end
|
28
42
|
end
|
@@ -36,9 +36,12 @@
|
|
36
36
|
.menu
|
37
37
|
- GeoLabels::Contact::STATE_OPTIONS.each do |option|
|
38
38
|
.item data-value=option = option
|
39
|
+
.field
|
40
|
+
label= at :url
|
41
|
+
= f.text_field :url
|
39
42
|
.field
|
40
43
|
label= at :description
|
41
|
-
= f.
|
44
|
+
= f.text_area :description, rows: 3
|
42
45
|
.field
|
43
46
|
.ui.compact.accordion
|
44
47
|
.title
|
@@ -53,7 +56,11 @@
|
|
53
56
|
i.dropdown.icon
|
54
57
|
.default.text= GeoLabels::Label.model_name.human_plural
|
55
58
|
.menu
|
56
|
-
-
|
59
|
+
- GeoLabels::Label.each_h_with_level do |label_h, level|
|
60
|
+
.item data-value=label_h[:id]
|
61
|
+
=> '-' * level
|
62
|
+
= label_h[:name]
|
63
|
+
/- nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
|
57
64
|
.item data-value=label_id
|
58
65
|
= label_name
|
59
66
|
.ui.bottom.attached.segment
|
@@ -11,6 +11,7 @@
|
|
11
11
|
th= sort_link @q, :city, at(:city), default_order: :asc
|
12
12
|
th= sort_link @q, :department, at(:department), default_order: :asc
|
13
13
|
th= sort_link @q, :country, at(:country), default_order: :asc
|
14
|
+
th= GeoLabels::Label.model_name.human_plural
|
14
15
|
th.collapsing.column-filter.actions
|
15
16
|
= search_result_info @records
|
16
17
|
= search_row class: 'ui mini form' do
|
@@ -18,6 +19,7 @@
|
|
18
19
|
th= f.search_field :city_cont, placeholder: at(:city)
|
19
20
|
th= f.search_field :department_cont, placeholder: at(:department)
|
20
21
|
th= f.search_field :country_cont, placeholder: at(:country)
|
22
|
+
th
|
21
23
|
th.collapsing.column-filter.actions
|
22
24
|
button.ui.mini.primary.icon.button
|
23
25
|
i.filter.icon
|
@@ -32,6 +34,10 @@
|
|
32
34
|
td= record.city
|
33
35
|
td= record.department
|
34
36
|
td= record.country
|
37
|
+
td
|
38
|
+
.ui.horizontal.divided.list
|
39
|
+
- record.labels.each do |label|
|
40
|
+
= link_to label.name, label, class: 'item'
|
35
41
|
td.collapsing.actions
|
36
42
|
= table_show_link record
|
37
43
|
= table_edit_link record
|
@@ -27,9 +27,13 @@
|
|
27
27
|
= link_to 'Approve', geo_labels.approve_contact_path(@record.id), method: :put, class: 'ui right floated button positive'
|
28
28
|
.ui.basic.label class=(@record.state == 'rejected' ? 'red' : (@record.state == 'approved' ? 'green' : 'yelow'))
|
29
29
|
= @record.state
|
30
|
+
tr
|
31
|
+
td= at :url
|
32
|
+
td
|
33
|
+
a href=@record.url target='_blank' = @record.url
|
30
34
|
tr
|
31
35
|
td= at :description
|
32
|
-
td
|
36
|
+
td== @record.description
|
33
37
|
- if @record.geocoded?
|
34
38
|
tr
|
35
39
|
td Location
|
@@ -1,4 +1,8 @@
|
|
1
|
-
.ui.
|
1
|
+
.ui.container
|
2
2
|
= page_title t('geo_labels.export')
|
3
|
-
.ui.segment
|
4
|
-
|
3
|
+
.ui.top.attached.segment
|
4
|
+
= link_to t('geo_labels.export_yml.title'), geo_labels.export_file_path(format: :yml), class: 'ui primary button'
|
5
|
+
.ui.basic.left.pointing.label= t 'geo_labels.export_yml.explanation'
|
6
|
+
.ui.bottom.attached.segment
|
7
|
+
= link_to t('geo_labels.export_csv.title'), geo_labels.export_file_path(format: :csv), class: 'ui primary button'
|
8
|
+
.ui.basic.left.pointing.label= t 'geo_labels.export_csv.explanation'
|
@@ -11,7 +11,11 @@
|
|
11
11
|
.default.text= GeoLabels::Label.model_name.human_plural
|
12
12
|
.menu
|
13
13
|
/- ISO3166::Country.all.each do |country|
|
14
|
-
-
|
14
|
+
- GeoLabels::Label.each_h_with_level do |label_h, level|
|
15
|
+
.item data-value=label_h[:id]
|
16
|
+
=> '-' * level
|
17
|
+
= label_h[:name]
|
18
|
+
/- nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
|
15
19
|
.item data-value=label_id
|
16
20
|
= label_name
|
17
21
|
.four.wide.field
|
@@ -47,6 +51,9 @@
|
|
47
51
|
.ui.item
|
48
52
|
= link_to contact.name, contact
|
49
53
|
= address_tag contact.address, title: contact.description
|
54
|
+
- contact.labels.sort_by(&:name).each do |label|
|
55
|
+
span.ui.label= label.name
|
56
|
+
|
50
57
|
.ui.bottom.attached.tab.segment data-tab='map'
|
51
58
|
#map-canvas
|
52
59
|
/.ui.horizontal.divider= GeoLabels::Contact.model_name.human_plural
|
@@ -0,0 +1,23 @@
|
|
1
|
+
.ui.inverted.menu.fixed
|
2
|
+
= link_to main_app.root_path, class: 'header item'
|
3
|
+
= Rails.application.config.x.geo_labels.link_home_content.call.to_s.html_safe
|
4
|
+
= link_to geo_labels.root_path, class: 'header item'
|
5
|
+
.ui.icon
|
6
|
+
i.map.icon
|
7
|
+
= Rails.application.config.x.geo_labels.application_title.call.to_s.html_safe
|
8
|
+
- if can? :read, GeoLabels::Contact
|
9
|
+
= link_to GeoLabels::Contact.model_name.human_plural, [GeoLabels::Contact], class: ['item', active_class('geo_labels/contacts')]
|
10
|
+
- if can? :read, GeoLabels::Label
|
11
|
+
= link_to GeoLabels::Label.model_name.human_plural, [GeoLabels::Label], class: ['item', active_class('geo_labels/labels')]
|
12
|
+
.right.menu
|
13
|
+
.ui.dropdown.item
|
14
|
+
span= current_user.try(:nickname) || current_user.try(:email) || 'Menu'
|
15
|
+
i.dropdown.icon
|
16
|
+
.menu
|
17
|
+
= render 'geo_labels/menu/right_menu_general'
|
18
|
+
- if current_user.present?
|
19
|
+
= link_to main_app.destroy_user_session_path, method: :delete, class: 'item'
|
20
|
+
i.sign.out.icon
|
21
|
+
span.text= t 'user.sign_out'
|
22
|
+
- else
|
23
|
+
= link_to t('devise.sessions.new.sign_in'), main_app.new_user_session_path, class: 'item'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
= link_to geo_labels.about_path, class: 'item'
|
2
|
+
i.question.circle.outline.icon
|
3
|
+
span.text= t 'geo_labels.about.title'
|
4
|
+
- if can? :read, GeoLabels::Contact and can? :read, GeoLabels::Label
|
5
|
+
= link_to geo_labels.export_path, class: 'item'
|
6
|
+
i.file.export.icon
|
7
|
+
span.text= t('geo_labels.export')
|
8
|
+
- if can? :update, GeoLabels::Contact and can? :update, GeoLabels::Label
|
9
|
+
= link_to geo_labels.import_path, class: 'item'
|
10
|
+
i.file.import.icon
|
11
|
+
span.text= t('geo_labels.import')
|
@@ -0,0 +1,20 @@
|
|
1
|
+
.ui.top.attached.block.header= Rails.application.config.x.geo_labels.application_title.call
|
2
|
+
table.roles.ui.bottom.attached.compact.celled.table
|
3
|
+
thead
|
4
|
+
/th= HawkEye::Analysis.model_name.human
|
5
|
+
th
|
6
|
+
th read
|
7
|
+
th manage
|
8
|
+
tbody
|
9
|
+
tr
|
10
|
+
td Engine
|
11
|
+
td= dunlop_ability :read, 'engine', 'geo_labels'
|
12
|
+
td= dunlop_ability :manage, 'engine', 'geo_labels'
|
13
|
+
tr
|
14
|
+
td= GeoLabels::Contact.model_name.human
|
15
|
+
td= dunlop_class_ability :read, GeoLabels::Contact
|
16
|
+
td= dunlop_class_ability :manage, GeoLabels::Contact
|
17
|
+
tr
|
18
|
+
td= GeoLabels::Label.model_name.human
|
19
|
+
td= dunlop_class_ability :read, GeoLabels::Label
|
20
|
+
td= dunlop_class_ability :manage, GeoLabels::Label
|
@@ -15,7 +15,7 @@ html lang="en"
|
|
15
15
|
window.geo_labels = #{{paths: GeoLabels::Engine.client_paths}.to_json.html_safe};
|
16
16
|
= content_for :head
|
17
17
|
body
|
18
|
-
header= render 'navigation'
|
18
|
+
header= render 'geo_labels/menu/navigation'
|
19
19
|
span= Rails.env
|
20
20
|
main role="main"
|
21
21
|
= render 'messages'
|
@@ -1,6 +1,23 @@
|
|
1
1
|
en:
|
2
|
+
activerecord:
|
3
|
+
models:
|
4
|
+
geo_labels/contact: Contact
|
5
|
+
geo_labels/label: Label
|
6
|
+
plural:
|
7
|
+
geo_labels/contact: Contacts
|
8
|
+
geo_labels/label: Labels
|
9
|
+
attributes:
|
10
|
+
geo_labels/contact:
|
11
|
+
url: 'URL'
|
12
|
+
|
2
13
|
geo_labels:
|
3
14
|
export: Export
|
15
|
+
export_yml:
|
16
|
+
title: 'Export backup file (yaml)'
|
17
|
+
explanation: 'This export can be used to change and rebuild the data'
|
18
|
+
export_csv:
|
19
|
+
title: 'Export CSV'
|
20
|
+
explanation: 'This file is for spreadsheet views'
|
4
21
|
import: Import
|
5
22
|
tree_title: Tree
|
6
23
|
select_labels_text: Select %{models} and search...
|
@@ -6,9 +6,19 @@ es:
|
|
6
6
|
plural:
|
7
7
|
geo_labels/contact: Contactos
|
8
8
|
geo_labels/label: Etiquetas
|
9
|
+
attributes:
|
10
|
+
geo_labels/contact:
|
11
|
+
url: 'URL'
|
12
|
+
|
9
13
|
|
10
14
|
geo_labels:
|
11
15
|
export: Exportar
|
16
|
+
export_yml:
|
17
|
+
title: 'Exportar archivo de restaurar (yaml)'
|
18
|
+
explanation: 'Ese archivo se puede utilizar en rehacer el sistema'
|
19
|
+
export_csv:
|
20
|
+
title: 'Exportar CSV'
|
21
|
+
explanation: 'Ese archivo es para ver los datos en hoja de cálculo'
|
12
22
|
import: Importar
|
13
23
|
tree_title: Árbol
|
14
24
|
select_labels_text: Seleccionar %{models} y busca...
|
@@ -20,3 +30,13 @@ es:
|
|
20
30
|
Pues en Google maps se puede copiar y pegar<br>
|
21
31
|
coordinatos manuál. Ejemplo:<br>
|
22
32
|
<code>4.973122, -75.623981</code>
|
33
|
+
about:
|
34
|
+
title: 'Sobre GEO'
|
35
|
+
content: >
|
36
|
+
<p>El sistema para manegar listas de contactos y poner etiquetas para buscarlos tiene
|
37
|
+
algunas opciones</p>
|
38
|
+
|
39
|
+
<h3>Manegar etiquetas</h3>
|
40
|
+
<p>Las etiquetas son flexibles en como estructurar los contactos. Etiquetas pueden tener
|
41
|
+
una jerarquia.
|
42
|
+
</p>
|
data/config/routes.rb
CHANGED
data/lib/geo_labels/exporter.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'yaml'
|
3
4
|
module GeoLabels
|
5
|
+
# Yaml exporter of the engine
|
4
6
|
class Exporter
|
5
7
|
|
6
8
|
def self.export_h
|
@@ -20,7 +22,7 @@ module GeoLabels
|
|
20
22
|
output = ''
|
21
23
|
tree_formatter = proc do |items, lead_space|
|
22
24
|
items.each do |item|
|
23
|
-
output
|
25
|
+
output = "#{output}#{lead_space}#{item[:name]}\n"
|
24
26
|
tree_formatter.call(item[:children], "#{lead_space} ") if item[:children].present?
|
25
27
|
end
|
26
28
|
end
|
@@ -33,7 +35,7 @@ module GeoLabels
|
|
33
35
|
GeoLabels::Contact.includes(:labels).map do |contact|
|
34
36
|
contact
|
35
37
|
.attributes
|
36
|
-
.slice(*%w[name street subsection city department country state description latitude longitude])
|
38
|
+
.slice(*%w[name street subsection city department country state url description latitude longitude])
|
37
39
|
.merge('labels' => contact.labels.map(&:name))
|
38
40
|
.select { |_, v| v.present? }
|
39
41
|
.transform_values do |value|
|
@@ -58,5 +60,6 @@ module GeoLabels
|
|
58
60
|
end
|
59
61
|
tree[nil][:children]
|
60
62
|
end
|
63
|
+
|
61
64
|
end
|
62
65
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'csv'
|
3
|
+
|
4
|
+
module GeoLabels
|
5
|
+
module Importer
|
6
|
+
class CsvData
|
7
|
+
attr_reader :new_contacts
|
8
|
+
ROW_ATTRIBUTES = %w[name description state street subsection city department country url latitude longitude]
|
9
|
+
|
10
|
+
def import(data)
|
11
|
+
@new_contacts = []
|
12
|
+
|
13
|
+
counter = 2 # start counting from 1 and skip the header. Works with Apple -> Numbers application
|
14
|
+
CSV.parse(data, headers: true).each do |row|
|
15
|
+
name = row['name']
|
16
|
+
raise "Row #{counter} has no name" unless name.present?
|
17
|
+
|
18
|
+
label_names = row['labels'].to_s.strip.split('|').map(&:strip).select(&:present?)
|
19
|
+
raise "Row #{counter} (#{name}) has no labels" unless label_names.any?
|
20
|
+
labels = label_names.map do |label_name|
|
21
|
+
labels_h[label_name] or raise "Row #{counter} has a label: '#{label_name}' that is not yet in the system. Please add before using it in an import"
|
22
|
+
end
|
23
|
+
|
24
|
+
contact = Contact.new(row.to_h.slice(*ROW_ATTRIBUTES))
|
25
|
+
contact.labels = labels
|
26
|
+
raise "Row #{counter} creates an invalid contact. Errors: #{contact.errors.full_messages.to_sentence}" unless contact.valid?
|
27
|
+
new_contacts.push contact
|
28
|
+
counter += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
Contact.delete_all
|
32
|
+
ContactLabel.delete_all
|
33
|
+
new_contacts.each do |contact|
|
34
|
+
contact.save!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def labels_h
|
39
|
+
@labels_h ||= Label.all.map{ |l| [l.name, l] }.to_h
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GeoLabels
|
4
|
+
module Importer
|
5
|
+
class YamlData
|
6
|
+
def self.import(yaml_h)
|
7
|
+
GeoLabels::Contact.delete_all
|
8
|
+
GeoLabels::Label.delete_all
|
9
|
+
GeoLabels::ContactLabel.delete_all
|
10
|
+
labels_dict = persist_labels_tree yaml_h['labels']
|
11
|
+
yaml_h['contacts'].each do |contact_attributes|
|
12
|
+
label_names = Array(contact_attributes.delete('labels'))
|
13
|
+
contact = GeoLabels::Contact.new(contact_attributes)
|
14
|
+
contact.labels = label_names.map{ labels_dict[_1] }.compact
|
15
|
+
contact.save
|
16
|
+
end
|
17
|
+
GeoLabels::Contact.count
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.labels_text_to_tree(tree_text)
|
21
|
+
txt = tree_text.gsub("\n\u0001", "\n") # remove START OF HEADING TRASH
|
22
|
+
txt.gsub! /^(\s*)[-*] /, '\1 ' # remove list punctuations and interpret just the spaces
|
23
|
+
lines = txt.split("\n")
|
24
|
+
root_label, current_indentation = ImportLabel.new('root'), -1
|
25
|
+
working_object = root_label
|
26
|
+
lines.each do |line|
|
27
|
+
line_indentation = (line.match(/\s+/).try(:[], 0).try(:size) || 0) / 2
|
28
|
+
# name = line[(2*line_indentation)..] # strip the leading spaces
|
29
|
+
name = line.strip
|
30
|
+
label = ImportLabel.new(name)
|
31
|
+
if line_indentation > current_indentation # level deeper
|
32
|
+
working_object = working_object.add_child(label)
|
33
|
+
elsif line_indentation < current_indentation
|
34
|
+
(current_indentation - line_indentation).times do
|
35
|
+
working_object = working_object.parent unless working_object.parent.is_root?
|
36
|
+
end
|
37
|
+
working_object = working_object.add_sibling(label)
|
38
|
+
else
|
39
|
+
working_object = working_object.add_sibling label
|
40
|
+
end
|
41
|
+
current_indentation = line_indentation
|
42
|
+
end
|
43
|
+
root_label
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.persist_labels_tree(tree_or_text = nil)
|
47
|
+
tree = case tree_or_text
|
48
|
+
when ImportLabel then tree_or_text
|
49
|
+
when String then labels_text_to_tree(tree_or_text)
|
50
|
+
else
|
51
|
+
raise 'Must provide a tree label object or tree text'
|
52
|
+
end
|
53
|
+
record_lookup_dict = {}
|
54
|
+
persister = proc do |items, parent|
|
55
|
+
items.each do |item|
|
56
|
+
record = Label.create name: item.name, parent: parent
|
57
|
+
record_lookup_dict[item.name] = record
|
58
|
+
persister.call item.children, record if item.children.present?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
persister.call(tree.children, nil)
|
62
|
+
record_lookup_dict
|
63
|
+
end
|
64
|
+
|
65
|
+
class LabelList < Array
|
66
|
+
attr_accessor :parent
|
67
|
+
end
|
68
|
+
|
69
|
+
class ImportLabel
|
70
|
+
attr_accessor :name, :parent
|
71
|
+
|
72
|
+
def initialize(name)
|
73
|
+
@name = name
|
74
|
+
end
|
75
|
+
|
76
|
+
def is_root?
|
77
|
+
name == 'root'
|
78
|
+
end
|
79
|
+
|
80
|
+
def children
|
81
|
+
@children ||= LabelList.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_sibling(label)
|
85
|
+
label.parent = parent
|
86
|
+
parent.children.push label
|
87
|
+
label
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_child(label)
|
91
|
+
children.push label
|
92
|
+
label.parent = self
|
93
|
+
label
|
94
|
+
end
|
95
|
+
|
96
|
+
def inspect
|
97
|
+
# "#{name} => #{children.inspect}"
|
98
|
+
# children.any? ? "<#{name}> => #{children.inpsect}" : "#{name}"
|
99
|
+
base = name.to_s
|
100
|
+
base = "#{base} => #{children.inspect}" if children.any?
|
101
|
+
base
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_h
|
105
|
+
if is_root?
|
106
|
+
{labels: children.map(&:to_h)}
|
107
|
+
else
|
108
|
+
r = {name: name}
|
109
|
+
r[:children] = children.map(&:to_h) if children.any?
|
110
|
+
r
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/geo_labels/importer.rb
CHANGED
@@ -1,128 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GeoLabels
|
3
4
|
module Importer
|
4
5
|
def self.import(subject)
|
5
|
-
|
6
|
-
when String
|
7
|
-
if subject.start_with?('/') or subject.start_with?('./')
|
8
|
-
YAML.load_file(subject)
|
9
|
-
else
|
10
|
-
YAML.safe_load(subject)
|
11
|
-
end
|
12
|
-
when File then YAML.safe_load(subject.read)
|
13
|
-
when Pathname then YAML.safe_load(subject.read)
|
14
|
-
when ActionDispatch::Http::UploadedFile then YAML.safe_load(subject.read)
|
15
|
-
else
|
16
|
-
raise 'Argument not recognized'
|
17
|
-
end
|
18
|
-
GeoLabels::Contact.delete_all
|
19
|
-
GeoLabels::Label.delete_all
|
20
|
-
GeoLabels::ContactLabel.delete_all
|
21
|
-
labels_dict = persist_labels_tree yaml_h['labels']
|
22
|
-
yaml_h['contacts'].each do |contact_attributes|
|
23
|
-
label_names = Array(contact_attributes.delete('labels'))
|
24
|
-
contact = GeoLabels::Contact.new(contact_attributes)
|
25
|
-
contact.labels = label_names.map{ labels_dict[_1] }.compact
|
26
|
-
contact.save
|
27
|
-
end
|
28
|
-
GeoLabels::Contact.count
|
29
|
-
end
|
6
|
+
type, data = get_type_and_data(subject)
|
30
7
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
lines.each do |line|
|
38
|
-
line_indentation = (line.match(/\s+/).try(:[], 0).try(:size) || 0) / 2
|
39
|
-
# name = line[(2*line_indentation)..] # strip the leading spaces
|
40
|
-
name = line.strip
|
41
|
-
label = ImportLabel.new(name)
|
42
|
-
if line_indentation > current_indentation # level deeper
|
43
|
-
working_object = working_object.add_child(label)
|
44
|
-
elsif line_indentation < current_indentation
|
45
|
-
(current_indentation - line_indentation).times do
|
46
|
-
working_object = working_object.parent unless working_object.parent.is_root?
|
47
|
-
end
|
48
|
-
working_object = working_object.add_sibling(label)
|
49
|
-
else
|
50
|
-
working_object = working_object.add_sibling label
|
51
|
-
end
|
52
|
-
current_indentation = line_indentation
|
8
|
+
if type == :yml
|
9
|
+
GeoLabels::Importer::YamlData.import(YAML.safe_load(data))
|
10
|
+
elsif type == :csv
|
11
|
+
GeoLabels::Importer::CsvData.new.import(data)
|
12
|
+
else
|
13
|
+
raise 'Argument not recognized'
|
53
14
|
end
|
54
|
-
root_label
|
55
15
|
end
|
56
16
|
|
57
|
-
def self.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
17
|
+
def self.get_type_and_data(subject)
|
18
|
+
case subject
|
19
|
+
when String
|
20
|
+
if subject.start_with?('/') or subject.start_with?('./') # is a path spec
|
21
|
+
return recognize_path(subject), File.read(subject)
|
22
|
+
else
|
23
|
+
if subject.start_with? "---\n" or subject.start_with?("labels: |\n")
|
24
|
+
return [:yml, subject]
|
25
|
+
elsif subject.start_with?('name,labels,')
|
26
|
+
return [:csv, subject]
|
27
|
+
else
|
28
|
+
raise 'The import string could not be recognized as either YAML or CSV'
|
29
|
+
end
|
70
30
|
end
|
31
|
+
when File
|
32
|
+
return recognize_path(subject.path), subject.read
|
33
|
+
when Pathname
|
34
|
+
return recognize_path(subject.to_s), subject.read
|
35
|
+
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
36
|
+
return recognize_path(subject.original_filename), subject.read
|
37
|
+
else
|
38
|
+
raise 'Format of import not recognized'
|
71
39
|
end
|
72
|
-
persister.call(tree.children, nil)
|
73
|
-
record_lookup_dict
|
74
|
-
end
|
75
|
-
|
76
|
-
class LabelList < Array
|
77
|
-
|
78
|
-
attr_accessor :parent
|
79
40
|
end
|
80
41
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
def initialize(name)
|
86
|
-
@name = name
|
87
|
-
end
|
42
|
+
def self.recognize_path(path)
|
43
|
+
return :yml if path.end_with?('.yml')
|
44
|
+
return :csv if path.end_with?('.csv')
|
88
45
|
|
89
|
-
|
90
|
-
name == 'root'
|
91
|
-
end
|
92
|
-
|
93
|
-
def children
|
94
|
-
@children ||= LabelList.new
|
95
|
-
end
|
96
|
-
|
97
|
-
def add_sibling(label)
|
98
|
-
label.parent = parent
|
99
|
-
parent.children.push label
|
100
|
-
label
|
101
|
-
end
|
102
|
-
|
103
|
-
def add_child(label)
|
104
|
-
children.push label
|
105
|
-
label.parent = self
|
106
|
-
label
|
107
|
-
end
|
108
|
-
|
109
|
-
def inspect
|
110
|
-
# "#{name} => #{children.inspect}"
|
111
|
-
# children.any? ? "<#{name}> => #{children.inpsect}" : "#{name}"
|
112
|
-
base = name.to_s
|
113
|
-
base = "#{base} => #{children.inspect}" if children.any?
|
114
|
-
base
|
115
|
-
end
|
116
|
-
|
117
|
-
def to_h
|
118
|
-
if is_root?
|
119
|
-
{labels: children.map(&:to_h)}
|
120
|
-
else
|
121
|
-
r = {name: name}
|
122
|
-
r[:children] = children.map(&:to_h) if children.any?
|
123
|
-
r
|
124
|
-
end
|
125
|
-
end
|
46
|
+
raise 'Imported path is neither YAML or CSV file'
|
126
47
|
end
|
127
48
|
end
|
128
49
|
end
|
data/lib/geo_labels/version.rb
CHANGED
data/lib/geo_labels.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geo_labels
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin ter Kuile
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_nested_set
|
@@ -128,14 +128,14 @@ dependencies:
|
|
128
128
|
requirements:
|
129
129
|
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
131
|
+
version: '7.1'
|
132
132
|
type: :runtime
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
138
|
+
version: '7.1'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: ransack
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -228,12 +228,12 @@ files:
|
|
228
228
|
- app/views/geo_labels/application/_button-edit-record.html.slim
|
229
229
|
- app/views/geo_labels/application/_button-new-record.html.slim
|
230
230
|
- app/views/geo_labels/application/_messages.html.slim
|
231
|
-
- app/views/geo_labels/application/_navigation.html.slim
|
232
231
|
- app/views/geo_labels/contacts/_form.html.slim
|
233
232
|
- app/views/geo_labels/contacts/edit.html.slim
|
234
233
|
- app/views/geo_labels/contacts/index.html.slim
|
235
234
|
- app/views/geo_labels/contacts/new.html.slim
|
236
235
|
- app/views/geo_labels/contacts/show.html.slim
|
236
|
+
- app/views/geo_labels/dashboard/about.html.slim
|
237
237
|
- app/views/geo_labels/dashboard/export.html.slim
|
238
238
|
- app/views/geo_labels/dashboard/import.html.slim
|
239
239
|
- app/views/geo_labels/dashboard/main.html.slim
|
@@ -242,6 +242,9 @@ files:
|
|
242
242
|
- app/views/geo_labels/labels/index.html.slim
|
243
243
|
- app/views/geo_labels/labels/new.html.slim
|
244
244
|
- app/views/geo_labels/labels/show.html.slim
|
245
|
+
- app/views/geo_labels/menu/_navigation.html.slim
|
246
|
+
- app/views/geo_labels/menu/_right_menu_general.html.slim
|
247
|
+
- app/views/geo_labels/users/_abilities.html.slim
|
245
248
|
- app/views/layouts/geo_labels/application.html.slim
|
246
249
|
- config/environment.rb
|
247
250
|
- config/initializers/human_plural.rb
|
@@ -254,10 +257,13 @@ files:
|
|
254
257
|
- db/migrate/20230602182522_geo_labels_contacts_complex_name_to_name.rb
|
255
258
|
- db/migrate/20230726154822_add_state_to_geo_labels_contacts.rb
|
256
259
|
- db/migrate/20230801145902_add_food_rating_to_geo_labels_contacts.rb
|
260
|
+
- db/migrate/20231206183544_add_url_to_geo_labels_contacts.rb
|
257
261
|
- lib/geo_labels.rb
|
258
262
|
- lib/geo_labels/engine.rb
|
259
263
|
- lib/geo_labels/exporter.rb
|
260
264
|
- lib/geo_labels/importer.rb
|
265
|
+
- lib/geo_labels/importer/csv_data.rb
|
266
|
+
- lib/geo_labels/importer/yaml_data.rb
|
261
267
|
- lib/geo_labels/version.rb
|
262
268
|
- lib/tasks/geo_labels_tasks.rake
|
263
269
|
homepage: https://gitlab.com/benja-2/geo_labels
|
@@ -282,7 +288,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
282
288
|
- !ruby/object:Gem::Version
|
283
289
|
version: '0'
|
284
290
|
requirements: []
|
285
|
-
rubygems_version: 3.
|
291
|
+
rubygems_version: 3.3.26
|
286
292
|
signing_key:
|
287
293
|
specification_version: 4
|
288
294
|
summary: Rails mountable engine to label search locations
|
@@ -1,29 +0,0 @@
|
|
1
|
-
.ui.inverted.menu.fixed
|
2
|
-
= link_to main_app.root_path, class: 'header item'
|
3
|
-
= Rails.application.config.x.geo_labels.link_home_content.call.to_s.html_safe
|
4
|
-
= link_to geo_labels.root_path, class: 'header item'
|
5
|
-
.ui.icon
|
6
|
-
i.map.icon
|
7
|
-
= Rails.application.config.x.geo_labels.application_title.call.to_s.html_safe
|
8
|
-
- if current_user.present?
|
9
|
-
- if can? :read, GeoLabels::Contact
|
10
|
-
= link_to GeoLabels::Contact.model_name.human_plural, [GeoLabels::Contact], class: ['item', active_class('geo_labels/contacts')]
|
11
|
-
- if can? :read, GeoLabels::Label
|
12
|
-
= link_to GeoLabels::Label.model_name.human_plural, [GeoLabels::Label], class: ['item', active_class('geo_labels/labels')]
|
13
|
-
.right.menu
|
14
|
-
- if current_user.present?
|
15
|
-
.ui.dropdown.item
|
16
|
-
span= current_user.respond_to?(:nickname) ? current_user.nickname : current_user.email
|
17
|
-
i.dropdown.icon
|
18
|
-
.menu
|
19
|
-
= link_to geo_labels.export_path, class: 'item'
|
20
|
-
i.file.export.icon
|
21
|
-
span.text= t('geo_labels.export')
|
22
|
-
= link_to geo_labels.import_path, class: 'item'
|
23
|
-
i.file.import.icon
|
24
|
-
span.text= t('geo_labels.import')
|
25
|
-
= link_to main_app.destroy_user_session_path, method: :delete, class: 'item'
|
26
|
-
i.sign.out.icon
|
27
|
-
span.text= t 'user.sign_out'
|
28
|
-
- else
|
29
|
-
= link_to t('devise.sessions.new.sign_in'), main_app.new_user_session_path, class: 'item'
|