avo 2.9.2.pre1 → 2.10.0
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 +5 -7
- data/README.md +4 -0
- data/app/assets/stylesheets/css/buttons.css +4 -1
- data/app/components/avo/actions_component.rb +6 -2
- data/app/components/avo/base_component.rb +2 -0
- data/app/components/avo/button_component.rb +3 -1
- 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 +10 -25
- 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/fields/edit_component.rb +5 -0
- data/app/components/avo/fields/show_component.rb +1 -1
- data/app/components/avo/index/ordering/button_component.rb +2 -12
- 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/index/resource_table_component.html.erb +1 -1
- data/app/components/avo/index/table_row_component.html.erb +1 -1
- data/app/components/avo/item_switcher_component.html.erb +19 -0
- data/app/components/avo/item_switcher_component.rb +45 -0
- data/app/components/avo/panel_component.html.erb +23 -24
- data/app/components/avo/panel_component.rb +8 -5
- data/app/components/avo/tab_group_component.html.erb +53 -0
- data/app/components/avo/tab_group_component.rb +51 -0
- data/app/components/avo/tab_switcher_component.html.erb +21 -0
- data/app/components/avo/tab_switcher_component.rb +86 -0
- data/app/components/avo/views/resource_edit_component.html.erb +34 -56
- data/app/components/avo/views/resource_edit_component.rb +11 -1
- data/app/components/avo/views/resource_index_component.html.erb +1 -1
- data/app/components/avo/views/resource_index_component.rb +3 -3
- data/app/components/avo/views/resource_show_component.html.erb +58 -89
- data/app/components/avo/views/resource_show_component.rb +2 -2
- data/app/controllers/avo/actions_controller.rb +1 -1
- data/app/controllers/avo/application_controller.rb +20 -3
- data/app/helpers/avo/application_helper.rb +0 -6
- data/app/helpers/avo/url_helpers.rb +1 -1
- data/app/javascript/avo.js +5 -1
- data/app/javascript/js/controllers/fields/date_field_controller.js +25 -87
- data/app/javascript/js/controllers/loading_button_controller.js +25 -21
- data/app/javascript/js/controllers/tabs_controller.js +86 -0
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/base/index.html.erb +1 -1
- data/app/views/avo/base/show.html.erb +1 -1
- data/app/views/avo/cards/show.html.erb +1 -1
- data/app/views/avo/debug/index.html.erb +1 -1
- data/app/views/avo/home/_actions.html.erb +1 -1
- data/app/views/avo/home/_dashboards.html.erb +19 -0
- data/app/views/avo/home/_filters.html.erb +1 -1
- data/app/views/avo/home/_resources.html.erb +1 -1
- data/app/views/avo/home/failed_to_load.html.erb +1 -1
- data/app/views/avo/home/index.html.erb +14 -2
- data/app/views/avo/partials/_javascript.html.erb +1 -1
- data/app/views/avo/partials/_tabs_toggle.html.erb +20 -0
- data/app/views/avo/private/design.html.erb +1 -1
- data/config/routes.rb +1 -1
- data/db/migrate/20210421064037_add_color_to_teams.rb +5 -0
- data/db/migrate/20210423075924_add_progress_to_projects.rb +5 -0
- data/db/migrate/20210525143134_add_slug_to_users.rb +6 -0
- data/lib/avo/app.rb +11 -4
- data/lib/avo/base_action.rb +2 -19
- data/lib/avo/base_card.rb +1 -7
- data/lib/avo/base_resource.rb +1 -95
- data/lib/avo/base_resource_tool.rb +3 -1
- data/lib/avo/concerns/handles_field_args.rb +1 -1
- data/lib/avo/concerns/has_fields.rb +247 -50
- data/lib/avo/concerns/has_html_attributes.rb +1 -1
- data/lib/avo/concerns/is_resource_item.rb +36 -0
- data/lib/avo/concerns/model_class_constantized.rb +23 -0
- data/lib/avo/dashboards/base_dashboard.rb +1 -1
- data/lib/avo/dsl/field_parser.rb +83 -0
- data/lib/avo/fields/base_field.rb +19 -2
- data/lib/avo/fields/date_field.rb +2 -0
- data/lib/avo/fields/date_time_field.rb +9 -21
- data/lib/avo/fields/field_extensions/visible_in_different_views.rb +18 -1
- data/lib/avo/fields/has_base_field.rb +20 -1
- data/lib/avo/fields/has_one_field.rb +4 -1
- data/lib/avo/grid_collector.rb +6 -3
- data/lib/avo/items_holder.rb +68 -0
- data/lib/avo/licensing/h_q.rb +10 -0
- data/lib/avo/main_panel.rb +3 -0
- data/lib/avo/menu/builder.rb +8 -7
- data/lib/avo/panel.rb +25 -0
- data/lib/avo/panel_builder.rb +23 -0
- data/lib/avo/services/uri_service.rb +71 -0
- data/lib/avo/tab.rb +78 -0
- data/lib/avo/tab_builder.rb +25 -0
- data/lib/avo/tab_group.rb +40 -0
- data/lib/avo/tab_group_builder.rb +43 -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/lib/generators/avo/templates/resource/controller.tt +2 -0
- data/lib/generators/avo/templates/resource_tools/partial.tt +1 -1
- data/lib/generators/avo/templates/tool/view.tt +1 -1
- data/public/avo-assets/avo.css +27 -3
- data/public/avo-assets/avo.js +77 -77
- data/public/avo-assets/avo.js.map +3 -3
- metadata +28 -13
- data/app/assets/builds/action_cable.js +0 -2
- data/app/assets/builds/action_cable.js.map +0 -7
- data/app/assets/builds/application.js +0 -2
- data/app/assets/builds/application.js.map +0 -7
- data/app/assets/builds/avo.css +0 -9028
- data/app/assets/builds/avo.js +0 -512
- data/app/assets/builds/avo.js.map +0 -7
- data/app/assets/builds/avo_custom.js +0 -6
- data/app/assets/builds/avo_custom.js.map +0 -7
- data/lib/avo/concerns/has_tools.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75872d452d6a4918c39857b6dbc0075477f13a87fa42d6bba3cc1da5fa924be0
|
4
|
+
data.tar.gz: e3b24ecd70d7fc005a33fd4b4fd34b79fd1c228db6125be80574968f639677b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88ba446afaeca394f7231314e12e87d535c995fc92009f1f9010729a43a817680fd2290e8513bc643a5b4c446c3f4323b4db15f3f073b536b138ca8751a8d2dc
|
7
|
+
data.tar.gz: f3e4ca2474e497d4693c68cb899d724ce511bba93a55db8d2c9666658c3f46c4744d635ed489001e9a9ca4e3921979fc503329a64b8686e875e2072a3033ce84
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
avo (2.
|
4
|
+
avo (2.10.0)
|
5
5
|
active_link_to
|
6
6
|
addressable
|
7
7
|
breadcrumbs_on_rails
|
@@ -138,7 +138,7 @@ GEM
|
|
138
138
|
xpath (~> 3.2)
|
139
139
|
chartkick (4.2.0)
|
140
140
|
childprocess (4.1.0)
|
141
|
-
concurrent-ruby (1.1.
|
141
|
+
concurrent-ruby (1.1.10)
|
142
142
|
countries (4.2.1)
|
143
143
|
i18n_data (~> 0.15.0)
|
144
144
|
sixarm_ruby_unaccent (~> 1.1)
|
@@ -238,7 +238,7 @@ GEM
|
|
238
238
|
mini_magick (4.11.0)
|
239
239
|
mini_mime (1.1.2)
|
240
240
|
mini_portile2 (2.8.0)
|
241
|
-
minitest (5.
|
241
|
+
minitest (5.16.2)
|
242
242
|
msgpack (1.4.4)
|
243
243
|
multi_xml (0.6.0)
|
244
244
|
net-protocol (0.1.2)
|
@@ -252,8 +252,6 @@ GEM
|
|
252
252
|
nokogiri (1.13.4)
|
253
253
|
mini_portile2 (~> 2.8.0)
|
254
254
|
racc (~> 1.4)
|
255
|
-
nokogiri (1.13.4-x86_64-linux)
|
256
|
-
racc (~> 1.4)
|
257
255
|
orm_adapter (0.5.0)
|
258
256
|
pagy (5.10.1)
|
259
257
|
activesupport
|
@@ -393,7 +391,7 @@ GEM
|
|
393
391
|
tzinfo (2.0.4)
|
394
392
|
concurrent-ruby (~> 1.0)
|
395
393
|
unicode-display_width (2.1.0)
|
396
|
-
view_component (2.
|
394
|
+
view_component (2.57.1)
|
397
395
|
activesupport (>= 5.0.0, < 8.0)
|
398
396
|
method_source (~> 1.0)
|
399
397
|
warden (1.2.9)
|
@@ -416,7 +414,7 @@ GEM
|
|
416
414
|
websocket-extensions (0.1.5)
|
417
415
|
xpath (3.2.0)
|
418
416
|
nokogiri (~> 1.8)
|
419
|
-
zeitwerk (2.
|
417
|
+
zeitwerk (2.6.0)
|
420
418
|
|
421
419
|
PLATFORMS
|
422
420
|
ruby
|
data/README.md
CHANGED
@@ -69,6 +69,10 @@ Please read [CONTRIBUTING.MD](./CONTRIBUTING.MD)
|
|
69
69
|
|
70
70
|
Please read the [UPGRADE_GUIDE.MD](https://docs.avohq.io/2.0/upgrade.html)
|
71
71
|
|
72
|
+
# Release schedule
|
73
|
+
|
74
|
+
Please read the [RELEASE.MD](./RELEASE.MD)
|
75
|
+
|
72
76
|
# ✨ Contributors
|
73
77
|
|
74
78
|
<a href="https://github.com/avo-hq/avo/graphs/contributors">
|
@@ -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
|
@@ -26,9 +26,11 @@ class Avo::ButtonComponent < ViewComponent::Base
|
|
26
26
|
def args
|
27
27
|
if @args[:loading]
|
28
28
|
@args[:"data-controller"] = "loading-button"
|
29
|
+
@args[:"data-loading-button-confirmed-value"] = false
|
30
|
+
@args[:"data-action"] = "click->loading-button#attemptSubmit"
|
29
31
|
|
30
32
|
if @args[:confirm]
|
31
|
-
@args[:"data-
|
33
|
+
@args[:"data-loading-button-confirmation-message-value"] = @args.delete(:confirm)
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
@@ -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,31 +1,16 @@
|
|
1
1
|
<%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
|
2
|
-
|
3
|
-
|
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 %>
|
11
|
-
<%= datetime_field "fake_#{@field.id}", "fake",
|
12
|
-
value: @field.edit_formatted_value,
|
2
|
+
<div data-controller="date-field">
|
3
|
+
<%= @form.datetime_field @field.id,
|
13
4
|
class: classes("w-full"),
|
14
|
-
data: {
|
15
|
-
'date-field-target': 'fakeInput',
|
16
|
-
placeholder: @field.placeholder,
|
17
|
-
relative: @field.relative,
|
18
|
-
**@field.get_html(:data, view: view, element: :input)
|
19
|
-
},
|
20
|
-
disabled: @field.readonly,
|
21
|
-
placeholder: @field.placeholder,
|
22
|
-
style: @field.get_html(:style, view: view, element: :input)
|
23
|
-
%>
|
24
|
-
<%= @form.text_field @field.id,
|
25
|
-
value: @field.edit_formatted_value,
|
26
|
-
class: classes("w-full hidden"),
|
27
5
|
data: {
|
28
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,
|
29
14
|
placeholder: @field.placeholder,
|
30
15
|
relative: @field.relative,
|
31
16
|
**@field.get_html(:data, view: view, element: :input)
|
@@ -34,5 +19,5 @@
|
|
34
19
|
placeholder: @field.placeholder,
|
35
20
|
style: @field.get_html(:style, view: view, element: :input)
|
36
21
|
%>
|
37
|
-
|
22
|
+
</div>
|
38
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 %>
|
@@ -3,6 +3,7 @@
|
|
3
3
|
class Avo::Fields::EditComponent < ViewComponent::Base
|
4
4
|
include Avo::ResourcesHelper
|
5
5
|
|
6
|
+
attr_reader :field
|
6
7
|
attr_reader :view
|
7
8
|
|
8
9
|
def initialize(field: nil, resource: nil, index: 0, form: nil, displayed_in_modal: false)
|
@@ -17,4 +18,8 @@ class Avo::Fields::EditComponent < ViewComponent::Base
|
|
17
18
|
def classes(extra_classes = "")
|
18
19
|
helpers.input_classes("#{@field.get_html(:classes, view: view, element: :input)} #{extra_classes}", has_error: @field.model_errors.include?(@field.id))
|
19
20
|
end
|
21
|
+
|
22
|
+
def render?
|
23
|
+
!field.computed
|
24
|
+
end
|
20
25
|
end
|
@@ -19,19 +19,9 @@ class Avo::Index::Ordering::ButtonComponent < Avo::Index::Ordering::BaseComponen
|
|
19
19
|
|
20
20
|
def order_path(args)
|
21
21
|
if reflection.present?
|
22
|
-
|
22
|
+
Avo::App.view_context.avo.associations_order_path(reflection_parent_resource.route_key, params[:id], field.id, resource.model.id, **args)
|
23
23
|
else
|
24
|
-
|
24
|
+
Avo::App.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
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<div class="w-full ">
|
2
2
|
<table class="w-full px-4 bg-white" data-resource-name='<%= @resource.model_key %>' data-controller='item-select-all'>
|
3
|
-
<%= render partial: 'avo/partials/table_header', locals: {fields: @resource.get_fields(reflection: @reflection)} %>
|
3
|
+
<%= render partial: 'avo/partials/table_header', locals: {fields: @resource.get_fields(reflection: @reflection, only_root: true)} %>
|
4
4
|
<tbody class="divide-y">
|
5
5
|
<% @resources.each_with_index do |resource, index| %>
|
6
6
|
<% cache_if Avo.configuration.cache_resources_on_index_view, resource.cache_hash(@parent_model), expires_in: 1.day do %>
|
@@ -9,7 +9,7 @@
|
|
9
9
|
</div>
|
10
10
|
</td>
|
11
11
|
<% end %>
|
12
|
-
<% @resource.get_fields(reflection: @reflection).each_with_index do |field, index| %>
|
12
|
+
<% @resource.get_fields(reflection: @reflection, only_root: true).each_with_index do |field, index| %>
|
13
13
|
<%= render field.component_for_view(:index).new(field: field, resource: @resource, index: index, parent_model: @parent_model) %>
|
14
14
|
<% end %>
|
15
15
|
<td class="text-right whitespace-nowrap px-2">
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<% if item.is_tool? %>
|
2
|
+
<% if item&.partial.present? %>
|
3
|
+
<%= render item.partial, tool: item %>
|
4
|
+
<% end %>
|
5
|
+
<% elsif item.is_panel? %>
|
6
|
+
<%= render Avo::PanelComponent.new(title: item.name, description: item.description, index: index, view: view) do |c| %>
|
7
|
+
<% c.body do %>
|
8
|
+
<div class="divide-y">
|
9
|
+
<% item.items.each_with_index do |field, index| %>
|
10
|
+
<%= render field.hydrate(resource: @resource, model: @resource.model, user: resource.user, view: view).component_for_view(view).new(field: field, resource: @resource, index: index, form: form) %>
|
11
|
+
<% end %>
|
12
|
+
</div>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
<% elsif item.is_tab_group? %>
|
16
|
+
<%= render tab_group_component %>
|
17
|
+
<% elsif item.is_field? %>
|
18
|
+
<%= render field_component %>
|
19
|
+
<% end %>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Avo::ItemSwitcherComponent < Avo::BaseComponent
|
4
|
+
include Turbo::FramesHelper
|
5
|
+
|
6
|
+
attr_reader :resource
|
7
|
+
attr_reader :reflection
|
8
|
+
attr_reader :index
|
9
|
+
attr_reader :item
|
10
|
+
attr_reader :view
|
11
|
+
|
12
|
+
def initialize(resource: nil, reflection: nil, item: nil, index: nil, view: nil, form: nil)
|
13
|
+
@resource = resource
|
14
|
+
@reflection = reflection
|
15
|
+
@form = form
|
16
|
+
@index = index
|
17
|
+
@item = item
|
18
|
+
@view = view
|
19
|
+
end
|
20
|
+
|
21
|
+
def form
|
22
|
+
@form || nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def render?
|
26
|
+
# Stops rendering if the field should be hidden in reflections
|
27
|
+
if item.is_field?
|
28
|
+
return false if in_reflection? && item.hidden_in_reflection?
|
29
|
+
end
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def in_reflection?
|
35
|
+
@reflection.present?
|
36
|
+
end
|
37
|
+
|
38
|
+
def tab_group_component
|
39
|
+
Avo::TabGroupComponent.new resource: @resource, group: item.hydrate(view: view), index: index, params: params, form: form, view: view
|
40
|
+
end
|
41
|
+
|
42
|
+
def field_component
|
43
|
+
item.component_for_view(@view).new(field: item.hydrate(resource: @resource, view: @view, model: @resource.model), resource: @resource, index: index, form: form)
|
44
|
+
end
|
45
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
<%= content_tag :div, data: data_attributes, class: classes do %>
|
2
2
|
<% if render_header? %>
|
3
3
|
<div class="flex-1 flex flex-col xl:flex-row justify-between mb-4">
|
4
4
|
<div class="overflow-hidden flex flex-col">
|
@@ -7,18 +7,15 @@
|
|
7
7
|
<%= helpers.render_breadcrumbs(separator: helpers.svg('chevron-right', class: 'inline-block h-3 stroke-current relative top-[-1px] ml-1' )) if Avo.configuration.display_breadcrumbs %>
|
8
8
|
</div>
|
9
9
|
<% end %>
|
10
|
-
|
11
10
|
<div class="text-2xl tracking-normal font-semibold text-gray-800 truncate items-center flex flex-1" data-target="title">
|
12
|
-
<span><%= @
|
11
|
+
<span><%= @name %></span>
|
13
12
|
</div>
|
14
|
-
|
15
13
|
<% if description.present? %>
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
<div class="text-sm tracking-normal font-medium text-gray-600" data-target="description">
|
15
|
+
<%== description %>
|
16
|
+
</div>
|
19
17
|
<% end %>
|
20
18
|
</div>
|
21
|
-
|
22
19
|
<% if tools.present? %>
|
23
20
|
<div class="flex-1 w-full flex flex-col sm:flex-row xl:justify-end sm:items-end space-y-2 sm:space-y-0 sm:space-x-2 mt-4 xl:mt-0">
|
24
21
|
<%= tools %>
|
@@ -26,26 +23,28 @@
|
|
26
23
|
<% end %>
|
27
24
|
</div>
|
28
25
|
<% end %>
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
<%
|
26
|
+
<% if body? %>
|
27
|
+
<div class="relative <%= white_panel_classes %> <%= @body_classes %>">
|
28
|
+
<%= body %>
|
29
|
+
</div>
|
30
|
+
<% end %>
|
31
|
+
<% if bare_content? %>
|
32
|
+
<div class="relative">
|
33
|
+
<%= bare_content %>
|
34
|
+
</div>
|
35
|
+
<% end %>
|
36
|
+
<% if footer_tools? %>
|
39
37
|
<div class="<%= white_panel_classes %> p-4 flex-1 flex flex-col xl:flex-row justify-between mt-6">
|
40
38
|
<div class="flex-1 w-full flex flex-col sm:flex-row xl:justify-end sm:items-end space-y-2 sm:space-y-0 sm:space-x-2 mt-4 xl:mt-0">
|
41
39
|
<%= footer_tools %>
|
42
40
|
</div>
|
43
41
|
</div>
|
44
42
|
<% end %>
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
<% if footer? %>
|
44
|
+
<div class="flex justify-end w-full">
|
45
|
+
<div>
|
46
|
+
<%= footer %>
|
47
|
+
</div>
|
49
48
|
</div>
|
50
|
-
|
51
|
-
|
49
|
+
<% end %>
|
50
|
+
<% end %>
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
class Avo::PanelComponent < ViewComponent::Base
|
4
4
|
attr_reader :title
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :classes
|
5
7
|
|
6
8
|
renders_one :tools
|
7
9
|
renders_one :body
|
@@ -9,13 +11,17 @@ class Avo::PanelComponent < ViewComponent::Base
|
|
9
11
|
renders_one :footer_tools
|
10
12
|
renders_one :footer
|
11
13
|
|
12
|
-
def initialize(title: nil, description: nil, body_classes: nil, data: {}, display_breadcrumbs: false, index: nil)
|
14
|
+
def initialize(title: nil, name: nil, description: nil, body_classes: nil, data: {}, display_breadcrumbs: false, index: nil, classes: nil, view: nil)
|
15
|
+
# deprecating title in favor of name
|
13
16
|
@title = title
|
17
|
+
@name = name || title
|
14
18
|
@description = description
|
19
|
+
@classes = classes
|
15
20
|
@body_classes = body_classes
|
16
21
|
@data = data
|
17
22
|
@display_breadcrumbs = display_breadcrumbs
|
18
23
|
@index = index
|
24
|
+
@view = view
|
19
25
|
end
|
20
26
|
|
21
27
|
private
|
@@ -26,9 +32,6 @@ class Avo::PanelComponent < ViewComponent::Base
|
|
26
32
|
|
27
33
|
def data_attributes
|
28
34
|
@data.merge({"panel-index": @index})
|
29
|
-
.map do |key, value|
|
30
|
-
" data-#{key}=\"#{value}\""
|
31
|
-
end.join
|
32
35
|
end
|
33
36
|
|
34
37
|
def display_breadcrumbs?
|
@@ -42,7 +45,7 @@ class Avo::PanelComponent < ViewComponent::Base
|
|
42
45
|
end
|
43
46
|
|
44
47
|
def render_header?
|
45
|
-
@
|
48
|
+
@name.present? || description.present? || tools.present? || display_breadcrumbs?
|
46
49
|
end
|
47
50
|
|
48
51
|
def render_footer_tools?
|
@@ -0,0 +1,53 @@
|
|
1
|
+
<%= content_tag :div,
|
2
|
+
data: {
|
3
|
+
target: "tab-group",
|
4
|
+
index: index,
|
5
|
+
controller: "tabs",
|
6
|
+
tabs_view_value: view,
|
7
|
+
tabs_active_tab_value: active_tab_name
|
8
|
+
},
|
9
|
+
class: 'space-y-12' do %>
|
10
|
+
<% visible_tabs.each_with_index do |tab, index| %>
|
11
|
+
<%
|
12
|
+
args = {
|
13
|
+
# Hide the turbo frames that aren't in the current tab
|
14
|
+
# This way we can lazy load the un-selected tabs on the show view
|
15
|
+
class: "block #{'hidden' unless tab.name == active_tab_name}",
|
16
|
+
data: {
|
17
|
+
# Add a marker to know if we already loaded a turbo frame
|
18
|
+
loaded: tab.name == active_tab_name,
|
19
|
+
tabs_target: :tab,
|
20
|
+
tab_id: tab.name,
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
is_current_tab = active_tab_name.to_s == tab.name.to_s
|
25
|
+
|
26
|
+
# On edit screens we want to load each tab because we wnst the DOM to have the fields present on form submission.
|
27
|
+
# If you have a field which is in the second tab and it's required, the form submission will fail because the required field is not in view, and we don't want that.
|
28
|
+
# We also want to load the current tab
|
29
|
+
should_lazy_load = if @view.to_s.in?(['edit', 'new'])
|
30
|
+
false
|
31
|
+
else
|
32
|
+
!is_current_tab
|
33
|
+
end
|
34
|
+
|
35
|
+
if should_lazy_load
|
36
|
+
args[:src] = helpers.resource_path(resource: @resource, model: @resource.model, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id)
|
37
|
+
args[:loading] = :lazy
|
38
|
+
end
|
39
|
+
%>
|
40
|
+
<%= turbo_frame_tag tab.turbo_frame_id(parent: @group), **args do %>
|
41
|
+
<div class="border rounded-lg p-2 -mx-2 -my-2 lg:p-4 lg:-mx-4 lg:-my-4 space-y-4">
|
42
|
+
<%= render Avo::TabSwitcherComponent.new resource: @resource, current_tab: tab, group: group, active_tab_name: active_tab_name, view: view %>
|
43
|
+
<% if !should_lazy_load && !tab.empty? %>
|
44
|
+
<div class="space-y-12">
|
45
|
+
<% tab.visible_items.each do |item| %>
|
46
|
+
<%= render Avo::ItemSwitcherComponent.new resource: @resource, item: item, index: index, form: form, view: @view %>
|
47
|
+
<% end %>
|
48
|
+
</div>
|
49
|
+
<% end %>
|
50
|
+
</div>
|
51
|
+
<% end %>
|
52
|
+
<% end %>
|
53
|
+
<% end %>
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Avo::TabGroupComponent < Avo::BaseComponent
|
4
|
+
attr_reader :group
|
5
|
+
attr_reader :index
|
6
|
+
attr_reader :view
|
7
|
+
attr_reader :form
|
8
|
+
|
9
|
+
def initialize(resource:, group:, index:, form:, params:, view:)
|
10
|
+
@resource = resource
|
11
|
+
@group = group
|
12
|
+
@index = index
|
13
|
+
@form = form
|
14
|
+
@params = params
|
15
|
+
@view = view
|
16
|
+
|
17
|
+
@group.index = index
|
18
|
+
end
|
19
|
+
|
20
|
+
def render?
|
21
|
+
tabs_have_content? && visible_tabs.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def tabs_have_content?
|
25
|
+
visible_tabs.present?
|
26
|
+
end
|
27
|
+
|
28
|
+
def active_tab_name
|
29
|
+
params[:active_tab_name] || group.visible_items&.first&.name
|
30
|
+
end
|
31
|
+
|
32
|
+
def tabs
|
33
|
+
@group.items.map do |tab|
|
34
|
+
tab.hydrate(view: view)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def visible_tabs
|
39
|
+
tabs.select do |tab|
|
40
|
+
!tab.empty?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def active_tab
|
45
|
+
return if group.visible_items.blank?
|
46
|
+
|
47
|
+
group.visible_items.find do |tab|
|
48
|
+
tab.name.to_s == active_tab_name.to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|