From 5d2996d0c21b7fd0d6d6a66c37c854f5dbc79b0f Mon Sep 17 00:00:00 2001 From: evazion Date: Sun, 30 Jan 2022 14:17:43 -0600 Subject: [PATCH] media assets: add media asset preview component. Add a view component for rendering thumbnails for media assets. This lets us properly show thumbnails on the upload listing page and the media assets listing page, including support for high pixel density thumbnails and video length icons for videos. Fixes not being able to see thumbnails on the /media_assets page. This is mostly copy/pasted from the post preview component. FIXME: don't duplicate code. --- .../media_asset_preview_component.rb | 39 +++++++++++++++++++ .../media_asset_preview_component.html.erb | 24 ++++++++++++ .../media_asset_preview_component.scss | 4 ++ .../src/styles/common/utilities.scss | 13 +++++-- app/views/media_assets/index.html.erb | 5 ++- app/views/media_assets/show.html.erb | 20 +++++++--- app/views/uploads/index.html.erb | 10 ++--- .../media_asset_preview_component_test.rb | 32 +++++++++++++++ 8 files changed, 130 insertions(+), 17 deletions(-) create mode 100644 app/components/media_asset_preview_component.rb create mode 100644 app/components/media_asset_preview_component/media_asset_preview_component.html.erb create mode 100644 app/components/media_asset_preview_component/media_asset_preview_component.scss create mode 100644 test/components/media_asset_preview_component_test.rb 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