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
@@ -1,249 +0,0 @@
1
- module EasyAdmin
2
- module ResourceModules
3
- # LayoutBuilder module for handling form and show layout configuration
4
- # Manages form tabs and show page layout elements
5
- module LayoutBuilder
6
- extend ActiveSupport::Concern
7
-
8
- included do
9
- class_attribute :form_tabs_config, :show_layout_config
10
-
11
- def self.initialize_layout_builder
12
- self.form_tabs_config = []
13
- self.show_layout_config = []
14
- end
15
- end
16
-
17
- class_methods do
18
- # Form tabs management
19
- def add_form_tab(tab_config)
20
- self.form_tabs_config = (self.form_tabs_config || []) + [tab_config]
21
- end
22
-
23
- def form_tabs
24
- form_tabs_config
25
- end
26
-
27
- def has_form_tabs?
28
- form_tabs_config.any?
29
- end
30
-
31
- def clear_form_tabs
32
- self.form_tabs_config = []
33
- end
34
-
35
- # Show layout management
36
- def add_show_layout_element(element_config)
37
- self.show_layout_config = (self.show_layout_config || []) + [element_config]
38
- end
39
-
40
- def show_layout
41
- show_layout_config
42
- end
43
-
44
- def has_show_layout?
45
- show_layout_config.any?
46
- end
47
-
48
- def clear_show_layout
49
- self.show_layout_config = []
50
- end
51
-
52
- # Form tab utilities
53
- def find_form_tab(tab_name)
54
- form_tabs_config.find { |tab| tab[:name].to_s == tab_name.to_s }
55
- end
56
-
57
- def form_tab_names
58
- form_tabs_config.map { |tab| tab[:name] }
59
- end
60
-
61
- def form_tab_fields(tab_name)
62
- tab = find_form_tab(tab_name)
63
- tab ? tab[:fields] : []
64
- end
65
-
66
- def total_form_tabs_count
67
- form_tabs_config.count
68
- end
69
-
70
- # Show layout utilities
71
- def show_layout_rows
72
- show_layout_config.select { |element| element[:type] == :row }
73
- end
74
-
75
- def show_layout_elements_count
76
- count = 0
77
- show_layout_config.each do |element|
78
- count += count_nested_elements(element)
79
- end
80
- count
81
- end
82
-
83
- def find_show_layout_element(element_type)
84
- find_element_in_layout(show_layout_config, element_type)
85
- end
86
-
87
- def has_show_layout_tabs?
88
- find_show_layout_element(:tabs).present?
89
- end
90
-
91
- def has_show_layout_cards?
92
- find_show_layout_element(:card).present?
93
- end
94
-
95
- def has_metric_cards?
96
- find_show_layout_element(:metric_card).present?
97
- end
98
-
99
- def has_chart_cards?
100
- find_show_layout_element(:chart_card).present?
101
- end
102
-
103
- # Layout validation
104
- def validate_form_structure
105
- errors = []
106
-
107
- if has_form_tabs?
108
- form_tabs_config.each_with_index do |tab, index|
109
- unless tab[:name].present?
110
- errors << "Form tab at index #{index} is missing a name"
111
- end
112
-
113
- unless tab[:fields].is_a?(Array)
114
- errors << "Form tab '#{tab[:name]}' fields must be an array"
115
- end
116
- end
117
- end
118
-
119
- errors
120
- end
121
-
122
- def validate_show_layout_structure
123
- errors = []
124
-
125
- show_layout_config.each_with_index do |element, index|
126
- case element[:type]
127
- when :row
128
- unless element[:columns].is_a?(Array)
129
- errors << "Row at index #{index} must have columns array"
130
- end
131
-
132
- if element[:columns].empty?
133
- errors << "Row at index #{index} cannot have empty columns"
134
- end
135
- when :column
136
- unless element[:elements].is_a?(Array)
137
- errors << "Column at index #{index} must have elements array"
138
- end
139
- end
140
- end
141
-
142
- errors
143
- end
144
-
145
- # Layout rebuild methods
146
- def rebuild_form_from_fields
147
- return if fields_config.empty?
148
-
149
- # Create a default tab with all non-readonly fields
150
- default_tab = {
151
- name: "General",
152
- label: "General",
153
- icon: nil,
154
- fields: fields_config.reject { |field| field[:readonly] }
155
- }
156
-
157
- self.form_tabs_config = [default_tab]
158
- end
159
-
160
- def rebuild_show_layout_from_fields
161
- return if fields_config.empty?
162
-
163
- # Create a simple single-column layout with all fields
164
- row_config = {
165
- type: :row,
166
- columns_count: 1,
167
- spacing: "medium",
168
- columns: [{
169
- type: :column,
170
- size: 12,
171
- elements: [{
172
- type: :card,
173
- title: "Details",
174
- color: "light",
175
- padding: "medium",
176
- shadow: "small",
177
- elements: fields_config.map { |field|
178
- {
179
- type: :field,
180
- name: field[:name],
181
- label: field[:label],
182
- field_type: :default,
183
- format: field[:format],
184
- css_classes: nil,
185
- show_label: true
186
- }
187
- }
188
- }]
189
- }]
190
- }
191
-
192
- self.show_layout_config = [row_config]
193
- end
194
-
195
- private
196
-
197
- def count_nested_elements(element)
198
- count = 1
199
-
200
- case element[:type]
201
- when :row
202
- element[:columns]&.each { |column| count += count_nested_elements(column) }
203
- when :column
204
- element[:elements]&.each { |child| count += count_nested_elements(child) }
205
- when :card
206
- element[:elements]&.each { |child| count += count_nested_elements(child) }
207
- when :tabs
208
- element[:tabs]&.each { |tab| count += count_nested_elements(tab) }
209
- when :tab
210
- element[:elements]&.each { |child| count += count_nested_elements(child) }
211
- end
212
-
213
- count
214
- end
215
-
216
- def find_element_in_layout(layout, element_type)
217
- layout.each do |element|
218
- return element if element[:type] == element_type
219
-
220
- case element[:type]
221
- when :row
222
- element[:columns]&.each do |column|
223
- found = find_element_in_layout([column], element_type)
224
- return found if found
225
- end
226
- when :column, :card
227
- if element[:elements]
228
- found = find_element_in_layout(element[:elements], element_type)
229
- return found if found
230
- end
231
- when :tabs
232
- element[:tabs]&.each do |tab|
233
- found = find_element_in_layout([tab], element_type)
234
- return found if found
235
- end
236
- when :tab
237
- if element[:elements]
238
- found = find_element_in_layout(element[:elements], element_type)
239
- return found if found
240
- end
241
- end
242
- end
243
-
244
- nil
245
- end
246
- end
247
- end
248
- end
249
- end
@@ -1,359 +0,0 @@
1
- module EasyAdmin
2
- module ResourceModules
3
- # ShowBuilder class for show layout DSL
4
- # Handles all show page layout definitions with rows, columns, cards, and tabs
5
- class ShowBuilder
6
- PREDEFINED_COLORS = %w[primary secondary success danger warning info light dark white transparent].freeze
7
- COLUMN_SIZES = [1, 2, 3, 4, 6, 8, 12].freeze
8
- SPACING_OPTIONS = %w[none small medium large].freeze
9
- PADDING_OPTIONS = %w[none small medium large].freeze
10
- HEADING_SIZES = %w[small medium large].freeze
11
- DIVIDER_STYLES = %w[default dashed dotted thick].freeze
12
- SHADOW_OPTIONS = %w[none small medium large].freeze
13
-
14
- def initialize(resource_class)
15
- @resource_class = resource_class
16
- @context_stack = []
17
- @current_row = nil
18
- @current_column = nil
19
- @current_card = nil
20
- @current_tab_container = nil
21
- @current_tab = nil
22
- end
23
-
24
- # Layout methods
25
- def row(columns: 1, spacing: "medium", &block)
26
- validate_spacing!(spacing)
27
-
28
- row_config = {
29
- type: :row,
30
- columns_count: columns,
31
- spacing: spacing,
32
- columns: []
33
- }
34
-
35
- push_context
36
- @current_row = row_config
37
-
38
- if @current_column
39
- @current_column[:elements] << row_config
40
- else
41
- @resource_class.add_show_layout_element(row_config)
42
- end
43
-
44
- instance_eval(&block) if block_given?
45
- pop_context
46
- end
47
-
48
- def column(size: nil, &block)
49
- raise "column must be called within a row block" unless @current_row
50
-
51
- calculated_size = size || (12 / [@current_row[:columns_count], 1].max)
52
- validate_column_size!(calculated_size)
53
-
54
- column_config = {
55
- type: :column,
56
- size: calculated_size,
57
- elements: []
58
- }
59
-
60
- push_context
61
- @current_column = column_config
62
- @current_row[:columns] << column_config
63
-
64
- instance_eval(&block) if block_given?
65
- pop_context
66
- end
67
-
68
- # Card methods
69
- def card(title: nil, color: "light", padding: "medium", shadow: "small", &block)
70
- raise "card must be called within a column block" unless @current_column
71
- validate_color!(color)
72
- validate_padding!(padding)
73
- validate_shadow!(shadow)
74
-
75
- card_config = {
76
- type: :card,
77
- title: title,
78
- color: color,
79
- padding: padding,
80
- shadow: shadow,
81
- elements: []
82
- }
83
-
84
- push_context
85
- @current_card = card_config
86
- @current_column[:elements] << card_config
87
-
88
- instance_eval(&block) if block_given?
89
- pop_context
90
- end
91
-
92
- def metric_card(title:, value:, color: "primary", icon: nil, trend: nil)
93
- raise "metric_card must be called within a column block" unless @current_column
94
- validate_color!(color)
95
-
96
- metric_config = {
97
- type: :metric_card,
98
- title: title,
99
- value: value,
100
- icon: icon,
101
- color: color,
102
- trend: trend
103
- }
104
-
105
- @current_column[:elements] << metric_config
106
- end
107
-
108
- def chart_card(title:, chart_type:, **options, &block)
109
- raise "chart_card must be called within a column block" unless @current_column
110
-
111
- chart_config = {
112
- type: :chart_card,
113
- title: title,
114
- chart_type: chart_type,
115
- height: options[:height] || 350,
116
- data_source: options[:data_source],
117
- css_classes: options[:class] || "card",
118
- config: {}
119
- }
120
-
121
- if block_given?
122
- chart_builder = ChartBuilder.new
123
- chart_builder.instance_eval(&block)
124
- chart_config[:config] = chart_builder.config
125
- end
126
-
127
- @current_column[:elements] << chart_config
128
- end
129
-
130
- # Tab methods
131
- def tabs(**options, &block)
132
- container = @current_card || @current_column
133
- raise "tabs must be called within a card or column block" unless container
134
-
135
- tabs_config = {
136
- type: :tabs,
137
- css_classes: options[:class] || "tabs-menu",
138
- tabs: []
139
- }
140
-
141
- push_context
142
- @current_tab_container = tabs_config
143
- container[:elements] << tabs_config
144
-
145
- instance_eval(&block) if block_given?
146
- pop_context
147
- end
148
-
149
- def tab(name, **options, &block)
150
- raise "tab must be called within a tabs block" unless @current_tab_container
151
-
152
- tab_config = {
153
- name: name,
154
- label: options[:label] || name,
155
- icon: options[:icon],
156
- active: options[:active] || false,
157
- elements: []
158
- }
159
-
160
- push_context
161
- @current_tab = tab_config
162
- @current_tab_container[:tabs] << tab_config
163
-
164
- instance_eval(&block) if block_given?
165
- pop_context
166
- end
167
-
168
- # Field display methods
169
- def field(name, **options)
170
- container = @current_tab || @current_card || @current_column
171
- raise "field must be called within a tab, card, or column block" unless container
172
-
173
- label_value = if options.key?(:label) && options[:label] == false
174
- nil
175
- else
176
- options[:label] || name.to_s.humanize
177
- end
178
-
179
- field_config = {
180
- type: :field,
181
- name: name,
182
- label: label_value,
183
- field_type: options[:field_type] || :default,
184
- format: options[:format],
185
- css_classes: options[:class],
186
- show_label: options.fetch(:show_label, true)
187
- }
188
-
189
- container[:elements] << field_config
190
- end
191
-
192
- def fields(*field_names, **options)
193
- field_names.each do |name|
194
- field(name, **options)
195
- end
196
- end
197
-
198
- # Component rendering support with context
199
- def render(component_class_or_instance, **options)
200
- container = @current_tab || @current_card || @current_column
201
- raise "render must be called within a tab, card, or column block" unless container
202
-
203
- component_config = {
204
- type: :custom_component,
205
- component_class: component_class_or_instance.class,
206
- component_options: options,
207
- component_instance: component_class_or_instance
208
- }
209
-
210
- container[:elements] << component_config
211
- component_class_or_instance
212
- end
213
-
214
- # Content methods
215
- def content(html = nil, **options, &block)
216
- container = @current_tab || @current_card || @current_column
217
- raise "content must be called within a tab, card, or column block" unless container
218
-
219
- content_config = {
220
- type: :content,
221
- html: html,
222
- css_classes: options[:class],
223
- block: block
224
- }
225
-
226
- container[:elements] << content_config
227
- end
228
-
229
- def heading(text, level: 2, size: "medium")
230
- container = @current_tab || @current_card || @current_column
231
- raise "heading must be called within a tab, card, or column block" unless container
232
-
233
- validate_heading_level!(level)
234
- validate_heading_size!(size)
235
-
236
- heading_config = {
237
- type: :heading,
238
- text: text,
239
- level: level,
240
- size: size
241
- }
242
-
243
- container[:elements] << heading_config
244
- end
245
-
246
- def divider(style: "default")
247
- container = @current_tab || @current_card || @current_column
248
- raise "divider must be called within a tab, card, or column block" unless container
249
-
250
- validate_divider_style!(style)
251
-
252
- divider_config = {
253
- type: :divider,
254
- style: style
255
- }
256
-
257
- container[:elements] << divider_config
258
- end
259
-
260
- private
261
-
262
- def validate_color!(color)
263
- return if PREDEFINED_COLORS.include?(color.to_s)
264
- raise ArgumentError, "Invalid color '#{color}'. Available colors: #{PREDEFINED_COLORS.join(', ')}"
265
- end
266
-
267
- def validate_column_size!(size)
268
- return if COLUMN_SIZES.include?(size)
269
- raise ArgumentError, "Invalid column size '#{size}'. Available sizes: #{COLUMN_SIZES.join(', ')}"
270
- end
271
-
272
- def validate_spacing!(spacing)
273
- return if SPACING_OPTIONS.include?(spacing.to_s)
274
- raise ArgumentError, "Invalid spacing '#{spacing}'. Available options: #{SPACING_OPTIONS.join(', ')}"
275
- end
276
-
277
- def validate_padding!(padding)
278
- return if PADDING_OPTIONS.include?(padding.to_s)
279
- raise ArgumentError, "Invalid padding '#{padding}'. Available options: #{PADDING_OPTIONS.join(', ')}"
280
- end
281
-
282
- def validate_shadow!(shadow)
283
- return if SHADOW_OPTIONS.include?(shadow.to_s)
284
- raise ArgumentError, "Invalid shadow '#{shadow}'. Available options: #{SHADOW_OPTIONS.join(', ')}"
285
- end
286
-
287
- def validate_heading_level!(level)
288
- return if (1..6).include?(level)
289
- raise ArgumentError, "Invalid heading level '#{level}'. Must be between 1 and 6"
290
- end
291
-
292
- def validate_heading_size!(size)
293
- return if HEADING_SIZES.include?(size.to_s)
294
- raise ArgumentError, "Invalid heading size '#{size}'. Available sizes: #{HEADING_SIZES.join(', ')}"
295
- end
296
-
297
- def validate_divider_style!(style)
298
- return if DIVIDER_STYLES.include?(style.to_s)
299
- raise ArgumentError, "Invalid divider style '#{style}'. Available styles: #{DIVIDER_STYLES.join(', ')}"
300
- end
301
-
302
- def push_context
303
- @context_stack.push({
304
- row: @current_row,
305
- column: @current_column,
306
- card: @current_card,
307
- tab_container: @current_tab_container,
308
- tab: @current_tab
309
- })
310
- end
311
-
312
- def pop_context
313
- if @context_stack.any?
314
- context = @context_stack.pop
315
- @current_row = context[:row]
316
- @current_column = context[:column]
317
- @current_card = context[:card]
318
- @current_tab_container = context[:tab_container]
319
- @current_tab = context[:tab]
320
- else
321
- @current_row = nil
322
- @current_column = nil
323
- @current_card = nil
324
- @current_tab_container = nil
325
- @current_tab = nil
326
- end
327
- end
328
- end
329
-
330
- # ChartBuilder class for chart configuration
331
- class ChartBuilder
332
- attr_reader :config
333
-
334
- def initialize
335
- @config = {}
336
- end
337
-
338
- def series(data)
339
- @config[:series] = data
340
- end
341
-
342
- def categories(data)
343
- @config[:categories] = data
344
- end
345
-
346
- def colors(data)
347
- @config[:colors] = data
348
- end
349
-
350
- def title(text)
351
- @config[:title] = text
352
- end
353
-
354
- def subtitle(text)
355
- @config[:subtitle] = text
356
- end
357
- end
358
- end
359
- end
@@ -1,6 +0,0 @@
1
- class UpdateUsersForEasyAdminPermissions < ActiveRecord::Migration<%= migration_version %>
2
- def change
3
- add_column :<%= user_model_name.tableize %>, :permissions_cache, :json, default: {}, null: false
4
- add_index :<%= user_model_name.tableize %>, :permissions_cache, using: :gin
5
- end
6
- end