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? %>
+
+ | Post |
+ <%= link_to "##{@post.id}", @post %> |
+
+ <% end %>
+
+
+ | MD5 |
+ <%= @media_asset.md5 %> |
+
+
<% @media_asset.metadata.sort.each do |key, value| %>
| <%= key %> |
@@ -17,4 +25,4 @@
<% end %>
-
\ 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