easy-admin-rails 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/easy_admin.base.js +7 -0
  3. data/app/assets/builds/easy_admin.base.js.map +2 -2
  4. data/app/assets/builds/easy_admin.css +207 -35
  5. data/app/components/easy_admin/fields/form/belongs_to_component.rb +0 -1
  6. data/app/components/easy_admin/form_layout_component.rb +553 -0
  7. data/app/components/easy_admin/permissions/user_role_permissions_component.rb +1 -3
  8. data/app/components/easy_admin/show_layout_component.rb +694 -24
  9. data/app/controllers/easy_admin/application_controller.rb +0 -5
  10. data/app/controllers/easy_admin/batch_actions_controller.rb +0 -1
  11. data/app/controllers/easy_admin/concerns/inline_field_editing.rb +4 -11
  12. data/app/controllers/easy_admin/concerns/resource_loading.rb +10 -9
  13. data/app/controllers/easy_admin/concerns/resource_pagination.rb +3 -0
  14. data/app/controllers/easy_admin/dashboards_controller.rb +0 -1
  15. data/app/controllers/easy_admin/resources_controller.rb +1 -5
  16. data/app/controllers/easy_admin/row_actions_controller.rb +1 -4
  17. data/app/helpers/easy_admin/fields_helper.rb +8 -22
  18. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +12 -0
  19. data/app/views/easy_admin/resources/edit.html.erb +2 -2
  20. data/app/views/easy_admin/resources/new.html.erb +2 -2
  21. data/app/views/easy_admin/resources/show.html.erb +3 -1
  22. data/lib/easy_admin/field.rb +3 -2
  23. data/lib/easy_admin/layouts/builders/base_layout_builder.rb +245 -0
  24. data/lib/easy_admin/layouts/builders/form_layout_builder.rb +208 -0
  25. data/lib/easy_admin/layouts/builders/index_layout_builder.rb +22 -0
  26. data/lib/easy_admin/layouts/builders/show_layout_builder.rb +199 -0
  27. data/lib/easy_admin/layouts/dsl.rb +200 -0
  28. data/lib/easy_admin/layouts/layout_context.rb +189 -0
  29. data/lib/easy_admin/layouts/nodes/base_node.rb +88 -0
  30. data/lib/easy_admin/layouts/nodes/divider.rb +27 -0
  31. data/lib/easy_admin/layouts/nodes/field_node.rb +57 -0
  32. data/lib/easy_admin/layouts/nodes/grid.rb +60 -0
  33. data/lib/easy_admin/layouts/nodes/render_node.rb +41 -0
  34. data/lib/easy_admin/layouts/nodes/root.rb +25 -0
  35. data/lib/easy_admin/layouts/nodes/section.rb +46 -0
  36. data/lib/easy_admin/layouts/nodes/spacer.rb +17 -0
  37. data/lib/easy_admin/layouts/nodes/stubs.rb +109 -0
  38. data/lib/easy_admin/layouts/nodes/tab.rb +40 -0
  39. data/lib/easy_admin/layouts/nodes/tabs.rb +40 -0
  40. data/lib/easy_admin/layouts.rb +28 -0
  41. data/lib/easy_admin/permissions/resource_permissions.rb +1 -5
  42. data/lib/easy_admin/resource/base.rb +2 -2
  43. data/lib/easy_admin/resource/dsl.rb +2 -11
  44. data/lib/easy_admin/resource/field_registry.rb +58 -2
  45. data/lib/easy_admin/resource.rb +0 -9
  46. data/lib/easy_admin/resource_modules.rb +21 -4
  47. data/lib/easy_admin/version.rb +1 -1
  48. data/lib/generators/easy_admin/permissions/install_generator.rb +0 -10
  49. data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +33 -3
  50. metadata +21 -9
  51. data/lib/easy_admin/resource/form_builder.rb +0 -123
  52. data/lib/easy_admin/resource/layout_builder.rb +0 -249
  53. data/lib/easy_admin/resource/show_builder.rb +0 -359
  54. data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +0 -6
  55. data/lib/generators/easy_admin/rbac/rbac_generator.rb +0 -244
  56. data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +0 -23
  57. data/lib/generators/easy_admin/rbac/templates/super_admin.rb +0 -34
@@ -0,0 +1,208 @@
1
+ module EasyAdmin
2
+ module Layouts
3
+ module Builders
4
+ # Builder specifically for form layouts
5
+ class FormLayoutBuilder < BaseLayoutBuilder
6
+ def initialize(resource_class: nil)
7
+ super(:form, resource_class: resource_class)
8
+ end
9
+
10
+ # Form-specific DSL methods
11
+
12
+ # Add fieldset for grouping form fields
13
+ def fieldset(legend = nil, **attributes, &block)
14
+ fieldset_node = Nodes::Fieldset.new(legend, attributes)
15
+
16
+ if block_given?
17
+ with_container(fieldset_node, &block)
18
+ else
19
+ add_node(fieldset_node)
20
+ end
21
+
22
+ fieldset_node
23
+ end
24
+
25
+ # Add form actions (submit, cancel, etc.)
26
+ def form_actions(**attributes, &block)
27
+ actions_node = Nodes::FormActions.new(attributes)
28
+ add_node(actions_node)
29
+
30
+ if block_given?
31
+ @current_container_stack.push(actions_node)
32
+ form_actions_builder = FormActionsBuilder.new(self, actions_node)
33
+ form_actions_builder.instance_exec(&block)
34
+ @current_container_stack.pop
35
+ end
36
+
37
+ actions_node
38
+ end
39
+
40
+ # Add inline fields (multiple fields in one row)
41
+ def inline_fields(**attributes, &block)
42
+ inline_node = Nodes::InlineFields.new(attributes)
43
+
44
+ if block_given?
45
+ with_container(inline_node, &block)
46
+ else
47
+ add_node(inline_node)
48
+ end
49
+
50
+ inline_node
51
+ end
52
+
53
+ # Add conditional fields
54
+ def conditional_fields(condition:, **attributes, &block)
55
+ conditional_node = Nodes::ConditionalFields.new(
56
+ condition: condition,
57
+ **attributes
58
+ )
59
+
60
+ if block_given?
61
+ with_container(conditional_node, &block)
62
+ else
63
+ add_node(conditional_node)
64
+ end
65
+
66
+ conditional_node
67
+ end
68
+
69
+ # Add nested fields for associations
70
+ def nested_fields(association, **attributes, &block)
71
+ nested_node = Nodes::NestedFields.new(
72
+ association,
73
+ attributes
74
+ )
75
+
76
+ if block_given?
77
+ with_container(nested_node, &block)
78
+ else
79
+ add_node(nested_node)
80
+ end
81
+
82
+ nested_node
83
+ end
84
+
85
+ # Add help text
86
+ def help_text(text, **attributes)
87
+ help_node = Nodes::HelpText.new(text, attributes)
88
+ add_node(help_node)
89
+ end
90
+
91
+ # Add form errors summary
92
+ def errors_summary(**attributes)
93
+ errors_node = Nodes::ErrorsSummary.new(attributes)
94
+ add_node(errors_node)
95
+ end
96
+
97
+ # Override field method to include form-specific options
98
+ def field(name, **options)
99
+ # Set default form field options
100
+ options[:required] = true if options[:required].nil? && required_field?(name)
101
+ options[:readonly] = true if readonly_field?(name)
102
+
103
+ super(name, **options)
104
+ end
105
+
106
+ # Convenience methods for specific field types
107
+ def text_field(name, **options)
108
+ field(name, field_type: :text, **options)
109
+ end
110
+
111
+ def email_field(name, **options)
112
+ field(name, field_type: :email, **options)
113
+ end
114
+
115
+ def textarea_field(name, **options)
116
+ field(name, field_type: :textarea, **options)
117
+ end
118
+
119
+ def boolean_field(name, **options)
120
+ field(name, field_type: :boolean, **options)
121
+ end
122
+
123
+ def select_field(name, **options)
124
+ field(name, field_type: :select, **options)
125
+ end
126
+
127
+ def has_many_field(name, **options)
128
+ field(name, field_type: :has_many, **options)
129
+ end
130
+
131
+ def date_field(name, **options)
132
+ field(name, field_type: :date, **options)
133
+ end
134
+
135
+ def datetime_field(name, **options)
136
+ field(name, field_type: :datetime, **options)
137
+ end
138
+
139
+ def number_field(name, **options)
140
+ field(name, field_type: :number, **options)
141
+ end
142
+
143
+ def password_field(name, **options)
144
+ field(name, field_type: :password, **options)
145
+ end
146
+
147
+ private
148
+
149
+ def required_field?(name)
150
+ # Check if field is required based on model validations
151
+ # This would need to be implemented based on your validation setup
152
+ false
153
+ end
154
+
155
+ def readonly_field?(name)
156
+ # Check if field should be readonly
157
+ # This would need to be implemented based on your configuration
158
+ false
159
+ end
160
+ end
161
+
162
+ # Builder for form actions
163
+ class FormActionsBuilder
164
+ def initialize(parent_builder)
165
+ @parent_builder = parent_builder
166
+ end
167
+
168
+ def submit(text = "Save", **attributes)
169
+ submit_node = Nodes::FormAction.new(
170
+ :submit,
171
+ text: text,
172
+ **attributes
173
+ )
174
+ @parent_builder.add_node(submit_node)
175
+ end
176
+
177
+ def cancel(text = "Cancel", href: :back, **attributes)
178
+ cancel_node = Nodes::FormAction.new(
179
+ :cancel,
180
+ text: text,
181
+ href: href,
182
+ **attributes
183
+ )
184
+ @parent_builder.add_node(cancel_node)
185
+ end
186
+
187
+ def reset(text = "Reset", **attributes)
188
+ reset_node = Nodes::FormAction.new(
189
+ :reset,
190
+ text: text,
191
+ **attributes
192
+ )
193
+ @parent_builder.add_node(reset_node)
194
+ end
195
+
196
+ def button(text, action:, **attributes)
197
+ button_node = Nodes::FormAction.new(
198
+ :button,
199
+ text: text,
200
+ action: action,
201
+ **attributes
202
+ )
203
+ @parent_builder.add_node(button_node)
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,22 @@
1
+ module EasyAdmin
2
+ module Layouts
3
+ module Builders
4
+ # IndexLayoutBuilder for constructing index page layouts
5
+ # Specialized builder for index table field configurations
6
+ class IndexLayoutBuilder < BaseLayoutBuilder
7
+ def initialize(resource_class: nil)
8
+ super(:index, resource_class: resource_class)
9
+ end
10
+
11
+ # Simple field method for index - only accepts field name
12
+ # Configuration is extracted from already registered resource fields
13
+ def field(name, **options)
14
+ # Create a simple field node with just the name
15
+ # The actual field configuration will be looked up from resource fields_config
16
+ field_node = Nodes::FieldNode.new(name, options)
17
+ add_node(field_node)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,199 @@
1
+ module EasyAdmin
2
+ module Layouts
3
+ module Builders
4
+ # Builder specifically for show page layouts
5
+ class ShowLayoutBuilder < BaseLayoutBuilder
6
+ def initialize(resource_class: nil)
7
+ super(:show, resource_class: resource_class)
8
+ end
9
+
10
+ # Show-specific DSL methods
11
+
12
+ # Add a description list for key-value pairs
13
+ def description_list(**attributes, &block)
14
+ dl_node = Nodes::DescriptionList.new(attributes)
15
+
16
+ if block_given?
17
+ with_container(dl_node, &block)
18
+ else
19
+ add_node(dl_node)
20
+ end
21
+
22
+ dl_node
23
+ end
24
+
25
+ # Add a card component
26
+ def card(title: nil, **attributes, &block)
27
+ card_node = Nodes::Card.new(title: title, **attributes)
28
+
29
+ if block_given?
30
+ with_container(card_node, &block)
31
+ else
32
+ add_node(card_node)
33
+ end
34
+
35
+ card_node
36
+ end
37
+
38
+ # Add a metric card
39
+ def metric(label:, value:, **attributes)
40
+ metric_node = Nodes::MetricCard.new(
41
+ label: label,
42
+ value: value,
43
+ **attributes
44
+ )
45
+ add_node(metric_node)
46
+ end
47
+
48
+ # Add a metric card (accepts both label and title)
49
+ def metric_card(title: nil, label: nil, value: nil, **attributes)
50
+ metric_node = Nodes::MetricCard.new(
51
+ title: title,
52
+ label: label,
53
+ value: value,
54
+ **attributes
55
+ )
56
+ add_node(metric_node)
57
+ end
58
+
59
+ # Add an action bar
60
+ def action_bar(**attributes, &block)
61
+ action_bar_node = Nodes::ActionBar.new(attributes)
62
+ add_node(action_bar_node)
63
+
64
+ if block_given?
65
+ @current_container_stack.push(action_bar_node)
66
+ action_builder = ActionBarBuilder.new(self, action_bar_node)
67
+ action_builder.instance_exec(&block)
68
+ @current_container_stack.pop
69
+ end
70
+
71
+ action_bar_node
72
+ end
73
+
74
+ # Add a panel (collapsible section)
75
+ def panel(title, expanded: true, **attributes, &block)
76
+ panel_node = Nodes::Panel.new(
77
+ title,
78
+ attributes.merge(expanded: expanded)
79
+ )
80
+
81
+ if block_given?
82
+ with_container(panel_node, &block)
83
+ else
84
+ add_node(panel_node)
85
+ end
86
+
87
+ panel_node
88
+ end
89
+
90
+ # Add badge
91
+ def badge(text, variant: :default, **attributes)
92
+ badge_node = Nodes::Badge.new(
93
+ text,
94
+ attributes.merge(variant: variant)
95
+ )
96
+ add_node(badge_node)
97
+ end
98
+
99
+ # Add related resources section
100
+ def related(resource_name, **attributes, &block)
101
+ related_node = Nodes::RelatedResources.new(
102
+ resource_name,
103
+ attributes
104
+ )
105
+
106
+ if block_given?
107
+ with_container(related_node, &block)
108
+ else
109
+ add_node(related_node)
110
+ end
111
+
112
+ related_node
113
+ end
114
+
115
+ # Add row layout
116
+ def row(**attributes, &block)
117
+ row_node = Nodes::Row.new(attributes)
118
+
119
+ if block_given?
120
+ with_container(row_node, &block)
121
+ else
122
+ add_node(row_node)
123
+ end
124
+
125
+ row_node
126
+ end
127
+
128
+ # Add column layout
129
+ def column(**attributes, &block)
130
+ column_node = Nodes::Column.new(attributes)
131
+
132
+ if block_given?
133
+ with_container(column_node, &block)
134
+ else
135
+ add_node(column_node)
136
+ end
137
+
138
+ column_node
139
+ end
140
+
141
+ # Add heading
142
+ def heading(text, level: 2, **attributes)
143
+ heading_node = Nodes::Heading.new(text, attributes.merge(level: level))
144
+ add_node(heading_node)
145
+ end
146
+
147
+ # Add content block
148
+ def content(&block)
149
+ content_node = Nodes::Content.new(block: block)
150
+ add_node(content_node)
151
+ end
152
+ end
153
+
154
+ # Builder for action bars
155
+ class ActionBarBuilder
156
+ def initialize(parent_builder, action_bar_node)
157
+ @parent_builder = parent_builder
158
+ @action_bar_node = action_bar_node
159
+ end
160
+
161
+ def link(text, href:, **attributes)
162
+ action_node = Nodes::Action.new(
163
+ :link,
164
+ text: text,
165
+ href: href,
166
+ **attributes
167
+ )
168
+ @action_bar_node.add_child(action_node)
169
+ end
170
+
171
+ def button(text, action:, **attributes)
172
+ action_node = Nodes::Action.new(
173
+ :button,
174
+ text: text,
175
+ action: action,
176
+ **attributes
177
+ )
178
+ @action_bar_node.add_child(action_node)
179
+ end
180
+
181
+ def dropdown(text, **attributes, &block)
182
+ dropdown_node = Nodes::Dropdown.new(
183
+ text: text,
184
+ **attributes
185
+ )
186
+ @action_bar_node.add_child(dropdown_node)
187
+
188
+ if block_given?
189
+ @parent_builder.current_container_stack.push(dropdown_node)
190
+ @parent_builder.instance_exec(&block)
191
+ @parent_builder.current_container_stack.pop
192
+ end
193
+
194
+ dropdown_node
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,200 @@
1
+ module EasyAdmin
2
+ module Layouts
3
+ # DSL module to be included in Resource classes
4
+ module DSL
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :show_layout_definition, default: nil
9
+ class_attribute :form_layout_definition, default: nil
10
+ class_attribute :index_layout_definition, default: nil
11
+ end
12
+
13
+ class_methods do
14
+ # Define show page layout
15
+ def show(component_or_block = nil, &block)
16
+ if component_or_block.is_a?(Class)
17
+ # Direct component mode
18
+ self.show_layout_definition = [:component, component_or_block]
19
+ elsif block_given?
20
+ # DSL mode - build AST
21
+ builder = Builders::ShowLayoutBuilder.new(resource_class: self)
22
+ builder.instance_exec(&block)
23
+ self.show_layout_definition = [:ast, builder.build]
24
+ else
25
+ # Return current definition
26
+ self.show_layout_definition
27
+ end
28
+ end
29
+
30
+ # Define form page layout
31
+ def form(component_or_block = nil, &block)
32
+ if component_or_block.is_a?(Class)
33
+ # Direct component mode
34
+ self.form_layout_definition = [:component, component_or_block]
35
+ elsif block_given?
36
+ # DSL mode - build AST
37
+ builder = Builders::FormLayoutBuilder.new(resource_class: self)
38
+ builder.instance_exec(&block)
39
+ self.form_layout_definition = [:ast, builder.build]
40
+ else
41
+ # Return current definition
42
+ self.form_layout_definition
43
+ end
44
+ end
45
+
46
+ # Define index page layout
47
+ def index(component_or_block = nil, &block)
48
+ if component_or_block.is_a?(Class)
49
+ # Direct component mode
50
+ self.index_layout_definition = [:component, component_or_block]
51
+ elsif block_given?
52
+ # DSL mode - build AST
53
+ builder = Builders::IndexLayoutBuilder.new(resource_class: self)
54
+ builder.instance_exec(&block)
55
+ self.index_layout_definition = [:ast, builder.build]
56
+ else
57
+ # Return current definition
58
+ self.index_layout_definition
59
+ end
60
+ end
61
+
62
+ # Check if custom show layout is defined
63
+ def has_custom_show_layout?
64
+ !show_layout_definition.nil?
65
+ end
66
+
67
+ # Check if custom form layout is defined
68
+ def has_custom_form_layout?
69
+ !form_layout_definition.nil?
70
+ end
71
+
72
+ # Check if custom index layout is defined
73
+ def has_custom_index_layout?
74
+ !index_layout_definition.nil?
75
+ end
76
+
77
+ # Check if form layout contains tabs
78
+ def has_form_tabs?
79
+ return false unless has_custom_form_layout?
80
+
81
+ # Build the form layout to check if it contains tabs
82
+ begin
83
+ builder = Builders::FormLayoutBuilder.new(resource_class: self)
84
+ builder.instance_exec(&form_layout_content)
85
+ root_node = builder.build
86
+
87
+ # Check if the AST contains any tabs nodes
88
+ contains_tabs_node?(root_node)
89
+ rescue => e
90
+ false
91
+ end
92
+ end
93
+
94
+ # Get show layout type
95
+ def show_layout_type
96
+ return nil unless show_layout_definition
97
+ show_layout_definition[0]
98
+ end
99
+
100
+ # Get form layout type
101
+ def form_layout_type
102
+ return nil unless form_layout_definition
103
+ form_layout_definition[0]
104
+ end
105
+
106
+ # Get show layout content
107
+ def show_layout_content
108
+ return nil unless show_layout_definition
109
+ show_layout_definition[1]
110
+ end
111
+
112
+ # Get form layout content
113
+ def form_layout_content
114
+ return nil unless form_layout_definition
115
+ form_layout_definition[1]
116
+ end
117
+
118
+ # Get index layout type
119
+ def index_layout_type
120
+ return nil unless index_layout_definition
121
+ index_layout_definition[0]
122
+ end
123
+
124
+ # Get index layout content
125
+ def index_layout_content
126
+ return nil unless index_layout_definition
127
+ index_layout_definition[1]
128
+ end
129
+
130
+ # Reset layouts
131
+ def reset_layouts!
132
+ self.show_layout_definition = nil
133
+ self.form_layout_definition = nil
134
+ self.index_layout_definition = nil
135
+ end
136
+
137
+ # Generate default show layout from fields
138
+ def generate_default_show_layout
139
+ builder = Builders::ShowLayoutBuilder.new(resource_class: self)
140
+
141
+ # Group fields by category or just show all
142
+ if fields_config.any?
143
+ builder.card(title: resource_title) do
144
+ builder.grid(columns: 2) do
145
+ fields_config.each do |field_config|
146
+ next if field_config[:show] == false
147
+ builder.field(field_config[:name], **field_config.except(:name))
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ [:ast, builder.build]
154
+ end
155
+
156
+ # Generate default form layout from fields
157
+ def generate_default_form_layout
158
+ builder = Builders::FormLayoutBuilder.new(resource_class: self)
159
+
160
+ if fields_config.any?
161
+ # Group editable fields
162
+ editable_fields = fields_config.reject { |f| f[:readonly] || f[:form] == false }
163
+
164
+ if editable_fields.any?
165
+ builder.section("Details") do
166
+ editable_fields.each do |field_config|
167
+ builder.field(field_config[:name], **field_config.except(:name))
168
+ end
169
+ end
170
+ end
171
+
172
+ builder.form_actions do
173
+ builder.submit
174
+ builder.cancel
175
+ end
176
+ end
177
+
178
+ [:ast, builder.build]
179
+ end
180
+
181
+ private
182
+
183
+ # Recursively check if AST contains tabs nodes
184
+ def contains_tabs_node?(node)
185
+ return false unless node
186
+
187
+ # Check if current node is a tabs node
188
+ return true if node.is_a?(EasyAdmin::Layouts::Nodes::Tabs)
189
+
190
+ # Check children recursively
191
+ if node.respond_to?(:children) && node.children
192
+ node.children.any? { |child| contains_tabs_node?(child) }
193
+ else
194
+ false
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end