HTML: Use new buttons for thumbnail overlays

In addition, this commit also heavily changes the structure of the
generic "video card" item. Main benefits:
  * Improved accessibility for keyboard users
  * Many styling glitches were fixed
  * PlaylistVideos now use the same items as the rest
  * Elements all have distinct CSS classes
  * Design can be expanded to add more icons
This commit is contained in:
Samantaz Fox 2023-04-22 12:58:46 +02:00
parent 7bd6d0ac49
commit b6bbfb9b20
No known key found for this signature in database
GPG key ID: F42821059186176E
3 changed files with 98 additions and 118 deletions

View file

@ -152,9 +152,15 @@ body a.pure-button-primary:focus {
color: #fff; color: #fff;
} }
button.pure-button-secondary:hover, .pure-button-secondary:hover,
button.pure-button-secondary:focus { .pure-button-secondary:focus {
border-color: rgba(0, 182, 240, 1); color: rgb(0, 182, 240);
border-color: rgb(0, 182, 240);
}
.pure-button-secondary.low-profile {
padding: 5px 10px;
margin: 0;
} }
@ -163,21 +169,19 @@ button.pure-button-secondary:focus {
*/ */
div.thumbnail { div.thumbnail {
padding: 28.125%;
position: relative; position: relative;
width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
img.thumbnail { img.thumbnail {
position: absolute; display: block; /* See: https://stackoverflow.com/a/11635197 */
width: 100%; width: 100%;
height: 100%;
left: 0;
top: 0;
object-fit: cover; object-fit: cover;
} }
div.watched-overlay { div.watched-overlay {
z-index: 50;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -195,28 +199,27 @@ div.watched-indicator {
background-color: red; background-color: red;
} }
.length { div.thumbnail > .top-left-overlay,
div.thumbnail > .bottom-right-overlay {
z-index: 100; z-index: 100;
position: absolute; position: absolute;
background-color: rgba(35, 35, 35, 0.75); padding: 0;
color: #fff; margin: 0;
border-radius: 2px;
padding: 2px;
font-size: 16px; font-size: 16px;
right: 0.25em;
bottom: -0.75em;
} }
.watched { .top-left-overlay { top: 0.6em; left: 0.6em; }
z-index: 100; .bottom-right-overlay { bottom: 0.6em; right: 0.6em; }
position: absolute;
background-color: rgba(35, 35, 35, 0.75); .length {
padding: 1px;
margin: -2px 0;
color: #fff; color: #fff;
border-radius: 2px; border-radius: 3px;
padding: 4px 8px 4px 8px; }
font-size: 16px;
left: 0.2em; .length, .top-left-overlay button {
top: -0.7em; background-color: rgba(35, 35, 35, 0.85);
} }

View file

@ -7,7 +7,7 @@
<a href="/channel/<%= item.ucid %>"> <a href="/channel/<%= item.ucid %>">
<% if !env.get("preferences").as(Preferences).thin_mode %> <% if !env.get("preferences").as(Preferences).thin_mode %>
<center> <center>
<img loading="lazy" tabindex="-1" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>" alt="" /> <img loading="lazy" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>" alt="" />
</center> </center>
<% end %> <% end %>
<p dir="auto"><%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p> <p dir="auto"><%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
@ -25,7 +25,7 @@
<a style="width:100%" href="<%= url %>"> <a style="width:100%" href="<%= url %>">
<% if !env.get("preferences").as(Preferences).thin_mode %> <% if !env.get("preferences").as(Preferences).thin_mode %>
<div class="thumbnail"> <div class="thumbnail">
<img loading="lazy" tabindex="-1" class="thumbnail" src="<%= URI.parse(item.thumbnail || "/").request_target %>" alt="" /> <img loading="lazy" class="thumbnail" src="<%= URI.parse(item.thumbnail || "/").request_target %>" alt="" />
<p class="length"><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p> <p class="length"><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p>
</div> </div>
<% end %> <% end %>
@ -38,7 +38,7 @@
<a href="/watch?v=<%= item.id %>&list=<%= item.rdid %>"> <a href="/watch?v=<%= item.id %>&list=<%= item.rdid %>">
<% if !env.get("preferences").as(Preferences).thin_mode %> <% if !env.get("preferences").as(Preferences).thin_mode %>
<div class="thumbnail"> <div class="thumbnail">
<img loading="lazy" tabindex="-1" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" /> <img loading="lazy" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
<% if item.length_seconds != 0 %> <% if item.length_seconds != 0 %>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p> <p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<% end %> <% end %>
@ -54,104 +54,79 @@
<a href="/channel/<%= item.ucid %>"> <a href="/channel/<%= item.ucid %>">
<p dir="auto"><b><%= HTML.escape(item.author) %></b></p> <p dir="auto"><b><%= HTML.escape(item.author) %></b></p>
</a> </a>
<% when PlaylistVideo %>
<a style="width:100%" href="/watch?v=<%= item.id %>&list=<%= item.plid %>&index=<%= item.index %>">
<% if !env.get("preferences").as(Preferences).thin_mode %>
<div class="thumbnail">
<img loading="lazy" tabindex="-1" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
<% if plid_form = env.get?("remove_playlist_items") %>
<form data-onsubmit="return_false" action="/playlist_ajax?action_remove_video=1&set_video_id=<%= item.index %>&playlist_id=<%= plid_form %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<p class="watched">
<button type="submit" style="all:unset" data-onclick="remove_playlist_item" data-index="<%= item.index %>" data-plid="<%= plid_form %>"><i class="icon ion-md-trash"></i></button>
</p>
</form>
<% end %>
<% if item.responds_to?(:live_now) && item.live_now %>
<p class="length"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p>
<% elsif item.length_seconds != 0 %>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<% end %>
<% if item_watched %>
<div class="watched-overlay"></div>
<div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
<% end %>
</div>
<% end %>
<p dir="auto"><%= HTML.escape(item.title) %></p>
</a>
<div class="video-card-row flexible">
<div class="flex-left"><a href="/channel/<%= item.ucid %>">
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %></p>
</a></div>
<% endpoint_params = "?v=#{item.id}&list=#{item.plid}" %>
<%= rendered "components/video-context-buttons" %>
</div>
<div class="video-card-row flexible">
<div class="flex-left">
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %>
<p dir="auto"><%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %></p>
<% elsif Time.utc - item.published > 1.minute %>
<p dir="auto"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></p>
<% end %>
</div>
<% if item.responds_to?(:views) && item.views %>
<div class="flex-right">
<p dir="auto"><%= translate_count(locale, "generic_views_count", item.views || 0, NumberFormatting::Short) %></p>
</div>
<% end %>
</div>
<% when Category %> <% when Category %>
<% else %> <% else %>
<a style="width:100%" href="/watch?v=<%= item.id %>"> <%-
<% if !env.get("preferences").as(Preferences).thin_mode %> # `endpoint_params` is used for the "video-context-buttons" component
<div class="thumbnail"> if item.is_a?(PlaylistVideo)
<img loading="lazy" tabindex="-1" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" /> link_url = "/watch?v=#{item.id}&list=#{item.plid}&index=#{item.index}"
<% if env.get? "show_watched" %> endpoint_params = "?v=#{item.id}&list=#{item.plid}"
<form data-onsubmit="return_false" action="/watch_ajax?action_mark_watched=1&id=<%= item.id %>&referer=<%= env.get("current_page") %>" method="post"> else
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>"> link_url = "/watch?v=#{item.id}"
<p class="watched"> endpoint_params = "?v=#{item.id}"
<button type="submit" style="all:unset" data-onclick="mark_watched" data-id="<%= item.id %>"> end
<i data-mouse="switch_classes" data-switch-classes="ion-ios-eye-off,ion-ios-eye" class="icon ion-ios-eye"></i> -%>
</button>
</p>
</form>
<% elsif plid_form = env.get? "add_playlist_items" %>
<form data-onsubmit="return_false" action="/playlist_ajax?action_add_video=1&video_id=<%= item.id %>&playlist_id=<%= plid_form %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<p class="watched">
<button type="submit" style="all:unset" data-onclick="add_playlist_item" data-id="<%= item.id %>" data-plid="<%= plid_form %>"><i class="icon ion-md-add"></i></button>
</p>
</form>
<% end %>
<% if item.responds_to?(:live_now) && item.live_now %> <div class="thumbnail">
<p class="length" dir="auto"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p> <%- if !env.get("preferences").as(Preferences).thin_mode -%>
<% elsif item.length_seconds != 0 %> <a tabindex="-1" href="<%= link_url %>">
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p> <img loading="lazy" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
<% end %> </a>
<%- end -%>
<% if item_watched %> <div class="top-left-overlay">
<div class="watched-overlay"></div> <%- if env.get? "show_watched" -%>
<div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div> <form data-onsubmit="return_false" action="/watch_ajax?action_mark_watched=1&id=<%= item.id %>&referer=<%= env.get("current_page") %>" method="post">
<% end %> <input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
</div> <button type="submit" class="pure-button pure-button-secondary low-profile"
data-onclick="mark_watched" data-id="<%= item.id %>">
<i data-mouse="switch_classes" data-switch-classes="ion-ios-eye-off,ion-ios-eye" class="icon ion-ios-eye"></i>
</button>
</form>
<%- end -%>
<%- if plid_form = env.get?("add_playlist_items") -%>
<%- form_parameters = "action_add_video=1&video_id=#{item.id}&playlist_id=#{plid_form}&referer=#{env.get("current_page")}" -%>
<form data-onsubmit="return_false" action="/playlist_ajax?<%= form_parameters %>" method="post">
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<button type="submit" class="pure-button pure-button-secondary low-profile"
data-onclick="add_playlist_item" data-id="<%= item.id %>" data-plid="<%= plid_form %>"><i class="icon ion-md-add"></i></button>
</form>
<%- elsif item.is_a?(PlaylistVideo) && (plid_form = env.get?("remove_playlist_items")) -%>
<%- form_parameters = "action_remove_video=1&set_video_id=#{item.index}&playlist_id=#{plid_form}&referer=#{env.get("current_page")}" -%>
<form data-onsubmit="return_false" action="/playlist_ajax?<%= form_parameters %>" method="post">
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<button type="submit" class="pure-button pure-button-secondary low-profile"
data-onclick="remove_playlist_item" data-index="<%= item.index %>" data-plid="<%= plid_form %>"><i class="icon ion-md-trash"></i></button>
</form>
<%- end -%>
</div>
<div class="bottom-right-overlay">
<%- if item.responds_to?(:live_now) && item.live_now -%>
<p class="length" dir="auto"><i class="icon ion-ios-play-circle"></i>&nbsp;<%= translate(locale, "LIVE") %></p>
<%- elsif item.length_seconds != 0 -%>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<%- end -%>
</div>
<% if item_watched %>
<div class="watched-overlay"></div>
<div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
<% end %> <% end %>
<p dir="auto"><%= HTML.escape(item.title) %></p> </div>
</a>
<div class="video-card-row">
<a href="<%= link_url %>"><p dir="auto"><%= HTML.escape(item.title) %></p></a>
</div>
<div class="video-card-row flexible"> <div class="video-card-row flexible">
<div class="flex-left"><a href="/channel/<%= item.ucid %>"> <div class="flex-left"><a href="/channel/<%= item.ucid %>">
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %><% if !item.is_a?(ChannelVideo) && !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p> <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
<%- if item.responds_to?(:author_verified) && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end -%>
</p>
</a></div> </a></div>
<% endpoint_params = "?v=#{item.id}" %>
<%= rendered "components/video-context-buttons" %> <%= rendered "components/video-context-buttons" %>
</div> </div>

View file

@ -35,12 +35,14 @@
<% if !env.get("preferences").as(Preferences).thin_mode %> <% if !env.get("preferences").as(Preferences).thin_mode %>
<div class="thumbnail"> <div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item %>/mqdefault.jpg" alt="" /> <img class="thumbnail" src="/vi/<%= item %>/mqdefault.jpg" alt="" />
<div class="top-left-overlay"><div class="watched">
<form data-onsubmit="return_false" action="/watch_ajax?action_mark_unwatched=1&id=<%= item %>&referer=<%= env.get("current_page") %>" method="post"> <form data-onsubmit="return_false" action="/watch_ajax?action_mark_unwatched=1&id=<%= item %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>"> <input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
<p class="watched"> <button type="submit" class="pure-button pure-button-secondary low-profile"
<button type="submit" style="all:unset" data-onclick="mark_unwatched" data-id="<%= item %>"><i class="icon ion-md-trash"></i></button> data-onclick="mark_unwatched" data-id="<%= item %>"><i class="icon ion-md-trash"></i></button>
</p>
</form> </form>
</div></div>
</div> </div>
<p></p> <p></p>
<% end %> <% end %>