avo 2.9.1.pre3 → 2.9.1.pre6

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -1
  3. data/app/assets/builds/avo.css +28 -0
  4. data/app/assets/builds/avo.js +63 -63
  5. data/app/assets/builds/avo.js.map +2 -2
  6. data/app/components/avo/actions_component.rb +6 -2
  7. data/app/components/avo/fields/common/key_value_component.html.erb +2 -2
  8. data/app/components/avo/fields/common/single_file_viewer_component.rb +1 -1
  9. data/app/components/avo/fields/date_field/edit_component.html.erb +1 -0
  10. data/app/components/avo/fields/date_time_field/edit_component.html.erb +9 -11
  11. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -9
  12. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -9
  13. data/app/components/avo/index/ordering/button_component.rb +5 -13
  14. data/app/components/avo/index/resource_controls_component.html.erb +2 -2
  15. data/app/components/avo/index/resource_controls_component.rb +5 -1
  16. data/app/components/avo/views/resource_edit_component.rb +1 -1
  17. data/app/components/avo/views/resource_index_component.rb +2 -2
  18. data/app/controllers/avo/application_controller.rb +24 -3
  19. data/app/javascript/avo.js +5 -1
  20. data/app/javascript/js/controllers/fields/date_field_controller.js +24 -68
  21. data/app/javascript/js/controllers/tabs_controller.js +80 -0
  22. data/app/views/avo/partials/_javascript.html.erb +1 -1
  23. data/config/routes.rb +1 -1
  24. data/lib/avo/app.rb +11 -4
  25. data/lib/avo/base_card.rb +1 -7
  26. data/lib/avo/base_resource.rb +1 -1
  27. data/lib/avo/concerns/handles_field_args.rb +1 -1
  28. data/lib/avo/concerns/model_class_constantized.rb +23 -0
  29. data/lib/avo/dashboards/base_dashboard.rb +1 -1
  30. data/lib/avo/fields/date_field.rb +2 -0
  31. data/lib/avo/fields/date_time_field.rb +9 -13
  32. data/lib/avo/fields/has_base_field.rb +3 -1
  33. data/lib/avo/fields/has_one_field.rb +4 -1
  34. data/lib/avo/menu/builder.rb +8 -7
  35. data/lib/avo/services/uri_service.rb +71 -0
  36. data/lib/avo/version.rb +1 -1
  37. data/lib/avo.rb +1 -0
  38. data/lib/generators/avo/templates/locales/avo.fr.yml +115 -0
  39. data/public/avo-assets/avo.js +63 -63
  40. data/public/avo-assets/avo.js.map +2 -2
  41. metadata +6 -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
@@ -3,7 +3,7 @@
3
3
  data-key-value-target="controller"
4
4
  data-options="<%= @field.options.to_json %>"
5
5
  data-input-classes="<%= input_classes %>"
6
- data-editable="<%= @view.in?([:edit, :create]) %>"
6
+ data-editable="<%= @view.in?([:edit, :new]) %>"
7
7
  >
8
8
  <div class="w-full flex flex-col">
9
9
  <div class="flex w-full">
@@ -14,7 +14,7 @@
14
14
  <div class="w-1/2 py-3 px-3 uppercase font-semibold text-xs text-white">
15
15
  <%= @field.value_label %>
16
16
  </div>
17
- <% if @view.in?([:edit, :create]) %>
17
+ <% if @view.in?([:edit, :new]) %>
18
18
  <div class="flex items-center justify-center p-2 px-3 border-l border-gray-600">
19
19
  <a href="javascript:void(0);"
20
20
  title="<%= @field.action_text %>"
@@ -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 %>
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Index::Ordering::ButtonComponent < Avo::Index::Ordering::BaseComponent
4
+ delegate :view_context, to: ::Avo::App
5
+
4
6
  attr_accessor :resource
5
7
  attr_accessor :reflection
6
8
  attr_accessor :direction
@@ -18,20 +20,10 @@ class Avo::Index::Ordering::ButtonComponent < Avo::Index::Ordering::BaseComponen
18
20
  end
19
21
 
20
22
  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"
23
+ if reflection.present?
24
+ view_context.avo.associations_order_path(reflection_parent_resource.route_key, params[:id], field.id, resource.model.id, **args)
23
25
  else
24
- path = "#{::Avo::App.root_path}/resources/#{resource.route_key}/#{resource.model.id}/order"
26
+ view_context.avo.resources_order_path(resource.route_key, resource.model.id, **args)
25
27
  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
28
  end
37
29
  end
@@ -46,7 +46,7 @@
46
46
  }
47
47
  %>
48
48
  <%= hidden_field_tag :turbo_frame, params[:turbo_frame], id: "turbo_frame_detach_#{@resource.model.id}" if params[:turbo_frame] %>
49
- <%= hidden_field_tag :referrer, request.fullpath, id: "referrer_detach_#{@resource.model.id}" if params[:turbo_frame] %>
49
+ <%= hidden_field_tag :referrer, referrer_path, id: "referrer_detach_#{@resource.model.id}" if params[:turbo_frame] %>
50
50
  <% end %>
51
51
  <% end %>
52
52
 
@@ -71,7 +71,7 @@
71
71
  %>
72
72
  <%= form.hidden_field :view_type, value: params[:view_type], id: "turbo_view_type_#{@resource.model.id}" if params[:view_type] %>
73
73
  <%= form.hidden_field :turbo_frame, value: params[:turbo_frame], id: "turbo_frame_destroy_#{@resource.model.id}" if params[:turbo_frame] %>
74
- <%= form.hidden_field :referrer, value: request.fullpath, id: "referrer_destroy_#{@resource.model.id}" if params[:turbo_frame] %>
74
+ <%= form.hidden_field :referrer, value: referrer_path, id: "referrer_destroy_#{@resource.model.id}" if params[:turbo_frame] %>
75
75
  <% end %>
76
76
  <% end %>
77
77
  </div>
@@ -41,7 +41,7 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
41
41
  end
42
42
 
43
43
  def edit_path
44
- #Add the `view` param to let Avo know where to redirect back when the user clicks the `Cancel` button.
44
+ # Add the `view` param to let Avo know where to redirect back when the user clicks the `Cancel` button.
45
45
  args = {via_view: 'index'}
46
46
 
47
47
  if @parent_model.present?
@@ -71,4 +71,8 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
71
71
  def is_has_many_association
72
72
  @reflection.is_a?(::ActiveRecord::Reflection::HasManyReflection) || @reflection.is_a?(::ActiveRecord::Reflection::ThroughReflection)
73
73
  end
74
+
75
+ def referrer_path
76
+ Avo::App.root_path(paths: ['resources', params[:resource_name], params[:id], params[:related_name]], query: request.query_parameters.to_h)
77
+ end
74
78
  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
  }
@@ -0,0 +1,80 @@
1
+ import { AttributeObserver } from '@stimulus/mutation-observers'
2
+ import { Controller } from '@hotwired/stimulus'
3
+ import { castBoolean } from '../helpers/cast_boolean'
4
+
5
+ export default class extends Controller {
6
+ static targets = ['tab'];
7
+
8
+ static values = {
9
+ activeTab: String,
10
+ };
11
+
12
+ get currentTab() {
13
+ return this.tabTargets.find(
14
+ (element) => element.dataset.tabId === this.activeTabValue,
15
+ )
16
+ }
17
+
18
+ targetTab(id) {
19
+ return this.tabTargets.find((element) => element.dataset.tabId === id)
20
+ }
21
+
22
+ changeTab(e) {
23
+ e.preventDefault()
24
+
25
+ const { params } = e
26
+ const { id } = params
27
+
28
+ this.setTheTargetPanelHeight(id)
29
+
30
+ this.hideTabs()
31
+ this.showTab(id)
32
+ this.markTabLoaded(id)
33
+
34
+ this.activeTabValue = id
35
+ }
36
+
37
+ /**
38
+ * Sets the target container height to the previous panel height so we don't get jerky tab changes.
39
+ */
40
+ setTheTargetPanelHeight(id) {
41
+ // We don't need to add a height to this panel because it was loaded before
42
+ if (castBoolean(this.targetTab(id).dataset.loaded)) {
43
+ return
44
+ }
45
+
46
+ // Get the height of the active panel
47
+ const { height } = this.currentTab.getBoundingClientRect()
48
+ // Set it to the target panel
49
+ this.targetTab(id).style.height = `${height}px`
50
+
51
+ // Wait until the panel loaded it's content and then remove the forced height
52
+ const observer = new AttributeObserver(this.targetTab(id), 'busy', {
53
+ elementUnmatchedAttribute: () => {
54
+ // The content is not available in an instant so delay the height reset a bit.
55
+ setTimeout(() => {
56
+ this.targetTab(id).style.height = ''
57
+ }, 300)
58
+ if (observer) observer.stop()
59
+ },
60
+ })
61
+ observer.start()
62
+ }
63
+
64
+ markTabLoaded(id) {
65
+ this.targetTab(id).dataset.loaded = true
66
+ }
67
+
68
+ showTab(id) {
69
+ this.tabTargets.forEach((element) => {
70
+ if (element.dataset.tabId === id) {
71
+ element.classList.remove('hidden')
72
+ }
73
+ })
74
+ // this.tabTargets.map((element) => element.clasList.add('hidden'))
75
+ }
76
+
77
+ hideTabs() {
78
+ this.tabTargets.map((element) => element.classList.add('hidden'))
79
+ }
80
+ }
@@ -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,14 +29,21 @@ 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: [], query: {}, **args)
34
+ Avo::Services::URIService.parse(view_context.avo.root_url.to_s)
35
+ .append_paths(paths)
36
+ .append_query(query)
37
+ .to_s
38
+ end
39
+
40
+ def init(request:, context:, current_user:, view_context:, params:)
33
41
  self.error_messages = []
34
- self.request = request
35
42
  self.context = context
36
43
  self.current_user = current_user
37
- self.root_path = root_path
38
- self.view_context = view_context
39
44
  self.params = params
45
+ self.request = request
46
+ self.view_context = view_context
40
47
 
41
48
  self.license = Licensing::LicenseManager.new(Licensing::HQ.new(request).response).license
42
49
  self.translation_enabled = license.has(:localization)
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