sepia-search-motore-di-rice.../client/src/views/Search.vue

1048 lines
33 KiB
Vue

<template>
<div>
<my-header v-bind:indexName="indexName" v-bind:smallFormat="searchDone"></my-header>
<router-link v-if="searchDone" class="header-left-logo" to="/" @click.native="simpleFormReset(false)" :title="homeTitleMessage">
<img src="/img/peertube-logo.svg" alt="">
</router-link>
<main>
<form id="search-anchor" class="search-container" v-bind:class="{ 'search-container-small': !searchDone }" role="search" onsubmit="return false;">
<input v-bind:placeholder="inputPlaceholder" autofocus v-on:keyup.enter="doNewSearch()" type="text" v-model="formSearch" name="search" autocapitalize="off" autocomplete="off" autocorrect="off" maxlength="1024" />
<button v-on:click="doNewSearch()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<translate>Go!</translate>
</button>
</form>
<h3 v-if="!searchDone" v-bind:class="{ 'none-opacity': !instancesCount }" v-translate="{ instancesCount: instancesCount, indexedInstancesUrl: indexedInstancesUrl, indexName: indexName }">
Search for your favorite videos, channels and playlists on <a href="%{indexedInstancesUrl}" target="_blank">%{instancesCount} PeerTube websites</a> indexed by %{indexName}!
</h3>
<search-warning class="search-warning" v-bind:indexName="indexName" v-bind:highlight="!searchDone"></search-warning>
<div class="results" v-if="searchDone">
<div class="filters" v-if="searchDone && searchedTerm">
<div class="button-rows">
<button class="filters-button peertube-button" v-on:click="toggleFilters()">
<translate>Filters</translate>
&nbsp;
<span v-if="activeFilters">({{ activeFilters }})</span>
<div class="arrow-down"></div>
</button>
<div class="sort-select">
<label for="sort" v-translate>Sort by:</label>
<div class="sort-select select-container">
<select id="sort" name="sort" v-model="formSort">
<option v-translate value="-match">Best match</option>
<option v-translate value="-publishedAt">Most recent</option>
<option v-translate value="publishedAt">Least recent</option>
</select>
</div>
</div>
</div>
<form v-if="displayFilters" class="filters-content">
<div class="form-group small-height">
<div class="radio-label label-container">
<label v-translate>Display sensitive content</label>
<button v-translate class="reset-button" v-on:click="resetField('nsfw')" v-if="formNSFW !== undefined">
Reset
</button>
</div>
<div class="radio-container">
<input type="radio" name="sensitiveContent" id="sensitiveContentYes" value="both" v-model="formNSFW">
<label v-translate for="sensitiveContentYes" class="radio">Yes</label>
</div>
<div class="radio-container">
<input type="radio" name="sensitiveContent" id="sensitiveContentNo" value="false" v-model="formNSFW">
<label v-translate for="sensitiveContentNo" class="radio">No</label>
</div>
</div>
<div class="form-group small-height">
<div class="radio-label label-container">
<label v-translate>Display only</label>
<button v-translate class="reset-button" v-on:click="resetField('isLive')" v-if="formIsLive !== undefined">
Reset
</button>
</div>
<div class="radio-container">
<input type="radio" name="isLive" id="isLiveYes" value="both" v-model="formIsLive">
<label v-translate for="isLiveYes" class="radio">Live videos</label>
</div>
<div class="radio-container">
<input type="radio" name="isLive" id="isLiveNo" value="false" v-model="formIsLive">
<label v-translate for="isLiveNo" class="radio">VOD videos</label>
</div>
</div>
<div class="form-group">
<div class="radio-label label-container">
<label v-translate>Published date</label>
<button v-translate class="reset-button" v-on:click="resetField('publishedDateRange')" v-if="formPublishedDateRange !== undefined">
Reset
</button>
</div>
<div class="radio-container" v-for="date in publishedDateRanges" :key="date.id">
<input type="radio" name="publishedDateRange" v-bind:id="date.id" v-bind:value="date.id" v-model="formPublishedDateRange">
<label v-bind:for="date.id" class="radio">{{ date.label }}</label>
</div>
</div>
<div class="form-group">
<div class="radio-label label-container">
<label v-translate>Duration</label>
<button v-translate class="reset-button" v-on:click="resetField('durationRange')" v-if="formDurationRange !== undefined">
Reset
</button>
</div>
<div class="radio-container" v-for="duration in durationRanges" :key="duration.id">
<input type="radio" name="durationRange" v-bind:id="duration.id" v-bind:value="duration.id" v-model="formDurationRange">
<label v-bind:for="duration.id" class="radio">{{ duration.label }}</label>
</div>
</div>
<div class="form-group">
<label v-translate for="category">Category</label>
<button v-translate class="reset-button" v-on:click="resetField('categoryOneOf')" v-if="formCategoryOneOf !== undefined">
Reset
</button>
<div class="select-container">
<select id="category" name="category" v-model="formCategoryOneOf">
<option v-translate v-bind:value="undefined">Display all categories</option>
<option v-for="category in videoCategories" v-bind:value="category.id" :key="category.id">{{ category.label }}</option>
</select>
</div>
</div>
<div class="form-group">
<label v-translate for="licence">Licence</label>
<button v-translate class="reset-button" v-on:click="resetField('licenceOneOf')" v-if="formLicenceOneOf !== undefined">
Reset
</button>
<div class="select-container">
<select id="licence" name="licence" v-model="formLicenceOneOf">
<option v-translate v-bind:value="undefined">Display all licenses</option>
<option v-for="licence in videoLicences" v-bind:value="licence.id" :key="licence.id">{{ licence.label }}</option>
</select>
</div>
</div>
<div class="form-group">
<label v-translate for="language">Language</label>
<button v-translate class="reset-button" v-on:click="resetField('languageOneOf')" v-if="formLanguageOneOf !== undefined">
Reset
</button>
<div class="select-container">
<select id="language" name="language" v-model="formLanguageOneOf">
<option v-translate v-bind:value="undefined">Display all languages</option>
<option v-for="language in videoLanguages" v-bind:value="language.id" :key="language.id">{{ language.label }}</option>
</select>
</div>
</div>
<div class="form-group">
<label v-translate for="tagsAllOf">All of these tags</label>
<button v-translate class="reset-button" v-on:click="resetField('tagsAllOf')" v-if="formTagsAllOf.length !== 0">
Reset
</button>
<vue-tags-input v-bind:placeholder="tagsPlaceholder" @tags-changed="newTags => formTagsAllOf = newTags" v-model="formTagAllOf" :tags="formTagsAllOf" />
</div>
<div class="form-group">
<label v-translate for="tagsOneOf">One of these tags</label>
<button v-translate class="reset-button" v-on:click="resetField('tagsOneOf')" v-if="formTagsOneOf.length !== 0">
Reset
</button>
<vue-tags-input v-bind:placeholder="tagsPlaceholder" @tags-changed="newTags => formTagsOneOf = newTags" v-model="formTagOneOf" :tags="formTagsOneOf" />
</div>
<div class="button-block">
<input class="peertube-button" type="button" v-bind:value="applyFiltersLabel" v-on:click="doNewSearch()" />
</div>
</form>
</div>
<div id="results-anchor" class="results-summary" v-if="(searchedTerm && resultsCount === 0) || (resultsCount !== null && resultsCount !== 0)">
<span v-if="searchedTerm && resultsCount === 0" v-translate>No results found for</span>
&nbsp;
<span v-if="resultsCount !== null && resultsCount !== 0">
<translate :translate-n="resultsCount" translate-plural="%{resultsCount} results found for">%{resultsCount} result found for</translate>
</span>
&nbsp;
<strong>
{{ searchedTerm }}
<span v-if="activeFilters > 0">
<translate :translate-n="activeFilters" translate-plural="with %{activeFilters} active filters">with %{activeFilters} active filter</translate>
</span>
</strong>
&nbsp;
<span>
<translate :translate-n="instancesCount" translate-plural="on %{instancesCount} indexed PeerTube websites">on %{instancesCount} indexed PeerTube website</translate>
</span>
</div>
<div v-for="result in results" :key="getResultKey(result)">
<video-result v-if="isVideo(result)" v-bind:video="result"></video-result>
<channel-result v-else-if="isChannel(result)" v-bind:channel="result"></channel-result>
<playlist-result v-else v-bind:playlist="result"></playlist-result>
</div>
<pagination v-bind:maxPage="getMaxPage()" v-bind:searchDone="searchDone" v-model="currentPage" v-bind:pages="pages"></pagination>
</div>
</main>
</div>
</template>
<style lang="scss">
@import '../scss/_variables';
main {
margin: auto;
}
h3 {
max-width: 600px;
text-align: center;
margin: 40px auto 100px auto;
font-weight: normal;
font-size: 16px;
a {
color: $orange-darken;
&:hover {
color: $orange;
}
}
@media screen and (max-width: $small-screen) {
font-size: 14px;
}
}
.search-container {
background-color: #fff;
border-radius: 2px;
position: relative;
height: 45px;
margin: auto;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
input[type=text] {
background-color: transparent;
outline: none;
height: 35px;
font-size: 15px;
border: 0;
width: 100%;
height: 100%;
color: #000;
padding: 0 20px;
}
button {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
cursor: pointer;
position: absolute;
right: 0;
background-color: $orange-darken;
border: 0;
color: #FFF;
height: 100%;
outline: 0;
font-size: 15px;
padding: 0 15px 0 10px;
display: inline-flex;
align-items: center;
svg {
margin-right: 10px;
}
&:hover {
background-color: $orange;
}
}
button + div {
margin-top: 10px;
font-size: 80%;
text-align: right;
}
}
.search-container-small {
max-width: 500px;
margin-top: 30px;
}
.header-left-logo {
position: relative;
display: block;
height: 0;
img {
position: absolute;
left: -50px;
top: 0;
width: 32px;
}
}
.results-summary,
.no-results {
margin-top: 50px;
}
.results {
border-top: 1px solid rgba(0, 0, 0, 0.1);
padding-top: 20px;
}
.filters-content {
display: flex;
flex-wrap: wrap;
margin-top: 20px;
.form-group:nth-child(2n-1) {
padding-right: 20px;
}
.form-group {
width: 50%;
min-height: 80px;
display: inline-block;
margin: 10px 0;
font-size: 14px;
}
@media screen and (max-width: $small-view) {
.form-group {
width: 100%;
padding-right: 0;
}
}
.form-group > label,
.label-container label {
font-weight: $font-semibold;
}
.form-group > label,
.label-container {
display: inline-block;
margin-bottom: 5px;
}
.form-group.small-height {
min-height: 30px;
}
.radio-container {
display: inline-block;
margin: 0 10px 5px 0;
}
.button-block {
width: 100%;
text-align: right;
}
.radio-label {
display: block;
}
.reset-button {
background: none;
border: none;
font-weight: 600;
font-size: 11px;
opacity: 0.7;
margin-left: 5px;
cursor: pointer;
}
}
.filters-button {
position: relative;
padding-right: 25px;
.arrow-down {
$size: 4px;
position: absolute;
right: 0;
top: 50%;
margin-top: -$size/2;
margin-right: 10px;
width: 0;
height: 0;
border-left: $size solid transparent;
border-right: $size solid transparent;
border-top: $size solid #fff;
}
}
.button-rows {
display: flex;
justify-content: space-between;
.sort-select {
display: flex;
label {
color: $grey;
font-size: 14px;
min-width: fit-content;
margin: auto 5px auto 0;
}
.select-container {
max-width: 150px;
}
}
}
</style>
<script lang="ts">
import Vue from 'vue'
import Header from '../components/Header.vue'
import SearchWarning from '../components/SearchWarning.vue'
import VideoResult from '../components/VideoResult.vue'
import ChannelResult from '../components/ChannelResult.vue'
import PlaylistResult from '../components/PlaylistResult.vue'
import { searchVideos, searchVideoChannels, searchVideoPlaylists } from '../shared/search'
import { getConfig } from '../shared/config'
import { pageToAPIParams, durationRangeToAPIParams, publishedDateRangeToAPIParams, extractTagsFromQuery, buildApiUrl } from '../shared/utils'
import { SearchUrl } from '../models'
import { EnhancedVideo } from '../../../server/types/video.model'
import { EnhancedVideoChannel } from '../../../server/types/channel.model'
import VueTagsInput from '@johmun/vue-tags-input'
import Pagination from '../components/Pagination.vue'
import { VideoChannelsSearchQuery, ResultList, VideosSearchQuery } from '../../../PeerTube/shared/models'
import Nprogress from 'nprogress'
import { EnhancedPlaylist } from '../../../server/types/playlist.model'
import { PlaylistsSearchQuery } from '../../../server/types/search-query/playlist-search.model'
export default Vue.extend({
components: {
'my-header': Header,
'search-warning': SearchWarning,
'video-result': VideoResult,
'channel-result': ChannelResult,
'playlist-result': PlaylistResult,
'vue-tags-input': VueTagsInput,
'pagination': Pagination
},
data () {
return {
searchDone: false,
indexName: '',
instancesCount: 0,
indexedInstancesUrl: '',
results: [] as (EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist)[],
resultsCount: null as number,
channelsCount: null as number,
videosCount: null as number,
playlistsCount: null as number,
searchedTerm: '',
currentPage: 1,
pages: [],
resultsPerVideosPage: 10,
resultsPerChannelsPage: 3,
resultsPerPlaylistsPage: 3,
displayFilters: false,
oldQuery: '',
formSearch: '',
formSort: '-match',
formNSFW: undefined,
formPublishedDateRange: undefined,
formDurationRange: undefined,
formCategoryOneOf: undefined,
formLicenceOneOf: undefined,
formLanguageOneOf: undefined,
formTagAllOf: '',
formTagOneOf: '',
formTagsAllOf: [],
formTagsOneOf: [],
formIsLive: undefined,
activeFilters: 0
}
},
async mounted () {
const config = await getConfig()
this.instancesCount = config.indexedHostsCount
this.indexedInstancesUrl = config.indexedInstancesUrl
this.indexName = config.searchInstanceName
if (Object.keys(this.$route.query).length !== 0) {
// Speed up search done variable if the user came directly with a query
// So it does not see the homepage for a few ms
this.searchDone = true
this.loadUrl()
this.doSearch()
}
},
watch: {
// For pagination change
$route(to, from) {
const urlPage = this.$route.query.page
const scrollToResults = urlPage && parseInt(urlPage) !== this.currentPage
this.loadUrl()
if (!this.formSearch) {
this.simpleFormReset(false)
this.scrollToTop()
return
}
this.doSearch()
if (scrollToResults) this.scrollToResults()
},
formSort () {
this.updateUrl()
}
},
computed: {
applyFiltersLabel () {
return this.$gettext('Apply filters')
},
inputPlaceholder () {
return this.$gettext('Keyword, channel, video, playlist, etc.')
},
tagsPlaceholder () {
return this.$gettext('Add tag')
},
publishedDateRanges () {
return [
{
id: 'any_published_date',
label: this.$gettext('Any')
},
{
id: 'today',
label: this.$gettext('Today')
},
{
id: 'last_7days',
label: this.$gettext('Last 7 days')
},
{
id: 'last_30days',
label: this.$gettext('Last 30 days')
},
{
id: 'last_365days',
label: this.$gettext('Last 365 days')
}
]
},
durationRanges () {
return [
{
id: 'any_duration',
label: this.$gettext('Any')
},
{
id: 'short',
label: this.$gettext('Short (< 4 min)')
},
{
id: 'medium',
label: this.$gettext('Medium (4-10 min)')
},
{
id: 'long',
label: this.$gettext('Long (> 10 min)')
}
]
},
videoCategories () {
return [
{ id: '1', label: this.$gettext('Music') },
{ id: '2', label: this.$gettext('Films') },
{ id: '3', label: this.$gettext('Vehicles') },
{ id: '4', label: this.$gettext('Art') },
{ id: '5', label: this.$gettext('Sports') },
{ id: '6', label: this.$gettext('Travels') },
{ id: '7', label: this.$gettext('Gaming') },
{ id: '8', label: this.$gettext('People') },
{ id: '9', label: this.$gettext('Comedy') },
{ id: '10', label: this.$gettext('Entertainment') },
{ id: '11', label: this.$gettext('News & Politics') },
{ id: '12', label: this.$gettext('How To') },
{ id: '13', label: this.$gettext('Education') },
{ id: '14', label: this.$gettext('Activism') },
{ id: '15', label: this.$gettext('Science & Technology') },
{ id: '16', label: this.$gettext('Animals') },
{ id: '17', label: this.$gettext('Kids') },
{ id: '18', label: this.$gettext('Food') }
]
},
videoLicences () {
return [
{ id: '1', label: this.$gettext('Attribution') },
{ id: '2', label: this.$gettext('Attribution - Share Alike') },
{ id: '3', label: this.$gettext('Attribution - No Derivatives') },
{ id: '4', label: this.$gettext('Attribution - Non Commercial') },
{ id: '5', label: this.$gettext('Attribution - Non Commercial - Share Alike') },
{ id: '6', label: this.$gettext('Attribution - Non Commercial - No Derivatives') },
{ id: '7', label: this.$gettext('Public Domain Dedication') }
]
},
videoLanguages () {
return [
{ id: 'en', label: this.$gettext('English') },
{ id: 'fr', label: this.$gettext('Français') },
{ id: 'ja', label: this.$gettext('日本語') },
{ id: 'eu', label: this.$gettext('Euskara') },
{ id: 'ca', label: this.$gettext('Català') },
{ id: 'cs', label: this.$gettext('Čeština') },
{ id: 'eo', label: this.$gettext('Esperanto') },
{ id: 'el', label: this.$gettext('ελληνικά') },
{ id: 'de', label: this.$gettext('Deutsch') },
{ id: 'it', label: this.$gettext('Italiano') },
{ id: 'nl', label: this.$gettext('Nederlands') },
{ id: 'es', label: this.$gettext('Español') },
{ id: 'oc', label: this.$gettext('Occitan') },
{ id: 'gd', label: this.$gettext('Gàidhlig') },
{ id: 'zh', label: this.$gettext('简体中文(中国)') },
{ id: 'pt', label: this.$gettext('Português (Portugal)') },
{ id: 'sv', label: this.$gettext('svenska') },
{ id: 'pl', label: this.$gettext('Polski') },
{ id: 'fi', label: this.$gettext('suomi') },
{ id: 'ru', label: this.$gettext('русский') }
]
},
boostLanguagesQuery () {
const languages = new Set<string>()
for (const completeLanguage of navigator.languages) {
const language = completeLanguage.split('-')[0]
if (this.videoLanguages.find(vl => vl.id === language)) {
languages.add(language)
}
}
return Array.from(languages)
},
homeTitleMessage () {
return this.$gettext('Display homepage')
}
},
methods: {
doNewSearch () {
this.currentPage = 1
this.channelsCount = null
this.videosCount = null
this.playlistsCount = null
this.resultsCount = null
this.updateUrl()
},
async doSearch () {
// First search, scroll to top
if (this.searchDone !== true) this.scrollToTop()
this.results = []
Nprogress.start()
try {
const [ videosResult, channelsResult, playlistsResult ] = await Promise.all([
this.searchVideos(),
this.searchChannels(),
this.searchPlaylists()
])
Nprogress.done()
this.activeFilters = this.countActiveFilters()
this.channelsCount = channelsResult.total
this.videosCount = videosResult.total
this.playlistsCount = playlistsResult.total
this.resultsCount = videosResult.total + channelsResult.total + playlistsResult.total
this.results = channelsResult.data.concat(playlistsResult.data)
.concat(videosResult.data)
if (this.formSort === '-match') {
this.results.sort((r1, r2) => {
if (r1.score < r2.score) return 1
else if (r1.score === r2.score) return 0
return -1
})
}
this.buildPages()
this.searchDone = true
this.searchedTerm = this.formSearch
} catch (err) {
console.error('Cannot do search.', err)
} finally {
Nprogress.done()
}
},
isVideo (result: EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist) {
if ((result as EnhancedVideo).language) return true
return false
},
isPlaylist (result: EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist) {
if ((result as EnhancedPlaylist).videosLength !== undefined) return true
return false
},
isChannel (result: EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist) {
if ((result as EnhancedVideoChannel).followingCount !== undefined) return true
return false
},
getResultKey (result: EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist) {
if (this.isVideo(result)) return (result as EnhancedVideo).uuid
if (this.isChannel(result)) return result.id + (result as EnhancedVideoChannel).host
if (this.isPlaylist(result)) return (result as EnhancedPlaylist).uuid
throw new Error('Unknown result')
},
updateUrl () {
const query: SearchUrl = {
search: this.formSearch,
sort: this.formSort,
nsfw: this.formNSFW,
isLive: this.formIsLive,
publishedDateRange: this.formPublishedDateRange,
durationRange: this.formDurationRange,
categoryOneOf: this.formCategoryOneOf,
licenceOneOf: this.formLicenceOneOf,
languageOneOf: this.formLanguageOneOf,
tagsAllOf: this.formTagsAllOf.map(t => t.text),
tagsOneOf: this.formTagsOneOf.map(t => t.text),
page: this.currentPage
}
this.$router.push({ path: '/search', query })
},
loadUrl () {
const query = this.$route.query as SearchUrl & { page: number }
if (query.search) this.formSearch = query.search
else this.formSearch = undefined
if (query.nsfw) this.formNSFW = query.nsfw
else this.formNSFW = undefined
if (query.publishedDateRange) this.formPublishedDateRange = query.publishedDateRange
else this.formPublishedDateRange = undefined
if (query.durationRange) this.formDurationRange = query.durationRange
else this.formDurationRange = undefined
if (query.categoryOneOf) this.formCategoryOneOf = query.categoryOneOf
else this.formCategoryOneOf = undefined
if (query.licenceOneOf) this.formLicenceOneOf = query.licenceOneOf
else this.formLicenceOneOf = undefined
if (query.languageOneOf) this.formLanguageOneOf = query.languageOneOf
else this.formLanguageOneOf = undefined
if (query.tagsAllOf) this.formTagsAllOf = extractTagsFromQuery(query.tagsAllOf)
else this.formTagsAllOf = []
if (query.tagsOneOf) this.formTagsOneOf = extractTagsFromQuery(query.tagsOneOf)
else this.formTagsOneOf = []
if (query.sort) this.formSort = query.sort
else this.formSort = '-match'
if (query.isLive) this.formIsLive = query.isLive
else this.formIsLive = undefined
if (query.page && this.currentPage !== query.page) {
this.currentPage = parseInt(query.page + '')
} else {
this.currentPage = 1
}
},
buildVideoSearchQuery () {
const { start, count } = pageToAPIParams(this.currentPage, this.resultsPerVideosPage)
const { durationMin, durationMax } = durationRangeToAPIParams(this.formDurationRange)
const { startDate, endDate } = publishedDateRangeToAPIParams(this.formPublishedDateRange)
const boostLanguages = this.boostLanguagesQuery
return {
search: this.formSearch,
durationMin,
durationMax,
startDate,
endDate,
boostLanguages,
nsfw: this.formNSFW || false,
categoryOneOf: this.formCategoryOneOf ? [ this.formCategoryOneOf ] : undefined,
licenceOneOf: this.formLicenceOneOf ? [ this.formLicenceOneOf ] : undefined,
languageOneOf: this.formLanguageOneOf ? [ this.formLanguageOneOf ] : undefined,
tagsOneOf: this.formTagsOneOf.map(t => t.text),
tagsAllOf: this.formTagsAllOf.map(t => t.text),
isLive: this.formIsLive !== undefined ? this.formIsLive : undefined,
start,
count,
sort: this.formSort
} as VideosSearchQuery
},
buildChannelSearchQuery () {
const { start, count } = pageToAPIParams(this.currentPage, this.resultsPerChannelsPage)
let sort: string
if (this.formSort === '-matched') sort = '-matched'
else if (this.formSort === '-publishedAt') sort = '-createdAt'
else if (this.formSort === 'publishedAt') sort = 'createdAt'
return {
search: this.formSearch,
start,
sort,
count
} as VideoChannelsSearchQuery
},
buildPlaylistSearchQuery () {
const { start, count } = pageToAPIParams(this.currentPage, this.resultsPerChannelsPage)
let sort: string
if (this.formSort === '-matched') sort = '-matched'
else if (this.formSort === '-publishedAt') sort = '-createdAt'
else if (this.formSort === 'publishedAt') sort = 'createdAt'
return {
search: this.formSearch,
start,
sort,
count
} as PlaylistsSearchQuery
},
searchVideos (): Promise<ResultList<EnhancedVideo>> {
if (!this.formSearch) {
return Promise.resolve({ data: [], total: 0 })
}
if (!this.hasStillMoreVideosResults()) {
return Promise.resolve({ data: [], total: this.videosCount })
}
const query = this.buildVideoSearchQuery()
return searchVideos(query)
},
searchChannels (): Promise<ResultList<EnhancedVideoChannel>> {
if (!this.formSearch || this.isChannelOrPlaylistSearchDisabled()) {
return Promise.resolve({ data: [], total: 0 })
}
if (!this.hasStillChannelsResult()) {
return Promise.resolve({ data: [], total: this.channelsCount })
}
const query = this.buildChannelSearchQuery()
return searchVideoChannels(query)
},
searchPlaylists (): Promise<ResultList<EnhancedPlaylist>> {
if (!this.formSearch || this.isChannelOrPlaylistSearchDisabled()) {
return Promise.resolve({ data: [], total: 0 })
}
if (!this.hasStillPlaylistsResult()) {
return Promise.resolve({ data: [], total: this.channelsCount })
}
const query = this.buildChannelSearchQuery()
return searchVideoPlaylists(query)
},
hasStillChannelsResult () {
// Not searched yet
if (this.channelsCount === null) return true
return this.getChannelsMaxPage() >= this.currentPage
},
hasStillPlaylistsResult () {
// Not searched yet
if (this.playlistsCount === null) return true
return this.getPlaylistsMaxPage() >= this.currentPage
},
hasStillMoreVideosResults () {
// Not searched yet
if (this.videosCount === null) return true
return this.getVideosMaxPage() >= this.currentPage
},
getChannelsMaxPage () {
return Math.ceil(this.channelsCount / this.resultsPerChannelsPage)
},
getPlaylistsMaxPage () {
return Math.ceil(this.playlistsCount / this.resultsPerPlaylistsPage)
},
getVideosMaxPage () {
return Math.ceil(this.videosCount / this.resultsPerVideosPage)
},
getMaxPage () {
// Limit to 10 pages
return Math.min(10, Math.max(this.getPlaylistsMaxPage(), this.getChannelsMaxPage(), this.getVideosMaxPage()))
},
buildPages () {
this.pages = []
for (let i = 1; i <= this.getMaxPage(); i++) {
this.pages.push(i)
}
},
toggleFilters () {
this.displayFilters = !this.displayFilters
},
resetField (field: string) {
if (field === 'nsfw') this.formNSFW = undefined
else if (field === 'publishedDateRange') this.formPublishedDateRange = undefined
else if (field === 'durationRange') this.formDurationRange = undefined
else if (field === 'categoryOneOf') this.formCategoryOneOf = undefined
else if (field === 'licenceOneOf') this.formLicenceOneOf = undefined
else if (field === 'languageOneOf') this.formLanguageOneOf = undefined
else if (field === 'isLive') this.formIsLive = undefined
else if (field === 'tagsAllOf') this.formTagsAllOf = []
else if (field === 'tagsOneOf') this.formTagsOneOf = []
},
simpleFormReset (searchDone = true) {
this.formSearch = ''
this.searchDone = searchDone
},
countActiveFilters () {
let count = 0
if (this.formNSFW) count++
if (this.formPublishedDateRange) count++
if (this.formDurationRange) count++
if (this.formCategoryOneOf) count++
if (this.formLicenceOneOf) count++
if (this.formLanguageOneOf) count++
if (this.formIsLive) count++
if (this.formTagsAllOf && this.formTagsAllOf.length !== 0) count++
if (this.formTagsOneOf && this.formTagsOneOf.length !== 0) count++
return count
},
isChannelOrPlaylistSearchDisabled () {
return this.countActiveFilters() > 0
},
scrollToResults () {
const anchor = document.getElementById('results-anchor')
if (anchor) anchor.scrollIntoView()
},
scrollToTop () {
window.scrollTo(0, 0)
}
}
})
</script>