avo 3.0.1.beta24 → 3.0.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.
- checksums.yaml +4 -4
- data/Gemfile +5 -5
- data/Gemfile.lock +164 -128
- data/README.md +19 -27
- data/app/components/avo/actions_component.rb +2 -2
- data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +122 -104
- data/app/components/avo/fields/belongs_to_field/edit_component.rb +11 -0
- data/app/components/avo/fields/location_field/edit_component.html.erb +12 -10
- data/app/components/avo/index/grid_item_component.html.erb +1 -1
- data/app/components/avo/index/resource_table_component.html.erb +2 -2
- data/app/components/avo/index/resource_table_component.rb +16 -0
- data/app/components/avo/index/table_row_component.html.erb +1 -1
- data/app/components/avo/items/panel_component.html.erb +25 -0
- data/app/components/avo/items/panel_component.rb +43 -0
- data/app/components/avo/items/switcher_component.html.erb +17 -0
- data/app/components/avo/items/switcher_component.rb +79 -0
- data/app/components/avo/items/visible_items_component.html.erb +10 -0
- data/app/components/avo/items/visible_items_component.rb +11 -0
- data/app/components/avo/modal_component.html.erb +11 -7
- data/app/components/avo/modal_component.rb +21 -0
- data/app/components/avo/paginator_component.html.erb +29 -40
- data/app/components/avo/paginator_component.rb +18 -0
- data/app/components/avo/panel_component.html.erb +2 -2
- data/app/components/avo/referrer_params_component.html.erb +1 -0
- data/app/components/avo/resource_component.rb +8 -15
- data/app/components/avo/resource_sidebar_component.html.erb +11 -18
- data/app/components/avo/resource_sidebar_component.rb +4 -2
- data/app/components/avo/sidebar_profile_component.html.erb +1 -1
- data/app/components/avo/tab_group_component.html.erb +1 -1
- data/app/components/avo/views/resource_edit_component.html.erb +15 -32
- data/app/components/avo/views/resource_edit_component.rb +18 -6
- data/app/components/avo/views/resource_index_component.rb +1 -0
- data/app/components/avo/views/resource_show_component.html.erb +14 -33
- data/app/components/avo/views/resource_show_component.rb +11 -0
- data/app/controllers/avo/actions_controller.rb +6 -4
- data/app/controllers/avo/application_controller.rb +2 -33
- data/app/controllers/avo/base_controller.rb +25 -16
- data/app/helpers/avo/application_helper.rb +4 -0
- data/app/javascript/js/controllers/fields/reload_belongs_to_field_controller.js +51 -0
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/actions/show.html.erb +4 -3
- data/app/views/avo/base/_new_via_belongs_to.html.erb +12 -0
- data/app/views/avo/base/close_modal_and_reload_field.turbo_stream.erb +8 -0
- data/app/views/avo/base/create_fail_action.turbo_stream.erb +13 -0
- data/app/views/avo/partials/_flash_alerts.turbo_stream.erb +3 -0
- data/config/i18n-tasks.yml +1 -1
- data/config/initializers/pagy.rb +2 -0
- data/config/master.key +1 -0
- data/lib/avo/base_action.rb +44 -38
- data/lib/avo/base_resource.rb +7 -8
- data/lib/avo/concerns/borrow_items_holder.rb +35 -0
- data/lib/avo/concerns/has_items.rb +65 -47
- data/lib/avo/concerns/hydration.rb +17 -0
- data/lib/avo/concerns/is_resource_item.rb +2 -10
- data/lib/avo/concerns/pagination.rb +53 -0
- data/lib/avo/concerns/visible_items.rb +7 -30
- data/lib/avo/dsl/field_parser.rb +1 -1
- data/lib/avo/fields/concerns/is_searchable.rb +3 -1
- data/lib/avo/fields/location_field.rb +2 -2
- data/lib/avo/filters/base_filter.rb +1 -0
- data/lib/avo/html/builder.rb +1 -0
- data/lib/avo/licensing/h_q.rb +4 -6
- data/lib/avo/licensing/license.rb +4 -0
- data/lib/avo/plugin.rb +5 -1
- data/lib/avo/resources/items/holder.rb +29 -11
- data/lib/avo/resources/items/item_group.rb +4 -9
- data/lib/avo/resources/items/main_panel.rb +10 -0
- data/lib/avo/resources/items/row.rb +4 -16
- data/lib/avo/resources/items/sidebar.rb +9 -10
- data/lib/avo/resources/items/tab.rb +3 -9
- data/lib/avo/resources/items/tab_group.rb +6 -12
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/locales/avo.ar.yml +7 -6
- data/lib/generators/avo/templates/locales/avo.en.yml +2 -0
- data/lib/generators/avo/templates/locales/avo.es.yml +127 -0
- data/lib/generators/avo/templates/locales/avo.fr.yml +2 -0
- data/lib/generators/avo/templates/locales/avo.nb.yml +2 -0
- data/lib/generators/avo/templates/locales/avo.nn.yml +2 -0
- data/lib/generators/avo/templates/locales/avo.pt-BR.yml +2 -0
- data/lib/generators/avo/templates/locales/avo.pt.yml +2 -0
- data/lib/generators/avo/templates/locales/avo.ro.yml +2 -0
- data/lib/generators/avo/templates/locales/avo.tr.yml +2 -0
- data/public/avo-assets/avo.base.css +121 -4
- data/public/avo-assets/avo.base.js +86 -86
- data/public/avo-assets/avo.base.js.map +3 -3
- data/public/avo-assets/avo.css +9744 -0
- data/public/avo-assets/avo.js +513 -0
- data/public/avo-assets/avo.js.map +7 -0
- metadata +23 -8
- data/app/components/avo/item_switcher_component.html.erb +0 -27
- data/app/components/avo/item_switcher_component.rb +0 -48
- data/app/views/avo/actions/keep_modal_open.turbo_stream.erb +0 -5
- data/lib/avo/action_model.rb +0 -20
@@ -0,0 +1,51 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
// Used as a custom Stream Action <turbo-stream action="update-belongs-to" />
|
4
|
+
export default class extends Controller {
|
5
|
+
static values = {
|
6
|
+
polymorphic: Boolean,
|
7
|
+
searchable: Boolean,
|
8
|
+
targetName: String,
|
9
|
+
relationName: String,
|
10
|
+
}
|
11
|
+
|
12
|
+
beforeStreamRender(event) {
|
13
|
+
const { relationName } = event.target.dataset
|
14
|
+
if (event.target.action !== 'update-belongs-to' || this.relationNameValue !== relationName) {
|
15
|
+
return false
|
16
|
+
}
|
17
|
+
|
18
|
+
event.detail.render = (stream) => {
|
19
|
+
if (this.searchableValue) {
|
20
|
+
this.updateSearchable(stream)
|
21
|
+
} else {
|
22
|
+
this.updateNonSearchable(stream)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
updateSearchable(stream) {
|
28
|
+
// Update the id component
|
29
|
+
document.querySelector(`input[name="${this.targetNameValue}"][type="hidden"]`).value = stream.dataset.targetRecordId
|
30
|
+
// Update the label
|
31
|
+
document.querySelector(`input[name="${this.targetNameValue}"][type="text"]`).value = stream.dataset.targetResourceLabel
|
32
|
+
}
|
33
|
+
|
34
|
+
updateNonSearchable(stream) {
|
35
|
+
const select = this.selectorContext(stream).querySelector(`select[name="${this.targetNameValue}"]`)
|
36
|
+
const option = document.createElement('option')
|
37
|
+
option.value = stream.dataset.targetRecordId
|
38
|
+
option.text = stream.dataset.targetResourceLabel
|
39
|
+
option.selected = true
|
40
|
+
select.appendChild(option)
|
41
|
+
}
|
42
|
+
|
43
|
+
selectorContext(stream) {
|
44
|
+
// if polymorphic, search for the select in the correct sub-container
|
45
|
+
if (this.polymorphicValue) {
|
46
|
+
return document.querySelector(`[data-type="${stream.dataset.targetResourceClass}"]`)
|
47
|
+
}
|
48
|
+
|
49
|
+
return document
|
50
|
+
}
|
51
|
+
}
|
@@ -24,6 +24,7 @@ import MultipleSelectFilterController from './controllers/multiple_select_filter
|
|
24
24
|
import PerPageController from './controllers/per_page_controller'
|
25
25
|
import PreviewController from './controllers/preview_controller'
|
26
26
|
import ProgressBarFieldController from './controllers/fields/progress_bar_field_controller'
|
27
|
+
import ReloadBelongsToFieldController from './controllers/fields/reload_belongs_to_field_controller'
|
27
28
|
import ResourceEditController from './controllers/resource_edit_controller'
|
28
29
|
import ResourceIndexController from './controllers/resource_index_controller'
|
29
30
|
import ResourceShowController from './controllers/resource_show_controller'
|
@@ -76,6 +77,7 @@ application.register('date-field', DateFieldController)
|
|
76
77
|
application.register('easy-mde', EasyMdeController)
|
77
78
|
application.register('key-value', KeyValueController)
|
78
79
|
application.register('progress-bar-field', ProgressBarFieldController)
|
80
|
+
application.register('reload-belongs-to-field', ReloadBelongsToFieldController)
|
79
81
|
application.register('trix-field', TrixFieldController)
|
80
82
|
|
81
83
|
// Custom controllers
|
@@ -7,8 +7,7 @@
|
|
7
7
|
data-resource-id="<%= params[:id] %>"
|
8
8
|
class="hidden text-slate-800"
|
9
9
|
>
|
10
|
-
<%= form_with
|
11
|
-
scope: 'fields',
|
10
|
+
<%= form_with scope: 'fields',
|
12
11
|
url: Avo::Services::URIService.parse(@resource.records_path).append_paths("actions").to_s,
|
13
12
|
local: true,
|
14
13
|
data: @action.class.form_data_attributes do |form|
|
@@ -17,10 +16,11 @@
|
|
17
16
|
<% c.with_heading do %>
|
18
17
|
<%= @action.action_name %>
|
19
18
|
<% end %>
|
19
|
+
|
20
20
|
<div class="flex-1 flex">
|
21
21
|
<%= @action.get_message %>
|
22
22
|
</div>
|
23
|
-
<%= hidden_field_tag :action_id, @action.
|
23
|
+
<%= hidden_field_tag :action_id, @action.to_param %>
|
24
24
|
<%= form.hidden_field :avo_resource_ids, value: params[:id] || params[:resource_ids], 'data-action-target': 'resourceIds' %>
|
25
25
|
<%= form.hidden_field :avo_selected_query, 'data-action-target': 'selectedAllQuery' %>
|
26
26
|
<%= form.hidden_field :arguments, value: params[:arguments] %>
|
@@ -35,6 +35,7 @@
|
|
35
35
|
<% end %>
|
36
36
|
</div>
|
37
37
|
<% end %>
|
38
|
+
|
38
39
|
<% c.with_controls do %>
|
39
40
|
<%= a_button type: :button,
|
40
41
|
data: { action: 'click->modal#close' },
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%= turbo_frame_tag "new_via_belongs_to" do %>
|
2
|
+
<%= render(Avo::ModalComponent.new(width: :xl, body_class: "bg-application")) do |c| %>
|
3
|
+
<div class="pt-4 pb-8">
|
4
|
+
<%= render Avo::Views::ResourceEditComponent.new(
|
5
|
+
resource: @resource,
|
6
|
+
record: @record,
|
7
|
+
view: @view,
|
8
|
+
display_breadcrumbs: false
|
9
|
+
) %>
|
10
|
+
</div>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<%= turbo_stream.remove("new_via_belongs_to") %>
|
2
|
+
|
3
|
+
<turbo-stream action="update-belongs-to"
|
4
|
+
data-relation-name="<%= params[:via_relation] %>"
|
5
|
+
data-target-record-id="<%= @record.id %>"
|
6
|
+
data-target-resource-label="<%= @resource.record_title %>"
|
7
|
+
data-target-resource-class="<%= @record.class.name %>">
|
8
|
+
</turbo-stream>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<%= turbo_stream.replace(frame_id(@resource), template: "avo/base/new") %>
|
2
|
+
|
3
|
+
<%= turbo_stream.append "alerts" do %>
|
4
|
+
<%= render Avo::FlashAlertsComponent.new flashes: flash %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<% if @record.errors.any? %>
|
8
|
+
<%= turbo_stream.append("alerts") do %>
|
9
|
+
<% @record.errors.full_messages.each do |message| %>
|
10
|
+
<%= render Avo::AlertComponent.new :error, message %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
data/config/i18n-tasks.yml
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# The "main" locale.
|
4
4
|
base_locale: en
|
5
5
|
## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
|
6
|
-
locales: [en, fr, nb, nn, pt-BR, pt, ro, tr]
|
6
|
+
locales: [ar, en, es, fr, nb, nn, pt-BR, pt, ro, tr]
|
7
7
|
## Reporting locale, default: en. Available: en, ru.
|
8
8
|
# internal_locale: en
|
9
9
|
|
data/config/initializers/pagy.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "pagy/extras/trim"
|
2
|
+
require "pagy/extras/countless"
|
2
3
|
|
3
4
|
# For locales without native pagy i18n support
|
4
5
|
def pagy_locale_path(file_name)
|
@@ -7,6 +8,7 @@ end
|
|
7
8
|
|
8
9
|
extra_locales = [
|
9
10
|
{locale: "en"},
|
11
|
+
{locale: "es"},
|
10
12
|
{locale: "fr"},
|
11
13
|
{locale: "nb"},
|
12
14
|
{locale: "pt-BR"},
|
data/config/master.key
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2aeb23d82b909d9c6b5abb62f7058c2a
|
data/lib/avo/base_action.rb
CHANGED
@@ -7,22 +7,19 @@ module Avo
|
|
7
7
|
class_attribute :confirm_button_label
|
8
8
|
class_attribute :cancel_button_label
|
9
9
|
class_attribute :no_confirmation, default: false
|
10
|
-
class_attribute :record
|
11
|
-
class_attribute :view
|
12
|
-
class_attribute :user
|
13
|
-
class_attribute :resource
|
14
10
|
class_attribute :standalone, default: false
|
15
11
|
class_attribute :visible
|
16
12
|
class_attribute :may_download_file, default: false
|
17
13
|
class_attribute :turbo
|
14
|
+
class_attribute :authorize, default: true
|
18
15
|
|
16
|
+
attr_accessor :view
|
19
17
|
attr_accessor :response
|
20
18
|
attr_accessor :record
|
21
19
|
attr_accessor :resource
|
22
20
|
attr_accessor :user
|
23
21
|
attr_reader :arguments
|
24
22
|
|
25
|
-
delegate :view, to: :class
|
26
23
|
# TODO: find a differnet way to delegate this to the uninitialized Current variable
|
27
24
|
delegate :context, to: Avo::Current
|
28
25
|
def current_user
|
@@ -32,6 +29,7 @@ module Avo
|
|
32
29
|
delegate :view_context, to: Avo::Current
|
33
30
|
delegate :avo, to: :view_context
|
34
31
|
delegate :main_app, to: :view_context
|
32
|
+
delegate :to_param, to: :class
|
35
33
|
|
36
34
|
class << self
|
37
35
|
delegate :context, to: ::Avo::Current
|
@@ -53,6 +51,23 @@ module Avo
|
|
53
51
|
{}
|
54
52
|
end
|
55
53
|
end
|
54
|
+
|
55
|
+
def to_param
|
56
|
+
to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def link_arguments(resource:, **args)
|
60
|
+
path = Avo::Services::URIService.parse(resource.record.present? ? resource.record_path : resource.records_path)
|
61
|
+
.append_paths("actions")
|
62
|
+
.append_query(action_id: to_param, **args)
|
63
|
+
.to_s
|
64
|
+
|
65
|
+
data = {
|
66
|
+
turbo_frame: "actions_show",
|
67
|
+
}
|
68
|
+
|
69
|
+
[path, data]
|
70
|
+
end
|
56
71
|
end
|
57
72
|
|
58
73
|
def action_name
|
@@ -62,11 +77,15 @@ module Avo
|
|
62
77
|
end
|
63
78
|
|
64
79
|
def initialize(record: nil, resource: nil, user: nil, view: nil, arguments: {})
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
@arguments =
|
80
|
+
@record = record
|
81
|
+
@resource = resource
|
82
|
+
@user = user
|
83
|
+
@view = Avo::ViewInquirer.new(view)
|
84
|
+
@arguments = Avo::ExecutionContext.new(
|
85
|
+
target: arguments,
|
86
|
+
resource: resource,
|
87
|
+
record: record
|
88
|
+
).handle.with_indifferent_access
|
70
89
|
|
71
90
|
self.class.message ||= I18n.t("avo.are_you_sure_you_want_to_run_this_option")
|
72
91
|
self.class.confirm_button_label ||= I18n.t("avo.run")
|
@@ -84,26 +103,17 @@ module Avo
|
|
84
103
|
end
|
85
104
|
|
86
105
|
def get_message
|
87
|
-
Avo::ExecutionContext.new(target: self.class.message, record:
|
88
|
-
end
|
89
|
-
|
90
|
-
def get_attributes_for_action
|
91
|
-
get_fields.map do |field|
|
92
|
-
value = field.value || Avo::ExecutionContext.new(
|
93
|
-
target: field.default,
|
94
|
-
record: self.class.record,
|
95
|
-
resource: self.class.resource,
|
96
|
-
view: view
|
97
|
-
).handle
|
98
|
-
|
99
|
-
[field.id, value]
|
100
|
-
end.to_h
|
106
|
+
Avo::ExecutionContext.new(target: self.class.message, record: @record, resource: @resource).handle
|
101
107
|
end
|
102
108
|
|
103
109
|
def handle_action(**args)
|
104
110
|
processed_fields = if args[:fields].present?
|
105
111
|
# Fetching the field definitions and not the actual fields (get_fields) because they will break if the user uses a `visible` block and adds a condition using the `params` variable. The params are different in the show method and the handle method.
|
106
|
-
action_fields = get_field_definitions.map
|
112
|
+
action_fields = get_field_definitions.map do |field|
|
113
|
+
field.hydrate(resource: @resource)
|
114
|
+
|
115
|
+
[field.id, field]
|
116
|
+
end.to_h
|
107
117
|
|
108
118
|
# For some fields, like belongs_to, the id and database_id differ (user vs user_id).
|
109
119
|
# That's why we need to fetch the database_id for when we process the action.
|
@@ -134,6 +144,8 @@ module Avo
|
|
134
144
|
end
|
135
145
|
|
136
146
|
def visible_in_view(parent_resource: nil)
|
147
|
+
return false unless authorized?
|
148
|
+
|
137
149
|
if visible.blank?
|
138
150
|
# Hide on the :new view by default
|
139
151
|
return false if view.new?
|
@@ -147,16 +159,12 @@ module Avo
|
|
147
159
|
target: visible,
|
148
160
|
params: params,
|
149
161
|
parent_resource: parent_resource,
|
150
|
-
resource:
|
151
|
-
view:
|
162
|
+
resource: @resource,
|
163
|
+
view: @view,
|
152
164
|
arguments: arguments
|
153
165
|
).handle
|
154
166
|
end
|
155
167
|
|
156
|
-
def param_id
|
157
|
-
self.class.to_s
|
158
|
-
end
|
159
|
-
|
160
168
|
def succeed(text)
|
161
169
|
add_message text, :success
|
162
170
|
|
@@ -220,13 +228,11 @@ module Avo
|
|
220
228
|
self
|
221
229
|
end
|
222
230
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
self
|
231
|
+
def authorized?
|
232
|
+
Avo::ExecutionContext.new(
|
233
|
+
target: authorize,
|
234
|
+
action: self
|
235
|
+
).handle
|
230
236
|
end
|
231
237
|
|
232
238
|
private
|
data/lib/avo/base_resource.rb
CHANGED
@@ -10,6 +10,8 @@ module Avo
|
|
10
10
|
include Avo::Concerns::ModelClassConstantized
|
11
11
|
include Avo::Concerns::HasDescription
|
12
12
|
include Avo::Concerns::HasHelpers
|
13
|
+
include Avo::Concerns::Hydration
|
14
|
+
include Avo::Concerns::Pagination
|
13
15
|
|
14
16
|
# Avo::Current methods
|
15
17
|
delegate :context, to: Avo::Current
|
@@ -232,6 +234,7 @@ module Avo
|
|
232
234
|
delegate :underscore_name, to: :class
|
233
235
|
delegate :find_record, to: :class
|
234
236
|
delegate :model_key, to: :class
|
237
|
+
delegate :tab, to: :items_holder
|
235
238
|
|
236
239
|
def initialize(record: nil, view: nil, user: nil, params: nil)
|
237
240
|
@view = Avo::ViewInquirer.new(view) if view.present?
|
@@ -252,7 +255,7 @@ module Avo
|
|
252
255
|
end
|
253
256
|
|
254
257
|
def detect_fields
|
255
|
-
self.items_holder = Avo::Resources::Items::Holder.new
|
258
|
+
self.items_holder = Avo::Resources::Items::Holder.new(parent: self)
|
256
259
|
|
257
260
|
# Used in testing to replace items
|
258
261
|
if temporary_items.present?
|
@@ -321,14 +324,10 @@ module Avo
|
|
321
324
|
end
|
322
325
|
end
|
323
326
|
|
324
|
-
def hydrate(
|
325
|
-
|
326
|
-
@user = user if user.present?
|
327
|
-
@params = params if params.present?
|
328
|
-
|
329
|
-
if record.present?
|
330
|
-
@record = record
|
327
|
+
def hydrate(...)
|
328
|
+
super(...)
|
331
329
|
|
330
|
+
if @record.present?
|
332
331
|
hydrate_model_with_default_values if @view&.new?
|
333
332
|
end
|
334
333
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Avo
|
2
|
+
module Concerns
|
3
|
+
module BorrowItemsHolder
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
attr_reader :items_holder
|
8
|
+
end
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def parse_block(parent:, **args, &block)
|
12
|
+
# item = Avo::Resources::Items:: ...
|
13
|
+
item = new(parent: parent, **args)
|
14
|
+
|
15
|
+
# Borrow the current items holder to the parent (parent = Action || Resource, etc.)
|
16
|
+
# Save parent's items holder to restore it after the block is parsed
|
17
|
+
# This is useful when you execute parent's methods like `some_fields_method` inside some DSL block.
|
18
|
+
# When you do that, Docile will not find the method in the current object (item), but in the parent.
|
19
|
+
# So we need to temporarily replace the parent's items holder with the current one because the parent's methods
|
20
|
+
# will be executed in the parent's context.
|
21
|
+
# For more context: https://github.com/ms-ati/docile/issues/107
|
22
|
+
parent_item_holder = parent.items_holder
|
23
|
+
parent.items_holder = item.items_holder
|
24
|
+
|
25
|
+
dsl_evaluation = Docile.dsl_eval(item, &block).build
|
26
|
+
|
27
|
+
# Restore the parent's items holder
|
28
|
+
parent.items_holder = parent_item_holder
|
29
|
+
|
30
|
+
dsl_evaluation
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -47,6 +47,7 @@ module Avo
|
|
47
47
|
delegate :tool, to: :items_holder
|
48
48
|
delegate :heading, to: :items_holder
|
49
49
|
delegate :sidebar, to: :items_holder
|
50
|
+
delegate :main_panel, to: :items_holder
|
50
51
|
|
51
52
|
def items_holder
|
52
53
|
@items_holder || Avo::Resources::Items::Holder.new
|
@@ -57,7 +58,15 @@ module Avo
|
|
57
58
|
# end
|
58
59
|
|
59
60
|
def invalid_fields
|
60
|
-
items_holder.invalid_fields
|
61
|
+
invalid_fields = items_holder.invalid_fields
|
62
|
+
|
63
|
+
items_holder.items.each do |item|
|
64
|
+
if item.respond_to? :items
|
65
|
+
invalid_fields += item.invalid_fields
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
invalid_fields
|
61
70
|
end
|
62
71
|
|
63
72
|
def fields(**args)
|
@@ -101,6 +110,10 @@ module Avo
|
|
101
110
|
if item.is_row?
|
102
111
|
fields << extract_fields_from_items(tab)
|
103
112
|
end
|
113
|
+
|
114
|
+
if item.is_main_panel?
|
115
|
+
fields << extract_fields_from_items(item)
|
116
|
+
end
|
104
117
|
end
|
105
118
|
|
106
119
|
fields.flatten
|
@@ -194,65 +207,56 @@ module Avo
|
|
194
207
|
end
|
195
208
|
end
|
196
209
|
|
197
|
-
# Separates the fields that are in a panel and those that are just hanging out.
|
198
|
-
# Take the ones that aren't placed into a panel and add them to the "default" panel.
|
199
|
-
# This is to keep compatibility with the versions before 2.10 when you didn't have the ability to add fields to panels.
|
200
210
|
def get_items
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
panelfull_items << item
|
209
|
-
else
|
210
|
-
panelless_items << item
|
211
|
-
end
|
212
|
-
else
|
213
|
-
panelfull_items << item
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# Make sure all tabs panelfull_items are setted as inside tabs
|
218
|
-
panelfull_items.grep(Avo::Resources::Items::TabGroup).each do |tab_group|
|
219
|
-
tab_group.items.grep(Avo::Resources::Items::Tab).each do |tab|
|
220
|
-
tab.items.grep(Avo::Resources::Items::Panel).each do |panel|
|
221
|
-
set_target_to_top panel.items.grep(Avo::Fields::BelongsToField)
|
211
|
+
# Each group is built only by standalone items or items that have their own panel, keeping the items order
|
212
|
+
grouped_items = visible_items.slice_when do |prev, curr|
|
213
|
+
# Slice when the item type changes from standalone to panel or vice-versa
|
214
|
+
is_standalone?(prev) != is_standalone?(curr)
|
215
|
+
end.to_a.map do |group|
|
216
|
+
{ elements: group, is_standalone: is_standalone?(group.first) }
|
217
|
+
end
|
222
218
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
219
|
+
# Creates a main panel if it's missing and adds first standalone group of items if present
|
220
|
+
if items.none? { |item| item.is_main_panel? }
|
221
|
+
if (standalone_group = grouped_items.find { |group| group[:is_standalone] }).present?
|
222
|
+
calculated_main_panel = Avo::Resources::Items::MainPanel.new
|
223
|
+
hydrate_item calculated_main_panel
|
224
|
+
calculated_main_panel.items_holder.items = standalone_group[:elements]
|
225
|
+
grouped_items[grouped_items.index standalone_group] = { elements: [calculated_main_panel], is_standalone: false }
|
227
226
|
end
|
228
227
|
end
|
229
228
|
|
230
|
-
#
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
229
|
+
# For each standalone group, wrap items in a panel
|
230
|
+
grouped_items.select { |group| group[:is_standalone] }.each do |group|
|
231
|
+
calculated_panel = Avo::Resources::Items::Panel.new
|
232
|
+
calculated_panel.items_holder.items = group[:elements]
|
233
|
+
hydrate_item calculated_panel
|
234
|
+
group[:elements] = calculated_panel
|
235
|
+
end
|
237
236
|
|
238
|
-
|
239
|
-
[main_panel, *panelfull_items]
|
237
|
+
grouped_items.flat_map { |group| group[:elements] }
|
240
238
|
end
|
241
239
|
|
242
240
|
def items
|
243
|
-
|
244
|
-
items_holder.items
|
245
|
-
else
|
246
|
-
[]
|
247
|
-
end
|
241
|
+
items_holder&.items || []
|
248
242
|
end
|
249
243
|
|
250
244
|
def visible_items
|
251
245
|
items
|
252
246
|
.map do |item|
|
253
|
-
|
254
|
-
|
255
|
-
|
247
|
+
hydrate_item item
|
248
|
+
|
249
|
+
if item.is_a? Avo::Resources::Items::TabGroup
|
250
|
+
# Set the target to _top for all belongs_to fields in the tab group
|
251
|
+
item.items.grep(Avo::Resources::Items::Tab).each do |tab|
|
252
|
+
tab.items.grep(Avo::Resources::Items::Panel).each do |panel|
|
253
|
+
set_target_to_top panel.items.grep(Avo::Fields::BelongsToField)
|
254
|
+
|
255
|
+
panel.items.grep(Avo::Resources::Items::Row).each do |row|
|
256
|
+
set_target_to_top row.items.grep(Avo::Fields::BelongsToField)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
256
260
|
end
|
257
261
|
|
258
262
|
item
|
@@ -288,7 +292,9 @@ module Avo
|
|
288
292
|
true
|
289
293
|
end
|
290
294
|
end
|
291
|
-
.
|
295
|
+
.select do |item|
|
296
|
+
!item.is_a?(Avo::Resources::Items::Sidebar)
|
297
|
+
end.compact
|
292
298
|
end
|
293
299
|
|
294
300
|
def is_empty?
|
@@ -316,6 +322,18 @@ module Avo
|
|
316
322
|
|
317
323
|
fields
|
318
324
|
end
|
325
|
+
|
326
|
+
# Standalone items are fields that don't have their own panel
|
327
|
+
def is_standalone?(item)
|
328
|
+
item.is_field? && !item.has_own_panel?
|
329
|
+
end
|
330
|
+
|
331
|
+
def hydrate_item(item)
|
332
|
+
return unless item.respond_to? :hydrate
|
333
|
+
|
334
|
+
res = self.class.ancestors.include?(Avo::BaseResource) ? self : resource
|
335
|
+
item.hydrate(view: view, resource: res)
|
336
|
+
end
|
319
337
|
end
|
320
338
|
end
|
321
339
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Avo
|
2
|
+
module Concerns
|
3
|
+
module Hydration
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def hydrate(**args)
|
7
|
+
args.each do |key, value|
|
8
|
+
value = Avo::ViewInquirer.new value if key == :view
|
9
|
+
|
10
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
11
|
+
end
|
12
|
+
|
13
|
+
self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -2,20 +2,12 @@
|
|
2
2
|
module Avo
|
3
3
|
module Concerns
|
4
4
|
module IsResourceItem
|
5
|
+
include Avo::Concerns::Hydration
|
6
|
+
|
5
7
|
# These attributes are required to be hydrated in order to properly find the visible_items
|
6
8
|
attr_accessor :resource
|
7
9
|
attr_accessor :view
|
8
10
|
|
9
|
-
def hydrate(**args)
|
10
|
-
args.each do |key, value|
|
11
|
-
if respond_to?("#{key}=")
|
12
|
-
send("#{key}=", value)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
self
|
17
|
-
end
|
18
|
-
|
19
11
|
# Returns the final state of if an item is visible or not
|
20
12
|
# For items that have children it checks to see if it contains any visible children.
|
21
13
|
def visible?
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Avo
|
2
|
+
module Concerns
|
3
|
+
module Pagination
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Pagy::Backend
|
8
|
+
|
9
|
+
class_attribute :pagination, default: {}
|
10
|
+
|
11
|
+
PAGINATION_METHOD = {
|
12
|
+
default: :pagy,
|
13
|
+
countless: :pagy_countless,
|
14
|
+
} unless defined? PAGINATION_METHOD
|
15
|
+
|
16
|
+
PAGINATION_DEFAULTS = {
|
17
|
+
type: :default,
|
18
|
+
size: [1, 2, 2, 1],
|
19
|
+
} unless defined? PAGINATION_DEFAULTS
|
20
|
+
end
|
21
|
+
|
22
|
+
def pagination_type
|
23
|
+
@pagination_type ||= ActiveSupport::StringInquirer.new(pagination_hash[:type].to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
def apply_pagination(index_params:, query:)
|
27
|
+
extra_pagy_params = {}
|
28
|
+
|
29
|
+
# Reset open filters when a user navigates to a new page
|
30
|
+
extra_pagy_params[:keep_filters_panel_open] = if params[:keep_filters_panel_open] == "1"
|
31
|
+
"0"
|
32
|
+
end
|
33
|
+
|
34
|
+
send PAGINATION_METHOD[pagination_type.to_sym],
|
35
|
+
query,
|
36
|
+
items: index_params[:per_page],
|
37
|
+
link_extra: "data-turbo-frame=\"#{params[:turbo_frame]}\"",
|
38
|
+
params: extra_pagy_params,
|
39
|
+
size: pagination_hash[:size]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def pagination_hash
|
45
|
+
@pagination ||= PAGINATION_DEFAULTS.merge Avo::ExecutionContext.new(
|
46
|
+
target: pagination,
|
47
|
+
resource: self,
|
48
|
+
view: @view
|
49
|
+
).handle
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|