avo 2.11.1 → 2.11.2.pre.1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/assets/stylesheets/avo.css +1 -4
  4. data/app/assets/stylesheets/css/sidebar.css +18 -0
  5. data/app/assets/svgs/failed_to_load.svg +15 -0
  6. data/app/components/avo/common_field_wrapper_component.html.erb +4 -0
  7. data/app/components/avo/fields/has_one_field/show_component.html.erb +1 -1
  8. data/app/components/avo/fields/has_one_field/show_component.rb +7 -6
  9. data/app/components/avo/fields/text_field/index_component.html.erb +2 -0
  10. data/app/components/avo/fields/text_field/show_component.html.erb +2 -0
  11. data/app/components/avo/fields/trix_field/edit_component.rb +1 -1
  12. data/app/components/avo/index/field_wrapper_component.html.erb +4 -0
  13. data/app/components/avo/index/resource_controls_component.html.erb +3 -0
  14. data/app/components/avo/index/resource_controls_component.rb +7 -5
  15. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  16. data/app/components/avo/index/resource_table_component.rb +2 -1
  17. data/app/components/avo/index/table_row_component.html.erb +14 -5
  18. data/app/components/avo/index/table_row_component.rb +2 -1
  19. data/app/components/avo/item_switcher_component.html.erb +1 -1
  20. data/app/components/avo/resource_component.rb +11 -5
  21. data/app/components/avo/sidebar_component.html.erb +8 -2
  22. data/app/components/avo/sidebar_component.rb +9 -0
  23. data/app/components/avo/tab_switcher_component.rb +4 -0
  24. data/app/components/avo/views/resource_edit_component.rb +4 -6
  25. data/app/components/avo/views/resource_index_component.html.erb +1 -1
  26. data/app/components/avo/views/resource_index_component.rb +5 -3
  27. data/app/components/avo/views/resource_show_component.html.erb +1 -1
  28. data/app/components/avo/views/resource_show_component.rb +3 -1
  29. data/app/controllers/avo/application_controller.rb +25 -10
  30. data/app/controllers/avo/associations_controller.rb +38 -12
  31. data/app/controllers/avo/base_controller.rb +7 -2
  32. data/app/javascript/js/controllers/fields/key_value_controller.js +3 -1
  33. data/app/javascript/js/controllers/sidebar_controller.js +49 -0
  34. data/app/javascript/js/controllers.js +2 -2
  35. data/app/views/avo/base/index.html.erb +1 -0
  36. data/app/views/avo/base/show.html.erb +7 -1
  37. data/app/views/avo/home/failed_to_load.html copy.erb +23 -0
  38. data/app/views/avo/home/failed_to_load.html.erb +9 -15
  39. data/app/views/avo/partials/_javascript.html.erb +1 -0
  40. data/app/views/avo/partials/_navbar.html.erb +5 -2
  41. data/app/views/avo/partials/_table_header.html.erb +12 -5
  42. data/app/views/layouts/avo/application.html.erb +9 -4
  43. data/bin/init +5 -0
  44. data/lib/avo/base_resource.rb +16 -1
  45. data/lib/avo/concerns/has_fields.rb +12 -3
  46. data/lib/avo/concerns/has_html_attributes.rb +1 -1
  47. data/lib/avo/concerns/model_class_constantized.rb +1 -1
  48. data/lib/avo/configuration/resource_configuration.rb +21 -0
  49. data/lib/avo/configuration.rb +2 -0
  50. data/lib/avo/fields/has_base_field.rb +11 -0
  51. data/lib/avo/fields/text_field.rb +4 -2
  52. data/lib/avo/items_holder.rb +6 -0
  53. data/lib/avo/panel_builder.rb +1 -0
  54. data/lib/avo/services/authorization_service.rb +41 -37
  55. data/lib/avo/version.rb +1 -1
  56. data/lib/avo.rb +1 -0
  57. data/public/avo-assets/avo.css +78 -16
  58. data/public/avo-assets/avo.js +70 -69
  59. data/public/avo-assets/avo.js.map +3 -3
  60. metadata +9 -5
  61. data/app/javascript/js/controllers/mobile_controller.js +0 -9
@@ -5,12 +5,16 @@ module Avo
5
5
  before_action :set_model, only: [:show, :index, :new, :create, :destroy, :order]
6
6
  before_action :set_related_resource_name
7
7
  before_action :set_related_resource, only: [:show, :index, :new, :create, :destroy, :order]
8
- before_action :hydrate_related_resource, only: [:show, :index, :new, :create, :destroy, :order]
8
+ before_action :set_reflection_field
9
+ before_action :hydrate_related_resource, only: [:show, :index, :create, :destroy, :order]
9
10
  before_action :set_related_model, only: [:show, :order]
11
+ before_action :set_reflection
10
12
  before_action :set_attachment_class, only: [:show, :index, :new, :create, :destroy, :order]
11
13
  before_action :set_attachment_resource, only: [:show, :index, :new, :create, :destroy, :order]
12
14
  before_action :set_attachment_model, only: [:create, :destroy, :order]
13
- before_action :set_reflection, only: [:index, :show, :order]
15
+ before_action :authorize_index_action, only: :index
16
+ before_action :authorize_attach_action, only: :new
17
+ before_action :authorize_detach_action, only: :destroy
14
18
 
15
19
  def index
16
20
  @parent_resource = @resource.dup
@@ -28,6 +32,8 @@ module Avo
28
32
  end
29
33
 
30
34
  def show
35
+ @parent_resource, @parent_model = @resource, @model
36
+
31
37
  @resource, @model = @related_resource, @related_model
32
38
 
33
39
  super
@@ -36,12 +42,6 @@ module Avo
36
42
  def new
37
43
  @resource.hydrate(model: @model)
38
44
 
39
- begin
40
- @field = @resource.get_field_definitions.find { |f| f.id == @related_resource_name.to_sym }
41
- @field.hydrate(resource: @resource, model: @model, view: :new)
42
- rescue
43
- end
44
-
45
45
  if @field.present? && !@field.searchable
46
46
  query = @authorization.apply_policy @attachment_class
47
47
 
@@ -93,8 +93,12 @@ module Avo
93
93
 
94
94
  private
95
95
 
96
+ def set_reflection
97
+ @reflection = @model._reflections[params[:related_name].to_s]
98
+ end
99
+
96
100
  def set_attachment_class
97
- @attachment_class = @model._reflections[params[:related_name].to_s].klass
101
+ @attachment_class = @reflection.klass
98
102
  end
99
103
 
100
104
  def set_attachment_resource
@@ -102,11 +106,13 @@ module Avo
102
106
  end
103
107
 
104
108
  def set_attachment_model
105
- @attachment_model = @model._reflections[params[:related_name].to_s].klass.find attachment_id
109
+ @attachment_model = @attachment_class.find attachment_id
106
110
  end
107
111
 
108
- def set_reflection
109
- @reflection = @model._reflections[params[:related_name].to_s]
112
+ def set_reflection_field
113
+ @field = @resource.get_field_definitions.find { |f| f.id == @related_resource_name.to_sym }
114
+ @field.hydrate(resource: @resource, model: @model, view: :new)
115
+ rescue
110
116
  end
111
117
 
112
118
  def attachment_id
@@ -121,5 +127,25 @@ module Avo
121
127
 
122
128
  klass
123
129
  end
130
+
131
+ def authorize_if_defined(method)
132
+ @authorization.set_record(@model)
133
+
134
+ if @authorization.has_method?(method.to_sym)
135
+ @authorization.authorize_action method.to_sym
136
+ end
137
+ end
138
+
139
+ def authorize_index_action
140
+ authorize_if_defined "view_#{@field.id}?"
141
+ end
142
+
143
+ def authorize_attach_action
144
+ authorize_if_defined "attach_#{@field.id}?"
145
+ end
146
+
147
+ def authorize_detach_action
148
+ authorize_if_defined "detach_#{@field.id}?"
149
+ end
124
150
  end
125
151
  end
@@ -10,7 +10,8 @@ module Avo
10
10
  before_action :set_model_to_fill
11
11
  before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update]
12
12
  before_action :fill_model, only: [:create, :update]
13
- before_action :authorize_action
13
+ # Don't run base authorizations for associations
14
+ before_action :authorize_base_action, if: -> {controller_name != "associations"}
14
15
 
15
16
  def index
16
17
  @page_title = @resource.plural_name.humanize
@@ -247,7 +248,11 @@ module Avo
247
248
  end
248
249
 
249
250
  def permitted_params
250
- @resource.get_field_definitions.select(&:updatable).map(&:to_permitted_param)
251
+ @resource.get_field_definitions.select(&:updatable).map(&:to_permitted_param).concat extra_params
252
+ end
253
+
254
+ def extra_params
255
+ @resource.class.extra_params || []
251
256
  end
252
257
 
253
258
  def cast_nullable(params)
@@ -110,13 +110,15 @@ export default class extends Controller {
110
110
  }
111
111
 
112
112
  inputField(id = 'key', index, key, value) {
113
+ const inputValue = id === 'key' ? key : value
114
+
113
115
  return `<input
114
116
  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}"
115
117
  data-action="input->key-value#${id}FieldUpdated"
116
118
  placeholder="${this.options[`${id}_label`]}"
117
119
  data-index="${index}"
118
120
  ${this[`${id}InputDisabled`] ? "disabled='disabled'" : ''}
119
- value="${id === 'key' ? key : value}"
121
+ value="${typeof inputValue === 'undefined' || inputValue === null ? '' : inputValue}"
120
122
  />`
121
123
  }
122
124
 
@@ -0,0 +1,49 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { enter, leave, toggle } from 'el-transition'
3
+ import Cookies from 'js-cookie'
4
+
5
+ export default class extends Controller {
6
+ static targets = ['sidebar', 'mobileSidebar', 'mainArea']
7
+
8
+ static values = {
9
+ open: Boolean,
10
+ }
11
+
12
+ get cookieKey() {
13
+ return `${window.Avo.configuration.cookies_key}.sidebar.open`
14
+ }
15
+
16
+ get sidebarOpen() {
17
+ return Cookies.get(this.cookieKey) === '1'
18
+ }
19
+
20
+ set cookie(state) {
21
+ Cookies.set(this.cookieKey, state === true ? 1 : 0)
22
+ }
23
+
24
+ markSidebarClosed() {
25
+ Cookies.set(this.cookieKey, '0')
26
+ this.openValue = false
27
+ leave(this.sidebarTarget)
28
+ this.mainAreaTarget.classList.remove('sidebar-open')
29
+ }
30
+
31
+ markSidebarOpen() {
32
+ Cookies.set(this.cookieKey, '1')
33
+ this.openValue = true
34
+ enter(this.sidebarTarget)
35
+ this.mainAreaTarget.classList.add('sidebar-open')
36
+ }
37
+
38
+ toggleSidebar() {
39
+ if (this.openValue) {
40
+ this.markSidebarClosed()
41
+ } else {
42
+ this.markSidebarOpen()
43
+ }
44
+ }
45
+
46
+ toggleSidebarOnMobile() {
47
+ toggle(this.mobileSidebarTarget)
48
+ }
49
+ }
@@ -16,7 +16,6 @@ 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
18
  import MenuController from './controllers/menu_controller'
19
- import MobileController from './controllers/mobile_controller'
20
19
  import ModalController from './controllers/modal_controller'
21
20
  import MultipleSelectFilterController from './controllers/multiple_select_filter_controller'
22
21
  import PerPageController from './controllers/per_page_controller'
@@ -26,6 +25,7 @@ import ResourceShowController from './controllers/resource_show_controller'
26
25
  import SearchController from './controllers/search_controller'
27
26
  import SelectController from './controllers/select_controller'
28
27
  import SelectFilterController from './controllers/select_filter_controller'
28
+ import SidebarController from './controllers/sidebar_controller'
29
29
  import SimpleMdeController from './controllers/fields/simple_mde_controller'
30
30
  import TabsController from './controllers/tabs_controller'
31
31
  import TagsFieldController from './controllers/fields/tags_field_controller'
@@ -46,7 +46,6 @@ application.register('item-select-all', ItemSelectAllController)
46
46
  application.register('item-selector', ItemSelectorController)
47
47
  application.register('loading-button', LoadingButtonController)
48
48
  application.register('menu', MenuController)
49
- application.register('mobile', MobileController)
50
49
  application.register('modal', ModalController)
51
50
  application.register('multiple-select-filter', MultipleSelectFilterController)
52
51
  application.register('per-page', PerPageController)
@@ -56,6 +55,7 @@ application.register('resource-show', ResourceShowController)
56
55
  application.register('search', SearchController)
57
56
  application.register('select', SelectController)
58
57
  application.register('select-filter', SelectFilterController)
58
+ application.register('sidebar', SidebarController)
59
59
  application.register('tabs', TabsController)
60
60
  application.register('tags-field', TagsFieldController)
61
61
  application.register('text-filter', TextFilterController)
@@ -10,6 +10,7 @@
10
10
  reflection: @reflection,
11
11
  turbo_frame: params[:turbo_frame],
12
12
  parent_model: @parent_model,
13
+ parent_resource: @parent_resource,
13
14
  applied_filters: @applied_filters,
14
15
  )
15
16
  %>
@@ -1,3 +1,9 @@
1
1
  <%= render Avo::TurboFrameWrapperComponent.new(params[:turbo_frame]) do %>
2
- <%= render Avo::Views::ResourceShowComponent.new(resource: @resource, reflection: @reflection, actions: @actions) %>
2
+ <%= render Avo::Views::ResourceShowComponent.new(
3
+ resource: @resource,
4
+ reflection: @reflection,
5
+ actions: @actions,
6
+ parent_model: @parent_model,
7
+ parent_resource: @parent_resource,
8
+ ) %>
3
9
  <% end %>
@@ -0,0 +1,23 @@
1
+ <%= render Avo::TurboFrameWrapperComponent.new(params[:turbo_frame]) do %>
2
+ <%
3
+ classes = 'absolute inset-auto left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2'
4
+ label = t 'avo.failed_to_load'
5
+ %>
6
+ <div class="relative flex-1 py-12">
7
+ <div class="relative block text-gray-300 h-40 w-full">
8
+ <%= svg 'avocado', class: "#{classes} h-20 text-gray-400" %>
9
+ <%= svg 'code', class: "#{classes} h-8 -ml-20 -mt-12" %>
10
+ <%= svg 'fire', class: "#{classes} h-8 -ml-10 -mt-24" %>
11
+ <%= svg 'color-swatch', class: "#{classes} h-8 ml-8 -mt-24" %>
12
+ <%= svg 'globe', class: "#{classes} h-8 ml-20 -mt-12" %>
13
+ <%= svg 'library', class: "#{classes} h-8 -ml-20 mt-4" %>
14
+ <%= svg 'photograph', class: "#{classes} h-8 ml-20 mt-4" %>
15
+ </div>
16
+ <div class="relative block text-center text-lg text-gray-400 font-semibold -mt-10"><%= label %> <span class="border-b-2 border-dashed"><%= params[:turbo_frame].to_s.humanize.downcase if params[:turbo_frame].present? %></span> frame</div>
17
+ </div>
18
+ <% if Rails.env.development? %>
19
+ <div class="text-center text-sm w-full">
20
+ This is not an issue with Avo. Click <%= link_to 'this link', params[:src], target: :_blank %> to see why this frame failed to load.
21
+ </div>
22
+ <% end %>
23
+ <% end %>
@@ -3,21 +3,15 @@
3
3
  classes = 'absolute inset-auto left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2'
4
4
  label = t 'avo.failed_to_load'
5
5
  %>
6
- <div class="relative flex-1 py-12">
7
- <div class="relative block text-gray-300 h-40 w-full">
8
- <%= svg 'avocado', class: "#{classes} h-20 text-gray-400" %>
9
- <%= svg 'code', class: "#{classes} h-8 -ml-20 -mt-12" %>
10
- <%= svg 'fire', class: "#{classes} h-8 -ml-10 -mt-24" %>
11
- <%= svg 'color-swatch', class: "#{classes} h-8 ml-8 -mt-24" %>
12
- <%= svg 'globe', class: "#{classes} h-8 ml-20 -mt-12" %>
13
- <%= svg 'library', class: "#{classes} h-8 -ml-20 mt-4" %>
14
- <%= svg 'photograph', class: "#{classes} h-8 ml-20 mt-4" %>
6
+ <div class="relative flex-1 py-4">
7
+ <div class="relative block text-gray-300 h-64 w-full">
8
+ <%= svg 'failed_to_load', class: "#{classes} h-52 text-gray-400" %>
15
9
  </div>
16
- <div class="relative block text-center text-lg text-gray-400 font-semibold -mt-10"><%= label %> <span class="border-b-2 border-dashed"><%= params[:turbo_frame].to_s.humanize.downcase if params[:turbo_frame].present? %></span> frame</div>
10
+ <div class="relative block text-center text-lg text-gray-400 font-semibold pb-6"><%= label %> <span class="border-b-2 border-dashed"><%= params[:turbo_frame].to_s.humanize.downcase if params[:turbo_frame].present? %></span> frame</div>
11
+ <% if Rails.env.development? %>
12
+ <div class="text-center text-sm w-full pb-3">
13
+ This is not an issue with Avo. Use <%= link_to 'this page', params[:src], target: :_blank %> to see why this frame failed to load.
14
+ </div>
15
+ <% end %>
17
16
  </div>
18
- <% if Rails.env.development? %>
19
- <div class="text-center text-sm w-full">
20
- This is not an issue with Avo. Click <%= link_to 'this link', params[:src], target: :_blank %> to see why this frame failed to load.
21
- </div>
22
- <% end %>
23
17
  <% end %>
@@ -3,4 +3,5 @@
3
3
  Avo.configuration.timezone = '<%= Avo.configuration.timezone %>'
4
4
  Avo.configuration.root_path = '<%= root_path_without_url %>'
5
5
  Avo.configuration.search_debounce = '<%= Avo.configuration.search_debounce %>'
6
+ Avo.configuration.cookies_key = '<%= Avo::COOKIES_KEY %>'
6
7
  <% end %>
@@ -2,8 +2,11 @@
2
2
  class="fixed bg-white p-2 w-full flex flex-shrink-0 items-center z-50 px-4 lg:px-4 border-b space-x-4 lg:space-x-0 h-16 <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
3
3
  v-if="layout !== 'blank'"
4
4
  >
5
- <div class="flex items-center space-x-2 lg:space-x-0 w-auto lg:w-64 flex-shrink-0 h-full">
6
- <%= a_button class: 'lg:hidden', icon: 'menu', size: :xs, compact: true, style: :text, data: { action: 'click->mobile#toggleSidebar' } %>
5
+ <div class="flex items-center space-x-2 w-auto lg:w-64 flex-shrink-0 h-full">
6
+ <div>
7
+ <%= a_button class: 'lg:hidden', icon: 'menu', size: :xs, compact: true, style: :text, data: { action: 'click->sidebar#toggleSidebarOnMobile' } %>
8
+ <%= a_button class: 'hidden lg:block', icon: 'menu', size: :xs, compact: true, style: :text, data: { action: 'click->sidebar#toggleSidebar' } %>
9
+ </div>
7
10
  <%= render partial: "avo/partials/logo" %>
8
11
  </div>
9
12
  <div class="flex-1 flex items-center justify-between lg:justify-start space-x-2 sm:space-x-8 lg:pl-4">
@@ -1,9 +1,14 @@
1
1
  <thead class="bg-white border-b border-gray-200 pb-1">
2
2
  <% if @resource.record_selector %>
3
- <th class="rounded-lg">
3
+ <th class="rounded-lg" data-control="item-select-th">
4
4
  <%== item_select_all_input %>
5
5
  </th>
6
6
  <% end %>
7
+ <% if Avo.configuration.resource_controls_on_the_left? %>
8
+ <th class="w-24" data-control="resource-controls-th">
9
+ <!-- Item controls cell -->
10
+ </th>
11
+ <% end %>
7
12
  <% fields.each_with_index do |field, index| %>
8
13
  <%
9
14
  if params[:sort_by] == field.id.to_s
@@ -39,7 +44,7 @@
39
44
  ""
40
45
  end
41
46
  %>
42
- <th class="text-left uppercase px-3 py-2 whitespace-nowrap rounded-l">
47
+ <th class="text-left uppercase px-3 py-2 whitespace-nowrap rounded-l" data-control="resource-field-th">
43
48
  <% if field.sortable %>
44
49
  <%= link_to params.permit!.merge(sort_by: sort_by, sort_direction: sort_direction), class: "flex items-center #{classes} #{'cursor-pointer' if field.sortable}", 'data-turbo-frame': params[:turbo_frame] do %>
45
50
  <%= field.name %>
@@ -52,7 +57,9 @@
52
57
  <% end %>
53
58
  </th>
54
59
  <% end %>
55
- <th class="w-24">
56
- <!-- Item controls cell -->
57
- </th>
60
+ <% if Avo.configuration.resource_controls_on_the_right? %>
61
+ <th class="w-24" data-control="resource-controls-th">
62
+ <!-- Item controls cell -->
63
+ </th>
64
+ <% end %>
58
65
  </thead>
@@ -19,12 +19,17 @@
19
19
  <% end %>
20
20
  </head>
21
21
  <body class="bg-gray-25 os-mac">
22
- <div class="relative flex flex-1 w-full min-h-full" data-controller="mobile">
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" %>
25
- <div class="flex-1 flex pt-16 relative">
26
- <%= render Avo::SidebarComponent.new %>
27
- <div class="lg:pl-64 flex-1 flex flex-col min-h-full max-w-full">
25
+ <div data-sidebar-target="mainArea" class="content-area flex-1 flex pt-16 relative <%= 'sidebar-open' if @sidebar_open %>">
26
+ <div class="hidden lg:flex">
27
+ <%= render Avo::SidebarComponent.new sidebar_open: @sidebar_open %>
28
+ </div>
29
+ <div class="flex lg:hidden">
30
+ <%= render Avo::SidebarComponent.new sidebar_open: false, for_mobile: true %>
31
+ </div>
32
+ <div class="main-content-area flex-1 flex flex-col min-h-full max-w-full">
28
33
  <div class="content p-4 lg:p-6 flex-1 flex flex-col justify-between items-stretch <%= @container_classes %>">
29
34
  <%= render partial: "avo/partials/custom_tools_alert" %>
30
35
  <div class="flex flex-1 flex-col justify-between items-stretch space-y-8">
data/bin/init CHANGED
@@ -28,6 +28,11 @@ app_root do
28
28
  header 'Preparing the database'
29
29
  run! 'bin/rails db:setup'
30
30
 
31
+ header 'Building assets'
32
+ run! 'yarn build:js'
33
+ run! 'yarn build:custom-js'
34
+ run! 'yarn build:css'
35
+
31
36
  if use_docker == 'y'
32
37
  header 'Stopping the Docker image'
33
38
  run! 'docker-compose stop'
@@ -47,6 +47,7 @@ module Avo
47
47
  class_attribute :after_update_path, default: :show
48
48
  class_attribute :record_selector, default: true
49
49
  class_attribute :keep_filters_panel_open, default: false
50
+ class_attribute :extra_params
50
51
 
51
52
  class << self
52
53
  delegate :t, to: ::I18n
@@ -251,7 +252,7 @@ module Avo
251
252
  end
252
253
  end
253
254
 
254
- def fill_model(model, params)
255
+ def fill_model(model, params, extra_params: [])
255
256
  # Map the received params to their actual fields
256
257
  fields_by_database_id = get_field_definitions
257
258
  .reject do |field|
@@ -262,6 +263,7 @@ module Avo
262
263
  end
263
264
  .to_h
264
265
 
266
+ # Write the field values
265
267
  params.each do |key, value|
266
268
  field = fields_by_database_id[key]
267
269
 
@@ -270,6 +272,19 @@ module Avo
270
272
  model = field.fill_field model, key, value, params
271
273
  end
272
274
 
275
+ # Write the user configured extra params to the model
276
+ if extra_params.present?
277
+ extra_params.each do |param_id|
278
+ # if it's a nested array, use the key
279
+ param_id = param_id.first.first if param_id.is_a? Hash
280
+
281
+ next unless @model.respond_to? "#{param_id}="
282
+
283
+ param_value = params[param_id]
284
+ @model.send("#{param_id}=", param_value)
285
+ end
286
+ end
287
+
273
288
  model
274
289
  end
275
290
 
@@ -13,8 +13,6 @@ module Avo
13
13
  end
14
14
 
15
15
  class_methods do
16
- delegate :tool, to: :items_holder
17
-
18
16
  # DSL methods
19
17
  def field(name, **args, &block)
20
18
  ensure_items_holder_initialized
@@ -34,8 +32,14 @@ module Avo
34
32
  self.items_holder.tabs Avo::TabGroupBuilder.parse_block(&block)
35
33
  end
36
34
 
35
+ def tool(klass, **args)
36
+ ensure_items_holder_initialized
37
+
38
+ items_holder.tool klass, **args
39
+ end
40
+
37
41
  def heading(body, **args)
38
- self.items_holder.add_item Avo::Fields::HeadingField.new(body, order_index: items_index, **args)
42
+ self.items_holder.heading body, **args
39
43
  end
40
44
  # END DSL methods
41
45
 
@@ -252,9 +256,14 @@ module Avo
252
256
  if item.respond_to? :visible_on?
253
257
  next unless item.visible_on?(view)
254
258
  end
259
+ # each field has it's own visibility checker
255
260
  if item.respond_to? :visible?
256
261
  next unless item.visible?
257
262
  end
263
+ # check if the user is authorized to view it
264
+ if item.respond_to? :authorized?
265
+ next unless item.hydrate(model: @model).authorized?
266
+ end
258
267
 
259
268
  if item.is_field?
260
269
  if item.has_own_panel?
@@ -58,7 +58,7 @@ module Avo
58
58
  end
59
59
  .to_h
60
60
 
61
- extra_attributes.merge attributes
61
+ attributes.merge extra_attributes
62
62
  else
63
63
  attributes
64
64
  end
@@ -14,7 +14,7 @@ module Avo
14
14
  when String, Symbol
15
15
  value.to_s.safe_constantize
16
16
  else
17
- raise ArgumentError.new "Failed to find a proper model class for #{self.to_s}"
17
+ raise ArgumentError.new "Failed to find a proper model class for #{self}"
18
18
  end
19
19
  end
20
20
  end
@@ -0,0 +1,21 @@
1
+ module Avo
2
+ class Configuration
3
+ module ResourceConfiguration
4
+ def resource_controls_placement=(val)
5
+ @resource_controls_placement_instance = val
6
+ end
7
+
8
+ def resource_controls_placement
9
+ @resource_controls_placement_instance || :right
10
+ end
11
+
12
+ def resource_controls_on_the_left?
13
+ resource_controls_placement == :left
14
+ end
15
+
16
+ def resource_controls_on_the_right?
17
+ resource_controls_placement == :right
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,7 @@
1
1
  module Avo
2
2
  class Configuration
3
+ include ResourceConfiguration
4
+
3
5
  attr_writer :root_path
4
6
  attr_accessor :app_name
5
7
  attr_accessor :timezone
@@ -78,6 +78,17 @@ module Avo
78
78
 
79
79
  super view
80
80
  end
81
+
82
+ def authorized?
83
+ method = "view_#{id}?".to_sym
84
+ service = resource.authorization
85
+
86
+ if service.has_method? method
87
+ service.authorize_action(method, raise_exception: false)
88
+ else
89
+ true
90
+ end
91
+ end
81
92
  end
82
93
  end
83
94
  end
@@ -3,12 +3,14 @@ module Avo
3
3
  class TextField < BaseField
4
4
  attr_reader :link_to_resource
5
5
  attr_reader :as_html
6
+ attr_reader :protocol
6
7
 
7
8
  def initialize(id, **args, &block)
8
9
  super(id, **args, &block)
9
10
 
10
- @link_to_resource = args[:link_to_resource].present? ? args[:link_to_resource] : false
11
- @as_html = args[:as_html].present? ? args[:as_html] : false
11
+ add_boolean_prop args, :link_to_resource
12
+ add_boolean_prop args, :as_html
13
+ add_string_prop args, :protocol
12
14
  end
13
15
  end
14
16
  end
@@ -49,6 +49,12 @@ module Avo
49
49
  add_item panel
50
50
  end
51
51
 
52
+ def heading(body = nil, **args, &block)
53
+ field = Avo::Fields::HeadingField.new(body, **args)
54
+
55
+ add_item field
56
+ end
57
+
52
58
  def add_item(instance)
53
59
  @items << instance
54
60
 
@@ -5,6 +5,7 @@ class Avo::PanelBuilder
5
5
  end
6
6
  end
7
7
 
8
+ delegate :heading, to: :items_holder
8
9
  delegate :field, to: :items_holder
9
10
  delegate :items, to: :items_holder
10
11