terrazzo 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/Rakefile +11 -0
- data/app/controllers/terrazzo/application_controller.rb +208 -0
- data/app/views/terrazzo/application/edit.json.props +70 -0
- data/app/views/terrazzo/application/index.json.props +97 -0
- data/app/views/terrazzo/application/new.json.props +65 -0
- data/app/views/terrazzo/application/show.json.props +44 -0
- data/app/views/terrazzo/application/superglue.html.erb +5 -0
- data/config/locales/en.yml +28 -0
- data/lib/generators/terrazzo/dashboard/dashboard_generator.rb +118 -0
- data/lib/generators/terrazzo/dashboard/templates/controller.rb.erb +4 -0
- data/lib/generators/terrazzo/dashboard/templates/dashboard.rb.erb +36 -0
- data/lib/generators/terrazzo/field/field_generator.rb +42 -0
- data/lib/generators/terrazzo/field/templates/FormField.jsx.erb +19 -0
- data/lib/generators/terrazzo/field/templates/IndexField.jsx.erb +5 -0
- data/lib/generators/terrazzo/field/templates/ShowField.jsx.erb +5 -0
- data/lib/generators/terrazzo/field/templates/field.rb.erb +23 -0
- data/lib/generators/terrazzo/install/install_generator.rb +85 -0
- data/lib/generators/terrazzo/install/templates/application.js.erb +27 -0
- data/lib/generators/terrazzo/install/templates/application.json.props +27 -0
- data/lib/generators/terrazzo/install/templates/application.json.props.erb +17 -0
- data/lib/generators/terrazzo/install/templates/application_controller.rb.erb +24 -0
- data/lib/generators/terrazzo/install/templates/application_visit.js.erb +8 -0
- data/lib/generators/terrazzo/install/templates/flash_slice.js.erb +42 -0
- data/lib/generators/terrazzo/install/templates/page_to_page_mapping.js.erb +22 -0
- data/lib/generators/terrazzo/install/templates/store.js.erb +24 -0
- data/lib/generators/terrazzo/install/templates/superglue.html.erb.erb +20 -0
- data/lib/generators/terrazzo/routes/routes_generator.rb +38 -0
- data/lib/generators/terrazzo/views/edit_generator.rb +28 -0
- data/lib/generators/terrazzo/views/field_generator.rb +32 -0
- data/lib/generators/terrazzo/views/index_generator.rb +24 -0
- data/lib/generators/terrazzo/views/layout_generator.rb +26 -0
- data/lib/generators/terrazzo/views/navigation_generator.rb +24 -0
- data/lib/generators/terrazzo/views/new_generator.rb +28 -0
- data/lib/generators/terrazzo/views/show_generator.rb +24 -0
- data/lib/generators/terrazzo/views/templates/components/FlashMessages.jsx +26 -0
- data/lib/generators/terrazzo/views/templates/components/Layout.jsx +23 -0
- data/lib/generators/terrazzo/views/templates/components/Pagination.jsx +69 -0
- data/lib/generators/terrazzo/views/templates/components/SearchBar.jsx +35 -0
- data/lib/generators/terrazzo/views/templates/components/SortableHeader.jsx +29 -0
- data/lib/generators/terrazzo/views/templates/components/app-sidebar.jsx +62 -0
- data/lib/generators/terrazzo/views/templates/components/site-header.jsx +19 -0
- data/lib/generators/terrazzo/views/templates/components/ui/avatar.jsx +35 -0
- data/lib/generators/terrazzo/views/templates/components/ui/badge.jsx +34 -0
- data/lib/generators/terrazzo/views/templates/components/ui/button.jsx +47 -0
- data/lib/generators/terrazzo/views/templates/components/ui/card.jsx +50 -0
- data/lib/generators/terrazzo/views/templates/components/ui/dropdown-menu.jsx +155 -0
- data/lib/generators/terrazzo/views/templates/components/ui/field.jsx +28 -0
- data/lib/generators/terrazzo/views/templates/components/ui/index.js +106 -0
- data/lib/generators/terrazzo/views/templates/components/ui/input.jsx +19 -0
- data/lib/generators/terrazzo/views/templates/components/ui/label.jsx +16 -0
- data/lib/generators/terrazzo/views/templates/components/ui/pagination.jsx +85 -0
- data/lib/generators/terrazzo/views/templates/components/ui/popover.jsx +27 -0
- data/lib/generators/terrazzo/views/templates/components/ui/select.jsx +127 -0
- data/lib/generators/terrazzo/views/templates/components/ui/separator.jsx +23 -0
- data/lib/generators/terrazzo/views/templates/components/ui/sheet.jsx +109 -0
- data/lib/generators/terrazzo/views/templates/components/ui/sidebar.jsx +629 -0
- data/lib/generators/terrazzo/views/templates/components/ui/skeleton.jsx +10 -0
- data/lib/generators/terrazzo/views/templates/components/ui/table.jsx +83 -0
- data/lib/generators/terrazzo/views/templates/components/ui/textarea.jsx +18 -0
- data/lib/generators/terrazzo/views/templates/components/ui/tooltip.jsx +24 -0
- data/lib/generators/terrazzo/views/templates/fields/FieldRenderer.jsx +103 -0
- data/lib/generators/terrazzo/views/templates/fields/belongs_to/FormField.jsx +29 -0
- data/lib/generators/terrazzo/views/templates/fields/belongs_to/IndexField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/belongs_to/ShowField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/boolean/FormField.jsx +21 -0
- data/lib/generators/terrazzo/views/templates/fields/boolean/IndexField.jsx +12 -0
- data/lib/generators/terrazzo/views/templates/fields/boolean/ShowField.jsx +6 -0
- data/lib/generators/terrazzo/views/templates/fields/date/FormField.jsx +8 -0
- data/lib/generators/terrazzo/views/templates/fields/date/IndexField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/date/ShowField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/date_time/FormField.jsx +8 -0
- data/lib/generators/terrazzo/views/templates/fields/date_time/IndexField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/date_time/ShowField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/email/FormField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/email/IndexField.jsx +10 -0
- data/lib/generators/terrazzo/views/templates/fields/email/ShowField.jsx +10 -0
- data/lib/generators/terrazzo/views/templates/fields/has_many/FormField.jsx +151 -0
- data/lib/generators/terrazzo/views/templates/fields/has_many/IndexField.jsx +8 -0
- data/lib/generators/terrazzo/views/templates/fields/has_many/ShowField.jsx +72 -0
- data/lib/generators/terrazzo/views/templates/fields/has_one/FormField.jsx +28 -0
- data/lib/generators/terrazzo/views/templates/fields/has_one/IndexField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/has_one/ShowField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/hstore/FormField.jsx +120 -0
- data/lib/generators/terrazzo/views/templates/fields/hstore/IndexField.jsx +15 -0
- data/lib/generators/terrazzo/views/templates/fields/hstore/ShowField.jsx +24 -0
- data/lib/generators/terrazzo/views/templates/fields/index.js +81 -0
- data/lib/generators/terrazzo/views/templates/fields/number/FormField.jsx +9 -0
- data/lib/generators/terrazzo/views/templates/fields/number/IndexField.jsx +9 -0
- data/lib/generators/terrazzo/views/templates/fields/number/ShowField.jsx +9 -0
- data/lib/generators/terrazzo/views/templates/fields/password/FormField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/password/IndexField.jsx +6 -0
- data/lib/generators/terrazzo/views/templates/fields/password/ShowField.jsx +6 -0
- data/lib/generators/terrazzo/views/templates/fields/polymorphic/FormField.jsx +58 -0
- data/lib/generators/terrazzo/views/templates/fields/polymorphic/IndexField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/polymorphic/ShowField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/rich_text/FormField.jsx +21 -0
- data/lib/generators/terrazzo/views/templates/fields/rich_text/IndexField.jsx +8 -0
- data/lib/generators/terrazzo/views/templates/fields/rich_text/ShowField.jsx +6 -0
- data/lib/generators/terrazzo/views/templates/fields/select/FormField.jsx +29 -0
- data/lib/generators/terrazzo/views/templates/fields/select/IndexField.jsx +8 -0
- data/lib/generators/terrazzo/views/templates/fields/select/ShowField.jsx +5 -0
- data/lib/generators/terrazzo/views/templates/fields/shared/TextInputFormField.jsx +24 -0
- data/lib/generators/terrazzo/views/templates/fields/string/FormField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/string/IndexField.jsx +5 -0
- data/lib/generators/terrazzo/views/templates/fields/string/ShowField.jsx +5 -0
- data/lib/generators/terrazzo/views/templates/fields/text/FormField.jsx +20 -0
- data/lib/generators/terrazzo/views/templates/fields/text/IndexField.jsx +5 -0
- data/lib/generators/terrazzo/views/templates/fields/text/ShowField.jsx +5 -0
- data/lib/generators/terrazzo/views/templates/fields/time/FormField.jsx +8 -0
- data/lib/generators/terrazzo/views/templates/fields/time/IndexField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/time/ShowField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/url/FormField.jsx +7 -0
- data/lib/generators/terrazzo/views/templates/fields/url/IndexField.jsx +10 -0
- data/lib/generators/terrazzo/views/templates/fields/url/ShowField.jsx +10 -0
- data/lib/generators/terrazzo/views/templates/pages/_form.jsx +76 -0
- data/lib/generators/terrazzo/views/templates/pages/edit.jsx +44 -0
- data/lib/generators/terrazzo/views/templates/pages/index.jsx +106 -0
- data/lib/generators/terrazzo/views/templates/pages/new.jsx +36 -0
- data/lib/generators/terrazzo/views/templates/pages/show.jsx +82 -0
- data/lib/generators/terrazzo/views/views_generator.rb +52 -0
- data/lib/terrazzo/base_dashboard.rb +88 -0
- data/lib/terrazzo/engine.rb +21 -0
- data/lib/terrazzo/field/associative.rb +56 -0
- data/lib/terrazzo/field/base.rb +114 -0
- data/lib/terrazzo/field/belongs_to.rb +53 -0
- data/lib/terrazzo/field/boolean.rb +9 -0
- data/lib/terrazzo/field/date.rb +10 -0
- data/lib/terrazzo/field/date_time.rb +10 -0
- data/lib/terrazzo/field/deferred.rb +50 -0
- data/lib/terrazzo/field/email.rb +15 -0
- data/lib/terrazzo/field/has_many.rb +98 -0
- data/lib/terrazzo/field/has_one.rb +33 -0
- data/lib/terrazzo/field/hstore.rb +37 -0
- data/lib/terrazzo/field/money.rb +33 -0
- data/lib/terrazzo/field/number.rb +17 -0
- data/lib/terrazzo/field/password.rb +16 -0
- data/lib/terrazzo/field/polymorphic.rb +36 -0
- data/lib/terrazzo/field/rich_text.rb +30 -0
- data/lib/terrazzo/field/select.rb +33 -0
- data/lib/terrazzo/field/string.rb +27 -0
- data/lib/terrazzo/field/text.rb +27 -0
- data/lib/terrazzo/field/time.rb +10 -0
- data/lib/terrazzo/field/url.rb +9 -0
- data/lib/terrazzo/filter.rb +26 -0
- data/lib/terrazzo/generator_helpers.rb +36 -0
- data/lib/terrazzo/namespace/resource.rb +39 -0
- data/lib/terrazzo/namespace.rb +34 -0
- data/lib/terrazzo/not_authorized_error.rb +4 -0
- data/lib/terrazzo/order.rb +71 -0
- data/lib/terrazzo/page/base.rb +12 -0
- data/lib/terrazzo/page/collection.rb +28 -0
- data/lib/terrazzo/page/form.rb +43 -0
- data/lib/terrazzo/page/show.rb +46 -0
- data/lib/terrazzo/resource_resolver.rb +40 -0
- data/lib/terrazzo/search.rb +56 -0
- data/lib/terrazzo/version.rb +3 -0
- data/lib/terrazzo.rb +47 -0
- data/terrazzo.gemspec +32 -0
- metadata +297 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0ff3b38b0cdec57570baa990aef1998197eba301cfa0c297d0619ff7d4003374
|
|
4
|
+
data.tar.gz: f0556e47ac6c647db017b2ebb0167a5f9585a5d3c9353bab740131fbd717b02b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b46cb63e1fe4754a6938fec445f76aa90eba36f55c3fde38f08713db3e325f504b3fe985816625a07e5a8b77e7fe6d820c62f262dd20d443deaa6566111c75db
|
|
7
|
+
data.tar.gz: '0974a53199cdbdb29b8e90096429ee853df3bbeb5414cb60d1ef2dceca0c6356a7533921a3ab383a19b04bc408e08658e8f5e4d58a55f7dbfb5caa06454ad39f'
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SuperglueAdmin Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require "superglue"
|
|
2
|
+
|
|
3
|
+
module Terrazzo
|
|
4
|
+
class ApplicationController < ::ActionController::Base
|
|
5
|
+
include Superglue::Controller
|
|
6
|
+
|
|
7
|
+
prepend_view_path(
|
|
8
|
+
Superglue::Resolver.new(Terrazzo::Engine.root.join("app/views"))
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
prepend_view_path(
|
|
12
|
+
Superglue::Resolver.new(Rails.root.join("app/views"))
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
before_action :use_jsx_rendering_defaults
|
|
16
|
+
|
|
17
|
+
helper_method :namespace, :dashboard, :resource_name, :resource_class, :application_title, :terrazzo_page_identifier
|
|
18
|
+
|
|
19
|
+
def index
|
|
20
|
+
search = Terrazzo::Search.new(scoped_resource, dashboard, params[:search])
|
|
21
|
+
resources = search.run
|
|
22
|
+
|
|
23
|
+
filter = Terrazzo::Filter.new(resources, dashboard, params[:filter], params[:filter_value])
|
|
24
|
+
resources = filter.run
|
|
25
|
+
|
|
26
|
+
order = Terrazzo::Order.new(
|
|
27
|
+
attribute: params[:order] || default_sorting_attribute,
|
|
28
|
+
direction: params[:direction] || (params[:order] ? nil : default_sorting_direction)
|
|
29
|
+
)
|
|
30
|
+
resources = order.apply(resources, dashboard)
|
|
31
|
+
|
|
32
|
+
includes = dashboard.collection_includes
|
|
33
|
+
resources = resources.includes(*includes) if includes.any?
|
|
34
|
+
|
|
35
|
+
@resources = resources.page(params[:_page]).per(params[:per_page] || 25)
|
|
36
|
+
@page = Terrazzo::Page::Collection.new(dashboard, resource_class, order: order)
|
|
37
|
+
@search_term = params[:search]
|
|
38
|
+
@active_filter = params[:filter]
|
|
39
|
+
@filter_value = params[:filter_value]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def show
|
|
43
|
+
@resource = find_resource(params[:id])
|
|
44
|
+
@page = Terrazzo::Page::Show.new(dashboard, @resource)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def new
|
|
48
|
+
@resource = resource_class.new
|
|
49
|
+
@page = Terrazzo::Page::Form.new(dashboard, @resource)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def edit
|
|
53
|
+
@resource = find_resource(params[:id])
|
|
54
|
+
@page = Terrazzo::Page::Form.new(dashboard, @resource)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def create
|
|
58
|
+
@resource = resource_class.new(resource_params("create"))
|
|
59
|
+
assign_has_one_associations(@resource)
|
|
60
|
+
|
|
61
|
+
if @resource.save
|
|
62
|
+
redirect_to after_resource_created_path(@resource),
|
|
63
|
+
notice: t("terrazzo.controllers.create.success",
|
|
64
|
+
resource_name: resource_name)
|
|
65
|
+
else
|
|
66
|
+
@page = Terrazzo::Page::Form.new(dashboard, @resource)
|
|
67
|
+
render :new, status: :unprocessable_entity
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def update
|
|
72
|
+
@resource = find_resource(params[:id])
|
|
73
|
+
|
|
74
|
+
rp = resource_params("update")
|
|
75
|
+
assign_has_one_associations(@resource)
|
|
76
|
+
|
|
77
|
+
if @resource.update(rp)
|
|
78
|
+
redirect_to after_resource_updated_path(@resource),
|
|
79
|
+
notice: t("terrazzo.controllers.update.success",
|
|
80
|
+
resource_name: resource_name)
|
|
81
|
+
else
|
|
82
|
+
@page = Terrazzo::Page::Form.new(dashboard, @resource)
|
|
83
|
+
render :edit, status: :unprocessable_entity
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def destroy
|
|
88
|
+
@resource = find_resource(params[:id])
|
|
89
|
+
@resource.destroy
|
|
90
|
+
|
|
91
|
+
redirect_to after_resource_destroyed_path,
|
|
92
|
+
notice: t("terrazzo.controllers.destroy.success",
|
|
93
|
+
resource_name: resource_name)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
protected
|
|
97
|
+
|
|
98
|
+
def application_title
|
|
99
|
+
Rails.application.class.module_parent_name.titleize
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def default_sorting_attribute
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def default_sorting_direction
|
|
107
|
+
:asc
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def after_resource_created_path(resource)
|
|
111
|
+
[namespace, resource]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def after_resource_updated_path(resource)
|
|
115
|
+
[namespace, resource]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def after_resource_destroyed_path
|
|
119
|
+
url_for(action: :index, only_path: true)
|
|
120
|
+
rescue ActionController::UrlGenerationError
|
|
121
|
+
"/"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def scoped_resource
|
|
125
|
+
resource_class.all
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def find_resource(id)
|
|
129
|
+
scoped_resource.find(id)
|
|
130
|
+
rescue ActiveRecord::RecordNotFound
|
|
131
|
+
# Support models that override to_param (e.g., slug-based URLs)
|
|
132
|
+
scoped_resource.find_by!(slug: id)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def resource_params(action = nil)
|
|
136
|
+
permitted = params.require(resource_class.model_name.param_key)
|
|
137
|
+
.permit(dashboard.permitted_attributes(action))
|
|
138
|
+
|
|
139
|
+
# Transform and extract special field params
|
|
140
|
+
@has_one_assignments = {}
|
|
141
|
+
dashboard.flatten_attributes(dashboard.form_attributes(action)).each do |attr|
|
|
142
|
+
field_type = dashboard.attribute_type_for(attr)
|
|
143
|
+
klass = field_type.is_a?(Terrazzo::Field::Deferred) ? field_type.deferred_class : field_type
|
|
144
|
+
|
|
145
|
+
# Transform params (e.g., JSON string → Hash for hstore fields)
|
|
146
|
+
if permitted.key?(attr.to_s) && klass.respond_to?(:transform_param)
|
|
147
|
+
permitted[attr.to_s] = klass.transform_param(permitted[attr.to_s])
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Extract has_one virtual _id params since ActiveRecord doesn't
|
|
151
|
+
# natively support assigning has_one associations by id
|
|
152
|
+
next unless klass <= Terrazzo::Field::HasOne
|
|
153
|
+
|
|
154
|
+
id_key = "#{attr}_id"
|
|
155
|
+
if permitted.key?(id_key)
|
|
156
|
+
id_value = permitted.delete(id_key)
|
|
157
|
+
@has_one_assignments[attr] = id_value if id_value.present?
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
permitted
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def assign_has_one_associations(resource)
|
|
165
|
+
return unless @has_one_assignments
|
|
166
|
+
|
|
167
|
+
@has_one_assignments.each do |attr, id_value|
|
|
168
|
+
reflection = resource.class.reflect_on_association(attr)
|
|
169
|
+
associated = reflection.klass.find(id_value)
|
|
170
|
+
resource.public_send(:"#{attr}=", associated)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def authorized_action?(resource, action)
|
|
175
|
+
true
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def namespace
|
|
179
|
+
@_namespace ||= resolver.namespace
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def dashboard
|
|
183
|
+
@_dashboard ||= resolver.dashboard_class.new
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def resource_class
|
|
187
|
+
@_resource_class ||= resolver.resource_class
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def resource_name
|
|
191
|
+
@_resource_name ||= resolver.resource_title
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Build a page identifier that matches the user's namespace, not the
|
|
195
|
+
# engine's internal template path. The React page-to-component mapping
|
|
196
|
+
# keys off this identifier (e.g. "admin/application/index").
|
|
197
|
+
def terrazzo_page_identifier
|
|
198
|
+
ns = controller_path.split("/").first
|
|
199
|
+
"#{ns}/application/#{action_name}"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
private
|
|
203
|
+
|
|
204
|
+
def resolver
|
|
205
|
+
@_resolver ||= Terrazzo::ResourceResolver.new(controller_path)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
param_key = resource_class.model_name.param_key
|
|
2
|
+
|
|
3
|
+
json.pageTitle t("terrazzo.actions.edit", resource_name: resource_name)
|
|
4
|
+
|
|
5
|
+
json.form do
|
|
6
|
+
json.props({
|
|
7
|
+
action: polymorphic_path([namespace, @resource]),
|
|
8
|
+
method: "post"
|
|
9
|
+
})
|
|
10
|
+
json.extras do
|
|
11
|
+
if protect_against_forgery?
|
|
12
|
+
json.set!("authenticity_token") do
|
|
13
|
+
json.name "authenticity_token"
|
|
14
|
+
json.type "hidden"
|
|
15
|
+
json.defaultValue form_authenticity_token
|
|
16
|
+
json.autoComplete "off"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
json.set!("_method") do
|
|
20
|
+
json.name "_method"
|
|
21
|
+
json.type "hidden"
|
|
22
|
+
json.value "patch"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
json.fields do
|
|
26
|
+
json.array! @page.attributes("update") do |field|
|
|
27
|
+
json.attribute field.attribute.to_s
|
|
28
|
+
json.label field.attribute.to_s.humanize
|
|
29
|
+
json.fieldType field.field_type
|
|
30
|
+
json.value field.serialize_value(:form)
|
|
31
|
+
json.options field.serializable_options
|
|
32
|
+
json.required field.required?
|
|
33
|
+
json.input field.form_input_attributes(param_key)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
json.fieldGroups do
|
|
37
|
+
json.array! @page.grouped_attributes("update") do |group|
|
|
38
|
+
json.name group[:name]
|
|
39
|
+
json.fields do
|
|
40
|
+
json.array! group[:fields] do |field|
|
|
41
|
+
json.attribute field.attribute.to_s
|
|
42
|
+
json.label field.attribute.to_s.humanize
|
|
43
|
+
json.fieldType field.field_type
|
|
44
|
+
json.value field.serialize_value(:form)
|
|
45
|
+
json.options field.serializable_options
|
|
46
|
+
json.required field.required?
|
|
47
|
+
json.input field.form_input_attributes(param_key)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
json.errors @resource.errors.full_messages
|
|
55
|
+
|
|
56
|
+
json.showPath polymorphic_path([namespace, @resource])
|
|
57
|
+
json.indexPath begin
|
|
58
|
+
url_for(controller: controller_path, action: :index, only_path: true)
|
|
59
|
+
rescue ActionController::UrlGenerationError
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
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
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
json.searchBar do
|
|
2
|
+
json.searchTerm @search_term
|
|
3
|
+
json.searchPath request.path
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
json.filters do
|
|
7
|
+
json.available dashboard.collection_filters.keys.map(&:to_s)
|
|
8
|
+
json.active @active_filter
|
|
9
|
+
json.value @filter_value
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
json.table do
|
|
13
|
+
json.headers do
|
|
14
|
+
json.array! @page.attribute_names do |attr|
|
|
15
|
+
field_class = dashboard.attribute_type_for(attr)
|
|
16
|
+
json.label attr.to_s.humanize
|
|
17
|
+
json.attribute attr.to_s
|
|
18
|
+
json.sortable field_class.sortable?
|
|
19
|
+
json.sortDirection @page.sort_direction_for(attr)
|
|
20
|
+
json.sortUrl url_for(
|
|
21
|
+
@page.order_params_for(attr).merge(
|
|
22
|
+
search: @search_term,
|
|
23
|
+
filter: @active_filter,
|
|
24
|
+
filter_value: @filter_value,
|
|
25
|
+
_page: 1,
|
|
26
|
+
only_path: true
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
json.rows do
|
|
33
|
+
json.array! @resources do |resource|
|
|
34
|
+
json.id resource.id
|
|
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
|
|
38
|
+
|
|
39
|
+
json.cells do
|
|
40
|
+
json.array! @page.attribute_names do |attr|
|
|
41
|
+
field = dashboard.attribute_type_for(attr).new(attr, nil, :index, resource: resource)
|
|
42
|
+
json.attribute attr.to_s
|
|
43
|
+
json.fieldType field.field_type
|
|
44
|
+
json.value field.serialize_value(:index)
|
|
45
|
+
json.options field.serializable_options
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
json.pagination do
|
|
53
|
+
json.currentPage @resources.current_page
|
|
54
|
+
json.totalPages @resources.total_pages
|
|
55
|
+
json.totalCount @resources.total_count
|
|
56
|
+
json.perPage @resources.limit_value
|
|
57
|
+
if @resources.next_page
|
|
58
|
+
json.nextPagePath url_for(
|
|
59
|
+
_page: @resources.next_page,
|
|
60
|
+
search: @search_term,
|
|
61
|
+
order: params[:order],
|
|
62
|
+
direction: params[:direction],
|
|
63
|
+
filter: @active_filter,
|
|
64
|
+
filter_value: @filter_value,
|
|
65
|
+
props_at: "data.pagination",
|
|
66
|
+
only_path: true
|
|
67
|
+
)
|
|
68
|
+
else
|
|
69
|
+
json.nextPagePath nil
|
|
70
|
+
end
|
|
71
|
+
if @resources.prev_page
|
|
72
|
+
json.prevPagePath url_for(
|
|
73
|
+
_page: @resources.prev_page,
|
|
74
|
+
search: @search_term,
|
|
75
|
+
order: params[:order],
|
|
76
|
+
direction: params[:direction],
|
|
77
|
+
filter: @active_filter,
|
|
78
|
+
filter_value: @filter_value,
|
|
79
|
+
props_at: "data.pagination",
|
|
80
|
+
only_path: true
|
|
81
|
+
)
|
|
82
|
+
else
|
|
83
|
+
json.prevPagePath nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
json.resourceName resource_name.pluralize
|
|
88
|
+
json.singularResourceName resource_name
|
|
89
|
+
json.newResourcePath new_polymorphic_path([namespace, resource_class]) rescue nil
|
|
90
|
+
|
|
91
|
+
json.navigation do
|
|
92
|
+
json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
|
|
93
|
+
json.label nav_resource.resource_name.humanize.pluralize
|
|
94
|
+
json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
|
|
95
|
+
json.active nav_resource.controller_path == controller_path
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
action = @resource.persisted? ? "update" : "create"
|
|
2
|
+
param_key = resource_class.model_name.param_key
|
|
3
|
+
|
|
4
|
+
json.pageTitle t("terrazzo.actions.new", resource_name: resource_name)
|
|
5
|
+
|
|
6
|
+
json.form do
|
|
7
|
+
json.props({
|
|
8
|
+
action: polymorphic_path([namespace, resource_class]),
|
|
9
|
+
method: "post"
|
|
10
|
+
})
|
|
11
|
+
json.extras do
|
|
12
|
+
if protect_against_forgery?
|
|
13
|
+
json.set!("authenticity_token") do
|
|
14
|
+
json.name "authenticity_token"
|
|
15
|
+
json.type "hidden"
|
|
16
|
+
json.defaultValue form_authenticity_token
|
|
17
|
+
json.autoComplete "off"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
json.fields do
|
|
22
|
+
json.array! @page.attributes(action) do |field|
|
|
23
|
+
json.attribute field.attribute.to_s
|
|
24
|
+
json.label field.attribute.to_s.humanize
|
|
25
|
+
json.fieldType field.field_type
|
|
26
|
+
json.value field.serialize_value(:form)
|
|
27
|
+
json.options field.serializable_options
|
|
28
|
+
json.required field.required?
|
|
29
|
+
json.input field.form_input_attributes(param_key)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
json.fieldGroups do
|
|
33
|
+
json.array! @page.grouped_attributes(action) do |group|
|
|
34
|
+
json.name group[:name]
|
|
35
|
+
json.fields do
|
|
36
|
+
json.array! group[:fields] do |field|
|
|
37
|
+
json.attribute field.attribute.to_s
|
|
38
|
+
json.label field.attribute.to_s.humanize
|
|
39
|
+
json.fieldType field.field_type
|
|
40
|
+
json.value field.serialize_value(:form)
|
|
41
|
+
json.options field.serializable_options
|
|
42
|
+
json.required field.required?
|
|
43
|
+
json.input field.form_input_attributes(param_key)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
json.errors @resource.errors.full_messages
|
|
51
|
+
|
|
52
|
+
json.indexPath begin
|
|
53
|
+
url_for(controller: controller_path, action: :index, only_path: true)
|
|
54
|
+
rescue ActionController::UrlGenerationError
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
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
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
json.pageTitle @page.page_title
|
|
2
|
+
|
|
3
|
+
json.attributeGroups do
|
|
4
|
+
json.array! @page.grouped_attributes do |group|
|
|
5
|
+
json.name group[:name]
|
|
6
|
+
json.attributes do
|
|
7
|
+
json.array! group[:fields] do |field|
|
|
8
|
+
json.attribute field.attribute.to_s
|
|
9
|
+
json.label field.attribute.to_s.humanize
|
|
10
|
+
json.fieldType field.field_type
|
|
11
|
+
json.value field.serialize_value(:show)
|
|
12
|
+
json.options field.serializable_options
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
json.attributes do
|
|
19
|
+
json.array! @page.attributes do |field|
|
|
20
|
+
json.attribute field.attribute.to_s
|
|
21
|
+
json.label field.attribute.to_s.humanize
|
|
22
|
+
json.fieldType field.field_type
|
|
23
|
+
json.value field.serialize_value(:show)
|
|
24
|
+
json.options field.serializable_options
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
json.editPath edit_polymorphic_path([namespace, @resource]) rescue nil
|
|
29
|
+
json.deletePath polymorphic_path([namespace, @resource]) rescue nil
|
|
30
|
+
json.indexPath begin
|
|
31
|
+
url_for(controller: controller_path, action: :index, only_path: true)
|
|
32
|
+
rescue ActionController::UrlGenerationError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
json.resourceName resource_name
|
|
36
|
+
json.pluralResourceName resource_name.pluralize
|
|
37
|
+
|
|
38
|
+
json.navigation do
|
|
39
|
+
json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
|
|
40
|
+
json.label nav_resource.resource_name.humanize.pluralize
|
|
41
|
+
json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
|
|
42
|
+
json.active nav_resource.controller_path == controller_path
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
en:
|
|
2
|
+
terrazzo:
|
|
3
|
+
actions:
|
|
4
|
+
new: "New %{resource_name}"
|
|
5
|
+
edit: "Edit %{resource_name}"
|
|
6
|
+
show: "Show %{resource_name}"
|
|
7
|
+
destroy: "Delete %{resource_name}"
|
|
8
|
+
confirm_destroy: "Are you sure you want to delete this %{resource_name}?"
|
|
9
|
+
back: "Back"
|
|
10
|
+
save: "Save"
|
|
11
|
+
cancel: "Cancel"
|
|
12
|
+
controllers:
|
|
13
|
+
create:
|
|
14
|
+
success: "%{resource_name} was successfully created."
|
|
15
|
+
update:
|
|
16
|
+
success: "%{resource_name} was successfully updated."
|
|
17
|
+
destroy:
|
|
18
|
+
success: "%{resource_name} was successfully destroyed."
|
|
19
|
+
search:
|
|
20
|
+
placeholder: "Search %{resource_name}..."
|
|
21
|
+
clear: "Clear search"
|
|
22
|
+
pagination:
|
|
23
|
+
previous: "Previous"
|
|
24
|
+
next: "Next"
|
|
25
|
+
page_info: "Page %{current} of %{total}"
|
|
26
|
+
total_count: "%{count} total"
|
|
27
|
+
navigation:
|
|
28
|
+
title: "Admin"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "terrazzo/generator_helpers"
|
|
3
|
+
|
|
4
|
+
module Terrazzo
|
|
5
|
+
module Generators
|
|
6
|
+
class DashboardGenerator < Rails::Generators::NamedBase
|
|
7
|
+
include Terrazzo::GeneratorHelpers
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
class_option :namespace, type: :string, default: "admin",
|
|
12
|
+
desc: "Admin namespace"
|
|
13
|
+
class_option :bundler, type: :string, default: "vite",
|
|
14
|
+
desc: "JavaScript bundler (vite or sprockets)"
|
|
15
|
+
|
|
16
|
+
def create_dashboard
|
|
17
|
+
template "dashboard.rb.erb",
|
|
18
|
+
"app/dashboards/#{file_name}_dashboard.rb"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_controller
|
|
22
|
+
template "controller.rb.erb",
|
|
23
|
+
"app/controllers/#{options[:namespace]}/#{file_name.pluralize}_controller.rb"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
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
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def model_class
|
|
54
|
+
class_name.constantize
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def attribute_types
|
|
58
|
+
columns = model_class.columns.reject { |c| c.name == "id" }
|
|
59
|
+
associations = model_class.reflect_on_all_associations
|
|
60
|
+
|
|
61
|
+
types = {}
|
|
62
|
+
|
|
63
|
+
# ID first
|
|
64
|
+
types[:id] = "Field::Number"
|
|
65
|
+
|
|
66
|
+
# Columns
|
|
67
|
+
columns.each do |col|
|
|
68
|
+
next if col.name.end_with?("_type") && columns.any? { |c| c.name == col.name.sub(/_type$/, "_id") }
|
|
69
|
+
next if association_foreign_key?(col.name, associations)
|
|
70
|
+
|
|
71
|
+
types[col.name.to_sym] = column_to_field_type(col)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Associations
|
|
75
|
+
associations.each do |assoc|
|
|
76
|
+
case assoc.macro
|
|
77
|
+
when :belongs_to
|
|
78
|
+
types[assoc.name] = "Field::BelongsTo"
|
|
79
|
+
when :has_many, :has_and_belongs_to_many
|
|
80
|
+
types[assoc.name] = "Field::HasMany"
|
|
81
|
+
when :has_one
|
|
82
|
+
types[assoc.name] = "Field::HasOne"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
types
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def collection_attributes
|
|
90
|
+
attrs = attribute_types.keys.reject { |a| %i[created_at updated_at].include?(a) }
|
|
91
|
+
attrs.first(4)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def show_page_attributes
|
|
95
|
+
attribute_types.keys
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def form_attributes
|
|
99
|
+
attribute_types.keys.reject { |a| %i[id created_at updated_at].include?(a) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def association_foreign_key?(column_name, associations)
|
|
103
|
+
associations.any? do |assoc|
|
|
104
|
+
assoc.macro == :belongs_to &&
|
|
105
|
+
assoc.foreign_key.to_s == column_name
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def has_enum?(column_name)
|
|
110
|
+
model_class.defined_enums.key?(column_name.to_s)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def enum_collection(column_name)
|
|
114
|
+
model_class.defined_enums[column_name.to_s]&.keys || []
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|