Fix Visual Bugs

This commit is contained in:
dragos-efy 2023-09-11 01:40:45 +03:00
parent 240c419823
commit 941d974503
21 changed files with 437 additions and 761 deletions

View file

@ -1,18 +1,22 @@
<template>
<div>
<NavBar />
<NavBar />
<div class="flex-1">
<router-view v-slot="{ Component }">
<keep-alive :max="5">
<component :is="Component" :key="$route.fullPath" />
</keep-alive>
</router-view>
<FooterComponent />
</div>
<FooterComponent />
</template>
<style>
#app {
min-height: calc(var(--efy_100vh) - var(--efy_gap2));
display: flex;
flex-direction: column;
}
/*Radius*/
input,
.btn,
@ -32,8 +36,7 @@ video {
border-radius: var(--efy_radius) !important;
}
/*Radius 0*/
.video-grid img {
.video-card .thumbnail {
border-radius: var(--efy_radius) var(--efy_radius) 0 0;
}
@ -61,14 +64,15 @@ video {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin: 0 0 5rem 0;
padding: 0 10rem 5rem 10rem;
line-height: 22rem;
overflow: hidden;
}
.pp-video-card-buttons {
display: flex;
flex-wrap: wrap;
gap: var(--efy_gap0);
margin: 5rem 15rem 15rem 15rem;
margin: 5rem 10rem 10rem 10rem;
}
.pp-video-card-buttons :is(a, button) {
padding: 4rem 8rem;
@ -113,7 +117,7 @@ video {
gap: var(--efy_gap0);
place-items: center;
background: transparent;
margin: var(--efy_gap0) 0 0;
margin: var(--efy_gap0) 0 var(--efy_gap0) var(--efy_gap0);
width: fit-content;
}
.pp-video-card-channel > a {

View file

@ -1,10 +1,10 @@
<template>
<ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" />
<div v-if="channel" v-show="!channel.error">
<div v-if="channel" v-show="!channel.error" class="mt-[15rem]">
<LoadingIndicatorPage :show-content="channel != null && !channel.error">
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full pb-1.5" loading="lazy" />
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full efy_shadow_trans" loading="lazy" />
<div class="pp-channel-page-author flex">
<img height="48" width="48" class="m-1" :src="channel.avatarUrl" />
<img height="48" width="48" class="efy_shadow_trans" :src="channel.avatarUrl" />
<h5 v-text="channel.name" />
<font-awesome-icon v-if="channel.verified" class="ml-1.5" icon="check" />
</div>
@ -71,6 +71,7 @@
}
.pp-channel-tabs :is(button, [role="button"]) {
margin: 0;
border: 0;
}
</style>

View file

@ -1,14 +1,15 @@
<template>
<!-- desktop view -->
<div v-if="!mobileLayout" class="pp-chapters flex-col overflow-y-scroll max-h-75vh min-h-64 lt-lg:hidden">
<h6 aria-label="chapters" title="chapters" class="efy_trans_filter">
<div v-if="!mobileLayout" class="pp-chapters flex-col max-h-75vh min-h-64 lt-lg:hidden">
<h6 aria-label="chapters" title="chapters" class="efy_trans_filter efy_shadow_trans">
{{ $t("video.chapters") }} - {{ chapters.length }}
</h6>
<div
v-for="(chapter, index) in chapters"
:key="chapter.start"
class="chapter efy_anim_pulse efy_trans_filter"
:class="{ 'pp-chapter-active': isCurrentChapter(index) }"
class="chapter efy_anim_pulse"
:class="isCurrentChapter(index) ? 'pp-chapter-active' : 'efy_shadow_trans efy_trans_filter'"
:role="isCurrentChapter(index) ? 'button' : ''"
@click="$emit('seek', chapter.start)"
>
<div class="flex">
@ -77,10 +78,37 @@ defineEmits(["seek"]);
.text-truncate {
@apply truncate overflow-hidden inline-block w-10em;
}
.pp-chapters {
margin: -15rem 0 0 0;
padding: var(--efy_gap);
max-width: 400rem;
gap: var(--efy_gap0);
border-radius: var(--efy_radius);
overflow: auto;
}
.pp-chapters .chapter {
padding: 10rem;
border-radius: var(--efy_radius);
border: var(--efy_border);
}
.pp-chapters [title="chapters"] {
padding: 5rem 10rem;
border-radius: var(--efy_radius);
border: var(--efy_border);
}
.pp-chapters .chapter .flex {
gap: 0 15rem;
}
.pp-chapters.pp-mobile {
margin: 15rem 0 0 0;
max-width: 100%;
}
.pp-chapter-active,
.pp-chapters .chapter:hover {
background: var(--efy_color);
background-clip: padding-box;
color: var(--efy_text2);
border: 0 !important;
margin: 0;
}
</style>

View file

@ -1,6 +1,6 @@
<template>
<hr />
<div class="flex flex-wrap align-center" style="place-content: space-between">
<div class="flex flex-wrap align-center" style="place-content: space-between; gap: var(--efy_gap0)">
<span class="buttons flex" style="gap: var(--efy_gap0)">
<router-link role="button" to="/subscriptions">Subscriptions</router-link>
<a :href="getRssUrl" role="button" class="pp-square">

View file

@ -1,5 +1,5 @@
<template>
<footer class="efy_trans_filter">
<footer class="efy_trans_filter efy_shadow_trans efy_shadow_button_off">
<a aria-label="GitHub" href="https://github.com/TeamPiped/Piped" target="_blank">
<font-awesome-icon :icon="['fab', 'github']" />
<span v-t="'actions.source_code'" />
@ -58,12 +58,13 @@ footer {
padding: 15rem 5rem;
border: var(--efy_border);
}
footer > a {
margin: 0;
}
footer a {
color: var(--efy_text) !important;
-webkit-text-fill-color: var(--efy_text) !important;
background: transparent !important;
margin: 0;
display: flex;
gap: 8rem;
align-items: center;
}
</style>

View file

@ -1,30 +1,34 @@
<template>
<div class="flex place-items-center">
<div class="flex flex-col gap-2 md:flex-row md:items-center">
<button v-t="'actions.clear_history'" class="btn" @click="clearHistory" />
<button v-t="'actions.export_to_json'" class="btn" @click="exportHistory" />
<div class="ml-auto flex items-center gap-1">
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
</div>
<hr />
<div class="flex flex-wrap items-center place-content-between" style="gap: var(--efy_gap0)">
<div class="flex" style="gap: var(--efy_gap0)">
<button v-t="'actions.clear_history'" class="m-0" @click="clearHistory" />
<button v-t="'actions.export_to_json'" class="m-0" @click="exportHistory" />
</div>
<div class="ml-4 flex items-center">
<input id="autoDelete" v-model="autoDeleteHistory" type="checkbox" @change="onChange" />
<label v-t="'actions.delete_automatically'" class="ml-2" for="autoDelete" />
<select v-model="autoDeleteDelayHours" class="select ml-3 pl-3" @change="onChange">
<option v-t="{ path: 'info.hours', args: { amount: '1' } }" value="1" />
<option v-t="{ path: 'info.hours', args: { amount: '3' } }" value="3" />
<option v-t="{ path: 'info.hours', args: { amount: '6' } }" value="6" />
<option v-t="{ path: 'info.hours', args: { amount: '12' } }" value="12" />
<option v-t="{ path: 'info.days', args: { amount: '1' } }" value="24" />
<option v-t="{ path: 'info.days', args: { amount: '3' } }" value="72" />
<option v-t="{ path: 'info.weeks', args: { amount: '1' } }" value="168" />
<option v-t="{ path: 'info.weeks', args: { amount: '3' } }" value="336" />
<option v-t="{ path: 'info.months', args: { amount: '1' } }" value="672" />
<option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" />
</select>
<div class="flex flex-wrap items-center" style="gap: var(--efy_gap0)">
<div efy_select class="flex flex-wrap" style="gap: var(--efy_gap0)">
<input id="autoDelete" v-model="autoDeleteHistory" type="checkbox" @change="onChange" />
<label v-t="'actions.delete_automatically'" style="margin: 0" for="autoDelete" />
<select
v-model="autoDeleteDelayHours"
class="w-auto"
style="margin: 0 var(--efy_gap0) 0 0"
@change="onChange"
>
<option v-t="{ path: 'info.hours', args: { amount: '1' } }" value="1" />
<option v-t="{ path: 'info.hours', args: { amount: '3' } }" value="3" />
<option v-t="{ path: 'info.hours', args: { amount: '6' } }" value="6" />
<option v-t="{ path: 'info.hours', args: { amount: '12' } }" value="12" />
<option v-t="{ path: 'info.days', args: { amount: '1' } }" value="24" />
<option v-t="{ path: 'info.days', args: { amount: '3' } }" value="72" />
<option v-t="{ path: 'info.weeks', args: { amount: '1' } }" value="168" />
<option v-t="{ path: 'info.weeks', args: { amount: '3' } }" value="336" />
<option v-t="{ path: 'info.months', args: { amount: '1' } }" value="672" />
<option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" />
</select>
</div>
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" style="gap: 0" />
</div>
</div>
@ -33,8 +37,6 @@
<div class="video-grid">
<VideoItem v-for="video in videos" :key="video.url" :item="video" />
</div>
<br />
</template>
<script>

View file

@ -1,4 +1,5 @@
<template>
<hr />
<div>
<form style="display: grid; gap: 15rem">
<div>
@ -11,9 +12,7 @@
<input v-model="override" id="import-override" type="checkbox" />
<label for="import-override">Override</label>
</div>
<div>
<a class="btn w-auto" @click="handleImport">Import</a>
</div>
<a class="btn w-auto" @click="handleImport" role="button" style="margin: 0">Import</a>
</form>
<br />
<strong>Importing Subscriptions from YouTube</strong>

View file

@ -121,13 +121,13 @@
<style>
.pp-nav {
margin-bottom: 15rem;
gap: 15rem;
}
.pp-nav > .pp-logo > a {
font-size: 25rem;
font-family: "nunito";
background: transparent;
margin-left: 5rem;
}
.pp-nav > div input {
margin: 0 !important;

View file

@ -1,30 +1,36 @@
<template>
<div class="flex flex-col flex-justify-between">
<div class="video-card flex flex-col flex-justify-between efy_shadow_trans">
<router-link :to="props.item.url">
<div class="relative">
<img class="w-full" :src="props.item.thumbnail" loading="lazy" />
<img class="thumbnail" :src="props.item.thumbnail" loading="lazy" />
</div>
<p>
<span v-text="props.item.name" />
<span v-text="props.item.name" class="pp-video-card-title" />
<font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
</p>
</router-link>
<p v-if="props.item.description" v-text="props.item.description" />
<router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
<p>
<span v-text="props.item.uploaderName" />
<font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
</p>
</router-link>
<a v-else-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
<template v-if="props.item.videos >= 0">
<br v-if="props.item.uploaderName" />
<strong v-text="`${props.item.videos} ${$t('video.videos')}`" />
</template>
<br />
<div class="pp-video-card-buttons">
<button
v-if="props.item.videos >= 0"
v-text="`${props.item.videos} ${$t('video.videos')}`"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
/>
<router-link
v-if="props.item.uploaderUrl && item.uploaderName"
:to="props.item.uploaderUrl"
:title="props.item.uploaderName"
class="pp-video-card-channel"
style="padding: 0; flex-grow: 1; background: transparent; border: 0"
>
<div class="pp-text efy_shadow_trans efy_shadow_button_off flex-grow-1">
<span v-text="props.item.uploaderName" style="max-width: 106rem" />
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
</div>
</router-link>
<a v-else-if="props.item.uploaderName" class="pp-video-card-channel" v-text="props.item.uploaderName" />
</div>
</div>
</template>

View file

@ -1,9 +1,18 @@
<template>
<hr />
<div class="flex justify-between">
<button v-t="'actions.create_playlist'" class="btn mr-2" @click="onCreatePlaylist" />
<div class="flex">
<button v-if="playlists.length > 0" v-t="'actions.export_to_json'" @click="exportPlaylists" />
<div class="flex justify-between items-center">
<button
v-t="'actions.create_playlist'"
style="height: var(--efy_ratio_width); margin: 0"
@click="onCreatePlaylist"
/>
<div class="flex flex-wrap" style="gap: var(--efy_gap0)">
<button
v-if="playlists.length > 0"
v-t="'actions.export_to_json'"
@click="exportPlaylists"
style="height: var(--efy_ratio_width); margin: 0"
/>
<input
id="fileSelector"
ref="fileSelector"
@ -12,14 +21,15 @@
multiple="multiple"
@change="importPlaylists"
/>
<label v-t="'actions.import_from_json_csv'" for="fileSelector" class="btn ml-2" role="button" />
<label v-t="'actions.import_from_json_csv'" for="fileSelector" class="m-0! font-bold" role="button" />
</div>
</div>
<hr />
<div class="video-grid">
<div v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
<div v-for="playlist in playlists" :key="playlist.id" class="video-card efy_trans_filter">
<router-link :to="`/playlist?list=${playlist.id}`">
<img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
<img class="thumbnail" :src="playlist.thumbnail" alt="thumbnail" />
<p
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 15rem"
class="flex link"
@ -68,32 +78,32 @@
</div>
<hr />
<h2 v-t="'titles.bookmarks'" class="my-4 font-bold" />
<h5 v-if="bookmarks" v-t="'titles.bookmarks'" class="mb-[15rem]" />
<div v-if="bookmarks" class="video-grid">
<router-link
<div
v-for="(playlist, index) in bookmarks"
:key="playlist.playlistId"
:to="`/playlist?list=${playlist.playlistId}`"
class="pp-bookmark video-card efy_trans_filter"
>
<img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
<div class="relative text-sm">
<span class="thumbnail-overlay thumbnail-right" v-text="`${playlist.videos} ${$t('video.videos')}`" />
<div class="absolute bottom-100px right-5px z-100 px-5px" @click.prevent="removeBookmark(index)">
<font-awesome-icon class="ml-3" icon="bookmark" />
<router-link :to="`/playlist?list=${playlist.playlistId}`">
<img class="thumbnail" :src="playlist.thumbnail" alt="thumbnail" />
<div class="flex items-center h-[44rem] overflow-hidden">
<p class="pp-video-card-title" :title="playlist.name" v-text="playlist.name" />
</div>
</router-link>
<div class="pp-video-card-buttons flex gap-15rem">
<button @click.prevent="removeBookmark(index)">
<font-awesome-icon class="ml-3" icon="bookmark" />
</button>
<button v-text="`${playlist.videos} ${$t('video.videos')}`" class="thumbnail-overlay" />
</div>
<p
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 0 0 10rem"
class="link my-2 flex overflow-hidden"
:title="playlist.name"
v-text="playlist.name"
/>
<a :href="playlist.uploaderUrl" class="flex items-center">
<img class="h-32px w-32px rounded-full" :src="playlist.uploaderAvatar" />
<span class="ml-3 hover:underline" v-text="playlist.uploader" />
<a :href="playlist.uploaderUrl" class="pp-video-card-channel">
<img class="w-36rem h-36rem efy_shadow_trans" :src="playlist.uploaderAvatar" width="36" height="36" />
<div class="pp-text efy_shadow_trans efy_shadow_button_off">
<span v-text="playlist.uploader" />
</div>
</a>
</router-link>
</div>
</div>
</template>

View file

@ -1,8 +1,7 @@
<template>
<!--efy-->
<div class="pp-pref-cards">
<div efy_card="grid">
<h2>Quick</h2>
<h5>Quick</h5>
<label class="pref" for="ddlLanguageSelection">
<strong v-t="'actions.language_selection'" />
<select
@ -86,378 +85,60 @@
</select>
</label>
</template>
<br />
<p v-t="'info.preferences_note'" />
<button class="btn" v-t="'actions.reset_preferences'" @click="resetPreferences()" />
<button class="btn mx-4" v-t="'actions.backup_preferences'" @click="backupPreferences()" />
<label
for="fileSelector"
class="btn text-center"
v-t="'actions.restore_preferences'"
@click="restorePreferences()"
/>
</div>
</div>
<!--master-->
<div class="flex">
<button @click="$router.go(-1) || $router.push('/')">
<font-awesome-icon icon="chevron-left" /><span v-t="'actions.back'" class="ml-1.5" />
</button>
</div>
<h1 v-t="'titles.preferences'" class="text-center font-bold" />
<hr />
<label for="ddlTheme" class="pref">
<strong v-t="'actions.theme'" />
<select id="ddlTheme" v-model="selectedTheme" class="select w-auto" @change="onChange($event)">
<option v-t="'actions.auto'" value="auto" />
<option v-t="'actions.dark'" value="dark" />
<option v-t="'actions.light'" value="light" />
</select>
</label>
<label class="pref" for="ddlLanguageSelection">
<strong v-t="'actions.language_selection'" />
<select id="ddlLanguageSelection" v-model="selectedLanguage" class="select w-auto" @change="onChange($event)">
<option v-for="language in languages" :key="language.code" :value="language.code" v-text="language.name" />
</select>
</label>
<label class="pref" for="ddlCountrySelection">
<strong v-t="'actions.country_selection'" />
<select id="ddlCountrySelection" v-model="countrySelected" class="select w-50" @change="onChange($event)">
<option v-for="country in countryMap" :key="country.code" :value="country.code" v-text="country.name" />
</select>
</label>
<label class="pref" for="ddlDefaultHomepage">
<strong v-t="'actions.default_homepage'" />
<select id="ddlDefaultHomepage" v-model="defaultHomepage" class="select w-auto" @change="onChange($event)">
<option v-t="'titles.trending'" value="trending" />
<option v-t="'titles.feed'" value="feed" />
</select>
</label>
<h2 v-t="'titles.player'" class="text-center" />
<label class="pref" for="chkAutoPlayVideo">
<strong v-t="'actions.autoplay_video'" />
<input
id="chkAutoPlayVideo"
v-model="autoPlayVideo"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAutoDisplayCaptions">
<strong v-t="'actions.auto_display_captions'" />
<input
id="chkAutoDisplayCaptions"
v-model="autoDisplayCaptions"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAutoPlayNextCountdown">
<strong v-t="'actions.autoplay_next_countdown'" />
<input
id="chkAutoPlayNextCountdown"
v-model="autoPlayNextCountdown"
class="input w-24"
type="number"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAudioOnly">
<strong v-t="'actions.audio_only'" />
<input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<label class="pref" for="ddlDefaultQuality">
<strong v-t="'actions.default_quality'" />
<select id="ddlDefaultQuality" v-model="defaultQuality" class="select w-auto" @change="onChange($event)">
<option v-t="'actions.auto'" value="0" />
<option v-for="resolution in resolutions" :key="resolution" :value="resolution" v-text="`${resolution}p`" />
</select>
</label>
<label class="pref" for="txtBufferingGoal">
<strong v-t="'actions.buffering_goal'" />
<input
id="txtBufferingGoal"
v-model="bufferingGoal"
class="input w-24"
type="text"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkMinimizeComments">
<strong v-t="'actions.minimize_comments_default'" />
<input
id="chkMinimizeComments"
v-model="minimizeComments"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkMinimizeDescription">
<strong v-t="'actions.minimize_description_default'" />
<input
id="chkMinimizeDescription"
v-model="minimizeDescription"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkMinimizeRecommendations">
<strong v-t="'actions.minimize_recommendations_default'" />
<input
id="chkMinimizeRecommendations"
v-model="minimizeRecommendations"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkMinimizeChapters">
<strong v-t="'actions.minimize_chapters_default'" />
<input
id="chkMinimizeChapters"
v-model="minimizeChapters"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<!-- chapters layout on mobile -->
<label class="pref lg:invisible" for="chkMinimizeChapters">
<strong v-t="'actions.chapters_layout_mobile'" />
<select id="ddlDefaultHomepage" v-model="mobileChapterLayout" class="select w-auto" @change="onChange($event)">
<option v-t="'video.chapters_horizontal'" value="Horizontal" />
<option v-t="'video.chapters_vertical'" value="Vertical" />
</select>
</label>
<label class="pref" for="chkShowWatchOnYouTube">
<strong v-t="'actions.show_watch_on_youtube'" />
<input
id="chkShowWatchOnYouTube"
v-model="showWatchOnYouTube"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkShowSearchSuggestions">
<strong v-t="'actions.show_search_suggestions'" />
<input
id="chkShowSearchSuggestions"
v-model="searchSuggestions"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkStoreSearchHistory">
<strong v-t="'actions.store_search_history'" />
<input
id="chkStoreSearchHistory"
v-model="searchHistory"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkStoreWatchHistory">
<strong v-t="'actions.store_watch_history'" />
<input
id="chkStoreWatchHistory"
v-model="watchHistory"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label v-if="watchHistory" class="pref" for="chkHideWatched">
<strong v-t="'actions.hide_watched'" />
<input id="chkHideWatched" v-model="hideWatched" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<label class="pref" for="ddlEnabledCodecs">
<strong v-t="'actions.enabled_codecs'" />
<select
id="ddlEnabledCodecs"
v-model="enabledCodecs"
class="select h-auto w-auto"
multiple
@change="onChange($event)"
>
<option value="av1">AV1</option>
<option value="vp9">VP9</option>
<option value="avc">AVC (h.264)</option>
</select>
</label>
<label class="pref" for="chkDisableLBRY">
<strong v-t="'actions.disable_lbry'" />
<input id="chkDisableLBRY" v-model="disableLBRY" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<label class="pref" for="chkEnableLBRYProxy">
<strong v-t="'actions.enable_lbry_proxy'" />
<input
id="chkEnableLBRYProxy"
v-model="proxyLBRY"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<h2 class="text-center">SponsorBlock</h2>
<p class="text-center">
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
</p>
<label class="pref" for="chkEnableSponsorblock">
<strong v-t="'actions.enable_sponsorblock'" />
<input
id="chkEnableSponsorblock"
v-model="sponsorBlock"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<div v-if="sponsorBlock">
<label v-for="[name, item] in skipOptions" :key="name" class="pref" :for="'ddlSkip_' + name">
<strong v-t="item.label" />
<select :id="'ddlSkip_' + name" v-model="item.value" class="select w-auto" @change="onChange($event)">
<option v-t="'actions.no'" value="no" />
<option v-t="'actions.skip_button_only'" value="button" />
<option v-t="'actions.skip_automatically'" value="auto" />
</select>
</label>
<label class="pref" for="chkShowMarkers">
<strong v-t="'actions.show_markers'" />
<input
id="chkShowMarkers"
v-model="showMarkers"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="txtMinSegmentLength">
<strong v-t="'actions.min_segment_length'" />
<input
id="txtMinSegmentLength"
v-model="minSegmentLength"
class="input w-24"
type="text"
@change="onChange($event)"
/>
</label>
</div>
<h2 v-t="'titles.dearrow'" class="text-center" />
<p class="text-center">
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
</p>
<label class="pref" for="chkDeArrow">
<strong v-t="'actions.enable_dearrow'" />
<input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<h2 v-t="'titles.instance'" class="text-center" />
<label class="pref" for="ddlInstanceSelection">
<strong v-text="`${$t('actions.instance_selection')}:`" />
<select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
<option
v-for="instance in instances"
:key="instance.name"
:value="instance.api_url"
v-text="instance.name"
/>
</select>
</label>
<label class="pref" for="chkAuthInstance">
<strong v-text="`${$t('actions.different_auth_instance')}:`" />
<input
id="chkAuthInstance"
v-model="authInstance"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<template v-if="authInstance">
<label class="pref" for="ddlAuthInstanceSelection">
<strong v-text="`${$t('actions.instance_auth_selection')}:`" />
<select
id="ddlAuthInstanceSelection"
v-model="selectedAuthInstance"
class="select w-auto"
@change="onChange($event)"
>
<option
v-for="instance in instances"
:key="instance.name"
:value="instance.api_url"
v-text="instance.name"
<div class="pref items-start! flex-col">
<strong>Preferences</strong>
<div class="flex flex-wrap" style="gap: var(--efy_gap0)">
<button style="height: var(--efy_ratio_width)" @click="showConfirmResetPrefsDialog = true">
Reset
</button>
<button style="height: var(--efy_ratio_width)" @click="backupPreferences()">Backup</button>
<label for="fileSelector" class="btn text-center" role="button" @click="restorePreferences()">
Restore
</label>
<input
id="fileSelector"
ref="fileSelector"
class="hidden"
type="file"
@change="restorePreferences()"
/>
</div>
<p v-t="'info.preferences_note'" />
<ConfirmModal
v-if="showConfirmResetPrefsDialog"
:message="$t('actions.confirm_reset_preferences')"
@close="showConfirmResetPrefsDialog = false"
@confirm="resetPreferences()"
/>
</select>
</label>
</template>
<br />
<!-- options that are visible only when logged in -->
<div v-if="authenticated">
<h2 v-t="'titles.account'" class="text-center"></h2>
<label class="pref" for="txtDeleteAccountPassword">
<strong v-t="'actions.delete_account'" />
<div class="flex items-center">
<input
id="txtDeleteAccountPassword"
ref="txtDeleteAccountPassword"
v-model="password"
:placeholder="$t('login.password')"
:aria-label="$t('login.password')"
class="input mr-2 w-auto"
type="password"
@keyup.enter="deleteAccount"
/>
<a v-t="'actions.delete_account'" class="btn w-auto" @click="deleteAccount" />
</div>
</label>
<div class="pref">
<a v-t="'actions.logout'" class="btn w-auto" @click="logout" />
<a
v-t="'actions.invalidate_session'"
class="btn w-auto"
style="margin-left: 0.5em"
@click="invalidateSession"
/>
<!--masterend-->
<input class="hidden" id="fileSelector" ref="fileSelector" type="file" @change="restorePreferences()" />
<!-- options that are visible only when logged in -->
<div v-if="this.authenticated">
<label class="pp-delete-account pref" for="txtDeleteAccountPassword" efy_card="grid">
<h6 v-t="'actions.delete_account'" />
<div v-if="authenticated" class="pref items-start! flex-col">
<label v-t="'actions.delete_account'" for="txtDeleteAccountPassword" class="font-bold" />
<div class="flex flex-wrap" style="gap: var(--efy_gap0)">
<input
id="txtDeleteAccountPassword"
ref="txtDeleteAccountPassword"
v-model="password"
v-on:keyup.enter="deleteAccount"
:placeholder="$t('login.password')"
:aria-label="$t('login.password')"
class="input w-auto mr-2"
class="input mr-2 w-auto"
type="password"
@keyup.enter="deleteAccount"
/>
<a class="btn w-full" @click="deleteAccount" v-t="'actions.delete_account'" />
</label>
<button class="btn w-full" @click="logout" v-t="'actions.logout'" />
<button class="btn w-full" @click="invalidateSession" v-t="'actions.invalidate_session'" />
<button v-t="'actions.delete_account'" class="w-auto" @click="deleteAccount" />
</div>
</div>
<div v-if="authenticated" class="pref items-start! flex-col" style="border-bottom: var(--efy_border)">
<strong>Logout</strong>
<div class="flex flex-wrap" style="gap: var(--efy_gap0)">
<button v-t="'actions.logout'" class="w-auto" @click="logout" />
<button v-t="'actions.invalidate_session'" class="w-auto" @click="invalidateSession" />
</div>
</div>
</div>
<div efy_card="grid">
<h2 v-t="'titles.player'" />
<h5 v-t="'titles.player'" />
<label class="pref" for="chkAutoPlayVideo">
<strong v-t="'actions.autoplay_video'" />
<input
@ -468,6 +149,26 @@
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAutoDisplayCaptions">
<strong v-t="'actions.auto_display_captions'" />
<input
id="chkAutoDisplayCaptions"
v-model="autoDisplayCaptions"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAutoPlayNextCountdown">
<strong v-t="'actions.autoplay_next_countdown'" />
<input
id="chkAutoPlayNextCountdown"
v-model="autoPlayNextCountdown"
class="input w-24"
type="number"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAudioOnly">
<strong v-t="'actions.audio_only'" />
<input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" />
@ -494,7 +195,7 @@
<input
id="txtBufferingGoal"
v-model="bufferingGoal"
class="input w-auto"
class="input w-24"
type="text"
@change="onChange($event)"
/>
@ -539,6 +240,20 @@
@change="onChange($event)"
/>
</label>
<!-- chapters layout on mobile -->
<label class="pref" for="chkMinimizeChapters">
<strong v-t="'actions.chapters_layout_mobile'" />
<select
id="ddlDefaultHomepage"
v-model="mobileChapterLayout"
class="select w-auto"
@change="onChange($event)"
>
<option v-t="'video.chapters_horizontal'" value="Horizontal" />
<option v-t="'video.chapters_vertical'" value="Vertical" />
</select>
</label>
<label class="pref" for="chkShowWatchOnYouTube">
<strong v-t="'actions.show_watch_on_youtube'" />
<input
@ -549,6 +264,16 @@
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkShowSearchSuggestions">
<strong v-t="'actions.show_search_suggestions'" />
<input
id="chkShowSearchSuggestions"
v-model="searchSuggestions"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkStoreSearchHistory">
<strong v-t="'actions.store_search_history'" />
<input
@ -584,7 +309,7 @@
<select
id="ddlEnabledCodecs"
v-model="enabledCodecs"
class="select w-auto h-auto"
class="select h-auto w-auto"
multiple
@change="onChange($event)"
>
@ -616,12 +341,16 @@
</div>
<div efy_card="grid">
<h2>SponsorBlock</h2>
<p>
<h5>SponsorBlock + DeArrow</h5>
<p class="pref" style="justify-content: unset !important">
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/"
>sponsor.ajay.app</a
>
</p>
<label class="pref" for="chkDeArrow">
<strong v-t="'actions.enable_dearrow'" />
<input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
</label>
<label class="pref" for="chkEnableSponsorblock">
<strong v-t="'actions.enable_sponsorblock'" />
<input
@ -632,106 +361,41 @@
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipSponsors">
<strong v-t="'actions.skip_sponsors'" />
<input
id="chkSkipSponsors"
v-model="skipSponsor"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipIntro">
<strong v-t="'actions.skip_intro'" />
<input
id="chkSkipIntro"
v-model="skipIntro"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipOutro">
<strong v-t="'actions.skip_outro'" />
<input
id="chkSkipOutro"
v-model="skipOutro"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipPreview">
<strong v-t="'actions.skip_preview'" />
<input
id="chkSkipPreview"
v-model="skipPreview"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipInteraction">
<strong v-t="'actions.skip_interaction'" />
<input
id="chkSkipInteraction"
v-model="skipInteraction"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipSelfPromo">
<strong v-t="'actions.skip_self_promo'" />
<input
id="chkSkipSelfPromo"
v-model="skipSelfPromo"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipNonMusic">
<strong v-t="'actions.skip_non_music'" />
<input
id="chkSkipNonMusic"
v-model="skipMusicOffTopic"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipHighlight">
<strong v-t="'actions.skip_highlight'" />
<input
id="chkSkipHighlight"
v-model="skipHighlight"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkSkipFiller">
<strong v-t="'actions.skip_filler_tangent'" />
<input
id="chkSkipFiller"
v-model="skipFiller"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkShowMarkers">
<strong v-t="'actions.show_markers'" />
<input
id="chkShowMarkers"
v-model="showMarkers"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<div v-if="sponsorBlock">
<label v-for="[name, item] in skipOptions" :key="name" class="pref" :for="'ddlSkip_' + name">
<strong v-t="item.label" />
<select
:id="'ddlSkip_' + name"
v-model="item.value"
class="select w-auto"
@change="onChange($event)"
>
<option v-t="'actions.no'" value="no" />
<option v-t="'actions.skip_button_only'" value="button" />
<option v-t="'actions.skip_automatically'" value="auto" />
</select>
</label>
<label class="pref" for="chkShowMarkers">
<strong v-t="'actions.show_markers'" />
<input
id="chkShowMarkers"
v-model="showMarkers"
class="checkbox"
type="checkbox"
@change="onChange($event)"
/>
</label>
<label class="pref" for="txtMinSegmentLength" style="border-bottom: var(--efy_border)">
<strong v-t="'actions.min_segment_length'" />
<input
id="txtMinSegmentLength"
v-model="minSegmentLength"
class="input w-24"
type="text"
@change="onChange($event)"
/>
</label>
</div>
</div>
</div>
@ -762,21 +426,6 @@
</tr>
</tbody>
</table>
<!--master-->
<br />
<p v-t="'info.preferences_note'" />
<br />
<button v-t="'actions.reset_preferences'" class="btn" @click="showConfirmResetPrefsDialog = true" />
<button v-t="'actions.backup_preferences'" class="btn mx-4" @click="backupPreferences()" />
<label v-t="'actions.restore_preferences'" for="fileSelector" class="btn" @click="restorePreferences()" />
<input id="fileSelector" ref="fileSelector" class="hidden" type="file" @change="restorePreferences()" />
<ConfirmModal
v-if="showConfirmResetPrefsDialog"
:message="$t('actions.confirm_reset_preferences')"
@close="showConfirmResetPrefsDialog = false"
@confirm="resetPreferences()"
/>
<!--masterend-->
</template>
<script>
@ -1082,11 +731,37 @@ export default {
<style>
.pref {
@apply flex justify-between items-center;
padding: 10rem;
border-top: var(--efy_border);
gap: 10rem;
}
.pref:nth-child(odd) {
@apply bg-gray-200;
/*.pref:nth-child(odd) {
background: var(--efy_bg1);
}*/
.pref :is(input, select, button, [role="button"]) {
margin: 0;
}
.dark .pref:nth-child(odd) {
@apply bg-dark-800;
.pref strong {
line-height: 1;
}
.pref :is([type="number"], [type="text"], select) {
min-width: 60rem;
max-width: 250rem;
}
.pp-pref-cards {
margin-top: 15rem;
}
[efy_card*="grid"] {
padding: 0;
gap: 0;
}
[efy_card*="grid"]:active {
transform: scale(1) !important;
}
[efy_card*="grid"] h5 {
padding: 5rem 10rem;
}
tbody:nth-child(odd) {
background: var(--efy_bg1) !important;
}
</style>

View file

@ -1,13 +1,20 @@
<template>
<h1 class="my-2 text-center" v-text="$route.query.search_query" />
<label for="ddlSearchFilters" class="mr-2">
<strong v-text="`${$t('actions.filter')}:`" />
</label>
<select id="ddlSearchFilters" v-model="selectedFilter" default="all" class="select w-auto" @change="updateFilter()">
<option v-for="filter in availableFilters" :key="filter" v-t="`search.${filter}`" :value="filter" />
</select>
<hr />
<div class="flex flex-wrap place-content-between items-center">
<h5 class="ml-[5rem]" v-text="$route.query.search_query" />
<div class="flex items-center" style="gap: var(--efy_gap0)">
<label v-text="`${$t('actions.filter')}:`" for="ddlSearchFilters" />
<select
id="ddlSearchFilters"
v-model="selectedFilter"
default="all"
class="w-auto; m-0"
@change="updateFilter()"
>
<option v-for="filter in availableFilters" :key="filter" v-t="`search.${filter}`" :value="filter" />
</select>
</div>
</div>
<hr />
<div v-if="results && results.corrected">

View file

@ -90,6 +90,6 @@ export default {
background: var(--efy_text2);
box-shadow: 0 0 20rem var(--efy_text_trans);
padding: var(--efy_gap);
margin: calc(-12rem + var(--efy_gap)) 0 var(--efy_gap) 0;
margin: calc(40rem + var(--efy_gap)) 0 var(--efy_gap) 0;
}
</style>

View file

@ -1,6 +1,6 @@
<template>
<label v-t="'actions.sort_by'" for="ddlSortBy" class="mr-2" />
<select id="ddlSortBy" v-model="selectedSort" class="select w-auto m-0">
<label v-t="'actions.sort_by'" for="ddlSortBy" class="m-0" />
<select id="ddlSortBy" v-model="selectedSort" class="w-auto m-0">
<option v-for="(value, key) in options" :key="key" v-t="`actions.${key}`" :value="value" />
</select>
</template>

View file

@ -1,7 +1,11 @@
<template>
<hr />
<!-- import / export section -->
<div class="w-full flex justify-between">
<div class="flex gap-2">
<div class="flex justify-between flex-wrap m0c">
<div efy_card class="w-auto!" style="padding: var(--efy_padding)">
<i18n-t keypath="titles.subscriptions" efy_card />{{ ": " + subscriptions.length }}
</div>
<div class="m0c flex flex-wrap">
<router-link v-t="'actions.import_from_json_csv'" to="/import" role="button" />
<button v-t="'actions.export_to_json'" @click="exportHandler" />
<input
@ -16,26 +20,28 @@
for="fileSelector"
role="button"
v-text="`${$t('actions.import_from_json')} (${$t('titles.channel_groups')})`"
class="font-bold"
/>
<button
@click="exportGroupsHandler"
v-text="`${$t('actions.export_to_json')} (${$t('titles.channel_groups')})`"
/>
</div>
<i18n-t keypath="subscriptions.subscribed_channels_count">{{ subscriptions.length }}</i18n-t>
</div>
<hr />
<div class="w-full flex flex-wrap">
<div class="m0c w-full flex flex-wrap">
<button
v-for="group in channelGroups"
:key="group.groupName"
class="mx-1 w-max"
class="flex gap-[10rem] items-center"
:class="{ selected: selectedGroup === group }"
@click="selectGroup(group)"
>
<span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
<div v-if="group.groupName != '' && selectedGroup == group">
<div v-if="group.groupName != '' && selectedGroup == group" class="flex flex-wrap gap-[10rem] items-center">
<div>|</div>
<font-awesome-icon class="mx-2" icon="edit" @click="showEditGroupModal = true" />
<div>|</div>
<font-awesome-icon class="mx-2" icon="circle-minus" @click="deleteGroup(group)" />
</div>
</button>
@ -43,7 +49,6 @@
<font-awesome-icon icon="circle-plus" @click="showCreateGroupModal = true" />
</button>
</div>
<br />
<hr />
<!-- Subscriptions card list -->
<div class="pp-subs-cards">
@ -61,7 +66,6 @@
/>
</div>
</div>
<br />
<ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
<h2 v-t="'actions.create_group'" />
@ -101,6 +105,9 @@
}
.pp-subs-card :is(a, span) {
-webkit-text-fill-color: var(--efy_text) !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.pp-subs-card button {
margin-bottom: 0;
@ -109,6 +116,12 @@
.selected {
border: 0.1rem outset red;
}
.m0c {
gap: var(--efy_gap0);
}
.m0c :is(button, [role="button"]) {
margin: 0;
}
</style>
<script>

View file

@ -1,7 +1,7 @@
<template>
<div class="toast">
<slot />
<button v-t="'actions.dismiss'" @click="dismiss" />
<button v-t="'actions.dismiss'" @click="dismiss" class="m-0 mt-[10rem]" />
</div>
</template>
@ -18,12 +18,11 @@ export default {
<style>
.toast {
@apply bg-white/80 text-black flex flex-col justify-center fixed top-12 right-12 p-4 min-w-max shadow rounded duration-200 z-9999;
}
.dark .toast {
@apply bg-dark-900/80 text-white;
}
.toast button {
@apply underline;
@apply flex flex-col justify-center fixed top-[15rem] right-[15rem] min-w-max z-9999;
background: var(--efy_text2);
color: var(--efy_text);
padding: 15rem;
border-radius: var(--efy_radius);
border: var(--efy_border);
}
</style>

View file

@ -1,5 +1,5 @@
<template>
<LoadingIndicatorPage :show-content="videos.length != 0" class="video-grid">
<LoadingIndicatorPage :show-content="videos.length != 0" class="video-grid mt-[15rem]">
<VideoItem v-for="video in videos" :key="video.url" :item="video" height="118" width="210" />
</LoadingIndicatorPage>
</template>

View file

@ -1,7 +1,8 @@
<template>
<div v-if="showVideo" class="efy_trans_filter efy_shadow_trans">
<div v-if="showVideo" class="video-card efy_trans_filter efy_shadow_trans">
<!-- EFY-->
<router-link
class="video_item_link inline-block w-full focus:underline hover:underline"
class="video_item_link"
:to="{
path: '/watch',
query: {
@ -11,15 +12,43 @@
},
}"
>
<!-- EFY
<img
:src="thumbnail"
:alt="title"
:class="{ 'shorts-img': item.isShort }"
class="thumbnail"
loading="lazy"
/>
<!-- progress bar -->
<div class="relative h-1 w-full">
<div
v-if="item.watched && item.duration > 0"
class="absolute bottom-0 left-0 h-1 bg-red-600"
:style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
/>
</div>
<div class="flex items-center h-[44rem] overflow-hidden">
<p v-text="title" class="pp-video-card-title" />
</div>
</router-link>
<div class="pp-video-card-buttons">
<button v-if="item.duration > 0" v-text="timeFormat(item.duration)" tabindex="-1" />
<button v-if="item.views >= 0" tabindex="-1">
<font-awesome-icon icon="eye" />
<span class="pl-0.5" v-text="`${numberFormat(item.views)}`" />
<button
v-if="item.duration > 0"
v-text="timeFormat(item.duration)"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
tabindex="-1"
/>
<button
v-if="item.views >= 0"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
tabindex="-1"
>
<font-awesome-icon icon="eye" style="margin-right: 5rem" />
<span v-text="`${numberFormat(item.views)}`" />
</button>
<router-link
class="btn"
class="btn efy_shadow_trans efy_shadow_button_off efy_button_text_off"
:to="{
path: '/watch',
query: {
@ -34,165 +63,64 @@
>
<font-awesome-icon icon="headphones" />
</router-link>
<button v-if="authenticated" :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
<button
:title="$t('actions.add_to_playlist')"
@click="showModal = !showModal"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
>
<font-awesome-icon icon="circle-plus" />
</button>
<button
v-if="admin"
:title="$t('actions.remove_from_playlist')"
ref="removeButton"
@click="removeVideo(item.url.substr(-11))"
:title="$t('actions.remove_from_playlist')"
@click="showConfirmRemove = true"
>
<font-awesome-icon icon="circle-minus" />
</button>
<button v-if="item.uploadedDate" class="pl-0.5" v-text="item.uploadedDate" tabindex="-1" />
<ConfirmModal
v-if="showConfirmRemove"
:message="$t('actions.delete_playlist_video_confirm')"
@close="showConfirmRemove = false"
@confirm="removeVideo(item.url.substr(-11))"
/>
<PlaylistAddModal
v-if="showModal"
:video-id="item.url.substr(-11)"
:video-info="item"
@close="showModal = !showModal"
/>
<button
v-if="item.uploaded > 0"
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
v-text="timeAgo(item.uploaded)"
/>
<button v-else-if="item.uploadedDate" v-text="item.uploadedDate" tabindex="-1" />
<button class="pp-color" v-if="item.isShort" v-t="'video.shorts'" tabindex="-1" />
<button v-else-if="item.duration < 0" v-t="'video.live'" class="pp-color" tabindex="-1" />
<button v-if="item.watched" v-t="'video.watched'" class="pp-color" tabindex="-1" />
</div>
<router-link
:to="item.uploaderUrl"
class="pp-video-card-channel"
v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
:to="item.uploaderUrl"
:title="item.uploaderName"
class="pp-video-card-channel"
>
<img
v-if="item.uploaderAvatar"
:src="item.uploaderAvatar"
loading="lazy"
:alt="item.uploaderName"
class="mt-0.5 w-36rem h-36rem"
class="mt-0.5 w-36rem h-36rem efy_shadow_trans efy_shadow_button_off"
width="36"
height="36"
/>
<div class="pp-text" title="item.uploaderName">
<div class="pp-text efy_shadow_trans efy_shadow_button_off">
<span v-text="item.uploaderName" />
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />-->
<div class="w-full">
<img
class="w-full"
:src="thumbnail"
:alt="title"
:class="{ 'shorts-img': item.isShort, 'opacity-75': item.watched }"
loading="lazy"
/>
<!-- progress bar -->
<div class="relative h-1 w-full">
<div
v-if="item.watched && item.duration > 0"
class="absolute bottom-0 left-0 h-1 bg-red-600"
:style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
/>
</div>
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
</div>
<div class="relative text-sm">
<span
v-if="item.duration > 0"
class="thumbnail-overlay thumbnail-right"
v-text="timeFormat(item.duration)"
/>
<!-- shorts thumbnail -->
<span v-if="item.isShort" v-t="'video.shorts'" class="thumbnail-overlay thumbnail-left" />
<span
v-else-if="item.duration >= 0"
class="thumbnail-overlay thumbnail-right"
v-text="timeFormat(item.duration)"
/>
<i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
<font-awesome-icon class="w-3" :icon="['fas', 'broadcast-tower']" />
</i18n-t>
<span v-if="item.watched" v-t="'video.watched'" class="thumbnail-overlay bottom-5px left-5px" />
</div>
<p
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
class="pp-video-card-title my-2 overflow-hidden flex link"
:title="title"
v-text="title"
/>
</router-link>
<div class="flex">
<router-link :to="item.uploaderUrl">
<img
v-if="item.uploaderAvatar"
:src="item.uploaderAvatar"
loading="lazy"
class="mr-0.5 mt-0.5 h-32px w-32px rounded-full"
width="68"
height="68"
/>
</router-link>
<div class="flex-1 px-2">
<router-link
v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
class="link-secondary block overflow-hidden text-sm"
:to="item.uploaderUrl"
:title="item.uploaderName"
>
<span v-text="item.uploaderName" />
<font-awesome-icon v-if="item.uploaderVerified" class="ml-1.5" icon="check" />
</router-link>
<div v-if="item.views >= 0 || item.uploadedDate" class="mt-1 text-xs font-normal text-gray-300">
<span v-if="item.views >= 0">
<font-awesome-icon icon="eye" />
<span class="pl-1" v-text="`${numberFormat(item.views)} •`" />
</span>
<span v-if="item.uploaded > 0" class="pl-0.5" v-text="timeAgo(item.uploaded)" />
<span v-else-if="item.uploadedDate" class="pl-0.5" v-text="item.uploadedDate" />
</div>
</div>
<div class="flex items-center gap-2.5">
<router-link
:to="{
path: '/watch',
query: {
v: item.url.substr(-11),
...(playlistId && { list: playlistId }),
...(index >= 0 && { index: index + 1 }),
listen: '1',
},
}"
:aria-label="'Listen to ' + title"
:title="'Listen to ' + title"
>
<font-awesome-icon icon="headphones" />
</router-link>
<button :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
<font-awesome-icon icon="circle-plus" />
</button>
<button
v-if="admin"
ref="removeButton"
:title="$t('actions.remove_from_playlist')"
@click="showConfirmRemove = true"
>
<font-awesome-icon icon="circle-minus" />
</button>
<ConfirmModal
v-if="showConfirmRemove"
:message="$t('actions.delete_playlist_video_confirm')"
@close="showConfirmRemove = false"
@confirm="removeVideo(item.url.substr(-11))"
/>
<PlaylistAddModal
v-if="showModal"
:video-id="item.url.substr(-11)"
:video-info="item"
@close="showModal = !showModal"
/>
<!--Master-->
</div>
<!-- </router-link> -->
</div>
</div>
<PlaylistAddModal v-if="showModal" :video-id="video.url.substr(-11)" @close="showModal = !showModal" />
</template>
<style>

View file

@ -11,8 +11,17 @@
ref="previewContainer"
class="absolute bottom-0 z-[2000] mb-[3.5%] hidden flex-col items-center"
>
<canvas id="preview" ref="preview" class="rounded-sm" />
<span class="mt-2 w-min rounded-xl bg-dark-700 px-2 pb-1 pt-1.5 text-sm" v-text="timeFormat(currentTime)" />
<canvas id="preview" ref="preview" style="border-radius: var(--efy_radius0)" />
<span
class="w-min"
style="
border-radius: var(--efy_radius0);
background: var(--efy_text2);
color: var(--efy_text);
padding: 3rem 6rem;
"
v-text="timeFormat(currentTime)"
/>
</span>
<button
v-if="inSegment"

View file

@ -10,7 +10,7 @@
/>
</div>
<LoadingIndicatorPage :show-content="video && !isEmbed" class="w-full">
<LoadingIndicatorPage :show-content="video && !isEmbed" class="w-full mt-[15rem]">
<ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" />
<Transition>
<ToastComponent v-if="shouldShowToast" @dismissed="dismiss">

View file

@ -7,7 +7,7 @@
--efy_gap: 15rem;
--efy_sidebar_button: right_middle, off;
--efy_body_width: 100%;
--efy_body_padding: 15rem calc(15rem + var(--efy_gap));
--efy_body_padding: 15rem;
--efy_font_family: 'nunito', sans-serif,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
--efy_audio_folder: ./efy/audio;
--efy_folder: ./efy;
@ -45,8 +45,6 @@
/*Video Grid */ .video-grid {display: grid; gap: var(--efy_gap); grid-template-columns: repeat(auto-fill, minmax(240rem, 1fr))}
tbody:nth-child(odd) {background: var(--efy_bg1)!important; box-shadow: inset 0 0 0 1.5px var(--efy_bg1)}
/*Bellow*/ .pp-watch-bellow-options {margin-top: 15rem}
.pp-watch-buttons {flex-wrap: wrap; gap: var(--efy_gap0)}
.pp-watch-buttons .btn {padding: 6rem 15rem; border: 0; color: var(--efy_text2); min-height: var(--efy_ratio_width); max-height: var(--efy_ratio_width); place-self: center; place-content: center}
@ -95,13 +93,6 @@ tbody:nth-child(odd) {background: var(--efy_bg1)!important; box-shadow: inset 0
/*Preferences*/ .pp-pref-cards {display: grid; grid-template-columns: repeat(auto-fit, minmax(300rem, 1fr))}
table {margin-top: 0}
/*Chapters*/ .pp-chapters {margin-left: var(--efy_gap); max-width: 400rem; gap: var(--efy_gap0); border-radius: var(--efy_radius)}
.pp-chapters .chapter {padding: 10rem; border-radius: var(--efy_radius); border: var(--efy_border)}
.pp-chapters [title=chapters] {padding: 5rem 10rem; border-radius: var(--efy_radius); border: var(--efy_border)}
.pp-chapters .chapter .flex {gap: 0 15rem}
.pp-chapters.pp-mobile {margin: 15rem 0 0 0; max-width: 100%}
/*Modal*/ .modal {backdrop-filter: blur(5px)}
.modal-container {background: var(--efy_text2)!important; padding: 15rem!important; box-shadow: 0 0 1px var(--efy_text_trans)}
.modal-container button + button {margin-left: 10rem}
@ -115,7 +106,7 @@ table {margin-top: 0}
/*Other*/ .pp-show-recs, .pp-show-playlist, .pp-show-playlist {display: grid; gap: var(--efy_gap)}
.pp-show-playlist {margin-bottom: 15rem}
:is(.video-grid, .pp-show-recs, .pp-show-playlist) div {padding: 15rem; background: var(--efy_bg1); border: var(--efy_border)}
:is(.video-grid, .pp-show-recs, .pp-show-playlist) div {background: var(--efy_bg1); border: var(--efy_border)}
:is(.video-grid, .pp-show-recs, .pp-show-playlist) div div {padding: 0; border: none; background: transparent}
.video-grid > div {
@ -160,6 +151,10 @@ table {margin-top: 0}
padding: 4rem 10rem;
font-weight: normal;
}
.thumbnail[src*="/?host=i.ytimg.com"] {
aspect-ratio: 16/9;
object-fit: cover;
}
/*Convergence*/
/*Desktop*/
@ -172,7 +167,6 @@ table {margin-top: 0}
@media (max-width: 767.9px) {
.pp-rec-vids {grid-template-columns: 1fr 1fr}
.\<md\:hidden {display: none}
.md\:hidden {margin-bottom: 16rem}
}
@media (max-width: 639px) {
.pp-rec-vids {grid-template-columns: 1fr}