avo 1.18.2.pre.0 → 1.19.1.pre.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -3
  3. data/Gemfile.lock +9 -8
  4. data/app/components/avo/fields/belongs_to_field/index_component.html.erb +1 -1
  5. data/app/components/avo/fields/belongs_to_field/show_component.html.erb +1 -1
  6. data/app/components/avo/fields/common/multiple_file_viewer_component.html.erb +1 -1
  7. data/app/components/avo/fields/common/single_file_viewer_component.html.erb +1 -2
  8. data/app/components/avo/fields/has_one_field/index_component.html.erb +1 -1
  9. data/app/components/avo/fields/has_one_field/show_component.html.erb +1 -1
  10. data/app/components/avo/fields/has_one_field/show_component.rb +6 -1
  11. data/app/components/avo/fields/index_component.rb +2 -2
  12. data/app/components/avo/fields/text_field/index_component.html.erb +5 -1
  13. data/app/components/avo/fields/text_field/show_component.html.erb +5 -1
  14. data/app/components/avo/fields/trix_field/edit_component.html.erb +1 -1
  15. data/app/components/avo/index/grid_item_component.html.erb +4 -4
  16. data/app/components/avo/index/resource_controls_component.html.erb +1 -1
  17. data/app/components/avo/index/resource_controls_component.rb +22 -6
  18. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  19. data/app/components/avo/panel_component.rb +3 -4
  20. data/app/components/avo/resource_component.rb +5 -1
  21. data/app/components/avo/views/resource_edit_component.html.erb +2 -2
  22. data/app/components/avo/views/resource_edit_component.rb +3 -3
  23. data/app/components/avo/views/resource_index_component.html.erb +2 -8
  24. data/app/components/avo/views/resource_index_component.rb +7 -15
  25. data/app/components/avo/views/resource_new_component.html.erb +2 -2
  26. data/app/components/avo/views/resource_new_component.rb +6 -2
  27. data/app/components/avo/views/resource_show_component.html.erb +5 -4
  28. data/app/components/avo/views/resource_show_component.rb +11 -6
  29. data/app/controllers/avo/actions_controller.rb +1 -1
  30. data/app/controllers/avo/application_controller.rb +17 -61
  31. data/app/controllers/avo/attachments_controller.rb +3 -2
  32. data/app/controllers/avo/base_controller.rb +30 -25
  33. data/app/controllers/avo/home_controller.rb +5 -1
  34. data/app/controllers/avo/relations_controller.rb +3 -3
  35. data/app/controllers/avo/search_controller.rb +1 -1
  36. data/app/helpers/avo/application_helper.rb +5 -11
  37. data/app/helpers/avo/resources_helper.rb +1 -1
  38. data/app/helpers/avo/url_helpers.rb +77 -0
  39. data/app/packs/entrypoints/application.css +1 -0
  40. data/app/packs/entrypoints/application.js +7 -0
  41. data/app/packs/js/controllers/loading_button_controller.js +22 -0
  42. data/app/packs/stylesheets/components/status.css +22 -15
  43. data/app/packs/stylesheets/spinner.css +49 -0
  44. data/app/views/avo/actions/show.html.erb +2 -2
  45. data/app/views/avo/base/_actions.html.erb +3 -3
  46. data/app/views/avo/base/_filters.html.erb +1 -1
  47. data/app/views/avo/home/failed_to_load.html.erb +3 -0
  48. data/app/views/avo/partials/_failed_state.html.erb +16 -0
  49. data/app/views/avo/partials/_paginator.html.erb +2 -2
  50. data/app/views/avo/partials/_profile_dropdown.html.erb +7 -5
  51. data/app/views/avo/relations/new.html.erb +0 -2
  52. data/app/views/avo/sidebar/_sidebar.html.erb +1 -1
  53. data/app/views/kaminari/_page.html.erb +1 -1
  54. data/config/routes.rb +7 -5
  55. data/lib/avo/app.rb +12 -12
  56. data/lib/avo/base_action.rb +9 -1
  57. data/lib/avo/base_resource.rb +33 -7
  58. data/lib/avo/configuration.rb +4 -0
  59. data/lib/avo/fields/base_field.rb +15 -4
  60. data/lib/avo/fields/has_and_belongs_to_many_field.rb +1 -19
  61. data/lib/avo/fields/has_base_field.rb +35 -0
  62. data/lib/avo/fields/has_many_field.rb +1 -19
  63. data/lib/avo/fields/has_one_field.rb +3 -19
  64. data/lib/avo/fields/key_value_field.rb +4 -4
  65. data/lib/avo/fields/text_field.rb +2 -0
  66. data/lib/avo/services/authorization_service.rb +8 -4
  67. data/lib/avo/version.rb +1 -1
  68. data/lib/generators/avo/templates/locales/avo.en.yml +12 -3
  69. data/lib/generators/avo/templates/locales/avo.ro.yml +7 -0
  70. data/public/avo-packs/css/{application-f9191617.css → application-c3b50b28.css} +54 -12
  71. data/public/avo-packs/css/application-c3b50b28.css.br +0 -0
  72. data/public/avo-packs/css/{application-f9191617.css.gz → application-c3b50b28.css.gz} +0 -0
  73. data/public/avo-packs/css/application-c3b50b28.css.map +1 -0
  74. data/public/avo-packs/css/application-c3b50b28.css.map.br +0 -0
  75. data/public/avo-packs/css/application-c3b50b28.css.map.gz +0 -0
  76. data/public/avo-packs/js/actioncable-7119dbc1a908641fb263.chunk.js +2 -0
  77. data/public/avo-packs/js/actioncable-7119dbc1a908641fb263.chunk.js.br +0 -0
  78. data/public/avo-packs/js/actioncable-7119dbc1a908641fb263.chunk.js.gz +0 -0
  79. data/public/avo-packs/js/actioncable-7119dbc1a908641fb263.chunk.js.map +1 -0
  80. data/public/avo-packs/js/actioncable-7119dbc1a908641fb263.chunk.js.map.br +0 -0
  81. data/public/avo-packs/js/actioncable-7119dbc1a908641fb263.chunk.js.map.gz +0 -0
  82. data/public/avo-packs/js/application-6fc968cfa52976c4582b.js +26 -0
  83. data/public/avo-packs/js/{application-cc89f096028eb1d4d971.js.LICENSE.txt → application-6fc968cfa52976c4582b.js.LICENSE.txt} +0 -0
  84. data/public/avo-packs/js/application-6fc968cfa52976c4582b.js.br +0 -0
  85. data/public/avo-packs/js/application-6fc968cfa52976c4582b.js.gz +0 -0
  86. data/public/avo-packs/js/application-6fc968cfa52976c4582b.js.map +1 -0
  87. data/public/avo-packs/js/application-6fc968cfa52976c4582b.js.map.br +0 -0
  88. data/public/avo-packs/js/application-6fc968cfa52976c4582b.js.map.gz +0 -0
  89. data/public/avo-packs/manifest.json +21 -21
  90. metadata +27 -21
  91. data/public/avo-packs/css/application-f9191617.css.br +0 -0
  92. data/public/avo-packs/css/application-f9191617.css.map +0 -1
  93. data/public/avo-packs/css/application-f9191617.css.map.br +0 -0
  94. data/public/avo-packs/css/application-f9191617.css.map.gz +0 -0
  95. data/public/avo-packs/js/219-9aa2b689f44613118203.chunk.js +0 -2
  96. data/public/avo-packs/js/219-9aa2b689f44613118203.chunk.js.br +0 -0
  97. data/public/avo-packs/js/219-9aa2b689f44613118203.chunk.js.gz +0 -0
  98. data/public/avo-packs/js/219-9aa2b689f44613118203.chunk.js.map +0 -1
  99. data/public/avo-packs/js/219-9aa2b689f44613118203.chunk.js.map.br +0 -0
  100. data/public/avo-packs/js/219-9aa2b689f44613118203.chunk.js.map.gz +0 -0
  101. data/public/avo-packs/js/application-cc89f096028eb1d4d971.js +0 -26
  102. data/public/avo-packs/js/application-cc89f096028eb1d4d971.js.br +0 -0
  103. data/public/avo-packs/js/application-cc89f096028eb1d4d971.js.gz +0 -0
  104. data/public/avo-packs/js/application-cc89f096028eb1d4d971.js.map +0 -1
  105. data/public/avo-packs/js/application-cc89f096028eb1d4d971.js.map.br +0 -0
  106. data/public/avo-packs/js/application-cc89f096028eb1d4d971.js.map.gz +0 -0
@@ -3,6 +3,7 @@ module Avo
3
3
  include Pundit
4
4
  include Pagy::Backend
5
5
  include Avo::ApplicationHelper
6
+ include Avo::UrlHelpers
6
7
 
7
8
  protect_from_forgery with: :exception
8
9
  before_action :init_app
@@ -11,6 +12,8 @@ module Avo
11
12
  before_action :_authenticate!
12
13
  before_action :set_container_classes
13
14
  before_action :add_initial_breadcrumbs
15
+ before_action :set_view
16
+ before_action :set_model_to_fill
14
17
 
15
18
  rescue_from Pundit::NotAuthorizedError, with: :render_unauthorized
16
19
  rescue_from ActiveRecord::RecordInvalid, with: :exception_logger
@@ -19,7 +22,7 @@ module Avo
19
22
  add_flash_types :info, :warning, :success, :error
20
23
 
21
24
  def init_app
22
- Avo::App.init request: request, context: context, root_path: avo.root_path.delete_suffix("/"), current_user: _current_user
25
+ Avo::App.init request: request, context: context, root_path: avo.root_path.delete_suffix("/"), current_user: _current_user, view_context: view_context
23
26
 
24
27
  @license = Avo::App.license
25
28
  end
@@ -70,66 +73,6 @@ module Avo
70
73
  instance_eval(&Avo.configuration.context)
71
74
  end
72
75
 
73
- def resources_path(model, keep_query_params: false, **args)
74
- return if model.nil?
75
-
76
- model_class = get_model_class model
77
-
78
- existing_params = {}
79
-
80
- begin
81
- if keep_query_params
82
- existing_params = Addressable::URI.parse(request.fullpath).query_values.symbolize_keys
83
- end
84
- rescue; end
85
- avo.send :"resources_#{model_class.base_class.model_name.route_key}_path", **existing_params, **args
86
- end
87
-
88
- def related_resources_path(parent_model, model, keep_query_params: false, **args)
89
- return if model.nil?
90
-
91
- existing_params = {}
92
-
93
- begin
94
- if keep_query_params
95
- existing_params = Addressable::URI.parse(request.fullpath).query_values.symbolize_keys
96
- end
97
- rescue; end
98
- Addressable::Template.new("#{Avo::App.root_path}/resources/#{@parent_resource.model.model_name.route_key}/#{@parent_resource.model.id}/#{@resource.route_key}{?query*}")
99
- .expand({query: {**existing_params, **args}})
100
- .to_str
101
- end
102
-
103
- def resource_path(model = nil, resource_id: nil, keep_query_params: false, **args)
104
- return avo.send :"resources_#{singular_name(model)}_path", resource_id, **args if resource_id.present?
105
-
106
- avo.send :"resources_#{singular_name(model)}_path", model, **args
107
- end
108
-
109
- def resource_attach_path(model_name, model_id, related_name, related_id = nil)
110
- path = "#{Avo::App.root_path}/resources/#{model_name}/#{model_id}/#{related_name}/new"
111
-
112
- path += "/#{related_id}" if related_id.present?
113
-
114
- path
115
- end
116
-
117
- def resource_detach_path(model_name, model_id, related_name, related_id = nil)
118
- path = "#{Avo::App.root_path}/resources/#{model_name}/#{model_id}/#{related_name}"
119
-
120
- path += "/#{related_id}" if related_id.present?
121
-
122
- path
123
- end
124
-
125
- def new_resource_path(model, **args)
126
- avo.send :"new_resources_#{singular_name(model)}_path", **args
127
- end
128
-
129
- def edit_resource_path(model, **args)
130
- avo.send :"edit_resources_#{singular_name(model)}_path", model.id, **args
131
- end
132
-
133
76
  private
134
77
 
135
78
  def set_resource_name
@@ -158,6 +101,19 @@ module Avo
158
101
  @related_model = eager_load_files(@related_resource, @related_resource.class.find_scope).find params[:related_id]
159
102
  end
160
103
 
104
+ def set_view
105
+ @view = params[:action].to_sym
106
+ end
107
+
108
+ def set_model_to_fill
109
+ @model_to_fill = @resource.model_class.new if @view == :create
110
+ @model_to_fill = @model if @view == :update
111
+ end
112
+
113
+ def fill_model
114
+ @model = @resource.fill_model(@model_to_fill, cast_nullable(model_params))
115
+ end
116
+
161
117
  def hydrate_resource
162
118
  @resource.hydrate(view: action_name.to_sym, user: _current_user)
163
119
  end
@@ -23,13 +23,14 @@ module Avo
23
23
  def destroy
24
24
  blob = ActiveStorage::Blob.find(params[:signed_attachment_id])
25
25
  attachment = blob.attachments.find_by record_id: params[:id], record_type: @model.class.to_s
26
+ path = resource_path(model: @model, resource: @resource)
26
27
 
27
28
  if attachment.present?
28
29
  attachment.destroy
29
30
 
30
- redirect_to params[:referrer] || resource_path(@model), notice: t("avo.attachment_destroyed")
31
+ redirect_to params[:referrer] || path, notice: t("avo.attachment_destroyed")
31
32
  else
32
- redirect_back fallback_location: resource_path(@model), notice: t("avo.failed_to_find_attachment")
33
+ redirect_back fallback_location: path, notice: t("avo.failed_to_find_attachment")
33
34
  end
34
35
  end
35
36
  end
@@ -5,8 +5,10 @@ module Avo
5
5
  before_action :set_resource_name
6
6
  before_action :set_resource
7
7
  before_action :hydrate_resource
8
- before_action :authorize_action
9
8
  before_action :set_model, only: [:show, :edit, :destroy, :update]
9
+ before_action :set_model_to_fill
10
+ before_action :fill_model, only: [:create, :update]
11
+ before_action :authorize_action
10
12
  before_action :reset_pagination_if_filters_changed, only: :index
11
13
  before_action :cache_applied_filters, only: :index
12
14
 
@@ -70,10 +72,10 @@ module Avo
70
72
  via_model = via_resource.class.find_scope.find params[:via_resource_id]
71
73
  via_resource.hydrate model: via_model
72
74
 
73
- add_breadcrumb via_resource.plural_name, resources_path(via_resource.model_class)
74
- add_breadcrumb via_resource.model_title, resource_path(via_model)
75
+ add_breadcrumb via_resource.plural_name, resources_path(resource: via_resource)
76
+ add_breadcrumb via_resource.model_title, resource_path(model: via_model, resource: via_resource)
75
77
  else
76
- add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
78
+ add_breadcrumb resource_name.humanize, resources_path(resource: @resource)
77
79
  end
78
80
 
79
81
  add_breadcrumb @resource.model_title
@@ -84,7 +86,7 @@ module Avo
84
86
  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
85
87
 
86
88
  @page_title = @resource.default_panel_name
87
- add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
89
+ add_breadcrumb resource_name.humanize, resources_path(resource: @resource)
88
90
  add_breadcrumb t("avo.new").humanize
89
91
  end
90
92
 
@@ -99,30 +101,31 @@ module Avo
99
101
  via_model = via_resource.class.find_scope.find params[:via_resource_id]
100
102
  via_resource.hydrate model: via_model
101
103
 
102
- add_breadcrumb via_resource.plural_name, resources_path(via_resource.model_class)
103
- add_breadcrumb via_resource.model_title, resource_path(via_model)
104
+ add_breadcrumb via_resource.plural_name, resources_path(resource: @resource)
105
+ add_breadcrumb via_resource.model_title, resource_path(model: via_model, resource: via_resource)
104
106
  else
105
- add_breadcrumb resource_name.humanize, resources_path(@resource.model_class)
107
+ add_breadcrumb resource_name.humanize, resources_path(resource: @resource)
106
108
  end
107
109
 
108
- add_breadcrumb @resource.model_title, resource_path(@resource.model)
110
+ add_breadcrumb @resource.model_title, resource_path(model: @resource.model, resource: @resource)
109
111
  add_breadcrumb t("avo.edit").humanize
110
112
  end
111
113
 
112
114
  def create
113
- @model = @resource.fill_model(@resource.model_class.new, cast_nullable(model_params))
115
+ # model gets instantiated and filled in the fill_model before_action
114
116
  saved = @model.save
115
117
  @resource.hydrate(model: @model, view: :new, user: _current_user)
116
118
 
117
119
  respond_to do |format|
118
120
  if saved
119
121
  redirect_path = if params[:via_relation_class].present? && params[:via_resource_id].present?
120
- resource_path(params[:via_relation_class].safe_constantize, resource_id: params[:via_resource_id])
122
+ parent_resource = ::Avo::App.get_resource_by_model_name params[:via_relation_class].safe_constantize
123
+ resource_path(model: params[:via_relation_class].safe_constantize, resource: parent_resource, resource_id: params[:via_resource_id])
121
124
  else
122
- resource_path(@model)
125
+ resource_path(model: @model, resource: @resource)
123
126
  end
124
127
 
125
- format.html { redirect_to redirect_path, notice: "#{@model.class.name} was successfully created." }
128
+ format.html { redirect_to redirect_path, notice: "#{@model.class.name} #{t("avo.was_successfully_created")}." }
126
129
  format.json { render :show, status: :created, location: @model }
127
130
  else
128
131
  flash[:error] = t "avo.you_missed_something_check_form"
@@ -133,13 +136,13 @@ module Avo
133
136
  end
134
137
 
135
138
  def update
136
- @model = @resource.fill_model(@model, cast_nullable(model_params))
139
+ # model gets instantiated and filled in the fill_model before_action
137
140
  saved = @model.save
138
141
  @resource = @resource.hydrate(model: @model, view: :edit, user: _current_user)
139
142
 
140
143
  respond_to do |format|
141
144
  if saved
142
- format.html { redirect_to params[:referrer] || resource_path(@model), notice: "#{@model.class.name} was successfully updated." }
145
+ format.html { redirect_to params[:referrer] || resource_path(model: @model, resource: @resource), notice: "#{@model.class.name} #{t("avo.was_successfully_updated")}." }
143
146
  format.json { render :show, status: :ok, location: @model }
144
147
  else
145
148
  flash[:error] = t "avo.you_missed_something_check_form"
@@ -153,19 +156,17 @@ module Avo
153
156
  @model.destroy!
154
157
 
155
158
  respond_to do |format|
156
- format.html { redirect_to params[:referrer] || resources_path(@model, turbo_frame: params[:turbo_frame], view_type: params[:view_type]), notice: t("avo.resource_destroyed", attachment_class: @attachment_class) }
159
+ format.html { redirect_to params[:referrer] || resources_path(resource: @resource, turbo_frame: params[:turbo_frame], view_type: params[:view_type]), notice: t("avo.resource_destroyed", attachment_class: @attachment_class) }
157
160
  format.json { head :no_content }
158
161
  end
159
162
  end
160
163
 
161
164
  private
162
165
 
163
- def model_route_key
164
- singular_name @resource.model_class
165
- end
166
-
167
166
  def model_params
168
- request_params = params.require(model_route_key).permit(permitted_params)
167
+ model_param_key = @resource.singular_model_key
168
+
169
+ request_params = params.require(model_param_key).permit(permitted_params)
169
170
 
170
171
  if @resource.devise_password_optional && request_params[:password].blank? && request_params[:password_confirmation].blank?
171
172
  request_params.delete(:password_confirmation)
@@ -248,9 +249,13 @@ module Avo
248
249
  model = @resource.class.find_scope.find params[:resource_id]
249
250
  end
250
251
 
251
- @actions = @resource.get_actions.map do |action|
252
- action.new(model: model, resource: @resource)
253
- end
252
+ @actions =
253
+ @resource
254
+ .get_actions
255
+ .map do |action|
256
+ action.new(model: model, resource: @resource, view: @view)
257
+ end
258
+ .select { |action| action.visible_in_view }
254
259
  end
255
260
 
256
261
  def applied_filters
@@ -282,7 +287,7 @@ module Avo
282
287
  end
283
288
 
284
289
  def applied_filters_cache_key
285
- "avo.base_controller.#{@resource.route_key}.applied_filters"
290
+ "avo.base_controller.#{@resource.model_key}.applied_filters"
286
291
  end
287
292
  end
288
293
  end
@@ -7,8 +7,12 @@ module Avo
7
7
  redirect_to Avo.configuration.home_path
8
8
  elsif !Rails.env.development?
9
9
  @page_title = "Get started"
10
- redirect_to resources_path Avo::App.resources.min_by { |resource| resource.route_key }.model_class
10
+ resource = Avo::App.resources.min_by { |resource| resource.model_key }
11
+ redirect_to resources_path(resource: resource)
11
12
  end
12
13
  end
14
+
15
+ def failed_to_load
16
+ end
13
17
  end
14
18
  end
@@ -48,8 +48,8 @@ module Avo
48
48
 
49
49
  respond_to do |format|
50
50
  if @model.save
51
- format.html { redirect_to resource_path(@model), notice: t("avo.attachment_class_attached", attachment_class: @attachment_class) }
52
- format.json { render :show, status: :created, location: resource_path(@model) }
51
+ format.html { redirect_to resource_path(model: @model, resource: @resource), notice: t("avo.attachment_class_attached", attachment_class: @attachment_class) }
52
+ format.json { render :show, status: :created, location: resource_path(model: @model, resource: @resource) }
53
53
  else
54
54
  format.html { render :new }
55
55
  format.json { render json: @model.errors, status: :unprocessable_entity }
@@ -65,7 +65,7 @@ module Avo
65
65
  end
66
66
 
67
67
  respond_to do |format|
68
- format.html { redirect_to params[:referrer] || resource_path(@model), notice: t("avo.attachment_class_detached", attachment_class: @attachment_class) }
68
+ format.html { redirect_to params[:referrer] || resource_path(model: @model, resource: @resource), notice: t("avo.attachment_class_detached", attachment_class: @attachment_class) }
69
69
  end
70
70
  end
71
71
 
@@ -59,7 +59,7 @@ module Avo
59
59
  result = {
60
60
  _id: model.id,
61
61
  _label: resource.label,
62
- _url: resource.avo_path,
62
+ _url: resource.record_path,
63
63
  model: model
64
64
  }
65
65
 
@@ -39,6 +39,10 @@ module Avo
39
39
 
40
40
  def a_button(label = nil, **args, &block)
41
41
  args[:class] = button_classes(args[:class], color: args[:color], variant: args[:variant], size: args[:size])
42
+ if args[:spinner]
43
+ args["data-controller"] = "loading-button"
44
+ # args["data-action"] = "click->loading-button#onClick"
45
+ end
42
46
 
43
47
  locals = {
44
48
  label: label,
@@ -159,17 +163,7 @@ module Avo
159
163
  if model.instance_of?(Class)
160
164
  model
161
165
  else
162
- model.class.base_class
163
- end
164
- end
165
-
166
- def singular_name(model_or_class)
167
- model_class = get_model_class model_or_class
168
-
169
- if ActiveModel::Naming.uncountable? model_class
170
- model_class.base_class.model_name.route_key.singularize.gsub('_index', '')
171
- else
172
- model_class.base_class.model_name.route_key.singularize
166
+ model.class
173
167
  end
174
168
  end
175
169
  end
@@ -42,7 +42,7 @@ module Avo
42
42
  end
43
43
 
44
44
  def item_selector_init(resource)
45
- "data-resource-name='#{resource.model_class.model_name.route_key}' data-resource-id='#{resource.model.id}' data-controller='item-selector'"
45
+ "data-resource-name='#{resource.model_key}' data-resource-id='#{resource.model.id}' data-controller='item-selector'"
46
46
  end
47
47
 
48
48
  def item_selector_input(floating: false, size: :md)
@@ -0,0 +1,77 @@
1
+ module Avo
2
+ module UrlHelpers
3
+ def resources_path(resource:, keep_query_params: false, **args)
4
+ return if resource.nil?
5
+
6
+ existing_params = {}
7
+ begin
8
+ if keep_query_params
9
+ existing_params =
10
+ Addressable::URI.parse(request.fullpath).query_values.symbolize_keys
11
+ end
12
+ rescue
13
+ end
14
+
15
+ # This entry uses `route_key` instead of `model_key` because it's rails that needs `fish_index` to build the correct path
16
+ avo.send :"resources_#{resource.route_key}_path", **existing_params, **args
17
+ end
18
+
19
+ def resource_path(
20
+ model:,
21
+ resource:,
22
+ resource_id: nil,
23
+ keep_query_params: false,
24
+ **args
25
+ )
26
+ if model.respond_to? :id
27
+ id = model
28
+ elsif resource_id.present?
29
+ id = resource_id
30
+ end
31
+
32
+ avo.send :"resources_#{resource.singular_model_key}_path", id, **args
33
+ end
34
+
35
+ def new_resource_path(model:, resource:, **args)
36
+ avo.send :"new_resources_#{resource.singular_model_key}_path", **args
37
+ end
38
+
39
+ def edit_resource_path(model:, resource:, **args)
40
+ avo.send :"edit_resources_#{resource.singular_model_key}_path", model, **args
41
+ end
42
+
43
+ def resource_attach_path(resource, model_id, related_name, related_id = nil)
44
+ helpers.avo.resources_associations_new_path(resource.singular_model_key, model_id, related_name)
45
+ end
46
+
47
+ def resource_detach_path(
48
+ model_name, # teams
49
+ model_id, # 1
50
+ related_name, # admin
51
+ related_id = nil
52
+ )
53
+ avo.resources_associations_destroy_path(model_name, model_id, related_name, related_id)
54
+ end
55
+
56
+ def related_resources_path(
57
+ parent_model,
58
+ model,
59
+ keep_query_params: false,
60
+ **args
61
+ )
62
+ return if model.nil?
63
+
64
+ existing_params = {}
65
+
66
+ begin
67
+ if keep_query_params
68
+ existing_params =
69
+ Addressable::URI.parse(request.fullpath).query_values.symbolize_keys
70
+ end
71
+ rescue
72
+ end
73
+
74
+ avo.resources_associations_index_path(@parent_resource.model_class.model_name.route_key, @parent_resource.model.id, **existing_params, **args )
75
+ end
76
+ end
77
+ end
@@ -17,6 +17,7 @@
17
17
  @import './../stylesheets/breadcrumbs.css';
18
18
  @import './../stylesheets/search.css';
19
19
  @import './../stylesheets/active-storage.css';
20
+ @import './../stylesheets/spinner.css';
20
21
 
21
22
  @import './../stylesheets/components/status.css';
22
23
  @import './../stylesheets/components/code.css';
@@ -46,6 +46,13 @@ document.addEventListener('turbo:load', () => {
46
46
  document.body.classList.remove('turbo-loading')
47
47
  initTippy()
48
48
  })
49
+
50
+ document.addEventListener('turbo:before-fetch-response', (e) => {
51
+ if (e.detail.fetchResponse.response.status === 500) {
52
+ const id = e.srcElement.getAttribute('id')
53
+ e.srcElement.src = `${window.Avo.configuration.root_path}/failed_to_load?turbo_frame=${id}`
54
+ }
55
+ })
49
56
  document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading'))
50
57
  document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading'))
51
58
  document.addEventListener('turbo:before-cache', () => {
@@ -0,0 +1,22 @@
1
+ import { Controller } from 'stimulus'
2
+
3
+ export default class extends Controller {
4
+ spinnerMarkup = `<div class="button-spinner">
5
+ <div class="double-bounce1"></div>
6
+ <div class="double-bounce2"></div>
7
+ </div>`;
8
+
9
+ connect() {
10
+ const button = this.context.scope.element
11
+ this.context.scope.element.addEventListener('click', () => {
12
+ button.style.width = `${button.getBoundingClientRect().width}px`
13
+ button.style.height = `${button.getBoundingClientRect().height}px`
14
+ button.innerHTML = this.spinnerMarkup
15
+ button.classList.add('justify-center')
16
+
17
+ setTimeout(() => {
18
+ button.setAttribute('disabled', 'disabled')
19
+ }, 1)
20
+ })
21
+ }
22
+ }
@@ -5,8 +5,8 @@
5
5
  position: relative;
6
6
  }
7
7
 
8
-
9
- .double-bounce1, .double-bounce2 {
8
+ .spinner .double-bounce1,
9
+ .spinner .double-bounce2 {
10
10
  width: 100%;
11
11
  height: 100%;
12
12
  border-radius: 50%;
@@ -16,26 +16,33 @@
16
16
  top: 0;
17
17
  left: 0;
18
18
 
19
- -webkit-animation: sk-bounce 2.0s infinite ease-in-out;
20
- animation: sk-bounce 2.0s infinite ease-in-out;
19
+ -webkit-animation: sk-bounce 2s infinite ease-in-out;
20
+ animation: sk-bounce 2s infinite ease-in-out;
21
21
  }
22
22
 
23
- .double-bounce2 {
24
- -webkit-animation-delay: -1.0s;
25
- animation-delay: -1.0s;
23
+ .spinner .double-bounce2 {
24
+ -webkit-animation-delay: -1s;
25
+ animation-delay: -1s;
26
26
  }
27
27
 
28
28
  @-webkit-keyframes sk-bounce {
29
- 0%, 100% { -webkit-transform: scale(0.0) }
30
- 50% { -webkit-transform: scale(1.0) }
29
+ 0%,
30
+ 100% {
31
+ -webkit-transform: scale(0);
32
+ }
33
+ 50% {
34
+ -webkit-transform: scale(1);
35
+ }
31
36
  }
32
37
 
33
38
  @keyframes sk-bounce {
34
- 0%, 100% {
35
- transform: scale(0.0);
36
- -webkit-transform: scale(0.0);
37
- } 50% {
38
- transform: scale(1.0);
39
- -webkit-transform: scale(1.0);
39
+ 0%,
40
+ 100% {
41
+ transform: scale(0);
42
+ -webkit-transform: scale(0);
43
+ }
44
+ 50% {
45
+ transform: scale(1);
46
+ -webkit-transform: scale(1);
40
47
  }
41
48
  }
@@ -0,0 +1,49 @@
1
+ .button-spinner {
2
+ width: 24px;
3
+ height: 24px;
4
+
5
+ position: relative;
6
+ margin: 100px auto;
7
+ }
8
+
9
+ .button-spinner > .double-bounce1,
10
+ .button-spinner > .double-bounce2 {
11
+ width: 100%;
12
+ height: 100%;
13
+ border-radius: 50%;
14
+ background-color: #333;
15
+ opacity: 0.5;
16
+ position: absolute;
17
+ top: 0;
18
+ left: 0;
19
+
20
+ -webkit-animation: sk-bounce 2s infinite ease-in-out;
21
+ animation: sk-bounce 2s infinite ease-in-out;
22
+ }
23
+
24
+ .button-spinner > .double-bounce2 {
25
+ -webkit-animation-delay: -1s;
26
+ animation-delay: -1s;
27
+ }
28
+
29
+ @-webkit-keyframes sk-bounce {
30
+ 0%,
31
+ 100% {
32
+ -webkit-transform: scale(0);
33
+ }
34
+ 50% {
35
+ -webkit-transform: scale(1);
36
+ }
37
+ }
38
+
39
+ @keyframes sk-bounce {
40
+ 0%,
41
+ 100% {
42
+ transform: scale(0);
43
+ -webkit-transform: scale(0);
44
+ }
45
+ 50% {
46
+ transform: scale(1);
47
+ -webkit-transform: scale(1);
48
+ }
49
+ }
@@ -3,11 +3,11 @@
3
3
  data-controller="action"
4
4
  data-no-confirmation="<%= @action.no_confirmation %>"
5
5
  data-action-target="controllerDiv"
6
- data-resource-name="<%= @resource.model_class.model_name.route_key %>"
6
+ data-resource-name="<%= @resource.model_key %>"
7
7
  data-resource-id="<%= params[:id] %>"
8
8
  class="hidden text-blue-gray-800"
9
9
  >
10
- <%= form_with model: @model, scope: 'fields', url: "#{avo.root_path}resources/#{@resource.model_class.model_name.route_key}/actions/#{@action.param_id}", data: {'turbo-frame': '_top', 'action-target': 'form'} do |form| %>
10
+ <%= form_with model: @model, scope: 'fields', url: "#{@resource.records_path}/actions/#{@action.param_id}", data: {'turbo-frame': '_top', 'action-target': 'form'} do |form| %>
11
11
  <%= render Avo::ModalComponent.new do |c| %>
12
12
  <% c.heading do %>
13
13
  <%= @action.action_name %>
@@ -3,7 +3,7 @@
3
3
  <%= a_button class: "focus:outline-none",
4
4
  color: 'light-blue',
5
5
  'data-action': "click->toggle-panel#togglePanel",
6
- 'data-actions-dropdown-button': @resource.model_class.model_name.route_key do
6
+ 'data-actions-dropdown-button': @resource.model_key do
7
7
  %>
8
8
  <%= svg 'arrow-left', class: 'h-4 mr-1 transform -rotate-90' %> <%= t 'avo.actions' %>
9
9
  <% end %>
@@ -14,8 +14,8 @@
14
14
  <%
15
15
  @actions.each_with_index do |action, index|
16
16
  path = action_name == 'show' ?
17
- "#{avo.root_path}resources/#{@resource.model_class.model_name.route_key}/#{@model.id}/actions/#{action.param_id}" :
18
- "#{avo.root_path}resources/#{@resource.model_class.model_name.route_key}/actions/#{action.param_id}"
17
+ "#{@resource.record_path}/actions/#{action.param_id}" :
18
+ "#{@resource.records_path}/actions/#{action.param_id}"
19
19
  if action_name == 'show' || action.standalone
20
20
  disabled = false
21
21
  else
@@ -24,7 +24,7 @@
24
24
 
25
25
  <div class="p-4 border-gray-300 border-t">
26
26
  <% if params[:filters].present? %>
27
- <%= a_link t('avo.reset_filters'), resources_path(@resource.model_class, filters: nil, keep_query_params: true), color: 'blue-gray', class: 'w-full justify-center' %>
27
+ <%= a_link t('avo.reset_filters'), resources_path(resource: @resource, filters: nil, keep_query_params: true), color: 'blue-gray', class: 'w-full justify-center' %>
28
28
  <% else %>
29
29
  <%= a_button t('avo.reset_filters'), color: 'blue-gray', class: 'w-full justify-center', disabled: true %>
30
30
  <% end %>
@@ -0,0 +1,3 @@
1
+ <%= turbo_frame_wrap(params[:turbo_frame]) do %>
2
+ <%= render 'avo/partials/failed_state' %>
3
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <%
2
+ classes = 'absolute inset-auto left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2'
3
+ label = t 'avo.failed_to_load'
4
+ %>
5
+ <div class="relative flex-1 py-12">
6
+ <div class="relative block text-gray-300 h-40 w-full">
7
+ <%= svg 'avocado', class: "#{classes} h-20 text-gray-400" %>
8
+ <%= svg 'code', class: "#{classes} h-8 -ml-20 -mt-12" %>
9
+ <%= svg 'fire', class: "#{classes} h-8 -ml-10 -mt-24" %>
10
+ <%= svg 'color-swatch', class: "#{classes} h-8 ml-8 -mt-24" %>
11
+ <%= svg 'globe', class: "#{classes} h-8 ml-20 -mt-12" %>
12
+ <%= svg 'library', class: "#{classes} h-8 -ml-20 mt-4" %>
13
+ <%= svg 'photograph', class: "#{classes} h-8 ml-20 mt-4" %>
14
+ </div>
15
+ <div class="relative block text-center text-lg text-gray-400 font-semibold -mt-10"><%= label %> <%= params[:turbo_frame].to_s.humanize.downcase if params[:turbo_frame].present? %></div>
16
+ </div>
@@ -29,9 +29,9 @@
29
29
 
30
30
  <% per_page_options.each do |option| %>
31
31
  <% if @parent_resource.present? %>
32
- <%= link_to "Change to #{option} items per page", related_resources_path(@parent_resource.model, @resource.model_class, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
32
+ <%= link_to "Change to #{option} items per page", related_resources_path(@parent_resource.model_class, @resource.model_class, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
33
33
  <% else %>
34
- <%= link_to "Change to #{option} items per page", resources_path(@resource.model_class, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
34
+ <%= link_to "Change to #{option} items per page", resources_path(resource: @resource, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
35
35
  <% end %>
36
36
  <% end %>
37
37
  </div>