headmin 0.5.1 → 0.5.4

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -2
  3. data/Gemfile +14 -0
  4. data/Gemfile.lock +79 -2
  5. data/app/assets/javascripts/headmin/controllers/media_controller.js +237 -0
  6. data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +110 -0
  7. data/app/assets/javascripts/headmin/controllers/remote_modal_controller.js +9 -0
  8. data/app/assets/javascripts/headmin/controllers/textarea_controller.js +34 -0
  9. data/app/assets/javascripts/headmin/index.js +8 -0
  10. data/app/assets/javascripts/headmin.js +294 -0
  11. data/app/assets/stylesheets/headmin/forms/file.scss +40 -5
  12. data/app/assets/stylesheets/headmin/forms/media.scss +10 -0
  13. data/app/assets/stylesheets/headmin/forms.scss +1 -0
  14. data/app/assets/stylesheets/headmin/layout/sidebar.scss +0 -1
  15. data/app/assets/stylesheets/headmin/media/index.scss +9 -0
  16. data/app/assets/stylesheets/headmin/media.scss +1 -0
  17. data/app/assets/stylesheets/headmin/overrides/redactorx.scss +1 -1
  18. data/app/assets/stylesheets/headmin/table.scss +8 -0
  19. data/app/assets/stylesheets/headmin/vendor/{tom-select-bootstrap.css → tom-select-bootstrap.scss} +0 -1
  20. data/app/assets/stylesheets/headmin.css +61 -8
  21. data/app/assets/stylesheets/headmin.scss +1 -0
  22. data/app/controllers/headmin/media_controller.rb +52 -0
  23. data/app/controllers/headmin_controller.rb +2 -0
  24. data/app/helpers/headmin/form_helper.rb +2 -2
  25. data/app/models/concerns/headmin/field.rb +2 -2
  26. data/app/models/concerns/headmin/fieldable.rb +19 -10
  27. data/app/models/concerns/headmin/form/hintable.rb +6 -1
  28. data/app/models/headmin/filter/association.rb +86 -0
  29. data/app/models/headmin/filter/association_view.rb +74 -0
  30. data/app/models/headmin/filter/base.rb +5 -2
  31. data/app/models/headmin/filter/boolean_view.rb +1 -0
  32. data/app/models/headmin/filter/date.rb +49 -1
  33. data/app/models/headmin/filter/date_view.rb +1 -0
  34. data/app/models/headmin/filter/flatpickr_view.rb +1 -0
  35. data/app/models/headmin/filter/number_view.rb +1 -0
  36. data/app/models/headmin/filter/operator_view.rb +3 -1
  37. data/app/models/headmin/filter/options_view.rb +1 -0
  38. data/app/models/headmin/filter/text_view.rb +1 -0
  39. data/app/models/headmin/form/association_view.rb +102 -0
  40. data/app/models/headmin/form/blocks_view.rb +4 -1
  41. data/app/models/headmin/form/file_view.rb +0 -8
  42. data/app/models/headmin/form/flatpickr_view.rb +2 -1
  43. data/app/models/headmin/form/media_item_view.rb +39 -0
  44. data/app/models/headmin/form/media_view.rb +137 -0
  45. data/app/models/headmin/form/select_view.rb +2 -1
  46. data/app/models/headmin/form/textarea_view.rb +6 -1
  47. data/app/models/headmin/thumbnail_view.rb +40 -19
  48. data/app/models/view_model.rb +4 -0
  49. data/app/views/examples/admin.html.erb +8 -8
  50. data/app/views/examples/auth.html.erb +2 -2
  51. data/app/views/headmin/_breadcrumbs.html.erb +2 -2
  52. data/app/views/headmin/_dropdown.html.erb +1 -1
  53. data/app/views/headmin/_filters.html.erb +12 -7
  54. data/app/views/headmin/_pagination.html.erb +2 -2
  55. data/app/views/headmin/_popup.html.erb +4 -4
  56. data/app/views/headmin/_table.html.erb +1 -1
  57. data/app/views/headmin/_thumbnail.html.erb +33 -9
  58. data/app/views/headmin/dropdown/_button.html.erb +2 -2
  59. data/app/views/headmin/dropdown/_item.html.erb +2 -2
  60. data/app/views/headmin/dropdown/_list.html.erb +3 -3
  61. data/app/views/headmin/dropdown/_locale.html.erb +5 -5
  62. data/app/views/headmin/filters/_association.html.erb +24 -0
  63. data/app/views/headmin/filters/_options.html.erb +1 -1
  64. data/app/views/headmin/filters/filter/_button.html.erb +2 -2
  65. data/app/views/headmin/filters/filter/_null_select.html.erb +2 -2
  66. data/app/views/headmin/forms/_association.html.erb +30 -0
  67. data/app/views/headmin/forms/_errors.html.erb +1 -1
  68. data/app/views/headmin/forms/_file.html.erb +10 -11
  69. data/app/views/headmin/forms/_hint.html.erb +6 -1
  70. data/app/views/headmin/forms/_media.html.erb +60 -0
  71. data/app/views/headmin/forms/_repeater.html.erb +18 -16
  72. data/app/views/headmin/forms/_textarea.html.erb +1 -1
  73. data/app/views/headmin/forms/_wrapper.html.erb +0 -1
  74. data/app/views/headmin/forms/fields/_list.html.erb +6 -4
  75. data/app/views/headmin/forms/media/_item.html.erb +38 -0
  76. data/app/views/headmin/forms/media/_validation.html.erb +10 -0
  77. data/app/views/headmin/forms/repeater/_row.html.erb +17 -15
  78. data/app/views/headmin/heading/_title.html.erb +2 -2
  79. data/app/views/headmin/layout/_main.html.erb +2 -0
  80. data/app/views/headmin/layout/_remote_modal.html.erb +1 -0
  81. data/app/views/headmin/layout/_sidebar.html.erb +1 -1
  82. data/app/views/headmin/media/_item.html.erb +16 -0
  83. data/app/views/headmin/media/_media_item_modal.html.erb +51 -0
  84. data/app/views/headmin/media/_modal.html.erb +35 -0
  85. data/app/views/headmin/media/create.turbo_stream.erb +5 -0
  86. data/app/views/headmin/media/index.html.erb +3 -0
  87. data/app/views/headmin/media/show.html.erb +9 -0
  88. data/app/views/headmin/media/update.turbo_stream.erb +3 -0
  89. data/app/views/headmin/nav/_dropdown.html.erb +7 -7
  90. data/app/views/headmin/nav/_item.html.erb +5 -5
  91. data/app/views/headmin/nav/item/_locale.html.erb +6 -6
  92. data/app/views/headmin/pagination/_per_page.html.erb +7 -7
  93. data/app/views/headmin/pagination/kaminari/_first_page.html.erb +2 -2
  94. data/app/views/headmin/pagination/kaminari/_gap.html.erb +1 -1
  95. data/app/views/headmin/pagination/kaminari/_last_page.html.erb +2 -2
  96. data/app/views/headmin/pagination/kaminari/_next_page.html.erb +3 -3
  97. data/app/views/headmin/pagination/kaminari/_page.html.erb +2 -2
  98. data/app/views/headmin/pagination/kaminari/_paginator.html.erb +1 -1
  99. data/app/views/headmin/pagination/kaminari/_prev_page.html.erb +2 -2
  100. data/app/views/headmin/table/_actions.html.erb +9 -9
  101. data/app/views/headmin/table/_body.html.erb +1 -1
  102. data/app/views/headmin/table/actions/_action.html.erb +4 -4
  103. data/app/views/headmin/table/actions/_export.html.erb +1 -1
  104. data/app/views/headmin/table/body/_association.html.erb +17 -3
  105. data/app/views/headmin/table/body/_boolean.erb +4 -4
  106. data/app/views/headmin/table/body/_date.html.erb +2 -2
  107. data/app/views/headmin/table/body/_image.html.erb +18 -0
  108. data/app/views/headmin/table/body/_string.html.erb +1 -1
  109. data/app/views/headmin/table/head/_cell.html.erb +1 -1
  110. data/app/views/headmin/table/head/cell/_asc.html.erb +2 -2
  111. data/app/views/headmin/table/head/cell/_default.html.erb +1 -1
  112. data/app/views/headmin/table/head/cell/_desc.html.erb +1 -1
  113. data/app/views/headmin/views/devise/confirmations/_new.html.erb +1 -1
  114. data/app/views/headmin/views/devise/passwords/_edit.html.erb +1 -1
  115. data/app/views/headmin/views/devise/passwords/_new.html.erb +1 -1
  116. data/app/views/headmin/views/devise/registrations/_edit.html.erb +5 -5
  117. data/app/views/headmin/views/devise/registrations/_new.html.erb +1 -1
  118. data/app/views/headmin/views/devise/sessions/_new.html.erb +1 -1
  119. data/app/views/headmin/views/devise/shared/_links.html.erb +14 -20
  120. data/app/views/headmin/views/devise/unlocks/_new.html.erb +1 -1
  121. data/config/locales/activerecord/en.yml +1 -0
  122. data/config/locales/activerecord/nl.yml +1 -0
  123. data/config/locales/devise/nl.yml +1 -1
  124. data/config/locales/headmin/filters/en.yml +3 -1
  125. data/config/locales/headmin/filters/nl.yml +2 -0
  126. data/config/locales/headmin/forms/en.yml +8 -0
  127. data/config/locales/headmin/forms/nl.yml +8 -0
  128. data/config/locales/headmin/media/en.yml +23 -0
  129. data/config/locales/headmin/media/nl.yml +23 -0
  130. data/config/locales/headmin/table/en.yml +2 -0
  131. data/config/locales/headmin/table/nl.yml +2 -0
  132. data/config/routes.rb +10 -0
  133. data/lib/generators/templates/views/layouts/auth.html.erb +2 -2
  134. data/lib/headmin/version.rb +1 -1
  135. data/package.json +1 -1
  136. metadata +34 -3
@@ -45,16 +45,15 @@
45
45
  next unless attachment
46
46
  filename = attachment.blob.filename.to_s
47
47
  size = number_to_human_size(attachment.blob.byte_size)
48
- src = attachment.image? ? url_for(attachment.variant(resize_to_fill: [file.thumbnail_width, file.thumbnail_height])) : url_for(attachment)
49
48
  %>
50
49
  <div class="h-form-file-thumbnail" title="<%= "#{filename} (#{size})" %>" data-file-preview-target="thumbnail">
51
- <%= ff.hidden_field(:id, disabled: file.destroy) %>
52
- <%= ff.hidden_field(:_destroy, data: {'file-preview-target': 'thumbnailDestroy'}, disabled: file.destroy) %>
53
- <%= render "headmin/thumbnail", src: src, width: file.thumbnail_width, height: file.thumbnail_height %>
50
+ <%= ff.hidden_field(:id, disabled: !file.destroy) %>
51
+ <%= ff.hidden_field(:_destroy, data: {"file-preview-target": "thumbnailDestroy"}, disabled: !file.destroy) %>
52
+ <%= render "headmin/thumbnail", file: attachment %>
54
53
 
55
54
  <% if file.destroy %>
56
55
  <div class="h-form-file-thumbnail-remove" data-action="click->file-preview#remove" data-file-preview-name-param="<%= filename %>">
57
- <%= bootstrap_icon('x') %>
56
+ <%= bootstrap_icon("x") %>
58
57
  </div>
59
58
  <% end %>
60
59
  </div>
@@ -62,12 +61,12 @@
62
61
 
63
62
  <!-- Placeholder -->
64
63
  <% if file.dropzone %>
65
- <div class="h-dropzone-placeholder <%= 'd-none' if file.attachments.any? %>" data-file-preview-target="placeholder" style="height: <%= file.thumbnail_height %>px;">
66
- <%= t('headmin.forms.file.placeholder', count: file.number_of_files) %>
64
+ <div class="h-dropzone-placeholder <%= "d-none" if file.attachments.any? %>" data-file-preview-target="placeholder" style="height: 100px;">
65
+ <%= t("headmin.forms.file.placeholder", count: file.number_of_files) %>
67
66
  </div>
68
67
  <% else %>
69
- <div class="h-form-file-thumbnail <%= 'd-none' if file.attachments.any? %>" title="<%= t('headmin.forms.file.not_found') %>" data-file-preview-target="placeholder">
70
- <%= render "headmin/thumbnail", width: file.thumbnail_width, height: file.thumbnail_height, icon: "plus" %>
68
+ <div class="h-form-file-thumbnail <%= "d-none" if file.attachments.any? %>" title="<%= t("headmin.forms.file.not_found") %>" data-file-preview-target="placeholder">
69
+ <%= render "headmin/thumbnail", icon: "plus" %>
71
70
  </div>
72
71
  <% end %>
73
72
  </div>
@@ -76,11 +75,11 @@
76
75
  <!-- Template -->
77
76
  <template data-file-preview-target="template">
78
77
  <div class="h-form-file-thumbnail" title="" data-file-preview-target="thumbnail">
79
- <%= render "headmin/thumbnail", src: nil, width: file.thumbnail_width, height: file.thumbnail_height %>
78
+ <%= render "headmin/thumbnail" %>
80
79
 
81
80
  <% if file.destroy %>
82
81
  <div class="h-form-file-thumbnail-remove" data-action="click->file-preview#remove">
83
- <%= bootstrap_icon('x') %>
82
+ <%= bootstrap_icon("x") %>
84
83
  </div>
85
84
  <% end %>
86
85
  </div>
@@ -13,4 +13,9 @@
13
13
  hint = Headmin::Form::HintView.new(local_assigns)
14
14
  %>
15
15
 
16
- <div class="form-text"><%= raw(hint.content) %></div>
16
+ <div class="form-text d-flex justify-content-between">
17
+ <div><%= raw(hint.content) %></div>
18
+ <% if hint.maxlength %>
19
+ <div data-textarea-target="count"></div>
20
+ <% end %>
21
+ </div>
@@ -0,0 +1,60 @@
1
+ <%
2
+ # headmin/forms/media
3
+ #
4
+ # ==== Required parameters
5
+ # * +attribute+ - Name of the attribute of the form model
6
+ # * +form+ - Form object
7
+ #
8
+ # ==== Optional parameters
9
+ # * +destroy+ - Adds delete buttons to the preview thumbnails
10
+ # * +hint+ - Informative text to assist with data input. HTML markup is allowed.
11
+ # * +label+ - Text to display inside label tag. Defaults to the attribute name. Set to false if you don"t want to show a label.
12
+ # * +min+ - Limit the selection to a minimum amount of items.
13
+ # * +max+ - Limit the selection to a maximum amount of items.
14
+ # * +sort+ - Allow sorting by dragging items. `active_storage_attachments` must have a position column.
15
+ # * +wrapper+ - Hash with all options for the surrounding html tag
16
+ # * +width+ - Width of the thumbnail
17
+ # * +height+ - Height of the thumbnail
18
+ #
19
+ # ==== References
20
+ # https://headmin.dev/docs/forms/media
21
+ #
22
+ # ==== Examples
23
+ # Basic version
24
+ # <%= form_with do |form| %#>
25
+ # <%= render "headmin/forms/media", form: form, attribute: :file %#>
26
+ # <% end %#>
27
+
28
+ media = Headmin::Form::MediaView.new(local_assigns)
29
+ %>
30
+
31
+ <%= render "headmin/forms/wrapper", media.wrapper_options do %>
32
+ <%= render "headmin/forms/label", media.label_options if media.prepend_label? %>
33
+ <div class="h-form-file-thumbnails" data-media-target="thumbnails">
34
+ <%= render "headmin/forms/media/validation", media.custom_validation_options %>
35
+
36
+ <!-- Render previews for attachments -->
37
+ <%= form.fields_for(media.nested_attribute, media.association_object) do |ff| %>
38
+ <%= render "headmin/forms/media/item", media.item_options.merge(form: ff, url: headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max)) %>
39
+ <% end %>
40
+
41
+ <!-- Placeholder -->
42
+ <div class="<%= "d-none" if media.attachments.any? %>" data-media-target="placeholder">
43
+ <a href="<%= headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max) %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
44
+ <%= render "headmin/thumbnail", media.thumbnail_options %>
45
+ </a>
46
+ </div>
47
+ </div>
48
+
49
+ <!-- Template -->
50
+ <% association_object = ActiveStorage::Attachment.new %>
51
+ <template data-media-target="template" data-template-id-regex="<%= association_object.object_id %>">
52
+ <%= form.fields_for(media.nested_attribute, ActiveStorage::Attachment.new, child_index: association_object.object_id) do |ff| %>
53
+ <%= render "headmin/forms/media/item", media.item_options.merge(form: ff, url: headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max)) %>
54
+ <% end %>
55
+ </template>
56
+
57
+ <%= render "headmin/forms/validation", media.validation_options if media.validate? %>
58
+ <%= render "headmin/forms/hint", media.hint_options if media.hint? %>
59
+ <%= render "headmin/forms/label", media.label_options if media.append_label? %>
60
+ <% end %>
@@ -2,12 +2,13 @@
2
2
  # headmin/forms/repeater
3
3
  #
4
4
  # ==== Options
5
- # * +form</tt> - Form object
6
- # * +attribute</tt> - Name of the attribute of the form model
7
- # * +header</tt> - Name of the template to use as header
5
+ # * +form+ - Form object
6
+ # * +attribute+ - Name of the attribute of the form model
7
+ # * +header+ - Name of the template to use as header
8
8
  # * +label+ - Text to show as label. Label will be hidden if value is false
9
- # * +templates</tt> - List of all views that can be used as a template for a new block
10
- # * +flush</tt> - Set to true if you want the list items to sit flush with its parent.
9
+ # * +templates+ - List of all views that can be used as a template for a new block
10
+ # * +flush+ - Set to true if you want the list items to sit flush with its parent.
11
+ # * +row+ - Pass hash with options to pass to the row template.
11
12
  #
12
13
  # ==== Examples
13
14
  # # Basic version
@@ -21,12 +22,12 @@
21
22
  # <% end %#>
22
23
  #
23
24
  # # With fixed header row. A header row can be used to show the labels, so you can omit them in the repeated fields
24
- # <% render "headmin/forms/repeater", form: form, attribute: :questions, header: 'admin/questions/header' do |question| %#>
25
+ # <% render "headmin/forms/repeater", form: form, attribute: :questions, header: "admin/questions/header" do |question| %#>
25
26
  # <% render "admin/questions/fields, form: :question" %#>
26
27
  # <% end %#>
27
28
  #
28
29
  # # Allow more than one type of fields to be inserted. You must specify the templates as an array of view paths
29
- # <% templates = ['admin/questions/fields/type_1', 'admin/questions/fields/type_2'] %#>
30
+ # <% templates = ["admin/questions/fields/type_1", "admin/questions/fields/type_2"] %#>
30
31
  # <% render "headmin/forms/repeater", form: form, attribute: :questions, templates: templates do |question| %#>
31
32
  # <% render "admin/questions/fields, form: :question" %#>
32
33
  # <% end %#>
@@ -36,13 +37,14 @@
36
37
  header = local_assigns.has_key?(:header) ? header : nil
37
38
  templates = local_assigns.has_key?(:templates) ? templates : []
38
39
  flush = local_assigns.has_key?(:flush) ? flush : true
40
+ row_options = local_assigns.has_key?(:row) ? row : {}
39
41
 
40
- template_names = templates.map { |template| File.basename(template, '.html.erb') }
41
- template_names = template_names.any? ? template_names : ['new']
42
+ template_names = templates.map { |template| File.basename(template, ".html.erb") }
43
+ template_names = template_names.any? ? template_names : ["new"]
42
44
  object_model = form.object.class
43
45
  association_model = object_model.reflect_on_association(attribute).class_name.constantize
44
46
  association_object = association_model.new
45
- with_positions = association_object.attributes.keys.include?('position')
47
+ with_positions = association_object.attributes.keys.include?("position")
46
48
  associations = form.object.send(attribute)
47
49
  associations = with_positions ? associations.order(:position) : associations
48
50
  repeater_id = form.object_id
@@ -55,7 +57,7 @@
55
57
  <%= render "headmin/forms/label", form: form, attribute: attribute, text: label, required: required %>
56
58
  <% end %>
57
59
 
58
- <ul class="repeater list-group <%= 'list-group-flush' if flush %>" data-controller="repeater" data-repeater-target="list" data-repeater-id-value="<%= repeater_id %>">
60
+ <ul class="repeater list-group <%= "list-group-flush" if flush %> mb-3" data-controller="repeater" data-repeater-target="list" data-repeater-id-value="<%= repeater_id %>">
59
61
 
60
62
  <!-- Header -->
61
63
  <% if header %>
@@ -66,7 +68,7 @@
66
68
 
67
69
  <!-- Rows -->
68
70
  <%= form.fields_for attribute, associations do |ff| %>
69
- <%= render "headmin/forms/repeater/row", pass_thru: pass_thru, form: ff, repeater_id: repeater_id do %>
71
+ <%= render "headmin/forms/repeater/row", row_options.merge(pass_thru: pass_thru, form: ff, repeater_id: repeater_id) do %>
70
72
  <%= yield(ff) %>
71
73
  <% end %>
72
74
  <% end %>
@@ -76,7 +78,7 @@
76
78
 
77
79
  <!-- Empty notice -->
78
80
  <div class="text-secondary invisible" data-repeater-target="empty">
79
- <%= t('.empty') %>
81
+ <%= t(".empty") %>
80
82
  </div>
81
83
 
82
84
  <!-- Button -->
@@ -88,8 +90,8 @@
88
90
  data-popup-pass-thru="<%= pass_thru %>"
89
91
  data-action="click->repeater#resetButtonIndices click->popup#open"
90
92
  >
91
- <%= bootstrap_icon('plus') %>
92
- <%= t('.add', name: association_model.model_name.human) %>
93
+ <%= bootstrap_icon("plus") %>
94
+ <%= t(".add", name: association_model.model_name.human) %>
93
95
  </div>
94
96
 
95
97
  <!-- Popup -->
@@ -114,7 +116,7 @@
114
116
  <% template_names.each do |name| %>
115
117
  <template data-repeater-target="template" data-template-name="<%= name %>" data-template-id-regex="<%= association_object.object_id %>">
116
118
  <%= form.fields_for attribute, association_object, child_index: association_object.object_id do |ff| %>
117
- <%= render "headmin/forms/repeater/row", form: ff, pass_thru: pass_thru, repeater_id: repeater_id do %>
119
+ <%= render "headmin/forms/repeater/row", row_options.merge(form: ff, pass_thru: pass_thru, repeater_id: repeater_id) do %>
118
120
  <% yield(ff, name) %>
119
121
  <% end %>
120
122
  <% end %>
@@ -34,6 +34,6 @@
34
34
  <%= form.text_area(textarea.attribute, textarea.input_options) %>
35
35
  <% end %>
36
36
  <%= render "headmin/forms/validation", textarea.validation_options if textarea.validate? %>
37
- <%= render "headmin/forms/hint", textarea.hint_options if textarea.hint? %>
37
+ <%= render "headmin/forms/hint", textarea.hint_options if textarea.hint? || textarea.maxlength? %>
38
38
  <%= render "headmin/forms/label", textarea.label_options if textarea.append_label? %>
39
39
  <% end %>
@@ -1,5 +1,4 @@
1
1
  <% wrapper = Headmin::Form::WrapperView.new(local_assigns) %>
2
-
3
2
  <% if wrapper.bypass %>
4
3
  <%= yield %>
5
4
  <% else %>
@@ -19,8 +19,10 @@
19
19
  %>
20
20
 
21
21
  <%= render "headmin/forms/fields/base", form: form, name: name, field_type: :list do |list, field_label| %>
22
- <%= render "headmin/forms/repeater", form: list, attribute: :fields, label: show_label ? label || field_label : false, flush: false do |field| %>
23
- <%= field.hidden_field :field_type, value: :group %>
24
- <%= yield field %>
25
- <% end %>
22
+ <div class="mb-3">
23
+ <%= render "headmin/forms/repeater", form: list, attribute: :fields, label: show_label ? label || field_label : false, flush: false do |field| %>
24
+ <%= field.hidden_field :field_type, value: :group %>
25
+ <%= yield field %>
26
+ <% end %>
27
+ </div>
26
28
  <% end %>
@@ -0,0 +1,38 @@
1
+ <%
2
+ # headmin/forms/media/item
3
+ #
4
+ # ==== Required parameters
5
+ # * +form+ - Form object
6
+ # * +url+ - URL for the media modal
7
+ #
8
+ # ==== Optional parameters
9
+ # * +sort+ - Allow sorting by dragging items. `active_storage_attachments` must have a position column.
10
+ # * +width+ - Width of the thumbnail
11
+ # * +height+ - Height of the thumbnail
12
+ #
13
+
14
+ media_item = Headmin::Form::MediaItemView.new(local_assigns)
15
+ %>
16
+
17
+ <div class="h-form-file-thumbnail media-drag-sort-handle" title="<%= "#{media_item.filename} (#{media_item.size})" %>" data-media-target="item">
18
+ <%= form.hidden_field(:id) %>
19
+ <%= form.hidden_field(:blob_id) %>
20
+ <%= form.hidden_field(:position, value: media_item.position_value) if media_item.sort %>
21
+ <%= form.hidden_field(:_destroy) %>
22
+
23
+ <a href="<%= media_item.url %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
24
+ <%= render "headmin/thumbnail", media_item.thumbnail_options %>
25
+ </a>
26
+
27
+ <div class="h-form-file-thumbnail-actions">
28
+ <!-- Edit -->
29
+ <a href="<%= headmin_media_item_url(id: media_item.id) %>" class="h-form-file-thumbnail-edit" data-turbo-frame="remote_modal" data-media-target="editButton">
30
+ <%= bootstrap_icon("pencil") %>
31
+ </a>
32
+
33
+ <!-- Remove -->
34
+ <div class="h-form-file-thumbnail-remove" data-action="click->media#destroy">
35
+ <%= bootstrap_icon("x") %>
36
+ </div>
37
+ </div>
38
+ </div>
@@ -0,0 +1,10 @@
1
+ <!-- Custom validation field -->
2
+ <%= form.text_field :"validation_#{attribute}",
3
+ name: nil,
4
+ value: nil,
5
+ class: "h-form-media-validation",
6
+ data: {
7
+ "media-target": "validationInput",
8
+ "min-message": t(".min", count: min),
9
+ "max-message": t(".max", count: max),
10
+ } %>
@@ -7,9 +7,10 @@
7
7
 
8
8
  draggable = form.object.respond_to?(:position)
9
9
  destroyable = form.object.respond_to?(:destroy)
10
+ class_names = local_assigns.has_key?(:class) ? local_assigns[:class] : "repeater-row list-group-item"
10
11
  %>
11
12
 
12
- <div class="repeater-row list-group-item"
13
+ <div class="<%= class_names %>"
13
14
  data-repeater-target="row"
14
15
  data-row-index="<%= form.options[:child_index] %>"
15
16
  data-new-record="<%= form.object.new_record? %>"
@@ -18,34 +19,35 @@
18
19
  <%= form.hidden_field :id %>
19
20
  <%= form.hidden_field :_destroy if destroyable %>
20
21
  <%= form.hidden_field :position if draggable %>
21
- <%= yield %>
22
22
 
23
23
  <!-- Drag handle -->
24
24
  <% if draggable %>
25
25
  <div class="repeater-row-handle">
26
- <%= bootstrap_icon('grip-vertical') %>
26
+ <%= bootstrap_icon("grip-vertical") %>
27
27
  </div>
28
28
  <% end %>
29
29
 
30
30
  <!-- Add button-->
31
31
  <div
32
- class="repeater-row-add btn btn-link"
33
- title="<%= t('.add') %>"
34
- data-repeater-target="addButton"
35
- data-popup-target="button"
36
- data-popup-id="<%= "repeater-buttons-#{repeater_id}" %>"
37
- data-popup-pass-thru="<%= pass_thru %>"
38
- data-action="click->repeater#resetButtonIndices click->popup#open"
32
+ class="repeater-row-add btn btn-link"
33
+ title="<%= t(".add") %>"
34
+ data-repeater-target="addButton"
35
+ data-popup-target="button"
36
+ data-popup-id="<%= "repeater-buttons-#{repeater_id}" %>"
37
+ data-popup-pass-thru="<%= pass_thru %>"
38
+ data-action="click->repeater#resetButtonIndices click->popup#open"
39
39
  >
40
- <%= bootstrap_icon('plus-circle') %>
40
+ <%= bootstrap_icon("plus-circle") %>
41
41
  </div>
42
42
 
43
43
  <!-- Remove button-->
44
44
  <div
45
- class="repeater-row-remove btn btn-link"
46
- title="<%= t('.remove') %>"
47
- data-action="click->repeater#removeRow"
45
+ class="repeater-row-remove btn btn-link"
46
+ title="<%= t(".remove") %>"
47
+ data-action="click->repeater#removeRow"
48
48
  >
49
- <%= bootstrap_icon('dash-circle') %>
49
+ <%= bootstrap_icon("dash-circle") %>
50
50
  </div>
51
+
52
+ <%= yield %>
51
53
  </div>
@@ -13,11 +13,11 @@
13
13
  <div class="h-heading-title">
14
14
  <div class="d-flex align-items-center">
15
15
  <h1 class="me-2">
16
- <%= title ? title : t('.new') %>
16
+ <%= title ? title : t(".new") %>
17
17
  </h1>
18
18
  <% if new_link %>
19
19
  <a href="<%= new_link %>" class="btn btn-outline-primary btn-sm">
20
- <%= t('.new') %>
20
+ <%= t(".new") %>
21
21
  </a>
22
22
  <% end %>
23
23
  </div>
@@ -10,6 +10,8 @@
10
10
  class_names = local_assigns.has_key?(:class) ? local_assigns[:class] : false
11
11
  %>
12
12
 
13
+ <%= render "headmin/layout/remote_modal" %>
14
+
13
15
  <div class="main <%= class_names %>" data-controller="popup">
14
16
  <div class="container-fluid">
15
17
  <div class="row">
@@ -0,0 +1 @@
1
+ <%= turbo_frame_tag "remote_modal", target: "_top" %>
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div class="sidebar col-sm-12 col-md-1 col-lg-2 bg-dark overflow-y d-print-none">
9
9
  <nav class="navbar navbar-expand-md navbar-dark bg-dark w-100 h-100 flex-md-column">
10
- <a href="<%= local_assigns[:url] %>" class="nav-brand mb-lg-4 ms-lg-3 me-lg-auto">
10
+ <a href="<%= local_assigns[:url] %>" class="nav-brand mb-4 mt-3 me-lg-auto">
11
11
  <%= local_assigns[:logo] %>
12
12
  </a>
13
13
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#sidebar-nav" aria-controls="sidebar-nav" aria-expanded="false" aria-label="Toggle navigation">
@@ -0,0 +1,16 @@
1
+ <div data-media-modal-target="item" title="<%= "#{blob.filename} (#{l(blob.created_at, format: :long)})" %>">
2
+ <!-- Input -->
3
+ <input
4
+ id="media-item-<%= blob.id %>"
5
+ type="checkbox"
6
+ value="<%= blob.id %>"
7
+ <%= "checked" if params[:ids]&.include?(blob.id.to_s) %>
8
+ data-action="change->media-modal#inputChange"
9
+ data-media-modal-target="idCheckbox"
10
+ hidden>
11
+
12
+ <!-- Label -->
13
+ <label for="media-item-<%= blob.id %>">
14
+ <%= render "headmin/thumbnail", file: blob %>
15
+ </label>
16
+ </div>
@@ -0,0 +1,51 @@
1
+ <%= turbo_frame_tag "modal_content" do %>
2
+ <%= form_with url: headmin_media_item_url(id: @blob.id), model: @blob do |form| %>
3
+ <div class="modal-header">
4
+ <h5 class="modal-title"><%= t(".edit") %></h5>
5
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= t(".close") %>"></button>
6
+ </div>
7
+ <div class="modal-body">
8
+ <%= render "headmin/notifications" %>
9
+ <div class="row">
10
+ <div class="col-4">
11
+ <%= render "headmin/thumbnail", file: @blob %>
12
+ </div>
13
+ <div class="col-8 d-flex flex-column justify-content-center">
14
+ <p class="small text-secondary m-0">
15
+ <strong><%= t(".uploaded_at") %>:</strong>
16
+ <%= l(@blob.created_at, format: :long) %>
17
+ </p>
18
+ <p class="small text-secondary m-0">
19
+ <strong><%= t(".type") %>:</strong>
20
+ <%= @blob.content_type %>
21
+ </p>
22
+ <p class="small text-secondary m-0">
23
+ <strong><%= t(".dimensions") %>:</strong>
24
+ <% if @blob.metadata[:width] && @blob.metadata[:height] %>
25
+ <%= "#{@blob.metadata[:width]}px x #{@blob.metadata[:height]}px" %>
26
+ <% else %>
27
+ <%= t(".not_analysed") %>
28
+ <% end %>
29
+ </p>
30
+ <p class="small text-secondary m-0">
31
+ <strong><%= t(".size") %>:</strong>
32
+ <% if @blob.byte_size > 0 %>
33
+ <%= number_to_human_size(@blob.byte_size) %>
34
+ <% else %>
35
+ <%= t(".not_analysed") %>
36
+ <% end %>
37
+ </p>
38
+ </div>
39
+ </div>
40
+ <div class="row mt-3">
41
+ <div class="col-12">
42
+ <%= render "headmin/forms/text", form: form, attribute: :filename, append: "." + form.object.filename.to_s.rpartition(".").last, value: form.object.filename.to_s.rpartition(".").first %>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ <div class="modal-footer">
47
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(".close") %></button>
48
+ <%= form.submit t(".update"), class: "btn btn-primary" %>
49
+ </div>
50
+ <% end %>
51
+ <% end %>
@@ -0,0 +1,35 @@
1
+ <div class="media-modal modal fade" tabindex="-1" data-controller="remote-modal media-modal" data-name="<%= name %>" data-min="<%= min %>" data-max="<%= max %>">
2
+ <div class="modal-dialog modal-lg modal-dialog-scrollable">
3
+ <div class="modal-content">
4
+ <div class="modal-header">
5
+ <h5 class="modal-title">
6
+ <%= t(".title", count: min.to_i < 1 ? 1 : min.to_i) %>
7
+ </h5>
8
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= t(".close") %>"></button>
9
+ </div>
10
+ <div class="modal-body">
11
+ <%= turbo_frame_tag "thumbnails", class: "d-flex flex-wrap gap-2" do %>
12
+ <% @blobs.each do |blob| %>
13
+ <%= render "headmin/media/item", blob: blob %>
14
+ <% end %>
15
+ <div data-media-modal-target="placeholder" class="<%= "d-none" if !@blobs.empty? %>">
16
+ <p><%= t(".placeholder") %></p>
17
+ </div>
18
+ <% end %>
19
+ </div>
20
+ <div class="modal-footer">
21
+ <%= form_with url: headmin_media_path, multipart: true, data: {"media-modal-target": "form"}, class: "me-auto" do |form| %>
22
+ <%= form.label :files, class: "btn h-btn-outline-light" do %>
23
+ <%= bootstrap_icon("upload") %>
24
+ <%= t(".upload") %>
25
+ <%= form.file_field :files, class: "d-none", multiple: true, data: {action: "change->media-modal#submitForm"} %>
26
+ <% end %>
27
+ <% end %>
28
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(".close") %></button>
29
+ <button type="button" class="btn btn-primary" data-bs-dismiss="modal" data-action="click->media-modal#select" data-media-modal-target="selectButton">
30
+ <%= t(".select") %> (<span data-media-modal-target="count">0</span><%= t(".maximum", count: max.to_i) if max.present? %>)
31
+ </button>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </div>
@@ -0,0 +1,5 @@
1
+ <%= turbo_stream.prepend "thumbnails" do %>
2
+ <% @blobs.each do |blob| %>
3
+ <%= render "headmin/media/item", blob: blob %>
4
+ <% end %>
5
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%= turbo_frame_tag "remote_modal" do %>
2
+ <%= render "headmin/media/modal", blobs: @blobs, name: params[:name], min: params[:min], max: params[:max] %>
3
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <%= turbo_frame_tag "remote_modal" do %>
2
+ <div class="media-item-modal modal fade" tabindex="-1" data-controller="remote-modal">
3
+ <div class="modal-dialog modal-md modal-dialog-scrollable">
4
+ <div class="modal-content">
5
+ <%= render "headmin/media/media_item_modal" %>
6
+ </div>
7
+ </div>
8
+ </div>
9
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%= turbo_stream.update "modal_content" do %>
2
+ <%= render "headmin/media/media_item_modal" %>
3
+ <% end %>
@@ -9,12 +9,12 @@
9
9
  #
10
10
  # ==== Examples
11
11
  # Basic version.
12
- # <%= render "headmin/nav/dropdown", name: 'My Account', url: admin_user_path, icon: 'person-circle' do %#>
13
- # <%= render "headmin/nav/item", name: 'Edit info', url: edit_admin_user_path(current_user) %#>
14
- # <%= render "headmin/nav/item", name: 'Log out', url: destroy_user_session_path(current_user) %#>
12
+ # <%= render "headmin/nav/dropdown", name: "My Account", url: admin_user_path, icon: "person-circle" do %#>
13
+ # <%= render "headmin/nav/item", name: "Edit info", url: edit_admin_user_path(current_user) %#>
14
+ # <%= render "headmin/nav/item", name: "Log out", url: destroy_user_session_path(current_user) %#>
15
15
  # <% end %#>
16
16
 
17
- name = local_assigns.has_key?(:name) ? name : ''
17
+ name = local_assigns.has_key?(:name) ? name : ""
18
18
  icon = local_assigns.has_key?(:icon) ? icon : nil
19
19
  collapse_id = "nav-dropdown-#{name.parameterize}"
20
20
  url = local_assigns.has_key?(:url) ? url : request.url
@@ -22,13 +22,13 @@
22
22
  %>
23
23
 
24
24
  <li class="nav-item">
25
- <a class="nav-link d-flex align-items-center dropdown-toggle <%= 'active' if active %>" href="#<%= collapse_id %>" role="button" data-bs-toggle="collapse" aria-expanded="<%= active.to_s %>" aria-controls="<%= collapse_id %>">
26
- <%= bootstrap_icon(icon, class: 'me-2') if icon %>
25
+ <a class="nav-link d-flex align-items-center dropdown-toggle <%= "active" if active %>" href="#<%= collapse_id %>" role="button" data-bs-toggle="collapse" aria-expanded="<%= active.to_s %>" aria-controls="<%= collapse_id %>">
26
+ <%= bootstrap_icon(icon, class: "me-2") if icon %>
27
27
  <span class="d-block d-md-none d-lg-block">
28
28
  <%= name %>
29
29
  </span>
30
30
  </a>
31
- <ul class="collapse <%= 'show' if active %>" id="<%= collapse_id %>">
31
+ <ul class="collapse <%= "show" if active %>" id="<%= collapse_id %>">
32
32
  <%= yield %>
33
33
  </ul>
34
34
  </li>
@@ -9,20 +9,20 @@
9
9
  #
10
10
  # ==== Examples
11
11
  # Basic version.
12
- # <%= render "headmin/nav/item", name: 'Dashboard', url: admin_root_path %#>
12
+ # <%= render "headmin/nav/item", name: "Dashboard", url: admin_root_path %#>
13
13
  #
14
14
  # With icon
15
- # <%= render "headmin/nav/item", name: 'Dashboard', url: admin_root_path, icon: 'speedometer' %#>
15
+ # <%= render "headmin/nav/item", name: "Dashboard", url: admin_root_path, icon: "speedometer" %#>
16
16
 
17
- name = local_assigns.has_key?(:name) ? name : ''
17
+ name = local_assigns.has_key?(:name) ? name : ""
18
18
  icon = local_assigns.has_key?(:icon) ? icon : nil
19
19
  url = local_assigns.has_key?(:url) ? url : request.url
20
20
  active = local_assigns.has_key?(:active) ? active : current_url?(url)
21
21
  %>
22
22
 
23
23
  <li class="nav-item">
24
- <a class="nav-link d-flex align-items-center <%= 'active' if active %>" aria-current="page" href="<%= url %>">
25
- <%= bootstrap_icon(icon, class: 'me-2') if icon %>
24
+ <a class="nav-link d-flex align-items-center <%= "active" if active %>" aria-current="page" href="<%= url %>">
25
+ <%= bootstrap_icon(icon, class: "me-2") if icon %>
26
26
  <span class="d-block d-md-none d-lg-block">
27
27
  <%= name %>
28
28
  </span>
@@ -4,14 +4,14 @@
4
4
  parameters: none
5
5
  %>
6
6
 
7
- <%= render "headmin/dropdown", class: 'nav-item' do %>
8
- <%= render "headmin/dropdown/button", class: 'nav-link', id: 'nav-item-locale' do %>
9
- <%= bootstrap_icon('globe', class: 'me-2') %>
10
- <%= t('language_name', locale: ::I18n.locale) %>
7
+ <%= render "headmin/dropdown", class: "nav-item" do %>
8
+ <%= render "headmin/dropdown/button", class: "nav-link", id: "nav-item-locale" do %>
9
+ <%= bootstrap_icon("globe", class: "me-2") %>
10
+ <%= t("language_name", locale: ::I18n.locale) %>
11
11
  <% end %>
12
- <%= render "headmin/dropdown/list", id: 'nav-item-locale' do %>
12
+ <%= render "headmin/dropdown/list", id: "nav-item-locale" do %>
13
13
  <% I18n.available_locales.each do |locale| %>
14
- <%= render "headmin/dropdown/item", name: t('language_name', locale: locale), url: url_for({locale: locale.to_s}) %>
14
+ <%= render "headmin/dropdown/item", name: t("language_name", locale: locale), url: url_for({locale: locale.to_s}) %>
15
15
  <% end %>
16
16
  <% end %>
17
17
  <% end %>