avo 3.0.0.pre11 → 3.0.0.pre13

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/assets/stylesheets/avo.base.css +1 -1
  4. data/app/components/avo/actions_component.html.erb +1 -1
  5. data/app/components/avo/actions_component.rb +40 -16
  6. data/app/components/avo/alert_component.html.erb +1 -1
  7. data/app/components/avo/field_wrapper_component.html.erb +2 -2
  8. data/app/components/avo/fields/common/heading_component.html.erb +1 -1
  9. data/app/components/avo/fields/markdown_field/edit_component.html.erb +3 -3
  10. data/app/components/avo/fields/markdown_field/show_component.html.erb +3 -3
  11. data/app/components/avo/fields/trix_field/edit_component.html.erb +1 -1
  12. data/app/components/avo/fields/trix_field/show_component.html.erb +1 -1
  13. data/app/components/avo/index/field_wrapper_component.html.erb +1 -1
  14. data/app/components/avo/index/grid_item_component.html.erb +9 -35
  15. data/app/components/avo/index/grid_item_component.rb +36 -10
  16. data/app/components/avo/panel_component.html.erb +1 -1
  17. data/app/components/avo/profile_item_component.html.erb +17 -2
  18. data/app/components/avo/profile_item_component.rb +13 -1
  19. data/app/components/avo/resource_component.rb +1 -0
  20. data/app/components/avo/sidebar_profile_component.html.erb +27 -27
  21. data/app/components/avo/views/resource_edit_component.html.erb +1 -1
  22. data/app/components/avo/views/resource_index_component.rb +1 -1
  23. data/app/components/avo/views/resource_show_component.html.erb +1 -1
  24. data/app/controllers/avo/actions_controller.rb +12 -8
  25. data/app/controllers/avo/associations_controller.rb +1 -1
  26. data/app/controllers/avo/base_controller.rb +15 -3
  27. data/app/controllers/avo/home_controller.rb +1 -1
  28. data/app/controllers/avo/search_controller.rb +7 -11
  29. data/app/javascript/js/controllers/fields/{simple_mde_controller.js → easy_mde_controller.js} +3 -3
  30. data/app/javascript/js/controllers/search_controller.js +3 -1
  31. data/app/javascript/js/controllers.js +2 -2
  32. data/app/views/avo/actions/show.html.erb +2 -1
  33. data/app/views/avo/partials/_profile_menu_extra.html.erb +2 -0
  34. data/lib/avo/app.rb +1 -1
  35. data/lib/avo/base_action.rb +8 -1
  36. data/lib/avo/base_resource.rb +81 -112
  37. data/lib/avo/concerns/breadcrumbs.rb +2 -2
  38. data/lib/avo/concerns/filters_session_handler.rb +5 -4
  39. data/lib/avo/concerns/has_description.rb +23 -0
  40. data/lib/avo/concerns/has_items.rb +8 -8
  41. data/lib/avo/configuration.rb +6 -2
  42. data/lib/avo/engine.rb +5 -0
  43. data/lib/avo/fields/base_field.rb +0 -4
  44. data/lib/avo/fields/belongs_to_field.rb +14 -8
  45. data/lib/avo/fields/has_base_field.rb +1 -1
  46. data/lib/avo/resources/controls/actions_list.rb +2 -1
  47. data/lib/avo/services/debug_service.rb +1 -1
  48. data/lib/avo/version.rb +1 -1
  49. data/lib/generators/avo/eject_generator.rb +1 -0
  50. data/lib/generators/avo/install_generator.rb +0 -1
  51. data/lib/generators/avo/resource_generator.rb +4 -1
  52. data/lib/generators/avo/templates/initializer/avo.tt +1 -1
  53. data/lib/generators/avo/templates/resource/resource.tt +3 -4
  54. data/lib/tasks/avo_tasks.rake +27 -0
  55. data/public/avo-assets/avo.base.css +273 -138
  56. data/public/avo-assets/avo.base.js +245 -217
  57. data/public/avo-assets/avo.base.js.map +3 -3
  58. metadata +5 -4
  59. data/lib/avo/grid_collector.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a1674354633c9b8113bfdeba7a9e53ac54bd3f75af78c9492655c485c43a0ff
4
- data.tar.gz: 53bd8bb7f50267e0b4756164fb85ffa1fe013a1c7642c4417dabd9d2320e4b57
3
+ metadata.gz: cbf17f6b86663450788938a16475eba907f1a85a400f72d0efd58b59baea131c
4
+ data.tar.gz: be6d1682124dfbc7a0fdbff863b80caab538b7fdf28768d463169f2acd2fd086
5
5
  SHA512:
6
- metadata.gz: 857ed520648c9cbe1d2361c11bd6f48266c25c1c8fc0175b0843de23edbb7518eff352f8495c95bb6238a7d4e93e7a003fa1d86467d4999524baa6c1cb19042d
7
- data.tar.gz: 32bf4c5d55be3aeae79411fd30df1a550eb8b417fd76680cee40925b4a1a9265c5a7b9d241182d638b333666fea00c95d811d9bc868a8f80b32aba3457f48e21
6
+ metadata.gz: 6c47148a510c188098e524e70c1f7782fdb6fdb0ad8592f6d1e7cdf44fb175c6973db23f4672e2361be971639a5943c44309ea69409967ebe427fc558d90367c
7
+ data.tar.gz: f065601a617923ac787e0d468276d379f2cfd8798f34c9bdd604ed3a8df7c0722ded2f1013a121b0d69c9d75c85ae0b38ed7a61c745509c83264ca1502f8c9b3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (3.0.0.pre11)
4
+ avo (3.0.0.pre13)
5
5
  actionview (>= 6.1)
6
6
  active_link_to
7
7
  activerecord (>= 6.1)
@@ -1,4 +1,4 @@
1
- @import './../../../node_modules/simplemde/dist/simplemde.min.css';
1
+ @import './../../../node_modules/easymde/dist/easymde.min.css';
2
2
  @import './../../../node_modules/tippy.js/dist/tippy.css';
3
3
  @import './../../../node_modules/tippy.js/themes/light.css';
4
4
  @import './../../../node_modules/flatpickr/dist/flatpickr.css';
@@ -32,7 +32,7 @@
32
32
  >
33
33
  <div data-target="actions-list" class="w-full space divide-y">
34
34
  <% actions.each_with_index do |action, index| %>
35
- <%= link_to action_path(action.param_id),
35
+ <%= link_to action_path(action),
36
36
  data: {
37
37
  action_name: action.action_name,
38
38
  'turbo-frame': 'actions_show',
@@ -4,11 +4,12 @@ class Avo::ActionsComponent < ViewComponent::Base
4
4
  include Avo::ApplicationHelper
5
5
  attr_reader :label, :size, :as_row_control
6
6
 
7
- def initialize(actions: [], resource: nil, view: nil, exclude: [], style: :outline, color: :primary, label: nil, size: :md, as_row_control: false)
7
+ def initialize(actions: [], resource: nil, view: nil, exclude: [], include: [], style: :outline, color: :primary, label: nil, size: :md, as_row_control: false)
8
8
  @actions = actions || []
9
9
  @resource = resource
10
10
  @view = view
11
11
  @exclude = exclude
12
+ @include = include
12
13
  @color = color
13
14
  @style = style
14
15
  @label = label || I18n.t("avo.actions")
@@ -21,19 +22,25 @@ class Avo::ActionsComponent < ViewComponent::Base
21
22
  end
22
23
 
23
24
  def actions
24
- @actions.reject { |action| action.class.in?(@exclude) }
25
+ if @exclude.present?
26
+ @actions.reject { |action| action.class.in?(@exclude) }
27
+ elsif @include.present?
28
+ @actions.select { |action| action.class.in?(@include) }
29
+ else
30
+ @actions
31
+ end
25
32
  end
26
33
 
27
34
  # When running an action for one record we should do it on a special path.
28
35
  # We do that so we get the `record` param inside the action so we can prefill fields.
29
- def action_path(id)
30
- return single_record_path(id) if as_row_control
31
- return many_records_path(id) unless @resource.has_record_id?
36
+ def action_path(action)
37
+ return single_record_path(action) if as_row_control
38
+ return many_records_path(action) unless @resource.has_record_id?
32
39
 
33
40
  if on_record_page?
34
- single_record_path id
41
+ single_record_path action
35
42
  else
36
- many_records_path id
43
+ many_records_path action
37
44
  end
38
45
  end
39
46
 
@@ -54,17 +61,34 @@ class Avo::ActionsComponent < ViewComponent::Base
54
61
  !on_record_page?
55
62
  end
56
63
 
57
- def single_record_path(id)
58
- Avo::Services::URIService.parse(@resource.record_path)
59
- .append_paths("actions")
60
- .append_query(action_id: id)
61
- .to_s
64
+ def single_record_path(action)
65
+ action_url(action, @resource.record_path)
66
+ end
67
+
68
+ def many_records_path(action)
69
+ action_url(action, @resource.records_path)
62
70
  end
63
71
 
64
- def many_records_path(id)
65
- Avo::Services::URIService.parse(@resource.records_path)
72
+ def action_url(action, path)
73
+ Avo::Services::URIService.parse(path)
66
74
  .append_paths("actions")
67
- .append_query(action_id: id)
68
- .to_s
75
+ .append_query(
76
+ {
77
+ action_id: action.param_id,
78
+ arguments: encrypted_arguments(action)
79
+ }.compact
80
+ ).to_s
81
+ end
82
+
83
+ # Encrypt the arguments so we can pass them as a query param.
84
+ # EncryptionService can generate special characters that can break the URL.
85
+ # We use Base64 to encode the encrypted string so we can safely pass it as a query param and don't break the URL.
86
+ def encrypted_arguments(action)
87
+ return if action.arguments.blank?
88
+
89
+ Base64.encode64 Avo::Services::EncryptionService.encrypt(
90
+ message: action.arguments,
91
+ purpose: :action_arguments
92
+ )
69
93
  end
70
94
  end
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
  <div class="ml-3 w-0 flex-1 pt-0.5">
12
12
  <p class="text-sm leading-5 font-semibold">
13
- <%== message %>
13
+ <%= sanitize message %>
14
14
  </p>
15
15
  </div>
16
16
  <div class="ml-4 flex-shrink-0 flex items-center">
@@ -24,13 +24,13 @@
24
24
  <div class="text-red-600 mt-2 text-sm"><%= record.errors.full_messages_for(field.id).to_sentence %></div>
25
25
  <% end %>
26
26
  <% if help.present? %>
27
- <div class="text-gray-600 mt-2 text-sm"><%== help %></div>
27
+ <div class="text-gray-600 mt-2 text-sm"><%= sanitize help %></div>
28
28
  <% end %>
29
29
  <% end %>
30
30
  </div>
31
31
  </div>
32
32
  <% if params[:avo_debug].present? %>
33
33
  <!-- Raw value: -->
34
- <!-- <%== field.value.inspect %> -->
34
+ <!-- <%= sanitize field.value.inspect %> -->
35
35
  <% end %>
36
36
  <% end %>
@@ -3,7 +3,7 @@
3
3
  <% if empty %>
4
4
  <% elsif value.present? %>
5
5
  <% if as_html %>
6
- <%== value %>
6
+ <%= sanitize value %>
7
7
  <% else %>
8
8
  <div class="font-semibold uppercase"><%= value %></div>
9
9
  <% end %>
@@ -1,10 +1,10 @@
1
1
  <%= field_wrapper **field_wrapper_args, full_width: true do %>
2
- <div data-controller="simple-mde">
2
+ <div data-controller="easy-mde">
3
3
  <%= @form.text_area @field.id,
4
- class: classes("w-full js-has-simple-mde-editor"),
4
+ class: classes("w-full js-has-easy-mde-editor"),
5
5
  data: {
6
6
  view: view,
7
- 'simple-mde-target': 'element',
7
+ 'easy-mde-target': 'element',
8
8
  'component-options': @field.options.to_json,
9
9
  },
10
10
  disabled: disabled?,
@@ -1,10 +1,10 @@
1
1
  <%= field_wrapper **field_wrapper_args, full_width: true do %>
2
- <div data-controller="simple-mde">
2
+ <div data-controller="easy-mde">
3
3
  <%= text_area_tag @field.id, @field.value,
4
- class: helpers.input_classes('w-full js-has-simple-mde-editor'),
4
+ class: helpers.input_classes('w-full js-has-easy-mde-editor'),
5
5
  placeholder: @field.placeholder,
6
6
  disabled: disabled?,
7
- 'data-simple-mde-target': 'element',
7
+ 'data-easy-mde-target': 'element',
8
8
  'data-component-options': @field.options.to_json,
9
9
  'data-view': :show %>
10
10
  </div>
@@ -20,7 +20,7 @@
20
20
  },
21
21
  input: trix_id,
22
22
  placeholder: @field.placeholder do %>
23
- <%== @field.value %>
23
+ <%= sanitize @field.value %>
24
24
  <% end %>
25
25
  <%= @form.text_area @field.id,
26
26
  class: classes("w-full hidden"),
@@ -8,7 +8,7 @@
8
8
  <%= link_to t('avo.show_content'), 'javascript:void(0);', class: 'font-bold inline-block', data: { action: 'click->hidden-input#showContent' } %>
9
9
  <% end %>
10
10
  <div class="<%= content_classes %> " data-hidden-input-target="content">
11
- <%== @field.value %>
11
+ <%= sanitize @field.value %>
12
12
  </div>
13
13
  </div>
14
14
  <% end %>
@@ -19,6 +19,6 @@
19
19
  <% end %>
20
20
  <% if params[:avo_debug].present? %>
21
21
  <!-- Raw value: -->
22
- <!-- <%== @field.value.inspect %> -->
22
+ <!-- <%= sanitize @field.value.inspect %> -->
23
23
  <% end %>
24
24
  <% end %>
@@ -5,45 +5,19 @@
5
5
  data-resource-name="<%= @resource.class.to_s %>"
6
6
  data-record-id="<%= @resource.record.id %>"
7
7
  >
8
- <%= 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 %>
9
- <% if @resource.record_selector %>
10
- <%== item_selector_input floating: true, size: :lg %>
11
- <% end %>
12
- <% if cover.blank? %>
13
- <%= link_to resource_view_path do %>
14
- <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
15
- <% end %>
16
- <% elsif cover.respond_to?(:to_image) && cover.to_image.present? %>
17
- <%= link_to_if cover.link_to_resource, image_tag(cover.to_image, class: 'absolute h-full w-full object-cover'), resource_view_path, class: 'absolute h-full w-full object-cover', title: title.value %>
18
- <% elsif cover.type == 'file' %>
19
- <% if cover.value.attached? && cover.value.representable? %>
20
- <%= 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 %>
21
- <% else %>
22
- <%= link_to resource_view_path do %>
23
- <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
24
- <% end %>
25
- <% end %>
26
- <% elsif cover.value.present? %>
27
- <%= link_to_if cover.link_to_resource, image_tag(cover.value, class: 'absolute h-full w-full object-cover'), resource_view_path, class: 'absolute h-full w-full object-cover', title: title.value %>
28
- <% else %>
29
- <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
30
- <% end %>
8
+ <%= content_tag :div,
9
+ class: "relative w-full pb-3/4 rounded-t overflow-hidden #{html(:cover, :classes)}",
10
+ style: html(:cover, :style) do %>
11
+ <%== item_selector_input(floating: true, size: :lg) if @resource.record_selector%>
12
+ <%= render_cover %>
31
13
  <% end %>
32
- <%= content_tag :div, class: "grid grid-cols-1 place-content-between p-4 h-full" do %>
14
+ <div class="grid grid-cols-1 place-content-between p-4 h-full">
33
15
  <div class="mb-4 h-full flex flex-col space-between">
34
- <% if title.present? %>
35
- <%= 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 %>
36
- <%= link_to_if title.link_to_resource, title.value, resource_view_path %>
37
- <% end %>
38
- <% end %>
39
- <% if body.present? %>
40
- <%= 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 %>
41
- <%= body.value %>
42
- <% end %>
43
- <% end %>
16
+ <%= render_title %>
17
+ <%= render_body %>
44
18
  </div>
45
19
  <div class="w-full place-self-end">
46
20
  <%= render(Avo::Index::ResourceControlsComponent.new(resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, view_type: :grid, actions: actions)) %>
47
21
  </div>
48
- <% end %>
22
+ </div>
49
23
  </div>
@@ -2,30 +2,26 @@
2
2
 
3
3
  class Avo::Index::GridItemComponent < Avo::BaseComponent
4
4
  include Avo::ResourcesHelper
5
+ include Avo::Fields::Concerns::HasHTMLAttributes
5
6
 
6
7
  attr_reader :parent_resource, :actions
7
8
 
8
9
  def initialize(resource: nil, reflection: nil, parent_record: nil, parent_resource: nil, actions: nil)
9
10
  @resource = resource
10
11
  @reflection = reflection
11
- @grid_fields = resource.get_grid_fields
12
12
  @parent_record = parent_record
13
13
  @parent_resource = parent_resource
14
14
  @actions = actions
15
+ @card = Avo::ExecutionContext.new(target: resource.grid_view[:card], resource: resource, record: resource.record).handle
16
+ @whole_html = Avo::ExecutionContext.new(target: resource.grid_view[:html], resource: resource, record: resource.record).handle
15
17
  end
16
18
 
17
19
  private
18
20
 
19
- def cover
20
- @grid_fields.cover_field
21
- end
22
-
23
- def title
24
- @grid_fields.title_field
25
- end
21
+ def html(element, type)
22
+ return "" if @whole_html.nil? || (@html = @whole_html[element]).nil?
26
23
 
27
- def body
28
- @grid_fields.body_field
24
+ get_html(type, view: :index, element: :wrapper)
29
25
  end
30
26
 
31
27
  def resource_view_path
@@ -40,4 +36,34 @@ class Avo::Index::GridItemComponent < Avo::BaseComponent
40
36
 
41
37
  helpers.resource_view_path(record: @resource.record, resource: parent_or_child_resource, **args)
42
38
  end
39
+
40
+ def render_cover
41
+ return link_to_cover if @card[:cover_url].present?
42
+
43
+ link_to resource_view_path do
44
+ render Avo::Index::GridCoverEmptyStateComponent.new
45
+ end
46
+ end
47
+
48
+ def link_to_cover
49
+ classes = "absolute h-full w-full object-cover"
50
+
51
+ link_to image_tag(@card[:cover_url], class: classes), resource_view_path, class: classes, title: @card[:title]
52
+ end
53
+
54
+ def render_title
55
+ return if @card[:title].blank?
56
+
57
+ content_tag :div, class: "grid font-semibold leading-tight text-lg mb-2 #{html(:title, :classes)}", style: html(:title, :style) do
58
+ link_to @card[:title], resource_view_path
59
+ end
60
+ end
61
+
62
+ def render_body
63
+ return if @card[:body].blank?
64
+
65
+ content_tag :div, class: "text-sm break-words text-gray-500 #{html(:body, :classes)}", style: html(:body, :style) do
66
+ @card[:body]
67
+ end
68
+ end
43
69
  end
@@ -25,7 +25,7 @@
25
25
  <% end %>
26
26
  <% if body? %>
27
27
  <div data-target="panel-body" class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:gap-4 w-full">
28
- <div class="relative flex-1 <% if sidebar? %> w-2/3 <% else %> w-full <% end %>">
28
+ <div class="relative flex-1 w-full <% if sidebar? %> sm:w-2/3 <% end %>">
29
29
  <% # The body is wrapped inside another div in order to avoid long & tall panels next to sidebars when the sidebar taller. %>
30
30
  <div class="relative <%= white_panel_classes %> <%= @body_classes %>">
31
31
  <%= body %>
@@ -1,3 +1,18 @@
1
- <%= link_to path, class: "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-gray-800 font-semibold hover:bg-primary-100 block px-4 py-1 w-full py-3 text-center rounded w-full", target: target, title: title do %>
2
- <%= helpers.svg(icon, class: 'h-4 mr-1') if icon.present? %> <%= label %>
1
+ <% if method.present? %>
2
+ <%= button_to path,
3
+ form_class: "flex-1 flex",
4
+ class: button_classes,
5
+ target: target,
6
+ title: title,
7
+ method: method,
8
+ params: params do %>
9
+ <%= helpers.svg(icon, class: 'h-4 mr-1') if icon.present? %> <%= label %>
10
+ <% end %>
11
+ <% else %>
12
+ <%= link_to path,
13
+ class: button_classes,
14
+ target: target,
15
+ title: title do %>
16
+ <%= helpers.svg(icon, class: 'h-4 mr-1') if icon.present? %> <%= label %>
17
+ <% end %>
3
18
  <% end %>
@@ -6,17 +6,29 @@ class Avo::ProfileItemComponent < ViewComponent::Base
6
6
  attr_reader :path
7
7
  attr_reader :active
8
8
  attr_reader :target
9
+ attr_reader :method
10
+ attr_reader :params
11
+ attr_reader :classes
9
12
 
10
- def initialize(label: nil, icon: nil, path: nil, active: :inclusive, target: nil, title: nil)
13
+ def initialize(label: nil, icon: nil, path: nil, active: :inclusive, target: nil, title: nil, method: nil, params: {}, classes: "")
11
14
  @label = label
12
15
  @icon = icon
13
16
  @path = path
14
17
  @active = active
15
18
  @target = target
16
19
  @title = title
20
+ @method = method
21
+ @params = params
22
+ @classes = classes
17
23
  end
18
24
 
19
25
  def title
20
26
  @title || @label
21
27
  end
28
+
29
+ private
30
+
31
+ def button_classes
32
+ "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-gray-800 font-semibold hover:bg-primary-100 block px-4 py-1 w-full py-3 text-center rounded w-full"
33
+ end
22
34
  end
@@ -165,6 +165,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
165
165
  resource: @resource,
166
166
  view: @view,
167
167
  exclude: actions_list.exclude,
168
+ include: actions_list.include,
168
169
  style: actions_list.style,
169
170
  color: actions_list.color,
170
171
  label: actions_list.label,
@@ -16,32 +16,32 @@
16
16
  <% end %>
17
17
  </div>
18
18
  </div>
19
- <% if can_destroy_user? %>
20
- <div class="relative" data-controller="toggle">
21
- <a class="flex items-center h-full cursor-pointer" data-control="profile-dots" data-action="click->toggle#togglePanel">
22
- <%= helpers.svg 'three-dots', class: 'h-4' %>
23
- </a>
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 z-40"
26
- data-toggle-target="panel"
27
- data-transition-enter="transition ease-out duration-100"
28
- data-transition-enter-start="transform opacity-0 translate-y-1"
29
- data-transition-enter-end="transform opacity-100 translate-y-0"
30
- data-transition-leave="transition ease-in duration-75"
31
- data-transition-leave-start="transform opacity-100 translate-y-0"
32
- data-transition-leave-end="transform opacity-0 translate-y-1"
33
- >
34
- <% if Avo.plugin_manager.installed?(:avo_menu) && Avo::App.has_profile_menu? %>
35
- <div class="text-black space-y-4">
36
- <% Avo::App.profile_menu.items.each do |item| %>
37
- <% if item.is_a? AvoMenu::Link %>
38
- <%= render Avo::ProfileItemComponent.new label: item.name, path: item.path, icon: item.icon %>
39
- <% end %>
19
+ <div class="relative" data-controller="toggle">
20
+ <a class="flex items-center h-full cursor-pointer" data-control="profile-dots" data-action="click->toggle#togglePanel">
21
+ <%= helpers.svg 'three-dots', class: 'h-4' %>
22
+ </a>
23
+ <div
24
+ 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"
25
+ data-toggle-target="panel"
26
+ data-transition-enter="transition ease-out duration-100"
27
+ data-transition-enter-start="transform opacity-0 translate-y-1"
28
+ data-transition-enter-end="transform opacity-100 translate-y-0"
29
+ data-transition-leave="transition ease-in duration-75"
30
+ data-transition-leave-start="transform opacity-100 translate-y-0"
31
+ data-transition-leave-end="transform opacity-0 translate-y-1"
32
+ >
33
+ <% if Avo.plugin_manager.installed?(:avo_menu) && Avo::App.has_profile_menu? %>
34
+ <div class="text-black space-y-4">
35
+ <% Avo::App.profile_menu.items.each do |item| %>
36
+ <% if item.is_a? AvoMenu::Link %>
37
+ <%= render Avo::ProfileItemComponent.new label: item.name, path: item.path, icon: item.icon %>
40
38
  <% end %>
41
- </div>
42
- <% end %>
43
- <%# Example link below %>
44
- <%#= render Avo::ProfileItemComponent.new label: 'Profile', path: '/profile', icon: 'user-circle' %>
39
+ <% end %>
40
+ </div>
41
+ <% end %>
42
+ <%# Example link below %>
43
+ <%#= render Avo::ProfileItemComponent.new label: 'Profile', path: '/profile', icon: 'user-circle' %>
44
+ <% if can_destroy_user? %>
45
45
  <%= button_to helpers.main_app.send(destroy_user_session_path),
46
46
  form: { "data-turbo" => "false" },
47
47
  method: :delete,
@@ -52,7 +52,7 @@
52
52
  form_class: 'flex-1' do %>
53
53
  <%= helpers.svg 'logout', class: 'h-4 mr-1' %> <%= t('avo.sign_out') %>
54
54
  <% end %>
55
- </div>
55
+ <% end %>
56
56
  </div>
57
- <% end %>
57
+ </div>
58
58
  </div>
@@ -18,7 +18,7 @@
18
18
  multipart: true do |form| %>
19
19
  <%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
20
20
  <%= content_tag :div, class: 'space-y-12' do %>
21
- <%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
21
+ <%= render Avo::PanelComponent.new(name: title, description: @resource.description, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
22
22
  <% c.with_tools do %>
23
23
  <% @resource.render_edit_controls.each do |control| %>
24
24
  <%= render_control control %>
@@ -121,7 +121,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
121
121
  return
122
122
  end
123
123
 
124
- @resource.resource_description
124
+ @resource.description
125
125
  end
126
126
 
127
127
  def show_search_input
@@ -7,7 +7,7 @@
7
7
  selected_resources: [@resource.record.id],
8
8
  **@resource.stimulus_data_attributes
9
9
  } do %>
10
- <%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
10
+ <%= render Avo::PanelComponent.new(name: title, description: @resource.description, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
11
11
  <% c.with_tools do %>
12
12
  <% @resource.render_show_controls.each do |control| %>
13
13
  <%= render_control control %>
@@ -56,7 +56,7 @@ module Avo
56
56
  resource: @resource,
57
57
  user: _current_user,
58
58
  view: @view,
59
- arguments: @resource.get_action_arguments(action_class)
59
+ arguments: decrypted_arguments || {}
60
60
  )
61
61
  end
62
62
 
@@ -82,13 +82,7 @@ module Avo
82
82
  flash_messages messages
83
83
 
84
84
  if response[:type] == :redirect
85
- path = response[:path]
86
-
87
- if path.respond_to? :call
88
- path = instance_eval(&path)
89
- end
90
-
91
- redirect_to path
85
+ redirect_to Avo::ExecutionContext.new(target: response[:path]).handle
92
86
  elsif response[:type] == :reload
93
87
  redirect_back fallback_location: resources_path(resource: @resource)
94
88
  end
@@ -119,6 +113,16 @@ module Avo
119
113
  )
120
114
  end
121
115
 
116
+ def decrypted_arguments
117
+ arguments = params[:arguments] || params.dig(:fields, :arguments)
118
+ return if arguments.blank?
119
+
120
+ Avo::Services::EncryptionService.decrypt(
121
+ message: Base64.decode64(arguments),
122
+ purpose: :action_arguments
123
+ )
124
+ end
125
+
122
126
  def flash_messages(messages)
123
127
  messages.each do |message|
124
128
  flash[message[:type]] = message[:body]
@@ -54,7 +54,7 @@ module Avo
54
54
  end
55
55
 
56
56
  @options = query.all.map do |record|
57
- [record.send(@attachment_resource.class.title), record.id]
57
+ [@attachment_resource.hydrate(record: record).record_title, record.id]
58
58
  end
59
59
  end
60
60
  end
@@ -47,7 +47,7 @@ module Avo
47
47
  field_id = @index_params[:sort_by].to_sym
48
48
  field = @resource.get_field_definitions.find { |field| field.id == field_id }
49
49
  @query = if field&.sortable.is_a?(Proc)
50
- field.sortable.call(@query, @index_params[:sort_direction])
50
+ Avo::ExecutionContext.new(target: field.sortable, query: @query, direction: @index_params[:sort_direction]).handle
51
51
  else
52
52
  @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
53
53
  end
@@ -313,8 +313,20 @@ module Avo
313
313
  @index_params[:sort_direction] = params[:sort_direction] || :desc
314
314
 
315
315
  # View types
316
- @index_params[:view_type] = params[:view_type] || @resource.default_view_type || Avo.configuration.default_view_type
317
- @index_params[:available_view_types] = @resource.available_view_types
316
+ available_view_types = @resource.available_view_types
317
+ @index_params[:available_view_types] = available_view_types
318
+
319
+ @index_params[:view_type] = if params[:view_type].present?
320
+ params[:view_type]
321
+ elsif available_view_types.size == 1
322
+ available_view_types.first
323
+ else
324
+ @resource.default_view_type || Avo.configuration.default_view_type
325
+ end
326
+
327
+ if available_view_types.exclude? @index_params[:view_type].to_sym
328
+ raise "View type '#{@index_params[:view_type]}' is unavailable for #{@resource.class}."
329
+ end
318
330
  end
319
331
 
320
332
  def set_filters
@@ -14,7 +14,7 @@ module Avo
14
14
  redirect_to computed_path
15
15
  elsif !Rails.env.development?
16
16
  @page_title = "Get started"
17
- resource = Avo::App.resources.min_by { |resource| resource.model_key }
17
+ resource = Avo::Current.app.resource_manager.all.min_by { |resource| resource.model_key }
18
18
  redirect_to resources_path(resource: resource)
19
19
  end
20
20
  end