headmin 0.5.3 → 0.5.5

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/assets/javascripts/headmin/controllers/media_controller.js +14 -14
  4. data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +5 -5
  5. data/app/assets/javascripts/headmin/controllers/remote_modal_controller.js +1 -2
  6. data/app/assets/javascripts/headmin/controllers/textarea_controller.js +3 -3
  7. data/app/assets/javascripts/headmin.js +11 -11
  8. data/app/assets/stylesheets/headmin/forms/repeater.scss +0 -4
  9. data/app/assets/stylesheets/headmin/forms.scss +0 -6
  10. data/app/assets/stylesheets/headmin/overrides/redactorx.scss +1 -1
  11. data/app/assets/stylesheets/headmin/vendor/{tom-select-bootstrap.css → tom-select-bootstrap.scss} +0 -1
  12. data/app/assets/stylesheets/headmin.css +4 -8
  13. data/app/controllers/concerns/headmin/pagination.rb +5 -1
  14. data/app/models/concerns/headmin/field.rb +1 -1
  15. data/app/models/headmin/filter/association.rb +86 -0
  16. data/app/models/headmin/filter/association_view.rb +74 -0
  17. data/app/models/headmin/filter/base.rb +39 -19
  18. data/app/models/headmin/filter/boolean_view.rb +2 -5
  19. data/app/models/headmin/filter/date_view.rb +2 -5
  20. data/app/models/headmin/filter/field.rb +55 -0
  21. data/app/models/headmin/filter/field_view.rb +50 -0
  22. data/app/models/headmin/filter/filter_view.rb +25 -0
  23. data/app/models/headmin/filter/flatpickr_view.rb +1 -0
  24. data/app/models/headmin/filter/number_view.rb +2 -5
  25. data/app/models/headmin/filter/operator_view.rb +3 -1
  26. data/app/models/headmin/filter/options_view.rb +2 -5
  27. data/app/models/headmin/filter/text_view.rb +2 -5
  28. data/app/models/headmin/filters.rb +19 -4
  29. data/app/models/headmin/form/association_view.rb +102 -0
  30. data/app/models/headmin/form/blocks_view.rb +4 -1
  31. data/app/models/headmin/form/file_view.rb +0 -8
  32. data/app/models/headmin/form/flatpickr_view.rb +2 -1
  33. data/app/models/headmin/form/media_item_view.rb +39 -0
  34. data/app/models/headmin/form/media_view.rb +27 -3
  35. data/app/models/headmin/form/select_view.rb +2 -1
  36. data/app/models/headmin/thumbnail_view.rb +40 -19
  37. data/app/models/view_model.rb +4 -0
  38. data/app/views/headmin/_filters.html.erb +7 -2
  39. data/app/views/headmin/_thumbnail.html.erb +32 -8
  40. data/app/views/headmin/filters/_association.html.erb +24 -0
  41. data/app/views/headmin/filters/_field.html.erb +23 -0
  42. data/app/views/headmin/filters/_options.html.erb +1 -1
  43. data/app/views/headmin/forms/_association.html.erb +30 -0
  44. data/app/views/headmin/forms/_file.html.erb +4 -5
  45. data/app/views/headmin/forms/_media.html.erb +7 -5
  46. data/app/views/headmin/forms/_repeater.html.erb +10 -8
  47. data/app/views/headmin/forms/_wrapper.html.erb +0 -1
  48. data/app/views/headmin/forms/fields/_list.html.erb +6 -4
  49. data/app/views/headmin/forms/media/_item.html.erb +18 -12
  50. data/app/views/headmin/forms/repeater/_row.html.erb +2 -1
  51. data/app/views/headmin/media/_item.html.erb +1 -2
  52. data/app/views/headmin/media/_media_item_modal.html.erb +2 -2
  53. data/app/views/headmin/table/body/_association.html.erb +17 -3
  54. data/app/views/headmin/views/devise/shared/_links.html.erb +14 -20
  55. data/config/locales/activerecord/en.yml +1 -0
  56. data/config/locales/activerecord/nl.yml +1 -0
  57. data/config/locales/headmin/filters/en.yml +3 -1
  58. data/config/locales/headmin/filters/nl.yml +2 -0
  59. data/config/locales/headmin/media/en.yml +2 -2
  60. data/config/locales/headmin/media/nl.yml +2 -1
  61. data/lib/headmin/version.rb +1 -1
  62. data/package.json +1 -1
  63. metadata +13 -3
@@ -0,0 +1,55 @@
1
+ module Headmin
2
+ module Filter
3
+ class Field < Headmin::Filter::Base
4
+ OPERATORS = %w[eq not_eq matches does_not_match]
5
+
6
+ def initialize(attribute, params, association: nil)
7
+ @attribute = association ? "#{association}_#{attribute}".to_sym : attribute
8
+ @attribute = "field_#{attribute}".to_sym
9
+ @raw_value = params[@attribute]
10
+ @association = association
11
+ @instructions = []
12
+
13
+ if params.key?(@attribute)
14
+ parse(@raw_value)
15
+ end
16
+ end
17
+
18
+ def cast_value(value)
19
+ value
20
+ end
21
+
22
+ def display_value(value)
23
+ value.downcase
24
+ end
25
+
26
+ def query(collection)
27
+ return collection unless @instructions.any?
28
+
29
+ query = nil
30
+
31
+ @instructions.each do |instruction|
32
+ query = build_query(query, collection, instruction)
33
+ end
34
+
35
+ collection = collection.joins(:fields)
36
+ collection.where(query)
37
+ end
38
+
39
+ def build_query(query, collection, instruction)
40
+ query_operator = convert_to_query_operator(instruction[:operator])
41
+ query_value = convert_to_query_value(instruction[:value], instruction[:operator])
42
+
43
+ query_operator, query_value = process_null_operators(query_operator, query_value)
44
+
45
+ model = collection.is_a?(Class) ? collection : collection.model
46
+
47
+ new_attribute = attribute.to_s.gsub("field_", "").to_sym
48
+ fields_model = model.reflect_on_association(:fields).klass
49
+ new_query = fields_model.arel_table[:name].matches(new_attribute).and(fields_model.arel_table[:value].send(query_operator, query_value))
50
+
51
+ query ? query.send(instruction[:conditional], new_query) : new_query
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ module Headmin
2
+ module Filter
3
+ class FieldView < FilterView
4
+ def base_options
5
+ keys = %i[name label form]
6
+ options = to_h.slice(*keys)
7
+ default_base_options.merge(options)
8
+ end
9
+
10
+ def input_options
11
+ keys = %i[form]
12
+ options = to_h.slice(*keys)
13
+ default_input_options.merge(options)
14
+ end
15
+
16
+ private
17
+
18
+ def id
19
+ "#{name}_value"
20
+ end
21
+
22
+ def name
23
+ "field_#{@name}".to_sym || attribute
24
+ end
25
+
26
+ def default_base_options
27
+ {
28
+ label: label,
29
+ name: "field_#{attribute}".to_sym,
30
+ filter: Headmin::Filter::Field.new("field_#{attribute}".to_sym, @params),
31
+ allowed_operators: Headmin::Filter::Field::OPERATORS - %w[in not_in]
32
+ }
33
+ end
34
+
35
+ def default_input_options
36
+ {
37
+ label: false,
38
+ wrapper: false,
39
+ id: id,
40
+ name: nil,
41
+ data: {
42
+ action: "change->filter#updateHiddenValue",
43
+ filter_target: "value",
44
+ filter_row_target: "original"
45
+ }
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ module Headmin
2
+ module Filter
3
+ class FilterView < ViewModel
4
+ def attribute
5
+ @association ? "#{@association}_#{@attribute}".to_sym : @attribute
6
+ end
7
+
8
+ def label
9
+ @label || I18n.t("attributes.#{attribute}", default: @association ? "#{association_model.model_name.human(count: 1)} - #{association_model.human_attribute_name(@attribute)}" : name.to_s)
10
+ end
11
+
12
+ def reflection
13
+ if @association
14
+ form.object.class.reflect_on_association(@association)
15
+ end
16
+ end
17
+
18
+ def association_model
19
+ if @association
20
+ reflection.klass
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -40,6 +40,7 @@ module Headmin
40
40
  {
41
41
  label: false,
42
42
  wrapper: false,
43
+ name: nil,
43
44
  id: id,
44
45
  data: {
45
46
  action: "change->filter#updateHiddenValue",
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class NumberView < ViewModel
3
+ class NumberView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -23,10 +23,6 @@ module Headmin
23
23
  @name || attribute
24
24
  end
25
25
 
26
- def label
27
- @label || I18n.t("attributes.#{attribute}", default: name.to_s)
28
- end
29
-
30
26
  def default_base_options
31
27
  {
32
28
  label: label,
@@ -41,6 +37,7 @@ module Headmin
41
37
  label: false,
42
38
  wrapper: false,
43
39
  id: id,
40
+ name: nil,
44
41
  data: {
45
42
  action: "change->filter#updateHiddenValue",
46
43
  filter_target: "value",
@@ -22,7 +22,9 @@ module Headmin
22
22
  matches: "&approx; #{I18n.t("headmin.filters.operators.matches")}",
23
23
  does_not_match: "&napprox; #{I18n.t("headmin.filters.operators.does_not_match")}",
24
24
  is_null: "&#9675; #{I18n.t("headmin.filters.operators.is_null")}",
25
- is_not_null: "&#9679; #{I18n.t("headmin.filters.operators.is_not_null")}"
25
+ is_not_null: "&#9679; #{I18n.t("headmin.filters.operators.is_not_null")}",
26
+ in: "&ni; #{I18n.t("headmin.filters.operators.in")}",
27
+ not_in: "&notni; #{I18n.t("headmin.filters.operators.not_in")}"
26
28
  }
27
29
  end
28
30
  end
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class OptionsView < ViewModel
3
+ class OptionsView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -27,10 +27,6 @@ module Headmin
27
27
  @name || attribute
28
28
  end
29
29
 
30
- def label
31
- @label || I18n.t("attributes.#{attribute}", default: name.to_s)
32
- end
33
-
34
30
  def default_base_options
35
31
  {
36
32
  label: label,
@@ -46,6 +42,7 @@ module Headmin
46
42
  label: false,
47
43
  wrapper: false,
48
44
  id: id,
45
+ name: nil,
49
46
  data: {
50
47
  action: "change->filter#updateHiddenValue",
51
48
  filter_target: "value",
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class TextView < ViewModel
3
+ class TextView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -23,10 +23,6 @@ module Headmin
23
23
  @name || attribute
24
24
  end
25
25
 
26
- def label
27
- @label || I18n.t("attributes.#{attribute}", default: name.to_s)
28
- end
29
-
30
26
  def default_base_options
31
27
  {
32
28
  label: label,
@@ -41,6 +37,7 @@ module Headmin
41
37
  label: false,
42
38
  wrapper: false,
43
39
  id: id,
40
+ name: nil,
44
41
  data: {
45
42
  action: "change->filter#updateHiddenValue",
46
43
  filter_target: "value",
@@ -13,15 +13,30 @@ module Headmin
13
13
  @param_types = param_types
14
14
  end
15
15
 
16
- def parse(attribute, type)
16
+ def parse(attribute, type, association: nil)
17
17
  class_name = "Headmin::Filter::#{type.to_s.classify}".constantize
18
- class_name.new(attribute, @params)
18
+ class_name.new(attribute, @params, association: association)
19
19
  end
20
20
 
21
21
  def query(collection)
22
22
  @param_types.each do |attribute, type|
23
- filter = parse(attribute, type)
24
- collection = filter.query(collection)
23
+ if type.is_a? Hash
24
+ # We are given attribute filters of an association
25
+ association = attribute
26
+
27
+ # By default, we offer a filter of type association
28
+ association_filter = Headmin::Filter::Association.new(attribute, @params, association: nil)
29
+ collection = association_filter.query(collection)
30
+
31
+ # Query all the passed attribute filters for this association
32
+ type.each do |new_attribute, new_type|
33
+ filter = parse(new_attribute, new_type, association: association)
34
+ collection = filter.query(collection)
35
+ end
36
+ else
37
+ filter = parse(attribute, type)
38
+ collection = filter.query(collection)
39
+ end
25
40
  end
26
41
  collection
27
42
  end
@@ -0,0 +1,102 @@
1
+ module Headmin
2
+ module Form
3
+ class AssociationView < ViewModel
4
+ include Headmin::Form::Hintable
5
+ include Headmin::Form::InputGroupable
6
+ include Headmin::Form::Labelable
7
+ include Headmin::Form::Listable
8
+ include Headmin::Form::Placeholderable
9
+ include Headmin::Form::Validatable
10
+ include Headmin::Form::Wrappable
11
+
12
+ def input_options
13
+ keys = attributes - %i[append attribute collection float form input_group include_blank label prepend validate selected tags wrapper]
14
+ options = to_h.slice(*keys)
15
+ def_input_options = default_input_options
16
+ def_input_options = def_input_options.deep_merge(options)
17
+ def_input_options = def_input_options.merge({multiple: true}) if collection?
18
+ def_input_options
19
+ end
20
+
21
+ def input_group_options
22
+ default_input_group_options
23
+ .deep_merge(label_input_group_options)
24
+ .deep_merge(@input_group || {})
25
+ end
26
+
27
+ def wrapper_options
28
+ default_wrapper_options.deep_merge({
29
+ class: ["mb-3", ("form-floating" if float)]
30
+ }).deep_merge(@wrapper || {})
31
+ end
32
+
33
+ def select_options
34
+ keys = %i[include_blank selected]
35
+ options = to_h.slice(*keys)
36
+ default_options.deep_merge(options)
37
+ end
38
+
39
+ def attribute_with_id
40
+ attribute_with_id = collection? ? "#{association_foreign_key}s" : foreign_key
41
+
42
+ if attribute_with_id.nil?
43
+ raise(AssociationDoesNotExistError, "Association attribute that was passed does not exist.")
44
+ else
45
+ attribute_with_id
46
+ end
47
+ end
48
+
49
+ def collection
50
+ association_class.all.map { |item| [item.to_s, item.id] }
51
+ end
52
+
53
+ private
54
+
55
+ def association_foreign_key
56
+ reflection.association_foreign_key
57
+ end
58
+
59
+ def foreign_key
60
+ reflection.foreign_key
61
+ end
62
+
63
+ def reflection
64
+ form.object.class.reflect_on_association(attribute)
65
+ end
66
+
67
+ def association_class
68
+ reflection.klass
69
+ end
70
+
71
+ def collection?
72
+ reflection.collection?
73
+ end
74
+
75
+ def default_options
76
+ {
77
+ selected: form.object&.send(attribute_with_id)
78
+ }
79
+ end
80
+
81
+ def default_input_options
82
+ {
83
+ aria: {describedby: validation_id},
84
+ class: [form_control_class, validation_class],
85
+ data: {
86
+ tags: tags,
87
+ controller: "select"
88
+ },
89
+ multiple: tags,
90
+ placeholder: placeholder
91
+ }
92
+ end
93
+
94
+ def form_control_class
95
+ plaintext ? "form-control-plaintext" : "form-select"
96
+ end
97
+
98
+ class AssociationDoesNotExistError < StandardError
99
+ end
100
+ end
101
+ end
102
+ end
@@ -21,7 +21,10 @@ module Headmin
21
21
  {
22
22
  attribute: "blocks",
23
23
  label: false,
24
- templates: names
24
+ templates: names,
25
+ row: {
26
+ class: "repeater-row list-group-item pt-3"
27
+ }
25
28
  }
26
29
  end
27
30
  end
@@ -55,14 +55,6 @@ module Headmin
55
55
  attached.is_a?(ActiveStorage::Attached::Many) ? :"#{attribute}_attachments" : :"#{attribute}_attachment"
56
56
  end
57
57
 
58
- def thumbnail_width
59
- 100
60
- end
61
-
62
- def thumbnail_height
63
- 100
64
- end
65
-
66
58
  def dropzone_options
67
59
  if dropzone
68
60
  {
@@ -18,8 +18,9 @@ module Headmin
18
18
  def default_data
19
19
  {
20
20
  controller: "flatpickr",
21
+
21
22
  flatpickr: {
22
- defaultDate: form.object&.send(attribute)&.strftime("%d/%m/%Y")
23
+ defaultDate: attribute.nil? ? Date.today : form.object&.send(attribute)&.strftime("%d/%m/%Y")
23
24
  }
24
25
  }
25
26
  end
@@ -0,0 +1,39 @@
1
+ module Headmin
2
+ module Form
3
+ class MediaItemView < ViewModel
4
+ include Rails.application.routes.url_helpers
5
+
6
+ def thumbnail_options
7
+ options = {
8
+ file: attachment
9
+ }
10
+
11
+ # Don't pass width or height if it was not defined
12
+ options = options.merge(width: width) if is_defined?(:width)
13
+ options = options.merge(height: height) if is_defined?(:height)
14
+
15
+ options
16
+ end
17
+
18
+ def attachment
19
+ form.object
20
+ end
21
+
22
+ def position_value
23
+ attachment.new_record? ? nil : attachment.position
24
+ end
25
+
26
+ def id
27
+ attachment.blob ? attachment.blob.id : "$1"
28
+ end
29
+
30
+ def filename
31
+ attachment.blob&.filename&.to_s
32
+ end
33
+
34
+ def size
35
+ number_to_human_size(attachment.blob&.byte_size || 0)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -19,7 +19,7 @@ module Headmin
19
19
  class: ["mb-3", ("form-floating" if float)],
20
20
  data: {
21
21
  controller: "media",
22
- name: "#{attribute}_#{object_id}",
22
+ name: name,
23
23
  min: min,
24
24
  max: max,
25
25
  sort: sort,
@@ -28,6 +28,18 @@ module Headmin
28
28
  }).deep_merge(@wrapper || {})
29
29
  end
30
30
 
31
+ def item_options
32
+ options = {
33
+ sort: sort
34
+ }
35
+
36
+ # Don't pass width or height if it was not defined
37
+ options = options.merge(width: width) if is_defined?(:width)
38
+ options = options.merge(height: height) if is_defined?(:height)
39
+
40
+ options
41
+ end
42
+
31
43
  def custom_validation_options
32
44
  {
33
45
  form: form,
@@ -37,6 +49,18 @@ module Headmin
37
49
  }
38
50
  end
39
51
 
52
+ def thumbnail_options
53
+ options = {
54
+ icon: "plus"
55
+ }
56
+
57
+ # Don't pass width or height if it was not defined
58
+ options = options.merge(width: width) unless is_defined?(:width)
59
+ options = options.merge(height: height) unless is_defined?(:height)
60
+
61
+ options
62
+ end
63
+
40
64
  def association_object
41
65
  if attached.is_a?(ActiveStorage::Attached::Many)
42
66
  result = form.object.send(nested_attribute)
@@ -101,8 +125,8 @@ module Headmin
101
125
  attachments.map { |attachment| attachment.blob_id }
102
126
  end
103
127
 
104
- def media_modal_url
105
- headmin_media_url(name: "#{attribute}_#{object_id}", ids: blob_ids, min: min, max: max)
128
+ def name
129
+ "#{attribute}_#{object_id}"
106
130
  end
107
131
 
108
132
  def sort
@@ -36,8 +36,9 @@ module Headmin
36
36
  private
37
37
 
38
38
  def default_options
39
+ selected = attribute.nil? ? nil : form.object&.send(attribute)
39
40
  {
40
- selected: form.object&.send(attribute)
41
+ selected: selected
41
42
  }
42
43
  end
43
44
 
@@ -1,41 +1,62 @@
1
1
  module Headmin
2
- class ThumbnailView
3
- def initialize(local_assigns)
4
- @local_assigns = local_assigns
5
- end
6
-
2
+ class ThumbnailView < ViewModel
7
3
  def class_names
8
- class_names = [@local_assigns[:class]]
4
+ class_names = [@class]
9
5
  class_names << "img-thumbnail h-thumbnail"
10
6
  class_names.join(" ")
11
7
  end
12
8
 
13
9
  def width
14
- @local_assigns[:width] || 150
10
+ if is_defined?(:width)
11
+ @width
12
+ else
13
+ @width || 100
14
+ end
15
15
  end
16
16
 
17
17
  def height
18
- @local_assigns[:height] || 150
18
+ if is_defined?(:height)
19
+ @height
20
+ else
21
+ @height || 100
22
+ end
19
23
  end
20
24
 
21
- def src
22
- @local_assigns[:src]
25
+ def blob
26
+ file.is_a?(ActiveStorage::Blob) ? file : file&.blob
23
27
  end
24
28
 
25
- def icon
26
- @local_assigns[:icon]
29
+ def mime_type
30
+ blob&.content_type
27
31
  end
28
32
 
29
- def mime_type
30
- if src
31
- stripped_path = URI.parse(src).path
32
- extension = File.extname(stripped_path)
33
- Rack::Mime.mime_type(extension)
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
+ })
34
42
  end
35
43
  end
36
44
 
37
- def image?
38
- mime_type&.match?(/^image/)
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)
39
60
  end
40
61
 
41
62
  # https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
@@ -40,6 +40,10 @@ class ViewModel
40
40
 
41
41
  private
42
42
 
43
+ def is_defined?(attribute)
44
+ attributes.include?(attribute.to_sym)
45
+ end
46
+
43
47
  def value_for(attribute)
44
48
  reserved_methods.include?(attribute) ? instance_variable_get("@#{attribute}") : send(attribute)
45
49
  end
@@ -14,10 +14,15 @@
14
14
  # <%= render "headmin/filters", url: admin_polls_path %#>
15
15
 
16
16
  action = local_assigns.has_key?(:url) ? url : request.path
17
- %>
18
17
 
19
- <%= form_with url: action, method: :get, data: {controller: "filters", filters_target: "form"} do |form| %>
18
+ begin
19
+ model = controller_name.classify.constantize
20
+ rescue
21
+ raise "Cannot find class!"
22
+ end
23
+ %>
20
24
 
25
+ <%= form_with model: model.new, url: action, method: :get, data: {controller: "filters", filters_target: "form"} do |form| %>
21
26
  <%# Perform yield in order to capture content blocks, pass form so we can use headmin form inputs %>
22
27
  <%= yield(form) %>
23
28
 
@@ -1,11 +1,35 @@
1
- <% thumbnail = Headmin::ThumbnailView.new(local_assigns) %>
1
+ <%
2
+ # headmin/thumbnail
3
+ #
4
+ # ==== Required parameters
5
+ # * +file+ - Name of the attribute of the form model
6
+ #
7
+ # ==== Optional parameters
8
+ # * +width+ - Width of the thumbnail. Set to nil to fill according to the original aspect ratio
9
+ # * +height+ - Height of the thumbnail. Set to nil to fill according to the original aspect ratio
10
+ # * +icon+ - Force icon to be shown instead of a thumbnail
11
+ #
12
+ # ==== References
13
+ # https://headmin.dev/docs/thumbnail
14
+ #
15
+ # ==== Examples
16
+ # Basic version
17
+ # <%= render "headmin/thumbnail", file: file %#>
18
+ #
19
+ # Custom size (200px x 100px)
20
+ # <%= render "headmin/thumbnail", file: file, width: 200, height: 100 %#>
21
+ #
22
+ # Custom (bootstrap) icon
23
+ # <%= render "headmin/thumbnail", file: file, icon: "file-earmark-excel" %#>
24
+ thumbnail = Headmin::ThumbnailView.new(local_assigns)
25
+ %>
2
26
 
3
- <div class="<%= thumbnail.class_names %>" style="width: <%= thumbnail.width %>px; height: <%= thumbnail.height %>px;">
4
- <% if thumbnail.image? %>
5
- <div class="h-thumbnail-bg" style="background-image: url('<%= thumbnail.src %>');"></div>
6
- <% else %>
7
- <div class="h-thumbnail-bg">
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;">
29
+ <% if thumbnail.variable? && !thumbnail.icon? %>
30
+ <%= image_tag(thumbnail.file.variant(thumbnail.variant_options)) %>
31
+ <% else %>
8
32
  <%= bootstrap_icon(thumbnail.icon_name, class: "h-thumbnail-icon") %>
9
- </div>
10
- <% end %>
33
+ <% end %>
34
+ </div>
11
35
  </div>