avo 1.21.1.pre.1 → 1.22.1.pre.2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +61 -61
  3. data/README.md +1 -1
  4. data/app/assets/builds/avo.css +4 -4
  5. data/app/assets/builds/avo.js +40 -18
  6. data/app/assets/builds/avo.js.map +2 -2
  7. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +14 -7
  8. data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +54 -10
  9. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +31 -8
  10. data/app/components/avo/fields/belongs_to_field/edit_component.rb +37 -0
  11. data/app/components/avo/fields/select_field/edit_component.html.erb +3 -3
  12. data/app/components/avo/navigation_link_component.html.erb +1 -1
  13. data/app/components/avo/navigation_link_component.rb +2 -1
  14. data/app/controllers/avo/application_controller.rb +16 -14
  15. data/app/controllers/avo/base_controller.rb +2 -4
  16. data/app/controllers/avo/relations_controller.rb +1 -4
  17. data/app/javascript/avo.js +1 -0
  18. data/app/javascript/js/controllers/fields/belongs_to_field_controller.js +23 -15
  19. data/app/javascript/js/controllers/fields/date_field_controller.js +10 -2
  20. data/app/javascript/js/controllers/item_selector_controller.js +29 -19
  21. data/app/javascript/js/controllers/search_controller.js +8 -3
  22. data/app/views/avo/partials/_logo.html.erb +3 -1
  23. data/app/views/avo/partials/_turbo_frame_wrap.html.erb +7 -1
  24. data/app/views/avo/relations/new.html.erb +1 -1
  25. data/app/views/avo/sidebar/_sidebar.html.erb +1 -3
  26. data/db/factories.rb +9 -5
  27. data/lib/avo/base_resource.rb +16 -14
  28. data/lib/avo/fields/base_field.rb +7 -4
  29. data/lib/avo/fields/belongs_to_field.rb +92 -11
  30. data/lib/avo/fields/key_value_field.rb +28 -8
  31. data/lib/avo/version.rb +1 -1
  32. data/lib/generators/avo/templates/locales/avo.en.yml +1 -0
  33. data/lib/generators/avo/templates/locales/avo.nb-NO.yml +1 -0
  34. data/lib/generators/avo/templates/locales/avo.pt-BR.yml +1 -0
  35. data/lib/generators/avo/templates/locales/avo.ro.yml +1 -0
  36. data/public/avo-assets/avo.css +4 -4
  37. data/public/avo-assets/avo.js +40 -18
  38. data/public/avo-assets/avo.js.map +2 -2
  39. metadata +2 -2
@@ -8,15 +8,22 @@
8
8
  <div class="relative w-full" autocomplete="off">
9
9
  <%= @form.text_field @field.foreign_key,
10
10
  value: field_label,
11
- class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)), 'data-search-target': 'button clearValue',
11
+ class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
12
12
  placeholder: @field.placeholder,
13
+ 'data-search-target': 'button clearValue',
14
+ # This instructs the search_controller if it should enable/disabled this field when the user switches polymorphic associations
15
+ # It should not enable the field if the record is being created through an association
16
+ 'data-should-be-disabled': @disabled,
13
17
  disabled: true %>
14
- <div class="absolute top-1/2 left-auto right-3 mr-px -mt-2 cursor-pointer hidden text-gray-500"
15
- data-tippy="tooltip"
16
- data-search-target="clearButton"
17
- title="<%= I18n.translate 'avo.clear_value' %>"
18
- data-action="click->search#clearValue"
19
- ><%= helpers.svg 'x', class: 'h-4' %></div>
18
+ <% unless @disabled %>
19
+ <div class="absolute top-1/2 left-auto right-3 mr-px -mt-2 cursor-pointer hidden text-gray-500"
20
+ data-tippy="tooltip"
21
+ data-search-target="clearButton"
22
+ title="<%= I18n.translate 'avo.clear_value' %>"
23
+ data-action="click->search#clearValue"
24
+ ><%= helpers.svg 'x', class: 'h-4' %>
25
+ </div>
26
+ <% end %>
20
27
  </div>
21
28
  <%= @form.hidden_field @foreign_key, value: field_value, 'data-search-target': 'hiddenId clearValue' %>
22
29
  </div>
@@ -1,33 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Fields::BelongsToField::AutocompleteComponent < ViewComponent::Base
4
- def initialize(form:, field:, type: nil, model_key:, foreign_key:)
4
+ def initialize(form:, field:, model_key:, foreign_key:, disabled:, type: nil, resource: nil, polymorphic_record: nil)
5
5
  @form = form
6
6
  @field = field
7
7
  @type = type
8
8
  @model_key = model_key
9
9
  @foreign_key = foreign_key
10
+ @resource = resource
11
+ @disabled = disabled
12
+ @polymorphic_record = polymorphic_record
10
13
  end
11
14
 
12
15
  def field_label
13
- if searchable?
14
- @field.value&.class == @type ? @field.field_label : nil
15
- else
16
- @field.field_label
16
+ result = @field.field_label
17
+
18
+ # New records won't have the value (instantiated model) present but the polymorphic_type and polymorphic_id prefilled
19
+ if should_prefill?
20
+ result = @field.value&.class == @type ? @field.field_label : nil
17
21
  end
22
+
23
+ result
18
24
  end
19
25
 
20
26
  def field_value
21
- if searchable?
22
- @field.value&.class == @type ? @field.field_value : nil
23
- else
24
- @field.field_value
27
+ result = @field.field_value
28
+
29
+ # New records won't have the value (instantiated model) present but the polymorphic_type and polymorphic_id prefilled
30
+ if should_prefill?
31
+ result = @field.value&.class == @type ? @field.field_value : nil
25
32
  end
33
+
34
+ result
26
35
  end
27
36
 
28
37
  private
29
38
 
39
+ def should_prefill?
40
+ @field.is_polymorphic? && searchable? && !(new_record? && has_polymorphic_association?)
41
+ end
42
+
30
43
  def searchable?
31
- @type.present?
44
+ @field.searchable
45
+ end
46
+
47
+ def new_record?
48
+ @resource.model.new_record?
49
+ end
50
+
51
+ def has_polymorphic_association?
52
+ polymorphic_class.present? && polymorphic_id.present?
53
+ end
54
+
55
+ # Get the polymorphic class
56
+ def polymorphic_class
57
+ @resource.model["#{@field.foreign_key}_type"]
58
+ end
59
+
60
+ # Get the polymorphic id
61
+ def polymorphic_id
62
+ @resource.model["#{@field.foreign_key}_id"]
63
+ end
64
+
65
+ # Get the resource for that polymorphic class
66
+ def polymorphic_resource
67
+ ::Avo::App.get_resource_by_model_name polymorphic_class
68
+ end
69
+
70
+ # Extract the needed fields to identify the record for polymorphic associations
71
+ def polymorphic_fields
72
+ {
73
+ id: polymorphic_resource.id,
74
+ label: polymorphic_resource.title
75
+ }
32
76
  end
33
77
  end
@@ -1,5 +1,5 @@
1
1
  <%
2
- if @field.types.present? # It's a polymorphic association
2
+ if is_polymorphic?
3
3
 
4
4
  # Set the model keys so we can pass them over
5
5
  model_keys = @field.types.map do |type|
@@ -13,7 +13,7 @@
13
13
  data-association-class="<%= @field&.target_resource&.model_class || nil %>"
14
14
  >
15
15
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
16
- <%= @form.select "#{@field.foreign_key}_type", @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
16
+ <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
17
17
  {
18
18
  value: @field.value,
19
19
  include_blank: @field.placeholder,
@@ -29,7 +29,7 @@
29
29
  # If the select field is disabled, no value will be sent. It's how HTML works.
30
30
  # Thus the extra hidden field to actually send the related id to the server.
31
31
  if disabled %>
32
- <%= @form.hidden_field "#{@field.foreign_key}_type" %>
32
+ <%= @form.hidden_field @field.type_input_foreign_key %>
33
33
  <% end %>
34
34
  <% end %>
35
35
  <% @field.types.each do |type| %>
@@ -39,10 +39,20 @@
39
39
  >
40
40
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, label: type.to_s.underscore.humanize do %>
41
41
  <% if @field.searchable %>
42
- <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form, field: @field, type: type, model_key: model_keys[type.to_s], foreign_key: "#{@field.foreign_key}_id" %>
42
+ <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
43
+ field: @field,
44
+ type: type,
45
+ model_key: model_keys[type.to_s],
46
+ foreign_key: @field.id_input_foreign_key,
47
+ resource: @resource,
48
+ disabled: disabled,
49
+ polymorphic_record: polymorphic_record
50
+ %>
43
51
  <% else %>
44
- <%= @form.select "#{@field.foreign_key}_id", options_for_select(@field.values_for_type(type), @field.value&.class == type ? @field.field_value : nil),
52
+ <%= @form.select @field.id_input_foreign_key,
53
+ options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
45
54
  {
55
+ value: @resource.model[@field.id_input_foreign_key].to_s,
46
56
  include_blank: @field.placeholder,
47
57
  },
48
58
  {
@@ -50,6 +60,12 @@
50
60
  disabled: disabled
51
61
  }
52
62
  %>
63
+ <%
64
+ # If the select field is disabled, no value will be sent. It's how HTML works.
65
+ # Thus the extra hidden field to actually send the related id to the server.
66
+ if disabled %>
67
+ <%= @form.hidden_field @field.id_input_foreign_key %>
68
+ <% end %>
53
69
  <% end %>
54
70
  <% end %>
55
71
  </div>
@@ -58,11 +74,18 @@
58
74
  <% else %>
59
75
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
60
76
  <% if @field.searchable %>
61
- <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form, field: @field, model_key: @field.target_resource&.model_key, foreign_key: @field.foreign_key %>
77
+ <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
78
+ field: @field,
79
+ model_key: @field.target_resource&.model_key,
80
+ foreign_key: @field.id_input_foreign_key,
81
+ resource: @resource,
82
+ disabled: disabled
83
+ %>
62
84
  <% else %>
63
- <%= @form.select @field.foreign_key, @field.options.map { |o| [o[:label], o[:value]] },
85
+ <%= @form.select @field.id_input_foreign_key, @field.options,
64
86
  {
65
87
  include_blank: @field.placeholder,
88
+ value: @field.value
66
89
  },
67
90
  {
68
91
  class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
@@ -73,7 +96,7 @@
73
96
  # If the select field is disabled, no value will be sent. It's how HTML works.
74
97
  # Thus the extra hidden field to actually send the related id to the server.
75
98
  if disabled %>
76
- <%= @form.hidden_field @field.foreign_key %>
99
+ <%= @form.hidden_field @field.id_input_foreign_key %>
77
100
  <% end %>
78
101
  <% end %>
79
102
  <% end %>
@@ -1,6 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
4
+ def initialize(field: nil, resource: nil, index: 0, form: nil, displayed_in_modal: false)
5
+ super field: field, resource: resource, index: index, form: form, displayed_in_modal: displayed_in_modal
6
+
7
+ @polymorphic_record = nil
8
+ end
9
+
4
10
  def disabled
5
11
  return true if @field.readonly
6
12
  return true if @field.target_resource.present? && @field.target_resource.model_class.name == params[:via_resource_class]
@@ -8,4 +14,35 @@ class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
8
14
 
9
15
  false
10
16
  end
17
+
18
+ def is_polymorphic?
19
+ @field.types.present?
20
+ end
21
+
22
+ def has_polymorphic_association?
23
+ polymorphic_class.present? && polymorphic_id.present?
24
+ end
25
+
26
+ # Get the polymorphic class
27
+ def polymorphic_class
28
+ @resource.model["#{@field.foreign_key}_type"]
29
+ end
30
+
31
+ # Get the polymorphic id
32
+ def polymorphic_id
33
+ @resource.model["#{@field.foreign_key}_id"]
34
+ end
35
+
36
+ # Get the actual resource
37
+ def polymorphic_record
38
+ return unless has_polymorphic_association?
39
+
40
+ return unless is_polymorphic?
41
+
42
+ return @polymorphic_record if @polymorphic_record.present?
43
+
44
+ @polymorphic_record = polymorphic_class.safe_constantize.find polymorphic_id
45
+
46
+ @polymorphic_record
47
+ end
11
48
  end
@@ -1,6 +1,6 @@
1
1
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
2
2
  <%= @form.select @field.id, @field.options_for_select, { selected: @field.value, prompt: @field.placeholder }, {
3
- class: helpers.input_classes(' w-full', has_error: @field.model_errors.include?(@field.id)),
4
- disabled: @field.readonly,
5
- value: @field.model.present? ? @field.model[@field.id] : @field.value } %>
3
+ class: helpers.input_classes(' w-full', has_error: @field.model_errors.include?(@field.id)),
4
+ disabled: @field.readonly,
5
+ value: @field.model.present? ? @field.model[@field.id] : @field.value } %>
6
6
  <% end %>
@@ -1,3 +1,3 @@
1
- <%= active_link_to @path, class: 'text-gray-800 py-2 px-4 block font-normal hover:bg-gray-100 rounded-md mb-1 mx-3 text-sm leading-none', active: @active do %>
1
+ <%= active_link_to @path, class: 'text-gray-800 py-2 px-4 block font-normal hover:bg-gray-100 rounded-md mb-1 mx-3 text-sm leading-none', active: @active, target: @target do %>
2
2
  <div class="w-4"></div> <%= @label %>
3
3
  <% end %>
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::NavigationLinkComponent < ViewComponent::Base
4
- def initialize(label: nil, path: nil, active: :inclusive, size: :md)
4
+ def initialize(label: nil, path: nil, active: :inclusive, size: :md, target: "_self")
5
5
  @label = label
6
6
  @path = path
7
7
  @active = active
8
8
  @size = size
9
+ @target = target
9
10
  end
10
11
  end
@@ -1,6 +1,11 @@
1
1
  module Avo
2
2
  class ApplicationController < ::ActionController::Base
3
- include Pundit
3
+ if defined?(Pundit::Authorization)
4
+ include Pundit::Authorization
5
+ else
6
+ include Pundit
7
+ end
8
+
4
9
  include Pagy::Backend
5
10
  include Avo::ApplicationHelper
6
11
  include Avo::UrlHelpers
@@ -111,7 +116,12 @@ module Avo
111
116
  end
112
117
 
113
118
  def fill_model
114
- @model = @resource.fill_model(@model_to_fill, cast_nullable(model_params))
119
+ # We have to skip filling the the model if this is an attach action
120
+ is_attach_action = params[model_param_key].blank? && params[:related_name].present? && params[:fields].present?
121
+
122
+ unless is_attach_action
123
+ @model = @resource.fill_model(@model_to_fill, cast_nullable(model_params))
124
+ end
115
125
  end
116
126
 
117
127
  def hydrate_resource
@@ -187,18 +197,6 @@ module Avo
187
197
  query
188
198
  end
189
199
 
190
- # def authorize_user
191
- # return if params[:controller] == 'avo/search'
192
-
193
- # model = record = resource.model
194
-
195
- # if ['show', 'edit', 'update'].include?(params[:action]) && params[:controller] == 'avo/resources'
196
- # record = resource
197
- # end
198
-
199
- # # AuthorizationService::authorize_action _current_user, record, params[:action] return render_unauthorized unless
200
- # end
201
-
202
200
  def _authenticate!
203
201
  instance_eval(&Avo.configuration.authenticate)
204
202
  end
@@ -243,5 +241,9 @@ module Avo
243
241
  def on_api_path
244
242
  request.original_url.match?(/.*#{Avo::App.root_path}\/avo_api\/.*/)
245
243
  end
244
+
245
+ def model_param_key
246
+ @resource.form_scope
247
+ end
246
248
  end
247
249
  end
@@ -7,6 +7,7 @@ module Avo
7
7
  before_action :hydrate_resource
8
8
  before_action :set_model, only: [:show, :edit, :destroy, :update]
9
9
  before_action :set_model_to_fill
10
+ before_action :fill_model, only: [:create, :update]
10
11
  before_action :authorize_action
11
12
  before_action :reset_pagination_if_filters_changed, only: :index
12
13
  before_action :cache_applied_filters, only: :index
@@ -83,6 +84,7 @@ module Avo
83
84
  def new
84
85
  @model = @resource.model_class.new
85
86
  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
87
+ # abort @model.course.inspect
86
88
 
87
89
  @page_title = @resource.default_panel_name
88
90
  add_breadcrumb resource_name.humanize, resources_path(resource: @resource)
@@ -112,7 +114,6 @@ module Avo
112
114
 
113
115
  def create
114
116
  # model gets instantiated and filled in the fill_model method
115
- fill_model
116
117
  saved = @model.save
117
118
  @resource.hydrate(model: @model, view: :new, user: _current_user)
118
119
 
@@ -166,7 +167,6 @@ module Avo
166
167
 
167
168
  def update
168
169
  # model gets instantiated and filled in the fill_model method
169
- fill_model
170
170
  saved = @model.save
171
171
  @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
172
172
 
@@ -194,8 +194,6 @@ module Avo
194
194
  private
195
195
 
196
196
  def model_params
197
- model_param_key = @resource.form_scope
198
-
199
197
  request_params = params.require(model_param_key).permit(permitted_params)
200
198
 
201
199
  if @resource.devise_password_optional && request_params[:password].blank? && request_params[:password_confirmation].blank?
@@ -37,10 +37,7 @@ module Avo
37
37
  query = @authorization.apply_policy @attachment_class
38
38
 
39
39
  @options = query.all.map do |model|
40
- {
41
- value: model.id,
42
- label: model.send(@attachment_resource.class.title)
43
- }
40
+ [model.send(@attachment_resource.class.title), model.id]
44
41
  end
45
42
  end
46
43
 
@@ -59,6 +59,7 @@ document.addEventListener('turbo:before-fetch-response', (e) => {
59
59
  })
60
60
  document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading'))
61
61
  document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading'))
62
+ document.addEventListener('turbo:submit-end', () => document.body.classList.remove('turbo-loading'))
62
63
  document.addEventListener('turbo:before-cache', () => {
63
64
  document.querySelectorAll('[data-turbo-remove-before-cache]').forEach((element) => element.remove())
64
65
  })
@@ -58,22 +58,16 @@ export default class extends Controller {
58
58
 
59
59
  if (this.isSearchable) {
60
60
  const textInput = target.querySelector('input[type="text"]')
61
- if (textInput) {
62
- textInput.setAttribute('valid-name', textInput.getAttribute('name'))
63
- }
61
+ if (textInput) this.nameToValidName(textInput)
64
62
 
65
63
  const hiddenInput = target.querySelector('input[type="hidden"]')
66
- if (hiddenInput) {
67
- hiddenInput.setAttribute(
68
- 'valid-name',
69
- hiddenInput.getAttribute('name'),
70
- )
71
- }
64
+ if (hiddenInput) this.nameToValidName(hiddenInput)
72
65
  } else {
73
66
  const select = target.querySelector('select')
74
- if (select) {
75
- select.setAttribute('valid-name', select.getAttribute('name'))
76
- }
67
+ if (select) this.nameToValidName(select)
68
+
69
+ const hiddenInput = target.querySelector('input[type="hidden"]')
70
+ if (hiddenInput) this.nameToValidName(hiddenInput)
77
71
 
78
72
  if (this.selectedType !== type) {
79
73
  select.selectedIndex = 0
@@ -101,11 +95,16 @@ export default class extends Controller {
101
95
  const textInput = target.querySelector('input[type="text"]')
102
96
  const hiddenInput = target.querySelector('input[type="hidden"]')
103
97
 
104
- textInput.setAttribute('name', textInput.getAttribute('valid-name'))
105
- hiddenInput.setAttribute('name', hiddenInput.getAttribute('valid-name'))
98
+ this.validNameToName(textInput)
99
+ this.validNameToName(hiddenInput)
106
100
  } else {
107
101
  const select = target.querySelector('select')
108
- select.setAttribute('name', select.getAttribute('valid-name'))
102
+ const hiddenInput = target.querySelector('input[type="hidden"]')
103
+ this.validNameToName(select)
104
+
105
+ if (hiddenInput) {
106
+ this.validNameToName(hiddenInput)
107
+ }
109
108
  }
110
109
  }
111
110
 
@@ -122,7 +121,16 @@ export default class extends Controller {
122
121
  } else if (target) {
123
122
  try {
124
123
  target.querySelector('select').setAttribute('name', '')
124
+ target.querySelector('input[type="hidden"]').setAttribute('name', '')
125
125
  } catch (error) {}
126
126
  }
127
127
  }
128
+
129
+ validNameToName(target) {
130
+ target.setAttribute('name', target.getAttribute('valid-name'))
131
+ }
132
+
133
+ nameToValidName(target) {
134
+ target.setAttribute('valid-name', target.getAttribute('name'))
135
+ }
128
136
  }
@@ -1,8 +1,14 @@
1
1
  import { Controller } from 'stimulus'
2
2
  import { DateTime } from 'luxon'
3
- import { castBoolean } from '../../helpers/cast_boolean'
4
3
  import flatpickr from 'flatpickr'
5
4
 
5
+ import { castBoolean } from '../../helpers/cast_boolean'
6
+
7
+ // Get the DateTime with the TZ offset applied.
8
+ function universalTimestamp(timestampStr) {
9
+ return new Date(new Date(timestampStr).getTime() + (new Date(timestampStr).getTimezoneOffset() * 60 * 1000))
10
+ }
11
+
6
12
  export default class extends Controller {
7
13
  static targets = ['input']
8
14
 
@@ -43,7 +49,9 @@ export default class extends Controller {
43
49
  // this.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
44
50
  options.appTimezone = this.inputTarget.dataset.timezone
45
51
  } else {
46
- currentValue = new Date(this.inputTarget.value)
52
+ // Because the browser treats the date like a timestamp and updates it ot 00:00 hour, when on a western timezone the date will be converted with one day offset.
53
+ // Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
54
+ currentValue = universalTimestamp(this.inputTarget.value)
47
55
  }
48
56
 
49
57
  options.defaultDate = currentValue
@@ -1,9 +1,9 @@
1
1
  import { Controller } from 'stimulus'
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ['panel']
4
+ static targets = ['panel'];
5
5
 
6
- checkbox = {}
6
+ checkbox = {};
7
7
 
8
8
  get actionsPanelPresent() {
9
9
  return this.actionsButtonElement !== null
@@ -17,6 +17,12 @@ export default class extends Controller {
17
17
  }
18
18
  }
19
19
 
20
+ get actionLinks() {
21
+ return document.querySelectorAll(
22
+ '.js-actions-dropdown a[data-actions-picker-target="resourceAction"]',
23
+ )
24
+ }
25
+
20
26
  set currentIds(value) {
21
27
  this.stateHolderElement.dataset.selectedResources = JSON.stringify(value)
22
28
 
@@ -32,8 +38,12 @@ export default class extends Controller {
32
38
  connect() {
33
39
  this.resourceName = this.element.dataset.resourceName
34
40
  this.resourceId = this.element.dataset.resourceId
35
- this.actionsButtonElement = document.querySelector(`[data-actions-dropdown-button="${this.resourceName}"]`)
36
- this.stateHolderElement = document.querySelector(`[data-selected-resources-name="${this.resourceName}"]`)
41
+ this.actionsButtonElement = document.querySelector(
42
+ `[data-actions-dropdown-button="${this.resourceName}"]`,
43
+ )
44
+ this.stateHolderElement = document.querySelector(
45
+ `[data-selected-resources-name="${this.resourceName}"]`,
46
+ )
37
47
  }
38
48
 
39
49
  addToSelected() {
@@ -45,7 +55,9 @@ export default class extends Controller {
45
55
  }
46
56
 
47
57
  removeFromSelected() {
48
- this.currentIds = this.currentIds.filter((item) => item.toString() !== this.resourceId)
58
+ this.currentIds = this.currentIds.filter(
59
+ (item) => item.toString() !== this.resourceId,
60
+ )
49
61
  }
50
62
 
51
63
  toggle(event) {
@@ -59,22 +71,20 @@ export default class extends Controller {
59
71
  }
60
72
 
61
73
  enableResourceActions() {
62
- (document.querySelectorAll('.js-actions-dropdown a[data-actions-picker-target="resourceAction"]'))
63
- .forEach((link) => {
64
- link.classList.add('text-gray-700')
65
- link.classList.remove('text-gray-500')
66
- link.setAttribute('data-href', link.getAttribute('href'))
67
- link.dataset.disabled = false
68
- })
74
+ this.actionLinks.forEach((link) => {
75
+ link.classList.add('text-gray-700')
76
+ link.classList.remove('text-gray-500')
77
+ link.setAttribute('data-href', link.getAttribute('href'))
78
+ link.dataset.disabled = false
79
+ })
69
80
  }
70
81
 
71
82
  disableResourceActions() {
72
- (document.querySelectorAll('.js-actions-dropdown a[data-actions-picker-target="resourceAction"]'))
73
- .forEach((link) => {
74
- link.classList.remove('text-gray-700')
75
- link.classList.add('text-gray-500')
76
- link.setAttribute('href', link.getAttribute('data-href'))
77
- link.dataset.disabled = true
78
- })
83
+ this.actionLinks.forEach((link) => {
84
+ link.classList.remove('text-gray-700')
85
+ link.classList.add('text-gray-500')
86
+ link.setAttribute('href', link.getAttribute('data-href'))
87
+ link.dataset.disabled = true
88
+ })
79
89
  }
80
90
  }
@@ -82,7 +82,9 @@ export default class extends Controller {
82
82
 
83
83
  document.querySelector('.aa-DetachedOverlay').remove()
84
84
 
85
- this.clearButtonTarget.classList.remove('hidden')
85
+ if (this.hasClearButtonTarget) {
86
+ this.clearButtonTarget.classList.remove('hidden')
87
+ }
86
88
  } else {
87
89
  Turbo.visit(item._url, { action: 'advance' })
88
90
  }
@@ -157,7 +159,7 @@ export default class extends Controller {
157
159
  this.buttonTarget.onclick = () => this.showSearchPanel()
158
160
 
159
161
  this.clearValueTargets.forEach((target) => {
160
- if (target.getAttribute('value')) {
162
+ if (target.getAttribute('value') && this.hasClearButtonTarget) {
161
163
  this.clearButtonTarget.classList.remove('hidden')
162
164
  }
163
165
  })
@@ -184,6 +186,9 @@ export default class extends Controller {
184
186
  },
185
187
  })
186
188
 
187
- this.buttonTarget.removeAttribute('disabled')
189
+ // When using search for belongs-to
190
+ if (this.buttonTarget.dataset.shouldBeDisabled !== 'true') {
191
+ this.buttonTarget.removeAttribute('disabled')
192
+ }
188
193
  }
189
194
  }
@@ -1 +1,3 @@
1
- <%= image_tag '/avo-assets/logo.png', class: 'h-full', title: 'Avo' %>
1
+ <%= link_to root_path, class: 'logo-placeholder h-16 bg-white p-2 flex justify-center' do %>
2
+ <%= image_tag '/avo-assets/logo.png', class: 'h-full', title: 'Avo' %>
3
+ <% end %>
@@ -1,3 +1,9 @@
1
1
  <% if name.present? %><turbo-frame id="<%= name %>"><% end %>
2
- <%= yield %>
2
+ <%
3
+ # When rendering the frames the flashed content gets lost.
4
+ # By including the alerts partial, the stimulus will pick them up and display them to the user.
5
+ %>
6
+ <%= render partial: 'avo/partials/alerts' if flash.present? && name.present? %>
7
+
8
+ <%= yield %>
3
9
  <% if name.present? %></turbo-frame><% end %>
@@ -11,7 +11,7 @@
11
11
 
12
12
  <div class="flex-1 flex items-center justify-center px-8 text-lg mt-8 mb-12">
13
13
  <div class="flex-1 flex flex-col items-center justify-center px-24 text-base">
14
- <%= form.select :related_id, options_for_select(@options.map { |o| [o[:label], o[:value]] }, nil),
14
+ <%= form.select :related_id, options_for_select(@options, nil),
15
15
  {
16
16
  include_blank: t('avo.choose_an_option'),
17
17
  },
@@ -1,8 +1,6 @@
1
1
  <div class="application-sidebar flex h-full bg-white text-white w-56 z-50 border-r border-gray-300">
2
2
  <div class="flex flex-col w-full">
3
- <%= link_to root_path, class: 'logo-placeholder h-16 bg-white p-2 flex justify-center' do %>
4
- <%= render partial: "avo/partials/logo" %>
5
- <% end %>
3
+ <%= render partial: "avo/partials/logo" %>
6
4
 
7
5
  <div class="flex-1 flex flex-col justify-between">
8
6
  <div class="tools py-4">