trestle 0.9.1 → 0.9.2

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/bundle/trestle/bundle.css +0 -0
  3. data/app/assets/bundle/trestle/bundle.js +0 -0
  4. data/app/assets/javascripts/trestle/admin.js +1 -0
  5. data/app/assets/javascripts/trestle/i18n.js.erb +8 -0
  6. data/app/helpers/trestle/grid_helper.rb +44 -4
  7. data/app/helpers/trestle/i18n_helper.rb +8 -15
  8. data/app/helpers/trestle/table_helper.rb +7 -3
  9. data/app/helpers/trestle/title_helper.rb +11 -0
  10. data/app/helpers/trestle/toolbars_helper.rb +0 -11
  11. data/app/views/layouts/trestle/admin.html.erb +4 -4
  12. data/app/views/trestle/application/_dialog.html.erb +1 -1
  13. data/app/views/trestle/application/_header.html.erb +1 -3
  14. data/app/views/trestle/resource/edit.html.erb +2 -2
  15. data/app/views/trestle/resource/index.html.erb +1 -1
  16. data/app/views/trestle/resource/new.html.erb +1 -1
  17. data/app/views/trestle/resource/show.html.erb +2 -2
  18. data/app/views/trestle/shared/_sidebar.html.erb +1 -1
  19. data/config/locales/de.rb +18 -0
  20. data/config/locales/de.yml +101 -0
  21. data/frontend/js/components/tabs.js +2 -2
  22. data/lib/generators/trestle/resource/resource_generator.rb +15 -1
  23. data/lib/generators/trestle/resource/templates/admin.rb.erb +15 -8
  24. data/lib/trestle.rb +13 -0
  25. data/lib/trestle/adapters/active_record_adapter.rb +1 -1
  26. data/lib/trestle/admin.rb +5 -2
  27. data/lib/trestle/admin/builder.rb +5 -1
  28. data/lib/trestle/admin/controller.rb +8 -1
  29. data/lib/trestle/application_controller.rb +2 -0
  30. data/lib/trestle/configuration.rb +1 -1
  31. data/lib/trestle/controller/breadcrumbs.rb +1 -1
  32. data/lib/trestle/controller/title.rb +20 -0
  33. data/lib/trestle/controller/toolbars.rb +30 -0
  34. data/lib/trestle/form/automatic.rb +2 -2
  35. data/lib/trestle/form/field.rb +12 -2
  36. data/lib/trestle/form/fields/check_box_helpers.rb +1 -1
  37. data/lib/trestle/form/fields/collection_select.rb +1 -1
  38. data/lib/trestle/form/fields/date_select.rb +1 -1
  39. data/lib/trestle/form/fields/datetime_select.rb +1 -1
  40. data/lib/trestle/form/fields/select.rb +1 -1
  41. data/lib/trestle/form/fields/static_field.rb +5 -1
  42. data/lib/trestle/form/fields/tag_select.rb +1 -1
  43. data/lib/trestle/form/fields/time_select.rb +1 -1
  44. data/lib/trestle/form/fields/time_zone_select.rb +1 -1
  45. data/lib/trestle/navigation/item.rb +4 -0
  46. data/lib/trestle/resource.rb +1 -0
  47. data/lib/trestle/resource/controller.rb +9 -166
  48. data/lib/trestle/resource/controller/actions.rb +133 -0
  49. data/lib/trestle/resource/controller/data_methods.rb +52 -0
  50. data/lib/trestle/resource/controller/redirection.rb +23 -0
  51. data/lib/trestle/resource/controller/toolbar.rb +11 -0
  52. data/lib/trestle/resource/toolbar.rb +29 -0
  53. data/lib/trestle/table.rb +6 -0
  54. data/lib/trestle/version.rb +1 -1
  55. data/trestle.gemspec +1 -0
  56. data/yarn.lock +518 -496
  57. metadata +27 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3ae26567281306d04d1da21b166f39c068cfa459bcc29c9eaa883637707a762
4
- data.tar.gz: 6d3cf1573074b377051afcb03639f468f2346913bc05ac44020981a7292e066d
3
+ metadata.gz: 8ec0dcceb14b2184fb07724245272ba57926c91058ed554cb19653149aa5a61d
4
+ data.tar.gz: ec4b694ff3649bb301dffad74fba26fce2dc222c52ffeb48ff205fcda95c1e98
5
5
  SHA512:
6
- metadata.gz: 59b8c63538cfbe37b17530821cb908d288523ff0706b3cfdcb377a14037aa8dd961b7187846fe501ece13989646c19a41c751381ef2b2f90568f22e703d04e48
7
- data.tar.gz: c4d3ac261e2e754e942ec02be90cb4c4eff03be3ea6167a0ef1075d2c54bab5e5214bba15997703bc3cd4f99169881cba493cb7f27e38e0a4c630a778dda8cfb
6
+ metadata.gz: e07e61553b61a4f0ed93efc32178290de7623a37fb58e8caf4ce170e8b09cf1d15b1765b7cd28544e99c841fa5de25bd78d02220308da2ce98cc02fb2af36938
7
+ data.tar.gz: 58eb9ad54d778ace1d86fc8e7f39b069604d15049af4f3bd05b6cef8c629b9eec0b11589e44e5f63a371632d1f45396873eb3ac9c395c4ae002108c47f77e2b5
@@ -1,2 +1,3 @@
1
1
  //= require trestle/bundle
2
+ //= require trestle/i18n
2
3
  //= require trestle/custom
@@ -0,0 +1,8 @@
1
+ <% flatpickr_locale_conversions = { ca: "cat", el: "gr", nb: "no", vi: "vn" } %>
2
+
3
+ <% I18n.available_locales.each do |locale| %>
4
+ <% Trestle.i18n_fallbacks(locale).each do |candidate| %>
5
+ <% candidate = flatpickr_locale_conversions[candidate] || candidate %>
6
+ <% require_asset("trestle/flatpickr/#{candidate}") rescue nil %>
7
+ <% end %>
8
+ <% end %>
@@ -1,9 +1,44 @@
1
1
  module Trestle
2
2
  module GridHelper
3
- def row
4
- content_tag(:div, class: "row") { yield }
3
+ # Renders a row div, one of the building blocks of Bootstrap's grid system.
4
+ # https://getbootstrap.com/docs/4.4/layout/grid/
5
+ #
6
+ # attrs - Hash of attributes that will be passed to the tag (e.g. id, data, class).
7
+ #
8
+ # Examples
9
+ #
10
+ # <%= row do %>
11
+ # <%= col do %>Column content<% end %>
12
+ # <% end %>
13
+ #
14
+ # <%= row class: "row-cols-2", id: "my-row" do %> ...
15
+ #
16
+ # Returns a HTML-safe String.
17
+ def row(attrs={})
18
+ defaults = Trestle::Options.new(class: ["row"])
19
+ options = defaults.merge(attrs)
20
+
21
+ content_tag(:div, options) { yield }
5
22
  end
6
23
 
24
+ # Renders a column div, one of the building blocks of Bootstrap's grid system.
25
+ # https://getbootstrap.com/docs/4.4/layout/grid/
26
+ #
27
+ # Column divs should always be rendered inside of a row div.
28
+ #
29
+ # Examples
30
+ #
31
+ # # Standard column - evenly fills available space
32
+ # <%= col do %>...<% end %>
33
+ #
34
+ # # Column spans 4 (out of 12) grid columns (i.e. 1/3 width) at all breakpoints
35
+ # <%= col 4 do %> ...
36
+ #
37
+ # # Column spans 6 grid columns at smallest breakpoint, 4 at md breakpoint
38
+ # # and above (portrait tablet) and 3 at xl breakpoint and above (desktop)
39
+ # <%= col 6, md: 4, xl: 3 do %> ...
40
+ #
41
+ # Returns a HTML-safe String.
7
42
  def col(columns=nil, breakpoints={})
8
43
  if columns.is_a?(Hash)
9
44
  breakpoints = columns
@@ -17,8 +52,13 @@ module Trestle
17
52
  content_tag(:div, class: classes) { yield }
18
53
  end
19
54
 
20
- def divider
21
- content_tag(:hr)
55
+ # Renders an <HR> (horizontal rule) HTML tag.
56
+ #
57
+ # attrs - Hash of attributes that will be passed to the tag (e.g. id, data, class).
58
+ #
59
+ # Returns a HTML-safe String.
60
+ def divider(attrs={})
61
+ tag(:hr, attrs)
22
62
  end
23
63
  end
24
64
  end
@@ -1,20 +1,13 @@
1
1
  module Trestle
2
2
  module I18nHelper
3
- def i18n_fallbacks(locale=I18n.locale)
4
- if I18n.respond_to?(:fallbacks)
5
- I18n.fallbacks[locale]
6
- elsif locale.to_s.include?("-")
7
- fallback = locale.to_s.split("-").first
8
- [locale, fallback]
9
- else
10
- [locale]
11
- end
12
- end
13
-
14
- def default_translation(key)
15
- translate(key, locale: :en)
16
- rescue I18n::InvalidLocale
17
- key.split(".").last.humanize
3
+ def i18n_javascript_translations
4
+ Trestle.config.javascript_i18n_keys.map { |key|
5
+ begin
6
+ [key, t(key, raise: true)]
7
+ rescue I18n::MissingTranslationData
8
+ nil
9
+ end
10
+ }.compact
18
11
  end
19
12
  end
20
13
  end
@@ -32,10 +32,14 @@ module Trestle
32
32
  end
33
33
 
34
34
  table = Table::Builder.build(options, &block)
35
- elsif name.is_a?(Trestle::Table)
36
- table = name
37
35
  else
38
- table = admin.tables.fetch(name) { raise ArgumentError, "Unable to find table named #{name.inspect}" }
36
+ if name.is_a?(Trestle::Table)
37
+ table = name
38
+ else
39
+ table = admin.tables.fetch(name) { raise ArgumentError, "Unable to find table named #{name.inspect}" }
40
+ end
41
+
42
+ table = table.with_options(options.reverse_merge(sortable: false))
39
43
  end
40
44
 
41
45
  collection ||= options[:collection] || table.options[:collection]
@@ -0,0 +1,11 @@
1
+ module Trestle
2
+ module TitleHelper
3
+ def title
4
+ content_for(:title) || default_title
5
+ end
6
+
7
+ def default_title
8
+ action_name.titleize
9
+ end
10
+ end
11
+ end
@@ -1,16 +1,5 @@
1
1
  module Trestle
2
2
  module ToolbarsHelper
3
- def toolbar(name, options={}, &block)
4
- toolbar = (toolbars[name.to_s] ||= Toolbar.new)
5
- toolbar.clear! if options[:clear]
6
- toolbar.prepend(&block) if block_given?
7
- toolbar
8
- end
9
-
10
- def toolbars
11
- @_toolbars ||= {}
12
- end
13
-
14
3
  def render_toolbar(toolbar, *args)
15
4
  result = toolbar.groups(self, *args).map do |items|
16
5
  if items.many?
@@ -12,7 +12,7 @@
12
12
 
13
13
  <%= csrf_meta_tags %>
14
14
 
15
- <title><%= [yield(:title), Trestle.config.site_title].join(' :: ') %></title>
15
+ <title><%= [title, Trestle.config.site_title].join(' :: ') %></title>
16
16
 
17
17
  <%= favicon_link_tag Trestle.config.favicon if Trestle.config.favicon %>
18
18
 
@@ -26,11 +26,11 @@
26
26
  <%= javascript_include_tag "trestle/admin", 'data-turbolinks-track': 'reload' %>
27
27
 
28
28
  <%= javascript_tag nonce: true do %>
29
- <% Trestle.config.javascript_i18n_keys.each do |key| %>
30
- Trestle.i18n['<%= key %>'] = "<%= escape_javascript(t(key, default: default_translation(key))) %>";
29
+ <% i18n_javascript_translations.each do |key, value| %>
30
+ Trestle.i18n['<%= key %>'] = "<%= escape_javascript(value) %>";
31
31
  <% end %>
32
32
 
33
- Trestle.localize(<%= i18n_fallbacks.map { |l| "'#{l}'" }.join(", ").html_safe %>);
33
+ Trestle.localize(<%= Trestle.i18n_fallbacks.map { |l| "'#{l}'" }.join(", ").html_safe %>);
34
34
  <% end %>
35
35
 
36
36
  <%= hook :javascripts %>
@@ -1,5 +1,5 @@
1
1
  <div class="modal-header">
2
- <h4 class="modal-title"><%= content_for(:title) %></h4>
2
+ <h4 class="modal-title"><%= title %></h4>
3
3
  <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
4
4
  </div>
5
5
 
@@ -1,8 +1,6 @@
1
1
  <header class="content-header">
2
2
  <div class="content-header-title">
3
- <h1>
4
- <%= content_for(:title) %>
5
- </h1>
3
+ <h1><%= title %></h1>
6
4
 
7
5
  <% unless local_assigns.fetch(:hide_breadcrumbs, false) %>
8
6
  <ol class="breadcrumb">
@@ -4,11 +4,11 @@
4
4
  <% breadcrumb(title) %>
5
5
 
6
6
  <% toolbar(:primary) do |t| %>
7
- <%= t.button admin.t("buttons.save", default: "Save %{model_name}"), style: :success if admin.actions.include?(:update) %>
7
+ <%= t.save if admin.actions.include?(:update) %>
8
8
  <% end %>
9
9
 
10
10
  <% toolbar(:secondary) do |t| %>
11
- <%= t.link admin.t("buttons.delete", default: "Delete %{model_name}"), instance, action: :destroy, method: :delete, style: :danger, icon: "fa fa-trash", data: { toggle: "confirm-delete", placement: "bottom" } if admin.actions.include?(:destroy) %>
11
+ <%= t.delete if admin.actions.include?(:destroy) %>
12
12
  <% end %>
13
13
 
14
14
  <%= trestle_form_for instance, url: admin.actions.include?(:update) ? admin.instance_path(instance, action: :update) : "#", method: :patch do |f| %>
@@ -1,7 +1,7 @@
1
1
  <% content_for(:title, admin.t("titles.index", default: "Listing %{pluralized_model_name}")) %>
2
2
 
3
3
  <% toolbar(:primary) do |t| %>
4
- <%= t.link admin.t("buttons.new", default: "New %{model_name}"), action: :new, style: :light, icon: "fa fa-plus", class: "btn-new-resource" if admin.actions.include?(:new) %>
4
+ <%= t.new if admin.actions.include?(:new) %>
5
5
  <% end %>
6
6
 
7
7
  <% content_for(:utilities) do %>
@@ -4,7 +4,7 @@
4
4
  <% breadcrumb(title) %>
5
5
 
6
6
  <% toolbar(:primary) do |t| %>
7
- <%= t.button admin.t("buttons.save", default: "Save %{model_name}"), style: :success if admin.actions.include?(:create) %>
7
+ <%= t.save if admin.actions.include?(:create) %>
8
8
  <% end %>
9
9
 
10
10
  <%= trestle_form_for instance, url: admin.path(:create), method: :post do |f| %>
@@ -4,11 +4,11 @@
4
4
  <% breadcrumb(title) unless admin.singular? %>
5
5
 
6
6
  <% toolbar(:primary) do |t| %>
7
- <%= t.button admin.t("buttons.save", default: "Save %{model_name}"), style: :success if admin.actions.include?(:update) %>
7
+ <%= t.save if admin.actions.include?(:update) %>
8
8
  <% end %>
9
9
 
10
10
  <% toolbar(:secondary) do |t| %>
11
- <%= t.link admin.t("buttons.delete", default: "Delete %{model_name}"), instance, action: :destroy, method: :delete, style: :danger, icon: "fa fa-trash", data: { toggle: "confirm-delete", placement: "bottom" } if admin.actions.include?(:destroy) %>
11
+ <%= t.delete if admin.actions.include?(:destroy) %>
12
12
  <% end %>
13
13
 
14
14
  <%= trestle_form_for instance, url: admin.actions.include?(:update) ? admin.instance_path(instance, action: :update) : "#", method: :patch do |f| %>
@@ -20,7 +20,7 @@
20
20
 
21
21
  <% items.each do |item| %>
22
22
  <li<% if current_navigation_item?(item) %> class="active"<% end %>>
23
- <%= link_to item.path do %>
23
+ <%= link_to item.path, item.html_options do %>
24
24
  <%= icon("nav-icon", item.icon) %>
25
25
  <span class="nav-label"><%= item.label %></span>
26
26
 
@@ -0,0 +1,18 @@
1
+ {
2
+ de: {
3
+ date: {
4
+ formats: {
5
+ trestle_date: "%-d. %b. %Y",
6
+ trestle_calendar: "%d.%m.%Y"
7
+ }
8
+ },
9
+
10
+ time: {
11
+ formats: {
12
+ trestle_date: "%-d. %b. %Y",
13
+ trestle_time: "%H:%M",
14
+ trestle_time_with_seconds: "%H:%M:%S"
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,101 @@
1
+ de:
2
+ trestle:
3
+ title: "Trestle Admin"
4
+ footer: "Powered by Trestle"
5
+ version: "Version"
6
+
7
+ helpers:
8
+ page_entries_info:
9
+ one_page:
10
+ display_entries:
11
+ zero: "Kein %{entry_name} vorhanden"
12
+ one: "<strong>1</strong> %{entry_name}"
13
+ other: "<strong>%{count}</strong> %{entry_name}"
14
+
15
+ more_pages:
16
+ display_entries: "%{entry_name} <strong>%{first}&nbsp;-&nbsp;%{last}</strong> von <b>%{total}</b>"
17
+
18
+ onboarding:
19
+ welcome: "Willkommen bei Trestle"
20
+ no_admins: "Um zu beginnen, erstelle eine Trestle.resource in <code>app/admin</code>."
21
+ no_template: "Um dieses Tempalte anzupassen, erstelle <code>%{path}</code>."
22
+ no_form: "Definiere einen form block oder erstelle ein Partial <code>_form.html</code>."
23
+
24
+ ui:
25
+ toggle_navigation: "Navigation ein-/ausblenden"
26
+ toggle_sidebar: "Seitenleiste ein-/ausblenden"
27
+ toggle_dropdown: "Menü ein-/ausblenden"
28
+
29
+ dialog:
30
+ error: "Fehler: Die Aktion konnte nicht erfolgreich ausgeführt werden."
31
+
32
+ confirmation:
33
+ title: "Sind Sie sicher?"
34
+ delete: "Löschen"
35
+ cancel: "Abbrechen"
36
+
37
+ file:
38
+ choose_file: "Datei wählen..."
39
+ browse: "Durchsuchen"
40
+
41
+ admin:
42
+ titles:
43
+ index: "Liste von %{pluralized_model_name}"
44
+ new: "%{model_name} (neu)"
45
+ edit: "%{model_name} (bearbeiten)"
46
+
47
+ buttons:
48
+ new: "Neu"
49
+ save: "Speichern"
50
+ delete: "Löschen"
51
+ show: "Details"
52
+ edit: "Bearbeiten"
53
+ ok: "OK"
54
+
55
+ breadcrumbs:
56
+ home: "Home"
57
+
58
+ flash:
59
+ create:
60
+ success:
61
+ title: "Erfolgreich erstellt"
62
+ message: "%{model_name} wurde erfolgreich erstellt."
63
+
64
+ failure:
65
+ title: "Warnung: %{model_name} nicht erstellt"
66
+ message: "Bitte korrigieren Sie die unten angezeigten Fehler."
67
+
68
+ update:
69
+ success:
70
+ title: "Erfolgreich bearbeitet"
71
+ message: "%{model_name} wurde erfolgreich gespeichert."
72
+
73
+ failure:
74
+ title: "Warnung: %{model_name} nicht gespeichert"
75
+ message: "Bitte korrigieren Sie die unten angezeigten Fehler."
76
+
77
+ destroy:
78
+ success:
79
+ title: "Erfolgreich gelöscht"
80
+ message: "%{model_name} wurde erfolgreich gelöscht."
81
+
82
+ failure:
83
+ title: "Warnung: Nicht gelöscht"
84
+ message: "%{model_name} konnte nicht gelöscht werden."
85
+
86
+ table:
87
+ headers:
88
+ id: "ID"
89
+
90
+ form:
91
+ select:
92
+ prompt: "- %{attribute_name} auswählen -"
93
+
94
+ format:
95
+ blank: "n. a."
96
+
97
+ datepicker:
98
+ formats:
99
+ date: "d.m.Y"
100
+ datetime: "d.m.Y H:i"
101
+ time: "H:i"
@@ -2,7 +2,7 @@
2
2
 
3
3
  import $ from 'jquery'
4
4
 
5
- import { init, ready } from '../core/events'
5
+ import { init } from '../core/events'
6
6
 
7
7
  const TAB_SELECTOR = 'a[data-toggle="tab"]'
8
8
  const TAB_PANE_SELECTOR = '.tab-pane'
@@ -21,7 +21,7 @@ init(function (root) {
21
21
  })
22
22
 
23
23
  // Restore active tab when loading
24
- ready(function () {
24
+ init(function () {
25
25
  focusActiveTab()
26
26
  })
27
27
 
@@ -3,10 +3,24 @@ module Trestle
3
3
  class ResourceGenerator < ::Rails::Generators::NamedBase
4
4
  desc "Creates a Trestle admin resource"
5
5
 
6
+ class_option :singular, type: :boolean, default: false, desc: "Generate a singular resource"
7
+
6
8
  source_root File.expand_path("../templates", __FILE__)
7
9
 
8
10
  def create_admin
9
- template "admin.rb.erb", File.join("app/admin", class_path, "#{plural_name}_admin.rb")
11
+ template "admin.rb.erb", File.join("app/admin", class_path, "#{admin_name}_admin.rb")
12
+ end
13
+
14
+ def admin_name
15
+ singular? ? singular_name : plural_name
16
+ end
17
+
18
+ def parameter_name
19
+ singular_name.singularize
20
+ end
21
+
22
+ def singular?
23
+ options[:singular]
10
24
  end
11
25
 
12
26
  protected
@@ -1,8 +1,14 @@
1
- Trestle.resource(:<%= plural_name %><% if module? %>, scope: <%= module_name %><% end %>) do
1
+ Trestle.resource(:<%= admin_name %><% if module? %>, scope: <%= module_name %><% end %><% if singular? %>, singular: true<% end %>) do
2
2
  menu do
3
- item :<%= plural_name %>, icon: "fa fa-star"
3
+ item :<%= admin_name %>, icon: "fa fa-star"
4
4
  end
5
5
 
6
+ <%- if singular? -%>
7
+ instance do
8
+ # Override this block to define the instance that this resource represents.
9
+ raise NotImplementedError, "Singular resources must define an instance block."
10
+ end
11
+ <%- else -%>
6
12
  # Customize the table columns shown on the index view.
7
13
  #
8
14
  # table do
@@ -10,19 +16,20 @@ Trestle.resource(:<%= plural_name %><% if module? %>, scope: <%= module_name %><
10
16
  # column :created_at, align: :center
11
17
  # actions
12
18
  # end
19
+ <%- end -%>
13
20
 
14
- # Customize the form fields shown on the new/edit views.
21
+ # Customize the form fields shown on the <% if singular? %>edit view<% else %>new/edit views<% end %>.
15
22
  #
16
- # form do |<%= singular_table_name %>|
23
+ # form do |<%= parameter_name %>|
17
24
  # text_field :name
18
25
  #
19
26
  # row do
20
- # col(xs: 6) { datetime_field :updated_at }
21
- # col(xs: 6) { datetime_field :created_at }
27
+ # col { datetime_field :updated_at }
28
+ # col { datetime_field :created_at }
22
29
  # end
23
30
  # end
24
31
 
25
- # By default, all parameters passed to the update and create actions will be
32
+ # By default, all parameters passed to the <% if singular? %>update action<% else %>update and create actions<% end %> will be
26
33
  # permitted. If you do not have full trust in your users, you should explicitly
27
34
  # define the list of permitted parameters.
28
35
  #
@@ -30,6 +37,6 @@ Trestle.resource(:<%= plural_name %><% if module? %>, scope: <%= module_name %><
30
37
  # http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters
31
38
  #
32
39
  # params do |params|
33
- # params.require(:<%= singular_table_name %>).permit(:name, ...)
40
+ # params.require(:<%= parameter_name %>).permit(:name, ...)
34
41
  # end
35
42
  end