avo 2.9.2.pre1 → 2.10.0

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

Potentially problematic release.


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

Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +5 -7
  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/date_field/edit_component.html.erb +1 -0
  11. data/app/components/avo/fields/date_time_field/edit_component.html.erb +10 -25
  12. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -9
  13. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -9
  14. data/app/components/avo/fields/edit_component.rb +5 -0
  15. data/app/components/avo/fields/show_component.rb +1 -1
  16. data/app/components/avo/index/ordering/button_component.rb +2 -12
  17. data/app/components/avo/index/resource_controls_component.html.erb +2 -2
  18. data/app/components/avo/index/resource_controls_component.rb +5 -1
  19. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  20. data/app/components/avo/index/table_row_component.html.erb +1 -1
  21. data/app/components/avo/item_switcher_component.html.erb +19 -0
  22. data/app/components/avo/item_switcher_component.rb +45 -0
  23. data/app/components/avo/panel_component.html.erb +23 -24
  24. data/app/components/avo/panel_component.rb +8 -5
  25. data/app/components/avo/tab_group_component.html.erb +53 -0
  26. data/app/components/avo/tab_group_component.rb +51 -0
  27. data/app/components/avo/tab_switcher_component.html.erb +21 -0
  28. data/app/components/avo/tab_switcher_component.rb +86 -0
  29. data/app/components/avo/views/resource_edit_component.html.erb +34 -56
  30. data/app/components/avo/views/resource_edit_component.rb +11 -1
  31. data/app/components/avo/views/resource_index_component.html.erb +1 -1
  32. data/app/components/avo/views/resource_index_component.rb +3 -3
  33. data/app/components/avo/views/resource_show_component.html.erb +58 -89
  34. data/app/components/avo/views/resource_show_component.rb +2 -2
  35. data/app/controllers/avo/actions_controller.rb +1 -1
  36. data/app/controllers/avo/application_controller.rb +20 -3
  37. data/app/helpers/avo/application_helper.rb +0 -6
  38. data/app/helpers/avo/url_helpers.rb +1 -1
  39. data/app/javascript/avo.js +5 -1
  40. data/app/javascript/js/controllers/fields/date_field_controller.js +25 -87
  41. data/app/javascript/js/controllers/loading_button_controller.js +25 -21
  42. data/app/javascript/js/controllers/tabs_controller.js +86 -0
  43. data/app/javascript/js/controllers.js +2 -0
  44. data/app/views/avo/base/index.html.erb +1 -1
  45. data/app/views/avo/base/show.html.erb +1 -1
  46. data/app/views/avo/cards/show.html.erb +1 -1
  47. data/app/views/avo/debug/index.html.erb +1 -1
  48. data/app/views/avo/home/_actions.html.erb +1 -1
  49. data/app/views/avo/home/_dashboards.html.erb +19 -0
  50. data/app/views/avo/home/_filters.html.erb +1 -1
  51. data/app/views/avo/home/_resources.html.erb +1 -1
  52. data/app/views/avo/home/failed_to_load.html.erb +1 -1
  53. data/app/views/avo/home/index.html.erb +14 -2
  54. data/app/views/avo/partials/_javascript.html.erb +1 -1
  55. data/app/views/avo/partials/_tabs_toggle.html.erb +20 -0
  56. data/app/views/avo/private/design.html.erb +1 -1
  57. data/config/routes.rb +1 -1
  58. data/db/migrate/20210421064037_add_color_to_teams.rb +5 -0
  59. data/db/migrate/20210423075924_add_progress_to_projects.rb +5 -0
  60. data/db/migrate/20210525143134_add_slug_to_users.rb +6 -0
  61. data/lib/avo/app.rb +11 -4
  62. data/lib/avo/base_action.rb +2 -19
  63. data/lib/avo/base_card.rb +1 -7
  64. data/lib/avo/base_resource.rb +1 -95
  65. data/lib/avo/base_resource_tool.rb +3 -1
  66. data/lib/avo/concerns/handles_field_args.rb +1 -1
  67. data/lib/avo/concerns/has_fields.rb +247 -50
  68. data/lib/avo/concerns/has_html_attributes.rb +1 -1
  69. data/lib/avo/concerns/is_resource_item.rb +36 -0
  70. data/lib/avo/concerns/model_class_constantized.rb +23 -0
  71. data/lib/avo/dashboards/base_dashboard.rb +1 -1
  72. data/lib/avo/dsl/field_parser.rb +83 -0
  73. data/lib/avo/fields/base_field.rb +19 -2
  74. data/lib/avo/fields/date_field.rb +2 -0
  75. data/lib/avo/fields/date_time_field.rb +9 -21
  76. data/lib/avo/fields/field_extensions/visible_in_different_views.rb +18 -1
  77. data/lib/avo/fields/has_base_field.rb +20 -1
  78. data/lib/avo/fields/has_one_field.rb +4 -1
  79. data/lib/avo/grid_collector.rb +6 -3
  80. data/lib/avo/items_holder.rb +68 -0
  81. data/lib/avo/licensing/h_q.rb +10 -0
  82. data/lib/avo/main_panel.rb +3 -0
  83. data/lib/avo/menu/builder.rb +8 -7
  84. data/lib/avo/panel.rb +25 -0
  85. data/lib/avo/panel_builder.rb +23 -0
  86. data/lib/avo/services/uri_service.rb +71 -0
  87. data/lib/avo/tab.rb +78 -0
  88. data/lib/avo/tab_builder.rb +25 -0
  89. data/lib/avo/tab_group.rb +40 -0
  90. data/lib/avo/tab_group_builder.rb +43 -0
  91. data/lib/avo/version.rb +1 -1
  92. data/lib/avo.rb +1 -0
  93. data/lib/generators/avo/templates/locales/avo.fr.yml +115 -0
  94. data/lib/generators/avo/templates/resource/controller.tt +2 -0
  95. data/lib/generators/avo/templates/resource_tools/partial.tt +1 -1
  96. data/lib/generators/avo/templates/tool/view.tt +1 -1
  97. data/public/avo-assets/avo.css +27 -3
  98. data/public/avo-assets/avo.js +77 -77
  99. data/public/avo-assets/avo.js.map +3 -3
  100. metadata +28 -13
  101. data/app/assets/builds/action_cable.js +0 -2
  102. data/app/assets/builds/action_cable.js.map +0 -7
  103. data/app/assets/builds/application.js +0 -2
  104. data/app/assets/builds/application.js.map +0 -7
  105. data/app/assets/builds/avo.css +0 -9028
  106. data/app/assets/builds/avo.js +0 -512
  107. data/app/assets/builds/avo.js.map +0 -7
  108. data/app/assets/builds/avo_custom.js +0 -6
  109. data/app/assets/builds/avo_custom.js.map +0 -7
  110. data/lib/avo/concerns/has_tools.rb +0 -47
@@ -4,8 +4,11 @@ module Avo
4
4
  extend ActiveSupport::DescendantsTracker
5
5
  extend Avo::Fields::FieldExtensions::HasFieldName
6
6
 
7
- include Avo::Fields::FieldExtensions::VisibleInDifferentViews
7
+ include Avo::Concerns::IsResourceItem
8
+ include Avo::Concerns::HandlesFieldArgs
9
+
8
10
  include ActionView::Helpers::UrlHelper
11
+ include Avo::Fields::FieldExtensions::VisibleInDifferentViews
9
12
 
10
13
  include Avo::Concerns::HandlesFieldArgs
11
14
  include Avo::Concerns::HasHTMLAttributes
@@ -48,8 +51,9 @@ module Avo
48
51
  attr_reader :panel_name
49
52
 
50
53
  class_attribute :field_name_attribute
54
+ class_attribute :item_type, default: :field
51
55
 
52
- def initialize(id, _options: {}, **args, &block)
56
+ def initialize(id, **args, &block)
53
57
  super(id, **args, &block)
54
58
 
55
59
  @id = id
@@ -206,9 +210,14 @@ module Avo
206
210
 
207
211
  # Try and build the component class or fallback to a blank one
208
212
  def component_for_view(view = :index)
213
+ # Use the edit variant for all "update" views
214
+ view = :edit if view.in? [:new, :create, :update]
215
+
209
216
  component_class = "::Avo::Fields::#{view_component_name}::#{view.to_s.camelize}Component"
210
217
  component_class.constantize
211
218
  rescue
219
+ # When returning nil, a race condition happens and throws an error in some environments.
220
+ # See https://github.com/avo-hq/avo/pull/365
212
221
  ::Avo::BlankFieldComponent
213
222
  end
214
223
 
@@ -228,6 +237,14 @@ module Avo
228
237
  true
229
238
  end
230
239
 
240
+ def visible_in_reflection?
241
+ true
242
+ end
243
+
244
+ def hidden_in_reflection?
245
+ !visible_in_reflection?
246
+ end
247
+
231
248
  private
232
249
 
233
250
  def model_or_class(model)
@@ -3,6 +3,7 @@ module Avo
3
3
  class DateField < TextField
4
4
  attr_reader :first_day_of_week
5
5
  attr_reader :picker_format
6
+ attr_reader :disable_mobile
6
7
  attr_reader :format
7
8
  attr_reader :relative
8
9
 
@@ -13,6 +14,7 @@ module Avo
13
14
  @picker_format = args[:picker_format].present? ? args[:picker_format] : "Y-m-d"
14
15
  @format = args[:format].present? ? args[:format] : :long
15
16
  @relative = args[:relative].present? ? args[:relative] : false
17
+ @disable_mobile = args[:disable_mobile].present? ? args[:disable_mobile] : false
16
18
  end
17
19
 
18
20
  def formatted_value
@@ -2,29 +2,25 @@ module Avo
2
2
  module Fields
3
3
  class DateTimeField < DateField
4
4
  attr_reader :format
5
- attr_reader :picker_format
6
5
  attr_reader :time_24hr
7
6
  attr_reader :timezone
8
7
 
9
8
  def initialize(id, **args, &block)
10
9
  super(id, **args, &block)
11
10
 
12
- add_boolean_prop args, :time_24hr
13
- add_string_prop args, :picker_format, "Y-m-d H:i:S"
14
- add_string_prop args, :format, "yyyy-LL-dd TT"
15
- add_string_prop args, :timezone
11
+ @picker_format = args[:picker_format].present? ? args[:picker_format] : "Y-m-d H:i:S"
12
+ @time_24hr = args[:time_24hr].present? ? args[:time_24hr] : false
13
+ @timezone = args[:timezone].present? ? args[:timezone] : Rails.application.config.time_zone
16
14
  end
17
15
 
18
16
  def formatted_value
19
17
  return nil if value.nil?
20
18
 
21
- value.utc.to_time.iso8601
22
- end
23
-
24
- def edit_formatted_value
25
- return nil if value.nil?
26
-
27
- value.utc.iso8601
19
+ if @format.is_a?(Symbol)
20
+ value.to_time.in_time_zone(timezone).to_formatted_s(@format)
21
+ else
22
+ value.to_time.in_time_zone(timezone).strftime(@format)
23
+ end
28
24
  end
29
25
 
30
26
  def fill_field(model, key, value, params)
@@ -36,18 +32,10 @@ module Avo
36
32
 
37
33
  return model if value.blank?
38
34
 
39
- model[id] = utc_time(value)
35
+ model[id] = value.to_time.in_time_zone(Rails.application.config.time_zone)
40
36
 
41
37
  model
42
38
  end
43
-
44
- def utc_time(value)
45
- if timezone.present?
46
- ActiveSupport::TimeZone.new(timezone).local_to_utc(Time.parse(value))
47
- else
48
- value
49
- end
50
- end
51
39
  end
52
40
  end
53
41
  end
@@ -7,13 +7,20 @@ module Avo
7
7
  attr_accessor :show_on_new
8
8
  attr_accessor :show_on_edit
9
9
 
10
- def initialize(id, **args, &block)
10
+ def initialize(id = nil, **args, &block)
11
11
  @show_on_index = @show_on_index.nil? ? true : @show_on_index
12
12
  @show_on_show = @show_on_show.nil? ? true : @show_on_show
13
13
  @show_on_new = @show_on_new.nil? ? true : @show_on_new
14
14
  @show_on_edit = @show_on_edit.nil? ? true : @show_on_edit
15
15
  end
16
16
 
17
+ # Validates if the field is visible on certain view
18
+ def visible_on?(view)
19
+ raise "No view specified on visibility check." if view.blank?
20
+
21
+ send :"show_on_#{view.to_s}"
22
+ end
23
+
17
24
  def show_on(*where)
18
25
  return show_on_all if where.include? :all
19
26
 
@@ -44,6 +51,16 @@ module Avo
44
51
  end
45
52
  end
46
53
 
54
+ # When submitting the form on creation, the new page will be create but we don't have a visibility marker for create so we'll default to new
55
+ def show_on_create
56
+ show_on_new
57
+ end
58
+
59
+ # When submitting the form on update, the new page will be create but we don't have a visibility marker for update so we'll default to edit
60
+ def show_on_update
61
+ show_on_edit
62
+ end
63
+
47
64
  private
48
65
 
49
66
  def show_on_view(view)
@@ -29,7 +29,10 @@ module Avo
29
29
  end
30
30
 
31
31
  def frame_url
32
- "#{@resource.record_path}/#{id}?turbo_frame=#{turbo_frame}"
32
+ Avo::Services::URIService.parse(@resource.record_path)
33
+ .append_path(id.to_s)
34
+ .append_query(turbo_frame: turbo_frame.to_s)
35
+ .to_s
33
36
  end
34
37
 
35
38
  # The value
@@ -59,6 +62,22 @@ module Avo
59
62
  def placeholder
60
63
  @placeholder || I18n.t("avo.choose_an_option")
61
64
  end
65
+
66
+ def has_own_panel?
67
+ true
68
+ end
69
+
70
+ def visible_in_reflection?
71
+ false
72
+ end
73
+
74
+ # Adds the view override component
75
+ # has_one, has_many, has_and_belongs_to_many fields don't have edit views
76
+ def component_for_view(view = :index)
77
+ view = :show if view.in? [:new, :create, :update, :edit]
78
+
79
+ super view
80
+ end
62
81
  end
63
82
  end
64
83
  end
@@ -22,7 +22,10 @@ module Avo
22
22
  end
23
23
 
24
24
  def frame_url
25
- "#{@resource.record_path}/#{id}/#{value.id}?turbo_frame=#{turbo_frame}"
25
+ Avo::Services::URIService.parse(@resource.record_path)
26
+ .append_paths(id, value.id)
27
+ .append_query(turbo_frame: turbo_frame)
28
+ .to_s
26
29
  end
27
30
 
28
31
  def fill_field(model, key, value, params)
@@ -13,15 +13,18 @@ module Avo
13
13
  end
14
14
 
15
15
  def cover(field_name, as:, **args, &block)
16
- self.cover_field = self.class.parse_field(field_name, as: as, **args, &block)
16
+ field_parser = Avo::Dsl::FieldParser.new(id: field_name, as: as, order_index: items_index, **args, &block).parse
17
+ self.cover_field = field_parser.instance if field_parser.valid?
17
18
  end
18
19
 
19
20
  def title(field_name, as:, **args, &block)
20
- self.title_field = self.class.parse_field(field_name, as: as, **args, &block)
21
+ field_parser = Avo::Dsl::FieldParser.new(id: field_name, as: as, order_index: items_index, **args, &block).parse
22
+ self.title_field = field_parser.instance if field_parser.valid?
21
23
  end
22
24
 
23
25
  def body(field_name, as:, **args, &block)
24
- self.body_field = self.class.parse_field(field_name, as: as, **args, &block)
26
+ field_parser = Avo::Dsl::FieldParser.new(id: field_name, as: as, order_index: items_index, **args, &block).parse
27
+ self.body_field = field_parser.instance if field_parser.valid?
25
28
  end
26
29
 
27
30
  def hydrate(model:, view:, resource:)
@@ -0,0 +1,68 @@
1
+ module Avo
2
+ class ItemsHolder
3
+ attr_reader :tools
4
+ attr_accessor :items
5
+ attr_accessor :invalid_fields
6
+
7
+ def initialize
8
+ @items = []
9
+ @items_index = 0
10
+ @invalid_fields = []
11
+ end
12
+
13
+ # Adds a field to the bag
14
+ def field(field_name, **args, &block)
15
+ field_parser = Avo::Dsl::FieldParser.new(id: field_name, order_index: @items_index, **args, &block).parse
16
+
17
+ if field_parser.invalid?
18
+ as = args.fetch(:as, nil)
19
+
20
+ # End execution ehre and add the field to the invalid_fileds payload so we know to wanr the developer about that.
21
+ # @todo: Make sure this warning is still active
22
+ return add_invalid_field({
23
+ name: field_name,
24
+ as: as,
25
+ # resource: resource_class.name,
26
+ message: "There's an invalid field configuration for this resource. <br/> <code class='px-1 py-px rounded bg-red-600'>field :#{field_name}, as: #{as}</code>"
27
+ })
28
+ end
29
+
30
+ add_item field_parser.instance
31
+ end
32
+
33
+ def tabs(instance)
34
+ add_item instance
35
+ end
36
+
37
+ def tab(name, **args, &block)
38
+ add_item Avo::TabBuilder.parse_block(name: name, **args, &block)
39
+ end
40
+
41
+ def tool(klass, **args)
42
+ instance = klass.new(**args)
43
+ add_item instance
44
+ end
45
+
46
+ def panel(panel_name = nil, **args, &block)
47
+ panel = Avo::PanelBuilder.parse_block(name: panel_name, **args, &block)
48
+
49
+ add_item panel
50
+ end
51
+
52
+ def add_item(instance)
53
+ @items << instance
54
+
55
+ increment_order_index
56
+ end
57
+
58
+ private
59
+
60
+ def add_invalid_field(payload)
61
+ @invalid_fields << payload
62
+ end
63
+
64
+ def increment_order_index
65
+ @items_index += 1
66
+ end
67
+ end
68
+ end
@@ -98,6 +98,7 @@ module Avo
98
98
  **other_metadata(:filters),
99
99
  main_menu_present: Avo.configuration.main_menu.present?,
100
100
  profile_menu_present: Avo.configuration.profile_menu.present?,
101
+ **config_metadata
101
102
  }
102
103
  rescue
103
104
  {}
@@ -178,6 +179,15 @@ module Avo
178
179
  }
179
180
  end
180
181
 
182
+ def config_metadata
183
+ {
184
+ config: {
185
+ root_path: Avo.configuration.root_path,
186
+ app_name: Avo.configuration.app_name
187
+ }
188
+ }
189
+ end
190
+
181
191
  def cache_and_return_error(error, exception_message = "")
182
192
  cache_response response: {error: error, exception_message: exception_message}.stringify_keys, time: 5.minutes.to_i
183
193
  end
@@ -0,0 +1,3 @@
1
+ class Avo::MainPanel < Avo::Panel
2
+ class_attribute :item_type, default: :main_panel
3
+ end
@@ -5,6 +5,13 @@ class Avo::Menu::Builder
5
5
  end
6
6
  end
7
7
 
8
+ delegate :context, to: ::Avo::App
9
+ delegate :current_user, to: ::Avo::App
10
+ delegate :params, to: ::Avo::App
11
+ delegate :request, to: ::Avo::App
12
+ delegate :root_path, to: ::Avo::App
13
+ delegate :view_context, to: ::Avo::App
14
+
8
15
  def initialize(name: nil, items: [])
9
16
  @menu = Avo::Menu::Menu.new
10
17
 
@@ -60,7 +67,7 @@ class Avo::Menu::Builder
60
67
  # Add all the tools
61
68
  def all_tools(**args)
62
69
  Avo::App.tools_for_navigation.each do |tool|
63
- link tool.humanize, path: "#{root_path}/#{tool}"
70
+ link tool.humanize, path: root_path(paths: [tool])
64
71
  end
65
72
  end
66
73
 
@@ -68,10 +75,4 @@ class Avo::Menu::Builder
68
75
  def build
69
76
  @menu
70
77
  end
71
-
72
- protected
73
-
74
- def root_path
75
- Avo::App.root_path
76
- end
77
78
  end
data/lib/avo/panel.rb ADDED
@@ -0,0 +1,25 @@
1
+ class Avo::Panel
2
+ include Avo::Concerns::IsResourceItem
3
+
4
+ class_attribute :item_type, default: :panel
5
+
6
+ attr_reader :name
7
+ attr_reader :description
8
+ attr_accessor :items_holder
9
+
10
+ delegate :items, :add_item, to: :items_holder
11
+
12
+ def initialize(name: nil, description: nil)
13
+ @name = name
14
+ @description = description
15
+ @items_holder = Avo::ItemsHolder.new
16
+ end
17
+
18
+ def add_item(item)
19
+ @items << item
20
+ end
21
+
22
+ def has_items?
23
+ @items.present?
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ class Avo::PanelBuilder
2
+ class << self
3
+ def parse_block(**args, &block)
4
+ Docile.dsl_eval(new(**args), &block).build
5
+ end
6
+ end
7
+
8
+ delegate :field, to: :items_holder
9
+ delegate :items, to: :items_holder
10
+
11
+ attr_reader :items_holder
12
+
13
+ def initialize(name: nil, **args)
14
+ @panel = Avo::Panel.new(name: name, **args)
15
+ @items_holder = Avo::ItemsHolder.new
16
+ end
17
+
18
+ # Fetch the tab
19
+ def build
20
+ @panel.items_holder = @items_holder
21
+ @panel
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ module Avo
2
+ module Services
3
+ class URIService
4
+ class << self
5
+ def parse(path)
6
+ self.new path
7
+ end
8
+ end
9
+
10
+ attr_reader :uri
11
+
12
+ def initialize(path = '')
13
+ @uri = Addressable::URI.parse(path)
14
+ end
15
+
16
+ def append_paths(*paths)
17
+ paths = Array.wrap(paths).flatten
18
+
19
+ return self if paths.blank?
20
+
21
+ # Add the intermediary forward slash
22
+ @uri.path = @uri.path.concat("/") unless @uri.path.ends_with? "/"
23
+
24
+ # Add the paths to the URI
25
+ @uri.merge!(path: @uri.path.concat(join_paths(paths)))
26
+
27
+ self
28
+ end
29
+ alias_method :append_path, :append_paths
30
+
31
+ def append_query(params)
32
+ params = if params.is_a? Hash
33
+ params.map do |key, value|
34
+ "#{key}=#{value}"
35
+ end
36
+ else
37
+ {}
38
+ end
39
+
40
+ return self if params.blank?
41
+
42
+ # Add the query params to the URI
43
+ @uri.merge!(query: [@uri.query, *params].compact.join("&"))
44
+
45
+ self
46
+ end
47
+
48
+ def to_s
49
+ @uri.to_s
50
+ end
51
+
52
+ private
53
+
54
+ def join_paths(paths)
55
+ paths.map do |path|
56
+ sanitize_path path
57
+ end
58
+ .join("/")
59
+ end
60
+
61
+ # Removes the forward slash if it's present at the start of the path
62
+ def sanitize_path(path)
63
+ if path.to_s.starts_with? '/'
64
+ path = path[1..-1]
65
+ end
66
+
67
+ path
68
+ end
69
+ end
70
+ end
71
+ end
data/lib/avo/tab.rb ADDED
@@ -0,0 +1,78 @@
1
+ class Avo::Tab
2
+ include Avo::Concerns::IsResourceItem
3
+ include Avo::Fields::FieldExtensions::VisibleInDifferentViews
4
+
5
+ class_attribute :item_type, default: :tab
6
+ delegate :items, :add_item, to: :items_holder
7
+
8
+ attr_reader :name
9
+ attr_reader :view
10
+ attr_accessor :description
11
+ attr_accessor :items_holder
12
+ attr_accessor :holds_one_field
13
+
14
+ def initialize(name: nil, description: nil, view: nil, holds_one_field: false, **args)
15
+ # Initialize the visibility markers
16
+ super
17
+
18
+ @name = name
19
+ @description = description
20
+ @holds_one_field = holds_one_field
21
+ @items_holder = Avo::ItemsHolder.new
22
+ @view = view
23
+
24
+ show_on args[:show_on] if args[:show_on].present?
25
+ hide_on args[:hide_on] if args[:hide_on].present?
26
+ only_on args[:only_on] if args[:only_on].present?
27
+ except_on args[:except_on] if args[:except_on].present?
28
+ end
29
+
30
+ def hydrate(view: nil)
31
+ @view = view
32
+
33
+ self
34
+ end
35
+
36
+ def turbo_frame_id(parent: nil)
37
+ id = "#{Avo::Tab.to_s.parameterize} #{name}".parameterize
38
+
39
+ return id if parent.nil?
40
+
41
+ "#{parent.turbo_frame_id} #{id}".parameterize
42
+ end
43
+
44
+ def empty?
45
+ visible_items.blank?
46
+ end
47
+
48
+ # Checks for visibility on itself or on theone field it holds
49
+ def visible_on?(view)
50
+ if holds_one_field
51
+ super(view) && items.first.visible_on?(view)
52
+ else
53
+ super(view)
54
+ end
55
+ end
56
+
57
+ def items
58
+ if self.items_holder.present?
59
+ self.items_holder.items
60
+ else
61
+ []
62
+ end
63
+ end
64
+
65
+ def visible_items
66
+ items.map do |item|
67
+ if item.is_field?
68
+ visible = item.visible_on?(view)
69
+ # Remove the fields that shouldn't be visible in this view
70
+ # eg: has_many fields on edit
71
+ item = nil unless visible
72
+ end
73
+
74
+ item
75
+ end
76
+ .compact
77
+ end
78
+ end
@@ -0,0 +1,25 @@
1
+ class Avo::TabBuilder
2
+ class << self
3
+ def parse_block(**args, &block)
4
+ Docile.dsl_eval(new(**args), &block).build
5
+ end
6
+ end
7
+
8
+ attr_reader :items_holder
9
+
10
+ delegate :field, to: :items_holder
11
+ delegate :tool, to: :items_holder
12
+ delegate :panel, to: :items_holder
13
+ delegate :items, to: :items_holder
14
+
15
+ def initialize(name: nil, **args)
16
+ @tab = Avo::Tab.new(name: name, **args)
17
+ @items_holder = Avo::ItemsHolder.new
18
+ end
19
+
20
+ # Fetch the tab
21
+ def build
22
+ @tab.items_holder = @items_holder
23
+ @tab
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ class Avo::TabGroup
2
+ include Avo::Concerns::HasFields
3
+ include Avo::Concerns::IsResourceItem
4
+
5
+ class_attribute :item_type, default: :tab_group
6
+
7
+ attr_reader :view
8
+ attr_accessor :index
9
+ attr_accessor :items_holder
10
+
11
+ def initialize(index: 0, view: nil)
12
+ @index = index
13
+ @items_holder = Avo::ItemsHolder.new
14
+ @view = view
15
+ end
16
+
17
+ def hydrate(view: nil)
18
+ @view = view
19
+
20
+ self
21
+ end
22
+
23
+ def visible_items
24
+ items.map do |item|
25
+ item.hydrate view: view
26
+ end
27
+ .select do |item|
28
+ # Remove items hidden in this view
29
+ item.visible_on? view
30
+ end
31
+ .select do |item|
32
+ # Remove empty items
33
+ !item.empty?
34
+ end
35
+ end
36
+
37
+ def turbo_frame_id
38
+ "#{Avo::TabGroup.to_s.parameterize} #{index}".parameterize
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ class Avo::TabGroupBuilder
2
+ class << self
3
+ def parse_block(&block)
4
+ Docile.dsl_eval(new, &block).build
5
+ end
6
+ end
7
+
8
+ attr_reader :items_holder
9
+
10
+ delegate :tab, to: :items_holder
11
+
12
+ def initialize
13
+ @group = Avo::TabGroup.new
14
+ @items_holder = Avo::ItemsHolder.new
15
+ end
16
+
17
+ def field(field_name, **args, &block)
18
+ parsed = Avo::Dsl::FieldParser.new(id: field_name, order_index: @items_index, **args, &block).parse
19
+ field_instance = parsed.instance
20
+
21
+ name = field_instance.name
22
+ tab = Avo::Tab.new name: name
23
+
24
+ if field_instance.has_own_panel?
25
+ tab.items_holder.add_item parsed.instance
26
+ tab.holds_one_field = true
27
+ else
28
+ # If the field is not in a panel, create one and add it
29
+ panel = Avo::Panel.new name: name
30
+ panel.items_holder.add_item parsed.instance
31
+ # Add that panel to the bag
32
+ tab.items_holder.add_item panel
33
+ end
34
+
35
+ @items_holder.tabs tab
36
+ end
37
+
38
+ # Fetch the tab
39
+ def build
40
+ @group.items_holder = @items_holder
41
+ @group
42
+ end
43
+ end
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "2.9.2.pre1" unless const_defined?(:VERSION)
2
+ VERSION = "2.10.0" unless const_defined?(:VERSION)
3
3
  end
data/lib/avo.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "avo/engine" if defined?(Rails)
5
5
  loader = Zeitwerk::Loader.for_gem
6
6
  loader.inflector.inflect(
7
7
  "html" => "HTML",
8
+ "uri_service" => "URIService",
8
9
  "has_html_attributes" => "HasHTMLAttributes"
9
10
  )
10
11
  loader.ignore("#{__dir__}/generators")