avo 2.0.0 → 2.1.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +3 -3
  4. data/app/assets/svgs/save.svg +8 -1
  5. data/app/components/avo/actions_component.html.erb +1 -1
  6. data/app/components/avo/alert_component.rb +5 -5
  7. data/app/components/avo/blank_field_component.html.erb +0 -0
  8. data/app/components/avo/blank_field_component.rb +4 -0
  9. data/app/components/avo/fields/badge_field/index_component.html.erb +1 -1
  10. data/app/components/avo/fields/boolean_field/index_component.html.erb +1 -1
  11. data/app/components/avo/fields/external_image_field/index_component.html.erb +1 -2
  12. data/app/components/avo/fields/file_field/index_component.html.erb +3 -3
  13. data/app/components/avo/fields/file_field/index_component.rb +11 -0
  14. data/app/components/avo/fields/gravatar_field/index_component.html.erb +1 -1
  15. data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -1
  16. data/app/components/avo/index/field_wrapper_component.html.erb +1 -1
  17. data/app/components/avo/index/field_wrapper_component.rb +12 -1
  18. data/app/components/avo/index/resource_controls_component.html.erb +5 -1
  19. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  20. data/app/components/avo/panel_component.html.erb +10 -2
  21. data/app/components/avo/panel_component.rb +9 -0
  22. data/app/components/avo/referrer_params_component.html.erb +4 -0
  23. data/app/components/avo/referrer_params_component.rb +9 -0
  24. data/app/components/avo/sidebar_component.rb +3 -1
  25. data/app/components/avo/views/resource_edit_component.html.erb +14 -1
  26. data/app/components/avo/views/resource_new_component.html.erb +13 -0
  27. data/app/components/avo/views/resource_show_component.html.erb +1 -0
  28. data/app/controllers/avo/application_controller.rb +1 -1
  29. data/app/controllers/avo/base_controller.rb +57 -27
  30. data/app/controllers/avo/dashboards_controller.rb +1 -1
  31. data/app/controllers/avo/search_controller.rb +5 -1
  32. data/app/javascript/avo.js +4 -3
  33. data/app/views/avo/actions/show.html.erb +1 -0
  34. data/app/views/avo/associations/new.html.erb +1 -0
  35. data/app/views/avo/home/failed_to_load.html.erb +21 -1
  36. data/app/views/layouts/avo/application.html.erb +7 -0
  37. data/bin/dev +7 -6
  38. data/lib/avo/app.rb +8 -1
  39. data/lib/avo/base_action.rb +4 -4
  40. data/lib/avo/base_resource.rb +26 -7
  41. data/lib/avo/configuration.rb +2 -0
  42. data/lib/avo/dashboards/base_dashboard.rb +17 -0
  43. data/lib/avo/dashboards/chartkick_card.rb +1 -1
  44. data/lib/avo/fields/base_field.rb +19 -6
  45. data/lib/avo/fields/belongs_to_field.rb +1 -1
  46. data/lib/avo/fields/external_image_field.rb +2 -2
  47. data/lib/avo/hosts/dashboard_visibility.rb +19 -0
  48. data/lib/avo/licensing/pro_license.rb +1 -0
  49. data/lib/avo/version.rb +1 -1
  50. data/lib/generators/avo/templates/dashboards/dashboard.tt +4 -1
  51. data/public/avo-assets/avo.css +37 -21
  52. data/public/avo-assets/avo.js +166 -165
  53. data/public/avo-assets/avo.js.map +3 -3
  54. metadata +7 -3
  55. data/app/views/avo/partials/_failed_state.html.erb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f169116cd6e56e99ff318b5babd7394db5ad846ddc16c2399ce02df980c0edfc
4
- data.tar.gz: 4ed87d717ff0f174b4220637fe5b5b31be26fd84b8f383c7812ce7b55f785e8f
3
+ metadata.gz: 633ca9db11d83d470c185235c921f4c6d5d8c76929443fa459d64f3e10ac6ba3
4
+ data.tar.gz: 81a6fe46847a7829f88362f39f5a732dce6953a9bc1197e7bdcfae7c80eb644d
5
5
  SHA512:
6
- metadata.gz: c05244d5032c94bc2912e5ec726975544c3b5a2813a7fd477bc2e5bd5e1718a2fedd04ab53c296b73fd779e6d8dc43a9e711e71cf83260df4972752e456335a7
7
- data.tar.gz: a89f9be5b9c1d6f2fbefa344e0335de6823a1f9cebf66077da9b6ab0970a7a6713cbf03f76689344d590df32f946cac73a84ef6157b864a0c2651394d0ac065b
6
+ metadata.gz: 6f6135bc849ee84b9d637fd9495ff5836fad0c58fc4d1b65504ba7d6eb6b95afd8b7a1724b287ca4857de32920b251d1c749901fc4c28e7cf1f9e6cf2b65a389
7
+ data.tar.gz: 59e3248675295cbb521f71b7a66e0f80b3433ebe022d6ad05b48138cd93c4907b96a3672e5bfe6d661412ff1f0c7ea52c957445ba16896d63e425a17d734c1b7
data/Gemfile CHANGED
@@ -30,7 +30,7 @@ gem "rails", "~> 6.1.0"
30
30
  # Use postgresql as the database for Active Record
31
31
  gem "pg", ">= 0.18", "< 2.0"
32
32
  # Use Puma as the app server
33
- gem "puma", "~> 5.6.2"
33
+ gem "puma", "~> 5.6.4"
34
34
  # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
35
35
  # gem "jbuilder", "~> 2.7"
36
36
  # Use Redis adapter to run Action Cable in production
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.0.0)
4
+ avo (2.1.0)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -250,7 +250,7 @@ GEM
250
250
  ast (~> 2.4.1)
251
251
  pg (1.3.1)
252
252
  public_suffix (4.0.6)
253
- puma (5.6.2)
253
+ puma (5.6.4)
254
254
  nio4r (~> 2.0)
255
255
  pundit (2.2.0)
256
256
  activesupport (>= 3.0.0)
@@ -447,7 +447,7 @@ DEPENDENCIES
447
447
  meta-tags
448
448
  net-smtp
449
449
  pg (>= 0.18, < 2.0)
450
- puma (~> 5.6.2)
450
+ puma (~> 5.6.4)
451
451
  pundit
452
452
  rails (~> 6.1.0)
453
453
  rails-controller-testing
@@ -1 +1,8 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM272 80v80H144V80h128zm122 352H54a6 6 0 0 1-6-6V86a6 6 0 0 1 6-6h42v104c0 13.255 10.745 24 24 24h176c13.255 0 24-10.745 24-24V83.882l78.243 78.243a6 6 0 0 1 1.757 4.243V426a6 6 0 0 1-6 6zM224 232c-48.523 0-88 39.477-88 88s39.477 88 88 88 88-39.477 88-88-39.477-88-88-88zm0 128c-22.056 0-40-17.944-40-40s17.944-40 40-40 40 17.944 40 40-17.944 40-40 40z"/></svg>
1
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 515 450" style="enable-background:new 0 0 515 450;" xml:space="preserve">
2
+ <path d="M438.4,116.4l-72.3-72.3c-7.8-7.8-18.3-12.1-29.2-12.1h-231C83,32,64.5,50.5,64.5,73.4v303.3c0,22.8,18.5,41.4,41.4,41.4
3
+ h303.3c22.8,0,41.4-18.5,41.4-41.4v-231C450.5,134.7,446.1,124.1,438.4,116.4L438.4,116.4z M298.9,73.4v68.9H188.6V73.4H298.9z
4
+ M404,376.6H111c-2.9,0-5.2-2.3-5.2-5.2V78.5c0-2.9,2.3-5.2,5.2-5.2h36.2V163c0,11.4,9.3,20.7,20.7,20.7h151.6
5
+ c11.4,0,20.7-9.3,20.7-20.7V76.7l67.4,67.4c1,1,1.5,2.3,1.5,3.7v223.7C409.1,374.3,406.8,376.6,404,376.6L404,376.6z M257.5,204.3
6
+ c-41.8,0-75.8,34-75.8,75.8s34,75.8,75.8,75.8s75.8-34,75.8-75.8S299.3,204.3,257.5,204.3z M257.5,314.6c-19,0-34.5-15.5-34.5-34.5
7
+ s15.5-34.5,34.5-34.5s34.5,15.5,34.5,34.5S276.5,314.6,257.5,314.6z"/>
8
+ </svg>
@@ -12,7 +12,7 @@
12
12
  <%= t 'avo.actions' %>
13
13
  <% end %>
14
14
  <div
15
- class="absolute flex inset-auto right-0 top-full bg-white w-full sm:w-auto sm:min-w-[300px] mt-2 z-20 shadow-modal rounded overflow-hidden hidden"
15
+ class="absolute flex inset-auto xl:right-0 top-full bg-white w-full sm:w-auto sm:min-w-[300px] mt-2 z-20 shadow-modal rounded overflow-hidden hidden"
16
16
  data-toggle-panel-target="panel"
17
17
  >
18
18
  <div class="w-full space divide-y">
@@ -12,18 +12,18 @@ class Avo::AlertComponent < ViewComponent::Base
12
12
  end
13
13
 
14
14
  def icon
15
- return 'x-circle' if is_error?
15
+ return "x-circle" if is_error?
16
16
 
17
- 'check-circle'
17
+ "check-circle"
18
18
  end
19
19
 
20
20
  def classes
21
21
  result = "max-w-sm w-full shadow-lg rounded px-4 py-3 rounded relative border text-white pointer-events-auto"
22
22
 
23
- if is_error?
24
- result += " bg-red-400 border-red-700"
23
+ result += if is_error?
24
+ " bg-red-400 border-red-700"
25
25
  else
26
- result += " bg-green-400 border-green-700"
26
+ " bg-green-400 border-green-700"
27
27
  end
28
28
 
29
29
  result
File without changes
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::BlankFieldComponent < ViewComponent::Base
4
+ end
@@ -1,3 +1,3 @@
1
- <%= index_field_wrapper field: @field do %>
1
+ <%= index_field_wrapper field: @field, flush: true do %>
2
2
  <%= render Avo::Fields::Common::BadgeViewerComponent.new value: @field.value, options: @field.options %>
3
3
  <% end %>
@@ -1,3 +1,3 @@
1
- <%= index_field_wrapper field: @field, dash_if_blank: false, center_content: true do %>
1
+ <%= index_field_wrapper field: @field, dash_if_blank: false, center_content: true, flush: true do %>
2
2
  <%= render Avo::Fields::Common::BooleanCheckComponent.new checked: @field.value %>
3
3
  <% end %>
@@ -1,8 +1,7 @@
1
- <%= index_field_wrapper field: @field do %>
1
+ <%= index_field_wrapper field: @field, flush: true do %>
2
2
  <% if @field.value.present? %>
3
3
  <%= image_tag @field.value,
4
4
  height: @field.height,
5
- width: @field.width,
6
5
  style: "border-radius: #{@field.radius}px; max-height: #{@field.height}#{@field.height.to_s&.ends_with?('px') ? '' : 'px'};"
7
6
  %>
8
7
  <% end %>
@@ -1,9 +1,9 @@
1
- <%= index_field_wrapper field: @field do %>
1
+ <%= index_field_wrapper field: @field, flush: flush? do %>
2
2
  <% if @field.value.present? %>
3
3
  <% if @field.value.attached? && @field.value.representable? && @field.is_image %>
4
- <%= link_to_if @field.link_to_resource, image_tag(helpers.main_app.url_for(@field.value), class: 'max-h-full'), resource_path, class: 'block' %>
4
+ <%= link_to_if @field.link_to_resource, image_tag(helpers.main_app.url_for(@field.value), class: 'h-10'), resource_path, class: 'block' %>
5
5
  <% elsif @field.value.attached? && @field.is_audio %>
6
- <%= link_to_if @field.link_to_resource, audio_tag(helpers.main_app.url_for(@field.value), controls: true, preload: false, class: 'max-h-full'), resource_path, class: 'block h-8' %>
6
+ <%= link_to_if @field.link_to_resource, audio_tag(helpers.main_app.url_for(@field.value), controls: true, preload: false, class: 'max-h-full h-10'), resource_path, class: 'block h-8' %>
7
7
  <% else %>
8
8
  <%= @field.value.filename %>
9
9
  <% end %>
@@ -1,4 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Fields::FileField::IndexComponent < Avo::Fields::IndexComponent
4
+ def flush?
5
+ has_image_tag? || has_audio_tag?
6
+ end
7
+
8
+ def has_image_tag?
9
+ @field.value.attached? && @field.value.representable? && @field.is_image
10
+ end
11
+
12
+ def has_audio_tag?
13
+ @field.value.attached? && @field.is_audio
14
+ end
4
15
  end
@@ -1,4 +1,4 @@
1
- <%= index_field_wrapper field: @field do %>
1
+ <%= index_field_wrapper field: @field, flush: true do %>
2
2
  <%= render Avo::Fields::Common::GravatarViewerComponent.new(
3
3
  md5: @field.md5,
4
4
  default: @field.default,
@@ -1,4 +1,4 @@
1
- <%= index_field_wrapper field: @field do %>
1
+ <%= index_field_wrapper field: @field, flush: true do %>
2
2
  <% if @field.display_value %>
3
3
  <div class="text-center text-sm font-semibold w-full leading-none mb-1">
4
4
  <%= @field.value %><%= @field.value_suffix if @field.value_suffix.present? %>
@@ -1,4 +1,4 @@
1
- <td class="min-h-[3rem] px-4 py-3 leading-tight whitespace-nowrap h-full text-slate-800 <%= @classes %>" data-field-id="<%= @field.id %>" data-field-type="<%= @field.type %>">
1
+ <td class="min-h-[3rem] px-3 leading-tight whitespace-nowrap h-full text-slate-800 <%= classes %>" data-field-id="<%= @field.id %>" data-field-type="<%= @field.type %>">
2
2
  <% if @field.value.blank? && @dash_if_blank %>
3
3
 
4
4
  <% else %>
@@ -1,11 +1,22 @@
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, **args)
4
+ def initialize(field: nil, dash_if_blank: true, center_content: false, flush: false, **args)
5
5
  @field = field
6
6
  @dash_if_blank = dash_if_blank
7
7
  @center_content = center_content
8
8
  @classes = args[:class].present? ? args[:class] : ""
9
9
  @args = args
10
+ @flush = flush
11
+ end
12
+
13
+ def classes
14
+ result = @classes
15
+
16
+ unless @flush
17
+ result += " py-3"
18
+ end
19
+
20
+ result
10
21
  end
11
22
  end
@@ -28,7 +28,10 @@
28
28
  <% end %>
29
29
 
30
30
  <% if can_detach? %>
31
- <%= form_with url: helpers.resource_detach_path(params[:resource_name], params[:id], params[:related_name], @resource.model.id), method: :delete, html: {
31
+ <%= form_with url: helpers.resource_detach_path(params[:resource_name], params[:id], params[:related_name], @resource.model.id),
32
+ method: :delete,
33
+ local: true,
34
+ html: {
32
35
  'data-turbo-frame': params[:turbo_frame]
33
36
  } do |form| %>
34
37
  <%= form.button helpers.svg('detach', class: button_classes),
@@ -51,6 +54,7 @@
51
54
  <%= form_with url: helpers.resource_path(model: @resource.model, resource: @resource),
52
55
  method: :delete,
53
56
  class: 'flex items-center',
57
+ local: true,
54
58
  html: {
55
59
  'data-turbo-frame': params[:turbo_frame]
56
60
  } do |form| %>
@@ -3,7 +3,7 @@
3
3
  <%= render partial: 'avo/partials/table_header', locals: {fields: @resource.get_fields(reflection: @reflection)} %>
4
4
  <tbody class="divide-y">
5
5
  <% @resources.each_with_index do |resource, index| %>
6
- <% cache_if Avo.configuration.cache_resources_on_index_view, resource.cache_hash(@parent_model) do %>
6
+ <% cache_if Avo.configuration.cache_resources_on_index_view, resource.cache_hash(@parent_model), expires_in: 1.day do %>
7
7
  <%= render Avo::Index::TableRowComponent.new(resource: resource, reflection: @reflection, parent_model: @parent_model) %>
8
8
  <% end %>
9
9
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <div <%== data_attributes %>>
2
2
  <% if render_header? %>
3
- <div class="bg-white rounded shadow p-4 flex-1 flex flex-col xl:flex-row justify-between mb-6">
3
+ <div class="<%= white_panel_classes %> p-4 flex-1 flex flex-col xl:flex-row justify-between mb-6">
4
4
  <div class="overflow-hidden mr-4">
5
5
  <% if display_breadcrumbs? %>
6
6
  <div class="breadcrumbs truncate mb-2">
@@ -25,7 +25,7 @@
25
25
  </div>
26
26
  <% end %>
27
27
 
28
- <div class="relative bg-white rounded shadow <%= @body_classes %>">
28
+ <div class="relative <%= white_panel_classes %> <%= @body_classes %>">
29
29
  <%= body %>
30
30
  </div>
31
31
 
@@ -33,6 +33,14 @@
33
33
  <%= bare_content %>
34
34
  </div>
35
35
 
36
+ <% if footer_tools.present? %>
37
+ <div class="<%= white_panel_classes %> p-4 flex-1 flex flex-col xl:flex-row justify-between mt-6">
38
+ <div class="flex-1 w-full flex flex-col sm:flex-row xl:justify-end sm:items-end space-y-2 sm:space-y-0 sm:space-x-2 mt-4 xl:mt-0">
39
+ <%= footer_tools %>
40
+ </div>
41
+ </div>
42
+ <% end %>
43
+
36
44
  <div class="flex justify-end w-full">
37
45
  <div>
38
46
  <%= footer %>
@@ -6,6 +6,7 @@ class Avo::PanelComponent < ViewComponent::Base
6
6
  renders_one :tools
7
7
  renders_one :body
8
8
  renders_one :bare_content
9
+ renders_one :footer_tools
9
10
  renders_one :footer
10
11
 
11
12
  def initialize(title: nil, description: nil, body_classes: nil, data: {}, display_breadcrumbs: false, index: nil)
@@ -19,6 +20,10 @@ class Avo::PanelComponent < ViewComponent::Base
19
20
 
20
21
  private
21
22
 
23
+ def white_panel_classes
24
+ 'bg-white rounded shadow'
25
+ end
26
+
22
27
  def data_attributes
23
28
  @data.merge({'panel-index': @index}).map do |key, value|
24
29
  " data-#{key}=\"#{value}\""
@@ -38,4 +43,8 @@ class Avo::PanelComponent < ViewComponent::Base
38
43
  def render_header?
39
44
  @title.present? || description.present? || tools.present? || display_breadcrumbs?
40
45
  end
46
+
47
+ def render_footer_tools?
48
+ footer_tools.present?
49
+ end
41
50
  end
@@ -0,0 +1,4 @@
1
+ <%= hidden_field_tag :via_resource_class, params[:via_resource_class] if params[:via_resource_class] %>
2
+ <%= hidden_field_tag :via_resource_id, params[:via_resource_id] if params[:via_resource_id] %>
3
+ <%= hidden_field_tag :via_relation, params[:via_relation] if params[:via_relation] %>
4
+ <%= hidden_field_tag :referrer, back_path if params[:via_resource_class] %>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::ReferrerParamsComponent < ViewComponent::Base
4
+ attr_reader :back_path
5
+
6
+ def initialize(back_path: nil)
7
+ @back_path = back_path
8
+ end
9
+ end
@@ -4,7 +4,9 @@ class Avo::SidebarComponent < ViewComponent::Base
4
4
  def dashboards
5
5
  return [] if Avo::App.license.lacks_with_trial(:dashboards)
6
6
 
7
- Avo::App.get_dashboards(helpers._current_user)
7
+ Avo::App.get_dashboards(helpers._current_user).select do |dashboard|
8
+ dashboard.is_visible?
9
+ end
8
10
  end
9
11
 
10
12
  def resources
@@ -4,8 +4,9 @@
4
4
  scope: @resource.form_scope,
5
5
  url: helpers.resource_path(model: @resource.model, resource: @resource),
6
6
  method: :put,
7
+ local: true,
7
8
  multipart: true do |form| %>
8
- <%= hidden_field_tag :referrer, back_path if params[:via_resource_class] %>
9
+ <%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
9
10
  <%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true) do |c| %>
10
11
  <% c.tools do %>
11
12
  <%= a_link back_path, icon: 'arrow-left' do %>
@@ -17,6 +18,18 @@
17
18
  <% end %>
18
19
  <% end %>
19
20
  <% end %>
21
+ <% if Avo.configuration.buttons_on_form_footers %>
22
+ <% c.footer_tools do %>
23
+ <%= a_link back_path, icon: 'arrow-left' do %>
24
+ <%= t('avo.cancel').capitalize %>
25
+ <% end %>
26
+ <% if can_see_the_save_button? %>
27
+ <%= a_button color: :green, spinner: true, type: :submit, icon: 'save' do %>
28
+ <%= t('avo.save').capitalize %>
29
+ <% end %>
30
+ <% end %>
31
+ <% end %>
32
+ <% end %>
20
33
  <% c.body do %>
21
34
  <div class="divide-y">
22
35
  <% @resource.get_fields.each_with_index do |field, index| %>
@@ -10,6 +10,7 @@
10
10
  ),
11
11
  local: true,
12
12
  multipart: true do |form| %>
13
+ <%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
13
14
  <%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true) do |c| %>
14
15
  <% c.tools do %>
15
16
  <div class="flex justify-end space-x-2">
@@ -23,6 +24,18 @@
23
24
  <% end %>
24
25
  </div>
25
26
  <% end %>
27
+ <% if Avo.configuration.buttons_on_form_footers %>
28
+ <% c.footer_tools do %>
29
+ <%= a_link back_path, icon: 'arrow-left' do %>
30
+ <%= t('avo.cancel').capitalize %>
31
+ <% end %>
32
+ <% if can_see_the_save_button? %>
33
+ <%= a_button color: :green, spinner: true, type: :submit, icon: 'save' do %>
34
+ <%= t('avo.save').capitalize %>
35
+ <% end %>
36
+ <% end %>
37
+ <% end %>
38
+ <% end %>
26
39
  <% c.body do %>
27
40
  <div class="divide-y">
28
41
  <% @resource.get_fields.each_with_index do |field, index| %>
@@ -33,6 +33,7 @@
33
33
  <% if can_see_the_destroy_button? %>
34
34
  <%= a_button url: helpers.resource_path(model: @resource.model, resource: @resource),
35
35
  method: :delete,
36
+ local: true,
36
37
  title: t('avo.delete_item', item: @resource.model.model_name.name.downcase).capitalize,
37
38
  spinner: true,
38
39
  color: :red,
@@ -28,7 +28,7 @@ module Avo
28
28
  add_flash_types :info, :warning, :success, :error
29
29
 
30
30
  def init_app
31
- Avo::App.init request: request, context: context, root_path: avo.root_path.delete_suffix("/"), current_user: _current_user, view_context: view_context
31
+ Avo::App.init request: request, context: context, root_path: avo.root_path.delete_suffix("/"), current_user: _current_user, view_context: view_context, params: params
32
32
 
33
33
  @license = Avo::App.license
34
34
  end
@@ -7,6 +7,7 @@ module Avo
7
7
  before_action :hydrate_resource
8
8
  before_action :set_model, only: [:show, :edit, :destroy, :update, :order]
9
9
  before_action :set_model_to_fill
10
+ before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update]
10
11
  before_action :fill_model, only: [:create, :update]
11
12
  before_action :authorize_action
12
13
  before_action :reset_pagination_if_filters_changed, only: :index
@@ -91,30 +92,9 @@ module Avo
91
92
  add_breadcrumb t("avo.new").humanize
92
93
  end
93
94
 
94
- def edit
95
- @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
96
-
97
- @page_title = @resource.default_panel_name.to_s
98
-
99
- # If we're accessing this resource via another resource add the parent to the breadcrumbs.
100
- if params[:via_resource_class].present? && params[:via_resource_id].present?
101
- via_resource = Avo::App.get_resource_by_model_name params[:via_resource_class]
102
- via_model = via_resource.class.find_scope.find params[:via_resource_id]
103
- via_resource.hydrate model: via_model
104
-
105
- add_breadcrumb via_resource.plural_name, resources_path(resource: @resource)
106
- add_breadcrumb via_resource.model_title, resource_path(model: via_model, resource: via_resource)
107
- else
108
- add_breadcrumb @resource.plural_name.humanize, resources_path(resource: @resource)
109
- end
110
-
111
- add_breadcrumb @resource.model_title, resource_path(model: @resource.model, resource: @resource)
112
- add_breadcrumb t("avo.edit").humanize
113
- end
114
-
115
95
  def create
116
96
  # model gets instantiated and filled in the fill_model method
117
- saved = @model.save
97
+ saved = save_model
118
98
  @resource.hydrate(model: @model, view: :new, user: _current_user)
119
99
 
120
100
  # This means that the record has been created through another parent record and we need to attach it somehow.
@@ -163,9 +143,12 @@ module Avo
163
143
  end
164
144
  end
165
145
 
146
+ def edit
147
+ end
148
+
166
149
  def update
167
150
  # model gets instantiated and filled in the fill_model method
168
- saved = @model.save
151
+ saved = save_model
169
152
  @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
170
153
 
171
154
  respond_to do |format|
@@ -179,11 +162,14 @@ module Avo
179
162
  end
180
163
 
181
164
  def destroy
182
- @model.destroy!
183
-
184
165
  respond_to do |format|
185
- format.html { redirect_to params[:referrer] || resources_path(resource: @resource, turbo_frame: params[:turbo_frame], view_type: params[:view_type]), notice: t("avo.resource_destroyed", attachment_class: @attachment_class) }
186
- format.json { head :no_content }
166
+ if destroy_model
167
+ format.html { redirect_to params[:referrer] || resources_path(resource: @resource, turbo_frame: params[:turbo_frame], view_type: params[:view_type]), notice: t("avo.resource_destroyed", attachment_class: @attachment_class) }
168
+ else
169
+ error_message = @errors.present? ? @errors.first : t("avo.failed")
170
+
171
+ format.html { redirect_back fallback_location: params[:referrer] || resources_path(resource: @resource, turbo_frame: params[:turbo_frame], view_type: params[:view_type]), error: error_message }
172
+ end
187
173
  end
188
174
  end
189
175
 
@@ -204,6 +190,31 @@ module Avo
204
190
 
205
191
  private
206
192
 
193
+ def save_model
194
+ perform_action_and_record_errors do
195
+ @model.save!
196
+ end
197
+ end
198
+
199
+ def destroy_model
200
+ perform_action_and_record_errors do
201
+ @model.destroy!
202
+ end
203
+ end
204
+
205
+ def perform_action_and_record_errors(&block)
206
+ begin
207
+ succeeded = block.call
208
+ rescue => exception
209
+ # In case there's an error somewhere else than the model
210
+ # Example: When you save a license that should create a user for it and creating that user throws and error.
211
+ # Example: When you Try to delete a record and has a foreign key constraint.
212
+ @errors = Array.wrap(exception.message)
213
+ end
214
+
215
+ succeeded
216
+ end
217
+
207
218
  def model_params
208
219
  request_params = params.require(model_param_key).permit(permitted_params)
209
220
 
@@ -327,5 +338,24 @@ module Avo
327
338
  def applied_filters_cache_key
328
339
  "avo.base_controller.#{@resource.model_key}.applied_filters"
329
340
  end
341
+
342
+ def set_edit_title_and_breadcrumbs
343
+ @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
344
+ @page_title = @resource.default_panel_name.to_s
345
+ # If we're accessing this resource via another resource add the parent to the breadcrumbs.
346
+ if params[:via_resource_class].present? && params[:via_resource_id].present?
347
+ via_resource = Avo::App.get_resource_by_model_name params[:via_resource_class]
348
+ via_model = via_resource.class.find_scope.find params[:via_resource_id]
349
+ via_resource.hydrate model: via_model
350
+
351
+ add_breadcrumb via_resource.plural_name, resources_path(resource: @resource)
352
+ add_breadcrumb via_resource.model_title, resource_path(model: via_model, resource: via_resource)
353
+ else
354
+ add_breadcrumb @resource.plural_name.humanize, resources_path(resource: @resource)
355
+ end
356
+
357
+ add_breadcrumb @resource.model_title, resource_path(model: @resource.model, resource: @resource)
358
+ add_breadcrumb t("avo.edit").humanize
359
+ end
330
360
  end
331
361
  end
@@ -22,7 +22,7 @@ module Avo
22
22
  def set_dashboard
23
23
  @dashboard = Avo::App.get_dashboard_by_id params[:dashboard_id]
24
24
 
25
- raise ActionController::RoutingError.new("Not Found") if @dashboard.nil?
25
+ raise ActionController::RoutingError.new("Not Found") if @dashboard.nil? || @dashboard.is_hidden?
26
26
  end
27
27
  end
28
28
  end
@@ -10,7 +10,11 @@ module Avo
10
10
  def index
11
11
  raise ActionController::BadRequest.new("This feature requires the pro license https://avohq.io/purchase/pro") if App.license.lacks_with_trial(:global_search)
12
12
 
13
- render json: search_resources(Avo::App.resources)
13
+ resources = Avo::App.resources.reject do |resource|
14
+ resource.class.search_hide_from_global_search
15
+ end
16
+
17
+ render json: search_resources(resources)
14
18
  end
15
19
 
16
20
  def show
@@ -71,12 +71,13 @@ document.addEventListener('turbo:frame-load', () => {
71
71
  initTippy()
72
72
  })
73
73
 
74
- document.addEventListener('turbo:before-fetch-response', (e) => {
74
+ document.addEventListener('turbo:before-fetch-response', async (e) => {
75
75
  if (e.detail.fetchResponse.response.status === 500) {
76
- const id = e.srcElement.getAttribute('id')
77
- e.srcElement.src = `${window.Avo.configuration.root_path}/failed_to_load?turbo_frame=${id}`
76
+ const { id, src } = e.target
77
+ e.target.src = `${window.Avo.configuration.root_path}/failed_to_load?turbo_frame=${id}&src=${src}`
78
78
  }
79
79
  })
80
+
80
81
  document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading'))
81
82
  document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading'))
82
83
  document.addEventListener('turbo:submit-end', () => document.body.classList.remove('turbo-loading'))
@@ -10,6 +10,7 @@
10
10
  <%= form_with model: @model,
11
11
  scope: 'fields',
12
12
  url: "#{@resource.records_path}/actions/#{@action.param_id}",
13
+ local: true,
13
14
  data: @action.class.form_data_attributes do |form|
14
15
  %>
15
16
  <%= render Avo::ModalComponent.new do |c| %>
@@ -1,6 +1,7 @@
1
1
  <%= turbo_frame_tag 'attach_modal' do %>
2
2
  <%= form_with scope: 'fields',
3
3
  url: "#{avo.root_path}resources/#{params[:resource_name]}/#{params[:id]}/#{params[:related_name]}/",
4
+ local: true,
4
5
  data: {
5
6
  'turbo-frame': '_top'
6
7
  } do |form| %>
@@ -1,3 +1,23 @@
1
1
  <%= turbo_frame_wrap(params[:turbo_frame]) do %>
2
- <%= render 'avo/partials/failed_state' %>
2
+ <%
3
+ classes = 'absolute inset-auto left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2'
4
+ label = t 'avo.failed_to_load'
5
+ %>
6
+ <div class="relative flex-1 py-12">
7
+ <div class="relative block text-gray-300 h-40 w-full">
8
+ <%= svg 'avocado', class: "#{classes} h-20 text-gray-400" %>
9
+ <%= svg 'code', class: "#{classes} h-8 -ml-20 -mt-12" %>
10
+ <%= svg 'fire', class: "#{classes} h-8 -ml-10 -mt-24" %>
11
+ <%= svg 'color-swatch', class: "#{classes} h-8 ml-8 -mt-24" %>
12
+ <%= svg 'globe', class: "#{classes} h-8 ml-20 -mt-12" %>
13
+ <%= svg 'library', class: "#{classes} h-8 -ml-20 mt-4" %>
14
+ <%= svg 'photograph', class: "#{classes} h-8 ml-20 mt-4" %>
15
+ </div>
16
+ <div class="relative block text-center text-lg text-gray-400 font-semibold -mt-10"><%= label %> <span class="border-b-2 border-dashed"><%= params[:turbo_frame].to_s.humanize.downcase if params[:turbo_frame].present? %></span> frame</div>
17
+ </div>
18
+ <% if Rails.env.development? %>
19
+ <div class="text-center text-sm w-full">
20
+ This is not an issue with Avo. Click <%= link_to 'this link', params[:src], target: :_blank %> to see why this frame failed to load.
21
+ </div>
22
+ <% end %>
3
23
  <% end %>
@@ -42,6 +42,13 @@
42
42
 
43
43
  <%= turbo_frame_tag 'alerts', class: "fixed inset-0 bottom-0 flex flex-col space-y-4 items-end justify-right px-4 py-6 sm:p-6 justify-end z-50 pointer-events-none" do %>
44
44
  <%= render Avo::FlashAlertsComponent.new flashes: flash %>
45
+
46
+ <% # In case we have other general error messages %>
47
+ <% if @errors.present? %>
48
+ <% @errors.each do |message| %>
49
+ <%= render Avo::AlertComponent.new :error, message %>
50
+ <% end %>
51
+ <% end %>
45
52
  <% end %>
46
53
 
47
54
  <%= render partial: "avo/partials/scripts" %>