plutonium 0.8.0 → 0.9.0

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