traxxx-web/components/actors/actors.vue

306 lines
7.5 KiB
Vue

<template>
<div class="page">
<Filters :results="total">
<div class="filter search-container">
<!-- onsearch not compatible with FF and Safari -->
<input
v-model="q"
type="search"
placeholder="Search actors"
class="input search"
@keydown.enter="search"
>
<Icon
icon="search"
class="search-button"
@click="search"
/>
</div>
<GenderFilter
:filters="filters"
@update="updateFilter"
/>
<BirthdateFilter
:filters="filters"
@update="updateFilter"
/>
<BoobsFilter
:filters="filters"
:cup-range="cupRange"
@update="updateFilter"
/>
<PhysiqueFilter
:filters="filters"
@update="updateFilter"
/>
<CountryFilter
:filters="filters"
:countries="countries"
@update="updateFilter"
/>
<div class="filter">
<Checkbox
:checked="filters.avatarRequired"
label="Require photo"
@change="(checked) => updateFilter('avatarRequired', checked, true)"
/>
</div>
</Filters>
<div class="actors-container">
<div class="actors-header">
<div class="meta">
<span class="count">{{ total }} results</span>
<select
v-model="order"
class="input"
@change="search({ autoScope: false })"
>
<option
v-if="pageStash"
:selected="order === 'stashed.desc'"
value="stashed.desc"
>Added</option>
<option
v-if="q"
:selected="order === 'results.desc'"
value="results.desc"
>Relevance</option>
<option value="name.asc">Name</option>
<option value="likes.desc">Popular</option>
<option value="scenes.desc">Scenes</option>
</select>
</div>
</div>
<ul class="actors nolist">
<li
v-for="actor in actors"
:key="`actor-${actor.id}`"
>
<ActorTile
:actor="actor"
/>
</li>
</ul>
<Pagination
:page="currentPage"
:total="total"
:redirect="false"
@navigation="paginate"
/>
</div>
</div>
</template>
<script setup>
import { ref, inject } from 'vue';
import { format, subYears } from 'date-fns';
import { parse } from 'path-to-regexp';
import navigate from '#/src/navigate.js';
import { get } from '#/src/api.js';
import events from '#/src/events.js';
import ActorTile from '#/components/actors/tile.vue';
import Pagination from '#/components/pagination/pagination.vue';
import Checkbox from '#/components/form/checkbox.vue';
import Filters from '#/components/filters/filters.vue';
import GenderFilter from '#/components/filters/gender.vue';
import BirthdateFilter from '#/components/filters/birthdate.vue';
import BoobsFilter from '#/components/filters/boobs.vue';
import PhysiqueFilter from '#/components/filters/physique.vue';
import CountryFilter from '#/components/filters/country.vue';
const pageContext = inject('pageContext');
const { pageProps, urlParsed, routeParams } = pageContext;
const q = ref(urlParsed.search.q);
const actors = ref([]);
const pageStash = pageProps.stash;
const countries = ref(pageProps.countries);
const cupRange = ref(pageProps.cupRange);
actors.value = pageProps.actors;
const currentPage = ref(Number(routeParams.page));
const total = ref(Number(pageProps.total));
const order = ref(routeParams.order || urlParsed.search.order || 'likes.desc');
const filters = ref({
gender: urlParsed.search.gender,
ageRequired: !!urlParsed.search.age,
age: urlParsed.search.age?.split(',').map((age) => Number(age)) || [18, 100],
dobRequired: !!urlParsed.search.dob,
dobType: urlParsed.search.dobt ? ({ bd: 'birthday', dob: 'dob' })[urlParsed.search.dobt] : 'birthday',
dob: urlParsed.search.dob || format(subYears(new Date(), 21), 'yyyy-MM-dd'),
country: urlParsed.search.c,
braSizeRequired: !!urlParsed.search.cup,
braSize: urlParsed.search.cup?.split(',') || ['A', 'Z'],
naturalBoobs: urlParsed.search.nb ? urlParsed.search.nb === 'true' : undefined,
heightRequired: !!urlParsed.search.height,
height: urlParsed.search.height?.split(',').map((height) => Number(height)) || [50, 220],
weightRequired: !!urlParsed.search.weight,
weight: urlParsed.search.weight?.split(',').map((weight) => Number(weight)) || [30, 200],
avatarRequired: !!urlParsed.search.avatar,
});
function getPath(preserveQuery) {
const path = parse(routeParams.path).map((segment) => {
if (segment.name === 'page') {
return `${segment.prefix}${1}`;
}
return `${segment.prefix || ''}${routeParams[segment.name] || segment}`;
}).join('');
if (preserveQuery && urlParsed.searchOriginal) {
return `${path}${urlParsed.searchOriginal}`;
}
return path;
}
async function search(options = {}) {
if (options.resetPage !== false) {
currentPage.value = 1;
}
if (options.autoScope !== false) {
if (q.value) {
order.value = 'results.desc';
}
if (!q.value && order.value.includes('results')) {
order.value = 'likes.desc';
}
}
const query = {
q: q.value || undefined,
order: order.value,
gender: filters.value.gender || undefined,
age: filters.value.ageRequired ? filters.value.age.join(',') : undefined,
dob: filters.value.dobRequired ? filters.value.dob : undefined,
dobt: filters.value.dobRequired ? ({ birthday: 'bd', dob: 'dob' })[filters.value.dobType] : undefined,
cup: filters.value.braSizeRequired ? filters.value.braSize.join(',') : undefined,
c: filters.value.country || undefined,
nb: filters.value.naturalBoobs,
height: filters.value.heightRequired ? filters.value.height.join(',') : undefined,
weight: filters.value.weightRequired ? filters.value.weight.join(',') : undefined,
avatar: filters.value.avatarRequired || undefined,
stashId: pageStash?.id,
};
navigate(getPath(false), query, { redirect: false });
const res = await get('/actors', {
...query,
page: currentPage.value, // client uses param rather than query pagination
});
actors.value = res.actors;
total.value = res.total;
countries.value = res.countries;
events.emit('scrollUp');
}
function paginate({ page }) {
currentPage.value = page;
search({ resetPage: false });
}
function updateFilter(prop, value, reload = true) {
filters.value[prop] = value;
if (reload) {
search();
}
}
</script>
<style>
.gender-button {
&.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 scoped>
.page {
min-height: 100%;
display: flex;
align-items: stretch;
}
.actors-container {
display: flex;
flex-grow: 1;
flex-direction: column;
box-sizing: border-box;
padding: 0 1rem 1rem 1rem;
}
.actors-header {
display: flex;
align-items: center;
padding: .5rem 0 .25rem 2.25rem;
margin-bottom: .25rem;
}
.meta {
display: flex;
flex-grow: 1;
justify-content: space-between;
align-items: center;
}
.actors {
display: grid;
flex-grow: 1;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
gap: .25rem;
}
@media(--small-40) {
.actors {
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
}
}
</style>