avo 2.9.1.pre7 → 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.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -7
- data/README.md +4 -0
- data/app/assets/stylesheets/css/buttons.css +4 -1
- data/app/components/avo/base_component.rb +2 -0
- data/app/components/avo/button_component.rb +3 -1
- data/app/components/avo/fields/edit_component.rb +5 -0
- data/app/components/avo/fields/show_component.rb +1 -1
- data/app/components/avo/index/ordering/button_component.rb +3 -5
- data/app/components/avo/index/resource_table_component.html.erb +1 -1
- data/app/components/avo/index/table_row_component.html.erb +1 -1
- data/app/components/avo/item_switcher_component.html.erb +19 -0
- data/app/components/avo/item_switcher_component.rb +45 -0
- data/app/components/avo/panel_component.html.erb +23 -24
- data/app/components/avo/panel_component.rb +8 -5
- data/app/components/avo/tab_group_component.html.erb +53 -0
- data/app/components/avo/tab_group_component.rb +51 -0
- data/app/components/avo/tab_switcher_component.html.erb +21 -0
- data/app/components/avo/tab_switcher_component.rb +86 -0
- data/app/components/avo/views/resource_edit_component.html.erb +34 -56
- data/app/components/avo/views/resource_edit_component.rb +10 -0
- data/app/components/avo/views/resource_index_component.html.erb +1 -1
- data/app/components/avo/views/resource_index_component.rb +1 -1
- data/app/components/avo/views/resource_show_component.html.erb +58 -89
- data/app/components/avo/views/resource_show_component.rb +2 -2
- data/app/controllers/avo/actions_controller.rb +1 -1
- data/app/controllers/avo/application_controller.rb +6 -10
- data/app/helpers/avo/application_helper.rb +0 -6
- data/app/helpers/avo/url_helpers.rb +1 -1
- data/app/javascript/js/controllers/loading_button_controller.js +25 -21
- data/app/javascript/js/controllers/tabs_controller.js +9 -3
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/base/index.html.erb +1 -1
- data/app/views/avo/base/show.html.erb +1 -1
- data/app/views/avo/cards/show.html.erb +1 -1
- data/app/views/avo/debug/index.html.erb +1 -1
- data/app/views/avo/home/_actions.html.erb +1 -1
- data/app/views/avo/home/_dashboards.html.erb +19 -0
- data/app/views/avo/home/_filters.html.erb +1 -1
- data/app/views/avo/home/_resources.html.erb +1 -1
- data/app/views/avo/home/failed_to_load.html.erb +1 -1
- data/app/views/avo/home/index.html.erb +14 -2
- data/app/views/avo/partials/_tabs_toggle.html.erb +20 -0
- data/app/views/avo/private/design.html.erb +1 -1
- data/lib/avo/base_action.rb +2 -19
- data/lib/avo/base_resource.rb +0 -94
- data/lib/avo/base_resource_tool.rb +3 -1
- data/lib/avo/concerns/has_fields.rb +247 -50
- data/lib/avo/concerns/has_html_attributes.rb +1 -1
- data/lib/avo/concerns/is_resource_item.rb +36 -0
- data/lib/avo/dsl/field_parser.rb +83 -0
- data/lib/avo/fields/base_field.rb +19 -2
- data/lib/avo/fields/field_extensions/visible_in_different_views.rb +18 -1
- data/lib/avo/fields/has_base_field.rb +18 -1
- data/lib/avo/grid_collector.rb +6 -3
- data/lib/avo/items_holder.rb +68 -0
- data/lib/avo/licensing/h_q.rb +10 -0
- data/lib/avo/main_panel.rb +3 -0
- data/lib/avo/menu/builder.rb +6 -6
- data/lib/avo/panel.rb +25 -0
- data/lib/avo/panel_builder.rb +23 -0
- data/lib/avo/tab.rb +78 -0
- data/lib/avo/tab_builder.rb +25 -0
- data/lib/avo/tab_group.rb +40 -0
- data/lib/avo/tab_group_builder.rb +43 -0
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/resource/controller.tt +2 -0
- data/lib/generators/avo/templates/resource_tools/partial.tt +1 -1
- data/lib/generators/avo/templates/tool/view.tt +1 -1
- data/public/avo-assets/avo.css +27 -3
- data/public/avo-assets/avo.js +73 -73
- data/public/avo-assets/avo.js.map +3 -3
- metadata +22 -5
- data/lib/avo/concerns/has_tools.rb +0 -47
@@ -0,0 +1,83 @@
|
|
1
|
+
module Avo
|
2
|
+
module Dsl
|
3
|
+
class FieldParser
|
4
|
+
attr_reader :as
|
5
|
+
attr_reader :args
|
6
|
+
attr_reader :id
|
7
|
+
attr_reader :block
|
8
|
+
attr_reader :instance
|
9
|
+
attr_reader :order_index
|
10
|
+
|
11
|
+
def initialize(id:, order_index: 0, **args, &block)
|
12
|
+
@id = id
|
13
|
+
@as = args.fetch(:as, nil)
|
14
|
+
@order_index = order_index
|
15
|
+
@args = args
|
16
|
+
@block = block
|
17
|
+
@instance = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
instance.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def invalid?
|
25
|
+
!valid?
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse
|
29
|
+
# The field is passed as a symbol eg: :text, :color_picker, :trix
|
30
|
+
@instance = if as.is_a? Symbol
|
31
|
+
parse_from_symbol
|
32
|
+
elsif as.is_a? Class
|
33
|
+
parse_from_class
|
34
|
+
end
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def parse_from_symbol
|
42
|
+
field_class = field_class_from_symbol(as)
|
43
|
+
|
44
|
+
if field_class.present?
|
45
|
+
# The field has been registered before.
|
46
|
+
instantiate_field(id, klass: field_class, **args, &block)
|
47
|
+
else
|
48
|
+
# The symbol can be transformed to a class and found.
|
49
|
+
class_name = as.to_s.camelize
|
50
|
+
field_class = "#{class_name}Field"
|
51
|
+
|
52
|
+
# Discover & load custom field classes
|
53
|
+
if Object.const_defined? field_class
|
54
|
+
instantiate_field(id, klass: field_class.safe_constantize, **args, &block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_from_class
|
60
|
+
# The field has been passed as a class.
|
61
|
+
if Object.const_defined? as.to_s
|
62
|
+
instantiate_field(id, klass: as, **args, &block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def instantiate_field(id, klass:, **args, &block)
|
67
|
+
if block
|
68
|
+
klass.new(id, **args || {}, &block)
|
69
|
+
else
|
70
|
+
klass.new(id, **args || {})
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def field_class_from_symbol(symbol)
|
75
|
+
matched_field = Avo::App.fields.find do |field|
|
76
|
+
field[:name].to_s == symbol.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
return matched_field[:class] if matched_field.present? && matched_field[:class].present?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -4,8 +4,11 @@ module Avo
|
|
4
4
|
extend ActiveSupport::DescendantsTracker
|
5
5
|
extend Avo::Fields::FieldExtensions::HasFieldName
|
6
6
|
|
7
|
-
include Avo::
|
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,
|
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)
|
@@ -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)
|
@@ -31,7 +31,8 @@ module Avo
|
|
31
31
|
def frame_url
|
32
32
|
Avo::Services::URIService.parse(@resource.record_path)
|
33
33
|
.append_path(id.to_s)
|
34
|
-
.append_query(turbo_frame: turbo_frame.to_s)
|
34
|
+
.append_query(turbo_frame: turbo_frame.to_s)
|
35
|
+
.to_s
|
35
36
|
end
|
36
37
|
|
37
38
|
# The value
|
@@ -61,6 +62,22 @@ module Avo
|
|
61
62
|
def placeholder
|
62
63
|
@placeholder || I18n.t("avo.choose_an_option")
|
63
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
|
64
81
|
end
|
65
82
|
end
|
66
83
|
end
|
data/lib/avo/grid_collector.rb
CHANGED
@@ -13,15 +13,18 @@ module Avo
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def cover(field_name, as:, **args, &block)
|
16
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/avo/licensing/h_q.rb
CHANGED
@@ -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
|
data/lib/avo/menu/builder.rb
CHANGED
@@ -5,12 +5,12 @@ 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
|
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
14
|
|
15
15
|
def initialize(name: nil, items: [])
|
16
16
|
@menu = Avo::Menu::Menu.new
|
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
|
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,5 +1,5 @@
|
|
1
1
|
<div class="flex flex-col">
|
2
|
-
<%%= render Avo::PanelComponent.new
|
2
|
+
<%%= render Avo::PanelComponent.new(name: "<%= human_name %>") do |c| %>
|
3
3
|
<%% c.tools do %>
|
4
4
|
<%%= a_link('/avo', icon: 'heroicons/solid/academic-cap', style: :primary) do %>
|
5
5
|
Dummy link
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="flex flex-col">
|
2
|
-
<%%= render Avo::PanelComponent.new
|
2
|
+
<%%= render Avo::PanelComponent.new(name: '<%= human_name %>', display_breadcrumbs: true) do |c| %>
|
3
3
|
<%% c.tools do %>
|
4
4
|
<div class="text-sm italic">This is the panels tools section.</div>
|
5
5
|
<%% end %>
|
data/public/avo-assets/avo.css
CHANGED
@@ -3977,9 +3977,9 @@ a{
|
|
3977
3977
|
display:flex;
|
3978
3978
|
}
|
3979
3979
|
|
3980
|
-
.button-group .button-component
|
3981
|
-
|
3982
|
-
|
3980
|
+
.button-group .button-component{
|
3981
|
+
margin-right:-1px
|
3982
|
+
}
|
3983
3983
|
|
3984
3984
|
.button-group .button-component:first-child{
|
3985
3985
|
border-top-left-radius:0.25rem;
|
@@ -6354,6 +6354,16 @@ trix-editor .attachment__metadata .attachment__size {
|
|
6354
6354
|
margin-bottom:0px
|
6355
6355
|
}
|
6356
6356
|
|
6357
|
+
.-mx-2{
|
6358
|
+
margin-left:-0.5rem;
|
6359
|
+
margin-right:-0.5rem
|
6360
|
+
}
|
6361
|
+
|
6362
|
+
.-my-2{
|
6363
|
+
margin-top:-0.5rem;
|
6364
|
+
margin-bottom:-0.5rem
|
6365
|
+
}
|
6366
|
+
|
6357
6367
|
.mx-6{
|
6358
6368
|
margin-left:1.5rem;
|
6359
6369
|
margin-right:1.5rem
|
@@ -8885,6 +8895,16 @@ trix-editor {
|
|
8885
8895
|
}
|
8886
8896
|
|
8887
8897
|
@media (min-width: 1024px){
|
8898
|
+
.lg\:-mx-4{
|
8899
|
+
margin-left:-1rem;
|
8900
|
+
margin-right:-1rem
|
8901
|
+
}
|
8902
|
+
|
8903
|
+
.lg\:-my-4{
|
8904
|
+
margin-top:-1rem;
|
8905
|
+
margin-bottom:-1rem
|
8906
|
+
}
|
8907
|
+
|
8888
8908
|
.lg\:flex{
|
8889
8909
|
display:flex
|
8890
8910
|
}
|
@@ -8927,6 +8947,10 @@ trix-editor {
|
|
8927
8947
|
padding:1.5rem
|
8928
8948
|
}
|
8929
8949
|
|
8950
|
+
.lg\:p-4{
|
8951
|
+
padding:1rem
|
8952
|
+
}
|
8953
|
+
|
8930
8954
|
.lg\:px-8{
|
8931
8955
|
padding-left:2rem;
|
8932
8956
|
padding-right:2rem
|