headmin 0.6.1 → 0.6.2
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/Gemfile.lock +1 -1
- data/app/assets/javascripts/headmin/controllers/filter_controller.js +15 -3
- data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +75 -47
- data/app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js +30 -0
- data/app/assets/javascripts/headmin/controllers/media_controller.js +24 -8
- data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +142 -105
- data/app/assets/javascripts/headmin/index.js +2 -0
- data/app/assets/javascripts/headmin.js +122 -19
- data/app/assets/stylesheets/headmin.css +1 -1
- data/app/controllers/headmin/media_controller.rb +11 -0
- data/app/helpers/headmin/form_helper.rb +0 -11
- data/app/models/headmin/filter/association.rb +0 -12
- data/app/models/headmin/filter/association_count.rb +50 -0
- data/app/models/headmin/filter/association_count_view.rb +78 -0
- data/app/models/headmin/filter/base.rb +10 -0
- data/app/models/headmin/filter/date.rb +8 -1
- data/app/models/headmin/filter/date_view.rb +1 -1
- data/app/models/headmin/filter/number.rb +0 -12
- data/app/models/headmin/filter/operator_view.rb +3 -1
- data/app/views/headmin/_filters.html.erb +1 -1
- data/app/views/headmin/filters/_association_count.html.erb +28 -0
- data/app/views/headmin/filters/_date.html.erb +1 -0
- data/app/views/headmin/media/_modal.html.erb +8 -3
- data/app/views/headmin/media/_thumbnail.html.erb +20 -0
- data/app/views/headmin/media/create.turbo_stream.erb +1 -1
- data/app/views/headmin/media/index.turbo_stream.erb +11 -0
- data/app/views/headmin/media/thumbnail.html.erb +3 -0
- data/app/views/headmin/pagination/_infinite.html.erb +7 -0
- data/config/locales/headmin/filters/en.yml +2 -0
- data/config/locales/headmin/filters/nl.yml +2 -0
- data/config/locales/headmin/pagination/en.yml +2 -0
- data/config/locales/headmin/pagination/nl.yml +2 -0
- data/config/routes.rb +2 -1
- data/lib/headmin/version.rb +1 -1
- data/package.json +1 -1
- metadata +10 -3
- data/app/views/headmin/media/_item.html.erb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ec99ac85337bc096a16478a65e5434f37209281e861043a59f8cd4692197851
|
4
|
+
data.tar.gz: e3c9bf55658ce12c6d96ca0791ffa7e2dae14df3df9798e4ee21dc4b07b2f994
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0eaf4ed9453bbc2cf383c82d050c708f7f8e7ba930c58fb9a0c2432ada5f4e928644b2d2e26ba331bd793bd4d58e5cca261f8e61ee091e987c90b62d6d096da
|
7
|
+
data.tar.gz: 249378b6c61d2d02b4349af48080fc9c53d8bba28fe41da0d7492c725502f2932384e3b9dfde718be1eca5f1cbc17c4d0ef884ec6dd21be804f0592be10bef7f
|
data/Gemfile.lock
CHANGED
@@ -15,8 +15,6 @@ export default class extends Controller {
|
|
15
15
|
// This allows calling controller methods from the element in other controllers
|
16
16
|
connect () {
|
17
17
|
this.element.controller = this
|
18
|
-
|
19
|
-
this.updateHiddenValue()
|
20
18
|
}
|
21
19
|
|
22
20
|
toggle (event) {
|
@@ -89,7 +87,21 @@ export default class extends Controller {
|
|
89
87
|
for (const row of this.rowTargets) {
|
90
88
|
const conditional = row.previousElementSibling ? row.previousElementSibling.querySelector('[data-filter-target="conditional"]').value : null
|
91
89
|
const operator = row.querySelector('[data-filter-target="operator"]').value
|
92
|
-
|
90
|
+
let values = Array.from(row.querySelectorAll('[data-filter-target="value"]'))
|
91
|
+
|
92
|
+
// Only the visible elements are of interest
|
93
|
+
values = values.filter((element) => {
|
94
|
+
return element.style.display;
|
95
|
+
})
|
96
|
+
|
97
|
+
// Grab the value of each visible element
|
98
|
+
values = values.map((element) => {
|
99
|
+
return element.value
|
100
|
+
})
|
101
|
+
|
102
|
+
// Concatenate array to a string
|
103
|
+
const value = values.join(",")
|
104
|
+
|
93
105
|
string += `${conditional || ''}${operator}:${value}`
|
94
106
|
}
|
95
107
|
|
@@ -1,50 +1,78 @@
|
|
1
|
-
import {
|
1
|
+
import {Controller} from '@hotwired/stimulus'
|
2
2
|
|
3
3
|
export default class extends Controller {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
4
|
+
static get targets() {
|
5
|
+
return ['original', 'operator', 'null']
|
6
|
+
}
|
7
|
+
|
8
|
+
connect() {
|
9
|
+
this.operatorTarget.addEventListener('change', () => this.handleOperatorChange())
|
10
|
+
this.handleOperatorChange()
|
11
|
+
}
|
12
|
+
|
13
|
+
handleOperatorChange() {
|
14
|
+
if (this.operatorTarget.value === 'is_null' || this.operatorTarget.value === 'is_not_null') {
|
15
|
+
this.toggleNullInput()
|
16
|
+
} else if (this.operatorTarget.value === 'between' || this.operatorTarget.value === 'not_between') {
|
17
|
+
this.toggleSecondaryInput()
|
18
|
+
} else {
|
19
|
+
this.toggleOriginalInput()
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
toggleNullInput() {
|
24
|
+
this.hideOriginal()
|
25
|
+
this.hideSecondary()
|
26
|
+
this.showNull()
|
27
|
+
}
|
28
|
+
|
29
|
+
toggleOriginalInput() {
|
30
|
+
this.showOriginal()
|
31
|
+
this.hideSecondary()
|
32
|
+
this.hideNull()
|
33
|
+
}
|
34
|
+
|
35
|
+
toggleSecondaryInput() {
|
36
|
+
this.showSecondary()
|
37
|
+
this.hideOriginal()
|
38
|
+
this.hideNull()
|
39
|
+
}
|
40
|
+
|
41
|
+
hideOriginal() {
|
42
|
+
this.originalTarget.style.display = 'none'
|
43
|
+
this.originalTarget.setAttribute('data-filter-target', 'value_original')
|
44
|
+
}
|
45
|
+
|
46
|
+
showOriginal() {
|
47
|
+
this.originalTarget.style.display = 'block'
|
48
|
+
this.originalTarget.setAttribute('data-filter-target', 'value')
|
49
|
+
}
|
50
|
+
|
51
|
+
hideSecondary() {
|
52
|
+
for (const [index, value] of this.originalTargets.entries()) {
|
53
|
+
if (index != 0) {
|
54
|
+
value.style.display = 'none'
|
55
|
+
value.setAttribute('data-filter-target', 'value_original')
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
showSecondary() {
|
61
|
+
for (const [index, value] of this.originalTargets.entries()) {
|
62
|
+
if (index != 0) {
|
63
|
+
value.style.display = 'block'
|
64
|
+
value.setAttribute('data-filter-target', 'value')
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
hideNull() {
|
70
|
+
this.nullTarget.style.display = 'none'
|
71
|
+
this.nullTarget.setAttribute('data-filter-target', 'value_null')
|
72
|
+
}
|
73
|
+
|
74
|
+
showNull() {
|
75
|
+
this.nullTarget.style.display = 'block'
|
76
|
+
this.nullTarget.setAttribute('data-filter-target', 'value')
|
77
|
+
}
|
50
78
|
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
/* global fetch, Event */
|
2
|
+
import { Controller } from '@hotwired/stimulus'
|
3
|
+
|
4
|
+
export default class extends Controller {
|
5
|
+
connect() {
|
6
|
+
this.clickWhenInViewport()
|
7
|
+
|
8
|
+
document.querySelector(".modal-body").addEventListener("scroll", () => {
|
9
|
+
this.clickWhenInViewport()
|
10
|
+
})
|
11
|
+
}
|
12
|
+
|
13
|
+
clickWhenInViewport() {
|
14
|
+
if (!this.isLoading() && this.isInViewport()) {
|
15
|
+
this.element.setAttribute("clicked", 1)
|
16
|
+
this.element.click()
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
isLoading() {
|
21
|
+
return this.element.hasAttribute("clicked")
|
22
|
+
}
|
23
|
+
|
24
|
+
isInViewport() {
|
25
|
+
const rect = this.element.getBoundingClientRect()
|
26
|
+
|
27
|
+
return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
|
28
|
+
}
|
29
|
+
|
30
|
+
}
|
@@ -66,10 +66,10 @@ export default class extends Controller {
|
|
66
66
|
|
67
67
|
selectItems (items) {
|
68
68
|
// Destroy all deselected items
|
69
|
-
this.
|
69
|
+
this.removeAllDeselectedItems(items)
|
70
70
|
|
71
|
-
// Add all selected items
|
72
|
-
this.
|
71
|
+
// Add all new selected items
|
72
|
+
this.addNewItems(items)
|
73
73
|
|
74
74
|
this.postProcess()
|
75
75
|
}
|
@@ -129,8 +129,16 @@ export default class extends Controller {
|
|
129
129
|
})
|
130
130
|
}
|
131
131
|
|
132
|
-
|
133
|
-
|
132
|
+
addNewItems (items) {
|
133
|
+
const itemTargetIds = this.itemTargets.map((i) => { return parseInt(i.querySelectorAll("input")[1].value)})
|
134
|
+
items.forEach((item) => {
|
135
|
+
if (itemTargetIds.includes(item.blobId)) {
|
136
|
+
// Do not add this item (as it is already present)
|
137
|
+
return
|
138
|
+
}
|
139
|
+
|
140
|
+
this.addItem(item)
|
141
|
+
})
|
134
142
|
}
|
135
143
|
|
136
144
|
addItem (item) {
|
@@ -192,12 +200,20 @@ export default class extends Controller {
|
|
192
200
|
return template.innerHTML.replace(regex, randomNumber)
|
193
201
|
}
|
194
202
|
|
195
|
-
|
196
|
-
this.
|
203
|
+
removeAllDeselectedItems (items) {
|
204
|
+
this.removeDeselectedItems(items, this.itemTargets)
|
197
205
|
}
|
198
206
|
|
199
|
-
|
207
|
+
removeDeselectedItems (elements, items) {
|
208
|
+
const returnedBlobIds = elements.map((e) => { return e.blobId})
|
209
|
+
|
200
210
|
items.forEach((item) => {
|
211
|
+
const blobId = parseInt(item.querySelectorAll("input")[1].value)
|
212
|
+
if (returnedBlobIds.includes(blobId)) {
|
213
|
+
// Do not delete this one
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
|
201
217
|
this.removeItem(item)
|
202
218
|
})
|
203
219
|
}
|
@@ -1,110 +1,147 @@
|
|
1
1
|
/* global CustomEvent */
|
2
|
-
import {
|
2
|
+
import {Controller} from '@hotwired/stimulus'
|
3
3
|
|
4
4
|
export default class extends Controller {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
5
|
+
static get targets() {
|
6
|
+
return ['idCheckbox', 'item', 'form', 'selectButton', 'placeholder', 'count']
|
7
|
+
}
|
8
|
+
|
9
|
+
static get values () {
|
10
|
+
return { ids: Array }
|
11
|
+
}
|
12
|
+
|
13
|
+
connect() {
|
14
|
+
this.validate()
|
15
|
+
this.updateCount()
|
16
|
+
}
|
17
|
+
|
18
|
+
// Actions
|
19
|
+
select() {
|
20
|
+
this.dispatchSelectionEvent()
|
21
|
+
}
|
22
|
+
|
23
|
+
submitForm() {
|
24
|
+
this.hidePlaceholder()
|
25
|
+
this.triggerFormSubmission()
|
26
|
+
}
|
27
|
+
|
28
|
+
inputChange(event) {
|
29
|
+
this.handleIdsUpdate(event.target)
|
30
|
+
this.updateCount()
|
31
|
+
}
|
32
|
+
|
33
|
+
// Methods
|
34
|
+
hidePlaceholder() {
|
35
|
+
this.placeholderTarget.classList.add('d-none')
|
36
|
+
}
|
37
|
+
|
38
|
+
handleIdsUpdate(element) {
|
39
|
+
if (element.checked) {
|
40
|
+
let arr = this.idsValue
|
41
|
+
arr.push(element.value)
|
42
|
+
this.idsValue = arr
|
43
|
+
} else {
|
44
|
+
this.idsValue = this.idsValue.filter((value) => {
|
45
|
+
return element.value !== value
|
46
|
+
})
|
47
47
|
}
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
}
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
48
|
+
}
|
49
|
+
|
50
|
+
itemTargetConnected(element) {
|
51
|
+
this.updateItem(element.querySelector("input"))
|
52
|
+
}
|
53
|
+
|
54
|
+
updateItem(element) {
|
55
|
+
const arr = this.idsValue
|
56
|
+
|
57
|
+
if (arr.includes(element.value)) {
|
58
|
+
element.checked = true
|
59
|
+
} else {
|
60
|
+
element.checked = false
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
idsValueChanged() {
|
65
|
+
for (const item of this.itemTargets) {
|
66
|
+
this.updateItem(item.querySelector("input"))
|
67
|
+
}
|
68
|
+
this.validate()
|
69
|
+
}
|
70
|
+
|
71
|
+
dispatchSelectionEvent() {
|
72
|
+
document.dispatchEvent(
|
73
|
+
new CustomEvent(
|
74
|
+
'mediaSelectionSubmitted',
|
75
|
+
{
|
76
|
+
detail: {
|
77
|
+
name: this.element.dataset.name,
|
78
|
+
items: this.renderItemsForEvent()
|
79
|
+
}
|
80
|
+
}
|
81
|
+
)
|
82
|
+
)
|
83
|
+
}
|
84
|
+
|
85
|
+
triggerFormSubmission() {
|
86
|
+
this.formTarget.requestSubmit()
|
87
|
+
}
|
88
|
+
|
89
|
+
renderItemsForEvent() {
|
90
|
+
return this.idsValue.map((item) => this.renderItemForEvent(item)).filter((i) => { return i !== undefined })
|
91
|
+
}
|
92
|
+
|
93
|
+
renderItemForEvent(item) {
|
94
|
+
const id = parseInt(item)
|
95
|
+
const blob_id = `#blob_${id}`
|
96
|
+
const element = this.element.querySelector(blob_id)
|
97
|
+
|
98
|
+
return {
|
99
|
+
blobId: id,
|
100
|
+
thumbnail: element ? element.querySelector('.h-thumbnail') : ""
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
selectedItems() {
|
105
|
+
return this.itemTargets.filter((item) => {
|
106
|
+
const checkbox = item.querySelector('input[type="checkbox"]')
|
107
|
+
return checkbox.checked
|
108
|
+
})
|
109
|
+
}
|
110
|
+
|
111
|
+
selectedItemsCount() {
|
112
|
+
return this.idsValue.length
|
113
|
+
}
|
114
|
+
|
115
|
+
minSelectedItems() {
|
116
|
+
return parseInt(this.element.dataset.min, 10) || 0
|
117
|
+
}
|
118
|
+
|
119
|
+
maxSelectedItems() {
|
120
|
+
return parseInt(this.element.dataset.max, 10) || Infinity
|
121
|
+
}
|
122
|
+
|
123
|
+
validate() {
|
124
|
+
if (this.isValid()) {
|
125
|
+
this.enableSelectButton()
|
126
|
+
} else {
|
127
|
+
this.disableSelectButton()
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
enableSelectButton() {
|
132
|
+
this.selectButtonTarget.removeAttribute('disabled')
|
133
|
+
}
|
134
|
+
|
135
|
+
disableSelectButton() {
|
136
|
+
this.selectButtonTarget.setAttribute('disabled', '')
|
137
|
+
}
|
138
|
+
|
139
|
+
isValid() {
|
140
|
+
const count = this.selectedItemsCount()
|
141
|
+
return count >= this.minSelectedItems() && count <= this.maxSelectedItems()
|
142
|
+
}
|
143
|
+
|
144
|
+
updateCount() {
|
145
|
+
this.countTarget.innerHTML = this.selectedItemsCount()
|
146
|
+
}
|
110
147
|
}
|
@@ -10,6 +10,7 @@ import FilterRowController from './controllers/filter_row_controller'
|
|
10
10
|
import FiltersController from './controllers/filters_controller'
|
11
11
|
import FlatpickrController from './controllers/flatpickr_controller'
|
12
12
|
import HelloController from './controllers/hello_controller'
|
13
|
+
import InfiniteScrollerController from './controllers/infinite_scroller_controller'
|
13
14
|
import MediaController from './controllers/media_controller'
|
14
15
|
import MediaModalController from './controllers/media_modal_controller'
|
15
16
|
import NotificationController from './controllers/notification_controller'
|
@@ -35,6 +36,7 @@ export class Headmin {
|
|
35
36
|
Stimulus.register('filters', FiltersController)
|
36
37
|
Stimulus.register('flatpickr', FlatpickrController)
|
37
38
|
Stimulus.register('hello', HelloController)
|
39
|
+
Stimulus.register('infinite-scroller', InfiniteScrollerController)
|
38
40
|
Stimulus.register('media', MediaController)
|
39
41
|
Stimulus.register('media-modal', MediaModalController)
|
40
42
|
Stimulus.register('notification', NotificationController)
|