avo 2.16.1.pre.1.nativefields → 2.17.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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -2
  3. data/Gemfile.lock +3 -1
  4. data/README.md +1 -1
  5. data/app/assets/config/avo_manifest.js +1 -0
  6. data/app/assets/svgs/placeholder.svg +1 -0
  7. data/app/components/avo/actions_component.html.erb +3 -3
  8. data/app/components/avo/base_component.rb +7 -4
  9. data/app/components/avo/field_wrapper_component.html.erb +8 -10
  10. data/app/components/avo/field_wrapper_component.rb +14 -12
  11. data/app/components/avo/fields/badge_field/index_component.html.erb +1 -1
  12. data/app/components/avo/fields/belongs_to_field/edit_component.rb +9 -3
  13. data/app/components/avo/fields/belongs_to_field/index_component.html.erb +1 -1
  14. data/app/components/avo/fields/belongs_to_field/show_component.html.erb +1 -1
  15. data/app/components/avo/fields/belongs_to_field/show_component.rb +1 -1
  16. data/app/components/avo/fields/boolean_field/index_component.html.erb +1 -1
  17. data/app/components/avo/fields/boolean_field/show_component.html.erb +1 -1
  18. data/app/components/avo/fields/boolean_group_field/index_component.html.erb +1 -1
  19. data/app/components/avo/fields/boolean_group_field/show_component.html.erb +1 -1
  20. data/app/components/avo/fields/code_field/show_component.html.erb +1 -1
  21. data/app/components/avo/fields/common/heading_component.html.erb +5 -4
  22. data/app/components/avo/fields/common/heading_component.rb +6 -1
  23. data/app/components/avo/fields/country_field/index_component.html.erb +1 -1
  24. data/app/components/avo/fields/date_field/index_component.html.erb +1 -1
  25. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -1
  26. data/app/components/avo/fields/edit_component.rb +6 -4
  27. data/app/components/avo/fields/external_image_field/index_component.html.erb +1 -1
  28. data/app/components/avo/fields/file_field/edit_component.html.erb +2 -1
  29. data/app/components/avo/fields/file_field/index_component.html.erb +1 -1
  30. data/app/components/avo/fields/files_field/edit_component.html.erb +2 -1
  31. data/app/components/avo/fields/files_field/index_component.html.erb +1 -1
  32. data/app/components/avo/fields/files_field/show_component.html.erb +1 -1
  33. data/app/components/avo/fields/gravatar_field/index_component.html.erb +1 -1
  34. data/app/components/avo/fields/has_one_field/index_component.html.erb +1 -1
  35. data/app/components/avo/fields/heading_field/edit_component.html.erb +1 -1
  36. data/app/components/avo/fields/heading_field/show_component.html.erb +1 -1
  37. data/app/components/avo/fields/id_field/index_component.html.erb +1 -1
  38. data/app/components/avo/fields/index_component.rb +11 -2
  39. data/app/components/avo/fields/number_field/index_component.html.erb +1 -1
  40. data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -1
  41. data/app/components/avo/fields/select_field/index_component.html.erb +1 -1
  42. data/app/components/avo/fields/show_component.rb +7 -1
  43. data/app/components/avo/fields/status_field/index_component.html.erb +1 -1
  44. data/app/components/avo/fields/tags_field/index_component.html.erb +1 -1
  45. data/app/components/avo/fields/text_field/edit_component.html.erb +3 -1
  46. data/app/components/avo/fields/text_field/index_component.html.erb +1 -1
  47. data/app/components/avo/fields/trix_field/edit_component.html.erb +1 -1
  48. data/app/components/avo/filters_component.html.erb +2 -2
  49. data/app/components/avo/index/grid_cover_empty_state_component.html.erb +1 -1
  50. data/app/components/avo/index/grid_item_component.html.erb +15 -13
  51. data/app/components/avo/index/grid_item_component.rb +1 -1
  52. data/app/components/avo/index/ordering/buttons_component.html.erb +1 -1
  53. data/app/components/avo/index/resource_controls_component.rb +2 -2
  54. data/app/components/avo/index/table_row_component.html.erb +1 -1
  55. data/app/components/avo/panel_component.html.erb +11 -2
  56. data/app/components/avo/panel_component.rb +1 -0
  57. data/app/components/avo/resource_component.rb +18 -0
  58. data/app/components/avo/resource_sidebar_component.html.erb +19 -0
  59. data/app/components/avo/resource_sidebar_component.rb +26 -0
  60. data/app/components/avo/sidebar_profile_component.html.erb +1 -1
  61. data/app/components/avo/tab_switcher_component.html.erb +2 -2
  62. data/app/components/avo/views/resource_edit_component.html.erb +31 -25
  63. data/app/components/avo/views/resource_edit_component.rb +1 -1
  64. data/app/components/avo/views/resource_show_component.html.erb +8 -2
  65. data/app/components/avo/views/resource_show_component.rb +1 -1
  66. data/app/controllers/avo/actions_controller.rb +10 -2
  67. data/app/controllers/avo/application_controller.rb +4 -2
  68. data/app/controllers/avo/associations_controller.rb +10 -5
  69. data/app/controllers/avo/attachments_controller.rb +2 -1
  70. data/app/controllers/avo/base_controller.rb +6 -4
  71. data/app/controllers/avo/search_controller.rb +13 -4
  72. data/app/helpers/avo/application_helper.rb +3 -3
  73. data/app/helpers/avo/resources_helper.rb +2 -2
  74. data/app/javascript/avo.base.js +3 -1
  75. data/app/javascript/js/controllers/action_controller.js +1 -4
  76. data/app/javascript/js/controllers/actions_picker_controller.js +8 -9
  77. data/app/javascript/js/controllers/tabs_controller.js +14 -27
  78. data/app/views/avo/actions/show.html.erb +2 -2
  79. data/app/views/avo/home/failed_to_load.html.erb +3 -2
  80. data/config/brakeman.ignore +40 -0
  81. data/db/factories.rb +20 -0
  82. data/lib/avo/base_resource.rb +26 -0
  83. data/lib/avo/concerns/fetches_things.rb +1 -1
  84. data/lib/avo/concerns/has_fields.rb +22 -0
  85. data/lib/avo/concerns/is_resource_item.rb +4 -0
  86. data/lib/avo/configuration/branding.rb +9 -1
  87. data/lib/avo/fields/belongs_to_field.rb +3 -0
  88. data/lib/avo/fields/heading_field.rb +15 -0
  89. data/lib/avo/items_holder.rb +4 -0
  90. data/lib/avo/licensing/pro_license.rb +1 -0
  91. data/lib/avo/menu/builder.rb +1 -1
  92. data/lib/avo/menu/menu.rb +0 -2
  93. data/lib/avo/reloader.rb +27 -26
  94. data/lib/avo/services/encryption_service.rb +1 -1
  95. data/lib/avo/sidebar.rb +60 -0
  96. data/lib/avo/sidebar_builder.rb +24 -0
  97. data/lib/avo/version.rb +1 -1
  98. data/lib/generators/avo/templates/field/components/index_component.html.erb.tt +1 -1
  99. data/lib/generators/avo/templates/initializer/avo.tt +2 -1
  100. data/lib/generators/avo/templates/locales/avo.en.yml +0 -1
  101. data/lib/generators/avo/templates/locales/{avo.nb-NO.yml → avo.nb.yml} +35 -10
  102. data/lib/generators/avo/templates/locales/avo.nn.yml +118 -0
  103. data/lib/generators/avo/templates/locales/avo.tr.yml +119 -0
  104. data/public/avo-assets/avo.base.css +57 -75
  105. data/public/avo-assets/avo.base.js +63 -63
  106. data/public/avo-assets/avo.base.js.map +3 -3
  107. metadata +13 -7
  108. data/app/views/avo/home/failed_to_load.html copy.erb +0 -23
  109. data/config/master.key +0 -1
@@ -1,4 +1,4 @@
1
- <%= index_field_wrapper field: @field, resource: @resource do %>
1
+ <%= index_field_wrapper **field_wrapper_args do %>
2
2
  <% if @field.as_html %>
3
3
  <%== @field.value %>
4
4
  <% elsif @field.protocol.present? %>
@@ -1,4 +1,4 @@
1
- <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, full_width: true do %>
1
+ <%= field_wrapper **field_wrapper_args, full_width: true do %>
2
2
  <%= content_tag :div, class: "relative block overflow-x-auto max-w-full",
3
3
  data: {
4
4
  controller: "trix-field",
@@ -1,5 +1,5 @@
1
1
  <div data-controller="toggle-panel">
2
- <div class="relative w-full flex justify-between z-30">
2
+ <div class="relative w-full flex justify-between">
3
3
  <%= a_button class: 'focus:outline-none',
4
4
  color: :primary,
5
5
  size: :sm,
@@ -14,7 +14,7 @@
14
14
  <% end %>
15
15
  <% end %>
16
16
  <div
17
- class="absolute block inset-auto sm:right-0 top-full bg-white min-w-[300px] mt-2 z-20 shadow-modal rounded divide-y divide-gray-300 <%= 'hidden' unless params[:keep_filters_panel_open] == '1' %>"
17
+ class="absolute block inset-auto sm:right-0 top-full bg-white min-w-[300px] mt-2 z-40 shadow-modal rounded divide-y divide-gray-300 <%= 'hidden' unless params[:keep_filters_panel_open] == '1' %>"
18
18
  data-toggle-panel-target="panel"
19
19
  >
20
20
  <% @filters.each do |filter| %>
@@ -1,3 +1,3 @@
1
1
  <div class="absolute bg-gray-50 w-full h-full">
2
- <%= helpers.svg 'avocado', class: 'relative transform -translate-x-1/2 -translate-y-1/2 h-20 text-gray-400 inset-auto top-1/2 left-1/2' %>
2
+ <%= image_tag Avo.configuration.branding.placeholder, class: 'relative transform -translate-x-1/2 -translate-y-1/2 h-20 text-gray-400 inset-auto top-1/2 left-1/2' %>
3
3
  </div>
@@ -2,7 +2,7 @@
2
2
  class="relative bg-white rounded shadow-modal flex flex-col group"
3
3
  <%== item_selector_init @resource %>
4
4
  >
5
- <div class="relative w-full pb-3/4 rounded-t overflow-hidden">
5
+ <%= content_tag :div, class: "relative w-full pb-3/4 rounded-t overflow-hidden #{cover.present? ? cover.get_html(:classes, view: :index, element: :wrapper) : ""}", style: cover.present? ? cover.get_html(:style, view: :index, element: :wrapper) : "" do %>
6
6
  <% if @resource.record_selector %>
7
7
  <%== item_selector_input floating: true, size: :lg %>
8
8
  <% end %>
@@ -17,9 +17,7 @@
17
17
  <%= link_to_if cover.link_to_resource, image_tag(helpers.main_app.url_for(cover.value.variant(resize_to_limit: [480, 480])), class: 'absolute h-full w-full object-cover'), resource_view_path, class: 'absolute h-full w-full object-cover', title: title.value %>
18
18
  <% else %>
19
19
  <%= link_to resource_view_path do %>
20
- <div class="absolute bg-gray-100 w-full h-full">
21
- <%= helpers.svg 'avocado', class: 'relative transform -translate-x-1/2 -translate-y-1/2 h-20 text-gray-400 inset-auto top-1/2 left-1/2' %>
22
- </div>
20
+ <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
23
21
  <% end %>
24
22
  <% end %>
25
23
  <% elsif cover.value.present? %>
@@ -27,18 +25,22 @@
27
25
  <% else %>
28
26
  <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
29
27
  <% end %>
30
- </div>
31
- <div class="grid grid-cols-1 place-content-between p-4 h-full">
28
+ <% end %>
29
+ <%= content_tag :div, class: "grid grid-cols-1 place-content-between p-4 h-full" do %>
32
30
  <div class="mb-4 h-full flex flex-col space-between">
33
- <div class="grid font-semibold leading-tight text-lg mb-2">
34
- <%= link_to_if title.link_to_resource, title.value, resource_view_path if title.present? %>
35
- </div>
36
- <div class="text-sm break-words text-gray-500">
37
- <%= body.value if body.present? %>
38
- </div>
31
+ <% if title.present? %>
32
+ <%= content_tag :div, class: "grid font-semibold leading-tight text-lg mb-2 #{title.get_html(:classes, view: :index, element: :wrapper)}", style: title.get_html(:style, view: :index, element: :wrapper) do %>
33
+ <%= link_to_if title.link_to_resource, title.value, resource_view_path %>
34
+ <% end %>
35
+ <% end %>
36
+ <% if body.present? %>
37
+ <%= content_tag :div, class: "text-sm break-words text-gray-500 #{body.get_html(:classes, view: :index, element: :wrapper)}", style: body.get_html(:style, view: :index, element: :wrapper) do %>
38
+ <%= body.value %>
39
+ <% end %>
40
+ <% end %>
39
41
  </div>
40
42
  <div class="w-full place-self-end">
41
43
  <%= render(Avo::Index::ResourceControlsComponent.new(resource: @resource, reflection: @reflection, parent_model: @parent_model, parent_resource: @parent_resource, view_type: :grid)) %>
42
44
  </div>
43
- </div>
45
+ <% end %>
44
46
  </div>
@@ -32,7 +32,7 @@ class Avo::Index::GridItemComponent < ViewComponent::Base
32
32
 
33
33
  if @parent_model.present?
34
34
  args = {
35
- via_resource_class: parent_resource.model_class,
35
+ via_resource_class: parent_resource.class.to_s,
36
36
  via_resource_id: @parent_model.id
37
37
  }
38
38
  end
@@ -22,7 +22,7 @@
22
22
  %>
23
23
  <%= helpers.svg('switch-vertical', class: 'text-gray-400 h-6 hover:text-gray-600') %>
24
24
  <% end %>
25
- <div class="flex hidden absolute max-w-xs bg-white rounded p-2 z-40" data-popover-target="content">
25
+ <div class="flex hidden absolute max-w-xs bg-white rounded p-2" data-popover-target="content">
26
26
  <%= render Avo::Index::Ordering::ButtonComponent.new resource: @resource, reflection: @reflection, direction: :higher, svg: 'arrow-up' %>
27
27
  <%= render Avo::Index::Ordering::ButtonComponent.new resource: @resource, reflection: @reflection, direction: :lower, svg: 'arrow-down' %>
28
28
  <%= render Avo::Index::Ordering::ButtonComponent.new resource: @resource, reflection: @reflection, direction: :to_top, svg: 'download-solid-reversed' %>
@@ -36,7 +36,7 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
36
36
 
37
37
  if @parent_model.present?
38
38
  args = {
39
- via_resource_class: parent_resource.model_class,
39
+ via_resource_class: parent_resource.class.to_s,
40
40
  via_resource_id: @parent_model.id
41
41
  }
42
42
  end
@@ -50,7 +50,7 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
50
50
 
51
51
  if @parent_model.present?
52
52
  args = {
53
- via_resource_class: parent_resource.model_class,
53
+ via_resource_class: parent_resource.class.to_s,
54
54
  via_resource_id: @parent_model.id
55
55
  }
56
56
  end
@@ -17,7 +17,7 @@
17
17
  </td>
18
18
  <% end %>
19
19
  <% @resource.get_fields(reflection: @reflection, only_root: true).each_with_index do |field, index| %>
20
- <%= render field.component_for_view(:index).new(field: field, resource: @resource, index: index, parent_model: @parent_model) %>
20
+ <%= render field.component_for_view(:index).new(field: field, resource: @resource, index: index, parent_model: @parent_model, parent_resource: @parent_resource) %>
21
21
  <% end %>
22
22
  <% if Avo.configuration.resource_controls_on_the_right? %>
23
23
  <td class="text-right whitespace-nowrap px-2" data-control="resource-controls">
@@ -24,8 +24,17 @@
24
24
  </div>
25
25
  <% end %>
26
26
  <% if body? %>
27
- <div class="relative <%= white_panel_classes %> <%= @body_classes %>">
28
- <%= body %>
27
+ <div class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:gap-4 w-full">
28
+ <div class="flex-1 overflow-auto <% if sidebar? %> w-2/3 <% end %>">
29
+ <div class="relative <%= white_panel_classes %> <%= @body_classes %>">
30
+ <%= body %>
31
+ </div>
32
+ </div>
33
+ <% if sidebar? %>
34
+ <div class="w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
35
+ <%= sidebar %>
36
+ </div>
37
+ <% end %>
29
38
  </div>
30
39
  <% end %>
31
40
  <% if bare_content? %>
@@ -7,6 +7,7 @@ class Avo::PanelComponent < ViewComponent::Base
7
7
 
8
8
  renders_one :tools
9
9
  renders_one :body
10
+ renders_one :sidebar
10
11
  renders_one :bare_content
11
12
  renders_one :footer_tools
12
13
  renders_one :footer
@@ -85,6 +85,16 @@ class Avo::ResourceComponent < Avo::BaseComponent
85
85
  end
86
86
  end
87
87
 
88
+ def sidebar
89
+ return if Avo::App.license.lacks_with_trial(:resource_sidebar)
90
+
91
+ @sidebar ||= search_for_sidebar
92
+ end
93
+
94
+ def sidebar_component(form: nil)
95
+ Avo::ResourceSidebarComponent.new resource: @resource, fields: sidebar.items, params: params, view: view, form: form
96
+ end
97
+
88
98
  def has_reflection_and_is_read_only
89
99
  if @reflection.present? && @reflection.active_record.name && @reflection.name
90
100
  fields = ::Avo::App.get_resource_by_model_name(@reflection.active_record.name).get_field_definitions
@@ -105,4 +115,12 @@ class Avo::ResourceComponent < Avo::BaseComponent
105
115
  def via_resource?
106
116
  (params[:via_resource_class].present? || params[:via_relation_class].present?) && params[:via_resource_id].present?
107
117
  end
118
+
119
+ def search_for_sidebar
120
+ item = @resource.get_items.find do |item|
121
+ item.is_sidebar?
122
+ end
123
+
124
+ item&.hydrate(view: view)
125
+ end
108
126
  end
@@ -0,0 +1,19 @@
1
+ <div class="resource-sidebar-component divide-y">
2
+ <% fields.each_with_index do |field, index| %>
3
+ <%= render field
4
+ .hydrate(
5
+ resource: resource,
6
+ model: resource.model,
7
+ user: resource.user,
8
+ view: view
9
+ ).component_for_view(view)
10
+ .new(
11
+ field: field,
12
+ resource: resource,
13
+ form: form,
14
+ stacked: true,
15
+ index: index
16
+ )
17
+ %>
18
+ <% end %>
19
+ </div>
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::ResourceSidebarComponent < ViewComponent::Base
4
+ attr_reader :resource
5
+ attr_reader :params
6
+ attr_reader :view
7
+ attr_reader :form
8
+
9
+ def initialize(resource: nil, fields: nil, index: nil, params: nil, form: nil, view: nil)
10
+ @resource = resource
11
+ @fields = fields
12
+ @params = params
13
+ @view = view
14
+ @form = form
15
+ end
16
+
17
+ def fields
18
+ @fields.filter do |field|
19
+ field.visible_on? view
20
+ end
21
+ end
22
+
23
+ def render?
24
+ Avo::App.license.has_with_trial(:resource_sidebar)
25
+ end
26
+ end
@@ -22,7 +22,7 @@
22
22
  <%= helpers.svg 'three-dots', class: 'h-4' %>
23
23
  </a>
24
24
  <div
25
- class="hidden absolute flex flex-col inset-auto right-0 -mt-12 bg-white rounded min-w-[200px] shadow-context -translate-y-full"
25
+ class="hidden absolute flex flex-col inset-auto right-0 -mt-12 bg-white rounded min-w-[200px] shadow-context -translate-y-full z-40"
26
26
  data-toggle-panel-target="panel"
27
27
  >
28
28
  <% if Avo::App.has_profile_menu? %>
@@ -20,13 +20,13 @@
20
20
  </div>
21
21
  </div>
22
22
  <% else %>
23
- <div class="flex flex-wrap gap-2" data-target="tab-switcher" data-style="pills">
23
+ <div class="flex flex-wrap gap-2 bg-white p-2" data-target="tab-switcher" data-style="pills">
24
24
  <% visible_items.each do |tab| %>
25
25
  <%= a_link tab_path(tab),
26
26
  color: selected?(tab) ? :primary : :gray,
27
27
  style: selected?(tab) ? :outline : :text,
28
28
  size: :sm,
29
- class: selected?(tab) ? "z-20" : "",
29
+ class: selected?(tab) ? "z-20 bg-primary-100" : "",
30
30
  title: tab.description,
31
31
  data: {
32
32
  tippy: tab.description.present? ? 'tooltip' : '',
@@ -19,12 +19,12 @@
19
19
  <%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
20
20
  <% c.tools do %>
21
21
  <%= a_link back_path,
22
- style: :text,
23
- icon: 'arrow-left' do %>
24
- <%= t('avo.cancel').capitalize %>
25
- <% end %>
26
- <% if can_see_the_destroy_button? %>
27
- <%= a_link destroy_path,
22
+ style: :text,
23
+ icon: 'arrow-left' do %>
24
+ <%= t('avo.cancel').capitalize %>
25
+ <% end %>
26
+ <% if can_see_the_destroy_button? %>
27
+ <%= a_link destroy_path,
28
28
  method: :delete,
29
29
  local: true,
30
30
  style: :text,
@@ -37,36 +37,42 @@
37
37
  control: :destroy,
38
38
  'resource-id': @resource.model.id,
39
39
  } do %>
40
- <%= t('avo.delete').capitalize %>
40
+ <%= t('avo.delete').capitalize %>
41
+ <% end %>
41
42
  <% end %>
42
- <% end %>
43
- <% if can_see_the_actions_button? %>
44
- <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
45
- <% end %>
46
- <% if can_see_the_save_button? %>
47
- <%= a_button color: :primary,
43
+ <% if can_see_the_actions_button? %>
44
+ <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
45
+ <% end %>
46
+ <% if can_see_the_save_button? %>
47
+ <%= a_button color: :primary,
48
48
  style: :primary,
49
49
  loading: true,
50
50
  type: :submit,
51
51
  icon: 'save' do %>
52
- <%= t('avo.save').capitalize %>
52
+ <%= t('avo.save').capitalize %>
53
+ <% end %>
53
54
  <% end %>
54
55
  <% end %>
55
- <% end %>
56
- <%# Extract the main panel and display the fields here. %>
57
- <%# This way we'll be able to render the footer buttons under the main fields. %>
58
- <% if main_panel.present? %>
59
- <% c.body do %>
60
- <div class="divide-y">
61
- <% main_panel.items.each_with_index do |field, index| %>
62
- <%= render field.hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: view_for(field))
63
- .component_for_view(view_for field)
64
- .new(field: field, resource: @resource, index: index, form: form)
65
- %>
56
+ <%# Extract the main panel and display the fields here. %>
57
+ <%# This way we'll be able to render the footer buttons under the main fields. %>
58
+ <% if main_panel.present? %>
59
+ <% c.body do %>
60
+ <div class="divide-y">
61
+ <% main_panel.items.each_with_index do |field, index| %>
62
+ <%= render field
63
+ .hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: view_for(field))
64
+ .component_for_view(view_for field)
65
+ .new(field: field, resource: @resource, index: index, form: form, compact: sidebar.present?)
66
+ %>
66
67
  <% end %>
67
68
  </div>
68
69
  <% end %>
69
70
  <% end %>
71
+ <% if sidebar.present? %>
72
+ <% c.sidebar do %>
73
+ <%= render sidebar_component form: form %>
74
+ <% end %>
75
+ <% end %>
70
76
  <% if Avo.configuration.buttons_on_form_footers %>
71
77
  <% c.footer_tools do %>
72
78
  <div class="mt-4">
@@ -31,7 +31,7 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
31
31
  end
32
32
 
33
33
  def resource_view_path
34
- helpers.resource_view_path(model: relation_resource.model, resource: relation_resource)
34
+ helpers.resource_view_path(model: association_resource.model, resource: association_resource)
35
35
  end
36
36
 
37
37
  def can_see_the_destroy_button?
@@ -160,14 +160,20 @@
160
160
  <% c.body do %>
161
161
  <div class="divide-y">
162
162
  <% main_panel.items.each_with_index do |field, index| %>
163
- <%= render field.hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: view)
163
+ <%= render field
164
+ .hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: view)
164
165
  .component_for_view(view)
165
- .new(field: field, resource: @resource, index: index)
166
+ .new(field: field, resource: @resource, index: index, compact: sidebar.present?)
166
167
  %>
167
168
  <% end %>
168
169
  </div>
169
170
  <% end %>
170
171
  <% end %>
172
+ <% if sidebar.present? %>
173
+ <% c.sidebar do %>
174
+ <%= render sidebar_component %>
175
+ <% end %>
176
+ <% end %>
171
177
  <% end %>
172
178
  <% if @reflection.blank? %>
173
179
  <%= content_tag :div, class: 'space-y-12 mt-12' do %>
@@ -26,7 +26,7 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
26
26
 
27
27
  def back_path
28
28
  if via_resource?
29
- helpers.resource_path(model: params[:via_resource_class].safe_constantize, resource: relation_resource, resource_id: params[:via_resource_id])
29
+ helpers.resource_path(model: association_resource.model_class, resource: association_resource, resource_id: params[:via_resource_id])
30
30
  else
31
31
  helpers.resources_path(resource: @resource)
32
32
  end
@@ -48,11 +48,19 @@ module Avo
48
48
  end
49
49
 
50
50
  def set_action
51
- action_class = params[:action_id].gsub("avo_actions_", "").camelize.safe_constantize
52
-
53
51
  @action = action_class.new(model: @model, resource: @resource, user: _current_user, view: :edit)
54
52
  end
55
53
 
54
+ def action_class
55
+ klass_name = params[:action_id].gsub("avo_actions_", "").camelize
56
+
57
+ valid_klass = Avo::BaseAction.descendants.find do |action|
58
+ action.to_s == klass_name
59
+ end
60
+
61
+ valid_klass
62
+ end
63
+
56
64
  def respond(response)
57
65
  response[:type] ||= :reload
58
66
  messages = get_messages response
@@ -143,10 +143,12 @@ module Avo
143
143
  end
144
144
 
145
145
  def set_related_model
146
+ association_name = BaseResource.valid_association_name(@model, params[:related_name])
147
+
146
148
  @related_model = if @field.is_a? Avo::Fields::HasOneField
147
- @model.send params[:related_name]
149
+ @model.send association_name
148
150
  else
149
- eager_load_files(@related_resource, @model.send(params[:related_name])).find params[:related_id]
151
+ eager_load_files(@related_resource, @model.send(association_name)).find params[:related_id]
150
152
  end
151
153
  end
152
154
 
@@ -21,7 +21,8 @@ module Avo
21
21
  @resource = @related_resource
22
22
  @parent_model = @parent_resource.class.find_scope.find(params[:id])
23
23
  @parent_resource.hydrate(model: @parent_model)
24
- @query = @authorization.apply_policy @parent_model.public_send(params[:related_name])
24
+ association_name = BaseResource.valid_association_name(@parent_model, params[:related_name])
25
+ @query = @authorization.apply_policy @parent_model.send(association_name)
25
26
  @association_field = @parent_resource.get_field params[:related_name]
26
27
 
27
28
  if @association_field.present? && @association_field.scope.present?
@@ -57,10 +58,12 @@ module Avo
57
58
  end
58
59
 
59
60
  def create
61
+ association_name = BaseResource.valid_association_name(@model, params[:related_name])
62
+
60
63
  if reflection_class == "HasManyReflection"
61
- @model.send(params[:related_name].to_s) << @attachment_model
64
+ @model.send(association_name) << @attachment_model
62
65
  else
63
- @model.send("#{params[:related_name]}=", @attachment_model)
66
+ @model.send("#{association_name}=", @attachment_model)
64
67
  end
65
68
 
66
69
  respond_to do |format|
@@ -73,10 +76,12 @@ module Avo
73
76
  end
74
77
 
75
78
  def destroy
79
+ association_name = BaseResource.valid_association_name(@model, params[:related_name])
80
+
76
81
  if reflection_class == "HasManyReflection"
77
- @model.send(params[:related_name].to_s).delete @attachment_model
82
+ @model.send(association_name).delete @attachment_model
78
83
  else
79
- @model.send("#{params[:related_name]}=", nil)
84
+ @model.send("#{association_name}=", nil)
80
85
  end
81
86
 
82
87
  respond_to do |format|
@@ -8,8 +8,9 @@ module Avo
8
8
 
9
9
  def create
10
10
  blob = ActiveStorage::Blob.create_and_upload! io: params[:file], filename: params[:filename]
11
+ association_name = BaseResource.valid_attachment_name(@model, params[:attachment_key])
11
12
 
12
- @model.send(params[:attachment_key]).attach blob
13
+ @model.send(association_name).attach blob
13
14
 
14
15
  render json: {
15
16
  url: main_app.url_for(blob),
@@ -84,7 +84,7 @@ module Avo
84
84
 
85
85
  # If we're accessing this resource via another resource add the parent to the breadcrumbs.
86
86
  if params[:via_resource_class].present? && params[:via_resource_id].present?
87
- via_resource = Avo::App.get_resource_by_model_name(params[:via_resource_class]).dup
87
+ via_resource = Avo::App.get_resource(params[:via_resource_class]).dup
88
88
  via_model = via_resource.class.find_scope.find params[:via_resource_id]
89
89
  via_resource.hydrate model: via_model
90
90
 
@@ -141,8 +141,9 @@ module Avo
141
141
  # find the record
142
142
  via_resource = ::Avo::App.get_resource_by_model_name(params[:via_relation_class]).dup
143
143
  @related_record = via_resource.model_class.find params[:via_resource_id]
144
+ association_name = BaseResource.valid_association_name(@model, params[:via_relation])
144
145
 
145
- @model.send(params[:via_relation]) << @related_record
146
+ @model.send(association_name) << @related_record
146
147
  end
147
148
  end
148
149
 
@@ -370,7 +371,7 @@ module Avo
370
371
  last_crumb_args = {}
371
372
  # If we're accessing this resource via another resource add the parent to the breadcrumbs.
372
373
  if params[:via_resource_class].present? && params[:via_resource_id].present?
373
- via_resource = Avo::App.get_resource_by_model_name(params[:via_resource_class]).dup
374
+ via_resource = Avo::App.get_resource(params[:via_resource_class]).dup
374
375
  via_model = via_resource.class.find_scope.find params[:via_resource_id]
375
376
  via_resource.hydrate model: via_model
376
377
 
@@ -414,9 +415,10 @@ module Avo
414
415
  # If this is an associated record return to the association show page
415
416
  if is_associated_record?
416
417
  parent_resource = ::Avo::App.get_resource_by_model_name(params[:via_relation_class]).dup
418
+ association_name = BaseResource.valid_association_name(@model, params[:via_relation])
417
419
 
418
420
  return resource_view_path(
419
- model: @model.send(params[:via_relation]),
421
+ model: @model.send(association_name),
420
422
  resource: parent_resource,
421
423
  resource_id: params[:via_resource_id]
422
424
  )
@@ -87,9 +87,14 @@ module Avo
87
87
  # In these scenarios, try to find the grandparent for the new views where the parent is nil
88
88
  # and initialize the parent record with the grandparent attached so the user has the required information
89
89
  # to scope the query.
90
+ # Example usage: Got to a project, create a new review, and search for a user.
90
91
  if parent.blank? && params[:via_parent_resource_id].present? && params[:via_parent_resource_class].present? && params[:via_relation].present?
91
- grandparent = params[:via_parent_resource_class].safe_constantize.find params[:via_parent_resource_id]
92
- parent = params[:via_reflection_class].safe_constantize.new(
92
+ parent_resource_class = BaseResource.valid_model_class params[:via_parent_resource_class]
93
+
94
+ reflection_class = BaseResource.valid_model_class params[:via_reflection_class]
95
+
96
+ grandparent = parent_resource_class.find params[:via_parent_resource_id]
97
+ parent = reflection_class.new(
93
98
  params[:via_relation] => grandparent
94
99
  )
95
100
  end
@@ -97,8 +102,10 @@ module Avo
97
102
  Avo::Hosts::AssociationScopeHost.new(block: attach_scope, query: query, parent: parent).handle
98
103
  end
99
104
 
105
+ # This scope is applied if the search is being performed on a has_many association
100
106
  def apply_has_many_scope
101
- scope = parent.send(params[:via_association_id])
107
+ association_name = BaseResource.valid_association_name(parent, params[:via_association_id])
108
+ scope = parent.send(association_name)
102
109
 
103
110
  Avo::Hosts::SearchScopeHost.new(block: @resource.search_query, params: params, scope: scope).handle
104
111
  end
@@ -157,7 +164,9 @@ module Avo
157
164
  def fetch_parent
158
165
  return unless params[:via_reflection_id].present?
159
166
 
160
- params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
167
+ parent_class = BaseResource.valid_model_class params[:via_reflection_class]
168
+
169
+ parent_class.find params[:via_reflection_id]
161
170
  end
162
171
  end
163
172
  end
@@ -22,13 +22,13 @@ module Avo
22
22
 
23
23
  def a_button(**args, &block)
24
24
  render Avo::ButtonComponent.new(is_link: false, **args) do
25
- capture(&block) if block
25
+ capture(&block) if block.present?
26
26
  end
27
27
  end
28
28
 
29
29
  def a_link(path = nil, **args, &block)
30
30
  render Avo::ButtonComponent.new(path, is_link: true, **args) do
31
- capture(&block) if block
31
+ capture(&block) if block.present?
32
32
  end
33
33
  end
34
34
 
@@ -76,7 +76,7 @@ module Avo
76
76
  end
77
77
 
78
78
  def input_classes(extra_classes = "", has_error: false)
79
- classes = "appearance-none inline-flex bg-gray-25 disabled:cursor-not-allowed text-gray-600 disabled:opacity-50 rounded py-2 px-3 leading-tight border focus:border-gray-600 focus-visible:ring-0 focus:text-gray-700"
79
+ classes = "appearance-none inline-flex bg-gray-25 disabled:cursor-not-allowed text-gray-600 disabled:opacity-50 rounded py-2 px-3 leading-tight border focus:border-gray-600 focus-visible:ring-0 focus:text-gray-700 placeholder:text-gray-300"
80
80
 
81
81
  classes += if has_error
82
82
  " border-red-600"
@@ -25,8 +25,8 @@ module Avo
25
25
  capture(&block)
26
26
  end
27
27
  end
28
- alias :edit_field_wrapper :field_wrapper
29
- alias :show_field_wrapper :field_wrapper
28
+ alias_method :edit_field_wrapper, :field_wrapper
29
+ alias_method :show_field_wrapper, :field_wrapper
30
30
 
31
31
  def filter_wrapper(name: nil, index: nil, **args, &block)
32
32
  render layout: "layouts/avo/filter_wrapper", locals: {
@@ -53,7 +53,6 @@ window.initTippy = initTippy
53
53
  ActiveStorage.start()
54
54
 
55
55
  document.addEventListener('turbo:load', () => {
56
- document.body.classList.remove('turbo-loading')
57
56
  initTippy()
58
57
  isMac()
59
58
 
@@ -64,6 +63,9 @@ document.addEventListener('turbo:load', () => {
64
63
  scrollTop = 0
65
64
  }, 50)
66
65
  }
66
+ setTimeout(() => {
67
+ document.body.classList.remove('turbo-loading')
68
+ }, 1)
67
69
  })
68
70
 
69
71
  document.addEventListener('turbo:frame-load', () => {