avo 2.13.6.pre.2 → 2.14.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -3
  3. data/README.md +7 -11
  4. data/app/components/avo/button_component.rb +1 -1
  5. data/app/components/avo/fields/common/progress_bar_component.html.erb +8 -0
  6. data/app/components/avo/fields/common/progress_bar_component.rb +15 -0
  7. data/app/components/avo/fields/common/single_file_viewer_component.html.erb +1 -2
  8. data/app/components/avo/fields/common/single_file_viewer_component.rb +12 -0
  9. data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -6
  10. data/app/components/avo/fields/progress_bar_field/show_component.html.erb +1 -6
  11. data/app/components/avo/filters_component.html.erb +1 -1
  12. data/app/components/avo/filters_component.rb +11 -1
  13. data/app/components/avo/index/resource_table_component.html.erb +37 -2
  14. data/app/components/avo/index/resource_table_component.rb +15 -1
  15. data/app/components/avo/item_switcher_component.rb +1 -1
  16. data/app/components/avo/tab_group_component.html.erb +4 -4
  17. data/app/components/avo/tab_group_component.rb +7 -1
  18. data/app/components/avo/tab_switcher_component.html.erb +28 -8
  19. data/app/components/avo/tab_switcher_component.rb +3 -1
  20. data/app/components/avo/views/resource_index_component.html.erb +3 -6
  21. data/app/components/avo/views/resource_index_component.rb +3 -2
  22. data/app/controllers/avo/actions_controller.rb +17 -4
  23. data/app/helpers/avo/resources_helper.rb +1 -1
  24. data/app/javascript/js/controllers/action_controller.js +11 -2
  25. data/app/javascript/js/controllers/item_select_all_controller.js +42 -4
  26. data/app/javascript/js/controllers/tabs_controller.js +13 -9
  27. data/app/views/avo/actions/show.html.erb +2 -1
  28. data/app/views/avo/base/index.html.erb +1 -0
  29. data/app/views/avo/partials/_table_header.html.erb +1 -1
  30. data/app/views/avo/partials/_view_toggle_button.html.erb +2 -2
  31. data/lib/avo/concerns/has_fields.rb +2 -2
  32. data/lib/avo/configuration.rb +2 -0
  33. data/lib/avo/fields/base_field.rb +1 -1
  34. data/lib/avo/fields/concerns/is_required.rb +15 -1
  35. data/lib/avo/services/encryption_service.rb +33 -0
  36. data/lib/avo/tab_group.rb +3 -1
  37. data/lib/avo/tab_group_builder.rb +4 -4
  38. data/lib/avo/version.rb +1 -1
  39. data/lib/generators/avo/templates/initializer/avo.tt +1 -0
  40. data/lib/generators/avo/templates/locales/avo.en.yml +5 -1
  41. data/lib/generators/avo/templates/locales/avo.fr.yml +4 -0
  42. data/lib/generators/avo/templates/locales/avo.nb-NO.yml +5 -0
  43. data/lib/generators/avo/templates/locales/avo.pt-BR.yml +5 -0
  44. data/lib/generators/avo/templates/locales/avo.ro.yml +6 -0
  45. data/public/avo-assets/avo.css +44 -4
  46. data/public/avo-assets/avo.js +43 -43
  47. data/public/avo-assets/avo.js.map +2 -2
  48. metadata +7 -5
  49. data/app/views/avo/partials/_tabs_toggle.html.erb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df7e0432320ebd04724b2f38afaf35d5bfe652aa0c68e70b17efcf648b4d98bd
4
- data.tar.gz: 0bc309a2c8f0e756304504dea4ababec52defc34fc9bb24aed6547c1f09af6dd
3
+ metadata.gz: 58033ed16e041a89bae5bc13843079be1c9daa0f85004bf810645e24960e7526
4
+ data.tar.gz: d401c18ba4edb14825be275b68851c0b9e32e3ff2427a79a27b5bafb377f4d51
5
5
  SHA512:
6
- metadata.gz: c1537d80c8714711ad1d181f27153c60762ec9a685703c28870c914b4e57af5c6c300da433247bc459e4f42555866fdb83f4bff74e226cd320f8175c6b3d3831
7
- data.tar.gz: 6112b7a2b1e673938bcd7a0f048d0083eabe57b092710c40ddf20193e1efb3103d5fbcc9aa8ecdc29476fb805310a50ea5c806e3a25a0e8aff1a578b4921a64f
6
+ metadata.gz: 32d0cda8da582e3b3104a0f676c968d11ed28cd222b26014651fec4405bdd14b868c39685f257d473c4d41c2eab5e0c1923924df097e82cc8ace744150f46bab
7
+ data.tar.gz: 502abd413467a87d504ae3d82dffcee741fdc96a76a5458b8501826be5da494bf6e054af8a65d1a403a641e7475455a55acb1863e229ea6ad8a26037e255f731
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.13.6.pre.2)
4
+ avo (2.14.0)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -256,8 +256,6 @@ GEM
256
256
  nokogiri (1.13.7)
257
257
  mini_portile2 (~> 2.8.0)
258
258
  racc (~> 1.4)
259
- nokogiri (1.13.7-x86_64-linux)
260
- racc (~> 1.4)
261
259
  orm_adapter (0.5.0)
262
260
  pagy (5.10.1)
263
261
  activesupport
data/README.md CHANGED
@@ -7,12 +7,13 @@
7
7
 
8
8
  ![](./public/avo-assets/logo-on-white.png)
9
9
 
10
- **Configuration-based, no-maintenance, extendable Ruby on Rails admin**
10
+ **Ruby on Rails application building framework.**
11
11
 
12
12
  Avo is a beautiful next-generation framework that empowers you, the developer, to create fantastic admin panels for your Ruby on Rails apps with the flexibility to fit your needs as you grow.
13
13
 
14
14
  ## Get started
15
15
 
16
+ ⚡️ **Install**: [docs.avohq.io/2.0/installation](https://docs.avohq.io/2.0/installation.html)\
16
17
  ✨ **Website**: [avohq.io](https://avohq.io)\
17
18
  📚 **Documentation**: [docs.avohq.io](https://docs.avohq.io)\
18
19
  🗺 **Roadmap**: [GitHub Roadmap](https://github.com/orgs/avo-hq/projects/3)\
@@ -43,24 +44,19 @@ Avo is a beautiful next-generation framework that empowers you, the developer, t
43
44
  - **Localization** - Have it available in any language you need. [docs](https://docs.avohq.io/2.0/localization.html)
44
45
  - **No asset pipeline pollution** - Bring your own asset pipeline. [docs](https://docs.avohq.io/2.0/custom-asset-pipeline.html)
45
46
  - **Mobile interface** - Check your data on the go from any mobile device.
47
+ - **Tabbed interface** - Conditionally show the data you need
48
+ - **Menu builder** - Group and surface information as you need to
49
+ - **Branding** - Make it look
46
50
 
47
51
  ## Some of the things we're going focusing on next
48
52
 
49
- Theming ⭐️  notifications ⭐️  Resource segmentation ⭐️  filterable fields ⭐️  inline editing ⭐️  multilingual records ⭐️  associations in tabs ⭐️  keyboard shortcuts ⭐️  track resource changes ⭐️  smart resource generation ⭐️  sidebar editor ⭐️  live resources ⭐️  tags field ⭐️  columns view ⭐️  list view ⭐️  custom action items ⭐️  command bar
53
+ Theming ⭐️  notifications ⭐️  Resource segmentation ⭐️  filterable fields ⭐️  inline editing ⭐️  multilingual records ⭐️  keyboard shortcuts ⭐️  track resource changes ⭐️  smart resource generation ⭐️  live resources ⭐️  columns view ⭐️  list view ⭐️  custom action items ⭐️  command bar ⭐️   use fields DSL in your custom views
50
54
 
51
55
  For more up-to-date info check out our 🗺 [Roadmap](https://github.com/orgs/avo-hq/projects/3).
52
56
 
53
57
  # Installation
54
- Add this line to your application's `Gemfile`:
55
58
 
56
- ```ruby
57
- gem 'avo'
58
- ```
59
-
60
- And then execute:
61
- ```bash
62
- $ bundle install
63
- ```
59
+ Use this RailsBytes template to get started quick `rails app:template LOCATION='https://avohq.io/app-template'`. If you need a more detailed guide, follow [this page](https://docs.avohq.io/2.0/installation.html).
64
60
 
65
61
  # Contributing
66
62
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # A button/link can have the following settings:
4
- # style: primary/secondary/ternary
4
+ # style: primary/outline/text
5
5
  # size: :xs :sm, :md, :lg
6
6
  class Avo::ButtonComponent < ViewComponent::Base
7
7
  def initialize(path = nil, size: :md, style: :outline, color: :gray, icon: nil, icon_class: "", is_link: false, rounded: true, compact: false, **args)
@@ -0,0 +1,8 @@
1
+ <div class="flex items-center">
2
+ <% if display_value %>
3
+ <div class="text-right text-sm font-semibold leading-none mr-2 min-w-[2rem]">
4
+ <%= value %><%= value_suffix if value_suffix.present? %>
5
+ </div>
6
+ <% end %>
7
+ <progress min="0" max="<%= max %>" value="<%= value %>" class="block w-full min-w-[6rem]"></progress>
8
+ </div>
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Fields::Common::ProgressBarComponent < ViewComponent::Base
4
+ attr_reader :value
5
+ attr_reader :display_value
6
+ attr_reader :value_suffix
7
+ attr_reader :max
8
+
9
+ def initialize(value:, display_value: false, value_suffix: nil, max: 100)
10
+ @value = value
11
+ @display_value = display_value
12
+ @value_suffix = value_suffix
13
+ @max = max
14
+ end
15
+ end
@@ -21,8 +21,7 @@
21
21
  <div class="flex space-x-2">
22
22
  <div class="flex">
23
23
  <% if @resource.authorization.authorize_action(:download_attachments?, raise_exception: false) %>
24
- <!-- <%= file.inspect %> -->
25
- <%= a_link file.service_url(disposition: :attachment),
24
+ <%= a_link file.url(disposition: :attachment),
26
25
  icon: 'heroicons/outline/download',
27
26
  color: :primary,
28
27
  download: true,
@@ -40,4 +40,16 @@ class Avo::Fields::Common::SingleFileViewerComponent < ViewComponent::Base
40
40
  rescue
41
41
  false
42
42
  end
43
+
44
+ def render?
45
+ record_persisted?
46
+ end
47
+
48
+ # If model is not persistent blob is automatically destroyed otherwise it can be "lost" on storage
49
+ def record_persisted?
50
+ return true if @resource.model.persisted?
51
+
52
+ ActiveStorage::Blob.destroy(file.blob_id) if file.blob_id.present?
53
+ false
54
+ end
43
55
  end
@@ -1,8 +1,3 @@
1
1
  <%= index_field_wrapper field: @field, resource: @resource, flush: true do %>
2
- <% if @field.display_value %>
3
- <div class="text-center text-sm font-semibold w-full leading-none mb-1">
4
- <%= @field.value %><%= @field.value_suffix if @field.value_suffix.present? %>
5
- </div>
6
- <% end %>
7
- <progress min="0" max="<%= @field.max %>" value="<%= @field.value %>" class="block w-24"></progress>
2
+ <%= render Avo::Fields::Common::ProgressBarComponent.new value: @field.value, display_value: @field.display_value, value_suffix: @field.value_suffix, max: @field.max %>
8
3
  <% end %>
@@ -1,8 +1,3 @@
1
1
  <%= show_field_wrapper field: @field, resource: @resource, index: @index do %>
2
- <% if @field.display_value %>
3
- <div class="text-center text-sm font-semibold w-full leading-none mb-1">
4
- <%= @field.value %><%= @field.value_suffix if @field.value_suffix.present? %>
5
- </div>
6
- <% end %>
7
- <progress min="0" max="<%= @field.max %>" value="<%= @field.value %>" class="block w-full"></progress>
2
+ <%= render Avo::Fields::Common::ProgressBarComponent.new value: @field.value, display_value: @field.display_value, value_suffix: @field.value_suffix, max: @field.max %>
8
3
  <% end %>
@@ -22,7 +22,7 @@
22
22
  <% end %>
23
23
  <div class="p-4 border-gray-300 border-t">
24
24
  <% if params[:filters].present? %>
25
- <%= a_link helpers.resources_path(resource: @resource, filters: nil, keep_query_params: true), color: :gray, size: :sm, class: 'w-full justify-center' do %>
25
+ <%= a_link reset_path, data: {turbo_frame: params[:turbo_frame]}, color: :gray, size: :sm, class: 'w-full justify-center' do %>
26
26
  <%= t('avo.reset_filters') %>
27
27
  <% end %>
28
28
  <% else %>
@@ -3,13 +3,23 @@
3
3
  class Avo::FiltersComponent < ViewComponent::Base
4
4
  include Avo::ApplicationHelper
5
5
 
6
- def initialize(filters: [], resource: nil, applied_filters: [])
6
+ def initialize(filters: [], resource: nil, applied_filters: [], parent_model: nil)
7
7
  @filters = filters
8
8
  @resource = resource
9
9
  @applied_filters = applied_filters
10
+ @parent_model = parent_model
10
11
  end
11
12
 
12
13
  def render?
13
14
  @filters.present?
14
15
  end
16
+
17
+ def reset_path
18
+ # If come from a association page
19
+ if @parent_model.present?
20
+ helpers.related_resources_path(@parent_model, @parent_model, filters: nil, keep_query_params: true)
21
+ else
22
+ helpers.resources_path(resource: @resource, filters: nil, keep_query_params: true)
23
+ end
24
+ end
15
25
  end
@@ -1,5 +1,40 @@
1
- <div class="w-full ">
2
- <table class="w-full px-4 bg-white" data-resource-name='<%= @resource.model_key %>' data-controller='item-select-all'>
1
+ <div class="w-full relative"
2
+ data-controller="item-select-all"
3
+ data-resource-name="<%= @resource.model_key %>"
4
+ data-item-select-all-selected-all-value="false"
5
+ data-item-select-all-selected-all-query-value="<%= encrypted_query %>"
6
+ data-selected-resources-name="<%= @resource.model_key %>"
7
+ data-selected-resources="[]"
8
+ >
9
+ <% if pagy.pages > 1 %>
10
+ <div class="absolute z-30 w-full ml-10 pt-px hidden" data-item-select-all-target="selectAllOvelay">
11
+ <div class="bg-white flex items-center h-9 mt-0.5">
12
+ <div class="mt-1.5" data-item-select-all-target="unselectedMessage">
13
+ <%= t "avo.x_records_selected_from_a_total_of_x_html", selected: pagy.in, count: pagy.count %>
14
+ <%= a_link request.url,
15
+ size: :xs,
16
+ color: :primary,
17
+ class: "relative -top-[0.1rem] ml-2 cursor-pointer text-primary-500",
18
+ data: {action: "click->item-select-all#selectAll"} do
19
+ %>
20
+ <%= t "avo.select_all_matching" %>
21
+ <% end %>
22
+ </div>
23
+ <div class="mt-1.5" data-item-select-all-target="selectedMessage" class="hidden">
24
+ <%= t "avo.x_records_selected_from_all_pages_html", count: pagy.count %>
25
+ <%= a_link request.url,
26
+ size: :xs,
27
+ color: :primary,
28
+ class: "relative -top-[0.1rem] ml-2 cursor-pointer text-primary-500",
29
+ data: {action: "click->item-select-all#selectAll"} do
30
+ %>
31
+ <%= t("avo.undo").humanize %>
32
+ <% end %>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ <% end %>
37
+ <table class="w-full px-4 bg-white">
3
38
  <%= render partial: 'avo/partials/table_header', locals: {fields: @resource.get_fields(reflection: @reflection, only_root: true)} %>
4
39
  <tbody class="divide-y">
5
40
  <% @resources.each_with_index do |resource, index| %>
@@ -1,11 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Index::ResourceTableComponent < ViewComponent::Base
4
- def initialize(resources: nil, resource: nil, reflection: nil, parent_model: nil, parent_resource: nil)
4
+ include Avo::ApplicationHelper
5
+ attr_reader :pagy
6
+
7
+ def initialize(resources: nil, resource: nil, reflection: nil, parent_model: nil, parent_resource: nil, pagy: nil, query: nil)
5
8
  @resources = resources
6
9
  @resource = resource
7
10
  @reflection = reflection
8
11
  @parent_model = parent_model
9
12
  @parent_resource = parent_resource
13
+ @pagy = pagy
14
+ @query = query
15
+ end
16
+
17
+ def encrypted_query
18
+ return if @query.nil?
19
+
20
+ Avo::Services::EncryptionService.encrypt(
21
+ message: @query.to_sql,
22
+ purpose: :select_all
23
+ )
10
24
  end
11
25
  end
@@ -38,7 +38,7 @@ class Avo::ItemSwitcherComponent < Avo::BaseComponent
38
38
  end
39
39
 
40
40
  def tab_group_component
41
- Avo::TabGroupComponent.new resource: @resource, group: item.hydrate(view: view), index: index, params: params, form: form, view: view
41
+ Avo::TabGroupComponent.new resource: @resource, group: item.hydrate(view: view), index: index, params: params, form: form, view: view, tabs_style: item.style
42
42
  end
43
43
 
44
44
  def field_component
@@ -15,7 +15,7 @@
15
15
  data: {
16
16
  # Add a marker to know if we already loaded a turbo frame
17
17
  loaded: tab.name == active_tab_name,
18
- tabs_target: :tab,
18
+ tabs_target: :tabPanel,
19
19
  tab_id: tab.name,
20
20
  }
21
21
  }
@@ -32,17 +32,17 @@
32
32
  end
33
33
 
34
34
  if should_lazy_load
35
- args[:src] = helpers.resource_path(resource: @resource, model: @resource.model, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id)
35
+ args[:src] = helpers.resource_path(resource: resource, model: resource.model, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id)
36
36
  args[:loading] = :lazy
37
37
  end
38
38
  %>
39
39
  <%= turbo_frame_tag tab.turbo_frame_id(parent: @group), **args do %>
40
40
  <div class="border rounded-lg p-2 -mx-2 -my-2 lg:p-4 lg:-mx-4 lg:-my-4 space-y-4">
41
- <%= render Avo::TabSwitcherComponent.new resource: @resource, current_tab: tab, group: group, active_tab_name: active_tab_name, view: view %>
41
+ <%= render Avo::TabSwitcherComponent.new resource: resource, current_tab: tab, group: group, active_tab_name: active_tab_name, view: view, style: tabs_style %>
42
42
  <% if !should_lazy_load && !tab.empty? %>
43
43
  <div class="space-y-12">
44
44
  <% tab.visible_items.each do |item| %>
45
- <%= render Avo::ItemSwitcherComponent.new resource: @resource, item: item, index: index, form: form, view: @view %>
45
+ <%= render Avo::ItemSwitcherComponent.new resource: resource, item: item, index: index, form: form, view: @view %>
46
46
  <% end %>
47
47
  </div>
48
48
  <% end %>
@@ -5,14 +5,16 @@ class Avo::TabGroupComponent < Avo::BaseComponent
5
5
  attr_reader :index
6
6
  attr_reader :view
7
7
  attr_reader :form
8
+ attr_reader :resource
8
9
 
9
- def initialize(resource:, group:, index:, form:, params:, view:)
10
+ def initialize(resource:, group:, index:, form:, params:, view:, tabs_style:)
10
11
  @resource = resource
11
12
  @group = group
12
13
  @index = index
13
14
  @form = form
14
15
  @params = params
15
16
  @view = view
17
+ @tabs_style = tabs_style
16
18
 
17
19
  @group.index = index
18
20
  end
@@ -48,4 +50,8 @@ class Avo::TabGroupComponent < Avo::BaseComponent
48
50
  tab.name.to_s == active_tab_name.to_s
49
51
  end
50
52
  end
53
+
54
+ def tabs_style
55
+ @tabs_style || Avo.configuration.tabs_style
56
+ end
51
57
  end
@@ -1,21 +1,41 @@
1
- <div class="flex overflow-auto mac-styled-scrollbar" data-target="tab-switcher">
2
- <div class="button-group">
3
- <% visible_items.each do |tab| %>
4
- <%= a_link tab_path(tab),
5
- color: selected?(tab) ? :gray : :primary,
1
+ <% if style == :tabs %>
2
+ <div class="flex overflow-auto mac-styled-scrollbar" data-target="tab-switcher" data-style="tabs">
3
+ <div class="button-group">
4
+ <% visible_items.each do |tab| %>
5
+ <%= a_link tab_path(tab),
6
+ color: selected?(tab) ? :primary : :gray ,
6
7
  rounded: false,
7
8
  size: :sm,
8
- class: selected?(tab) ? ' bg-gray-100 border-gray-300' : ' z-20',
9
+ class: "active:outline-0 #{selected?(tab) ? "z-20" : "bg-gray-100 border-gray-300"}",
9
10
  title: tab.description,
10
11
  data: {
11
12
  tippy: tab.description.present? ? 'tooltip' : '',
12
- control: "view-type-toggle-#{tab.name}",
13
13
  selected: selected?(tab),
14
14
  action: 'click->tabs#changeTab',
15
15
  tabs_id_param: tab.name
16
16
  } do %>
17
+ <%= tab.name %>
18
+ <% end %>
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+ <% else %>
23
+ <div class="flex flex-wrap gap-2" data-target="tab-switcher" data-style="pills">
24
+ <% visible_items.each do |tab| %>
25
+ <%= a_link tab_path(tab),
26
+ color: selected?(tab) ? :primary : :gray,
27
+ style: selected?(tab) ? :outline : :text,
28
+ size: :sm,
29
+ class: selected?(tab) ? "z-20" : "",
30
+ title: tab.description,
31
+ data: {
32
+ tippy: tab.description.present? ? 'tooltip' : '',
33
+ selected: selected?(tab),
34
+ action: 'click->tabs#changeTab',
35
+ tabs_id_param: tab.name
36
+ } do %>
17
37
  <%= tab.name %>
18
38
  <% end %>
19
39
  <% end %>
20
40
  </div>
21
- </div>
41
+ <% end %>
@@ -9,14 +9,16 @@ class Avo::TabSwitcherComponent < Avo::BaseComponent
9
9
  attr_reader :current_tab
10
10
  attr_reader :tabs
11
11
  attr_reader :view
12
+ attr_reader :style
12
13
 
13
- def initialize(resource:, group:, current_tab:, active_tab_name:, view:)
14
+ def initialize(resource:, group:, current_tab:, active_tab_name:, view:, style:)
14
15
  @active_tab_name = active_tab_name
15
16
  @resource = resource
16
17
  @group = group
17
18
  @current_tab = current_tab
18
19
  @tabs = group.items
19
20
  @view = view
21
+ @style = style
20
22
  end
21
23
 
22
24
  def tab_path(tab)
@@ -28,10 +28,7 @@
28
28
  <% end %>
29
29
  <% end %>
30
30
  <% c.body do %>
31
- <div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= 'hidden' if @resource.search_query.nil? && @filters.empty? && available_view_types.count <= 1 %>"
32
- data-selected-resources-name="<%= @resource.model_key %>"
33
- data-selected-resources="[]"
34
- >
31
+ <div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= 'hidden' if @resource.search_query.nil? && @filters.empty? && available_view_types.count <= 1 %>">
35
32
  <% unless hide_search_input %>
36
33
  <div class="flex items-center px-4 w-64">
37
34
  <%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.route_key, via_reflection: via_reflection} %>
@@ -42,7 +39,7 @@
42
39
  <% end %>
43
40
  <% if @filters.present? || available_view_types.count > 1 %>
44
41
  <div class="justify-self-end flex justify-start xs:justify-end items-center px-4 space-x-3">
45
- <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource, applied_filters: @applied_filters %>
42
+ <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource, applied_filters: @applied_filters, parent_model: @parent_model %>
46
43
  <%= render partial: 'avo/partials/view_toggle_button', locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: @turbo_frame } if available_view_types.count > 1 %>
47
44
  </div>
48
45
  <% end %>
@@ -51,7 +48,7 @@
51
48
  <% if @resources.present? %>
52
49
  <div class="w-full overflow-auto flex flex-col mt-0 mac-styled-scrollbar">
53
50
  <div class="relative flex-1 flex">
54
- <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model, parent_resource: @parent_resource)) %>
51
+ <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
55
52
  </div>
56
53
  </div>
57
54
  <% else %>
@@ -16,7 +16,8 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
16
16
  turbo_frame: "",
17
17
  parent_model: nil,
18
18
  parent_resource: nil,
19
- applied_filters: []
19
+ applied_filters: [],
20
+ query: nil
20
21
  )
21
22
  @resource = resource
22
23
  @resources = resources
@@ -31,6 +32,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
31
32
  @parent_resource = parent_resource
32
33
  @applied_filters = applied_filters
33
34
  @view = :index
35
+ @query = query
34
36
  end
35
37
 
36
38
  def title
@@ -146,5 +148,4 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
146
148
  id: @parent_model.id
147
149
  }
148
150
  end
149
-
150
151
  end
@@ -11,10 +11,10 @@ module Avo
11
11
  end
12
12
 
13
13
  def handle
14
- resource_ids = action_params[:fields][:resource_ids].split(",")
15
- models = @resource.class.find_scope.find resource_ids
14
+ resource_ids = action_params[:fields][:avo_resource_ids].split(",")
15
+ @selected_query = action_params[:fields][:avo_selected_query]
16
16
 
17
- fields = action_params[:fields].except("resource_ids")
17
+ fields = action_params[:fields].except(:avo_resource_ids, :avo_selected_query)
18
18
 
19
19
  args = {
20
20
  fields: fields,
@@ -22,7 +22,13 @@ module Avo
22
22
  resource: resource
23
23
  }
24
24
 
25
- args[:models] = models unless @action.standalone
25
+ unless @action.standalone
26
+ args[:models] = if @selected_query.present?
27
+ @resource.model_class.find_by_sql decrypted_query
28
+ else
29
+ @resource.class.find_scope.find resource_ids
30
+ end
31
+ end
26
32
 
27
33
  performed_action = @action.handle_action(**args)
28
34
 
@@ -90,5 +96,12 @@ module Avo
90
96
  message[:type] != :silent
91
97
  end
92
98
  end
99
+
100
+ def decrypted_query
101
+ Avo::Services::EncryptionService.decrypt(
102
+ message: @selected_query,
103
+ purpose: :select_all
104
+ )
105
+ end
93
106
  end
94
107
  end
@@ -48,7 +48,7 @@ module Avo
48
48
  def item_selector_input(floating: false, size: :md)
49
49
  "<input type='checkbox'
50
50
  class='mx-3 rounded #{"absolute inset-auto left-0 mt-2 z-10 hidden group-hover:block checked:block" if floating} #{size.to_sym == :lg ? "w-5 h-5" : "w-4 h-4"}'
51
- data-action='input->item-selector#toggle input->item-select-all#update'
51
+ data-action='input->item-selector#toggle input->item-select-all#selectRow'
52
52
  data-item-select-all-target='itemCheckbox'
53
53
  name='#{t "avo.select_item"}'
54
54
  title='#{t "avo.select_item"}'
@@ -2,11 +2,16 @@ import { Controller } from '@hotwired/stimulus'
2
2
  import { castBoolean } from '../helpers/cast_boolean'
3
3
 
4
4
  export default class extends Controller {
5
- static targets = ['controllerDiv', 'resourceIds', 'form']
5
+ static targets = ['controllerDiv', 'resourceIds', 'form', 'selectedAllQuery']
6
6
 
7
7
  connect() {
8
8
  this.resourceIdsTarget.value = this.resourceIds
9
9
 
10
+ // This value is picked up from the DOM so we check true/false as strings
11
+ if (this.selectionOptions.itemSelectAllSelectedAllValue === 'true') {
12
+ this.selectedAllQueryTarget.value = this.selectionOptions.itemSelectAllSelectedAllQueryValue
13
+ }
14
+
10
15
  if (this.noConfirmation) {
11
16
  this.formTarget.submit()
12
17
  } else {
@@ -24,9 +29,13 @@ export default class extends Controller {
24
29
 
25
30
  get resourceIds() {
26
31
  try {
27
- return JSON.parse(document.querySelector(`[data-selected-resources-name="${this.resourceName}"]`).dataset.selectedResources)
32
+ return JSON.parse(this.selectionOptions.selectedResources)
28
33
  } catch (error) {
29
34
  return []
30
35
  }
31
36
  }
37
+
38
+ get selectionOptions() {
39
+ return document.querySelector(`[data-selected-resources-name="${this.resourceName}"]`).dataset
40
+ }
32
41
  }
@@ -1,21 +1,59 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ['itemCheckbox', 'checkbox']
4
+ static targets = ['itemCheckbox', 'checkbox', 'selectAllOvelay', 'unselectedMessage', 'selectedMessage']
5
+
6
+ static values = {
7
+ selectedAll: Boolean,
8
+ selectedAllQuery: String,
9
+ }
5
10
 
6
11
  connect() {
7
12
  this.resourceName = this.element.dataset.resourceName
8
13
  }
9
14
 
10
15
  toggle(event) {
11
- const value = !!event.target.checked
16
+ const checked = !!event.target.checked
12
17
  document.querySelectorAll(`[data-controller="item-selector"][data-resource-name="${this.resourceName}"] input[type=checkbox]`)
13
- .forEach((checkbox) => checkbox.checked != value && checkbox.click())
18
+ .forEach((checkbox) => checkbox.checked !== checked && checkbox.click())
19
+
20
+ this.selectAllOverlay(checked)
21
+
22
+ // When de-selecting everything, ensure the selectAll toggle is false and hide overlay.
23
+ if (!checked) {
24
+ this.resetUnselected()
25
+ }
14
26
  }
15
27
 
16
- update() {
28
+ selectRow() {
17
29
  let allSelected = true
30
+ // eslint-disable-next-line no-return-assign
18
31
  this.itemCheckboxTargets.forEach((checkbox) => allSelected = allSelected && checkbox.checked)
19
32
  this.checkboxTarget.checked = allSelected
33
+
34
+ this.selectAllOverlay(allSelected)
35
+ this.resetUnselected()
36
+ }
37
+
38
+ selectAll(event) {
39
+ event.preventDefault()
40
+
41
+ this.selectedAllValue = !this.selectedAllValue
42
+ this.unselectedMessageTarget.classList.toggle('hidden')
43
+ this.selectedMessageTarget.classList.toggle('hidden')
44
+ }
45
+
46
+ resetUnselected() {
47
+ this.selectedAllValue = false
48
+ this.unselectedMessageTarget.classList.remove('hidden')
49
+ this.selectedMessageTarget.classList.add('hidden')
50
+ }
51
+
52
+ selectAllOverlay(show) {
53
+ if (show) {
54
+ this.selectAllOvelayTarget.classList.remove('hidden')
55
+ } else {
56
+ this.selectAllOvelayTarget.classList.add('hidden')
57
+ }
20
58
  }
21
59
  }