ahoy_captain 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +23 -2
  3. data/app/assets/javascript/ahoy_captain/controllers/application_controller.js +12 -0
  4. data/app/assets/javascript/ahoy_captain/controllers/combobox_controller.js +50 -20
  5. data/app/assets/javascript/ahoy_captain/controllers/frame_link_controller.js +20 -0
  6. data/app/assets/javascript/ahoy_captain/controllers/line_chart_controller.js +67 -4
  7. data/app/assets/javascript/ahoy_captain/controllers/map_controller.js +47 -0
  8. data/app/assets/javascript/ahoy_captain/controllers/predicate_select_controller.js +1 -0
  9. data/app/assets/javascript/ahoy_captain/controllers/property_filter_controller.js +0 -1
  10. data/app/assets/javascript/ahoy_captain/helpers/countries.js +2261 -0
  11. data/app/components/ahoy_captain/combobox_component.html.erb +2 -2
  12. data/app/components/ahoy_captain/combobox_component.rb +1 -1
  13. data/app/components/ahoy_captain/dropdown_link_component.html.erb +2 -4
  14. data/app/components/ahoy_captain/dropdown_link_component.rb +4 -0
  15. data/app/components/ahoy_captain/previous_next_component.html.erb +8 -0
  16. data/app/components/ahoy_captain/previous_next_component.rb +11 -0
  17. data/app/components/ahoy_captain/stats/comparable_container_component.html.erb +1 -1
  18. data/app/components/ahoy_captain/stats/container_component.html.erb +1 -1
  19. data/app/components/ahoy_captain/sticky_nav_component.html.erb +26 -22
  20. data/app/components/ahoy_captain/sticky_nav_component.rb +8 -0
  21. data/app/components/ahoy_captain/tile_component.rb +7 -0
  22. data/app/controllers/ahoy_captain/locations/cities_controller.rb +22 -0
  23. data/app/controllers/ahoy_captain/locations/countries_controller.rb +22 -0
  24. data/app/controllers/ahoy_captain/locations/maps_controller.rb +24 -0
  25. data/app/controllers/ahoy_captain/locations/regions_controller.rb +22 -0
  26. data/app/helpers/ahoy_captain/application_helper.rb +0 -2
  27. data/app/models/ahoy_captain/range_from_params.rb +4 -0
  28. data/app/views/ahoy_captain/locations/maps/show.html.erb +3 -0
  29. data/app/views/ahoy_captain/properties/_form.html.erb +1 -1
  30. data/app/views/ahoy_captain/roots/show.html.erb +46 -54
  31. data/app/views/ahoy_captain/stats/base/index.html.erb +1 -0
  32. data/app/views/ahoy_captain/stats/show.html.erb +11 -6
  33. data/config/routes.rb +7 -3
  34. data/lib/ahoy_captain/filters_configuration.rb +5 -5
  35. data/lib/ahoy_captain/version.rb +1 -1
  36. data/lib/ahoy_captain.rb +1 -0
  37. metadata +102 -12
  38. data/app/assets/javascript/ahoy_captain/controllers/active_links_controller.js +0 -30
  39. data/app/controllers/ahoy_captain/cities_controller.rb +0 -20
  40. data/app/controllers/ahoy_captain/countries_controller.rb +0 -20
  41. data/app/controllers/ahoy_captain/regions_controller.rb +0 -20
  42. /data/app/views/ahoy_captain/{cities → locations/cities}/index.html+details.erb +0 -0
  43. /data/app/views/ahoy_captain/{cities → locations/cities}/index.html.erb +0 -0
  44. /data/app/views/ahoy_captain/{countries → locations/countries}/index.html+details.erb +0 -0
  45. /data/app/views/ahoy_captain/{countries → locations/countries}/index.html.erb +0 -0
  46. /data/app/views/ahoy_captain/{regions → locations/regions}/index.html+details.erb +0 -0
  47. /data/app/views/ahoy_captain/{regions → locations/regions}/index.html.erb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e860b8082ca3fad364eb34df632f017d81e63ff411c2fcae0501657aef5a578b
4
- data.tar.gz: 4d0227c2eb7443e4d6a15f817d971f6176c43daad634d057de88f98f7ec8ae7b
3
+ metadata.gz: a8ee0971d696dceab133146c4f8a53a0e7f687c474497bc49ee8bf914bda2430
4
+ data.tar.gz: 3fc99cc0c51f8abbb07acbbf6a39e9f77a5b2adcabc6bd8e5e0583c0bd77b1af
5
5
  SHA512:
6
- metadata.gz: 84a9922fec7838c6733263224f0b2a86e836b147fd8074e11428c47899ffd69317617b57f7dae503e59139d1c1ef134c46c2692ce298493830cd5b742a8fa780
7
- data.tar.gz: 66e113dc9b86039b504d20153384d3fa55afe1f842a69da47d36aa03829fa0830b56a81411fd4c742626848973d1b89d43c4b523cd909ae8eb7bd6324f8261a5
6
+ metadata.gz: 99352a390c1f4641d5664882e5807909f4948f77f8b3ab3de89c4e5bad2773b78f6ff4ced0433d490ffa8db1050e6f5b284ad43086b4521328ab5e9af2f01a2c
7
+ data.tar.gz: 7b13a9571dcc60c6d4a8ab4677cdd6f0fd77123a192a5833e1e837a2f3117e5beed71ab4cb3ab00b66de3b8dafa134f30eeb72f9da98cddf5d3040b7c2dfb963
data/Rakefile CHANGED
@@ -1,3 +1,24 @@
1
- require "bundler/setup"
1
+ # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Caffeinate'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
@@ -10,6 +10,18 @@ export default class extends Controller {
10
10
  }, 1000 * 30);
11
11
  });
12
12
  }
13
+
14
+ document.querySelectorAll('a[data-turbo-frame]').forEach(link => {
15
+ const frameSelector = link.dataset.turboFrame;
16
+ const frame = document.querySelector(`turbo-frame#${frameSelector}`);
17
+ if(frame) {
18
+ const src = frame.src;
19
+ if(link.href.includes(src)) {
20
+ link.classList.add('text-primary', 'font-semibold')
21
+ }
22
+ }
23
+
24
+ })
13
25
  }
14
26
 
15
27
  comboboxInit(event) {
@@ -22,7 +22,7 @@ export default class extends Controller {
22
22
  input: String,
23
23
  highlightedIndex: Number,
24
24
  singleOption: Boolean,
25
- freeChoice: Boolean,
25
+ freeChoice: { type: Boolean, default: false },
26
26
  selected: Array,
27
27
  url: String,
28
28
  query: String
@@ -51,11 +51,33 @@ export default class extends Controller {
51
51
  value: this,
52
52
  });
53
53
 
54
+ const targetNode = this.selectTarget;
55
+ const config = { attributes: true };
56
+
57
+ const callback = (mutationList, observer) => {
58
+ for (const mutation of mutationList) {
59
+ if (mutation.type === "attributes") {
60
+ this.handleNameChange()
61
+ }
62
+ }
63
+ };
64
+
65
+ const observer = new MutationObserver(callback);
66
+ observer.observe(targetNode, config);
67
+
68
+
54
69
  window.dispatchEvent(new CustomEvent('combobox:init', { detail: { combobox: this } }))
55
70
  this.search = new URLSearchParams(window.location.search);
56
71
  this.search.delete(this.selectTarget.name)
57
72
  }
58
73
 
74
+ handleNameChange() {
75
+ if(this.selectTarget.name.includes("_cont]")) {
76
+ this.freeChoiceValue = true
77
+ } else {
78
+ this.freeChoiceValue = false
79
+ }
80
+ }
59
81
  checkDisabledState() {
60
82
  if (this.disabledValue) {
61
83
  this.element.classList.add('opacity-80', 'cursor-default', 'pointer-events-none');
@@ -71,29 +93,30 @@ export default class extends Controller {
71
93
 
72
94
  fetchOptions(query) {
73
95
  if(this.disabledValue) { return }
74
- this.isLoadingValue = true;
75
- this.isOpenValue = true;
76
96
 
77
- const searchParams = new URLSearchParams(this.search.toString());
78
- const formData = new FormData(this.element.form);
97
+ if(this.freeChoiceValue) {
98
+ this.isLoadingValue = false;
99
+ this.highlightedIndexValue = 0;
100
+ this.optionsValue = [{ text: query, value: query }];
101
+ this.isOpenValue = true;
102
+
103
+ } else {
104
+ this.isLoadingValue = true;
105
+ this.isOpenValue = true;
79
106
 
80
- let deleted = [];
81
- for (const [key, value] of formData) {
82
- if(!deleted.includes(key)) {
83
- searchParams.delete(key)
84
- deleted.push(key)
85
- }
86
- }
107
+ const formData = new FormData(this.selectTarget.form);
108
+ const searchParams = new URLSearchParams([...formData.entries()]);
87
109
 
88
- searchParams.delete(this.element.name);
89
- searchParams.delete(this.queryValue);
90
- searchParams.set(this.queryValue, query);
110
+ searchParams.delete(this.selectTarget.name);
111
+ searchParams.delete(this.queryValue);
112
+ searchParams.set(this.queryValue, query);
91
113
 
92
- fetch(`${this.urlValue}?${searchParams.toString()}`).then(resp => resp.json()).then(loadedOptions => {
93
- this.isLoadingValue = false;
94
- this.highlightedIndexValue = 0;
95
- this.optionsValue = loadedOptions.map(option => ({ text: option.text, value: option.text }));
96
- });
114
+ fetch(`${this.urlValue}?${searchParams.toString()}`).then(resp => resp.json()).then(loadedOptions => {
115
+ this.isLoadingValue = false;
116
+ this.highlightedIndexValue = 0;
117
+ this.optionsValue = loadedOptions.map(option => ({ text: option.text, value: option.value }));
118
+ });
119
+ }
97
120
  }
98
121
 
99
122
  highlight(element) {
@@ -328,6 +351,13 @@ export default class extends Controller {
328
351
  }
329
352
  }
330
353
 
354
+ freeChoiceValueChanged(current, prev) {
355
+ if(this.selectedValue.filter(value => value.freeChoice).length) {
356
+ console.log("free choice changed")
357
+ this.setSelected([])
358
+ }
359
+
360
+ }
331
361
  disabledValueChanged(current) {
332
362
  if(current) {
333
363
  this.isOpenValue = false
@@ -0,0 +1,20 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ export default class extends Controller {
4
+ static targets = ["link", "alt"]
5
+ static values = {
6
+ classes: { type: Array, default: ["text-primary", "font-semibold"] }
7
+ }
8
+
9
+ connect() {
10
+ this.element.addEventListener('click', () => {
11
+ const frame = this.element.dataset.turboFrame;
12
+ const otherLinks = document.querySelectorAll(`[data-turbo-frame="${frame}"]`);
13
+ otherLinks.forEach(link => {
14
+ link.classList.remove('text-primary', 'font-semibold');
15
+ })
16
+
17
+ this.element.classList.add("text-primary", "font-semibold")
18
+ })
19
+ }
20
+ }
@@ -33,6 +33,7 @@ export default class extends Controller {
33
33
 
34
34
  connect() {
35
35
  const onClick = (e) => {
36
+ if(drag) { return }
36
37
  const element = this.chart.getElementsAtEventForMode(e, 'index', { intersect: false })[0];
37
38
  const searchParams = new URLSearchParams(window.location.search);
38
39
 
@@ -76,8 +77,7 @@ export default class extends Controller {
76
77
  }
77
78
 
78
79
  const typeForDate = this.comparisonValue === 'year' ? 'long' : "short"
79
- this.chart = new Chart(this.element,
80
- {
80
+ const options = {
81
81
  type: 'line',
82
82
  data: {
83
83
  labels: Object.keys(this.currentValue),
@@ -139,8 +139,71 @@ export default class extends Controller {
139
139
  }
140
140
  }
141
141
  }
142
- },
143
- );
142
+ }
143
+ this.chart = new Chart(this.element, options);
144
+
145
+
146
+ var canvas = this.element;
147
+ var overlay = document.getElementById('overlay');
148
+ var startIndex = 0;
149
+ overlay.width = canvas.width;
150
+ overlay.height = canvas.height;
151
+ var selectionContext = overlay.getContext('2d');
152
+ var selectionRect = {
153
+ w: 0,
154
+ startX: 0,
155
+ startY: 0
156
+ };
157
+ var drag = false;
158
+ canvas.addEventListener('pointerdown', evt => {
159
+ const points = this.chart.getElementsAtEventForMode(evt, 'index', {
160
+ intersect: false
161
+ });
162
+
163
+ startIndex = points[0].index;
164
+ const rect = canvas.getBoundingClientRect();
165
+ selectionRect.startX = evt.clientX - rect.left;
166
+ selectionRect.startY = this.chart.chartArea.top;
167
+ drag = true;
168
+ });
169
+ canvas.addEventListener('pointermove', evt => {
170
+ const rect = canvas.getBoundingClientRect();
171
+ if (drag) {
172
+
173
+ const rect = canvas.getBoundingClientRect();
174
+ selectionContext.fillStyle = getCSS('--p')
175
+ selectionRect.w = (evt.clientX - rect.left) - selectionRect.startX;
176
+ selectionContext.globalAlpha = 0.5;
177
+ selectionContext.clearRect(0, 0, canvas.width, canvas.height);
178
+ selectionContext.fillRect(selectionRect.startX,
179
+ selectionRect.startY,
180
+ selectionRect.w,
181
+ this.chart.chartArea.bottom - this.chart.chartArea.top);
182
+ } else {
183
+ selectionContext.clearRect(0, 0, canvas.width, canvas.height);
184
+ var x = evt.clientX - rect.left;
185
+ if (x > this.chart.chartArea.left) {
186
+ selectionContext.fillStyle = getCSS('--p')
187
+ selectionContext.fillRect(x,
188
+ this.chart.chartArea.top,
189
+ 1,
190
+ this.chart.chartArea.bottom - this.chart.chartArea.top);
191
+ }
192
+ }
193
+ });
194
+ canvas.addEventListener('pointerup', evt => {
195
+ const points = this.chart.getElementsAtEventForMode(evt, 'index', {
196
+ intersect: false
197
+ });
198
+ const dates = [options.data.labels[startIndex], options.data.labels[points[0].index]].sort()
199
+ const searchParams = new URLSearchParams(window.location.search);
200
+ searchParams.set('start_date', dates[0])
201
+ searchParams.set('end_date', dates[1])
202
+ searchParams.delete('period')
203
+ searchParams.delete('compare_to_start_date')
204
+ searchParams.delete('compare_to_end_date')
205
+ Turbo.visit(window.location.pathname + "?" + searchParams.toString())
206
+ });
144
207
  }
145
208
 
146
209
  formatLabel(label) {
@@ -0,0 +1,47 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import "chartjs-chart-geo";
3
+ import CountryMap from "helpers/countries"
4
+
5
+ export default class extends Controller {
6
+ static values = {
7
+ data: Object
8
+ }
9
+ connect() {
10
+ fetch('https://unpkg.com/world-atlas/countries-50m.json').then((r) => r.json()).then((data) => {
11
+ const countries = ChartGeo.topojson.feature(data, data.objects.countries).features;
12
+ const numericToCode = {};
13
+ Object.keys(CountryMap).forEach(key => { numericToCode[CountryMap[key]['Numeric code']] = key })
14
+
15
+ countries.forEach(country => {
16
+ const abbrev = numericToCode[country.id];
17
+ country.value = this.dataValue[abbrev]
18
+ })
19
+
20
+ const chart = new Chart(this.element.getContext("2d"), {
21
+ type: 'choropleth',
22
+ data: {
23
+ labels: countries.map((d) => d.properties.name),
24
+ datasets: [{
25
+ label: 'Countries',
26
+ data: countries.map((d) => ({feature: d, value: d.value || 0 })),
27
+ }]
28
+ },
29
+ options: {
30
+ showOutline: false,
31
+ showGraticule: false,
32
+ plugins: {
33
+ legend: {
34
+ display: false
35
+ },
36
+ },
37
+ scales: {
38
+ projection: {
39
+ axis: 'x',
40
+ projection: 'equalEarth'
41
+ }
42
+ }
43
+ }
44
+ });
45
+ });
46
+ }
47
+ }
@@ -4,6 +4,7 @@ export default class extends Controller {
4
4
  static targets = ["select"]
5
5
 
6
6
  handleChange(event) {
7
+ this.selectTarget.dataset.predicate = event.target.value
7
8
  this.selectTarget.name = event.target.value
8
9
  }
9
10
  }
@@ -12,7 +12,6 @@ export default class extends Controller {
12
12
  this.init()
13
13
  }
14
14
  }, 100)
15
-
16
15
  }
17
16
 
18
17
  init() {