avo 2.9.1.pre3 → 2.9.1.pre4

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -1
  3. data/app/assets/builds/avo.js +63 -63
  4. data/app/assets/builds/avo.js.map +2 -2
  5. data/app/components/avo/actions_component.rb +6 -2
  6. data/app/components/avo/base_component.rb +2 -0
  7. data/app/components/avo/fields/common/single_file_viewer_component.rb +1 -1
  8. data/app/components/avo/fields/date_field/edit_component.html.erb +1 -0
  9. data/app/components/avo/fields/date_time_field/edit_component.html.erb +9 -11
  10. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -9
  11. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -9
  12. data/app/components/avo/index/ordering/button_component.rb +3 -13
  13. data/app/components/avo/views/resource_edit_component.rb +1 -1
  14. data/app/components/avo/views/resource_index_component.rb +2 -2
  15. data/app/controllers/avo/application_controller.rb +24 -3
  16. data/app/javascript/avo.js +5 -1
  17. data/app/javascript/js/controllers/fields/date_field_controller.js +24 -68
  18. data/app/views/avo/partials/_javascript.html.erb +1 -1
  19. data/config/routes.rb +1 -1
  20. data/lib/avo/app.rb +6 -2
  21. data/lib/avo/base_card.rb +1 -7
  22. data/lib/avo/base_resource.rb +1 -1
  23. data/lib/avo/concerns/handles_field_args.rb +1 -1
  24. data/lib/avo/concerns/model_class_constantized.rb +23 -0
  25. data/lib/avo/dashboards/base_dashboard.rb +1 -1
  26. data/lib/avo/fields/date_field.rb +2 -0
  27. data/lib/avo/fields/date_time_field.rb +9 -13
  28. data/lib/avo/fields/has_base_field.rb +3 -1
  29. data/lib/avo/fields/has_one_field.rb +4 -1
  30. data/lib/avo/menu/builder.rb +3 -7
  31. data/lib/avo/services/uri_service.rb +41 -0
  32. data/lib/avo/version.rb +1 -1
  33. data/lib/avo.rb +1 -0
  34. data/public/avo-assets/avo.js +63 -63
  35. data/public/avo-assets/avo.js.map +2 -2
  36. metadata +4 -2
@@ -43,10 +43,14 @@ class Avo::ActionsComponent < ViewComponent::Base
43
43
  end
44
44
 
45
45
  def single_record_path(id)
46
- "#{@resource.record_path}/actions/#{id}"
46
+ Avo::Services::URIService.parse(@resource.record_path)
47
+ .append_paths("actions", id)
48
+ .to_s
47
49
  end
48
50
 
49
51
  def many_records_path(id)
50
- "#{@resource.records_path}/actions/#{id}"
52
+ Avo::Services::URIService.parse(@resource.records_path)
53
+ .append_paths("actions", id)
54
+ .to_s
51
55
  end
52
56
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::BaseComponent < ViewComponent::Base
4
+ delegate :view_context, to: Avo::App
5
+
4
6
  def has_with_trial(ability)
5
7
  ::Avo::App.license.has_with_trial(ability)
6
8
  end
@@ -10,7 +10,7 @@ class Avo::Fields::Common::SingleFileViewerComponent < ViewComponent::Base
10
10
  end
11
11
 
12
12
  def destroy_path
13
- "#{@resource.record_path}/active_storage_attachments/#{id}/#{file.id}"
13
+ Avo::Services::URIService.parse(@resource.record_path).append_paths("active_storage_attachments", id, file.id).to_s
14
14
  end
15
15
 
16
16
  def id
@@ -6,6 +6,7 @@
6
6
  'date-field-target': 'input',
7
7
  'first-day-of-week': @field.first_day_of_week,
8
8
  'picker-format': @field.picker_format,
9
+ 'disable-mobile': @field.disable_mobile,
9
10
  'enable-time': false,
10
11
  format: @field.format,
11
12
  placeholder: @field.placeholder,
@@ -1,18 +1,16 @@
1
1
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
2
- <%= content_tag :div, data: {
3
- controller: "date-field",
4
- date_field_view_value: @view,
5
- date_field_enable_time_value: true,
6
- date_field_picker_format_value: @field.picker_format,
7
- date_field_first_day_of_week_value: @field.first_day_of_week,
8
- date_field_time24_hr_value: @field.time_24hr,
9
- date_field_timezone_value: @field.timezone,
10
- } do %>
2
+ <div data-controller="date-field">
11
3
  <%= @form.datetime_field @field.id,
12
- value: @field.edit_formatted_value,
13
4
  class: classes("w-full"),
14
5
  data: {
15
6
  'date-field-target': 'input',
7
+ 'first-day-of-week': @field.first_day_of_week,
8
+ 'picker-format': @field.picker_format,
9
+ 'disable-mobile': @field.disable_mobile,
10
+ 'enable-time': true,
11
+ time24hr: @field.time_24hr,
12
+ timezone: @field.timezone,
13
+ format: @field.format,
16
14
  placeholder: @field.placeholder,
17
15
  relative: @field.relative,
18
16
  **@field.get_html(:data, view: view, element: :input)
@@ -21,5 +19,5 @@
21
19
  placeholder: @field.placeholder,
22
20
  style: @field.get_html(:style, view: view, element: :input)
23
21
  %>
24
- <% end %>
22
+ </div>
25
23
  <% end %>
@@ -1,11 +1,3 @@
1
1
  <%= index_field_wrapper field: @field, resource: @resource do %>
2
- <%= content_tag :div, data: {
3
- controller: "date-field",
4
- date_field_view_value: @view,
5
- date_field_format_value: @field.format,
6
- date_field_timezone_value: @field.timezone,
7
- date_field_picker_format_value: @field.picker_format,
8
- } do %>
9
- <%= @field.formatted_value %>
10
- <% end %>
2
+ <%= @field.formatted_value %>
11
3
  <% end %>
@@ -1,11 +1,3 @@
1
1
  <%= show_field_wrapper field: @field, resource: @resource, index: @index do %>
2
- <%= content_tag :div, data: {
3
- controller: "date-field",
4
- date_field_view_value: @view,
5
- date_field_format_value: @field.format,
6
- date_field_timezone_value: @field.timezone,
7
- date_field_picker_format_value: @field.picker_format,
8
- } do %>
9
- <%= @field.formatted_value %>
10
- <% end %>
2
+ <%= @field.formatted_value %>
11
3
  <% end %>
@@ -18,20 +18,10 @@ class Avo::Index::Ordering::ButtonComponent < Avo::Index::Ordering::BaseComponen
18
18
  end
19
19
 
20
20
  def order_path(args)
21
- if reflection.present?
22
- path = "#{::Avo::App.root_path}/resources/#{reflection_parent_resource.route_key}/#{params[:id]}/#{field.id}/#{resource.model.id}/order"
21
+ if reflection.present?
22
+ view_context.avo.associations_order_path(reflection_parent_resource.route_key, params[:id], field.id, resource.model.id, **args)
23
23
  else
24
- path = "#{::Avo::App.root_path}/resources/#{resource.route_key}/#{resource.model.id}/order"
24
+ view_context.avo.resources_order_path(resource.route_key, resource.model.id, **args)
25
25
  end
26
-
27
- if args.present?
28
- string_args = args.map do |key, value|
29
- "#{key}=#{value}"
30
- end.join('&')
31
-
32
- path = "#{path}?#{string_args}"
33
- end
34
-
35
- path
36
26
  end
37
27
  end
@@ -32,7 +32,7 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
32
32
  # The save button is dependent on the edit? policy method.
33
33
  # The update? method should be called only when the user clicks the Save button so the developer gets access to the params from the form.
34
34
  def can_see_the_save_button?
35
- @resource.authorization.authorize_action :edit, raise_exception: false
35
+ @resource.authorization.authorize_action @view, raise_exception: false
36
36
  end
37
37
 
38
38
  private
@@ -117,7 +117,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
117
117
  end
118
118
 
119
119
  def attach_path
120
- "#{Avo::App.root_path}#{request.env["PATH_INFO"]}/new"
120
+ Avo::App.root_path(paths: [request.env["PATH_INFO"], "new"])
121
121
  end
122
122
 
123
123
  def singular_resource_name
@@ -145,7 +145,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
145
145
  @reflection.active_record.to_s
146
146
  end
147
147
 
148
- def name
148
+ def name
149
149
  field.custom_name? ? field.name : field.plural_name
150
150
  end
151
151
 
@@ -13,7 +13,8 @@ module Avo
13
13
  protect_from_forgery with: :exception
14
14
  before_action :init_app
15
15
  before_action :check_avo_license
16
- before_action :set_locale
16
+ before_action :set_default_locale
17
+ around_action :set_force_locale
17
18
  before_action :set_authorization
18
19
  before_action :_authenticate!
19
20
  before_action :set_container_classes
@@ -27,7 +28,7 @@ module Avo
27
28
  add_flash_types :info, :warning, :success, :error
28
29
 
29
30
  def init_app
30
- Avo::App.init request: request, context: context, root_path: avo.root_path.delete_suffix("/"), current_user: _current_user, view_context: view_context, params: params
31
+ Avo::App.init request: request, context: context, current_user: _current_user, view_context: view_context, params: params
31
32
 
32
33
  @license = Avo::App.license
33
34
  end
@@ -287,10 +288,30 @@ module Avo
287
288
  @resource.form_scope
288
289
  end
289
290
 
290
- def set_locale
291
+ def set_default_locale
291
292
  I18n.locale = params[:set_locale] || I18n.default_locale
292
293
 
293
294
  I18n.default_locale = I18n.locale
294
295
  end
296
+
297
+ # Temporary set the locale
298
+ def set_force_locale
299
+ if params[:force_locale].present?
300
+ initial_locale = I18n.locale.to_s.dup
301
+ I18n.locale = params[:force_locale]
302
+ yield
303
+ I18n.locale = initial_locale
304
+ else
305
+ yield
306
+ end
307
+ end
308
+
309
+ def default_url_options
310
+ if params[:force_locale].present?
311
+ { **super, force_locale: params[:force_locale] }
312
+ else
313
+ super
314
+ end
315
+ end
295
316
  end
296
317
  end
@@ -74,7 +74,11 @@ document.addEventListener('turbo:frame-load', () => {
74
74
  document.addEventListener('turbo:before-fetch-response', async (e) => {
75
75
  if (e.detail.fetchResponse.response.status === 500) {
76
76
  const { id, src } = e.target
77
- e.target.src = `${window.Avo.configuration.root_path}/failed_to_load?turbo_frame=${id}&src=${src}`
77
+ // Don't try to redirect to failed to load if this is alread a redirection to failed to load and crashed somewhere.
78
+ // You'll end up with a request loop.
79
+ if (!e.detail.fetchResponse?.response?.url?.includes('/failed_to_load')) {
80
+ e.target.src = `${window.Avo.configuration.root_path}/failed_to_load?turbo_frame=${id}&src=${src}`
81
+ }
78
82
  }
79
83
  })
80
84
 
@@ -2,6 +2,8 @@ import { Controller } from '@hotwired/stimulus'
2
2
  import { DateTime } from 'luxon'
3
3
  import flatpickr from 'flatpickr'
4
4
 
5
+ import { castBoolean } from '../../helpers/cast_boolean'
6
+
5
7
  // Get the DateTime with the TZ offset applied.
6
8
  function universalTimestamp(timestampStr) {
7
9
  return new Date(new Date(timestampStr).getTime() + (new Date(timestampStr).getTimezoneOffset() * 60 * 1000))
@@ -10,99 +12,53 @@ function universalTimestamp(timestampStr) {
10
12
  export default class extends Controller {
11
13
  static targets = ['input']
12
14
 
13
- static values = {
14
- view: String,
15
- timezone: String,
16
- format: String,
17
- enableTime: Boolean,
18
- pickerFormat: String,
19
- firstDayOfWeek: Number,
20
- time24Hr: Boolean,
21
- }
22
-
23
- get browserZone() {
24
- const time = DateTime.local()
25
-
26
- return time.zoneName
27
- }
28
-
29
- get initialValue() {
30
- if (this.isOnShow || this.isOnIndex) {
31
- return this.context.element.innerText
32
- } if (this.isOnEdit) {
33
- return this.inputTarget.value
34
- }
35
-
36
- return null
37
- }
38
-
39
- get isOnIndex() {
40
- return this.viewValue === 'index'
41
- }
42
-
43
- get isOnEdit() {
44
- return this.viewValue === 'edit'
45
- }
46
-
47
- get isOnShow() {
48
- return this.viewValue === 'show'
49
- }
50
-
51
- // Parse the time as if it were UTC
52
- get parsedValue() {
53
- return DateTime.fromISO(this.initialValue, { zone: 'UTC' })
54
- }
55
-
56
- get displayTimezone() {
57
- return this.timezoneValue || this.browserZone
58
- }
59
-
60
15
  connect() {
61
- if (this.isOnShow || this.isOnIndex) {
62
- this.initShow()
63
- } else if (this.isOnEdit) {
64
- this.initEdit()
65
- }
66
- }
67
-
68
- // Turns the value in the controller wrapper into the timezone of the browser
69
- initShow() {
70
- this.context.element.innerText = this.parsedValue.setZone(this.displayTimezone).toFormat(this.formatValue)
71
- }
72
-
73
- initEdit() {
74
16
  const options = {
75
17
  enableTime: false,
76
18
  enableSeconds: false,
77
19
  // eslint-disable-next-line camelcase
78
- time_24hr: this.time24HrValue,
20
+ time_24hr: false,
79
21
  locale: {
80
22
  firstDayOfWeek: 0,
81
23
  },
82
24
  altInput: true,
83
25
  }
26
+ const enableTime = castBoolean(this.inputTarget.dataset.enableTime)
84
27
 
85
28
  // Set the format of the displayed input field.
86
- options.altFormat = this.pickerFormatValue
29
+ options.altFormat = this.inputTarget.dataset.pickerFormat
30
+
31
+ // Disable native input in mobile browsers
32
+ options.disableMobile = this.inputTarget.dataset.disableMobile
87
33
 
88
34
  // Set first day of the week.
89
- options.locale.firstDayOfWeek = this.firstDayOfWeekValue
35
+ options.locale.firstDayOfWeek = this.inputTarget.dataset.firstDayOfWeek
90
36
 
91
37
  // Enable time if needed.
92
- options.enableTime = this.enableTimeValue
93
- options.enableSeconds = this.enableTimeValue
38
+ options.enableTime = enableTime
39
+ options.enableSeconds = enableTime
40
+
41
+ let currentValue
94
42
 
95
43
  // enable timezone display
96
- if (this.enableTimeValue) {
97
- options.defaultDate = this.parsedValue.setZone(this.displayTimezone).toISO()
44
+ if (enableTime) {
45
+ currentValue = DateTime.fromISO(this.inputTarget.value, { zone: window.Avo.configuration.timezone })
46
+ currentValue = currentValue.setZone(this.inputTarget.dataset.timezone)
47
+ currentValue = currentValue.toISO()
98
48
 
99
49
  options.dateFormat = 'Y-m-d H:i:S'
50
+ // eslint-disable-next-line camelcase
51
+ options.time_24hr = castBoolean(this.inputTarget.dataset.time24hr)
52
+ // this.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
53
+ options.appTimezone = this.inputTarget.dataset.timezone
100
54
  } else {
101
55
  // 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.
102
56
  // Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
103
- options.defaultDate = universalTimestamp(this.initialValue)
57
+ currentValue = universalTimestamp(this.inputTarget.value)
104
58
  }
105
59
 
60
+ options.defaultDate = currentValue
61
+
106
62
  flatpickr(this.inputTarget, options)
107
63
  }
108
64
  }
@@ -1,6 +1,6 @@
1
1
  <%= javascript_tag nonce: true do %>
2
2
  window.Avo = window.Avo || { configuration: {} }
3
3
  Avo.configuration.timezone = '<%= Avo.configuration.timezone %>'
4
- Avo.configuration.root_path = '<%= Avo::App.root_path %>'
4
+ Avo.configuration.root_path = '<%= Avo.configuration.root_path %>'
5
5
  Avo.configuration.search_debounce = '<%= Avo.configuration.search_debounce %>'
6
6
  <% end %>
data/config/routes.rb CHANGED
@@ -23,7 +23,7 @@ Avo::Engine.routes.draw do
23
23
  delete "/:resource_name/:id/active_storage_attachments/:attachment_name/:attachment_id", to: "attachments#destroy"
24
24
 
25
25
  # Ordering
26
- patch "/:resource_name/:id/order", to: "resources#order"
26
+ patch "/:resource_name/:id/order", to: "resources#order", as: "order"
27
27
  patch "/:resource_name/:id/:related_name/:related_id/order", to: "associations#order", as: "associations_order"
28
28
 
29
29
  # Actions
data/lib/avo/app.rb CHANGED
@@ -29,12 +29,16 @@ module Avo
29
29
  end
30
30
  end
31
31
 
32
- def init(request:, context:, current_user:, root_path:, view_context:, params:)
32
+ # Renerate a dynamic root path using the URIService
33
+ def root_path(paths: [], **args)
34
+ Avo::Services::URIService.parse(view_context.avo.root_url.to_s).append_paths(paths).to_s
35
+ end
36
+
37
+ def init(request:, context:, current_user:, view_context:, params:)
33
38
  self.error_messages = []
34
39
  self.request = request
35
40
  self.context = context
36
41
  self.current_user = current_user
37
- self.root_path = root_path
38
42
  self.view_context = view_context
39
43
  self.params = params
40
44
 
data/lib/avo/base_card.rb CHANGED
@@ -59,13 +59,7 @@ module Avo
59
59
  def frame_url(enforced_range: nil, params: {})
60
60
  enforced_range ||= initial_range || ranges.first
61
61
 
62
- # append the parent params to the card request
63
- begin
64
- other_params = "&#{params.permit!.to_h.map { |k, v| "#{k}=#{v}" }.join("&")}"
65
- rescue
66
- end
67
-
68
- "#{Avo::App.root_path}/dashboards/#{dashboard.id}/cards/#{id}?turbo_frame=#{turbo_frame}&index=#{index}&range=#{enforced_range}#{other_params}"
62
+ Avo::App.view_context.avo.dashboard_card_path(dashboard.id, id, turbo_frame: turbo_frame, index: index, range: enforced_range, **params.permit!)
69
63
  end
70
64
 
71
65
  def card_classes
@@ -8,6 +8,7 @@ module Avo
8
8
  include Avo::Concerns::HasModel
9
9
  include Avo::Concerns::HasFields
10
10
  include Avo::Concerns::HasStimulusControllers
11
+ include Avo::Concerns::ModelClassConstantized
11
12
 
12
13
  delegate :view_context, to: ::Avo::App
13
14
  delegate :simple_format, :content_tag, to: :view_context
@@ -29,7 +30,6 @@ module Avo
29
30
  class_attribute :search_query, default: nil
30
31
  class_attribute :search_query_help, default: ""
31
32
  class_attribute :includes, default: []
32
- class_attribute :model_class
33
33
  class_attribute :translation_key
34
34
  class_attribute :default_view_type, default: :table
35
35
  class_attribute :devise_password_optional, default: false
@@ -28,7 +28,7 @@ module Avo
28
28
  add_prop_from_args args, name: name, default: default, type: :array
29
29
  end
30
30
 
31
- def add_string_prop(args, name, default = nil)
31
+ def add_string_prop(args, name, default = [])
32
32
  add_prop_from_args args, name: name, default: default, type: :string
33
33
  end
34
34
  end
@@ -0,0 +1,23 @@
1
+ module Avo
2
+ module Concerns
3
+ module ModelClassConstantized
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ attr_reader :model_class
8
+
9
+ # Cast the model class to a constantized version and memoize it like that
10
+ def model_class=(value)
11
+ @model_class = case value
12
+ when Class
13
+ value
14
+ when String, Symbol
15
+ value.to_s.safe_constantize
16
+ else
17
+ raise ArgumentError.new "Failed to find a proper model class for #{self.to_s}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -65,7 +65,7 @@ module Avo
65
65
  end
66
66
 
67
67
  def navigation_path
68
- "#{Avo::App.root_path}/dashboards/#{id}"
68
+ Avo::App.view_context.avo.dashboard_path id
69
69
  end
70
70
 
71
71
  def is_visible?
@@ -3,6 +3,7 @@ module Avo
3
3
  class DateField < TextField
4
4
  attr_reader :first_day_of_week
5
5
  attr_reader :picker_format
6
+ attr_reader :disable_mobile
6
7
  attr_reader :format
7
8
  attr_reader :relative
8
9
 
@@ -13,6 +14,7 @@ module Avo
13
14
  @picker_format = args[:picker_format].present? ? args[:picker_format] : "Y-m-d"
14
15
  @format = args[:format].present? ? args[:format] : :long
15
16
  @relative = args[:relative].present? ? args[:relative] : false
17
+ @disable_mobile = args[:disable_mobile].present? ? args[:disable_mobile] : false
16
18
  end
17
19
 
18
20
  def formatted_value
@@ -2,29 +2,25 @@ module Avo
2
2
  module Fields
3
3
  class DateTimeField < DateField
4
4
  attr_reader :format
5
- attr_reader :picker_format
6
5
  attr_reader :time_24hr
7
6
  attr_reader :timezone
8
7
 
9
8
  def initialize(id, **args, &block)
10
9
  super(id, **args, &block)
11
10
 
12
- add_boolean_prop args, :time_24hr
13
- add_string_prop args, :picker_format, "Y-m-d H:i:S"
14
- add_string_prop args, :format, "yyyy-LL-dd TT"
15
- add_string_prop args, :timezone
11
+ @picker_format = args[:picker_format].present? ? args[:picker_format] : "Y-m-d H:i:S"
12
+ @time_24hr = args[:time_24hr].present? ? args[:time_24hr] : false
13
+ @timezone = args[:timezone].present? ? args[:timezone] : Rails.application.config.time_zone
16
14
  end
17
15
 
18
16
  def formatted_value
19
17
  return nil if value.nil?
20
18
 
21
- value.utc.to_time.iso8601
22
- end
23
-
24
- def edit_formatted_value
25
- return nil if value.nil?
26
-
27
- value.utc.to_formatted_s(:db)
19
+ if @format.is_a?(Symbol)
20
+ value.to_time.in_time_zone(timezone).to_formatted_s(@format)
21
+ else
22
+ value.to_time.in_time_zone(timezone).strftime(@format)
23
+ end
28
24
  end
29
25
 
30
26
  def fill_field(model, key, value, params)
@@ -36,7 +32,7 @@ module Avo
36
32
 
37
33
  return model if value.blank?
38
34
 
39
- model[id] = value.in_time_zone(timezone)
35
+ model[id] = value.to_time.in_time_zone(Rails.application.config.time_zone)
40
36
 
41
37
  model
42
38
  end
@@ -29,7 +29,9 @@ module Avo
29
29
  end
30
30
 
31
31
  def frame_url
32
- "#{@resource.record_path}/#{id}?turbo_frame=#{turbo_frame}"
32
+ Avo::Services::URIService.parse(@resource.record_path)
33
+ .append_path(id.to_s)
34
+ .append_query("turbo_frame=#{turbo_frame.to_s}").to_s
33
35
  end
34
36
 
35
37
  # The value
@@ -22,7 +22,10 @@ module Avo
22
22
  end
23
23
 
24
24
  def frame_url
25
- "#{@resource.record_path}/#{id}/#{value.id}?turbo_frame=#{turbo_frame}"
25
+ Avo::Services::URIService.parse(@resource.record_path)
26
+ .append_paths(id, value.id)
27
+ .append_query("turbo_frame=#{turbo_frame}")
28
+ .to_s
26
29
  end
27
30
 
28
31
  def fill_field(model, key, value, params)
@@ -5,6 +5,8 @@ class Avo::Menu::Builder
5
5
  end
6
6
  end
7
7
 
8
+ delegate :root_path, to: Avo::App
9
+
8
10
  def initialize(name: nil, items: [])
9
11
  @menu = Avo::Menu::Menu.new
10
12
 
@@ -60,7 +62,7 @@ class Avo::Menu::Builder
60
62
  # Add all the tools
61
63
  def all_tools(**args)
62
64
  Avo::App.tools_for_navigation.each do |tool|
63
- link tool.humanize, path: "#{root_path}/#{tool}"
65
+ link tool.humanize, path: root_path(paths: [tool])
64
66
  end
65
67
  end
66
68
 
@@ -68,10 +70,4 @@ class Avo::Menu::Builder
68
70
  def build
69
71
  @menu
70
72
  end
71
-
72
- protected
73
-
74
- def root_path
75
- Avo::App.root_path
76
- end
77
73
  end
@@ -0,0 +1,41 @@
1
+ module Avo
2
+ module Services
3
+ class URIService
4
+ class << self
5
+ def parse(path)
6
+ self.new path
7
+ end
8
+ end
9
+
10
+ attr_reader = :uri
11
+
12
+ def initialize(path = '')
13
+ @uri = Addressable::URI.parse(path)
14
+ end
15
+
16
+ def append_paths(*paths)
17
+ paths = Array.wrap(paths)
18
+
19
+ # abort 11.inspect
20
+
21
+ return self if paths.blank?
22
+ # abort 12.inspect
23
+
24
+ @uri.merge!(path: @uri.path.concat("/#{paths.join("/")}"))
25
+ self
26
+ end
27
+ alias_method :append_path, :append_paths
28
+
29
+ def append_query(*params)
30
+ params = Array.wrap(params)
31
+
32
+ @uri.merge!(query: [@uri.query, *params].join("&"))
33
+ self
34
+ end
35
+
36
+ def to_s
37
+ @uri.to_s
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "2.9.1.pre3" unless const_defined?(:VERSION)
2
+ VERSION = "2.9.1.pre4" unless const_defined?(:VERSION)
3
3
  end
data/lib/avo.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "avo/engine" if defined?(Rails)
5
5
  loader = Zeitwerk::Loader.for_gem
6
6
  loader.inflector.inflect(
7
7
  "html" => "HTML",
8
+ "uri_service" => "URIService",
8
9
  "has_html_attributes" => "HasHTMLAttributes"
9
10
  )
10
11
  loader.ignore("#{__dir__}/generators")