avo 2.7.1.pre.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +5 -5
  4. data/app/components/avo/alert_component.rb +6 -0
  5. data/app/components/avo/card_component.html.erb +2 -2
  6. data/app/components/avo/common_field_wrapper_component.html.erb +10 -3
  7. data/app/components/avo/common_field_wrapper_component.rb +27 -1
  8. data/app/components/avo/edit/field_wrapper_component.html.erb +1 -1
  9. data/app/components/avo/fields/badge_field/index_component.html.erb +1 -1
  10. data/app/components/avo/fields/badge_field/show_component.html.erb +1 -1
  11. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +21 -10
  12. data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +7 -1
  13. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +27 -15
  14. data/app/components/avo/fields/belongs_to_field/edit_component.rb +4 -0
  15. data/app/components/avo/fields/belongs_to_field/index_component.html.erb +1 -1
  16. data/app/components/avo/fields/belongs_to_field/show_component.html.erb +1 -1
  17. data/app/components/avo/fields/boolean_field/edit_component.html.erb +4 -2
  18. data/app/components/avo/fields/boolean_field/index_component.html.erb +1 -1
  19. data/app/components/avo/fields/boolean_field/show_component.html.erb +1 -1
  20. data/app/components/avo/fields/boolean_group_field/edit_component.html.erb +7 -1
  21. data/app/components/avo/fields/boolean_group_field/index_component.html.erb +1 -1
  22. data/app/components/avo/fields/boolean_group_field/show_component.html.erb +1 -1
  23. data/app/components/avo/fields/code_field/edit_component.html.erb +7 -5
  24. data/app/components/avo/fields/code_field/show_component.html.erb +2 -2
  25. data/app/components/avo/fields/common/key_value_component.html.erb +10 -4
  26. data/app/components/avo/fields/common/key_value_component.rb +2 -0
  27. data/app/components/avo/fields/country_field/edit_component.html.erb +4 -2
  28. data/app/components/avo/fields/country_field/index_component.html.erb +1 -1
  29. data/app/components/avo/fields/country_field/show_component.html.erb +1 -1
  30. data/app/components/avo/fields/date_field/edit_component.html.erb +6 -4
  31. data/app/components/avo/fields/date_field/index_component.html.erb +1 -1
  32. data/app/components/avo/fields/date_field/show_component.html.erb +1 -1
  33. data/app/components/avo/fields/date_time_field/edit_component.html.erb +6 -4
  34. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -1
  35. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -1
  36. data/app/components/avo/fields/edit_component.rb +7 -0
  37. data/app/components/avo/fields/external_image_field/edit_component.html.erb +5 -2
  38. data/app/components/avo/fields/external_image_field/index_component.html.erb +6 -4
  39. data/app/components/avo/fields/external_image_field/show_component.html.erb +1 -1
  40. data/app/components/avo/fields/file_field/edit_component.html.erb +6 -1
  41. data/app/components/avo/fields/file_field/index_component.html.erb +1 -1
  42. data/app/components/avo/fields/file_field/show_component.html.erb +1 -1
  43. data/app/components/avo/fields/files_field/edit_component.html.erb +7 -1
  44. data/app/components/avo/fields/files_field/index_component.html.erb +1 -1
  45. data/app/components/avo/fields/files_field/show_component.html.erb +1 -1
  46. data/app/components/avo/fields/gravatar_field/index_component.html.erb +1 -1
  47. data/app/components/avo/fields/gravatar_field/show_component.html.erb +1 -1
  48. data/app/components/avo/fields/has_one_field/index_component.html.erb +1 -1
  49. data/app/components/avo/fields/hidden_field/edit_component.html.erb +5 -1
  50. data/app/components/avo/fields/id_field/edit_component.html.erb +1 -1
  51. data/app/components/avo/fields/id_field/index_component.html.erb +1 -1
  52. data/app/components/avo/fields/id_field/show_component.html.erb +1 -1
  53. data/app/components/avo/fields/index_component.rb +3 -0
  54. data/app/components/avo/fields/key_value_field/edit_component.html.erb +1 -1
  55. data/app/components/avo/fields/key_value_field/show_component.html.erb +1 -1
  56. data/app/components/avo/fields/markdown_field/edit_component.html.erb +8 -5
  57. data/app/components/avo/fields/markdown_field/show_component.html.erb +1 -1
  58. data/app/components/avo/fields/number_field/edit_component.html.erb +7 -4
  59. data/app/components/avo/fields/number_field/index_component.html.erb +1 -1
  60. data/app/components/avo/fields/number_field/show_component.html.erb +1 -1
  61. data/app/components/avo/fields/password_field/edit_component.html.erb +4 -2
  62. data/app/components/avo/fields/progress_bar_field/edit_component.html.erb +7 -4
  63. data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -1
  64. data/app/components/avo/fields/progress_bar_field/show_component.html.erb +1 -1
  65. data/app/components/avo/fields/select_field/edit_component.html.erb +9 -3
  66. data/app/components/avo/fields/select_field/index_component.html.erb +1 -1
  67. data/app/components/avo/fields/select_field/show_component.html.erb +1 -1
  68. data/app/components/avo/fields/show_component.rb +3 -0
  69. data/app/components/avo/fields/status_field/edit_component.html.erb +6 -3
  70. data/app/components/avo/fields/status_field/index_component.html.erb +1 -1
  71. data/app/components/avo/fields/status_field/show_component.html.erb +1 -1
  72. data/app/components/avo/fields/tags_field/edit_component.html.erb +19 -11
  73. data/app/components/avo/fields/tags_field/index_component.html.erb +1 -1
  74. data/app/components/avo/fields/tags_field/show_component.html.erb +1 -1
  75. data/app/components/avo/fields/text_field/edit_component.html.erb +5 -2
  76. data/app/components/avo/fields/text_field/index_component.html.erb +1 -1
  77. data/app/components/avo/fields/text_field/show_component.html.erb +1 -1
  78. data/app/components/avo/fields/textarea_field/edit_component.html.erb +6 -3
  79. data/app/components/avo/fields/textarea_field/show_component.html.erb +1 -1
  80. data/app/components/avo/fields/trix_field/edit_component.html.erb +13 -4
  81. data/app/components/avo/fields/trix_field/edit_component.rb +3 -0
  82. data/app/components/avo/fields/trix_field/show_component.html.erb +1 -1
  83. data/app/components/avo/index/field_wrapper_component.html.erb +12 -5
  84. data/app/components/avo/index/field_wrapper_component.rb +27 -3
  85. data/app/components/avo/panel_component.rb +4 -3
  86. data/app/components/avo/resource_component.rb +1 -0
  87. data/app/components/avo/show/field_wrapper_component.html.erb +1 -1
  88. data/app/components/avo/show/field_wrapper_component.rb +2 -1
  89. data/app/components/avo/views/resource_edit_component.html.erb +7 -4
  90. data/app/components/avo/views/resource_edit_component.rb +2 -1
  91. data/app/components/avo/views/resource_index_component.html.erb +6 -3
  92. data/app/components/avo/views/resource_index_component.rb +7 -1
  93. data/app/components/avo/views/resource_new_component.html.erb +7 -2
  94. data/app/components/avo/views/resource_new_component.rb +2 -1
  95. data/app/components/avo/views/resource_show_component.html.erb +9 -7
  96. data/app/components/avo/views/resource_show_component.rb +1 -0
  97. data/app/controllers/avo/actions_controller.rb +4 -1
  98. data/app/controllers/avo/base_controller.rb +14 -8
  99. data/app/controllers/avo/search_controller.rb +7 -1
  100. data/app/javascript/js/controllers/custom/course_resource_controller.js +102 -0
  101. data/app/javascript/js/controllers/fields/code_field_controller.js +7 -1
  102. data/app/javascript/js/controllers/fields/key_value_controller.js +1 -0
  103. data/app/javascript/js/controllers/fields/tags_field_controller.js +0 -1
  104. data/app/javascript/js/controllers/menu_controller.js +4 -3
  105. data/app/javascript/js/controllers/resource_edit_controller.js +72 -0
  106. data/app/javascript/js/controllers/resource_index_controller.js +4 -0
  107. data/app/javascript/js/controllers/resource_show_controller.js +4 -0
  108. data/app/javascript/js/controllers/search_controller.js +28 -5
  109. data/app/javascript/js/controllers.js +10 -0
  110. data/app/views/avo/associations/new.html.erb +2 -1
  111. data/app/views/avo/base/_select_filter.html.erb +1 -1
  112. data/app/views/avo/base/_text_filter.html.erb +1 -0
  113. data/app/views/avo/partials/_logo.html.erb +3 -2
  114. data/app/views/avo/partials/_navbar.html.erb +1 -1
  115. data/config/routes.rb +1 -1
  116. data/db/factories.rb +1 -0
  117. data/lib/avo/base_action.rb +9 -2
  118. data/lib/avo/base_card.rb +0 -23
  119. data/lib/avo/base_resource.rb +17 -15
  120. data/lib/avo/concerns/has_fields.rb +93 -0
  121. data/lib/avo/concerns/has_html_attributes.rb +110 -0
  122. data/lib/avo/concerns/has_stimulus_controllers.rb +42 -0
  123. data/lib/avo/fields/base_field.rb +23 -13
  124. data/lib/avo/fields/select_field.rb +1 -1
  125. data/lib/avo/grid_collector.rb +4 -4
  126. data/lib/avo/html/builder.rb +117 -0
  127. data/lib/avo/licensing/pro_license.rb +1 -0
  128. data/lib/avo/version.rb +1 -1
  129. data/lib/avo.rb +4 -0
  130. data/lib/generators/avo/templates/cards/chartkick_card_sample.tt +11 -1
  131. data/lib/generators/avo/templates/cards/metric_card_sample.tt +11 -1
  132. data/lib/generators/avo/templates/field/components/edit_component.html.erb.tt +1 -1
  133. data/lib/generators/avo/templates/field/components/index_component.html.erb.tt +1 -1
  134. data/lib/generators/avo/templates/field/components/show_component.html.erb.tt +1 -1
  135. data/lib/generators/avo/templates/initializer/avo.tt +1 -1
  136. data/lib/generators/avo/templates/locales/avo.en.yml +3 -3
  137. data/public/avo-assets/avo.css +28 -8
  138. data/public/avo-assets/avo.js +148 -148
  139. data/public/avo-assets/avo.js.map +3 -3
  140. data/public/avo-assets/logomark.png +0 -0
  141. metadata +13 -10
  142. data/app/assets/builds/action_cable.js +0 -2
  143. data/app/assets/builds/action_cable.js.map +0 -7
  144. data/app/assets/builds/avo.css +0 -9610
  145. data/app/assets/builds/avo.js +0 -512
  146. data/app/assets/builds/avo.js.map +0 -7
  147. data/lib/avo/fields_collector.rb +0 -70
@@ -1,6 +1,9 @@
1
- <div data-model-id="<%= @resource.model.id %>"
2
- class="space-y-8"
3
- >
1
+ <%= content_tag :div,
2
+ class: "space-y-12",
3
+ data: {
4
+ 'model-id': @resource.model.id,
5
+ **@resource.stimulus_data_attributes
6
+ } do %>
4
7
  <% @resource.panels.each do |resource_panel| %>
5
8
  <%= form_with model: @resource.model,
6
9
  scope: @resource.form_scope,
@@ -70,4 +73,4 @@
70
73
  <% end %>
71
74
  <% end %>
72
75
  <% end %>
73
- </div>
76
+ <% end %>
@@ -6,6 +6,7 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
6
6
 
7
7
  def initialize(resource: nil)
8
8
  @resource = resource
9
+ @view = :edit
9
10
 
10
11
  split_panel_fields
11
12
  end
@@ -29,6 +30,6 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
29
30
  private
30
31
 
31
32
  def via_index?
32
- params[:via_view] == 'index'
33
+ params[:via_view] == "index"
33
34
  end
34
35
  end
@@ -1,4 +1,7 @@
1
- <div>
1
+ <%= content_tag :div,
2
+ data: {
3
+ **@resource.stimulus_data_attributes
4
+ } do %>
2
5
  <%= render Avo::PanelComponent.new(title: title, description: description, data: { component: 'resources-index' }, display_breadcrumbs: @reflection.blank?) do |c| %>
3
6
  <% c.tools do %>
4
7
  <% if can_attach? %>
@@ -31,7 +34,7 @@
31
34
  >
32
35
  <% if @resource.search_query.present? %>
33
36
  <div class="flex items-center px-4 w-64">
34
- <%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.model_key} %>
37
+ <%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.model_name.collection} %>
35
38
  </div>
36
39
  <% else %>
37
40
  <%# Offset for the space-y-2 property when the serach is missing %>
@@ -72,4 +75,4 @@
72
75
  <% end %>
73
76
  <% end %>
74
77
  <% end %>
75
- </div>
78
+ <% end %>
@@ -28,11 +28,12 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
28
28
  @turbo_frame = turbo_frame
29
29
  @parent_model = parent_model
30
30
  @applied_filters = applied_filters
31
+ @view = :index
31
32
  end
32
33
 
33
34
  def title
34
35
  if @reflection.present?
35
- return field.plural_name if field.present?
36
+ return name if field.present?
36
37
 
37
38
  reflection_resource.plural_name
38
39
  else
@@ -143,4 +144,9 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
143
144
  def reflection_model_class
144
145
  @reflection.active_record.to_s
145
146
  end
147
+
148
+ def name
149
+ field.custom_name? ? field.name : field.plural_name
150
+ end
151
+
146
152
  end
@@ -1,4 +1,9 @@
1
- <div>
1
+ <%= content_tag :div,
2
+ class: "space-y-12",
3
+ data: {
4
+ 'model-id': @resource.model.id,
5
+ **@resource.stimulus_data_attributes
6
+ } do %>
2
7
  <% @resource.panels.each do |resource_panel| %>
3
8
  <%= form_with model: @resource.model,
4
9
  scope: @resource.form_scope,
@@ -52,4 +57,4 @@
52
57
  <% end %>
53
58
  <% end %>
54
59
  <% end %>
55
- </div>
60
+ <% end %>
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Avo::Views::ResourceNewComponent < ViewComponent::Base
3
+ class Avo::Views::ResourceNewComponent < Avo::ResourceComponent
4
4
  include Avo::ResourcesHelper
5
5
  include Avo::ApplicationHelper
6
6
 
@@ -10,6 +10,7 @@ class Avo::Views::ResourceNewComponent < ViewComponent::Base
10
10
  )
11
11
  @resource = resource
12
12
  @model = model
13
+ @view = :new
13
14
  end
14
15
 
15
16
  def back_path
@@ -1,8 +1,11 @@
1
- <div data-model-id="<%= @resource.model.id %>"
2
- data-selected-resources-name="<%= @resource.model_key %>"
3
- data-selected-resources='["<%= @resource.model.id %>"]'
4
- class="space-y-12"
5
- >
1
+ <%= content_tag :div,
2
+ class: "space-y-12",
3
+ data: {
4
+ 'model-id': @resource.model.id,
5
+ selected_resources_name: @resource.model_key,
6
+ selected_resources: [@resource.model.id],
7
+ **@resource.stimulus_data_attributes
8
+ } do %>
6
9
  <% @resource.panels.each_with_index do |resource_panel, index| %>
7
10
  <%= render Avo::PanelComponent.new(title: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: index) do |c| %>
8
11
  <% c.tools do %>
@@ -99,7 +102,6 @@
99
102
  <% end %>
100
103
  <% end %>
101
104
  <% end %>
102
-
103
105
  <% if should_display_invalid_fields_errors? %>
104
106
  <turbo-stream action="append" target="alerts">
105
107
  <template>
@@ -109,4 +111,4 @@
109
111
  </template>
110
112
  </turbo-stream>
111
113
  <% end %>
112
- </div>
114
+ <% end %>
@@ -9,6 +9,7 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
9
9
  @reflection = reflection
10
10
  @resource_panel = resource_panel
11
11
  @actions = actions
12
+ @view = :show
12
13
 
13
14
  split_panel_fields
14
15
  end
@@ -85,7 +85,10 @@ module Avo
85
85
 
86
86
  return [default_message] if response[:messages].blank?
87
87
 
88
- response[:messages]
88
+ response[:messages].select do |message|
89
+ # Remove the silent placeholder messages
90
+ message[:type] != :silent
91
+ end
89
92
  end
90
93
  end
91
94
  end
@@ -75,9 +75,9 @@ module Avo
75
75
  end
76
76
 
77
77
  def show
78
- set_actions
79
-
80
78
  @resource.hydrate(model: @model, view: :show, user: _current_user, params: params)
79
+
80
+ set_actions
81
81
 
82
82
  @page_title = @resource.default_panel_name.to_s
83
83
 
@@ -102,7 +102,17 @@ module Avo
102
102
  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
103
103
 
104
104
  @page_title = @resource.default_panel_name.to_s
105
- add_breadcrumb @resource.plural_name.humanize, resources_path(resource: @resource)
105
+
106
+ if params[:via_relation_class].present? && params[:via_resource_id].present?
107
+ via_resource = Avo::App.get_resource_by_model_name params[:via_relation_class]
108
+ via_model = via_resource.class.find_scope.find params[:via_resource_id]
109
+ via_resource.hydrate model: via_model
110
+
111
+ add_breadcrumb via_resource.plural_name, resources_path(resource: via_resource)
112
+ add_breadcrumb via_resource.model_title, resource_path(model: via_model, resource: via_resource)
113
+ end
114
+
115
+ add_breadcrumb @resource.plural_name.humanize
106
116
  add_breadcrumb t("avo.new").humanize
107
117
  end
108
118
 
@@ -302,14 +312,10 @@ module Avo
302
312
  end
303
313
 
304
314
  def set_actions
305
- if params[:resource_id].present?
306
- model = @resource.class.find_scope.find params[:resource_id]
307
- end
308
-
309
315
  @actions = @resource
310
316
  .get_actions
311
317
  .map do |action|
312
- action.new(model: model, resource: @resource, view: @view)
318
+ action.new(model: @model, resource: @resource, view: @view)
313
319
  end
314
320
  .select { |action| action.visible_in_view }
315
321
  end
@@ -51,8 +51,14 @@ module Avo
51
51
 
52
52
  results = apply_search_metadata(query, resource)
53
53
 
54
+ header = resource.plural_name
55
+
56
+ if results.length > 0
57
+ header += " (#{results.length})"
58
+ end
59
+
54
60
  result_object = {
55
- header: resource.name.pluralize,
61
+ header: header,
56
62
  help: resource.class.search_query_help,
57
63
  results: results,
58
64
  count: results.length
@@ -0,0 +1,102 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ const LOADER_CLASSES = 'absolute bg-gray-100 opacity-10 w-full h-full'
4
+
5
+ export default class extends Controller {
6
+ static targets = ['countrySelectInput', 'citySelectInput', 'citySelectWrapper'];
7
+
8
+ static values = {
9
+ view: String,
10
+ }
11
+
12
+ // Te fields initial value
13
+ static initialValue
14
+
15
+ get placeholder() {
16
+ return this.citySelectInputTarget.ariaPlaceholder
17
+ }
18
+
19
+ set loading(isLoading) {
20
+ if (isLoading) {
21
+ // create a loader overlay
22
+ const loadingDiv = document.createElement('div')
23
+ loadingDiv.className = LOADER_CLASSES
24
+ loadingDiv.dataset.target = 'city-loader'
25
+
26
+ // add the loader overlay
27
+ this.citySelectWrapperTarget.prepend(loadingDiv)
28
+ this.citySelectWrapperTarget.classList.add('opacity-50')
29
+ } else {
30
+ // remove the loader overlay
31
+ this.citySelectWrapperTarget.querySelector('[data-target="city-loader"]').remove()
32
+ this.citySelectWrapperTarget.classList.remove('opacity-50')
33
+ }
34
+ }
35
+
36
+ async connect() {
37
+ // Add the controller functionality only on forms
38
+ if (['edit', 'new'].includes(this.viewValue)) {
39
+ this.captureTheInitialValue()
40
+
41
+ // Trigger the change on load
42
+ await this.onCountryChange()
43
+ }
44
+ }
45
+
46
+ // Read the country select.
47
+ // If there's any value selected show the cities and prefill them.
48
+ async onCountryChange() {
49
+ if (this.hasCountrySelectInputTarget && this.countrySelectInputTarget) {
50
+ // Get the country
51
+ const country = this.countrySelectInputTarget.value
52
+ // Dynamically fetch the cities for this country
53
+ const cities = await this.fetchCitiesForCountry(country)
54
+
55
+ // Clear the select of options
56
+ Object.keys(this.citySelectInputTarget.options).forEach(() => {
57
+ this.citySelectInputTarget.options.remove(0)
58
+ })
59
+
60
+ // Add blank option
61
+ this.citySelectInputTarget.add(new Option(this.placeholder))
62
+
63
+ // Add the new cities
64
+ cities.forEach((city) => {
65
+ this.citySelectInputTarget.add(new Option(city, city))
66
+ })
67
+
68
+ // Check if the initial value is present in the cities array and select it.
69
+ // If not, select the first item
70
+ const currentOptions = Array.from(this.citySelectInputTarget.options).map((item) => item.value)
71
+ if (currentOptions.includes(this.initialValue)) {
72
+ this.citySelectInputTarget.value = this.initialValue
73
+ } else {
74
+ // Select the first item
75
+ this.citySelectInputTarget.value = this.citySelectInputTarget.options[0].value
76
+ }
77
+ }
78
+ }
79
+
80
+ // Private
81
+
82
+ captureTheInitialValue() {
83
+ this.initialValue = this.citySelectInputTarget.value
84
+ }
85
+
86
+ async fetchCitiesForCountry(country) {
87
+ if (!country) {
88
+ return []
89
+ }
90
+
91
+ this.loading = true
92
+
93
+ const response = await fetch(
94
+ `${window.Avo.configuration.root_path}/resources/courses/cities?country=${country}`,
95
+ )
96
+ const data = await response.json()
97
+
98
+ this.loading = false
99
+
100
+ return data
101
+ }
102
+ }
@@ -32,8 +32,14 @@ export default class extends Controller {
32
32
  lineNumbers: true,
33
33
  }
34
34
 
35
+ const vm = this
36
+
35
37
  setTimeout(() => {
36
- CodeMirror.fromTextArea(this.elementTarget, options)
38
+ CodeMirror.fromTextArea(this.elementTarget, options).on('change', (cm) => {
39
+ // Add this innerText change and dispatch an event to allow stimulus to pick up the input event.
40
+ vm.elementTarget.innerText = cm.getValue()
41
+ vm.elementTarget.dispatchEvent(new Event('input'))
42
+ })
37
43
  }, 1)
38
44
  }
39
45
  }
@@ -72,6 +72,7 @@ export default class extends Controller {
72
72
  result = Object.assign(...this.fieldValue.map(([key, val]) => ({ [key]: val })))
73
73
  }
74
74
  this.inputTarget.innerText = JSON.stringify(result)
75
+ this.inputTarget.dispatchEvent(new Event('input'))
75
76
  }
76
77
 
77
78
  updateKeyValueComponent() {
@@ -40,7 +40,6 @@ export default class extends BaseController {
40
40
  blacklist: this.disallowedItems,
41
41
  enforceWhitelist: this.enforceSuggestions,
42
42
  delimiters: this.delimiters.join('|'),
43
- maxTags: 10,
44
43
  dropdown: {
45
44
  maxItems: 20,
46
45
  enabled: 0,
@@ -22,10 +22,11 @@ export default class extends Controller {
22
22
  }
23
23
 
24
24
  get initiallyCollapsed() {
25
- if (this.userState === 'collapsed' || this.defaultState === 'collapsed') return true
26
- if (this.userState === 'expanded' || this.defaultState === 'expanded') return false
25
+ if (this.defaultState === 'collapsed') {
26
+ return this.userState === 'collapsed'
27
+ }
27
28
 
28
- return false
29
+ return this.userState === 'collapsed'
29
30
  }
30
31
 
31
32
  connect() {
@@ -0,0 +1,72 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import camelCase from 'lodash/camelCase'
3
+
4
+ export default class extends Controller {
5
+ static targets = []
6
+
7
+ static values = {
8
+ view: String,
9
+ }
10
+
11
+ debugOnInput(e) {
12
+ // eslint-disable-next-line no-console
13
+ console.log('onInput', e, e.target.value)
14
+ }
15
+
16
+ toggle({ params }) {
17
+ const { toggleTarget, toggleTargets } = params
18
+
19
+ if (toggleTarget) {
20
+ this.toggleAvoTarget(toggleTarget)
21
+ }
22
+
23
+ if (toggleTargets && toggleTargets.length > 0) {
24
+ toggleTargets.forEach(this.toggleAvoTarget.bind(this))
25
+ }
26
+ }
27
+
28
+ disable({ params }) {
29
+ const { disableTarget, disableTargets } = params
30
+
31
+ if (disableTarget) {
32
+ this.disableAvoTarget(disableTarget)
33
+ }
34
+
35
+ if (disableTargets && disableTargets.length > 0) {
36
+ disableTargets.forEach(this.disableAvoTarget.bind(this))
37
+ }
38
+ }
39
+
40
+ // Private
41
+
42
+ toggleAvoTarget(targetName) {
43
+ // compose the default wrapper data value
44
+ const target = camelCase(targetName)
45
+ const element = document.querySelector(`[data-resource-edit-target="${target}"]`)
46
+
47
+ if (element) {
48
+ element.classList.toggle('hidden')
49
+ }
50
+ }
51
+
52
+ disableAvoTarget(targetName) {
53
+ // compose the default wrapper data value
54
+ const target = camelCase(targetName)
55
+
56
+ // find & disable direct selector
57
+ document.querySelectorAll(`[data-resource-edit-target="${target}"]`).forEach(this.toggleItemDisabled)
58
+
59
+ // find & disable inputs
60
+ document.querySelectorAll(`[data-resource-edit-target="${target}"] input`).forEach(this.toggleItemDisabled)
61
+
62
+ // find & disable select fields
63
+ document.querySelectorAll(`[data-resource-edit-target="${target}"] select`).forEach(this.toggleItemDisabled)
64
+
65
+ // find & disable buttons for belongs_to
66
+ document.querySelectorAll(`[data-resource-edit-target="${target}"] [data-slot="value"] button`).forEach(this.toggleItemDisabled)
67
+ }
68
+
69
+ toggleItemDisabled(item) {
70
+ item.disabled = !item.disabled
71
+ }
72
+ }
@@ -0,0 +1,4 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ }
@@ -0,0 +1,4 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ }
@@ -55,7 +55,10 @@ export default class extends Controller {
55
55
  searchUrl(query) {
56
56
  const url = URI()
57
57
 
58
- let params = { q: query }
58
+ return url.segment(this.searchSegments()).search(this.searchParams(query)).toString()
59
+ }
60
+
61
+ searchSegments() {
59
62
  let segments = [
60
63
  window.Avo.configuration.root_path,
61
64
  'avo_api',
@@ -67,6 +70,19 @@ export default class extends Controller {
67
70
  segments = [window.Avo.configuration.root_path, 'avo_api', 'search']
68
71
  }
69
72
 
73
+ return segments
74
+ }
75
+
76
+ searchParams(query) {
77
+ let params = {
78
+ q: query,
79
+ global: false,
80
+ }
81
+
82
+ if (this.isGlobalSearch) {
83
+ params.global = true
84
+ }
85
+
70
86
  if (this.isBelongsToSearch) {
71
87
  params = {
72
88
  ...params,
@@ -85,13 +101,13 @@ export default class extends Controller {
85
101
  }
86
102
  }
87
103
 
88
- return url.segment(segments).search(params).toString()
104
+ return params
89
105
  }
90
106
 
91
107
  handleOnSelect({ item }) {
92
108
  if (this.isBelongsToSearch) {
93
- this.hiddenIdTarget.setAttribute('value', item._id)
94
- this.buttonTarget.setAttribute('value', item._label)
109
+ this.updateFieldAttribute(this.hiddenIdTarget, 'value', item._id)
110
+ this.updateFieldAttribute(this.buttonTarget, 'value', item._label)
95
111
 
96
112
  document.querySelector('.aa-DetachedOverlay').remove()
97
113
 
@@ -180,7 +196,7 @@ export default class extends Controller {
180
196
  }
181
197
 
182
198
  clearValue() {
183
- this.clearValueTargets.map((e) => e.setAttribute('value', ''))
199
+ this.clearValueTargets.map((t) => this.updateFieldAttribute(t, 'value', ''))
184
200
  this.clearButtonTarget.classList.add('hidden')
185
201
  }
186
202
 
@@ -229,4 +245,11 @@ export default class extends Controller {
229
245
  this.buttonTarget.removeAttribute('disabled')
230
246
  }
231
247
  }
248
+
249
+ // Private
250
+
251
+ updateFieldAttribute(target, attribute, value) {
252
+ target.setAttribute(attribute, value)
253
+ target.dispatchEvent(new Event('input'))
254
+ }
232
255
  }
@@ -7,6 +7,7 @@ import BelongsToFieldController from './controllers/fields/belongs_to_field_cont
7
7
  import BooleanFilterController from './controllers/boolean_filter_controller'
8
8
  import CodeFieldController from './controllers/fields/code_field_controller'
9
9
  import CopyToClipboardController from './controllers/copy_to_clipboard_controller'
10
+ import CourseResourceController from './controllers/custom/course_resource_controller'
10
11
  import DashboardCardController from './controllers/dashboard_card_controller'
11
12
  import DateFieldController from './controllers/fields/date_field_controller'
12
13
  import FilterController from './controllers/filter_controller'
@@ -20,6 +21,9 @@ import MobileController from './controllers/mobile_controller'
20
21
  import ModalController from './controllers/modal_controller'
21
22
  import MultipleSelectFilterController from './controllers/multiple_select_filter_controller'
22
23
  import PerPageController from './controllers/per_page_controller'
24
+ import ResourceEditController from './controllers/resource_edit_controller'
25
+ import ResourceIndexController from './controllers/resource_index_controller'
26
+ import ResourceShowController from './controllers/resource_show_controller'
23
27
  import SearchController from './controllers/search_controller'
24
28
  import SelectController from './controllers/select_controller'
25
29
  import SelectFilterController from './controllers/select_filter_controller'
@@ -46,6 +50,9 @@ application.register('mobile', MobileController)
46
50
  application.register('modal', ModalController)
47
51
  application.register('multiple-select-filter', MultipleSelectFilterController)
48
52
  application.register('per-page', PerPageController)
53
+ application.register('resource-edit', ResourceEditController)
54
+ application.register('resource-index', ResourceIndexController)
55
+ application.register('resource-show', ResourceShowController)
49
56
  application.register('search', SearchController)
50
57
  application.register('select', SelectController)
51
58
  application.register('select-filter', SelectFilterController)
@@ -61,3 +68,6 @@ application.register('date-field', DateFieldController)
61
68
  application.register('key-value', KeyValueController)
62
69
  application.register('simple-mde', SimpleMdeController)
63
70
  application.register('trix-field', TrixFieldController)
71
+
72
+ // Custom controllers
73
+ application.register('course-resource', CourseResourceController)
@@ -16,7 +16,8 @@
16
16
  field: @field,
17
17
  model_key: @field.target_resource&.model_key,
18
18
  foreign_key: 'related_id',
19
- resource: @resource
19
+ resource: @resource,
20
+ view: :new
20
21
  %>
21
22
  <% else %>
22
23
  <div class="flex-1 flex flex-col items-center justify-center px-0 md:px-24 text-base">
@@ -6,7 +6,7 @@
6
6
  <%= filter_wrapper name: filter.name do %>
7
7
  <%= select_tag filter.id, options_for_select(filter.options.invert, filter.applied_or_default_value(@applied_filters)),
8
8
  class: input_classes('w-full mb-0'),
9
- include_blank: '—',
9
+ include_blank: filter.class.empty_message || '—',
10
10
  id: "avo_filters_#{filter.id.parameterize.underscore}",
11
11
  'data-filter-class': filter.class,
12
12
  'data-select-filter-target': 'selector',
@@ -7,6 +7,7 @@
7
7
  <%= text_field_tag filter.id, filter.applied_or_default_value(@applied_filters),
8
8
  class: input_classes('w-full mb-0'),
9
9
  id: "avo_filters_#{filter.id.parameterize.underscore}",
10
+ placeholder: filter.class.empty_message,
10
11
  'data-filter-class': filter.class.to_s,
11
12
  'data-text-filter-target': 'text',
12
13
  'data-action': 'keypress->text-filter#tryToSubmit'
@@ -1,3 +1,4 @@
1
- <%= link_to root_path, class: 'logo-placeholder h-10 bg-white flex justify-start' do %>
2
- <%= image_tag '/avo-assets/logo.png', class: 'h-full', title: 'Avo' %>
1
+ <%= link_to root_path, class: 'logo-placeholder h-full w-full flex justify-start' do %>
2
+ <%= image_tag '/avo-assets/logo.png', class: 'hidden sm:block object-contain', title: 'Avo' %>
3
+ <%= image_tag '/avo-assets/logomark.png', class: 'sm:hidden object-contain', title: 'Avo' %>
3
4
  <% end %>
@@ -2,7 +2,7 @@
2
2
  class="fixed bg-white p-2 w-full flex flex-shrink-0 items-center z-50 px-4 lg:px-4 border-b space-x-4 lg:space-x-0 h-16 <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
3
3
  v-if="layout !== 'blank'"
4
4
  >
5
- <div class="flex items-center space-x-2 lg:space-x-0 w-32 sm:w-64 flex-shrink-0">
5
+ <div class="flex items-center space-x-2 lg:space-x-0 w-auto lg:w-64 flex-shrink-0 h-full">
6
6
  <%= a_button class: 'lg:hidden', icon: 'menu', size: :xs, compact: true, style: :text, data: { action: 'click->mobile#toggleSidebar' } %>
7
7
  <%= render partial: "avo/partials/logo" %>
8
8
  </div>
data/config/routes.rb CHANGED
@@ -48,7 +48,7 @@ Avo::Engine.routes.draw do
48
48
  post "/debug/refresh_license", to: "debug#refresh_license"
49
49
  end
50
50
 
51
- if Rails.env.development? or Rails.env.staging?
51
+ if Rails.env.development? || Rails.env.staging?
52
52
  scope "/avo_private", as: "avo_private" do
53
53
  get "/design", to: "private#design"
54
54
  end
data/db/factories.rb CHANGED
@@ -72,5 +72,6 @@ FactoryBot.define do
72
72
 
73
73
  factory :course_link, class: "Course::Link" do
74
74
  link { Faker::Internet.url }
75
+ course { create :course }
75
76
  end
76
77
  end