avo 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +5 -5
  4. data/app/assets/svgs/{dashboards-icon.svg → dashboards.svg} +1 -1
  5. data/app/assets/svgs/{resources-icon.svg → resources.svg} +0 -0
  6. data/app/assets/svgs/{tools-icon.svg → tools.svg} +0 -0
  7. data/app/components/avo/index/field_wrapper_component.rb +13 -0
  8. data/app/components/avo/profile_item_component.html.erb +2 -2
  9. data/app/components/avo/profile_item_component.rb +6 -0
  10. data/app/components/avo/sidebar/base_item_component.rb +31 -0
  11. data/app/components/avo/sidebar/group_component.html.erb +28 -0
  12. data/app/components/avo/sidebar/group_component.rb +4 -0
  13. data/app/components/avo/sidebar/heading_component.html.erb +14 -0
  14. data/app/components/avo/sidebar/heading_component.rb +17 -0
  15. data/app/components/avo/sidebar/item_switcher_component.html.erb +16 -0
  16. data/app/components/avo/sidebar/item_switcher_component.rb +15 -0
  17. data/app/components/avo/sidebar/link_component.html.erb +12 -0
  18. data/app/components/avo/sidebar/link_component.rb +28 -0
  19. data/app/components/avo/sidebar/section_component.html.erb +15 -0
  20. data/app/components/avo/sidebar/section_component.rb +9 -0
  21. data/app/components/avo/sidebar_component.html.erb +33 -24
  22. data/app/components/avo/sidebar_component.rb +3 -9
  23. data/app/components/avo/sidebar_profile_component.html.erb +10 -1
  24. data/app/controllers/avo/application_controller.rb +1 -2
  25. data/app/controllers/avo/base_controller.rb +9 -1
  26. data/app/helpers/avo/application_helper.rb +2 -0
  27. data/app/javascript/avo.js +13 -0
  28. data/app/javascript/js/controllers/menu_controller.js +65 -0
  29. data/app/javascript/js/controllers/search_controller.js +25 -10
  30. data/app/javascript/js/controllers.js +2 -0
  31. data/app/views/avo/base/_boolean_filter.html.erb +1 -1
  32. data/app/views/avo/base/_multiple_select_filter.html.erb +7 -4
  33. data/app/views/avo/base/_select_filter.html.erb +1 -1
  34. data/app/views/avo/base/_text_filter.html.erb +1 -1
  35. data/avo.gemspec +1 -0
  36. data/lib/avo/app.rb +8 -86
  37. data/lib/avo/concerns/fetches_things.rb +127 -0
  38. data/lib/avo/configuration.rb +4 -0
  39. data/lib/avo/fields/base_field.rb +2 -0
  40. data/lib/avo/hosts/base_host.rb +20 -0
  41. data/lib/avo/licensing/pro_license.rb +2 -1
  42. data/lib/avo/menu/base_item.rb +20 -0
  43. data/lib/avo/menu/builder.rb +77 -0
  44. data/lib/avo/menu/dashboard.rb +17 -0
  45. data/lib/avo/menu/group.rb +2 -0
  46. data/lib/avo/menu/link.rb +4 -0
  47. data/lib/avo/menu/menu.rb +2 -0
  48. data/lib/avo/menu/resource.rb +9 -0
  49. data/lib/avo/menu/section.rb +2 -0
  50. data/lib/avo/reloader.rb +10 -2
  51. data/lib/avo/services/authorization_service.rb +8 -2
  52. data/lib/avo/version.rb +1 -1
  53. data/lib/generators/avo/filter_generator.rb +2 -0
  54. data/lib/generators/avo/templates/filters/boolean_filter.tt +1 -1
  55. data/lib/generators/avo/templates/filters/multiple_select_filter.tt +11 -0
  56. data/lib/generators/avo/templates/filters/select_filter.tt +1 -1
  57. data/lib/generators/avo/templates/filters/text_filter.tt +1 -1
  58. data/lib/generators/avo/templates/tool/sidebar_item.tt +1 -1
  59. data/public/avo-assets/avo.css +21 -4
  60. data/public/avo-assets/avo.js +63 -63
  61. data/public/avo-assets/avo.js.map +3 -3
  62. metadata +42 -9
  63. data/app/components/avo/sidebar_heading_component.html.erb +0 -3
  64. data/app/components/avo/sidebar_heading_component.rb +0 -11
  65. data/app/components/avo/sidebar_item_component.html.erb +0 -3
  66. data/app/components/avo/sidebar_item_component.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ddda419ee56fc6958c6014c1c87c4fc42d33564b4c3c6642b369729f2948b48
4
- data.tar.gz: 8b76cd83e1c6fd3a4ea564ee09d4507b2de1034c390e3cf5d7ae4ec175a569e0
3
+ metadata.gz: 7aa88764021f40e6a32625569658172b802aa0c2ca31e0fb282cafa5ccc8ba33
4
+ data.tar.gz: 6e256e598fa099862691a623edfee9d53d0723d0101e681090686e4edd2ae460
5
5
  SHA512:
6
- metadata.gz: 52494cb524f70561a7ab099f44c1df29faf56897d7f487337a1d1831964e3732d31d69be7644b4adeb4a60b79e3199b2d931f5c2a726d8e2f136d6d40256b719
7
- data.tar.gz: 2ce7d38f174655a677de092a486357fef13deca755839bd305eaf65ca81f5fd33eea543b69939ce396f65f752673b1131910044bb5ac923b71c0178ff7b8736c
6
+ metadata.gz: ab132521622ae3dd8dfa75cbf513383b42e6361d64188ced7751560d9808c7b3bb591588ebe831460c992c1d0ee3a6aebed2abfce923d06ccb21da0502373c64
7
+ data.tar.gz: 3e60dfffdae92341ece77e3bf62236d6104db247399afea1fd6d7a684ad9d1323eff9be12f180ab9ee76f2097036f7cbb48a45720873b66e9dc958b4a0d8fbbf
data/Gemfile CHANGED
@@ -84,7 +84,7 @@ group :development do
84
84
  end
85
85
 
86
86
  group :development, :test do
87
- gem 'ap'
87
+ gem "awesome_print"
88
88
  gem "faker", require: false
89
89
  end
90
90
 
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.2.0)
4
+ avo (2.3.0)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
8
8
  chartkick
9
9
  countries
10
+ docile
10
11
  dry-initializer
11
12
  hotwire-rails
12
13
  httparty
@@ -89,13 +90,12 @@ GEM
89
90
  activerecord (>= 4.2)
90
91
  addressable (2.8.0)
91
92
  public_suffix (>= 2.0.2, < 5.0)
92
- ap (0.1.1)
93
- httparty (>= 0.7.7)
94
93
  appraisal (2.4.1)
95
94
  bundler
96
95
  rake
97
96
  thor (>= 0.14.0)
98
97
  ast (2.4.2)
98
+ awesome_print (1.9.2)
99
99
  aws-eventstream (1.2.0)
100
100
  aws-partitions (1.551.0)
101
101
  aws-sdk-core (3.125.5)
@@ -242,7 +242,7 @@ GEM
242
242
  net-protocol
243
243
  timeout
244
244
  nio4r (2.5.8)
245
- nokogiri (1.13.3)
245
+ nokogiri (1.13.4)
246
246
  mini_portile2 (~> 2.8.0)
247
247
  racc (~> 1.4)
248
248
  orm_adapter (0.5.0)
@@ -418,9 +418,9 @@ DEPENDENCIES
418
418
  active_median
419
419
  acts_as_list
420
420
  addressable
421
- ap
422
421
  appraisal
423
422
  avo!
423
+ awesome_print
424
424
  aws-sdk-s3
425
425
  bootsnap (>= 1.4.2)
426
426
  breadcrumbs_on_rails
@@ -1,4 +1,4 @@
1
- <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
1
+ <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
2
  <path d="M8.4 2H2V8.4H8.4V2Z" stroke="#757D8A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
3
3
  <path d="M18 2H11.6V8.4H18V2Z" stroke="#757D8A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
4
4
  <path d="M8.4 11.6001H2V18.0001H8.4V11.6001Z" stroke="#757D8A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
File without changes
@@ -17,6 +17,19 @@ class Avo::Index::FieldWrapperComponent < ViewComponent::Base
17
17
  result += " py-3"
18
18
  end
19
19
 
20
+ result += " #{text_align_classes}"
21
+
20
22
  result
21
23
  end
24
+
25
+ private
26
+
27
+ def text_align_classes
28
+ case @field.index_text_align.to_sym
29
+ when :right
30
+ 'text-right'
31
+ when :center
32
+ 'text-center'
33
+ end
34
+ end
22
35
  end
@@ -1,3 +1,3 @@
1
- <%= link_to @path, class: "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-gray-800 font-semibold hover:bg-blue-100 block px-4 py-1 w-full py-3 text-center rounded w-full" do %>
2
- <%= @icon if @icon.present? %> <%= @label %>
1
+ <%= link_to path, class: "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-gray-800 font-semibold hover:bg-blue-100 block px-4 py-1 w-full py-3 text-center rounded w-full", target: target do %>
2
+ <%= helpers.svg(icon, class: 'h-4 mr-1') if icon.present? %> <%= label %>
3
3
  <% end %>
@@ -1,6 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::ProfileItemComponent < ViewComponent::Base
4
+ attr_reader :label
5
+ attr_reader :icon
6
+ attr_reader :path
7
+ attr_reader :active
8
+ attr_reader :target
9
+
4
10
  def initialize(label: nil, icon: nil, path: nil, active: :inclusive, target: nil)
5
11
  @label = label
6
12
  @icon = icon
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Sidebar::BaseItemComponent < ViewComponent::Base
4
+ attr_reader :item
5
+
6
+ def initialize(item: nil)
7
+ @item = item
8
+ end
9
+
10
+ def items
11
+ item.items
12
+ end
13
+
14
+ def key
15
+ result = "avo.#{request.host}.main_menu.#{item.name.underscore}"
16
+
17
+ if item.icon.present?
18
+ result += ".#{item.icon.parameterize.underscore}"
19
+ end
20
+
21
+ result
22
+ end
23
+
24
+ def collapsable
25
+ item.collapsable
26
+ end
27
+
28
+ def collapsed
29
+ item.collapsed
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ <div class="pb-2 space-y-1"
2
+ <% if collapsable %>
3
+ data-controller="menu"
4
+ data-menu-target="self"
5
+ data-menu-key-param="<%= key %>"
6
+ data-menu-default-collapsed-state="<%= collapsed ? 'collapsed' : 'expanded' %>"
7
+ <% end %>
8
+ >
9
+ <div class="flex justify-between px-10 pt-2 pb-0 text-gray-600 ">
10
+ <div class="flex items-center text-xs uppercase font-semibold leading-none">
11
+ <%= item.name %>
12
+ </div>
13
+ <% if collapsable %>
14
+ <div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
15
+ data-action="click->menu#triggerCollapse"
16
+ data-menu-target="svg"
17
+ data-menu-key-param="<%= key %>"
18
+ >
19
+ <%= helpers.svg 'heroicons/outline/chevron-down', class: "h-4"%>
20
+ </div>
21
+ <% end %>
22
+ </div>
23
+ <div class="w-full space-y-1 <%= 'hidden' if collapsed %>" data-menu-target="items">
24
+ <% items.each do |item| %>
25
+ <%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
26
+ <% end %>
27
+ </div>
28
+ </div>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Sidebar::GroupComponent < Avo::Sidebar::BaseItemComponent
4
+ end
@@ -0,0 +1,14 @@
1
+ <div class="flex justify-between p-4 py-2 text-gray-500">
2
+ <div class="flex items-center text-sm uppercase font-semibold leading-none space-x-1">
3
+ <span class="min-w-[20px]"><%= icon %></span> <span><%= label %></span>
4
+ </div>
5
+ <% if collapsable %>
6
+ <div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
7
+ data-action="click->menu#triggerCollapse"
8
+ data-menu-key-param="<%= key %>"
9
+ data-menu-target="svg"
10
+ >
11
+ <%= helpers.svg 'heroicons/outline/chevron-down', class: 'h-5'%>
12
+ </div>
13
+ <% end %>
14
+ </div>
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Sidebar::HeadingComponent < ViewComponent::Base
4
+ attr_reader :collapsable
5
+ attr_reader :collapsed
6
+ attr_reader :icon
7
+ attr_reader :key
8
+ attr_reader :label
9
+
10
+ def initialize(label: nil, icon: nil, collapsable: false, collapsed: false, key: nil)
11
+ @collapsable = collapsable
12
+ @collapsed = collapsed
13
+ @icon = icon
14
+ @key = key
15
+ @label = label
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ <% if item.is_a? Avo::Menu::Link %>
2
+ <%= render Avo::Sidebar::LinkComponent.new label: item.name, path: item.path, target: item.target %>
3
+ <% end %>
4
+ <% if item.is_a? Avo::Menu::Resource %>
5
+ <%= render Avo::Sidebar::LinkComponent.new label: resource.navigation_label, path: helpers.resources_path(resource: resource) %>
6
+ <% end %>
7
+ <% if item.is_a? Avo::Menu::Dashboard %>
8
+ <%= render Avo::Sidebar::LinkComponent.new label: dashboard.navigation_label, path: dashboard.navigation_path %>
9
+ <% end %>
10
+ <% if item.is_a? Avo::Menu::Section %>
11
+ <%= render Avo::Sidebar::SectionComponent.new item: item %>
12
+ <% end %>
13
+ <% if item.is_a? Avo::Menu::Group %>
14
+ <%= render Avo::Sidebar::GroupComponent.new item: item %>
15
+ <% end %>
16
+
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Sidebar::ItemSwitcherComponent < Avo::Sidebar::BaseItemComponent
4
+ def resource
5
+ item.parsed_resource
6
+ end
7
+
8
+ def dashboard
9
+ item.parsed_dashboard
10
+ end
11
+
12
+ def render?
13
+ item.visible?
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ <% if path.present? %>
2
+ <%= send link_method, path, class: classes, active: active, target: target do %>
3
+ <%= label %>
4
+ <% if target == :_blank %>
5
+ <%= helpers.svg('heroicons/outline/external-link', class: 'self-center ml-auto h-3') %>
6
+ <% end %>
7
+ <% end %>
8
+ <% else %>
9
+ <%= content_tag :div, class: classes, active: active, target: target do %>
10
+ <%= label %>
11
+ <% end %>
12
+ <% end %>
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Sidebar::LinkComponent < ViewComponent::Base
4
+ attr_reader :active
5
+ attr_reader :target
6
+ attr_reader :label
7
+ attr_reader :path
8
+
9
+ def initialize(label: nil, path: nil, active: :inclusive, target: nil)
10
+ @label = label
11
+ @path = path
12
+ @active = active
13
+ @target = target
14
+ end
15
+
16
+ def is_external?
17
+ URI(path).scheme.present?
18
+ end
19
+
20
+ # For external links active_link_to marks them all as active.
21
+ def link_method
22
+ is_external? ? :link_to : :active_link_to
23
+ end
24
+
25
+ def classes
26
+ "px-4 flex-1 flex mx-6 leading-none py-2 text-black rounded font-medium hover:bg-gray-150"
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ <div class="space-y-1"
2
+ <% if collapsable %>
3
+ data-controller="menu"
4
+ data-menu-target="self"
5
+ data-menu-key-param="<%= key %>"
6
+ data-menu-default-collapsed-state="<%= collapsed ? 'collapsed' : 'expanded' %>"
7
+ <% end %>
8
+ >
9
+ <%= render Avo::Sidebar::HeadingComponent.new label: item.name, icon: helpers.svg(icon, class: "h-5"), collapsable: item.collapsable, collapsed: collapsed, key: key %>
10
+ <div class="w-full space-y-1 <%= 'hidden' if collapsed %>" data-menu-target="items">
11
+ <% items.each do |item| %>
12
+ <%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
13
+ <% end %>
14
+ </div>
15
+ </div>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::Sidebar::SectionComponent < Avo::Sidebar::BaseItemComponent
4
+ def icon
5
+ return nil if item.icon.nil?
6
+
7
+ item.icon
8
+ end
9
+ end
@@ -7,41 +7,50 @@
7
7
  </div>
8
8
  </div>
9
9
  <div class="flex-1 flex flex-col justify-between overflow-auto mt-3">
10
- <div class="space-y-6">
11
- <%= render Avo::SidebarItemComponent.new label: 'Get started', path: helpers.avo.root_path, active: :exclusive if Rails.env.development? && Avo.configuration.home_path.nil? %>
10
+ <div class="space-y-6 mb-4">
11
+ <%= render Avo::Sidebar::LinkComponent.new label: 'Get started', path: helpers.avo.root_path, active: :exclusive if Rails.env.development? && Avo.configuration.home_path.nil? %>
12
12
 
13
- <% if dashboards.present? %>
14
- <div>
15
- <%= render Avo::SidebarHeadingComponent.new label: t('avo.dashboards'), icon: helpers.svg('dashboards-icon', class: 'h-4') %>
16
-
17
- <div class="w-full space-y-1">
18
- <% dashboards.sort_by { |r| r.navigation_label }.each do |dashboard| %>
19
- <%= render Avo::SidebarItemComponent.new label: dashboard.navigation_label, path: dashboard.navigation_path %>
20
- <% end %>
21
- </div>
13
+ <% if Avo::App.license.has_with_trial(:menu_editor) && Avo.configuration.main_menu.present? %>
14
+ <div class="text-black space-y-4">
15
+ <% Avo::App.main_menu.items.each do |item| %>
16
+ <%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
17
+ <% end %>
22
18
  </div>
23
- <% end %>
19
+ <% else %>
24
20
 
25
- <div>
26
- <%= render Avo::SidebarHeadingComponent.new label: t('avo.resources'), icon: helpers.svg('resources-icon', class: 'h-4') %>
21
+ <% if dashboards.present? %>
22
+ <div>
23
+ <%= render Avo::Sidebar::HeadingComponent.new label: t('avo.dashboards'), icon: helpers.svg('dashboards', class: 'h-4') %>
27
24
 
28
- <div class="w-full space-y-1">
29
- <% resources.sort_by { |r| r.navigation_label }.each do |resource| %>
30
- <%= render Avo::SidebarItemComponent.new label: resource.navigation_label, path: helpers.resources_path(resource: resource) %>
31
- <% end %>
32
- </div>
33
- </div>
25
+ <div class="w-full space-y-1">
26
+ <% dashboards.sort_by { |r| r.navigation_label }.each do |dashboard| %>
27
+ <%= render Avo::Sidebar::LinkComponent.new label: dashboard.navigation_label, path: dashboard.navigation_path %>
28
+ <% end %>
29
+ </div>
30
+ </div>
31
+ <% end %>
34
32
 
35
- <% if tools.present? %>
36
33
  <div>
37
- <%= render Avo::SidebarHeadingComponent.new label: t('avo.tools'), icon: helpers.svg('tools-icon', class: 'h-4') %>
34
+ <%= render Avo::Sidebar::HeadingComponent.new label: t('avo.resources'), icon: helpers.svg('resources', class: 'h-4') %>
38
35
 
39
36
  <div class="w-full space-y-1">
40
- <% tools.each do |partial| %>
41
- <%= render partial: "/avo/sidebar/items/#{partial}" %>
37
+ <% resources.sort_by { |r| r.navigation_label }.each do |resource| %>
38
+ <%= render Avo::Sidebar::LinkComponent.new label: resource.navigation_label, path: helpers.resources_path(resource: resource) %>
42
39
  <% end %>
43
40
  </div>
44
41
  </div>
42
+
43
+ <% if tools.present? %>
44
+ <div>
45
+ <%= render Avo::Sidebar::HeadingComponent.new label: t('avo.tools'), icon: helpers.svg('tools', class: 'h-4') %>
46
+
47
+ <div class="w-full space-y-1">
48
+ <% tools.each do |partial| %>
49
+ <%= render partial: "/avo/sidebar/items/#{partial}" %>
50
+ <% end %>
51
+ </div>
52
+ </div>
53
+ <% end %>
45
54
  <% end %>
46
55
 
47
56
  <%= render partial: "/avo/partials/sidebar_extra" %>
@@ -2,20 +2,14 @@
2
2
 
3
3
  class Avo::SidebarComponent < ViewComponent::Base
4
4
  def dashboards
5
- return [] if Avo::App.license.lacks_with_trial(:dashboards)
6
-
7
- Avo::App.get_dashboards(helpers._current_user).select do |dashboard|
8
- dashboard.is_visible?
9
- end
5
+ Avo::App.dashboards_for_navigation
10
6
  end
11
7
 
12
8
  def resources
13
- Avo::App.resources_navigation(helpers._current_user)
9
+ Avo::App.resources_for_navigation
14
10
  end
15
11
 
16
12
  def tools
17
- return [] if Avo::App.license.lacks_with_trial(:custom_tools)
18
-
19
- Avo::App.get_sidebar_partials
13
+ Avo::App.tools_for_navigation
20
14
  end
21
15
  end
@@ -25,8 +25,17 @@
25
25
  class="hidden absolute flex flex-col inset-auto right-0 -mt-12 bg-white rounded min-w-[200px] shadow-context -translate-y-full"
26
26
  data-toggle-panel-target="panel"
27
27
  >
28
+ <% if Avo::App.license.has_with_trial(:menu_editor) && Avo.configuration.profile_menu.present? %>
29
+ <div class="text-black space-y-4">
30
+ <% Avo::App.profile_menu.items.each do |item| %>
31
+ <% if item.is_a? Avo::Menu::Link %>
32
+ <%= render Avo::ProfileItemComponent.new label: item.name, path: item.path, icon: item.icon %>
33
+ <% end %>
34
+ <% end %>
35
+ </div>
36
+ <% end %>
28
37
  <%# Example link below %>
29
- <%#= render Avo::ProfileItemComponent.new label: 'Profile', path: '/profile', icon: helpers.svg('user-circle', class: 'h-4 mr-1') %>
38
+ <%#= render Avo::ProfileItemComponent.new label: 'Profile', path: '/profile', icon: 'user-circle' %>
30
39
  <%= button_to helpers.main_app.send(destroy_user_session_path),
31
40
  method: :delete,
32
41
  form: { "data-turbo" => "false" },
@@ -19,7 +19,6 @@ module Avo
19
19
  before_action :set_container_classes
20
20
  before_action :add_initial_breadcrumbs
21
21
  before_action :set_view
22
- before_action :set_model_to_fill
23
22
 
24
23
  rescue_from Pundit::NotAuthorizedError, with: :render_unauthorized
25
24
  rescue_from ActiveRecord::RecordInvalid, with: :exception_logger
@@ -285,7 +284,7 @@ module Avo
285
284
  end
286
285
 
287
286
  def set_locale
288
- I18n.locale = params[:locale] || I18n.default_locale
287
+ I18n.locale = params[:set_locale] || I18n.default_locale
289
288
 
290
289
  I18n.default_locale = I18n.locale
291
290
  end
@@ -44,7 +44,15 @@ module Avo
44
44
  unless @index_params[:sort_by].eql? :created_at
45
45
  @query = @query.unscope(:order)
46
46
  end
47
- @query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
47
+
48
+ # Check if the sortable field option is actually a proc and we need to do a custom sort
49
+ field_id = @index_params[:sort_by].to_sym
50
+ field = @resource.get_field_definitions.find { |field| field.id == field_id }
51
+ if field&.sortable.is_a?(Proc)
52
+ @query = field.sortable.call(@query, @index_params[:sort_direction])
53
+ else
54
+ @query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
55
+ end
48
56
  end
49
57
 
50
58
  # Apply filters
@@ -71,6 +71,8 @@ module Avo
71
71
  end
72
72
 
73
73
  def svg(file_name, **args)
74
+ return if file_name.nil?
75
+
74
76
  options = {}
75
77
  options[:class] = args[:class].present? ? args[:class] : ""
76
78
  options[:class] += args[:extra_class].present? ? " #{args[:extra_class]}" : ""
@@ -84,3 +84,16 @@ document.addEventListener('turbo:submit-end', () => document.body.classList.remo
84
84
  document.addEventListener('turbo:before-cache', () => {
85
85
  document.querySelectorAll('[data-turbo-remove-before-cache]').forEach((element) => element.remove())
86
86
  })
87
+
88
+ window.Avo = window.Avo || { configuration: {} }
89
+
90
+ window.Avo.menus = {
91
+ resetCollapsedState() {
92
+ Array.from(document.querySelectorAll('[data-menu-key-param]'))
93
+ .map((i) => i.getAttribute('data-menu-key-param'))
94
+ .filter(Boolean)
95
+ .forEach((key) => {
96
+ window.localStorage.removeItem(key)
97
+ })
98
+ },
99
+ }
@@ -0,0 +1,65 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = ['svg', 'items', 'self'];
5
+
6
+ collapsed = true;
7
+
8
+ get key() {
9
+ return this.selfTarget.getAttribute('data-menu-key-param')
10
+ }
11
+
12
+ get defaultState() {
13
+ return this.selfTarget.getAttribute('data-menu-default-collapsed-state')
14
+ }
15
+
16
+ get userState() {
17
+ return window.localStorage.getItem(this.key)
18
+ }
19
+
20
+ set userState(payload) {
21
+ window.localStorage.setItem(this.key, payload)
22
+ }
23
+
24
+ get initiallyCollapsed() {
25
+ if (this.userState === 'collapsed' || this.defaultState === 'collapsed') return true
26
+ if (this.userState === 'expanded' || this.defaultState === 'expanded') return false
27
+
28
+ return false
29
+ }
30
+
31
+ connect() {
32
+ if (this.initiallyCollapsed) {
33
+ this.collapsed = true
34
+ this.markCollapsed()
35
+ } else {
36
+ this.collapsed = false
37
+ this.markExpanded()
38
+ }
39
+ }
40
+
41
+ triggerCollapse() {
42
+ this.collapsed = !this.collapsed
43
+ this.userState = this.collapsed ? 'collapsed' : 'expanded'
44
+
45
+ this.updateDom()
46
+ }
47
+
48
+ updateDom() {
49
+ if (this.collapsed) {
50
+ this.markCollapsed()
51
+ } else {
52
+ this.markExpanded()
53
+ }
54
+ }
55
+
56
+ markCollapsed() {
57
+ this.svgTarget.classList.add('rotate-90')
58
+ this.itemsTarget.classList.add('hidden')
59
+ }
60
+
61
+ markExpanded() {
62
+ this.svgTarget.classList.remove('rotate-90')
63
+ this.itemsTarget.classList.remove('hidden')
64
+ }
65
+ }
@@ -105,7 +105,7 @@ export default class extends Controller {
105
105
  return `${data.header.toUpperCase()} ${data.help}`
106
106
  },
107
107
  item({ item, createElement }) {
108
- let element = ''
108
+ const children = []
109
109
 
110
110
  if (item._avatar) {
111
111
  let classes
@@ -120,22 +120,37 @@ export default class extends Controller {
120
120
  break
121
121
  }
122
122
 
123
- element += `<img src="${item._avatar}" alt="${item._label}" class="flex-shrink-0 w-8 h-8 my-[2px] inline mr-2 ${classes}" />`
123
+ children.push(
124
+ createElement('img', {
125
+ src: item._avatar,
126
+ alt: item._label,
127
+ class: `flex-shrink-0 w-8 h-8 my-[2px] inline mr-2 ${classes}`,
128
+ }),
129
+ )
124
130
  }
125
- element += `<div>${item._label}`
126
131
 
132
+ const labelChildren = [item._label]
127
133
  if (item._description) {
128
- element += `<div class="aa-ItemDescription">${item._description}</div>`
134
+ labelChildren.push(
135
+ createElement(
136
+ 'div',
137
+ {
138
+ class: 'aa-ItemDescription',
139
+ },
140
+ item._description,
141
+ ),
142
+ )
129
143
  }
130
144
 
131
- element += '</div>'
145
+ children.push(createElement('div', null, labelChildren))
132
146
 
133
- return createElement('div', {
134
- class: 'flex',
135
- dangerouslySetInnerHTML: {
136
- __html: element,
147
+ return createElement(
148
+ 'div',
149
+ {
150
+ class: 'flex',
137
151
  },
138
- })
152
+ children,
153
+ )
139
154
  },
140
155
  noResults() {
141
156
  return that.translationKeys.no_item_found.replace(
@@ -15,6 +15,7 @@ import ItemSelectAllController from './controllers/item_select_all_controller'
15
15
  import ItemSelectorController from './controllers/item_selector_controller'
16
16
  import KeyValueController from './controllers/fields/key_value_controller'
17
17
  import LoadingButtonController from './controllers/loading_button_controller'
18
+ import MenuController from './controllers/menu_controller'
18
19
  import MobileController from './controllers/mobile_controller'
19
20
  import ModalController from './controllers/modal_controller'
20
21
  import MultipleSelectFilterController from './controllers/multiple_select_filter_controller'
@@ -39,6 +40,7 @@ application.register('hidden-input', HiddenInputController)
39
40
  application.register('item-select-all', ItemSelectAllController)
40
41
  application.register('item-selector', ItemSelectorController)
41
42
  application.register('loading-button', LoadingButtonController)
43
+ application.register('menu', MenuController)
42
44
  application.register('mobile', MobileController)
43
45
  application.register('modal', ModalController)
44
46
  application.register('multiple-select-filter', MultipleSelectFilterController)