media assets: show placeholder thumbnail when image is missing.

Make media assets show a placeholder thumbnail when the image is
missing. This can happen if the upload is still processing, or if the
media asset's image was expunged, or if the asset failed during upload
(usually because of some temporary network failure when trying to
distribute thumbnails to the backend image servers).

Fixes a problem where new images on the My Uploads or All Uploads pages
could have broken thumbnails if they were still in the uploading phase.
This commit is contained in:
evazion
2022-02-13 12:27:25 -06:00
parent eb032d54c1
commit 879363b585
7 changed files with 67 additions and 31 deletions

View File

@@ -5,25 +5,25 @@
class MediaAssetPreviewComponent < ApplicationComponent
DEFAULT_SIZE = 180
attr_reader :media_asset, :size, :fit, :link_target, :shrink_to_fit, :save_data
attr_reader :media_asset, :size, :link_target, :classes, :save_data
delegate :duration_to_hhmmss, :sound_icon, to: :helpers
renders_one :header
renders_one :missing_image
renders_one :footer
# @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)
def initialize(media_asset:, size: DEFAULT_SIZE, link_target: media_asset, classes: [], save_data: CurrentUser.save_data)
super
@media_asset = media_asset
@size = size.presence&.to_i || DEFAULT_SIZE
@link_target = link_target
@classes = classes
@save_data = save_data
@shrink_to_fit = shrink_to_fit
end
def variant

View File

@@ -1,28 +1,44 @@
<%= tag.article class: "media-asset-preview media-asset-preview-#{size}" do -%>
<%= link_to link_target, class: "inline-block relative flex justify-center", 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 %>
<%= tag.article class: ["media-asset-preview media-asset-preview-#{size}", *classes] do -%>
<%= link_to link_target, class: "inline-block relative", draggable: "false" do -%>
<%= header %>
<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 %>
<% if media_asset.nil? %>
<div class="media-asset-placeholder rounded border border-solid flex items-center justify-center w-<%= size %>px h-<%= size %>px">
<%= missing_image || "No image" %>
</div>
<% elsif media_asset.active? %>
<% 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 %>
<%= 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>
<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", crossorigin: "anonymous", draggable: "false" -%>
</picture>
<% else %>
<div class="media-asset-placeholder rounded border border-solid flex items-center justify-center w-<%= size %>px h-<%= size %>px">
<% if media_asset.processing? %>
Loading
<% elsif media_asset.failed? %>
Failed
<% else %>
Deleted
<% end %>
</div>
<% end %>
<% end %>
<div>
<%= footer %>
</div>
<%= footer %>
<% end %>

View File

@@ -2,3 +2,7 @@
color: var(--preview-icon-color);
background: var(--preview-icon-background);
}
.media-asset-placeholder {
background-color: var(--media-asset-placeholder-background-color);
}

View File

@@ -163,6 +163,8 @@ html {
--preview-icon-color: var(--inverse-text-color);
--preview-icon-background: rgba(0, 0, 0, 0.7);
--media-asset-placeholder-background-color: var(--grey-1);
--modqueue-tag-warning-color: var(--red-1);
--file-upload-component-background-color: var(--body-background-color);
@@ -374,6 +376,8 @@ body[data-current-user-theme="dark"] {
--preview-has-parent-color: var(--orange-3);
--preview-selected-color: rgba(255, 255, 255, 0.25);
--media-asset-placeholder-background-color: var(--grey-8);
--modqueue-tag-warning-color: var(--red-7);
--file-upload-component-background-color: var(--grey-8);

View File

@@ -116,10 +116,17 @@ $spacer: 0.25rem; /* 4px */
.pr-4 { padding-right: 4 * $spacer; }
.w-auto { width: auto; }
.w-min { width: min-content; }
.w-max { width: max-content; }
.w-sm { width: 24rem; }
.w-md { width: 28rem; }
.w-1\/4 { width: 25%; }
.w-full { width: 100%; }
.w-150px { width: 150px; }
.w-180px { width: 180px; }
.w-225px { width: 225px; }
.w-270px { width: 270px; }
.w-360px { width: 360px; }
.max-w-full { max-width: 100%; }
@@ -130,6 +137,11 @@ $spacer: 0.25rem; /* 4px */
.h-8 { height: 8 * $spacer; }
.h-10 { height: 10 * $spacer; }
.h-12 { height: 12 * $spacer; }
.h-150px { height: 150px; }
.h-180px { height: 180px; }
.h-225px { height: 225px; }
.h-270px { height: 270px; }
.h-360px { height: 360px; }
.max-h-150px { max-height: 150px; }
.max-h-180px { max-height: 180px; }

View File

@@ -1,7 +1,7 @@
<%= table_for @media_assets, class: "striped autofit" do |t| %>
<% t.column "File", td: { class: "text-center" } do |media_asset| %>
<% if policy(media_asset).can_see_image? %>
<%= render MediaAssetPreviewComponent.new(media_asset: media_asset, save_data: CurrentUser.save_data, shrink_to_fit: false) %>
<%= render MediaAssetPreviewComponent.new(media_asset: media_asset, save_data: CurrentUser.save_data) %>
<% end %>
<% end %>

View File

@@ -7,14 +7,14 @@
</span>
</div>
<%= table_for @uploads, class: "striped autofit", width: "100%" do |t| %>
<%= table_for @uploads, class: "striped", width: "100%" do |t| %>
<% t.column "Upload", td: { class: "text-center" } do |upload| %>
<% upload.media_assets.first.tap do |media_asset| %>
<% if media_asset.present? %>
<% if media_asset.post.present? %>
<%= render MediaAssetPreviewComponent.new(media_asset: media_asset, link_target: media_asset.post, save_data: CurrentUser.save_data, shrink_to_fit: false) %>
<%= render MediaAssetPreviewComponent.new(media_asset: media_asset, link_target: media_asset.post, save_data: CurrentUser.save_data) %>
<% else %>
<%= render MediaAssetPreviewComponent.new(media_asset: media_asset, link_target: upload, save_data: CurrentUser.save_data, shrink_to_fit: false) %>
<%= render MediaAssetPreviewComponent.new(media_asset: media_asset, link_target: upload, save_data: CurrentUser.save_data) %>
<% end %>
<% end %>
<% end %>