super 0.0.2 → 0.0.7

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/README.md +68 -76
  4. data/app/assets/javascripts/super/application.js +1252 -316
  5. data/app/assets/stylesheets/super/application.css +102704 -17321
  6. data/app/controllers/super/application_controller.rb +49 -71
  7. data/app/views/layouts/super/application.html.erb +10 -8
  8. data/app/views/super/application/_collection_header.html.erb +15 -0
  9. data/app/views/super/application/_filter.html.erb +14 -0
  10. data/app/views/super/application/_filter_type_select.html.erb +31 -0
  11. data/app/views/super/application/_filter_type_text.html.erb +22 -0
  12. data/app/views/super/application/_filter_type_timestamp.html.erb +35 -0
  13. data/app/views/super/application/_flash.html.erb +13 -13
  14. data/app/views/super/application/_form_field__destroy.html.erb +9 -0
  15. data/app/views/super/application/_form_field_select.html.erb +23 -0
  16. data/app/views/super/application/_form_field_text.html.erb +13 -0
  17. data/app/views/super/application/_form_fieldset.html.erb +8 -0
  18. data/app/views/super/application/_form_has_many.html.erb +21 -0
  19. data/app/views/super/application/_form_has_one.html.erb +11 -0
  20. data/app/views/super/application/_form_inline_errors.html.erb +10 -0
  21. data/app/views/super/application/_member_header.html.erb +16 -0
  22. data/app/views/super/application/_super_layout.html.erb +29 -0
  23. data/app/views/super/application/_super_pagination.html.erb +16 -0
  24. data/app/views/super/application/_super_panel.html.erb +7 -0
  25. data/app/views/super/application/_super_schema_display_actions.html.erb +5 -0
  26. data/app/views/super/application/_super_schema_display_index.html.erb +24 -0
  27. data/app/views/super/application/_super_schema_display_show.html.erb +8 -0
  28. data/app/views/super/application/_super_schema_form.html.erb +15 -0
  29. data/app/views/super/application/edit.html.erb +1 -6
  30. data/app/views/super/application/index.html.erb +1 -1
  31. data/app/views/super/application/new.html.erb +1 -6
  32. data/app/views/super/application/show.html.erb +1 -1
  33. data/app/views/super/feather/{_chevron_down.svg → _chevron_down.html} +0 -0
  34. data/config/locales/en.yml +5 -0
  35. data/docs/README.md +6 -0
  36. data/docs/cheat.md +41 -0
  37. data/docs/faq.md +44 -0
  38. data/docs/quick_start.md +45 -0
  39. data/docs/webpacker.md +17 -0
  40. data/docs/yard_customizations.rb +41 -0
  41. data/frontend/super-frontend/build.js +14 -12
  42. data/frontend/super-frontend/dist/application.css +102704 -17321
  43. data/frontend/super-frontend/dist/application.js +1252 -316
  44. data/frontend/super-frontend/package.json +11 -4
  45. data/frontend/super-frontend/postcss.config.js +4 -4
  46. data/frontend/super-frontend/src/javascripts/super/application.ts +18 -0
  47. data/frontend/super-frontend/src/javascripts/super/apply_template_controller.ts +19 -0
  48. data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +1 -0
  49. data/frontend/super-frontend/src/javascripts/super/toggle_pending_destruction_controller.ts +15 -0
  50. data/frontend/super-frontend/src/stylesheets/super/application.css +63 -0
  51. data/frontend/super-frontend/tailwind.config.js +12 -4
  52. data/frontend/super-frontend/tsconfig.json +13 -0
  53. data/frontend/super-frontend/yarn.lock +1891 -1798
  54. data/lib/generators/super/install/install_generator.rb +16 -0
  55. data/lib/generators/super/webpacker/webpacker_generator.rb +8 -0
  56. data/lib/super.rb +16 -6
  57. data/lib/super/action_inquirer.rb +14 -1
  58. data/lib/super/assets.rb +1 -0
  59. data/lib/super/client_error.rb +43 -0
  60. data/lib/super/compatibility.rb +25 -0
  61. data/lib/super/configuration.rb +66 -44
  62. data/lib/super/controls.rb +26 -15
  63. data/lib/super/controls/optional.rb +54 -0
  64. data/lib/super/controls/required.rb +41 -0
  65. data/lib/super/controls/steps.rb +128 -0
  66. data/lib/super/display/schema_types.rb +90 -18
  67. data/lib/super/engine.rb +7 -0
  68. data/lib/super/error.rb +9 -8
  69. data/lib/super/filter.rb +137 -0
  70. data/lib/super/filter/operator.rb +103 -0
  71. data/lib/super/filter/schema_types.rb +118 -0
  72. data/lib/super/form.rb +48 -0
  73. data/lib/super/form/schema_types.rb +96 -21
  74. data/lib/super/layout.rb +47 -0
  75. data/lib/super/link.rb +110 -0
  76. data/lib/super/navigation/automatic.rb +2 -0
  77. data/lib/super/pagination.rb +80 -8
  78. data/lib/super/panel.rb +30 -0
  79. data/lib/super/partial.rb +23 -0
  80. data/lib/super/partial/resolving.rb +24 -0
  81. data/lib/super/plugin.rb +34 -63
  82. data/lib/super/schema.rb +65 -1
  83. data/lib/super/version.rb +1 -1
  84. data/lib/super/view_helper.rb +43 -0
  85. metadata +132 -33
  86. data/app/views/super/application/_form.html.erb +0 -15
  87. data/app/views/super/application/_form_field.html.erb +0 -7
  88. data/app/views/super/application/_form_generic_select.html.erb +0 -19
  89. data/app/views/super/application/_form_generic_text.html.erb +0 -7
  90. data/app/views/super/application/_index.html.erb +0 -60
  91. data/app/views/super/application/_show.html.erb +0 -12
  92. data/frontend/super-frontend/src/javascripts/super/application.js +0 -11
  93. data/lib/super/display.rb +0 -9
  94. data/lib/super/inline_callback.rb +0 -82
  95. data/lib/super/test_support/copy_app_templates/20190216224956_create_members.rb +0 -11
  96. data/lib/super/test_support/copy_app_templates/20190803143320_create_ships.rb +0 -11
  97. data/lib/super/test_support/copy_app_templates/20190806014121_add_ship_to_members.rb +0 -5
  98. data/lib/super/test_support/copy_app_templates/member.rb +0 -16
  99. data/lib/super/test_support/copy_app_templates/members_controller.rb +0 -52
  100. data/lib/super/test_support/copy_app_templates/routes.rb +0 -10
  101. data/lib/super/test_support/copy_app_templates/seeds.rb +0 -2
  102. data/lib/super/test_support/copy_app_templates/ship.rb +0 -3
  103. data/lib/super/test_support/copy_app_templates/ships_controller.rb +0 -47
  104. data/lib/super/test_support/fixtures/members.yml +0 -336
  105. data/lib/super/test_support/fixtures/ships.yml +0 -10
  106. data/lib/super/test_support/generate_copy_app.rb +0 -52
  107. data/lib/super/test_support/generate_dummy.rb +0 -94
  108. data/lib/super/test_support/starfleet_seeder.rb +0 -49
  109. data/lib/super/view.rb +0 -25
  110. data/lib/tasks/super_tasks.rake +0 -4
@@ -1,116 +1,94 @@
1
1
  module Super
2
+ # Provides a default implementation for each of the resourceful actions
2
3
  class ApplicationController < ActionController::Base
3
- include Super::InlineCallback
4
- include Pluggable.new(:super_application_controller)
5
-
6
- register_inline_callback(:index_paginate, on: :index, after: :yield)
4
+ include ClientError::Handling
7
5
 
8
6
  helper_method :action_inquirer
9
7
  helper_method :controls
10
8
 
11
- rescue_from Error::ClientError do |exception|
12
- code, default_message =
13
- case exception
14
- when Error::UnprocessableEntity
15
- [422, "Unprocessable entity"]
16
- when Error::NotFound
17
- [404, "Not found"]
18
- when Error::Forbidden
19
- [403, "Forbidden"]
20
- when Error::Unauthorized
21
- [401, "Unauthorized"]
22
- when Error::BadRequest, Error::ClientError
23
- [400, "Bad request"]
24
- end
25
-
26
- flash.now.alert =
27
- if exception.message == exception.class.name.to_s
28
- default_message
29
- else
30
- exception.message
31
- end
9
+ # Displays a list of records to the user
10
+ def index
11
+ @records = controls.load_records(action: action_inquirer, params: params)
12
+ @display = controls.display_schema(action: action_inquirer)
13
+ @view = controls.build_index_view
14
+ end
32
15
 
33
- render "nothing", status: code
16
+ # Displays a specific record to the user
17
+ def show
18
+ @record = controls.load_record(action: action_inquirer, params: params)
19
+ @display = controls.display_schema(action: action_inquirer)
20
+ @view = controls.build_show_view
34
21
  end
35
22
 
36
- def index
37
- with_inline_callbacks do
38
- @resources = controls.scope(action: action_inquirer)
39
- end
23
+ # Displays a form to allow the user to create a new record
24
+ def new
25
+ @record = controls.build_record(action: action_inquirer)
26
+ @form = controls.form_schema(action: action_inquirer)
27
+ @view = controls.build_new_view
40
28
  end
41
29
 
30
+ # Creates a record, or shows the validation errors
42
31
  def create
43
- @resource = controls.scope(action: action_inquirer).build(create_permitted_params)
32
+ @record = controls.build_record_with_params(action: action_inquirer, params: params)
44
33
 
45
- if @resource.save
46
- redirect_to polymorphic_path(Super.configuration.path_parts(@resource))
34
+ if controls.save_record(action: action_inquirer, record: @record, params: params)
35
+ redirect_to polymorphic_path(Super.configuration.path_parts(@record))
47
36
  else
37
+ @form = controls.form_schema(action: action_inquirer_for("new"))
38
+ @view = controls.build_new_view
48
39
  render :new, status: :bad_request
49
40
  end
50
41
  end
51
42
 
52
- def new
53
- @resource = controls.scope(action: action_inquirer).build
54
- end
55
-
43
+ # Displays a form to allow the user to update an existing record
56
44
  def edit
57
- @resource = controls.scope(action: action_inquirer).find(params[:id])
58
- end
59
-
60
- def show
61
- @resource = controls.scope(action: action_inquirer).find(params[:id])
45
+ @record = controls.load_record(action: action_inquirer, params: params)
46
+ @form = controls.form_schema(action: action_inquirer)
47
+ @view = controls.build_edit_view
62
48
  end
63
49
 
50
+ # Updates a record, or shows validation errors
64
51
  def update
65
- @resource = controls.scope(action: action_inquirer).find(params[:id])
52
+ @record = controls.load_record(action: action_inquirer, params: params)
66
53
 
67
- if @resource.update(update_permitted_params)
68
- redirect_to polymorphic_path(Super.configuration.path_parts(@resource))
54
+ if controls.update_record(action: action_inquirer, record: @record, params: params)
55
+ redirect_to polymorphic_path(Super.configuration.path_parts(@record))
69
56
  else
57
+ @form = controls.form_schema(action: action_inquirer_for("edit"))
58
+ @view = controls.build_edit_view
70
59
  render :edit, status: :bad_request
71
60
  end
72
61
  end
73
62
 
63
+ # Deletes a record, or shows validation errors
74
64
  def destroy
75
- @resource = controls.scope(action: action_inquirer).find(params[:id])
76
- if @resource.destroy
65
+ @record = controls.load_record(action: action_inquirer, params: params)
66
+
67
+ if controls.destroy_record(action: action_inquirer, record: @record, params: params)
77
68
  redirect_to polymorphic_path(Super.configuration.path_parts(controls.model))
78
69
  else
79
- redirect_to polymorphic_path(Super.configuration.path_parts(@resource))
70
+ flash.alert = "Couldn't delete record"
71
+ redirect_to polymorphic_path(Super.configuration.path_parts(@record))
80
72
  end
73
+ rescue ActiveRecord::InvalidForeignKey => e
74
+ flash.alert = "Couldn't delete record: #{e.class}"
75
+ redirect_to polymorphic_path(Super.configuration.path_parts(@record))
81
76
  end
82
77
 
83
78
  private
84
79
 
85
- def index_paginate
86
- @pagination = Pagination.new(
87
- total_count: @resources.size,
88
- limit: Super.configuration.index_resources_per_page,
89
- query_params: request.GET,
90
- page_query_param: :page
91
- )
92
-
93
- @resources = @resources
94
- .limit(@pagination.limit)
95
- .offset(@pagination.offset)
96
- end
97
-
98
80
  def controls
99
81
  Super::Controls.new(new_controls)
100
82
  end
101
83
 
102
- def create_permitted_params
103
- controls.permitted_params(params, action: action_inquirer)
104
- end
105
-
106
- def update_permitted_params
107
- controls.permitted_params(params, action: action_inquirer)
84
+ def action_inquirer
85
+ @action_inquirer ||= action_inquirer_for(params[:action])
108
86
  end
109
87
 
110
- def action_inquirer
111
- @action_inquirer ||= ActionInquirer.new(
112
- ActionInquirer.default_resources,
113
- params[:action]
88
+ def action_inquirer_for(action)
89
+ ActionInquirer.new(
90
+ ActionInquirer.default_for_resources,
91
+ action
114
92
  )
115
93
  end
116
94
  end
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html class="bg-white">
2
+ <html class="bg-gray-100">
3
3
  <head>
4
4
  <title><%= Super.configuration.title %></title>
5
5
  <%= csrf_meta_tags %>
@@ -7,6 +7,8 @@
7
7
  <%= csp_meta_tag %>
8
8
  <% end -%>
9
9
 
10
+ <meta name="viewport" content="width=device-width, initial-scale=1">
11
+
10
12
  <% if Super.configuration.asset_handler.sprockets? %>
11
13
  <%= stylesheet_link_tag "super/application", media: "all" %>
12
14
  <%= javascript_include_tag "super/application" %>
@@ -16,10 +18,10 @@
16
18
  <% end %>
17
19
  </head>
18
20
 
19
- <body class="font-sans bg-white">
21
+ <body class="font-sans pb-4">
20
22
  <div class="px-4">
21
23
  <header class="pt-4">
22
- <h1 class="text-base font-bold mr-6"><%= Super.configuration.title %></h1>
24
+ <h1 class="text-lg font-bold mr-6 mb-1"><%= Super.configuration.title %></h1>
23
25
  <%
24
26
  route_namespace = Super.configuration.route_namespace.map(&:to_s).join("/")
25
27
  Super::Navigation::Automatic.new(route_namespace: route_namespace).each do |text, href| %>
@@ -29,13 +31,13 @@
29
31
 
30
32
  <%= render "flash" %>
31
33
 
32
- <div class="pt-8"></div>
33
-
34
- <% if content_for?(:header) %>
35
- <%= yield :header %>
36
- <% end %>
34
+ <div class="pt-4"></div>
37
35
 
38
36
  <%= yield %>
37
+
38
+ <div class="pt-8 text-sm text-gray-800">
39
+ <%= t("super.layout.powered_by", env: Rails.env.capitalize, version: Super::VERSION) %>
40
+ </div>
39
41
  </div>
40
42
  </body>
41
43
  </html>
@@ -0,0 +1,15 @@
1
+ <header class="flex justify-between content-end">
2
+ <h1 class="text-xl">
3
+ <%= controls.title %>
4
+ </h1>
5
+ <div>
6
+ <% controls.collection_actions(action: action_inquirer).each do |link| %>
7
+ <%= link.to_s(
8
+ default_options: {
9
+ class: "super-button super-button--border-blue super-button-sm inline-block ml-2"
10
+ },
11
+ params: params
12
+ ) %>
13
+ <% end %>
14
+ </div>
15
+ </header>
@@ -0,0 +1,14 @@
1
+ <%= render(Super::Panel.new) do %>
2
+ <h1 class="text-xl">Filters</h1>
3
+ <%= form_for(filter, url: filter.url, method: :get, as: :q, html: { class: "mt-4" }) do |form| %>
4
+ <% filter.each_field do |filter_field| %>
5
+ <div class="mt-4">
6
+ <%= render(filter_field, form: form) %>
7
+ </div>
8
+ <% end %>
9
+
10
+ <div>
11
+ <%= form.submit "Filter", class: "super-button super-button--border-gray mt-6" %>
12
+ </div>
13
+ <% end %>
14
+ <% end %>
@@ -0,0 +1,31 @@
1
+ <div class="super-field-group">
2
+ <%= form.fields_for(filter_type_select.field_name, filter_type_select) do |form_field| %>
3
+ <%= form_field.label(:q, filter_type_select.humanized_field_name) %>
4
+ <div class="super-input-select inline-block">
5
+ <%= form_field.select(
6
+ :op,
7
+ filter_type_select.operators,
8
+ {},
9
+ { class: "super-input super-input-select-field" }
10
+ ) %>
11
+ <div class="super-input-select-icon text-gray-700">
12
+ <span class="h-4 w-4">
13
+ <%= render "super/feather/chevron_down" %>
14
+ </span>
15
+ </div>
16
+ </div>
17
+ <div class="super-input-select mt-3">
18
+ <%= form_field.select(
19
+ :q,
20
+ filter_type_select.field_type.collection,
21
+ { include_blank: true },
22
+ { class: "super-input super-input-select-field" }
23
+ ) %>
24
+ <div class="super-input-select-icon text-gray-700">
25
+ <span class="h-4 w-4">
26
+ <%= render "super/feather/chevron_down" %>
27
+ </span>
28
+ </div>
29
+ </div>
30
+ <% end %>
31
+ </div>
@@ -0,0 +1,22 @@
1
+ <div class="super-field-group">
2
+ <%= form.fields_for(filter_type_text.field_name, filter_type_text) do |form_field| %>
3
+ <%= form_field.label(:q, filter_type_text.humanized_field_name) %>
4
+ <div class="relative inline-block">
5
+ <%= form_field.select(
6
+ :op,
7
+ filter_type_text.operators,
8
+ {},
9
+ { class: "super-input super-input-select-field" }
10
+ ) %>
11
+ <div class="super-input-select-icon text-gray-700">
12
+ <span class="h-4 w-4">
13
+ <%= render "super/feather/chevron_down" %>
14
+ </span>
15
+ </div>
16
+ </div>
17
+ <%= form_field.text_field(
18
+ :q,
19
+ class: "super-input w-full mt-3"
20
+ ) %>
21
+ <% end %>
22
+ </div>
@@ -0,0 +1,35 @@
1
+ <div class="super-field-group">
2
+ <%= form.fields_for(filter_type_timestamp.field_name, filter_type_timestamp) do |form_field| %>
3
+ <%= form_field.label(:q0, filter_type_timestamp.humanized_field_name) %>
4
+ <div class="relative inline-block mt-2">
5
+ <%= form_field.select(
6
+ :op,
7
+ filter_type_timestamp.operators,
8
+ {},
9
+ { class: "super-input super-input-select-field" }
10
+ ) %>
11
+ <div class="super-input-select-icon text-gray-700">
12
+ <span class="h-4 w-4">
13
+ <%= render "super/feather/chevron_down" %>
14
+ </span>
15
+ </div>
16
+ </div>
17
+ <div class="flex items-center mt-3">
18
+ <div class="flex-initial">
19
+ <%= form_field.text_field(
20
+ :q0,
21
+ class: "super-input w-full"
22
+ ) %>
23
+ </div>
24
+ <div class="flex-initial px-2">
25
+ &ndash;
26
+ </div>
27
+ <div class="flex-initial">
28
+ <%= form_field.text_field(
29
+ :q1,
30
+ class: "super-input w-full"
31
+ ) %>
32
+ </div>
33
+ </div>
34
+ <% end %>
35
+ </div>
@@ -1,17 +1,17 @@
1
- <%
2
- colors = {
1
+ <% if flash.any? %>
2
+ <% colors = {
3
3
  "notice" => { bg: "bg-blue-100", fg: "text-blue-400", border: "border-blue-600" },
4
4
  "alert" => { bg: "bg-red-100", fg: "text-red-400", border: "border-red-600" },
5
- }
6
- %>
5
+ } %>
7
6
 
8
- <div data-flash="anchor">
9
- <% flash.each do |level, messages| %>
10
- <% Array(messages).each do |message| %>
11
- <% color = colors[level.to_s] || colors["notice"] %>
12
- <div class="<%= "%<bg>s %<fg>s border-l-4 %<border>s p-4 mt-2" % color %>">
13
- <%= message %>
14
- </div>
7
+ <div class="mt-8">
8
+ <% flash.each do |level, messages| %>
9
+ <% Array(messages).each do |message| %>
10
+ <% color = colors[level.to_s] || colors["notice"] %>
11
+ <div class="<%= "%<bg>s %<fg>s border-l-4 %<border>s p-4 mt-2" % color %>">
12
+ <%= message %>
13
+ </div>
14
+ <% end %>
15
15
  <% end %>
16
- <% end %>
17
- </div>
16
+ </div>
17
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% if !local_assigns[:ungrouped] %>
2
+ <div class="super-field-group">
3
+ <% end %>
4
+ <%= form.check_box(column, data: { action: "toggle-pending-destruction#call" }) %>
5
+ <%= form.label(column) %>
6
+ <%= render "form_inline_errors", form: form, column: column %>
7
+ <% if !local_assigns[:ungrouped] %>
8
+ </div>
9
+ <% end %>
@@ -0,0 +1,23 @@
1
+ <% if !local_assigns[:ungrouped] %>
2
+ <div class="super-field-group">
3
+ <% end %>
4
+ <% if !local_assigns[:hide_label] %>
5
+ <%= form.label(column, class: "block") %>
6
+ <% end %>
7
+ <div class="<%= Super::ViewHelper.classes("super-input-select", ["mt-1", !local_assigns[:hide_label]]) %>">
8
+ <%= form.select(
9
+ column,
10
+ form_field_select[:collection],
11
+ { include_blank: true }.merge(form_field_select[:options] || {}),
12
+ { class: "super-input super-input-select-field" }
13
+ ) %>
14
+ <div class="super-input-select-icon text-gray-700">
15
+ <span class="h-4 w-4">
16
+ <%= render "super/feather/chevron_down" %>
17
+ </span>
18
+ </div>
19
+ </div>
20
+ <%= render "form_inline_errors", form: form, column: column %>
21
+ <% if !local_assigns[:ungrouped] %>
22
+ </div>
23
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <% if !local_assigns[:ungrouped] %>
2
+ <div class="super-field-group">
3
+ <% end %>
4
+ <% if !local_assigns[:hide_label] %>
5
+ <%= form.label(column, class: "block") %>
6
+ <% end %>
7
+ <div class="<%= Super::ViewHelper.classes(["mt-1", !local_assigns[:hide_label]]) %>">
8
+ <%= form.text_field(column, class: "super-input w-full") %>
9
+ <%= render "form_inline_errors", form: form, column: column %>
10
+ </div>
11
+ <% if !local_assigns[:ungrouped] %>
12
+ </div>
13
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <fieldset class="border border-gray-300 rounded pb-4 px-4 mt-4 shadow" data-controller="toggle-pending-destruction">
2
+ <% if form_fieldset.label.present? %>
3
+ <legend class="bg-white p-1 -mx-1"><%= form_fieldset.label %></legend>
4
+ <% end %>
5
+ <% form_fieldset.nested_fields.each do |field, type| %>
6
+ <%= render(type, form: form, column: field) %>
7
+ <% end %>
8
+ </fieldset>
@@ -0,0 +1,21 @@
1
+ <div data-controller="apply-template">
2
+ <%= form.fields_for(form_has_many.reader) do |ff| %>
3
+ <%= render "form_fieldset", form_fieldset: form_has_many, form: ff %>
4
+ <% end %>
5
+
6
+ <%=
7
+ form.fields_for(
8
+ form_has_many.reader,
9
+ form.object.public_send(form_has_many.reader).build,
10
+ child_index: "TEMPLATEINDEX"
11
+ ) do |ff|
12
+ %>
13
+ <template data-apply-template-target="template">
14
+ <%= render "form_fieldset", form_fieldset: form_has_many, form: ff %>
15
+ </template>
16
+ <% end %>
17
+
18
+ <a href="#" data-action="apply-template#call" class="super-button super-button--fill-blue mt-2 inline-block">
19
+ Add <%= form_has_many.label %>
20
+ </a>
21
+ </div>