plutonium 0.15.7 → 0.15.9

Sign up to get free protection for your applications and to get access to all the features.
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