refine-rails 2.9.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.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +413 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/refine_rails_manifest.js +0 -0
  5. data/app/assets/javascripts/refine-stimulus.esm.js +2 -0
  6. data/app/assets/javascripts/refine-stimulus.esm.js.map +1 -0
  7. data/app/assets/javascripts/refine-stimulus.js +2 -0
  8. data/app/assets/javascripts/refine-stimulus.js.map +1 -0
  9. data/app/assets/javascripts/refine-stimulus.modern.js +2 -0
  10. data/app/assets/javascripts/refine-stimulus.modern.js.map +1 -0
  11. data/app/assets/javascripts/refine-stimulus.umd.js +2 -0
  12. data/app/assets/javascripts/refine-stimulus.umd.js.map +1 -0
  13. data/app/assets/stylesheets/index.css +1873 -0
  14. data/app/assets/stylesheets/index.tailwind.css +1035 -0
  15. data/app/controllers/refine/blueprints_controller.rb +80 -0
  16. data/app/controllers/refine/filter_application_controller.rb +29 -0
  17. data/app/controllers/refine/inline/criteria_controller.rb +161 -0
  18. data/app/controllers/refine/inline/stored_filters_controller.rb +84 -0
  19. data/app/controllers/refine/stored_filters_controller.rb +69 -0
  20. data/app/javascript/controllers/index.js +66 -0
  21. data/app/javascript/controllers/refine/add-controller.js +42 -0
  22. data/app/javascript/controllers/refine/criterion-form-controller.js +31 -0
  23. data/app/javascript/controllers/refine/date-controller.js +113 -0
  24. data/app/javascript/controllers/refine/defaults-controller.js +32 -0
  25. data/app/javascript/controllers/refine/delete-controller.js +13 -0
  26. data/app/javascript/controllers/refine/filter-pills-controller.js +63 -0
  27. data/app/javascript/controllers/refine/form-controller.js +51 -0
  28. data/app/javascript/controllers/refine/inline-conditions-controller.js +33 -0
  29. data/app/javascript/controllers/refine/popup-controller.js +46 -0
  30. data/app/javascript/controllers/refine/search-filter-controller.js +50 -0
  31. data/app/javascript/controllers/refine/server-refresh-controller.js +43 -0
  32. data/app/javascript/controllers/refine/state-controller.js +220 -0
  33. data/app/javascript/controllers/refine/stored-filter-controller.js +23 -0
  34. data/app/javascript/controllers/refine/submit-form-controller.js +8 -0
  35. data/app/javascript/controllers/refine/toggle-controller.js +12 -0
  36. data/app/javascript/controllers/refine/turbo-stream-form-controller.js +24 -0
  37. data/app/javascript/controllers/refine/turbo-stream-link-controller.js +24 -0
  38. data/app/javascript/controllers/refine/update-controller.js +86 -0
  39. data/app/javascript/index.js +1 -0
  40. data/app/javascript/refine/helpers/index.js +77 -0
  41. data/app/models/refine/blueprints/blueprint.rb +58 -0
  42. data/app/models/refine/blueprints/blueprint_example.json +25 -0
  43. data/app/models/refine/conditions/boolean_condition.rb +112 -0
  44. data/app/models/refine/conditions/clause.rb +38 -0
  45. data/app/models/refine/conditions/clauses.rb +38 -0
  46. data/app/models/refine/conditions/condition.rb +285 -0
  47. data/app/models/refine/conditions/condition_error.rb +1 -0
  48. data/app/models/refine/conditions/date_condition.rb +464 -0
  49. data/app/models/refine/conditions/date_with_time_condition.rb +8 -0
  50. data/app/models/refine/conditions/errors/condition_clause_error.rb +7 -0
  51. data/app/models/refine/conditions/errors/criteria_limit_exceeded_error.rb +2 -0
  52. data/app/models/refine/conditions/errors/option_error.rb +2 -0
  53. data/app/models/refine/conditions/errors/relationship_error.rb +1 -0
  54. data/app/models/refine/conditions/filter_condition.rb +93 -0
  55. data/app/models/refine/conditions/has_clauses.rb +117 -0
  56. data/app/models/refine/conditions/has_meta.rb +10 -0
  57. data/app/models/refine/conditions/has_refinements.rb +156 -0
  58. data/app/models/refine/conditions/numeric_condition.rb +224 -0
  59. data/app/models/refine/conditions/option_condition.rb +260 -0
  60. data/app/models/refine/conditions/text_condition.rb +152 -0
  61. data/app/models/refine/conditions/uses_attributes.rb +168 -0
  62. data/app/models/refine/filter.rb +302 -0
  63. data/app/models/refine/filters/blueprint_editor.rb +102 -0
  64. data/app/models/refine/filters/builder.rb +59 -0
  65. data/app/models/refine/filters/criterion.rb +87 -0
  66. data/app/models/refine/filters/query.rb +82 -0
  67. data/app/models/refine/inline/criteria/input.rb +50 -0
  68. data/app/models/refine/inline/criteria/numeric_refinement.rb +13 -0
  69. data/app/models/refine/inline/criteria/option.rb +2 -0
  70. data/app/models/refine/inline/criterion.rb +141 -0
  71. data/app/models/refine/invalid_filter_error.rb +8 -0
  72. data/app/models/refine/stabilize.rb +29 -0
  73. data/app/models/refine/stabilizers/database_stabilizer.rb +21 -0
  74. data/app/models/refine/stabilizers/errors/url_stabilizer_error.rb +2 -0
  75. data/app/models/refine/stabilizers/url_encoded_stabilizer.rb +21 -0
  76. data/app/models/refine/stored_filter.rb +14 -0
  77. data/app/models/refine/tracks_pending_relationship_subqueries.rb +196 -0
  78. data/app/views/_filter_builder_dropdown.html.erb +63 -0
  79. data/app/views/_filter_pills.html.erb +40 -0
  80. data/app/views/_loading.html.erb +32 -0
  81. data/app/views/refine/blueprints/_add_and.html.erb +25 -0
  82. data/app/views/refine/blueprints/_add_group.html.erb +24 -0
  83. data/app/views/refine/blueprints/_clause_select.html.erb +24 -0
  84. data/app/views/refine/blueprints/_condition_select.html.erb +53 -0
  85. data/app/views/refine/blueprints/_criterion.html.erb +41 -0
  86. data/app/views/refine/blueprints/_criterion_errors.html.erb +7 -0
  87. data/app/views/refine/blueprints/_delete_criterion.html.erb +11 -0
  88. data/app/views/refine/blueprints/_group.html.erb +13 -0
  89. data/app/views/refine/blueprints/_query.html.erb +34 -0
  90. data/app/views/refine/blueprints/_stored_filters.html.erb +23 -0
  91. data/app/views/refine/blueprints/clauses/_date_condition.html.erb +80 -0
  92. data/app/views/refine/blueprints/clauses/_date_picker.html.erb +26 -0
  93. data/app/views/refine/blueprints/clauses/_filter_condition.html.erb +36 -0
  94. data/app/views/refine/blueprints/clauses/_numeric_condition.html.erb +35 -0
  95. data/app/views/refine/blueprints/clauses/_option_condition.html.erb +37 -0
  96. data/app/views/refine/blueprints/clauses/_text_condition.html.erb +13 -0
  97. data/app/views/refine/blueprints/create.turbo_stream.erb +22 -0
  98. data/app/views/refine/blueprints/new.html.erb +7 -0
  99. data/app/views/refine/blueprints/show.html.erb +4 -0
  100. data/app/views/refine/blueprints/show.turbo_stream.erb +22 -0
  101. data/app/views/refine/inline/criteria/_form_fields.html.erb +62 -0
  102. data/app/views/refine/inline/criteria/create.turbo_stream.erb +19 -0
  103. data/app/views/refine/inline/criteria/edit.turbo_stream.erb +26 -0
  104. data/app/views/refine/inline/criteria/index.html.erb +64 -0
  105. data/app/views/refine/inline/criteria/new.turbo_stream.erb +24 -0
  106. data/app/views/refine/inline/filters/_add_first_condition_button.html.erb +19 -0
  107. data/app/views/refine/inline/filters/_and_button.html.erb +26 -0
  108. data/app/views/refine/inline/filters/_criterion.html.erb +23 -0
  109. data/app/views/refine/inline/filters/_group.html.erb +13 -0
  110. data/app/views/refine/inline/filters/_load_button.html.erb +15 -0
  111. data/app/views/refine/inline/filters/_or_button.html.erb +26 -0
  112. data/app/views/refine/inline/filters/_popup.html.erb +26 -0
  113. data/app/views/refine/inline/filters/_save_button.html.erb +15 -0
  114. data/app/views/refine/inline/filters/_show.html.erb +40 -0
  115. data/app/views/refine/inline/inputs/_date_condition.html.erb +7 -0
  116. data/app/views/refine/inline/inputs/_date_condition_days.html.erb +18 -0
  117. data/app/views/refine/inline/inputs/_date_condition_range.html.erb +22 -0
  118. data/app/views/refine/inline/inputs/_date_condition_single.html.erb +9 -0
  119. data/app/views/refine/inline/inputs/_date_picker.html.erb +20 -0
  120. data/app/views/refine/inline/inputs/_numeric_condition.html.erb +23 -0
  121. data/app/views/refine/inline/inputs/_option_condition.html.erb +14 -0
  122. data/app/views/refine/inline/inputs/_text_condition.html.erb +8 -0
  123. data/app/views/refine/inline/stored_filters/find.turbo_stream.erb +19 -0
  124. data/app/views/refine/inline/stored_filters/index.html.erb +28 -0
  125. data/app/views/refine/inline/stored_filters/new.turbo_stream.erb +47 -0
  126. data/app/views/refine/stored_filters/create.turbo_stream.erb +2 -0
  127. data/app/views/refine/stored_filters/find.turbo_stream.erb +5 -0
  128. data/app/views/refine/stored_filters/index.html.erb +39 -0
  129. data/app/views/refine/stored_filters/new.html.erb +29 -0
  130. data/app/views/refine/stored_filters/show.html.erb +1 -0
  131. data/config/locales/en/dates.en.yml +29 -0
  132. data/config/locales/en/en.yml +20 -0
  133. data/config/locales/en/refine.en.yml +187 -0
  134. data/config/routes.rb +17 -0
  135. data/lib/generators/filter/filter_generator.rb +27 -0
  136. data/lib/generators/filter/templates/filter.rb.erb +20 -0
  137. data/lib/refine/rails/engine.rb +15 -0
  138. data/lib/refine/rails/version.rb +5 -0
  139. data/lib/refine/rails.rb +38 -0
  140. data/lib/tasks/refine/rails_tasks.rake +13 -0
  141. metadata +202 -0
@@ -0,0 +1,32 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ criterionId: Number,
6
+ input: Object,
7
+ };
8
+
9
+ connect() {
10
+ this.state = this.getStateController()
11
+
12
+ this.state.updateInput(
13
+ this.criterionIdValue,
14
+ this.inputValue,
15
+ );
16
+ }
17
+
18
+ getStateController() {
19
+ let currentElement = this.element
20
+
21
+ while(currentElement !== document.body) {
22
+ const controller = this.application.getControllerForElementAndIdentifier(currentElement, 'refine--state')
23
+ if (controller) {
24
+ return controller
25
+ } else {
26
+ currentElement = currentElement.parentNode
27
+ }
28
+ }
29
+
30
+ return null
31
+ }
32
+ }
@@ -0,0 +1,13 @@
1
+ import ServerRefreshController from './server-refresh-controller';
2
+
3
+ export default class extends ServerRefreshController {
4
+ static values = {
5
+ criterionId: Number,
6
+ }
7
+
8
+ criterion() {
9
+ const { state, criterionIdValue } = this;
10
+ state.deleteCriterion(criterionIdValue);
11
+ this.refreshFromServer()
12
+ }
13
+ }
@@ -0,0 +1,63 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { FetchRequest } from '@rails/request.js'
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ submitUrl: String
7
+ }
8
+
9
+ connect() {
10
+ const urlParams = new URLSearchParams(window.location.search)
11
+ this.existingParams = urlParams
12
+ this.existingParams.delete('stable_id')
13
+ }
14
+
15
+ delete(event) {
16
+ const { criterionId } = event.currentTarget.dataset
17
+ var index = parseInt(criterionId)
18
+ this.stateController.deleteCriterion(index)
19
+ this.reloadPage()
20
+ }
21
+
22
+ async reloadPage() {
23
+ const {blueprint} = this.stateController
24
+ const request = new FetchRequest(
25
+ "POST",
26
+ this.submitUrlValue,
27
+ {
28
+ responseKind: "turbo-stream",
29
+ body: JSON.stringify({
30
+ refine_filters_builder: {
31
+ filter_class: this.stateController.filterName,
32
+ blueprint_json: JSON.stringify(blueprint),
33
+ client_id: this.stateController.clientIdValue
34
+ }
35
+ })
36
+ }
37
+ )
38
+ await request.perform()
39
+ }
40
+
41
+ redirectToStableId(stableId) {
42
+ const params = new URLSearchParams()
43
+ if (stableId) {
44
+ params.append('stable_id', stableId)
45
+ }
46
+ const allParams = new URLSearchParams({
47
+ ...Object.fromEntries(this.existingParams),
48
+ ...Object.fromEntries(params),
49
+ }).toString()
50
+ const url = `${window.location.pathname}?${allParams}`
51
+
52
+ history.pushState({}, document.title, url)
53
+ window.location.reload()
54
+ }
55
+
56
+ get stateController() {
57
+ return this.element.refineStateController
58
+ }
59
+
60
+ get stabilizeFilterController() {
61
+ return this.element.stabilizeFilterController
62
+ }
63
+ }
@@ -0,0 +1,51 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.state = this.getStateController()
6
+ this.blueprintInput = this.addHiddenInput('blueprint')
7
+ this.addHiddenInput('filter', this.state.filterName)
8
+ this.addHiddenInput('form_id', this.state.formIdValue)
9
+ this.finishUpdate()
10
+ }
11
+
12
+ getStateController() {
13
+ let currentElement = this.element
14
+
15
+ while(currentElement !== document.body) {
16
+ const controller = this.application.getControllerForElementAndIdentifier(currentElement, 'refine--state')
17
+ if (controller) {
18
+ return controller
19
+ } else {
20
+ currentElement = currentElement.parentNode
21
+ }
22
+ }
23
+
24
+ return null
25
+ }
26
+
27
+ addHiddenInput(name, initialValue) {
28
+ const input = document.createElement('input')
29
+ input.type = 'hidden'
30
+ input.name = name
31
+ input.value = initialValue || ''
32
+ this.element.appendChild(input)
33
+ return input
34
+ }
35
+
36
+ // called on connect
37
+ finishUpdate() {
38
+ this.state.finishUpdate()
39
+ }
40
+
41
+ // Call this on submit
42
+ startUpdate() {
43
+ this.blueprintInput.value = JSON.stringify(this.state.blueprint)
44
+ this.state.startUpdate()
45
+ }
46
+
47
+ submitForm() {
48
+ this.startUpdate()
49
+ this.element.requestSubmit()
50
+ }
51
+ }
@@ -0,0 +1,33 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+
5
+ static targets = ['condition', 'category']
6
+
7
+ filterConditions(event) {
8
+ const query = event.currentTarget.value.toLowerCase()
9
+ const visibleCategories = new Set()
10
+
11
+ // hide / show condition links that match the query and note which
12
+ // categories should be visible
13
+ this.conditionTargets.forEach(conditionNode => {
14
+ const conditionName = conditionNode.innerHTML.toLowerCase()
15
+ if (conditionName.includes(query)) {
16
+ conditionNode.hidden = false
17
+ visibleCategories.add(conditionNode.dataset.category)
18
+ } else {
19
+ conditionNode.hidden = true
20
+ }
21
+ })
22
+
23
+ // hide / show category headers that have
24
+ this.categoryTargets.forEach(categoryNode => {
25
+ const categoryName = categoryNode.innerHTML
26
+ if (visibleCategories.has(categoryName)) {
27
+ categoryNode.hidden = false
28
+ } else {
29
+ categoryNode.hidden = true
30
+ }
31
+ })
32
+ }
33
+ }
@@ -0,0 +1,46 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { useClickOutside } from 'stimulus-use'
3
+
4
+ // simple controller to hide/show the filter modal
5
+ export default class extends Controller {
6
+ static targets = ["frame"]
7
+
8
+ static values = {
9
+ src: String,
10
+ isOpen: {type: Boolean, default: false}
11
+ }
12
+
13
+ connect() {
14
+ useClickOutside(this)
15
+ this.boundHandleKeyUp = this.handleKeyUp.bind(this)
16
+ document.addEventListener("keyup", this.boundHandleKeyUp)
17
+ }
18
+
19
+ disconnect() {
20
+ document.removeEventListener("keyup", this.boundHandleKeyUp)
21
+ }
22
+
23
+ show(event) {
24
+ event.preventDefault()
25
+ this.frameTarget.src = this.srcValue;
26
+ this.isOpenValue = true
27
+ }
28
+
29
+ hide(event) {
30
+ if (this.isOpenValue) {
31
+ event?.preventDefault()
32
+ this.frameTarget.innerHTML = "";
33
+ this.isOpenValue = false
34
+ }
35
+ }
36
+
37
+ clickOutside(event) {
38
+ this.hide(event)
39
+ }
40
+
41
+ handleKeyUp(event) {
42
+ if (event.key === "Escape" || event.key === "Esc") {
43
+ this.hide(event)
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,50 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { FetchRequest } from '@rails/request.js'
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ submitUrl: String
7
+ }
8
+
9
+
10
+ search(event) {
11
+ event.preventDefault()
12
+ this.submitFilter()
13
+ document.activeElement.blur()
14
+ }
15
+
16
+ async submitFilter() {
17
+ const {blueprint} = this.stateController
18
+ const request = new FetchRequest(
19
+ "POST",
20
+ this.submitUrlValue,
21
+ {
22
+ responseKind: "turbo-stream",
23
+ body: JSON.stringify({
24
+ refine_filters_builder: {
25
+ filter_class: this.stateController.filterName,
26
+ blueprint_json: JSON.stringify(blueprint),
27
+ client_id: this.stateController.clientIdValue
28
+ }
29
+ })
30
+ }
31
+ )
32
+ await request.perform()
33
+ }
34
+
35
+ get stateController() {
36
+ return this
37
+ .element
38
+ .querySelector('[data-controller~="refine--state"]')
39
+ .refineStateController
40
+ }
41
+
42
+ loadResults({detail: {url}}) {
43
+ console.log("filter submit success")
44
+ if (window.Turbo) {
45
+ window.Turbo.visit(url)
46
+ } else {
47
+ window.location.href = url
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,43 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { FetchRequest } from '@rails/request.js'
3
+
4
+
5
+ // Base class for controllers that reload form content from the server
6
+ export default class extends Controller {
7
+ connect() {
8
+ this.state.finishUpdate()
9
+ }
10
+
11
+ get state() {
12
+ let currentElement = this.element
13
+
14
+ while(currentElement !== document.body) {
15
+ if (currentElement.matches('[data-controller~="refine--state"]'))
16
+ return this.application.getControllerForElementAndIdentifier(currentElement, 'refine--state')
17
+ else {
18
+ currentElement = currentElement.parentNode
19
+ }
20
+ }
21
+
22
+ return null
23
+ }
24
+
25
+ async refreshFromServer(options = {}) {
26
+ const { includeErrors } = options
27
+ this.state.startUpdate()
28
+ const request = new FetchRequest(
29
+ "GET",
30
+ this.state.refreshUrlValue,
31
+ {
32
+ responseKind: "turbo-stream",
33
+ query: {
34
+ "refine_filters_builder[filter_class]": this.state.filterName,
35
+ "refine_filters_builder[blueprint_json]": JSON.stringify(this.state.blueprint),
36
+ "refine_filters_builder[client_id]": this.state.clientIdValue,
37
+ include_errors: !!includeErrors
38
+ }
39
+ }
40
+ )
41
+ await request.perform()
42
+ }
43
+ }
@@ -0,0 +1,220 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { delegate, abnegate } from 'jquery-events-to-dom-events'
3
+ import { blueprintUpdatedEvent } from '../../refine/helpers'
4
+ import { isEqual } from 'lodash'
5
+
6
+ const criterion = (id, depth, condition) => {
7
+ const component = condition?.component
8
+ const meta = condition?.meta || { clauses: [], options: {}}
9
+ const refinements = condition?.refinements || []
10
+ const { clauses, options } = meta
11
+ let selected
12
+ if (component === 'option-condition') {
13
+ selected = options[0] ? [options[0].id] : []
14
+ } else {
15
+ selected = undefined
16
+ }
17
+ // Set newInput based on component
18
+
19
+ let newInput = {
20
+ clause: clauses[0]?.id,
21
+ selected: selected,
22
+ }
23
+
24
+ // If refinements are present, add to input array
25
+ refinements.forEach((refinement) => {
26
+ const { meta, component } = refinement
27
+ const { clauses, options } = meta
28
+ let selected
29
+ if (component === 'option-condition') {
30
+ selected = options[0] ? [options[0].id] : []
31
+ } else {
32
+ selected = undefined
33
+ }
34
+ newInput[refinement.id] = {
35
+ clause: clauses[0].id,
36
+ selected: selected,
37
+ }
38
+ })
39
+
40
+ return {
41
+ depth,
42
+ type: 'criterion',
43
+ condition_id: id,
44
+ input: newInput,
45
+ }
46
+ }
47
+
48
+ const or = function (depth) {
49
+ depth = depth === undefined ? 0 : depth
50
+ return {
51
+ depth,
52
+ type: 'conjunction',
53
+ word: 'or',
54
+ }
55
+ }
56
+
57
+ const and = function (depth) {
58
+ depth = depth === undefined ? 1 : depth
59
+ return {
60
+ depth,
61
+ type: 'conjunction',
62
+ word: 'and',
63
+ }
64
+ }
65
+ export default class extends Controller {
66
+ static values = {
67
+ blueprint: Array,
68
+ conditions: Array,
69
+ className: String,
70
+ refreshUrl: String,
71
+ clientId: String,
72
+ validateBlueprintUrl: String,
73
+ defaultConditionId: String
74
+ }
75
+ static targets = ['loading']
76
+
77
+
78
+ connect() {
79
+ // for select2 jquery events and datepicker
80
+ this.element.refineStateController = this
81
+ this.changeDelegate = delegate('change', ['event', 'picker'])
82
+ this.blueprint = this.blueprintValue
83
+ this.conditions = this.conditionsValue
84
+ this.filterName = this.classNameValue
85
+ this.conditionsLookup = this.conditions.reduce((lookup, condition) => {
86
+ lookup[condition.id] = condition
87
+ return lookup
88
+ }, {})
89
+ this.loadingTimeout = null
90
+ blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
91
+ }
92
+
93
+ disconnect() {
94
+ abnegate('change', this.changeDelegate)
95
+ }
96
+
97
+ startUpdate() {
98
+ if (this.loadingTimeout) {
99
+ window.clearTimeout(this.loadingTimeout)
100
+ }
101
+ // only show the loading overlay if it's taking a long time
102
+ // to render the updates
103
+ this.loadingTimeout = window.setTimeout(() => {
104
+ this.loadingTarget.classList.remove('hidden')
105
+ }, 1000)
106
+ }
107
+
108
+ finishUpdate() {
109
+ if (this.loadingTimeout) {
110
+ window.clearTimeout(this.loadingTimeout)
111
+ }
112
+ this.loadingTarget.classList.add('hidden')
113
+ }
114
+
115
+ conditionConfigFor(conditionId) {
116
+ return this.conditionsLookup[conditionId]
117
+ }
118
+
119
+ addGroup() {
120
+ const { blueprint, conditions } = this
121
+ const condition = ( conditions.find(c => c.id == this.defaultConditionIdValue) || conditions[0] )
122
+ const { meta } = condition
123
+
124
+ if (this.blueprint.length > 0) {
125
+ this.blueprint.push(or())
126
+ }
127
+ this.blueprint.push(criterion(condition.id, 1, condition))
128
+ blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
129
+ }
130
+
131
+ addCriterion(previousCriterionId) {
132
+ const { blueprint, conditions } = this
133
+ const condition = ( conditions.find(c => c.id == this.defaultConditionIdValue) || conditions[0] )
134
+ const { meta } = condition
135
+ blueprint.splice(previousCriterionId + 1, 0, and(), criterion(condition.id, 1, condition))
136
+ blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
137
+ }
138
+
139
+ deleteCriterion(criterionId) {
140
+ /**
141
+ To support 'groups' there is some complicated logic for deleting criterion.
142
+
143
+ Imagine this simplified blueprint: [eq, and, sw, or, eq]
144
+
145
+ User clicks to delete the last eq. We also have to delete the preceding or
146
+ otherwise we're left with a hanging empty group
147
+
148
+ What if the user deletes the sw? We have to clean up the preceding and.
149
+
150
+ Imagine another scenario: [eq or sw and ew]
151
+ Now we delete the first eq but this time we need to clean up the or.
152
+
153
+ These conditionals cover these cases.
154
+ **/
155
+ const { blueprint } = this
156
+ const previous = blueprint[criterionId - 1]
157
+ const next = blueprint[criterionId + 1]
158
+
159
+ const nextIsOr = next && next.word === 'or'
160
+ const previousIsOr = previous && previous.word === 'or'
161
+
162
+ const nextIsRightParen = nextIsOr || !next
163
+ const previousIsLeftParen = previousIsOr || !previous
164
+
165
+ const isFirstInGroup = previousIsLeftParen && !nextIsRightParen
166
+ const isLastInGroup = previousIsLeftParen && nextIsRightParen
167
+ const isLastCriterion = !previous && !next
168
+
169
+ if (isLastCriterion) {
170
+ this.blueprint = []
171
+ } else if (isLastInGroup && previousIsOr) {
172
+ blueprint.splice(criterionId - 1, 2)
173
+ } else if (isLastInGroup && !previous) {
174
+ blueprint.splice(criterionId, 2)
175
+ } else if (isFirstInGroup) {
176
+ blueprint.splice(criterionId, 2)
177
+ } else {
178
+ blueprint.splice(criterionId - 1, 2)
179
+ }
180
+
181
+ blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
182
+ }
183
+
184
+ /*
185
+ Updates a criterion in the blueprint
186
+ Returns true if an update was actually performed, or false if no-op
187
+ */
188
+ replaceCriterion(criterionId, conditionId, condition) {
189
+ const criterionRow = this.blueprint[criterionId]
190
+ if (criterionRow.type !== 'criterion') {
191
+ throw new Error(
192
+ `You can't call updateConditionId on a non-criterion type. Trying to update ${JSON.stringify(criterion)}`
193
+ )
194
+ }
195
+ const existingCriterion = this.blueprint[criterionId]
196
+ const newCriterion = criterion(conditionId, criterionRow.depth, condition)
197
+ if (isEqual(existingCriterion, newCriterion)) {
198
+ return false
199
+ } else {
200
+ this.blueprint[criterionId] = newCriterion
201
+ blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
202
+ return true
203
+ }
204
+ }
205
+
206
+ updateInput(criterionId, input, inputId) {
207
+ // Input id is an array of hash keys that define the path for this input such as ["input", "date_refinement"]
208
+ const { blueprint } = this
209
+ const criterion = blueprint[criterionId]
210
+ inputId = inputId || 'input'
211
+ const blueprintPath = inputId.split(', ')
212
+ // If the inputId contains more than one element, add input at appropriate depth
213
+ if (blueprintPath.length > 1) {
214
+ criterion[blueprintPath[0]][blueprintPath[1]] = { ...criterion[blueprintPath[0]][blueprintPath[1]], ...input }
215
+ } else {
216
+ criterion[inputId] = { ...criterion[inputId], ...input }
217
+ }
218
+ blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
219
+ }
220
+ }
@@ -0,0 +1,23 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { filterStoredEvent } from '../../refine/helpers'
3
+
4
+ export default class extends Controller {
5
+ static targets = ['blueprintField']
6
+ static values = { formId: String, stateDomId: String }
7
+
8
+ connect() {
9
+ const stateController = document
10
+ .getElementById(this.stateDomIdValue)
11
+ .refineStateController
12
+ this.blueprintFieldTarget.value = JSON.stringify(stateController.blueprint)
13
+ console.log("connect", this.blueprintFieldTarget.value)
14
+ }
15
+
16
+ updateBlueprintField(event) {
17
+ if (event.detail.formId != this.formIdValue) { return null }
18
+ const { detail } = event
19
+ const { blueprint } = detail
20
+ this.blueprintFieldTarget.value = JSON.stringify(blueprint)
21
+ console.log("update blueprint", this.blueprintFieldTarget.value)
22
+ }
23
+ }
@@ -0,0 +1,8 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ submit(event) {
5
+ event.preventDefault()
6
+ this.element.submit()
7
+ }
8
+ }
@@ -0,0 +1,12 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // simple controller to hide/show the filter modal
4
+ export default class extends Controller {
5
+ static targets = ["content"]
6
+
7
+ toggle(_event) {
8
+ this.contentTargets.forEach(node => {
9
+ node.toggleAttribute("hidden")
10
+ })
11
+ }
12
+ }
@@ -0,0 +1,24 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { FetchRequest } from '@rails/request.js'
3
+
4
+ /*
5
+ attach to a form element to have it submit to a turbo-stream endpoint
6
+
7
+ <form action="/contacts" data-controller="refine--turbo-stream-form" data-action="submit->refine--turbo-stream-form#submit">
8
+
9
+ Turbo is supposed to handle this natively but we're seeing issues when the form is inside an iframe
10
+ */
11
+ export default class extends Controller {
12
+ async submit(event) {
13
+ event.preventDefault()
14
+ const request = new FetchRequest(
15
+ (this.element.method || "POST"),
16
+ this.element.action,
17
+ {
18
+ responseKind: "turbo-stream",
19
+ body: new FormData(this.element)
20
+ }
21
+ )
22
+ await request.perform()
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { FetchRequest } from '@rails/request.js'
3
+
4
+ /*
5
+ attach to a link element to have it request turbo stream responses
6
+
7
+ <a href="/contacts" data-controller="refine--turbo-stream-link" data-action="refine--turbo-stream-link#get">Click me</a>
8
+
9
+ Turbo is supposed to handle this natively with data-turbo-stream but we're
10
+ seeing issues using that attribute inside iframes
11
+ */
12
+ export default class extends Controller {
13
+ async visit(event) {
14
+ event.preventDefault()
15
+ const request = new FetchRequest(
16
+ (this.element.dataset.turboMethod || "GET"),
17
+ this.element.href,
18
+ {
19
+ responseKind: "turbo-stream",
20
+ }
21
+ )
22
+ await request.perform()
23
+ }
24
+ }