posts: add adjustable thumbnail sizes (#4932).

Add a menu in the top right of the post index page that lets you select
the thumbnail size.

This menu is currently hidden until the new thumbnails have been generated.

On desktop, there are five thumbnail sizes:

* Small:    150x150 (https://danbooru.donmai.us/posts?size=150)
* Medium:   180x180 (https://danbooru.donmai.us/posts?size=180)
* Large:    225x225 (https://danbooru.donmai.us/posts?size=225)
* Huge:     270x270 (https://danbooru.donmai.us/posts?size=270)
* Gigantic: 360x360 (https://danbooru.donmai.us/posts?size=360)

On mobile, there are four sizes:

* Small:  150x150 / 3 posts per row (https://danbooru.donmai.us/posts?size=150)
* Medium: 180x180 / 2 posts per row (https://danbooru.donmai.us/posts?size=180)
* Large:  225x225 / 2 posts per row (https://danbooru.donmai.us/posts?size=225)
* Huge:   360x360 / 1 posts per row (https://danbooru.donmai.us/posts?size=360)

There are two extra sizes that aren't listed in the menu:

* 225x360 (https://danbooru.donmai.us/posts?size=225w)
* 270x360 (https://danbooru.donmai.us/posts?size=270w)

These sizes are good for tall thumbnails, but not so much for wide
thumbnails. They aren't listed because in practice they're a bit too big.

The 225x225 and 270x270 sizes are really just 360x360 thumbnails scaled
down in HTML. This means 225x225 and 360x360 thumbnails both use the
same amount of bandwidth.

Thumbnail size is currently a per-search option, not a persistent
account-level setting.

This changes the HTML structure of thumbnails somewhat, so this may
break userscripts and custom CSS.
This commit is contained in:
evazion
2021-12-04 06:04:13 -06:00
parent 17537084fe
commit 8841de68ac
20 changed files with 224 additions and 95 deletions

View File

@@ -2,7 +2,14 @@
class PopupMenuComponent < ApplicationComponent
include ViewComponent::SlotableV2
delegate :ellipsis_icon, to: :helpers
attr_reader :classes
renders_one :button
renders_many :items
# @param classes [String] A list of CSS classes for the root element.
def initialize(classes: nil)
@classes = classes
end
end

View File

@@ -1,6 +1,10 @@
<div class="popup-menu">
<div class="popup-menu <%= classes %>">
<a class="popup-menu-button" href="javascript:void(0)">
<%= ellipsis_icon %>
<% if button.present? %>
<%= button %>
<% else %>
<%= helpers.ellipsis_icon %>
<% end %>
</a>
<ul class="popup-menu-content">

View File

@@ -9,7 +9,8 @@ div.popup-menu {
justify-content: center;
width: 100%;
height: 100%;
border-radius: 50%;
border-radius: 4px;
padding: 4px 8px;
color: var(--muted-text-color);
// the popup menu is open

View File

@@ -7,7 +7,7 @@
# * Inline galleries that fit on a single row, as seen in parent/child post sets.
#
class PostGalleryComponent < ApplicationComponent
attr_reader :posts, :current_user, :inline, :options
attr_reader :posts, :current_user, :inline, :size, :options
delegate :post_preview, :numbered_paginator, to: :helpers
@@ -18,14 +18,22 @@ class PostGalleryComponent < ApplicationComponent
# @param current_user [User] The current user.
# @param inline [Boolean] If true, the gallery is rendered as a single row with a
# horizontal scrollbar. If false, the gallery is rendered as a grid of thumbnails.
# @param size [String] The size of thumbnails in the gallery. Can be "150",
# "180", "225", "225w", "270", "270w", or "360".
# @param options [Hash] A set of options given to the thumbnail in `post_preview`.
def initialize(posts:, current_user:, inline: false, **options)
def initialize(posts:, current_user:, inline: false, size: PostPreviewComponent::DEFAULT_SIZE, **options)
super
@posts = posts
@posts = @posts.includes(:media_asset) if posts.is_a?(ActiveRecord::Relation)
@current_user = current_user
@inline = inline
@options = options
if size.to_s.in?(PostPreviewComponent::SIZES)
@size = size
else
@size = PostPreviewComponent::DEFAULT_SIZE
end
end
def gallery_type

View File

@@ -1,8 +1,8 @@
<div class="post-gallery post-gallery-<%= gallery_type %>">
<div class="post-gallery post-gallery-<%= gallery_type %> post-gallery-<%= size %>">
<% if empty? %>
<p>No posts found.</p>
<% else %>
<div class="posts-container grid gap-1 grid-cols-2 md:block">
<div class="posts-container grid gap-2">
<% posts.each do |post| -%>
<% %><%= post_preview(post, **options) -%>
<% end -%>

View File

@@ -12,12 +12,30 @@
}
}
@media screen and (max-width: 660px) {
.post-gallery-full {
.posts-container {
&.user-disable-cropped-false article.post-preview img.has-cropped-true {
object-fit: none;
}
}
.post-gallery {
.posts-container {
place-items: center;
}
.post-preview-image {
max-width: 100%;
}
&.post-gallery-150 .posts-container { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
&.post-gallery-180 .posts-container { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
&.post-gallery-225 .posts-container { grid-template-columns: repeat(auto-fill, minmax(225px, 1fr)); }
&.post-gallery-225w .posts-container { grid-template-columns: repeat(auto-fill, minmax(225px, 1fr)); }
&.post-gallery-270 .posts-container { grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); }
&.post-gallery-270w .posts-container { grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); }
&.post-gallery-360 .posts-container { grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); }
@media screen and (max-width: 660px) {
&.post-gallery-150 .posts-container { grid-template-columns: repeat(3, auto); }
&.post-gallery-180 .posts-container { grid-template-columns: repeat(2, auto); }
&.post-gallery-225 .posts-container { grid-template-columns: repeat(2, auto); }
&.post-gallery-225w .posts-container { grid-template-columns: repeat(2, auto); }
&.post-gallery-270 .posts-container { grid-template-columns: repeat(2, auto); }
&.post-gallery-270w .posts-container { grid-template-columns: repeat(2, auto); }
&.post-gallery-360 .posts-container { grid-template-columns: repeat(1, auto); }
}
}

View File

@@ -1,18 +1,33 @@
# frozen_string_literal: true
class PostPreviewComponent < ApplicationComponent
DEFAULT_SIZE = "150"
SIZES = %w[150 180 225 225w 270 270w 360]
with_collection_parameter :post
attr_reader :post, :tags, :show_deleted, :link_target, :pool, :similarity, :recommended, :show_votes, :compact, :size, :current_user, :options
attr_reader :post, :tags, :size, :show_deleted, :link_target, :pool, :similarity, :recommended, :show_votes, :compact, :show_size, :current_user, :options
delegate :external_link_to, :time_ago_in_words_tagged, :duration_to_hhmmss, :render_post_votes, :empty_heart_icon, :sound_icon, to: :helpers
delegate :image_width, :image_height, :file_ext, :file_size, :duration, :is_animated?, to: :media_asset
delegate :media_asset, to: :post
def initialize(post:, tags: "", show_deleted: false, show_votes: false, link_target: post, pool: nil, similarity: nil, recommended: nil, compact: nil, size: nil, current_user: CurrentUser.user, **options)
# @param post [Post] The post to show the thumbnail for.
# @param tags [String] The current tag search, if any.
# @param size [String] The size of the thumbnail. One of "150", "180", "225",
# "225w", "270", "270w", or "360".
# @param show_deleted [Boolean] If true, show thumbnails for deleted posts.
# If false, hide thumbnails of deleted posts.
# @param show_votes [Boolean] If true, show scores and vote buttons beneath the thumbnail.
# @param show_size [Boolean] If true, show filesize and resolution beneath the thumbnail.
# @param link_target [ApplicationRecord] What the thumbnail links to (default: the post).
# @param current_user [User] The current user.
def initialize(post:, tags: "", size: DEFAULT_SIZE, show_deleted: false, show_votes: false, link_target: post, pool: nil, similarity: nil, recommended: nil, compact: nil, show_size: nil, current_user: CurrentUser.user, **options)
super
@post = post
@tags = tags.presence
@size = size.presence || DEFAULT_SIZE
@show_deleted = show_deleted
@show_votes = show_votes
@link_target = link_target
@@ -20,7 +35,7 @@ class PostPreviewComponent < ApplicationComponent
@similarity = similarity.round(1) if similarity.present?
@recommended = recommended.round(1) if recommended.present?
@compact = compact
@size = post.file_size if size.present?
@show_size = show_size
@current_user = current_user
@options = options
end
@@ -33,16 +48,33 @@ class PostPreviewComponent < ApplicationComponent
{ class: [classes, *preview_class].compact.join(" "), **data_attributes }
end
def preview_width
variant.width
end
def preview_height
variant.height
end
def variant
@variant ||= media_asset.variant(:preview)
case size
when "150"
media_asset.variant("preview")
when "180"
media_asset.variant("180x180")
when "225", "225w"
media_asset.variant("360x360")
when "270", "270w"
media_asset.variant("360x360")
when "360"
media_asset.variant("360x360")
else
raise NotImplementedError
end
end
def preview_srcset
if size == "180"
"#{media_asset.variant("180x180").file_url} 1x, #{media_asset.variant("360x360").file_url} 2x"
else
nil
end
end
def preview_url
variant.file_url
end
def tooltip
@@ -51,13 +83,14 @@ class PostPreviewComponent < ApplicationComponent
def preview_class
klass = ["post-preview"]
klass << "captioned" if pool || size || similarity || recommended
klass << "captioned" if pool || show_size || similarity || recommended
klass << "post-status-pending" if post.is_pending?
klass << "post-status-flagged" if post.is_flagged?
klass << "post-status-deleted" if post.is_deleted?
klass << "post-status-has-parent" if post.parent_id
klass << "post-status-has-children" if post.has_visible_children?
klass << "post-preview-compact" if compact
klass << "post-preview-#{size}"
klass
end

View File

@@ -1,5 +1,5 @@
<%= tag.article id: "post_#{post.id}", **article_attrs do -%>
<%= link_to polymorphic_path(link_target, q: tags) do -%>
<%= link_to polymorphic_path(link_target, q: tags), class: "post-preview-link" do -%>
<% if is_animated? || has_sound? %>
<div class="post-animation-icon absolute top-0.5 left-0.5 p-0.5 m-0.5 leading-none rounded text-xs font-arial font-bold">
<% if is_animated? %>
@@ -13,9 +13,7 @@
</div>
<% end %>
<%= tag.picture do -%>
<%= tag.img src: post.preview_file_url, style: "min-width: #{preview_width}px; min-height: #{preview_height}px;", title: tooltip, alt: "post ##{post.id}", crossorigin: "anonymous" -%>
<% end -%>
<%= tag.img srcset: preview_srcset, src: preview_url, width: variant.width, height: variant.height, class: "post-preview-image", title: tooltip, alt: "post ##{post.id}", crossorigin: "anonymous" -%>
<% end -%>
<% if pool -%>
<p class="desc">
@@ -37,9 +35,9 @@
<p class="desc">
<%= link_to "#{similarity}%", iqdb_queries_path(post_id: post.id) %> similarity
</p>
<% elsif size -%>
<% elsif show_size -%>
<p class="desc">
<%= link_to number_to_human_size(size), post.file_url %>
<%= link_to number_to_human_size(file_size), post.file_url %>
(<%= post.image_width %>x<%= post.image_height %>)
</p>
<% elsif recommended -%>
@@ -53,7 +51,7 @@
<% end %>
</p>
<% elsif show_votes -%>
<div class="post-preview-score text-sm mt-1">
<div class="post-preview-score text-sm text-center mt-1">
<%= render_post_votes post, current_user: current_user %>
</div>
<% end -%>

View File

@@ -1,12 +1,11 @@
@import "../../javascript/src/styles/base/000_vars.scss";
article.post-preview {
text-align: center;
display: inline-block;
position: relative;
overflow: hidden;
a {
.post-preview-link {
display: inline-block;
position: relative;
@@ -30,17 +29,16 @@ article.post-preview {
font-size: var(--text-sm);
margin-bottom: 0;
}
img {
margin: auto;
}
}
/* Avoid dead space around thumbnails in tables. */
table article.post-preview {
height: auto;
.post-preview-image {
width: auto;
margin: 0;
height: auto;
}
@media screen and (min-width: 660px) {
.post-preview-225 .post-preview-image { max-height: 225px; }
.post-preview-270 .post-preview-image { max-height: 270px; }
}
.post-preview {
@@ -117,16 +115,8 @@ body[data-current-user-can-approve-posts="true"] .post-preview {
}
}
@media screen and (min-width: 660px) {
article.post-preview {
width: 154px;
margin: 0 10px 10px 0;
vertical-align: top;
}
}
@media screen and (max-width: 660px) {
article.post-preview img {
.post-preview-image {
border: none !important;
}
}