terrazzo 0.2.3 → 0.3.1
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/controllers/terrazzo/application_controller.rb +18 -2
- data/app/views/terrazzo/application/_navigation.json.props +12 -0
- data/app/views/terrazzo/application/edit.json.props +2 -9
- data/app/views/terrazzo/application/index.json.props +3 -11
- data/app/views/terrazzo/application/new.json.props +2 -9
- data/app/views/terrazzo/application/show.json.props +2 -10
- data/lib/generators/terrazzo/dashboard/dashboard_generator.rb +26 -0
- data/lib/generators/terrazzo/eject/eject_generator.rb +232 -0
- data/lib/generators/terrazzo/field/field_generator.rb +16 -9
- data/lib/generators/terrazzo/field/templates/FormField.jsx.erb +1 -2
- data/lib/generators/terrazzo/install/install_generator.rb +1 -1
- data/lib/generators/terrazzo/install/templates/admin.css +1 -0
- data/lib/generators/terrazzo/install/templates/{application.json.props → application.json.props.tt} +1 -0
- data/lib/generators/terrazzo/install/templates/page_to_page_mapping.js.erb +4 -15
- data/lib/generators/terrazzo/views/templates/components/Layout.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/components/Pagination.jsx +3 -3
- data/lib/generators/terrazzo/views/templates/components/SearchBar.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/components/SortableHeader.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/components/app-sidebar.jsx +23 -22
- data/lib/generators/terrazzo/views/templates/components/site-header.jsx +2 -2
- data/lib/generators/terrazzo/views/templates/components/ui/index.js +1 -1
- data/lib/generators/terrazzo/views/templates/fields/belongs_to/FormField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/boolean/FormField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/boolean/IndexField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/has_many/FormField.jsx +3 -3
- data/lib/generators/terrazzo/views/templates/fields/has_many/IndexField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/has_many/ShowField.jsx +3 -3
- data/lib/generators/terrazzo/views/templates/fields/has_one/FormField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/hstore/FormField.jsx +3 -3
- data/lib/generators/terrazzo/views/templates/fields/hstore/IndexField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/hstore/ShowField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/polymorphic/FormField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/rich_text/FormField.jsx +2 -2
- data/lib/generators/terrazzo/views/templates/fields/select/FormField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/select/IndexField.jsx +1 -1
- data/lib/generators/terrazzo/views/templates/fields/shared/TextInputFormField.jsx +2 -2
- data/lib/generators/terrazzo/views/templates/fields/text/FormField.jsx +2 -2
- data/lib/generators/terrazzo/views/templates/pages/_form.jsx +2 -2
- data/lib/generators/terrazzo/views/templates/pages/_navigation.json.props +5 -0
- data/lib/generators/terrazzo/views/templates/pages/edit.jsx +2 -3
- data/lib/generators/terrazzo/views/templates/pages/index.jsx +3 -14
- data/lib/generators/terrazzo/views/templates/pages/new.jsx +2 -3
- data/lib/generators/terrazzo/views/templates/pages/show.jsx +3 -4
- data/lib/generators/terrazzo/views/views_generator.rb +45 -26
- data/lib/terrazzo/base_dashboard.rb +4 -1
- data/lib/terrazzo/field/asset.rb +34 -0
- data/lib/terrazzo/field/associative.rb +1 -1
- data/lib/terrazzo/field/belongs_to.rb +2 -2
- data/lib/terrazzo/field/date.rb +2 -1
- data/lib/terrazzo/field/date_time.rb +2 -1
- data/lib/terrazzo/field/has_many.rb +8 -5
- data/lib/terrazzo/field/has_one.rb +2 -2
- data/lib/terrazzo/field/number.rb +4 -2
- data/lib/terrazzo/field/polymorphic.rb +3 -3
- data/lib/terrazzo/field/time.rb +2 -1
- data/lib/terrazzo/version.rb +1 -1
- data/lib/terrazzo.rb +1 -0
- data/terrazzo.gemspec +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f76fda78e25cb79116bd5f5f00b0b57e750503e3ebfdbee9a7bfe55dd8c17f34
|
|
4
|
+
data.tar.gz: 5cf113e4e1bfdd17591a3e50e1e993668da1408041efcc28a7f17b4a1b0eac05
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b60b58629158604c651402f6f920372f07146115b05f46613a2909be8af868f17005092bf7c84e288a4789724408e59150801fdc0b4f59c6575db1e05bb60f37
|
|
7
|
+
data.tar.gz: 9f6342b35c22e98a56f465221ef5e0bcc8911fcf75ee33ebea146144232e84a85b3c199b4ae14ce5969868c983587ca2cb59660ba96e2d2123c9ee1cc71aec36
|
|
@@ -16,7 +16,7 @@ module Terrazzo
|
|
|
16
16
|
|
|
17
17
|
prepend Terrazzo::UsesSuperglue::TemplateLookupOverride
|
|
18
18
|
|
|
19
|
-
helper_method :namespace, :dashboard, :resource_name, :resource_class, :application_title, :terrazzo_page_identifier
|
|
19
|
+
helper_method :namespace, :dashboard, :resource_name, :resource_class, :application_title, :terrazzo_page_identifier, :route_exists?
|
|
20
20
|
|
|
21
21
|
def index
|
|
22
22
|
search = Terrazzo::Search.new(scoped_resource, dashboard, params[:search])
|
|
@@ -196,9 +196,25 @@ module Terrazzo
|
|
|
196
196
|
# Build a page identifier that matches the user's namespace, not the
|
|
197
197
|
# engine's internal template path. The React page-to-component mapping
|
|
198
198
|
# keys off this identifier (e.g. "admin/application/index").
|
|
199
|
+
#
|
|
200
|
+
# Map create → new and update → edit so that failed validations
|
|
201
|
+
# (which render :new / :edit) resolve to the correct React component.
|
|
202
|
+
TERRAZZO_ACTION_MAP = { "create" => "new", "update" => "edit" }.freeze
|
|
203
|
+
|
|
199
204
|
def terrazzo_page_identifier
|
|
200
205
|
ns = controller_path.split("/").first
|
|
201
|
-
|
|
206
|
+
mapped_action = TERRAZZO_ACTION_MAP[action_name] || action_name
|
|
207
|
+
"#{ns}/application/#{mapped_action}"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def route_exists?(action)
|
|
211
|
+
@_route_exists_cache ||= {}
|
|
212
|
+
return @_route_exists_cache[action] if @_route_exists_cache.key?(action)
|
|
213
|
+
|
|
214
|
+
@_route_exists_cache[action] = Rails.application.routes.routes.any? do |route|
|
|
215
|
+
route.defaults[:controller] == controller_path &&
|
|
216
|
+
route.defaults[:action] == action.to_s
|
|
217
|
+
end
|
|
202
218
|
end
|
|
203
219
|
|
|
204
220
|
private
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
resources = Terrazzo::Namespace.new(namespace).resources_with_index_route
|
|
2
|
+
|
|
3
|
+
json.array! [{ label: "Resources", resources: resources }] do |group|
|
|
4
|
+
json.label group[:label]
|
|
5
|
+
json.items do
|
|
6
|
+
json.array! group[:resources] do |r|
|
|
7
|
+
json.label r.resource_name.humanize.pluralize
|
|
8
|
+
json.path url_for(controller: "/#{r.controller_path}", action: :index, only_path: true)
|
|
9
|
+
json.active r.controller_path == controller_path
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -5,7 +5,8 @@ json.pageTitle t("terrazzo.actions.edit", resource_name: resource_name)
|
|
|
5
5
|
json.form do
|
|
6
6
|
json.props({
|
|
7
7
|
action: polymorphic_path([namespace, @resource]),
|
|
8
|
-
method: "post"
|
|
8
|
+
method: "post",
|
|
9
|
+
encType: "multipart/form-data"
|
|
9
10
|
})
|
|
10
11
|
json.extras do
|
|
11
12
|
if protect_against_forgery?
|
|
@@ -60,11 +61,3 @@ rescue ActionController::UrlGenerationError
|
|
|
60
61
|
nil
|
|
61
62
|
end
|
|
62
63
|
json.resourceName resource_name
|
|
63
|
-
|
|
64
|
-
json.navigation do
|
|
65
|
-
json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
|
|
66
|
-
json.label nav_resource.resource_name.humanize.pluralize
|
|
67
|
-
json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
|
|
68
|
-
json.active nav_resource.controller_path == controller_path
|
|
69
|
-
end
|
|
70
|
-
end
|
|
@@ -33,8 +33,8 @@ json.table do
|
|
|
33
33
|
json.array! @resources do |resource|
|
|
34
34
|
json.id resource.id
|
|
35
35
|
json.showPath polymorphic_path([namespace, resource]) rescue nil
|
|
36
|
-
json.editPath edit_polymorphic_path([namespace, resource]) rescue nil
|
|
37
|
-
json.deletePath polymorphic_path([namespace, resource]) rescue nil
|
|
36
|
+
json.editPath route_exists?(:edit) ? (edit_polymorphic_path([namespace, resource]) rescue nil) : nil
|
|
37
|
+
json.deletePath route_exists?(:destroy) ? (polymorphic_path([namespace, resource]) rescue nil) : nil
|
|
38
38
|
|
|
39
39
|
json.cells do
|
|
40
40
|
json.array! @page.attribute_names do |attr|
|
|
@@ -89,12 +89,4 @@ end
|
|
|
89
89
|
|
|
90
90
|
json.resourceName resource_name.pluralize
|
|
91
91
|
json.singularResourceName resource_name
|
|
92
|
-
json.newResourcePath new_polymorphic_path([namespace, resource_class]) rescue nil
|
|
93
|
-
|
|
94
|
-
json.navigation do
|
|
95
|
-
json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
|
|
96
|
-
json.label nav_resource.resource_name.humanize.pluralize
|
|
97
|
-
json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
|
|
98
|
-
json.active nav_resource.controller_path == controller_path
|
|
99
|
-
end
|
|
100
|
-
end
|
|
92
|
+
json.newResourcePath route_exists?(:new) ? (new_polymorphic_path([namespace, resource_class]) rescue nil) : nil
|
|
@@ -6,7 +6,8 @@ json.pageTitle t("terrazzo.actions.new", resource_name: resource_name)
|
|
|
6
6
|
json.form do
|
|
7
7
|
json.props({
|
|
8
8
|
action: polymorphic_path([namespace, resource_class]),
|
|
9
|
-
method: "post"
|
|
9
|
+
method: "post",
|
|
10
|
+
encType: "multipart/form-data"
|
|
10
11
|
})
|
|
11
12
|
json.extras do
|
|
12
13
|
if protect_against_forgery?
|
|
@@ -55,11 +56,3 @@ rescue ActionController::UrlGenerationError
|
|
|
55
56
|
nil
|
|
56
57
|
end
|
|
57
58
|
json.resourceName resource_name
|
|
58
|
-
|
|
59
|
-
json.navigation do
|
|
60
|
-
json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
|
|
61
|
-
json.label nav_resource.resource_name.humanize.pluralize
|
|
62
|
-
json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
|
|
63
|
-
json.active nav_resource.controller_path == controller_path
|
|
64
|
-
end
|
|
65
|
-
end
|
|
@@ -34,8 +34,8 @@ json.attributes do
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
json.editPath edit_polymorphic_path([namespace, @resource]) rescue nil
|
|
38
|
-
json.deletePath polymorphic_path([namespace, @resource]) rescue nil
|
|
37
|
+
json.editPath route_exists?(:edit) ? (edit_polymorphic_path([namespace, @resource]) rescue nil) : nil
|
|
38
|
+
json.deletePath route_exists?(:destroy) ? (polymorphic_path([namespace, @resource]) rescue nil) : nil
|
|
39
39
|
json.indexPath begin
|
|
40
40
|
url_for(controller: controller_path, action: :index, only_path: true)
|
|
41
41
|
rescue ActionController::UrlGenerationError
|
|
@@ -43,11 +43,3 @@ rescue ActionController::UrlGenerationError
|
|
|
43
43
|
end
|
|
44
44
|
json.resourceName resource_name
|
|
45
45
|
json.pluralResourceName resource_name.pluralize
|
|
46
|
-
|
|
47
|
-
json.navigation do
|
|
48
|
-
json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
|
|
49
|
-
json.label nav_resource.resource_name.humanize.pluralize
|
|
50
|
-
json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
|
|
51
|
-
json.active nav_resource.controller_path == controller_path
|
|
52
|
-
end
|
|
53
|
-
end
|
|
@@ -71,8 +71,18 @@ module Terrazzo
|
|
|
71
71
|
types[col.name.to_sym] = column_to_field_type(col)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# Active Storage attachment names (used to filter internal associations)
|
|
75
|
+
attachment_names = if model_class.respond_to?(:reflect_on_all_attachments)
|
|
76
|
+
model_class.reflect_on_all_attachments.map(&:name).to_set
|
|
77
|
+
else
|
|
78
|
+
Set.new
|
|
79
|
+
end
|
|
80
|
+
|
|
74
81
|
# Associations
|
|
75
82
|
associations.each do |assoc|
|
|
83
|
+
# Skip Active Storage internal associations (e.g., document_attachment, document_blob)
|
|
84
|
+
next if active_storage_internal?(assoc.name, attachment_names)
|
|
85
|
+
|
|
76
86
|
case assoc.macro
|
|
77
87
|
when :belongs_to
|
|
78
88
|
types[assoc.name] = assoc.options[:polymorphic] ? "Field::Polymorphic" : "Field::BelongsTo"
|
|
@@ -83,6 +93,13 @@ module Terrazzo
|
|
|
83
93
|
end
|
|
84
94
|
end
|
|
85
95
|
|
|
96
|
+
# Active Storage attachments
|
|
97
|
+
attachment_names.each do |name|
|
|
98
|
+
attachment = model_class.reflect_on_attachment(name)
|
|
99
|
+
next if attachment.macro == :has_many_attached
|
|
100
|
+
types[name] = "Field::Asset"
|
|
101
|
+
end
|
|
102
|
+
|
|
86
103
|
types
|
|
87
104
|
end
|
|
88
105
|
|
|
@@ -106,6 +123,15 @@ module Terrazzo
|
|
|
106
123
|
end
|
|
107
124
|
end
|
|
108
125
|
|
|
126
|
+
def active_storage_internal?(assoc_name, attachment_names)
|
|
127
|
+
name = assoc_name.to_s
|
|
128
|
+
attachment_names.any? do |att|
|
|
129
|
+
att_s = att.to_s
|
|
130
|
+
name == "#{att_s}_attachment" || name == "#{att_s}_blob" ||
|
|
131
|
+
name == "#{att_s}_attachments" || name == "#{att_s}_blobs"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
109
135
|
def has_enum?(column_name)
|
|
110
136
|
model_class.defined_enums.key?(column_name.to_s)
|
|
111
137
|
end
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
|
|
3
|
+
module Terrazzo
|
|
4
|
+
module Generators
|
|
5
|
+
class EjectGenerator < Rails::Generators::Base
|
|
6
|
+
source_root File.expand_path("../views/templates", __dir__)
|
|
7
|
+
|
|
8
|
+
argument :target, type: :string,
|
|
9
|
+
desc: "What to eject (e.g., fields/string, components/Layout, ui/button, pages/index)"
|
|
10
|
+
|
|
11
|
+
class_option :namespace, type: :string, default: "admin",
|
|
12
|
+
desc: "Admin namespace"
|
|
13
|
+
|
|
14
|
+
def eject
|
|
15
|
+
case category
|
|
16
|
+
when "fields"
|
|
17
|
+
eject_field
|
|
18
|
+
when "components"
|
|
19
|
+
eject_component
|
|
20
|
+
when "ui"
|
|
21
|
+
eject_ui
|
|
22
|
+
when "pages"
|
|
23
|
+
eject_page
|
|
24
|
+
when "navigation"
|
|
25
|
+
eject_navigation
|
|
26
|
+
else
|
|
27
|
+
say_status :error, "Unknown category '#{category}'. Use fields/, components/, ui/, pages/, or navigation", :red
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def category
|
|
34
|
+
target.split("/").first
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def component_name
|
|
38
|
+
target.split("/", 2).last
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def namespace_name
|
|
42
|
+
options[:namespace]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def eject_field
|
|
46
|
+
field_type = component_name
|
|
47
|
+
source_dir = "fields/#{field_type}"
|
|
48
|
+
|
|
49
|
+
unless File.directory?(File.join(self.class.source_root, source_dir))
|
|
50
|
+
say_status :error, "Unknown field type '#{field_type}'", :red
|
|
51
|
+
return
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
%w[IndexField.jsx ShowField.jsx FormField.jsx].each do |file|
|
|
55
|
+
source = File.join(source_dir, file)
|
|
56
|
+
next unless File.exist?(File.join(self.class.source_root, source))
|
|
57
|
+
copy_file source, "app/views/#{namespace_name}/fields/#{field_type}/#{file}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Copy shared dependencies if needed
|
|
61
|
+
if field_uses_shared?(field_type)
|
|
62
|
+
copy_file "fields/shared/TextInputFormField.jsx",
|
|
63
|
+
"app/views/#{namespace_name}/fields/shared/TextInputFormField.jsx"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
update_fields_barrel(field_type)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def eject_component
|
|
70
|
+
name = component_name
|
|
71
|
+
source = "components/#{name}.jsx"
|
|
72
|
+
|
|
73
|
+
unless File.exist?(File.join(self.class.source_root, source))
|
|
74
|
+
say_status :error, "Unknown component '#{name}'", :red
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
copy_file source, "app/views/#{namespace_name}/components/#{name}.jsx"
|
|
79
|
+
update_components_barrel(name)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def eject_ui
|
|
83
|
+
name = component_name
|
|
84
|
+
source = "components/ui/#{name}.jsx"
|
|
85
|
+
|
|
86
|
+
unless File.exist?(File.join(self.class.source_root, source))
|
|
87
|
+
say_status :error, "Unknown UI component '#{name}'", :red
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
copy_file source, "app/views/#{namespace_name}/components/ui/#{name}.jsx"
|
|
92
|
+
update_ui_barrel(name)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def eject_page
|
|
96
|
+
name = component_name
|
|
97
|
+
source = "pages/#{name}.jsx"
|
|
98
|
+
|
|
99
|
+
unless File.exist?(File.join(self.class.source_root, source))
|
|
100
|
+
say_status :error, "Unknown page template '#{name}'", :red
|
|
101
|
+
return
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
copy_file source, "app/views/#{namespace_name}/application/#{name}.jsx"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def eject_navigation
|
|
108
|
+
source = File.join(Terrazzo::Engine.root, "app/views/terrazzo/application/_navigation.json.props")
|
|
109
|
+
dest = "app/views/#{namespace_name}/application/_navigation.json.props"
|
|
110
|
+
copy_file source, dest
|
|
111
|
+
say "\nNavigation partial ejected to #{dest}.", :green
|
|
112
|
+
say "Edit it to customize your admin navigation."
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def field_uses_shared?(field_type)
|
|
116
|
+
%w[string number email url password date date_time time].include?(field_type)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def update_fields_barrel(field_type)
|
|
120
|
+
barrel_path = "app/views/#{namespace_name}/fields/index.js"
|
|
121
|
+
barrel_file = File.join(destination_root, barrel_path)
|
|
122
|
+
|
|
123
|
+
type_label = field_type.split("_").map(&:capitalize).join("")
|
|
124
|
+
local_exports = <<~JS.strip
|
|
125
|
+
// #{type_label} - ejected
|
|
126
|
+
export { IndexField as #{type_label}IndexField } from "./#{field_type}/IndexField";
|
|
127
|
+
export { ShowField as #{type_label}ShowField } from "./#{field_type}/ShowField";
|
|
128
|
+
export { FormField as #{type_label}FormField } from "./#{field_type}/FormField";
|
|
129
|
+
JS
|
|
130
|
+
|
|
131
|
+
if File.exist?(barrel_file)
|
|
132
|
+
content = File.read(barrel_file)
|
|
133
|
+
|
|
134
|
+
# If barrel is still the default re-export-all, replace with explicit exports
|
|
135
|
+
if content.include?('export * from "terrazzo/fields"')
|
|
136
|
+
new_content = build_fields_barrel_with_ejection(field_type)
|
|
137
|
+
create_file barrel_path, new_content, force: true
|
|
138
|
+
else
|
|
139
|
+
# Barrel already has explicit exports; replace the line for this field type
|
|
140
|
+
# by inserting local exports before the terrazzo re-exports
|
|
141
|
+
unless content.include?("./#{field_type}/")
|
|
142
|
+
append_to_file barrel_path, "\n#{local_exports}\n"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def build_fields_barrel_with_ejection(ejected_field_type)
|
|
149
|
+
all_field_types = %w[
|
|
150
|
+
string text number boolean date date_time time email url password
|
|
151
|
+
select rich_text belongs_to has_many has_one polymorphic hstore
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
lines = ['export { FieldRenderer, registerFieldType } from "terrazzo/fields";', ""]
|
|
155
|
+
|
|
156
|
+
all_field_types.each do |ft|
|
|
157
|
+
label = ft.split("_").map(&:capitalize).join("")
|
|
158
|
+
if ft == ejected_field_type
|
|
159
|
+
lines << "// #{label} - ejected"
|
|
160
|
+
lines << "export { IndexField as #{label}IndexField } from \"./#{ft}/IndexField\";"
|
|
161
|
+
lines << "export { ShowField as #{label}ShowField } from \"./#{ft}/ShowField\";"
|
|
162
|
+
lines << "export { FormField as #{label}FormField } from \"./#{ft}/FormField\";"
|
|
163
|
+
else
|
|
164
|
+
lines << "export { #{label}IndexField, #{label}ShowField, #{label}FormField } from \"terrazzo/fields\";"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
lines.join("\n") + "\n"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def update_components_barrel(name)
|
|
172
|
+
barrel_path = "app/views/#{namespace_name}/components/index.js"
|
|
173
|
+
barrel_file = File.join(destination_root, barrel_path)
|
|
174
|
+
|
|
175
|
+
return unless File.exist?(barrel_file)
|
|
176
|
+
|
|
177
|
+
content = File.read(barrel_file)
|
|
178
|
+
if content.include?('export * from "terrazzo/components"')
|
|
179
|
+
export_name = component_export_name(name)
|
|
180
|
+
new_content = build_components_barrel_with_ejection(name, export_name)
|
|
181
|
+
create_file barrel_path, new_content, force: true
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def build_components_barrel_with_ejection(ejected_name, export_name)
|
|
186
|
+
all_components = {
|
|
187
|
+
"Layout" => "Layout",
|
|
188
|
+
"app-sidebar" => "AppSidebar",
|
|
189
|
+
"site-header" => "SiteHeader",
|
|
190
|
+
"FlashMessages" => "FlashMessages",
|
|
191
|
+
"SearchBar" => "SearchBar",
|
|
192
|
+
"Pagination" => "Pagination",
|
|
193
|
+
"SortableHeader" => "SortableHeader",
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
lines = []
|
|
197
|
+
all_components.each do |file_name, export|
|
|
198
|
+
if file_name == ejected_name
|
|
199
|
+
lines << "export { #{export} } from \"./#{file_name}\"; // ejected"
|
|
200
|
+
else
|
|
201
|
+
lines << "export { #{export} } from \"terrazzo/components\";"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
lines.join("\n") + "\n"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def update_ui_barrel(name)
|
|
209
|
+
barrel_path = "app/views/#{namespace_name}/components/ui/index.js"
|
|
210
|
+
barrel_file = File.join(destination_root, barrel_path)
|
|
211
|
+
|
|
212
|
+
return unless File.exist?(barrel_file)
|
|
213
|
+
|
|
214
|
+
content = File.read(barrel_file)
|
|
215
|
+
if content.include?('export * from "terrazzo/ui"')
|
|
216
|
+
new_content = "export * from \"terrazzo/ui\";\n"
|
|
217
|
+
new_content += "// Override ejected UI component:\n"
|
|
218
|
+
new_content += "export * from \"./#{name}\";\n"
|
|
219
|
+
create_file barrel_path, new_content, force: true
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def component_export_name(file_name)
|
|
224
|
+
case file_name
|
|
225
|
+
when "app-sidebar" then "AppSidebar"
|
|
226
|
+
when "site-header" then "SiteHeader"
|
|
227
|
+
else file_name
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -20,15 +20,22 @@ module Terrazzo
|
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
def register_in_barrel
|
|
24
|
+
barrel_path = File.join(destination_root, "app/views/#{namespace_name}/fields/index.js")
|
|
25
|
+
return unless File.exist?(barrel_path)
|
|
26
|
+
|
|
27
|
+
registration = <<~JS
|
|
28
|
+
|
|
29
|
+
// #{class_name} - custom field
|
|
30
|
+
export { IndexField as #{class_name}IndexField } from "./#{file_name}/IndexField";
|
|
31
|
+
export { ShowField as #{class_name}ShowField } from "./#{file_name}/ShowField";
|
|
32
|
+
export { FormField as #{class_name}FormField } from "./#{file_name}/FormField";
|
|
33
|
+
JS
|
|
34
|
+
|
|
35
|
+
append_to_file "app/views/#{namespace_name}/fields/index.js", registration
|
|
36
|
+
|
|
37
|
+
say "\nCustom field '#{file_name}' registered in fields/index.js.", :green
|
|
38
|
+
say "Use it in your dashboard:"
|
|
32
39
|
say " #{file_name}: Terrazzo::Field::#{class_name},"
|
|
33
40
|
end
|
|
34
41
|
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
for (const key in pages) {
|
|
7
|
-
const identifier = key.replace("../../views/", "").replace(/\.jsx$/, "")
|
|
8
|
-
pageToPageMapping[identifier] = pages[key].default
|
|
9
|
-
}
|
|
10
|
-
<% else -%>
|
|
11
|
-
import AdminIndex from "../../views/<%= namespace_name %>/application/index"
|
|
12
|
-
import AdminShow from "../../views/<%= namespace_name %>/application/show"
|
|
13
|
-
import AdminNew from "../../views/<%= namespace_name %>/application/new"
|
|
14
|
-
import AdminEdit from "../../views/<%= namespace_name %>/application/edit"
|
|
1
|
+
import AdminIndex from "../../views/<%= namespace_name %>/application/index";
|
|
2
|
+
import AdminShow from "../../views/<%= namespace_name %>/application/show";
|
|
3
|
+
import AdminNew from "../../views/<%= namespace_name %>/application/new";
|
|
4
|
+
import AdminEdit from "../../views/<%= namespace_name %>/application/edit";
|
|
15
5
|
|
|
16
6
|
export const pageToPageMapping = {
|
|
17
7
|
'<%= namespace_name %>/application/index': AdminIndex,
|
|
@@ -19,4 +9,3 @@ export const pageToPageMapping = {
|
|
|
19
9
|
'<%= namespace_name %>/application/new': AdminNew,
|
|
20
10
|
'<%= namespace_name %>/application/edit': AdminEdit,
|
|
21
11
|
}
|
|
22
|
-
<% end -%>
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { AppSidebar } from "./app-sidebar";
|
|
4
4
|
import { SiteHeader } from "./site-header";
|
|
5
5
|
import { FlashMessages } from "./FlashMessages";
|
|
6
|
-
import { SidebarProvider, SidebarInset } from "
|
|
6
|
+
import { SidebarProvider, SidebarInset } from "terrazzo/ui";
|
|
7
7
|
|
|
8
8
|
export function Layout({ navigation, title, actions, children }) {
|
|
9
9
|
return (
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React, { useContext } from "react";
|
|
2
2
|
import { NavigationContext } from "@thoughtbot/superglue";
|
|
3
3
|
|
|
4
|
-
import { Field, FieldLabel } from "
|
|
4
|
+
import { Field, FieldLabel } from "terrazzo/ui";
|
|
5
5
|
import {
|
|
6
6
|
Pagination as PaginationRoot,
|
|
7
7
|
PaginationContent,
|
|
8
8
|
PaginationItem,
|
|
9
9
|
PaginationNext,
|
|
10
10
|
PaginationPrevious,
|
|
11
|
-
} from "
|
|
11
|
+
} from "terrazzo/ui";
|
|
12
12
|
import {
|
|
13
13
|
Select,
|
|
14
14
|
SelectContent,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
SelectItem,
|
|
17
17
|
SelectTrigger,
|
|
18
18
|
SelectValue,
|
|
19
|
-
} from "
|
|
19
|
+
} from "terrazzo/ui";
|
|
20
20
|
|
|
21
21
|
export function Pagination({ currentPage, totalPages, totalCount, perPage, nextPagePath, prevPagePath }) {
|
|
22
22
|
const { remote, pageKey } = useContext(NavigationContext);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useRef, useContext } from "react";
|
|
2
2
|
import { NavigationContext } from "@thoughtbot/superglue";
|
|
3
3
|
|
|
4
|
-
import { Input } from "
|
|
4
|
+
import { Input } from "terrazzo/ui";
|
|
5
5
|
import { Search } from "lucide-react";
|
|
6
6
|
|
|
7
7
|
export function SearchBar({ searchTerm, searchPath }) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { ChevronUp, ChevronDown, ChevronsUpDown } from "lucide-react";
|
|
3
3
|
|
|
4
|
-
import { TableHead } from "
|
|
4
|
+
import { TableHead } from "terrazzo/ui";
|
|
5
5
|
|
|
6
6
|
export function SortableHeader({ label, sortable, sortUrl, sortDirection }) {
|
|
7
7
|
if (!sortable) {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
SidebarMenu,
|
|
12
12
|
SidebarMenuButton,
|
|
13
13
|
SidebarMenuItem,
|
|
14
|
-
} from "
|
|
14
|
+
} from "terrazzo/ui";
|
|
15
15
|
|
|
16
16
|
export function AppSidebar({
|
|
17
17
|
navigation,
|
|
@@ -27,7 +27,7 @@ export function AppSidebar({
|
|
|
27
27
|
asChild
|
|
28
28
|
className="data-[slot=sidebar-menu-button]:!p-1.5">
|
|
29
29
|
|
|
30
|
-
<a href={navigation[0]?.path ?? "#"} data-sg-visit>
|
|
30
|
+
<a href={navigation[0]?.items?.[0]?.path ?? navigation[0]?.path ?? "#"} data-sg-visit>
|
|
31
31
|
<LayoutDashboardIcon className="h-5 w-5" />
|
|
32
32
|
<span className="text-base font-semibold">{title}</span>
|
|
33
33
|
</a>
|
|
@@ -36,26 +36,27 @@ export function AppSidebar({
|
|
|
36
36
|
</SidebarMenu>
|
|
37
37
|
</SidebarHeader>
|
|
38
38
|
<SidebarContent>
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
{navigation.map((group) =>
|
|
40
|
+
<SidebarGroup key={group.label}>
|
|
41
|
+
<SidebarGroupLabel>{group.label}</SidebarGroupLabel>
|
|
42
|
+
<SidebarGroupContent>
|
|
43
|
+
<SidebarMenu>
|
|
44
|
+
{group.items.map((item) =>
|
|
45
|
+
<SidebarMenuItem key={item.path}>
|
|
46
|
+
<SidebarMenuButton
|
|
47
|
+
asChild
|
|
48
|
+
isActive={item.active}
|
|
49
|
+
tooltip={item.label}>
|
|
50
|
+
<a href={item.path} data-sg-visit>
|
|
51
|
+
<span>{item.label}</span>
|
|
52
|
+
</a>
|
|
53
|
+
</SidebarMenuButton>
|
|
54
|
+
</SidebarMenuItem>
|
|
55
|
+
)}
|
|
56
|
+
</SidebarMenu>
|
|
57
|
+
</SidebarGroupContent>
|
|
58
|
+
</SidebarGroup>
|
|
59
|
+
)}
|
|
59
60
|
</SidebarContent>
|
|
60
61
|
</Sidebar>);
|
|
61
62
|
|