avo 2.9.1.pre5 → 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 -5
- 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/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 +10 -0
- 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/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/lib/avo/app.rb +9 -2
- data/lib/avo/base_action.rb +2 -19
- data/lib/avo/base_card.rb +1 -7
- 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/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/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 +7 -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/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 +24 -14
- 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
@@ -1,77 +1,55 @@
|
|
1
1
|
<%= content_tag :div,
|
2
|
-
class: "space-y-12",
|
3
2
|
data: {
|
4
3
|
'model-id': @resource.model.id,
|
5
4
|
selected_resources_name: @resource.model_key,
|
6
5
|
selected_resources: [@resource.model.id],
|
7
6
|
**@resource.stimulus_data_attributes
|
8
7
|
} do %>
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
8
|
+
<%= form_with model: @resource.model,
|
9
|
+
scope: @resource.form_scope,
|
10
|
+
url: form_url,
|
11
|
+
method: form_method,
|
12
|
+
local: true,
|
13
|
+
html: {
|
14
|
+
novalidate: true
|
15
|
+
},
|
16
|
+
multipart: true do |form| %>
|
17
|
+
<%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
|
18
|
+
<%= render Avo::PanelComponent.new(title: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
|
19
|
+
<% c.tools do %>
|
20
|
+
<%= a_link back_path,
|
21
|
+
style: :text,
|
22
|
+
icon: 'arrow-left' do %>
|
23
|
+
<%= t('avo.cancel').capitalize %>
|
24
|
+
<% end %>
|
25
|
+
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
|
26
|
+
<% if can_see_the_save_button? %>
|
27
|
+
<%= a_button color: :primary,
|
26
28
|
style: :primary,
|
27
29
|
loading: true,
|
28
30
|
type: :submit,
|
29
31
|
icon: 'save' do %>
|
30
|
-
|
31
|
-
<% end %>
|
32
|
+
<%= t('avo.save').capitalize %>
|
32
33
|
<% end %>
|
33
34
|
<% end %>
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
<% if can_see_the_save_button? %>
|
40
|
-
<%= a_button color: :green, loading: true, type: :submit, icon: 'save' do %>
|
41
|
-
<%= t('avo.save').capitalize %>
|
42
|
-
<% end %>
|
43
|
-
<% end %>
|
35
|
+
<% end %>
|
36
|
+
<% if Avo.configuration.buttons_on_form_footers %>
|
37
|
+
<% c.footer_tools do %>
|
38
|
+
<%= a_link back_path, icon: 'arrow-left' do %>
|
39
|
+
<%= t('avo.cancel').capitalize %>
|
44
40
|
<% end %>
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
<% @resource.get_fields.each_with_index do |field, index| %>
|
49
|
-
<%= render field.component_for_view(:edit).new(field: field, resource: @resource, index: index, form: form) unless field.computed %>
|
41
|
+
<% if can_see_the_save_button? %>
|
42
|
+
<%= a_button color: :green, loading: true, type: :submit, icon: 'save' do %>
|
43
|
+
<%= t('avo.save').capitalize %>
|
50
44
|
<% end %>
|
51
|
-
|
45
|
+
<% end %>
|
52
46
|
<% end %>
|
53
47
|
<% end %>
|
54
48
|
<% end %>
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
<%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
|
60
|
-
<% end %>
|
61
|
-
<% end %>
|
62
|
-
<% if has_many_panels.present? %>
|
63
|
-
<% has_many_panels.each_with_index do |field, index| %>
|
64
|
-
<%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
|
65
|
-
<% end %>
|
66
|
-
<% end %>
|
67
|
-
<% if has_as_belongs_to_many_panels.present? %>
|
68
|
-
<% has_as_belongs_to_many_panels.each_with_index do |field, index| %>
|
69
|
-
<%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
|
70
|
-
<% end %>
|
71
|
-
<% end %>
|
72
|
-
<% if resource_tools.present? %>
|
73
|
-
<% resource_tools.each do |tool, index| %>
|
74
|
-
<%= render tool.partial, tool: tool %>
|
49
|
+
<%= content_tag :div, class: 'space-y-12' do %>
|
50
|
+
<% @resource.get_items.each_with_index do |item, index| %>
|
51
|
+
<% next if item.nil? %>
|
52
|
+
<%= render Avo::ItemSwitcherComponent.new resource: @resource, item: item, index: index + 1, view: @view, form: form %>
|
75
53
|
<% end %>
|
76
54
|
<% end %>
|
77
55
|
<% end %>
|
@@ -15,6 +15,10 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
|
|
15
15
|
split_panel_fields
|
16
16
|
end
|
17
17
|
|
18
|
+
def title
|
19
|
+
@resource.default_panel_name
|
20
|
+
end
|
21
|
+
|
18
22
|
def back_path
|
19
23
|
if via_resource?
|
20
24
|
helpers.resource_path(model: params[:via_resource_class].safe_constantize, resource: relation_resource, resource_id: params[:via_resource_id])
|
@@ -45,6 +49,12 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent
|
|
45
49
|
view == :edit
|
46
50
|
end
|
47
51
|
|
52
|
+
def form_method
|
53
|
+
return :put if is_edit?
|
54
|
+
|
55
|
+
:post
|
56
|
+
end
|
57
|
+
|
48
58
|
def form_url
|
49
59
|
if is_edit?
|
50
60
|
helpers.resource_path(
|
@@ -37,7 +37,7 @@
|
|
37
37
|
<%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.model_name.collection} %>
|
38
38
|
</div>
|
39
39
|
<% else %>
|
40
|
-
<%# Offset for the space-y-2 property when the
|
40
|
+
<%# Offset for the space-y-2 property when the search is missing %>
|
41
41
|
<div class="-mb-2"></div>
|
42
42
|
<% end %>
|
43
43
|
<% if @filters.present? || available_view_types.count > 1 %>
|
@@ -113,11 +113,11 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
-
helpers.new_resource_path(
|
116
|
+
helpers.new_resource_path(resource: @resource, **args)
|
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
|
|
@@ -1,105 +1,74 @@
|
|
1
1
|
<%= content_tag :div,
|
2
|
-
class: "space-y-12",
|
3
2
|
data: {
|
4
3
|
'model-id': @resource.model.id,
|
5
4
|
selected_resources_name: @resource.model_key,
|
6
5
|
selected_resources: [@resource.model.id],
|
7
6
|
**@resource.stimulus_data_attributes
|
8
7
|
} do %>
|
9
|
-
|
10
|
-
|
11
|
-
<%
|
12
|
-
<% if
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
} do %>
|
23
|
-
<%= t('avo.detach_item', item: title).capitalize %>
|
24
|
-
<% end %>
|
25
|
-
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
|
26
|
-
<% end %>
|
27
|
-
<% if can_see_the_edit_button? %>
|
28
|
-
<%= a_link edit_path,
|
29
|
-
color: :primary,
|
30
|
-
style: :primary,
|
31
|
-
icon: 'edit' do %>
|
32
|
-
<%= t('avo.edit').capitalize %>
|
33
|
-
<% end %>
|
34
|
-
<% end %>
|
35
|
-
<% else %>
|
36
|
-
<%= a_link back_path,
|
37
|
-
style: :text,
|
38
|
-
icon: 'arrow-left' do %>
|
39
|
-
<%= t('avo.go_back') %>
|
40
|
-
<% end %>
|
41
|
-
<% if can_see_the_destroy_button? %>
|
42
|
-
<%= a_button url: helpers.resource_path(model: @resource.model, resource: @resource),
|
43
|
-
method: :delete,
|
44
|
-
local: true,
|
45
|
-
style: :text,
|
46
|
-
title: t('avo.delete_item', item: @resource.model.model_name.name.downcase).capitalize,
|
47
|
-
loading: true,
|
48
|
-
confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
|
49
|
-
color: :red,
|
50
|
-
icon: 'trash',
|
51
|
-
form_class: 'flex flex-col sm:flex-row sm:inline-flex',
|
52
|
-
data: {
|
53
|
-
control: :destroy,
|
54
|
-
'resource-id': @resource.model.id,
|
55
|
-
'tippy': 'tooltip',
|
56
|
-
} do %>
|
57
|
-
<%= t('avo.delete').capitalize %>
|
58
|
-
<% end %>
|
59
|
-
<% end %>
|
60
|
-
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
|
61
|
-
<% if @resource.authorization.authorize_action(:edit, raise_exception: false) %>
|
62
|
-
<%= a_link edit_path,
|
63
|
-
color: :primary,
|
64
|
-
style: :primary,
|
65
|
-
icon: 'edit' do %>
|
66
|
-
<%= t('avo.edit').capitalize %>
|
67
|
-
<% end %>
|
68
|
-
<% end %>
|
8
|
+
<%= render Avo::PanelComponent.new(title: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: 0, data: { panel_id: "main" }) do |c| %>
|
9
|
+
<% c.tools do %>
|
10
|
+
<% if @reflection.present? && @resource.model.present? %>
|
11
|
+
<% if can_detach? %>
|
12
|
+
<%= a_button url: detach_path,
|
13
|
+
icon: 'detach',
|
14
|
+
method: :delete,
|
15
|
+
form_class: 'flex flex-col sm:flex-row sm:inline-flex',
|
16
|
+
style: :text,
|
17
|
+
data: {
|
18
|
+
confirm: "Are you sure you want to detach this #{title}."
|
19
|
+
} do %>
|
20
|
+
<%= t('avo.detach_item', item: title).capitalize %>
|
69
21
|
<% end %>
|
22
|
+
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
|
70
23
|
<% end %>
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
24
|
+
<% if can_see_the_edit_button? %>
|
25
|
+
<%= a_link edit_path,
|
26
|
+
color: :primary,
|
27
|
+
style: :primary,
|
28
|
+
icon: 'edit' do %>
|
29
|
+
<%= t('avo.edit').capitalize %>
|
30
|
+
<% end %>
|
31
|
+
<% end %>
|
32
|
+
<% else %>
|
33
|
+
<%= a_link back_path,
|
34
|
+
style: :text,
|
35
|
+
icon: 'arrow-left' do %>
|
36
|
+
<%= t('avo.go_back') %>
|
37
|
+
<% end %>
|
38
|
+
<% if can_see_the_destroy_button? %>
|
39
|
+
<%= a_button url: helpers.resource_path(model: @resource.model, resource: @resource),
|
40
|
+
method: :delete,
|
41
|
+
local: true,
|
42
|
+
style: :text,
|
43
|
+
loading: true,
|
44
|
+
confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
|
45
|
+
color: :red,
|
46
|
+
icon: 'trash',
|
47
|
+
form_class: 'flex flex-col sm:flex-row sm:inline-flex',
|
48
|
+
data: {
|
49
|
+
control: :destroy,
|
50
|
+
'resource-id': @resource.model.id,
|
51
|
+
'tippy': 'tooltip',
|
52
|
+
} do %>
|
53
|
+
<%= t('avo.delete').capitalize %>
|
54
|
+
<% end %>
|
55
|
+
<% end %>
|
56
|
+
<%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view %>
|
57
|
+
<% if @resource.authorization.authorize_action(:edit, raise_exception: false) %>
|
58
|
+
<%= a_link edit_path,
|
59
|
+
color: :primary,
|
60
|
+
style: :primary,
|
61
|
+
icon: 'edit' do %>
|
62
|
+
<%= t('avo.edit').capitalize %>
|
63
|
+
<% end %>
|
79
64
|
<% end %>
|
80
65
|
<% end %>
|
81
66
|
<% end %>
|
82
67
|
<% end %>
|
83
|
-
|
84
|
-
<%
|
85
|
-
<%
|
86
|
-
|
87
|
-
<% end %>
|
88
|
-
<% end %>
|
89
|
-
<% if has_many_panels.present? %>
|
90
|
-
<% has_many_panels.each_with_index do |field, index| %>
|
91
|
-
<%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
|
92
|
-
<% end %>
|
93
|
-
<% end %>
|
94
|
-
<% if has_as_belongs_to_many_panels.present? %>
|
95
|
-
<% has_as_belongs_to_many_panels.each_with_index do |field, index| %>
|
96
|
-
<%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
|
97
|
-
<% end %>
|
98
|
-
<% end %>
|
99
|
-
<% if resource_tools.present? %>
|
100
|
-
<% resource_tools.each do |tool, index| %>
|
101
|
-
<%= render tool.partial, tool: tool %>
|
102
|
-
<% end %>
|
68
|
+
<%= content_tag :div, class: 'space-y-12' do %>
|
69
|
+
<% @resource.get_items.each_with_index do |item, index| %>
|
70
|
+
<% next if item.nil? %>
|
71
|
+
<%= render Avo::ItemSwitcherComponent.new resource: @resource, reflection: @reflection, item: item, index: index + 1, view: @view %>
|
103
72
|
<% end %>
|
104
73
|
<% end %>
|
105
74
|
<% if should_display_invalid_fields_errors? %>
|
@@ -19,7 +19,7 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
|
|
19
19
|
return field.name if has_one_field?
|
20
20
|
reflection_resource.name
|
21
21
|
else
|
22
|
-
@resource.
|
22
|
+
@resource.default_panel_name
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -52,6 +52,6 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def has_one_field?
|
55
|
-
field.present? and field.
|
55
|
+
field.present? and field.instance_of? Avo::Fields::HasOneField
|
56
56
|
end
|
57
57
|
end
|
@@ -42,7 +42,7 @@ module Avo
|
|
42
42
|
model = @resource.class.find_scope.find params[:id]
|
43
43
|
end
|
44
44
|
|
45
|
-
@action = action_class.new(model: model, resource: resource, user: _current_user)
|
45
|
+
@action = action_class.new(model: model, resource: resource, user: _current_user, view: :edit)
|
46
46
|
end
|
47
47
|
|
48
48
|
def respond(response)
|
@@ -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, if: -> { params[:force_locale].present? }
|
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,26 @@ 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 and reverting at the end of the request.
|
298
|
+
def set_force_locale
|
299
|
+
initial_locale = I18n.locale.to_s.dup
|
300
|
+
I18n.locale = params[:force_locale]
|
301
|
+
yield
|
302
|
+
I18n.locale = initial_locale
|
303
|
+
end
|
304
|
+
|
305
|
+
def default_url_options
|
306
|
+
if params[:force_locale].present?
|
307
|
+
{ **super, force_locale: params[:force_locale] }
|
308
|
+
else
|
309
|
+
super
|
310
|
+
end
|
311
|
+
end
|
295
312
|
end
|
296
313
|
end
|
@@ -20,12 +20,6 @@ module Avo
|
|
20
20
|
render Avo::EmptyStateComponent.new **args
|
21
21
|
end
|
22
22
|
|
23
|
-
def turbo_frame_wrap(name, &block)
|
24
|
-
render Avo::TurboFrameWrapperComponent.new name do
|
25
|
-
capture(&block)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
23
|
def a_button(**args, &block)
|
30
24
|
render Avo::ButtonComponent.new(is_link: false, **args) do
|
31
25
|
capture(&block) if block_given?
|
@@ -35,7 +35,7 @@ module Avo
|
|
35
35
|
avo.send :"resources_#{resource.singular_route_key}_path", id, **args
|
36
36
|
end
|
37
37
|
|
38
|
-
def new_resource_path(
|
38
|
+
def new_resource_path(resource:, **args)
|
39
39
|
avo.send :"new_resources_#{resource.singular_route_key}_path", **args
|
40
40
|
end
|
41
41
|
|
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
|
|
@@ -7,33 +7,37 @@ export default class extends Controller {
|
|
7
7
|
<div class="double-bounce2"></div>
|
8
8
|
</div>`;
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
10
|
+
static values = {
|
11
|
+
confirmationMessage: String,
|
12
|
+
confirmed: Boolean,
|
13
|
+
}
|
14
|
+
|
15
|
+
attemptSubmit(e) {
|
16
|
+
// If the user has to confirm the action
|
17
|
+
if (this.confirmationMessageValue) {
|
18
|
+
this.confirmAndApply(e)
|
19
|
+
} else {
|
20
|
+
this.applyLoader()
|
21
|
+
}
|
22
|
+
|
23
|
+
return null
|
24
|
+
}
|
25
|
+
|
26
|
+
confirmAndApply(e) {
|
27
|
+
// Intervene only if not confirmed
|
28
|
+
if (!this.confirmedValue) {
|
29
|
+
e.preventDefault()
|
30
|
+
|
31
|
+
if (window.confirm(this.confirmationMessageValue)) {
|
24
32
|
this.applyLoader()
|
25
33
|
}
|
26
|
-
}
|
34
|
+
}
|
27
35
|
}
|
28
36
|
|
29
37
|
get button() {
|
30
38
|
return this.context.scope.element
|
31
39
|
}
|
32
40
|
|
33
|
-
get confirmationMessage() {
|
34
|
-
return this.context.scope.element.getAttribute('data-avo-confirm')
|
35
|
-
}
|
36
|
-
|
37
41
|
applyLoader() {
|
38
42
|
const { button } = this
|
39
43
|
|
@@ -50,10 +54,10 @@ export default class extends Controller {
|
|
50
54
|
}
|
51
55
|
|
52
56
|
markConfirmed() {
|
53
|
-
this.
|
57
|
+
this.confirmedValue = true
|
54
58
|
}
|
55
59
|
|
56
60
|
markUnconfirmed() {
|
57
|
-
this.
|
61
|
+
this.confirmedValue = false
|
58
62
|
}
|
59
63
|
}
|
@@ -0,0 +1,86 @@
|
|
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
|
+
view: String,
|
10
|
+
activeTab: String,
|
11
|
+
};
|
12
|
+
|
13
|
+
get currentTab() {
|
14
|
+
return this.tabTargets.find(
|
15
|
+
(element) => element.dataset.tabId === this.activeTabValue,
|
16
|
+
)
|
17
|
+
}
|
18
|
+
|
19
|
+
targetTab(id) {
|
20
|
+
return this.tabTargets.find((element) => element.dataset.tabId === id)
|
21
|
+
}
|
22
|
+
|
23
|
+
changeTab(e) {
|
24
|
+
e.preventDefault()
|
25
|
+
|
26
|
+
const { params } = e
|
27
|
+
const { id } = params
|
28
|
+
|
29
|
+
this.setTheTargetPanelHeight(id)
|
30
|
+
|
31
|
+
this.hideTabs()
|
32
|
+
this.showTab(id)
|
33
|
+
this.markTabLoaded(id)
|
34
|
+
|
35
|
+
this.activeTabValue = id
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Sets the target container height to the previous panel height so we don't get jerky tab changes.
|
40
|
+
*/
|
41
|
+
setTheTargetPanelHeight(id) {
|
42
|
+
// Ignore this on edit.
|
43
|
+
// All tabs are loaded beforehand, they have their own height, and the page won't jiggle when the user toggles between them.
|
44
|
+
if (this.viewValue === 'edit' || this.viewValue === 'new') {
|
45
|
+
return
|
46
|
+
}
|
47
|
+
|
48
|
+
// We don't need to add a height to this panel because it was loaded before
|
49
|
+
if (castBoolean(this.targetTab(id).dataset.loaded)) {
|
50
|
+
return
|
51
|
+
}
|
52
|
+
|
53
|
+
// Get the height of the active panel
|
54
|
+
const { height } = this.currentTab.getBoundingClientRect()
|
55
|
+
// Set it to the target panel
|
56
|
+
this.targetTab(id).style.height = `${height}px`
|
57
|
+
|
58
|
+
// Wait until the panel loaded it's content and then remove the forced height
|
59
|
+
const observer = new AttributeObserver(this.targetTab(id), 'busy', {
|
60
|
+
elementUnmatchedAttribute: () => {
|
61
|
+
// The content is not available in an instant so delay the height reset a bit.
|
62
|
+
setTimeout(() => {
|
63
|
+
this.targetTab(id).style.height = ''
|
64
|
+
}, 300)
|
65
|
+
if (observer) observer.stop()
|
66
|
+
},
|
67
|
+
})
|
68
|
+
observer.start()
|
69
|
+
}
|
70
|
+
|
71
|
+
markTabLoaded(id) {
|
72
|
+
this.targetTab(id).dataset.loaded = true
|
73
|
+
}
|
74
|
+
|
75
|
+
showTab(id) {
|
76
|
+
this.tabTargets.forEach((element) => {
|
77
|
+
if (element.dataset.tabId === id) {
|
78
|
+
element.classList.remove('hidden')
|
79
|
+
}
|
80
|
+
})
|
81
|
+
}
|
82
|
+
|
83
|
+
hideTabs() {
|
84
|
+
this.tabTargets.map((element) => element.classList.add('hidden'))
|
85
|
+
}
|
86
|
+
}
|
@@ -27,6 +27,7 @@ import SearchController from './controllers/search_controller'
|
|
27
27
|
import SelectController from './controllers/select_controller'
|
28
28
|
import SelectFilterController from './controllers/select_filter_controller'
|
29
29
|
import SimpleMdeController from './controllers/fields/simple_mde_controller'
|
30
|
+
import TabsController from './controllers/tabs_controller'
|
30
31
|
import TagsFieldController from './controllers/fields/tags_field_controller'
|
31
32
|
import TextFilterController from './controllers/text_filter_controller'
|
32
33
|
import TippyController from './controllers/tippy_controller'
|
@@ -55,6 +56,7 @@ application.register('resource-show', ResourceShowController)
|
|
55
56
|
application.register('search', SearchController)
|
56
57
|
application.register('select', SelectController)
|
57
58
|
application.register('select-filter', SelectFilterController)
|
59
|
+
application.register('tabs', TabsController)
|
58
60
|
application.register('tags-field', TagsFieldController)
|
59
61
|
application.register('text-filter', TextFilterController)
|
60
62
|
application.register('tippy', TippyController)
|
@@ -14,7 +14,7 @@
|
|
14
14
|
hq_payload = Avo::Licensing::HQ.new(request).payload
|
15
15
|
%>
|
16
16
|
<div class="flex flex-col">
|
17
|
-
<%= render Avo::PanelComponent.new
|
17
|
+
<%= render Avo::PanelComponent.new(title: 'Debug Avo', description: 'Use this page to debug the Avo license.') do |c| %>
|
18
18
|
<% c.tools do %>
|
19
19
|
<% end %>
|
20
20
|
<% c.bare_content do %>
|
@@ -11,6 +11,6 @@
|
|
11
11
|
</div>
|
12
12
|
</div>
|
13
13
|
|
14
|
-
<a href="https://docs.avohq.io/
|
14
|
+
<a href="https://docs.avohq.io/2.0/actions.html" target="_blank" title="Avo Actions documentation" class="text-bold cursor-pointer block mt-2">Actions in the docs 👉</a>
|
15
15
|
</div>
|
16
16
|
</div>
|