terrazzo 0.6.0 → 0.7.0

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -0
  3. data/Rakefile +1 -0
  4. data/app/controllers/terrazzo/application_controller.rb +63 -14
  5. data/app/helpers/terrazzo/collection_actions_helper.rb +38 -13
  6. data/app/helpers/terrazzo/resource_paths_helper.rb +55 -0
  7. data/app/views/terrazzo/application/_edit_base.json.props +7 -4
  8. data/app/views/terrazzo/application/_index_base.json.props +83 -7
  9. data/app/views/terrazzo/application/_navigation.json.props +7 -4
  10. data/app/views/terrazzo/application/_new_base.json.props +6 -3
  11. data/app/views/terrazzo/application/_show_base.json.props +7 -5
  12. data/lib/generators/terrazzo/dashboard/dashboard_generator.rb +48 -24
  13. data/lib/generators/terrazzo/dashboard/templates/controller.rb.erb +8 -4
  14. data/lib/generators/terrazzo/dashboard/templates/dashboard.rb.erb +33 -25
  15. data/lib/generators/terrazzo/eject/eject_generator.rb +223 -113
  16. data/lib/generators/terrazzo/field/field_generator.rb +51 -7
  17. data/lib/generators/terrazzo/field/templates/FormField.jsx.erb +2 -3
  18. data/lib/generators/terrazzo/install/install_generator.rb +641 -4
  19. data/lib/generators/terrazzo/install/templates/admin.css +2 -0
  20. data/lib/generators/terrazzo/install/templates/application.js.erb +3 -0
  21. data/lib/generators/terrazzo/install/templates/components.json.erb +17 -0
  22. data/lib/generators/terrazzo/install/templates/custom_page_mapping.js.erb +5 -0
  23. data/lib/generators/terrazzo/install/templates/generated_page_mapping.js.erb +10 -0
  24. data/lib/generators/terrazzo/install/templates/jsconfig.json +8 -0
  25. data/lib/generators/terrazzo/install/templates/page_to_page_mapping.js.erb +9 -1
  26. data/lib/generators/terrazzo/install/templates/superglue.html.erb.erb +2 -2
  27. data/lib/generators/terrazzo/routes/routes_generator.rb +55 -20
  28. data/lib/generators/terrazzo/views/edit_generator.rb +16 -10
  29. data/lib/generators/terrazzo/views/eject_compatibility.rb +19 -0
  30. data/lib/generators/terrazzo/views/field_generator.rb +18 -6
  31. data/lib/generators/terrazzo/views/generated_defaults_helper.rb +110 -0
  32. data/lib/generators/terrazzo/views/index_generator.rb +9 -8
  33. data/lib/generators/terrazzo/views/layout_generator.rb +4 -3
  34. data/lib/generators/terrazzo/views/navigation_generator.rb +5 -1
  35. data/lib/generators/terrazzo/views/new_generator.rb +16 -10
  36. data/lib/generators/terrazzo/views/page_mapping_helper.rb +131 -8
  37. data/lib/generators/terrazzo/views/show_generator.rb +7 -6
  38. data/lib/generators/terrazzo/views/templates/components/CollectionFilters.jsx +25 -0
  39. data/lib/generators/terrazzo/views/templates/components/CollectionItemActions.jsx +68 -0
  40. data/lib/generators/terrazzo/views/templates/components/CollectionToolbarActions.jsx +60 -0
  41. data/lib/generators/terrazzo/views/templates/components/HasManyPagination.jsx +36 -0
  42. data/lib/generators/terrazzo/views/templates/components/Layout.jsx +9 -4
  43. data/lib/generators/terrazzo/views/templates/components/Pagination.jsx +32 -24
  44. data/lib/generators/terrazzo/views/templates/components/ResourceTable.jsx +172 -0
  45. data/lib/generators/terrazzo/views/templates/components/SearchBar.jsx +14 -6
  46. data/lib/generators/terrazzo/views/templates/components/SortableHeader.jsx +7 -5
  47. data/lib/generators/terrazzo/views/templates/components/app-sidebar.jsx +1 -1
  48. data/lib/generators/terrazzo/views/templates/components/site-header.jsx +2 -2
  49. data/lib/generators/terrazzo/views/templates/fields/asset/FormField.jsx +15 -0
  50. data/lib/generators/terrazzo/views/templates/fields/asset/IndexField.jsx +5 -0
  51. data/lib/generators/terrazzo/views/templates/fields/asset/ShowField.jsx +13 -0
  52. data/lib/generators/terrazzo/views/templates/fields/belongs_to/FormField.jsx +1 -1
  53. data/lib/generators/terrazzo/views/templates/fields/boolean/FormField.jsx +1 -1
  54. data/lib/generators/terrazzo/views/templates/fields/boolean/IndexField.jsx +1 -1
  55. data/lib/generators/terrazzo/views/templates/fields/has_many/FormField.jsx +3 -3
  56. data/lib/generators/terrazzo/views/templates/fields/has_many/IndexField.jsx +4 -3
  57. data/lib/generators/terrazzo/views/templates/fields/has_many/ShowField.jsx +21 -35
  58. data/lib/generators/terrazzo/views/templates/fields/has_one/FormField.jsx +1 -1
  59. data/lib/generators/terrazzo/views/templates/fields/hstore/FormField.jsx +5 -5
  60. data/lib/generators/terrazzo/views/templates/fields/hstore/IndexField.jsx +0 -2
  61. data/lib/generators/terrazzo/views/templates/fields/hstore/ShowField.jsx +1 -1
  62. data/lib/generators/terrazzo/views/templates/fields/number/IndexField.jsx +1 -1
  63. data/lib/generators/terrazzo/views/templates/fields/number/ShowField.jsx +1 -1
  64. data/lib/generators/terrazzo/views/templates/fields/polymorphic/FormField.jsx +3 -2
  65. data/lib/generators/terrazzo/views/templates/fields/rich_text/FormField.jsx +2 -2
  66. data/lib/generators/terrazzo/views/templates/fields/select/FormField.jsx +1 -1
  67. data/lib/generators/terrazzo/views/templates/fields/select/IndexField.jsx +1 -1
  68. data/lib/generators/terrazzo/views/templates/fields/shared/TextInputFormField.jsx +2 -2
  69. data/lib/generators/terrazzo/views/templates/fields/text/FormField.jsx +2 -2
  70. data/lib/generators/terrazzo/views/templates/pages/_collection.jsx +5 -3
  71. data/lib/generators/terrazzo/views/templates/pages/_form.jsx +37 -30
  72. data/lib/generators/terrazzo/views/templates/pages/_navigation.json.props +14 -4
  73. data/lib/generators/terrazzo/views/templates/pages/edit.jsx +6 -4
  74. data/lib/generators/terrazzo/views/templates/pages/index.jsx +20 -8
  75. data/lib/generators/terrazzo/views/templates/pages/new.jsx +8 -4
  76. data/lib/generators/terrazzo/views/templates/pages/show.jsx +30 -32
  77. data/lib/generators/terrazzo/views/views_generator.rb +9 -5
  78. data/lib/terrazzo/base_dashboard.rb +167 -5
  79. data/lib/terrazzo/csv_export.rb +38 -0
  80. data/lib/terrazzo/field/associative.rb +16 -4
  81. data/lib/terrazzo/field/base.rb +4 -0
  82. data/lib/terrazzo/field/belongs_to.rb +1 -2
  83. data/lib/terrazzo/field/deferred.rb +4 -0
  84. data/lib/terrazzo/field/has_many.rb +79 -11
  85. data/lib/terrazzo/field/password.rb +6 -0
  86. data/lib/terrazzo/field/rich_text.rb +13 -0
  87. data/lib/terrazzo/generator_helpers.rb +1 -1
  88. data/lib/terrazzo/has_many_pagination.rb +9 -1
  89. data/lib/terrazzo/namespace/resource.rb +30 -0
  90. data/lib/terrazzo/namespace.rb +26 -1
  91. data/lib/terrazzo/search.rb +29 -9
  92. data/lib/terrazzo/version.rb +1 -1
  93. data/lib/terrazzo.rb +1 -0
  94. data/terrazzo.gemspec +11 -1
  95. metadata +40 -6
  96. data/lib/generators/terrazzo/install/templates/application.json.props.erb +0 -17
  97. data/lib/generators/terrazzo/views/templates/fields/FieldRenderer.jsx +0 -103
  98. data/lib/generators/terrazzo/views/templates/fields/index.js +0 -81
@@ -2,7 +2,8 @@ json.pageTitle @page.page_title
2
2
 
3
3
  show_field_json = ->(json, field) do
4
4
  json.attribute field.attribute.to_s
5
- json.label field.attribute.to_s.humanize
5
+ json.label dashboard.attribute_label(field.attribute, :show)
6
+ json.hint dashboard.attribute_hint(field.attribute, :show)
6
7
  json.fieldType field.field_type
7
8
  json.value field.serialize_value(:show)
8
9
  json.options field.serializable_options
@@ -12,14 +13,14 @@ show_field_json = ->(json, field) do
12
13
  render_actions = field.options.fetch(:render_actions, true)
13
14
 
14
15
  json.hasManyRowExtras(field.page_records.each_with_object({}) do |record, hash|
15
- extras = { showPath: (polymorphic_path([namespace, record]) rescue nil) }
16
+ extras = { showPath: collection_action_path(record, :show) }
16
17
  extras[:collectionItemActions] = collection_item_actions(record) if render_actions
17
18
  hash[record.id.to_s] = extras
18
19
  end)
19
20
 
20
21
  json.paginationPaths has_many_pagination_paths(field, @resource)
21
22
  else
22
- json.showPath polymorphic_path([namespace, field.data]) rescue nil
23
+ json.showPath collection_action_path(field.data, :show)
23
24
  end
24
25
  end
25
26
  end
@@ -39,8 +40,9 @@ json.attributeGroups do
39
40
  end
40
41
  end
41
42
 
42
- json.editPath route_exists?(:edit) ? (edit_polymorphic_path([namespace, @resource]) rescue nil) : nil
43
- json.deletePath route_exists?(:destroy) ? (polymorphic_path([namespace, @resource]) rescue nil) : nil
43
+ json.layoutActions Array(dashboard.layout_actions(:show, self, resource: @resource))
44
+ json.editPath route_exists?(:edit) && authorized_action?(@resource, :edit) ? terrazzo_resource_member_path(@resource, action: :edit) : nil
45
+ json.deletePath route_exists?(:destroy) && authorized_action?(@resource, :destroy) ? terrazzo_resource_member_path(@resource, action: :destroy) : nil
44
46
  json.indexPath begin
45
47
  url_for(controller: controller_path, action: :index, only_path: true)
46
48
  rescue ActionController::UrlGenerationError
@@ -1,4 +1,5 @@
1
1
  require "rails/generators"
2
+ require "set"
2
3
  require "terrazzo/generator_helpers"
3
4
 
4
5
  module Terrazzo
@@ -11,7 +12,7 @@ module Terrazzo
11
12
  class_option :namespace, type: :string, default: "admin",
12
13
  desc: "Admin namespace"
13
14
  class_option :bundler, type: :string, default: "vite",
14
- desc: "JavaScript bundler (vite or sprockets)"
15
+ desc: "JavaScript bundler (vite or esbuild)"
15
16
 
16
17
  def create_dashboard
17
18
  template "dashboard.rb.erb",
@@ -24,34 +25,22 @@ module Terrazzo
24
25
  end
25
26
 
26
27
  def update_page_to_page_mapping
27
- return if options[:bundler] == "vite"
28
-
29
- mapping_path = "app/javascript/#{options[:namespace]}/page_to_page_mapping.js"
30
- return unless File.exist?(mapping_path)
31
-
32
- namespace_name = options[:namespace]
33
- action_to_component = {
34
- "index" => "AdminIndex",
35
- "show" => "AdminShow",
36
- "new" => "AdminNew",
37
- "edit" => "AdminEdit"
38
- }
39
-
40
- action_to_component.each do |action, component|
41
- key = "'#{namespace_name}/application/#{action}'"
42
- mapping_path_content = File.read(mapping_path)
43
- next if mapping_path_content.include?(key)
44
-
45
- inject_into_file mapping_path, before: "}" do
46
- " #{key}: #{component},\n"
47
- end
48
- end
28
+ # The generated page mapping falls back from resource identifiers
29
+ # (admin/posts/index) to shared application pages (admin/application/index).
30
+ # Resource-specific page generators register their own mappings in
31
+ # generated_page_mapping.js when needed.
49
32
  end
50
33
 
51
34
  private
52
35
 
53
36
  def model_class
54
37
  class_name.constantize
38
+ rescue NameError
39
+ raise Thor::Error, <<~MESSAGE
40
+ Terrazzo could not find model '#{class_name}'.
41
+
42
+ Generate the model first, then run `bin/rails generate terrazzo:dashboard #{class_name}` again.
43
+ MESSAGE
55
44
  end
56
45
 
57
46
  def attribute_types
@@ -61,7 +50,7 @@ module Terrazzo
61
50
  types = {}
62
51
 
63
52
  # ID first
64
- types[:id] = "Field::Number"
53
+ types[:id] = "Field::String"
65
54
 
66
55
  # Columns
67
56
  columns.each do |col|
@@ -77,11 +66,18 @@ module Terrazzo
77
66
  else
78
67
  Set.new
79
68
  end
69
+ rich_text_association_names = rich_text_internal_association_names
70
+
71
+ rich_text_attribute_names.each do |name|
72
+ types[name] = "Field::RichText"
73
+ end
80
74
 
81
75
  # Associations
82
76
  associations.each do |assoc|
83
77
  # Skip Active Storage internal associations (e.g., document_attachment, document_blob)
84
78
  next if active_storage_internal?(assoc.name, attachment_names)
79
+ # Skip Action Text internals (e.g., rich_text_description)
80
+ next if rich_text_association_names.include?(assoc.name)
85
81
 
86
82
  case assoc.macro
87
83
  when :belongs_to
@@ -116,6 +112,22 @@ module Terrazzo
116
112
  attribute_types.keys.reject { |a| %i[id created_at updated_at].include?(a) }
117
113
  end
118
114
 
115
+ def controller_modules
116
+ [options[:namespace].camelize, *class_path.map(&:camelize)]
117
+ end
118
+
119
+ def controller_class_name
120
+ "#{file_name.pluralize.camelize}Controller"
121
+ end
122
+
123
+ def dashboard_modules
124
+ class_path.map(&:camelize)
125
+ end
126
+
127
+ def dashboard_class_name
128
+ "#{file_name.camelize}Dashboard"
129
+ end
130
+
119
131
  def association_foreign_key?(column_name, associations)
120
132
  associations.any? do |assoc|
121
133
  assoc.macro == :belongs_to &&
@@ -129,6 +141,18 @@ module Terrazzo
129
141
  att_s = att.to_s
130
142
  name == "#{att_s}_attachment" || name == "#{att_s}_blob" ||
131
143
  name == "#{att_s}_attachments" || name == "#{att_s}_blobs"
144
+ end
145
+ end
146
+
147
+ def rich_text_internal_association_names
148
+ return Set.new unless model_class.respond_to?(:rich_text_association_names)
149
+
150
+ model_class.rich_text_association_names.map(&:to_sym).to_set
151
+ end
152
+
153
+ def rich_text_attribute_names
154
+ rich_text_internal_association_names.map do |name|
155
+ name.to_s.sub(/\Arich_text_/, "").to_sym
132
156
  end
133
157
  end
134
158
 
@@ -1,4 +1,8 @@
1
- module <%= options[:namespace].camelize %>
2
- class <%= class_name.pluralize %>Controller < ApplicationController
3
- end
4
- end
1
+ <% controller_modules.each_with_index do |module_name, index| -%>
2
+ <%= " " * index %>module <%= module_name %>
3
+ <% end -%>
4
+ <%= " " * controller_modules.size %>class <%= controller_class_name %> < ApplicationController
5
+ <%= " " * controller_modules.size %>end
6
+ <% controller_modules.reverse.each_with_index do |_module_name, index| -%>
7
+ <%= " " * (controller_modules.size - index - 1) %>end
8
+ <% end -%>
@@ -1,43 +1,51 @@
1
1
  require "terrazzo/base_dashboard"
2
2
 
3
- class <%= class_name %>Dashboard < Terrazzo::BaseDashboard
4
- ATTRIBUTE_TYPES = {
3
+ <% dashboard_modules.each_with_index do |module_name, index| -%>
4
+ <%= " " * index %>module <%= module_name %>
5
+ <% end -%>
6
+ <% class_indent = " " * dashboard_modules.size -%>
7
+ <% body_indent = " " * (dashboard_modules.size + 1) -%>
8
+ <%= class_indent %>class <%= dashboard_class_name %> < Terrazzo::BaseDashboard
9
+ <%= body_indent %>ATTRIBUTE_TYPES = {
5
10
  <% attribute_types.each do |attr, type| -%>
6
11
  <% if has_enum?(attr.to_s) -%>
7
- <%= attr %>: <%= type %>.with_options(collection: <%= enum_collection(attr.to_s).inspect %>),
12
+ <%= body_indent %> <%= attr %>: <%= type %>.with_options(collection: <%= enum_collection(attr.to_s).inspect %>),
8
13
  <% else -%>
9
- <%= attr %>: <%= type %>,
14
+ <%= body_indent %> <%= attr %>: <%= type %>,
10
15
  <% end -%>
11
16
  <% end -%>
12
- }.freeze
17
+ <%= body_indent %>}.freeze
13
18
 
14
- COLLECTION_ATTRIBUTES = %i[
19
+ <%= body_indent %>COLLECTION_ATTRIBUTES = %i[
15
20
  <% collection_attributes.each do |attr| -%>
16
- <%= attr %>
21
+ <%= body_indent %> <%= attr %>
17
22
  <% end -%>
18
- ].freeze
23
+ <%= body_indent %>].freeze
19
24
 
20
- SHOW_PAGE_ATTRIBUTES = %i[
25
+ <%= body_indent %>SHOW_PAGE_ATTRIBUTES = %i[
21
26
  <% show_page_attributes.each do |attr| -%>
22
- <%= attr %>
27
+ <%= body_indent %> <%= attr %>
23
28
  <% end -%>
24
- ].freeze
29
+ <%= body_indent %>].freeze
25
30
 
26
- FORM_ATTRIBUTES = %i[
31
+ <%= body_indent %>FORM_ATTRIBUTES = %i[
27
32
  <% form_attributes.each do |attr| -%>
28
- <%= attr %>
33
+ <%= body_indent %> <%= attr %>
29
34
  <% end -%>
30
- ].freeze
35
+ <%= body_indent %>].freeze
31
36
 
32
- # COLLECTION_FILTERS = {
33
- # active: ->(resources) { resources.where(active: true) },
34
- # recent: ->(resources) { resources.where("created_at > ?", 30.days.ago) },
35
- # }.freeze
37
+ <%= body_indent %># COLLECTION_FILTERS = {
38
+ <%= body_indent %># active: ->(resources) { resources.where(active: true) },
39
+ <%= body_indent %># recent: ->(resources) { resources.where("created_at > ?", 30.days.ago) },
40
+ <%= body_indent %># }.freeze
36
41
 
37
- # Overwrite this method to customize how <%= file_name.pluralize.humanize.downcase %> are displayed
38
- # across all pages of the admin dashboard.
39
- #
40
- # def display_resource(resource)
41
- # "<%= class_name %> ##{resource.id}"
42
- # end
43
- end
42
+ <%= body_indent %># Override this method to customize how <%= file_name.pluralize.humanize.downcase %> are displayed
43
+ <%= body_indent %># across all pages of the admin dashboard.
44
+ <%= body_indent %>#
45
+ <%= body_indent %># def display_resource(resource)
46
+ <%= body_indent %># "<%= class_name %> ##{resource.id}"
47
+ <%= body_indent %># end
48
+ <%= class_indent %>end
49
+ <% dashboard_modules.reverse.each_with_index do |_module_name, index| -%>
50
+ <%= " " * (dashboard_modules.size - index - 1) %>end
51
+ <% end -%>