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
@@ -4,6 +4,9 @@ import { Application } from "@thoughtbot/superglue"
4
4
  import { buildVisitAndRemote } from "./application_visit"
5
5
  import { pageToPageMapping } from "./page_to_page_mapping"
6
6
  import { store } from "./store"
7
+ import "../../views/<%= namespace_name %>/fields"
8
+ import "../../views/<%= namespace_name %>/components"
9
+ import "../../views/<%= namespace_name %>/components/ui"
7
10
 
8
11
  if (typeof window !== "undefined") {
9
12
  document.addEventListener("DOMContentLoaded", function () {
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": false,
5
+ "tsx": false,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "app/assets/stylesheets/<%= namespace_name %>.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true
11
+ },
12
+ "aliases": {
13
+ "components": "app/views/<%= namespace_name %>/components",
14
+ "ui": "app/views/<%= namespace_name %>/components/ui",
15
+ "utils": "terrazzo"
16
+ }
17
+ }
@@ -0,0 +1,5 @@
1
+ // Manual custom page mappings for pages you own.
2
+ // Resource-specific Terrazzo view generators write to generated_page_mapping.js.
3
+
4
+ export const customPageMapping = {
5
+ }
@@ -0,0 +1,10 @@
1
+ // Resource-specific page mappings generated by Terrazzo.
2
+ // Add manual custom pages to custom_page_mapping.js instead.
3
+
4
+ // Terrazzo generated page imports start
5
+ // Terrazzo generated page imports end
6
+
7
+ export const generatedPageMapping = {
8
+ // Terrazzo generated page mappings start
9
+ // Terrazzo generated page mappings end
10
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "@/*": ["./*"]
6
+ }
7
+ }
8
+ }
@@ -2,14 +2,22 @@ import AdminIndex from "../../views/<%= namespace_name %>/application/index";
2
2
  import AdminShow from "../../views/<%= namespace_name %>/application/show";
3
3
  import AdminNew from "../../views/<%= namespace_name %>/application/new";
4
4
  import AdminEdit from "../../views/<%= namespace_name %>/application/edit";
5
+ import { generatedPageMapping } from "./generated_page_mapping";
6
+ import { customPageMapping } from "./custom_page_mapping";
5
7
 
6
- const pages = {
8
+ const applicationPages = {
7
9
  '<%= namespace_name %>/application/index': AdminIndex,
8
10
  '<%= namespace_name %>/application/show': AdminShow,
9
11
  '<%= namespace_name %>/application/new': AdminNew,
10
12
  '<%= namespace_name %>/application/edit': AdminEdit,
11
13
  }
12
14
 
15
+ const pages = {
16
+ ...applicationPages,
17
+ ...generatedPageMapping,
18
+ ...customPageMapping,
19
+ }
20
+
13
21
  // Resolves resource-specific page identifiers (e.g. "admin/orders/index")
14
22
  // with fallback to the shared application component (e.g. "admin/application/index").
15
23
  export const pageToPageMapping = new Proxy(pages, {
@@ -7,8 +7,8 @@
7
7
  <%%= csp_meta_tag %>
8
8
  <%%= yield :head %>
9
9
  <% if vite? -%>
10
- <%%= vite_react_refresh_tag %>
11
- <%%= vite_javascript_tag "<%= namespace_name %>/application" %>
10
+ <%%= vite_client_tag %>
11
+ <%%= vite_javascript_tag "<%= vite_entry_point_tag_name %>" %>
12
12
  <% else -%>
13
13
  <%%= javascript_include_tag "<%= namespace_name %>", type: "module" %>
14
14
  <% end -%>
@@ -11,36 +11,25 @@ module Terrazzo
11
11
  def insert_routes
12
12
  namespace_name = options[:namespace]
13
13
  models = application_models
14
+ raise_no_models_error if models.empty?
14
15
 
15
- # Group models by their module namespace
16
- namespaced = {}
17
- top_level = []
16
+ route_tree = empty_route_tree
18
17
 
19
18
  models.each do |model|
20
19
  parts = model.name.split("::")
21
20
  if parts.size > 1
22
- ns = parts[0..-2].join("::").underscore
21
+ namespaces = parts[0..-2].map(&:underscore)
23
22
  resource = parts.last.underscore.pluralize
24
- namespaced[ns] ||= []
25
- namespaced[ns] << resource
23
+ add_route_resource(route_tree, namespaces, resource)
26
24
  else
27
- top_level << model.model_name.plural
25
+ add_route_resource(route_tree, [], model.model_name.plural)
28
26
  end
29
27
  end
30
28
 
31
- lines = []
32
- top_level.sort.each { |r| lines << " resources :#{r}" }
29
+ lines = route_lines(route_tree, indent_level: 1)
30
+ first_resource = first_route_target(route_tree)
33
31
 
34
- namespaced.sort.each do |ns, resources|
35
- lines << ""
36
- lines << " namespace :#{ns} do"
37
- resources.sort.each { |r| lines << " resources :#{r}" }
38
- lines << " end"
39
- end
40
-
41
- first_resource = top_level.sort.first || namespaced.values.flatten.first || "dashboard"
42
-
43
- route_block = <<~RUBY.indent(2)
32
+ route_block = <<~RUBY
44
33
  namespace :#{namespace_name} do
45
34
  #{lines.join("\n")}
46
35
 
@@ -55,7 +44,7 @@ module Terrazzo
55
44
  say ""
56
45
  lines.each { |line| say line }
57
46
  say ""
58
- say " root to: \"#{first_resource}#index\"", :green if first_resource
47
+ say " root to: \"#{first_resource}#index\"", :green if first_resource
59
48
  say ""
60
49
  return
61
50
  end
@@ -78,6 +67,52 @@ module Terrazzo
78
67
  end.select { |klass| klass < ApplicationRecord && !klass.abstract_class? }
79
68
  .sort_by { |klass| klass.name }
80
69
  end
70
+
71
+ def empty_route_tree
72
+ { resources: [], namespaces: {} }
73
+ end
74
+
75
+ def add_route_resource(tree, namespaces, resource)
76
+ if namespaces.empty?
77
+ tree[:resources] << resource
78
+ return
79
+ end
80
+
81
+ namespace = namespaces.first
82
+ child_tree = tree[:namespaces][namespace] ||= empty_route_tree
83
+ add_route_resource(child_tree, namespaces.drop(1), resource)
84
+ end
85
+
86
+ def route_lines(tree, indent_level:)
87
+ indent = " " * indent_level
88
+ lines = tree[:resources].uniq.sort.map { |resource| "#{indent}resources :#{resource}" }
89
+
90
+ tree[:namespaces].sort.each do |namespace, child_tree|
91
+ lines << "" if lines.any?
92
+ lines << "#{indent}namespace :#{namespace} do"
93
+ lines.concat(route_lines(child_tree, indent_level: indent_level + 1))
94
+ lines << "#{indent}end"
95
+ end
96
+
97
+ lines
98
+ end
99
+
100
+ def first_route_target(tree)
101
+ resource = tree[:resources].uniq.sort.first
102
+ return resource if resource
103
+
104
+ namespace, child_tree = tree[:namespaces].sort.first
105
+ child_target = first_route_target(child_tree) if child_tree
106
+ return "#{namespace}/#{child_target}" if namespace && child_target
107
+ end
108
+
109
+ def raise_no_models_error
110
+ raise Thor::Error, <<~MESSAGE
111
+ Terrazzo could not find any ApplicationRecord models to route.
112
+
113
+ Add at least one model first, then run `bin/rails generate terrazzo:routes` again.
114
+ MESSAGE
115
+ end
81
116
  end
82
117
  end
83
118
  end
@@ -1,10 +1,12 @@
1
1
  require "rails/generators"
2
+ require_relative "generated_defaults_helper"
2
3
  require_relative "page_mapping_helper"
3
4
 
4
5
  module Terrazzo
5
6
  module Generators
6
7
  module Views
7
8
  class EditGenerator < Rails::Generators::Base
9
+ include GeneratedDefaultsHelper
8
10
  include PageMappingHelper
9
11
 
10
12
  source_root File.expand_path("templates", __dir__)
@@ -14,17 +16,22 @@ module Terrazzo
14
16
 
15
17
  class_option :namespace, type: :string, default: "admin",
16
18
  desc: "Admin namespace"
19
+ class_option :with_counterpart, type: :boolean, default: nil,
20
+ desc: "Also eject the new view that shares this form partial"
17
21
 
18
22
  def copy_edit_template
23
+ ensure_page_barrels
24
+
19
25
  if resource.present?
20
26
  eject_json_props
21
- copy_file "pages/edit.jsx", "app/views/#{namespace_name}/#{resource_path}/edit.jsx"
22
- copy_file "pages/_form.jsx", "app/views/#{namespace_name}/#{resource_path}/_form.jsx"
27
+ copy_resource_page_file "pages/edit.jsx", "app/views/#{namespace_name}/#{resource_path}/edit.jsx"
28
+ copy_resource_page_file_unless_exists "pages/_form.jsx", "app/views/#{namespace_name}/#{resource_path}/_form.jsx"
23
29
  register_page_mapping("edit")
24
30
  eject_new_view if should_eject_new?
25
31
  else
26
- copy_file "pages/edit.jsx", "app/views/#{namespace_name}/application/edit.jsx"
27
- copy_file "pages/_form.jsx", "app/views/#{namespace_name}/application/_form.jsx"
32
+ copy_file_over_generated "pages/edit.jsx", "app/views/#{namespace_name}/application/edit.jsx",
33
+ generated_content: generated_page_stub("edit")
34
+ copy_file_unless_exists "pages/_form.jsx", "app/views/#{namespace_name}/application/_form.jsx"
28
35
  eject_new_view if should_eject_new?
29
36
  end
30
37
  end
@@ -45,7 +52,9 @@ module Terrazzo
45
52
  else
46
53
  "app/views/#{namespace_name}/application/new.jsx"
47
54
  end
55
+ return true if generated_file?(new_path, generated_page_stub("new"))
48
56
  return false if File.exist?(new_path)
57
+ return options[:with_counterpart] unless options[:with_counterpart].nil?
49
58
 
50
59
  yes?("Also eject the new view to share the custom form partial? (y/n)")
51
60
  end
@@ -53,10 +62,11 @@ module Terrazzo
53
62
  def eject_new_view
54
63
  if resource.present?
55
64
  eject_new_json_props
56
- copy_file "pages/new.jsx", "app/views/#{namespace_name}/#{resource_path}/new.jsx"
65
+ copy_resource_page_file "pages/new.jsx", "app/views/#{namespace_name}/#{resource_path}/new.jsx"
57
66
  register_page_mapping("new")
58
67
  else
59
- copy_file "pages/new.jsx", "app/views/#{namespace_name}/application/new.jsx"
68
+ copy_file_over_generated "pages/new.jsx", "app/views/#{namespace_name}/application/new.jsx",
69
+ generated_content: generated_page_stub("new")
60
70
  end
61
71
  end
62
72
 
@@ -68,10 +78,6 @@ module Terrazzo
68
78
  RUBY
69
79
  end
70
80
 
71
- def resource_path
72
- resource.underscore.pluralize
73
- end
74
-
75
81
  def namespace_name
76
82
  options[:namespace]
77
83
  end
@@ -0,0 +1,19 @@
1
+ require "generators/terrazzo/eject/eject_generator"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ module Views
6
+ module EjectCompatibility
7
+ private
8
+
9
+ def run_eject(target)
10
+ Terrazzo::Generators::EjectGenerator.start(
11
+ [target, "--namespace=#{namespace_name}"],
12
+ destination_root: destination_root,
13
+ behavior: behavior
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,9 +1,12 @@
1
1
  require "rails/generators"
2
+ require "generators/terrazzo/views/eject_compatibility"
2
3
 
3
4
  module Terrazzo
4
5
  module Generators
5
6
  module Views
6
7
  class FieldGenerator < Rails::Generators::Base
8
+ include EjectCompatibility
9
+
7
10
  source_root File.expand_path("templates", __dir__)
8
11
 
9
12
  class_option :namespace, type: :string, default: "admin",
@@ -13,16 +16,25 @@ module Terrazzo
13
16
  desc: "Specific field type to copy (e.g., string, number) or 'all'"
14
17
 
15
18
  def copy_field_templates
16
- if field_type == "all"
17
- directory "fields", "app/views/#{namespace_name}/fields"
18
- else
19
- directory "fields/shared", "app/views/#{namespace_name}/fields/shared"
20
- directory "fields/#{field_type}", "app/views/#{namespace_name}/fields/#{field_type}"
21
- end
19
+ field_types_to_eject.each { |name| run_eject("fields/#{name}") }
22
20
  end
23
21
 
24
22
  private
25
23
 
24
+ def field_types_to_eject
25
+ return built_in_field_types if field_type == "all"
26
+
27
+ [field_type == "money" ? "number" : field_type]
28
+ end
29
+
30
+ def built_in_field_types
31
+ Dir[File.join(self.class.source_root, "fields/*")]
32
+ .select { |path| File.directory?(path) }
33
+ .map { |path| File.basename(path) }
34
+ .reject { |name| name == "shared" }
35
+ .sort
36
+ end
37
+
26
38
  def namespace_name
27
39
  options[:namespace]
28
40
  end
@@ -0,0 +1,110 @@
1
+ module Terrazzo
2
+ module Generators
3
+ module Views
4
+ module GeneratedDefaultsHelper
5
+ PAGE_COMPONENTS = {
6
+ "index" => "AdminIndex",
7
+ "show" => "AdminShow",
8
+ "new" => "AdminNew",
9
+ "edit" => "AdminEdit",
10
+ }.freeze
11
+
12
+ private
13
+
14
+ def copy_file_over_generated(source, destination, generated_content:)
15
+ options = generated_file?(destination, generated_content) ? { force: true } : {}
16
+
17
+ copy_file source, destination, **options
18
+ end
19
+
20
+ def copy_file_unless_exists(source, destination)
21
+ if File.exist?(File.join(destination_root, destination))
22
+ say_status :skip, destination, :yellow
23
+ return
24
+ end
25
+
26
+ copy_file source, destination
27
+ end
28
+
29
+ def copy_resource_page_file(source, destination)
30
+ before = read_destination(destination)
31
+ generated_before = before == read_template(source)
32
+
33
+ copy_file source, destination
34
+ after = read_destination(destination)
35
+ rewrite_resource_page_imports(destination) if after && (before.nil? || after != before || generated_before)
36
+ end
37
+
38
+ def copy_resource_page_file_unless_exists(source, destination)
39
+ if File.exist?(File.join(destination_root, destination))
40
+ rewrite_resource_page_imports(destination) if read_destination(destination) == read_template(source)
41
+ say_status :skip, destination, :yellow
42
+ return
43
+ end
44
+
45
+ copy_file source, destination
46
+ rewrite_resource_page_imports(destination)
47
+ end
48
+
49
+ def generated_file?(destination, generated_content)
50
+ path = File.join(destination_root, destination)
51
+ File.exist?(path) && File.read(path) == generated_content
52
+ end
53
+
54
+ def generated_page_stub(name)
55
+ component = PAGE_COMPONENTS.fetch(name)
56
+
57
+ %(export { #{component} as default } from "terrazzo/pages";\n)
58
+ end
59
+
60
+ def generated_navigation_partial
61
+ <<~RUBY
62
+ resources = Terrazzo::Namespace.new(namespace).navigation_resources
63
+ navigation_groups = resources.group_by(&:navigation_group).map do |label, items|
64
+ { label: label, items: items }
65
+ end
66
+
67
+ json.array! navigation_groups do |group|
68
+ json.label group[:label]
69
+ json.items do
70
+ json.array! group[:items] do |r|
71
+ json.label r.navigation_label
72
+ json.path url_for(controller: "/\#{r.controller_path}", action: :index, only_path: true)
73
+ json.active r.controller_path == controller_path
74
+ end
75
+ end
76
+ end
77
+ RUBY
78
+ end
79
+
80
+ def rewrite_resource_page_imports(destination)
81
+ path = File.join(destination_root, destination)
82
+ source = File.read(path)
83
+ prefix = page_barrel_import_prefix(destination)
84
+ rewritten = source.gsub(/from "\.\.\/(fields|components(?:\/ui)?)"/) do
85
+ %(from "#{prefix}#{Regexp.last_match(1)}")
86
+ end
87
+
88
+ File.write(path, rewritten) if rewritten != source
89
+ end
90
+
91
+ def read_destination(destination)
92
+ path = File.join(destination_root, destination)
93
+ File.read(path) if File.exist?(path)
94
+ end
95
+
96
+ def read_template(source)
97
+ File.read(File.join(self.class.source_root, source))
98
+ end
99
+
100
+ def page_barrel_import_prefix(destination)
101
+ relative_path = destination.delete_prefix("app/views/#{namespace_name}/")
102
+ directory = File.dirname(relative_path)
103
+ depth = directory.split("/").reject(&:empty?).length
104
+
105
+ "../" * depth
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,10 +1,12 @@
1
1
  require "rails/generators"
2
+ require_relative "generated_defaults_helper"
2
3
  require_relative "page_mapping_helper"
3
4
 
4
5
  module Terrazzo
5
6
  module Generators
6
7
  module Views
7
8
  class IndexGenerator < Rails::Generators::Base
9
+ include GeneratedDefaultsHelper
8
10
  include PageMappingHelper
9
11
 
10
12
  source_root File.expand_path("templates", __dir__)
@@ -16,14 +18,17 @@ module Terrazzo
16
18
  desc: "Admin namespace"
17
19
 
18
20
  def copy_index_template
21
+ ensure_page_barrels
22
+
19
23
  if resource.present?
20
24
  eject_json_props
21
- copy_file "pages/index.jsx", "app/views/#{namespace_name}/#{resource_path}/index.jsx"
22
- copy_file "pages/_collection.jsx", "app/views/#{namespace_name}/#{resource_path}/_collection.jsx"
25
+ copy_resource_page_file "pages/index.jsx", "app/views/#{namespace_name}/#{resource_path}/index.jsx"
26
+ copy_resource_page_file_unless_exists "pages/_collection.jsx", "app/views/#{namespace_name}/#{resource_path}/_collection.jsx"
23
27
  register_page_mapping("index")
24
28
  else
25
- copy_file "pages/index.jsx", "app/views/#{namespace_name}/application/index.jsx"
26
- copy_file "pages/_collection.jsx", "app/views/#{namespace_name}/application/_collection.jsx"
29
+ copy_file_over_generated "pages/index.jsx", "app/views/#{namespace_name}/application/index.jsx",
30
+ generated_content: generated_page_stub("index")
31
+ copy_file_unless_exists "pages/_collection.jsx", "app/views/#{namespace_name}/application/_collection.jsx"
27
32
  end
28
33
  end
29
34
 
@@ -37,10 +42,6 @@ module Terrazzo
37
42
  RUBY
38
43
  end
39
44
 
40
- def resource_path
41
- resource.underscore.pluralize
42
- end
43
-
44
45
  def namespace_name
45
46
  options[:namespace]
46
47
  end
@@ -1,18 +1,19 @@
1
1
  require "rails/generators"
2
+ require "generators/terrazzo/views/eject_compatibility"
2
3
 
3
4
  module Terrazzo
4
5
  module Generators
5
6
  module Views
6
7
  class LayoutGenerator < Rails::Generators::Base
8
+ include EjectCompatibility
9
+
7
10
  source_root File.expand_path("templates", __dir__)
8
11
 
9
12
  class_option :namespace, type: :string, default: "admin",
10
13
  desc: "Admin namespace"
11
14
 
12
15
  def copy_layout_components
13
- %w[Layout.jsx app-sidebar.jsx site-header.jsx FlashMessages.jsx].each do |file|
14
- copy_file "components/#{file}", "app/views/#{namespace_name}/components/#{file}"
15
- end
16
+ run_eject("components/Layout")
16
17
  end
17
18
 
18
19
  private
@@ -1,16 +1,20 @@
1
1
  require "rails/generators"
2
+ require "generators/terrazzo/views/eject_compatibility"
2
3
 
3
4
  module Terrazzo
4
5
  module Generators
5
6
  module Views
6
7
  class NavigationGenerator < Rails::Generators::Base
8
+ include EjectCompatibility
9
+
7
10
  source_root File.expand_path("templates", __dir__)
8
11
 
9
12
  class_option :namespace, type: :string, default: "admin",
10
13
  desc: "Admin namespace"
11
14
 
12
15
  def copy_navigation
13
- copy_file "components/app-sidebar.jsx", "app/views/#{namespace_name}/components/app-sidebar.jsx"
16
+ run_eject("navigation")
17
+ run_eject("components/app-sidebar")
14
18
  end
15
19
 
16
20
  private
@@ -1,10 +1,12 @@
1
1
  require "rails/generators"
2
+ require_relative "generated_defaults_helper"
2
3
  require_relative "page_mapping_helper"
3
4
 
4
5
  module Terrazzo
5
6
  module Generators
6
7
  module Views
7
8
  class NewGenerator < Rails::Generators::Base
9
+ include GeneratedDefaultsHelper
8
10
  include PageMappingHelper
9
11
 
10
12
  source_root File.expand_path("templates", __dir__)
@@ -14,17 +16,22 @@ module Terrazzo
14
16
 
15
17
  class_option :namespace, type: :string, default: "admin",
16
18
  desc: "Admin namespace"
19
+ class_option :with_counterpart, type: :boolean, default: nil,
20
+ desc: "Also eject the edit view that shares this form partial"
17
21
 
18
22
  def copy_new_template
23
+ ensure_page_barrels
24
+
19
25
  if resource.present?
20
26
  eject_json_props
21
- copy_file "pages/new.jsx", "app/views/#{namespace_name}/#{resource_path}/new.jsx"
22
- copy_file "pages/_form.jsx", "app/views/#{namespace_name}/#{resource_path}/_form.jsx"
27
+ copy_resource_page_file "pages/new.jsx", "app/views/#{namespace_name}/#{resource_path}/new.jsx"
28
+ copy_resource_page_file_unless_exists "pages/_form.jsx", "app/views/#{namespace_name}/#{resource_path}/_form.jsx"
23
29
  register_page_mapping("new")
24
30
  eject_edit_view if should_eject_edit?
25
31
  else
26
- copy_file "pages/new.jsx", "app/views/#{namespace_name}/application/new.jsx"
27
- copy_file "pages/_form.jsx", "app/views/#{namespace_name}/application/_form.jsx"
32
+ copy_file_over_generated "pages/new.jsx", "app/views/#{namespace_name}/application/new.jsx",
33
+ generated_content: generated_page_stub("new")
34
+ copy_file_unless_exists "pages/_form.jsx", "app/views/#{namespace_name}/application/_form.jsx"
28
35
  eject_edit_view if should_eject_edit?
29
36
  end
30
37
  end
@@ -45,7 +52,9 @@ module Terrazzo
45
52
  else
46
53
  "app/views/#{namespace_name}/application/edit.jsx"
47
54
  end
55
+ return true if generated_file?(edit_path, generated_page_stub("edit"))
48
56
  return false if File.exist?(edit_path)
57
+ return options[:with_counterpart] unless options[:with_counterpart].nil?
49
58
 
50
59
  yes?("Also eject the edit view to share the custom form partial? (y/n)")
51
60
  end
@@ -53,10 +62,11 @@ module Terrazzo
53
62
  def eject_edit_view
54
63
  if resource.present?
55
64
  eject_edit_json_props
56
- copy_file "pages/edit.jsx", "app/views/#{namespace_name}/#{resource_path}/edit.jsx"
65
+ copy_resource_page_file "pages/edit.jsx", "app/views/#{namespace_name}/#{resource_path}/edit.jsx"
57
66
  register_page_mapping("edit")
58
67
  else
59
- copy_file "pages/edit.jsx", "app/views/#{namespace_name}/application/edit.jsx"
68
+ copy_file_over_generated "pages/edit.jsx", "app/views/#{namespace_name}/application/edit.jsx",
69
+ generated_content: generated_page_stub("edit")
60
70
  end
61
71
  end
62
72
 
@@ -68,10 +78,6 @@ module Terrazzo
68
78
  RUBY
69
79
  end
70
80
 
71
- def resource_path
72
- resource.underscore.pluralize
73
- end
74
-
75
81
  def namespace_name
76
82
  options[:namespace]
77
83
  end