katalyst-koi 4.18.0 → 5.0.0.alpha.1

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 (222) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/koi/icons/add.svg +3 -0
  3. data/app/assets/images/koi/icons/close.svg +1 -0
  4. data/app/assets/images/koi/koi.png +0 -0
  5. data/app/assets/javascripts/koi/controllers/file_field_controller.js +2 -2
  6. data/app/assets/javascripts/koi/controllers/index.js +0 -3
  7. data/app/assets/javascripts/koi/controllers/koi/modal_controller.js +40 -0
  8. data/app/assets/javascripts/koi/controllers/navigation_controller.js +14 -21
  9. data/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js +4 -1
  10. data/app/assets/stylesheets/koi/blocks/actions.css +8 -0
  11. data/app/assets/stylesheets/koi/blocks/application-header.css +15 -0
  12. data/app/assets/stylesheets/koi/blocks/application-navigation.css +54 -0
  13. data/app/assets/stylesheets/koi/blocks/button.css +90 -0
  14. data/app/assets/stylesheets/koi/blocks/flash.css +19 -0
  15. data/app/assets/stylesheets/koi/blocks/icon.css +15 -0
  16. data/app/assets/stylesheets/koi/blocks/index.css +13 -0
  17. data/app/assets/stylesheets/koi/blocks/modal.css +26 -0
  18. data/app/assets/stylesheets/koi/blocks/navigation.css +23 -0
  19. data/app/assets/stylesheets/koi/blocks/page-header.css +31 -0
  20. data/app/assets/stylesheets/koi/blocks/pagy.css +82 -0
  21. data/app/assets/stylesheets/koi/blocks/prose.css +37 -0
  22. data/app/assets/stylesheets/koi/blocks/tables/index.css +4 -0
  23. data/app/assets/stylesheets/koi/{components/_query.scss → blocks/tables/query.css} +13 -13
  24. data/app/assets/stylesheets/koi/{base/_tables.scss → blocks/tables/table.css} +11 -59
  25. data/app/assets/stylesheets/koi/compositions/cover.css +17 -0
  26. data/app/assets/stylesheets/koi/{base/_flow.scss → compositions/flow.css} +1 -1
  27. data/app/assets/stylesheets/koi/compositions/index.css +4 -0
  28. data/app/assets/stylesheets/koi/compositions/wrapper.css +11 -0
  29. data/app/assets/stylesheets/koi/forms/caption.css +22 -0
  30. data/app/assets/stylesheets/koi/forms/checkboxes.css +153 -0
  31. data/app/assets/stylesheets/koi/forms/date-input.css +12 -0
  32. data/app/assets/stylesheets/koi/{components/_document-field.scss → forms/document-field.css} +20 -15
  33. data/app/assets/stylesheets/koi/forms/errors.css +38 -0
  34. data/app/assets/stylesheets/koi/forms/fieldset.css +73 -0
  35. data/app/assets/stylesheets/koi/forms/file-upload.css +20 -0
  36. data/app/assets/stylesheets/koi/forms/form-group.css +19 -0
  37. data/app/assets/stylesheets/koi/forms/hint.css +11 -0
  38. data/app/assets/stylesheets/koi/forms/image-field.css +96 -0
  39. data/app/assets/stylesheets/koi/forms/index.css +44 -0
  40. data/app/assets/stylesheets/koi/forms/input.css +194 -0
  41. data/app/assets/stylesheets/koi/forms/label.css +43 -0
  42. data/app/assets/stylesheets/koi/forms/password.css +18 -0
  43. data/app/assets/stylesheets/koi/forms/radios.css +162 -0
  44. data/app/assets/stylesheets/koi/forms/select.css +18 -0
  45. data/app/assets/stylesheets/koi/forms/textarea.css +3 -0
  46. data/app/assets/stylesheets/koi/forms/trix.css +33 -0
  47. data/app/assets/stylesheets/koi/global/fonts.css +22 -0
  48. data/app/assets/stylesheets/koi/global/global-styles.css +297 -0
  49. data/app/assets/stylesheets/koi/global/reset.css +98 -0
  50. data/app/assets/stylesheets/koi/global/variables.css +97 -0
  51. data/app/assets/stylesheets/koi/icons.css +14 -0
  52. data/app/assets/stylesheets/koi/{admin.scss → index.css} +16 -7
  53. data/app/assets/stylesheets/koi/login.css +26 -0
  54. data/app/assets/stylesheets/koi/themes/_index.scss +0 -1
  55. data/app/assets/stylesheets/koi/utilities/index.css +1 -0
  56. data/app/assets/stylesheets/koi/utilities/visually-hidden.css +18 -0
  57. data/app/components/concerns/koi/tables/cells.rb +3 -3
  58. data/app/components/koi/header/edit_component.rb +1 -1
  59. data/app/components/koi/header/index_component.rb +1 -1
  60. data/app/components/koi/header/new_component.rb +1 -1
  61. data/app/components/koi/header/show_component.rb +1 -1
  62. data/app/components/koi/header_component.html.erb +12 -11
  63. data/app/components/koi/header_component.rb +4 -2
  64. data/app/components/koi/table_component.rb +8 -0
  65. data/app/controllers/admin/admin_users_controller.rb +24 -18
  66. data/app/controllers/admin/application_controller.rb +1 -3
  67. data/app/controllers/admin/credentials_controller.rb +18 -14
  68. data/app/controllers/admin/otps_controller.rb +15 -13
  69. data/app/controllers/admin/sessions_controller.rb +12 -1
  70. data/app/controllers/admin/url_rewrites_controller.rb +19 -17
  71. data/app/controllers/admin/well_knowns_controller.rb +20 -18
  72. data/app/controllers/concerns/koi/controller.rb +37 -0
  73. data/app/helpers/koi/form_helper.rb +18 -0
  74. data/app/helpers/koi/header_helper.rb +122 -0
  75. data/app/helpers/koi/index_actions_helper.rb +3 -2
  76. data/app/helpers/koi/modal_helper.rb +71 -0
  77. data/app/models/admin/user.rb +7 -1
  78. data/app/models/url_rewrite.rb +1 -9
  79. data/app/views/admin/admin_users/_form.html+self.erb +8 -0
  80. data/app/views/admin/admin_users/_form.html.erb +8 -0
  81. data/app/views/admin/admin_users/archived.html.erb +7 -4
  82. data/app/views/admin/admin_users/edit.html+self.erb +12 -0
  83. data/app/views/admin/admin_users/edit.html.erb +13 -8
  84. data/app/views/admin/admin_users/index.html.erb +10 -5
  85. data/app/views/admin/admin_users/new.html.erb +8 -8
  86. data/app/views/admin/admin_users/show.html+self.erb +26 -14
  87. data/app/views/admin/admin_users/show.html.erb +22 -20
  88. data/app/views/admin/credentials/_credentials.html+self.erb +8 -6
  89. data/app/views/admin/credentials/_credentials.html.erb +3 -1
  90. data/app/views/admin/credentials/create.turbo_stream.erb +4 -3
  91. data/app/views/admin/credentials/destroy.turbo_stream.erb +4 -2
  92. data/app/views/admin/credentials/new.html.erb +42 -36
  93. data/app/views/admin/dashboards/show.html.erb +13 -1
  94. data/app/views/admin/otps/_form.html.erb +7 -7
  95. data/app/views/admin/otps/create.turbo_stream.erb +3 -3
  96. data/app/views/admin/otps/new.html.erb +5 -3
  97. data/app/views/admin/sessions/new.html.erb +2 -3
  98. data/app/views/admin/sessions/otp.html.erb +1 -3
  99. data/app/views/admin/sessions/password.html.erb +1 -3
  100. data/app/views/admin/tokens/show.html.erb +4 -6
  101. data/app/views/admin/url_rewrites/_form.html.erb +9 -0
  102. data/app/views/admin/url_rewrites/edit.html.erb +13 -9
  103. data/app/views/admin/url_rewrites/index.html.erb +10 -7
  104. data/app/views/admin/url_rewrites/new.html.erb +8 -8
  105. data/app/views/admin/url_rewrites/show.html.erb +17 -12
  106. data/app/views/admin/well_knowns/_form.html.erb +9 -0
  107. data/app/views/admin/well_knowns/edit.html.erb +13 -9
  108. data/app/views/admin/well_knowns/index.html.erb +8 -5
  109. data/app/views/admin/well_knowns/new.html.erb +8 -8
  110. data/app/views/admin/well_knowns/show.html.erb +14 -13
  111. data/app/views/katalyst/content/asides/_aside.html+form.erb +6 -4
  112. data/app/views/katalyst/content/columns/_column.html+form.erb +5 -3
  113. data/app/views/katalyst/content/contents/_content.html+form.erb +8 -6
  114. data/app/views/katalyst/content/figures/_figure.html+form.erb +8 -5
  115. data/app/views/katalyst/content/groups/_group.html+form.erb +5 -3
  116. data/app/views/katalyst/content/items/_item.html+form.erb +5 -3
  117. data/app/views/katalyst/content/sections/_section.html+form.erb +5 -3
  118. data/app/views/katalyst/content/tables/_table.html+form.erb +16 -11
  119. data/app/views/katalyst/navigation/items/_button.html.erb +6 -12
  120. data/app/views/katalyst/navigation/items/_heading.html.erb +3 -10
  121. data/app/views/katalyst/navigation/items/_link.html.erb +6 -11
  122. data/app/views/katalyst/navigation/menus/edit.html.erb +10 -6
  123. data/app/views/katalyst/navigation/menus/index.html.erb +4 -2
  124. data/app/views/katalyst/navigation/menus/new.html.erb +5 -3
  125. data/app/views/katalyst/navigation/menus/show.html.erb +8 -7
  126. data/app/views/layouts/koi/_application_header.html.erb +20 -0
  127. data/app/views/layouts/koi/_application_navigation.html.erb +34 -0
  128. data/app/views/layouts/koi/_flash.html.erb +6 -3
  129. data/app/views/layouts/koi/_navigation_header.html.erb +0 -2
  130. data/app/views/layouts/koi/application.html.erb +22 -27
  131. data/app/views/layouts/koi/frame.html.erb +1 -3
  132. data/app/views/layouts/koi/login.html.erb +12 -5
  133. data/config/locales/koi.en.yml +9 -1
  134. data/config/routes.rb +1 -1
  135. data/lib/generators/koi/admin/admin_generator.rb +3 -12
  136. data/lib/generators/koi/admin_controller/admin_controller_generator.rb +6 -16
  137. data/lib/generators/koi/admin_controller/templates/controller.rb.tt +82 -18
  138. data/lib/generators/koi/admin_controller/templates/controller_spec.rb.tt +113 -47
  139. data/lib/generators/koi/admin_route/admin_route_generator.rb +60 -6
  140. data/lib/generators/koi/admin_views/USAGE +18 -7
  141. data/lib/generators/koi/admin_views/admin_views_generator.rb +19 -11
  142. data/lib/generators/koi/admin_views/templates/_form.html.erb.tt +8 -0
  143. data/lib/generators/koi/admin_views/templates/archived.html.erb.tt +33 -0
  144. data/lib/generators/koi/admin_views/templates/edit.html.erb.tt +17 -9
  145. data/lib/generators/koi/admin_views/templates/index.html.erb.tt +31 -3
  146. data/lib/generators/koi/admin_views/templates/new.html.erb.tt +8 -8
  147. data/lib/generators/koi/admin_views/templates/show.html.erb.tt +15 -18
  148. data/lib/generators/koi/helpers/attribute_helpers.rb +147 -0
  149. data/lib/generators/koi/helpers/attribute_types.rb +218 -0
  150. data/lib/generators/koi/helpers/resource_helpers.rb +121 -0
  151. data/lib/generators/koi/{active_record/active_record_generator.rb → model/model_generator.rb} +1 -1
  152. data/lib/koi/config.rb +3 -1
  153. data/lib/koi/engine.rb +0 -9
  154. data/lib/koi/form/builder.rb +4 -4
  155. data/lib/koi/form/content.rb +55 -0
  156. data/lib/koi/form/elements/document.rb +1 -1
  157. data/lib/koi/form/elements/image.rb +1 -1
  158. data/lib/koi/form_builder.rb +1 -0
  159. data/lib/koi/menu.rb +14 -1
  160. data/spec/factories/admins.rb +1 -1
  161. metadata +92 -101
  162. data/app/assets/builds/koi/admin.css +0 -1
  163. data/app/assets/stylesheets/koi/base/_button.scss +0 -122
  164. data/app/assets/stylesheets/koi/base/_icon.scss +0 -29
  165. data/app/assets/stylesheets/koi/base/_index.scss +0 -21
  166. data/app/assets/stylesheets/koi/base/_input.scss +0 -19
  167. data/app/assets/stylesheets/koi/base/_link.scss +0 -26
  168. data/app/assets/stylesheets/koi/base/_list.scss +0 -11
  169. data/app/assets/stylesheets/koi/base/_typography.scss +0 -160
  170. data/app/assets/stylesheets/koi/components/_actions-group.scss +0 -7
  171. data/app/assets/stylesheets/koi/components/_image-field.scss +0 -95
  172. data/app/assets/stylesheets/koi/components/_index.scss +0 -9
  173. data/app/assets/stylesheets/koi/components/_pagy.scss +0 -29
  174. data/app/assets/stylesheets/koi/components/_summary-list.scss +0 -40
  175. data/app/assets/stylesheets/koi/layouts/_banner.scss +0 -7
  176. data/app/assets/stylesheets/koi/layouts/_content.scss +0 -40
  177. data/app/assets/stylesheets/koi/layouts/_flash.scss +0 -41
  178. data/app/assets/stylesheets/koi/layouts/_header.scss +0 -61
  179. data/app/assets/stylesheets/koi/layouts/_index.scss +0 -48
  180. data/app/assets/stylesheets/koi/layouts/_main.scss +0 -23
  181. data/app/assets/stylesheets/koi/layouts/_navigation.scss +0 -180
  182. data/app/assets/stylesheets/koi/layouts/_stack.scss +0 -13
  183. data/app/assets/stylesheets/koi/pages/_index.scss +0 -1
  184. data/app/assets/stylesheets/koi/pages/_login.scss +0 -46
  185. data/app/assets/stylesheets/koi/themes/_govuk.scss +0 -56
  186. data/app/assets/stylesheets/koi/themes/_kpop.scss +0 -5
  187. data/app/assets/stylesheets/koi/utils/_breakpoints.scss +0 -13
  188. data/app/assets/stylesheets/koi/utils/_hide.scss +0 -11
  189. data/app/assets/stylesheets/koi/utils/_index.scss +0 -2
  190. data/app/assets/stylesheets/koi/utils/_typography.scss +0 -42
  191. data/app/components/koi/content/editor/item_form_component.html.erb +0 -11
  192. data/app/components/koi/content/editor/item_form_component.rb +0 -94
  193. data/app/components/koi/summary_list/attachment_component.rb +0 -47
  194. data/app/components/koi/summary_list/base.rb +0 -59
  195. data/app/components/koi/summary_list/boolean_component.rb +0 -15
  196. data/app/components/koi/summary_list/date_component.rb +0 -17
  197. data/app/components/koi/summary_list/datetime_component.rb +0 -17
  198. data/app/components/koi/summary_list/item_component.rb +0 -26
  199. data/app/components/koi/summary_list/number_component.rb +0 -21
  200. data/app/components/koi/summary_list/rich_text_component.rb +0 -8
  201. data/app/components/koi/summary_list/text_component.rb +0 -8
  202. data/app/components/koi/summary_list_component.html.erb +0 -5
  203. data/app/components/koi/summary_list_component.rb +0 -75
  204. data/app/controllers/concerns/koi/controller/is_admin_controller.rb +0 -66
  205. data/app/helpers/koi/application_helper.rb +0 -7
  206. data/app/helpers/koi/date_helper.rb +0 -26
  207. data/app/helpers/koi/definition_list_helper.rb +0 -10
  208. data/app/views/admin/admin_users/_fields.html+self.erb +0 -3
  209. data/app/views/admin/admin_users/_fields.html.erb +0 -3
  210. data/app/views/admin/url_rewrites/_fields.html.erb +0 -4
  211. data/app/views/admin/well_knowns/_fields.html.erb +0 -6
  212. data/app/views/layouts/koi/_environment.html.erb +0 -4
  213. data/app/views/layouts/koi/_header.html.erb +0 -11
  214. data/app/views/layouts/koi/_navigation.html.erb +0 -23
  215. data/app/views/layouts/koi/_navigation_collapse.html.erb +0 -3
  216. data/lib/generators/koi/admin_views/templates/_fields.html.erb.tt +0 -3
  217. data/lib/generators/koi/helpers/admin_generator_attributes.rb +0 -66
  218. data/lib/koi/extensions/dartsass.rb +0 -23
  219. /data/app/assets/stylesheets/koi/{components/_clipboard.scss → blocks/clipboard.css} +0 -0
  220. /data/app/assets/stylesheets/koi/{components/_index-actions.scss → blocks/index-actions.css} +0 -0
  221. /data/app/assets/stylesheets/koi/{components/_toolbar.scss → blocks/toolbar.css} +0 -0
  222. /data/app/assets/stylesheets/koi/{base/_repel.scss → compositions/repel.css} +0 -0
@@ -3,12 +3,20 @@
3
3
  require "rails/generators/named_base"
4
4
  require "rails/generators/resource_helpers"
5
5
 
6
+ require_relative "../helpers/attribute_helpers"
7
+ require_relative "../helpers/resource_helpers"
8
+
6
9
  module Koi
7
10
  class AdminRouteGenerator < Rails::Generators::NamedBase
8
- include Rails::Generators::ResourceHelpers
11
+ include Helpers::AttributeHelpers
12
+ include Helpers::ResourceHelpers
9
13
 
10
14
  source_root File.expand_path("templates", __dir__)
11
15
 
16
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
17
+
18
+ class_option :force, type: :boolean, default: true
19
+
12
20
  def create_routes
13
21
  return if Pathname.new(destination_root).join("config/routes/admin.rb").exist?
14
22
 
@@ -16,7 +24,17 @@ module Koi
16
24
  end
17
25
 
18
26
  def add_route
19
- route "resources :#{file_name.pluralize}", namespace: regular_class_path
27
+ route "resources :#{file_name.pluralize}"
28
+
29
+ if orderable?
30
+ resource_route "patch :order, on: :collection"
31
+ end
32
+
33
+ if archivable?
34
+ resource_route "put :restore, on: :collection"
35
+ resource_route "put :archive, on: :collection"
36
+ resource_route "get :archived, on: :collection"
37
+ end
20
38
  end
21
39
 
22
40
  def create_navigation
@@ -30,7 +48,7 @@ module Koi
30
48
  # Safe because we know that this only called during code generation
31
49
  config = eval(match) # rubocop:disable Security/Eval
32
50
  label = [*regular_class_path.map(&:humanize), human_name.pluralize].join(" ")
33
- path = "/admin#{route_url}"
51
+ path = route_url
34
52
  config[label] = path
35
53
  config = config.sort.to_h
36
54
  StringIO.new.tap do |io|
@@ -55,7 +73,7 @@ module Koi
55
73
 
56
74
  # See Rails::Generators::Actions
57
75
  # Replaces hard-coded route with admin route file
58
- def route(routing_code, namespace: nil)
76
+ def route(routing_code, namespace: regular_class_path)
59
77
  namespace = Array(namespace)
60
78
  namespace_pattern = route_namespace_pattern(namespace)
61
79
  routing_code = namespace.reverse.reduce(routing_code) do |code, name|
@@ -76,10 +94,39 @@ module Koi
76
94
  end
77
95
  end
78
96
 
97
+ def resource_route(routing_code, namespace: regular_class_path, resource: "resources :#{file_name.pluralize}")
98
+ namespace = Array(namespace)
99
+ resource_pattern = route_namespace_pattern(namespace, resource)
100
+ block_pattern = /#{resource_pattern}( do)?\n/
101
+
102
+ log :route, routing_code
103
+
104
+ in_root do
105
+ resource_match = match_file(route_file, block_pattern)
106
+
107
+ if resource_match.captures.last.nil?
108
+ *, current_indent = resource_match.captures.compact.map(&:length)
109
+ inject_into_file route_file, " do\n#{' ' * current_indent}end",
110
+ after: resource_pattern,
111
+ verbose: false,
112
+ force: false
113
+ end
114
+
115
+ resource_match = match_file(route_file, block_pattern)
116
+
117
+ *, existing_block_indent, _ = resource_match.captures.compact.map(&:length)
118
+ routing_code = rebase_indentation(routing_code, existing_block_indent + 2)
119
+
120
+ inject_into_file route_file, routing_code, after: block_pattern, verbose: true, force: false
121
+ end
122
+ end
123
+
79
124
  # See Rails::Generators::Actions
80
125
  # Replaces Routes.draw with namespace :admin as the search term
81
- def route_namespace_pattern(namespace)
82
- namespace.each_with_index.reverse_each.reduce(nil) do |pattern, (name, i)|
126
+ def route_namespace_pattern(namespace, resource = nil)
127
+ seed = resource ? route_resource_pattern(namespace, resource) : nil
128
+
129
+ namespace.each_with_index.reverse_each.reduce(seed) do |pattern, (name, i)|
83
130
  cummulative_margin = "\\#{i + 1}[ ]{2}"
84
131
  blank_or_indented_line = "^[ ]*\n|^#{cummulative_margin}.*\n"
85
132
  "(?:(?:#{blank_or_indented_line})*?^(#{cummulative_margin})namespace :#{name} do\n#{pattern})?"
@@ -88,6 +135,13 @@ module Koi
88
135
  end
89
136
  end
90
137
 
138
+ # Generate a regex for the resource block at the appropriate level of nesting
139
+ def route_resource_pattern(namespace, resource)
140
+ cummulative_margin = "\\#{namespace.count + 1}[ ]{2}"
141
+ blank_or_indented_line = "^[ ]*\n|^#{cummulative_margin}.*\n"
142
+ "(?:(?:#{blank_or_indented_line})*?^(#{cummulative_margin})#{resource})"
143
+ end
144
+
91
145
  def route_file
92
146
  "config/routes/admin.rb"
93
147
  end
@@ -1,12 +1,23 @@
1
1
  Description:
2
- Generates a Koi admin crud views for the given model
2
+ Generates Koi admin CRUD views for the given model.
3
+
4
+ When run without field:type arguments, the generator will automatically
5
+ introspect the model's attributes and associations to generate appropriate
6
+ admin views. This makes it easy to create admin views for existing models
7
+ or re-generate views when models change.
8
+
9
+ Options:
10
+ --force Overwrite existing view files
3
11
 
4
12
  Example:
5
- bin/rails generate koi:admin_views Test
13
+ bin/rails generate koi:admin_views Product name:string price:integer active:boolean
6
14
 
7
15
  This will create:
8
- app/views/admin/tests/_fields.html.erb
9
- app/views/admin/tests/edit.html.erb
10
- app/views/admin/tests/index.html.erb
11
- app/views/admin/tests/new.html.erb
12
- app/views/admin/tests/show.html.erb
16
+ app/views/admin/products/_fields.html.erb
17
+ app/views/admin/products/edit.html.erb
18
+ app/views/admin/products/index.html.erb
19
+ app/views/admin/products/new.html.erb
20
+ app/views/admin/products/show.html.erb
21
+
22
+ For existing models, run without field arguments to auto-introspect:
23
+ bin/rails generate koi:admin_views Product --force
@@ -1,38 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators/named_base"
4
- require "rails/generators/resource_helpers"
5
4
 
6
- require_relative "../helpers/admin_generator_attributes"
5
+ require_relative "../helpers/attribute_helpers"
6
+ require_relative "../helpers/resource_helpers"
7
7
 
8
8
  module Koi
9
9
  class AdminViewsGenerator < Rails::Generators::NamedBase
10
- include Rails::Generators::ResourceHelpers
11
- include Helpers::AdminGeneratorAttributes
10
+ include Helpers::AttributeHelpers
11
+ include Helpers::ResourceHelpers
12
12
 
13
13
  source_root File.expand_path("templates", __dir__)
14
14
 
15
15
  argument :attributes, type: :array, default: [], banner: "field:type field:type"
16
16
 
17
+ class_option :force, type: :boolean, default: true
18
+
17
19
  def create_root_folder
18
20
  empty_directory File.join("app/views", controller_file_path)
19
21
  end
20
22
 
21
23
  def copy_view_files
22
24
  available_views.each do |filename|
23
- target = filename.gsub("record", singular_name)
24
- template filename, File.join("app/views", controller_file_path, target)
25
+ template(filename, File.join("app/views", controller_file_path, filename))
25
26
  end
26
27
  end
27
28
 
29
+ def remove_legacy
30
+ remove_file(File.join("app/views", controller_file_path, "_fields.html.erb"), force: true)
31
+ end
32
+
28
33
  private
29
34
 
30
35
  def available_views
31
- %w(index.html.erb edit.html.erb show.html.erb new.html.erb _fields.html.erb)
32
- end
33
-
34
- def controller_class_path
35
- ["admin"] + super
36
+ [
37
+ "index.html.erb",
38
+ ("archived.html.erb" if archivable?),
39
+ "show.html.erb",
40
+ "new.html.erb",
41
+ "edit.html.erb",
42
+ "_form.html.erb",
43
+ ].compact
36
44
  end
37
45
  end
38
46
  end
@@ -0,0 +1,8 @@
1
+ <%%# locals: (<%= singular_name %>:) %>
2
+
3
+ <%%= form_with(model: <%= singular_name %>) do |form| %>
4
+ <%- attributes.each do |attribute| -%>
5
+ <%= govuk_input_for attribute %>
6
+ <%- end -%>
7
+ <%%= form.button(type: :submit, class: "button") %>
8
+ <%% end %>
@@ -0,0 +1,33 @@
1
+ <%%# locals: (collection:) %>
2
+
3
+ <%% content_for(:header) do %>
4
+ <%%= breadcrumb_list do %>
5
+ <li><%%= link_to("<%= human_name.pluralize.titleize %>", <%= admin_index_helper %>) %></li>
6
+ <%% end %>
7
+
8
+ <h1>Archived <%= human_name.pluralize.titleize %></h1>
9
+ <%% end %>
10
+
11
+ <% if query? %>
12
+ <%%= table_query_with(collection:) %>
13
+ <% end %>
14
+
15
+ <%%= table_selection_with(collection:) do %>
16
+ <%%= tag.button("Restore", formaction: <%= restore_admin_helper %>, formmethod: :put, class: "button") %>
17
+ <%% end %>
18
+
19
+ <%%= table_with(collection:) do |row, <%= singular_name %>| %>
20
+ <%% row.select %>
21
+ <%- index_attributes.each_with_index do |attribute, index| -%>
22
+ <%- if index.zero? -%>
23
+ <%% row.link :<%= attribute.name %> %>
24
+ <%- else -%>
25
+ <%= index_attribute_for attribute %>
26
+ <%- end -%>
27
+ <%- end -%>
28
+ <%% row.date :archived_at %>
29
+ <%% end %>
30
+
31
+ <%- if paginate? -%>
32
+ <%%= table_pagination_with(collection:) %>
33
+ <%- end -%>
@@ -1,12 +1,20 @@
1
- <%% content_for :header do %>
2
- <%%= render(Koi::Header::EditComponent.new(resource: <%= singular_name %>)) %>
3
- <%% end %>
1
+ <%%# locals: (<%= singular_name %>:) %>
2
+
3
+ <%% content_for(:header) do %>
4
+ <%%= breadcrumb_list do %>
5
+ <li><%%= link_to("<%= human_name.pluralize.titleize %>", <%= admin_index_helper %>) %></li>
6
+ <li><%%= link_to(<%= singular_name %>, <%= admin_show_helper %>) %></li>
7
+ <%% end %>
4
8
 
5
- <%%= form_with(model: <%= singular_name %>, url: <%= show_helper(singular_name, type: :path) %>) do |form| %>
6
- <%%= render "fields", form: %>
9
+ <h1>Edit <%= human_name.downcase %></h1>
7
10
 
8
- <div class="actions">
9
- <%%= form.admin_save %>
10
- <%%= form.admin_delete %>
11
- </div>
11
+ <%%= actions_list do %>
12
+ <%- if archivable? -%>
13
+ <li><%%= link_to_archive_or_delete(<%= singular_name %>) %></li>
14
+ <%- else -%>
15
+ <li><%%= link_to_delete(<%= singular_name %>) %></li>
16
+ <%- end -%>
17
+ <%% end %>
12
18
  <%% end %>
19
+
20
+ <%%= render("form", <%= singular_name %>:) %>
@@ -1,12 +1,35 @@
1
- <%% content_for :header do %>
2
- <%%= render(Koi::Header::IndexComponent.new(model: <%= class_name %>)) do |component| %>
3
- <%% component.with_action "New", <%= new_helper(type: :path) %> %>
1
+ <%%# locals: (collection:) %>
2
+
3
+ <%% content_for(:header) do %>
4
+ <h1><%= human_name.pluralize.titleize %></h1>
5
+
6
+ <%%= actions_list do %>
7
+ <li><%%= link_to("New", <%= new_admin_helper %>) %></li>
8
+ <%- if archivable? -%>
9
+ <li><%%= link_to("Archived", <%= archived_admin_helper %>) %></li>
10
+ <%- end -%>
4
11
  <%% end %>
5
12
  <%% end %>
13
+ <%- if query? -%>
6
14
 
7
15
  <%%= table_query_with(collection:) %>
16
+ <%- end -%>
17
+ <%- if selectable? -%>
18
+
19
+ <%%= table_selection_with(collection:) do %>
20
+ <%- if archivable? -%>
21
+ <%%= tag.button("Archive", formaction: <%= archive_admin_helper %>, formmethod: :put, class: "button") %>
22
+ <%- end -%>
23
+ <%% end %>
24
+ <%- end -%>
8
25
 
9
26
  <%%= table_with(collection:) do |row, <%= singular_name %>| %>
27
+ <%- if orderable? -%>
28
+ <%% row.ordinal unless collection.filtered? %>
29
+ <%- end -%>
30
+ <%- if selectable? -%>
31
+ <%% row.select %>
32
+ <%- end -%>
10
33
  <%- index_attributes.each_with_index do |attribute, index| -%>
11
34
  <%- if index.zero? -%>
12
35
  <%% row.link :<%= attribute.name %> %>
@@ -15,5 +38,10 @@
15
38
  <%- end -%>
16
39
  <%- end -%>
17
40
  <%% end %>
41
+ <%- if orderable? -%>
42
+
43
+ <%%= table_orderable_with(collection:, url: <%= order_admin_helper(type: :path) %>) %>
44
+ <%- elsif paginate? -%>
18
45
 
19
46
  <%%= table_pagination_with(collection:) %>
47
+ <%- end -%>
@@ -1,11 +1,11 @@
1
- <%% content_for :header do %>
2
- <%%= render(Koi::Header::NewComponent.new(model: <%= class_name %>)) %>
3
- <%% end %>
1
+ <%%# locals: (<%= singular_name %>:) %>
4
2
 
5
- <%%= form_with(model: <%= singular_name %>, url: <%= index_helper(type: :path) %>) do |form| %>
6
- <%%= render "fields", form: %>
3
+ <%% content_for(:header) do %>
4
+ <%%= breadcrumb_list do %>
5
+ <li><%%= link_to("<%= human_name.pluralize.titleize %>", <%= admin_index_helper %>) %></li>
6
+ <%% end %>
7
7
 
8
- <div class="actions">
9
- <%%= form.admin_save %>
10
- </div>
8
+ <h1>New <%= human_name.downcase %></h1>
11
9
  <%% end %>
10
+
11
+ <%%= render("form", <%= singular_name %>:) %>
@@ -1,22 +1,19 @@
1
- <%% content_for :header do %>
2
- <%%= render(Koi::Header::ShowComponent.new(resource: <%= singular_name %>)) %>
3
- <%% end %>
1
+ <%%# locals: (<%= singular_name %>:) %>
4
2
 
5
- <h2>Summary</h2>
3
+ <%% content_for(:header) do %>
4
+ <%%= breadcrumb_list do %>
5
+ <li><%%= link_to("<%= human_name.pluralize.titleize %>", <%= admin_index_helper %>) %></li>
6
+ <%% end %>
6
7
 
7
- <%%= summary_table_with(model: <%= singular_name %>) do |row| %>
8
- <%- attributes.each_with_index do |attribute, index| -%>
9
- <%- if index.zero? -%>
10
- <%% row.link :<%= attribute.name %> %>
11
- <%- else -%>
12
- <%= summary_attribute_for attribute %>
13
- <%- end -%>
14
- <%- end -%>
8
+ <h1><%%= <%= singular_name %> %></h1>
9
+
10
+ <%%= actions_list do %>
11
+ <li><%%= link_to("Edit", <%= edit_admin_helper %>) %></li>
12
+ <%% end %>
15
13
  <%% end %>
16
14
 
17
- <div class="actions">
18
- <%%= button_to "Delete", <%= show_helper(singular_name, type: :path) %>,
19
- class: "button button--secondary",
20
- method: :delete,
21
- form: { data: { turbo_confirm: "Are you sure?" } } %>
22
- </div>
15
+ <%%= summary_table_with(model: <%= singular_name %>) do |row| %>
16
+ <%- show_attributes.each do |attribute| -%>
17
+ <%= show_attribute_for(attribute) %>
18
+ <%- end -%>
19
+ <%% end %>
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/resource_helpers"
4
+
5
+ require_relative "attribute_types"
6
+
7
+ module Koi
8
+ module Helpers
9
+ module AttributeHelpers
10
+ extend ActiveSupport::Concern
11
+
12
+ class IntrospectedAttribute < Rails::Generators::GeneratedAttribute
13
+ attr_reader :association, :attachment, :enum
14
+
15
+ def initialize(name, type, association: nil, attachment: nil, enum: nil, **)
16
+ super(name, type, **)
17
+
18
+ @association = association
19
+ @attachment = attachment
20
+ @enum = enum
21
+ end
22
+
23
+ def association?
24
+ @association.present?
25
+ end
26
+
27
+ def attachment?
28
+ @attachment.present?
29
+ end
30
+
31
+ def enum?
32
+ @enum.present?
33
+ end
34
+ end
35
+
36
+ def initialize(args, *options)
37
+ super
38
+
39
+ load_attributes! if attributes.empty?
40
+ end
41
+
42
+ def govuk_input_for(attribute)
43
+ AttributeTypes.for(attribute).govuk_input
44
+ end
45
+
46
+ def index_attribute_for(attribute)
47
+ AttributeTypes.for(attribute).index_row
48
+ end
49
+
50
+ def show_attribute_for(attribute)
51
+ AttributeTypes.for(attribute).show_row
52
+ end
53
+
54
+ def collection_attribute_for(attribute)
55
+ AttributeTypes.for(attribute).collection_attribute
56
+ end
57
+
58
+ def index_attributes
59
+ attributes.select { |attribute| index_attribute_for(attribute).present? }
60
+ end
61
+
62
+ def show_attributes
63
+ attributes.select { |attribute| show_attribute_for(attribute).present? }
64
+ end
65
+
66
+ def default_sort_attribute
67
+ if orderable?
68
+ :ordinal
69
+ elsif (attribute = attributes.find { |attr| attr.type == :string })
70
+ attribute.name
71
+ else
72
+ attributes.first&.name
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def model_class
79
+ @model_class ||= class_name.safe_constantize
80
+ end
81
+
82
+ def load_attributes!
83
+ return unless model_class && model_class < ActiveRecord::Base
84
+
85
+ load_basic_attributes!
86
+ load_attachment_attributes!
87
+ load_rich_text_attributes!
88
+ load_association_attributes!
89
+ end
90
+
91
+ def load_basic_attributes!
92
+ model_class.attribute_types.each do |name, type|
93
+ next if internal_column?(name) || foreign_key_column?(name)
94
+
95
+ @attributes << IntrospectedAttribute.new(name, type.type, enum: model_class.defined_enums[name])
96
+ end
97
+ end
98
+
99
+ def load_attachment_attributes!
100
+ return unless model_class.respond_to?(:attachment_reflections)
101
+
102
+ model_class.attachment_reflections.each do |name, attachment|
103
+ @attributes << IntrospectedAttribute.new(name, :attachment, attachment:)
104
+ end
105
+ end
106
+
107
+ def load_rich_text_attributes!
108
+ return unless model_class.respond_to?(:rich_text_association_names)
109
+
110
+ model_class.rich_text_association_names.each do |name|
111
+ @attributes << IntrospectedAttribute.new(name.to_s.gsub(/^rich_text_/, "").to_sym, :rich_text)
112
+ end
113
+ end
114
+
115
+ def load_association_attributes!
116
+ model_class.reflect_on_all_associations.each do |association|
117
+ next unless association.macro == :belongs_to
118
+
119
+ @attributes << IntrospectedAttribute.new(
120
+ association.name.to_s,
121
+ :string, # associations display as text/links
122
+ association:,
123
+ )
124
+ end
125
+ end
126
+
127
+ def attachments?(name)
128
+ attribute = attributes.find { |attr| attr.name == name }
129
+ attribute&.attachments?
130
+ end
131
+
132
+ def internal_column?(name)
133
+ %w[id created_at updated_at].include?(name.to_s)
134
+ end
135
+
136
+ def foreign_key_columns
137
+ model_class
138
+ .reflect_on_all_associations(:belongs_to)
139
+ .map(&:foreign_key)
140
+ end
141
+
142
+ def foreign_key_column?(name)
143
+ foreign_key_columns.include?(name.to_s)
144
+ end
145
+ end
146
+ end
147
+ end