Changed scene year filter to range input.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import Range from '#/components/form/range.vue';
|
||||
|
||||
const props = defineProps({
|
||||
filters: {
|
||||
@@ -13,36 +15,86 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update']);
|
||||
const selected = ref(props.filters.years);
|
||||
|
||||
function updateYears() {
|
||||
emit('update', 'years', selected.value);
|
||||
// values while dragging, before the change is committed and the scenes reload
|
||||
const dragging = ref(null);
|
||||
|
||||
const slider = computed(() => {
|
||||
const aggYears = (props.years || []).map((year) => year.year);
|
||||
const currentYear = new Date().getFullYear();
|
||||
const min = aggYears.length > 0 ? Math.min(...aggYears) : currentYear - 1;
|
||||
const max = Math.max(aggYears.length > 0 ? Math.max(...aggYears) : currentYear, min + 1); // keep at least one step
|
||||
const range = props.filters.yearRange;
|
||||
const list = props.filters.years || [];
|
||||
|
||||
// a year list from an older link is shown as the span it covers
|
||||
const value = list.length > 0
|
||||
? [Math.min(...list), Math.max(...list)]
|
||||
: [range?.min ?? min, range?.max ?? max];
|
||||
|
||||
return {
|
||||
min,
|
||||
max,
|
||||
value,
|
||||
positions: [flipYear(value[1], min, max), flipYear(value[0], min, max)],
|
||||
display: dragging.value || value,
|
||||
isFiltered: !!range || list.length > 0,
|
||||
mountKey: `${min}-${max}-${value.join('-')}`, // remount when the bounds or filter change, the slider only reads its value on mount
|
||||
};
|
||||
});
|
||||
|
||||
// the slider runs from newest on the left to oldest on the right, by mirroring the years onto the ascending slider positions
|
||||
// the same function converts both ways: years to positions and positions to years
|
||||
function flipYear(yearOrPosition, min, max) {
|
||||
return min + max - yearOrPosition;
|
||||
}
|
||||
|
||||
function clearYears() {
|
||||
selected.value = [];
|
||||
emit('update', 'years', selected.value);
|
||||
emit('update', 'years', [], false);
|
||||
emit('update', 'yearRange', null, true);
|
||||
}
|
||||
|
||||
function updateRange([positionA, positionB], reload) {
|
||||
const min = flipYear(positionB, slider.value.min, slider.value.max);
|
||||
const max = flipYear(positionA, slider.value.min, slider.value.max);
|
||||
|
||||
if (!reload) {
|
||||
dragging.value = [min, max];
|
||||
return;
|
||||
}
|
||||
|
||||
dragging.value = null;
|
||||
|
||||
// ends at the slider limits are left open, so 'and later' keeps including new years, the full range means no filter
|
||||
const range = {
|
||||
min: min === slider.value.min ? null : min,
|
||||
max: max === slider.value.max ? null : max,
|
||||
};
|
||||
|
||||
emit('update', 'years', [], false); // replaces a year list from an older link
|
||||
emit('update', 'yearRange', range.min === null && range.max === null ? null : range, true);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="years-container">
|
||||
<select
|
||||
v-model="selected"
|
||||
class="years nobar"
|
||||
multiple
|
||||
@change="updateYears"
|
||||
>
|
||||
<option
|
||||
v-for="year in years"
|
||||
:key="`year-${year.year}`"
|
||||
class="year"
|
||||
:value="year.year"
|
||||
>{{ year.year }}</option>
|
||||
</select>
|
||||
<div class="years-filter">
|
||||
<div class="years-range">
|
||||
<Range
|
||||
:key="slider.mountKey"
|
||||
:min="slider.min"
|
||||
:max="slider.max"
|
||||
:value="slider.positions"
|
||||
:allow-enable="false"
|
||||
@input="(positions) => updateRange(positions, false)"
|
||||
@change="(positions) => updateRange(positions, true)"
|
||||
>
|
||||
<template #start><span class="range-label">{{ slider.display[1] }}</span></template>
|
||||
<template #end><span class="range-label">{{ slider.display[0] }}</span></template>
|
||||
</Range>
|
||||
</div>
|
||||
|
||||
<Icon
|
||||
v-show="selected.length > 0"
|
||||
v-show="slider.isFiltered"
|
||||
icon="cross2"
|
||||
class="clear"
|
||||
@click="clearYears"
|
||||
@@ -51,54 +103,22 @@ function clearYears() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.years-container {
|
||||
.years-filter {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
border-bottom: solid 1px var(--glass-weak-30);
|
||||
}
|
||||
|
||||
.years {
|
||||
height: 2.5rem;
|
||||
padding: .5rem;
|
||||
.years-range {
|
||||
padding: .25rem 0;
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
background: none;
|
||||
color: var(--text);
|
||||
|
||||
&:focus .year:checked {
|
||||
background: var(--primary) linear-gradient(0deg, var(--primary) 0%, var(--primary) 100%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.year {
|
||||
display: inline-block;
|
||||
padding: .25rem;
|
||||
color: var(--text);
|
||||
font-size: .8rem;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:checked {
|
||||
color: var(--primary);
|
||||
background: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.clear {
|
||||
height: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 .5rem;
|
||||
padding: 0 .5rem 0 0;
|
||||
fill: var(--glass);
|
||||
|
||||
&:hover {
|
||||
@@ -106,4 +126,13 @@ function clearYears() {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.range-label {
|
||||
display: inline-block;
|
||||
min-width: 2.5rem;
|
||||
color: var(--glass-strong-10);
|
||||
font-size: .8rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { get } from '#/src/api.js';
|
||||
import events from '#/src/events.js';
|
||||
import navigate from '#/src/navigate.js';
|
||||
import { getActorIdentifier, parseActorIdentifier } from '#/src/query.js';
|
||||
import { formatYears, parseYears } from '#/utils/parse-range.js';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const { pageProps, routeParams, urlParsed } = pageContext;
|
||||
@@ -49,7 +50,7 @@ const queryEntity = networks[urlParsed.search.e] || channels[urlParsed.search.e]
|
||||
|
||||
const filters = ref({
|
||||
search: urlParsed.search.q,
|
||||
years: urlParsed.search.years?.split(',').map((year) => Number(year)).filter(Boolean) || [],
|
||||
...parseYears(urlParsed.search.years),
|
||||
tags: urlParsed.search.tags?.split(',').filter(Boolean) || [],
|
||||
entity: queryEntity,
|
||||
actors: queryActors,
|
||||
@@ -103,7 +104,7 @@ async function search(options = {}) {
|
||||
|
||||
navigate(getPath(scope.value, false), {
|
||||
...query,
|
||||
years: filters.value.years.join(',') || undefined,
|
||||
years: formatYears(filters.value),
|
||||
actors: filters.value.actors.map((filterActor) => getActorIdentifier(filterActor)).join(',') || undefined, // don't include page actor ID in query, already a parameter
|
||||
tags: filters.value.tags.join(',') || undefined,
|
||||
e: filters.value.entity?.type === 'network' ? `_${filters.value.entity.slug}` : (filters.value.entity?.slug || undefined),
|
||||
@@ -111,7 +112,7 @@ async function search(options = {}) {
|
||||
|
||||
const res = await get('/movies', {
|
||||
...query,
|
||||
years: filters.value.years.filter(Boolean).join(','), // if we're on an actor page, that actor ID needs to be included
|
||||
years: formatYears(filters.value),
|
||||
actors: [pageActor, ...filters.value.actors].filter(Boolean).map((filterActor) => getActorIdentifier(filterActor)).join(','), // if we're on an actor page, that actor ID needs to be included
|
||||
tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','),
|
||||
e: entitySlug,
|
||||
|
||||
@@ -24,7 +24,7 @@ import { get } from '#/src/api.js';
|
||||
import entityPrefixes from '#/src/entities-prefixes.js';
|
||||
import navigate from '#/src/navigate.js';
|
||||
import { getActorIdentifier, parseActorIdentifier } from '#/src/query.js';
|
||||
import { formatRange, parseRange } from '#/utils/parse-range.js';
|
||||
import { formatRange, formatYears, parseRange, parseYears } from '#/utils/parse-range.js';
|
||||
|
||||
const props = defineProps({
|
||||
showFilters: {
|
||||
@@ -86,7 +86,7 @@ const queryEntity = networks[urlParsed.search.e] || channels[urlParsed.search.e]
|
||||
|
||||
const filters = ref({
|
||||
search: urlParsed.search.q,
|
||||
years: urlParsed.search.years?.split(',').filter(Boolean).map(Number) || [],
|
||||
...parseYears(urlParsed.search.years),
|
||||
tags: urlParsed.search.tags?.split(',').filter(Boolean) || [],
|
||||
onlyActorTags: Object.hasOwn(urlParsed.search, 'at'),
|
||||
entity: queryEntity,
|
||||
@@ -153,7 +153,7 @@ async function search(options = {}) {
|
||||
|
||||
navigate(getPath(scope.value, false), {
|
||||
...query,
|
||||
years: filters.value.years.join(',') || undefined,
|
||||
years: formatYears(filters.value),
|
||||
actors: filters.value.actors.map((filterActor) => getActorIdentifier(filterActor)).join(',') || undefined, // don't include page actor ID in query, already a parameter
|
||||
tags: filters.value.tags.join(',') || undefined,
|
||||
at: (filters.value.tags.length > 0 && filters.value.onlyActorTags) || undefined,
|
||||
@@ -163,7 +163,7 @@ async function search(options = {}) {
|
||||
|
||||
const res = await get('/scenes', {
|
||||
...query,
|
||||
years: filters.value.years.filter(Boolean).join(','), // if we're on an actor page, that actor ID needs to be included
|
||||
years: formatYears(filters.value),
|
||||
actors: [pageActor, ...filters.value.actors].filter(Boolean).map((filterActor) => getActorIdentifier(filterActor)).join(','), // if we're on an actor page, that actor ID needs to be included
|
||||
tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','),
|
||||
at: !!filters.value.onlyActorTags,
|
||||
|
||||
Reference in New Issue
Block a user