avo 2.9.2.pre1 → 2.10.3.pre.1

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.

Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +69 -69
  3. data/README.md +4 -0
  4. data/app/assets/stylesheets/css/buttons.css +4 -1
  5. data/app/components/avo/actions_component.rb +6 -2
  6. data/app/components/avo/base_component.rb +2 -0
  7. data/app/components/avo/button_component.rb +3 -1
  8. data/app/components/avo/fields/common/key_value_component.html.erb +2 -2
  9. data/app/components/avo/fields/common/single_file_viewer_component.rb +1 -1
  10. data/app/components/avo/fields/country_field/edit_component.html.erb +9 -3
  11. data/app/components/avo/fields/date_field/edit_component.html.erb +26 -7
  12. data/app/components/avo/fields/date_field/index_component.html.erb +7 -1
  13. data/app/components/avo/fields/date_field/show_component.html.erb +7 -1
  14. data/app/components/avo/fields/date_time_field/edit_component.html.erb +1 -0
  15. data/app/components/avo/fields/edit_component.rb +5 -0
  16. data/app/components/avo/fields/file_field/edit_component.html.erb +1 -0
  17. data/app/components/avo/fields/files_field/edit_component.html.erb +1 -0
  18. data/app/components/avo/fields/select_field/edit_component.html.erb +14 -10
  19. data/app/components/avo/fields/show_component.rb +1 -1
  20. data/app/components/avo/index/ordering/button_component.rb +1 -15
  21. data/app/components/avo/index/resource_controls_component.html.erb +2 -2
  22. data/app/components/avo/index/resource_controls_component.rb +5 -1
  23. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  24. data/app/components/avo/index/table_row_component.html.erb +1 -1
  25. data/app/components/avo/item_switcher_component.html.erb +19 -0
  26. data/app/components/avo/item_switcher_component.rb +45 -0
  27. data/app/components/avo/panel_component.html.erb +23 -24
  28. data/app/components/avo/panel_component.rb +8 -5
  29. data/app/components/avo/tab_group_component.html.erb +53 -0
  30. data/app/components/avo/tab_group_component.rb +51 -0
  31. data/app/components/avo/tab_switcher_component.html.erb +21 -0
  32. data/app/components/avo/tab_switcher_component.rb +86 -0
  33. data/app/components/avo/views/resource_edit_component.html.erb +34 -56
  34. data/app/components/avo/views/resource_edit_component.rb +11 -1
  35. data/app/components/avo/views/resource_index_component.html.erb +2 -2
  36. data/app/components/avo/views/resource_index_component.rb +3 -3
  37. data/app/components/avo/views/resource_show_component.html.erb +58 -89
  38. data/app/components/avo/views/resource_show_component.rb +2 -2
  39. data/app/controllers/avo/actions_controller.rb +1 -1
  40. data/app/controllers/avo/application_controller.rb +33 -10
  41. data/app/controllers/avo/base_controller.rb +11 -3
  42. data/app/controllers/avo/reorder_controller.rb +25 -0
  43. data/app/helpers/avo/application_helper.rb +6 -6
  44. data/app/helpers/avo/url_helpers.rb +1 -5
  45. data/app/javascript/avo.js +5 -1
  46. data/app/javascript/js/controllers/fields/date_field_controller.js +15 -3
  47. data/app/javascript/js/controllers/loading_button_controller.js +25 -21
  48. data/app/javascript/js/controllers/search_controller.js +3 -0
  49. data/app/javascript/js/controllers/tabs_controller.js +86 -0
  50. data/app/javascript/js/controllers.js +2 -0
  51. data/app/views/avo/base/index.html.erb +1 -1
  52. data/app/views/avo/base/show.html.erb +1 -1
  53. data/app/views/avo/cards/show.html.erb +1 -1
  54. data/app/views/avo/debug/index.html.erb +1 -1
  55. data/app/views/avo/home/_actions.html.erb +1 -1
  56. data/app/views/avo/home/_dashboards.html.erb +19 -0
  57. data/app/views/avo/home/_filters.html.erb +1 -1
  58. data/app/views/avo/home/_resources.html.erb +1 -1
  59. data/app/views/avo/home/failed_to_load.html.erb +1 -1
  60. data/app/views/avo/home/index.html.erb +14 -2
  61. data/app/views/avo/partials/_javascript.html.erb +1 -1
  62. data/app/views/avo/partials/_tabs_toggle.html.erb +20 -0
  63. data/app/views/avo/private/design.html.erb +1 -1
  64. data/config/routes.rb +5 -4
  65. data/db/factories.rb +1 -0
  66. data/lib/avo/app.rb +12 -7
  67. data/lib/avo/base_action.rb +2 -19
  68. data/lib/avo/base_card.rb +1 -7
  69. data/lib/avo/base_resource.rb +6 -98
  70. data/lib/avo/base_resource_tool.rb +3 -1
  71. data/lib/avo/concerns/has_fields.rb +249 -50
  72. data/lib/avo/concerns/has_html_attributes.rb +1 -1
  73. data/lib/avo/concerns/is_resource_item.rb +36 -0
  74. data/lib/avo/concerns/model_class_constantized.rb +23 -0
  75. data/lib/avo/configuration.rb +2 -12
  76. data/lib/avo/dashboards/base_dashboard.rb +1 -1
  77. data/lib/avo/dsl/field_parser.rb +83 -0
  78. data/lib/avo/fields/base_field.rb +21 -2
  79. data/lib/avo/fields/country_field.rb +2 -0
  80. data/lib/avo/fields/date_field.rb +13 -9
  81. data/lib/avo/fields/field_extensions/has_include_blank.rb +17 -0
  82. data/lib/avo/fields/field_extensions/visible_in_different_views.rb +18 -1
  83. data/lib/avo/fields/file_field.rb +2 -0
  84. data/lib/avo/fields/files_field.rb +2 -0
  85. data/lib/avo/fields/has_base_field.rb +20 -1
  86. data/lib/avo/fields/has_one_field.rb +4 -1
  87. data/lib/avo/fields/key_value_field.rb +4 -4
  88. data/lib/avo/fields/select_field.rb +2 -0
  89. data/lib/avo/grid_collector.rb +6 -3
  90. data/lib/avo/items_holder.rb +68 -0
  91. data/lib/avo/licensing/h_q.rb +12 -0
  92. data/lib/avo/main_panel.rb +3 -0
  93. data/lib/avo/menu/builder.rb +8 -7
  94. data/lib/avo/panel.rb +25 -0
  95. data/lib/avo/panel_builder.rb +23 -0
  96. data/lib/avo/services/uri_service.rb +75 -0
  97. data/lib/avo/tab.rb +78 -0
  98. data/lib/avo/tab_builder.rb +25 -0
  99. data/lib/avo/tab_group.rb +40 -0
  100. data/lib/avo/tab_group_builder.rb +43 -0
  101. data/lib/avo/version.rb +1 -1
  102. data/lib/avo.rb +1 -0
  103. data/lib/generators/avo/action_generator.rb +3 -2
  104. data/lib/generators/avo/base_generator.rb +14 -0
  105. data/lib/generators/avo/card/chartkick_generator.rb +18 -0
  106. data/lib/generators/avo/card/metric_generator.rb +18 -0
  107. data/lib/generators/avo/card/partial_generator.rb +19 -0
  108. data/lib/generators/avo/controller_generator.rb +9 -3
  109. data/lib/generators/avo/dashboard_generator.rb +2 -2
  110. data/lib/generators/avo/eject_generator.rb +2 -3
  111. data/lib/generators/avo/field_generator.rb +2 -2
  112. data/lib/generators/avo/filter_generator.rb +3 -2
  113. data/lib/generators/avo/install_generator.rb +2 -2
  114. data/lib/generators/avo/locales_generator.rb +2 -2
  115. data/lib/generators/avo/named_base_generator.rb +14 -0
  116. data/lib/generators/avo/resource_generator.rb +2 -2
  117. data/lib/generators/avo/resource_tool_generator.rb +4 -4
  118. data/lib/generators/avo/templates/locales/avo.fr.yml +115 -0
  119. data/lib/generators/avo/templates/resource/controller.tt +2 -0
  120. data/lib/generators/avo/templates/resource_tools/partial.tt +1 -1
  121. data/lib/generators/avo/templates/tool/view.tt +1 -1
  122. data/lib/generators/avo/tool_generator.rb +4 -4
  123. data/lib/generators/avo/version_generator.rb +23 -0
  124. data/public/avo-assets/avo.css +31 -3
  125. data/public/avo-assets/avo.js +72 -72
  126. data/public/avo-assets/avo.js.map +3 -3
  127. metadata +32 -16
  128. data/app/assets/builds/action_cable.js +0 -2
  129. data/app/assets/builds/action_cable.js.map +0 -7
  130. data/app/assets/builds/application.js +0 -2
  131. data/app/assets/builds/application.js.map +0 -7
  132. data/app/assets/builds/avo.css +0 -9028
  133. data/app/assets/builds/avo.js +0 -512
  134. data/app/assets/builds/avo.js.map +0 -7
  135. data/app/assets/builds/avo_custom.js +0 -6
  136. data/app/assets/builds/avo_custom.js.map +0 -7
  137. data/lib/avo/concerns/has_tools.rb +0 -47
  138. data/lib/avo/fields/currency_field.rb +0 -15
  139. data/lib/generators/avo/chartkick_card_generator.rb +0 -16
  140. data/lib/generators/avo/metric_card_generator.rb +0 -16
  141. data/lib/generators/avo/partial_card_generator.rb +0 -17
@@ -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
- <% @resource.panels.each_with_index do |resource_panel, index| %>
10
- <%= render Avo::PanelComponent.new(title: title, description: @resource.resource_description, display_breadcrumbs: @reflection.blank?, index: index) do |c| %>
11
- <% c.tools do %>
12
- <% if resource_panel[:name] == @resource.default_panel_name %>
13
- <% if @reflection.present? && @resource.model.present? %>
14
- <% if can_detach? %>
15
- <%= a_button url: detach_path,
16
- icon: 'detach',
17
- method: :delete,
18
- form_class: 'flex flex-col sm:flex-row sm:inline-flex',
19
- style: :text,
20
- data: {
21
- confirm: "Are you sure you want to detach this #{title}."
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
- <% end %>
72
- <% c.body do %>
73
- <% if fields_by_panel[resource_panel[:name]].present? %>
74
- <div class="divide-y">
75
- <% fields_by_panel[resource_panel[:name]].each_with_index do |field, index| %>
76
- <%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
77
- <% end %>
78
- </div>
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
- <% if @reflection.blank? %>
84
- <% if has_one_panels.present? %>
85
- <% has_one_panels.each_with_index do |field, index| %>
86
- <%= render field.component_for_view(:show).new(field: field, resource: @resource, index: index) %>
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.panels.first[:name]
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.class == Avo::Fields::HasOneField
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)
@@ -11,9 +11,11 @@ module Avo
11
11
  include Avo::UrlHelpers
12
12
 
13
13
  protect_from_forgery with: :exception
14
+ around_action :set_avo_locale
15
+ around_action :set_force_locale, if: -> { params[:force_locale].present? }
16
+ before_action :set_default_locale, if: -> { params[:set_locale].present? }
14
17
  before_action :init_app
15
18
  before_action :check_avo_license
16
- before_action :set_locale
17
19
  before_action :set_authorization
18
20
  before_action :_authenticate!
19
21
  before_action :set_container_classes
@@ -27,7 +29,7 @@ module Avo
27
29
  add_flash_types :info, :warning, :success, :error
28
30
 
29
31
  def init_app
30
- Avo::App.init request: request, context: context, root_path: avo.root_path.delete_suffix("/"), current_user: _current_user, view_context: view_context, params: params
32
+ Avo::App.init request: request, context: context, current_user: _current_user, view_context: view_context, params: params
31
33
 
32
34
  @license = Avo::App.license
33
35
  end
@@ -260,23 +262,23 @@ module Avo
260
262
  end
261
263
 
262
264
  def on_root_path
263
- [Avo::App.root_path, "#{Avo::App.root_path}/"].include?(request.original_fullpath)
265
+ [Avo.configuration.root_path, "#{Avo.configuration.root_path}/"].include?(request.original_fullpath)
264
266
  end
265
267
 
266
268
  def on_resources_path
267
- request.original_url.match?(/.*#{Avo::App.root_path}\/resources\/.*/)
269
+ request.original_url.match?(/.*#{Avo.configuration.root_path}\/resources\/.*/)
268
270
  end
269
271
 
270
272
  def on_api_path
271
- request.original_url.match?(/.*#{Avo::App.root_path}\/avo_api\/.*/)
273
+ request.original_url.match?(/.*#{Avo.configuration.root_path}\/avo_api\/.*/)
272
274
  end
273
275
 
274
276
  def on_dashboards_path
275
- request.original_url.match?(/.*#{Avo::App.root_path}\/dashboards\/.*/)
277
+ request.original_url.match?(/.*#{Avo.configuration.root_path}\/dashboards\/.*/)
276
278
  end
277
279
 
278
280
  def on_debug_path
279
- request.original_url.match?(/.*#{Avo::App.root_path}\/avo_private\/debug.*/)
281
+ request.original_url.match?(/.*#{Avo.configuration.root_path}\/avo_private\/debug.*/)
280
282
  end
281
283
 
282
284
  def on_custom_tool_page
@@ -287,10 +289,31 @@ module Avo
287
289
  @resource.form_scope
288
290
  end
289
291
 
290
- def set_locale
291
- I18n.locale = params[:set_locale] || I18n.default_locale
292
+ # Sets the locale set in avo.rb initializer
293
+ def set_avo_locale(&action)
294
+ locale = Avo.configuration.locale || I18n.default_locale
295
+ I18n.with_locale(locale, &action)
296
+ end
297
+
298
+ # Enable the user to change the default locale with the `?set_locale=pt-BR` param
299
+ def set_default_locale
300
+ locale = params[:set_locale] || I18n.default_locale
301
+
302
+ I18n.default_locale = locale
303
+ end
292
304
 
293
- I18n.default_locale = I18n.locale
305
+ # Temporary set the locale and reverting at the end of the request.
306
+ def set_force_locale(&action)
307
+ locale = params[:force_locale] || I18n.default_locale
308
+ I18n.with_locale(locale, &action)
309
+ end
310
+
311
+ def default_url_options
312
+ if params[:force_locale].present?
313
+ { **super, force_locale: params[:force_locale] }
314
+ else
315
+ super
316
+ end
294
317
  end
295
318
  end
296
319
  end
@@ -154,7 +154,7 @@ module Avo
154
154
 
155
155
  respond_to do |format|
156
156
  if saved
157
- format.html { redirect_to after_create_path, notice: "#{@model.class.name} #{t("avo.was_successfully_created")}." }
157
+ format.html { redirect_to after_create_path, notice: "#{@resource.name} #{t("avo.was_successfully_created")}." }
158
158
  else
159
159
  flash.now[:error] = t "avo.you_missed_something_check_form"
160
160
  format.html { render :new, status: :unprocessable_entity }
@@ -173,7 +173,7 @@ module Avo
173
173
 
174
174
  respond_to do |format|
175
175
  if saved
176
- format.html { redirect_to after_update_path, notice: "#{@model.class.name} #{t("avo.was_successfully_updated")}." }
176
+ format.html { redirect_to after_update_path, notice: "#{@resource.name} #{t("avo.was_successfully_updated")}." }
177
177
  else
178
178
  flash.now[:error] = t "avo.you_missed_something_check_form"
179
179
  format.html { render :edit, status: :unprocessable_entity }
@@ -370,6 +370,8 @@ module Avo
370
370
  def set_edit_title_and_breadcrumbs
371
371
  @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
372
372
  @page_title = @resource.default_panel_name.to_s
373
+
374
+ last_crumb_args = {}
373
375
  # If we're accessing this resource via another resource add the parent to the breadcrumbs.
374
376
  if params[:via_resource_class].present? && params[:via_resource_id].present?
375
377
  via_resource = Avo::App.get_resource_by_model_name params[:via_resource_class]
@@ -378,11 +380,17 @@ module Avo
378
380
 
379
381
  add_breadcrumb via_resource.plural_name, resources_path(resource: @resource)
380
382
  add_breadcrumb via_resource.model_title, resource_path(model: via_model, resource: via_resource)
383
+ puts ["via_resource.model_title->", via_resource.model_title].inspect
384
+
385
+ last_crumb_args = {
386
+ via_resource_class: params[:via_resource_class],
387
+ via_resource_id: params[:via_resource_id]
388
+ }
381
389
  else
382
390
  add_breadcrumb @resource.plural_name.humanize, resources_path(resource: @resource)
383
391
  end
384
392
 
385
- add_breadcrumb @resource.model_title, resource_path(model: @resource.model, resource: @resource)
393
+ add_breadcrumb @resource.model_title, resource_path(model: @resource.model, resource: @resource, **last_crumb_args)
386
394
  add_breadcrumb t("avo.edit").humanize
387
395
  end
388
396
 
@@ -0,0 +1,25 @@
1
+ require_dependency "avo/application_controller"
2
+
3
+ module Avo
4
+ class ReorderController < ApplicationController
5
+ before_action :set_resource_name
6
+ before_action :set_resource
7
+ before_action :hydrate_resource
8
+ before_action :set_model
9
+
10
+ def order
11
+ direction = params[:direction].to_sym
12
+
13
+ if direction.present?
14
+ @resource
15
+ .hydrate(model: @model, params: params)
16
+ .ordering_host
17
+ .order direction
18
+ end
19
+
20
+ respond_to do |format|
21
+ format.html { redirect_to params[:referrer] || resources_path(resource: @resource) }
22
+ end
23
+ end
24
+ end
25
+ 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?
@@ -138,5 +132,11 @@ module Avo
138
132
  model.class
139
133
  end
140
134
  end
135
+
136
+ def root_path_without_url
137
+ Avo::App.root_path.to_s.delete_prefix(request.base_url.to_s).delete_suffix "/"
138
+ rescue
139
+ Avo.configuration.root_path
140
+ end
141
141
  end
142
142
  end
@@ -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(model:, resource:, **args)
38
+ def new_resource_path(resource:, **args)
39
39
  avo.send :"new_resources_#{resource.singular_route_key}_path", **args
40
40
  end
41
41
 
@@ -76,9 +76,5 @@ module Avo
76
76
 
77
77
  avo.resources_associations_index_path(parent_model.model_name.route_key, record.id, **existing_params, **args)
78
78
  end
79
-
80
- def order_up_resource_path(model:, resource:, **args)
81
- avo.send :"order_up_resources_#{resource.singular_route_key}_path", model, **args
82
- end
83
79
  end
84
80
  end
@@ -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
- e.target.src = `${window.Avo.configuration.root_path}/failed_to_load?turbo_frame=${id}&src=${src}`
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
 
@@ -18,6 +18,7 @@ export default class extends Controller {
18
18
  pickerFormat: String,
19
19
  firstDayOfWeek: Number,
20
20
  time24Hr: Boolean,
21
+ disableMobile: Boolean,
21
22
  }
22
23
 
23
24
  get browserZone() {
@@ -86,6 +87,9 @@ export default class extends Controller {
86
87
  // Set the format of the displayed input field.
87
88
  options.altFormat = this.pickerFormatValue
88
89
 
90
+ // Disable native input in mobile browsers
91
+ options.disableMobile = this.disableMobileValue
92
+
89
93
  // Set first day of the week.
90
94
  options.locale.firstDayOfWeek = this.firstDayOfWeekValue
91
95
 
@@ -99,7 +103,7 @@ export default class extends Controller {
99
103
 
100
104
  options.dateFormat = 'Y-m-d H:i:S'
101
105
  } else {
102
- // Because the browser treats the date like a timestamp and updates it ot 00:00 hour, when on a western timezone the date will be converted with one day offset.
106
+ // Because the browser treats the date like a timestamp and updates it at 00:00 hour, when on a western timezone the date will be converted with one day offset.
103
107
  // Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
104
108
  options.defaultDate = universalTimestamp(this.initialValue)
105
109
  }
@@ -111,12 +115,20 @@ export default class extends Controller {
111
115
 
112
116
  onChange(selectedDates) {
113
117
  let time
118
+ let args = {}
114
119
 
115
120
  if (this.timezoneValue) {
116
- time = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: true })
121
+ args = { keepLocalTime: true }
122
+ } else {
123
+ args = { keepLocalTime: false }
124
+ }
125
+
126
+ if (this.enableTimeValue) {
127
+ time = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', args)
117
128
  } else {
118
- time = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: false })
129
+ time = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: true })
119
130
  }
131
+
120
132
  this.updateRealInput(time)
121
133
  }
122
134
 
@@ -7,33 +7,37 @@ export default class extends Controller {
7
7
  <div class="double-bounce2"></div>
8
8
  </div>`;
9
9
 
10
- confirmed = false
11
-
12
- connect() {
13
- this.context.scope.element.addEventListener('click', (e) => {
14
- // If the user has to confirm the action
15
- if (this.confirmationMessage) {
16
- // Intervene only if not confirmed
17
- if (!this.confirmed) {
18
- e.preventDefault()
19
- if (window.confirm(this.confirmationMessage)) {
20
- this.applyLoader()
21
- }
22
- }
23
- } else {
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.confirmed = true
57
+ this.confirmedValue = true
54
58
  }
55
59
 
56
60
  markUnconfirmed() {
57
- this.confirmed = false
61
+ this.confirmedValue = false
58
62
  }
59
63
  }
@@ -147,6 +147,9 @@ export default class extends Controller {
147
147
  case 'rounded':
148
148
  classes = 'rounded'
149
149
  break
150
+ case 'square':
151
+ classes = 'rounded-none'
152
+ break
150
153
  }
151
154
 
152
155
  children.push(
@@ -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)
@@ -1,4 +1,4 @@
1
- <%= turbo_frame_wrap(params[:turbo_frame]) do %>
1
+ <%= render Avo::TurboFrameWrapperComponent.new(params[:turbo_frame]) do %>
2
2
  <%= render Avo::Views::ResourceIndexComponent.new(
3
3
  resource: @resource,
4
4
  resources: @resources,
@@ -1,3 +1,3 @@
1
- <%= turbo_frame_wrap(params[:turbo_frame]) do %>
1
+ <%= render Avo::TurboFrameWrapperComponent.new(params[:turbo_frame]) do %>
2
2
  <%= render Avo::Views::ResourceShowComponent.new(resource: @resource, reflection: @reflection, actions: @actions) %>
3
3
  <% end %>
@@ -1,3 +1,3 @@
1
- <%= turbo_frame_wrap(params[:turbo_frame]) do %>
1
+ <%= render Avo::TurboFrameWrapperComponent.new(params[:turbo_frame]) do %>
2
2
  <%= render Avo::CardComponent.new card: @card %>
3
3
  <% end %>