super 0.0.14 → 0.18.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/assets/javascripts/super/application.js +148 -15
  4. data/app/assets/stylesheets/super/application.css +15 -22
  5. data/app/controllers/super/application_controller.rb +44 -42
  6. data/app/controllers/super/substructure_controller.rb +313 -0
  7. data/app/helpers/super/form_builder_helper.rb +7 -0
  8. data/app/views/layouts/super/application.html.erb +3 -21
  9. data/app/views/super/application/_collection_header.html.erb +2 -2
  10. data/app/views/super/application/_display_actions.html.erb +1 -1
  11. data/app/views/super/application/_display_index.html.erb +2 -2
  12. data/app/views/super/application/_display_show.html.erb +1 -1
  13. data/app/views/super/application/_filter.html.erb +62 -2
  14. data/app/views/super/application/_form_field.html.erb +5 -0
  15. data/app/views/super/application/_member_header.html.erb +2 -2
  16. data/app/views/super/application/_pagination.html.erb +1 -1
  17. data/app/views/super/application/_site_footer.html.erb +3 -0
  18. data/app/views/super/application/_site_header.html.erb +17 -0
  19. data/app/views/super/application/_sort_expression.html.erb +2 -2
  20. data/app/views/super/application/index.csv.erb +14 -0
  21. data/app/views/super/feather/README.md +0 -1
  22. data/frontend/super-frontend/dist/application.css +15 -22
  23. data/frontend/super-frontend/dist/application.js +148 -15
  24. data/lib/generators/super/install/install_generator.rb +0 -16
  25. data/lib/generators/super/install/templates/base_controller.rb.tt +0 -8
  26. data/lib/generators/super/resource/templates/resources_controller.rb.tt +4 -4
  27. data/lib/super/action_inquirer.rb +18 -3
  28. data/lib/super/badge.rb +60 -0
  29. data/lib/super/cheat.rb +6 -6
  30. data/lib/super/display/guesser.rb +1 -1
  31. data/lib/super/display/schema_types.rb +49 -1
  32. data/lib/super/display.rb +2 -1
  33. data/lib/super/error.rb +3 -0
  34. data/lib/super/filter/form_object.rb +74 -48
  35. data/lib/super/filter/guesser.rb +2 -0
  36. data/lib/super/filter/operator.rb +90 -64
  37. data/lib/super/filter/schema_types.rb +63 -80
  38. data/lib/super/filter.rb +1 -1
  39. data/lib/super/form/builder.rb +30 -39
  40. data/lib/super/form/field_transcript.rb +43 -0
  41. data/lib/super/form/guesser.rb +4 -4
  42. data/lib/super/form/schema_types.rb +66 -22
  43. data/lib/super/link.rb +2 -2
  44. data/lib/super/pagination.rb +2 -44
  45. data/lib/super/reset.rb +25 -0
  46. data/lib/super/schema.rb +4 -0
  47. data/lib/super/useful/builder.rb +4 -4
  48. data/lib/super/version.rb +1 -1
  49. data/lib/super.rb +3 -1
  50. data/lib/tasks/super/cheat.rake +1 -1
  51. metadata +25 -19
  52. data/app/views/super/application/_filter_type_select.html.erb +0 -21
  53. data/app/views/super/application/_filter_type_text.html.erb +0 -18
  54. data/app/views/super/application/_filter_type_timestamp.html.erb +0 -24
  55. data/app/views/super/application/_form_field_checkbox.html.erb +0 -1
  56. data/app/views/super/application/_form_field_flatpickr_date.html.erb +0 -8
  57. data/app/views/super/application/_form_field_flatpickr_datetime.html.erb +0 -8
  58. data/app/views/super/application/_form_field_flatpickr_time.html.erb +0 -8
  59. data/app/views/super/application/_form_field_rich_text_area.html.erb +0 -1
  60. data/app/views/super/application/_form_field_select.html.erb +0 -1
  61. data/app/views/super/application/_form_field_text.html.erb +0 -1
  62. data/app/views/super/feather/_chevron_down.html +0 -1
  63. data/docs/cheat.md +0 -41
  64. data/lib/super/controls/optional.rb +0 -113
  65. data/lib/super/controls/steps.rb +0 -106
  66. data/lib/super/controls/view.rb +0 -55
  67. data/lib/super/controls.rb +0 -22
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ # Various methods that determine the behavior of your controllers. These
5
+ # methods can and should be overridden.
6
+ class SubstructureController < ActionController::Base
7
+ private
8
+
9
+ helper_method def model
10
+ raise Error::NotImplementedError
11
+ end
12
+
13
+ # This is an optional method
14
+ #
15
+ # @return [String]
16
+ helper_method def title
17
+ model.name.pluralize
18
+ end
19
+
20
+ # This defines what to set in the <title> tag. It works in conjunction with
21
+ # the `#site_title` method
22
+ #
23
+ # @return [String, void]
24
+ helper_method def page_title
25
+ model.name.pluralize
26
+ rescue Error::NotImplementedError, NameError
27
+ return nil
28
+ end
29
+
30
+ # Configures what database records are visible on load. This is an optional
31
+ # method, it defaults to "`all`" methods
32
+ #
33
+ # @return [ActiveRecord::Relation]
34
+ helper_method def base_scope
35
+ model.all
36
+ end
37
+
38
+ # Configures the fields that are displayed on the index and show actions.
39
+ # This is a required method
40
+ #
41
+ # @return [Display]
42
+ helper_method def display_schema
43
+ Display.new do |fields, type|
44
+ Display::Guesser.new(model: model, action: current_action, fields: fields, type: type).call
45
+ end
46
+ end
47
+
48
+ # Configures the editable fields on the new and edit actions. This is a
49
+ # required method
50
+ #
51
+ # @return [Form]
52
+ helper_method def form_schema
53
+ Form.new do |fields, type|
54
+ Form::Guesser.new(model: model, fields: fields, type: type).call
55
+ end
56
+ end
57
+
58
+ # Configures which parameters could be written to the database. This is a
59
+ # required method
60
+ #
61
+ # @return [ActionController::Parameters]
62
+ helper_method def permitted_params
63
+ strong_params =
64
+ if current_action.create?
65
+ with_current_action("new") do
66
+ Super::Form::StrongParams.new(form_schema)
67
+ end
68
+ elsif current_action.update?
69
+ with_current_action("edit") do
70
+ Super::Form::StrongParams.new(form_schema)
71
+ end
72
+ else
73
+ Super::Form::StrongParams.new(form_schema)
74
+ end
75
+ params.require(strong_params.require(model)).permit(strong_params.permit)
76
+ end
77
+
78
+ # Configures the actions linked to on the index page. This is an optional
79
+ # method
80
+ #
81
+ # @return [Array<Link>]
82
+ helper_method def collection_actions
83
+ Super::Link.find_all(:new)
84
+ end
85
+
86
+ # Configures the actions linked to on the show page as well as each row of
87
+ # the table on the index page. This is an optional method
88
+ #
89
+ # Favor the `record` argument over the `@record` instance variable;
90
+ # `@record` won't always be set, notably on the index page where it's
91
+ # called on every row
92
+ #
93
+ # @return [Array<Link>]
94
+ helper_method def member_actions(record)
95
+ if current_action.show?
96
+ Super::Link.find_all(:edit, :destroy)
97
+ elsif current_action.edit?
98
+ Super::Link.find_all(:show, :destroy)
99
+ else
100
+ Super::Link.find_all(:show, :edit, :destroy)
101
+ end
102
+ end
103
+
104
+ helper_method def filters_enabled?
105
+ true
106
+ end
107
+
108
+ helper_method def filter_schema
109
+ Super::Filter.new do |fields, type|
110
+ Super::Filter::Guesser.new(model: model, fields: fields, type: type).call
111
+ end
112
+ end
113
+
114
+ helper_method def sort_enabled?
115
+ true
116
+ end
117
+
118
+ helper_method def sortable_columns
119
+ action = ActionInquirer.new(
120
+ ActionInquirer.default_for_resources,
121
+ "index"
122
+ )
123
+ attribute_names =
124
+ display_schema.each_attribute.map do |key, val|
125
+ val = val.build if val.respond_to?(:build)
126
+ key if val.real?
127
+ end
128
+
129
+ attribute_names.compact
130
+ end
131
+
132
+ helper_method def default_sort
133
+ { id: :desc }
134
+ end
135
+
136
+ # Specifies how many records to show per page
137
+ #
138
+ # @return [ActiveRecord::Relation]
139
+ helper_method def records_per_page
140
+ Super.configuration.index_records_per_page
141
+ end
142
+
143
+ def load_records
144
+ base_scope
145
+ end
146
+
147
+ def load_record
148
+ base_scope.find(params[:id])
149
+ end
150
+
151
+ def build_record
152
+ base_scope.build
153
+ end
154
+
155
+ def set_record_attributes
156
+ @record.attributes = permitted_params
157
+ end
158
+
159
+ def save_record
160
+ @record.save
161
+ end
162
+
163
+ def destroy_record
164
+ @record.destroy
165
+ end
166
+
167
+ def initialize_query_form
168
+ Super::Query::FormObject.new(
169
+ model: model,
170
+ params: params,
171
+ namespace: :q,
172
+ current_path: request.path,
173
+ )
174
+ end
175
+
176
+ def apply_queries
177
+ @query_form.apply_changes(@records)
178
+ end
179
+
180
+ def initialize_filter_form
181
+ if filters_enabled?
182
+ @query_form.add(
183
+ Super::Filter::FormObject,
184
+ namespace: :f,
185
+ schema: filter_schema
186
+ )
187
+ end
188
+ end
189
+
190
+ def initialize_sort_form
191
+ if sort_enabled?
192
+ @query_form.add(
193
+ Super::Sort::FormObject,
194
+ namespace: :s,
195
+ default: default_sort,
196
+ sortable_columns: sortable_columns
197
+ )
198
+ end
199
+ end
200
+
201
+ def pagination_disabled_param
202
+ :_all_pages
203
+ end
204
+
205
+ def pagination_enabled?
206
+ !params.key?(pagination_disabled_param)
207
+ end
208
+
209
+ # Sets up pagination
210
+ #
211
+ # @return [Pagination]
212
+ def initialize_pagination
213
+ Pagination.new(
214
+ total_count: @records.size,
215
+ limit: records_per_page,
216
+ query_params: request.GET,
217
+ page_query_param: :page
218
+ )
219
+ end
220
+
221
+ # Paginates
222
+ #
223
+ # @return [ActiveRecord::Relation]
224
+ def paginate_records
225
+ return @records if !pagination_enabled?
226
+
227
+ @records
228
+ .limit(@pagination.limit)
229
+ .offset(@pagination.offset)
230
+ end
231
+
232
+ def index_view
233
+ Super::Layout.new(
234
+ mains: [
235
+ Super::Panel.new(
236
+ Super::Partial.new("collection_header"),
237
+ :@display
238
+ ),
239
+ ],
240
+ asides: [
241
+ :@query_form,
242
+ ]
243
+ )
244
+ end
245
+
246
+ def show_view
247
+ Super::Layout.new(
248
+ mains: [
249
+ Super::Panel.new(
250
+ Super::Partial.new("member_header"),
251
+ :@display
252
+ ),
253
+ ]
254
+ )
255
+ end
256
+
257
+ def new_view
258
+ Super::Layout.new(
259
+ mains: [
260
+ Super::Panel.new(
261
+ Super::Partial.new("collection_header"),
262
+ :@form
263
+ ),
264
+ ]
265
+ )
266
+ end
267
+
268
+ def edit_view
269
+ Super::Layout.new(
270
+ mains: [
271
+ Super::Panel.new(
272
+ Super::Partial.new("member_header"),
273
+ :@form
274
+ ),
275
+ ]
276
+ )
277
+ end
278
+
279
+ concerning :Sitewide do
280
+ included do
281
+ helper_method :site_title
282
+ helper_method :site_navigation
283
+ helper_method :document_title
284
+ end
285
+
286
+ private
287
+
288
+ def site_title
289
+ Super.configuration.title
290
+ end
291
+
292
+ def site_navigation
293
+ Super::Navigation.new(&:all)
294
+ end
295
+
296
+ def document_title
297
+ if instance_variable_defined?(:@document_title)
298
+ return @document_title
299
+ end
300
+
301
+ document_title_segments.map(&:presence).compact.join(document_title_separator)
302
+ end
303
+
304
+ def document_title_segments
305
+ @document_title_segments ||= [page_title, site_title]
306
+ end
307
+
308
+ def document_title_separator
309
+ @document_title_separator ||= " - "
310
+ end
311
+ end
312
+ end
313
+ end
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Super
4
+ # These are form builder view helpers. They are similar to what Rails ships
5
+ # out of the box but adds some styling and functionality.
6
+ #
7
+ # These helpers are available both in Super views and in your application's
8
+ # views.
4
9
  module FormBuilderHelper
10
+ # Super's version of `#form_for`
5
11
  def super_form_for(record, options = {}, &block)
6
12
  original = ActionView::Base.field_error_proc
7
13
  ActionView::Base.field_error_proc = Form::Builder::FIELD_ERROR_PROC
@@ -12,6 +18,7 @@ module Super
12
18
  ActionView::Base.field_error_proc = original
13
19
  end
14
20
 
21
+ # Super's version of `#form_with`
15
22
  def super_form_with(**options, &block)
16
23
  original = ActionView::Base.field_error_proc
17
24
  ActionView::Base.field_error_proc = Form::Builder::FIELD_ERROR_PROC
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html class="bg-gray-100">
3
3
  <head>
4
- <title><%= Super.configuration.title %></title>
4
+ <title><%= document_title %></title>
5
5
  <%= csrf_meta_tags %>
6
6
  <% if respond_to?(:csp_meta_tag) -%>
7
7
  <%= csp_meta_tag %>
@@ -28,23 +28,7 @@
28
28
 
29
29
  <body class="font-sans pb-4">
30
30
  <div class="px-4">
31
- <header class="pt-4">
32
- <h1 class="text-lg font-bold mr-6 mb-1"><%= Super.configuration.title %></h1>
33
- <% navigation.definition.each do |link_or_menu| %>
34
- <% if link_or_menu.is_a?(Super::Link) %>
35
- <%= link_or_menu.to_s(default_options: { class: "inline-block mr-6" }) %>
36
- <% else %>
37
- <details class="details-reset select-none inline-block mr-6" data-controller="click-outside-to-close" data-action="click@window->click-outside-to-close#close">
38
- <summary class="text-blue-700"><%= link_or_menu.title %></summary>
39
- <ul class="absolute bg-white shadow-md px-4 py-3">
40
- <% link_or_menu.links.each do |link| %>
41
- <li><%= link.to_s(default_options: { class: "" }) %></li>
42
- <% end %>
43
- </ul>
44
- </details>
45
- <% end %>
46
- <% end %>
47
- </header>
31
+ <%= render "site_header" %>
48
32
 
49
33
  <%= render "flash" %>
50
34
 
@@ -52,9 +36,7 @@
52
36
 
53
37
  <%= yield %>
54
38
 
55
- <div class="pt-8 text-sm text-gray-800">
56
- <%= t("super.layout.powered_by", env: Rails.env.capitalize, version: Super::VERSION) %>
57
- </div>
39
+ <%= render "site_footer" %>
58
40
  </div>
59
41
  </body>
60
42
  </html>
@@ -1,9 +1,9 @@
1
1
  <header class="flex justify-between content-end">
2
2
  <h1 class="text-xl">
3
- <%= controls.title %>
3
+ <%= title %>
4
4
  </h1>
5
5
  <div>
6
- <% controls.collection_actions(action: action_inquirer).each do |link| %>
6
+ <% collection_actions.each do |link| %>
7
7
  <%= link.to_s(
8
8
  default_options: {
9
9
  class: "super-button super-button--border-blue super-button-sm inline-block ml-2"
@@ -1,5 +1,5 @@
1
1
  <div>
2
- <% controls.member_actions(action: action_inquirer).each do |link| %>
2
+ <% member_actions(record).each do |link| %>
3
3
  <span class="pr-2 last:pr-0"><%= link.to_s(record: record, params: params) %></span>
4
4
  <% end %>
5
5
  </div>
@@ -1,10 +1,10 @@
1
1
  <div class="mt-4 overflow-x-auto">
2
- <table class="w-full border-separate relative" cellspacing="0" cellpadding="0">
2
+ <table class="w-full border-separate" cellspacing="0" cellpadding="0">
3
3
  <thead class="">
4
4
  <tr class="">
5
5
  <% display_index.each_attribute_name do |attribute_name| %>
6
6
  <th class="p-2 first:pl-6 border-b border-b-2 border-gray-400 text-gray-600 text-left text-sm font-normal bg-white top-0 z-10">
7
- <%= controls.model.human_attribute_name(attribute_name) %>
7
+ <%= model.human_attribute_name(attribute_name) %>
8
8
  </th>
9
9
  <% end %>
10
10
  </tr>
@@ -1,7 +1,7 @@
1
1
  <table class="max-w-full mt-4">
2
2
  <% display_show.each_attribute_name do |attribute_name| %>
3
3
  <tr>
4
- <th class="py-1 align-baseline text-right px-4"><%= controls.model.human_attribute_name(attribute_name) %></th>
4
+ <th class="py-1 align-baseline text-right px-4"><%= model.human_attribute_name(attribute_name) %></th>
5
5
  <td class="py-1 align-baseline"><%= display_show.render_attribute(template: self, record: @record, column: attribute_name) %></td>
6
6
  </tr>
7
7
  <% end %>
@@ -1,6 +1,66 @@
1
1
  <h1 class="text-lg">Filter</h1>
2
- <% filter.each_field do |filter_field| %>
2
+ <% filter.each_attribute do |attribute_form_object| %>
3
3
  <div class="mt-4">
4
- <%= render(filter_field, form: form) %>
4
+ <%= form.fields_for(attribute_form_object.field_name, attribute_form_object) do |attribute_form| %>
5
+ <% selected_index = 0 %>
6
+ <div data-controller="tab-container" data-tab-container-tab-identifier-getter-value="tabIdentifierValue" data-tab-container-tab-controller-name-value="tab">
7
+ <div>
8
+ <span class="inline-block"><%= attribute_form_object.humanized_attribute_name %></span>
9
+ <select data-tab-container-target="control" data-action="tab-container#change" class="super-input super-input-select inline-block">
10
+ <% selected = false %>
11
+ <% attribute_form_object.each_operator.with_index do |operator_form_object, index| %>
12
+ <%
13
+ selected_attribute =
14
+ if !selected && operator_form_object.specified?
15
+ selected = true
16
+ selected_index = index
17
+ %(selected=selected)
18
+ else
19
+ ""
20
+ end
21
+ %>
22
+ <option value="<%= operator_form_object.identifier %>" <%= selected_attribute %>><%= operator_form_object.operator.humanized_operator_name %></option>
23
+ <% end %>
24
+ </select>
25
+ </div>
26
+ <div>
27
+ <% attribute_form_object.each_operator.with_index do |operator_form_object, index| %>
28
+ <div data-controller="tab" data-tab-tab-container-getter-value="tabContainer" data-tab-container-target="tab" data-tab-identifier-value="<%= operator_form_object.identifier %>">
29
+ <% form_html = capture do %>
30
+ <div data-tab-target="content">
31
+ <%= attribute_form.fields_for(operator_form_object.identifier, operator_form_object) do |operator_form| %>
32
+ <div class="flex gap-x-2 mt-2">
33
+ <% operator_form_object.each_field.with_index do |operator_field_name, index| %>
34
+ <div class="flex-1">
35
+ <% if operator_field_name == Super::Filter::FormObject::OperatorForm::NULLARY %>
36
+ <%= operator_form.super.check_box(operator_field_name, {}, "1", "") %>
37
+ <%= operator_form.super.label(operator_field_name, nil, super: { class: "select-none ml-1" }) %>
38
+ <% elsif operator_form_object.operator.respond_to?(:field_transcript) && operator_form_object.operator.field_transcript %>
39
+ <% field_transcript = operator_form_object.operator.field_transcript %>
40
+ <% if field_transcript.super? %>
41
+ <%= operator_form.super.public_send(field_transcript.method_name, operator_field_name, *field_transcript.args, **field_transcript.kwargs) %>
42
+ <% else %>
43
+ <%= operator_form.public_send(field_transcript.method_name, operator_field_name, *field_transcript.args, **field_transcript.kwargs) %>
44
+ <% end %>
45
+ <% else %>
46
+ <%= operator_form.super.text_field(operator_field_name) %>
47
+ <% end %>
48
+ </div>
49
+ <% end %>
50
+ </div>
51
+ <% end %>
52
+ </div>
53
+ <% end %>
54
+
55
+ <%= form_html if index == selected_index %>
56
+
57
+ <template data-tab-target="pocket">
58
+ <%= form_html %>
59
+ </template>
60
+ </div>
61
+ <% end %>
62
+ </div>
63
+ </div>
64
+ <% end %>
5
65
  </div>
6
66
  <% end %>