refine-rails 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }