joinpeertube/src/components/InstancesList.vue
2019-11-18 09:12:44 +01:00

508 lines
13 KiB
Vue

<template>
<div id="instances-list">
<div class="filters">
<div v-translate class="title">Filter according to your preferences</div>
<form @change="onFormChange()">
<div class="group">
<label for="profile" v-translate>Your profile</label>
<b-form-radio-group id="profile" buttons name="profile" v-model="profile">
<b-form-radio value="viewer">
<translate>Viewer</translate>
</b-form-radio>
<b-form-radio value="video-maker">
<translate>Video maker</translate>
</b-form-radio>
</b-form-radio-group>
</div>
<div class="group">
<label for="profile" v-translate>You want to</label>
<b-form-radio-group id="wantTo" buttons name="wantTo" v-model="wantTo">
<b-form-radio value="create-account">
<translate>Create an account</translate>
</b-form-radio>
<b-form-radio value="discover-instances">
<translate>Discover instances</translate>
</b-form-radio>
</b-form-radio-group>
</div>
<div class="group">
<label for="themes" class="label-checkbox" v-translate>Themes</label>
<b-form-checkbox-group
v-model="themes"
:options="availableThemes"
name="themes"
id="themes"
size="lg"
></b-form-checkbox-group>
</div>
<div class="group">
<label for="profile" v-translate>Sensitive videos</label>
<b-form-radio-group id="nsfw" buttons name="nsfw" v-model="nsfw">
<b-form-radio value="do_not_list">
<translate>Hide</translate>
</b-form-radio>
<b-form-radio value="blur">
<translate>Blur</translate>
</b-form-radio>
<b-form-radio value="display">
<translate>Display</translate>
</b-form-radio>
<b-form-radio value="no-opinion">
<translate>No opinion</translate>
</b-form-radio>
</b-form-radio-group>
</div>
<div class="group">
<label for="instance-languages" class="label-checkbox" v-translate>Instance languages</label>
<b-form-checkbox-group
v-model="languages"
:options="availableLanguages"
name="instance-languages"
id="instance-languages"
size="lg"
></b-form-checkbox-group>
</div>
<div class="group" v-if="isQuotaEnabled()">
<label for="quota" v-translate>Allowed video space</label>
<b-form-select v-model="quota" id="quota" name="quota">
<option value="1000000000">
<translate>At least 1GB</translate>
</option>
<option value="5000000000">
<translate>At least 5GB</translate>
</option>
<option value="20000000000">
<translate>At least 20GB</translate>
</option>
<option value="50000000000">
<translate>At least 50GB</translate>
</option>
</b-form-select>
</div>
</form>
</div>
<div class="list">
<div class="title">
<translate>Instances list</translate>
<div class="mascot-loading-block">
<img v-bind:class="{ animate: loadingAnimation }" v-on:animationend="loadingAnimation = false" class="mascot-loading" :src="buildImgUrl('mascot/happy.png')" alt="Loading...">
</div>
</div>
<div class="list" v-bind:class="{ unloaded: !error && !noResults && instances.length === 0 }">
<div v-for="instance of instances" class="instance" :key="instance.host">
<instance-card
:instance="instance" :isVideoMaker="isVideoMaker()"
:translatedLanguages="translatedLanguages" :translatedThemes="translatedThemes"
>
</instance-card>
</div>
</div>
<div v-if="error" class="message">
<img :src="buildImgUrl('mascot/defeated.png')" alt="">
<div class="alert alert-danger">Sorry, but we cannot fetch the instances list. Please retry later.</div>
</div>
<div v-if="noResults" class="message">
<img :src="buildImgUrl('mascot/oh.png')" alt="">
<div class="alert alert-info">Sorry, but we did not find any instance matching your filters.</div>
</div>
</div>
</div>
</template>
<style lang="scss">
@import '../scss/_variables.scss';
.custom-checkbox {
.custom-control-label > span {
font-size: 16px;
}
.custom-control-input:checked + label {
font-weight: $font-semibold;
}
}
#themes {
.custom-control {
&:nth-child(4n) {
width: 100px;
margin-right: 0;
}
&:nth-child(4n-1) {
width: 190px;
}
&:nth-child(4n-2) {
width: 130px;
}
&:nth-child(4n-3) {
width: 100px;
}
}
}
.list {
.title {
height: 150px;
display: flex;
align-items: center;
}
.instance {
margin-bottom: 40px;
}
.list.unloaded {
min-height: 400px;
}
}
.mascot-loading-block {
flex-grow: 1;
.mascot-loading {
display: none;
@media screen and (min-width: $responsive-screen) {
&.animate {
display: block;
animation: mascotLoadingAnimation 1s normal ease-out;
@keyframes mascotLoadingAnimation {
0% {
margin-left: 0;
}
50% {
opacity: 1;
}
100% {
margin-left: calc(100% - 150px);
opacity: 0;
}
}
}
}
}
}
@media screen and (max-width: $responsive-screen) {
.custom-checkbox {
.custom-control-label > span {
font-size: 15px;
}
}
#themes {
.custom-control {
margin-right: 10px;
width: calc(50% - 10px) !important;
}
}
}
#instances-list {
.title {
font-size: 24px;
}
.filters {
margin-bottom: 100px;
.title {
margin-bottom: 25px;
}
form {
border-left: 6px solid $orange;
padding-left: 24px;
.group {
margin-bottom: 30px;
display: flex;
align-items: center;
& > label {
min-width: 140px;
margin: 0 10px 0 0;
&.label-checkbox {
align-self: flex-start;
}
}
}
select {
width: 280px;
height: 35px;
line-height: 21px;
border-radius: 0;
}
.btn-group-toggle {
.btn {
display: flex;
align-items: center;
color: #000;
background-color: #fff;
border-color: $orange;
min-height: 30px;
padding: 0 15px;
cursor: pointer;
&.active {
color: #fff;
font-weight: $font-semibold;
background-color: $orange;
}
}
}
}
}
.message img {
display: block;
margin: 0 auto 50px;
}
@media screen and (max-width: $responsive-screen) {
.filters {
margin-bottom: 100px;
form {
padding-left: 15px;
.group {
flex-direction: column;
align-items: flex-start;
label {
margin-bottom: 5px;
font-size: 15px;
}
}
.btn-group-toggle {
.btn {
padding: 0 8px;
}
}
}
}
}
}
</style>
<script>
import { BFormCheckboxGroup, BFormRadio, BFormRadioGroup, BFormSelect } from 'bootstrap-vue'
import axios from 'axios'
import InstanceCard from './InstanceCard'
export default {
components: {
'b-form-radio-group': BFormRadioGroup,
'b-form-radio': BFormRadio,
'b-form-checkbox-group': BFormCheckboxGroup,
'b-form-select': BFormSelect,
InstanceCard
},
data () {
return {
error: false,
noResults: false,
loadingAnimation: false,
instances: [],
translatedThemes: {
'1': this.$gettext('Music'),
'2': this.$gettext('Films'),
'3': this.$gettext('Vehicles'),
'4': this.$gettext('Art'),
'5': this.$gettext('Sports'),
'6': this.$gettext('Travels'),
'7': this.$gettext('Gaming'),
'8': this.$gettext('People'),
'9': this.$gettext('Comedy'),
'10': this.$gettext('Entertainment'),
'11': this.$gettext('News & Politics'),
'12': this.$gettext('How To'),
'13': this.$gettext('Education'),
'14': this.$gettext('Activism'),
'15': this.$gettext('Science & Technology'),
'16': this.$gettext('Animals'),
'17': this.$gettext('Kids'),
'18': this.$gettext('Food')
},
translatedLanguages: {
'en': this.$gettext('English'),
'fr': this.$gettext('Français'),
'ja': this.$gettext('日本語'),
'eu': this.$gettext('Euskara'),
'ca': this.$gettext('Català'),
'cs': this.$gettext('Čeština'),
'eo': this.$gettext('Esperanto'),
'el': this.$gettext('ελληνικά'),
'de': this.$gettext('Deutsch'),
'it': this.$gettext('Italiano'),
'nl': this.$gettext('Nederlands'),
'es': this.$gettext('Español'),
'oc': this.$gettext('Occitan'),
'gd': this.$gettext('Gàidhlig'),
'zh': this.$gettext('简体中文(中国)'),
'pt': this.$gettext('Português (Portugal)'),
'sv': this.$gettext('svenska'),
'pl': this.$gettext('Polski'),
'fi': this.$gettext('suomi'),
'ru': this.$gettext('русский')
},
profile: 'viewer',
wantTo: 'create-account',
themes: [],
nsfw: 'no-opinion',
languages: [],
quota: '5000000000'
}
},
computed: {
availableThemes: function () {
const result = []
for (const key of Object.keys(this.translatedThemes)) {
result.push({
value: key,
text: this.translatedThemes[key]
})
}
return result
},
availableLanguages: function () {
const available = [
{
value: 'en',
text: this.translatedLanguages['en']
}
]
const navigatorLanguage = window.navigator.userLanguage || window.navigator.language
const smallLanguage = navigatorLanguage.split('-')[0]
const found = this.translatedLanguages[smallLanguage]
if (found && smallLanguage !== 'en') {
available.push({
value: smallLanguage,
text: found
})
}
return available
}
},
methods: {
isVideoMaker () {
return this.profile === 'video-maker'
},
wantToRegister () {
return this.wantTo === 'create-account'
},
// Thanks https://stackoverflow.com/a/6274381
shuffle (a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]]
}
return a
},
onFormChange () {
// Wait for v-model to be updated
// https://github.com/vuejs/vue/issues/293#issuecomment-265716984
setTimeout(() => {
console.log('Updating instances list.')
this.fetchInstances()
})
},
isQuotaEnabled () {
return this.isVideoMaker() && this.wantToRegister()
},
fetchInstances () {
this.noResults = false
this.loadingAnimation = true
const params = {
start: 0,
count: 250,
healthy: true
}
if (this.wantTo === 'create-account') params.signup = true
if (this.themes.length !== 0) params.categoriesOr = this.themes
if (this.nsfw !== 'no-opinion') params.nsfwPolicy = [ this.nsfw ]
if (this.languages.length !== 0) params.languagesOr = this.languages
if (this.isQuotaEnabled()) params.minUserQuota = this.quota
const options = {
method: 'GET',
params
}
axios('https://instances.joinpeertube.org/api/v1/instances', options)
.then(response => {
this.instances = this.shuffle(response.data.data)
if (this.instances.length === 0) this.noResults = true
})
.catch(err => {
console.error(err)
this.error = true
})
}
},
mounted () {
this.fetchInstances()
}
}
</script>