ahoy_captain 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +23 -2
- data/app/assets/javascript/ahoy_captain/controllers/application_controller.js +12 -0
- data/app/assets/javascript/ahoy_captain/controllers/combobox_controller.js +50 -20
- data/app/assets/javascript/ahoy_captain/controllers/frame_link_controller.js +20 -0
- data/app/assets/javascript/ahoy_captain/controllers/line_chart_controller.js +67 -4
- data/app/assets/javascript/ahoy_captain/controllers/map_controller.js +47 -0
- data/app/assets/javascript/ahoy_captain/controllers/predicate_select_controller.js +1 -0
- data/app/assets/javascript/ahoy_captain/helpers/countries.js +2261 -0
- data/app/components/ahoy_captain/combobox_component.html.erb +2 -2
- data/app/components/ahoy_captain/combobox_component.rb +1 -1
- data/app/components/ahoy_captain/dropdown_link_component.html.erb +2 -4
- data/app/components/ahoy_captain/dropdown_link_component.rb +4 -0
- data/app/components/ahoy_captain/previous_next_component.html.erb +8 -0
- data/app/components/ahoy_captain/previous_next_component.rb +11 -0
- data/app/components/ahoy_captain/stats/comparable_container_component.html.erb +1 -1
- data/app/components/ahoy_captain/stats/container_component.html.erb +1 -1
- data/app/components/ahoy_captain/sticky_nav_component.html.erb +26 -22
- data/app/components/ahoy_captain/sticky_nav_component.rb +8 -0
- data/app/components/ahoy_captain/tile_component.rb +7 -0
- data/app/controllers/ahoy_captain/locations/cities_controller.rb +22 -0
- data/app/controllers/ahoy_captain/locations/countries_controller.rb +22 -0
- data/app/controllers/ahoy_captain/locations/maps_controller.rb +24 -0
- data/app/controllers/ahoy_captain/locations/regions_controller.rb +22 -0
- data/app/helpers/ahoy_captain/application_helper.rb +0 -2
- data/app/models/ahoy_captain/range_from_params.rb +4 -0
- data/app/views/ahoy_captain/locations/maps/show.html.erb +3 -0
- data/app/views/ahoy_captain/properties/_form.html.erb +1 -1
- data/app/views/ahoy_captain/roots/show.html.erb +46 -54
- data/app/views/ahoy_captain/stats/base/index.html.erb +1 -0
- data/app/views/ahoy_captain/stats/show.html.erb +11 -6
- data/config/routes.rb +7 -3
- data/lib/ahoy_captain/filters_configuration.rb +5 -5
- data/lib/ahoy_captain/version.rb +1 -1
- data/lib/ahoy_captain.rb +1 -0
- metadata +102 -12
- data/app/assets/javascript/ahoy_captain/controllers/active_links_controller.js +0 -30
- data/app/controllers/ahoy_captain/cities_controller.rb +0 -20
- data/app/controllers/ahoy_captain/countries_controller.rb +0 -20
- data/app/controllers/ahoy_captain/regions_controller.rb +0 -20
- /data/app/views/ahoy_captain/{cities → locations/cities}/index.html+details.erb +0 -0
- /data/app/views/ahoy_captain/{cities → locations/cities}/index.html.erb +0 -0
- /data/app/views/ahoy_captain/{countries → locations/countries}/index.html+details.erb +0 -0
- /data/app/views/ahoy_captain/{countries → locations/countries}/index.html.erb +0 -0
- /data/app/views/ahoy_captain/{regions → locations/regions}/index.html+details.erb +0 -0
- /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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8ee0971d696dceab133146c4f8a53a0e7f687c474497bc49ee8bf914bda2430
|
4
|
+
data.tar.gz: 3fc99cc0c51f8abbb07acbbf6a39e9f77a5b2adcabc6bd8e5e0583c0bd77b1af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99352a390c1f4641d5664882e5807909f4948f77f8b3ab3de89c4e5bad2773b78f6ff4ced0433d490ffa8db1050e6f5b284ad43086b4521328ab5e9af2f01a2c
|
7
|
+
data.tar.gz: 7b13a9571dcc60c6d4a8ab4677cdd6f0fd77123a192a5833e1e837a2f3117e5beed71ab4cb3ab00b66de3b8dafa134f30eeb72f9da98cddf5d3040b7c2dfb963
|
data/Rakefile
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
78
|
-
|
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
|
-
|
81
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
110
|
+
searchParams.delete(this.selectTarget.name);
|
111
|
+
searchParams.delete(this.queryValue);
|
112
|
+
searchParams.set(this.queryValue, query);
|
91
113
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
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
|
+
}
|