avo 2.5.2.pre.7 → 2.6.1.pre.2
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 +2 -0
- data/Gemfile.lock +5 -1
- data/app/assets/builds/action_cable.js +2 -0
- data/app/assets/builds/action_cable.js.map +7 -0
- data/app/assets/builds/avo.css +175 -122
- data/app/assets/builds/avo.js +63 -63
- data/app/assets/builds/avo.js.map +3 -3
- data/app/assets/stylesheets/avo.css +33 -1
- data/app/assets/stylesheets/css/search.css +1 -1
- data/app/assets/svgs/heroicons/solid/user-remove.svg +1 -1
- data/app/components/avo/actions_component.html.erb +3 -2
- data/app/components/avo/alert_component.html.erb +1 -1
- data/app/components/avo/alert_component.rb +24 -5
- data/app/components/avo/button_component.rb +50 -17
- data/app/components/avo/card_component.rb +12 -0
- data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +3 -0
- data/app/components/avo/fields/concerns/item_labels.rb +40 -0
- data/app/components/avo/fields/tags_field/edit_component.html.erb +1 -1
- data/app/components/avo/fields/tags_field/index_component.html.erb +1 -5
- data/app/components/avo/fields/tags_field/index_component.rb +2 -0
- data/app/components/avo/fields/tags_field/show_component.rb +3 -7
- data/app/components/avo/fields/tags_field/tag_component.html.erb +1 -1
- data/app/components/avo/filters_component.html.erb +1 -1
- data/app/components/avo/index/field_wrapper_component.html.erb +1 -1
- data/app/components/avo/index/grid_cover_empty_state_component.html.erb +1 -1
- data/app/components/avo/index/table_row_component.html.erb +1 -1
- data/app/components/avo/paginator_component.html.erb +2 -2
- data/app/components/avo/panel_component.html.erb +3 -3
- data/app/components/avo/panel_component.rb +1 -1
- data/app/components/avo/resource_component.rb +50 -0
- data/app/components/avo/sidebar/group_component.html.erb +4 -2
- data/app/components/avo/sidebar/heading_component.html.erb +1 -1
- data/app/components/avo/sidebar/link_component.html.erb +1 -1
- data/app/components/avo/sidebar/link_component.rb +1 -1
- data/app/components/avo/sidebar_component.html.erb +5 -13
- data/app/components/avo/sidebar_profile_component.html.erb +1 -1
- data/app/components/avo/views/resource_edit_component.html.erb +28 -3
- data/app/components/avo/views/resource_edit_component.rb +4 -6
- data/app/components/avo/views/resource_index_component.html.erb +17 -9
- data/app/components/avo/views/resource_new_component.html.erb +8 -2
- data/app/components/avo/views/resource_show_component.html.erb +16 -6
- data/app/components/avo/views/resource_show_component.rb +0 -45
- data/app/controllers/avo/actions_controller.rb +23 -8
- data/app/controllers/avo/associations_controller.rb +3 -3
- data/app/controllers/avo/base_controller.rb +11 -21
- data/app/controllers/avo/private_controller.rb +1 -0
- data/app/controllers/avo/search_controller.rb +33 -13
- data/app/helpers/avo/application_helper.rb +1 -1
- data/app/javascript/js/controllers/fields/key_value_controller.js +1 -1
- data/app/javascript/js/controllers/fields/tags_field_controller.js +3 -3
- data/app/javascript/js/controllers/filter_controller.js +4 -1
- data/app/javascript/js/controllers/search_controller.js +9 -1
- data/app/javascript/js/controllers.js +0 -2
- data/app/views/avo/actions/show.html.erb +5 -2
- data/app/views/avo/dashboards/_chartkick_card.html.erb +1 -1
- data/app/views/avo/dashboards/_metric_card.html.erb +1 -1
- data/app/views/avo/partials/_global_search.html.erb +1 -1
- data/app/views/avo/partials/_logo.html.erb +1 -1
- data/app/views/avo/partials/_navbar.html.erb +9 -6
- data/app/views/avo/partials/_resource_search.html.erb +1 -1
- data/app/views/avo/partials/_table_header.html.erb +3 -2
- data/app/views/avo/private/_links_and_buttons.html.erb +2 -2
- data/app/views/layouts/avo/application.html.erb +50 -53
- data/lib/avo/base_action.rb +24 -6
- data/lib/avo/engine.rb +1 -1
- data/lib/avo/fields/belongs_to_field.rb +4 -4
- data/lib/avo/fields/has_and_belongs_to_many_field.rb +2 -2
- data/lib/avo/fields/has_base_field.rb +2 -0
- data/lib/avo/fields/has_many_field.rb +2 -2
- data/lib/avo/fields/has_one_field.rb +2 -2
- data/lib/avo/fields/tags_field.rb +5 -5
- data/lib/avo/hosts/base_host.rb +2 -0
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/cards/chartkick_card.tt +1 -1
- data/lib/generators/avo/templates/cards/chartkick_card_sample.tt +1 -1
- data/lib/generators/avo/templates/cards/metric_card.tt +1 -1
- data/lib/generators/avo/templates/cards/metric_card_sample.tt +1 -1
- data/lib/tasks/avo_tasks.rake +7 -3
- data/public/avo-assets/avo.css +225 -172
- data/public/avo-assets/avo.js +63 -63
- data/public/avo-assets/avo.js.map +3 -3
- metadata +5 -4
- data/app/assets/stylesheets/css/alerts.css +0 -35
- data/app/javascript/js/controllers/alerts_controller.js +0 -26
@@ -1,4 +1,6 @@
|
|
1
|
-
<div data-model-id="<%= @resource.model.id %>"
|
1
|
+
<div data-model-id="<%= @resource.model.id %>"
|
2
|
+
class="space-y-8"
|
3
|
+
>
|
2
4
|
<% @resource.panels.each do |resource_panel| %>
|
3
5
|
<%= form_with model: @resource.model,
|
4
6
|
scope: @resource.form_scope,
|
@@ -9,11 +11,17 @@
|
|
9
11
|
<%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
|
10
12
|
<%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true) do |c| %>
|
11
13
|
<% c.tools do %>
|
12
|
-
<%= a_link back_path,
|
14
|
+
<%= a_link back_path,
|
15
|
+
style: :text,
|
16
|
+
icon: 'arrow-left' do %>
|
13
17
|
<%= t('avo.cancel').capitalize %>
|
14
18
|
<% end %>
|
15
19
|
<% if can_see_the_save_button? %>
|
16
|
-
<%= a_button color: :
|
20
|
+
<%= a_button color: :primary,
|
21
|
+
style: :primary,
|
22
|
+
loading: true,
|
23
|
+
type: :submit,
|
24
|
+
icon: 'save' do %>
|
17
25
|
<%= t('avo.save').capitalize %>
|
18
26
|
<% end %>
|
19
27
|
<% end %>
|
@@ -40,4 +48,21 @@
|
|
40
48
|
<% end %>
|
41
49
|
<% end %>
|
42
50
|
<% end %>
|
51
|
+
<% if @reflection.blank? %>
|
52
|
+
<% if has_one_panels.present? %>
|
53
|
+
<% has_one_panels.each_with_index do |field, index| %>
|
54
|
+
<%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
|
55
|
+
<% end %>
|
56
|
+
<% end %>
|
57
|
+
<% if has_many_panels.present? %>
|
58
|
+
<% has_many_panels.each_with_index do |field, index| %>
|
59
|
+
<%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
|
60
|
+
<% end %>
|
61
|
+
<% end %>
|
62
|
+
<% if has_as_belongs_to_many_panels.present? %>
|
63
|
+
<% has_as_belongs_to_many_panels.each_with_index do |field, index| %>
|
64
|
+
<%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
|
65
|
+
<% end %>
|
66
|
+
<% end %>
|
67
|
+
<% end %>
|
43
68
|
</div>
|
@@ -4,8 +4,12 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
|
|
4
4
|
include Avo::ResourcesHelper
|
5
5
|
include Avo::ApplicationHelper
|
6
6
|
|
7
|
+
attr_reader :fields_by_panel, :has_one_panels, :has_many_panels, :has_as_belongs_to_many_panels
|
8
|
+
|
7
9
|
def initialize(resource: nil)
|
8
10
|
@resource = resource
|
11
|
+
|
12
|
+
split_panel_fields
|
9
13
|
end
|
10
14
|
|
11
15
|
def back_path
|
@@ -21,10 +25,4 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
|
|
21
25
|
def can_see_the_save_button?
|
22
26
|
@resource.authorization.authorize_action :edit, raise_exception: false
|
23
27
|
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def via_resource?
|
28
|
-
params[:via_resource_class].present? && params[:via_resource_id].present?
|
29
|
-
end
|
30
28
|
end
|
@@ -1,19 +1,28 @@
|
|
1
1
|
<div>
|
2
2
|
<%= render Avo::PanelComponent.new(title: title, description: description, data: { component: 'resources-index' }, display_breadcrumbs: @reflection.blank?) do |c| %>
|
3
3
|
<% c.tools do %>
|
4
|
+
<% if can_attach? %>
|
5
|
+
<%= a_link attach_path,
|
6
|
+
icon: 'heroicons/outline/link',
|
7
|
+
color: :primary,
|
8
|
+
style: :text,
|
9
|
+
'data-turbo-frame': 'attach_modal',
|
10
|
+
'data-target': 'attach' do %>
|
11
|
+
<%= t('avo.attach_item', item: singular_resource_name).capitalize %>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
4
14
|
<% if can_see_the_actions_button? %>
|
5
15
|
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
6
16
|
<% end %>
|
7
17
|
<% if can_see_the_create_button? %>
|
8
|
-
<%= a_link create_path,
|
18
|
+
<%= a_link create_path,
|
19
|
+
icon: 'heroicons/outline/plus',
|
20
|
+
'data-target': 'create',
|
21
|
+
style: :primary,
|
22
|
+
color: :primary do %>
|
9
23
|
<%= t('avo.create_new_item', item: singular_resource_name.downcase ) %>
|
10
24
|
<% end %>
|
11
25
|
<% end %>
|
12
|
-
<% if can_attach? %>
|
13
|
-
<%= a_link attach_path, icon: 'heroicons/outline/link', color: :primary, 'data-turbo-frame': 'attach_modal', 'data-target': 'attach' do %>
|
14
|
-
<%= t('avo.attach_item', item: singular_resource_name).capitalize %>
|
15
|
-
<% end %>
|
16
|
-
<% end %>
|
17
26
|
<% end %>
|
18
27
|
<% c.body do %>
|
19
28
|
<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 %>"
|
@@ -50,15 +59,14 @@
|
|
50
59
|
<% c.bare_content do %>
|
51
60
|
<% if view_type.to_sym == :table %>
|
52
61
|
<% if @models.present? %>
|
53
|
-
<div class="mt-
|
62
|
+
<div class="mt-4">
|
54
63
|
<%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
|
55
64
|
</div>
|
56
65
|
<% end %>
|
57
66
|
<% end %>
|
58
|
-
|
59
67
|
<% if view_type.to_sym == :grid %>
|
60
68
|
<%= render Avo::Index::ResourceGridComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model) %>
|
61
|
-
<div class="mt-
|
69
|
+
<div class="mt-6">
|
62
70
|
<%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
|
63
71
|
</div>
|
64
72
|
<% end %>
|
@@ -14,11 +14,17 @@
|
|
14
14
|
<%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true) do |c| %>
|
15
15
|
<% c.tools do %>
|
16
16
|
<div class="flex justify-end space-x-2">
|
17
|
-
<%= a_link back_path,
|
17
|
+
<%= a_link back_path,
|
18
|
+
style: :text,
|
19
|
+
icon: 'arrow-left' do %>
|
18
20
|
<%= t('avo.cancel').capitalize %>
|
19
21
|
<% end %>
|
20
22
|
<% if can_see_the_save_button? %>
|
21
|
-
<%= a_button color:
|
23
|
+
<%= a_button color: :primary,
|
24
|
+
style: :primary,
|
25
|
+
loading: true,
|
26
|
+
type: :submit,
|
27
|
+
icon: 'save' do %>
|
22
28
|
<%= t('avo.save').capitalize %>
|
23
29
|
<% end %>
|
24
30
|
<% end %>
|
@@ -1,39 +1,45 @@
|
|
1
1
|
<div data-model-id="<%= @resource.model.id %>"
|
2
2
|
data-selected-resources-name="<%= @resource.model_key %>"
|
3
3
|
data-selected-resources='["<%= @resource.model.id %>"]'
|
4
|
-
class="space-y-
|
4
|
+
class="space-y-12"
|
5
5
|
>
|
6
6
|
<% @resource.panels.each_with_index do |resource_panel, index| %>
|
7
7
|
<%= render Avo::PanelComponent.new(title: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: index) do |c| %>
|
8
8
|
<% c.tools do %>
|
9
9
|
<% if resource_panel[:name] == @resource.default_panel_name %>
|
10
10
|
<% if @reflection.present? && @resource.model.present? %>
|
11
|
-
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
12
11
|
<% if can_detach? %>
|
13
12
|
<%= a_button url: detach_path,
|
14
13
|
icon: 'detach',
|
15
14
|
method: :delete,
|
16
15
|
form_class: 'flex flex-col sm:flex-row sm:inline-flex',
|
16
|
+
style: :text,
|
17
17
|
data: {
|
18
18
|
confirm: "Are you sure you want to detach this #{@reflection.name.to_s}."
|
19
19
|
} do %>
|
20
20
|
<%= t('avo.detach_item', item: @reflection.name.to_s).capitalize %>
|
21
21
|
<% end %>
|
22
|
+
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
22
23
|
<% end %>
|
23
24
|
<% if can_see_the_edit_button? %>
|
24
|
-
<%= a_link edit_path,
|
25
|
+
<%= a_link edit_path,
|
26
|
+
color: :primary,
|
27
|
+
style: :primary,
|
28
|
+
icon: 'edit' do %>
|
25
29
|
<%= t('avo.edit').capitalize %>
|
26
30
|
<% end %>
|
27
31
|
<% end %>
|
28
32
|
<% else %>
|
29
|
-
<%= a_link back_path,
|
33
|
+
<%= a_link back_path,
|
34
|
+
style: :text,
|
35
|
+
icon: 'arrow-left' do %>
|
30
36
|
<%= t('avo.go_back') %>
|
31
37
|
<% end %>
|
32
|
-
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
33
38
|
<% if can_see_the_destroy_button? %>
|
34
39
|
<%= a_button url: helpers.resource_path(model: @resource.model, resource: @resource),
|
35
40
|
method: :delete,
|
36
41
|
local: true,
|
42
|
+
style: :text,
|
37
43
|
title: t('avo.delete_item', item: @resource.model.model_name.name.downcase).capitalize,
|
38
44
|
loading: true,
|
39
45
|
confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
|
@@ -48,8 +54,12 @@
|
|
48
54
|
<%= t('avo.delete').capitalize %>
|
49
55
|
<% end %>
|
50
56
|
<% end %>
|
57
|
+
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
51
58
|
<% if @resource.authorization.authorize_action(:edit, raise_exception: false) %>
|
52
|
-
<%= a_link edit_path,
|
59
|
+
<%= a_link edit_path,
|
60
|
+
color: :primary,
|
61
|
+
style: :primary,
|
62
|
+
icon: 'edit' do %>
|
53
63
|
<%= t('avo.edit').capitalize %>
|
54
64
|
<% end %>
|
55
65
|
<% end %>
|
@@ -44,53 +44,8 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
|
|
44
44
|
helpers.edit_resource_path(model: @resource.model, resource: @resource, **args)
|
45
45
|
end
|
46
46
|
|
47
|
-
def detach_path
|
48
|
-
helpers.resource_detach_path(params[:resource_name], params[:id], @reflection.name.to_s, @resource.model.id)
|
49
|
-
end
|
50
|
-
|
51
|
-
def destroy_path
|
52
|
-
helpers.resource_path(model: @resource.model, resource: @resource)
|
53
|
-
end
|
54
|
-
|
55
|
-
def can_detach?
|
56
|
-
authorize_association_for("detach")
|
57
|
-
end
|
58
|
-
|
59
|
-
def can_see_the_edit_button?
|
60
|
-
@resource.authorization.authorize_action(:edit, raise_exception: false)
|
61
|
-
end
|
62
|
-
|
63
|
-
def can_see_the_destroy_button?
|
64
|
-
@resource.authorization.authorize_action(:destroy, raise_exception: false)
|
65
|
-
end
|
66
|
-
|
67
47
|
private
|
68
48
|
|
69
|
-
def via_resource?
|
70
|
-
params[:via_resource_class].present? && params[:via_resource_id].present?
|
71
|
-
end
|
72
|
-
|
73
|
-
def split_panel_fields
|
74
|
-
@fields_by_panel = {}
|
75
|
-
@has_one_panels = []
|
76
|
-
@has_many_panels = []
|
77
|
-
@has_as_belongs_to_many_panels = []
|
78
|
-
|
79
|
-
@resource.get_fields.each do |field|
|
80
|
-
case field.class.to_s
|
81
|
-
when "Avo::Fields::HasOneField"
|
82
|
-
@has_one_panels << field
|
83
|
-
when "Avo::Fields::HasManyField"
|
84
|
-
@has_many_panels << field
|
85
|
-
when "Avo::Fields::HasAndBelongsToManyField"
|
86
|
-
@has_as_belongs_to_many_panels << field
|
87
|
-
else
|
88
|
-
@fields_by_panel[field.panel_name] ||= []
|
89
|
-
@fields_by_panel[field.panel_name] << field
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
49
|
# In development and test environments we shoudl show the invalid field errors
|
95
50
|
def should_display_invalid_fields_errors?
|
96
51
|
(Rails.env.development? || Rails.env.test?) && @resource.invalid_fields.present?
|
@@ -14,14 +14,12 @@ module Avo
|
|
14
14
|
resource_ids = action_params[:fields][:resource_ids].split(",")
|
15
15
|
models = @resource.class.find_scope.find resource_ids
|
16
16
|
|
17
|
-
fields = action_params[:fields].
|
18
|
-
key != "resource_ids"
|
19
|
-
end
|
17
|
+
fields = action_params[:fields].except("resource_ids")
|
20
18
|
|
21
19
|
args = {
|
22
20
|
fields: fields,
|
23
21
|
current_user: _current_user,
|
24
|
-
resource: resource
|
22
|
+
resource: resource
|
25
23
|
}
|
26
24
|
|
27
25
|
args[:models] = models unless @action.standalone
|
@@ -49,8 +47,7 @@ module Avo
|
|
49
47
|
|
50
48
|
def respond(response)
|
51
49
|
response[:type] ||= :reload
|
52
|
-
|
53
|
-
response[:message] ||= I18n.t("avo.action_ran_successfully")
|
50
|
+
messages = get_messages response
|
54
51
|
|
55
52
|
if response[:type] == :download
|
56
53
|
return send_data response[:path], filename: response[:filename]
|
@@ -58,6 +55,11 @@ module Avo
|
|
58
55
|
|
59
56
|
respond_to do |format|
|
60
57
|
format.html do
|
58
|
+
# Flash the messages collected from the action
|
59
|
+
messages.each do |message|
|
60
|
+
flash[message[:type]] = message[:body]
|
61
|
+
end
|
62
|
+
|
61
63
|
if response[:type] == :redirect
|
62
64
|
path = response[:path]
|
63
65
|
|
@@ -65,12 +67,25 @@ module Avo
|
|
65
67
|
path = instance_eval(&path)
|
66
68
|
end
|
67
69
|
|
68
|
-
redirect_to path
|
70
|
+
redirect_to path
|
69
71
|
elsif response[:type] == :reload
|
70
|
-
redirect_back fallback_location: resources_path(resource: @resource)
|
72
|
+
redirect_back fallback_location: resources_path(resource: @resource)
|
71
73
|
end
|
72
74
|
end
|
73
75
|
end
|
74
76
|
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def get_messages(response)
|
81
|
+
default_message = {
|
82
|
+
type: :info,
|
83
|
+
body: I18n.t("avo.action_ran_successfully")
|
84
|
+
}
|
85
|
+
|
86
|
+
return [default_message] if response[:messages].blank?
|
87
|
+
|
88
|
+
response[:messages]
|
89
|
+
end
|
75
90
|
end
|
76
91
|
end
|
@@ -46,8 +46,8 @@ module Avo
|
|
46
46
|
query = @authorization.apply_policy @attachment_class
|
47
47
|
|
48
48
|
# Add the association scope to the query scope
|
49
|
-
if @field.
|
50
|
-
query = Avo::Hosts::AssociationScopeHost.new(block: @field.
|
49
|
+
if @field.attach_scope.present?
|
50
|
+
query = Avo::Hosts::AssociationScopeHost.new(block: @field.attach_scope, query: query, parent: @model).handle
|
51
51
|
end
|
52
52
|
|
53
53
|
@options = query.all.map do |model|
|
@@ -65,7 +65,7 @@ module Avo
|
|
65
65
|
|
66
66
|
respond_to do |format|
|
67
67
|
if @model.save
|
68
|
-
format.html {
|
68
|
+
format.html { redirect_back fallback_location: resource_path(model: @model, resource: @resource), notice: t("avo.attachment_class_attached", attachment_class: @related_resource.name) }
|
69
69
|
else
|
70
70
|
format.html { render :new }
|
71
71
|
end
|
@@ -11,8 +11,6 @@ module Avo
|
|
11
11
|
before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update]
|
12
12
|
before_action :fill_model, only: [:create, :update]
|
13
13
|
before_action :authorize_action
|
14
|
-
before_action :reset_pagination_if_filters_changed, only: :index
|
15
|
-
before_action :cache_applied_filters, only: :index
|
16
14
|
|
17
15
|
def index
|
18
16
|
@page_title = @resource.plural_name.humanize
|
@@ -49,10 +47,10 @@ module Avo
|
|
49
47
|
# Check if the sortable field option is actually a proc and we need to do a custom sort
|
50
48
|
field_id = @index_params[:sort_by].to_sym
|
51
49
|
field = @resource.get_field_definitions.find { |field| field.id == field_id }
|
52
|
-
if field&.sortable.is_a?(Proc)
|
53
|
-
|
50
|
+
@query = if field&.sortable.is_a?(Proc)
|
51
|
+
field.sortable.call(@query, @index_params[:sort_direction])
|
54
52
|
else
|
55
|
-
@query
|
53
|
+
@query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
@@ -61,7 +59,14 @@ module Avo
|
|
61
59
|
@query = filter_class.safe_constantize.new.apply_query request, @query, filter_value
|
62
60
|
end
|
63
61
|
|
64
|
-
|
62
|
+
extra_pagy_params = {}
|
63
|
+
|
64
|
+
# Reset open filters when a user navigates to a new page
|
65
|
+
extra_pagy_params[:keep_filters_panel_open] = if params[:keep_filters_panel_open] == "1"
|
66
|
+
"0"
|
67
|
+
end
|
68
|
+
|
69
|
+
@pagy, @models = pagy(@query, items: @index_params[:per_page], link_extra: "data-turbo-frame=\"#{params[:turbo_frame]}\"", size: [1, 2, 2, 1], params: extra_pagy_params)
|
65
70
|
|
66
71
|
# Create resources for each model
|
67
72
|
@resources = @models.map do |model|
|
@@ -353,21 +358,6 @@ module Avo
|
|
353
358
|
filter_defaults.merge(@applied_filters)
|
354
359
|
end
|
355
360
|
|
356
|
-
# Caching these so we know when the filters have changed so we reset the pagination
|
357
|
-
def cache_applied_filters
|
358
|
-
::Avo::App.cache_store.delete applied_filters_cache_key if params[:filters].nil?
|
359
|
-
|
360
|
-
::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
|
361
|
-
end
|
362
|
-
|
363
|
-
def reset_pagination_if_filters_changed
|
364
|
-
params[:page] = 1 if params[:filters] != ::Avo::App.cache_store.read(applied_filters_cache_key)
|
365
|
-
end
|
366
|
-
|
367
|
-
def applied_filters_cache_key
|
368
|
-
"avo.base_controller.#{@resource.model_key}.applied_filters"
|
369
|
-
end
|
370
|
-
|
371
361
|
def set_edit_title_and_breadcrumbs
|
372
362
|
@resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
|
373
363
|
@page_title = @resource.default_panel_name.to_s
|
@@ -46,19 +46,8 @@ module Avo
|
|
46
46
|
def search_resource(resource)
|
47
47
|
query = resource.search_query.call(params: params).limit(8)
|
48
48
|
|
49
|
-
# Figure
|
50
|
-
|
51
|
-
# Fetch the field
|
52
|
-
field = belongs_to_field
|
53
|
-
|
54
|
-
if field.scope.present?
|
55
|
-
# Fetch the parent
|
56
|
-
parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
|
57
|
-
|
58
|
-
# Add to the query
|
59
|
-
query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.scope, query: query, parent: parent).handle
|
60
|
-
end
|
61
|
-
end
|
49
|
+
# Figure out if this is a belongs_to search
|
50
|
+
query = apply_attach_scope query
|
62
51
|
|
63
52
|
results = apply_search_metadata(query, resource)
|
64
53
|
|
@@ -72,6 +61,37 @@ module Avo
|
|
72
61
|
[resource.name.pluralize.downcase, result_object]
|
73
62
|
end
|
74
63
|
|
64
|
+
# Figure out if it's a belongs to search and if it has the attach_scope block present.
|
65
|
+
# If so, set the parent for those edit view and the parent with the grandparent for the new view.
|
66
|
+
def apply_attach_scope(query)
|
67
|
+
return query if params[:via_reflection_class].blank?
|
68
|
+
|
69
|
+
# Fetch the field
|
70
|
+
field = belongs_to_field
|
71
|
+
|
72
|
+
# No need to modify the query if there's no attach_scope present.
|
73
|
+
return query if field.attach_scope.blank?
|
74
|
+
|
75
|
+
# Try to fetch the parent.
|
76
|
+
if params[:via_reflection_id].present?
|
77
|
+
parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
|
78
|
+
end
|
79
|
+
|
80
|
+
# If the parent is nil it probably means that someone's creating the record so it's not attached yet.
|
81
|
+
# In these scenarios, try to find the grandparent for the new views where the parent is nil
|
82
|
+
# and initialize the parent record with the grandparent attached so the user has the required information
|
83
|
+
# to scope the query.
|
84
|
+
if parent.blank? && params[:via_parent_resource_id].present? && params[:via_parent_resource_class].present? && params[:via_relation].present?
|
85
|
+
grandparent = params[:via_parent_resource_class].safe_constantize.find params[:via_parent_resource_id]
|
86
|
+
parent = params[:via_reflection_class].safe_constantize.new(
|
87
|
+
params[:via_relation] => grandparent
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Add to the query
|
92
|
+
Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.attach_scope, query: query, parent: parent).handle
|
93
|
+
end
|
94
|
+
|
75
95
|
def apply_search_metadata(models, avo_resource)
|
76
96
|
models.map do |model|
|
77
97
|
resource = avo_resource.dup.hydrate(model: model).hydrate_fields(model: model)
|
@@ -118,7 +118,7 @@ module Avo
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def input_classes(extra_classes = "", has_error: false)
|
121
|
-
classes = "appearance-none inline-flex bg-gray-
|
121
|
+
classes = "appearance-none inline-flex bg-gray-25 disabled:cursor-not-allowed text-gray-600 disabled:opacity-50 rounded py-2 px-3 leading-tight border focus:border-gray-600 focus-visible:ring-0 focus:text-gray-700"
|
122
122
|
|
123
123
|
classes += if has_error
|
124
124
|
" border-red-600"
|
@@ -110,7 +110,7 @@ export default class extends Controller {
|
|
110
110
|
|
111
111
|
inputField(id = 'key', index, key, value) {
|
112
112
|
return `<input
|
113
|
-
class="${this.options.inputClasses} !rounded-none border-gray-600 border-r border-l-0 border-b-0 border-t-0 focus:border-gray-300 w-1/2 focus:outline-none outline-none key-value-input-${id}"
|
113
|
+
class="${this.options.inputClasses} focus:bg-gray-100 !rounded-none border-gray-600 border-r border-l-0 border-b-0 border-t-0 focus:border-gray-300 w-1/2 focus:outline-none outline-none key-value-input-${id}"
|
114
114
|
data-action="input->key-value#${id}FieldUpdated"
|
115
115
|
placeholder="${this.options[`${id}_label`]}"
|
116
116
|
data-index="${index}"
|
@@ -14,8 +14,8 @@ export default class extends BaseController {
|
|
14
14
|
return this.getJsonAttribute(this.inputTarget, 'data-whitelist-items', [])
|
15
15
|
}
|
16
16
|
|
17
|
-
get
|
18
|
-
return this.getJsonAttribute(this.inputTarget, 'data-
|
17
|
+
get disallowedItems() {
|
18
|
+
return this.getJsonAttribute(this.inputTarget, 'data-disallowed-items', [])
|
19
19
|
}
|
20
20
|
|
21
21
|
get enforceSuggestions() {
|
@@ -37,7 +37,7 @@ export default class extends BaseController {
|
|
37
37
|
get tagifyOptions() {
|
38
38
|
let options = {
|
39
39
|
whitelist: this.whitelistItems,
|
40
|
-
blacklist: this.
|
40
|
+
blacklist: this.disallowedItems,
|
41
41
|
enforceWhitelist: this.enforceSuggestions,
|
42
42
|
delimiters: this.delimiters.join('|'),
|
43
43
|
maxTags: 10,
|
@@ -84,9 +84,12 @@ export default class extends Controller {
|
|
84
84
|
|
85
85
|
if (this.keepFiltersPanelOpenValue) {
|
86
86
|
// eslint-disable-next-line camelcase
|
87
|
-
query.keep_filters_panel_open = this.keepFiltersPanelOpenValue
|
87
|
+
query.keep_filters_panel_open = this.keepFiltersPanelOpenValue ? 1 : null
|
88
88
|
}
|
89
89
|
|
90
|
+
// force to go to the first page if the filters changed
|
91
|
+
query.page = 1
|
92
|
+
|
90
93
|
if (encodedFilters) {
|
91
94
|
query.filters = encodedFilters
|
92
95
|
} else {
|
@@ -76,6 +76,12 @@ export default class extends Controller {
|
|
76
76
|
via_reflection_class: this.dataset.viaReflectionClass,
|
77
77
|
// eslint-disable-next-line camelcase
|
78
78
|
via_reflection_id: this.dataset.viaReflectionId,
|
79
|
+
// eslint-disable-next-line camelcase
|
80
|
+
via_parent_resource_id: this.dataset.viaParentResourceId,
|
81
|
+
// eslint-disable-next-line camelcase
|
82
|
+
via_parent_resource_class: this.dataset.viaParentResourceClass,
|
83
|
+
// eslint-disable-next-line camelcase
|
84
|
+
via_relation: this.dataset.viaRelation,
|
79
85
|
}
|
80
86
|
}
|
81
87
|
|
@@ -193,7 +199,7 @@ export default class extends Controller {
|
|
193
199
|
Mousetrap.bind(['command+k', 'ctrl+k'], () => this.showSearchPanel())
|
194
200
|
}
|
195
201
|
|
196
|
-
autocomplete({
|
202
|
+
const { destroy } = autocomplete({
|
197
203
|
container: this.autocompleteTarget,
|
198
204
|
placeholder: this.translationKeys.placeholder,
|
199
205
|
translations: {
|
@@ -216,6 +222,8 @@ export default class extends Controller {
|
|
216
222
|
},
|
217
223
|
})
|
218
224
|
|
225
|
+
document.addEventListener('turbo:before-render', destroy)
|
226
|
+
|
219
227
|
// When using search for belongs-to
|
220
228
|
if (this.buttonTarget.dataset.shouldBeDisabled !== 'true') {
|
221
229
|
this.buttonTarget.removeAttribute('disabled')
|
@@ -2,7 +2,6 @@ import { application } from './application'
|
|
2
2
|
|
3
3
|
import ActionController from './controllers/action_controller'
|
4
4
|
import ActionsPickerController from './controllers/actions_picker_controller'
|
5
|
-
import AlertsController from './controllers/alerts_controller'
|
6
5
|
import AttachmentsController from './controllers/attachments_controller'
|
7
6
|
import BelongsToFieldController from './controllers/fields/belongs_to_field_controller'
|
8
7
|
import BooleanFilterController from './controllers/boolean_filter_controller'
|
@@ -33,7 +32,6 @@ import TrixFieldController from './controllers/fields/trix_field_controller'
|
|
33
32
|
|
34
33
|
application.register('action', ActionController)
|
35
34
|
application.register('actions-picker', ActionsPickerController)
|
36
|
-
application.register('alerts', AlertsController)
|
37
35
|
application.register('attachments', AttachmentsController)
|
38
36
|
application.register('boolean-filter', BooleanFilterController)
|
39
37
|
application.register('copy-to-clipboard', CopyToClipboardController)
|
@@ -29,11 +29,14 @@
|
|
29
29
|
</div>
|
30
30
|
<% end %>
|
31
31
|
<% c.controls do %>
|
32
|
-
<%= a_button data: { action: 'click->modal#close' },
|
32
|
+
<%= a_button data: { action: 'click->modal#close' },
|
33
|
+
size: :sm,
|
34
|
+
color: :primary do %>
|
33
35
|
<%= @action.cancel_button_label %>
|
34
36
|
<% end %>
|
35
37
|
<%= a_button type: :submit,
|
36
|
-
color: :
|
38
|
+
color: :primary,
|
39
|
+
style: :primary,
|
37
40
|
size: :sm,
|
38
41
|
data: @action.class.submit_button_data_attributes do %>
|
39
42
|
<%= @action.confirm_button_label %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="flex mt-4 items-end">
|
2
2
|
<span class="text-3xl"><%= @card.prefix %></span>
|
3
|
-
<span class="text-5xl"><%= @card.
|
3
|
+
<span class="text-5xl"><%= @card.result_data %></span>
|
4
4
|
<span class="text-3xl"><%= @card.suffix %></span>
|
5
5
|
</div>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<div data-controller="search" class="relative flex global-search rounded border border-gray-200 sm:min-w-[16rem]"
|
1
|
+
<div data-controller="search" class="relative flex global-search rounded border border-gray-200 sm:min-w-[16rem]">
|
2
2
|
<div class="flex-1 text-gray-500"
|
3
3
|
data-search-target="autocomplete"
|
4
4
|
data-search-resource="global"
|