avo 2.12.1.pre.1 → 2.13.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -3
  3. data/README.md +2 -1
  4. data/app/components/avo/actions_component.html.erb +3 -3
  5. data/app/components/avo/actions_component.rb +12 -3
  6. data/app/components/avo/button_component.rb +1 -3
  7. data/app/components/avo/fields/common/gravatar_viewer_component.html.erb +1 -1
  8. data/app/components/avo/fields/date_field/edit_component.html.erb +0 -3
  9. data/app/components/avo/fields/date_time_field/edit_component.html.erb +0 -2
  10. data/app/components/avo/fields/has_one_field/show_component.html.erb +1 -1
  11. data/app/components/avo/fields/tags_field/show_component.rb +0 -2
  12. data/app/components/avo/item_switcher_component.html.erb +1 -1
  13. data/app/components/avo/panel_component.html.erb +1 -1
  14. data/app/components/avo/panel_component.rb +3 -4
  15. data/app/components/avo/resource_component.rb +6 -4
  16. data/app/components/avo/sidebar_component.html.erb +1 -1
  17. data/app/components/avo/tab_group_component.html.erb +1 -1
  18. data/app/components/avo/tab_switcher_component.rb +2 -2
  19. data/app/components/avo/views/resource_edit_component.html.erb +1 -1
  20. data/app/components/avo/views/resource_edit_component.rb +1 -1
  21. data/app/components/avo/views/resource_index_component.html.erb +3 -3
  22. data/app/components/avo/views/resource_index_component.rb +18 -1
  23. data/app/components/avo/views/resource_show_component.html.erb +139 -48
  24. data/app/components/avo/views/resource_show_component.rb +1 -0
  25. data/app/controllers/avo/base_controller.rb +8 -11
  26. data/app/controllers/avo/search_controller.rb +57 -20
  27. data/app/javascript/js/controllers/menu_controller.js +2 -2
  28. data/app/javascript/js/controllers/search_controller.js +42 -15
  29. data/app/views/avo/associations/new.html.erb +1 -0
  30. data/app/views/avo/dashboards/show.html.erb +1 -1
  31. data/app/views/avo/debug/index.html.erb +1 -1
  32. data/app/views/avo/debug/report.html.erb +1 -0
  33. data/app/views/avo/home/index.html.erb +1 -1
  34. data/app/views/avo/partials/_resource_search.html.erb +6 -0
  35. data/app/views/avo/private/design.html.erb +2 -2
  36. data/app/views/layouts/avo/application.html.erb +1 -1
  37. data/lib/avo/base_resource.rb +5 -1
  38. data/lib/avo/concerns/has_editable_controls.rb +34 -0
  39. data/lib/avo/fields/base_field.rb +1 -1
  40. data/lib/avo/fields/date_field.rb +1 -3
  41. data/lib/avo/fields/field_extensions/has_include_blank.rb +1 -1
  42. data/lib/avo/fields/has_base_field.rb +2 -0
  43. data/lib/avo/fields/key_value_field.rb +6 -6
  44. data/lib/avo/hosts/search_scope_host.rb +7 -0
  45. data/lib/avo/licensing/h_q.rb +12 -4
  46. data/lib/avo/licensing/pro_license.rb +1 -0
  47. data/lib/avo/menu/builder.rb +2 -0
  48. data/lib/avo/resources/controls/action.rb +32 -0
  49. data/lib/avo/resources/controls/actions_list.rb +19 -0
  50. data/lib/avo/resources/controls/back_button.rb +13 -0
  51. data/lib/avo/resources/controls/base_control.rb +59 -0
  52. data/lib/avo/resources/controls/delete_button.rb +13 -0
  53. data/lib/avo/resources/controls/detach_button.rb +13 -0
  54. data/lib/avo/resources/controls/edit_button.rb +13 -0
  55. data/lib/avo/resources/controls/execution_context.rb +58 -0
  56. data/lib/avo/resources/controls/items_holder.rb +19 -0
  57. data/lib/avo/resources/controls/link_to.rb +27 -0
  58. data/lib/avo/version.rb +1 -1
  59. data/lib/generators/avo/templates/action.tt +1 -1
  60. data/lib/generators/avo/templates/initializer/avo.tt +1 -0
  61. data/lib/generators/avo/templates/resource/resource.tt +1 -1
  62. data/lib/generators/avo/templates/resource_tools/partial.tt +1 -1
  63. data/lib/generators/avo/templates/standalone_action.tt +1 -1
  64. data/public/avo-assets/avo.css +564 -44
  65. data/public/avo-assets/avo.js +47 -47
  66. data/public/avo-assets/avo.js.map +2 -2
  67. metadata +16 -5
  68. data/lib/avo/concerns/has_model.rb +0 -11
@@ -44,10 +44,13 @@ module Avo
44
44
  end
45
45
 
46
46
  def search_resource(resource)
47
- query = resource.search_query.call(params: params).limit(8)
47
+ query = Avo::Hosts::SearchScopeHost.new(
48
+ block: resource.search_query,
49
+ params: params,
50
+ scope: resource.class.scope
51
+ ).handle
48
52
 
49
- # Figure out if this is a belongs_to search
50
- query = apply_attach_scope query
53
+ query = apply_scope(query) if should_apply_any_scope?
51
54
 
52
55
  results = apply_search_metadata(query, resource)
53
56
 
@@ -67,22 +70,19 @@ module Avo
67
70
  [resource.name.pluralize.downcase, result_object]
68
71
  end
69
72
 
70
- # Figure out if it's a belongs to search and if it has the attach_scope block present.
71
- # If so, set the parent for those edit view and the parent with the grandparent for the new view.
72
- def apply_attach_scope(query)
73
- return query if params[:via_reflection_class].blank?
74
-
75
- # Fetch the field
76
- field = belongs_to_field
77
-
78
- # No need to modify the query if there's no attach_scope present.
79
- return query if field.attach_scope.blank?
80
-
81
- # Try to fetch the parent.
82
- if params[:via_reflection_id].present?
83
- parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
73
+ # When searching in a `has_many` association and will scope out the records against the parent record.
74
+ # This is also used when looking for `belongs_to` associations, and this method applies the parents `attach_scope` if present.
75
+ def apply_scope(query)
76
+ if should_apply_has_many_scope?
77
+ apply_has_many_scope
78
+ elsif should_apply_attach_scope?
79
+ apply_attach_scope(query, parent)
84
80
  end
81
+ end
85
82
 
83
+ # Parent passed as argument to be used as a variable instead of the method "def parent"
84
+ # Otherwise parent = params...safe_constantize... will try to call method "def parent="
85
+ def apply_attach_scope(query, parent)
86
86
  # If the parent is nil it probably means that someone's creating the record so it's not attached yet.
87
87
  # In these scenarios, try to find the grandparent for the new views where the parent is nil
88
88
  # and initialize the parent record with the grandparent attached so the user has the required information
@@ -94,8 +94,13 @@ module Avo
94
94
  )
95
95
  end
96
96
 
97
- # Add to the query
98
- Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.attach_scope, query: query, parent: parent).handle
97
+ Avo::Hosts::AssociationScopeHost.new(block: attach_scope, query: query, parent: parent).handle
98
+ end
99
+
100
+ def apply_has_many_scope
101
+ scope = parent.send(params[:via_association_id])
102
+
103
+ Avo::Hosts::SearchScopeHost.new(block: @resource.search_query, params: params, scope: scope).handle
99
104
  end
100
105
 
101
106
  def apply_search_metadata(models, avo_resource)
@@ -118,9 +123,41 @@ module Avo
118
123
  end
119
124
  end
120
125
 
121
- def belongs_to_field
126
+ private
127
+
128
+ def should_apply_has_many_scope?
129
+ params[:via_association] == "has_many" && @resource.search_query.present?
130
+ end
131
+
132
+ def should_apply_attach_scope?
133
+ params[:via_association] == "belongs_to" && attach_scope.present?
134
+ end
135
+
136
+ def should_apply_any_scope?
137
+ should_apply_has_many_scope? || should_apply_attach_scope?
138
+ end
139
+
140
+ def attach_scope
141
+ @attach_scope ||= field&.attach_scope
142
+ end
143
+
144
+ def field
145
+ @field ||= fetch_field
146
+ end
147
+
148
+ def parent
149
+ @parent ||= fetch_parent
150
+ end
151
+
152
+ def fetch_field
122
153
  fields = ::Avo::App.get_resource_by_model_name(params[:via_reflection_class]).get_field_definitions
123
154
  fields.find { |f| f.id.to_s == params[:via_association_id] }
124
155
  end
156
+
157
+ def fetch_parent
158
+ return unless params[:via_reflection_id].present?
159
+
160
+ params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
161
+ end
125
162
  end
126
163
  end
@@ -22,8 +22,8 @@ export default class extends Controller {
22
22
  }
23
23
 
24
24
  get initiallyCollapsed() {
25
- if (this.defaultState === 'collapsed') {
26
- return this.userState === 'collapsed'
25
+ if (!this.userState) {
26
+ return this.defaultState === 'collapsed'
27
27
  }
28
28
 
29
29
  return this.userState === 'collapsed'
@@ -48,6 +48,10 @@ export default class extends Controller {
48
48
  return this.dataset.viaAssociation === 'belongs_to'
49
49
  }
50
50
 
51
+ get isHasManySearch() {
52
+ return this.dataset.viaAssociation === 'has_many'
53
+ }
54
+
51
55
  get isGlobalSearch() {
52
56
  return this.dataset.searchResource === 'global'
53
57
  }
@@ -83,27 +87,50 @@ export default class extends Controller {
83
87
  params.global = true
84
88
  }
85
89
 
86
- if (this.isBelongsToSearch) {
87
- params = {
88
- ...params,
89
- // eslint-disable-next-line camelcase
90
- via_association_id: this.dataset.viaAssociationId,
91
- // eslint-disable-next-line camelcase
92
- via_reflection_class: this.dataset.viaReflectionClass,
93
- // eslint-disable-next-line camelcase
94
- via_reflection_id: this.dataset.viaReflectionId,
95
- // eslint-disable-next-line camelcase
96
- via_parent_resource_id: this.dataset.viaParentResourceId,
97
- // eslint-disable-next-line camelcase
98
- via_parent_resource_class: this.dataset.viaParentResourceClass,
99
- // eslint-disable-next-line camelcase
100
- via_relation: this.dataset.viaRelation,
90
+ if (this.isBelongsToSearch || this.isHasManySearch) {
91
+ params = this.addAssociationParams(params)
92
+ params = this.addReflectionParams(params)
93
+
94
+ if (this.isBelongsToSearch) {
95
+ params = {
96
+ ...params,
97
+ // eslint-disable-next-line camelcase
98
+ via_parent_resource_id: this.dataset.viaParentResourceId,
99
+ // eslint-disable-next-line camelcase
100
+ via_parent_resource_class: this.dataset.viaParentResourceClass,
101
+ // eslint-disable-next-line camelcase
102
+ via_relation: this.dataset.viaRelation,
103
+ }
101
104
  }
102
105
  }
103
106
 
104
107
  return params
105
108
  }
106
109
 
110
+ addAssociationParams(params) {
111
+ params = {
112
+ ...params,
113
+ // eslint-disable-next-line camelcase
114
+ via_association: this.dataset.viaAssociation,
115
+ // eslint-disable-next-line camelcase
116
+ via_association_id: this.dataset.viaAssociationId,
117
+ }
118
+
119
+ return params
120
+ }
121
+
122
+ addReflectionParams(params) {
123
+ params = {
124
+ ...params,
125
+ // eslint-disable-next-line camelcase
126
+ via_reflection_class: this.dataset.viaReflectionClass,
127
+ // eslint-disable-next-line camelcase
128
+ via_reflection_id: this.dataset.viaReflectionId,
129
+ }
130
+
131
+ return params
132
+ }
133
+
107
134
  handleOnSelect({ item }) {
108
135
  if (this.isBelongsToSearch) {
109
136
  this.updateFieldAttribute(this.hiddenIdTarget, 'value', item._id)
@@ -13,6 +13,7 @@
13
13
  <div class="flex-1 flex items-center justify-center px-0 lg:px-8 text-lg mt-8 mb-12">
14
14
  <% if @field.searchable %>
15
15
  <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: form,
16
+ classes: input_classes("w-full"),
16
17
  field: @field,
17
18
  model_key: @field.target_resource&.model_key,
18
19
  foreign_key: 'related_id',
@@ -1,4 +1,4 @@
1
- <%= render Avo::PanelComponent.new(title: @dashboard.name, description: @dashboard.description) do |c| %>
1
+ <%= render Avo::PanelComponent.new(name: @dashboard.name, description: @dashboard.description) do |c| %>
2
2
  <% c.bare_content do %>
3
3
  <% if @dashboard.items.present? %>
4
4
  <div class="min-h-24">
@@ -14,7 +14,7 @@
14
14
  hq_payload = Avo::Licensing::HQ.new(request).payload
15
15
  %>
16
16
  <div class="flex flex-col">
17
- <%= render Avo::PanelComponent.new(title: 'Debug Avo', description: 'Use this page to debug the Avo license.') do |c| %>
17
+ <%= render Avo::PanelComponent.new(name: 'Debug Avo', description: 'Use this page to debug the Avo license.') do |c| %>
18
18
  <% c.tools do %>
19
19
  <% end %>
20
20
  <% c.bare_content do %>
@@ -9,6 +9,7 @@
9
9
  </div>
10
10
  <div class="flex justify-end">
11
11
  <%= a_button icon: 'heroicons/outline/clipboard',
12
+ color: :primary,
12
13
  style: :primary,
13
14
  data: {
14
15
  controller: 'copy-to-clipboard',
@@ -1,5 +1,5 @@
1
1
  <div class="flex flex-col">
2
- <%= render Avo::PanelComponent.new(title: 'Welcome to Avo', description: 'This page is visible only in development. It will be hidden in other environments.') do |c| %>
2
+ <%= render Avo::PanelComponent.new(name: 'Welcome to Avo', description: 'This page is visible only in development. It will be hidden in other environments.') do |c| %>
3
3
  <% c.body do %>
4
4
  <div class="flex flex-col justify-between py-6 min-h-24">
5
5
  <div class="px-6 space-y-4">
@@ -3,6 +3,12 @@
3
3
  data-search-target="autocomplete"
4
4
  data-search-resource="<%= resource %>"
5
5
  data-translation-keys='{"no_item_found": "<%= I18n.translate 'avo.no_item_found' %>"}'
6
+ <% if via_reflection.present? %>
7
+ data-via-association="<%= via_reflection[:association] %>"
8
+ data-via-association-id="<%= via_reflection[:association_id] %>"
9
+ data-via-reflection-class="<%= via_reflection[:class] %>"
10
+ data-via-reflection-id="<%= via_reflection[:id] %>"
11
+ <% end %>
6
12
  >
7
13
  </div>
8
14
  <div class="hidden relative inline-flex text-gray-400 text-sm border border-gray-300 rounded cursor-pointer" data-search-target="button"></div>
@@ -1,7 +1,7 @@
1
1
  <div class="flex flex-col">
2
- <%= render Avo::PanelComponent.new(title: 'Welcome to Avo', description: 'This page is visible only in development. It will be hidden in other environments.') do |c| %>
2
+ <%= render Avo::PanelComponent.new(name: 'Welcome to Avo', description: 'This page is visible only in development. It will be hidden in other environments.') do |c| %>
3
3
  <% c.tools do %>
4
- <%= a_link('/admin', icon: 'arrow-left', style: :primary, is_link: true) do %>
4
+ <%= a_link('/admin', icon: 'arrow-left', color: :green, style: :primary, is_link: true) do %>
5
5
  Primary
6
6
  <% end %>
7
7
 
@@ -18,7 +18,7 @@
18
18
  <% end %>
19
19
  <% end %>
20
20
  </head>
21
- <body class="bg-gray-25 os-mac">
21
+ <body class="bg-application os-mac">
22
22
  <div class="relative flex flex-1 w-full min-h-full" data-controller="sidebar" data-sidebar-open-value="<%= @sidebar_open %>">
23
23
  <div class="flex-1 flex flex-col max-w-full">
24
24
  <%= render partial: "avo/partials/navbar" %>
@@ -3,8 +3,8 @@ module Avo
3
3
  extend ActiveSupport::DescendantsTracker
4
4
 
5
5
  include ActionView::Helpers::UrlHelper
6
- include Avo::Concerns::HasModel
7
6
  include Avo::Concerns::HasFields
7
+ include Avo::Concerns::HasEditableControls
8
8
  include Avo::Concerns::HasStimulusControllers
9
9
  include Avo::Concerns::ModelClassConstantized
10
10
 
@@ -439,5 +439,9 @@ module Avo
439
439
  def ordering_host(**args)
440
440
  Avo::Hosts::Ordering.new resource: self, options: self.class.ordering, **args
441
441
  end
442
+
443
+ def has_model_id?
444
+ model.present? && model.id.present?
445
+ end
442
446
  end
443
447
  end
@@ -0,0 +1,34 @@
1
+ module Avo
2
+ module Concerns
3
+ module HasEditableControls
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :show_controls
8
+ class_attribute :show_controls_holder
9
+ class_attribute :show_controls_holder_called, default: false
10
+ end
11
+
12
+ def has_show_controls?
13
+ return false if ::Avo::App.license.lacks_with_trial(:resource_show_controls)
14
+
15
+ self.class.show_controls.present?
16
+ end
17
+
18
+ def render_show_controls
19
+ return [] if ::Avo::App.license.lacks_with_trial(:resource_show_controls)
20
+
21
+ if show_controls.present?
22
+ Avo::Resources::Controls::ExecutionContext.new(
23
+ block: show_controls,
24
+ resource: self,
25
+ record: model,
26
+ view: view
27
+ ).handle&.items || []
28
+ else
29
+ []
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -160,7 +160,7 @@ module Avo
160
160
  final_value = @model.send(property) if (model_or_class(@model) == "model") && @model.respond_to?(property)
161
161
 
162
162
  # On new views and actions modals we need to prefill the fields
163
- if (@view === :new) || @action.present?
163
+ if @view.in?([:new, :create]) || @action.present?
164
164
  if default.present?
165
165
  final_value = if default.respond_to?(:call)
166
166
  default.call
@@ -5,15 +5,13 @@ module Avo
5
5
  attr_reader :picker_format
6
6
  attr_reader :disable_mobile
7
7
  attr_reader :format
8
- attr_reader :relative
9
8
 
10
9
  def initialize(id, **args, &block)
11
10
  super(id, **args, &block)
12
11
 
13
- add_string_prop args, :first_day_of_week, 0
12
+ add_string_prop args, :first_day_of_week, 1
14
13
  add_string_prop args, :picker_format, "Y-m-d"
15
14
  add_string_prop args, :format, "yyyy-LL-dd"
16
- add_boolean_prop args, :relative
17
15
  add_boolean_prop args, :disable_mobile
18
16
  end
19
17
 
@@ -4,7 +4,7 @@ module Avo
4
4
  module HasIncludeBlank
5
5
  def include_blank
6
6
  if @args[:include_blank] == true
7
- placeholder || ''
7
+ placeholder || ""
8
8
  elsif @args[:include_blank] == false
9
9
  false
10
10
  else
@@ -6,6 +6,7 @@ module Avo
6
6
  attr_accessor :attach_scope
7
7
  attr_accessor :description
8
8
  attr_accessor :discreet_pagination
9
+ attr_accessor :hide_search_input
9
10
 
10
11
  def initialize(id, **args, &block)
11
12
  super(id, **args, &block)
@@ -14,6 +15,7 @@ module Avo
14
15
  @attach_scope = args[:attach_scope].present? ? args[:attach_scope] : nil
15
16
  @display = args[:display].present? ? args[:display] : :show
16
17
  @searchable = args[:searchable] == true
18
+ @hide_search_input = args[:hide_search_input] || false
17
19
  @description = args[:description]
18
20
  @use_resource = args[:use_resource] || nil
19
21
  @discreet_pagination = args[:discreet_pagination] || false
@@ -12,8 +12,8 @@ module Avo
12
12
  hide_on :index
13
13
 
14
14
  @key_label = args[:key_label] if args[:key_label].present?
15
- @value_label = args[:value_label]if args[:value_label].present?
16
- @action_text = args[:action_text] if args[:action_text].present?
15
+ @value_label = args[:value_label] if args[:value_label].present?
16
+ @action_text = args[:action_text] if args[:action_text].present? # This should be add_row_label
17
17
  @delete_text = args[:delete_text] if args[:delete_text].present?
18
18
 
19
19
  @disable_editing_keys = args[:disable_editing_keys].present? ? args[:disable_editing_keys] : false
@@ -31,25 +31,25 @@ module Avo
31
31
  def key_label
32
32
  return @key_label if @key_label.present?
33
33
 
34
- I18n.translate('avo.key_value_field.key')
34
+ I18n.translate("avo.key_value_field.key")
35
35
  end
36
36
 
37
37
  def value_label
38
38
  return @value_label if @value_label.present?
39
39
 
40
- I18n.translate('avo.key_value_field.value')
40
+ I18n.translate("avo.key_value_field.value")
41
41
  end
42
42
 
43
43
  def action_text
44
44
  return @action_text if @action_text.present?
45
45
 
46
- I18n.translate('avo.key_value_field.add_row')
46
+ I18n.translate("avo.key_value_field.add_row")
47
47
  end
48
48
 
49
49
  def delete_text
50
50
  return @delete_text if @delete_text.present?
51
51
 
52
- I18n.translate('avo.key_value_field.delete_row')
52
+ I18n.translate("avo.key_value_field.delete_row")
53
53
  end
54
54
 
55
55
  def to_permitted_param
@@ -0,0 +1,7 @@
1
+ module Avo
2
+ module Hosts
3
+ class SearchScopeHost < BaseHost
4
+ option :scope
5
+ end
6
+ end
7
+ end
@@ -22,16 +22,24 @@ module Avo
22
22
  def response
23
23
  expire_cache_if_overdue
24
24
 
25
+ # ------------------------------------------------------------------
26
+ # You could set this to true to become a pro user for free.
27
+ # I'd rather you didn't. Avo takes time & love to build,
28
+ # and I can't do that if it doesn't pay my bills!
29
+ #
30
+ # If you want Pro, help pay for its development.
31
+ # Can't afford it? Get in touch: adrian@avohq.io
32
+ # ------------------------------------------------------------------
25
33
  make_request
26
34
  end
27
35
 
28
36
  # Some cache stores don't auto-expire their keys and payloads so we need to do it for them
29
37
  def expire_cache_if_overdue
30
38
  return unless cached_response.present?
31
- return unless cached_response['fetched_at'].present?
39
+ return unless cached_response["fetched_at"].present?
32
40
 
33
41
  allowed_time = 1.hour
34
- parsed_time = Time.parse(cached_response['fetched_at'].to_s)
42
+ parsed_time = Time.parse(cached_response["fetched_at"].to_s)
35
43
  time_has_passed = parsed_time < Time.now - allowed_time
36
44
 
37
45
  clear_response if time_has_passed
@@ -159,7 +167,7 @@ module Avo
159
167
  def perform_request
160
168
  ::Rails.logger.debug "[Avo] Performing request to avohq.io API to check license availability." if Rails.env.development?
161
169
 
162
- HTTParty.post ENDPOINT, body: payload.to_json, headers: {'Content-type': "application/json"}, timeout: REQUEST_TIMEOUT
170
+ HTTParty.post ENDPOINT, body: payload.to_json, headers: {"Content-type": "application/json"}, timeout: REQUEST_TIMEOUT
163
171
  end
164
172
 
165
173
  def app_name
@@ -177,7 +185,7 @@ module Avo
177
185
 
178
186
  {
179
187
  "#{type}_count": type_count,
180
- "#{type}_per_resource": type_per_resource,
188
+ "#{type}_per_resource": type_per_resource
181
189
  }
182
190
  end
183
191
 
@@ -15,6 +15,7 @@ module Avo
15
15
  :dashboards,
16
16
  :menu_editor,
17
17
  :stimulus_js_integration,
18
+ :resource_show_controls,
18
19
  :advanced_fields
19
20
  ]
20
21
  end
@@ -11,6 +11,8 @@ class Avo::Menu::Builder
11
11
  delegate :request, to: ::Avo::App
12
12
  delegate :root_path, to: ::Avo::App
13
13
  delegate :view_context, to: ::Avo::App
14
+ delegate :main_app, to: :view_context
15
+ delegate :avo, to: :view_context
14
16
 
15
17
  def initialize(name: nil, items: [])
16
18
  @menu = Avo::Menu::Menu.new
@@ -0,0 +1,32 @@
1
+ module Avo
2
+ module Resources
3
+ module Controls
4
+ class Action < BaseControl
5
+ attr_reader :klass
6
+
7
+ def initialize(klass, model: nil, resource: nil, view: nil, **args)
8
+ super(**args)
9
+
10
+ @klass = klass
11
+ @resource = resource
12
+ @model = model
13
+ @view = view
14
+ end
15
+
16
+ def action
17
+ return @instance if @instance.present?
18
+
19
+ @instance = @klass.new(model: @model, resource: @resource, view: @view)
20
+ end
21
+
22
+ def path
23
+ Avo::Services::URIService.parse(@resource.record_path).append_paths("actions", action.param_id).to_s
24
+ end
25
+
26
+ def label
27
+ @args[:label] || action.action_name
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module Avo
2
+ module Resources
3
+ module Controls
4
+ class ActionsList < BaseControl
5
+ def exclude
6
+ Array.wrap(@args[:exclude]) || []
7
+ end
8
+
9
+ def color
10
+ @args[:color] || :primary
11
+ end
12
+
13
+ def style
14
+ @args[:style] || :outline
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Avo
2
+ module Resources
3
+ module Controls
4
+ class BackButton < BaseControl
5
+ def initialize(**args)
6
+ super(**args)
7
+
8
+ @label = I18n.t("avo.go_back")
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+ module Avo
2
+ module Resources
3
+ module Controls
4
+ class BaseControl
5
+ def initialize(**args)
6
+ @args = args
7
+ end
8
+
9
+ def label
10
+ @args[:label] || @label
11
+ end
12
+
13
+ def title
14
+ @args[:title]
15
+ end
16
+
17
+ def color
18
+ @args[:color] || :gray
19
+ end
20
+
21
+ def style
22
+ @args[:style] || :text
23
+ end
24
+
25
+ def icon
26
+ @args[:icon] || nil
27
+ end
28
+
29
+ def back_button?
30
+ is_a? Avo::Resources::Controls::BackButton
31
+ end
32
+
33
+ def edit_button?
34
+ is_a? Avo::Resources::Controls::EditButton
35
+ end
36
+
37
+ def delete_button?
38
+ is_a? Avo::Resources::Controls::DeleteButton
39
+ end
40
+
41
+ def actions_list?
42
+ is_a? Avo::Resources::Controls::ActionsList
43
+ end
44
+
45
+ def link_to?
46
+ is_a? Avo::Resources::Controls::LinkTo
47
+ end
48
+
49
+ def detach_button?
50
+ is_a? Avo::Resources::Controls::DetachButton
51
+ end
52
+
53
+ def action?
54
+ is_a? Avo::Resources::Controls::Action
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ module Avo
2
+ module Resources
3
+ module Controls
4
+ class DeleteButton < BaseControl
5
+ def initialize(**args)
6
+ super(**args)
7
+
8
+ @label = I18n.t("avo.delete").capitalize
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Avo
2
+ module Resources
3
+ module Controls
4
+ class DetachButton < BaseControl
5
+ def initialize(**args)
6
+ super(**args)
7
+
8
+ @label = I18n.t("avo.detach_item", item: title).capitalize
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end