307 lines
7.6 KiB
Vue
307 lines
7.6 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.actorTotal || 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;
|
|
background: var(--background-base-10);
|
|
}
|
|
|
|
.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>
|