plutonium 0.15.7 → 0.15.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -81
- data/app/assets/plutonium.css +1 -1
- data/docs/.vitepress/config.ts +1 -0
- data/docs/guide/getting-started/index.md +4 -1
- data/docs/guide/tutorial.md +403 -0
- data/docs/index.md +19 -15
- data/docs/public/tutorial/plutonium-association-panel.png +0 -0
- data/docs/public/tutorial/plutonium-dashboard.png +0 -0
- data/docs/public/tutorial/plutonium-login-page.png +0 -0
- data/docs/public/tutorial/plutonium-nested-form.png +0 -0
- data/docs/public/tutorial/plutonium-posts-dashboard-customized.png +0 -0
- data/docs/public/tutorial/plutonium-posts-dashboard.png +0 -0
- data/docs/public/tutorial/plutonium-posts-detail-customized.png +0 -0
- data/docs/public/tutorial/plutonium-posts-detail.png +0 -0
- data/docs/public/tutorial/plutonium-publish-post.png +0 -0
- data/lib/generators/pu/core/assets/templates/tailwind.config.js +11 -1
- data/lib/generators/pu/extra/colorized_logger/colorized_logger_generator.rb +21 -0
- data/lib/generators/pu/extra/colorized_logger/templates/config/initializers/colorized_logger.rb +22 -0
- data/lib/generators/pu/gem/standard/standard_generator.rb +1 -1
- data/lib/plutonium/core/controllers/authorizable.rb +1 -1
- data/lib/plutonium/resource/controller.rb +12 -8
- data/lib/plutonium/resource/controllers/authorizable.rb +2 -2
- data/lib/plutonium/resource/controllers/presentable.rb +15 -4
- data/lib/plutonium/ui/dyna_frame/host.rb +1 -1
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +73 -70
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/package-lock.json +2 -2
- data/package.json +1 -1
- data/tailwind.config.js +11 -1
- data/tailwind.options.js +7 -1
- metadata +14 -2
@@ -5,7 +5,6 @@ module Plutonium
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
helper_method :presentable_attributes
|
9
8
|
helper_method :build_form, :build_detail, :build_collection
|
10
9
|
end
|
11
10
|
|
@@ -14,12 +13,24 @@ module Plutonium
|
|
14
13
|
def presentable_attributes
|
15
14
|
@presentable_attributes ||= begin
|
16
15
|
presentable_attributes = permitted_attributes
|
17
|
-
|
18
|
-
|
16
|
+
if current_parent
|
17
|
+
presentable_attributes -= [parent_input_param, :"#{parent_input_param}_id"]
|
18
|
+
elsif scoped_to_entity?
|
19
|
+
presentable_attributes -= [scoped_entity_param_key, :"#{scoped_entity_param_key}_id"]
|
20
|
+
end
|
19
21
|
presentable_attributes
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
25
|
+
def submittable_attributes
|
26
|
+
@submittable_attributes ||= begin
|
27
|
+
submittable_attributes = permitted_attributes
|
28
|
+
submittable_attributes -= [parent_input_param, :"#{parent_input_param}_id"] if current_parent
|
29
|
+
submittable_attributes -= [scoped_entity_param_key, :"#{scoped_entity_param_key}_id"] if scoped_to_entity?
|
30
|
+
submittable_attributes
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
23
34
|
def build_collection
|
24
35
|
current_definition.collection_class.new(@resource_records, resource_fields: presentable_attributes, resource_definition: current_definition)
|
25
36
|
end
|
@@ -29,7 +40,7 @@ module Plutonium
|
|
29
40
|
end
|
30
41
|
|
31
42
|
def build_form(record = resource_record)
|
32
|
-
current_definition.form_class.new(record, resource_fields:
|
43
|
+
current_definition.form_class.new(record, resource_fields: submittable_attributes, resource_definition: current_definition)
|
33
44
|
end
|
34
45
|
end
|
35
46
|
end
|
@@ -5,6 +5,7 @@ module Plutonium
|
|
5
5
|
module Form
|
6
6
|
module Concerns
|
7
7
|
# Handles rendering of nested resource fields in forms
|
8
|
+
# TODO: further decompose this into components
|
8
9
|
# @api private
|
9
10
|
module RendersNestedResourceFields
|
10
11
|
extend ActiveSupport::Concern
|
@@ -19,75 +20,51 @@ module Plutonium
|
|
19
20
|
defineable_props :field, :input
|
20
21
|
end
|
21
22
|
|
22
|
-
# Template object for new nested records
|
23
|
-
class NotPersisted
|
24
|
-
def persisted?
|
25
|
-
false
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
# Renders a nested resource field with associated inputs
|
32
|
-
# @param [Symbol] name The name of the nested resource field
|
33
|
-
# @raise [ArgumentError] if the nested input definition is missing required configuration
|
34
|
-
def render_nested_resource_field(name)
|
35
|
-
context = NestedFieldContext.new(
|
36
|
-
name: name,
|
37
|
-
definition: build_nested_definition(name),
|
38
|
-
resource_class: resource_class,
|
39
|
-
resource_definition: resource_definition
|
40
|
-
)
|
41
|
-
|
42
|
-
render_nested_field_container(context) do
|
43
|
-
render_nested_field_header(context)
|
44
|
-
render_nested_field_content(context)
|
45
|
-
render_nested_add_button(context)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
23
|
class NestedFieldContext
|
52
24
|
attr_reader :name, :definition, :options, :permitted_fields
|
53
25
|
|
54
|
-
def initialize(name:, definition:, resource_class:, resource_definition:)
|
26
|
+
def initialize(name:, definition:, resource_class:, resource_definition:, object_class:)
|
55
27
|
@name = name
|
56
28
|
@definition = definition
|
57
29
|
@resource_definition = resource_definition
|
58
30
|
@resource_class = resource_class
|
59
31
|
@options = build_options
|
60
32
|
@permitted_fields = build_permitted_fields
|
33
|
+
@object_class = object_class
|
61
34
|
end
|
62
35
|
|
63
36
|
def nested_attribute_options
|
64
37
|
@nested_attribute_options ||= @resource_class.all_nested_attributes_options[@name] || {}
|
65
38
|
end
|
66
39
|
|
67
|
-
def
|
40
|
+
def nested_fields_input_param
|
68
41
|
@options[:as] || :"#{@name}_attributes"
|
69
42
|
end
|
70
43
|
|
71
|
-
def
|
44
|
+
def nested_fields_multiple?
|
72
45
|
@options[:multiple]
|
73
46
|
end
|
74
47
|
|
48
|
+
def blank_object
|
49
|
+
(@object_class || nested_attribute_options[:class])&.new
|
50
|
+
end
|
51
|
+
|
75
52
|
private
|
76
53
|
|
77
54
|
def build_options
|
78
55
|
options = @resource_definition.defined_nested_inputs[@name][:options].dup || {}
|
79
|
-
|
80
|
-
|
56
|
+
merge_nested_fields_options(options)
|
57
|
+
set_nested_fields_limits(options)
|
81
58
|
options
|
82
59
|
end
|
83
60
|
|
84
|
-
def
|
61
|
+
def merge_nested_fields_options(options)
|
85
62
|
NESTED_OPTION_KEYS.each do |key|
|
86
63
|
options.fetch(key) { options[key] = nested_attribute_options[key] }
|
87
64
|
end
|
88
65
|
end
|
89
66
|
|
90
|
-
def
|
67
|
+
def set_nested_fields_limits(options)
|
91
68
|
options.fetch(:limit) do
|
92
69
|
options[:limit] = if SINGULAR_MACROS.include?(nested_attribute_options[:macro])
|
93
70
|
1
|
@@ -106,19 +83,43 @@ module Plutonium
|
|
106
83
|
end
|
107
84
|
end
|
108
85
|
|
109
|
-
|
86
|
+
# Template object for new nested records
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Renders a nested resource field with associated inputs
|
91
|
+
# @param [Symbol] name The name of the nested resource field
|
92
|
+
# @raise [ArgumentError] if the nested input definition is missing required configuration
|
93
|
+
def render_nested_resource_field(name)
|
94
|
+
# debugger if $extracting_input
|
95
|
+
context = NestedFieldContext.new(
|
96
|
+
name: name,
|
97
|
+
definition: build_nested_fields_definition(name),
|
98
|
+
resource_class: resource_class,
|
99
|
+
resource_definition: resource_definition,
|
100
|
+
object_class: resource_definition.defined_nested_inputs[name][:options]&.fetch(:object_class, nil)
|
101
|
+
)
|
102
|
+
|
103
|
+
render_nested_field_container(context) do
|
104
|
+
render_nested_field_header(context)
|
105
|
+
render_nested_field_content(context)
|
106
|
+
render_nested_fields_add_button(context)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_nested_fields_definition(name)
|
110
111
|
nested_input_definition = resource_definition.defined_nested_inputs[name]
|
111
112
|
|
112
113
|
if nested_input_definition[:options]&.fetch(:using, nil)
|
113
114
|
nested_input_definition[:options][:using]
|
114
115
|
elsif nested_input_definition[:block]
|
115
|
-
|
116
|
+
build_nested_fields_definition_from_block(nested_input_definition[:block])
|
116
117
|
else
|
117
118
|
raise_missing_nested_definition_error(name)
|
118
119
|
end
|
119
120
|
end
|
120
121
|
|
121
|
-
def
|
122
|
+
def build_nested_fields_definition_from_block(block)
|
122
123
|
definition = NestedInputsDefinition.new
|
123
124
|
block.call(definition)
|
124
125
|
definition
|
@@ -138,16 +139,16 @@ module Plutonium
|
|
138
139
|
def render_nested_field_header(context)
|
139
140
|
div do
|
140
141
|
h2(class: "text-lg font-semibold text-gray-900 dark:text-white") { context.name.to_s.humanize }
|
141
|
-
|
142
|
+
render_nested_fields_header_description(context.options[:description]) if context.options[:description]
|
142
143
|
end
|
143
144
|
end
|
144
145
|
|
145
|
-
def
|
146
|
+
def render_nested_fields_header_description(description)
|
146
147
|
p(class: "text-md font-normal text-gray-500 dark:text-gray-400") { description }
|
147
148
|
end
|
148
149
|
|
149
150
|
def render_nested_field_content(context)
|
150
|
-
if context.
|
151
|
+
if context.nested_fields_multiple?
|
151
152
|
render_multiple_nested_fields(context)
|
152
153
|
else
|
153
154
|
render_single_nested_field(context)
|
@@ -157,77 +158,79 @@ module Plutonium
|
|
157
158
|
end
|
158
159
|
|
159
160
|
def render_multiple_nested_fields(context)
|
160
|
-
|
161
|
-
|
161
|
+
nesting_method = :nest_many
|
162
|
+
options = {default: {NEW_RECORD: context.blank_object}}
|
163
|
+
render_template_for_nested_fields(context, options, nesting_method:)
|
164
|
+
render_existing_nested_fields(context, options, nesting_method:)
|
162
165
|
end
|
163
166
|
|
164
167
|
def render_single_nested_field(context)
|
165
|
-
|
166
|
-
|
168
|
+
nesting_method = :nest_one
|
169
|
+
options = {default: context.blank_object}
|
170
|
+
render_template_for_nested_fields(context, options, nesting_method:)
|
171
|
+
render_existing_nested_fields(context, options, nesting_method:)
|
167
172
|
end
|
168
173
|
|
169
|
-
def render_template_for_nested_fields(context,
|
174
|
+
def render_template_for_nested_fields(context, options, nesting_method:)
|
170
175
|
template_tag data_nested_resource_form_fields_target: "template" do
|
171
|
-
nesting_method
|
172
|
-
|
173
|
-
render_fieldset(nested, context)
|
176
|
+
send(nesting_method, context.name, as: context.nested_fields_input_param, **options, template: true) do |nested|
|
177
|
+
render_nested_fields_fieldset(nested, context)
|
174
178
|
end
|
175
179
|
end
|
176
180
|
end
|
177
181
|
|
178
|
-
def render_existing_nested_fields(context,
|
179
|
-
nesting_method
|
180
|
-
|
181
|
-
render_fieldset(nested, context)
|
182
|
+
def render_existing_nested_fields(context, options, nesting_method:)
|
183
|
+
send(nesting_method, context.name, as: context.nested_fields_input_param, **options) do |nested|
|
184
|
+
render_nested_fields_fieldset(nested, context)
|
182
185
|
end
|
183
186
|
end
|
184
187
|
|
185
|
-
def
|
188
|
+
def render_nested_fields_fieldset(nested, context)
|
186
189
|
fieldset(
|
187
190
|
data_new_record: !nested.object&.persisted?,
|
188
191
|
class: "nested-resource-form-fields border border-gray-200 dark:border-gray-700 rounded-lg p-4 space-y-4 relative"
|
189
192
|
) do
|
190
|
-
|
191
|
-
|
193
|
+
render_nested_fields_fieldset_content(nested, context)
|
194
|
+
render_nested_fields_delete_button(nested, context.options)
|
192
195
|
end
|
193
196
|
end
|
194
197
|
|
195
|
-
def
|
198
|
+
def render_nested_fields_fieldset_content(nested, context)
|
196
199
|
div(class: "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-4 grid-flow-row-dense") do
|
197
|
-
|
198
|
-
|
200
|
+
render_nested_fields_hidden_fields(nested, context)
|
201
|
+
render_nested_fields_visible_fields(nested, context)
|
199
202
|
end
|
200
203
|
end
|
201
204
|
|
202
|
-
def
|
205
|
+
def render_nested_fields_hidden_fields(nested, context)
|
203
206
|
if !context.options[:update_only] && context.options[:class]&.respond_to?(:primary_key)
|
204
207
|
render nested.field(context.options[:class].primary_key).hidden_tag
|
205
208
|
end
|
206
209
|
render nested.field(:_destroy).hidden_tag if context.options[:allow_destroy]
|
207
210
|
end
|
208
211
|
|
209
|
-
def
|
212
|
+
def render_nested_fields_visible_fields(nested, context)
|
210
213
|
context.permitted_fields.each do |input|
|
211
214
|
render_simple_resource_field(input, context.definition, nested)
|
212
215
|
end
|
213
216
|
end
|
214
217
|
|
215
|
-
def
|
218
|
+
def render_nested_fields_delete_button(nested, options)
|
216
219
|
return unless !nested.object&.persisted? || options[:allow_destroy]
|
217
220
|
|
218
|
-
|
221
|
+
render_nested_fields_delete_button_content
|
219
222
|
end
|
220
223
|
|
221
|
-
def
|
224
|
+
def render_nested_fields_delete_button_content
|
222
225
|
div(class: "flex items-center justify-end") do
|
223
226
|
label(class: "inline-flex items-center text-md font-medium text-red-900 cursor-pointer") do
|
224
227
|
plain "Delete"
|
225
|
-
|
228
|
+
render_nested_fields_delete_checkbox
|
226
229
|
end
|
227
230
|
end
|
228
231
|
end
|
229
232
|
|
230
|
-
def
|
233
|
+
def render_nested_fields_delete_checkbox
|
231
234
|
input(
|
232
235
|
type: :checkbox,
|
233
236
|
class: "w-4 h-4 ms-2 text-red-600 bg-red-100 border-red-300 rounded focus:ring-red-500 dark:focus:ring-red-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer",
|
@@ -235,7 +238,7 @@ module Plutonium
|
|
235
238
|
)
|
236
239
|
end
|
237
240
|
|
238
|
-
def
|
241
|
+
def render_nested_fields_add_button(context)
|
239
242
|
div do
|
240
243
|
button(
|
241
244
|
type: :button,
|
@@ -245,12 +248,12 @@ module Plutonium
|
|
245
248
|
nested_resource_form_fields_target: "addButton"
|
246
249
|
}
|
247
250
|
) do
|
248
|
-
|
251
|
+
render_nested_fields_add_button_content(context.name)
|
249
252
|
end
|
250
253
|
end
|
251
254
|
end
|
252
255
|
|
253
|
-
def
|
256
|
+
def render_nested_fields_add_button_content(name)
|
254
257
|
span(class: "bg-secondary-700 text-white hover:bg-secondary-800 focus:ring-secondary-300 dark:bg-secondary-600 dark:hover:bg-secondary-700 dark:focus:ring-secondary-800 flex items-center justify-center px-4 py-1.5 text-sm font-medium rounded-lg focus:outline-none focus:ring-4") do
|
255
258
|
render Phlex::TablerIcons::Plus.new(class: "w-4 h-4 mr-1")
|
256
259
|
span { "Add #{name.to_s.singularize.humanize}" }
|
@@ -94,7 +94,7 @@ module Plutonium
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def render_footer
|
97
|
-
div(class: "sticky bottom-[-2px] mt-1 p-4 pb-6 w-full z-50 bg-gray-50 dark:bg-gray-900") {
|
97
|
+
div(class: "sticky dyna:static bottom-[-2px] mt-1 p-4 pb-6 w-full z-50 bg-gray-50 dark:bg-gray-900") {
|
98
98
|
TableInfo(pagy_instance)
|
99
99
|
TablePagination(pagy_instance)
|
100
100
|
}
|
data/lib/plutonium/version.rb
CHANGED
data/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "@radioactive-labs/plutonium",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.6",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "@radioactive-labs/plutonium",
|
9
|
-
"version": "0.1.
|
9
|
+
"version": "0.1.6",
|
10
10
|
"license": "MIT",
|
11
11
|
"dependencies": {
|
12
12
|
"@hotwired/stimulus": "^3.2.2",
|
data/package.json
CHANGED
data/tailwind.config.js
CHANGED
@@ -1,9 +1,19 @@
|
|
1
1
|
/** @type {import('tailwindcss').Config} */
|
2
2
|
|
3
|
+
const tailwindPlugin = require('tailwindcss/plugin')
|
3
4
|
const options = require("./tailwind.options.js")
|
4
5
|
|
5
6
|
export const content = options.content
|
6
7
|
export const darkMode = options.darkMode
|
7
|
-
export const plugins = options.plugins.map((plugin)
|
8
|
+
export const plugins = options.plugins.map(function (plugin) {
|
9
|
+
switch (typeof plugin) {
|
10
|
+
case "function":
|
11
|
+
return tailwindPlugin(plugin)
|
12
|
+
case "string":
|
13
|
+
return require(plugin)
|
14
|
+
default:
|
15
|
+
throw Error(`unsupported plugin: ${plugin}: ${(typeof plugin)}`)
|
16
|
+
}
|
17
|
+
})
|
8
18
|
export const theme = options.theme
|
9
19
|
export const safelist = options.safelist
|
data/tailwind.options.js
CHANGED
@@ -12,7 +12,13 @@ export const darkMode = "selector";
|
|
12
12
|
export const plugins = [
|
13
13
|
// requires users to have the required packages installed in their own project.
|
14
14
|
"@tailwindcss/forms",
|
15
|
-
"flowbite/plugin"
|
15
|
+
"flowbite/plugin",
|
16
|
+
function ({ addVariant }) {
|
17
|
+
// This creates a variant that applies when an ancestor has data-dyna="true"
|
18
|
+
// addVariant('dyna-frame', '&:has([data-dyna-frame="true"] &)')
|
19
|
+
// Or if you prefer using a class instead of data attribute:
|
20
|
+
addVariant('dyna', ':where(.dyna, .dyna *) &')
|
21
|
+
}
|
16
22
|
];
|
17
23
|
export const theme = {
|
18
24
|
extend: {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plutonium
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.15.
|
4
|
+
version: 0.15.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -1039,6 +1039,7 @@ files:
|
|
1039
1039
|
- docs/guide/getting-started/index.md
|
1040
1040
|
- docs/guide/getting-started/installation.md
|
1041
1041
|
- docs/guide/getting-started/resources.md
|
1042
|
+
- docs/guide/tutorial.md
|
1042
1043
|
- docs/guide/what-is-plutonium.md
|
1043
1044
|
- docs/index.md
|
1044
1045
|
- docs/markdown-examples.md
|
@@ -1052,6 +1053,15 @@ files:
|
|
1052
1053
|
- docs/public/site.webmanifest
|
1053
1054
|
- docs/public/templates/base.rb
|
1054
1055
|
- docs/public/templates/plutonium.rb
|
1056
|
+
- docs/public/tutorial/plutonium-association-panel.png
|
1057
|
+
- docs/public/tutorial/plutonium-dashboard.png
|
1058
|
+
- docs/public/tutorial/plutonium-login-page.png
|
1059
|
+
- docs/public/tutorial/plutonium-nested-form.png
|
1060
|
+
- docs/public/tutorial/plutonium-posts-dashboard-customized.png
|
1061
|
+
- docs/public/tutorial/plutonium-posts-dashboard.png
|
1062
|
+
- docs/public/tutorial/plutonium-posts-detail-customized.png
|
1063
|
+
- docs/public/tutorial/plutonium-posts-detail.png
|
1064
|
+
- docs/public/tutorial/plutonium-publish-post.png
|
1055
1065
|
- esbuild.config.js
|
1056
1066
|
- exe/pug
|
1057
1067
|
- gemfiles/rails_7.gemfile
|
@@ -1083,6 +1093,8 @@ files:
|
|
1083
1093
|
- lib/generators/pu/docker/install/templates/docker-compose.yml
|
1084
1094
|
- lib/generators/pu/eject/layout/layout_generator.rb
|
1085
1095
|
- lib/generators/pu/eject/shell/shell_generator.rb
|
1096
|
+
- lib/generators/pu/extra/colorized_logger/colorized_logger_generator.rb
|
1097
|
+
- lib/generators/pu/extra/colorized_logger/templates/config/initializers/colorized_logger.rb
|
1086
1098
|
- lib/generators/pu/field/input/input_generator.rb
|
1087
1099
|
- lib/generators/pu/field/input/templates/.keep
|
1088
1100
|
- lib/generators/pu/field/input/templates/input.rb.tt
|