kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
Migrate pagination to v-model and start moving away from mixins
rodzic
a25f1bbb1f
commit
3ab0435f27
|
@ -1,3 +1,66 @@
|
|||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { range, clamp } from 'lodash-es'
|
||||
import { computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
interface Props {
|
||||
current?: number
|
||||
paginateBy?: number
|
||||
total: number,
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
current: 1,
|
||||
paginateBy: 25,
|
||||
compact: false
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:current', 'pageChanged'])
|
||||
const current = useVModel(props, 'current', emit)
|
||||
|
||||
const RANGE = 2
|
||||
const pages = computed(() => {
|
||||
const start = range(1, 1 + RANGE)
|
||||
const end = range(maxPage.value - RANGE, maxPage.value)
|
||||
const middle = range(
|
||||
clamp(props.current - RANGE + 1, 1, maxPage.value),
|
||||
clamp(props.current + RANGE, 1, maxPage.value)
|
||||
).filter(i => !start.includes(i) && !end.includes(i))
|
||||
|
||||
console.log(middle, end)
|
||||
|
||||
return [
|
||||
...start,
|
||||
middle.length === 0 && 'skip',
|
||||
middle.length !== 0 && start[start.length - 1] + 1 !== middle[0] && 'skip',
|
||||
...middle,
|
||||
middle.length !== 0 && middle[middle.length - 1] + 1 !== end[0] && 'skip',
|
||||
...end
|
||||
].filter(i => i !== false) as Array<'skip' | number>
|
||||
})
|
||||
|
||||
const maxPage = computed(() => Math.ceil(props.total / props.paginateBy))
|
||||
|
||||
const setPage = (page: number) => {
|
||||
if (page > maxPage.value || page < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
current.value = page
|
||||
// TODO (wvffle): Compat before change to v-model
|
||||
emit('pageChanged', page)
|
||||
}
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const labels = computed(() => ({
|
||||
pagination: $pgettext('Content/*/Hidden text/Noun', 'Pagination'),
|
||||
previousPage: $pgettext('Content/*/Link', 'Previous Page'),
|
||||
nextPage: $pgettext('Content/*/Link', 'Next Page')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="maxPage > 1"
|
||||
|
@ -6,107 +69,38 @@
|
|||
:aria-label="labels.pagination"
|
||||
>
|
||||
<a
|
||||
href
|
||||
href="#"
|
||||
:disabled="current - 1 < 1 || null"
|
||||
role="button"
|
||||
:aria-label="labels.previousPage"
|
||||
:class="[{'disabled': current - 1 < 1}, 'item']"
|
||||
@click.prevent.stop="selectPage(current - 1)"
|
||||
><i class="angle left icon" /></a>
|
||||
:class="[{ 'disabled': current - 1 < 1 }, 'item']"
|
||||
@click.prevent.stop="setPage(current - 1)"
|
||||
>
|
||||
<i class="angle left icon" />
|
||||
</a>
|
||||
|
||||
<template v-if="!compact">
|
||||
<a
|
||||
v-for="page in pages"
|
||||
:key="page"
|
||||
href
|
||||
:class="[{'active': page === current}, {'disabled': page === 'skip'}, 'item']"
|
||||
@click.prevent.stop="selectPage(page)"
|
||||
href="#"
|
||||
:class="[{ active: page === current, disabled: page === 'skip' }, 'item']"
|
||||
@click.prevent.stop="page !== 'skip' && setPage(page)"
|
||||
>
|
||||
<span v-if="page !== 'skip'">{{ page }}</span>
|
||||
<span v-else>…</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<a
|
||||
href
|
||||
href="#"
|
||||
:disabled="current + 1 > maxPage || null"
|
||||
role="button"
|
||||
:aria-label="labels.nextPage"
|
||||
:class="[{'disabled': current + 1 > maxPage}, 'item']"
|
||||
@click.prevent.stop="selectPage(current + 1)"
|
||||
><i class="angle right icon" /></a>
|
||||
:class="[{ disabled: current + 1 > maxPage }, 'item']"
|
||||
@click.prevent.stop="setPage(current + 1)"
|
||||
>
|
||||
<i class="angle right icon" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { range as lodashRange, sortBy, uniq } from 'lodash-es'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
current: { type: Number, default: 1 },
|
||||
paginateBy: { type: Number, default: 25 },
|
||||
total: { type: Number, required: true },
|
||||
compact: { type: Boolean, default: false }
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
pagination: this.$pgettext('Content/*/Hidden text/Noun', 'Pagination'),
|
||||
previousPage: this.$pgettext('Content/*/Link', 'Previous Page'),
|
||||
nextPage: this.$pgettext('Content/*/Link', 'Next Page')
|
||||
}
|
||||
},
|
||||
pages: function () {
|
||||
const range = 2
|
||||
const current = this.current
|
||||
const beginning = lodashRange(1, Math.min(this.maxPage, 1 + range))
|
||||
const middle = lodashRange(
|
||||
Math.max(1, current - range + 1),
|
||||
Math.min(this.maxPage, current + range)
|
||||
)
|
||||
const end = lodashRange(this.maxPage, Math.max(1, this.maxPage - range))
|
||||
let allowed = beginning.concat(middle, end)
|
||||
allowed = uniq(allowed)
|
||||
allowed = sortBy(allowed, [
|
||||
e => {
|
||||
return e
|
||||
}
|
||||
])
|
||||
const final = []
|
||||
allowed.forEach(p => {
|
||||
const last = final.slice(-1)[0]
|
||||
let consecutive = true
|
||||
if (last === 'skip') {
|
||||
consecutive = false
|
||||
} else {
|
||||
if (!last) {
|
||||
consecutive = true
|
||||
} else {
|
||||
consecutive = last + 1 === p
|
||||
}
|
||||
}
|
||||
if (consecutive) {
|
||||
final.push(p)
|
||||
} else {
|
||||
if (p !== 'skip') {
|
||||
final.push('skip')
|
||||
final.push(p)
|
||||
}
|
||||
}
|
||||
})
|
||||
return final
|
||||
},
|
||||
maxPage: function () {
|
||||
return Math.ceil(this.total / this.paginateBy)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectPage: function (page) {
|
||||
if (page > this.maxPage || page < 1) {
|
||||
return
|
||||
}
|
||||
if (this.current !== page) {
|
||||
this.$emit('page-changed', page)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -196,6 +196,7 @@
|
|||
<tr>
|
||||
<th v-if="actions.length > 0">
|
||||
<div class="ui checkbox">
|
||||
<!-- TODO (wvffle): Check if we don't have to migrate to v-model -->
|
||||
<input
|
||||
type="checkbox"
|
||||
:aria-label="labels.selectAllItems"
|
||||
|
@ -217,6 +218,7 @@
|
|||
v-if="actions.length > 0"
|
||||
class="collapsing"
|
||||
>
|
||||
<!-- TODO (wvffle): Check if we don't have to migrate to v-model -->
|
||||
<input
|
||||
type="checkbox"
|
||||
:aria-label="labels.selectItem"
|
||||
|
|
|
@ -1,245 +1,195 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
import RadioButton from '~/components/radios/Button.vue'
|
||||
import Pagination from '~/components/Pagination.vue'
|
||||
import { checkRedirectToLogin } from '~/utils'
|
||||
import TrackTable from '~/components/audio/track/Table.vue'
|
||||
import useLogger from '~/composables/useLogger'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import useOrdering from '~/composables/useOrdering'
|
||||
import { onBeforeRouteUpdate, useRouter } from 'vue-router'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useStore } from '~/store'
|
||||
import { Track } from '~/types'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { OrderingField, RouteWithPreferences } from '~/store/ui'
|
||||
|
||||
interface Props {
|
||||
orderingConfigName: RouteWithPreferences | null
|
||||
defaultPage?: number,
|
||||
defaultPaginateBy?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultPage: 1,
|
||||
defaultPaginateBy: 1
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
await checkRedirectToLogin(store, useRouter())
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(+props.defaultPage)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'creation_date'],
|
||||
['title', 'track_title'],
|
||||
['album__title', 'album_title'],
|
||||
['artist__name', 'artist_name']
|
||||
]
|
||||
|
||||
const logger = useLogger()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
|
||||
const router = useRouter()
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
|
||||
const updateQueryString = () => router.replace({
|
||||
query: {
|
||||
page: page.value,
|
||||
paginateBy: paginateBy.value,
|
||||
ordering: orderingString.value
|
||||
}
|
||||
})
|
||||
|
||||
const results = reactive<Track[]>([])
|
||||
const nextLink = ref()
|
||||
const previousLink = ref()
|
||||
const count = ref(0)
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchFavorites = async () => {
|
||||
isLoading.value = true
|
||||
|
||||
const params = {
|
||||
favorites: 'true',
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
ordering: orderingString.value
|
||||
}
|
||||
|
||||
try {
|
||||
logger.time('Loading user favorites')
|
||||
const response = await axios.get('tracks/', { params: params })
|
||||
|
||||
results.length = 0
|
||||
results.push(...response.data.results)
|
||||
|
||||
for (const track of results) {
|
||||
store.commit('favorites/track', { id: track.id, value: true })
|
||||
}
|
||||
|
||||
count.value = response.data.count
|
||||
nextLink.value = response.data.next
|
||||
previousLink.value = response.data.previous
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
} finally {
|
||||
logger.timeEnd('Loading user favorites')
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(page, updateQueryString)
|
||||
onOrderingUpdate(updateQueryString)
|
||||
onBeforeRouteUpdate(fetchFavorites)
|
||||
fetchFavorites()
|
||||
|
||||
// @ts-expect-error semantic ui
|
||||
onMounted(() => $('.ui.dropdown').dropdown())
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/Favorites/Title', 'Your Favorites')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
v-title="labels.title"
|
||||
class="main pusher"
|
||||
>
|
||||
<main v-title="labels.title" class="main pusher">
|
||||
<section class="ui vertical center aligned stripe segment">
|
||||
<div :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
|
||||
<div :class="['ui', { 'active': isLoading }, 'inverted', 'dimmer']">
|
||||
<div class="ui text loader">
|
||||
<translate translate-context="Content/Favorites/Message">
|
||||
Loading your favorites…
|
||||
</translate>
|
||||
<translate translate-context="Content/Favorites/Message">Loading your favorites…</translate>
|
||||
</div>
|
||||
</div>
|
||||
<h2
|
||||
v-if="results"
|
||||
class="ui center aligned icon header"
|
||||
>
|
||||
<h2 v-if="results" class="ui center aligned icon header">
|
||||
<i class="circular inverted heart pink icon" />
|
||||
<translate
|
||||
translate-plural="%{ count } favorites"
|
||||
:translate-n="$store.state.favorites.count"
|
||||
:translate-params="{count: results.count}"
|
||||
:translate-params="{ count }"
|
||||
translate-context="Content/Favorites/Title"
|
||||
>
|
||||
%{ count } favorite
|
||||
</translate>
|
||||
>%{ count } favorite</translate>
|
||||
</h2>
|
||||
<radio-button
|
||||
v-if="hasFavorites"
|
||||
type="favorites"
|
||||
/>
|
||||
<radio-button v-if="$store.state.favorites.count > 0" type="favorites" />
|
||||
</section>
|
||||
<section
|
||||
v-if="hasFavorites"
|
||||
class="ui vertical stripe segment"
|
||||
>
|
||||
<div :class="['ui', {'loading': isLoading}, 'form']">
|
||||
<section v-if="$store.state.favorites.count > 0" class="ui vertical stripe segment">
|
||||
<div :class="['ui', { 'loading': isLoading }, 'form']">
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="favorites-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||
<select
|
||||
id="favorites-ordering"
|
||||
v-model="ordering"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<label for="favorites-ordering">
|
||||
<translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate>
|
||||
</label>
|
||||
<select id="favorites-ordering" v-model="ordering" class="ui dropdown">
|
||||
<option
|
||||
v-for="option in orderingOptions"
|
||||
:key="option[0]"
|
||||
:value="option[0]"
|
||||
>
|
||||
{{ sharedLabels.filters[option[1]] }}
|
||||
</option>
|
||||
>{{ sharedLabels.filters[option[1]] }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="favorites-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
|
||||
<label for="favorites-ordering-direction">
|
||||
<translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate>
|
||||
</label>
|
||||
<select
|
||||
id="favorites-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
Ascending
|
||||
</translate>
|
||||
<translate translate-context="Content/Search/Dropdown">Ascending</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
Descending
|
||||
</translate>
|
||||
<translate translate-context="Content/Search/Dropdown">Descending</translate>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="favorites-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
|
||||
<select
|
||||
id="favorites-results"
|
||||
v-model="paginateBy"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option :value="parseInt(12)">
|
||||
12
|
||||
</option>
|
||||
<option :value="parseInt(25)">
|
||||
25
|
||||
</option>
|
||||
<option :value="parseInt(50)">
|
||||
50
|
||||
</option>
|
||||
<label for="favorites-results">
|
||||
<translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate>
|
||||
</label>
|
||||
<select id="favorites-results" v-model="paginateBy" class="ui dropdown">
|
||||
<option :value="12">12</option>
|
||||
<option :value="25">25</option>
|
||||
<option :value="50">50</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<track-table
|
||||
v-if="results"
|
||||
:show-artist="true"
|
||||
:show-album="true"
|
||||
:tracks="results.results"
|
||||
/>
|
||||
<track-table v-if="results" :show-artist="true" :show-album="true" :tracks="results" />
|
||||
<div class="ui center aligned basic segment">
|
||||
<pagination
|
||||
v-if="results && results.count > paginateBy"
|
||||
:current="page"
|
||||
v-if="results && count > paginateBy"
|
||||
v-model:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="results.count"
|
||||
@page-changed="selectPage"
|
||||
:total="count"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<div
|
||||
v-else
|
||||
class="ui placeholder segment"
|
||||
>
|
||||
<div v-else class="ui placeholder segment">
|
||||
<div class="ui icon header">
|
||||
<i class="broken heart icon" />
|
||||
<translate
|
||||
translate-context="Content/Home/Placeholder"
|
||||
>
|
||||
No tracks have been added to your favorites yet
|
||||
</translate>
|
||||
>No tracks have been added to your favorites yet</translate>
|
||||
</div>
|
||||
<router-link
|
||||
:to="'/library'"
|
||||
class="ui success labeled icon button"
|
||||
>
|
||||
<router-link :to="'/library'" class="ui success labeled icon button">
|
||||
<i class="headphones icon" />
|
||||
<translate translate-context="Content/*/Verb">
|
||||
Browse the library
|
||||
</translate>
|
||||
<translate translate-context="Content/*/Verb">Browse the library</translate>
|
||||
</router-link>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
import RadioButton from '~/components/radios/Button.vue'
|
||||
import Pagination from '~/components/Pagination.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import PaginationMixin from '~/components/mixins/Pagination.vue'
|
||||
import { checkRedirectToLogin } from '~/utils'
|
||||
import TrackTable from '~/components/audio/track/Table.vue'
|
||||
import useLogger from '~/composables/useLogger'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
const logger = useLogger()
|
||||
|
||||
const FAVORITES_URL = 'tracks/'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RadioButton,
|
||||
Pagination,
|
||||
TrackTable
|
||||
},
|
||||
mixins: [OrderingMixin, PaginationMixin],
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
results: null,
|
||||
isLoading: false,
|
||||
nextLink: null,
|
||||
previousLink: null,
|
||||
page: parseInt(this.defaultPage),
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['title', 'track_title'],
|
||||
['album__title', 'album_title'],
|
||||
['artist__name', 'artist_name']
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
title: this.$pgettext('Head/Favorites/Title', 'Your Favorites')
|
||||
}
|
||||
},
|
||||
hasFavorites () {
|
||||
return this.$store.state.favorites.count > 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
page: function () {
|
||||
this.updateQueryString()
|
||||
},
|
||||
paginateBy: function () {
|
||||
this.updateQueryString()
|
||||
},
|
||||
orderingDirection: function () {
|
||||
this.updateQueryString()
|
||||
},
|
||||
ordering: function () {
|
||||
this.updateQueryString()
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
await checkRedirectToLogin(this.$store, this.$router)
|
||||
this.fetchFavorites(FAVORITES_URL)
|
||||
},
|
||||
mounted () {
|
||||
$('.ui.dropdown').dropdown()
|
||||
},
|
||||
methods: {
|
||||
updateQueryString: function () {
|
||||
this.$router.replace({
|
||||
query: {
|
||||
page: this.page,
|
||||
paginateBy: this.paginateBy,
|
||||
ordering: this.getOrderingAsString()
|
||||
}
|
||||
})
|
||||
this.fetchFavorites(FAVORITES_URL)
|
||||
},
|
||||
fetchFavorites (url) {
|
||||
const self = this
|
||||
this.isLoading = true
|
||||
const params = {
|
||||
favorites: 'true',
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
ordering: this.getOrderingAsString()
|
||||
}
|
||||
logger.time('Loading user favorites')
|
||||
axios.get(url, { params: params }).then(response => {
|
||||
self.results = response.data
|
||||
self.nextLink = response.data.next
|
||||
self.previousLink = response.data.previous
|
||||
self.results.results.forEach(track => {
|
||||
self.$store.commit('favorites/track', { id: track.id, value: true })
|
||||
})
|
||||
logger.timeEnd('Loading user favorites')
|
||||
self.isLoading = false
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
defaultOrdering: { type: String, required: false, default: '' },
|
||||
orderingConfigName: { type: String, required: false, default: '' }
|
||||
},
|
||||
computed: {
|
||||
orderingConfig () {
|
||||
return this.$store.state.ui.routePreferences[this.orderingConfigName || this.$route.name]
|
||||
},
|
||||
paginateBy: {
|
||||
set (paginateBy) {
|
||||
this.$store.commit('ui/paginateBy', {
|
||||
route: this.$route.name,
|
||||
value: paginateBy
|
||||
})
|
||||
},
|
||||
get () {
|
||||
return this.orderingConfig.paginateBy
|
||||
}
|
||||
},
|
||||
ordering: {
|
||||
set (ordering) {
|
||||
this.$store.commit('ui/ordering', {
|
||||
route: this.$route.name,
|
||||
value: ordering
|
||||
})
|
||||
},
|
||||
get () {
|
||||
return this.orderingConfig.ordering
|
||||
}
|
||||
},
|
||||
orderingDirection: {
|
||||
set (orderingDirection) {
|
||||
this.$store.commit('ui/orderingDirection', {
|
||||
route: this.$route.name,
|
||||
value: orderingDirection
|
||||
})
|
||||
},
|
||||
get () {
|
||||
return this.orderingConfig.orderingDirection
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getOrderingFromString (s) {
|
||||
const parts = s.split('-')
|
||||
if (parts.length > 1) {
|
||||
return {
|
||||
direction: '-',
|
||||
field: parts.slice(1).join('-')
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
direction: '+',
|
||||
field: s
|
||||
}
|
||||
}
|
||||
},
|
||||
getOrderingAsString () {
|
||||
let direction = this.orderingDirection
|
||||
if (direction === '+') {
|
||||
direction = ''
|
||||
}
|
||||
return [direction, this.ordering].join('')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,38 @@
|
|||
import { MaybeRef, reactiveComputed, toRefs } from '@vueuse/core'
|
||||
import { computed, unref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
import { OrderingDirection, OrderingField, RouteWithPreferences } from '~/store/ui'
|
||||
|
||||
export default (orderingConfigName: MaybeRef<RouteWithPreferences | null>) => {
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
|
||||
const config = reactiveComputed(() => {
|
||||
const name = unref(orderingConfigName) ?? route.name as RouteWithPreferences
|
||||
return store.state.ui.routePreferences[name]
|
||||
})
|
||||
|
||||
const { paginateBy, ordering, orderingDirection } = toRefs(config)
|
||||
|
||||
const orderingString = computed(() => {
|
||||
if (orderingDirection.value === '-') return `-${ordering.value}`
|
||||
return ordering.value
|
||||
})
|
||||
|
||||
const getOrderingFromString = (str: string) => ({
|
||||
direction: (str[0] === '-' ? '-' : '+') as OrderingDirection,
|
||||
field: (str[0] === '-' || str[0] === '+' ? str.slice(1) : str) as OrderingField
|
||||
})
|
||||
|
||||
const onOrderingUpdate = (fn: () => void) => watch(config, fn)
|
||||
|
||||
return {
|
||||
paginateBy,
|
||||
ordering,
|
||||
orderingDirection,
|
||||
orderingString,
|
||||
getOrderingFromString,
|
||||
onOrderingUpdate
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ Promise.all(modules).finally(() => {
|
|||
logger.info('Everything loaded!')
|
||||
})
|
||||
|
||||
// TODO (wvffle): Rename filters from useSharedLabels to filters from backend
|
||||
// TODO (wvffle): Check for mixin merging: https://v3-migration.vuejs.org/breaking-changes/data-option.html#mixin-merge-behavior-change=
|
||||
// TODO (wvffle): Use emits options: https://v3-migration.vuejs.org/breaking-changes/emits-option.html
|
||||
// TODO (wvffle): Find all array watchers and make them deep
|
||||
|
|
|
@ -6,7 +6,7 @@ import { availableLanguages } from '~/init/locale'
|
|||
|
||||
type SupportedExtension = 'flac' | 'ogg' | 'mp3' | 'opus' | 'aac' | 'm4a' | 'aiff' | 'aif'
|
||||
|
||||
type RouteWithPreferences = 'library.artists.browse' | 'library.podcasts.browse' | 'library.radios.browse'
|
||||
export type RouteWithPreferences = 'library.artists.browse' | 'library.podcasts.browse' | 'library.radios.browse'
|
||||
| 'library.playlists.browse' | 'library.albums.me' | 'library.artists.me' | 'library.radios.me'
|
||||
| 'library.playlists.me' | 'content.libraries.files' | 'library.detail.upload' | 'library.detail.edit'
|
||||
| 'library.detail' | 'favorites' | 'manage.channels' | 'manage.library.tags' | 'manage.library.uploads'
|
||||
|
@ -18,12 +18,12 @@ type RouteWithPreferences = 'library.artists.browse' | 'library.podcasts.browse'
|
|||
export type WebSocketEventName = 'inbox.item_added' | 'import.status_updated' | 'mutation.created' | 'mutation.updated'
|
||||
| 'report.created' | 'user_request.created' | 'Listen'
|
||||
|
||||
type Ordering = 'creation_date'
|
||||
type OrderingDirection = '-'
|
||||
export type OrderingField = 'creation_date' | 'title' | 'album__title' | 'artist__name'
|
||||
export type OrderingDirection = '-' | '+'
|
||||
interface RoutePreferences {
|
||||
paginateBy: number
|
||||
orderingDirection: OrderingDirection
|
||||
ordering: Ordering
|
||||
ordering: OrderingField
|
||||
}
|
||||
|
||||
interface WebSocketEvent {
|
||||
|
@ -351,16 +351,6 @@ const store: Module<State, RootState> = {
|
|||
pageTitle: (state, value) => {
|
||||
state.pageTitle = value
|
||||
},
|
||||
paginateBy: (state, { route, value }: { route: RouteWithPreferences, value: number }) => {
|
||||
state.routePreferences[route].paginateBy = value
|
||||
},
|
||||
ordering: (state, { route, value }: { route: RouteWithPreferences, value: Ordering }) => {
|
||||
state.routePreferences[route].ordering = value
|
||||
},
|
||||
orderingDirection: (state, { route, value }: { route: RouteWithPreferences, value: OrderingDirection }) => {
|
||||
state.routePreferences[route].orderingDirection = value
|
||||
},
|
||||
|
||||
window: (state, value) => {
|
||||
state.window = value
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue