avo 2.9.2.pre1 → 2.10.3.pre.1

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

Potentially problematic release.


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

Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +69 -69
  3. data/README.md +4 -0
  4. data/app/assets/stylesheets/css/buttons.css +4 -1
  5. data/app/components/avo/actions_component.rb +6 -2
  6. data/app/components/avo/base_component.rb +2 -0
  7. data/app/components/avo/button_component.rb +3 -1
  8. data/app/components/avo/fields/common/key_value_component.html.erb +2 -2
  9. data/app/components/avo/fields/common/single_file_viewer_component.rb +1 -1
  10. data/app/components/avo/fields/country_field/edit_component.html.erb +9 -3
  11. data/app/components/avo/fields/date_field/edit_component.html.erb +26 -7
  12. data/app/components/avo/fields/date_field/index_component.html.erb +7 -1
  13. data/app/components/avo/fields/date_field/show_component.html.erb +7 -1
  14. data/app/components/avo/fields/date_time_field/edit_component.html.erb +1 -0
  15. data/app/components/avo/fields/edit_component.rb +5 -0
  16. data/app/components/avo/fields/file_field/edit_component.html.erb +1 -0
  17. data/app/components/avo/fields/files_field/edit_component.html.erb +1 -0
  18. data/app/components/avo/fields/select_field/edit_component.html.erb +14 -10
  19. data/app/components/avo/fields/show_component.rb +1 -1
  20. data/app/components/avo/index/ordering/button_component.rb +1 -15
  21. data/app/components/avo/index/resource_controls_component.html.erb +2 -2
  22. data/app/components/avo/index/resource_controls_component.rb +5 -1
  23. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  24. data/app/components/avo/index/table_row_component.html.erb +1 -1
  25. data/app/components/avo/item_switcher_component.html.erb +19 -0
  26. data/app/components/avo/item_switcher_component.rb +45 -0
  27. data/app/components/avo/panel_component.html.erb +23 -24
  28. data/app/components/avo/panel_component.rb +8 -5
  29. data/app/components/avo/tab_group_component.html.erb +53 -0
  30. data/app/components/avo/tab_group_component.rb +51 -0
  31. data/app/components/avo/tab_switcher_component.html.erb +21 -0
  32. data/app/components/avo/tab_switcher_component.rb +86 -0
  33. data/app/components/avo/views/resource_edit_component.html.erb +34 -56
  34. data/app/components/avo/views/resource_edit_component.rb +11 -1
  35. data/app/components/avo/views/resource_index_component.html.erb +2 -2
  36. data/app/components/avo/views/resource_index_component.rb +3 -3
  37. data/app/components/avo/views/resource_show_component.html.erb +58 -89
  38. data/app/components/avo/views/resource_show_component.rb +2 -2
  39. data/app/controllers/avo/actions_controller.rb +1 -1
  40. data/app/controllers/avo/application_controller.rb +33 -10
  41. data/app/controllers/avo/base_controller.rb +11 -3
  42. data/app/controllers/avo/reorder_controller.rb +25 -0
  43. data/app/helpers/avo/application_helper.rb +6 -6
  44. data/app/helpers/avo/url_helpers.rb +1 -5
  45. data/app/javascript/avo.js +5 -1
  46. data/app/javascript/js/controllers/fields/date_field_controller.js +15 -3
  47. data/app/javascript/js/controllers/loading_button_controller.js +25 -21
  48. data/app/javascript/js/controllers/search_controller.js +3 -0
  49. data/app/javascript/js/controllers/tabs_controller.js +86 -0
  50. data/app/javascript/js/controllers.js +2 -0
  51. data/app/views/avo/base/index.html.erb +1 -1
  52. data/app/views/avo/base/show.html.erb +1 -1
  53. data/app/views/avo/cards/show.html.erb +1 -1
  54. data/app/views/avo/debug/index.html.erb +1 -1
  55. data/app/views/avo/home/_actions.html.erb +1 -1
  56. data/app/views/avo/home/_dashboards.html.erb +19 -0
  57. data/app/views/avo/home/_filters.html.erb +1 -1
  58. data/app/views/avo/home/_resources.html.erb +1 -1
  59. data/app/views/avo/home/failed_to_load.html.erb +1 -1
  60. data/app/views/avo/home/index.html.erb +14 -2
  61. data/app/views/avo/partials/_javascript.html.erb +1 -1
  62. data/app/views/avo/partials/_tabs_toggle.html.erb +20 -0
  63. data/app/views/avo/private/design.html.erb +1 -1
  64. data/config/routes.rb +5 -4
  65. data/db/factories.rb +1 -0
  66. data/lib/avo/app.rb +12 -7
  67. data/lib/avo/base_action.rb +2 -19
  68. data/lib/avo/base_card.rb +1 -7
  69. data/lib/avo/base_resource.rb +6 -98
  70. data/lib/avo/base_resource_tool.rb +3 -1
  71. data/lib/avo/concerns/has_fields.rb +249 -50
  72. data/lib/avo/concerns/has_html_attributes.rb +1 -1
  73. data/lib/avo/concerns/is_resource_item.rb +36 -0
  74. data/lib/avo/concerns/model_class_constantized.rb +23 -0
  75. data/lib/avo/configuration.rb +2 -12
  76. data/lib/avo/dashboards/base_dashboard.rb +1 -1
  77. data/lib/avo/dsl/field_parser.rb +83 -0
  78. data/lib/avo/fields/base_field.rb +21 -2
  79. data/lib/avo/fields/country_field.rb +2 -0
  80. data/lib/avo/fields/date_field.rb +13 -9
  81. data/lib/avo/fields/field_extensions/has_include_blank.rb +17 -0
  82. data/lib/avo/fields/field_extensions/visible_in_different_views.rb +18 -1
  83. data/lib/avo/fields/file_field.rb +2 -0
  84. data/lib/avo/fields/files_field.rb +2 -0
  85. data/lib/avo/fields/has_base_field.rb +20 -1
  86. data/lib/avo/fields/has_one_field.rb +4 -1
  87. data/lib/avo/fields/key_value_field.rb +4 -4
  88. data/lib/avo/fields/select_field.rb +2 -0
  89. data/lib/avo/grid_collector.rb +6 -3
  90. data/lib/avo/items_holder.rb +68 -0
  91. data/lib/avo/licensing/h_q.rb +12 -0
  92. data/lib/avo/main_panel.rb +3 -0
  93. data/lib/avo/menu/builder.rb +8 -7
  94. data/lib/avo/panel.rb +25 -0
  95. data/lib/avo/panel_builder.rb +23 -0
  96. data/lib/avo/services/uri_service.rb +75 -0
  97. data/lib/avo/tab.rb +78 -0
  98. data/lib/avo/tab_builder.rb +25 -0
  99. data/lib/avo/tab_group.rb +40 -0
  100. data/lib/avo/tab_group_builder.rb +43 -0
  101. data/lib/avo/version.rb +1 -1
  102. data/lib/avo.rb +1 -0
  103. data/lib/generators/avo/action_generator.rb +3 -2
  104. data/lib/generators/avo/base_generator.rb +14 -0
  105. data/lib/generators/avo/card/chartkick_generator.rb +18 -0
  106. data/lib/generators/avo/card/metric_generator.rb +18 -0
  107. data/lib/generators/avo/card/partial_generator.rb +19 -0
  108. data/lib/generators/avo/controller_generator.rb +9 -3
  109. data/lib/generators/avo/dashboard_generator.rb +2 -2
  110. data/lib/generators/avo/eject_generator.rb +2 -3
  111. data/lib/generators/avo/field_generator.rb +2 -2
  112. data/lib/generators/avo/filter_generator.rb +3 -2
  113. data/lib/generators/avo/install_generator.rb +2 -2
  114. data/lib/generators/avo/locales_generator.rb +2 -2
  115. data/lib/generators/avo/named_base_generator.rb +14 -0
  116. data/lib/generators/avo/resource_generator.rb +2 -2
  117. data/lib/generators/avo/resource_tool_generator.rb +4 -4
  118. data/lib/generators/avo/templates/locales/avo.fr.yml +115 -0
  119. data/lib/generators/avo/templates/resource/controller.tt +2 -0
  120. data/lib/generators/avo/templates/resource_tools/partial.tt +1 -1
  121. data/lib/generators/avo/templates/tool/view.tt +1 -1
  122. data/lib/generators/avo/tool_generator.rb +4 -4
  123. data/lib/generators/avo/version_generator.rb +23 -0
  124. data/public/avo-assets/avo.css +31 -3
  125. data/public/avo-assets/avo.js +72 -72
  126. data/public/avo-assets/avo.js.map +3 -3
  127. metadata +32 -16
  128. data/app/assets/builds/action_cable.js +0 -2
  129. data/app/assets/builds/action_cable.js.map +0 -7
  130. data/app/assets/builds/application.js +0 -2
  131. data/app/assets/builds/application.js.map +0 -7
  132. data/app/assets/builds/avo.css +0 -9028
  133. data/app/assets/builds/avo.js +0 -512
  134. data/app/assets/builds/avo.js.map +0 -7
  135. data/app/assets/builds/avo_custom.js +0 -6
  136. data/app/assets/builds/avo_custom.js.map +0 -7
  137. data/lib/avo/concerns/has_tools.rb +0 -47
  138. data/lib/avo/fields/currency_field.rb +0 -15
  139. data/lib/generators/avo/chartkick_card_generator.rb +0 -16
  140. data/lib/generators/avo/metric_card_generator.rb +0 -16
  141. data/lib/generators/avo/partial_card_generator.rb +0 -17
@@ -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)
@@ -6,6 +6,7 @@ module Avo
6
6
  attr_accessor :is_image
7
7
  attr_accessor :is_audio
8
8
  attr_accessor :direct_upload
9
+ attr_accessor :accept
9
10
 
10
11
  def initialize(id, **args, &block)
11
12
  super(id, **args, &block)
@@ -15,6 +16,7 @@ module Avo
15
16
  @is_image = args[:is_image].present? ? args[:is_image] : @is_avatar
16
17
  @is_audio = args[:is_audio].present? ? args[:is_audio] : false
17
18
  @direct_upload = args[:direct_upload].present? ? args[:direct_upload] : false
19
+ @accept = args[:accept].present? ? args[:accept] : nil
18
20
  end
19
21
 
20
22
  def path
@@ -4,6 +4,7 @@ module Avo
4
4
  attr_accessor :is_audio
5
5
  attr_accessor :is_image
6
6
  attr_accessor :direct_upload
7
+ attr_accessor :accept
7
8
 
8
9
  def initialize(id, **args, &block)
9
10
  super(id, **args, &block)
@@ -11,6 +12,7 @@ module Avo
11
12
  @is_audio = args[:is_audio].present? ? args[:is_audio] : false
12
13
  @is_image = args[:is_image].present? ? args[:is_image] : @is_avatar
13
14
  @direct_upload = args[:direct_upload].present? ? args[:direct_upload] : false
15
+ @accept = args[:accept].present? ? args[:accept] : nil
14
16
  end
15
17
 
16
18
  def view_component_name
@@ -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)
@@ -64,10 +64,10 @@ module Avo
64
64
 
65
65
  def options
66
66
  {
67
- key_label: @key_label,
68
- value_label: @value_label,
69
- action_text: @action_text,
70
- delete_text: @delete_text,
67
+ key_label: key_label,
68
+ value_label: value_label,
69
+ action_text: action_text,
70
+ delete_text: delete_text,
71
71
  disable_editing_keys: @disable_editing_keys,
72
72
  disable_adding_rows: @disable_adding_rows,
73
73
  disable_deleting_rows: @disable_deleting_rows
@@ -1,6 +1,8 @@
1
1
  module Avo
2
2
  module Fields
3
3
  class SelectField < BaseField
4
+ include Avo::Fields::FieldExtensions::HasIncludeBlank
5
+
4
6
  attr_reader :options
5
7
  attr_reader :enum
6
8
  attr_reader :display_value
@@ -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
  {}
@@ -120,6 +121,8 @@ module Avo
120
121
  cache_and_return_error "HTTP connection reset error.", exception.message
121
122
  rescue Errno::ECONNREFUSED => exception
122
123
  cache_and_return_error "HTTP connection refused error.", exception.message
124
+ rescue OpenSSL::SSL::SSLError => exception
125
+ cache_and_return_error "OpenSSL error.", exception.message
123
126
  rescue HTTParty::Error => exception
124
127
  cache_and_return_error "HTTP client error.", exception.message
125
128
  rescue Net::OpenTimeout => exception
@@ -178,6 +181,15 @@ module Avo
178
181
  }
179
182
  end
180
183
 
184
+ def config_metadata
185
+ {
186
+ config: {
187
+ root_path: Avo.configuration.root_path,
188
+ app_name: Avo.configuration.app_name
189
+ }
190
+ }
191
+ end
192
+
181
193
  def cache_and_return_error(error, exception_message = "")
182
194
  cache_response response: {error: error, exception_message: exception_message}.stringify_keys, time: 5.minutes.to_i
183
195
  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,75 @@
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 call
17
+ to_s
18
+ end
19
+
20
+ def append_paths(*paths)
21
+ paths = Array.wrap(paths).flatten
22
+
23
+ return self if paths.blank?
24
+
25
+ # Add the intermediary forward slash
26
+ @uri.path = @uri.path.concat("/") unless @uri.path.ends_with? "/"
27
+
28
+ # Add the paths to the URI
29
+ @uri.merge!(path: @uri.path.concat(join_paths(paths)))
30
+
31
+ self
32
+ end
33
+ alias_method :append_path, :append_paths
34
+
35
+ def append_query(params)
36
+ params = if params.is_a? Hash
37
+ params.map do |key, value|
38
+ "#{key}=#{value}"
39
+ end
40
+ else
41
+ {}
42
+ end
43
+
44
+ return self if params.blank?
45
+
46
+ # Add the query params to the URI
47
+ @uri.merge!(query: [@uri.query, *params].compact.join("&"))
48
+
49
+ self
50
+ end
51
+
52
+ def to_s
53
+ @uri.to_s
54
+ end
55
+
56
+ private
57
+
58
+ def join_paths(paths)
59
+ paths.map do |path|
60
+ sanitize_path path
61
+ end
62
+ .join("/")
63
+ end
64
+
65
+ # Removes the forward slash if it's present at the start of the path
66
+ def sanitize_path(path)
67
+ if path.to_s.starts_with? '/'
68
+ path = path[1..-1]
69
+ end
70
+
71
+ path
72
+ end
73
+ end
74
+ end
75
+ 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.3.pre.1" 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")
@@ -1,9 +1,10 @@
1
- require "rails/generators"
1
+ require_relative "named_base_generator"
2
2
 
3
3
  module Generators
4
4
  module Avo
5
- class ActionGenerator < Rails::Generators::NamedBase
5
+ class ActionGenerator < NamedBaseGenerator
6
6
  source_root File.expand_path("templates", __dir__)
7
+
7
8
  class_option :standalone, type: :boolean
8
9
 
9
10
  namespace "avo:action"
@@ -0,0 +1,14 @@
1
+ require "rails/generators"
2
+
3
+ module Generators
4
+ module Avo
5
+ class BaseGenerator < ::Rails::Generators::Base
6
+ hide!
7
+
8
+ def initialize(*args)
9
+ super(*args)
10
+ invoke "avo:version", *args
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "../named_base_generator"
2
+
3
+ module Generators
4
+ module Avo
5
+ module Card
6
+ class ChartkickGenerator < Generators::Avo::NamedBaseGenerator
7
+ source_root File.expand_path("../templates", __dir__)
8
+
9
+ namespace "avo:card:chartkick"
10
+ desc "Add a chartkick card for your Avo dashboard."
11
+
12
+ def handle
13
+ template "cards/chartkick_card_sample.tt", "app/avo/cards/#{name.underscore}.rb"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end