active_element 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -0
  3. data/.strong_versions.yml +2 -0
  4. data/Gemfile +11 -2
  5. data/Gemfile.lock +230 -3
  6. data/Rakefile +1 -0
  7. data/active_element.gemspec +7 -0
  8. data/app/assets/config/active_element/manifest.js +2 -0
  9. data/app/assets/javascripts/active_element/application.js +10 -0
  10. data/app/assets/javascripts/active_element/confirm.js +67 -0
  11. data/app/assets/javascripts/active_element/form.js +61 -0
  12. data/app/assets/javascripts/active_element/json_field.js +316 -0
  13. data/app/assets/javascripts/active_element/pagination.js +18 -0
  14. data/app/assets/javascripts/active_element/search_field.js +127 -0
  15. data/app/assets/javascripts/active_element/secret.js +40 -0
  16. data/app/assets/javascripts/active_element/setup.js +36 -0
  17. data/app/assets/javascripts/active_element/theme.js +42 -0
  18. data/app/assets/stylesheets/active_element/_variables.scss +142 -0
  19. data/app/assets/stylesheets/active_element/application.scss +77 -0
  20. data/app/controllers/active_element/application_controller.rb +41 -0
  21. data/app/controllers/active_element/text_searches_controller.rb +189 -0
  22. data/app/views/active_element/components/_horizontal_tabs.html.erb +32 -0
  23. data/app/views/active_element/components/_vertical_tabs.html.erb +38 -0
  24. data/app/views/active_element/components/button.html.erb +27 -0
  25. data/app/views/active_element/components/fields/_boolean.html.erb +11 -0
  26. data/app/views/active_element/components/form/_check_box.html.erb +3 -0
  27. data/app/views/active_element/components/form/_check_boxes.html.erb +33 -0
  28. data/app/views/active_element/components/form/_field.html.erb +28 -0
  29. data/app/views/active_element/components/form/_generic_field.html.erb +3 -0
  30. data/app/views/active_element/components/form/_json.html.erb +12 -0
  31. data/app/views/active_element/components/form/_label.html.erb +17 -0
  32. data/app/views/active_element/components/form/_option_groups_summary.html.erb +17 -0
  33. data/app/views/active_element/components/form/_select.html.erb +4 -0
  34. data/app/views/active_element/components/form/_summary.html.erb +40 -0
  35. data/app/views/active_element/components/form/_templates.html.erb +85 -0
  36. data/app/views/active_element/components/form/_text_area.html.erb +4 -0
  37. data/app/views/active_element/components/form/_text_search.html.erb +16 -0
  38. data/app/views/active_element/components/form.html.erb +78 -0
  39. data/app/views/active_element/components/json.html.erb +8 -0
  40. data/app/views/active_element/components/page_description.html.erb +3 -0
  41. data/app/views/active_element/components/secret/_field.html.erb +1 -0
  42. data/app/views/active_element/components/secret/_templates.html.erb +11 -0
  43. data/app/views/active_element/components/table/_collection_row.html.erb +30 -0
  44. data/app/views/active_element/components/table/_grouped_collection.html.erb +88 -0
  45. data/app/views/active_element/components/table/_pagination.html.erb +17 -0
  46. data/app/views/active_element/components/table/_ungrouped_collection.html.erb +49 -0
  47. data/app/views/active_element/components/table/collection.html.erb +39 -0
  48. data/app/views/active_element/components/table/item.html.erb +39 -0
  49. data/app/views/active_element/components/tabs.html.erb +7 -0
  50. data/app/views/active_element/decorators/_boolean.html.erb +5 -0
  51. data/app/views/active_element/decorators/_date.html.erb +3 -0
  52. data/app/views/active_element/decorators/_datetime.html.erb +3 -0
  53. data/app/views/active_element/decorators/_time.html.erb +3 -0
  54. data/app/views/active_element/forbidden.html.erb +33 -0
  55. data/app/views/active_element/main_menu/_item.html.erb +9 -0
  56. data/app/views/active_element/navbar/_menu.html.erb +30 -0
  57. data/app/views/active_element/theme/_select.html.erb +1 -0
  58. data/app/views/active_element/theme/_templates.html.erb +6 -0
  59. data/app/views/kaminari/_first_page.html.erb +3 -0
  60. data/app/views/kaminari/_gap.html.erb +3 -0
  61. data/app/views/kaminari/_last_page.html.erb +3 -0
  62. data/app/views/kaminari/_next_page.html.erb +3 -0
  63. data/app/views/kaminari/_page.html.erb +9 -0
  64. data/app/views/kaminari/_paginator.html.erb +17 -0
  65. data/app/views/kaminari/_prev_page.html.erb +3 -0
  66. data/app/views/layouts/active_element.html.erb +65 -0
  67. data/app/views/layouts/active_element_error.html.erb +40 -0
  68. data/config/routes.rb +5 -0
  69. data/lib/active_element/active_menu_link.rb +80 -0
  70. data/lib/active_element/active_record_text_search_authorization.rb +12 -0
  71. data/lib/active_element/colorized_string.rb +33 -0
  72. data/lib/active_element/component.rb +122 -0
  73. data/lib/active_element/components/button.rb +156 -0
  74. data/lib/active_element/components/collection_table.rb +118 -0
  75. data/lib/active_element/components/form.rb +210 -0
  76. data/lib/active_element/components/item_table.rb +57 -0
  77. data/lib/active_element/components/json.rb +31 -0
  78. data/lib/active_element/components/link_helpers.rb +9 -0
  79. data/lib/active_element/components/page_description.rb +28 -0
  80. data/lib/active_element/components/secret_fields.rb +15 -0
  81. data/lib/active_element/components/tab.rb +37 -0
  82. data/lib/active_element/components/tabs.rb +35 -0
  83. data/lib/active_element/components/translations.rb +18 -0
  84. data/lib/active_element/components/util/association_mapping.rb +80 -0
  85. data/lib/active_element/components/util/decorator.rb +107 -0
  86. data/lib/active_element/components/util/display_value_mapping.rb +48 -0
  87. data/lib/active_element/components/util/field_mapping.rb +144 -0
  88. data/lib/active_element/components/util/form_field_mapping.rb +104 -0
  89. data/lib/active_element/components/util/form_value_mapping.rb +49 -0
  90. data/lib/active_element/components/util/i18n.rb +66 -0
  91. data/lib/active_element/components/util/record_mapping.rb +111 -0
  92. data/lib/active_element/components/util.rb +43 -0
  93. data/lib/active_element/components.rb +20 -0
  94. data/lib/active_element/controller_action.rb +91 -0
  95. data/lib/active_element/engine.rb +26 -0
  96. data/lib/active_element/permissions_check.rb +101 -0
  97. data/lib/active_element/rails_component.rb +40 -0
  98. data/lib/active_element/route.rb +112 -0
  99. data/lib/active_element/routes.rb +62 -0
  100. data/lib/active_element/version.rb +1 -1
  101. data/lib/active_element.rb +91 -1
  102. data/lib/tasks/active_element.rake +23 -0
  103. data/rspec-documentation/dummy +1 -0
  104. data/rspec-documentation/pages/Components/Forms.md +1 -0
  105. data/rspec-documentation/pages/Components/Tables.md +47 -0
  106. data/rspec-documentation/pages/Components/Tabs.md +1 -0
  107. data/rspec-documentation/pages/Components.md +1 -0
  108. data/rspec-documentation/pages/Decorators/Inline Decorators.md +1 -0
  109. data/rspec-documentation/pages/Decorators/View Decorators.md +1 -0
  110. data/rspec-documentation/pages/Index.md +3 -0
  111. data/rspec-documentation/pages/Util/I18n.md +1 -0
  112. data/rspec-documentation/spec_helper.rb +35 -0
  113. metadata +191 -3
@@ -0,0 +1,4 @@
1
+ <%= form.select field,
2
+ component.options_for_select(field, options),
3
+ { selected: component.value_for(field) },
4
+ { class: "form-select #{component.valid?(field) ? nil : 'is-invalid'}" } %>
@@ -0,0 +1,40 @@
1
+ <% values = [] %>
2
+ <% fields.each do |field_name, type, options| %>
3
+ <% value = component.value_for(field_name) %>
4
+
5
+ <% if type == :check_box %>
6
+
7
+ <% if ['1', true].include?(value) %>
8
+ <% values << value %>
9
+ <div class="d-inline me-3">
10
+ <i class="text-success fa-regular fa-fw fa-circle-check"></i>
11
+ <span><%= options.fetch(:label) %></span>
12
+ </div>
13
+ <% end %>
14
+
15
+ <% elsif type == :select %>
16
+ <% value = component.display_value_for_select(field_name, options) %>
17
+ <% if value.present? %>
18
+ <% values << value %>
19
+ <div class="d-inline me-3">
20
+ <span class="text-secondary"><%= options.fetch(:label) %>:</span>
21
+ <span class="ms-1"><%= value %></span>
22
+ </div>
23
+ <% end %>
24
+ <% elsif value.present? %>
25
+ <%# TODO: Handle other field types %>
26
+
27
+ <% values << value %>
28
+ <div class="d-inline me-3">
29
+ <span class="text-secondary"><%= options.fetch(:label) %>:</span>
30
+ <span class="ms-1"><%= value %></span>
31
+ </div>
32
+
33
+ <% end %>
34
+ <% end %>
35
+
36
+ <% if values.empty? %>
37
+ <div class="d-inline ms-3 me-3">
38
+ <span class="fst-italic text-secondary">(No filters applied)</span>
39
+ </div>
40
+ <% end %>
@@ -0,0 +1,85 @@
1
+ <div class="d-none" id="form-templates">
2
+ <div id="json-templates">
3
+ <ol id="json-list-group-template"></ol>
4
+
5
+ <li id="json-list-item-template" class="text-nowrap"></li>
6
+
7
+ <label id="json-form-check-label-template" class="form-check-label"></label>
8
+ <label id="json-label-template" class="mb-4 fw-bold"></label>
9
+
10
+ <div id="json-form-group-template" class="form-group p-3 text-wrap m-3"></div>
11
+ <div id="json-form-group-floating-template" class="form-floating m-3 text-wrap"></div>
12
+ <div id="json-form-check-template" class="form-check m-3"></div>
13
+
14
+ <input id="json-checkbox-field-template" type="checkbox" class="form-check-input json-checkbox-field" />
15
+
16
+ <input id="json-text-field-template" type="text" class="form-control m-1 json-text-field" />
17
+
18
+ <select id="json-select-template" class="form-select m-1 json-select-field"></select>
19
+
20
+ <%= active_element_component.destroy_button id: 'json-delete-button-template',
21
+ class: 'button-sm json-delete-button' %>
22
+
23
+ <%= active_element_component.button 'Delete Item', type: 'danger', id: 'json-delete-object-button-template',
24
+ class: 'json-delete-button w-25 float-end json-delete-object-button' %>
25
+
26
+ <%= active_element_component.button id: 'json-append-button-template',
27
+ class: 'btn-sm mt-3 mb-3 json-add-field-button' %>
28
+
29
+ <%= active_element_component.button 'Hide', id: 'json-expand-collapse-button-template',
30
+ class: 'float-end expand-collapse-button' %>
31
+
32
+ </div>
33
+
34
+ <%= active_element_component.button 'Expand Form', id: 'form-expand-button-template', class: 'mb-1 btn-sm' do %>
35
+ <i class="fa-solid fa-fw fa-up-right-and-down-left-from-center"></i>
36
+ <% end %>
37
+
38
+ <%= active_element_component.button 'Collapse Form', id: 'form-collapse-button-template', class: 'mb-1 btn-sm' do %>
39
+ <i class="fa-solid fa-fw fa-down-left-and-up-right-to-center"></i>
40
+ <% end %>
41
+
42
+
43
+ <div id="form-search-field-templates">
44
+ <input id="form-search-field-hidden-input-template" type="hidden" autocomplete="off" />
45
+ <p id="form-search-field-response-error-template" class="text-danger position-absolute validation-error-message pt-1 m-0"></p>
46
+ <div id="form-search-field-results-template" class="search-field-results d-none border border-top-0"></div>
47
+ <div id="form-search-field-results-item-template" class="search-field-result p-2"></div>
48
+ <div id="form-search-field-spinner-template" class="invisible text-end">
49
+ <div style="position: relative; float: right; width: auto; right: 0.5rem; top: -1.7rem;">
50
+ <i class="fa-solid fa-spinner fa-spin"></i>
51
+ </div>
52
+ </div>
53
+ <div id="form-search-field-clear-button-template" class="invisible text-end">
54
+ <div style="position: relative; float: right; width: auto; right: 0.5rem; top: -1.7rem;">
55
+ <i class="fa-solid fa-delete-left" style="cursor: pointer;"></i>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <div id="form-modal-template"
61
+ class="modal fade"
62
+ tabindex="-1"
63
+ aria-hidden="true">
64
+ <div class="modal-dialog modal-dialog-centered modal-xl modal-dialog-scrollable">
65
+ <div class="modal-content">
66
+ <div class="modal-header">
67
+ <h5 class="modal-title" data-field-type="modal-title"></h5>
68
+ <button type="button" class="btn-close fs-3" data-bs-dismiss="modal" aria-label="Close">
69
+ <i class="fa-regular fa-rectangle-xmark"></i>
70
+ </button>
71
+ </div>
72
+ <div class="modal-body" data-field-type="modal-body"></div>
73
+ <div class="modal-footer" data-field-type="modal-footer"></div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <div id="form-confirm-button-template">
79
+ <%= active_element_component.button 'Confirm' %>
80
+ </div>
81
+
82
+ <div id="form-cancel-button-template">
83
+ <%= active_element_component.button 'Cancel' %>
84
+ </div>
85
+ </div>
@@ -0,0 +1,4 @@
1
+ <%= form.text_area field,
2
+ value: component.value_for(field),
3
+ rows: 5,
4
+ class: "form-control #{component.valid?(field) ? nil : 'is-invalid'}" %>
@@ -0,0 +1,16 @@
1
+ <%=
2
+ form.text_field field, value: component.value_for(field),
3
+ id: "#{form_id}-#{field}-text-search",
4
+ class: "form-control #{component.valid?(field) ? nil : 'is-invalid'}",
5
+ autocomplete: 'off',
6
+ placeholder: options[:placeholder].presence || 'Search...',
7
+ data: {
8
+ field_type: 'text-search',
9
+ form_id: form_id,
10
+ search_attributes: (
11
+ options.dig(:search, :attributes) || [options.dig(:search, :attribute)]
12
+ )&.to_json,
13
+ search_value: options.dig(:search, :value),
14
+ search_model: options.dig(:search, :model)
15
+ }
16
+ %>
@@ -0,0 +1,78 @@
1
+ <% if destroy %>
2
+ <%= active_element_component.destroy_button(record, float: 'end') %>
3
+ <% end %>
4
+
5
+ <% if modal %>
6
+
7
+ <div class="mt-4 mb-5 ms-3">
8
+
9
+ <div class="p-3 pb-4 border-bottom d-inline">
10
+ <%= active_element_component.button(
11
+ title: title.presence || 'Show Form',
12
+ icon: 'arrow-up-right-dots',
13
+ data: { field_type: 'form-modal', form_id: id, form_title: title }) %>
14
+ </div>
15
+
16
+ <div class="form-summary d-inline border-bottom border-start mt-1 p-3 pb-4">
17
+ <%= render partial: 'active_element/components/form/summary',
18
+ locals: { fields: fields, component: component } %>
19
+ </div>
20
+
21
+ </div>
22
+
23
+ <% elsif title.present? && expanded == false %>
24
+
25
+ <div data-field-type="form-expand" data-form-id="<%= id %>" class="mb-3">
26
+ <span class="fs-4 align-bottom"><%= title %></span>
27
+ </div>
28
+
29
+ <% elsif title.blank? && expanded == false %>
30
+
31
+ <div data-field-type="form-expand" data-form-id="<%= id %>" class="mb-3"></div>
32
+
33
+ <% elsif title.present? %>
34
+
35
+ <span class="fs-4 align-bottom"><%= title %></span>
36
+
37
+ <% end %>
38
+
39
+ <div class="expand-collapse-form <%= modal ? 'd-none' : nil %>" id="form-wrapper-<%= id %>">
40
+ <%= form_with local: true, **kwargs, method: method, id: id, class: "#{class_name} m-3 #{modal == false && expanded == false ? 'd-none' : nil}" do |form| %>
41
+ <% if %i[top both].include?(submit_position) %>
42
+ <div class="form-group sticky-top" style="top: 0.5rem;">
43
+ <%= form.submit submit_label, class: 'btn btn-success float-end' %>
44
+ </div>
45
+ <% end %>
46
+
47
+ <% fields.each_slice(columns) do |field_group| %>
48
+ <div class="row mb-3">
49
+ <% field_group.each do |field, type, options| %>
50
+ <div class="col-sm-3">
51
+ <%= render partial: 'active_element/components/form/label',
52
+ locals: { type: type, form: form, field: field, options: options } %>
53
+ </div>
54
+
55
+
56
+ <div class="col">
57
+ <%= render partial: 'active_element/components/form/field',
58
+ locals: {
59
+ id: id,
60
+ type: type,
61
+ form: form,
62
+ field: field,
63
+ options: options,
64
+ component: component,
65
+ record: record }
66
+ %>
67
+ </div>
68
+ <% end %>
69
+ </div>
70
+ <% end %>
71
+
72
+ <% if %i[bottom both].include?(submit_position) %>
73
+ <div class="form-group">
74
+ <%= form.submit submit_label, class: "btn btn-#{method == :post ? 'success' : 'primary'}" %>
75
+ </div>
76
+ <% end %>
77
+ <% end %>
78
+ </div>
@@ -0,0 +1,8 @@
1
+ <script type="text/javascript">
2
+ ActiveElement.jsonData = ActiveElement.jsonData || {};
3
+
4
+ ActiveElement.jsonData = {
5
+ ...ActiveElement.jsonData,
6
+ ...<%= raw({ key.camelize(:lower) => object }.to_json) %>
7
+ };
8
+ </script>
@@ -0,0 +1,3 @@
1
+ <div class="page-description p-2 fst-italic">
2
+ <%= content %>
3
+ </div>
@@ -0,0 +1 @@
1
+ <span data-field-type="secret" data-secret="<%= secret %>"></span>
@@ -0,0 +1,11 @@
1
+ <div class="d-none" id="secret-templates">
2
+ <span id="secret-show-button-template">
3
+ <a href="#"><i class="fa-solid fa-fw fa-eye"></i></a>
4
+ </span>
5
+
6
+ <span id="secret-hide-button-template">
7
+ <a href="#"><i class="fa-solid fa-fw fa-eye-slash"></i></a>
8
+ </span>
9
+
10
+ <span id="secret-content-template"></span>
11
+ </div>
@@ -0,0 +1,30 @@
1
+ <tr class="<%= (index % 2).zero? ? 'even' : 'odd' %> <%= row_class_mapper.call(item) %>">
2
+ <% fields.each do |field, class_mapper, label, value_mapper| %>
3
+ <td class="align-middle <%= class_mapper.call(item) %>">
4
+ <% if component.secret_field?(field) %>
5
+ <%= controller.helpers.render partial: 'active_element/components/secret/field',
6
+ locals: { secret: value_mapper.call(item), label: label } %>
7
+ <% else %>
8
+ <%= value_mapper.call(item) %>
9
+ <% end %>
10
+ </td>
11
+ <% end %>
12
+
13
+ <% if show %>
14
+ <td class="<%= "#{class_name}-show" %> action-column text-end">
15
+ <%= active_element_component.show_button(item, show, class: 'btn-sm') %>
16
+ </td>
17
+ <% end %>
18
+
19
+ <% if edit %>
20
+ <td class="<%= "#{class_name}-edit" %> action-column text-end">
21
+ <%= active_element_component.edit_button(item, show, class: 'btn-sm') %>
22
+ </td>
23
+ <% end %>
24
+
25
+ <% if destroy %>
26
+ <td class="<%= "#{class_name}-destroy" %> action-column text-end">
27
+ <%= active_element_component.destroy_button(item, destroy, class: 'btn-sm') %>
28
+ </td>
29
+ <% end %>
30
+ </tr>
@@ -0,0 +1,88 @@
1
+ <div class="container grouped-table">
2
+ <div class="row">
3
+ <div class="col-sm-2">
4
+ <% if group_title == true %>
5
+ <h4><%= i18n.label(grouping_field).pluralize %></h4>
6
+ <% elsif group_title.present? %>
7
+ <h4><%= group_title %></h4>
8
+ <% end %>
9
+
10
+ <ol class="list-group pt-2 <%= group_title.present? ? 'mt-3' : nil %> sticky-top">
11
+ <% grouped_collection.each do |group_identifier, group| %>
12
+ <li class="list-group-item d-flex justify-content-between align-items-start">
13
+
14
+ <div class="ms-2 me-auto pe-3">
15
+ <a style="font-size: 0.8rem;"
16
+ href="#<%= "#{class_name}-collection-group-#{group_identifier}" %>">
17
+ <%= i18n.label(group_identifier) %>
18
+ </a>
19
+ </div>
20
+
21
+ <a href="#<%= "#{class_name}-collection-group-#{group_identifier}" %>">
22
+ <span class="badge bg-primary rounded-pill">
23
+ <%= group.size %>
24
+ </span>
25
+ </a>
26
+
27
+ </li>
28
+ <% end %>
29
+ </ol>
30
+ </div>
31
+
32
+ <div class="col ms-3 ps-4 border-start">
33
+ <table class="table <%= class_name %>">
34
+ <thead class="sticky-top">
35
+ <tr>
36
+ <% fields.each do |field, _class_mapper, label, _value_mapper| %>
37
+ <th>
38
+ <%= label %>
39
+ </th>
40
+ <% end %>
41
+
42
+ <% if show %>
43
+ <th></th>
44
+ <% end %>
45
+
46
+ <% if edit %>
47
+ <th></th>
48
+ <% end %>
49
+
50
+ <% if destroy %>
51
+ <th></th>
52
+ <% end %>
53
+ </tr>
54
+ </thead>
55
+
56
+ <tbody>
57
+ <% grouped_collection.each do |group_identifier, group| %>
58
+
59
+ <tr>
60
+ <th class="collection-group-separator" colspan="<%= fields.size + (show ? 1 : 0) + (edit ? 1 : 0) + (destroy ? 1 : 0) %>">
61
+ <%= i18n.label(group_identifier) %>
62
+ <a name="<%= "#{class_name}-collection-group-#{group_identifier}" %>"></a>
63
+ </th>
64
+ <% fields.each do %>
65
+
66
+ <% end %>
67
+ </tr>
68
+
69
+ <% group.sort.each_with_index do |item, index| %>
70
+ <%= render partial: 'active_element/components/table/collection_row',
71
+ locals: {
72
+ component: component,
73
+ item: item,
74
+ fields: fields,
75
+ index: index,
76
+ class_name: class_name,
77
+ show: show,
78
+ edit: edit,
79
+ destroy: destroy,
80
+ row_class_mapper: row_class_mapper
81
+ } %>
82
+ <% end %>
83
+ <% end %>
84
+ </tbody>
85
+ </table>
86
+ </div>
87
+ </div>
88
+ </div>
@@ -0,0 +1,17 @@
1
+ <div class="row ms-3 pt-3 pb-3 row-cols-lg-auto">
2
+ <div class="col-auto">
3
+ <%= paginate collection %>
4
+ </div>
5
+
6
+ <div class="col-auto mt-2 pe-0">
7
+ Displaying
8
+ </div>
9
+ <div class="col-auto">
10
+ <%= select_tag 'collection-table-page-size-selector',
11
+ options_for_select(page_sizes, page_size),
12
+ class: 'form-select form-inline' %>
13
+ </div>
14
+ <div class="col-auto mt-2 ps-0">
15
+ of <%= number_to_human(collection.total_count) %> <%= 'record'.pluralize(collection.total_count) %>.
16
+ </div>
17
+ </div>
@@ -0,0 +1,49 @@
1
+ <table class="<%= class_name %> table" style="<%= style %>">
2
+ <thead>
3
+ <tr>
4
+ <% fields.each do |field, class_name, label, _mapper, options| %>
5
+ <th class="text-nowrap">
6
+ <%= label %>
7
+ <% if options[:description].present? %>
8
+ <button type="button"
9
+ style="background: none; border: none; outline: 0; position: absolute; margin-top: 0.3rem"
10
+ data-bs-toggle="popover"
11
+ data-bs-content="<%= options[:description] %>">
12
+ <i class="text-secondary fa-solid fa-circle-info"></i>
13
+ </button>
14
+ <% end %>
15
+ </th>
16
+ <% end %>
17
+
18
+ <% if show %>
19
+ <th></th>
20
+ <% end %>
21
+
22
+ <% if edit %>
23
+ <th></th>
24
+ <% end %>
25
+
26
+ <% if destroy %>
27
+ <th></th>
28
+ <% end %>
29
+ </tr>
30
+ </thead>
31
+
32
+ <tbody>
33
+ <% collection.each_with_index do |item, index| %>
34
+ <%= render partial: 'active_element/components/table/collection_row',
35
+ locals: {
36
+ component: component,
37
+ item: item,
38
+ index: index,
39
+ fields: fields,
40
+ class_name: class_name,
41
+ show: show,
42
+ edit: edit,
43
+ destroy: destroy,
44
+ row_class_mapper: row_class_mapper
45
+ } %>
46
+ <% end %>
47
+ </tbody>
48
+ </table>
49
+
@@ -0,0 +1,39 @@
1
+ <% if new %>
2
+ <%= active_element_component.new_button(collection, float: 'end', class: 'mb-3') %>
3
+ <% end %>
4
+
5
+ <% if display_pagination %>
6
+ <%=
7
+ render partial: 'active_element/components/table/pagination',
8
+ locals: { collection: collection, params: params, page_size: page_size, page_sizes: page_sizes }
9
+ %>
10
+ <% end %>
11
+
12
+ <% if group %>
13
+ <%= render partial: 'active_element/components/table/grouped_collection',
14
+ locals: { component: component,
15
+ collection: collection,
16
+ fields: fields,
17
+ grouped_collection: component.grouped_collection,
18
+ grouping_field: group,
19
+ group_title: group_title,
20
+ show: show,
21
+ edit: edit,
22
+ destroy: destroy,
23
+ class_name: class_name,
24
+ style: style,
25
+ i18n: i18n,
26
+ row_class_mapper: row_class_mapper } %>
27
+ <% else %>
28
+ <%= render partial: 'active_element/components/table/ungrouped_collection',
29
+ locals: { component: component,
30
+ collection: collection,
31
+ fields: fields,
32
+ show: show,
33
+ edit: edit,
34
+ destroy: destroy,
35
+ class_name: class_name,
36
+ style: style,
37
+ i18n: i18n,
38
+ row_class_mapper: row_class_mapper } %>
39
+ <% end %>
@@ -0,0 +1,39 @@
1
+ <% if destroy && item.present? %>
2
+ <%= active_element_component.destroy_button(item, destroy, float: 'end') %>
3
+ <% end %>
4
+
5
+ <% if edit && item.present? %>
6
+ <%= active_element_component.edit_button(item, edit, float: 'end') %>
7
+ <% end %>
8
+
9
+ <% if new %>
10
+ <%= active_element_component.new_button(item, float: 'end', class: 'mb-3') %>
11
+ <% end %>
12
+
13
+ <table class="<%= class_name %> table" style="<%= style %>">
14
+ <tbody>
15
+ <% fields.each do |field, class_mapper, label, value_mapper, options| %>
16
+ <tr>
17
+ <th>
18
+ <%= label %>
19
+ <% if options[:description].present? %>
20
+ <button type="button"
21
+ style="background: none; border: none; outline: 0; position: absolute; margin-top: 0.3rem"
22
+ data-bs-toggle="popover"
23
+ data-bs-content="<%= options[:description] %>">
24
+ <i class="text-secondary fa-solid fa-circle-info"></i>
25
+ </button>
26
+ <% end %>
27
+ </th>
28
+ <td class="<%= class_mapper.call(item) %>">
29
+ <% if component.secret_field?(field) %>
30
+ <%= controller.helpers.render partial: 'active_element/components/secret/field',
31
+ locals: { secret: value_mapper.call(item), label: label } %>
32
+ <% else %>
33
+ <%= value_mapper.call(item) %>
34
+ <% end %>
35
+ </td>
36
+ </tr>
37
+ <% end %>
38
+ </tbody>
39
+ </table>
@@ -0,0 +1,7 @@
1
+ <div class="p-3">
2
+ <% if tabs.size > 10 %>
3
+ <%= controller.helpers.render partial: 'active_element/components/vertical_tabs', locals: { tabs: tabs, class_name: class_name } %>
4
+ <% else %>
5
+ <%= controller.helpers.render partial: 'active_element/components/horizontal_tabs', locals: { tabs: tabs, class_name: class_name } %>
6
+ <% end %>
7
+ </div>
@@ -0,0 +1,5 @@
1
+ <% if value %>
2
+ <i class="text-success fa-regular fa-fw fa-circle-check align-center align-middle"></i>
3
+ <% else %>
4
+ <i class="text-danger fa-regular fa-fw fa-circle-xmark align-center align-middle"></i>
5
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% if value.present? %>
2
+ <%= value.strftime('%F') %>
3
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% if value.present? %>
2
+ <%= value.strftime('%c') %>
3
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% if value.present? %>
2
+ <%= value.strftime('%T') %>
3
+ <% end %>
@@ -0,0 +1,33 @@
1
+ <h1>Forbidden</h1>
2
+
3
+ <h2>You do not have access to this page</h2>
4
+
5
+ <hr/>
6
+
7
+ <% if missing_permissions.present? %>
8
+ <p>Your user account requires the following permission(s) to view this content:</p>
9
+
10
+ <div>
11
+ <ul style="list-style-type: none;">
12
+ <% missing_permissions.each do |missing_permission| %>
13
+ <li class="font-monospace"><%= missing_permission %></li>
14
+ <% end %>
15
+ </ul>
16
+ </div>
17
+
18
+ <hr/>
19
+ <% end %>
20
+
21
+ <p>Please contact your administrator if you need access to this page, or <%= link_to 'go back', request.referrer %> to the previous page and try a different option.</p>
22
+
23
+ <% if alternatives.present? %>
24
+ <div class="alternative-links">
25
+ <h5>Alternatively, try one of the following suggestions:</h5>
26
+
27
+ <ul class="pt-1" style="list-style-type: none">
28
+ <% alternatives.first(5).each do |route| %>
29
+ <li class="mt-1 font-monospace"><%= link_to route.title, route.path %></li>
30
+ <% end %>
31
+ </ul>
32
+ </div>
33
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% if item.separator? %>
2
+ <li>
3
+ <div class="menu-item-separator">
4
+ <div class="menu-item-separator-line"></div>
5
+ </div>
6
+ </li>
7
+ <% else %>
8
+ <li><%= link_to item.name, item.url, class: 'menu-section-dropdown-item dropdown-item' %></li>
9
+ <% end %>
@@ -0,0 +1,30 @@
1
+ <div class="application-menu sticky-top navbar navbar-dark bg-dark navbar-expand-lg">
2
+ <%= render partial: 'active_element/theme/select' %>
3
+ <div class="container p-0">
4
+ <a class="navbar-brand" href="#"><%= ActiveElement.application_title %></a>
5
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
6
+ <span class="navbar-toggler-icon"></span>
7
+ </button>
8
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
9
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
10
+ <% ActiveElement.navbar_items(current_user).each do |navbar_item| %>
11
+ <li class="nav-item">
12
+ <%=
13
+ link_to navbar_item.fetch(:title) { navbar_item.fetch(:label) },
14
+ navbar_item.fetch(:path),
15
+ class: "nav-link #{
16
+ ActiveElement.active_path_class(
17
+ user: current_user,
18
+ current_navbar_item: navbar_item,
19
+ current_path: request.path,
20
+ controller_path: controller_path,
21
+ action_name: action_name)
22
+ }"
23
+ %>
24
+ </li>
25
+ <% end %>
26
+ </li>
27
+ </ul>
28
+ </div>
29
+ </div>
30
+ </div>
@@ -0,0 +1 @@
1
+ <div id="theme-select"></div>
@@ -0,0 +1,6 @@
1
+ <div id="theme-templates" class="d-none">
2
+ <div id="theme-select-buttons">
3
+ <a id="theme-dark-button-template" href="#" class="dark-theme" data-theme-switch-to="light"><i class="fa-regular fa-fw fa-moon"></i></a>
4
+ <a id="theme-light-button-template" href="#" class="light-theme" data-theme-switch-to="dark"><i class="fa-regular fa-fw fa-sun"></i></a>
5
+ </div>
6
+ </div>
@@ -0,0 +1,3 @@
1
+ <li class="page-item">
2
+ <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link' %>
3
+ </li>