avo 2.17.1.pre.4.issue.1342 → 2.17.1.pre.5.stackedlayout

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +20 -9
  3. data/Gemfile.lock +78 -79
  4. data/app/components/avo/field_wrapper_component.html.erb +9 -11
  5. data/app/components/avo/field_wrapper_component.rb +10 -3
  6. data/app/components/avo/fields/date_field/edit_component.html.erb +6 -6
  7. data/app/components/avo/fields/date_time_field/edit_component.html.erb +6 -6
  8. data/app/components/avo/fields/edit_component.rb +1 -1
  9. data/app/components/avo/fields/show_component.rb +1 -1
  10. data/app/components/avo/fields/time_field/edit_component.html.erb +6 -6
  11. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  12. data/app/components/avo/item_switcher_component.html.erb +4 -1
  13. data/app/components/avo/panel_component.html.erb +5 -2
  14. data/app/components/avo/views/resource_edit_component.html.erb +3 -1
  15. data/app/components/avo/views/resource_index_component.html.erb +3 -1
  16. data/app/components/avo/views/resource_show_component.html.erb +5 -2
  17. data/app/controllers/avo/actions_controller.rb +6 -5
  18. data/app/controllers/avo/cards_controller.rb +12 -2
  19. data/app/javascript/js/controllers/fields/date_field_controller.js +22 -21
  20. data/app/views/avo/actions/show.html.erb +1 -1
  21. data/app/views/avo/cards/chartkick_missing.html.erb +14 -0
  22. data/avo.gemspec +2 -5
  23. data/db/factories.rb +5 -5
  24. data/lib/avo/base_action.rb +1 -1
  25. data/lib/avo/base_resource.rb +1 -0
  26. data/lib/avo/concerns/can_replace_fields.rb +36 -0
  27. data/lib/avo/concerns/has_fields.rb +0 -25
  28. data/lib/avo/configuration.rb +3 -1
  29. data/lib/avo/engine.rb +10 -1
  30. data/lib/avo/fields/base_field.rb +2 -0
  31. data/lib/avo/fields/country_field.rb +5 -1
  32. data/lib/avo/html/builder.rb +14 -0
  33. data/lib/avo/services/authorization_clients/pundit_client.rb +7 -7
  34. data/lib/avo/services/authorization_service.rb +15 -14
  35. data/lib/avo/version.rb +1 -1
  36. data/lib/generators/avo/templates/initializer/avo.tt +2 -1
  37. data/public/avo-assets/avo.base.css +5 -0
  38. data/public/avo-assets/avo.base.js +1 -1
  39. data/public/avo-assets/avo.base.js.map +2 -2
  40. metadata +10 -50
@@ -1,5 +1,7 @@
1
1
  <%= content_tag :div,
2
2
  data: {
3
+ model_name: @resource.model_name.to_s,
4
+ resource_name: @resource.class.to_s,
3
5
  **@resource.stimulus_data_attributes
4
6
  } do %>
5
7
  <%= render Avo::PanelComponent.new(name: title, description: description, data: { component: 'resources-index' }, display_breadcrumbs: @reflection.blank?) do |c| %>
@@ -46,7 +48,7 @@
46
48
  </div>
47
49
  <% if view_type.to_sym == :table %>
48
50
  <% if @resources.present? %>
49
- <div class="w-full relative flex-1 flex mac-styled-scrollbar overflow-auto mt-0">
51
+ <div class="w-full relative flex-1 flex mt-0">
50
52
  <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
51
53
  </div>
52
54
  <% else %>
@@ -1,6 +1,8 @@
1
1
  <%= content_tag :div,
2
2
  data: {
3
- 'model-id': @resource.model.id,
3
+ model_name: @resource.model_name.to_s,
4
+ resource_name: @resource.class.to_s,
5
+ model_id: @resource.model.id,
4
6
  selected_resources_name: @resource.model_key,
5
7
  selected_resources: [@resource.model.id],
6
8
  **@resource.stimulus_data_attributes
@@ -154,7 +156,8 @@
154
156
  <% end %>
155
157
  <% if main_panel.present? %>
156
158
  <% c.body do %>
157
- <div class="divide-y">
159
+ <%# the overflow helps with long values %>
160
+ <div class="divide-y overflow-auto">
158
161
  <% main_panel.items.each_with_index do |field, index| %>
159
162
  <%= render field
160
163
  .hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: view)
@@ -12,7 +12,10 @@ module Avo
12
12
  before_action :set_action, only: [:show, :handle]
13
13
 
14
14
  def show
15
- @resource.hydrate(model: @model, view: :show, user: _current_user, params: params)
15
+ # Se the view to :new so the default value gets prefilled
16
+ @view = :new
17
+
18
+ @resource.hydrate(model: @model, view: @view, user: _current_user, params: params)
16
19
  @model = ActionModel.new @action.get_attributes_for_action
17
20
  end
18
21
 
@@ -48,17 +51,15 @@ module Avo
48
51
  end
49
52
 
50
53
  def set_action
51
- @action = action_class.new(model: @model, resource: @resource, user: _current_user, view: :edit)
54
+ @action = action_class.new(model: @model, resource: @resource, user: _current_user, view: @view)
52
55
  end
53
56
 
54
57
  def action_class
55
58
  klass_name = params[:action_id].gsub("avo_actions_", "").camelize
56
59
 
57
- valid_klass = Avo::BaseAction.descendants.find do |action|
60
+ Avo::BaseAction.descendants.find do |action|
58
61
  action.to_s == klass_name
59
62
  end
60
-
61
- valid_klass
62
63
  end
63
64
 
64
65
  def respond(response)
@@ -2,10 +2,12 @@ require_dependency "avo/application_controller"
2
2
 
3
3
  module Avo
4
4
  class CardsController < ApplicationController
5
- before_action :set_dashboard, only: :show
6
- before_action :set_card, only: :show
5
+ before_action :set_dashboard
6
+ before_action :set_card
7
+ before_action :detect_chartkick
7
8
 
8
9
  def show
10
+ render(:chartkick_missing) unless @chartkick_installed
9
11
  end
10
12
 
11
13
  private
@@ -21,5 +23,13 @@ module Avo
21
23
  card.hydrate(dashboard: @dashboard, params: params)
22
24
  end
23
25
  end
26
+
27
+ def detect_chartkick
28
+ @chartkick_installed = if @card.class.ancestors.map(&:to_s).include?("Avo::Dashboards::ChartkickCard")
29
+ defined?(Chartkick)
30
+ else
31
+ true
32
+ end
33
+ end
24
34
  end
25
35
  end
@@ -72,6 +72,10 @@ export default class extends Controller {
72
72
  return this.fieldTypeValue === 'time'
73
73
  }
74
74
 
75
+ get fieldHasTime() {
76
+ return this.fieldIsTime || this.fieldIsDateTime
77
+ }
78
+
75
79
  // Parse the time as if it were UTC
76
80
  get parsedValue() {
77
81
  return DateTime.fromISO(this.initialValue, { zone: 'UTC' })
@@ -110,7 +114,7 @@ export default class extends Controller {
110
114
  let value = this.parsedValue
111
115
 
112
116
  // Set the zone only if the type of field is date time or relative time.
113
- if (this.enableTimeValue && this.relativeValue) {
117
+ if (this.fieldHasTime && this.relativeValue) {
114
118
  value = value.setZone(this.displayTimezone)
115
119
  }
116
120
 
@@ -148,16 +152,22 @@ export default class extends Controller {
148
152
  // Hide calendar and only keep time picker.
149
153
  options.noCalendar = this.noCalendarValue
150
154
 
155
+ if (this.fieldHasTime) {
156
+ options.dateFormat = 'Y-m-d H:i:S'
157
+ }
158
+
151
159
  if (this.initialValue) {
152
- // Enable timezone display
153
- if (this.enableTimeValue && this.relativeValue) {
154
- options.defaultDate = this.parsedValue.setZone(this.displayTimezone).toISO()
155
-
156
- options.dateFormat = 'Y-m-d H:i:S'
157
- } else {
158
- // 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.
159
- // Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
160
- options.defaultDate = universalTimestamp(this.initialValue)
160
+ switch (this.fieldTypeValue) {
161
+ case 'date':
162
+ options.defaultDate = universalTimestamp(this.initialValue)
163
+ break
164
+ default:
165
+ case 'time':
166
+ options.defaultDate = this.parsedValue.setZone(this.displayTimezone, { keepLocalTime: !this.relativeValue }).toISO()
167
+ break
168
+ case 'dateTime':
169
+ options.defaultDate = this.parsedValue.setZone(this.displayTimezone, { keepLocalTime: !this.relativeValue }).toISO()
170
+ break
161
171
  }
162
172
  }
163
173
 
@@ -194,27 +204,18 @@ export default class extends Controller {
194
204
  return
195
205
  }
196
206
 
197
- let args = {}
198
-
199
- // For values that involve time we should keep the local time.
200
- if (this.timezoneValue || !this.relativeValue) {
201
- args = { keepLocalTime: true }
202
- } else {
203
- args = { keepLocalTime: false }
204
- }
205
-
206
207
  let value
207
208
  switch (this.fieldTypeValue) {
208
209
  case 'time':
209
210
  // For time values, we should maintain the real value and format it to a time-friendly format.
210
- value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', args).toFormat(RAW_TIME_FORMAT)
211
+ value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: !this.relativeValue }).toFormat(RAW_TIME_FORMAT)
211
212
  break
212
213
  case 'date':
213
214
  value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: true }).toFormat(RAW_DATE_FORMAT)
214
215
  break
215
216
  default:
216
217
  case 'dateTime':
217
- value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', args).toISO()
218
+ value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: !this.relativeValue }).toISO()
218
219
  break
219
220
  }
220
221
 
@@ -27,7 +27,7 @@
27
27
  <% @action.get_fields.each_with_index do |field, index| %>
28
28
  <%= render field
29
29
  .hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: @view)
30
- .component_for_view(:edit)
30
+ .component_for_view(@view)
31
31
  .new(field: field, resource: @resource, index: index, form: form, compact: true)
32
32
  %>
33
33
  <% end %>
@@ -0,0 +1,14 @@
1
+ <%= render Avo::TurboFrameWrapperComponent.new(params[:turbo_frame]) do %>
2
+ <div class="w-full h-full flex items-center text-center">
3
+ <div class="flex-1">
4
+ Chartkick is missing.
5
+ <a
6
+ href="https://docs.avohq.io/2.0/upgrade.html#upgrade-from-2-17-to-2-18"
7
+ target="_blank"
8
+ >
9
+ Manually require it in your
10
+ <code class="inline">Gemfile</code>
11
+ </a>.
12
+ </div>
13
+ </div>
14
+ <% end %>
data/avo.gemspec CHANGED
@@ -32,20 +32,17 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  spec.files = Dir["{bin,app,config,db,lib,public}/**/*", "MIT-LICENSE", "Rakefile", "README.md", "avo.gemspec", "Gemfile", "Gemfile.lock"]
34
34
 
35
- spec.add_dependency "rails", ">= 6.0"
35
+ spec.add_dependency "activerecord", ">= 6.0"
36
+ spec.add_dependency "actionview", ">= 6.0"
36
37
  spec.add_dependency "pagy"
37
38
  spec.add_dependency "zeitwerk"
38
- spec.add_dependency "countries"
39
- spec.add_dependency "pundit"
40
39
  spec.add_dependency "httparty"
41
40
  spec.add_dependency "active_link_to"
42
- spec.add_dependency "image_processing"
43
41
  spec.add_dependency "view_component"
44
42
  spec.add_dependency "turbo-rails"
45
43
  spec.add_dependency "addressable"
46
44
  spec.add_dependency "meta-tags"
47
45
  spec.add_dependency "breadcrumbs_on_rails"
48
- spec.add_dependency "chartkick"
49
46
  spec.add_dependency "dry-initializer"
50
47
  spec.add_dependency "docile"
51
48
  spec.add_dependency "inline_svg"
data/db/factories.rb CHANGED
@@ -90,10 +90,10 @@ FactoryBot.define do
90
90
  end
91
91
 
92
92
  factory :product do
93
- title { "MyString" }
94
- description { "MyText" }
95
- price { 1 }
96
- status { "MyString" }
97
- category { "MyString" }
93
+ title { Faker::App.name }
94
+ description { Faker::Lorem.paragraphs(number: rand(1...3)).join("\n") }
95
+ price { rand(10000..999000) }
96
+ status { "status" }
97
+ category { ::Product.categories.values.sample }
98
98
  end
99
99
  end
@@ -72,7 +72,7 @@ module Avo
72
72
 
73
73
  def get_attributes_for_action
74
74
  get_fields.map do |field|
75
- [field.id, field.value]
75
+ [field.id, field.value || field.default]
76
76
  end.to_h
77
77
  end
78
78
 
@@ -4,6 +4,7 @@ module Avo
4
4
 
5
5
  include ActionView::Helpers::UrlHelper
6
6
  include Avo::Concerns::HasFields
7
+ include Avo::Concerns::CanReplaceFields
7
8
  include Avo::Concerns::HasEditableControls
8
9
  include Avo::Concerns::HasStimulusControllers
9
10
  include Avo::Concerns::ModelClassConstantized
@@ -0,0 +1,36 @@
1
+ module Avo
2
+ module Concerns
3
+ module CanReplaceFields
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :backup_items_holder
8
+ end
9
+
10
+ class_methods do
11
+ def with_temporary_items(&block)
12
+ # back-up the previous items
13
+ self.backup_items_holder = items_holder
14
+
15
+ self.items_holder = Avo::ItemsHolder.new
16
+
17
+ instance_eval(&block)
18
+ end
19
+
20
+ def restore_items_from_backup
21
+ self.items_holder = backup_items_holder if backup_items_holder.present?
22
+ end
23
+
24
+ def with_new_items(&block)
25
+ self.items_holder = Avo::ItemsHolder.new
26
+
27
+ instance_eval(&block)
28
+ end
29
+ end
30
+
31
+ def with_new_items(&block)
32
+ self.class.with_new_items(&block)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -10,8 +10,6 @@ module Avo
10
10
  class_attribute :tabs_tabs_holder
11
11
  class_attribute :raw_tabs
12
12
  class_attribute :tools_holder
13
-
14
- class_attribute :backup_items_holder
15
13
  end
16
14
 
17
15
  class_methods do
@@ -105,25 +103,6 @@ module Avo
105
103
  end
106
104
  end
107
105
 
108
- def with_temporary_items(&block)
109
- # back-up the previous items
110
- self.backup_items_holder = items_holder
111
-
112
- self.items_holder = Avo::ItemsHolder.new
113
-
114
- instance_eval(&block)
115
- end
116
-
117
- def restore_items_from_backup
118
- self.items_holder = backup_items_holder if backup_items_holder.present?
119
- end
120
-
121
- def with_new_items(&block)
122
- self.items_holder = Avo::ItemsHolder.new
123
-
124
- instance_eval(&block)
125
- end
126
-
127
106
  private
128
107
 
129
108
  def extract_fields_from_items(thing)
@@ -332,10 +311,6 @@ module Avo
332
311
  [main_panel, *panelfull_items]
333
312
  end
334
313
 
335
- def with_new_items(&block)
336
- self.class.with_new_items(&block)
337
- end
338
-
339
314
  private
340
315
 
341
316
  def check_license
@@ -38,6 +38,7 @@ module Avo
38
38
  attr_accessor :tabs_style
39
39
  attr_accessor :resource_default_view
40
40
  attr_accessor :authorization_client
41
+ attr_accessor :field_wrapper_layout
41
42
  attr_writer :branding
42
43
 
43
44
  def initialize
@@ -86,7 +87,8 @@ module Avo
86
87
  @model_resource_mapping = {}
87
88
  @tabs_style = :tabs
88
89
  @resource_default_view = :show
89
- @authorization_client = nil
90
+ @authorization_client = :pundit
91
+ @field_wrapper_layout = :inline
90
92
  end
91
93
 
92
94
  def current_user_method(&block)
data/lib/avo/engine.rb CHANGED
@@ -1,6 +1,15 @@
1
1
  # requires all dependencies
2
2
  Gem.loaded_specs["avo"].dependencies.each do |d|
3
- require d.name
3
+ case d.name
4
+ when "activerecord"
5
+ require "active_record/railtie"
6
+ when "actionview"
7
+ require "action_view/railtie"
8
+ when "activestorage"
9
+ require "active_storage/engine"
10
+ else
11
+ require d.name
12
+ end
4
13
  end
5
14
 
6
15
  # In development we should load the engine so we get the autoload for components
@@ -36,6 +36,7 @@ module Avo
36
36
  attr_reader :as_avatar
37
37
  attr_reader :as_description
38
38
  attr_reader :index_text_align
39
+ attr_reader :stacked
39
40
 
40
41
  # Private options
41
42
  attr_reader :updatable
@@ -78,6 +79,7 @@ module Avo
78
79
  @html = args[:html] || nil
79
80
  @view = args[:view] || nil
80
81
  @value = args[:value] || nil
82
+ @stacked = args[:stacked] || nil
81
83
 
82
84
  @args = args
83
85
 
@@ -11,7 +11,11 @@ module Avo
11
11
 
12
12
  super(id, **args, &block)
13
13
 
14
- @countries = ISO3166::Country.translations.sort_by { |code, name| name }.to_h
14
+ @countries = begin
15
+ ISO3166::Country.translations.sort_by { |code, name| name }.to_h
16
+ rescue
17
+ {none: "You need to install the countries gem for this field to work properly"}
18
+ end
15
19
  @display_code = args[:display_code].present? ? args[:display_code] : false
16
20
  end
17
21
 
@@ -13,6 +13,8 @@ class Avo::HTML::Builder
13
13
  attr_accessor :edit_stack
14
14
  attr_accessor :index_stack
15
15
  attr_accessor :input_stack
16
+ attr_accessor :label_stack
17
+ attr_accessor :content_stack
16
18
 
17
19
  attr_accessor :record
18
20
  attr_accessor :resource
@@ -30,6 +32,8 @@ class Avo::HTML::Builder
30
32
  @edit_stack = {}
31
33
  @index_stack = {}
32
34
  @input_stack = {}
35
+ @label_stack = {}
36
+ @content_stack = {}
33
37
 
34
38
  @record = record
35
39
  @resource = resource
@@ -77,6 +81,16 @@ class Avo::HTML::Builder
77
81
  capture_block :input, &block
78
82
  end
79
83
 
84
+ # Takes a block
85
+ def label(&block)
86
+ capture_block :label, &block
87
+ end
88
+
89
+ # Takes a block
90
+ def content(&block)
91
+ capture_block :content, &block
92
+ end
93
+
80
94
  # Takes a block
81
95
  def show(&block)
82
96
  capture_block :show, &block
@@ -4,10 +4,10 @@ module Avo
4
4
  class PunditClient
5
5
  def authorize(user, record, action, policy_class: nil)
6
6
  Pundit.authorize(user, record, action, policy_class: policy_class)
7
- rescue Pundit::NotDefinedError
8
- raise NoPolicyError
9
- rescue Pundit::NotAuthorizedError
10
- raise NotAuthorizedError
7
+ rescue Pundit::NotDefinedError => error
8
+ raise NoPolicyError.new error.message
9
+ rescue Pundit::NotAuthorizedError => error
10
+ raise NotAuthorizedError.new error.message
11
11
  end
12
12
 
13
13
  def policy(user, record)
@@ -16,8 +16,8 @@ module Avo
16
16
 
17
17
  def policy!(user, record)
18
18
  Pundit.policy!(user, record)
19
- rescue Pundit::NotDefinedError
20
- raise NoPolicyError
19
+ rescue Pundit::NotDefinedError => error
20
+ raise NoPolicyError.new error.message
21
21
  end
22
22
 
23
23
  def apply_policy(user, model, policy_class: nil)
@@ -32,7 +32,7 @@ module Avo
32
32
  Pundit.policy_scope!(user, model)
33
33
  end
34
34
  rescue Pundit::NotDefinedError => error
35
- raise NoPolicyError
35
+ raise NoPolicyError.new error.message
36
36
  end
37
37
 
38
38
  private
@@ -7,7 +7,20 @@ module Avo
7
7
 
8
8
  class << self
9
9
  def client
10
- (configuration_client || default_client).new
10
+ client = Avo.configuration.authorization_client
11
+
12
+ klass = case client
13
+ when :pundit, nil
14
+ pundit_client
15
+ else
16
+ if client.is_a?(String)
17
+ client.safe_constantize
18
+ else
19
+ client
20
+ end
21
+ end
22
+
23
+ klass.new
11
24
  end
12
25
 
13
26
  def authorize(user, record, action, policy_class: nil, **args)
@@ -78,19 +91,7 @@ module Avo
78
91
  end
79
92
  end
80
93
 
81
- def configuration_client
82
- client = Avo.configuration.authorization_client
83
-
84
- return if client.blank?
85
-
86
- if client.is_a?(String)
87
- client.safe_constantize
88
- else
89
- client
90
- end
91
- end
92
-
93
- def default_client
94
+ def pundit_client
94
95
  Avo::Services::AuthorizationClients::PunditClient
95
96
  end
96
97
  end
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "2.17.1.pre.4.issue.1342" unless const_defined?(:VERSION)
2
+ VERSION = "2.17.1.pre.5.stackedlayout" unless const_defined?(:VERSION)
3
3
  end
@@ -30,7 +30,7 @@ Avo.configure do |config|
30
30
  # destroy: 'destroy?',
31
31
  # }
32
32
  # config.raise_error_on_missing_policy = false
33
- # config.authorization_client = false
33
+ # config.authorization_client = :pundit
34
34
 
35
35
  ## == Localization ==
36
36
  # config.locale = 'en-US'
@@ -59,6 +59,7 @@ Avo.configure do |config|
59
59
  # config.resource_controls = :right
60
60
  # config.tabs_style = :tabs # can be :tabs or :pills
61
61
  # config.buttons_on_form_footers = true
62
+ # config.field_wrapper_layout = true
62
63
 
63
64
  ## == Branding ==
64
65
  # config.branding = {
@@ -8034,6 +8034,11 @@ trix-toolbar .trix-button-group:not(:first-child){
8034
8034
  color:rgb(22 163 74 / var(--tw-text-opacity))
8035
8035
  }
8036
8036
 
8037
+ .\!text-pink-600{
8038
+ --tw-text-opacity:1 !important;
8039
+ color:rgb(219 39 119 / var(--tw-text-opacity)) !important
8040
+ }
8041
+
8037
8042
  .underline{
8038
8043
  -webkit-text-decoration-line:underline;
8039
8044
  text-decoration-line:underline