Fix search and dynamic ordering (#1567)

This commit is contained in:
wvffle 2022-09-03 14:51:20 +00:00 committed by Georg Krause
parent fb27bccaa3
commit fb4f94fb73
49 changed files with 685 additions and 813 deletions

View File

@ -24,6 +24,7 @@
"@vue/runtime-core": "3.2.38", "@vue/runtime-core": "3.2.38",
"@vueuse/core": "9.1.1", "@vueuse/core": "9.1.1",
"@vueuse/integrations": "9.1.1", "@vueuse/integrations": "9.1.1",
"@vueuse/router": "9.1.1",
"axios": "0.27.2", "axios": "0.27.2",
"axios-auth-refresh": "3.3.3", "axios-auth-refresh": "3.3.3",
"diff": "5.1.0", "diff": "5.1.0",

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ContentCategory, Channel, BackendError, Tag } from '~/types' import type { ContentCategory, Channel, BackendError } from '~/types'
import { slugify } from 'transliteration' import { slugify } from 'transliteration'
import { reactive, computed, ref, watchEffect, watch } from 'vue' import { reactive, computed, ref, watchEffect, watch } from 'vue'
@ -34,7 +34,7 @@ const { $pgettext } = useGettext()
const newValues = reactive({ const newValues = reactive({
name: props.object?.artist?.name ?? '', name: props.object?.artist?.name ?? '',
username: props.object?.actor.preferred_username ?? '', username: props.object?.actor.preferred_username ?? '',
tags: props.object?.artist?.tags?.map(name => ({ name } as Tag)) ?? [] as Tag[], tags: props.object?.artist?.tags ?? [] as string[],
description: props.object?.artist?.description?.text ?? '', description: props.object?.artist?.description?.text ?? '',
cover: props.object?.artist?.cover?.uuid ?? null, cover: props.object?.artist?.cover?.uuid ?? null,
content_category: props.object?.artist?.content_category ?? 'podcast', content_category: props.object?.artist?.content_category ?? 'podcast',

View File

@ -13,7 +13,7 @@ import useFormData from '~/composables/useFormData'
interface Props { interface Props {
clientId: string clientId: string
redirectUri: string redirectUri: string
scope: string scope: 'me' | 'all'
responseType: string responseType: string
nonce: string nonce: string
state: string state: string

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Upload, Tag, Track } from '~/types' import type { Upload, Track } from '~/types'
import { reactive, computed, watch } from 'vue' import { reactive, computed, watch } from 'vue'
@ -21,16 +21,12 @@ const props = withDefaults(defineProps<Props>(), {
values: null values: null
}) })
const newValues = reactive<Omit<Values, 'tags'> & { tags: Tag[] }>({ const newValues = reactive<Values>({
...(props.values ?? props.upload.import_metadata ?? {}) as Values, ...(props.values ?? props.upload.import_metadata ?? {}) as Values
tags: ((props.values ?? props.upload.import_metadata)?.tags?.map(name => ({ name })) ?? []) as Tag[]
}) })
const isLoading = computed(() => !props.upload) const isLoading = computed(() => !props.upload)
watch(newValues, (values) => emit('update:values', { watch(newValues, (values) => emit('update:values', values), { immediate: true })
...values,
tags: values.tags?.map(({ name }) => name)
}), { immediate: true })
</script> </script>
<template> <template>

View File

@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import type { Track } from '~/types' import type { Track } from '~/types'
import type { OrderingProps } from '~/composables/useOrdering'
import { computed, onMounted, reactive, ref, watch } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import { onBeforeRouteUpdate, useRouter } from 'vue-router'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
@ -16,24 +17,24 @@ import RadioButton from '~/components/radios/Button.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import usePage from '~/composables/navigation/usePage'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
interface Props extends OrderingProps { interface Props extends OrderingProps {
defaultPage?: number
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultPage: 1 defaultPage: 1,
orderingConfigName: undefined
}) })
const store = useStore() const store = useStore()
const page = ref(+props.defaultPage) const page = usePage()
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],
@ -45,19 +46,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
const logger = useLogger() const logger = useLogger()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const router = useRouter() const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
const updateQueryString = () => router.replace({
query: {
page: page.value,
paginateBy: paginateBy.value,
ordering: orderingString.value
}
})
watch(page, updateQueryString)
onOrderingUpdate(updateQueryString)
const results = reactive<Track[]>([]) const results = reactive<Track[]>([])
const nextLink = ref() const nextLink = ref()
@ -97,15 +86,22 @@ const fetchFavorites = async () => {
} }
} }
onBeforeRouteUpdate(fetchFavorites) watch(page, fetchFavorites)
fetchFavorites() fetchFavorites()
onOrderingUpdate(() => {
page.value = 1
fetchFavorites()
})
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { $pgettext } = useGettext()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/Favorites/Title', 'Your Favorites') title: $pgettext('Head/Favorites/Title', 'Your Favorites')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b)))
</script> </script>
<template> <template>
@ -194,14 +190,12 @@ const labels = computed(() => ({
v-model="paginateBy" v-model="paginateBy"
class="ui dropdown" class="ui dropdown"
> >
<option :value="12"> <option
12 v-for="opt in paginateOptions"
</option> :key="opt"
<option :value="25"> :value="opt"
25 >
</option> {{ opt }}
<option :value="50">
50
</option> </option>
</select> </select>
</div> </div>

View File

@ -1,10 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { Album, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { onBeforeRouteUpdate, useRouter } from 'vue-router' import { computed, onMounted, ref, watch } from 'vue'
import { computed, onMounted, reactive, ref, watch } from 'vue' import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
@ -16,32 +20,32 @@ import AlbumCard from '~/components/audio/album/Card.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import usePage from '~/composables/navigation/usePage'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
interface Props extends OrderingProps { interface Props extends OrderingProps {
defaultPage?: number scope?: 'me' | 'all'
defaultQuery?: string
defaultTags?: string[]
scope?: string
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultPage: 1, scope: 'all',
defaultQuery: '', orderingConfigName: undefined
defaultTags: () => [],
scope: 'all'
}) })
const page = ref(+props.defaultPage) const page = usePage()
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const tags = useRouteQuery<string[]>('tag', [])
const query = ref(props.defaultQuery)
const tags = reactive(props.defaultTags.map(name => ({ name }))) const q = useRouteQuery('query', '')
const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' })
const result = ref<BackendResponse<Album>>()
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],
@ -52,21 +56,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
const logger = useLogger() const logger = useLogger()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const router = useRouter()
const updateQueryString = () => router.replace({
query: {
query: query.value,
page: page.value,
tag: tags.map(({ name }) => name),
paginateBy: paginateBy.value,
ordering: orderingString.value
}
})
watch(page, updateQueryString)
onOrderingUpdate(updateQueryString)
const isLoading = ref(false) const isLoading = ref(false)
const fetchData = async () => { const fetchData = async () => {
@ -78,7 +68,7 @@ const fetchData = async () => {
q: query.value, q: query.value,
ordering: orderingString.value, ordering: orderingString.value,
playable: 'true', playable: 'true',
tag: tags, tag: tags.value,
include_channels: 'true', include_channels: 'true',
content_category: 'music' content_category: 'music'
} }
@ -95,7 +85,7 @@ const fetchData = async () => {
result.value = response.data result.value = response.data
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
logger.timeEnd('Fetching albums') logger.timeEnd('Fetching albums')
isLoading.value = false isLoading.value = false
@ -104,9 +94,19 @@ const fetchData = async () => {
const store = useStore() const store = useStore()
watch(() => store.state.moderation.lastUpdate, fetchData) watch(() => store.state.moderation.lastUpdate, fetchData)
onBeforeRouteUpdate(fetchData) watch(page, fetchData)
fetchData() fetchData()
const search = () => {
page.value = 1
q.value = query.value
}
onOrderingUpdate(() => {
page.value = 1
fetchData()
})
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { $pgettext } = useGettext()
@ -114,6 +114,8 @@ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Enter album title…'), searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Enter album title…'),
title: $pgettext('*/*/*', 'Albums') title: $pgettext('*/*/*', 'Albums')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b)))
</script> </script>
<template> <template>
@ -126,7 +128,7 @@ const labels = computed(() => ({
</h2> </h2>
<form <form
:class="['ui', {'loading': isLoading}, 'form']" :class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="page = props.defaultPage" @submit.prevent="search"
> >
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
@ -196,14 +198,12 @@ const labels = computed(() => ({
v-model="paginateBy" v-model="paginateBy"
class="ui dropdown" class="ui dropdown"
> >
<option :value="12"> <option
12 v-for="opt in paginateOptions"
</option> :key="opt"
<option :value="25"> :value="opt"
25 >
</option> {{ opt }}
<option :value="50">
50
</option> </option>
</select> </select>
</div> </div>

View File

@ -1,10 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { Artist, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { computed, reactive, ref, watch, onMounted } from 'vue' import { computed, ref, watch, onMounted } from 'vue'
import { onBeforeRouteUpdate, useRouter } from 'vue-router' import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
@ -16,32 +20,32 @@ import ArtistCard from '~/components/audio/artist/Card.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import usePage from '~/composables/navigation/usePage'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
interface Props extends OrderingProps { interface Props extends OrderingProps {
defaultPage?: number scope?: 'me' | 'all'
defaultQuery?: string
defaultTags?: string[]
scope?: string
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultPage: 1, scope: 'all',
defaultQuery: '', orderingConfigName: undefined
defaultTags: () => [],
scope: 'all'
}) })
const page = ref(+props.defaultPage) const page = usePage()
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const tags = useRouteQuery<string[]>('tag', [])
const query = ref(props.defaultQuery)
const tags = reactive(props.defaultTags.map(name => ({ name }))) const q = useRouteQuery('query', '')
const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' })
const result = ref<BackendResponse<Artist>>()
const excludeCompilation = ref(true) const excludeCompilation = ref(true)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
@ -52,23 +56,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
const logger = useLogger() const logger = useLogger()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const router = useRouter()
const updateQueryString = () => router.replace({
query: {
query: query.value,
page: page.value,
tag: tags.map(({ name }) => name),
paginateBy: paginateBy.value,
ordering: orderingString.value,
content_category: 'music',
include_channels: 'true'
}
})
watch(page, updateQueryString)
onOrderingUpdate(updateQueryString)
const isLoading = ref(false) const isLoading = ref(false)
const fetchData = async () => { const fetchData = async () => {
@ -80,7 +68,7 @@ const fetchData = async () => {
q: query.value, q: query.value,
ordering: orderingString.value, ordering: orderingString.value,
playable: 'true', playable: 'true',
tag: tags, tag: tags.value,
include_channels: 'true', include_channels: 'true',
content_category: 'music', content_category: 'music',
has_albums: excludeCompilation.value has_albums: excludeCompilation.value
@ -98,7 +86,7 @@ const fetchData = async () => {
result.value = response.data result.value = response.data
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
logger.timeEnd('Fetching artists') logger.timeEnd('Fetching artists')
isLoading.value = false isLoading.value = false
@ -107,9 +95,19 @@ const fetchData = async () => {
const store = useStore() const store = useStore()
watch([() => store.state.moderation.lastUpdate, excludeCompilation], fetchData) watch([() => store.state.moderation.lastUpdate, excludeCompilation], fetchData)
onBeforeRouteUpdate(fetchData) watch(page, fetchData)
fetchData() fetchData()
const search = () => {
page.value = 1
q.value = query.value
}
onOrderingUpdate(() => {
page.value = 1
fetchData()
})
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { $pgettext } = useGettext()
@ -117,6 +115,8 @@ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'), searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'),
title: $pgettext('*/*/*/Noun', 'Artists') title: $pgettext('*/*/*/Noun', 'Artists')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))
</script> </script>
<template> <template>
@ -129,7 +129,7 @@ const labels = computed(() => ({
</h2> </h2>
<form <form
:class="['ui', {'loading': isLoading}, 'form']" :class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="page = props.defaultPage" @submit.prevent="search"
> >
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
@ -199,14 +199,12 @@ const labels = computed(() => ({
v-model="paginateBy" v-model="paginateBy"
class="ui dropdown" class="ui dropdown"
> >
<option :value="12"> <option
12 v-for="opt in paginateOptions"
</option> :key="opt"
<option :value="30"> :value="opt"
30 >
</option> {{ opt }}
<option :value="50">
50
</option> </option>
</select> </select>
</div> </div>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VueUploadItem } from 'vue-upload-component'
import type { BackendError, Library, FileSystem } from '~/types' import type { BackendError, Library, FileSystem } from '~/types'
import type { VueUploadItem } from 'vue-upload-component'
import { computed, ref, reactive, watch, nextTick } from 'vue' import { computed, ref, reactive, watch, nextTick } from 'vue'
import { useEventListener, useIntervalFn } from '@vueuse/core' import { useEventListener, useIntervalFn } from '@vueuse/core'
@ -268,7 +268,8 @@ const inputFile = (newFile: VueUploadItem) => {
} }
} }
const retry = (files: VueUploadItem[]) => { // NOTE: For some weird reason typescript thinks that xhr field is not compatible with the same type
const retry = (files: Omit<VueUploadItem, 'xhr'>[]) => {
for (const file of files) { for (const file of files) {
upload.value.update(file, { error: '', progress: '0.00' }) upload.value.update(file, { error: '', progress: '0.00' })
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="main pusher page-library"> <div class="main pusher page-library">
<router-view :key="$route.fullPath" /> <router-view />
</div> </div>
</template> </template>

View File

@ -1,10 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { Artist, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { computed, reactive, ref, watch, onMounted } from 'vue' import { computed, ref, watch, onMounted } from 'vue'
import { onBeforeRouteUpdate, useRouter } from 'vue-router' import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
@ -18,33 +22,32 @@ import ArtistCard from '~/components/audio/artist/Card.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import usePage from '~/composables/navigation/usePage'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
interface Props extends OrderingProps { interface Props extends OrderingProps {
defaultPage?: number scope?: 'me' | 'all'
defaultQuery?: string
defaultTags?: string[]
scope?: string
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultPage: 1, scope: 'all',
defaultQuery: '', orderingConfigName: undefined
defaultTags: () => [],
scope: 'all'
}) })
const page = ref(+props.defaultPage) const page = usePage()
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const tags = useRouteQuery<string[]>('tag', [])
const query = ref(props.defaultQuery)
const tags = reactive(props.defaultTags.map(name => ({ name }))) const q = useRouteQuery('query', '')
const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' })
const result = ref<BackendResponse<Artist>>()
const showSubscribeModal = ref(false) const showSubscribeModal = ref(false)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
@ -55,23 +58,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
const logger = useLogger() const logger = useLogger()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const router = useRouter()
const updateQueryString = () => router.replace({
query: {
query: query.value,
page: page.value,
tag: tags.map(({ name }) => name),
paginateBy: paginateBy.value,
ordering: orderingString.value,
content_category: 'podcast',
include_channels: 'true'
}
})
watch(page, updateQueryString)
onOrderingUpdate(updateQueryString)
const isLoading = ref(false) const isLoading = ref(false)
const fetchData = async () => { const fetchData = async () => {
@ -83,7 +70,7 @@ const fetchData = async () => {
q: query.value, q: query.value,
ordering: orderingString.value, ordering: orderingString.value,
playable: 'true', playable: 'true',
tag: tags, tag: tags.value,
include_channels: 'true', include_channels: 'true',
content_category: 'podcast' content_category: 'podcast'
} }
@ -100,7 +87,7 @@ const fetchData = async () => {
result.value = response.data result.value = response.data
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
logger.timeEnd('Fetching podcasts') logger.timeEnd('Fetching podcasts')
isLoading.value = false isLoading.value = false
@ -109,9 +96,19 @@ const fetchData = async () => {
const store = useStore() const store = useStore()
watch(() => store.state.moderation.lastUpdate, fetchData) watch(() => store.state.moderation.lastUpdate, fetchData)
onBeforeRouteUpdate(fetchData) watch(page, fetchData)
fetchData() fetchData()
const search = () => {
page.value = 1
q.value = query.value
}
onOrderingUpdate(() => {
page.value = 1
fetchData()
})
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { $pgettext } = useGettext()
@ -119,6 +116,8 @@ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'), searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'),
title: $pgettext('*/*/*/Noun', 'Podcasts') title: $pgettext('*/*/*/Noun', 'Podcasts')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))
</script> </script>
<template> <template>
@ -131,7 +130,7 @@ const labels = computed(() => ({
</h2> </h2>
<form <form
:class="['ui', {'loading': isLoading}, 'form']" :class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="page = props.defaultPage" @submit.prevent="search"
> >
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
@ -201,14 +200,12 @@ const labels = computed(() => ({
v-model="paginateBy" v-model="paginateBy"
class="ui dropdown" class="ui dropdown"
> >
<option :value="12"> <option
12 v-for="opt in paginateOptions"
</option> :key="opt"
<option :value="30"> :value="opt"
30 >
</option> {{ opt }}
<option :value="50">
50
</option> </option>
</select> </select>
</div> </div>

View File

@ -1,10 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { Radio, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { onBeforeRouteUpdate, useRouter } from 'vue-router' import { computed, onMounted, ref, watch } from 'vue'
import { computed, ref, watch, onMounted } from 'vue' import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
@ -14,30 +18,30 @@ import Pagination from '~/components/vui/Pagination.vue'
import RadioCard from '~/components/radios/Card.vue' import RadioCard from '~/components/radios/Card.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import usePage from '~/composables/navigation/usePage'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
interface Props extends OrderingProps { interface Props extends OrderingProps {
defaultPage?: number scope?: 'me' | 'all'
defaultQuery?: string
scope?: string
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultPage: 1, scope: 'all',
defaultQuery: '', orderingConfigName: undefined
scope: 'all'
}) })
const page = ref(+props.defaultPage) const page = usePage()
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const q = useRouteQuery('query', '')
const query = ref(props.defaultQuery) const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' })
const result = ref<BackendResponse<Radio>>()
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],
@ -47,20 +51,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
const logger = useLogger() const logger = useLogger()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const router = useRouter()
const updateQueryString = () => router.replace({
query: {
query: query.value,
page: page.value,
paginateBy: paginateBy.value,
ordering: orderingString.value
}
})
watch(page, updateQueryString)
onOrderingUpdate(updateQueryString)
const isLoading = ref(false) const isLoading = ref(false)
const fetchData = async () => { const fetchData = async () => {
@ -82,7 +73,7 @@ const fetchData = async () => {
result.value = response.data result.value = response.data
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
logger.timeEnd('Fetching radios') logger.timeEnd('Fetching radios')
isLoading.value = false isLoading.value = false
@ -93,9 +84,19 @@ const store = useStore()
const isAuthenticated = computed(() => store.state.auth.authenticated) const isAuthenticated = computed(() => store.state.auth.authenticated)
const hasFavorites = computed(() => store.state.favorites.count > 0) const hasFavorites = computed(() => store.state.favorites.count > 0)
onBeforeRouteUpdate(fetchData) watch(page, fetchData)
fetchData() fetchData()
const search = () => {
page.value = 1
q.value = query.value
}
onOrderingUpdate(() => {
page.value = 1
fetchData()
})
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { $pgettext } = useGettext()
@ -103,6 +104,8 @@ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Enter a radio name…'), searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Enter a radio name…'),
title: $pgettext('*/*/*', 'Radios') title: $pgettext('*/*/*', 'Radios')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b)))
</script> </script>
<template> <template>
@ -157,7 +160,7 @@ const labels = computed(() => ({
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<form <form
:class="['ui', {'loading': isLoading}, 'form']" :class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="page = props.defaultPage" @submit.prevent="search"
> >
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
@ -221,14 +224,12 @@ const labels = computed(() => ({
v-model="paginateBy" v-model="paginateBy"
class="ui dropdown" class="ui dropdown"
> >
<option :value="12"> <option
12 v-for="opt in paginateOptions"
</option> :key="opt"
<option :value="25"> :value="opt"
25 >
</option> {{ opt }}
<option :value="50">
50
</option> </option>
</select> </select>
</div> </div>

View File

@ -8,11 +8,11 @@ import { useStore } from '~/store'
import $ from 'jquery' import $ from 'jquery'
interface Events { interface Events {
(e: 'update:modelValue', tags: Tag[]): void (e: 'update:modelValue', tags: string[]): void
} }
interface Props { interface Props {
modelValue: Tag[] modelValue: string[]
} }
const emit = defineEmits<Events>() const emit = defineEmits<Events>()

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -11,16 +12,17 @@ import axios from 'axios'
import ActionTable from '~/components/common/ActionTable.vue' import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -28,16 +30,17 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const search = ref() const search = ref()
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -11,16 +12,17 @@ import axios from 'axios'
import ActionTable from '~/components/common/ActionTable.vue' import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -28,17 +30,18 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { OrderingProps } from '~/composables/useOrdering' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -11,16 +12,17 @@ import axios from 'axios'
import ActionTable from '~/components/common/ActionTable.vue' import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -28,17 +30,18 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],

View File

@ -1,9 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { EditObjectType } from '~/composables/moderation/useEditConfigs' import type { EditObjectType } from '~/composables/moderation/useEditConfigs'
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { SmartSearchProps } from '~/composables/useSmartSearch'
import type { OrderingProps } from '~/composables/useOrdering'
import type { ReviewState, Review } from '~/types' import type { ReviewState, Review } from '~/types'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { ref, reactive, watch, computed } from 'vue' import { ref, reactive, watch, computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -15,16 +16,17 @@ import Pagination from '~/components/vui/Pagination.vue'
import EditCard from '~/components/library/EditCard.vue' import EditCard from '~/components/library/EditCard.vue'
import useEditConfigs from '~/composables/moderation/useEditConfigs' import useEditConfigs from '~/composables/moderation/useEditConfigs'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -32,20 +34,21 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const configs = useEditConfigs() const configs = useEditConfigs()
const search = ref() const search = ref()
const page = ref(1) const page = usePage()
type StateTarget = Review['target'] type StateTarget = Review['target']
type ResponseType = { count: number, results: Review[] } type ResponseType = { count: number, results: Review[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import type { PrivacyLevel } from '~/types' import type { PrivacyLevel } from '~/types'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
@ -12,15 +13,16 @@ import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -28,17 +30,18 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { truncate } from '~/utils/filters' import { truncate } from '~/utils/filters'
@ -14,15 +15,16 @@ import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -30,17 +32,18 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onSearch, query } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -12,15 +13,16 @@ import useSharedLabels from '~/composables/locale/useSharedLabels'
import ActionTable from '~/components/common/ActionTable.vue' import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -28,17 +30,18 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'] ['creation_date', 'creation_date']

View File

@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ImportStatus, PrivacyLevel, Upload, BackendResponse } from '~/types' import type { ImportStatus, PrivacyLevel, Upload, BackendResponse } from '~/types'
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { humanSize, truncate } from '~/utils/filters' import { humanSize, truncate } from '~/utils/filters'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
@ -14,16 +15,17 @@ import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import ActionTable from '~/components/common/ActionTable.vue' import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -31,16 +33,17 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
const page = ref(1) const page = usePage()
const result = ref<BackendResponse<Upload>>() const result = ref<BackendResponse<Upload>>()
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { OrderingProps } from '~/composables/useOrdering' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -12,15 +13,16 @@ import useSharedLabels from '~/composables/locale/useSharedLabels'
import ActionTable from '~/components/common/ActionTable.vue' import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -28,17 +30,18 @@ interface Props extends SmartSearchProps, OrderingProps {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', defaultQuery: '',
updateUrl: false, updateUrl: false,
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'first_seen'], ['creation_date', 'first_seen'],

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { watchDebounced } from '@vueuse/core' import { watchDebounced } from '@vueuse/core'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
@ -12,27 +13,29 @@ import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import usePage from '~/composables/navigation/usePage'
interface Props extends OrderingProps { interface Props extends OrderingProps {
filters?: object filters?: object
allowListEnabled?: boolean allowListEnabled?: boolean
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
filters: () => ({}), filters: () => ({}),
allowListEnabled: false allowListEnabled: false,
orderingConfigName: undefined
}) })
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['name', 'name'], ['name', 'name'],

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { watchDebounced } from '@vueuse/core' import { watchDebounced } from '@vueuse/core'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
@ -13,25 +14,27 @@ import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import usePage from '~/composables/navigation/usePage'
interface Props extends OrderingProps { interface Props extends OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const page = ref(1) const page = usePage()
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onOrderingUpdate, orderingString, paginateBy, ordering } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['expiration_date', 'expiration_date'], ['expiration_date', 'expiration_date'],

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { watchDebounced } from '@vueuse/core' import { watchDebounced } from '@vueuse/core'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
@ -13,17 +14,18 @@ import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import useOrdering from '~/composables/navigation/useOrdering'
interface Props extends OrderingProps { interface Props extends OrderingProps {
filters?: object filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
filters: () => ({}) filters: () => ({}),
orderingConfigName: undefined
}) })
const page = ref(1) const page = ref(1)
@ -31,7 +33,7 @@ const query = ref('')
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['date_joined', 'date_joined'], ['date_joined', 'date_joined'],

View File

@ -4,6 +4,8 @@ import { range, clamp } from 'lodash-es'
import { computed } from 'vue' import { computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
const RANGE = 2
interface Events { interface Events {
(e: 'update:current', page: number): void (e: 'update:current', page: number): void
} }
@ -24,7 +26,6 @@ const props = withDefaults(defineProps<Props>(), {
const current = useVModel(props, 'current', emit) const current = useVModel(props, 'current', emit)
const RANGE = 2
const pages = computed(() => { const pages = computed(() => {
const start = range(1, 1 + RANGE) const start = range(1, 1 + RANGE)
const end = range(maxPage.value - RANGE + 1, maxPage.value + 1) const end = range(maxPage.value - RANGE + 1, maxPage.value + 1)
@ -33,6 +34,13 @@ const pages = computed(() => {
clamp(props.current + RANGE, 1, maxPage.value) clamp(props.current + RANGE, 1, maxPage.value)
).filter(i => !start.includes(i) && !end.includes(i)) ).filter(i => !start.includes(i) && !end.includes(i))
if (end[0] - 1 <= start[RANGE - 1]) {
return [
...start,
...end.filter(i => i > start[RANGE - 1])
]
}
return [ return [
...start, ...start,
middle.length === 0 && 'skip', middle.length === 0 && 'skip',

View File

@ -0,0 +1,73 @@
import type { RouteRecordName } from 'vue-router'
import { toRefs, useStorage, syncRef } from '@vueuse/core'
import { useRouteQuery } from '@vueuse/router'
import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'
export interface OrderingProps {
orderingConfigName?: RouteRecordName
}
export default (props: OrderingProps) => {
const route = useRoute()
const preferences = useStorage(`route-preferences:${props.orderingConfigName?.toString() ?? route.name?.toString() ?? '*'}`, {
orderingDirection: route.meta.orderingDirection ?? '-',
ordering: route.meta.ordering ?? 'creation_date',
paginateBy: route.meta.paginateBy ?? 50
})
const {
orderingDirection: perfOrderingDirection,
paginateBy: perfPaginateBy,
ordering: perfOrdering
} = toRefs(preferences)
const queryPaginateBy = useRouteQuery<string>('paginateBy', perfPaginateBy.value.toString())
const paginateBy = ref()
syncRef(queryPaginateBy, paginateBy, {
transform: {
ltr: (left) => +left,
rtl: (right) => right.toString()
}
})
const queryOrdering = useRouteQuery('ordering', perfOrderingDirection.value + perfOrdering.value)
console.log(queryOrdering.value)
watch(queryOrdering, (ordering) => {
perfOrderingDirection.value = ordering[0] === '-' ? '-' : '+'
perfOrdering.value = ordering[0] === '-' || ordering[0] === '+'
? ordering.slice(1)
: ordering
})
watch(perfOrderingDirection, (direction) => {
if (direction === '-') {
queryOrdering.value = direction + perfOrdering.value
return
}
queryOrdering.value = perfOrdering.value
})
watch(perfOrdering, (field) => {
const direction = perfOrderingDirection.value
queryOrdering.value = (direction === '-' ? '-' : '') + field
})
watch(queryPaginateBy, (paginateBy) => {
perfPaginateBy.value = +paginateBy
})
const onOrderingUpdate = (fn: () => void) => watch(preferences, fn)
return {
paginateBy,
ordering: perfOrdering,
orderingDirection: perfOrderingDirection,
orderingString: queryOrdering,
onOrderingUpdate
}
}

View File

@ -0,0 +1,16 @@
import { useRouteQuery } from '@vueuse/router'
import { syncRef } from '@vueuse/core'
import { ref } from 'vue'
export default () => {
const pageQuery = useRouteQuery<string>('page', '1')
const page = ref()
syncRef(pageQuery, page, {
transform: {
ltr: (left) => +left,
rtl: (right) => right.toString()
}
})
return page
}

View File

@ -1,18 +1,17 @@
import type { MaybeRef } from '@vueuse/core'
import type { Token } from '~/utils/search' import type { Token } from '~/utils/search'
import { refWithControl } from '@vueuse/core'
import { computed, ref, unref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { compileTokens, normalizeQuery, parseTokens } from '~/utils/search' import { compileTokens, normalizeQuery, parseTokens } from '~/utils/search'
import { refWithControl } from '@vueuse/core'
import { computed, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
export interface SmartSearchProps { export interface SmartSearchProps {
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
export default (defaultQuery: MaybeRef<string>, updateUrl: MaybeRef<boolean>) => { export default (props: SmartSearchProps) => {
const query = refWithControl(unref(defaultQuery)) const query = refWithControl(props.defaultQuery ?? '')
const tokens = ref([] as Token[]) const tokens = ref([] as Token[])
watch(query, (value) => { watch(query, (value) => {
@ -28,7 +27,7 @@ export default (defaultQuery: MaybeRef<string>, updateUrl: MaybeRef<boolean>) =>
const router = useRouter() const router = useRouter()
watch(tokens, (value) => { watch(tokens, (value) => {
const newQuery = compileTokens(value) const newQuery = compileTokens(value)
if (unref(updateUrl)) { if (props.updateUrl) {
return router.replace({ query: { q: newQuery } }) return router.replace({ query: { q: newQuery } })
} }
@ -67,7 +66,6 @@ export default (defaultQuery: MaybeRef<string>, updateUrl: MaybeRef<boolean>) =>
return return
} }
// TODO (wvffle): Check if triggers reactivity
for (const token of existing) { for (const token of existing) {
token.value = value token.value = value
} }

View File

@ -54,6 +54,10 @@ async function useErrorHandler (error: Error | BackendError, eventId?: string):
} }
} }
if ('isHandled' in error && error.isHandled) {
return
}
store.commit('ui/addMessage', { store.commit('ui/addMessage', {
content, content,
date, date,

View File

@ -1,44 +0,0 @@
import type { OrderingDirection, OrderingField, RouteWithPreferences } from '~/store/ui'
import type { MaybeRef } from '@vueuse/core'
import { reactiveComputed, toRefs } from '@vueuse/core'
import { computed, unref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from '~/store'
export interface OrderingProps {
orderingConfigName: RouteWithPreferences | null
}
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
}
}

View File

@ -30,6 +30,7 @@ export const install: InitModule = ({ store, router }) => {
return response return response
}, async (error: BackendError) => { }, async (error: BackendError) => {
error.backendErrors = [] error.backendErrors = []
error.isHandled = false
if (store.state.auth.authenticated && !store.state.auth.oauth.accessToken && error.response?.status === 401) { if (store.state.auth.authenticated && !store.state.auth.oauth.accessToken && error.response?.status === 401) {
store.commit('auth/authenticated', false) store.commit('auth/authenticated', false)
@ -40,8 +41,10 @@ export const install: InitModule = ({ store, router }) => {
switch (error.response?.status) { switch (error.response?.status) {
case 404: case 404:
error.backendErrors.push('Resource not found') error.backendErrors.push('Resource not found')
error.isHandled = true
store.commit('ui/addMessage', { store.commit('ui/addMessage', {
content: error.response?.data, // @ts-expect-error TS does not know about .data structure
content: error.response?.data?.detail ?? error.response?.data,
class: 'error' class: 'error'
}) })
break break
@ -71,6 +74,7 @@ export const install: InitModule = ({ store, router }) => {
} }
error.backendErrors.push(message) error.backendErrors.push(message)
error.isHandled = true
store.commit('ui/addMessage', { store.commit('ui/addMessage', {
content: message, content: message,
date: new Date(), date: new Date(),

View File

@ -54,13 +54,7 @@ export default [
{ {
path: '/search', path: '/search',
name: 'search', name: 'search',
component: () => import('~/views/Search.vue'), component: () => import('~/views/Search.vue')
props: route => ({
initialId: route.query.id,
initialType: route.query.type || 'artists',
initialQuery: route.query.q,
initialPage: route.query.page ? +route.query.page : undefined
})
}, },
...auth, ...auth,
...settings, ...settings,

View File

@ -20,96 +20,60 @@ export default [
path: 'artists/', path: 'artists/',
name: 'library.artists.browse', name: 'library.artists.browse',
component: () => import('~/components/library/Artists.vue'), component: () => import('~/components/library/Artists.vue'),
props: route => ({ meta: {
defaultOrdering: route.query.ordering, paginateBy: 30
defaultQuery: route.query.query, }
defaultTags: Array.isArray(route.query.tag || [])
? route.query.tag
: [route.query.tag],
defaultPage: route.query.page ? +route.query.page : undefined,
orderingConfigName: null
})
}, },
{ {
path: 'me/artists', path: 'me/artists',
name: 'library.artists.me', name: 'library.artists.me',
component: () => import('~/components/library/Artists.vue'), component: () => import('~/components/library/Artists.vue'),
props: route => ({ props: { scope: 'me' },
scope: 'me', meta: {
defaultOrdering: route.query.ordering, paginateBy: 30
defaultQuery: route.query.query, }
defaultTags: Array.isArray(route.query.tag || [])
? route.query.tag
: [route.query.tag],
defaultPage: route.query.page ? +route.query.page : undefined,
orderingConfigName: null
})
}, },
{ {
path: 'albums/', path: 'albums/',
name: 'library.albums.browse', name: 'library.albums.browse',
component: () => import('~/components/library/Albums.vue'), component: () => import('~/components/library/Albums.vue'),
props: route => ({ meta: {
defaultOrdering: route.query.ordering, paginateBy: 25
defaultQuery: route.query.query, }
defaultTags: Array.isArray(route.query.tag || [])
? route.query.tag
: [route.query.tag],
defaultPage: route.query.page ? +route.query.page : undefined,
orderingConfigName: null
})
},
{
path: 'podcasts/',
name: 'library.podcasts.browse',
component: () => import('~/components/library/Podcasts.vue'),
props: route => ({
defaultOrdering: route.query.ordering,
defaultQuery: route.query.query,
defaultTags: Array.isArray(route.query.tag || [])
? route.query.tag
: [route.query.tag],
defaultPage: route.query.page ? +route.query.page : undefined,
orderingConfigName: null
})
}, },
{ {
path: 'me/albums', path: 'me/albums',
name: 'library.albums.me', name: 'library.albums.me',
component: () => import('~/components/library/Albums.vue'), component: () => import('~/components/library/Albums.vue'),
props: route => ({ props: { scope: 'me' },
scope: 'me', meta: {
defaultOrdering: route.query.ordering, paginateBy: 25
defaultQuery: route.query.query, }
defaultTags: Array.isArray(route.query.tag || []) },
? route.query.tag {
: [route.query.tag], path: 'podcasts/',
defaultPage: route.query.page ? +route.query.page : undefined, name: 'library.podcasts.browse',
orderingConfigName: null component: () => import('~/components/library/Podcasts.vue'),
}) meta: {
paginateBy: 30
}
}, },
{ {
path: 'radios/', path: 'radios/',
name: 'library.radios.browse', name: 'library.radios.browse',
component: () => import('~/components/library/Radios.vue'), component: () => import('~/components/library/Radios.vue'),
props: route => ({ meta: {
defaultOrdering: route.query.ordering, paginateBy: 12
defaultQuery: route.query.query, }
defaultPage: route.query.page ? +route.query.page : undefined,
orderingConfigName: null
})
}, },
{ {
path: 'me/radios/', path: 'me/radios/',
name: 'library.radios.me', name: 'library.radios.me',
component: () => import('~/components/library/Radios.vue'), component: () => import('~/components/library/Radios.vue'),
props: route => ({ props: { scope: 'me' },
scope: 'me', meta: {
defaultOrdering: route.query.ordering, paginateBy: 12
defaultQuery: route.query.query, }
defaultPage: route.query.page ? +route.query.page : undefined,
orderingConfigName: null
})
}, },
{ {
path: 'radios/build', path: 'radios/build',
@ -133,24 +97,18 @@ export default [
path: 'playlists/', path: 'playlists/',
name: 'library.playlists.browse', name: 'library.playlists.browse',
component: () => import('~/views/playlists/List.vue'), component: () => import('~/views/playlists/List.vue'),
props: route => ({ meta: {
defaultOrdering: route.query.ordering, paginateBy: 25
defaultQuery: route.query.query, }
defaultPage: route.query.page ? +route.query.page : undefined,
orderingConfigName: null
})
}, },
{ {
path: 'me/playlists/', path: 'me/playlists/',
name: 'library.playlists.me', name: 'library.playlists.me',
component: () => import('~/views/playlists/List.vue'), component: () => import('~/views/playlists/List.vue'),
props: route => ({ props: { scope: 'me' },
scope: 'me', meta: {
defaultOrdering: route.query.ordering, paginateBy: 25
defaultQuery: route.query.query, }
defaultPage: route.query.page ? +route.query.page : undefined,
orderingConfigName: null
})
}, },
{ {
path: 'playlists/:id', path: 'playlists/:id',

View File

@ -157,7 +157,9 @@ export default [
path: 'reports', path: 'reports',
name: 'manage.moderation.reports.list', name: 'manage.moderation.reports.list',
component: () => import('~/views/admin/moderation/ReportsList.vue'), component: () => import('~/views/admin/moderation/ReportsList.vue'),
props: route => ({ defaultQuery: route.query.q, updateUrl: true, orderingConfigName: null }) meta: {
paginateBy: 25
}
}, },
{ {
path: 'reports/:id', path: 'reports/:id',
@ -169,7 +171,9 @@ export default [
path: 'requests', path: 'requests',
name: 'manage.moderation.requests.list', name: 'manage.moderation.requests.list',
component: () => import('~/views/admin/moderation/RequestsList.vue'), component: () => import('~/views/admin/moderation/RequestsList.vue'),
props: route => ({ defaultQuery: route.query.q, updateUrl: true, orderingConfigName: null }) meta: {
paginateBy: 25
}
}, },
{ {
path: 'requests/:id', path: 'requests/:id',

9
front/src/shims-vue-router.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
orderingDirection?: '-' | '+'
ordering?: OrderingField
paginateBy?: number
}
}

View File

@ -1,6 +1,6 @@
/* eslint-disable */
declare module '*.vue' { declare module '*.vue' {
import type { DefineComponent } from 'vue' import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any> const component: DefineComponent<{}, {}, any>
export default component export default component
} }

View File

@ -7,15 +7,6 @@ import moment from 'moment'
type SupportedExtension = 'flac' | 'ogg' | 'mp3' | 'opus' | 'aac' | 'm4a' | 'aiff' | 'aif' type SupportedExtension = 'flac' | 'ogg' | 'mp3' | 'opus' | 'aac' | 'm4a' | 'aiff' | 'aif'
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'
| 'manage.library.libraries' | 'manage.library.tracks' | 'manage.library.albums' | 'manage.library.artists'
| 'manage.library.edits' | 'manage.users.users.list' | 'manage.users.invitations.list'
| 'manage.moderation.accounts.list' | 'manage.moderation.domains.list' | 'manage.moderation.requests.list'
| 'manage.moderation.reports.list' | 'library.albums.browse'
export type WebSocketEventName = 'inbox.item_added' | 'import.status_updated' | 'mutation.created' | 'mutation.updated' export type WebSocketEventName = 'inbox.item_added' | 'import.status_updated' | 'mutation.created' | 'mutation.updated'
| 'report.created' | 'user_request.created' | 'Listen' | 'report.created' | 'user_request.created' | 'Listen'
@ -26,12 +17,6 @@ export type OrderingField = 'creation_date' | 'title' | 'album__title' | 'artist
| 'last_activity' | 'username' | 'last_activity' | 'username'
export type OrderingDirection = '-' | '+' export type OrderingDirection = '-' | '+'
interface RoutePreferences {
paginateBy: number
orderingDirection: OrderingDirection
ordering: OrderingField
}
interface WebSocketEvent { interface WebSocketEvent {
type: WebSocketEventName type: WebSocketEventName
} }
@ -63,7 +48,6 @@ export interface State {
notifications: Record<NotificationsKey, number> notifications: Record<NotificationsKey, number>
websocketEventsHandlers: Record<WebSocketEventName, WebSocketHandlers> websocketEventsHandlers: Record<WebSocketEventName, WebSocketHandlers>
routePreferences: Record<RouteWithPreferences, RoutePreferences>
} }
const store: Module<State, RootState> = { const store: Module<State, RootState> = {
@ -97,149 +81,7 @@ const store: Module<State, RootState> = {
'user_request.created': {}, 'user_request.created': {},
Listen: {} Listen: {}
}, },
pageTitle: null, pageTitle: null
routePreferences: {
'library.albums.browse': {
paginateBy: 25,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.artists.browse': {
paginateBy: 30,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.podcasts.browse': {
paginateBy: 30,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.radios.browse': {
paginateBy: 12,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.playlists.browse': {
paginateBy: 25,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.albums.me': {
paginateBy: 25,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.artists.me': {
paginateBy: 30,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.radios.me': {
paginateBy: 12,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.playlists.me': {
paginateBy: 25,
orderingDirection: '-',
ordering: 'creation_date'
},
'content.libraries.files': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.detail.upload': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.detail.edit': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'library.detail': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
favorites: {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.channels': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.library.tags': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.library.uploads': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.library.libraries': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.library.tracks': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.library.albums': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.library.artists': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.library.edits': {
paginateBy: 25,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.users.users.list': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.users.invitations.list': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.moderation.accounts.list': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.moderation.domains.list': {
paginateBy: 50,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.moderation.requests.list': {
paginateBy: 25,
orderingDirection: '-',
ordering: 'creation_date'
},
'manage.moderation.reports.list': {
paginateBy: 25,
orderingDirection: '-',
ordering: 'creation_date'
}
}
}, },
getters: { getters: {
showInstanceSupportMessage: (state, getters, rootState) => { showInstanceSupportMessage: (state, getters, rootState) => {

View File

@ -218,6 +218,7 @@ export interface Listening {
export interface APIErrorResponse extends Record<string, APIErrorResponse | string[] | { code: string }[]> {} export interface APIErrorResponse extends Record<string, APIErrorResponse | string[] | { code: string }[]> {}
export interface BackendError extends AxiosError { export interface BackendError extends AxiosError {
isHandled: boolean
backendErrors: string[] backendErrors: string[]
rawPayload?: APIErrorResponse rawPayload?: APIErrorResponse
} }

View File

@ -1,39 +1,42 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RadioConfig } from '~/store/radios' import type { RadioConfig } from '~/store/radios'
import { ref, reactive, computed, watch } from 'vue'
import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext'
import { syncRef } from '@vueuse/core'
import axios from 'axios'
import PlaylistCardList from '~/components/playlists/CardList.vue'
import RemoteSearchForm from '~/components/RemoteSearchForm.vue' import RemoteSearchForm from '~/components/RemoteSearchForm.vue'
import ArtistCard from '~/components/audio/artist/Card.vue' import ArtistCard from '~/components/audio/artist/Card.vue'
import AlbumCard from '~/components/audio/album/Card.vue'
import TrackTable from '~/components/audio/track/Table.vue' import TrackTable from '~/components/audio/track/Table.vue'
import AlbumCard from '~/components/audio/album/Card.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import PlaylistCardList from '~/components/playlists/CardList.vue'
import RadioCard from '~/components/radios/Card.vue'
import RadioButton from '~/components/radios/Button.vue' import RadioButton from '~/components/radios/Button.vue'
import RadioCard from '~/components/radios/Card.vue'
import TagsList from '~/components/tags/List.vue' import TagsList from '~/components/tags/List.vue'
import { ref, reactive, computed, watch } from 'vue'
import { useRouter, onBeforeRouteUpdate } from 'vue-router' import useErrorHandler from '~/composables/useErrorHandler'
import { useGettext } from 'vue3-gettext'
import axios from 'axios'
type QueryType = 'artists' | 'albums' | 'tracks' | 'playlists' | 'tags' | 'radios' | 'podcasts' | 'series' | 'rss' type QueryType = 'artists' | 'albums' | 'tracks' | 'playlists' | 'tags' | 'radios' | 'podcasts' | 'series' | 'rss'
interface Props { const type = useRouteQuery<QueryType>('type', 'artists')
initialId?: string const id = useRouteQuery<string>('id')
initialType?: QueryType
initialQuery?: string
initialPage?: number
}
const props = withDefaults(defineProps<Props>(), { const pageQuery = useRouteQuery<string>('page', '1')
initialId: '', const page = ref(+pageQuery.value)
initialType: 'artists', syncRef(pageQuery, page, {
initialQuery: '', transform: {
initialPage: 1 ltr: (left) => +left,
rtl: (right) => right.toString()
}
}) })
const query = ref(props.initialQuery) const q = useRouteQuery('q', '')
const type = ref(props.initialType) const query = ref(q.value)
const page = ref(props.initialPage) syncRef(q, query, { direction: 'ltr' })
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const results = reactive({ const results = reactive({
@ -122,19 +125,12 @@ const axiosParams = computed(() => {
const currentResults = computed(() => results[currentType.value?.id ?? 'artists']) const currentResults = computed(() => results[currentType.value?.id ?? 'artists'])
const router = useRouter()
const updateQueryString = () => router.replace({
query: {
q: query.value,
page: page.value,
type: type.value
}
})
const isLoading = ref(false) const isLoading = ref(false)
const search = async () => { const search = async () => {
if (!currentType.value) return if (!currentType.value) return
q.value = query.value
if (!query.value) { if (!query.value) {
for (const type of types.value) { for (const type of types.value) {
results[type.id] = null results[type.id] = null
@ -144,11 +140,16 @@ const search = async () => {
} }
isLoading.value = true isLoading.value = true
try {
const response = await axios.get(currentType.value.endpoint ?? currentType.value.id, { const response = await axios.get(currentType.value.endpoint ?? currentType.value.id, {
params: axiosParams.value params: axiosParams.value
}) })
results[currentType.value.id] = response.data results[currentType.value.id] = response.data
} catch (error) {
useErrorHandler(error as Error)
}
isLoading.value = false isLoading.value = false
@ -164,25 +165,20 @@ const search = async () => {
} }
}).then(response => { }).then(response => {
results[type.id] = response.data results[type.id] = response.data
}) }).catch(() => undefined)
} }
} }
} }
watch(type, () => (page.value = 1)) watch(type, () => {
watch(page, updateQueryString) page.value = 1
search()
})
onBeforeRouteUpdate(search) search()
// TODO: (wvffle): Check if it's needed
// watch: {
// '$route.query.q': async function (v) {
// this.query = v
// }
// },
const labels = computed(() => ({ const labels = computed(() => ({
title: props.initialId title: id.value
? ( ? (
type.value === 'rss' type.value === 'rss'
? $pgettext('Head/Fetch/Title', 'Subscribe to a podcast RSS feed') ? $pgettext('Head/Fetch/Title', 'Subscribe to a podcast RSS feed')
@ -224,13 +220,13 @@ const radioConfig = computed(() => {
> >
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<div <div
v-if="initialId" v-if="id"
class="ui small text container" class="ui small text container"
> >
<h2>{{ labels.title }}</h2> <h2>{{ labels.title }}</h2>
<remote-search-form <remote-search-form
:initial-id="initialId" :initial-id="id"
:type="initialType" :type="type"
/> />
</div> </div>
<div <div

View File

@ -54,58 +54,44 @@ const title = computed(() => labels.value[props.type])
v-if="type === 'accounts'" v-if="type === 'accounts'"
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
/> />
<albums-table <albums-table
v-else-if="type === 'albums'" v-else-if="type === 'albums'"
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
/> />
<artists-table <artists-table
v-else-if="type === 'artists'" v-else-if="type === 'artists'"
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
/> />
<channels-table <channels-table
v-else-if="type === 'channels'" v-else-if="type === 'channels'"
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
/>
<invitations-table
v-else-if="type === 'invitations'"
:ordering-config-name="null"
/> />
<invitations-table v-else-if="type === 'invitations'" />
<libraries-table <libraries-table
v-else-if="type === 'libraries'" v-else-if="type === 'libraries'"
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
/> />
<tags-table <tags-table
v-else-if="type === 'tags'" v-else-if="type === 'tags'"
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
/> />
<tracks-table <tracks-table
v-else-if="type === 'tracks'" v-else-if="type === 'tracks'"
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
/> />
<uploads-table <uploads-table
v-else-if="type === 'uploads'" v-else-if="type === 'uploads'"
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
/>
<users-table
v-else-if="type === 'users'"
:ordering-config-name="null"
/> />
<users-table v-else-if="type === 'users'" />
</section> </section>
</main> </main>
</template> </template>

View File

@ -24,7 +24,6 @@ const labels = computed(() => ({
<edits-card-list <edits-card-list
:update-url="true" :update-url="true"
:default-query="defaultQuery" :default-query="defaultQuery"
:ordering-config-name="null"
> >
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Admin/Title/Noun"> <translate translate-context="Content/Admin/Title/Noun">

View File

@ -113,10 +113,7 @@ const createDomain = async () => {
</div> </div>
</form> </form>
<div class="ui clearing hidden divider" /> <div class="ui clearing hidden divider" />
<domains-table <domains-table :allow-list-enabled="allowListEnabled" />
:ordering-config-name="null"
:allow-list-enabled="allowListEnabled"
/>
</section> </section>
</main> </main>
</template> </template>

View File

@ -1,7 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { Report, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -13,35 +15,33 @@ import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdo
import ReportCard from '~/components/manage/moderation/ReportCard.vue' import ReportCard from '~/components/manage/moderation/ReportCard.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
mode?: 'card' mode?: 'card'
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '',
updateUrl: false, updateUrl: false,
mode: 'card' mode: 'card',
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
// TODO (wvffle): Make sure everything is it's own type const page = usePage()
const page = ref(1) const result = ref<BackendResponse<Report>>()
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],
@ -74,7 +74,7 @@ const fetchData = async () => {
} }
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
isLoading.value = false isLoading.value = false
} }

View File

@ -1,7 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { OrderingProps } from '~/composables/useOrdering' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { UserRequest, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -12,32 +14,30 @@ import axios from 'axios'
import UserRequestCard from '~/components/manage/moderation/UserRequestCard.vue' import UserRequestCard from '~/components/manage/moderation/UserRequestCard.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps { interface Props extends SmartSearchProps, OrderingProps {
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultQuery: '', updateUrl: false,
updateUrl: false orderingConfigName: undefined
}) })
const search = ref() const search = ref()
// TODO (wvffle): Make sure everything is it's own type const page = usePage()
const page = ref(1) const result = ref<BackendResponse<UserRequest>>()
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],
@ -70,7 +70,7 @@ const fetchData = async () => {
} }
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
isLoading.value = false isLoading.value = false
} }

View File

@ -10,9 +10,6 @@ defineProps<Props>()
<template> <template>
<section class="ui vertical aligned stripe segment"> <section class="ui vertical aligned stripe segment">
<library-files-table <library-files-table :default-query="query" />
:ordering-config-name="null"
:default-query="query"
/>
</section> </section>
</template> </template>

View File

@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { ImportStatus, BackendResponse, Upload } from '~/types'
import type { ImportStatus } from '~/types' import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { humanSize, truncate } from '~/utils/filters' import { humanSize, truncate } from '~/utils/filters'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
@ -16,9 +17,10 @@ import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch' import usePage from '~/composables/navigation/usePage'
import useOrdering from '~/composables/useOrdering'
interface Events { interface Events {
(e: 'fetch-start'): void (e: 'fetch-start'): void
@ -30,7 +32,7 @@ interface Props extends SmartSearchProps, OrderingProps {
customObjects?: any[] customObjects?: any[]
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
defaultQuery?: string defaultQuery?: string
updateUrl?: boolean updateUrl?: boolean
} }
@ -41,18 +43,17 @@ const props = withDefaults(defineProps<Props>(), {
updateUrl: false, updateUrl: false,
filters: () => ({}), filters: () => ({}),
needsRefresh: false, needsRefresh: false,
customObjects: () => [] customObjects: () => [],
orderingConfigName: undefined
}) })
const search = ref() const search = ref()
// TODO (wvffle): Make sure everything is it's own type const page = usePage()
const page = ref(1) const result = ref<BackendResponse<Upload>>()
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],
@ -79,8 +80,7 @@ const actions = computed(() => [
label: $pgettext('Content/Library/Dropdown/Verb', 'Restart import'), label: $pgettext('Content/Library/Dropdown/Verb', 'Restart import'),
isDangerous: true, isDangerous: true,
allowAll: true, allowAll: true,
// TODO (wvffle): Find correct type filterCheckable: (filter: { import_status: ImportStatus }) => {
filterCheckable: (filter: { import_status: string }) => {
return filter.import_status !== 'finished' return filter.import_status !== 'finished'
} }
} }
@ -107,7 +107,7 @@ const fetchData = async () => {
result.value = response.data result.value = response.data
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
isLoading.value = false isLoading.value = false
} }

View File

@ -63,10 +63,7 @@ const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
Library contents Library contents
</translate> </translate>
</h2> </h2>
<library-files-table <library-files-table :filters="{ library: object.uuid }" />
:filters="{library: object.uuid}"
:ordering-config-name="null"
/>
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<h2 class="ui header"> <h2 class="ui header">

View File

@ -1,41 +1,46 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { OrderingProps } from '~/composables/navigation/useOrdering'
import type { OrderingProps } from '~/composables/useOrdering' import type { Playlist, BackendResponse } from '~/types'
import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui'
import { computed, onMounted, ref, watch } from 'vue'
import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext'
import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es'
import axios from 'axios' import axios from 'axios'
import $ from 'jquery' import $ from 'jquery'
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
import { computed, ref, watch, onMounted } from 'vue'
import { useGettext } from 'vue3-gettext'
import PlaylistCardList from '~/components/playlists/CardList.vue' import PlaylistCardList from '~/components/playlists/CardList.vue'
import Pagination from '~/components/vui/Pagination.vue' import Pagination from '~/components/vui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering' import usePage from '~/composables/navigation/usePage'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
interface Props extends OrderingProps { interface Props extends OrderingProps {
defaultPage?: number scope?: 'me' | 'all'
defaultQuery?: string
scope?: string
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged // TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
orderingConfigName: RouteWithPreferences | null orderingConfigName?: RouteRecordName
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
defaultPage: 1, scope: 'all',
defaultQuery: '', orderingConfigName: undefined
scope: 'all'
}) })
const page = ref(+props.defaultPage) const page = usePage()
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const q = useRouteQuery('query', '')
const query = ref(props.defaultQuery) const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' })
const result = ref<BackendResponse<Playlist>>()
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],
@ -46,20 +51,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
const logger = useLogger() const logger = useLogger()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props)
const router = useRouter()
const updateQueryString = () => router.replace({
query: {
query: query.value,
page: page.value,
paginateBy: paginateBy.value,
ordering: orderingString.value
}
})
watch(page, updateQueryString)
onOrderingUpdate(updateQueryString)
const isLoading = ref(false) const isLoading = ref(false)
const fetchData = async () => { const fetchData = async () => {
@ -82,15 +74,25 @@ const fetchData = async () => {
result.value = response.data result.value = response.data
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
logger.timeEnd('Fetching albums') logger.timeEnd('Fetching albums')
isLoading.value = false isLoading.value = false
} }
} }
onBeforeRouteUpdate(fetchData) watch(page, fetchData)
fetchData() fetchData()
const search = () => {
page.value = 1
q.value = query.value
}
onOrderingUpdate(() => {
page.value = 1
fetchData()
})
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { $pgettext } = useGettext()
@ -98,6 +100,8 @@ const labels = computed(() => ({
playlists: $pgettext('*/*/*', 'Playlists'), playlists: $pgettext('*/*/*', 'Playlists'),
searchPlaceholder: $pgettext('Content/Playlist/Placeholder/Call to action', 'Enter playlist name…') searchPlaceholder: $pgettext('Content/Playlist/Placeholder/Call to action', 'Enter playlist name…')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b)))
</script> </script>
<template> <template>
@ -121,7 +125,7 @@ const labels = computed(() => ({
</template> </template>
<form <form
:class="['ui', {'loading': isLoading}, 'form']" :class="['ui', {'loading': isLoading}, 'form']"
@submit.prevent="updateQueryString();fetchData()" @submit.prevent="search"
> >
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
@ -185,14 +189,12 @@ const labels = computed(() => ({
v-model="paginateBy" v-model="paginateBy"
class="ui dropdown" class="ui dropdown"
> >
<option :value="12"> <option
12 v-for="opt in paginateOptions"
</option> :key="opt"
<option :value="25"> :value="opt"
25 >
</option> {{ opt }}
<option :value="50">
50
</option> </option>
</select> </select>
</div> </div>

View File

@ -1905,6 +1905,14 @@
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.1.1.tgz#b3fe4b97e62096f7566cd8eb107c503998b2c9a6" resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.1.1.tgz#b3fe4b97e62096f7566cd8eb107c503998b2c9a6"
integrity sha512-XZ2KtSW+85LLHB/IdGILPAtbIVHasPsAW7aqz3BRMzJdAQWRiM/FGa1OKBwLbXtUw/AmjKYFlZJo7eOFIBXRog== integrity sha512-XZ2KtSW+85LLHB/IdGILPAtbIVHasPsAW7aqz3BRMzJdAQWRiM/FGa1OKBwLbXtUw/AmjKYFlZJo7eOFIBXRog==
"@vueuse/router@^9.1.1":
version "9.1.1"
resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-9.1.1.tgz#356946b97e2499d96d8e9b5cfced5f778edd63c9"
integrity sha512-1HE09QYoHEUF2vWJqGEV1GgoFy6ti7gxzahiN9o/GJpyWM11koQd03BhP4RjVbUx3ua2wTYNSmaCKvLJGCnNGg==
dependencies:
"@vueuse/shared" "9.1.1"
vue-demi "*"
"@vueuse/shared@9.1.1": "@vueuse/shared@9.1.1":
version "9.1.1" version "9.1.1"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.1.1.tgz#811f47629e281a19013ae6dcdf11ed3e1e91e023" resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.1.1.tgz#811f47629e281a19013ae6dcdf11ed3e1e91e023"