traxxx/assets/components/actors/actors.vue

523 lines
11 KiB
Vue

<template>
<div class="actors">
<nav class="filter">
<ul class="genders nolist">
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'all', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'all' }"
class="gender-link all"
>all</router-link>
</li>
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'female', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'female' }"
class="gender-link female"
><Gender gender="female" /></router-link>
</li>
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'male', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'male' }"
class="gender-link male"
replace
><Gender gender="male" /></router-link>
</li>
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'trans', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'trans' }"
class="gender-link transsexual"
replace
><Gender gender="transsexual" /></router-link>
</li>
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'other', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'other' }"
class="gender-link other"
replace
><Icon icon="question5" /></router-link>
</li>
</ul>
<ul class="letters nolist">
<li
v-for="letterX in letters"
:key="letterX"
class="letter"
>
<router-link
:to="{ name: 'actors', params: { gender, letter: letterX, pageNumber: 1 } }"
:class="{ selected: letterX === letter }"
class="letter-link"
replace
>{{ letterX || 'All' }}</router-link>
</li>
</ul>
</nav>
<nav class="filter">
<ul class="nolist">
<li>
<Tooltip class="filter boobs">
<span class="filter-trigger"><Icon icon="boobs" />Boobs</span>
<template v-slot:tooltip>
<div class="filter-section boobsize">
<label class="filter-label off">Size</label>
<span class="filter-split">
<Checkbox
:checked="boobSizeRequired"
class="checkbox"
@change="(checked) => updateValue('boobSizeRequired', checked)"
/>
<Range
:min="0"
:max="boobSizes.length - 1"
:value="boobSize"
:disabled="!boobSizeRequired"
@change="(sizeRange) => updateValue('boobSize', sizeRange)"
>
<template v-slot:start>
<span class="range-label"><Icon icon="boobs-small" /></span>
</template>
<template v-slot:end>
<span class="range-label on"><Icon icon="boobs-big" /></span>
</template>
</Range>
</span>
</div>
<div class="filter-section">
<label class="filter-label">Enhanced</label>
<span
:class="{ [['off', 'default', 'on'][naturalBoobs]]: true }"
class="toggle-container noclick"
>
<span
class="toggle-label off"
@click="updateValue('naturalBoobs', 0)"
>no</span>
<input
v-model="naturalBoobs"
class="toggle"
type="range"
min="0"
max="2"
@change="updateFilters"
>
<span
class="toggle-label on"
@click="updateValue('naturalBoobs', 2)"
>yes</span>
</span>
</div>
</template>
</Tooltip>
</li>
</ul>
</nav>
<div class="tiles">
<Actor
v-for="actor in actors"
:key="`actor-${actor.id}`"
:actor="actor"
/>
</div>
<Pagination
v-if="totalCount > 0"
:items-total="totalCount"
:items-per-page="limit"
class="pagination-top"
/>
<Footer />
</div>
</template>
<script>
import Actor from './tile.vue';
import Gender from './gender.vue';
import Checkbox from '../form/checkbox.vue';
import Range from '../form/range.vue';
import Pagination from '../pagination/pagination.vue';
const toggleValues = [true, undefined, false];
const boobSizes = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
function updateFilters() {
this.$router.push({
name: 'actors',
params: this.$route.params,
query: {
naturalBoobs: toggleValues[this.naturalBoobs],
bs: this.boobSizeRequired ? this.boobSize.join(',') : undefined,
},
});
}
function updateValue(prop, value) {
this[prop] = value;
this.updateFilters();
}
async function fetchActors(scroll) {
const curatedGender = this.gender.replace('trans', 'transsexual');
const { actors, totalCount } = await this.$store.dispatch('fetchActors', {
limit: this.limit,
pageNumber: Number(this.$route.params.pageNumber) || 1,
letter: this.letter.replace('all', ''),
gender: curatedGender === 'other' ? null : curatedGender,
boobSize: this.boobSizeRequired && this.boobSizes.slice(this.boobSize[0], this.boobSize[1]),
naturalBoobs: toggleValues[this.naturalBoobs] ?? null,
});
this.actors = actors;
this.totalCount = totalCount;
if (scroll) {
this.$refs.pagination.$el.scrollIntoView();
}
}
function letter() {
return this.$route.params.letter || 'all';
}
function gender() {
return this.$route.params.gender || 'all';
}
async function route(to, from) {
const scroll = to.params.pageNumber !== from.params.pageNumber
|| to.params.gender !== from.params.gender
|| to.params.letter !== from.params.letter;
await this.fetchActors(scroll);
}
async function mounted() {
this.pageTitle = 'Actors';
await this.fetchActors();
}
export default {
components: {
Actor,
Checkbox,
Gender,
Range,
Pagination,
},
data() {
const naturalBoobs = ['true', undefined, 'false'].indexOf(this.$route.query.nb);
return {
actors: [],
pageTitle: null,
totalCount: 0,
limit: 50,
letters: ['all'].concat(Array.from({ length: 26 }, (value, index) => String.fromCharCode(index + 97).toUpperCase())),
boobSizes,
boobSize: this.$route.query.bs?.split(',') || [0, boobSizes.length - 1],
boobSizeRequired: this.$route.query.bs || false,
naturalBoobs: naturalBoobs > -1 ? naturalBoobs : 1,
};
},
computed: {
letter,
gender,
},
watch: {
$route: route,
},
mounted,
methods: {
fetchActors,
updateFilters,
updateValue,
},
};
</script>
<style lang="scss">
.gender-link {
&.selected .gender .icon {
fill: var(--text-light);
filter: none;
}
&:hover:not(.selected) {
.gender .icon {
fill: var(--text-light);
}
.male .icon {
filter: drop-shadow(0 0 1px var(--male));
}
.female .icon {
filter: drop-shadow(0 0 1px var(--female));
}
}
&:hover:not(.selected) .transsexual .icon {
fill: var(--female);
filter: drop-shadow(1px 0 0 var(--text-light)) drop-shadow(-1px 0 0 var(--text-light)) drop-shadow(0 1px 0 var(--text-light)) drop-shadow(0 -1px 0 var(--text-light)) drop-shadow(1px 0 0 var(--male)) drop-shadow(-1px 0 0 var(--male)) drop-shadow(0 1px 0 var(--male)) drop-shadow(0 -1px 0 var(--male)) drop-shadow(0 0 1px rgba(0, 0, 0, 0.5));
}
}
</style>
<style lang="scss" scoped>
@import 'breakpoints';
.actors {
display: flex;
flex-direction: column;
}
.tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
grid-gap: .5rem;
padding: 1rem;
flex-grow: 1;
}
.filter {
display: flex;
justify-content: center;
align-items: center;
padding: 0 1rem;
margin: 0 0 1rem 0;
&:first-child {
margin: 1rem 0;
}
}
.genders {
display: flex;
flex-shrink: 0;
padding: 0 .5rem 0 0;
border-right: solid 1px var(--shadow-hint);
margin: 0 1rem 0 0;
}
.letter,
.gender {
display: inline-block;
}
.letter-link,
.gender-link {
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
margin: .25rem .5rem .25rem 0;
color: var(--shadow);
background: var(--background);
font-weight: bold;
text-decoration: none;
box-shadow: 0 0 3px var(--darken-weak);
.male,
.female,
.transsexual {
padding: .2rem 0 0 0;
}
.icon {
fill: var(--shadow);
}
&:hover {
color: var(--text);
cursor: pointer;
.icon {
fill: var(--text);
}
}
&.selected {
background: var(--primary);
color: var(--text-light);
&.other .icon {
fill: var(--text-light);
}
}
}
.filter-trigger {
display: inline-flex;
align-items: center;
color: var(--shadow);
font-weight: bold;
.icon {
fill: var(--shadow);
width: 1.5rem;
height: 1.5rem;
margin: 0 .5rem 0 0;
}
&:hover {
color: var(--shadow-strong);
cursor: pointer;
.icon {
fill: var(--shadow-strong);
}
}
}
.filter-label {
display: flex;
justify-content: center;
padding: 0 .5rem;
margin: .5rem 0 0 0;
color: var(--darken);
font-weight: bold;
font-size: .9rem;
.icon {
margin: 0 .5rem 0 0;
}
}
.filter-split {
display: flex;
align-items: center;
.checkbox {
display: inline-block;
padding: 0 0 0 .5rem;
}
}
.toggle-container,
.range-container {
display: flex;
flex-grow: 1;
align-items: center;
padding: .5rem 0;
&.on {
.toggle-label.on {
color: var(--enabled);
}
.toggle {
background-color: var(--enabled-background);
&::-webkit-slider-thumb {
background: var(--enabled);
}
&::-moz-range-thumb {
background: var(--enabled);
}
}
}
&.off {
.toggle-label.off {
color: var(--disabled);
}
.toggle {
background-color: var(--disabled-background);
&::-webkit-slider-thumb {
background: var(--disabled);
}
&::-moz-range-thumb {
background: var(--disabled);
}
}
}
}
.toggle-label {
width: 1.5rem;
padding: 0 .5rem;
color: var(--darken);
font-weight: bold;
font-size: .9rem;
&.on {
text-align: right;
}
&:hover {
cursor: pointer;
&.on {
color: var(--enabled);
}
&.off {
color: var(--disabled);
}
}
}
.toggle {
flex-grow: 1;
height: 1.25rem;
appearance: none;
border-radius: 1rem;
background-color: var(--darken-hint);
background-image: radial-gradient(circle, var(--shadow-weak) .3rem, transparent calc(.3rem + 1px));
cursor: pointer;
&::-webkit-slider-thumb {
appearance: none;
background: var(--disabled-handle);
width: 1.25rem;
height: 1.25rem;
border-radius: .625rem;
box-shadow: 0 0 3px var(--darken-weak);
}
&::-moz-range-thumb {
appearance: none;
background: var(--disabled-handle);
width: 1.25rem;
height: 1.25rem;
border: none;
border-radius: .625rem;
box-shadow: 0 0 3px var(--darken-weak);
}
}
@media(max-width: $breakpoint) {
.genders {
flex-direction: column;
}
}
@media(max-width: $breakpoint-micro) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
}
</style>