plutonium 0.15.7 → 0.15.9

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -81
  3. data/app/assets/plutonium.css +1 -1
  4. data/docs/.vitepress/config.ts +1 -0
  5. data/docs/guide/getting-started/index.md +4 -1
  6. data/docs/guide/tutorial.md +403 -0
  7. data/docs/index.md +19 -15
  8. data/docs/public/tutorial/plutonium-association-panel.png +0 -0
  9. data/docs/public/tutorial/plutonium-dashboard.png +0 -0
  10. data/docs/public/tutorial/plutonium-login-page.png +0 -0
  11. data/docs/public/tutorial/plutonium-nested-form.png +0 -0
  12. data/docs/public/tutorial/plutonium-posts-dashboard-customized.png +0 -0
  13. data/docs/public/tutorial/plutonium-posts-dashboard.png +0 -0
  14. data/docs/public/tutorial/plutonium-posts-detail-customized.png +0 -0
  15. data/docs/public/tutorial/plutonium-posts-detail.png +0 -0
  16. data/docs/public/tutorial/plutonium-publish-post.png +0 -0
  17. data/lib/generators/pu/core/assets/templates/tailwind.config.js +11 -1
  18. data/lib/generators/pu/extra/colorized_logger/colorized_logger_generator.rb +21 -0
  19. data/lib/generators/pu/extra/colorized_logger/templates/config/initializers/colorized_logger.rb +22 -0
  20. data/lib/generators/pu/gem/standard/standard_generator.rb +1 -1
  21. data/lib/plutonium/core/controllers/authorizable.rb +1 -1
  22. data/lib/plutonium/resource/controller.rb +12 -8
  23. data/lib/plutonium/resource/controllers/authorizable.rb +2 -2
  24. data/lib/plutonium/resource/controllers/presentable.rb +15 -4
  25. data/lib/plutonium/ui/dyna_frame/host.rb +1 -1
  26. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +73 -70
  27. data/lib/plutonium/ui/table/resource.rb +1 -1
  28. data/lib/plutonium/version.rb +1 -1
  29. data/package-lock.json +2 -2
  30. data/package.json +1 -1
  31. data/tailwind.config.js +11 -1
  32. data/tailwind.options.js +7 -1
  33. 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
- presentable_attributes -= [scoped_entity_param_key, :"#{scoped_entity_param_key}_id"] if scoped_to_entity?
18
- presentable_attributes -= [parent_input_param, :"#{parent_input_param}_id"] if current_parent.present?
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: presentable_attributes, resource_definition: current_definition)
43
+ current_definition.form_class.new(record, resource_fields: submittable_attributes, resource_definition: current_definition)
33
44
  end
34
45
  end
35
46
  end
@@ -12,7 +12,7 @@ module Plutonium
12
12
  end
13
13
 
14
14
  def view_template(&)
15
- turbo_frame_tag(@id, src: @src, loading: @loading, **@attributes, &)
15
+ turbo_frame_tag(@id, src: @src, loading: @loading, **@attributes, class: 'dyna', &)
16
16
  end
17
17
  end
18
18
  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 nested_input_param
40
+ def nested_fields_input_param
68
41
  @options[:as] || :"#{@name}_attributes"
69
42
  end
70
43
 
71
- def multiple?
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
- merge_nested_options(options)
80
- set_nested_limits(options)
56
+ merge_nested_fields_options(options)
57
+ set_nested_fields_limits(options)
81
58
  options
82
59
  end
83
60
 
84
- def merge_nested_options(options)
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 set_nested_limits(options)
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
- def build_nested_definition(name)
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
- build_definition_from_block(nested_input_definition[:block])
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 build_definition_from_block(block)
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
- render_description(context.options[:description]) if context.options[:description]
142
+ render_nested_fields_header_description(context.options[:description]) if context.options[:description]
142
143
  end
143
144
  end
144
145
 
145
- def render_description(description)
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.multiple?
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
- render_template_for_nested_fields(context, collection: {NEW_RECORD: NotPersisted.new})
161
- render_existing_nested_fields(context)
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
- render_template_for_nested_fields(context, object: NotPersisted.new)
166
- render_existing_nested_fields(context, single: true)
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, field_options)
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 = field_options[:collection] ? :nest_many : :nest_one
172
- send(nesting_method, context.name, as: context.nested_input_param, template: true, **field_options) do |nested|
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, single: false)
179
- nesting_method = single ? :nest_one : :nest_many
180
- send(nesting_method, context.name, as: context.nested_input_param) do |nested|
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 render_fieldset(nested, context)
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
- render_fieldset_content(nested, context)
191
- render_delete_button(nested, context.options)
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 render_fieldset_content(nested, context)
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
- render_hidden_fields(nested, context)
198
- render_input_fields(nested, context)
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 render_hidden_fields(nested, context)
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 render_input_fields(nested, context)
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 render_delete_button(nested, options)
218
+ def render_nested_fields_delete_button(nested, options)
216
219
  return unless !nested.object&.persisted? || options[:allow_destroy]
217
220
 
218
- render_delete_button_content
221
+ render_nested_fields_delete_button_content
219
222
  end
220
223
 
221
- def render_delete_button_content
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
- render_delete_checkbox
228
+ render_nested_fields_delete_checkbox
226
229
  end
227
230
  end
228
231
  end
229
232
 
230
- def render_delete_checkbox
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 render_nested_add_button(context)
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
- render_add_button_content(context.name)
251
+ render_nested_fields_add_button_content(context.name)
249
252
  end
250
253
  end
251
254
  end
252
255
 
253
- def render_add_button_content(name)
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
  }
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.15.7"
2
+ VERSION = "0.15.9"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.1.5",
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.5",
9
+ "version": "0.1.6",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@hotwired/stimulus": "^3.2.2",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Core assets for the Plutonium gem",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
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) => require(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.7
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-05 00:00:00.000000000 Z
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