avo 2.9.1.pre7 → 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/base_component.rb +2 -0
- data/app/components/avo/button_component.rb +3 -1
- 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 +3 -5
- 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 +10 -0
- data/app/components/avo/views/resource_index_component.html.erb +1 -1
- data/app/components/avo/views/resource_index_component.rb +1 -1
- 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 +6 -10
- data/app/helpers/avo/application_helper.rb +0 -6
- data/app/helpers/avo/url_helpers.rb +1 -1
- data/app/javascript/js/controllers/loading_button_controller.js +25 -21
- data/app/javascript/js/controllers/tabs_controller.js +9 -3
- 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/_tabs_toggle.html.erb +20 -0
- data/app/views/avo/private/design.html.erb +1 -1
- data/lib/avo/base_action.rb +2 -19
- data/lib/avo/base_resource.rb +0 -94
- data/lib/avo/base_resource_tool.rb +3 -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/dsl/field_parser.rb +83 -0
- data/lib/avo/fields/base_field.rb +19 -2
- data/lib/avo/fields/field_extensions/visible_in_different_views.rb +18 -1
- data/lib/avo/fields/has_base_field.rb +18 -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 +6 -6
- data/lib/avo/panel.rb +25 -0
- data/lib/avo/panel_builder.rb +23 -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/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 +73 -73
- data/public/avo-assets/avo.js.map +3 -3
- metadata +22 -5
- 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">
|
@@ -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,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
|
@@ -1,8 +1,6 @@
|
|
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
|
-
|
6
4
|
attr_accessor :resource
|
7
5
|
attr_accessor :reflection
|
8
6
|
attr_accessor :direction
|
@@ -20,10 +18,10 @@ class Avo::Index::Ordering::ButtonComponent < Avo::Index::Ordering::BaseComponen
|
|
20
18
|
end
|
21
19
|
|
22
20
|
def order_path(args)
|
23
|
-
|
24
|
-
view_context.avo.associations_order_path(reflection_parent_resource.route_key, params[:id], field.id, resource.model.id, **args)
|
21
|
+
if reflection.present?
|
22
|
+
Avo::App.view_context.avo.associations_order_path(reflection_parent_resource.route_key, params[:id], field.id, resource.model.id, **args)
|
25
23
|
else
|
26
|
-
view_context.avo.resources_order_path(resource.route_key, resource.model.id, **args)
|
24
|
+
Avo::App.view_context.avo.resources_order_path(resource.route_key, resource.model.id, **args)
|
27
25
|
end
|
28
26
|
end
|
29
27
|
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
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<div class="flex" data-target="tab-switcher">
|
2
|
+
<div class="button-group">
|
3
|
+
<% visible_items.each do |tab| %>
|
4
|
+
<%= a_link tab_path(tab),
|
5
|
+
color: selected?(tab) ? :gray : :primary,
|
6
|
+
rounded: false,
|
7
|
+
size: :sm,
|
8
|
+
class: selected?(tab) ? ' bg-gray-100 border-gray-300' : ' z-20',
|
9
|
+
title: tab.description,
|
10
|
+
data: {
|
11
|
+
tippy: tab.description.present? ? 'tooltip' : '',
|
12
|
+
control: "view-type-toggle-#{tab.name}",
|
13
|
+
selected: selected?(tab),
|
14
|
+
action: 'click->tabs#changeTab',
|
15
|
+
tabs_id_param: tab.name
|
16
|
+
} do %>
|
17
|
+
<%= tab.name %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
20
|
+
</div>
|
21
|
+
</div>
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Avo::TabSwitcherComponent < Avo::BaseComponent
|
4
|
+
include Avo::UrlHelpers
|
5
|
+
include Avo::ApplicationHelper
|
6
|
+
|
7
|
+
attr_reader :active_tab_name
|
8
|
+
attr_reader :group
|
9
|
+
attr_reader :current_tab
|
10
|
+
attr_reader :tabs
|
11
|
+
attr_reader :view
|
12
|
+
|
13
|
+
def initialize(resource:, group:, current_tab:, active_tab_name:, view:)
|
14
|
+
@active_tab_name = active_tab_name
|
15
|
+
@resource = resource
|
16
|
+
@group = group
|
17
|
+
@current_tab = current_tab
|
18
|
+
@tabs = group.items
|
19
|
+
@view = view
|
20
|
+
end
|
21
|
+
|
22
|
+
def tab_path(tab)
|
23
|
+
if is_edit?
|
24
|
+
helpers.edit_resource_path(resource: @resource, model: @resource.model, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id)
|
25
|
+
elsif is_new?
|
26
|
+
helpers.new_resource_path(resource: @resource, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id)
|
27
|
+
else
|
28
|
+
helpers.resource_path(resource: @resource, model: @resource.model, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_edit?
|
33
|
+
@view == :edit
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_new?
|
37
|
+
@view == :new
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_initial_load?
|
41
|
+
params[:active_tab_name].blank?
|
42
|
+
end
|
43
|
+
|
44
|
+
# On initial load we want that each tab button to be the selected one.
|
45
|
+
# We do that so we don't get the wrongly selected item for a quick brief when first switching from one panel to another.
|
46
|
+
def selected?(tab)
|
47
|
+
if is_initial_load?
|
48
|
+
current_tab.name.to_s == tab.name.to_s
|
49
|
+
else
|
50
|
+
tab.name.to_s == active_tab_name.to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Goes through all items and removes the ones that are not supposed to be visible.
|
55
|
+
# Example below:
|
56
|
+
# tabs do
|
57
|
+
# field :comments, as: :has_many
|
58
|
+
# end
|
59
|
+
# Because the developer hasn't specified that it should be visible on edit views (with the show_on: :edit option),
|
60
|
+
# the field should not be visible in the item switcher either.
|
61
|
+
def visible_items
|
62
|
+
tabs.select do |item|
|
63
|
+
visible = true
|
64
|
+
|
65
|
+
if item.items.blank?
|
66
|
+
visible = false
|
67
|
+
end
|
68
|
+
|
69
|
+
first_item = item.items.first
|
70
|
+
if item.items.count == 1 && first_item.is_field? && first_item.has_own_panel? && !first_item.visible_on?(view)
|
71
|
+
# Return nil if tab contians a has_many type of fields and it's hidden in current view
|
72
|
+
visible = false
|
73
|
+
end
|
74
|
+
|
75
|
+
if item.respond_to?(:visible_on?)
|
76
|
+
visible = item.visible_on? view
|
77
|
+
end
|
78
|
+
|
79
|
+
if item.respond_to?(:visible?)
|
80
|
+
visible = item.visible?
|
81
|
+
end
|
82
|
+
|
83
|
+
visible
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|