avo 2.6.0 → 2.7.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +3 -1
  4. data/app/assets/svgs/heroicons/solid/user-remove.svg +1 -1
  5. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +3 -0
  6. data/app/components/avo/fields/has_one_field/show_component.html.erb +2 -2
  7. data/app/components/avo/fields/tags_field/index_component.html.erb +1 -1
  8. data/app/components/avo/index/resource_controls_component.rb +2 -1
  9. data/app/components/avo/paginator_component.html.erb +2 -2
  10. data/app/components/avo/resource_component.rb +7 -0
  11. data/app/components/avo/sidebar_component.html.erb +1 -1
  12. data/app/components/avo/views/resource_edit_component.html.erb +5 -0
  13. data/app/components/avo/views/resource_edit_component.rb +9 -3
  14. data/app/components/avo/views/resource_show_component.html.erb +7 -2
  15. data/app/components/avo/views/resource_show_component.rb +5 -2
  16. data/app/controllers/avo/search_controller.rb +33 -13
  17. data/app/javascript/js/controllers/search_controller.js +9 -1
  18. data/app/views/avo/partials/_custom_tools_alert.html.erb +16 -5
  19. data/app/views/avo/partials/_global_search.html.erb +1 -1
  20. data/app/views/avo/partials/_resource_search.html.erb +1 -1
  21. data/app/views/layouts/avo/application.html.erb +1 -1
  22. data/lib/avo/app.rb +2 -0
  23. data/lib/avo/base_resource.rb +1 -0
  24. data/lib/avo/base_resource_tool.rb +34 -0
  25. data/lib/avo/concerns/has_tools.rb +47 -0
  26. data/lib/avo/engine.rb +2 -1
  27. data/lib/avo/hosts/base_host.rb +2 -0
  28. data/lib/avo/licensing/pro_license.rb +1 -0
  29. data/lib/avo/version.rb +1 -1
  30. data/lib/generators/avo/install_generator.rb +3 -0
  31. data/lib/generators/avo/resource_tool_generator.rb +40 -0
  32. data/lib/generators/avo/templates/resource_tools/partial.tt +37 -0
  33. data/lib/generators/avo/templates/resource_tools/resource_tool.tt +4 -0
  34. data/public/avo-assets/avo.css +4 -8
  35. data/public/avo-assets/avo.js +1 -1
  36. data/public/avo-assets/avo.js.map +2 -2
  37. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 886b0051c7a9b11b46dcd99cdb419559f1b7326d8b8a90a3514d110935bf6d05
4
- data.tar.gz: 4e21cf6d49133dc5551fdfea9759366bedfd3d487dcfd1df562a40b808383ef2
3
+ metadata.gz: c49705a72c31381fe9a133a58b1953d56513fda407157b4c92a17f933424b3e5
4
+ data.tar.gz: 6680ac6be68f89e9ecdbce81f3ee2ecd18915369509c27914835f824f8da550e
5
5
  SHA512:
6
- metadata.gz: 33608aef95479841514e639bd5e08c81f05a1fa4c73fa3067b59530774e7f11024a5f3e6f9c27e6a10125f9c6d9fef3684436b8b12deeb7c21d988ffa18379ed
7
- data.tar.gz: ec6b40cda212f0a2ab04d0e672d099a5ebb672b2351cdfa0d8354c7b1ee3334704b5f128421e642d88da09b56fac98178ed7b7da93a370ee8cc53645419559df
6
+ metadata.gz: b34915f9540a2e057df9cde8c228077eec22bafee204cb076399a1eb2b3ee36834aff7fb50b9fb57060c3271cb31387d7fcbd27642fca8222a245db04cc70182
7
+ data.tar.gz: 0a3ce0cad78f3fd6b3cde3534d879e9729569e3e3ac8ea595fd4266c77b6025227bad0154a2f69263c1e8858337906c788d63f635976e2678a94f95d5419d72b
data/Gemfile CHANGED
@@ -150,3 +150,5 @@ gem "active_median"
150
150
  gem 'acts_as_list'
151
151
 
152
152
  gem 'acts-as-taggable-on', '~> 9.0'
153
+
154
+ gem "bundler-integrity", "~> 1.0"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.6.0)
4
+ avo (2.7.0)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -125,6 +125,7 @@ GEM
125
125
  railties (>= 5.0)
126
126
  builder (3.2.4)
127
127
  bump (0.10.0)
128
+ bundler-integrity (1.0.7)
128
129
  byebug (11.1.3)
129
130
  capybara (3.36.0)
130
131
  addressable
@@ -432,6 +433,7 @@ DEPENDENCIES
432
433
  bootsnap (>= 1.4.2)
433
434
  breadcrumbs_on_rails
434
435
  bump
436
+ bundler-integrity (~> 1.0)
435
437
  byebug
436
438
  capybara (= 3.36)
437
439
  countries
@@ -1,3 +1,3 @@
1
- or<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
2
2
  <path d="M11 6a3 3 0 11-6 0 3 3 0 016 0zM14 17a6 6 0 00-12 0h12zM13 8a1 1 0 100 2h4a1 1 0 100-2h-4z"/>
3
3
  </svg>
@@ -7,6 +7,9 @@
7
7
  data-via-association-id="<%= @field.id %>"
8
8
  data-via-reflection-id="<%= @field.model.id %>"
9
9
  data-via-reflection-class="<%= @field.model.class.to_s %>"
10
+ data-via-parent-resource-id="<%= params[:via_resource_id] %>"
11
+ data-via-parent-resource-class="<%= params[:via_relation_class] %>"
12
+ data-via-relation="<%= params[:via_relation] %>"
10
13
  ></div>
11
14
  <div class="relative w-full" autocomplete="off">
12
15
  <%= @form.text_field @foreign_key,
@@ -3,11 +3,11 @@
3
3
  <%= render(Avo::LoadingComponent.new(title: @field.name)) %>
4
4
  </turbo-frame>
5
5
  <% else %>
6
- <%= render Avo::PanelComponent.new(title: @field.id.capitalize) do |c| %>
6
+ <%= render Avo::PanelComponent.new(title: @field.name) do |c| %>
7
7
  <% c.tools do %>
8
8
  <% if !@field.readonly && can_attach? %>
9
9
  <%= a_link attach_path, icon: 'heroicons/outline/link', color: :primary, 'data-turbo-frame': 'attach_modal' do %>
10
- <%= t('avo.attach_item', item: @field.id).capitalize %>
10
+ <%= t('avo.attach_item', item: @field.name) %>
11
11
  <% end %>
12
12
  <% end %>
13
13
  <% end %>
@@ -1,4 +1,4 @@
1
- <%= index_field_wrapper field: @field do %>
1
+ <%= index_field_wrapper field: @field, flush: true do %>
2
2
  <div class="flex gap-1 items-center flex-nowrap">
3
3
  <% value.take(3).each do |item| %>
4
4
  <%= render Avo::Fields::TagsField::TagComponent.new(label: label_from_item(item)) %>
@@ -41,7 +41,8 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
41
41
  end
42
42
 
43
43
  def edit_path
44
- args = {}
44
+ #Add the `view` param to let Avo know where to redirect back when the user clicks the `Cancel` button.
45
+ args = {via_view: 'index'}
45
46
 
46
47
  if @parent_model.present?
47
48
  args = {
@@ -25,9 +25,9 @@
25
25
  </div>
26
26
  <% per_page_options.each do |option| %>
27
27
  <% if parent_model.present? %>
28
- <%= link_to "Change to #{option} items per page", helpers.related_resources_path(parent_model, parent_model, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
28
+ <%= link_to "Change to #{option} items per page", helpers.related_resources_path(parent_model, parent_model, per_page: option, keep_query_params: true, page: 1), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
29
29
  <% else %>
30
- <%= link_to "Change to #{option} items per page", helpers.resources_path(resource: resource, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
30
+ <%= link_to "Change to #{option} items per page", helpers.resources_path(resource: resource, per_page: option, keep_query_params: true, page: 1), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
31
31
  <% end %>
32
32
  <% end %>
33
33
  </div>
@@ -1,4 +1,10 @@
1
1
  class Avo::ResourceComponent < Avo::BaseComponent
2
+ attr_reader :fields_by_panel
3
+ attr_reader :has_one_panels
4
+ attr_reader :has_many_panels
5
+ attr_reader :has_as_belongs_to_many_panels
6
+ attr_reader :resource_tools
7
+
2
8
  def can_create?
3
9
  return authorize_association_for(:create) if @reflection.present?
4
10
 
@@ -75,6 +81,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
75
81
  @has_one_panels = []
76
82
  @has_many_panels = []
77
83
  @has_as_belongs_to_many_panels = []
84
+ @resource_tools = @resource.tools
78
85
  end
79
86
 
80
87
  def via_resource?
@@ -1,5 +1,5 @@
1
1
  <div
2
- class="fixed z-[60] t-0 application-sidebar hidden lg:flex flex-1 border-r lg:border-none bg-none min-w-[16rem] h-[calc(100vh-4rem)] bg-gray-25 lg:bg-transparent <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
2
+ class="fixed z-[60] t-0 application-sidebar w-64 hidden lg:flex flex-1 border-r lg:border-none bg-none h-[calc(100vh-4rem)] bg-gray-25 lg:bg-transparent <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
3
3
  data-mobile-target="sidebar"
4
4
  >
5
5
  <div class="flex flex-col w-full h-full">
@@ -64,5 +64,10 @@
64
64
  <%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
65
65
  <% end %>
66
66
  <% end %>
67
+ <% if resource_tools.present? %>
68
+ <% resource_tools.each do |tool, index| %>
69
+ <%= render tool.partial, tool: tool %>
70
+ <% end %>
71
+ <% end %>
67
72
  <% end %>
68
73
  </div>
@@ -4,8 +4,6 @@ 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
9
 
@@ -15,7 +13,9 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
15
13
  def back_path
16
14
  if via_resource?
17
15
  helpers.resource_path(model: params[:via_resource_class].safe_constantize, resource: relation_resource, resource_id: params[:via_resource_id])
18
- else
16
+ elsif via_index?
17
+ helpers.resources_path(resource: @resource)
18
+ else # via resource show page
19
19
  helpers.resource_path(model: @resource.model, resource: @resource)
20
20
  end
21
21
  end
@@ -25,4 +25,10 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
25
25
  def can_see_the_save_button?
26
26
  @resource.authorization.authorize_action :edit, raise_exception: false
27
27
  end
28
+
29
+ private
30
+
31
+ def via_index?
32
+ params[:via_view] == 'index'
33
+ end
28
34
  end
@@ -15,9 +15,9 @@
15
15
  form_class: 'flex flex-col sm:flex-row sm:inline-flex',
16
16
  style: :text,
17
17
  data: {
18
- confirm: "Are you sure you want to detach this #{@reflection.name.to_s}."
18
+ confirm: "Are you sure you want to detach this #{title}."
19
19
  } do %>
20
- <%= t('avo.detach_item', item: @reflection.name.to_s).capitalize %>
20
+ <%= t('avo.detach_item', item: title).capitalize %>
21
21
  <% end %>
22
22
  <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource %>
23
23
  <% end %>
@@ -93,6 +93,11 @@
93
93
  <%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
94
94
  <% end %>
95
95
  <% end %>
96
+ <% if resource_tools.present? %>
97
+ <% resource_tools.each do |tool, index| %>
98
+ <%= render tool.partial, tool: tool %>
99
+ <% end %>
100
+ <% end %>
96
101
  <% end %>
97
102
 
98
103
  <% if should_display_invalid_fields_errors? %>
@@ -4,8 +4,6 @@ class Avo::Views::ResourceShowComponent < 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, reflection: nil, parent_model: nil, resource_panel: nil, actions: [])
10
8
  @resource = resource
11
9
  @reflection = reflection
@@ -17,6 +15,7 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
17
15
 
18
16
  def title
19
17
  if @reflection.present?
18
+ return field.name if has_one_field?
20
19
  reflection_resource.name
21
20
  else
22
21
  @resource.panels.first[:name]
@@ -50,4 +49,8 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
50
49
  def should_display_invalid_fields_errors?
51
50
  (Rails.env.development? || Rails.env.test?) && @resource.invalid_fields.present?
52
51
  end
52
+
53
+ def has_one_field?
54
+ field.present? and field.class == Avo::Fields::HasOneField
55
+ end
53
56
  end
@@ -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 oute if this is a belongs_to search
50
- if params[:via_reflection_class].present? && params[:via_reflection_id].present?
51
- # Fetch the field
52
- field = belongs_to_field
53
-
54
- if field.attach_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.attach_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)
@@ -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')
@@ -1,6 +1,17 @@
1
- <div class="w-full inset-auto bottom-0 z-50 mb-4 opacity-75 hover:opacity-100 transition-opacity duration-150">
2
- <a href="https://avohq.io/pricing" target="_blank" class="rounded bg-orange-700 text-white py-2 px-4 text-sm block items-center flex leading-tight">
3
- <%= svg "exclamation", class: "h-6 inline mr-2 text-bold flex-shrink-0 mr-1" %> Warning. <%= @custom_tools_alert_visible %> This page will not be visible in a production environment.
4
- </a>
5
- </div>
1
+ <% if @custom_tools_alert_visible %>
2
+ <div class="w-full inset-auto bottom-0 z-50 mb-4 opacity-75 hover:opacity-100 transition-opacity duration-150">
3
+ <a href="https://avohq.io/pricing" target="_blank" class="rounded bg-orange-700 text-white py-2 px-4 text-sm block items-center flex leading-tight">
4
+ <%= svg "exclamation", class: "h-6 inline mr-2 text-bold flex-shrink-0 mr-1" %> Warning. <%= @custom_tools_alert_visible %> This page will not be visible in a production environment.
5
+ </a>
6
+ </div>
7
+ <% end %>
6
8
 
9
+ <% if Avo::App.error_messages.present? %>
10
+ <% Avo::App.error_messages.each do |message| %>
11
+ <div class="w-full inset-auto bottom-0 z-50 mb-4 opacity-75 hover:opacity-100 transition-opacity duration-150">
12
+ <a href="https://avohq.io/pricing" target="_blank" class="rounded bg-orange-700 text-white py-2 px-4 text-sm block items-center flex leading-tight">
13
+ <%= svg "exclamation", class: "h-6 inline mr-2 text-bold flex-shrink-0 mr-1" %> <%= message %>
14
+ </a>
15
+ </div>
16
+ <% end %>
17
+ <% end %>
@@ -1,4 +1,4 @@
1
- <div data-controller="search" class="relative flex global-search rounded border border-gray-200 sm:min-w-[16rem]" data-turbo-remove-before-cache>
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"
@@ -1,4 +1,4 @@
1
- <div data-controller="search" class="resource-search flex items-center h-full w-full" data-turbo-remove-before-cache>
1
+ <div data-controller="search" class="resource-search flex items-center h-full w-full">
2
2
  <div class="w-full"
3
3
  data-search-target="autocomplete"
4
4
  data-search-resource="<%= resource %>"
@@ -26,7 +26,7 @@
26
26
  <%= render Avo::SidebarComponent.new %>
27
27
  <div class="lg:pl-64 flex-1 flex flex-col min-h-full max-w-full">
28
28
  <div class="content p-4 lg:p-6 flex-1 flex flex-col justify-between items-stretch <%= @container_classes %>">
29
- <%= render partial: "avo/partials/custom_tools_alert" if @custom_tools_alert_visible %>
29
+ <%= render partial: "avo/partials/custom_tools_alert" %>
30
30
  <div class="flex flex-1 flex-col justify-between items-stretch space-y-8">
31
31
  <%= yield %>
32
32
  <%= render partial: "avo/partials/footer" %>
data/lib/avo/app.rb CHANGED
@@ -14,6 +14,7 @@ module Avo
14
14
  class_attribute :view_context, default: nil
15
15
  class_attribute :params, default: {}
16
16
  class_attribute :translation_enabled, default: false
17
+ class_attribute :error_messages, default: []
17
18
 
18
19
  class << self
19
20
  def boot
@@ -29,6 +30,7 @@ module Avo
29
30
  end
30
31
 
31
32
  def init(request:, context:, current_user:, root_path:, view_context:, params:)
33
+ self.error_messages = []
32
34
  self.request = request
33
35
  self.context = context
34
36
  self.current_user = current_user
@@ -5,6 +5,7 @@ module Avo
5
5
  extend HasContext
6
6
 
7
7
  include ActionView::Helpers::UrlHelper
8
+ include Avo::Concerns::HasTools
8
9
 
9
10
  delegate :view_context, to: "Avo::App"
10
11
  delegate :main_app, to: :view_context
@@ -0,0 +1,34 @@
1
+ module Avo
2
+ class BaseResourceTool
3
+ include Avo::Fields::FieldExtensions::VisibleInDifferentViews
4
+
5
+ class_attribute :name
6
+ class_attribute :partial
7
+
8
+ attr_accessor :params
9
+ attr_accessor :resource
10
+ attr_accessor :view
11
+
12
+ def initialize(**args)
13
+ # Set the visibility
14
+ show_on :show
15
+
16
+ show_on args[:show_on] if args[:show_on].present?
17
+ hide_on args[:hide_on] if args[:hide_on].present?
18
+ only_on args[:only_on] if args[:only_on].present?
19
+ except_on args[:except_on] if args[:except_on].present?
20
+ end
21
+
22
+ def hydrate(view: nil)
23
+ @view = view
24
+
25
+ self
26
+ end
27
+
28
+ def partial
29
+ return self.class.partial if self.class.partial.present?
30
+
31
+ "avo/resource_tools/#{self.class.to_s.underscore}"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ module Avo
2
+ module Concerns
3
+ module HasTools
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :tools_holder
8
+
9
+ def tools
10
+ check_license
11
+
12
+ return [] if App.license.lacks_with_trial :resource_tools
13
+ return [] if self.class.tools.blank?
14
+
15
+ self.class.tools
16
+ .map do |tool|
17
+ tool.hydrate view: view
18
+ end
19
+ .select do |field|
20
+ field.send("show_on_#{view}")
21
+ end
22
+ end
23
+ end
24
+
25
+ class_methods do
26
+ def tool(klass, **args)
27
+ self.tools_holder ||= []
28
+
29
+ self.tools_holder << klass.new(**args)
30
+ end
31
+
32
+ def tools
33
+ self.tools_holder
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def check_license
40
+ if !Rails.env.production? && App.license.lacks(:resource_tools)
41
+ # Add error message to let the developer know the resource tool will not be available in a production environment.
42
+ Avo::App.error_messages.push "Warning: Your license is invalid or doesn't support resource tools. The resource tools will not be visible in a production environment."
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/avo/engine.rb CHANGED
@@ -26,7 +26,8 @@ module Avo
26
26
  ["app", "avo", "actions"],
27
27
  ["app", "avo", "resources"],
28
28
  ["app", "avo", "dashboards"],
29
- ["app", "avo", "cards"]
29
+ ["app", "avo", "cards"],
30
+ ["app", "avo", "resource_tools"]
30
31
  ].each do |path_params|
31
32
  path = Rails.root.join(*path_params)
32
33
 
@@ -12,6 +12,8 @@ module Avo
12
12
  option :current_user, default: proc { Avo::App.current_user }
13
13
  option :block, optional: true
14
14
 
15
+ delegate :authorize, to: Avo::Services::AuthorizationService
16
+
15
17
  def handle
16
18
  instance_exec(&block)
17
19
  end
@@ -7,6 +7,7 @@ module Avo
7
7
  :localization,
8
8
  :custom_tools,
9
9
  :custom_fields,
10
+ :resource_tools,
10
11
  :global_search,
11
12
  :enhanced_search_results,
12
13
  :searchable_associations,
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "2.6.0" unless const_defined?(:VERSION)
2
+ VERSION = "2.7.0" unless const_defined?(:VERSION)
3
3
  end
@@ -14,6 +14,9 @@ module Generators
14
14
 
15
15
  template "initializer/avo.tt", "config/initializers/avo.rb"
16
16
  template "locales/avo.en.yml", "config/locales/avo.en.yml"
17
+ template "locales/avo.nb-NO.yml", "config/locales/avo.nb-NO.yml"
18
+ template "locales/avo.pt-BR.yml", "config/locales/avo.pt-BR.yml"
19
+ template "locales/avo.ro.yml", "config/locales/avo.ro.yml"
17
20
  end
18
21
  end
19
22
  end
@@ -0,0 +1,40 @@
1
+ require "rails/generators"
2
+ require "fileutils"
3
+
4
+ module Generators
5
+ module Avo
6
+ class ResourceToolGenerator < ::Rails::Generators::NamedBase
7
+ argument :name, type: :string, required: true
8
+
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ namespace "avo:resource_tool"
12
+
13
+ def handle
14
+ # Add configuration file
15
+ template "resource_tools/resource_tool.tt", "app/avo/resource_tools/#{file_name}.rb"
16
+
17
+ # Add view file
18
+ template "resource_tools/partial.tt", "app/views/avo/resource_tools/_#{file_name}.html.erb"
19
+ end
20
+
21
+ no_tasks do
22
+ def file_name
23
+ name.to_s.underscore
24
+ end
25
+
26
+ def controller_name
27
+ file_name.to_s
28
+ end
29
+
30
+ def human_name
31
+ file_name.humanize
32
+ end
33
+
34
+ def in_code(text)
35
+ "<code class='p-1 rounded bg-gray-500 text-white text-sm'>#{text}</code>"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ <div class="flex flex-col">
2
+ <%%= render Avo::PanelComponent.new title: "<%= human_name %>" do |c| %>
3
+ <%% c.tools do %>
4
+ <%%= a_link('/avo', icon: 'heroicons/solid/academic-cap', style: :primary) do %>
5
+ Dummy link
6
+ <%% end %>
7
+ <%% end %>
8
+
9
+ <%% c.body do %>
10
+ <div class="flex flex-col p-4 min-h-24">
11
+ <div class="space-y-4">
12
+ <h3>🪧 This partial is waiting to be updated</h3>
13
+
14
+ <p>
15
+ You can edit this file here <%= in_code "app/views/avo/resource_tools/#{file_name}.html.erb" %>.
16
+ </p>
17
+
18
+ <p>
19
+ The resource tool configuration file should be here <%= in_code "app/avo/resource_tools/#{file_name}.rb" %>.
20
+ </p>
21
+
22
+ <%%
23
+ # In this partial you have access to the following variables:
24
+ # tool
25
+ # @resource
26
+ # @resource.model
27
+ # params
28
+ # Avo::App.context
29
+ # current_user
30
+ %>
31
+ </div>
32
+ </div>
33
+ <%% end %>
34
+ <%% end %>
35
+ </div>
36
+
37
+
@@ -0,0 +1,4 @@
1
+ class <%= class_name %> < Avo::BaseResourceTool
2
+ self.name = "<%= human_name %>"
3
+ # self.partial = "avo/resource_tools/<%= file_name %>"
4
+ end
@@ -7277,14 +7277,14 @@ progress[value]::-moz-progress-bar{
7277
7277
  width:4rem
7278
7278
  }
7279
7279
 
7280
- .w-10{
7281
- width:2.5rem
7282
- }
7283
-
7284
7280
  .w-64{
7285
7281
  width:16rem
7286
7282
  }
7287
7283
 
7284
+ .w-10{
7285
+ width:2.5rem
7286
+ }
7287
+
7288
7288
  .w-\[1\%\]{
7289
7289
  width:1%
7290
7290
  }
@@ -7297,10 +7297,6 @@ progress[value]::-moz-progress-bar{
7297
7297
  min-width:300px
7298
7298
  }
7299
7299
 
7300
- .min-w-\[16rem\]{
7301
- min-width:16rem
7302
- }
7303
-
7304
7300
  .min-w-full{
7305
7301
  min-width:100%
7306
7302
  }