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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -3
- data/README.md +7 -11
- data/app/components/avo/button_component.rb +1 -1
- data/app/components/avo/fields/common/progress_bar_component.html.erb +8 -0
- data/app/components/avo/fields/common/progress_bar_component.rb +15 -0
- data/app/components/avo/fields/common/single_file_viewer_component.html.erb +1 -2
- data/app/components/avo/fields/common/single_file_viewer_component.rb +12 -0
- data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -6
- data/app/components/avo/fields/progress_bar_field/show_component.html.erb +1 -6
- data/app/components/avo/filters_component.html.erb +1 -1
- data/app/components/avo/filters_component.rb +11 -1
- data/app/components/avo/index/resource_table_component.html.erb +37 -2
- data/app/components/avo/index/resource_table_component.rb +15 -1
- data/app/components/avo/item_switcher_component.rb +1 -1
- data/app/components/avo/tab_group_component.html.erb +4 -4
- data/app/components/avo/tab_group_component.rb +7 -1
- data/app/components/avo/tab_switcher_component.html.erb +28 -8
- data/app/components/avo/tab_switcher_component.rb +3 -1
- data/app/components/avo/views/resource_index_component.html.erb +3 -6
- data/app/components/avo/views/resource_index_component.rb +3 -2
- data/app/controllers/avo/actions_controller.rb +17 -4
- data/app/helpers/avo/resources_helper.rb +1 -1
- data/app/javascript/js/controllers/action_controller.js +11 -2
- data/app/javascript/js/controllers/item_select_all_controller.js +42 -4
- data/app/javascript/js/controllers/tabs_controller.js +13 -9
- data/app/views/avo/actions/show.html.erb +2 -1
- data/app/views/avo/base/index.html.erb +1 -0
- data/app/views/avo/partials/_table_header.html.erb +1 -1
- data/app/views/avo/partials/_view_toggle_button.html.erb +2 -2
- data/lib/avo/concerns/has_fields.rb +2 -2
- data/lib/avo/configuration.rb +2 -0
- data/lib/avo/fields/base_field.rb +1 -1
- data/lib/avo/fields/concerns/is_required.rb +15 -1
- data/lib/avo/services/encryption_service.rb +33 -0
- data/lib/avo/tab_group.rb +3 -1
- data/lib/avo/tab_group_builder.rb +4 -4
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/initializer/avo.tt +1 -0
- data/lib/generators/avo/templates/locales/avo.en.yml +5 -1
- data/lib/generators/avo/templates/locales/avo.fr.yml +4 -0
- data/lib/generators/avo/templates/locales/avo.nb-NO.yml +5 -0
- data/lib/generators/avo/templates/locales/avo.pt-BR.yml +5 -0
- data/lib/generators/avo/templates/locales/avo.ro.yml +6 -0
- data/public/avo-assets/avo.css +44 -4
- data/public/avo-assets/avo.js +43 -43
- data/public/avo-assets/avo.js.map +2 -2
- metadata +7 -5
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58033ed16e041a89bae5bc13843079be1c9daa0f85004bf810645e24960e7526
|
4
|
+
data.tar.gz: d401c18ba4edb14825be275b68851c0b9e32e3ff2427a79a27b5bafb377f4d51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
**
|
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 ⭐️
|
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
|
-
|
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/
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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: :
|
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:
|
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:
|
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:
|
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
|
-
|
2
|
-
<div class="
|
3
|
-
|
4
|
-
|
5
|
-
|
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) ?
|
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
|
-
|
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][:
|
15
|
-
|
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(
|
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
|
-
|
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#
|
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(
|
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
|
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
|
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
|
-
|
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
|
}
|