super 0.0.14 → 0.18.0

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