#9 Active Storage - thumbnails
This commit is contained in:
parent
15384c61e4
commit
187c65248f
1
Gemfile
1
Gemfile
@ -20,6 +20,7 @@
|
|||||||
source 'https://rubygems.org' do
|
source 'https://rubygems.org' do
|
||||||
gem 'active_record_union'
|
gem 'active_record_union'
|
||||||
gem 'activestorage'
|
gem 'activestorage'
|
||||||
|
gem 'image_processing', '~> 1.2'
|
||||||
gem 'ox' # Dav4Rack
|
gem 'ox' # Dav4Rack
|
||||||
gem 'rake' unless Dir.exist?(File.expand_path('../../redmine_dashboard', __FILE__))
|
gem 'rake' unless Dir.exist?(File.expand_path('../../redmine_dashboard', __FILE__))
|
||||||
gem 'simple_enum'
|
gem 'simple_enum'
|
||||||
|
|||||||
@ -322,20 +322,6 @@ class DmsfFilesController < ApplicationController
|
|||||||
redirect_to trash_dmsf_path(@project)
|
redirect_to trash_dmsf_path(@project)
|
||||||
end
|
end
|
||||||
|
|
||||||
def thumbnail
|
|
||||||
tbnail = @file.thumbnail(size: params[:size])
|
|
||||||
if tbnail
|
|
||||||
if stale?(etag: tbnail)
|
|
||||||
send_file tbnail,
|
|
||||||
filename: filename_for_content_disposition(@file.name),
|
|
||||||
type: @file.last_revision.content_type,
|
|
||||||
disposition: 'inline'
|
|
||||||
end
|
|
||||||
else
|
|
||||||
head :not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_file
|
def find_file
|
||||||
|
|||||||
@ -34,7 +34,7 @@ module DmsfHelper
|
|||||||
def self.sanitize_filename(filename)
|
def self.sanitize_filename(filename)
|
||||||
# Get only the filename, not the whole path
|
# Get only the filename, not the whole path
|
||||||
just_filename = File.basename(filename.gsub('\\\\', '/'))
|
just_filename = File.basename(filename.gsub('\\\\', '/'))
|
||||||
# Replace all non alphanumeric, hyphens or periods with underscore
|
# Replace all non-alphanumeric, hyphens or periods with underscore
|
||||||
just_filename.gsub!(/[^\w.\-]/, '_')
|
just_filename.gsub!(/[^\w.\-]/, '_')
|
||||||
# Keep the extension if any
|
# Keep the extension if any
|
||||||
if !/^[a-zA-Z0-9_.\-]*$/.match?(just_filename) && just_filename =~ /(.[a-zA-Z0-9]+)$/
|
if !/^[a-zA-Z0-9_.\-]*$/.match?(just_filename) && just_filename =~ /(.[a-zA-Z0-9]+)$/
|
||||||
|
|||||||
@ -546,7 +546,7 @@ class DmsfFile < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def thumbnailable?
|
def thumbnailable?
|
||||||
Redmine::Thumbnail.convert_available? && (image? || (pdf? && Redmine::Thumbnail.gs_available?))
|
last_revision.file&.variable?
|
||||||
end
|
end
|
||||||
|
|
||||||
def previewable?
|
def previewable?
|
||||||
@ -632,29 +632,6 @@ class DmsfFile < ApplicationRecord
|
|||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def thumbnail(options = {})
|
|
||||||
size = options[:size].to_i
|
|
||||||
if size.positive?
|
|
||||||
# Limit the number of thumbnails per image
|
|
||||||
size = (size / 50) * 50
|
|
||||||
# Maximum thumbnail size
|
|
||||||
size = 800 if size > 800
|
|
||||||
else
|
|
||||||
size = Setting.thumbnails_size.to_i
|
|
||||||
end
|
|
||||||
size = 100 unless size.positive?
|
|
||||||
target = File.join(Attachment.thumbnails_storage_path, "#{id}_#{last_revision.digest}_#{size}.thumb")
|
|
||||||
begin
|
|
||||||
Redmine::Thumbnail.generate last_revision.file.download, target, size, pdf?
|
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.error do
|
|
||||||
%(An error occured while generating thumbnail for #{last_revision.file&.blob&.filename} to #{target}\n
|
|
||||||
Exception was: #{e.message})
|
|
||||||
end
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def locked_title
|
def locked_title
|
||||||
if locked_for_user?
|
if locked_for_user?
|
||||||
return l(:title_locked_by_user, user: lock.reverse[0].user) if lock.reverse[0].user
|
return l(:title_locked_by_user, user: lock.reverse[0].user) if lock.reverse[0].user
|
||||||
|
|||||||
@ -25,12 +25,20 @@
|
|||||||
<div class="thumbnails">
|
<div class="thumbnails">
|
||||||
<% end %>
|
<% end %>
|
||||||
<% images.each do |file| %>
|
<% images.each do |file| %>
|
||||||
<div>
|
<div class="thumbnail" title="<%= file.name %>">
|
||||||
<% if link_to # Redmine classic %>
|
<% if link_to # Redmine classic %>
|
||||||
<%= link_to image_tag(dmsf_thumbnail_path(file), alt: file.title), view_dmsf_file_url(file) %>
|
<% size = Setting.thumbnails_size.to_i %>
|
||||||
|
<%= link_to image_tag(file.last_revision&.file&.variant(resize_to_limit: [size, size]),
|
||||||
|
alt: file.title,
|
||||||
|
style: "max-width: #{size}px; max-height: #{size}px;",
|
||||||
|
loading: 'lazy'),
|
||||||
|
view_dmsf_file_url(file) %>
|
||||||
<% else # jQuery gallery %>
|
<% else # jQuery gallery %>
|
||||||
<%= image_tag(dmsf_thumbnail_path(file),
|
<%= image_tag(file.last_revision&.file&.variant(resize_to_limit: [size, size]),
|
||||||
{ :'data-fullsrc' => view_dmsf_file_url(file), alt: file.title }) %>
|
:'data-fullsrc' => view_dmsf_file_url(file),
|
||||||
|
alt: file.title,
|
||||||
|
style: "max-width: #{size}px; max-height: #{size}px;",
|
||||||
|
loading: 'lazy') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@ -241,16 +241,22 @@ module RedmineDmsf
|
|||||||
{{dmsftn(file_id)}} -- with default height 200 (auto width)
|
{{dmsftn(file_id)}} -- with default height 200 (auto width)
|
||||||
{{dmsftn(file_id1 file_id2 file_id3)}} -- multiple thumbnails
|
{{dmsftn(file_id1 file_id2 file_id3)}} -- multiple thumbnails
|
||||||
{{dmsftn(file_id, size=300)}} -- with size 300x300
|
{{dmsftn(file_id, size=300)}} -- with size 300x300
|
||||||
{{dmsftn(file_id, height=300)}} -- with height (auto width)
|
{{dmsftn(file_id, height=300)}} -- with height (default width)
|
||||||
{{dmsftn(file_id, width=300)}} -- with width (auto height)
|
{{dmsftn(file_id, width=300)}} -- with width (default height)
|
||||||
{{dmsftn(file_id, size=640x480)}} -- with size 640x480}
|
{{dmsftn(file_id, size=640x480)}} -- with size 640x480}
|
||||||
macro :dmsftn do |_obj, args|
|
macro :dmsftn do |_obj, args|
|
||||||
raise ArgumentError if args.empty? # Requires file id
|
raise ArgumentError if args.empty? # Requires file id
|
||||||
|
|
||||||
args, options = extract_macro_options(args, :size, :width, :height, :title)
|
args, options = extract_macro_options(args, :size, :width, :height, :title)
|
||||||
size = options[:size]
|
|
||||||
width = options[:width]
|
if options[:size].present?
|
||||||
height = options[:height]
|
width, height = options[:size].split('x')
|
||||||
|
height = width if height.blank?
|
||||||
|
else
|
||||||
|
width = options[:width].presence || Setting.thumbnails_size.to_i
|
||||||
|
height = options[:height].presence || Setting.thumbnails_size.to_i
|
||||||
|
end
|
||||||
|
|
||||||
ids = args[0].split
|
ids = args[0].split
|
||||||
html = []
|
html = []
|
||||||
ids.each do |id|
|
ids.each do |id|
|
||||||
@ -260,21 +266,17 @@ module RedmineDmsf
|
|||||||
next
|
next
|
||||||
end
|
end
|
||||||
raise ::I18n.t(:notice_not_authorized) unless User.current&.allowed_to?(:view_dmsf_files, file.project)
|
raise ::I18n.t(:notice_not_authorized) unless User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||||
raise ::I18n.t(:error_not_supported_image_format) unless file.image?
|
raise ::I18n.t(:error_not_supported_image_format) unless file&.thumbnailable?
|
||||||
|
|
||||||
member = Member.find_by(user_id: User.current.id, project_id: file.project.id)
|
member = Member.find_by(user_id: User.current.id, project_id: file.project.id)
|
||||||
filename = file.last_revision.formatted_name(member)
|
filename = file.last_revision.formatted_name(member)
|
||||||
url = static_dmsf_file_url(file, filename: filename)
|
url = static_dmsf_file_url(file, filename: filename)
|
||||||
img = if size
|
img = image_tag(file.last_revision&.file&.variant(resize_to_limit: [width, height]),
|
||||||
image_tag(url, alt: filename, title: file.title, size: size)
|
alt: filename,
|
||||||
elsif height
|
style: "max-width: #{width}px; max-height: #{height}px;",
|
||||||
image_tag(url, alt: filename, title: file.title, width: 'auto', height: height)
|
loading: 'lazy')
|
||||||
elsif width
|
html << link_to(img,
|
||||||
image_tag(url, alt: filename, title: file.title, width: width, height: 'auto')
|
url,
|
||||||
else
|
|
||||||
image_tag(url, alt: filename, title: file.title, width: 'auto', height: 200)
|
|
||||||
end
|
|
||||||
html << link_to(img, url,
|
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
rel: 'noopener',
|
rel: 'noopener',
|
||||||
title: h(file.last_revision.try(:tooltip)),
|
title: h(file.last_revision.try(:tooltip)),
|
||||||
|
|||||||
@ -254,21 +254,23 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
|
|||||||
size = '50%'
|
size = '50%'
|
||||||
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
|
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
|
||||||
text = textilizable("{{dmsf_image(#{@file7.id}, size=#{size})}}")
|
text = textilizable("{{dmsf_image(#{@file7.id}, size=#{size})}}")
|
||||||
assert text.include?(image_tag(url, alt: @file7.name, title: @file7.title, width: size, height: size)), text
|
assert_equal "<p>#{image_tag(url, alt: @file7.name, title: @file7.title, width: size, height: size)}</p>", text
|
||||||
# TODO: arguments src and with and height are swapped
|
# TODO: Swaped parameters src and size
|
||||||
# size = '300'
|
# size = '300'
|
||||||
# text = textilizable("{{dmsf_image(#{@file7.id}, size=#{size})}}")
|
# text = textilizable("{{dmsf_image(#{@file7.id}, size=#{size})}}")
|
||||||
# assert text.include?(image_tag(url, alt: @file7.name, title: @file7.title, width: size, height: size)), text
|
# assert_equal "<p>#{image_tag(url, alt: @file7.name, title: @file7.title, width: size, height: size)}</p>", text
|
||||||
# TODO: arguments src and with and height are swapped
|
|
||||||
# size = '640x480'
|
# size = '640x480'
|
||||||
# text = textilizable("{{dmsf_image(#{@file7.id}, size=#{size})}}")
|
# text = textilizable("{{dmsf_image(#{@file7.id}, size=#{size})}}")
|
||||||
# assert text.include?(image_tag(url, alt: @file7.name, title: @file7.title, width: '640', height: '480')), text
|
# assert_equal "<p>#{image_tag(url, alt: @file7.name, title: @file7.title, width: '640', height: '480')}</p>",
|
||||||
|
# text
|
||||||
height = '480'
|
height = '480'
|
||||||
text = textilizable("{{dmsf_image(#{@file7.id}, height=#{height})}}")
|
text = textilizable("{{dmsf_image(#{@file7.id}, height=#{height})}}")
|
||||||
assert text.include?(image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: height)), text
|
assert_equal "<p>#{image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: height)}</p>",
|
||||||
|
text
|
||||||
width = '480'
|
width = '480'
|
||||||
text = textilizable("{{dmsf_image(#{@file7.id}, width=#{height})}}")
|
text = textilizable("{{dmsf_image(#{@file7.id}, width=#{height})}}")
|
||||||
assert text.include?(image_tag(url, alt: @file7.name, title: @file7.title, width: width, height: 'auto')), text
|
assert_equal "<p>#{image_tag(url, alt: @file7.name, title: @file7.title, width: width, height: 'auto')}</p>",
|
||||||
|
text
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_macro_dmsf_image_no_permissions
|
def test_macro_dmsf_image_no_permissions
|
||||||
@ -344,74 +346,86 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
|
|||||||
def test_macro_dmsftn
|
def test_macro_dmsftn
|
||||||
text = textilizable("{{dmsftn(#{@file7.id})}}")
|
text = textilizable("{{dmsftn(#{@file7.id})}}")
|
||||||
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
|
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
|
||||||
img = image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: 200)
|
size = Setting.thumbnails_size.to_i
|
||||||
|
img = image_tag(@file7.last_revision&.file&.variant(resize_to_limit: [size, size]),
|
||||||
|
alt: @file7.name,
|
||||||
|
style: "max-width: #{size}px; max-height: #{size}px;",
|
||||||
|
loading: 'lazy')
|
||||||
link = link_to(img,
|
link = link_to(img,
|
||||||
url,
|
url,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
rel: 'noopener',
|
rel: 'noopener',
|
||||||
title: h(@file7.last_revision.try(:tooltip)),
|
title: h(@file7.last_revision.try(:tooltip)),
|
||||||
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
||||||
assert text.include?(link), text
|
assert_equal "<p>#{link}</p>", text
|
||||||
end
|
end
|
||||||
|
|
||||||
# {{dmsftn(file_id file_id)}}
|
# {{dmsftn(file_id file_id)}}
|
||||||
def test_macro_dmsftn_multiple
|
def test_macro_dmsftn_multiple
|
||||||
text = textilizable("{{dmsftn(#{@file7.id} #{@file7.id})}}")
|
text = textilizable("{{dmsftn(#{@file7.id} #{@file7.id})}}")
|
||||||
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
|
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
|
||||||
img = image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: 200)
|
img = image_tag(@file7.last_revision&.file&.variant(resize_to_limit: [100, 100]),
|
||||||
|
alt: @file7.name,
|
||||||
|
style: 'max-width: 100px; max-height: 100px;',
|
||||||
|
loading: 'lazy')
|
||||||
link = link_to(img,
|
link = link_to(img,
|
||||||
url,
|
url,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
rel: 'noopener',
|
rel: 'noopener',
|
||||||
title: h(@file7.last_revision.try(:tooltip)),
|
title: h(@file7.last_revision.try(:tooltip)),
|
||||||
'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif')
|
'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif')
|
||||||
assert text.include?(link + link), text
|
assert_equal "<p>#{link}#{link}</p>", text
|
||||||
end
|
end
|
||||||
|
|
||||||
# {{dmsftn(file_id size=300)}}
|
# {{dmsftn(file_id size=300)}}
|
||||||
def test_macro_dmsftn_size
|
def test_macro_dmsftn_size
|
||||||
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
|
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
|
||||||
size = '300'
|
size = Setting.thumbnails_size.to_i
|
||||||
text = textilizable("{{dmsftn(#{@file7.id}, size=#{size})}}")
|
|
||||||
img = image_tag(url, alt: @file7.name, title: @file7.title, size: size)
|
# Size
|
||||||
|
text = textilizable("{{dmsftn(#{@file7.id}, size=300)}}")
|
||||||
|
img = image_tag(@file7.last_revision&.file&.variant(resize_to_limit: [300, 300]),
|
||||||
|
alt: @file7.name,
|
||||||
|
style: 'max-width: 300px; max-height: 300px;',
|
||||||
|
loading: 'lazy')
|
||||||
link = link_to(img,
|
link = link_to(img,
|
||||||
url,
|
url,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
rel: 'noopener',
|
rel: 'noopener',
|
||||||
title: h(@file7.last_revision.try(:tooltip)),
|
title: h(@file7.last_revision.try(:tooltip)),
|
||||||
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
||||||
assert text.include?(link), text
|
assert_equal "<p>#{link.gsub(/redirect\/.*\/#{@file7.name}/, '...')}</p>",
|
||||||
# TODO: arguments src and with and height are swapped
|
text.gsub(/redirect\/.*\/#{@file7.name}/, '...')
|
||||||
# size = '640x480'
|
|
||||||
# text = textilizable("{{dmsftn(#{@file7.id}, size=#{size})}}")
|
# Height
|
||||||
# img = image_tag(url, alt: @file7.name, title: @file7.title, width: 640, height: 480)
|
text = textilizable("{{dmsftn(#{@file7.id}, height=480)}}")
|
||||||
# link = link_to(img,
|
img = image_tag(@file7.last_revision&.file&.variant(resize_to_limit: [size, 480]),
|
||||||
# url,
|
alt: @file7.name,
|
||||||
# target: '_blank',
|
style: "max-width: #{size}px; max-height: 480px;",
|
||||||
# rel: 'noopener',
|
loading: 'lazy')
|
||||||
# title: h(@file7.last_revision.try(:tooltip)),
|
|
||||||
# 'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
|
||||||
# assert text.include?(link), text
|
|
||||||
height = '480'
|
|
||||||
text = textilizable("{{dmsftn(#{@file7.id}, height=#{height})}}")
|
|
||||||
img = image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: 480)
|
|
||||||
link = link_to(img,
|
|
||||||
url,
|
|
||||||
target: '_blank',
|
|
||||||
rel: 'noopener',
|
|
||||||
title: h(@file7.last_revision.try(:tooltip)),
|
|
||||||
'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif')
|
|
||||||
assert text.include?(link), text
|
|
||||||
width = '640'
|
|
||||||
text = textilizable("{{dmsftn(#{@file7.id}, width=#{width})}}")
|
|
||||||
img = image_tag(url, alt: @file7.name, title: @file7.title, width: 640, height: 'auto')
|
|
||||||
link = link_to(img,
|
link = link_to(img,
|
||||||
url,
|
url,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
rel: 'noopener',
|
rel: 'noopener',
|
||||||
title: h(@file7.last_revision.try(:tooltip)),
|
title: h(@file7.last_revision.try(:tooltip)),
|
||||||
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
||||||
assert text.include?(link), text
|
assert_equal "<p>#{link.gsub(/redirect\/.*\/#{@file7.name}/, '...')}</p>",
|
||||||
|
text.gsub(/redirect\/.*\/#{@file7.name}/, '...')
|
||||||
|
|
||||||
|
# Width
|
||||||
|
text = textilizable("{{dmsftn(#{@file7.id}, width=640)}}")
|
||||||
|
img = image_tag(@file7.last_revision&.file&.variant(resize_to_limit: [640, size]),
|
||||||
|
alt: @file7.name,
|
||||||
|
style: "max-width: 640px; max-height: #{size}px;",
|
||||||
|
loading: 'lazy')
|
||||||
|
link = link_to(img,
|
||||||
|
url,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noopener',
|
||||||
|
title: h(@file7.last_revision.try(:tooltip)),
|
||||||
|
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
||||||
|
assert_equal "<p>#{link.gsub(/redirect\/.*\/#{@file7.name}/, '...')}</p>",
|
||||||
|
text.gsub(/redirect\/.*\/#{@file7.name}/, '...')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_macro_dmsftn_no_permissions
|
def test_macro_dmsftn_no_permissions
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user