plutonium 0.8.0 → 0.9.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -0
  3. data/app/assets/js/controllers/application.js +1 -0
  4. data/app/assets/js/controllers/index.js +9 -0
  5. data/app/assets/js/controllers/resource_dismiss_controller.js +37 -0
  6. data/app/assets/js/controllers/resource_drop_down_controller.js +29 -0
  7. data/app/assets/js/turbo/index.js +1 -1
  8. data/app/views/application/_flash.html.erb +1 -1
  9. data/app/views/application/_flash_alerts.html.erb +51 -7
  10. data/app/views/application/_flash_toasts.html.erb +53 -23
  11. data/app/views/application/_resource_header.html.erb +563 -561
  12. data/app/views/components/form/form_builder.rb +2 -2
  13. data/app/views/components/form/form_component.html.erb +5 -6
  14. data/app/views/components/interactive_action_form/interactive_action_form_component.html.erb +1 -1
  15. data/app/views/components/nested_resource_form_fields/nested_resource_form_fields_component.html.erb +66 -0
  16. data/app/views/components/nested_resource_form_fields/nested_resource_form_fields_component.rb +23 -0
  17. data/app/views/components/nested_resource_form_fields/nested_resource_form_fields_controller.js +64 -0
  18. data/app/views/components/sidebar/sidebar_component.html.erb +61 -63
  19. data/app/views/components/table_search_input/table_search_input_component.html.erb +1 -1
  20. data/app/views/layouts/resource.html copy.erb +0 -2
  21. data/app/views/layouts/resource.html.erb +1 -3
  22. data/app/views/layouts/rodauth.html.erb +0 -1
  23. data/app/views/resource/_interactive_resource_action_form.html.erb +1 -1
  24. data/exe/pug +6 -0
  25. data/lib/generators/pu/gen/component/component_generator.rb +1 -1
  26. data/lib/generators/pu/gen/pug/pug_generator.rb +1 -1
  27. data/lib/generators/pu/lib/plutonium_generators/cli.rb +42 -0
  28. data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -5
  29. data/lib/generators/pu/lib/plutonium_generators/model_generator.rb +1 -1
  30. data/lib/generators/pu/lib/plutonium_generators.rb +8 -0
  31. data/lib/plutonium/core/actions/collection.rb +1 -1
  32. data/lib/plutonium/core/controllers/authorizable.rb +4 -4
  33. data/lib/plutonium/core/fields/inputs/base.rb +1 -1
  34. data/lib/plutonium/core/fields/inputs/nested_input.rb +57 -0
  35. data/lib/plutonium/core/fields/inputs/noop_input.rb +1 -1
  36. data/lib/plutonium/core/fields/inputs/phone_input.rb +1 -1
  37. data/lib/plutonium/core/fields/inputs/polymorphic_belongs_to_association_input.rb +1 -1
  38. data/lib/plutonium/core/fields/inputs/simple_form_association_input.rb +1 -1
  39. data/lib/plutonium/core/fields/inputs/simple_form_input.rb +1 -1
  40. data/lib/plutonium/core/fields/renderers/factory.rb +1 -0
  41. data/lib/plutonium/core/fields/renderers/map_renderer.rb +19 -0
  42. data/lib/plutonium/resource/policy.rb +6 -0
  43. data/lib/plutonium/resource/presenter.rb +35 -0
  44. data/lib/plutonium/resource/record.rb +40 -7
  45. data/lib/plutonium/version.rb +1 -1
  46. data/package-lock.json +7 -0
  47. data/package.json +5 -4
  48. data/templates/base.rb +8 -0
  49. metadata +14 -4
  50. data/app/assets/js/controllers/dropdown_controller.js +0 -12
@@ -4,12 +4,12 @@ module Plutonium::Ui
4
4
  class FormBuilder < SimpleForm::FormBuilder
5
5
  def input(attribute_name, options = {}, &block)
6
6
  label_class = options.dig(:label_html, :class)
7
- if object.errors[attribute_name].present?
7
+ if object&.errors&.[](attribute_name).present?
8
8
  # Don't show the hint
9
9
  options.delete(:hint)
10
10
  # Apply error class if there are errors
11
11
  label_class = [label_class, "text-red-700 dark:text-red-500"].compact.join(" ")
12
- elsif object.persisted? || !object.errors.empty?
12
+ elsif object&.persisted? || !object&.errors&.empty?
13
13
  # Apply success class if the object is persisted, has been validated (errors are not empty), and the field has no errors
14
14
  label_class = [label_class, "block mb-2 text-sm font-medium text-green-700 dark:text-green-500"].compact.join(" ")
15
15
  else
@@ -21,13 +21,13 @@
21
21
 
22
22
  <div>
23
23
  <% form.inputs.values.each do |input| %>
24
- <%= input.render f, form.record %>
24
+ <%= input.render self, f, form.record %>
25
25
  <% end %>
26
26
  </div>
27
27
 
28
28
  <div class="flex justify-end space-x-2">
29
29
  <%# TODO: move this into its own component %>
30
- <div class="flex">
30
+ <div class="flex" data-controller="resource-drop-down">
31
31
  <button type="submit"
32
32
  name="commit"
33
33
  value="<%= preferred_action_after_submit %>"
@@ -40,19 +40,18 @@
40
40
  </button>
41
41
  <button type="button"
42
42
  id="form-submit-options-toggle"
43
- data-dropdown-toggle="form-submit-options-dropdown"
43
+ data-resource-drop-down-target="trigger"
44
44
  class="inline-flex items-center px-4 py-2 bg-primary-600 text-white
45
45
  rounded-e-md hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500
46
46
  dark:border-gray-300
47
- border-s"
48
- >
47
+ border-s">
49
48
  <svg class="w-2.5 h-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
50
49
  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
51
50
  </svg>
52
51
  </button>
53
52
 
54
53
  <!-- Dropdown menu -->
55
- <div id="form-submit-options-dropdown"
54
+ <div data-resource-drop-down-target="menu"
56
55
  class="z-10 hidden w-56 bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600">
57
56
  <ul class="p-3 space-y-3 text-sm text-gray-700 dark:text-gray-200"
58
57
  aria-labelledby="form-submit-options-toggle">
@@ -21,7 +21,7 @@
21
21
 
22
22
  <div>
23
23
  <% interactive_action.inputs.values.each do |input| %>
24
- <%= input.render f, interaction %>
24
+ <%= input.render self, f, interaction %>
25
25
  <% end %>
26
26
  </div>
27
27
 
@@ -0,0 +1,66 @@
1
+ <div data-controller="nested-resource-form-fields"
2
+ data-nested-resource-form-fields-limit-value="<%= limit %>"
3
+ <%= render_component_attributes %>>
4
+ <h2 class="text-lg font-bold dark:text-white ">
5
+ <%= label %>
6
+ </h2>
7
+ <p class="mb-4 text-md font-normal text-gray-500 dark:text-gray-400">
8
+ <%= description %>
9
+ </p>
10
+
11
+ <template data-nested-resource-form-fields-target="template">
12
+ <% new_record = resource_class.new %>
13
+ <%= form.simple_fields_for name, new_record, child_index: 'NEW_RECORD' do |nested| %>
14
+ <fieldset class="border-t mt-4 pt-4 first:border-t-0 first:pt-0 nested-resource-form-fields" data-new-record="<%= nested.object.new_record? %>">
15
+ <% inputs.values.each do |input| %>
16
+ <%= input.render self, nested, new_record %>
17
+ <% end %>
18
+
19
+ <div class="text-right">
20
+ <% if nested.object.new_record? || allow_destroy %>
21
+ <label class="text-md font-medium text-red-900">
22
+ <%= "Delete" %>
23
+ <input type="checkbox" value=""
24
+ data-action="nested-resource-form-fields#remove"
25
+ 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">
26
+ </label>
27
+ <% end %>
28
+ </div>
29
+
30
+ <%= nested.hidden_field :_destroy %>
31
+ </fieldset>
32
+ <% end %>
33
+ </template>
34
+
35
+ <div>
36
+ <%= form.simple_fields_for name do |nested| %>
37
+ <fieldset class="border-t mt-4 pt-4 first:border-t-0 first:pt-0 nested-resource-form-fields" data-new-record="<%= nested.object.new_record? %>">
38
+ <% inputs.values.each do |input| %>
39
+ <%= input.render self, nested, new_record %>
40
+ <% end %>
41
+
42
+ <div>
43
+ <% if nested.object.new_record? || allow_destroy %>
44
+ <label class="flex items-center justify-end text-md font-medium text-red-900">
45
+ <%= "Delete" %>
46
+ <input type="checkbox" value=""
47
+ data-action="nested-resource-form-fields#remove"
48
+ 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">
49
+ </label>
50
+ <% end %>
51
+ </div>
52
+
53
+ <%= nested.hidden_field :_destroy %>
54
+ </fieldset>
55
+ <% end %>
56
+
57
+ <div data-nested-resource-form-fields-target="target" hidden></div>
58
+ </div>
59
+
60
+ <%=
61
+ render_component :button, label: "Add #{name.to_s.singularize.humanize}", classname: "mt-4", data: {
62
+ action: "nested-resource-form-fields#add",
63
+ "nested-resource-form-fields-target": "addButton"
64
+ }
65
+ %>
66
+ </div>
@@ -0,0 +1,23 @@
1
+ module Plutonium::Ui
2
+ class NestedResourceFormFieldsComponent < Plutonium::Ui::Base
3
+ option :name
4
+ option :resource_class
5
+ option :form
6
+ option :inputs
7
+ option :label, optional: true
8
+ option :description, optional: true
9
+ option :allow_destroy, optional: true
10
+ option :update_only, optional: true
11
+ option :limit, optional: true
12
+
13
+ def base_classname
14
+ "mt-6 mb-4"
15
+ end
16
+
17
+ def label
18
+ super || name.to_s.humanize
19
+ end
20
+ end
21
+ end
22
+
23
+ Plutonium::ComponentRegistry.register :nested_resource_form_fields, to: Plutonium::Ui::NestedResourceFormFieldsComponent
@@ -0,0 +1,64 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="nested-resource-form-fields"
4
+ // Copied from https://github.com/stimulus-components/stimulus-rails-nested-form/blob/master/src/index.ts
5
+ export default class extends Controller {
6
+ static targets = ["target", "template", "addButton"]
7
+
8
+ static values = {
9
+ wrapperSelector: {
10
+ type: String,
11
+ default: ".nested-resource-form-fields",
12
+ },
13
+ limit: Number,
14
+ }
15
+
16
+ connect() {
17
+ this.updateState()
18
+ }
19
+
20
+ add(e) {
21
+ e.preventDefault()
22
+
23
+ const content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime().toString())
24
+ this.targetTarget.insertAdjacentHTML("beforebegin", content)
25
+
26
+ const event = new CustomEvent("nested-resource-form-fields:add", { bubbles: true })
27
+ this.element.dispatchEvent(event)
28
+
29
+ this.updateState()
30
+ }
31
+
32
+ remove(e) {
33
+ e.preventDefault()
34
+
35
+ const wrapper = e.target.closest(this.wrapperSelectorValue)
36
+
37
+ if (wrapper.dataset.newRecord === "true") {
38
+ wrapper.remove()
39
+ } else {
40
+ wrapper.style.display = "none"
41
+
42
+ const input = wrapper.querySelector("input[name*='_destroy']")
43
+ input.value = "1"
44
+ }
45
+
46
+ const event = new CustomEvent("nested-resource-form-fields:remove", { bubbles: true })
47
+ this.element.dispatchEvent(event)
48
+
49
+ this.updateState()
50
+ }
51
+
52
+ updateState() {
53
+ if (!this.hasAddButtonTarget || this.limitValue == 0) return
54
+
55
+ if (this.childCount >= this.limitValue)
56
+ this.addButtonTarget.style.display = "none"
57
+ else
58
+ this.addButtonTarget.style.display = "initial"
59
+ }
60
+
61
+ get childCount() {
62
+ return this.element.querySelectorAll(this.wrapperSelectorValue).length
63
+ }
64
+ }
@@ -86,69 +86,67 @@
86
86
  updateColorMode()
87
87
  </script>
88
88
 
89
-
90
- <button
91
- type="button"
92
- data-dropdown-toggle="color-mode-dropdown"
93
- class="inline-flex justify-center p-2 text-gray-500 rounded cursor-pointer dark:hover:text-white dark:text-gray-200 hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600"
94
- >
95
- <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
96
- <path d="M5 13.17a3.001 3.001 0 0 0 0 5.66V20a1 1 0 1 0 2 0v-1.17a3.001 3.001 0 0 0 0-5.66V4a1 1 0 0 0-2 0v9.17ZM11 20v-9.17a3.001 3.001 0 0 1 0-5.66V4a1 1 0 1 1 2 0v1.17a3.001 3.001 0 0 1 0 5.66V20a1 1 0 1 1-2 0Zm6-1.17V20a1 1 0 1 0 2 0v-1.17a3.001 3.001 0 0 0 0-5.66V4a1 1 0 1 0-2 0v9.17a3.001 3.001 0 0 0 0 5.66Z"/>
97
- </svg>
98
- </button>
99
-
100
-
101
- <div
102
- class="hidden z-50 my-4 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700"
103
- id="color-mode-dropdown"
104
- >
105
- <ul class="py-1" role="none">
106
- <li>
107
- <button
108
- type="button"
109
- class="w-full block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600"
110
- role="menuitem"
111
- onclick="setLightColorMode()"
112
- >
113
- <div class="flex justify-start">
114
- <svg class="w-6 h-6 me-2 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
115
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5V3m0 18v-2M7.05 7.05 5.636 5.636m12.728 12.728L16.95 16.95M5 12H3m18 0h-2M7.05 16.95l-1.414 1.414M18.364 5.636 16.95 7.05M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"/>
116
- </svg>
117
- Light
118
- </div>
119
- </a>
120
- </li>
121
- <li>
122
- <button
123
- type="button"
124
- class="w-full block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600"
125
- role="menuitem"
126
- onclick="setDarkColorMode()"
127
- >
128
- <div class="flex justify-start">
129
- <svg class="w-6 h-6 me-2 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
130
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 21a9 9 0 0 1-.5-17.986V3c-.354.966-.5 1.911-.5 3a9 9 0 0 0 9 9c.239 0 .254.018.488 0A9.004 9.004 0 0 1 12 21Z"/>
131
- </svg>
132
- Dark
133
- </div>
134
- </a>
135
- </li>
136
- <li>
137
- <button
138
- type="button"
139
- class="w-full block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600"
140
- role="menuitem"
141
- onclick="setSystemColorMode()"
142
- >
143
- <div class="flex justify-start">
144
- <svg class="w-6 h-6 me-2 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
145
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 16H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v1M9 12H4m8 8V9h8v11h-8Zm0 0H9m8-4a1 1 0 1 0-2 0 1 1 0 0 0 2 0Z"/>
146
- </svg>
147
- System
148
- </div>
149
- </a>
150
- </li>
151
- </ul>
89
+ <!-- Color Modes -->
90
+ <div data-controller="resource-drop-down">
91
+ <button
92
+ type="button"
93
+ data-resource-drop-down-target="trigger"
94
+ class="inline-flex justify-center p-2 text-gray-500 rounded cursor-pointer dark:hover:text-white dark:text-gray-200 hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600">
95
+ <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
96
+ <path d="M5 13.17a3.001 3.001 0 0 0 0 5.66V20a1 1 0 1 0 2 0v-1.17a3.001 3.001 0 0 0 0-5.66V4a1 1 0 0 0-2 0v9.17ZM11 20v-9.17a3.001 3.001 0 0 1 0-5.66V4a1 1 0 1 1 2 0v1.17a3.001 3.001 0 0 1 0 5.66V20a1 1 0 1 1-2 0Zm6-1.17V20a1 1 0 1 0 2 0v-1.17a3.001 3.001 0 0 0 0-5.66V4a1 1 0 1 0-2 0v9.17a3.001 3.001 0 0 0 0 5.66Z"/>
97
+ </svg>
98
+ </button>
99
+ <div
100
+ class="hidden z-50 my-4 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700"
101
+ data-resource-drop-down-target="menu">
102
+ <ul class="py-1" role="none">
103
+ <li>
104
+ <button
105
+ type="button"
106
+ class="w-full block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600"
107
+ role="menuitem"
108
+ onclick="setLightColorMode()"
109
+ >
110
+ <div class="flex justify-start">
111
+ <svg class="w-6 h-6 me-2 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
112
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5V3m0 18v-2M7.05 7.05 5.636 5.636m12.728 12.728L16.95 16.95M5 12H3m18 0h-2M7.05 16.95l-1.414 1.414M18.364 5.636 16.95 7.05M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"/>
113
+ </svg>
114
+ Light
115
+ </div>
116
+ </a>
117
+ </li>
118
+ <li>
119
+ <button
120
+ type="button"
121
+ class="w-full block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600"
122
+ role="menuitem"
123
+ onclick="setDarkColorMode()"
124
+ >
125
+ <div class="flex justify-start">
126
+ <svg class="w-6 h-6 me-2 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
127
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 21a9 9 0 0 1-.5-17.986V3c-.354.966-.5 1.911-.5 3a9 9 0 0 0 9 9c.239 0 .254.018.488 0A9.004 9.004 0 0 1 12 21Z"/>
128
+ </svg>
129
+ Dark
130
+ </div>
131
+ </a>
132
+ </li>
133
+ <li>
134
+ <button
135
+ type="button"
136
+ class="w-full block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600"
137
+ role="menuitem"
138
+ onclick="setSystemColorMode()"
139
+ >
140
+ <div class="flex justify-start">
141
+ <svg class="w-6 h-6 me-2 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
142
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 16H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v1M9 12H4m8 8V9h8v11h-8Zm0 0H9m8-4a1 1 0 1 0-2 0 1 1 0 0 0 2 0Z"/>
143
+ </svg>
144
+ System
145
+ </div>
146
+ </a>
147
+ </li>
148
+ </ul>
149
+ </div>
152
150
  </div>
153
151
  </div>
154
152
  </aside>
@@ -7,7 +7,7 @@
7
7
  <path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
8
8
  </svg>
9
9
  </div>
10
- <%= search_object.search_filter.input_definitions[:search].render f, nil,
10
+ <%= search_object.search_filter.input_definitions[:search].render self, f, nil,
11
11
  wrapper: false, label: false,
12
12
  as: :string, # force string for search
13
13
  placeholder: "search...",
@@ -15,8 +15,6 @@
15
15
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.css" integrity="sha512-GvqWM4KWH8mbgWIyvwdH8HgjUbyZTXrCq0sjGij9fDNiXz3vJoy3jCcAaWNekH2rJe4hXVWCJKN+bEW8V7AAEQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
16
16
  <script src="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.js" integrity="sha512-0E8oaoA2v32h26IycsmRDShtQ8kMgD91zWVBxdIvUCjU3xBw81PV61QBsBqNQpWkp/zYJZip8Ag3ifmzz1wCKQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
17
17
 
18
- <script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"></script>
19
-
20
18
  <%== Plutonium::Config.stylesheet_tag.call self %>
21
19
  <%== Plutonium::Config.script_tag.call self %>
22
20
  <%= yield(:head) %>
@@ -21,8 +21,6 @@
21
21
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.css" integrity="sha512-GvqWM4KWH8mbgWIyvwdH8HgjUbyZTXrCq0sjGij9fDNiXz3vJoy3jCcAaWNekH2rJe4hXVWCJKN+bEW8V7AAEQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
22
22
  <script src="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.js" integrity="sha512-0E8oaoA2v32h26IycsmRDShtQ8kMgD91zWVBxdIvUCjU3xBw81PV61QBsBqNQpWkp/zYJZip8Ag3ifmzz1wCKQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
23
23
 
24
- <script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"></script>
25
-
26
24
  <%= yield(:assets) %>
27
25
 
28
26
  <%== Plutonium::Config.stylesheet_tag.call self %>
@@ -34,6 +32,7 @@
34
32
  <%= render("resource_header") %>
35
33
  <%= render("resource_sidebar") %>
36
34
  <main class="p-4 lg:ml-64 h-auto pt-20">
35
+ <%= render "flash" %>
37
36
  <%= yield %>
38
37
  </main>
39
38
  </body>
@@ -45,6 +44,5 @@
45
44
  <%= modal_frame_tag do %>
46
45
  <%= yield(:modal) %>
47
46
  <% end %>
48
- <%= render "flash" %>
49
47
 
50
48
  -->
@@ -12,7 +12,6 @@
12
12
  <link rel="preconnect" href="https://fonts.googleapis.com">
13
13
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
14
  <link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap" rel="stylesheet">
15
- <script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"></script>
16
15
  <%== Plutonium::Config.stylesheet_tag.call self %>
17
16
  <%== Plutonium::Config.script_tag.call self %>
18
17
  </head>
@@ -25,7 +25,7 @@
25
25
  </div>
26
26
  <div class="form-inputs">
27
27
  <% interactive_action.inputs.values.each do |input| %>
28
- <%= input.render f, @interaction %>
28
+ <%= input.render self, f, @interaction %>
29
29
  <% end %>
30
30
  </div>
31
31
  </div>
data/exe/pug ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path("../lib/generators/pu/lib/plutonium_generators.rb", __dir__)
4
+
5
+ ENV["PU_CLI"] = "1"
6
+ PlutoniumGenerators::CLI.start(ARGV)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- return if PlutoniumGenerators.rails?
3
+ return unless PlutoniumGenerators.cli?
4
4
 
5
5
  module Pu
6
6
  module Gen
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- return if PlutoniumGenerators.rails?
3
+ return unless PlutoniumGenerators.cli?
4
4
 
5
5
  module Pu
6
6
  module Gen
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require File.expand_path("../plutonium_generators", __dir__)
5
+
6
+ module PlutoniumGenerators
7
+ class CLI < Thor
8
+ map "i" => :install
9
+ map "g" => :generate
10
+ map "ls" => :list
11
+ map %w[--version -v] => :__print_version
12
+
13
+ # desc "install", "Install Plutonium"
14
+ # def install
15
+ # Rails::Generators.invoke("pu:install")
16
+ # end
17
+
18
+ desc "generate, g GENERATOR [options]", "Run plutonium generator"
19
+ def generate(generator, *options)
20
+ Rails::Generators.invoke("pu:#{generator}", options)
21
+ end
22
+
23
+ desc "list, ls", "View list of available generators"
24
+ def list
25
+ generators = Rails::Generators.sorted_groups.to_h["pu"]
26
+ puts
27
+ generators.each { |gen| puts gen.sub(/^pu:/, "") }
28
+ puts
29
+ end
30
+
31
+ desc "--version, -v", "Print gem version"
32
+ def __print_version
33
+ puts "Plutonium generators #{PlutoniumGenerators::VERSION}"
34
+ end
35
+
36
+ class << self
37
+ def exit_on_failure?
38
+ true
39
+ end
40
+ end
41
+ end
42
+ end
@@ -64,12 +64,8 @@ module PlutoniumGenerators
64
64
  @prompt ||= TTY::Prompt.new
65
65
  end
66
66
 
67
- def rails?
68
- PlutoniumGenerators.rails?
69
- end
70
-
71
67
  def appname
72
- rails? ? Rails.application.class.module_parent.name : "PlutoniumGenerators"
68
+ defined?(Rails.application) ? Rails.application.class.module_parent.name : "PlutoniumGenerators"
73
69
  end
74
70
 
75
71
  def app_name
@@ -7,7 +7,7 @@ module PlutoniumGenerators
7
7
  class ModelGenerator < ActiveRecord::Generators::ModelGenerator
8
8
  include PlutoniumGenerators::Generator
9
9
 
10
- remove_hook_for :test_framework
10
+ # remove_hook_for :test_framework
11
11
  remove_task :create_migration_file
12
12
  remove_task :create_model_file
13
13
  remove_task :create_module_file
@@ -1,7 +1,15 @@
1
1
  require "zeitwerk"
2
2
 
3
3
  loader = Zeitwerk::Loader.for_gem # (warn_on_extra_files: false)
4
+ loader.inflector.inflect(
5
+ "cli" => "CLI"
6
+ )
4
7
  loader.setup
5
8
 
6
9
  module PlutoniumGenerators
10
+ class << self
11
+ def cli?
12
+ ENV["PU_CLI"] == "1"
13
+ end
14
+ end
7
15
  end
@@ -9,7 +9,7 @@ module Plutonium
9
9
  end
10
10
 
11
11
  def permitted_for(policy)
12
- Collection.new(@collection.select { |name, action| policy.send :"#{action.name}?" })
12
+ Collection.new(@collection.select { |name, action| policy.send_with_report :"#{action.name}?" })
13
13
  end
14
14
 
15
15
  def collection_actions
@@ -42,7 +42,7 @@ module Plutonium
42
42
  end
43
43
 
44
44
  def permitted_attributes
45
- @permitted_attributes ||= current_policy.send :"permitted_attributes_for_#{action_name}"
45
+ @permitted_attributes ||= current_policy.send_with_report :"permitted_attributes_for_#{action_name}"
46
46
  end
47
47
 
48
48
  def current_policy
@@ -52,9 +52,9 @@ module Plutonium
52
52
  end
53
53
  end
54
54
 
55
- def parent_policy
56
- @parent_policy ||= policy(current_parent) if current_parent.present?
57
- end
55
+ # def parent_policy
56
+ # @parent_policy ||= policy(current_parent) if current_parent.present?
57
+ # end
58
58
  end
59
59
  end
60
60
  end
@@ -10,7 +10,7 @@ module Plutonium
10
10
  @user_options = user_options
11
11
  end
12
12
 
13
- def render(f, record, **)
13
+ def render(view_context, f, record, **)
14
14
  raise NotImplementedError, "#{self.class}#render"
15
15
  end
16
16
 
@@ -0,0 +1,57 @@
1
+ module Plutonium
2
+ module Core
3
+ module Fields
4
+ module Inputs
5
+ class NestedInput < Base
6
+ include Plutonium::Core::Definers::InputDefiner
7
+
8
+ attr_reader :inputs, :resource_class
9
+
10
+ def initialize(name, inputs:, resource_class:, allow_destroy:, update_only:, limit:, **user_options)
11
+ @inputs = inputs
12
+ @resource_class = resource_class
13
+ @allow_destroy = allow_destroy
14
+ @update_only = update_only
15
+ @limit = limit
16
+
17
+ super(name, **user_options)
18
+ end
19
+
20
+ def render(view_context, f, record, **opts)
21
+ opts = options.deep_merge opts
22
+ view_context.render_component :nested_resource_form_fields, form: f, **opts
23
+ end
24
+
25
+ def collect(params)
26
+ attributes = {}
27
+ params[param].each do |index, nested_params|
28
+ collected = defined_inputs.collect_all(nested_params)
29
+ collected[:id] = nested_params[:id] if nested_params.key?(:id) && !@update_only
30
+ collected[:_destroy] = nested_params[:_destroy] if @allow_destroy
31
+ attributes[index] = collected
32
+ end
33
+
34
+ {param => attributes}
35
+ end
36
+
37
+ private
38
+
39
+ def param = :"#{name}_attributes"
40
+
41
+ def input_options = {
42
+ name:,
43
+ resource_class:,
44
+ allow_destroy: @allow_destroy,
45
+ update_only: @update_only,
46
+ limit: @limit,
47
+ inputs: defined_inputs
48
+ }
49
+
50
+ def defined_inputs
51
+ @defined_inputs ||= defined_inputs_for(*inputs)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -6,7 +6,7 @@ module Plutonium
6
6
  def initialize(*)
7
7
  end
8
8
 
9
- def render(f, record, **)
9
+ def render(view_context, f, record, **)
10
10
  end
11
11
 
12
12
  def collect(params)