Added boob and country aggregation to actors page.

This commit is contained in:
2023-12-31 03:02:03 +01:00
parent b5a730764c
commit 6408365933
276 changed files with 37792 additions and 166 deletions

View File

@@ -14,6 +14,48 @@
>
</div>
<ul class="filter genders nolist">
<li>
<button
:class="{ selected: filters.gender === undefined }"
class="gender-button all"
@click="updateFilter('gender', undefined, true)"
>all</button>
</li>
<li>
<button
:class="{ selected: filters.gender === 'female' }"
class="gender-button female"
@click="updateFilter('gender', 'female', true)"
><Gender gender="female" /></button>
</li>
<li>
<button
:class="{ selected: filters.gender === 'male' }"
class="gender-button male"
@click="updateFilter('gender', 'male', true)"
><Gender gender="male" /></button>
</li>
<li>
<button
:class="{ selected: filters.gender === 'transsexual' }"
class="gender-button transsexual"
@click="updateFilter('gender', 'transsexual', true)"
><Gender gender="transsexual" /></button>
</li>
<li>
<button
:class="{ selected: filters.gender === 'other' }"
class="gender-button other"
@click="updateFilter('gender', 'other', true)"
><Icon icon="question5" /></button>
</li>
</ul>
<div class="filter">
<RangeFilter
label="age"
@@ -30,6 +72,49 @@
</RangeFilter>
</div>
<div class="filter">
<RangeFilter
label="bra size"
:min="0"
:max="braSizes.length - 1"
:value="filters.braSize"
:values="braSizes"
:disabled="!filters.braSizeRequired"
@enable="(checked) => updateFilter('braSizeRequired', checked, true)"
@input="(range) => updateFilter('braSize', range, false)"
@change="(range) => updateFilter('braSize', range, true)"
>
<template #start><Icon icon="boobs-small" /></template>
<template #end><Icon icon="boobs-big" /></template>
</RangeFilter>
<span class="filter-label">Enhanced Boobs</span>
<span
:class="{ [['off', 'default', 'on'][naturalBoobsValues.indexOf(filters.naturalBoobs)]]: true }"
class="toggle-container noclick"
>
<span
class="toggle-label off"
@click="updateFilter('naturalBoobs', true, true)"
><Icon icon="leaf" /></span>
<input
:value="naturalBoobsValues.indexOf(filters.naturalBoobs)"
class="toggle"
type="range"
min="0"
max="2"
@change="updateFilter('naturalBoobs', naturalBoobsValues[$event.target.value], true)"
>
<span
class="toggle-label on"
@click="updateFilter('naturalBoobs', false, true)"
><Icon icon="magic-wand2" /></span>
</span>
</div>
<div class="filter">
<RangeFilter
label="height"
@@ -45,9 +130,7 @@
<template #start><Icon icon="height-short" /></template>
<template #end><Icon icon="height" /></template>
</RangeFilter>
</div>
<div class="filter">
<RangeFilter
label="weight"
:min="30"
@@ -64,11 +147,50 @@
</RangeFilter>
</div>
<Tooltip class="filter">
<span
:class="{ enabled: filters.country }"
class="filter-trigger"
>
<img
v-if="filters.country"
:src="`/img/flags/${filters.country.toLowerCase()}.svg`"
class="flag"
>
<Icon
v-else
icon="earth2"
/>Country
</span>
<template #tooltip>
<input
v-model="countryQuery"
placeholder="Search"
class="input"
>
<Countries
v-if="!countryQuery"
:countries="topCountries"
:selected-country="country"
:update-value="updateFilter"
/>
<Countries
:countries="filteredCountries"
:selected-country="country"
:update-value="updateFilter"
/>
</template>
</Tooltip>
<div class="filter">
<Checkbox
:checked="avatarRequired"
:checked="filters.avatarRequired"
label="Require photo"
@change="(checked) => { avatarRequired = checked; search(); }"
@change="(checked) => updateFilter('avatarRequired', checked, true)"
/>
</div>
</form>
@@ -87,41 +209,61 @@
</template>
<script setup>
import { ref, inject } from 'vue';
import { ref, computed, inject } from 'vue';
import navigate from '#/src/navigate.js';
import { get } from '#/src/api.js';
import ActorTile from '#/components/actors/tile.vue';
import Gender from '#/components/actors/gender.vue';
import Checkbox from '#/components/form/checkbox.vue';
import RangeFilter from '#/components/filters/range.vue';
const actors = ref([]);
import Tooltip from '#/components/tooltip/tooltip.vue';
import Countries from '#/components/filters/countries.vue';
const { pageProps, urlParsed } = inject('pageContext');
const q = ref(urlParsed.search.q);
const actors = ref([]);
const countries = ref(pageProps.countries);
const countryQuery = ref('');
const topCountryAlpha2s = ['AU', 'BR', 'CZ', 'DE', 'JP', 'RU', 'GB', 'US'];
const topCountries = computed(() => countries.value.filter((country) => topCountryAlpha2s.includes(country.alpha2)));
const filteredCountries = computed(() => countries.value.filter((country) => new RegExp(countryQuery.value, 'i').test(country.name)));
actors.value = pageProps.actors;
const q = ref(urlParsed.search.q);
console.log(countries.value);
const braSizes = 'ABCDEFGHIJKZ'.split('');
const naturalBoobsValues = [true, undefined, false];
const filters = ref({
gender: urlParsed.search.gender,
ageRequired: !!urlParsed.search.age,
age: urlParsed.search.age?.split(',').map((age) => Number(age)) || [18, 100],
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,
});
const avatarRequired = ref(Object.hasOwn(urlParsed.search, 'avatar'));
async function search() {
const query = {
q: q.value || undefined,
avatar: avatarRequired.value || undefined,
gender: filters.value.gender || undefined,
age: filters.value.ageRequired ? filters.value.age.join(',') : undefined,
cup: filters.value.braSizeRequired ? filters.value.braSize.join(',') : 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,
};
navigate('/actors', query, { redirect: false });
@@ -129,6 +271,7 @@ async function search() {
const res = await get('/actors', query);
actors.value = res.actors;
countries.value = res.countries;
}
function updateFilter(prop, value, reload = true) {
@@ -140,6 +283,34 @@ function updateFilter(prop, value, reload = true) {
}
</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 {
height: 100%;
@@ -148,6 +319,7 @@ function updateFilter(prop, value, reload = true) {
}
.filters {
width: 17rem;
flex-shrink: 0;
padding: .5rem 0;
border-right: solid 1px var(--shadow-weak-30);
@@ -160,6 +332,7 @@ function updateFilter(prop, value, reload = true) {
.filter {
padding: .5rem;
border-bottom: solid 1px var(--shadow-weak-30);
}
.actors {
@@ -170,4 +343,58 @@ function updateFilter(prop, value, reload = true) {
padding: 1rem;
overflow-y: auto;
}
.genders {
display: flex;
justify-content: center;
gap: .5rem;
}
.gender-button {
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
border: none;
color: var(--shadow);
background: var(--background);
font-weight: bold;
text-decoration: none;
font-size: .9rem;
box-shadow: 0 0 3px var(--shadow-weak-20);
.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);
}
}
}
.countries {
overflow: hidden;
}
</style>

View File

@@ -1,15 +1,15 @@
import { fetchActors } from '#/src/actors.js';
import { curateActorsQuery } from '#/src/web/actors.js';
export async function onBeforeRender(pageContext) {
const query = pageContext.urlParsed.search;
console.log(pageContext);
const { actors, limit, total } = await fetchActors({
query: query.q,
requireAvatar: Object.hasOwn(query, 'avatar'),
age: query.age?.split(',').map((age) => Number(age)),
height: query.height?.split(',').map((height) => Number(height)),
weight: query.weight?.split(',').map((weight) => Number(weight)),
}, {
const {
actors,
countries,
limit,
total,
} = await fetchActors(curateActorsQuery(pageContext.urlQuery), {
page: Number(pageContext.routeParams.page) || 1,
limit: Number(pageContext.urlParsed.search.limit) || 50,
});
@@ -19,6 +19,7 @@ export async function onBeforeRender(pageContext) {
title: 'actors',
pageProps: {
actors,
countries,
limit,
total,
},