avo 2.1.2.pre1 → 2.2.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +7 -1
  4. data/app/components/avo/button_component.rb +6 -2
  5. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +1 -0
  6. data/app/components/avo/fields/belongs_to_field/edit_component.rb +5 -2
  7. data/app/components/avo/filters_component.html.erb +19 -20
  8. data/app/components/avo/index/grid_item_component.html.erb +3 -1
  9. data/app/components/avo/index/table_row_component.html.erb +7 -5
  10. data/app/components/avo/panel_component.html.erb +4 -4
  11. data/app/components/avo/sidebar_profile_component.html.erb +14 -10
  12. data/app/components/avo/views/resource_edit_component.html.erb +2 -2
  13. data/app/components/avo/views/resource_new_component.html.erb +2 -2
  14. data/app/components/avo/views/resource_show_component.html.erb +2 -2
  15. data/app/controllers/avo/base_controller.rb +1 -1
  16. data/app/javascript/js/controllers/loading_button_controller.js +47 -10
  17. data/app/javascript/js/controllers/search_controller.js +28 -10
  18. data/app/views/avo/partials/_table_header.html.erb +12 -13
  19. data/app/views/layouts/avo/application.html.erb +3 -0
  20. data/bin/helpers.rb +7 -1
  21. data/bin/init +2 -2
  22. data/lib/avo/app.rb +7 -3
  23. data/lib/avo/base_resource.rb +1 -0
  24. data/lib/avo/dynamic_router.rb +1 -1
  25. data/lib/avo/engine.rb +6 -16
  26. data/lib/avo/fields/belongs_to_field.rb +2 -0
  27. data/lib/avo/reloader.rb +51 -0
  28. data/lib/avo/version.rb +1 -1
  29. data/public/avo-assets/avo.css +2 -2
  30. data/public/avo-assets/avo.js +2 -2
  31. data/public/avo-assets/avo.js.map +2 -2
  32. metadata +5 -7
  33. data/app/assets/builds/avo.css +0 -8810
  34. data/app/assets/builds/avo.js +0 -423
  35. data/app/assets/builds/avo.js.map +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ab813d1cba92231f9f5ea655e200739567db3d33a93434add2000d42281f29e
4
- data.tar.gz: 6357611e7245cb45e68749b4c0e72c2c97f353d12a509b5b467f9ae7ff7a261d
3
+ metadata.gz: 1ed006aa7c57368aa03156290f54f757484daff2434ad0f235fa1cdabd0489e4
4
+ data.tar.gz: 5596ead68c48c161977b477ad5b689be6228957b56f818d0a2ba015f3bf250be
5
5
  SHA512:
6
- metadata.gz: c2723ce4a864c153c8ae515c168e50954a4415a9be8aae5b190fbe8d1fbe5d4227f165cb9f9957f606aa757cc3141e33204eacc53d270c22f14d995c0a3f285b
7
- data.tar.gz: dff128333e8a5700d12c5d951d1ba29cb9a11980b68beb137916547eb1c1ed9bcb44ccc2e3c167ce772e6e6f976a28d29bed0ffde958936f0833ff1f29427b0a
6
+ metadata.gz: 68a9010fd5eec2c2c45527cc3bb46c751eefbde06894d4e8857f9d86c8417fdc13d835422479efd5361e16c677ecfa57a3ae0a375b4cd84598d5e88bd85dd2bd
7
+ data.tar.gz: e2adc97bfcac2b881d9780d60bdc323186362f76c3139f03b0282ac1275f64cba2555c46ad6617aab3fff886c12e1322debbd1e7b38cd107f082129a6f392900
data/Gemfile CHANGED
@@ -34,7 +34,7 @@ gem "puma", "~> 5.6.4"
34
34
  # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
35
35
  # gem "jbuilder", "~> 2.7"
36
36
  # Use Redis adapter to run Action Cable in production
37
- # gem 'redis', '~> 4.0'
37
+ gem 'redis', '~> 4.0'
38
38
  # Use Active Model has_secure_password
39
39
  # gem 'bcrypt', '~> 3.1.7'
40
40
 
@@ -79,6 +79,8 @@ group :development do
79
79
  # gem 'pry-rails'
80
80
 
81
81
  gem 'htmlbeautifier'
82
+
83
+ gem "hotwire-livereload", "~> 1.1"
82
84
  end
83
85
 
84
86
  group :development, :test do
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.1.2.pre1)
4
+ avo (2.2.1)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -183,6 +183,9 @@ GEM
183
183
  hashdiff (1.0.1)
184
184
  hightop (0.3.0)
185
185
  activesupport (>= 5.2)
186
+ hotwire-livereload (1.1.0)
187
+ listen (>= 3.0.0)
188
+ rails (>= 6.0.0)
186
189
  hotwire-rails (0.1.3)
187
190
  rails (>= 6.0.0)
188
191
  stimulus-rails
@@ -297,6 +300,7 @@ GEM
297
300
  rb-fsevent (0.11.0)
298
301
  rb-inotify (0.10.1)
299
302
  ffi (~> 1.0)
303
+ redis (4.6.0)
300
304
  regexp_parser (2.2.0)
301
305
  responders (3.0.1)
302
306
  actionpack (>= 5.0)
@@ -435,6 +439,7 @@ DEPENDENCIES
435
439
  gem-release
436
440
  groupdate
437
441
  hightop
442
+ hotwire-livereload (~> 1.1)
438
443
  hotwire-rails
439
444
  htmlbeautifier
440
445
  httparty
@@ -452,6 +457,7 @@ DEPENDENCIES
452
457
  rails (~> 6.1.0)
453
458
  rails-controller-testing
454
459
  ransack
460
+ redis (~> 4.0)
455
461
  rspec-rails (~> 4.0.0)
456
462
  rubocop
457
463
  rubocop-shopify
@@ -24,8 +24,12 @@ class Avo::ButtonComponent < ViewComponent::Base
24
24
  end
25
25
 
26
26
  def args
27
- if @args[:spinner]
27
+ if @args[:loading]
28
28
  @args[:"data-controller"] = "loading-button"
29
+
30
+ if @args[:confirm]
31
+ @args[:"data-avo-confirm"] = @args.delete(:confirm)
32
+ end
29
33
  end
30
34
 
31
35
  @args[:class] = button_classes
@@ -34,7 +38,7 @@ class Avo::ButtonComponent < ViewComponent::Base
34
38
  end
35
39
 
36
40
  def button_classes
37
- classes = "button-component inline-flex flex-grow-0 items-center text-sm font-semibold leading-6 fill-current whitespace-nowrap transition duration-100 transform transition duration-100 cursor-pointer disabled:cursor-not-allowed disabled:opacity-30 border justify-center active:outline active:outline-1 #{@class}"
41
+ classes = "button-component inline-flex flex-grow-0 items-center text-sm font-semibold leading-6 fill-current whitespace-nowrap transition duration-100 transform transition duration-100 cursor-pointer disabled:cursor-not-allowed disabled:opacity-70 border justify-center active:outline active:outline-1 #{@class}"
38
42
 
39
43
  classes += " rounded" if @rounded.present?
40
44
 
@@ -7,6 +7,7 @@
7
7
  ></div>
8
8
  <div class="relative w-full" autocomplete="off">
9
9
  <%= @form.text_field @foreign_key,
10
+ type: :text,
10
11
  value: field_label,
11
12
  class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
12
13
  placeholder: @field.placeholder,
@@ -9,8 +9,11 @@ class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
9
9
 
10
10
  def disabled
11
11
  return true if @field.readonly
12
- return true if @field.target_resource.present? && @field.target_resource.model_class.name == params[:via_resource_class]
13
- return true if @field.id.to_s == params[:via_relation].to_s
12
+
13
+ # When visiting the record through it's association we keep the field disabled by default
14
+ # We make an exception when the user deliberately instructs Avo to allow detaching in this scenario
15
+ return !@field.allow_via_detaching if @field.target_resource.present? && @field.target_resource.model_class.name == params[:via_resource_class]
16
+ return !@field.allow_via_detaching if @field.id.to_s == params[:via_relation].to_s
14
17
 
15
18
  false
16
19
  end
@@ -7,31 +7,30 @@
7
7
  title: t('avo.click_to_reveal_filters'),
8
8
  'data-button': 'resource-filters',
9
9
  'data-action': 'click->toggle-panel#togglePanel',
10
- 'data-tippy': 'tooltip' do
11
- %>
12
- <%= t 'avo.filters' %>
13
- <% if params[:filters].present? %>
14
- <span class="ml-1">(<%=JSON.parse(Base64.decode64(params[:filters])).count%> applied)</span>
10
+ 'data-tippy': 'tooltip' do %>
11
+ <%= t 'avo.filters' %>
12
+ <% if params[:filters].present? %>
13
+ <span class="ml-1">(<%=JSON.parse(Base64.decode64(params[:filters])).count%> applied)</span>
14
+ <% end %>
15
15
  <% end %>
16
- <% end %>
17
- <div
16
+ <div
18
17
  class="absolute block inset-auto sm:right-0 top-full bg-white min-w-[300px] mt-2 z-20 shadow-modal rounded hidden divide-y divide-gray-300"
19
18
  data-toggle-panel-target="panel"
20
19
  >
21
- <% @filters.each do |filter| %>
22
- <%= render partial: filter.class.template, locals: {filter: filter} %>
23
- <% end %>
24
- <div class="p-4 border-gray-300 border-t">
25
- <% if params[:filters].present? %>
26
- <%= a_link helpers.resources_path(resource: @resource, filters: nil, keep_query_params: true), color: :gray, size: :sm, class: 'w-full justify-center' do %>
27
- <%= t('avo.reset_filters') %>
28
- <% end %>
29
- <% else %>
30
- <%= a_button class: 'w-full justify-center', disabled: true do %>
31
- <%= t('avo.reset_filters') %>
32
- <% end %>
20
+ <% @filters.each do |filter| %>
21
+ <%= render partial: filter.class.template, locals: {filter: filter} %>
33
22
  <% end %>
23
+ <div class="p-4 border-gray-300 border-t">
24
+ <% if params[:filters].present? %>
25
+ <%= a_link helpers.resources_path(resource: @resource, filters: nil, keep_query_params: true), color: :gray, size: :sm, class: 'w-full justify-center' do %>
26
+ <%= t('avo.reset_filters') %>
27
+ <% end %>
28
+ <% else %>
29
+ <%= a_button class: 'w-full justify-center', disabled: true do %>
30
+ <%= t('avo.reset_filters') %>
31
+ <% end %>
32
+ <% end %>
33
+ </div>
34
34
  </div>
35
35
  </div>
36
36
  </div>
37
- </div>
@@ -3,7 +3,9 @@
3
3
  <%== item_selector_init @resource %>
4
4
  >
5
5
  <div class="relative w-full pb-3/4 rounded-t overflow-hidden">
6
- <%== item_selector_input floating: true, size: :lg %>
6
+ <% if @resource.record_selector %>
7
+ <%== item_selector_input floating: true, size: :lg %>
8
+ <% end %>
7
9
  <% if cover.blank? %>
8
10
  <%= link_to cover.link_to_resource do %>
9
11
  <%= render Avo::Index::GridCoverEmptyStateComponent.new %>
@@ -2,11 +2,13 @@
2
2
  class="bg-white hover:bg-blue-50 hover:shadow-row hover:z-[21] relative z-20 border-b"
3
3
  <%== item_selector_init @resource %>
4
4
  >
5
- <td class="w-10">
6
- <div class="flex justify-center h-full">
7
- <%== item_selector_input floating: false %>
8
- </div>
9
- </td>
5
+ <% if @resource.record_selector %>
6
+ <td class="w-10">
7
+ <div class="flex justify-center h-full">
8
+ <%== item_selector_input floating: false %>
9
+ </div>
10
+ </td>
11
+ <% end %>
10
12
  <% @resource.get_fields(reflection: @reflection).each_with_index do |field, index| %>
11
13
  <%= render field.component_for_view(:index).new(field: field, resource: @resource, index: index, parent_model: @parent_model) %>
12
14
  <% end %>
@@ -1,19 +1,19 @@
1
1
  <div <%== data_attributes %>>
2
2
  <% if render_header? %>
3
3
  <div class="<%= white_panel_classes %> p-4 flex-1 flex flex-col xl:flex-row justify-between mb-6">
4
- <div class="overflow-hidden mr-4">
4
+ <div class="overflow-hidden mr-4 flex flex-col">
5
5
  <% if display_breadcrumbs? %>
6
6
  <div class="breadcrumbs truncate mb-2">
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
10
 
11
- <div class="text-2xl tracking-normal font-semibold text-gray-800 truncate" data-target="title">
12
- <%= @title %>
11
+ <div class="text-2xl tracking-normal font-semibold text-gray-800 truncate items-center flex flex-1" data-target="title">
12
+ <span><%= @title %></span>
13
13
  </div>
14
14
 
15
15
  <% if description.present? %>
16
- <div class="text-base tracking-normal font-medium text-gray-600 truncate" data-target="description">
16
+ <div class="text-base tracking-normal font-medium text-gray-600" data-target="description">
17
17
  <%== description %>
18
18
  </div>
19
19
  <% end %>
@@ -1,19 +1,23 @@
1
1
  <div class="text-black border-t border-gray-200 p-4 flex">
2
2
  <div class="flex-1 flex space-x-4">
3
- <div class="relative aspect-square w-10 h-10 overflow-hidden rounded">
4
- <%= image_tag avatar, class: "object-cover min-w-full min-h-full h-full" %>
5
- </div>
3
+ <% if avatar.present? %>
4
+ <div class="relative aspect-square w-10 h-10 overflow-hidden rounded">
5
+ <%= image_tag avatar, class: "object-cover min-w-full min-h-full h-full" %>
6
+ </div>
7
+ <% end %>
6
8
  <div class="flex flex-col">
7
9
  <div class="font-medium">
8
10
  <%= name %>
9
11
  </div>
10
- <div class="text-xs text-gray-500 uppercase">
11
- <%= title %>
12
- </div>
12
+ <% if title.present? %>
13
+ <div class="text-xs text-gray-500 uppercase">
14
+ <%= title %>
15
+ </div>
16
+ <% end %>
13
17
  </div>
14
18
  </div>
15
- <div class="relative" data-controller="toggle-panel">
16
- <% if can_destroy_user? %>
19
+ <% if can_destroy_user? %>
20
+ <div class="relative" data-controller="toggle-panel">
17
21
  <a class="flex items-center h-full cursor-pointer" data-control="profile-dots" data-action="click->toggle-panel#togglePanel">
18
22
  <%= helpers.svg 'three-dots', class: 'h-4' %>
19
23
  </a>
@@ -31,6 +35,6 @@
31
35
  <%= helpers.svg 'logout', class: 'h-4 mr-1' %> <%= t('avo.sign_out') %>
32
36
  <% end %>
33
37
  </div>
34
- <% end %>
35
- </div>
38
+ </div>
39
+ <% end %>
36
40
  </div>
@@ -13,7 +13,7 @@
13
13
  <%= t('avo.cancel').capitalize %>
14
14
  <% end %>
15
15
  <% if can_see_the_save_button? %>
16
- <%= a_button color: :green, spinner: true, type: :submit, icon: 'save' do %>
16
+ <%= a_button color: :green, loading: true, type: :submit, icon: 'save' do %>
17
17
  <%= t('avo.save').capitalize %>
18
18
  <% end %>
19
19
  <% end %>
@@ -24,7 +24,7 @@
24
24
  <%= t('avo.cancel').capitalize %>
25
25
  <% end %>
26
26
  <% if can_see_the_save_button? %>
27
- <%= a_button color: :green, spinner: true, type: :submit, icon: 'save' do %>
27
+ <%= a_button color: :green, loading: true, type: :submit, icon: 'save' do %>
28
28
  <%= t('avo.save').capitalize %>
29
29
  <% end %>
30
30
  <% end %>
@@ -18,7 +18,7 @@
18
18
  <%= t('avo.cancel').capitalize %>
19
19
  <% end %>
20
20
  <% if can_see_the_save_button? %>
21
- <%= a_button color: 'green', spinner: true, type: :submit, icon: 'save' do %>
21
+ <%= a_button color: 'green', loading: true, type: :submit, icon: 'save' do %>
22
22
  <%= t('avo.save').capitalize %>
23
23
  <% end %>
24
24
  <% end %>
@@ -30,7 +30,7 @@
30
30
  <%= t('avo.cancel').capitalize %>
31
31
  <% end %>
32
32
  <% if can_see_the_save_button? %>
33
- <%= a_button color: :green, spinner: true, type: :submit, icon: 'save' do %>
33
+ <%= a_button color: :green, loading: true, type: :submit, icon: 'save' do %>
34
34
  <%= t('avo.save').capitalize %>
35
35
  <% end %>
36
36
  <% end %>
@@ -35,12 +35,12 @@
35
35
  method: :delete,
36
36
  local: true,
37
37
  title: t('avo.delete_item', item: @resource.model.model_name.name.downcase).capitalize,
38
- spinner: true,
38
+ loading: true,
39
+ confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
39
40
  color: :red,
40
41
  icon: 'trash',
41
42
  form_class: 'flex flex-col sm:flex-row sm:inline-flex',
42
43
  data: {
43
- confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase),
44
44
  control: :destroy,
45
45
  'resource-id': @resource.model.id,
46
46
  'tippy': 'tooltip',
@@ -44,7 +44,7 @@ module Avo
44
44
  unless @index_params[:sort_by].eql? :created_at
45
45
  @query = @query.unscope(:order)
46
46
  end
47
- @query = @query.order(Arel.sql("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}"))
47
+ @query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
48
48
  end
49
49
 
50
50
  # Apply filters
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-alert */
1
2
  import { Controller } from '@hotwired/stimulus'
2
3
 
3
4
  export default class extends Controller {
@@ -6,17 +7,53 @@ export default class extends Controller {
6
7
  <div class="double-bounce2"></div>
7
8
  </div>`;
8
9
 
10
+ confirmed = false
11
+
9
12
  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)
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 {
24
+ this.applyLoader()
25
+ }
20
26
  })
21
27
  }
28
+
29
+ get button() {
30
+ return this.context.scope.element
31
+ }
32
+
33
+ get confirmationMessage() {
34
+ return this.context.scope.element.getAttribute('data-avo-confirm')
35
+ }
36
+
37
+ applyLoader() {
38
+ const { button } = this
39
+
40
+ button.style.width = `${button.getBoundingClientRect().width}px`
41
+ button.style.height = `${button.getBoundingClientRect().height}px`
42
+ button.innerHTML = this.spinnerMarkup
43
+ button.classList.add('justify-center')
44
+
45
+ setTimeout(() => {
46
+ this.markConfirmed()
47
+ button.click()
48
+ button.setAttribute('disabled', 'disabled')
49
+ }, 1)
50
+ }
51
+
52
+ markConfirmed() {
53
+ this.confirmed = true
54
+ }
55
+
56
+ markUnconfirmed() {
57
+ this.confirmed = false
58
+ }
22
59
  }
@@ -88,6 +88,9 @@ export default class extends Controller {
88
88
  } else {
89
89
  Turbo.visit(item._url, { action: 'advance' })
90
90
  }
91
+
92
+ // On searchable belongs to the class `aa-Detached` remains on the body making it unscrollable
93
+ document.body.classList.remove('aa-Detached')
91
94
  }
92
95
 
93
96
  addSource(resourceName, data) {
@@ -102,7 +105,7 @@ export default class extends Controller {
102
105
  return `${data.header.toUpperCase()} ${data.help}`
103
106
  },
104
107
  item({ item, createElement }) {
105
- let element = ''
108
+ const children = []
106
109
 
107
110
  if (item._avatar) {
108
111
  let classes
@@ -117,22 +120,37 @@ export default class extends Controller {
117
120
  break
118
121
  }
119
122
 
120
- element += `<img src="${item._avatar}" alt="${item._label}" class="flex-shrink-0 w-8 h-8 my-[2px] inline mr-2 ${classes}" />`
123
+ children.push(
124
+ createElement('img', {
125
+ src: item._avatar,
126
+ alt: item._label,
127
+ class: `flex-shrink-0 w-8 h-8 my-[2px] inline mr-2 ${classes}`,
128
+ }),
129
+ )
121
130
  }
122
- element += `<div>${item._label}`
123
131
 
132
+ const labelChildren = [item._label]
124
133
  if (item._description) {
125
- element += `<div class="aa-ItemDescription">${item._description}</div>`
134
+ labelChildren.push(
135
+ createElement(
136
+ 'div',
137
+ {
138
+ class: 'aa-ItemDescription',
139
+ },
140
+ item._description,
141
+ ),
142
+ )
126
143
  }
127
144
 
128
- element += '</div>'
145
+ children.push(createElement('div', null, labelChildren))
129
146
 
130
- return createElement('div', {
131
- class: 'flex',
132
- dangerouslySetInnerHTML: {
133
- __html: element,
147
+ return createElement(
148
+ 'div',
149
+ {
150
+ class: 'flex',
134
151
  },
135
- })
152
+ children,
153
+ )
136
154
  },
137
155
  noResults() {
138
156
  return that.translationKeys.no_item_found.replace(
@@ -1,10 +1,11 @@
1
-
2
1
  <thead class="bg-white border-b border-gray-200 pb-1">
3
- <th class="rounded-lg">
4
- <%== item_select_all_input %>
5
- </th>
6
- <%
7
- fields.each_with_index do |field, index|
2
+ <% if @resource.record_selector %>
3
+ <th class="rounded-lg">
4
+ <%== item_select_all_input %>
5
+ </th>
6
+ <% end %>
7
+ <% fields.each_with_index do |field, index| %>
8
+ <%
8
9
  if params[:sort_by] == field.id.to_s
9
10
  if params[:sort_direction] == 'asc'
10
11
  sort_by = nil
@@ -28,8 +29,7 @@
28
29
  sort_by = field.id
29
30
  sort_direction = 'desc'
30
31
  end
31
- classes = "text-gray-500 tracking-tight leading-tight text-sm font-semibold"
32
- %>
32
+ classes = "text-gray-500 tracking-tight leading-tight text-sm font-semibold" %>
33
33
  <th class="text-left uppercase px-3 py-4 whitespace-nowrap rounded-l">
34
34
  <% if field.sortable %>
35
35
  <%= link_to params.permit!.merge(sort_by: sort_by, sort_direction: sort_direction), class: "flex items-center #{classes} #{'cursor-pointer' if field.sortable}", 'data-turbo-frame': params[:turbo_frame] do %>
@@ -37,14 +37,13 @@
37
37
  <%= render partial: 'avo/partials/sortable_component', locals: {field: field} %>
38
38
  <% end %>
39
39
  <% else %>
40
- <div class="flex items-center <%= classes %>">
41
- <%= field.name %>
42
- </div>
40
+ <div class="flex items-center <%= classes %>">
41
+ <%= field.name %>
42
+ </div>
43
43
  <% end %>
44
44
  </th>
45
45
  <% end %>
46
-
47
46
  <th class="w-24">
48
- <!-- Controls cell -->
47
+ <!-- Item controls cell -->
49
48
  </th>
50
49
  </thead>
@@ -16,6 +16,9 @@
16
16
  <% else %>
17
17
  <%= javascript_include_tag "avo", "data-turbo-track": "reload", defer: true %>
18
18
  <%= stylesheet_link_tag "avo", "data-turbo-track": "reload", defer: true %>
19
+ <% if Rails.env.development? %>
20
+ <%= javascript_include_tag "hotwire-livereload", defer: true %>
21
+ <% end %>
19
22
  <% end %>
20
23
  </head>
21
24
  <body class="bg-application os-mac">
data/bin/helpers.rb CHANGED
@@ -22,8 +22,14 @@ end
22
22
 
23
23
  def ask(question:, valid_answers: [])
24
24
  puts "\n#{question} (#{valid_answers.join('/')})"
25
+ # An uppercase option is treated as a default answer. Otherwise, we disregard case, and always
26
+ # return the answer in lowercase.
27
+ default_answer = valid_answers.select { |val| val == val.upcase }.first&.downcase
25
28
 
26
- input = gets.chomp
29
+ valid_answers.map!(&:downcase)
30
+
31
+ input = gets.downcase.chomp
32
+ input = default_answer if input == ''
27
33
 
28
34
  while !valid_answers.include?(input)
29
35
  puts 'Invalid input, please try again.'
data/bin/init CHANGED
@@ -17,7 +17,7 @@ app_root do
17
17
  header 'Installing Yarn packages'
18
18
  run! 'yarn'
19
19
 
20
- if use_docker
20
+ if use_docker == 'y'
21
21
  header 'Creating the Docker volume'
22
22
  run! 'docker volume create --name=avo-db-data'
23
23
 
@@ -28,7 +28,7 @@ app_root do
28
28
  header 'Preparing the database'
29
29
  run! 'bin/rails db:setup'
30
30
 
31
- if use_docker
31
+ if use_docker == 'y'
32
32
  header 'Stopping the Docker image'
33
33
  run! 'docker-compose stop'
34
34
  end
data/lib/avo/app.rb CHANGED
@@ -79,12 +79,16 @@ module Avo
79
79
  def init_resources
80
80
  self.resources = BaseResource.descendants
81
81
  .select do |resource|
82
+ # Remove the BaseResource. We only need the descendants
82
83
  resource != BaseResource
83
84
  end
85
+ .uniq do |klass|
86
+ # On invalid resource configuration the resource classes get duplicated in `ObjectSpace`
87
+ # We need to de-duplicate them
88
+ klass.name
89
+ end
84
90
  .map do |resource|
85
- if resource.is_a? Class
86
- resource.new
87
- end
91
+ resource.new if resource.is_a? Class
88
92
  end
89
93
  end
90
94
 
@@ -43,6 +43,7 @@ module Avo
43
43
  class_attribute :after_create_path, default: :show
44
44
  class_attribute :after_update_path, default: :show
45
45
  class_attribute :invalid_fields
46
+ class_attribute :record_selector, default: true
46
47
 
47
48
  class << self
48
49
  delegate :t, to: ::I18n
@@ -1,7 +1,7 @@
1
1
  module Avo
2
2
  module DynamicRouter
3
3
  def self.routes(router)
4
- Rails.application.eager_load!
4
+ Rails.application.eager_load! unless Rails.env.production?
5
5
 
6
6
  BaseResource.descendants
7
7
  .select do |resource|
data/lib/avo/engine.rb CHANGED
@@ -19,7 +19,7 @@ module Avo
19
19
 
20
20
  config.i18n.load_path += Dir[Avo::Engine.root.join('lib', 'generators', 'avo', 'templates', 'locales', '*.{rb,yml}')]
21
21
 
22
- initializer "avo.autoload", before: :set_autoload_paths do |app|
22
+ initializer "avo.autoload" do |app|
23
23
  [
24
24
  ["app", "avo", "fields"],
25
25
  ["app", "avo", "filters"],
@@ -41,21 +41,11 @@ module Avo
41
41
  ::Avo::App.init_fields
42
42
  end
43
43
 
44
- initializer "avo.reload_avo_files" do |app|
45
- if Avo::IN_DEVELOPMENT && ENV["RELOAD_AVO_FILES"]
46
- avo_root_path = Avo::Engine.root.to_s
47
- # This should only be happening when ENV["RELOAD_AVO_FILES"] is true because it loads all the files into memory on every change and makes rails sluggish.
48
- app.config.autoload_paths += [Avo::Engine.root.join("lib", "avo").to_s]
49
- # Register reloader
50
- app.reloaders << app.config.file_watcher.new([], {
51
- Avo::Engine.root.join("lib", "avo").to_s => ["rb"]
52
- }) {}
53
-
54
- # What to do on file change
55
- config.to_prepare do
56
- Dir.glob(avo_root_path + "/lib/avo/**/*.rb".to_s).each { |c| load c }
57
- Avo::App.boot
58
- end
44
+ initializer "avo.reloader" do |app|
45
+ Avo::Reloader.new.tap do |reloader|
46
+ reloader.execute
47
+ app.reloaders << reloader
48
+ app.reloader.to_run { reloader.execute }
59
49
  end
60
50
  end
61
51