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.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/app/assets/builds/avo.css +28 -0
- data/app/assets/builds/avo.js +63 -63
- data/app/assets/builds/avo.js.map +2 -2
- data/app/components/avo/actions_component.rb +6 -2
- data/app/components/avo/fields/common/key_value_component.html.erb +2 -2
- data/app/components/avo/fields/common/single_file_viewer_component.rb +1 -1
- data/app/components/avo/fields/date_field/edit_component.html.erb +1 -0
- data/app/components/avo/fields/date_time_field/edit_component.html.erb +9 -11
- data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -9
- data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -9
- data/app/components/avo/index/ordering/button_component.rb +5 -13
- data/app/components/avo/index/resource_controls_component.html.erb +2 -2
- data/app/components/avo/index/resource_controls_component.rb +5 -1
- data/app/components/avo/views/resource_edit_component.rb +1 -1
- data/app/components/avo/views/resource_index_component.rb +2 -2
- data/app/controllers/avo/application_controller.rb +24 -3
- data/app/javascript/avo.js +5 -1
- data/app/javascript/js/controllers/fields/date_field_controller.js +24 -68
- data/app/javascript/js/controllers/tabs_controller.js +80 -0
- data/app/views/avo/partials/_javascript.html.erb +1 -1
- data/config/routes.rb +1 -1
- data/lib/avo/app.rb +11 -4
- data/lib/avo/base_card.rb +1 -7
- data/lib/avo/base_resource.rb +1 -1
- data/lib/avo/concerns/handles_field_args.rb +1 -1
- data/lib/avo/concerns/model_class_constantized.rb +23 -0
- data/lib/avo/dashboards/base_dashboard.rb +1 -1
- data/lib/avo/fields/date_field.rb +2 -0
- data/lib/avo/fields/date_time_field.rb +9 -13
- data/lib/avo/fields/has_base_field.rb +3 -1
- data/lib/avo/fields/has_one_field.rb +4 -1
- data/lib/avo/menu/builder.rb +8 -7
- data/lib/avo/services/uri_service.rb +71 -0
- data/lib/avo/version.rb +1 -1
- data/lib/avo.rb +1 -0
- data/lib/generators/avo/templates/locales/avo.fr.yml +115 -0
- data/public/avo-assets/avo.js +63 -63
- data/public/avo-assets/avo.js.map +2 -2
- 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
|
-
|
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
|
-
|
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, :
|
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, :
|
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
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
22
|
+
</div>
|
25
23
|
<% end %>
|
@@ -1,11 +1,3 @@
|
|
1
1
|
<%= index_field_wrapper field: @field, resource: @resource do %>
|
2
|
-
|
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
|
-
<%=
|
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
|
-
|
22
|
-
|
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
|
-
|
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,
|
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:
|
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
|
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
|
-
|
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 :
|
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,
|
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
|
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
|
data/app/javascript/avo.js
CHANGED
@@ -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
|
-
|
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:
|
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.
|
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.
|
35
|
+
options.locale.firstDayOfWeek = this.inputTarget.dataset.firstDayOfWeek
|
90
36
|
|
91
37
|
// Enable time if needed.
|
92
|
-
options.enableTime =
|
93
|
-
options.enableSeconds =
|
38
|
+
options.enableTime = enableTime
|
39
|
+
options.enableSeconds = enableTime
|
40
|
+
|
41
|
+
let currentValue
|
94
42
|
|
95
43
|
// enable timezone display
|
96
|
-
if (
|
97
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
data/lib/avo/base_resource.rb
CHANGED
@@ -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 =
|
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
|
@@ -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
|