avo 2.7.0 → 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 (174) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +6 -4
  4. data/README.md +11 -0
  5. data/app/assets/stylesheets/avo.css +4 -4
  6. data/app/assets/stylesheets/css/{components → fields}/code.css +0 -0
  7. data/app/assets/stylesheets/css/{components → fields}/progress.css +0 -0
  8. data/app/assets/stylesheets/css/{components → fields}/status.css +0 -0
  9. data/app/assets/stylesheets/css/fields/trix.css +17 -0
  10. data/app/assets/svgs/download-solid-reversed.svg +2 -2
  11. data/app/components/avo/actions_component.html.erb +5 -13
  12. data/app/components/avo/actions_component.rb +39 -1
  13. data/app/components/avo/alert_component.rb +6 -0
  14. data/app/components/avo/card_component.html.erb +2 -2
  15. data/app/components/avo/common_field_wrapper_component.html.erb +11 -4
  16. data/app/components/avo/common_field_wrapper_component.rb +27 -1
  17. data/app/components/avo/edit/field_wrapper_component.html.erb +1 -1
  18. data/app/components/avo/fields/badge_field/index_component.html.erb +1 -1
  19. data/app/components/avo/fields/badge_field/show_component.html.erb +1 -1
  20. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +21 -10
  21. data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +7 -1
  22. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +27 -15
  23. data/app/components/avo/fields/belongs_to_field/edit_component.rb +4 -0
  24. data/app/components/avo/fields/belongs_to_field/index_component.html.erb +1 -1
  25. data/app/components/avo/fields/belongs_to_field/show_component.html.erb +1 -1
  26. data/app/components/avo/fields/boolean_field/edit_component.html.erb +4 -2
  27. data/app/components/avo/fields/boolean_field/index_component.html.erb +1 -1
  28. data/app/components/avo/fields/boolean_field/show_component.html.erb +1 -1
  29. data/app/components/avo/fields/boolean_group_field/edit_component.html.erb +7 -1
  30. data/app/components/avo/fields/boolean_group_field/index_component.html.erb +1 -1
  31. data/app/components/avo/fields/boolean_group_field/show_component.html.erb +1 -1
  32. data/app/components/avo/fields/code_field/edit_component.html.erb +7 -5
  33. data/app/components/avo/fields/code_field/show_component.html.erb +2 -2
  34. data/app/components/avo/fields/common/key_value_component.html.erb +10 -4
  35. data/app/components/avo/fields/common/key_value_component.rb +2 -0
  36. data/app/components/avo/fields/country_field/edit_component.html.erb +4 -2
  37. data/app/components/avo/fields/country_field/index_component.html.erb +1 -1
  38. data/app/components/avo/fields/country_field/show_component.html.erb +1 -1
  39. data/app/components/avo/fields/date_field/edit_component.html.erb +6 -4
  40. data/app/components/avo/fields/date_field/index_component.html.erb +1 -1
  41. data/app/components/avo/fields/date_field/show_component.html.erb +1 -1
  42. data/app/components/avo/fields/date_time_field/edit_component.html.erb +6 -4
  43. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -1
  44. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -1
  45. data/app/components/avo/fields/edit_component.rb +7 -0
  46. data/app/components/avo/fields/external_image_field/edit_component.html.erb +5 -2
  47. data/app/components/avo/fields/external_image_field/index_component.html.erb +6 -4
  48. data/app/components/avo/fields/external_image_field/show_component.html.erb +1 -1
  49. data/app/components/avo/fields/file_field/edit_component.html.erb +6 -1
  50. data/app/components/avo/fields/file_field/index_component.html.erb +1 -1
  51. data/app/components/avo/fields/file_field/show_component.html.erb +1 -1
  52. data/app/components/avo/fields/files_field/edit_component.html.erb +7 -1
  53. data/app/components/avo/fields/files_field/index_component.html.erb +1 -1
  54. data/app/components/avo/fields/files_field/show_component.html.erb +1 -1
  55. data/app/components/avo/fields/gravatar_field/index_component.html.erb +1 -1
  56. data/app/components/avo/fields/gravatar_field/show_component.html.erb +1 -1
  57. data/app/components/avo/fields/has_one_field/index_component.html.erb +1 -1
  58. data/app/components/avo/fields/hidden_field/edit_component.html.erb +5 -1
  59. data/app/components/avo/fields/id_field/edit_component.html.erb +1 -1
  60. data/app/components/avo/fields/id_field/index_component.html.erb +1 -1
  61. data/app/components/avo/fields/id_field/show_component.html.erb +1 -1
  62. data/app/components/avo/fields/index_component.rb +3 -0
  63. data/app/components/avo/fields/key_value_field/edit_component.html.erb +1 -1
  64. data/app/components/avo/fields/key_value_field/show_component.html.erb +1 -1
  65. data/app/components/avo/fields/markdown_field/edit_component.html.erb +8 -5
  66. data/app/components/avo/fields/markdown_field/show_component.html.erb +1 -1
  67. data/app/components/avo/fields/number_field/edit_component.html.erb +7 -4
  68. data/app/components/avo/fields/number_field/index_component.html.erb +1 -1
  69. data/app/components/avo/fields/number_field/show_component.html.erb +1 -1
  70. data/app/components/avo/fields/password_field/edit_component.html.erb +4 -2
  71. data/app/components/avo/fields/progress_bar_field/edit_component.html.erb +7 -4
  72. data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -1
  73. data/app/components/avo/fields/progress_bar_field/show_component.html.erb +1 -1
  74. data/app/components/avo/fields/select_field/edit_component.html.erb +9 -3
  75. data/app/components/avo/fields/select_field/index_component.html.erb +1 -1
  76. data/app/components/avo/fields/select_field/show_component.html.erb +1 -1
  77. data/app/components/avo/fields/show_component.rb +3 -0
  78. data/app/components/avo/fields/status_field/edit_component.html.erb +6 -3
  79. data/app/components/avo/fields/status_field/index_component.html.erb +1 -1
  80. data/app/components/avo/fields/status_field/show_component.html.erb +1 -1
  81. data/app/components/avo/fields/tags_field/edit_component.html.erb +19 -11
  82. data/app/components/avo/fields/tags_field/index_component.html.erb +1 -1
  83. data/app/components/avo/fields/tags_field/show_component.html.erb +1 -1
  84. data/app/components/avo/fields/text_field/edit_component.html.erb +5 -2
  85. data/app/components/avo/fields/text_field/index_component.html.erb +1 -1
  86. data/app/components/avo/fields/text_field/show_component.html.erb +1 -1
  87. data/app/components/avo/fields/textarea_field/edit_component.html.erb +6 -3
  88. data/app/components/avo/fields/textarea_field/show_component.html.erb +1 -1
  89. data/app/components/avo/fields/trix_field/edit_component.html.erb +14 -4
  90. data/app/components/avo/fields/trix_field/edit_component.rb +3 -0
  91. data/app/components/avo/fields/trix_field/show_component.html.erb +2 -2
  92. data/app/components/avo/index/field_wrapper_component.html.erb +12 -5
  93. data/app/components/avo/index/field_wrapper_component.rb +27 -3
  94. data/app/components/avo/panel_component.rb +4 -3
  95. data/app/components/avo/resource_component.rb +1 -0
  96. data/app/components/avo/show/field_wrapper_component.html.erb +1 -1
  97. data/app/components/avo/show/field_wrapper_component.rb +2 -1
  98. data/app/components/avo/sidebar/item_switcher_component.html.erb +2 -2
  99. data/app/components/avo/views/resource_edit_component.html.erb +13 -8
  100. data/app/components/avo/views/resource_edit_component.rb +32 -3
  101. data/app/components/avo/views/resource_index_component.html.erb +7 -4
  102. data/app/components/avo/views/resource_index_component.rb +7 -1
  103. data/app/components/avo/views/resource_show_component.html.erb +11 -9
  104. data/app/components/avo/views/resource_show_component.rb +1 -0
  105. data/app/controllers/avo/actions_controller.rb +4 -1
  106. data/app/controllers/avo/base_controller.rb +24 -13
  107. data/app/controllers/avo/cards_controller.rb +25 -0
  108. data/app/controllers/avo/dashboards_controller.rb +2 -8
  109. data/app/controllers/avo/home_controller.rb +8 -1
  110. data/app/controllers/avo/search_controller.rb +7 -1
  111. data/app/helpers/avo/url_helpers.rb +8 -9
  112. data/app/javascript/js/controllers/fields/code_field_controller.js +7 -1
  113. data/app/javascript/js/controllers/fields/key_value_controller.js +1 -0
  114. data/app/javascript/js/controllers/fields/tags_field_controller.js +0 -1
  115. data/app/javascript/js/controllers/menu_controller.js +4 -3
  116. data/app/javascript/js/controllers/resource_edit_controller.js +72 -0
  117. data/app/javascript/js/controllers/resource_index_controller.js +4 -0
  118. data/app/javascript/js/controllers/resource_show_controller.js +4 -0
  119. data/app/javascript/js/controllers/search_controller.js +28 -5
  120. data/app/javascript/js/controllers.js +8 -0
  121. data/app/views/avo/associations/new.html.erb +2 -1
  122. data/app/views/avo/base/_select_filter.html.erb +1 -1
  123. data/app/views/avo/base/_text_filter.html.erb +1 -0
  124. data/app/views/avo/base/edit.html.erb +2 -1
  125. data/app/views/avo/base/new.html.erb +1 -1
  126. data/app/views/avo/{dashboards → cards}/_chartkick_card.html.erb +0 -0
  127. data/app/views/avo/{dashboards → cards}/_metric_card.html.erb +0 -0
  128. data/app/views/avo/{dashboards/card.html.erb → cards/show.html.erb} +0 -0
  129. data/app/views/avo/partials/_custom_tools_alert.html.erb +21 -7
  130. data/app/views/avo/partials/_logo.html.erb +3 -2
  131. data/app/views/avo/partials/_navbar.html.erb +1 -1
  132. data/app/views/avo/partials/_table_header.html.erb +9 -1
  133. data/bin/test +1 -0
  134. data/config/routes.rb +7 -4
  135. data/db/factories.rb +1 -0
  136. data/lib/avo/app.rb +18 -1
  137. data/lib/avo/base_action.rb +16 -4
  138. data/lib/avo/base_card.rb +0 -23
  139. data/lib/avo/base_resource.rb +23 -16
  140. data/lib/avo/concerns/fetches_things.rb +19 -12
  141. data/lib/avo/concerns/has_fields.rb +93 -0
  142. data/lib/avo/concerns/has_html_attributes.rb +110 -0
  143. data/lib/avo/concerns/has_model.rb +11 -0
  144. data/lib/avo/concerns/has_stimulus_controllers.rb +42 -0
  145. data/lib/avo/dynamic_router.rb +1 -1
  146. data/lib/avo/engine.rb +1 -3
  147. data/lib/avo/fields/base_field.rb +24 -13
  148. data/lib/avo/fields/concerns/is_required.rb +17 -0
  149. data/lib/avo/fields/select_field.rb +1 -1
  150. data/lib/avo/grid_collector.rb +4 -4
  151. data/lib/avo/hosts/view_record_host.rb +7 -0
  152. data/lib/avo/html/builder.rb +117 -0
  153. data/lib/avo/licensing/pro_license.rb +1 -0
  154. data/lib/avo/menu/base_item.rb +4 -0
  155. data/lib/avo/menu/dashboard.rb +5 -0
  156. data/lib/avo/menu/resource.rb +5 -0
  157. data/lib/avo/version.rb +1 -1
  158. data/lib/avo.rb +5 -0
  159. data/lib/generators/avo/install_generator.rb +1 -4
  160. data/lib/generators/avo/templates/cards/chartkick_card_sample.tt +11 -1
  161. data/lib/generators/avo/templates/cards/metric_card_sample.tt +11 -1
  162. data/lib/generators/avo/templates/field/components/edit_component.html.erb.tt +1 -1
  163. data/lib/generators/avo/templates/field/components/index_component.html.erb.tt +1 -1
  164. data/lib/generators/avo/templates/field/components/show_component.html.erb.tt +1 -1
  165. data/lib/generators/avo/templates/initializer/avo.tt +1 -1
  166. data/lib/generators/avo/templates/locales/avo.en.yml +3 -3
  167. data/public/avo-assets/avo.css +473 -1055
  168. data/public/avo-assets/avo.js +147 -147
  169. data/public/avo-assets/avo.js.map +3 -3
  170. data/public/avo-assets/logomark.png +0 -0
  171. metadata +21 -11
  172. data/app/components/avo/views/resource_new_component.html.erb +0 -55
  173. data/app/components/avo/views/resource_new_component.rb +0 -38
  174. data/lib/avo/fields_collector.rb +0 -70
@@ -11,13 +11,23 @@
11
11
  data-hide-attachment-url="<%= @field.hide_attachment_url %>"
12
12
  class="relative block overflow-x-auto max-w-full"
13
13
  >
14
- <% trix_id = "trix_#{@resource.name.underscore}_#{@field.id}" %>
15
- <trix-editor data-trix-field-target="editor" input="<%= trix_id %>" placeholder="<%= @field.placeholder %>"><%== @field.value %></trix-editor>
14
+ <%= content_tag 'trix-editor',
15
+ class: 'trix-content',
16
+ data: {
17
+ "trix-field-target": "editor",
18
+ **@field.get_html(:data, view: view, element: :input)
19
+ },
20
+ input: trix_id,
21
+ placeholder: @field.placeholder do %>
22
+ <%== @field.value %>
23
+ <% end %>
16
24
  <%= @form.text_area @field.id,
25
+ class: classes("w-full hidden"),
26
+ data: @field.get_html(:data, view: view, element: :input),
27
+ disabled: @field.readonly,
17
28
  id: trix_id,
18
- class: helpers.input_classes('w-full hidden', has_error: (@resource.model.present? and @resource.model.errors.include?(@field.id))),
19
29
  placeholder: @field.placeholder,
20
- disabled: @field.readonly
30
+ style: @field.get_html(:style, view: view, element: :input)
21
31
  %>
22
32
  </div>
23
33
  <% end %>
@@ -1,4 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Fields::TrixField::EditComponent < Avo::Fields::EditComponent
4
+ def trix_id
5
+ "trix_#{@resource.name.underscore}_#{@field.id}"
6
+ end
4
7
  end
@@ -1,6 +1,6 @@
1
- <%= show_field_wrapper field: @field, index: @index do %>
1
+ <%= show_field_wrapper field: @field, resource: @resource, index: @index do %>
2
2
  <%
3
- content_classes = 'prose prose-sm'
3
+ content_classes = 'trix-content py-2'
4
4
  content_classes << ' hidden' unless @field.always_show
5
5
  %>
6
6
  <div data-controller="hidden-input">
@@ -1,13 +1,20 @@
1
- <td class="min-h-[3rem] px-3 leading-tight whitespace-nowrap h-full text-slate-800 text-base <%= classes %>" data-field-id="<%= @field.id %>" data-field-type="<%= @field.type %>">
1
+ <%= content_tag :td,
2
+ class: "min-h-[3rem] px-3 leading-tight whitespace-nowrap h-full text-slate-800 text-base #{classes}",
3
+ data: {
4
+ field_id: @field.id,
5
+ field_type: @field.type,
6
+ **stimulus_attributes
7
+ },
8
+ style: style do %>
2
9
  <% if @field.value.blank? && @dash_if_blank %>
3
10
 
4
11
  <% else %>
5
12
  <% if @center_content %>
6
- <div class="flex items-center justify-center">
7
- <%= content %>
8
- </div>
13
+ <div class="flex items-center justify-center">
14
+ <%= content %>
15
+ </div>
9
16
  <% else %>
10
17
  <%= content %>
11
18
  <% end %>
12
19
  <% end %>
13
- </td>
20
+ <% end %>
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Index::FieldWrapperComponent < ViewComponent::Base
4
- def initialize(field: nil, dash_if_blank: true, center_content: false, flush: false, **args)
4
+ attr_reader :view
5
+
6
+ def initialize(field: nil, resource: nil, dash_if_blank: true, center_content: false, flush: false, **args)
5
7
  @field = field
8
+ @resource = resource
6
9
  @dash_if_blank = dash_if_blank
7
10
  @center_content = center_content
8
11
  @classes = args[:class].present? ? args[:class] : ""
9
12
  @args = args
10
13
  @flush = flush
14
+ @view = :index
11
15
  end
12
16
 
13
17
  def classes
@@ -18,18 +22,38 @@ class Avo::Index::FieldWrapperComponent < ViewComponent::Base
18
22
  end
19
23
 
20
24
  result += " #{text_align_classes}"
25
+ result += " #{@field.get_html(:classes, view: view, element: :wrapper)}"
21
26
 
22
27
  result
23
28
  end
24
29
 
30
+ def style
31
+ @field.get_html(:style, view: view, element: :wrapper)
32
+ end
33
+
34
+ def stimulus_attributes
35
+ attributes = {}
36
+
37
+ @resource.get_stimulus_controllers.split(" ").each do |controller|
38
+ attributes["#{controller}-target"] = "#{@field.id.to_s.underscore}_#{@field.type.to_s.underscore}_wrapper".camelize(:lower)
39
+ end
40
+
41
+ wrapper_data_attributes = @field.get_html :data, view: view, element: :wrapper
42
+ if wrapper_data_attributes.present?
43
+ attributes.merge! wrapper_data_attributes
44
+ end
45
+
46
+ attributes
47
+ end
48
+
25
49
  private
26
50
 
27
51
  def text_align_classes
28
52
  case @field.index_text_align.to_sym
29
53
  when :right
30
- 'text-right'
54
+ "text-right"
31
55
  when :center
32
- 'text-center'
56
+ "text-center"
33
57
  end
34
58
  end
35
59
  end
@@ -25,9 +25,10 @@ class Avo::PanelComponent < ViewComponent::Base
25
25
  end
26
26
 
27
27
  def data_attributes
28
- @data.merge({'panel-index': @index}).map do |key, value|
29
- " data-#{key}=\"#{value}\""
30
- end.join
28
+ @data.merge({"panel-index": @index})
29
+ .map do |key, value|
30
+ " data-#{key}=\"#{value}\""
31
+ end.join
31
32
  end
32
33
 
33
34
  def display_breadcrumbs?
@@ -4,6 +4,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
4
4
  attr_reader :has_many_panels
5
5
  attr_reader :has_as_belongs_to_many_panels
6
6
  attr_reader :resource_tools
7
+ attr_reader :view
7
8
 
8
9
  def can_create?
9
10
  return authorize_association_for(:create) if @reflection.present?
@@ -1,4 +1,4 @@
1
- <%= render Avo::CommonFieldWrapperComponent.new(field: @field, dash_if_blank: @dash_if_blank, full_width: @full_width) do %>
1
+ <%= render Avo::CommonFieldWrapperComponent.new(field: @field, resource: @resource, dash_if_blank: @dash_if_blank, full_width: @full_width, view: :show) do %>
2
2
  <div class="self-center <% if @full_width %> w-full <% else %> w-full md:w-8/12 <% end %>" data-slot="value">
3
3
  <% if @field.value.blank? and @dash_if_blank %>
4
4
 
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Show::FieldWrapperComponent < ViewComponent::Base
4
- def initialize(field: nil, dash_if_blank: true, full_width: false, **args)
4
+ def initialize(field: nil, resource: nil, dash_if_blank: true, full_width: false, **args)
5
5
  @field = field
6
+ @resource = resource
6
7
  @dash_if_blank = dash_if_blank
7
8
  @classes = args[:class].present? ? args[:class] : ""
8
9
  @args = args
@@ -2,10 +2,10 @@
2
2
  <%= render Avo::Sidebar::LinkComponent.new label: item.name, path: item.path, target: item.target %>
3
3
  <% end %>
4
4
  <% if item.is_a? Avo::Menu::Resource %>
5
- <%= render Avo::Sidebar::LinkComponent.new label: resource.navigation_label, path: helpers.resources_path(resource: resource) %>
5
+ <%= render Avo::Sidebar::LinkComponent.new label: item.navigation_label, path: helpers.resources_path(resource: resource) %>
6
6
  <% end %>
7
7
  <% if item.is_a? Avo::Menu::Dashboard %>
8
- <%= render Avo::Sidebar::LinkComponent.new label: dashboard.navigation_label, path: dashboard.navigation_path %>
8
+ <%= render Avo::Sidebar::LinkComponent.new label: item.navigation_label, path: dashboard.navigation_path %>
9
9
  <% end %>
10
10
  <% if item.is_a? Avo::Menu::Section %>
11
11
  <%= render Avo::Sidebar::SectionComponent.new item: item %>
@@ -1,21 +1,26 @@
1
- <div data-model-id="<%= @resource.model.id %>"
2
- class="space-y-8"
3
- >
4
- <% @resource.panels.each do |resource_panel| %>
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 %>
9
+ <% @resource.panels.each_with_index do |resource_panel, index| %>
5
10
  <%= form_with model: @resource.model,
6
11
  scope: @resource.form_scope,
7
- url: helpers.resource_path(model: @resource.model, resource: @resource),
8
- method: :put,
12
+ url: form_url,
9
13
  local: true,
10
14
  multipart: true do |form| %>
11
15
  <%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
12
- <%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true) do |c| %>
16
+ <%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true, index: index) do |c| %>
13
17
  <% c.tools do %>
14
18
  <%= a_link back_path,
15
19
  style: :text,
16
20
  icon: 'arrow-left' do %>
17
21
  <%= t('avo.cancel').capitalize %>
18
22
  <% end %>
23
+ <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
19
24
  <% if can_see_the_save_button? %>
20
25
  <%= a_button color: :primary,
21
26
  style: :primary,
@@ -70,4 +75,4 @@
70
75
  <% end %>
71
76
  <% end %>
72
77
  <% end %>
73
- </div>
78
+ <% end %>
@@ -4,8 +4,13 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
4
4
  include Avo::ResourcesHelper
5
5
  include Avo::ApplicationHelper
6
6
 
7
- def initialize(resource: nil)
7
+ attr_reader :view
8
+
9
+ def initialize(resource: nil, model: nil, actions: [], view: :edit)
8
10
  @resource = resource
11
+ @model = model
12
+ @actions = actions
13
+ @view = view
9
14
 
10
15
  split_panel_fields
11
16
  end
@@ -16,7 +21,11 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
16
21
  elsif via_index?
17
22
  helpers.resources_path(resource: @resource)
18
23
  else # via resource show page
19
- helpers.resource_path(model: @resource.model, resource: @resource)
24
+ if is_edit?
25
+ helpers.resource_path(model: @resource.model, resource: @resource)
26
+ else
27
+ helpers.resources_path(resource: @resource)
28
+ end
20
29
  end
21
30
  end
22
31
 
@@ -29,6 +38,26 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
29
38
  private
30
39
 
31
40
  def via_index?
32
- params[:via_view] == 'index'
41
+ params[:via_view] == "index"
42
+ end
43
+
44
+ def is_edit?
45
+ view == :edit
46
+ end
47
+
48
+ def form_url
49
+ if is_edit?
50
+ helpers.resource_path(
51
+ model: @resource.model,
52
+ resource: @resource
53
+ )
54
+ else
55
+ helpers.resources_path(
56
+ resource: @resource,
57
+ via_relation_class: params[:via_relation_class],
58
+ via_relation: params[:via_relation],
59
+ via_resource_id: params[:via_resource_id]
60
+ )
61
+ end
33
62
  end
34
63
  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? %>
@@ -12,7 +15,7 @@
12
15
  <% end %>
13
16
  <% end %>
14
17
  <% if can_see_the_actions_button? %>
15
- <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
18
+ <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
16
19
  <% end %>
17
20
  <% if can_see_the_create_button? %>
18
21
  <%= a_link create_path,
@@ -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,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 %>
@@ -19,7 +22,7 @@
19
22
  } do %>
20
23
  <%= t('avo.detach_item', item: title).capitalize %>
21
24
  <% end %>
22
- <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
25
+ <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
23
26
  <% end %>
24
27
  <% if can_see_the_edit_button? %>
25
28
  <%= a_link edit_path,
@@ -54,7 +57,7 @@
54
57
  <%= t('avo.delete').capitalize %>
55
58
  <% end %>
56
59
  <% end %>
57
- <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
60
+ <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
58
61
  <% if @resource.authorization.authorize_action(:edit, raise_exception: false) %>
59
62
  <%= a_link edit_path,
60
63
  color: :primary,
@@ -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,10 +75,10 @@ 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)
81
79
 
80
+ set_actions
81
+
82
82
  @page_title = @resource.default_panel_name.to_s
83
83
 
84
84
  # If we're accessing this resource via another resource add the parent to the breadcrumbs.
@@ -101,8 +101,20 @@ module Avo
101
101
  @model = @resource.model_class.new
102
102
  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
103
103
 
104
+ set_actions
105
+
104
106
  @page_title = @resource.default_panel_name.to_s
105
- add_breadcrumb @resource.plural_name.humanize, resources_path(resource: @resource)
107
+
108
+ if params[:via_relation_class].present? && params[:via_resource_id].present?
109
+ via_resource = Avo::App.get_resource_by_model_name params[:via_relation_class]
110
+ via_model = via_resource.class.find_scope.find params[:via_resource_id]
111
+ via_resource.hydrate model: via_model
112
+
113
+ add_breadcrumb via_resource.plural_name, resources_path(resource: via_resource)
114
+ add_breadcrumb via_resource.model_title, resource_path(model: via_model, resource: via_resource)
115
+ end
116
+
117
+ add_breadcrumb @resource.plural_name.humanize
106
118
  add_breadcrumb t("avo.new").humanize
107
119
  end
108
120
 
@@ -151,6 +163,7 @@ module Avo
151
163
  end
152
164
 
153
165
  def edit
166
+ set_actions
154
167
  end
155
168
 
156
169
  def update
@@ -302,14 +315,10 @@ module Avo
302
315
  end
303
316
 
304
317
  def set_actions
305
- if params[:resource_id].present?
306
- model = @resource.class.find_scope.find params[:resource_id]
307
- end
308
-
309
318
  @actions = @resource
310
319
  .get_actions
311
320
  .map do |action|
312
- action.new(model: model, resource: @resource, view: @view)
321
+ action.new(model: @model, resource: @resource, view: @view)
313
322
  end
314
323
  .select { |action| action.visible_in_view }
315
324
  end
@@ -385,20 +394,22 @@ module Avo
385
394
  return resource_path(model: params[:via_relation_class].safe_constantize, resource: parent_resource, resource_id: params[:via_resource_id])
386
395
  end
387
396
 
388
- redirect_path_from_resource_option || resource_path(model: @model, resource: @resource)
397
+ redirect_path_from_resource_option(:after_create_path) || resource_path(model: @model, resource: @resource)
389
398
  end
390
399
 
391
400
  def after_update_path
392
401
  return params[:referrer] if params[:referrer].present?
393
402
 
394
- redirect_path_from_resource_option || resource_path(model: @model, resource: @resource)
403
+ redirect_path_from_resource_option(:after_update_path) || resource_path(model: @model, resource: @resource)
395
404
  end
396
405
 
397
- def redirect_path_from_resource_option
398
- return nil if @resource.class.after_update_path.blank?
406
+ def redirect_path_from_resource_option(action = :after_update_path)
407
+ return nil if @resource.class.send(action).blank?
399
408
 
400
- if @resource.class.after_create_path == :index
409
+ if @resource.class.send(action) == :index
401
410
  resources_path(resource: @resource)
411
+ elsif @resource.class.send(action) == :edit
412
+ edit_resource_path(resource: @resource, model: @resource.model)
402
413
  else
403
414
  resource_path(model: @model, resource: @resource)
404
415
  end
@@ -0,0 +1,25 @@
1
+ require_dependency "avo/application_controller"
2
+
3
+ module Avo
4
+ class CardsController < ApplicationController
5
+ before_action :set_dashboard, only: :show
6
+ before_action :set_card, only: :show
7
+
8
+ def show
9
+ end
10
+
11
+ private
12
+
13
+ def set_dashboard
14
+ @dashboard = Avo::App.get_dashboard_by_id params[:dashboard_id]
15
+
16
+ raise ActionController::RoutingError.new("Not Found") if @dashboard.nil? || @dashboard.is_hidden?
17
+ end
18
+
19
+ def set_card
20
+ @card = @dashboard.item_at_index(params[:index].to_i).tap do |card|
21
+ card.hydrate(dashboard: @dashboard, params: params)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,21 +2,15 @@ require_dependency "avo/application_controller"
2
2
 
3
3
  module Avo
4
4
  class DashboardsController < ApplicationController
5
- before_action :set_dashboard
5
+ before_action :set_dashboard, only: :show
6
6
 
7
7
  def show
8
8
  end
9
9
 
10
- def card
11
- @card = @dashboard.item_at_index(params[:index].to_i).tap do |card|
12
- card.hydrate(dashboard: @dashboard, params: params)
13
- end
14
- end
15
-
16
10
  private
17
11
 
18
12
  def set_dashboard
19
- @dashboard = Avo::App.get_dashboard_by_id params[:dashboard_id]
13
+ @dashboard = Avo::App.get_dashboard_by_id params[:id]
20
14
 
21
15
  raise ActionController::RoutingError.new("Not Found") if @dashboard.nil? || @dashboard.is_hidden?
22
16
  end
@@ -4,7 +4,14 @@ module Avo
4
4
  class HomeController < ApplicationController
5
5
  def index
6
6
  if Avo.configuration.home_path.present?
7
- redirect_to Avo.configuration.home_path
7
+ # If the home_path is a block run it, if not, just use it
8
+ computed_path = if Avo.configuration.home_path.respond_to? :call
9
+ instance_exec(&Avo.configuration.home_path)
10
+ else
11
+ Avo.configuration.home_path
12
+ end
13
+
14
+ redirect_to computed_path
8
15
  elsif !Rails.env.development?
9
16
  @page_title = "Get started"
10
17
  resource = Avo::App.resources.min_by { |resource| resource.model_key }
@@ -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
@@ -12,10 +12,9 @@ module Avo
12
12
  end
13
13
  end
14
14
 
15
- # Create the `route_key` from the model key so the namespaced models get the proper path (SomeModule::Post -> some_module_post).
16
- # Add the `_index` suffix for the uncountable models so they get the correct path (`fish_index`)
17
- route_key = resource.model_key
18
- route_key << "_index" if resource.model_name.singular == resource.model_name.plural
15
+ route_key = resource.route_key
16
+ # Add the `_index` suffix for the uncountable names so they get the correct path (`fish_index`)
17
+ route_key << "_index" if resource.route_key == resource.singular_route_key
19
18
 
20
19
  avo.send :"resources_#{route_key}_path", **existing_params, **args
21
20
  end
@@ -33,19 +32,19 @@ module Avo
33
32
  id = resource_id
34
33
  end
35
34
 
36
- avo.send :"resources_#{resource.singular_model_key}_path", id, **args
35
+ avo.send :"resources_#{resource.singular_route_key}_path", id, **args
37
36
  end
38
37
 
39
38
  def new_resource_path(model:, resource:, **args)
40
- avo.send :"new_resources_#{resource.singular_model_key}_path", **args
39
+ avo.send :"new_resources_#{resource.singular_route_key}_path", **args
41
40
  end
42
41
 
43
42
  def edit_resource_path(model:, resource:, **args)
44
- avo.send :"edit_resources_#{resource.singular_model_key}_path", model, **args
43
+ avo.send :"edit_resources_#{resource.singular_route_key}_path", model, **args
45
44
  end
46
45
 
47
46
  def resource_attach_path(resource, model_id, related_name, related_id = nil)
48
- helpers.avo.resources_associations_new_path(resource.singular_model_key, model_id, related_name)
47
+ helpers.avo.resources_associations_new_path(resource.singular_route_key, model_id, related_name)
49
48
  end
50
49
 
51
50
  def resource_detach_path(
@@ -79,7 +78,7 @@ module Avo
79
78
  end
80
79
 
81
80
  def order_up_resource_path(model:, resource:, **args)
82
- avo.send :"order_up_resources_#{resource.singular_model_key}_path", model, **args
81
+ avo.send :"order_up_resources_#{resource.singular_route_key}_path", model, **args
83
82
  end
84
83
  end
85
84
  end
@@ -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() {