formstrap 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -1
- data/app/assets/javascripts/formstrap/controllers/file_preview_controller.js +5 -5
- data/app/assets/javascripts/formstrap/controllers/media_controller.js +4 -2
- data/app/assets/javascripts/formstrap/controllers/media_modal_controller.js +38 -2
- data/app/assets/javascripts/formstrap/controllers/popup_controller.js +75 -0
- data/app/assets/javascripts/formstrap/controllers/repeater_controller.js +4 -4
- data/app/assets/javascripts/formstrap/index.js +2 -0
- data/app/assets/javascripts/formstrap.js +1790 -129
- data/app/assets/stylesheets/formstrap/{forms/autocomplete.scss → autocomplete.scss} +1 -1
- data/app/assets/stylesheets/formstrap/{forms/file.scss → file.scss} +10 -10
- data/app/assets/stylesheets/formstrap/general.scss +8 -1
- data/app/assets/stylesheets/formstrap/media/_modal.scss +9 -0
- data/app/assets/stylesheets/formstrap/{forms/media.scss → media/_validation.scss} +2 -2
- data/app/assets/stylesheets/formstrap/media.scss +2 -1
- data/app/assets/stylesheets/formstrap/{forms/repeater.scss → repeater.scss} +8 -8
- data/app/assets/stylesheets/formstrap/shared/popup.scss +17 -0
- data/app/assets/stylesheets/formstrap/shared/thumbnail.scss +23 -0
- data/app/assets/stylesheets/formstrap/shared.scss +2 -0
- data/app/assets/stylesheets/formstrap/utilities/dropzone.scss +2 -2
- data/app/assets/stylesheets/formstrap/utilities.scss +0 -1
- data/app/assets/stylesheets/formstrap.css +155 -143
- data/app/assets/stylesheets/formstrap.scss +5 -1
- data/app/controllers/formstrap/media_controller.rb +41 -23
- data/app/helpers/application_helper.rb +20 -0
- data/app/models/concerns/formstrap/attachment.rb +16 -0
- data/app/models/concerns/formstrap/blob.rb +56 -0
- data/app/models/formstrap/file_view.rb +3 -3
- data/app/models/formstrap/media_view.rb +12 -1
- data/app/models/formstrap/thumbnail_view.rb +108 -0
- data/app/views/formstrap/_autocomplete.html.erb +1 -1
- data/app/views/formstrap/_file.html.erb +8 -8
- data/app/views/formstrap/_media.html.erb +6 -4
- data/app/views/formstrap/_repeater.html.erb +3 -3
- data/app/views/formstrap/media/_item.html.erb +10 -7
- data/app/views/formstrap/media/_modal.html.erb +35 -8
- data/app/views/formstrap/media/_thumbnail.html.erb +12 -8
- data/app/views/formstrap/media/_validation.html.erb +1 -1
- data/app/views/formstrap/media/create.turbo_stream.erb +3 -1
- data/app/views/formstrap/media/index.html.erb +1 -1
- data/app/views/formstrap/repeater/_row.html.erb +4 -4
- data/app/views/formstrap/shared/_popup.html.erb +1 -1
- data/app/views/formstrap/shared/_thumbnail.html.erb +5 -3
- data/config/locales/formstrap/forms/en.yml +0 -14
- data/config/locales/formstrap/forms/nl.yml +1 -15
- data/config/locales/formstrap/media/en.yml +2 -10
- data/config/locales/formstrap/media/nl.yml +2 -10
- data/config/routes.rb +0 -2
- data/lib/formstrap/version.rb +1 -1
- metadata +17 -30
- data/app/assets/stylesheets/formstrap/forms.scss +0 -12
- data/app/assets/stylesheets/formstrap/media/index.scss +0 -9
- data/app/assets/stylesheets/formstrap/utilities/buttons.scss +0 -27
- data/app/models/formstrap/blocks_view.rb +0 -43
- data/app/views/formstrap/_blocks.html.erb +0 -45
- data/app/views/formstrap/_to_ary.html.erb +0 -0
- data/app/views/formstrap/blocks/_modal.html.erb +0 -20
- data/app/views/formstrap/fields/_base.html.erb +0 -25
- data/app/views/formstrap/fields/_file.html.erb +0 -17
- data/app/views/formstrap/fields/_files.html.erb +0 -17
- data/app/views/formstrap/fields/_group.html.erb +0 -52
- data/app/views/formstrap/fields/_list.html.erb +0 -31
- data/app/views/formstrap/fields/_text.html.erb +0 -17
- data/app/views/formstrap/media/_media_item_modal.html.erb +0 -77
- data/app/views/formstrap/media/show.html.erb +0 -9
- data/app/views/formstrap/media/update.turbo_stream.erb +0 -3
- data/config/locales/activerecord/en.yml +0 -12
- data/config/locales/activerecord/nl.yml +0 -13
- data/config/locales/defaults/en.yml +0 -215
- data/config/locales/defaults/nl.yml +0 -213
- data/config/locales/devise/en.yml +0 -65
- data/config/locales/devise/nl.yml +0 -85
- /data/app/assets/stylesheets/formstrap/{forms/search.scss → search.scss} +0 -0
@@ -3,14 +3,11 @@ class Formstrap::MediaController < FormstrapController
|
|
3
3
|
layout false
|
4
4
|
|
5
5
|
def index
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
.group(:id)
|
12
|
-
.all
|
13
|
-
@blobs = paginate(@blobs)
|
6
|
+
blobs = ActiveStorage::Blob
|
7
|
+
blobs = filter(blobs)
|
8
|
+
blobs = sort(blobs)
|
9
|
+
blobs = blobs.group(:id)
|
10
|
+
@blobs = paginate(blobs)
|
14
11
|
@mimetypes = media_params[:mimetype]
|
15
12
|
|
16
13
|
respond_to do |format|
|
@@ -37,32 +34,53 @@ class Formstrap::MediaController < FormstrapController
|
|
37
34
|
@blob = ActiveStorage::Blob.find(params[:id])
|
38
35
|
end
|
39
36
|
|
40
|
-
def update
|
41
|
-
@blob = ActiveStorage::Blob.find(params[:id])
|
42
|
-
media_item_params[:filename] = media_item_params[:filename] + "." + @blob.filename.to_s.rpartition(".").last
|
43
|
-
if @blob.update(media_item_params)
|
44
|
-
flash.now[:notice] = t("admin.flash.updated", name: @blob.filename)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
37
|
def thumbnail
|
49
38
|
@blob = ActiveStorage::Blob.find(params[:id])
|
50
39
|
end
|
51
40
|
|
52
41
|
private
|
53
42
|
|
43
|
+
def filter(blobs)
|
44
|
+
blobs = filter_unattached(blobs)
|
45
|
+
blobs = filter_by_mimetype(blobs, media_params[:mimetype]) if media_params[:mimetype].present?
|
46
|
+
blobs = filter_excluded_models(blobs, media_params[:exclude_models]) if media_params[:exclude_models].present?
|
47
|
+
blobs = filter_search(blobs, media_params[:search]) if media_params[:search].present?
|
48
|
+
blobs
|
49
|
+
end
|
50
|
+
|
51
|
+
def filter_unattached(blobs)
|
52
|
+
blobs.not_attached_to_variant
|
53
|
+
end
|
54
|
+
|
55
|
+
def filter_by_mimetype(blobs, mimetype)
|
56
|
+
blobs.by_mimetypes_string(mimetype)
|
57
|
+
end
|
58
|
+
|
59
|
+
def filter_excluded_models(blobs, model_names = [])
|
60
|
+
blobs.not_attached_to(model_names)
|
61
|
+
end
|
62
|
+
|
63
|
+
def filter_search(blobs, string)
|
64
|
+
blobs.search(string)
|
65
|
+
end
|
66
|
+
|
67
|
+
def sort(blobs)
|
68
|
+
blobs.order(created_at: :desc)
|
69
|
+
end
|
70
|
+
|
54
71
|
def media_params
|
55
72
|
params.permit(
|
56
|
-
:min,
|
57
73
|
:max,
|
58
|
-
:name,
|
59
74
|
:mimetype,
|
75
|
+
:min,
|
76
|
+
:name,
|
77
|
+
:search,
|
78
|
+
:page,
|
79
|
+
:page_start,
|
80
|
+
:per_page,
|
60
81
|
ids: [],
|
61
|
-
files: []
|
82
|
+
files: [],
|
83
|
+
exclude_models: []
|
62
84
|
)
|
63
85
|
end
|
64
|
-
|
65
|
-
def media_item_params
|
66
|
-
params.require(:blob).permit!
|
67
|
-
end
|
68
86
|
end
|
@@ -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
|
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: ["
|
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: "
|
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
|
@@ -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="
|
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="
|
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="
|
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="
|
65
|
-
<%= t("
|
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="
|
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="
|
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="
|
81
|
+
<div class="formstrap-file-thumbnail-remove" data-action="click->file-preview#remove">
|
82
82
|
<%= bootstrap_icon("x") %>
|
83
83
|
</div>
|
84
84
|
<% end %>
|
@@ -15,6 +15,8 @@
|
|
15
15
|
# * +wrapper+ - Hash with all options for the surrounding html tag
|
16
16
|
# * +width+ - Width of the thumbnail
|
17
17
|
# * +height+ - Height of the thumbnail
|
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
|
18
20
|
#
|
19
21
|
# ==== References
|
20
22
|
# https://headmin.dev/docs/forms/media
|
@@ -30,17 +32,17 @@
|
|
30
32
|
|
31
33
|
<%= render "formstrap/wrapper", media.wrapper_options do %>
|
32
34
|
<%= render "formstrap/label", media.label_options if media.prepend_label? %>
|
33
|
-
<div class="
|
35
|
+
<div class="formstrap-file-thumbnails" data-media-target="thumbnails">
|
34
36
|
<%= render "formstrap/media/validation", media.custom_validation_options %>
|
35
37
|
|
36
38
|
<!-- Render previews for attachments -->
|
37
39
|
<%= form.fields_for(media.nested_attribute, media.association_object) do |ff| %>
|
38
|
-
<%= render "formstrap/media/item", media.item_options.merge(form: ff,
|
40
|
+
<%= render "formstrap/media/item", media.item_options.merge(form: ff, edit_url: media.edit_modal_url(ff.object)) %>
|
39
41
|
<% end %>
|
40
42
|
|
41
43
|
<!-- Placeholder -->
|
42
44
|
<div class="<%= "d-none" if media.attachments.any? %>" data-media-target="placeholder">
|
43
|
-
<a href="<%=
|
45
|
+
<a href="<%= media.modal_url %>" data-turbo-frame="modal" data-media-target="modalButton">
|
44
46
|
<%= render "formstrap/shared/thumbnail", media.thumbnail_options %>
|
45
47
|
</a>
|
46
48
|
</div>
|
@@ -50,7 +52,7 @@
|
|
50
52
|
<% association_object = ActiveStorage::Attachment.new %>
|
51
53
|
<template data-media-target="template" data-template-id-regex="<%= association_object.object_id %>">
|
52
54
|
<%= form.fields_for(media.nested_attribute, ActiveStorage::Attachment.new, child_index: association_object.object_id) do |ff| %>
|
53
|
-
<%= render "formstrap/media/item", media.item_options.merge(form: ff,
|
55
|
+
<%= render "formstrap/media/item", media.item_options.merge(form: ff, edit_url: media.edit_modal_url(ff.object)) %>
|
54
56
|
<% end %>
|
55
57
|
</template>
|
56
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
|
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
|
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="
|
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="
|
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="
|
28
|
+
<div class="formstrap-file-thumbnail-actions">
|
28
29
|
<!-- Edit -->
|
29
|
-
|
30
|
-
<%=
|
31
|
-
|
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="
|
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="
|
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
|
-
|
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
|
-
|
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
|
-
<%=
|
27
|
-
<%= form.label :files, class: "btn
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
19
|
+
<div>
|
20
|
+
<label class="d-flex">
|
21
|
+
<%= render "formstrap/shared/thumbnail", file: nil %>
|
22
|
+
</label>
|
23
|
+
</div>
|
20
24
|
<% end %>
|
@@ -1,5 +1,7 @@
|
|
1
1
|
<%= turbo_stream.prepend "thumbnails" do %>
|
2
2
|
<% @blobs.each do |blob| %>
|
3
|
-
|
3
|
+
<turbo-frame id="blob_<%= blob.id %>">
|
4
|
+
<%= render "formstrap/media/thumbnail", blob: blob %>
|
5
|
+
</turbo-frame>
|
4
6
|
<% end %>
|
5
7
|
<% end %>
|