diff --git a/app/components/media_asset_preview_component.rb b/app/components/media_asset_preview_component.rb new file mode 100644 index 000000000..3bb91e2d2 --- /dev/null +++ b/app/components/media_asset_preview_component.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# A component for showing a thumbnail preview for a media asset. +# XXX This is mostly duplicated from PostPreviewComponent. +class MediaAssetPreviewComponent < ApplicationComponent + DEFAULT_SIZE = 180 + + attr_reader :media_asset, :size, :fit, :link_target, :shrink_to_fit, :save_data + delegate :duration_to_hhmmss, :sound_icon, to: :helpers + + # @param media_asset [MediaAsset] The media asset to show the thumbnail for. + # @param size [String] The size of the thumbnail. One of 150, 180, 225, 270, or 360. + # @param link_target [ApplicationRecord] What the thumbnail links to (default: the media asset). + # @param shrink_to_fit [Boolean] If true, allow the thumbnail to shrink to fit the containing element. + # If false, make the thumbnail a fixed width and height. + # @param save_data [Boolean] If true, save data by not serving higher quality thumbnails + # on 2x pixel density displays. Default: false. + def initialize(media_asset:, size: DEFAULT_SIZE, link_target: media_asset, shrink_to_fit: true, save_data: CurrentUser.save_data) + super + @media_asset = media_asset + @size = size.presence&.to_i || DEFAULT_SIZE + @link_target = link_target + @save_data = save_data + @shrink_to_fit = shrink_to_fit + end + + def variant + case size + when 150, 180 + media_asset.variant("180x180") + when 225, 270, 360 + media_asset.variant("360x360") + when 720 + media_asset.variant("720x720") + else + raise NotImplementedError + end + end +end diff --git a/app/components/media_asset_preview_component/media_asset_preview_component.html.erb b/app/components/media_asset_preview_component/media_asset_preview_component.html.erb new file mode 100644 index 000000000..cf5c56820 --- /dev/null +++ b/app/components/media_asset_preview_component/media_asset_preview_component.html.erb @@ -0,0 +1,24 @@ +<%= tag.article class: "media-asset-preview media-asset-preview-#{size}" do -%> + <%= link_to link_target, class: "inline-block relative", draggable: "false" do -%> + <% if media_asset.is_animated? %> +
+ + <%= duration_to_hhmmss(media_asset.duration) %> + +
+ <% end %> + + + <% unless save_data %> + <% case size %> + <% when 150, 180 %> + <%= tag.source type: "image/jpeg", srcset: "#{media_asset.variant("180x180").file_url} 1x, #{media_asset.variant("360x360").file_url} 2x" %> + <% when 225, 270, 360 %> + <%= tag.source type: "image/webp", srcset: "#{media_asset.variant("360x360").file_url} 1x, #{media_asset.variant("720x720").file_url} 2x" %> + <% end %> + <% end %> + + <%= tag.img src: variant.file_url, width: variant.width, height: variant.height, class: "media-asset-preview-image w-auto h-auto max-h-#{size}px #{"max-w-full" if shrink_to_fit}", crossorigin: "anonymous", draggable: "false" -%> + + <% end %> +<% end %> diff --git a/app/components/media_asset_preview_component/media_asset_preview_component.scss b/app/components/media_asset_preview_component/media_asset_preview_component.scss new file mode 100644 index 000000000..3288e8bd7 --- /dev/null +++ b/app/components/media_asset_preview_component/media_asset_preview_component.scss @@ -0,0 +1,4 @@ +.media-asset-animation-icon { + color: var(--preview-icon-color); + background: var(--preview-icon-background); +} diff --git a/app/javascript/src/styles/common/utilities.scss b/app/javascript/src/styles/common/utilities.scss index 0b5267adc..88ae1c377 100644 --- a/app/javascript/src/styles/common/utilities.scss +++ b/app/javascript/src/styles/common/utilities.scss @@ -97,6 +97,7 @@ $spacer: 0.25rem; /* 4px */ .pr-2 { padding-right: 2 * $spacer; } .pr-4 { padding-right: 4 * $spacer; } +.w-auto { width: auto; } .w-sm { width: 24rem; } .w-md { width: 28rem; } .w-1\/4 { width: 25%; } @@ -104,11 +105,17 @@ $spacer: 0.25rem; /* 4px */ .max-w-full { max-width: 100%; } -.h-1 { height: 1 * $spacer; } -.h-3 { height: 3 * $spacer; } -.h-10 { height: 10 * $spacer; } +.h-auto { height: auto; } +.h-1 { height: 1 * $spacer; } +.h-3 { height: 3 * $spacer; } +.h-10 { height: 10 * $spacer; } +.max-h-150px { max-height: 150px; } +.max-h-180px { max-height: 180px; } +.max-h-225px { max-height: 225px; } +.max-h-270px { max-height: 270px; } .max-h-360px { max-height: 360px; } +.max-h-720px { max-height: 720px; } .space-x-1 > * + * { margin-left: 1 * $spacer; } .space-x-2 > * + * { margin-left: 2 * $spacer; } diff --git a/app/views/media_assets/index.html.erb b/app/views/media_assets/index.html.erb index f69972245..3a0dbc2e4 100644 --- a/app/views/media_assets/index.html.erb +++ b/app/views/media_assets/index.html.erb @@ -17,9 +17,10 @@ <% end %> <%= table_for @media_assets, class: "striped autofit" do |t| %> - <% t.column "MD5", td: { class: "font-monospace" } do |media_asset| %> - <%= link_to media_asset.md5, posts_path(md5: media_asset.md5) %> + <% t.column "File", td: { class: "text-center" } do |media_asset| %> + <%= render MediaAssetPreviewComponent.new(media_asset: media_asset, save_data: CurrentUser.save_data, shrink_to_fit: false) %> <% end %> + <% t.column :image_width %> <% t.column :image_height %> <% t.column :file_size %> diff --git a/app/views/media_assets/show.html.erb b/app/views/media_assets/show.html.erb index dbb20a74f..38d902516 100644 --- a/app/views/media_assets/show.html.erb +++ b/app/views/media_assets/show.html.erb @@ -2,13 +2,21 @@

Media Asset

- <% if @post.present? %> - <%= post_preview(@post, show_deleted: true) %> - <% else %> -

MD5: <%= link_to @media_asset.md5, posts_path(md5: @media_asset.md5) %>

- <% end %> + <%= render MediaAssetPreviewComponent.new(media_asset: @media_asset, link_target: @media_asset.variant("original").file_url, save_data: CurrentUser.save_data) %> + <% if @post.present? %> + + + + + <% end %> + + + + + + <% @media_asset.metadata.sort.each do |key, value| %> @@ -17,4 +25,4 @@ <% end %>
Post<%= link_to "##{@post.id}", @post %>
MD5<%= @media_asset.md5 %>
<%= key %>
- \ No newline at end of file + diff --git a/app/views/uploads/index.html.erb b/app/views/uploads/index.html.erb index 086559a05..b4174f828 100644 --- a/app/views/uploads/index.html.erb +++ b/app/views/uploads/index.html.erb @@ -8,18 +8,16 @@ <%= f.submit "Search" %> <% end %> - <%= table_for @uploads, class: "striped autofit", width: "100%" do |t| %> - <% t.column "File" do |upload| %> + <%= table_for @uploads, class: "striped autofit" do |t| %> + <% t.column "File", td: { class: "text-center" } do |upload| %> <% upload.media_assets.first.tap do |media_asset| %> <% if media_asset.present? %> - <%= link_to upload, class: "inline-block" do %> - <%= tag.img src: media_asset.variant("180x180").file_url %> - <% end %> + <%= render MediaAssetPreviewComponent.new(media_asset: media_asset, link_target: upload, save_data: CurrentUser.save_data, shrink_to_fit: false) %> <% end %> <% end %> <% end %> - <% t.column "Info", td: {class: "col-expand upload-info"} do |upload| %> + <% t.column "Info", td: {class: "upload-info"} do |upload| %>
Upload <%= link_to "##{upload.id}", upload %> diff --git a/test/components/media_asset_preview_component_test.rb b/test/components/media_asset_preview_component_test.rb new file mode 100644 index 000000000..3ae2d2882 --- /dev/null +++ b/test/components/media_asset_preview_component_test.rb @@ -0,0 +1,32 @@ +require "test_helper" + +class MediaAssetPreviewComponentTest < ViewComponent::TestCase + include Rails.application.routes.url_helpers + + def render_preview(media_asset, **options) + render_inline(MediaAssetPreviewComponent.new(media_asset: media_asset, **options)) + end + + context "The MediaAssetPreviewComponent" do + context "for an image" do + should "render the preview" do + media_asset = create(:media_asset) + node = render_preview(media_asset) + + assert_equal(media_asset_path(media_asset), node.css("article a").attr("href").value) + assert_equal(media_asset.variant("180x180").file_url, node.css("article img").attr("src").value) + end + end + + context "for a video" do + should "render the icon" do + media_asset = create(:media_asset, file_ext: "mp4", duration: 30) + node = render_preview(media_asset) + + assert_equal(media_asset_path(media_asset), node.css("article a").attr("href").value) + assert_equal(media_asset.variant("180x180").file_url, node.css("article img").attr("src").value) + assert_equal("0:30", node.css("article .media-asset-duration").text.strip) + end + end + end +end