139 lines
3.5 KiB
Vue
139 lines
3.5 KiB
Vue
<script setup>
|
|
import { computed, ref } from 'vue';
|
|
|
|
import Range from '#/components/form/range.vue';
|
|
|
|
const props = defineProps({
|
|
filters: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
years: {
|
|
type: Array,
|
|
default: null,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(['update']);
|
|
|
|
// 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() {
|
|
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-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="slider.isFiltered"
|
|
icon="cross2"
|
|
class="clear"
|
|
@click="clearYears"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.years-filter {
|
|
display: flex;
|
|
align-items: stretch;
|
|
border-bottom: solid 1px var(--glass-weak-30);
|
|
}
|
|
|
|
.years-range {
|
|
padding: .25rem 0;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.clear {
|
|
height: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 .5rem 0 0;
|
|
fill: var(--glass);
|
|
|
|
&:hover {
|
|
fill: var(--primary);
|
|
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>
|