easy-admin-rails 0.2.5 → 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.
- checksums.yaml +4 -4
- data/app/assets/builds/easy_admin.base.js +95 -0
- data/app/assets/builds/easy_admin.base.js.map +3 -3
- data/app/assets/builds/easy_admin.css +226 -0
- data/app/components/easy_admin/fields/form/belongs_to_component.rb +0 -1
- data/app/components/easy_admin/form_layout_component.rb +553 -0
- data/app/components/easy_admin/navbar_component.rb +19 -4
- data/app/components/easy_admin/permissions/user_role_permissions_component.rb +1 -3
- data/app/components/easy_admin/profile/change_password_modal_component.rb +75 -0
- data/app/components/easy_admin/profile/profile_tab_component.rb +92 -0
- data/app/components/easy_admin/profile/security_tab_component.rb +53 -0
- data/app/components/easy_admin/profile/settings_component.rb +103 -0
- data/app/components/easy_admin/show_layout_component.rb +694 -24
- data/app/components/easy_admin/two_factor/backup_codes_component.rb +118 -0
- data/app/components/easy_admin/two_factor/setup_component.rb +124 -0
- data/app/components/easy_admin/two_factor/status_component.rb +92 -0
- data/app/controllers/concerns/easy_admin/two_factor.rb +110 -0
- data/app/controllers/easy_admin/application_controller.rb +10 -5
- data/app/controllers/easy_admin/batch_actions_controller.rb +0 -1
- data/app/controllers/easy_admin/concerns/inline_field_editing.rb +4 -11
- data/app/controllers/easy_admin/concerns/resource_loading.rb +10 -9
- data/app/controllers/easy_admin/concerns/resource_pagination.rb +3 -0
- data/app/controllers/easy_admin/dashboards_controller.rb +0 -1
- data/app/controllers/easy_admin/profile_controller.rb +25 -0
- data/app/controllers/easy_admin/resources_controller.rb +1 -5
- data/app/controllers/easy_admin/row_actions_controller.rb +1 -4
- data/app/controllers/easy_admin/sessions_controller.rb +107 -1
- data/app/helpers/easy_admin/fields_helper.rb +8 -22
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +12 -0
- data/app/javascript/easy_admin/controllers/vertical_tabs_controller.js +112 -0
- data/app/javascript/easy_admin/controllers.js +3 -1
- data/app/models/easy_admin/admin_user.rb +3 -0
- data/app/views/easy_admin/profile/backup_codes_regenerated.turbo_stream.erb +12 -0
- data/app/views/easy_admin/profile/change_password.html.erb +24 -0
- data/app/views/easy_admin/profile/index.html.erb +1 -0
- data/app/views/easy_admin/profile/password_error.turbo_stream.erb +6 -0
- data/app/views/easy_admin/profile/password_invalid_current.turbo_stream.erb +6 -0
- data/app/views/easy_admin/profile/password_updated.turbo_stream.erb +9 -0
- data/app/views/easy_admin/profile/two_factor_backup_codes.html.erb +24 -0
- data/app/views/easy_admin/profile/two_factor_enabled.turbo_stream.erb +12 -0
- data/app/views/easy_admin/profile/two_factor_invalid_code.turbo_stream.erb +6 -0
- data/app/views/easy_admin/profile/two_factor_not_enabled.turbo_stream.erb +6 -0
- data/app/views/easy_admin/profile/two_factor_setup.html.erb +24 -0
- data/app/views/easy_admin/profile/two_factor_unavailable.turbo_stream.erb +6 -0
- data/app/views/easy_admin/resources/edit.html.erb +2 -2
- data/app/views/easy_admin/resources/new.html.erb +2 -2
- data/app/views/easy_admin/resources/show.html.erb +3 -1
- data/app/views/easy_admin/sessions/two_factor_verification.html.erb +48 -0
- data/app/views/easy_admin/sessions/verify_2fa_error.turbo_stream.erb +13 -0
- data/config/routes.rb +20 -1
- data/lib/easy-admin-rails.rb +1 -0
- data/lib/easy_admin/field.rb +3 -2
- data/lib/easy_admin/layouts/builders/base_layout_builder.rb +245 -0
- data/lib/easy_admin/layouts/builders/form_layout_builder.rb +208 -0
- data/lib/easy_admin/layouts/builders/index_layout_builder.rb +22 -0
- data/lib/easy_admin/layouts/builders/show_layout_builder.rb +199 -0
- data/lib/easy_admin/layouts/dsl.rb +200 -0
- data/lib/easy_admin/layouts/layout_context.rb +189 -0
- data/lib/easy_admin/layouts/nodes/base_node.rb +88 -0
- data/lib/easy_admin/layouts/nodes/divider.rb +27 -0
- data/lib/easy_admin/layouts/nodes/field_node.rb +57 -0
- data/lib/easy_admin/layouts/nodes/grid.rb +60 -0
- data/lib/easy_admin/layouts/nodes/render_node.rb +41 -0
- data/lib/easy_admin/layouts/nodes/root.rb +25 -0
- data/lib/easy_admin/layouts/nodes/section.rb +46 -0
- data/lib/easy_admin/layouts/nodes/spacer.rb +17 -0
- data/lib/easy_admin/layouts/nodes/stubs.rb +109 -0
- data/lib/easy_admin/layouts/nodes/tab.rb +40 -0
- data/lib/easy_admin/layouts/nodes/tabs.rb +40 -0
- data/lib/easy_admin/layouts.rb +28 -0
- data/lib/easy_admin/permissions/resource_permissions.rb +1 -5
- data/lib/easy_admin/resource/base.rb +2 -2
- data/lib/easy_admin/resource/dsl.rb +2 -11
- data/lib/easy_admin/resource/field_registry.rb +58 -2
- data/lib/easy_admin/resource.rb +0 -9
- data/lib/easy_admin/resource_modules.rb +21 -4
- data/lib/easy_admin/two_factor_authentication.rb +156 -0
- data/lib/easy_admin/version.rb +1 -1
- data/lib/generators/easy_admin/permissions/install_generator.rb +0 -10
- data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +33 -3
- data/lib/generators/easy_admin/two_factor/templates/README +29 -0
- data/lib/generators/easy_admin/two_factor/templates/migration.rb +10 -0
- data/lib/generators/easy_admin/two_factor/two_factor_generator.rb +22 -0
- metadata +49 -9
- data/lib/easy_admin/resource/form_builder.rb +0 -123
- data/lib/easy_admin/resource/layout_builder.rb +0 -249
- data/lib/easy_admin/resource/show_builder.rb +0 -359
- data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +0 -6
- data/lib/generators/easy_admin/rbac/rbac_generator.rb +0 -244
- data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +0 -23
- data/lib/generators/easy_admin/rbac/templates/super_admin.rb +0 -34
data/lib/easy_admin/field.rb
CHANGED
@@ -92,9 +92,9 @@ module EasyAdmin
|
|
92
92
|
|
93
93
|
begin
|
94
94
|
action_component_name.constantize
|
95
|
-
rescue NameError
|
95
|
+
rescue NameError => e
|
96
96
|
# Fallback to base text component for the action
|
97
|
-
case action
|
97
|
+
fallback_class = case action
|
98
98
|
when :index
|
99
99
|
EasyAdmin::Fields::Index::TextComponent
|
100
100
|
when :show
|
@@ -104,6 +104,7 @@ module EasyAdmin
|
|
104
104
|
else
|
105
105
|
EasyAdmin::Fields::Index::TextComponent
|
106
106
|
end
|
107
|
+
fallback_class
|
107
108
|
end
|
108
109
|
end
|
109
110
|
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Layouts
|
3
|
+
module Builders
|
4
|
+
# Base builder for constructing layout AST
|
5
|
+
class BaseLayoutBuilder
|
6
|
+
attr_reader :root_node, :resource_class
|
7
|
+
attr_accessor :current_container_stack
|
8
|
+
|
9
|
+
def initialize(layout_type = :show, resource_class: nil)
|
10
|
+
@root_node = Nodes::Root.new(layout_type: layout_type)
|
11
|
+
@current_container_stack = [@root_node]
|
12
|
+
@resource_class = resource_class
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get current container for adding nodes
|
16
|
+
def current_container
|
17
|
+
@current_container_stack.last
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add node to current container
|
21
|
+
def add_node(node)
|
22
|
+
current_container.add_child(node)
|
23
|
+
node
|
24
|
+
end
|
25
|
+
|
26
|
+
# Execute block with new container
|
27
|
+
def with_container(container_node, &block)
|
28
|
+
add_node(container_node)
|
29
|
+
@current_container_stack.push(container_node)
|
30
|
+
instance_exec(&block) if block_given?
|
31
|
+
@current_container_stack.pop
|
32
|
+
container_node
|
33
|
+
end
|
34
|
+
|
35
|
+
# DSL Methods
|
36
|
+
|
37
|
+
# Add tabs container
|
38
|
+
def tabs(type: :horizontal, **attributes, &block)
|
39
|
+
tabs_node = Nodes::Tabs.new(attributes.merge(type: type))
|
40
|
+
add_node(tabs_node)
|
41
|
+
|
42
|
+
if block_given?
|
43
|
+
@current_container_stack.push(tabs_node)
|
44
|
+
tabs_builder = TabsBuilder.new(self, tabs_node)
|
45
|
+
tabs_builder.instance_exec(&block)
|
46
|
+
@current_container_stack.pop
|
47
|
+
end
|
48
|
+
|
49
|
+
tabs_node
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add section
|
53
|
+
def section(title = nil, **attributes, &block)
|
54
|
+
section_node = Nodes::Section.new(title, attributes)
|
55
|
+
|
56
|
+
if block_given?
|
57
|
+
with_container(section_node, &block)
|
58
|
+
else
|
59
|
+
add_node(section_node)
|
60
|
+
end
|
61
|
+
|
62
|
+
section_node
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add grid layout
|
66
|
+
def grid(columns: 2, **attributes, &block)
|
67
|
+
grid_node = Nodes::Grid.new(attributes.merge(columns: columns))
|
68
|
+
|
69
|
+
if block_given?
|
70
|
+
with_container(grid_node, &block)
|
71
|
+
else
|
72
|
+
add_node(grid_node)
|
73
|
+
end
|
74
|
+
|
75
|
+
grid_node
|
76
|
+
end
|
77
|
+
|
78
|
+
# Add field
|
79
|
+
def field(name, **options)
|
80
|
+
field_node = Nodes::FieldNode.new(name, options)
|
81
|
+
add_node(field_node)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Add multiple fields at once
|
85
|
+
def fields(*names, **common_options)
|
86
|
+
names.each do |name|
|
87
|
+
field(name, **common_options)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Render custom component
|
92
|
+
def render(component_class, **props)
|
93
|
+
render_node = Nodes::RenderNode.new(component_class, props)
|
94
|
+
add_node(render_node)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Add divider
|
98
|
+
def divider(**attributes)
|
99
|
+
add_node(Nodes::Divider.new(attributes))
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add spacer
|
103
|
+
def spacer(size: 4, **attributes)
|
104
|
+
add_node(Nodes::Spacer.new(attributes.merge(size: size)))
|
105
|
+
end
|
106
|
+
|
107
|
+
# Build and return the AST
|
108
|
+
def build
|
109
|
+
@root_node
|
110
|
+
end
|
111
|
+
|
112
|
+
# Allow custom DSL extensions
|
113
|
+
def method_missing(method, *args, &block)
|
114
|
+
# Check if it's a registered component type
|
115
|
+
if EasyAdmin::Layouts.registered_components.key?(method)
|
116
|
+
component_class = EasyAdmin::Layouts.registered_components[method]
|
117
|
+
render(component_class, *args)
|
118
|
+
else
|
119
|
+
super
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def respond_to_missing?(method, include_private = false)
|
124
|
+
EasyAdmin::Layouts.registered_components.key?(method) ||
|
125
|
+
field_method?(method) ||
|
126
|
+
super
|
127
|
+
end
|
128
|
+
|
129
|
+
# Field DSL delegation methods - delegate to resource class
|
130
|
+
|
131
|
+
def id_field(**options)
|
132
|
+
delegate_to_resource(:id_field, **options)
|
133
|
+
field(:id, type: :number, **options)
|
134
|
+
end
|
135
|
+
|
136
|
+
def text_field(name, **options)
|
137
|
+
delegate_to_resource(:text_field, name, **options)
|
138
|
+
field(name, type: :string, **options)
|
139
|
+
end
|
140
|
+
|
141
|
+
def textarea_field(name, **options)
|
142
|
+
delegate_to_resource(:textarea_field, name, **options)
|
143
|
+
field(name, type: :text, **options)
|
144
|
+
end
|
145
|
+
|
146
|
+
def number_field(name, **options)
|
147
|
+
delegate_to_resource(:number_field, name, **options)
|
148
|
+
field(name, type: :number, **options)
|
149
|
+
end
|
150
|
+
|
151
|
+
def email_field(name, **options)
|
152
|
+
delegate_to_resource(:email_field, name, **options)
|
153
|
+
field(name, type: :email, **options)
|
154
|
+
end
|
155
|
+
|
156
|
+
def date_field(name, **options)
|
157
|
+
delegate_to_resource(:date_field, name, **options)
|
158
|
+
field(name, type: :date, **options)
|
159
|
+
end
|
160
|
+
|
161
|
+
def datetime_field(name, **options)
|
162
|
+
delegate_to_resource(:datetime_field, name, **options)
|
163
|
+
field(name, type: :datetime, **options)
|
164
|
+
end
|
165
|
+
|
166
|
+
def boolean_field(name, **options)
|
167
|
+
delegate_to_resource(:boolean_field, name, **options)
|
168
|
+
field(name, type: :boolean, **options)
|
169
|
+
end
|
170
|
+
|
171
|
+
def select_field(name, **options)
|
172
|
+
delegate_to_resource(:select_field, name, **options)
|
173
|
+
field(name, type: :select, **options)
|
174
|
+
end
|
175
|
+
|
176
|
+
def belongs_to_field(name, **options)
|
177
|
+
delegate_to_resource(:belongs_to_field, name, **options)
|
178
|
+
field(name, type: :belongs_to, **options)
|
179
|
+
end
|
180
|
+
|
181
|
+
def has_many_field(name, **options)
|
182
|
+
delegate_to_resource(:has_many_field, name, **options)
|
183
|
+
field(name, type: :has_many, **options)
|
184
|
+
end
|
185
|
+
|
186
|
+
def file_field(name, **options)
|
187
|
+
delegate_to_resource(:file_field, name, **options)
|
188
|
+
field(name, type: :file, **options)
|
189
|
+
end
|
190
|
+
|
191
|
+
def json_field(name, **options)
|
192
|
+
delegate_to_resource(:json_field, name, **options)
|
193
|
+
field(name, type: :json, **options)
|
194
|
+
end
|
195
|
+
|
196
|
+
def password_field(name = :password, **options)
|
197
|
+
delegate_to_resource(:password_field, name, **options)
|
198
|
+
field(name, type: :password, **options)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Delegate content method calls
|
202
|
+
def content(&block)
|
203
|
+
content_node = Nodes::Content.new(block: block)
|
204
|
+
add_node(content_node)
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def field_method?(method)
|
210
|
+
method.to_s.end_with?('_field') && resource_class&.respond_to?(method)
|
211
|
+
end
|
212
|
+
|
213
|
+
def delegate_to_resource(method, *args, **options)
|
214
|
+
if resource_class
|
215
|
+
resource_class.send(method, *args, **options)
|
216
|
+
else
|
217
|
+
raise "Resource class not available for field delegation"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Specialized builder for tabs
|
223
|
+
class TabsBuilder
|
224
|
+
def initialize(parent_builder, tabs_node)
|
225
|
+
@parent_builder = parent_builder
|
226
|
+
@tabs_node = tabs_node
|
227
|
+
end
|
228
|
+
|
229
|
+
def tab(name, **attributes, &block)
|
230
|
+
tab_node = Nodes::Tab.new(name, attributes)
|
231
|
+
@tabs_node.add_child(tab_node)
|
232
|
+
|
233
|
+
if block_given?
|
234
|
+
# Push tab as current container and execute block in parent builder context
|
235
|
+
@parent_builder.current_container_stack.push(tab_node)
|
236
|
+
@parent_builder.instance_exec(&block)
|
237
|
+
@parent_builder.current_container_stack.pop
|
238
|
+
end
|
239
|
+
|
240
|
+
tab_node
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
@@ -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
|