formstrap 0.1.3 → 0.2.0

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -1
  3. data/app/assets/javascripts/formstrap/controllers/file_preview_controller.js +5 -5
  4. data/app/assets/javascripts/formstrap/controllers/media_controller.js +4 -2
  5. data/app/assets/javascripts/formstrap/controllers/media_modal_controller.js +38 -2
  6. data/app/assets/javascripts/formstrap/controllers/popup_controller.js +75 -0
  7. data/app/assets/javascripts/formstrap/controllers/repeater_controller.js +4 -4
  8. data/app/assets/javascripts/formstrap/index.js +2 -0
  9. data/app/assets/javascripts/formstrap.js +1790 -129
  10. data/app/assets/stylesheets/formstrap/{forms/autocomplete.scss → autocomplete.scss} +1 -1
  11. data/app/assets/stylesheets/formstrap/{forms/file.scss → file.scss} +10 -10
  12. data/app/assets/stylesheets/formstrap/general.scss +8 -1
  13. data/app/assets/stylesheets/formstrap/media/_modal.scss +9 -0
  14. data/app/assets/stylesheets/formstrap/{forms/media.scss → media/_validation.scss} +2 -2
  15. data/app/assets/stylesheets/formstrap/media.scss +2 -1
  16. data/app/assets/stylesheets/formstrap/{forms/repeater.scss → repeater.scss} +8 -8
  17. data/app/assets/stylesheets/formstrap/shared/popup.scss +17 -0
  18. data/app/assets/stylesheets/formstrap/shared/thumbnail.scss +23 -0
  19. data/app/assets/stylesheets/formstrap/shared.scss +2 -0
  20. data/app/assets/stylesheets/formstrap/utilities/dropzone.scss +2 -2
  21. data/app/assets/stylesheets/formstrap/utilities.scss +0 -1
  22. data/app/assets/stylesheets/formstrap.css +155 -143
  23. data/app/assets/stylesheets/formstrap.scss +5 -1
  24. data/app/controllers/formstrap/media_controller.rb +6 -12
  25. data/app/helpers/application_helper.rb +20 -0
  26. data/app/models/concerns/formstrap/attachment.rb +16 -0
  27. data/app/models/concerns/formstrap/blob.rb +56 -0
  28. data/app/models/formstrap/file_view.rb +3 -3
  29. data/app/models/formstrap/media_view.rb +12 -1
  30. data/app/models/formstrap/thumbnail_view.rb +108 -0
  31. data/app/views/formstrap/_autocomplete.html.erb +1 -1
  32. data/app/views/formstrap/_file.html.erb +8 -8
  33. data/app/views/formstrap/_media.html.erb +5 -4
  34. data/app/views/formstrap/_repeater.html.erb +3 -3
  35. data/app/views/formstrap/media/_item.html.erb +10 -7
  36. data/app/views/formstrap/media/_modal.html.erb +35 -8
  37. data/app/views/formstrap/media/_thumbnail.html.erb +12 -8
  38. data/app/views/formstrap/media/_validation.html.erb +1 -1
  39. data/app/views/formstrap/media/create.turbo_stream.erb +3 -1
  40. data/app/views/formstrap/media/index.html.erb +1 -1
  41. data/app/views/formstrap/repeater/_row.html.erb +4 -4
  42. data/app/views/formstrap/shared/_popup.html.erb +1 -1
  43. data/app/views/formstrap/shared/_thumbnail.html.erb +5 -3
  44. data/config/locales/formstrap/forms/en.yml +0 -14
  45. data/config/locales/formstrap/forms/nl.yml +1 -15
  46. data/config/locales/formstrap/media/en.yml +2 -10
  47. data/config/locales/formstrap/media/nl.yml +2 -10
  48. data/config/routes.rb +0 -2
  49. data/lib/formstrap/version.rb +1 -1
  50. metadata +16 -29
  51. data/app/assets/stylesheets/formstrap/forms.scss +0 -12
  52. data/app/assets/stylesheets/formstrap/media/index.scss +0 -9
  53. data/app/assets/stylesheets/formstrap/utilities/buttons.scss +0 -27
  54. data/app/models/formstrap/blocks_view.rb +0 -43
  55. data/app/views/formstrap/_blocks.html.erb +0 -45
  56. data/app/views/formstrap/_to_ary.html.erb +0 -0
  57. data/app/views/formstrap/blocks/_modal.html.erb +0 -20
  58. data/app/views/formstrap/fields/_base.html.erb +0 -25
  59. data/app/views/formstrap/fields/_file.html.erb +0 -17
  60. data/app/views/formstrap/fields/_files.html.erb +0 -17
  61. data/app/views/formstrap/fields/_group.html.erb +0 -52
  62. data/app/views/formstrap/fields/_list.html.erb +0 -31
  63. data/app/views/formstrap/fields/_text.html.erb +0 -17
  64. data/app/views/formstrap/media/_media_item_modal.html.erb +0 -77
  65. data/app/views/formstrap/media/show.html.erb +0 -9
  66. data/app/views/formstrap/media/update.turbo_stream.erb +0 -3
  67. data/config/locales/activerecord/en.yml +0 -12
  68. data/config/locales/activerecord/nl.yml +0 -13
  69. data/config/locales/defaults/en.yml +0 -215
  70. data/config/locales/defaults/nl.yml +0 -213
  71. data/config/locales/devise/en.yml +0 -65
  72. data/config/locales/devise/nl.yml +0 -85
  73. /data/app/assets/stylesheets/formstrap/{forms/search.scss → search.scss} +0 -0
@@ -0,0 +1,20 @@
1
+ class ApplicationHelper
2
+ def show_svg(attachment, options = {})
3
+ return if !attachment.content_type.include?('svg')
4
+
5
+ attachment.open do |file|
6
+ content = file.read
7
+ doc = Nokogiri::HTML::DocumentFragment.parse content
8
+ svg = doc.at_css 'svg'
9
+
10
+ # for security
11
+ doc.search('script').each do |src|
12
+ src.remove
13
+ end
14
+
15
+ options.each { |attr, value| svg[attr.to_s] = value }
16
+
17
+ doc.to_html.html_safe
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module Formstrap
2
+ module Attachment
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ scope :not_a_variant, -> { where.not(record_type: "ActiveStorage::VariantRecord") }
6
+
7
+ def root_record
8
+ record
9
+ end
10
+
11
+ def root_record_name
12
+ "#{record.class.name} #{record.id}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,56 @@
1
+ module Formstrap
2
+ module Blob
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class << self
7
+ def search(query)
8
+ where("active_storage_blobs.filename LIKE ?", "%#{query}%")
9
+ end
10
+
11
+ def not_attached_to_variant
12
+ not_attached_to("ActiveStorage::VariantRecord")
13
+ end
14
+
15
+ def not_attached_to(arg)
16
+ case arg
17
+ when String
18
+ record_types = [arg]
19
+ when Class
20
+ record_types = [arg.to_s]
21
+ when Array
22
+ record_types = arg.map(&:to_s)
23
+ end
24
+
25
+ left_outer_joins(:attachments)
26
+ .where.not(active_storage_attachments: {record_type: record_types})
27
+ .or(is_orphan)
28
+ end
29
+
30
+ def by_mimetypes_string(mimetype_string)
31
+ return where({}) if mimetype_string.blank?
32
+ by_mimetypes(mimetype_string.split(","))
33
+ end
34
+
35
+ private
36
+
37
+ def is_orphan
38
+ left_outer_joins(:attachments)
39
+ .where(active_storage_attachments: {id: nil})
40
+ end
41
+
42
+ def by_mimetypes(mimetypes = [])
43
+ results = self
44
+
45
+ mimetypes.map.with_index do |mimetype, index|
46
+ content_type = mimetype.tr("*", "%")
47
+ query = where(arel_table[:content_type].matches(content_type))
48
+ results = (index == 0) ? query : results.or(query)
49
+ end
50
+
51
+ results
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -23,7 +23,7 @@ module Formstrap
23
23
 
24
24
  def wrapper_options
25
25
  default_wrapper_options.deep_merge({
26
- class: ["mb-3 h-form-file", ("form-floating" if float)],
26
+ class: ["mb-3 formstrap-file", ("form-floating" if float)],
27
27
  data: {
28
28
  controller: ("file-preview" if preview)
29
29
  }
@@ -57,7 +57,7 @@ module Formstrap
57
57
  def dropzone_options
58
58
  if dropzone
59
59
  {
60
- class: ["h-dropzone", validation_class],
60
+ class: ["formstrap-dropzone", validation_class],
61
61
  data: {
62
62
  controller: "dropzone"
63
63
  }
@@ -73,7 +73,7 @@ module Formstrap
73
73
  # {
74
74
  # prepend: prepend,
75
75
  # append: append,
76
- # class: "h-form-file",
76
+ # class: "formstrap-file",
77
77
  # data: {
78
78
  # controller: "#{"file-preview" if preview} #{"dropzone" if dropzone}"
79
79
  # }
@@ -30,7 +30,8 @@ module Formstrap
30
30
 
31
31
  def item_options
32
32
  options = {
33
- sort: sort
33
+ sort: sort,
34
+ url: modal_url,
34
35
  }
35
36
 
36
37
  # Don't pass width or height if it was not defined
@@ -122,6 +123,16 @@ module Formstrap
122
123
  end
123
124
  end
124
125
 
126
+ def modal_url
127
+ formstrap_media_path(name: name, ids: blob_ids, min: min, max: max, mimetype: accept, exclude_models: exclude_models)
128
+ end
129
+
130
+ def edit_modal_url(attachment)
131
+ return nil unless edit_url.present?
132
+ return edit_url unless attachment&.persisted?
133
+ edit_url.gsub(":id", attachment.id.to_s)
134
+ end
135
+
125
136
  attr_reader :accept
126
137
 
127
138
  def required
@@ -0,0 +1,108 @@
1
+ module Formstrap
2
+ class ThumbnailView < ViewModel
3
+ def class_names
4
+ class_names = [@class]
5
+ class_names << "img-thumbnail formstrap-thumbnail"
6
+ class_names.join(" ")
7
+ end
8
+
9
+ def width
10
+ if is_defined?(:width)
11
+ @width
12
+ else
13
+ @width || 100
14
+ end
15
+ end
16
+
17
+ def height
18
+ if is_defined?(:height)
19
+ @height
20
+ else
21
+ @height || 100
22
+ end
23
+ end
24
+
25
+ def blob
26
+ file.is_a?(ActiveStorage::Blob) ? file : file&.blob
27
+ end
28
+
29
+ def mime_type
30
+ blob&.content_type
31
+ end
32
+
33
+ def variant_options
34
+ if width.nil? || height.nil?
35
+ default_variant_options.merge({
36
+ resize_to_fit: [width, height]
37
+ })
38
+ else
39
+ default_variant_options.merge({
40
+ resize_to_fill: [width, height, crop: :attention]
41
+ })
42
+ end
43
+ end
44
+
45
+ def default_variant_options
46
+ {
47
+ saver: {
48
+ quality: 80
49
+ }
50
+ }
51
+ end
52
+
53
+ # Is it possible to create a ActiveStorage::Variant?
54
+ def variable?
55
+ blob&.variable?
56
+ end
57
+
58
+ def icon?
59
+ is_defined?(:icon)
60
+ end
61
+
62
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
63
+ def icon_name
64
+ return icon if icon
65
+ type_map = {
66
+ image: %w[image/bmp image/gif image/vnd.microsoft.icon image/jpeg image/png image/svg+xml image/tiff image/webp],
67
+ play: %w[video/mp4 video/mpeg video/ogg video/mp2t video/webm video/3gpp video/3gpp2],
68
+ music: %w[audio/aac audio/midi audio/x-midi audio/mpeg audio/ogg audio/opus audio/wav audio/webm audio/3gpp audio/3gpp2],
69
+ word: %w[application/msword application/vnd.openxmlformats-officedocument.wordprocessingml.document],
70
+ ppt: %w[application/vnd.ms-powerpoint application/vnd.openxmlformats-officedocument.presentationml.presentation],
71
+ excel: %w[application/vnd.ms-excel application/vnd.openxmlformats-officedocument.spreadsheetml.sheet],
72
+ slides: %w[application/vnd.oasis.opendocument.presentation],
73
+ spreadsheet: %w[application/vnd.oasis.opendocument.spreadsheet],
74
+ richtext: %w[application/vnd.oasis.opendocument.text],
75
+ zip: %w[application/zip application/x-7z-compressed application/x-bzip application/x-bzip2 application/gzip application/vnd.rar],
76
+ pdf: %w[application/pdf]
77
+ }
78
+
79
+ type = type_map.find { |key, mime_types| mime_types.include?(mime_type) }
80
+ if type
81
+ ["file", "earmark", type.first].compact.join("-")
82
+ else
83
+ "question"
84
+ end
85
+ end
86
+
87
+ def svg?
88
+ blob&.content_type == "image/svg+xml"
89
+ end
90
+
91
+ def inline_svg(options = {})
92
+ blob.open do |file|
93
+ content = file.read
94
+ doc = Nokogiri::HTML::DocumentFragment.parse content
95
+ svg = doc.at_css 'svg'
96
+
97
+ # for security
98
+ doc.search('script').each do |src|
99
+ src.remove
100
+ end
101
+
102
+ options.each { |attr, value| svg[attr.to_s] = value }
103
+
104
+ doc.to_html.html_safe
105
+ end
106
+ end
107
+ end
108
+ end
@@ -1,4 +1,4 @@
1
- <div class="h-autocomplete d-none" data-autocomplete-target="dropdown">
1
+ <div class="formstrap-autocomplete d-none" data-autocomplete-target="dropdown">
2
2
  <% if collection.any? %>
3
3
  <%= render "formstrap/autocomplete/list" do %>
4
4
  <% collection.each do |value, name| %>
@@ -36,7 +36,7 @@
36
36
  <%= render "formstrap/input_group", file.input_group_options do %>
37
37
 
38
38
  <% if file.preview %>
39
- <div class="h-form-file-thumbnails" data-file-preview-target="thumbnails">
39
+ <div class="formstrap-file-thumbnails" data-file-preview-target="thumbnails">
40
40
 
41
41
  <!-- Render previews for attachments -->
42
42
  <%= form.fields_for(file.nested_attribute) do |ff| %>
@@ -46,13 +46,13 @@
46
46
  filename = attachment.blob.filename.to_s
47
47
  size = number_to_human_size(attachment.blob.byte_size)
48
48
  %>
49
- <div class="h-form-file-thumbnail" title="<%= "#{filename} (#{size})" %>" data-file-preview-target="thumbnail">
49
+ <div class="formstrap-file-thumbnail" title="<%= "#{filename} (#{size})" %>" data-file-preview-target="thumbnail">
50
50
  <%= ff.hidden_field(:id, disabled: !file.destroy) %>
51
51
  <%= ff.hidden_field(:_destroy, data: {"file-preview-target": "thumbnailDestroy"}, disabled: !file.destroy) %>
52
52
  <%= render "formstrap/shared/thumbnail", file: attachment %>
53
53
 
54
54
  <% if file.destroy %>
55
- <div class="h-form-file-thumbnail-remove" data-action="click->file-preview#remove" data-file-preview-name-param="<%= filename %>">
55
+ <div class="formstrap-file-thumbnail-remove" data-action="click->file-preview#remove" data-file-preview-name-param="<%= filename %>">
56
56
  <%= bootstrap_icon("x") %>
57
57
  </div>
58
58
  <% end %>
@@ -61,11 +61,11 @@
61
61
 
62
62
  <!-- Placeholder -->
63
63
  <% if file.dropzone %>
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) %>
64
+ <div class="formstrap-dropzone-placeholder <%= "d-none" if file.attachments.any? %>" data-file-preview-target="placeholder" style="height: 100px;">
65
+ <%= t(".placeholder", count: file.number_of_files) %>
66
66
  </div>
67
67
  <% else %>
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">
68
+ <div class="formstrap-file-thumbnail <%= "d-none" if file.attachments.any? %>" title="<%= t(".not_found") %>" data-file-preview-target="placeholder">
69
69
  <%= render "formstrap/shared/thumbnail", icon: "plus" %>
70
70
  </div>
71
71
  <% end %>
@@ -74,11 +74,11 @@
74
74
 
75
75
  <!-- Template -->
76
76
  <template data-file-preview-target="template">
77
- <div class="h-form-file-thumbnail" title="" data-file-preview-target="thumbnail">
77
+ <div class="formstrap-file-thumbnail" title="" data-file-preview-target="thumbnail">
78
78
  <%= render "formstrap/shared/thumbnail" %>
79
79
 
80
80
  <% if file.destroy %>
81
- <div class="h-form-file-thumbnail-remove" data-action="click->file-preview#remove">
81
+ <div class="formstrap-file-thumbnail-remove" data-action="click->file-preview#remove">
82
82
  <%= bootstrap_icon("x") %>
83
83
  </div>
84
84
  <% end %>
@@ -16,6 +16,7 @@
16
16
  # * +width+ - Width of the thumbnail
17
17
  # * +height+ - Height of the thumbnail
18
18
  # * +exclude_models+ - Array of model names to exclude from selection (e.g. ["User", "Company"])
19
+ # * +edit_url+ - Placeholder link for the edit page of the attachment. Needs to contain a ":id" part, e.g. "admin_media_edit_url(":id"). This url be opened in a modal
19
20
  #
20
21
  # ==== References
21
22
  # https://headmin.dev/docs/forms/media
@@ -31,17 +32,17 @@
31
32
 
32
33
  <%= render "formstrap/wrapper", media.wrapper_options do %>
33
34
  <%= render "formstrap/label", media.label_options if media.prepend_label? %>
34
- <div class="h-form-file-thumbnails" data-media-target="thumbnails">
35
+ <div class="formstrap-file-thumbnails" data-media-target="thumbnails">
35
36
  <%= render "formstrap/media/validation", media.custom_validation_options %>
36
37
 
37
38
  <!-- Render previews for attachments -->
38
39
  <%= form.fields_for(media.nested_attribute, media.association_object) do |ff| %>
39
- <%= render "formstrap/media/item", media.item_options.merge(form: ff, url: formstrap_media_path(name: media.name, ids: media.blob_ids, min: media.min, max: media.max, mimetype: media.accept, exclude_models: media.exclude_models)) %>
40
+ <%= render "formstrap/media/item", media.item_options.merge(form: ff, edit_url: media.edit_modal_url(ff.object)) %>
40
41
  <% end %>
41
42
 
42
43
  <!-- Placeholder -->
43
44
  <div class="<%= "d-none" if media.attachments.any? %>" data-media-target="placeholder">
44
- <a href="<%= formstrap_media_path(name: media.name, ids: media.blob_ids, min: media.min, max: media.max, mimetype: media.accept, exclude_models: media.exclude_models) %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
45
+ <a href="<%= media.modal_url %>" data-turbo-frame="modal" data-media-target="modalButton">
45
46
  <%= render "formstrap/shared/thumbnail", media.thumbnail_options %>
46
47
  </a>
47
48
  </div>
@@ -51,7 +52,7 @@
51
52
  <% association_object = ActiveStorage::Attachment.new %>
52
53
  <template data-media-target="template" data-template-id-regex="<%= association_object.object_id %>">
53
54
  <%= form.fields_for(media.nested_attribute, ActiveStorage::Attachment.new, child_index: association_object.object_id) do |ff| %>
54
- <%= render "formstrap/media/item", media.item_options.merge(form: ff, url: formstrap_media_path(name: media.name, ids: media.blob_ids, min: media.min, max: media.max, mimetype: media.accept, exclude_models: media.exclude_models)) %>
55
+ <%= render "formstrap/media/item", media.item_options.merge(form: ff, edit_url: media.edit_modal_url(ff.object)) %>
55
56
  <% end %>
56
57
  </template>
57
58
 
@@ -59,7 +59,7 @@
59
59
  <%= render "formstrap/label", form: form, attribute: attribute, text: label, required: required %>
60
60
  <% end %>
61
61
 
62
- <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 %>">
62
+ <ul class="formstrap-repeater list-group <%= "list-group-flush" if flush %> mb-3" data-controller="repeater" data-repeater-target="list" data-repeater-id-value="<%= repeater_id %>">
63
63
 
64
64
  <!-- Header -->
65
65
  <% if header %>
@@ -85,7 +85,7 @@
85
85
 
86
86
  <!-- Button -->
87
87
  <div
88
- class="btn btn-sm h-btn-outline-light"
88
+ class="btn btn-sm btn-outline-secondary"
89
89
  data-repeater-target="addButton"
90
90
  data-popup-target="button"
91
91
  data-popup-id="<%= "repeater-buttons-#{repeater_id}" %>"
@@ -101,7 +101,7 @@
101
101
  <div class="d-grid gap-2">
102
102
  <% template_names.each do |name| %>
103
103
  <div
104
- class="btn btn-sm h-btn-outline-light"
104
+ class="btn btn-sm btn-outline-secondary"
105
105
  data-popup-target="button"
106
106
  data-popup-id="<%= "repeater-buttons-#{repeater_id}" %>"
107
107
  data-action="click->repeater#addRow click->popup#close"
@@ -9,29 +9,32 @@
9
9
  # * +sort+ - Allow sorting by dragging items. `active_storage_attachments` must have a position column.
10
10
  # * +width+ - Width of the thumbnail
11
11
  # * +height+ - Height of the thumbnail
12
+ # * +edit_url+ - URL for the edit modal
12
13
  #
13
14
 
14
15
  media_item = Formstrap::MediaItemView.new(local_assigns)
15
16
  %>
16
17
 
17
- <div class="h-form-file-thumbnail media-drag-sort-handle" title="<%= "#{media_item.filename} (#{media_item.size})" %>" data-media-target="item">
18
+ <div class="formstrap-file-thumbnail media-drag-sort-handle" title="<%= "#{media_item.filename} (#{media_item.size})" %>" data-media-target="item">
18
19
  <%= form.hidden_field(:id) %>
19
20
  <%= form.hidden_field(:blob_id) %>
20
21
  <%= form.hidden_field(:position, value: media_item.position_value) if media_item.sort %>
21
22
  <%= form.hidden_field(:_destroy) %>
22
23
 
23
- <a href="<%= media_item.url %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
24
+ <a href="<%= media_item.url %>" data-turbo-frame="modal" data-media-target="modalButton">
24
25
  <%= render "formstrap/shared/thumbnail", media_item.thumbnail_options %>
25
26
  </a>
26
27
 
27
- <div class="h-form-file-thumbnail-actions">
28
+ <div class="formstrap-file-thumbnail-actions">
28
29
  <!-- Edit -->
29
- <a href="<%= formstrap_media_item_path(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>
30
+ <% if media_item.edit_url %>
31
+ <a href="<%= media_item.edit_url %>" class="formstrap-file-thumbnail-edit" data-turbo-frame="modal" data-media-target="editButton">
32
+ <%= bootstrap_icon("pencil") %>
33
+ </a>
34
+ <% end %>
32
35
 
33
36
  <!-- Remove -->
34
- <div class="h-form-file-thumbnail-remove" data-action="click->media#destroy">
37
+ <div class="formstrap-file-thumbnail-remove" data-action="click->media#destroy">
35
38
  <%= bootstrap_icon("x") %>
36
39
  </div>
37
40
  </div>
@@ -1,11 +1,30 @@
1
- <div class="media-modal modal fade" tabindex="-1" data-controller="remote-modal media-modal" data-media-modal-ids-value="<%= params[:ids] ? params[:ids] : [] %>" data-name="<%= name %>" data-min="<%= min %>" data-max="<%= max %>">
1
+ <div class="formstrap-media-modal modal fade" tabindex="-1" data-controller="modal media-modal" data-media-modal-ids-value="<%= params[:ids] ? params[:ids] : [] %>" data-name="<%= name %>" data-min="<%= min %>" data-max="<%= max %>">
2
2
  <div class="modal-dialog modal-lg modal-dialog-scrollable">
3
3
  <div class="modal-content">
4
4
  <div class="modal-header">
5
5
  <h5 class="modal-title">
6
6
  <%= t(".title", count: min.to_i < 1 ? 1 : min.to_i) %>
7
7
  </h5>
8
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= t(".close") %>"></button>
8
+
9
+ <div class="modal-header-actions d-flex align-items-center gap-3">
10
+ <%= formstrap_form_with url: formstrap_media_path, method: :get, data: {turbo_frame: "modal", media_modal_target: "search"}, class: "modal-search d-flex gap-1" do |form| %>
11
+ <%= form.hidden_field :name, value: request.query_parameters["name"] %>
12
+ <%= form.hidden_field :min, value: request.query_parameters["min"] %>
13
+ <%= form.hidden_field :max, value: request.query_parameters["max"] %>
14
+ <%= form.hidden_field :mimetype, value: request.query_parameters["mimetype"] %>
15
+ <%= form.hidden_field "exclude_models[]", value: request.query_parameters["exclude_models"] %>
16
+
17
+ <% if request.query_parameters["ids"] %>
18
+ <% request.query_parameters["ids"].each do |id| %>
19
+ <%= form.hidden_field "ids[]", value: id, data: {media_modal_target: "searchId"} %>
20
+ <% end %>
21
+ <% end %>
22
+
23
+ <%= form.search :search, value: request.query_parameters["search"], class: "form-control", label: false, wrapper: {class: ""}, placeholder: t(".search") %>
24
+ <% end %>
25
+
26
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= t(".close") %>"></button>
27
+ </div>
9
28
  </div>
10
29
  <div class="modal-body">
11
30
  <%= turbo_frame_tag "thumbnails", class: "d-flex flex-wrap gap-2" do %>
@@ -14,17 +33,25 @@
14
33
  <%= render "thumbnail" %>
15
34
  <% end %>
16
35
  <% end %>
17
- <div data-media-modal-target="placeholder" class="<%= "d-none" if !@blobs.empty? %>">
36
+
37
+ <div data-media-modal-target="placeholder" class="<%= "d-none" if !@blobs.empty? && request.request_parameters["search"].nil? %>">
18
38
  <p><%= t(".placeholder") %></p>
19
39
  </div>
40
+
41
+ <div data-media-modal-target="search_no_results" class="<%= "d-none" if !@blobs.empty? || request.request_parameters["search"].present? %>">
42
+ <p><%= t(".search_no_results") %></p>
43
+ </div>
44
+ <% end %>
45
+
46
+ <% unless @blobs.empty? %>
47
+ <div class="mt-3">
48
+ <%= render "formstrap/pagination/infinite", items: @blobs %>
49
+ </div>
20
50
  <% end %>
21
- <div class="mt-3">
22
- <%= render "formstrap/pagination/infinite", items: @blobs %>
23
- </div>
24
51
  </div>
25
52
  <div class="modal-footer">
26
- <%= form_with url: formstrap_new_media_path, multipart: true, data: {"media-modal-target": "form"}, class: "me-auto" do |form| %>
27
- <%= form.label :files, class: "btn h-btn-outline-light" do %>
53
+ <%= formstrap_form_with url: formstrap_new_media_path, multipart: true, data: {"media-modal-target": "form", turbo_frame: "modal"}, class: "me-auto" do |form| %>
54
+ <%= form.label :files, class: "btn btn-outline-secondary" do %>
28
55
  <%= bootstrap_icon("upload") %>
29
56
  <%= t(".upload") %>
30
57
  <%= form.file_field :files, accept: mimetypes, class: "d-none", multiple: true, data: {action: "change->media-modal#submitForm"} %>
@@ -3,18 +3,22 @@
3
3
  <div data-media-modal-target="item" title="<%= "#{blob.filename} (#{l(blob.created_at, format: :long)})" %>">
4
4
  <!-- Input -->
5
5
  <input
6
- id="media-item-<%= blob.id %>"
7
- type="checkbox"
8
- value="<%= blob.id %>"
9
- data-action="change->media-modal#inputChange"
10
- data-media-modal-target="idCheckbox"
11
- hidden>
6
+ id="media-item-<%= blob.id %>"
7
+ type="checkbox"
8
+ value="<%= blob.id %>"
9
+ data-action="change->media-modal#inputChange"
10
+ data-media-modal-target="idCheckbox"
11
+ hidden>
12
12
 
13
13
  <!-- Label -->
14
- <label for="media-item-<%= blob.id %>">
14
+ <label for="media-item-<%= blob.id %>" class="d-flex">
15
15
  <%= render "formstrap/shared/thumbnail", file: blob %>
16
16
  </label>
17
17
  </div>
18
18
  <% else %>
19
- <%= render "formstrap/shared/thumbnail", file: nil %>
19
+ <div>
20
+ <label class="d-flex">
21
+ <%= render "formstrap/shared/thumbnail", file: nil %>
22
+ </label>
23
+ </div>
20
24
  <% end %>
@@ -2,7 +2,7 @@
2
2
  <%= form.text_field :"validation_#{attribute}",
3
3
  name: nil,
4
4
  value: nil,
5
- class: "h-form-media-validation",
5
+ class: "formstrap-media-validation",
6
6
  data: {
7
7
  "media-target": "validationInput",
8
8
  "min-message": t(".min", count: min),
@@ -1,5 +1,7 @@
1
1
  <%= turbo_stream.prepend "thumbnails" do %>
2
2
  <% @blobs.each do |blob| %>
3
- <%= render "formstrap/media/thumbnail", blob: blob %>
3
+ <turbo-frame id="blob_<%= blob.id %>">
4
+ <%= render "formstrap/media/thumbnail", blob: blob %>
5
+ </turbo-frame>
4
6
  <% end %>
5
7
  <% end %>
@@ -1,3 +1,3 @@
1
- <%= turbo_frame_tag "remote_modal" do %>
1
+ <%= turbo_frame_tag "modal" do %>
2
2
  <%= render "formstrap/media/modal", blobs: @blobs, mimetypes: @mimetypes, name: params[:name], min: params[:min], max: params[:max] %>
3
3
  <% end %>
@@ -8,7 +8,7 @@
8
8
  draggable = form.object.respond_to?(:position)
9
9
  destroyable = form.object.respond_to?(:destroy)
10
10
  error_class = form.object.errors.present? ? "border border-danger" : ""
11
- class_names = local_assigns.has_key?(:class) ? local_assigns[:class] : "repeater-row list-group-item"
11
+ class_names = local_assigns.has_key?(:class) ? local_assigns[:class] : "formstrap-repeater-row list-group-item"
12
12
  %>
13
13
  <div class="<%= class_names %> <%= error_class %>"
14
14
  data-repeater-target="row"
@@ -22,14 +22,14 @@
22
22
 
23
23
  <!-- Drag handle -->
24
24
  <% if draggable %>
25
- <div class="repeater-row-handle">
25
+ <div class="formstrap-repeater-row-handle">
26
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"
32
+ class="formstrap-repeater-row-add btn btn-link"
33
33
  title="<%= t(".add") %>"
34
34
  data-repeater-target="addButton"
35
35
  data-popup-target="button"
@@ -42,7 +42,7 @@
42
42
 
43
43
  <!-- Remove button-->
44
44
  <div
45
- class="repeater-row-remove btn btn-link"
45
+ class="formstrap-repeater-row-remove btn btn-link"
46
46
  title="<%= t(".remove") %>"
47
47
  data-action="click->repeater#removeRow"
48
48
  >
@@ -22,7 +22,7 @@
22
22
  data = local_assigns.has_key?(:data) ? data : {}
23
23
 
24
24
  options = {
25
- class: ["h-popup", "closed"],
25
+ class: ["formstrap-popup", "closed"],
26
26
  data: data.merge("popup-target": "popup", "popup-id": id)
27
27
  }
28
28
  %>
@@ -21,15 +21,17 @@
21
21
  #
22
22
  # Custom (bootstrap) icon
23
23
  # <%= render "formstrap/shared/thumbnail", file: file, icon: "file-earmark-excel" %#>
24
- thumbnail = Admin::ThumbnailView.new(local_assigns)
24
+ thumbnail = Formstrap::ThumbnailView.new(local_assigns)
25
25
  %>
26
26
 
27
27
  <div class="<%= thumbnail.class_names %>">
28
- <div class="h-thumbnail-bg" style="min-width: <%= thumbnail.width || 100 %>px; min-height: <%= thumbnail.height || 100 %>px;">
28
+ <div class="formstrap-thumbnail-bg" style="min-width: <%= thumbnail.width || 100 %>px; min-height: <%= thumbnail.height || 100 %>px;">
29
29
  <% if thumbnail.variable? && !thumbnail.icon? %>
30
30
  <%= image_tag(thumbnail.file.variant(thumbnail.variant_options)) %>
31
+ <% elsif thumbnail.svg? %>
32
+ <%= thumbnail.inline_svg(width: thumbnail.width || 100, height: thumbnail.height || 100) %>
31
33
  <% else %>
32
- <%= bootstrap_icon(thumbnail.icon_name, class: "h-thumbnail-icon") %>
34
+ <%= bootstrap_icon(thumbnail.icon_name, class: "formstrap-thumbnail-icon") %>
33
35
  <% end %>
34
36
  </div>
35
37
  </div>
@@ -7,19 +7,6 @@ en:
7
7
  other: Browse files or drag & drop them here
8
8
  group:
9
9
  save: Save
10
- blocks:
11
- add: "Add %{name}"
12
- empty:
13
- title: No blocks are added yet.
14
- add: Add block
15
- block:
16
- add: Add
17
- remove:
18
- title: Delete
19
- confirm: Are you sure you want to delete this?
20
- modal:
21
- close: Close
22
- title: "Edit block '%{name}'"
23
10
  media:
24
11
  validation:
25
12
  min:
@@ -28,7 +15,6 @@ en:
28
15
  max:
29
16
  one: Please limit your selection to maximum 1 item
30
17
  other: "Please limit your selection to maximum %{count} items"
31
-
32
18
  select:
33
19
  blank: Make a choice
34
20
  repeater: