avo 2.5.2.pre.6 → 2.5.2.pre.7
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 +4 -1
- data/app/assets/builds/avo.css +766 -301
- data/app/assets/builds/avo.js +212 -123
- data/app/assets/builds/avo.js.map +3 -3
- data/app/assets/stylesheets/avo.css +3 -33
- data/app/assets/stylesheets/css/alerts.css +35 -0
- data/app/assets/stylesheets/css/search.css +1 -1
- data/app/assets/stylesheets/css/tags.css +16 -0
- data/app/assets/svgs/heroicons/solid/user-remove.svg +1 -1
- data/app/components/avo/actions_component.html.erb +1 -2
- data/app/components/avo/alert_component.html.erb +1 -1
- data/app/components/avo/alert_component.rb +5 -24
- data/app/components/avo/button_component.rb +7 -46
- data/app/components/avo/fields/tags_field/edit_component.html.erb +27 -0
- data/app/components/avo/fields/tags_field/edit_component.rb +4 -0
- data/app/components/avo/fields/tags_field/index_component.html.erb +14 -0
- data/app/components/avo/fields/tags_field/index_component.rb +7 -0
- data/app/components/avo/fields/tags_field/show_component.html.erb +7 -0
- data/app/components/avo/fields/tags_field/show_component.rb +11 -0
- data/app/components/avo/fields/tags_field/tag_component.html.erb +9 -0
- data/app/components/avo/fields/tags_field/tag_component.rb +11 -0
- 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/resource_table_component.html.erb +1 -1
- data/app/components/avo/index/table_row_component.html.erb +1 -1
- 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 +0 -50
- data/app/components/avo/sidebar/group_component.html.erb +2 -4
- 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 +13 -5
- data/app/components/avo/sidebar_profile_component.html.erb +1 -1
- data/app/components/avo/views/resource_edit_component.html.erb +3 -28
- data/app/components/avo/views/resource_edit_component.rb +6 -4
- data/app/components/avo/views/resource_index_component.html.erb +9 -17
- data/app/components/avo/views/resource_new_component.html.erb +2 -8
- data/app/components/avo/views/resource_show_component.html.erb +6 -16
- data/app/components/avo/views/resource_show_component.rb +45 -0
- data/app/controllers/avo/actions_controller.rb +8 -23
- data/app/controllers/avo/associations_controller.rb +3 -3
- data/app/controllers/avo/base_controller.rb +16 -25
- data/app/controllers/avo/private_controller.rb +0 -1
- data/app/controllers/avo/search_controller.rb +2 -2
- data/app/helpers/avo/application_helper.rb +1 -1
- data/app/javascript/js/application.js +1 -1
- data/app/javascript/js/controllers/alerts_controller.js +26 -0
- data/app/javascript/js/controllers/base_controller.js +22 -0
- data/app/javascript/js/controllers/fields/key_value_controller.js +1 -1
- data/app/javascript/js/controllers/fields/tags_field_controller.js +86 -0
- data/app/javascript/js/controllers/fields/tags_field_helpers.js +47 -0
- data/app/javascript/js/controllers/filter_controller.js +1 -4
- data/app/javascript/js/controllers.js +4 -0
- data/app/views/avo/actions/show.html.erb +2 -5
- data/app/views/avo/partials/_logo.html.erb +1 -1
- data/app/views/avo/partials/_navbar.html.erb +6 -9
- data/app/views/avo/partials/_table_header.html.erb +2 -3
- data/app/views/avo/private/_links_and_buttons.html.erb +2 -2
- data/app/views/layouts/avo/application.html.erb +53 -50
- data/db/factories.rb +2 -0
- data/lib/avo/base_action.rb +6 -24
- data/lib/avo/base_resource.rb +6 -0
- data/lib/avo/concerns/handles_field_args.rb +36 -0
- data/lib/avo/engine.rb +1 -1
- data/lib/avo/fields/base_field.rb +2 -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 +0 -2
- 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 +82 -0
- data/lib/avo/hosts/record_host.rb +7 -0
- data/lib/avo/licensing/pro_license.rb +2 -1
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/locales/avo.en.yml +4 -0
- data/public/avo-assets/avo.css +825 -262
- data/public/avo-assets/avo.js +212 -123
- data/public/avo-assets/avo.js.map +3 -3
- metadata +19 -2
@@ -1,6 +1,4 @@
|
|
1
|
-
<div data-model-id="<%= @resource.model.id %>"
|
2
|
-
class="space-y-8"
|
3
|
-
>
|
1
|
+
<div data-model-id="<%= @resource.model.id %>">
|
4
2
|
<% @resource.panels.each do |resource_panel| %>
|
5
3
|
<%= form_with model: @resource.model,
|
6
4
|
scope: @resource.form_scope,
|
@@ -11,17 +9,11 @@
|
|
11
9
|
<%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
|
12
10
|
<%= render Avo::PanelComponent.new(title: resource_panel[:name], description: @resource.resource_description, display_breadcrumbs: true) do |c| %>
|
13
11
|
<% c.tools do %>
|
14
|
-
<%= a_link back_path,
|
15
|
-
style: :text,
|
16
|
-
icon: 'arrow-left' do %>
|
12
|
+
<%= a_link back_path, icon: 'arrow-left' do %>
|
17
13
|
<%= t('avo.cancel').capitalize %>
|
18
14
|
<% end %>
|
19
15
|
<% if can_see_the_save_button? %>
|
20
|
-
<%= a_button color: :
|
21
|
-
style: :primary,
|
22
|
-
loading: true,
|
23
|
-
type: :submit,
|
24
|
-
icon: 'save' do %>
|
16
|
+
<%= a_button color: :green, loading: true, type: :submit, icon: 'save' do %>
|
25
17
|
<%= t('avo.save').capitalize %>
|
26
18
|
<% end %>
|
27
19
|
<% end %>
|
@@ -48,21 +40,4 @@
|
|
48
40
|
<% end %>
|
49
41
|
<% end %>
|
50
42
|
<% 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 %>
|
68
43
|
</div>
|
@@ -4,12 +4,8 @@ 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
|
-
|
9
7
|
def initialize(resource: nil)
|
10
8
|
@resource = resource
|
11
|
-
|
12
|
-
split_panel_fields
|
13
9
|
end
|
14
10
|
|
15
11
|
def back_path
|
@@ -25,4 +21,10 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
|
|
25
21
|
def can_see_the_save_button?
|
26
22
|
@resource.authorization.authorize_action :edit, raise_exception: false
|
27
23
|
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def via_resource?
|
28
|
+
params[:via_resource_class].present? && params[:via_resource_id].present?
|
29
|
+
end
|
28
30
|
end
|
@@ -1,28 +1,19 @@
|
|
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 %>
|
14
4
|
<% if can_see_the_actions_button? %>
|
15
5
|
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
16
6
|
<% end %>
|
17
7
|
<% if can_see_the_create_button? %>
|
18
|
-
<%= a_link create_path,
|
19
|
-
icon: 'heroicons/outline/plus',
|
20
|
-
'data-target': 'create',
|
21
|
-
style: :primary,
|
22
|
-
color: :primary do %>
|
8
|
+
<%= a_link create_path, icon: 'heroicons/outline/plus', 'data-target': 'create', color: :primary do %>
|
23
9
|
<%= t('avo.create_new_item', item: singular_resource_name.downcase ) %>
|
24
10
|
<% end %>
|
25
11
|
<% 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 %>
|
26
17
|
<% end %>
|
27
18
|
<% c.body do %>
|
28
19
|
<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 %>"
|
@@ -59,14 +50,15 @@
|
|
59
50
|
<% c.bare_content do %>
|
60
51
|
<% if view_type.to_sym == :table %>
|
61
52
|
<% if @models.present? %>
|
62
|
-
<div class="mt-
|
53
|
+
<div class="mt-8">
|
63
54
|
<%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
|
64
55
|
</div>
|
65
56
|
<% end %>
|
66
57
|
<% end %>
|
58
|
+
|
67
59
|
<% if view_type.to_sym == :grid %>
|
68
60
|
<%= render Avo::Index::ResourceGridComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model) %>
|
69
|
-
<div class="mt-
|
61
|
+
<div class="mt-14">
|
70
62
|
<%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
|
71
63
|
</div>
|
72
64
|
<% end %>
|
@@ -14,17 +14,11 @@
|
|
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,
|
18
|
-
style: :text,
|
19
|
-
icon: 'arrow-left' do %>
|
17
|
+
<%= a_link back_path, icon: 'arrow-left' do %>
|
20
18
|
<%= t('avo.cancel').capitalize %>
|
21
19
|
<% end %>
|
22
20
|
<% if can_see_the_save_button? %>
|
23
|
-
<%= a_button color: :
|
24
|
-
style: :primary,
|
25
|
-
loading: true,
|
26
|
-
type: :submit,
|
27
|
-
icon: 'save' do %>
|
21
|
+
<%= a_button color: 'green', loading: true, type: :submit, icon: 'save' do %>
|
28
22
|
<%= t('avo.save').capitalize %>
|
29
23
|
<% end %>
|
30
24
|
<% end %>
|
@@ -1,45 +1,39 @@
|
|
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-8"
|
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 %>
|
11
12
|
<% if can_detach? %>
|
12
13
|
<%= a_button url: detach_path,
|
13
14
|
icon: 'detach',
|
14
15
|
method: :delete,
|
15
16
|
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 %>
|
23
22
|
<% end %>
|
24
23
|
<% if can_see_the_edit_button? %>
|
25
|
-
<%= a_link edit_path,
|
26
|
-
color: :primary,
|
27
|
-
style: :primary,
|
28
|
-
icon: 'edit' do %>
|
24
|
+
<%= a_link edit_path, color: :primary, icon: 'edit' do %>
|
29
25
|
<%= t('avo.edit').capitalize %>
|
30
26
|
<% end %>
|
31
27
|
<% end %>
|
32
28
|
<% else %>
|
33
|
-
<%= a_link back_path,
|
34
|
-
style: :text,
|
35
|
-
icon: 'arrow-left' do %>
|
29
|
+
<%= a_link back_path, icon: 'arrow-left' do %>
|
36
30
|
<%= t('avo.go_back') %>
|
37
31
|
<% end %>
|
32
|
+
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
38
33
|
<% if can_see_the_destroy_button? %>
|
39
34
|
<%= a_button url: helpers.resource_path(model: @resource.model, resource: @resource),
|
40
35
|
method: :delete,
|
41
36
|
local: true,
|
42
|
-
style: :text,
|
43
37
|
title: t('avo.delete_item', item: @resource.model.model_name.name.downcase).capitalize,
|
44
38
|
loading: true,
|
45
39
|
confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
|
@@ -54,12 +48,8 @@
|
|
54
48
|
<%= t('avo.delete').capitalize %>
|
55
49
|
<% end %>
|
56
50
|
<% end %>
|
57
|
-
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
|
58
51
|
<% if @resource.authorization.authorize_action(:edit, raise_exception: false) %>
|
59
|
-
<%= a_link edit_path,
|
60
|
-
color: :primary,
|
61
|
-
style: :primary,
|
62
|
-
icon: 'edit' do %>
|
52
|
+
<%= a_link edit_path, color: :primary, icon: 'edit' do %>
|
63
53
|
<%= t('avo.edit').capitalize %>
|
64
54
|
<% end %>
|
65
55
|
<% end %>
|
@@ -44,8 +44,53 @@ 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
|
+
|
47
67
|
private
|
48
68
|
|
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
|
+
|
49
94
|
# In development and test environments we shoudl show the invalid field errors
|
50
95
|
def should_display_invalid_fields_errors?
|
51
96
|
(Rails.env.development? || Rails.env.test?) && @resource.invalid_fields.present?
|
@@ -14,12 +14,14 @@ 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].
|
17
|
+
fields = action_params[:fields].select do |key, value|
|
18
|
+
key != "resource_ids"
|
19
|
+
end
|
18
20
|
|
19
21
|
args = {
|
20
22
|
fields: fields,
|
21
23
|
current_user: _current_user,
|
22
|
-
resource: resource
|
24
|
+
resource: resource,
|
23
25
|
}
|
24
26
|
|
25
27
|
args[:models] = models unless @action.standalone
|
@@ -47,7 +49,8 @@ module Avo
|
|
47
49
|
|
48
50
|
def respond(response)
|
49
51
|
response[:type] ||= :reload
|
50
|
-
|
52
|
+
response[:message_type] ||= :notice
|
53
|
+
response[:message] ||= I18n.t("avo.action_ran_successfully")
|
51
54
|
|
52
55
|
if response[:type] == :download
|
53
56
|
return send_data response[:path], filename: response[:filename]
|
@@ -55,11 +58,6 @@ module Avo
|
|
55
58
|
|
56
59
|
respond_to do |format|
|
57
60
|
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
|
-
|
63
61
|
if response[:type] == :redirect
|
64
62
|
path = response[:path]
|
65
63
|
|
@@ -67,25 +65,12 @@ module Avo
|
|
67
65
|
path = instance_eval(&path)
|
68
66
|
end
|
69
67
|
|
70
|
-
redirect_to path
|
68
|
+
redirect_to path, "#{response[:message_type]}": response[:message]
|
71
69
|
elsif response[:type] == :reload
|
72
|
-
redirect_back fallback_location: resources_path(resource: @resource)
|
70
|
+
redirect_back fallback_location: resources_path(resource: @resource), "#{response[:message_type]}": response[:message]
|
73
71
|
end
|
74
72
|
end
|
75
73
|
end
|
76
74
|
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
|
90
75
|
end
|
91
76
|
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.scope.present?
|
50
|
+
query = Avo::Hosts::AssociationScopeHost.new(block: @field.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_to 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,8 @@ 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
|
-
|
15
|
-
|
14
|
+
before_action :reset_pagination_if_filters_changed, only: :index
|
15
|
+
before_action :cache_applied_filters, only: :index
|
16
16
|
|
17
17
|
def index
|
18
18
|
@page_title = @resource.plural_name.humanize
|
@@ -49,10 +49,10 @@ module Avo
|
|
49
49
|
# Check if the sortable field option is actually a proc and we need to do a custom sort
|
50
50
|
field_id = @index_params[:sort_by].to_sym
|
51
51
|
field = @resource.get_field_definitions.find { |field| field.id == field_id }
|
52
|
-
|
53
|
-
field.sortable.call(@query, @index_params[:sort_direction])
|
52
|
+
if field&.sortable.is_a?(Proc)
|
53
|
+
@query = field.sortable.call(@query, @index_params[:sort_direction])
|
54
54
|
else
|
55
|
-
@query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
|
55
|
+
@query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -61,14 +61,7 @@ module Avo
|
|
61
61
|
@query = filter_class.safe_constantize.new.apply_query request, @query, filter_value
|
62
62
|
end
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
# Reset open filters when a user navigates to a new page
|
67
|
-
extra_pagy_params[:keep_filters_panel_open] = if params[:keep_filters_panel_open] == "1"
|
68
|
-
"0"
|
69
|
-
end
|
70
|
-
|
71
|
-
@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)
|
64
|
+
@pagy, @models = pagy(@query, items: @index_params[:per_page], link_extra: "data-turbo-frame=\"#{params[:turbo_frame]}\"", size: [1, 2, 2, 1])
|
72
65
|
|
73
66
|
# Create resources for each model
|
74
67
|
@resources = @models.map do |model|
|
@@ -361,21 +354,19 @@ module Avo
|
|
361
354
|
end
|
362
355
|
|
363
356
|
# Caching these so we know when the filters have changed so we reset the pagination
|
364
|
-
|
365
|
-
|
366
|
-
# session[:avo_applied_filters] = params[:filters]
|
367
|
-
# # ::Avo::App.cache_store.delete(applied_filters_cache_key) if params[:filters].nil?
|
357
|
+
def cache_applied_filters
|
358
|
+
::Avo::App.cache_store.delete applied_filters_cache_key if params[:filters].nil?
|
368
359
|
|
369
|
-
|
370
|
-
|
360
|
+
::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
|
361
|
+
end
|
371
362
|
|
372
|
-
|
373
|
-
|
374
|
-
|
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
|
375
366
|
|
376
|
-
|
377
|
-
|
378
|
-
|
367
|
+
def applied_filters_cache_key
|
368
|
+
"avo.base_controller.#{@resource.model_key}.applied_filters"
|
369
|
+
end
|
379
370
|
|
380
371
|
def set_edit_title_and_breadcrumbs
|
381
372
|
@resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
|
@@ -51,12 +51,12 @@ module Avo
|
|
51
51
|
# Fetch the field
|
52
52
|
field = belongs_to_field
|
53
53
|
|
54
|
-
if field.
|
54
|
+
if field.scope.present?
|
55
55
|
# Fetch the parent
|
56
56
|
parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
|
57
57
|
|
58
58
|
# Add to the query
|
59
|
-
query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.
|
59
|
+
query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.scope, query: query, parent: parent).handle
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -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-100 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"
|
@@ -4,7 +4,7 @@ import { Application } from '@hotwired/stimulus'
|
|
4
4
|
const application = Application.start()
|
5
5
|
|
6
6
|
// Configure Stimulus development experience
|
7
|
-
application.debug =
|
7
|
+
application.debug = window?.localStorage.getItem('avo.debug')
|
8
8
|
window.Stimulus = application
|
9
9
|
|
10
10
|
// Register stimulus-components controller
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ['container']
|
5
|
+
|
6
|
+
connect() {
|
7
|
+
window.toastr[this.type](this.message)
|
8
|
+
}
|
9
|
+
|
10
|
+
get type() {
|
11
|
+
const typeMap = {
|
12
|
+
info: 'info',
|
13
|
+
warning: 'warning',
|
14
|
+
success: 'success',
|
15
|
+
error: 'error',
|
16
|
+
notice: 'info',
|
17
|
+
alert: 'error',
|
18
|
+
}
|
19
|
+
|
20
|
+
return typeMap[this.containerTarget.dataset.alertType]
|
21
|
+
}
|
22
|
+
|
23
|
+
get message() {
|
24
|
+
return this.containerTarget.innerHTML
|
25
|
+
}
|
26
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
/**
|
5
|
+
* Helper that parses the data attribute value to JSON
|
6
|
+
*/
|
7
|
+
getJsonAttribute(target, attribute, defaultValue = []) {
|
8
|
+
let result = defaultValue
|
9
|
+
try {
|
10
|
+
result = JSON.parse(target.getAttribute(attribute))
|
11
|
+
} catch (error) {}
|
12
|
+
|
13
|
+
return result
|
14
|
+
}
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Parses the attribute to boolean
|
18
|
+
*/
|
19
|
+
getBooleanAttribute(target, attribute) {
|
20
|
+
return target.getAttribute(attribute) === '1'
|
21
|
+
}
|
22
|
+
}
|
@@ -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}
|
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}"
|
114
114
|
data-action="input->key-value#${id}FieldUpdated"
|
115
115
|
placeholder="${this.options[`${id}_label`]}"
|
116
116
|
data-index="${index}"
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import { first, isObject, merge } from 'lodash'
|
2
|
+
import Tagify from '@yaireo/tagify'
|
3
|
+
|
4
|
+
import BaseController from '../base_controller'
|
5
|
+
|
6
|
+
import { suggestionItemTemplate, tagTemplate } from './tags_field_helpers'
|
7
|
+
|
8
|
+
export default class extends BaseController {
|
9
|
+
static targets = ['input', 'fakeInput'];
|
10
|
+
|
11
|
+
tagify = null;
|
12
|
+
|
13
|
+
get whitelistItems() {
|
14
|
+
return this.getJsonAttribute(this.inputTarget, 'data-whitelist-items', [])
|
15
|
+
}
|
16
|
+
|
17
|
+
get blacklistItems() {
|
18
|
+
return this.getJsonAttribute(this.inputTarget, 'data-blacklist-items', [])
|
19
|
+
}
|
20
|
+
|
21
|
+
get enforceSuggestions() {
|
22
|
+
return this.getBooleanAttribute(this.inputTarget, 'data-enforce-suggestions')
|
23
|
+
}
|
24
|
+
|
25
|
+
get closeOnSelect() {
|
26
|
+
return this.getBooleanAttribute(this.inputTarget, 'data-close-on-select')
|
27
|
+
}
|
28
|
+
|
29
|
+
get delimiters() {
|
30
|
+
return this.getJsonAttribute(this.inputTarget, 'data-delimiters', [])
|
31
|
+
}
|
32
|
+
|
33
|
+
get suggestionsAreObjects() {
|
34
|
+
return isObject(first(this.whitelistItems))
|
35
|
+
}
|
36
|
+
|
37
|
+
get tagifyOptions() {
|
38
|
+
let options = {
|
39
|
+
whitelist: this.whitelistItems,
|
40
|
+
blacklist: this.blacklistItems,
|
41
|
+
enforceWhitelist: this.enforceSuggestions,
|
42
|
+
delimiters: this.delimiters.join('|'),
|
43
|
+
maxTags: 10,
|
44
|
+
dropdown: {
|
45
|
+
maxItems: 20,
|
46
|
+
enabled: 0,
|
47
|
+
closeOnSelect: this.closeOnSelect,
|
48
|
+
},
|
49
|
+
}
|
50
|
+
|
51
|
+
if (this.suggestionsAreObjects) {
|
52
|
+
options = merge(options, {
|
53
|
+
tagTextProp: 'label',
|
54
|
+
dropdown: {
|
55
|
+
searchKeys: ['label'],
|
56
|
+
},
|
57
|
+
templates: {
|
58
|
+
tag: tagTemplate,
|
59
|
+
dropdownItem: suggestionItemTemplate,
|
60
|
+
},
|
61
|
+
})
|
62
|
+
}
|
63
|
+
|
64
|
+
return options
|
65
|
+
}
|
66
|
+
|
67
|
+
connect() {
|
68
|
+
if (this.hasInputTarget) {
|
69
|
+
this.hideFakeInput()
|
70
|
+
this.showRealInput()
|
71
|
+
this.initTagify()
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
initTagify() {
|
76
|
+
this.tagify = new Tagify(this.inputTarget, this.tagifyOptions)
|
77
|
+
}
|
78
|
+
|
79
|
+
hideFakeInput() {
|
80
|
+
this.fakeInputTarget.classList.add('hidden')
|
81
|
+
}
|
82
|
+
|
83
|
+
showRealInput() {
|
84
|
+
this.inputTarget.classList.remove('hidden')
|
85
|
+
}
|
86
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
export function tagTemplate(tagData) {
|
2
|
+
const suggestions = this.settings.whitelist || []
|
3
|
+
|
4
|
+
const possibleSuggestion = suggestions.find(
|
5
|
+
// eslint-disable-next-line eqeqeq
|
6
|
+
(item) => item.value == tagData.value,
|
7
|
+
)
|
8
|
+
const possibleLabel = possibleSuggestion
|
9
|
+
? possibleSuggestion.label
|
10
|
+
: tagData.value
|
11
|
+
|
12
|
+
return `
|
13
|
+
<tag title="${tagData.value}"
|
14
|
+
contenteditable='false'
|
15
|
+
spellcheck='false'
|
16
|
+
tabIndex="-1"
|
17
|
+
class="tagify__tag ${tagData.class ? tagData.class : ''}"
|
18
|
+
${this.getAttributes(tagData)}
|
19
|
+
>
|
20
|
+
<x title='' class='tagify__tag__removeBtn' role='button' aria-label='remove tag'></x>
|
21
|
+
<div>
|
22
|
+
<span class='tagify__tag-text'>${possibleLabel}</span>
|
23
|
+
</div>
|
24
|
+
</tag>
|
25
|
+
`
|
26
|
+
}
|
27
|
+
|
28
|
+
export function suggestionItemTemplate(tagData) {
|
29
|
+
return `
|
30
|
+
<div ${this.getAttributes(tagData)}
|
31
|
+
class='tagify__dropdown__item flex items-center ${
|
32
|
+
tagData.class ? tagData.class : ''
|
33
|
+
}'
|
34
|
+
tabindex="0"
|
35
|
+
role="option">
|
36
|
+
${
|
37
|
+
tagData.avatar
|
38
|
+
? `
|
39
|
+
<div class='rounded w-8 h-8 block mr-2'>
|
40
|
+
<img onerror="this.style.visibility='hidden'" class="w-full" src="${tagData.avatar}">
|
41
|
+
</div>`
|
42
|
+
: ''
|
43
|
+
}
|
44
|
+
<span>${tagData.label}</span>
|
45
|
+
</div>
|
46
|
+
`
|
47
|
+
}
|