Improved scene actors filter.
This commit is contained in:
257
components/filters/actors.vue
Normal file
257
components/filters/actors.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="actors-container">
|
||||
<div class="actors-filters">
|
||||
<input
|
||||
v-model="search"
|
||||
type="search"
|
||||
placeholder="Filter actors"
|
||||
class="input input-inline actors-search"
|
||||
>
|
||||
|
||||
<div
|
||||
class="filter-sort noselect"
|
||||
@click="selectGender"
|
||||
>
|
||||
<div
|
||||
v-if="!selectedGender"
|
||||
class="gender-unselected"
|
||||
><Icon icon="genders" /></div>
|
||||
|
||||
<Gender
|
||||
v-else
|
||||
:gender="selectedGender"
|
||||
class="gender"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-show="order === 'name'"
|
||||
class="filter-sort order noselect"
|
||||
@click="order = 'count'"
|
||||
>
|
||||
<Icon
|
||||
icon="sort-alpha-asc"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-show="order === 'count'"
|
||||
class="filter-sort order noselect"
|
||||
@click="order = 'name'"
|
||||
>
|
||||
<Icon
|
||||
icon="sort-numeric-desc"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
v-for="(group, groupKey) in groupedActors"
|
||||
:key="groupKey"
|
||||
class="filter-items nolist"
|
||||
>
|
||||
<li
|
||||
v-for="(actor, index) in group"
|
||||
:key="`filter-actor-${actor.id}`"
|
||||
class="filter-item"
|
||||
:class="{ selected: filters.actors.includes(actor.id), first: groupKey === 'available' && index === 0 && filters.actors.length > 0 }"
|
||||
@click="emit('update', 'actors', [actor.id])"
|
||||
>
|
||||
<div
|
||||
class="filter-include"
|
||||
@click.stop="toggleActor(actor)"
|
||||
>
|
||||
<Icon
|
||||
icon="checkmark"
|
||||
class="filter-add"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
icon="cross2"
|
||||
class="filter-remove"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span class="filter-name actor-name">{{ actor.name }}</span>
|
||||
|
||||
<span class="actor-details">
|
||||
<div class="actor-gender">
|
||||
<Gender
|
||||
:gender="actor.gender"
|
||||
class="gender"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="actor.count"
|
||||
class="actor-count"
|
||||
>{{ actor.count }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, inject } from 'vue';
|
||||
|
||||
import Gender from '#/components/actors/gender.vue';
|
||||
|
||||
const props = defineProps({
|
||||
filters: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
actors: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update']);
|
||||
|
||||
const search = ref('');
|
||||
const searchRegexp = computed(() => new RegExp(search.value, 'i'));
|
||||
const selectedGender = ref(null);
|
||||
const order = ref('name');
|
||||
|
||||
const { pageProps } = inject('pageContext');
|
||||
const { actor: pageActor } = pageProps;
|
||||
|
||||
const groupedActors = computed(() => ({
|
||||
selected: props.actors.filter((actor) => props.filters.actors.includes(actor.id)),
|
||||
available: props.actors
|
||||
.filter((actor) => !props.filters.actors.includes(actor.id)
|
||||
&& actor.id !== pageActor?.id
|
||||
&& searchRegexp.value.test(actor.name)
|
||||
&& (!selectedGender.value || actor.gender === selectedGender.value))
|
||||
.sort((actorA, actorB) => {
|
||||
if (order.value === 'count') {
|
||||
return actorB.count - actorA.count;
|
||||
}
|
||||
|
||||
return actorA.name.localeCompare(actorB.name);
|
||||
}),
|
||||
}));
|
||||
|
||||
const genders = computed(() => [null, ...['female', 'male', 'transsexual', 'other'].filter((gender) => props.actors.some((actor) => actor.gender === gender))]);
|
||||
|
||||
function toggleActor(actor) {
|
||||
if (props.filters.actors.includes(actor.id)) {
|
||||
emit('update', 'actors', props.filters.actors.filter((actorId) => actorId !== actor.id));
|
||||
return;
|
||||
}
|
||||
|
||||
emit('update', 'actors', props.filters.actors.concat(actor.id));
|
||||
}
|
||||
|
||||
function selectGender() {
|
||||
console.log(genders.value);
|
||||
const genderIndex = genders.value.indexOf(selectedGender.value);
|
||||
|
||||
if (genderIndex >= genders.value.length - 1) {
|
||||
selectedGender.value = genders.value[0];
|
||||
return;
|
||||
}
|
||||
|
||||
selectedGender.value = genders.value[genderIndex + 1];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.actors-container {
|
||||
box-shadow: inset 0 -1px 3px var(--shadow-weak-30);
|
||||
}
|
||||
|
||||
.actors-filters {
|
||||
display: flex;
|
||||
border-bottom: solid 1px var(--shadow-weak-30);
|
||||
}
|
||||
|
||||
.actors-search {
|
||||
flex-grow: 1;
|
||||
border-bottom: none;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.filter-sort {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
justify-content: center;
|
||||
padding: 0 .5rem;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
color: var(--shadow);
|
||||
|
||||
&.order {
|
||||
padding: 0 1rem 0 .25rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
|
||||
.icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gender-unselected {
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.filter-items {
|
||||
max-height: 15rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.filter-item.first {
|
||||
border-top: solid 1px var(--shadow-weak-30);
|
||||
}
|
||||
|
||||
.filter-name {
|
||||
height: 1rem;
|
||||
align-items: stretch;
|
||||
padding: .25rem .75rem .25rem .25rem;
|
||||
}
|
||||
|
||||
.actor-name {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.actor-details {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.actor-gender {
|
||||
width: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.gender {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.actor-count {
|
||||
width: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 .25rem;
|
||||
overflow: hidden;
|
||||
color: var(--shadow-weak-10);
|
||||
font-size: .9rem;
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@
|
||||
v-if="!filters.country"
|
||||
v-model="countryQuery"
|
||||
type="search"
|
||||
placeholder="Filter country"
|
||||
placeholder="Filter countries"
|
||||
class="input input-inline countries-search"
|
||||
>
|
||||
|
||||
|
||||
@@ -67,6 +67,120 @@ function toggleFilters(state) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.filter-mode {
|
||||
width: 100%;
|
||||
color: var(--shadow);
|
||||
background: none;
|
||||
padding: .75rem;
|
||||
margin: 0 0 .5rem 0;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
|
||||
option {
|
||||
color: var(--text-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.filter-clear {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 1rem;
|
||||
color: var(--shadow-weak-10);
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow-weak-30);
|
||||
margin: 0 0 0 1rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--shadow);
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow-weak-10);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--text);
|
||||
background: var(--shadow-hint);
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--alert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-items .filter-item {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
&:hover {
|
||||
background: var(--shadow-weak-30);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
.filter-include {
|
||||
.filter-add {
|
||||
fill: var(--success);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.filter-add {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filter-remove {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-name {
|
||||
min-width: 8rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
padding: .25rem .75rem .25rem 1rem;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.filter-include {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
width: 1rem;
|
||||
padding: 0 .75rem;
|
||||
fill: var(--shadow-weak-30);
|
||||
}
|
||||
|
||||
.filter-remove {
|
||||
display: none;
|
||||
fill: var(--alert);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-include:hover,
|
||||
.filter-name:hover {
|
||||
background: var(--shadow-weak-30);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.filters-frame {
|
||||
position: relative;
|
||||
@@ -88,7 +202,7 @@ function toggleFilters(state) {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: .5rem 0;
|
||||
padding: 0 0 .5rem 0;
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 3px var(--shadow-weak-30);
|
||||
|
||||
|
||||
@@ -1,35 +1,18 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<Filters v-if="showFilters">
|
||||
<ul class="filter-actors selected nolist">
|
||||
<li
|
||||
v-for="aggActor in selectedActors"
|
||||
:key="`filter-actor-${aggActor.id}`"
|
||||
class="filter-actor"
|
||||
@click="updateFilter('actors', [...filters.actors, aggActor.id])"
|
||||
>
|
||||
{{ aggActor.name }}
|
||||
|
||||
<button
|
||||
v-if="filters.actors.includes(aggActor.id)"
|
||||
class="filter-actor-remove"
|
||||
@click.stop="updateFilter('actors', filters.actors.filter((actorId) => actorId !== aggActor.id))"
|
||||
><Icon icon="cross2" /></button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="filter-actors available nolist">
|
||||
<li
|
||||
v-for="aggActor in actors"
|
||||
:key="`filter-actor-${aggActor.id}`"
|
||||
class="filter-actor"
|
||||
@click="updateFilter('actors', [...filters.actors, aggActor.id])"
|
||||
>{{ aggActor.name }}</li>
|
||||
</ul>
|
||||
<ActorsFilter
|
||||
:filters="filters"
|
||||
:actors="actors"
|
||||
@update="updateFilter"
|
||||
/>
|
||||
</Filters>
|
||||
|
||||
<div class="scenes-container">
|
||||
<div class="scenes-header">
|
||||
<div
|
||||
v-if="showMeta"
|
||||
class="scenes-header"
|
||||
>
|
||||
<div class="meta">{{ total }} results</div>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +51,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, inject } from 'vue';
|
||||
import { ref, inject } from 'vue';
|
||||
import { parse } from 'path-to-regexp';
|
||||
|
||||
import navigate from '#/src/navigate.js';
|
||||
@@ -76,6 +59,7 @@ import { get } from '#/src/api.js';
|
||||
import events from '#/src/events.js';
|
||||
|
||||
import Filters from '#/components/filters/filters.vue';
|
||||
import ActorsFilter from '#/components/filters/actors.vue';
|
||||
import Scene from './tile.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
|
||||
@@ -84,13 +68,15 @@ defineProps({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showMeta: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { pageProps, routeParams, urlParsed } = inject('pageContext');
|
||||
const { scope } = routeParams;
|
||||
|
||||
console.log(scope);
|
||||
|
||||
const {
|
||||
actor,
|
||||
} = pageProps;
|
||||
@@ -105,24 +91,6 @@ const filters = ref({
|
||||
actors: urlParsed.search.actors?.split(',').filter(Boolean).map((actorId) => Number(actorId)) || [],
|
||||
});
|
||||
|
||||
/*
|
||||
const sortedActors = computed(() => Array.from(actors.value).sort((actorA, actorB) => {
|
||||
if (filters.value.actors.includes(actorA.id) && !filters.value.actors.includes(actorB.id)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (filters.value.actors.includes(actorB.id) && !filters.value.actors.includes(actorA.id)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}));
|
||||
*/
|
||||
|
||||
const selectedActors = computed(() => actors.value.filter((filterActor) => filters.value.actors.includes(filterActor.id)));
|
||||
|
||||
console.log(filters.value.actors);
|
||||
|
||||
function getPath(targetScope, preserveQuery) {
|
||||
const path = parse(routeParams.path)
|
||||
.map((segment) => {
|
||||
@@ -152,8 +120,6 @@ async function search(resetPage = true) {
|
||||
|
||||
const query = {};
|
||||
|
||||
console.log(query);
|
||||
|
||||
const res = await get('/scenes', {
|
||||
...query,
|
||||
actors: [actor?.id, filters.value.actors].filter(Boolean).join(','), // if we're on an actor page, actor ID needs to be included
|
||||
@@ -199,6 +165,7 @@ function updateFilter(prop, value, reload = true) {
|
||||
.scenes-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.meta {
|
||||
@@ -231,39 +198,4 @@ function updateFilter(prop, value, reload = true) {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-actors {
|
||||
max-height: 15rem;
|
||||
|
||||
&.available {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-actor {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-actor-remove {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--error);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user