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.
This commit is contained in:
evazion
2022-01-30 14:17:43 -06:00
parent 43c4158d36
commit 5d2996d0c2
8 changed files with 130 additions and 17 deletions

View File

@@ -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

View File

@@ -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? %>
<div class="media-asset-animation-icon absolute top-0.5 left-0.5 p-0.5 m-0.5 leading-none rounded text-xs font-arial font-bold">
<span class="media-asset-duration align-middle">
<%= duration_to_hhmmss(media_asset.duration) %>
</span>
</div>
<% end %>
<picture>
<% 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" -%>
</picture>
<% end %>
<% end %>

View File

@@ -0,0 +1,4 @@
.media-asset-animation-icon {
color: var(--preview-icon-color);
background: var(--preview-icon-background);
}

View File

@@ -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; }

View File

@@ -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 %>

View File

@@ -2,13 +2,21 @@
<div id="a-show" class="fixed-width-container">
<h1 class="mb-4">Media Asset</h1>
<% if @post.present? %>
<%= post_preview(@post, show_deleted: true) %>
<% else %>
<p>MD5: <%= link_to @media_asset.md5, posts_path(md5: @media_asset.md5) %></p>
<% end %>
<%= render MediaAssetPreviewComponent.new(media_asset: @media_asset, link_target: @media_asset.variant("original").file_url, save_data: CurrentUser.save_data) %>
<table class="striped aligned-vertical">
<% if @post.present? %>
<tr>
<th>Post</th>
<td><%= link_to "##{@post.id}", @post %></td>
</tr>
<% end %>
<tr>
<th>MD5</th>
<td><%= @media_asset.md5 %></td>
</tr>
<% @media_asset.metadata.sort.each do |key, value| %>
<tr>
<th><%= key %></th>
@@ -17,4 +25,4 @@
<% end %>
</table>
</div>
</div>
</div>

View File

@@ -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| %>
<div>
<strong>Upload</strong>
<span><%= link_to "##{upload.id}", upload %></span>

View File

@@ -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