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.

Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +4 -1
  4. data/app/assets/builds/avo.css +766 -301
  5. data/app/assets/builds/avo.js +212 -123
  6. data/app/assets/builds/avo.js.map +3 -3
  7. data/app/assets/stylesheets/avo.css +3 -33
  8. data/app/assets/stylesheets/css/alerts.css +35 -0
  9. data/app/assets/stylesheets/css/search.css +1 -1
  10. data/app/assets/stylesheets/css/tags.css +16 -0
  11. data/app/assets/svgs/heroicons/solid/user-remove.svg +1 -1
  12. data/app/components/avo/actions_component.html.erb +1 -2
  13. data/app/components/avo/alert_component.html.erb +1 -1
  14. data/app/components/avo/alert_component.rb +5 -24
  15. data/app/components/avo/button_component.rb +7 -46
  16. data/app/components/avo/fields/tags_field/edit_component.html.erb +27 -0
  17. data/app/components/avo/fields/tags_field/edit_component.rb +4 -0
  18. data/app/components/avo/fields/tags_field/index_component.html.erb +14 -0
  19. data/app/components/avo/fields/tags_field/index_component.rb +7 -0
  20. data/app/components/avo/fields/tags_field/show_component.html.erb +7 -0
  21. data/app/components/avo/fields/tags_field/show_component.rb +11 -0
  22. data/app/components/avo/fields/tags_field/tag_component.html.erb +9 -0
  23. data/app/components/avo/fields/tags_field/tag_component.rb +11 -0
  24. data/app/components/avo/filters_component.html.erb +1 -1
  25. data/app/components/avo/index/field_wrapper_component.html.erb +1 -1
  26. data/app/components/avo/index/grid_cover_empty_state_component.html.erb +1 -1
  27. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  28. data/app/components/avo/index/table_row_component.html.erb +1 -1
  29. data/app/components/avo/panel_component.html.erb +3 -3
  30. data/app/components/avo/panel_component.rb +1 -1
  31. data/app/components/avo/resource_component.rb +0 -50
  32. data/app/components/avo/sidebar/group_component.html.erb +2 -4
  33. data/app/components/avo/sidebar/heading_component.html.erb +1 -1
  34. data/app/components/avo/sidebar/link_component.html.erb +1 -1
  35. data/app/components/avo/sidebar/link_component.rb +1 -1
  36. data/app/components/avo/sidebar_component.html.erb +13 -5
  37. data/app/components/avo/sidebar_profile_component.html.erb +1 -1
  38. data/app/components/avo/views/resource_edit_component.html.erb +3 -28
  39. data/app/components/avo/views/resource_edit_component.rb +6 -4
  40. data/app/components/avo/views/resource_index_component.html.erb +9 -17
  41. data/app/components/avo/views/resource_new_component.html.erb +2 -8
  42. data/app/components/avo/views/resource_show_component.html.erb +6 -16
  43. data/app/components/avo/views/resource_show_component.rb +45 -0
  44. data/app/controllers/avo/actions_controller.rb +8 -23
  45. data/app/controllers/avo/associations_controller.rb +3 -3
  46. data/app/controllers/avo/base_controller.rb +16 -25
  47. data/app/controllers/avo/private_controller.rb +0 -1
  48. data/app/controllers/avo/search_controller.rb +2 -2
  49. data/app/helpers/avo/application_helper.rb +1 -1
  50. data/app/javascript/js/application.js +1 -1
  51. data/app/javascript/js/controllers/alerts_controller.js +26 -0
  52. data/app/javascript/js/controllers/base_controller.js +22 -0
  53. data/app/javascript/js/controllers/fields/key_value_controller.js +1 -1
  54. data/app/javascript/js/controllers/fields/tags_field_controller.js +86 -0
  55. data/app/javascript/js/controllers/fields/tags_field_helpers.js +47 -0
  56. data/app/javascript/js/controllers/filter_controller.js +1 -4
  57. data/app/javascript/js/controllers.js +4 -0
  58. data/app/views/avo/actions/show.html.erb +2 -5
  59. data/app/views/avo/partials/_logo.html.erb +1 -1
  60. data/app/views/avo/partials/_navbar.html.erb +6 -9
  61. data/app/views/avo/partials/_table_header.html.erb +2 -3
  62. data/app/views/avo/private/_links_and_buttons.html.erb +2 -2
  63. data/app/views/layouts/avo/application.html.erb +53 -50
  64. data/db/factories.rb +2 -0
  65. data/lib/avo/base_action.rb +6 -24
  66. data/lib/avo/base_resource.rb +6 -0
  67. data/lib/avo/concerns/handles_field_args.rb +36 -0
  68. data/lib/avo/engine.rb +1 -1
  69. data/lib/avo/fields/base_field.rb +2 -1
  70. data/lib/avo/fields/belongs_to_field.rb +4 -4
  71. data/lib/avo/fields/has_and_belongs_to_many_field.rb +2 -2
  72. data/lib/avo/fields/has_base_field.rb +0 -2
  73. data/lib/avo/fields/has_many_field.rb +2 -2
  74. data/lib/avo/fields/has_one_field.rb +2 -2
  75. data/lib/avo/fields/tags_field.rb +82 -0
  76. data/lib/avo/hosts/record_host.rb +7 -0
  77. data/lib/avo/licensing/pro_license.rb +2 -1
  78. data/lib/avo/version.rb +1 -1
  79. data/lib/generators/avo/templates/locales/avo.en.yml +4 -0
  80. data/public/avo-assets/avo.css +825 -262
  81. data/public/avo-assets/avo.js +212 -123
  82. data/public/avo-assets/avo.js.map +3 -3
  83. 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: :primary,
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-4">
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-6">
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: :primary,
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-12"
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].except("resource_ids")
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
- messages = get_messages response
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.attach_scope.present?
50
- query = Avo::Hosts::AssociationScopeHost.new(block: @field.attach_scope, query: query, parent: @model).handle
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 { redirect_back fallback_location: resource_path(model: @model, resource: @resource), notice: t("avo.attachment_class_attached", attachment_class: @related_resource.name) }
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
- # before_action :reset_pagination_if_filters_changed, only: :index
15
- # before_action :cache_applied_filters, only: :index
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
- @query = if field&.sortable.is_a?(Proc)
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
- extra_pagy_params = {}
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
- # def cache_applied_filters
365
- # # puts ["Rails.session->", session].inspect
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
- # # ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
370
- # end
360
+ ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
361
+ end
371
362
 
372
- # def reset_pagination_if_filters_changed
373
- # params[:page] = 1 if params[:filters] != session[:avo_applied_filters]
374
- # end
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
- # def applied_filters_cache_key
377
- # "avo.base_controller.#{@resource.model_key}.applied_filters"
378
- # end
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)
@@ -3,7 +3,6 @@ require_dependency "avo/application_controller"
3
3
  module Avo
4
4
  class PrivateController < ApplicationController
5
5
  def design
6
- @page_title = "Design [Private]"
7
6
  end
8
7
  end
9
8
  end
@@ -51,12 +51,12 @@ module Avo
51
51
  # Fetch the field
52
52
  field = belongs_to_field
53
53
 
54
- if field.attach_scope.present?
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.attach_scope, query: query, parent: parent).handle
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-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"
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 = false
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} 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}"
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
+ }