Migrate rest of ordering/pagination mixins to composables
This commit is contained in:
parent
21e5d8ddf0
commit
a8fd0e3f28
|
@ -42,7 +42,7 @@ const logger = useLogger()
|
|||
const sharedLabels = useSharedLabels()
|
||||
|
||||
const router = useRouter()
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName, props.defaultPaginateBy)
|
||||
|
||||
const updateQueryString = () => router.replace({
|
||||
query: {
|
||||
|
|
|
@ -47,7 +47,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
|||
const logger = useLogger()
|
||||
const sharedLabels = useSharedLabels()
|
||||
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName, props.defaultPaginateBy)
|
||||
|
||||
const router = useRouter()
|
||||
const updateQueryString = () => router.replace({
|
||||
|
|
|
@ -47,7 +47,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
|||
const logger = useLogger()
|
||||
const sharedLabels = useSharedLabels()
|
||||
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName, props.defaultPaginateBy)
|
||||
|
||||
const router = useRouter()
|
||||
const updateQueryString = () => router.replace({
|
||||
|
|
|
@ -50,7 +50,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
|||
const logger = useLogger()
|
||||
const sharedLabels = useSharedLabels()
|
||||
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName, props.defaultPaginateBy)
|
||||
|
||||
const router = useRouter()
|
||||
const updateQueryString = () => router.replace({
|
||||
|
|
|
@ -42,7 +42,7 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
|||
const logger = useLogger()
|
||||
const sharedLabels = useSharedLabels()
|
||||
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName, props.defaultPaginateBy)
|
||||
|
||||
const router = useRouter()
|
||||
const updateQueryString = () => router.replace({
|
||||
|
|
|
@ -34,7 +34,8 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
|||
]
|
||||
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = []
|
||||
// TODO (wvffle): Find correct type
|
||||
const actions: unknown[] = []
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
|
@ -65,11 +66,7 @@ const fetchData = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
onSearch(() => {
|
||||
page.value = 1
|
||||
fetchData()
|
||||
})
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
|
|
@ -76,11 +76,7 @@ const fetchData = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
onSearch(() => {
|
||||
page.value = 1
|
||||
fetchData()
|
||||
})
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
|
|
@ -74,11 +74,7 @@ const fetchData = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
onSearch(() => {
|
||||
page.value = 1
|
||||
fetchData()
|
||||
})
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
|
|
@ -81,7 +81,8 @@ const fetchTargets = async () => {
|
|||
targets[key as keyof typeof targets][payload.id] = {
|
||||
payload,
|
||||
currentState: configs[key as keyof typeof targets].fields.reduce((state, field) => {
|
||||
state[field.id] = { value: field.getValue(payload) }
|
||||
// TODO (wvffle): This cast may result in an `undefined` key added, make sure to test that.
|
||||
state[field.id as EditObjectType] = { value: field.getValue(payload) }
|
||||
return state
|
||||
}, {} as Record<EditObjectType, { value: unknown }>)
|
||||
}
|
||||
|
@ -119,17 +120,13 @@ const fetchData = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
onSearch(() => {
|
||||
page.value = 1
|
||||
fetchData()
|
||||
})
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const { $pgettext } = useGettext()
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by account, summary, domain…')
|
||||
}))
|
||||
|
|
|
@ -76,11 +76,7 @@ const fetchData = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
onSearch(() => {
|
||||
page.value = 1
|
||||
fetchData()
|
||||
})
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
|
|
@ -1,16 +1,112 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||
import { truncate } from '~/utils/filters'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import useSmartSearch, { SmartSearchProps } from '~/composables/useSmartSearch'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
interface Props extends SmartSearchProps, OrderingProps {
|
||||
// TODO (wvffle): find object type
|
||||
filters?: object
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultQuery: '',
|
||||
updateUrl: false,
|
||||
filters: () => ({})
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
type ResponseType = { count: number, results: any[] }
|
||||
const result = ref<null | ResponseType>(null)
|
||||
|
||||
const { onSearch, query } = useSmartSearch(props.defaultQuery, props.updateUrl)
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'creation_date'],
|
||||
['name', 'name'],
|
||||
['length', 'length'],
|
||||
['items_count', 'items_count']
|
||||
]
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = [
|
||||
{
|
||||
name: 'delete',
|
||||
label: $pgettext('*/*/*/Verb', 'Delete'),
|
||||
confirmationMessage: $pgettext('Popup/*/Paragraph', 'The selected tag will be removed and unlinked with existing content, if any. This action is irreversible.'),
|
||||
isDangerous: true,
|
||||
allowAll: false,
|
||||
confirmColor: 'danger'
|
||||
}
|
||||
]
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
...props.filters
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/manage/tags/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by name')
|
||||
}))
|
||||
|
||||
// TODO (wvffle): Check if we can remove the prop
|
||||
const detailedUpload = ref({})
|
||||
const showUploadDetailModal = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui six wide field">
|
||||
<label for="tags-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<form @submit.prevent="search.query = $refs.search.value">
|
||||
<form @submit.prevent="query = $refs.search.value">
|
||||
<input
|
||||
id="tags-search"
|
||||
ref="search"
|
||||
name="search"
|
||||
type="text"
|
||||
:value="search.query"
|
||||
:value="query"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
>
|
||||
</form>
|
||||
|
@ -125,11 +221,10 @@
|
|||
<div>
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
v-model:current="page"
|
||||
:compact="true"
|
||||
:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
|
@ -143,125 +238,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import { normalizeQuery, parseTokens } from '~/utils/search'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||
import { truncate } from '~/utils/filters'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ActionTable,
|
||||
ImportStatusModal
|
||||
},
|
||||
mixins: [OrderingMixin, SmartSearchMixin],
|
||||
props: {
|
||||
filters: { type: Object, required: false, default: () => { return {} } }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels, truncate }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
detailedUpload: {},
|
||||
showUploadDetailModal: false,
|
||||
time,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: {
|
||||
query: this.defaultQuery,
|
||||
tokens: parseTokens(normalizeQuery(this.defaultQuery))
|
||||
},
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['name', 'name'],
|
||||
['length', 'length'],
|
||||
['items_count', 'items_count']
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by name')
|
||||
}
|
||||
},
|
||||
actionFilters () {
|
||||
const currentFilters = {
|
||||
q: this.search.query
|
||||
}
|
||||
if (this.filters) {
|
||||
return merge(currentFilters, this.filters)
|
||||
} else {
|
||||
return currentFilters
|
||||
}
|
||||
},
|
||||
actions () {
|
||||
const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
|
||||
const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected tag will be removed and unlinked with existing content, if any. This action is irreversible.')
|
||||
return [
|
||||
{
|
||||
name: 'delete',
|
||||
label: deleteLabel,
|
||||
confirmationMessage: confirmationMessage,
|
||||
isDangerous: true,
|
||||
allowAll: false,
|
||||
confirmColor: 'danger'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const params = merge({
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search.query,
|
||||
ordering: this.getOrderingAsString()
|
||||
}, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.checked = []
|
||||
axios.get('/manage/tags/', { params: params }).then((response) => {
|
||||
self.isLoading = false
|
||||
self.result = response.data
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,16 +1,103 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import useSmartSearch, { SmartSearchProps } from '~/composables/useSmartSearch'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
|
||||
interface Props extends SmartSearchProps, OrderingProps {
|
||||
// TODO (wvffle): find object type
|
||||
filters?: object
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultQuery: '',
|
||||
updateUrl: false,
|
||||
filters: () => ({})
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
type ResponseType = { count: number, results: any[] }
|
||||
const result = ref<null | ResponseType>(null)
|
||||
|
||||
const { onSearch, query, addSearchToken } = useSmartSearch(props.defaultQuery, props.updateUrl)
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'creation_date']
|
||||
]
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = [
|
||||
{
|
||||
name: 'delete',
|
||||
label: $pgettext('*/*/*/Verb', 'Delete'),
|
||||
confirmationMessage: $pgettext('Popup/*/Paragraph', 'The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible.'),
|
||||
isDangerous: true,
|
||||
allowAll: false,
|
||||
confirmColor: 'danger'
|
||||
}
|
||||
]
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
...props.filters
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/manage/library/tracks/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by domain, title, artist, album, MusicBrainz ID…')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui six wide field">
|
||||
<label for="tracks-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<form @submit.prevent="search.query = $refs.search.value">
|
||||
<form @submit.prevent="query = $refs.search.value">
|
||||
<input
|
||||
id="tracks-search"
|
||||
ref="search"
|
||||
name="search"
|
||||
type="text"
|
||||
:value="search.query"
|
||||
:value="query"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
>
|
||||
</form>
|
||||
|
@ -177,11 +264,10 @@
|
|||
<div>
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
v-model:current="page"
|
||||
:compact="true"
|
||||
:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
|
@ -195,117 +281,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import { normalizeQuery, parseTokens } from '~/utils/search'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ActionTable
|
||||
},
|
||||
mixins: [OrderingMixin, SmartSearchMixin],
|
||||
props: {
|
||||
filters: { type: Object, required: false, default: () => { return {} } }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
time,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: {
|
||||
query: this.defaultQuery,
|
||||
tokens: parseTokens(normalizeQuery(this.defaultQuery))
|
||||
},
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date']
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, title, artist, album, MusicBrainz ID…')
|
||||
}
|
||||
},
|
||||
actionFilters () {
|
||||
const currentFilters = {
|
||||
q: this.search.query
|
||||
}
|
||||
if (this.filters) {
|
||||
return merge(currentFilters, this.filters)
|
||||
} else {
|
||||
return currentFilters
|
||||
}
|
||||
},
|
||||
actions () {
|
||||
const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
|
||||
const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible.')
|
||||
return [
|
||||
{
|
||||
name: 'delete',
|
||||
label: deleteLabel,
|
||||
confirmationMessage: confirmationMessage,
|
||||
isDangerous: true,
|
||||
allowAll: false,
|
||||
confirmColor: 'danger'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const params = merge({
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search.query,
|
||||
ordering: this.getOrderingAsString()
|
||||
}, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.checked = []
|
||||
axios.get('/manage/library/tracks/', { params: params }).then((response) => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,16 +1,118 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||
import { humanSize, truncate } from '~/utils/filters'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import useSmartSearch, { SmartSearchProps } from '~/composables/useSmartSearch'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
import { Upload } from '~/types'
|
||||
|
||||
interface Props extends SmartSearchProps, OrderingProps {
|
||||
// TODO (wvffle): find object type
|
||||
filters?: object
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultQuery: '',
|
||||
updateUrl: false,
|
||||
filters: () => ({})
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
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.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'creation_date'],
|
||||
['modification_date', 'modification_date'],
|
||||
['accessed_date', 'accessed_date'],
|
||||
['size', 'size'],
|
||||
['bitrate', 'bitrate'],
|
||||
['duration', 'duration']
|
||||
]
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = [
|
||||
{
|
||||
name: 'delete',
|
||||
label: $pgettext('*/*/*/Verb', 'Delete'),
|
||||
confirmationMessage: $pgettext('Popup/*/Paragraph', 'The selected upload will be removed. This action is irreversible.'),
|
||||
isDangerous: true,
|
||||
allowAll: false,
|
||||
confirmColor: 'danger'
|
||||
}
|
||||
]
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
...props.filters
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/manage/library/uploads/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by domain, actor, name, reference, source…')
|
||||
}))
|
||||
|
||||
const displayName = (upload: Upload): string => {
|
||||
return upload.filename ?? upload.source ?? upload.uuid
|
||||
}
|
||||
|
||||
const detailedUpload = ref({})
|
||||
const showUploadDetailModal = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui six wide field">
|
||||
<label for="uploads-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<form @submit.prevent="search.query = $refs.search.value">
|
||||
<form @submit.prevent="query = $refs.search.value">
|
||||
<input
|
||||
id="uploads-search"
|
||||
ref="search"
|
||||
name="search"
|
||||
type="text"
|
||||
:value="search.query"
|
||||
:value="query"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
>
|
||||
</form>
|
||||
|
@ -21,7 +123,7 @@
|
|||
id="uploads-visibility"
|
||||
class="ui dropdown"
|
||||
:value="getTokenValue('privacy_level', '')"
|
||||
@change="addSearchToken('privacy_level', $event.target.value)"
|
||||
@change="addSearchToken('privacy_level', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="Content/*/Dropdown">
|
||||
|
@ -45,7 +147,7 @@
|
|||
id="uploads-status"
|
||||
class="ui dropdown"
|
||||
:value="getTokenValue('status', '')"
|
||||
@change="addSearchToken('status', $event.target.value)"
|
||||
@change="addSearchToken('status', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="Content/*/Dropdown">
|
||||
|
@ -111,6 +213,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO (wvffle): Check if :upload shouldn't be v-modl:upload -->
|
||||
<import-status-modal
|
||||
v-model:show="showUploadDetailModal"
|
||||
:upload="detailedUpload"
|
||||
|
@ -286,11 +389,10 @@
|
|||
<div>
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
v-model:current="page"
|
||||
:compact="true"
|
||||
:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
|
@ -304,136 +406,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import { normalizeQuery, parseTokens } from '~/utils/search'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||
import { humanSize, truncate } from '~/utils/filters'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ActionTable,
|
||||
ImportStatusModal
|
||||
},
|
||||
mixins: [OrderingMixin, SmartSearchMixin],
|
||||
props: {
|
||||
filters: { type: Object, required: false, default: function () { return {} } }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels, humanSize, truncate }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
detailedUpload: {},
|
||||
showUploadDetailModal: false,
|
||||
time,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: {
|
||||
query: this.defaultQuery,
|
||||
tokens: parseTokens(normalizeQuery(this.defaultQuery))
|
||||
},
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['modification_date', 'modification_date'],
|
||||
['accessed_date', 'accessed_date'],
|
||||
['size', 'size'],
|
||||
['bitrate', 'bitrate'],
|
||||
['duration', 'duration']
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, actor, name, reference, source…')
|
||||
}
|
||||
},
|
||||
actionFilters () {
|
||||
const currentFilters = {
|
||||
q: this.search.query
|
||||
}
|
||||
if (this.filters) {
|
||||
return merge(currentFilters, this.filters)
|
||||
} else {
|
||||
return currentFilters
|
||||
}
|
||||
},
|
||||
actions () {
|
||||
const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
|
||||
const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected upload will be removed. This action is irreversible.')
|
||||
return [
|
||||
{
|
||||
name: 'delete',
|
||||
label: deleteLabel,
|
||||
confirmationMessage: confirmationMessage,
|
||||
isDangerous: true,
|
||||
allowAll: false,
|
||||
confirmColor: 'danger'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const params = merge({
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search.query,
|
||||
ordering: this.getOrderingAsString()
|
||||
}, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.checked = []
|
||||
axios.get('/manage/library/uploads/', { params: params }).then((response) => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
},
|
||||
displayName (upload) {
|
||||
if (upload.filename) {
|
||||
return upload.filename
|
||||
}
|
||||
if (upload.source) {
|
||||
return upload.source
|
||||
}
|
||||
return upload.uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,16 +1,104 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import useSmartSearch, { SmartSearchProps } from '~/composables/useSmartSearch'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
|
||||
interface Props extends SmartSearchProps, OrderingProps {
|
||||
// TODO (wvffle): find object type
|
||||
filters?: object
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultQuery: '',
|
||||
updateUrl: false,
|
||||
filters: () => ({})
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
type ResponseType = { count: number, results: any[] }
|
||||
const result = ref<null | ResponseType>(null)
|
||||
|
||||
const { onSearch, query, addSearchToken } = useSmartSearch(props.defaultQuery, props.updateUrl)
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'first_seen'],
|
||||
['last_fetch_date', 'last_seen'],
|
||||
['preferred_username', 'username'],
|
||||
['domain', 'domain'],
|
||||
['uploads_count', 'uploads']
|
||||
]
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = [
|
||||
{
|
||||
name: 'purge',
|
||||
label: $pgettext('*/*/*/Verb', 'Purge'),
|
||||
isDangerous: true
|
||||
}
|
||||
]
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
...props.filters
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/manage/accounts/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by domain, username, bio…')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui six wide field">
|
||||
<label for="accounts-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<form @submit.prevent="search.query = $refs.search.value">
|
||||
<form @submit.prevent="query = $refs.search.value">
|
||||
<input
|
||||
id="accounts-search"
|
||||
ref="search"
|
||||
name="search"
|
||||
type="text"
|
||||
:value="search.query"
|
||||
:value="query"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
>
|
||||
</form>
|
||||
|
@ -150,11 +238,10 @@
|
|||
<div>
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
v-model:current="page"
|
||||
:compact="true"
|
||||
:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
|
@ -168,116 +255,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import { normalizeQuery, parseTokens } from '~/utils/search'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ActionTable
|
||||
},
|
||||
mixins: [OrderingMixin, SmartSearchMixin],
|
||||
props: {
|
||||
filters: { type: Object, required: false, default: function () { return {} } }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
time,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: {
|
||||
query: this.defaultQuery,
|
||||
tokens: parseTokens(normalizeQuery(this.defaultQuery))
|
||||
},
|
||||
orderingOptions: [
|
||||
['creation_date', 'first_seen'],
|
||||
['last_fetch_date', 'last_seen'],
|
||||
['preferred_username', 'username'],
|
||||
['domain', 'domain'],
|
||||
['uploads_count', 'uploads']
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, username, bio…')
|
||||
}
|
||||
},
|
||||
actionFilters () {
|
||||
const currentFilters = {
|
||||
q: this.search.query
|
||||
}
|
||||
if (this.filters) {
|
||||
return merge(currentFilters, this.filters)
|
||||
} else {
|
||||
return currentFilters
|
||||
}
|
||||
},
|
||||
actions () {
|
||||
return [
|
||||
{
|
||||
name: 'purge',
|
||||
label: this.$pgettext('*/*/*/Verb', 'Purge'),
|
||||
isDangerous: true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const params = merge({
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search.query,
|
||||
ordering: this.getOrderingAsString()
|
||||
}, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.checked = []
|
||||
axios.get('/manage/accounts/', { params: params }).then((response) => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,112 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
|
||||
interface Props extends OrderingProps {
|
||||
// TODO (wvffle): find object type
|
||||
filters?: object
|
||||
allowListEnabled?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
filters: () => ({}),
|
||||
allowListEnabled: false
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
type ResponseType = { count: number, results: any[] }
|
||||
const result = ref<null | ResponseType>(null)
|
||||
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['name', 'name'],
|
||||
['creation_date', 'first_seen'],
|
||||
['actors_count', 'users'],
|
||||
['outbox_activities_count', 'received_messages']
|
||||
]
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const query = ref('')
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = [
|
||||
{
|
||||
name: 'purge',
|
||||
label: $pgettext('*/*/*/Verb', 'Purge'),
|
||||
isDangerous: true
|
||||
},
|
||||
{
|
||||
name: 'allow_list_add',
|
||||
label: $pgettext('Content/Moderation/Action/Verb', 'Add to allow-list'),
|
||||
filterCheckable: (obj: { allowed: boolean }) => {
|
||||
return !obj.allowed
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'allow_list_remove',
|
||||
label: $pgettext('Content/Moderation/Action/Verb', 'Remove from allow-list'),
|
||||
filterCheckable: (obj: { allowed: boolean }) => {
|
||||
return obj.allowed
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const allowed = ref(null)
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
allowed: allowed.value,
|
||||
...props.filters
|
||||
}
|
||||
|
||||
if (params.allowed === null) {
|
||||
delete params.allowed
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/manage/federation/domains/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watchDebounced(query, () => (page.value = 1), { debounce: 300 })
|
||||
watch(page, fetchData)
|
||||
watch(allowed, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by name…'),
|
||||
allowListTitle: $pgettext('Content/Moderation/Popup', 'This domain is present in your allow-list')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui inline form">
|
||||
|
@ -6,7 +115,7 @@
|
|||
<label for="domains-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<input
|
||||
id="domains-search"
|
||||
v-model="search"
|
||||
v-model="query"
|
||||
name="search"
|
||||
type="text"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
|
@ -161,11 +270,10 @@
|
|||
<div>
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
v-model:current="page"
|
||||
:compact="true"
|
||||
:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
|
@ -179,135 +287,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ActionTable
|
||||
},
|
||||
mixins: [OrderingMixin],
|
||||
props: {
|
||||
filters: { type: Object, required: false, default: function () { return {} } },
|
||||
allowListEnabled: { type: Boolean, default: false }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
time,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: '',
|
||||
allowed: null,
|
||||
orderingOptions: [
|
||||
['name', 'name'],
|
||||
['creation_date', 'first_seen'],
|
||||
['actors_count', 'users'],
|
||||
['outbox_activities_count', 'received_messages']
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by name…'),
|
||||
allowListTitle: this.$pgettext('Content/Moderation/Popup', 'This domain is present in your allow-list')
|
||||
}
|
||||
},
|
||||
actionFilters () {
|
||||
const currentFilters = {
|
||||
q: this.search
|
||||
}
|
||||
if (this.filters) {
|
||||
return merge(currentFilters, this.filters)
|
||||
} else {
|
||||
return currentFilters
|
||||
}
|
||||
},
|
||||
actions () {
|
||||
return [
|
||||
{
|
||||
name: 'purge',
|
||||
label: this.$pgettext('*/*/*/Verb', 'Purge'),
|
||||
isDangerous: true
|
||||
},
|
||||
{
|
||||
name: 'allow_list_add',
|
||||
label: this.$pgettext('Content/Moderation/Action/Verb', 'Add to allow-list'),
|
||||
filterCheckable: (obj) => {
|
||||
return !obj.allowed
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'allow_list_remove',
|
||||
label: this.$pgettext('Content/Moderation/Action/Verb', 'Remove from allow-list'),
|
||||
filterCheckable: (obj) => {
|
||||
return obj.allowed
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
allowed () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const baseFilters = {
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search,
|
||||
ordering: this.getOrderingAsString()
|
||||
}
|
||||
if (this.allowed !== null) {
|
||||
baseFilters.allowed = this.allowed
|
||||
}
|
||||
const params = merge(baseFilters, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.checked = []
|
||||
axios.get('/manage/federation/domains/', { params: params }).then((response) => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,92 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import moment from 'moment'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
|
||||
interface Props extends OrderingProps {
|
||||
// TODO (wvffle): find object type
|
||||
filters?: object
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
filters: () => ({})
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
type ResponseType = { count: number, results: any[] }
|
||||
const result = ref<null | ResponseType>(null)
|
||||
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering } = useOrdering(props.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['expiration_date', 'expiration_date'],
|
||||
['creation_date', 'creation_date']
|
||||
]
|
||||
|
||||
const query = ref('')
|
||||
const isOpen = ref(false)
|
||||
const { $pgettext } = useGettext()
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = [
|
||||
{
|
||||
name: 'delete',
|
||||
label: $pgettext('*/*/*/Verb', 'Delete'),
|
||||
filterCheckable: (obj: { users: unknown[], expiration_date: Date }) => {
|
||||
return obj.users.length === 0 && moment().isBefore(obj.expiration_date)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
is_open: isOpen.value,
|
||||
...props.filters
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/manage/users/invitations/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watchDebounced(query, () => (page.value = 1), { debounce: 300 })
|
||||
watch(isOpen, () => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Admin/Input.Placeholder/Verb', 'Search by username, e-mail address, code…')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui inline form">
|
||||
|
@ -6,7 +95,7 @@
|
|||
<label for="invitations-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<input
|
||||
id="invitations-search"
|
||||
v-model="search"
|
||||
v-model="query"
|
||||
name="search"
|
||||
type="text"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
|
@ -133,11 +222,10 @@
|
|||
<div>
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
v-model:current="page"
|
||||
:compact="true"
|
||||
:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
|
@ -153,120 +241,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import moment from 'moment'
|
||||
import { merge } from 'lodash-es'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ActionTable
|
||||
},
|
||||
mixins: [OrderingMixin],
|
||||
props: {
|
||||
filters: { type: Object, required: false, default: function () { return {} } }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
moment,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: '',
|
||||
isOpen: null,
|
||||
orderingOptions: [
|
||||
['expiration_date', 'expiration_date'],
|
||||
['creation_date', 'creation_date']
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Admin/Input.Placeholder/Verb', 'Search by username, e-mail address, code…')
|
||||
}
|
||||
},
|
||||
actionFilters () {
|
||||
const currentFilters = {
|
||||
q: this.search
|
||||
}
|
||||
if (this.filters) {
|
||||
return merge(currentFilters, this.filters)
|
||||
} else {
|
||||
return currentFilters
|
||||
}
|
||||
},
|
||||
actions () {
|
||||
const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
|
||||
return [
|
||||
{
|
||||
name: 'delete',
|
||||
label: deleteLabel,
|
||||
filterCheckable: (obj) => {
|
||||
return obj.users.length === 0 && moment().isBefore(obj.expiration_date)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
isOpen () {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const params = merge({
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search,
|
||||
is_open: this.isOpen,
|
||||
ordering: this.getOrderingAsString()
|
||||
}, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.checked = []
|
||||
axios.get('/manage/users/invitations/', { params: params }).then((response) => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,97 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
interface Props extends OrderingProps {
|
||||
// TODO (wvffle): find object type
|
||||
filters?: object
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
filters: () => ({})
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
const query = ref('')
|
||||
type ResponseType = { count: number, results: any[] }
|
||||
const result = ref<null | ResponseType>(null)
|
||||
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['date_joined', 'date_joined'],
|
||||
['last_activity', 'last_activity'],
|
||||
['username', 'username']
|
||||
]
|
||||
|
||||
const permissions = computed(() => [
|
||||
{
|
||||
code: 'library',
|
||||
label: $pgettext('*/*/*/Noun', 'Library')
|
||||
},
|
||||
{
|
||||
code: 'moderation',
|
||||
label: $pgettext('*/Moderation/*', 'Moderation')
|
||||
},
|
||||
{
|
||||
code: 'settings',
|
||||
label: $pgettext('*/*/*/Noun', 'Settings')
|
||||
}
|
||||
])
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
// TODO (wvffle): Find correct type
|
||||
const actions: unknown[] = []
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
...props.filters
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/manage/users/users/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watchDebounced(query, () => (page.value = 1), { debounce: 300 })
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by username, e-mail address, name…')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui inline form">
|
||||
|
@ -6,7 +100,7 @@
|
|||
<label for="users-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<input
|
||||
id="users-search"
|
||||
v-model="search"
|
||||
v-model="query"
|
||||
name="search"
|
||||
type="text"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
|
@ -176,11 +270,10 @@
|
|||
<div>
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
v-model:current="page"
|
||||
:compact="true"
|
||||
:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
|
@ -196,129 +289,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ActionTable
|
||||
},
|
||||
mixins: [OrderingMixin],
|
||||
props: {
|
||||
filters: { type: Object, required: false, default: function () { return {} } }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
time,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: '',
|
||||
orderingOptions: [
|
||||
['date_joined', 'date_joined'],
|
||||
['last_activity', 'last_activity'],
|
||||
['username', 'username']
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by username, e-mail address, name…')
|
||||
}
|
||||
},
|
||||
privacyLevels () {
|
||||
return {}
|
||||
},
|
||||
permissions () {
|
||||
return [
|
||||
{
|
||||
code: 'library',
|
||||
label: this.$pgettext('*/*/*/Noun', 'Library')
|
||||
},
|
||||
{
|
||||
code: 'moderation',
|
||||
label: this.$pgettext('*/Moderation/*', 'Moderation')
|
||||
},
|
||||
{
|
||||
code: 'settings',
|
||||
label: this.$pgettext('*/*/*/Noun', 'Settings')
|
||||
}
|
||||
]
|
||||
},
|
||||
actionFilters () {
|
||||
const currentFilters = {
|
||||
q: this.search
|
||||
}
|
||||
if (this.filters) {
|
||||
return merge(currentFilters, this.filters)
|
||||
} else {
|
||||
return currentFilters
|
||||
}
|
||||
},
|
||||
actions () {
|
||||
return [
|
||||
// {
|
||||
// name: 'delete',
|
||||
// label: this.$pgettext('Content/Admin/Button.Label/Verb', 'Delete'),
|
||||
// isDangerous: true
|
||||
// }
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const params = merge({
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search,
|
||||
ordering: this.getOrderingAsString()
|
||||
}, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.checked = []
|
||||
axios.get('/manage/users/users/', { params: params }).then((response) => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
defaultPage: { type: Number, required: false, default: 1 },
|
||||
defaultPaginateBy: { type: Number, required: false, default: 1 }
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -70,6 +70,7 @@ export default () => ({
|
|||
release_date: $pgettext('Content/*/*/Noun', 'Release date'),
|
||||
accessed_date: $pgettext('Content/*/*/Noun', 'Accessed date'),
|
||||
applied_date: $pgettext('Content/*/*/Noun', 'Applied date'),
|
||||
handled_date: $pgettext('Content/*/*/Noun', 'Handled date'),
|
||||
first_seen: $pgettext('Content/Moderation/Dropdown/Noun', 'First seen date'),
|
||||
last_seen: $pgettext('Content/Moderation/Dropdown/Noun', 'Last seen date'),
|
||||
modification_date: $pgettext('Content/Playlist/Dropdown/Noun', 'Modification date'),
|
||||
|
|
|
@ -11,13 +11,13 @@ interface ConfigField {
|
|||
getValueRepr?: (obj: any) => string
|
||||
}
|
||||
|
||||
interface EditableConfigField extends ConfigField {
|
||||
export interface EditableConfigField extends ConfigField {
|
||||
id: EditObjectType
|
||||
}
|
||||
|
||||
export type EditObject = Artist | Album | Track
|
||||
export type EditObjectType = 'artist' | 'album' | 'track'
|
||||
type Configs = Record<EditObjectType, { fields: EditableConfigField[] }>
|
||||
type Configs = Record<EditObjectType, { fields: (EditableConfigField|ConfigField)[] }>
|
||||
|
||||
const { $pgettext } = gettext
|
||||
const getContentValueRepr = (val: Content) => val.text
|
||||
|
|
|
@ -8,7 +8,7 @@ export interface OrderingProps {
|
|||
orderingConfigName: RouteWithPreferences | null
|
||||
}
|
||||
|
||||
export default (orderingConfigName: MaybeRef<RouteWithPreferences | null>) => {
|
||||
export default (orderingConfigName: MaybeRef<RouteWithPreferences | null>, defaultPaginateBy?: MaybeRef<number>) => {
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
|
||||
|
@ -18,6 +18,9 @@ export default (orderingConfigName: MaybeRef<RouteWithPreferences | null>) => {
|
|||
})
|
||||
|
||||
const { paginateBy, ordering, orderingDirection } = toRefs(config)
|
||||
if (defaultPaginateBy !== undefined) {
|
||||
paginateBy.value = unref(defaultPaginateBy)
|
||||
}
|
||||
|
||||
const orderingString = computed(() => {
|
||||
if (orderingDirection.value === '-') return `-${ordering.value}`
|
||||
|
|
|
@ -19,7 +19,10 @@ export type WebSocketEventName = 'inbox.item_added' | 'import.status_updated' |
|
|||
| 'report.created' | 'user_request.created' | 'Listen'
|
||||
|
||||
export type OrderingField = 'creation_date' | 'title' | 'album__title' | 'artist__name' | 'release_date' | 'name'
|
||||
| 'applied_date' | 'followers_count' | 'uploads_count'
|
||||
| 'applied_date' | 'followers_count' | 'uploads_count' | 'length' | 'items_count' | 'modification_date' | 'size'
|
||||
| 'accessed_date' | 'bitrate' | 'duration' | 'last_fetch_date' | 'preferred_username' | 'domain' | 'handled_date'
|
||||
| 'album_title' | 'artist_name' | 'actors_count' | 'outbox_activities_count' | 'expiration_date' | 'date_joined'
|
||||
| 'last_activity' | 'username'
|
||||
|
||||
export type OrderingDirection = '-' | '+'
|
||||
interface RoutePreferences {
|
||||
|
|
|
@ -160,6 +160,13 @@ export interface Form {
|
|||
help_text: Content
|
||||
}
|
||||
|
||||
// Upload stuff
|
||||
export interface Upload {
|
||||
filename?: string
|
||||
source?: string
|
||||
uuid: string
|
||||
}
|
||||
|
||||
// Yet uncategorized stuff
|
||||
export interface Actor {
|
||||
preferred_username: string
|
||||
|
|
|
@ -1,3 +1,89 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ReportCard from '~/components/manage/moderation/ReportCard.vue'
|
||||
import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import { useStore } from '~/store'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import useSmartSearch, { SmartSearchProps } from '~/composables/useSmartSearch'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
|
||||
interface Props extends SmartSearchProps, OrderingProps {
|
||||
// TODO (wvffle): find more types
|
||||
mode?: 'card'
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultQuery: '',
|
||||
updateUrl: false,
|
||||
mode: 'card'
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
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.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'creation_date'],
|
||||
['applied_date', 'applied_date']
|
||||
]
|
||||
|
||||
const store = useStore()
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('manage/moderation/reports/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
if (query.value === 'resolved:no') {
|
||||
console.log('Refreshing sidebar notifications')
|
||||
store.commit('ui/incrementNotifications', {
|
||||
type: 'pendingReviewReports',
|
||||
value: response.data.count
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by account, summary, domain…'),
|
||||
reports: $pgettext('*/Moderation/*/Noun', 'Reports')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-title="labels.reports">
|
||||
<section class="ui vertical stripe segment">
|
||||
|
@ -11,13 +97,13 @@
|
|||
<div class="fields">
|
||||
<div class="ui field">
|
||||
<label for="reports-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<form @submit.prevent="search.query = $refs.search.value">
|
||||
<form @submit.prevent="query = $refs.search.value">
|
||||
<input
|
||||
id="reports-search"
|
||||
ref="search"
|
||||
name="search"
|
||||
type="text"
|
||||
:value="search.query"
|
||||
:value="query"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
>
|
||||
</form>
|
||||
|
@ -28,7 +114,7 @@
|
|||
id="reports-status"
|
||||
class="ui dropdown"
|
||||
:value="getTokenValue('resolved', '')"
|
||||
@change="addSearchToken('resolved', $event.target.value)"
|
||||
@change="addSearchToken('resolved', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="Content/*/Dropdown">
|
||||
|
@ -114,135 +200,11 @@
|
|||
<div class="ui center aligned basic segment">
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
:current="page"
|
||||
v-model:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import ReportCard from '~/components/manage/moderation/ReportCard.vue'
|
||||
import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue'
|
||||
import { normalizeQuery, parseTokens } from '~/utils/search'
|
||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ReportCard,
|
||||
ReportCategoryDropdown
|
||||
},
|
||||
mixins: [OrderingMixin, SmartSearchMixin],
|
||||
props: {
|
||||
mode: { type: String, default: 'card' }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
time,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: {
|
||||
query: this.defaultQuery,
|
||||
tokens: parseTokens(normalizeQuery(this.defaultQuery))
|
||||
},
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['applied_date', 'applied_date']
|
||||
],
|
||||
targets: {
|
||||
track: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by account, summary, domain…'),
|
||||
reports: this.$pgettext('*/Moderation/*/Noun', 'Reports')
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const params = merge({
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search.query,
|
||||
ordering: this.getOrderingAsString()
|
||||
}, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
this.result = null
|
||||
axios.get('manage/moderation/reports/', { params: params }).then((response) => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
if (self.search.query === 'resolved:no') {
|
||||
console.log('Refreshing sidebar notifications')
|
||||
self.$store.commit('ui/incrementNotifications', { type: 'pendingReviewReports', value: response.data.count })
|
||||
}
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
},
|
||||
handle (type, id, value) {
|
||||
if (type === 'delete') {
|
||||
this.exclude.push(id)
|
||||
}
|
||||
|
||||
this.result.results.forEach((e) => {
|
||||
if (e.uuid === id) {
|
||||
e.is_approved = value
|
||||
}
|
||||
})
|
||||
},
|
||||
getCurrentState (target) {
|
||||
if (!target) {
|
||||
return {}
|
||||
}
|
||||
if (this.targets[target.type] && this.targets[target.type][String(target.id)]) {
|
||||
return this.targets[target.type][String(target.id)].currentState
|
||||
}
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,84 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import UserRequestCard from '~/components/manage/moderation/UserRequestCard.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import useSmartSearch, { SmartSearchProps } from '~/composables/useSmartSearch'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
interface Props extends SmartSearchProps, OrderingProps {}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultQuery: '',
|
||||
updateUrl: false
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
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.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'creation_date'],
|
||||
['handled_date', 'handled_date']
|
||||
]
|
||||
|
||||
const store = useStore()
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('manage/moderation/requests/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
|
||||
if (query.value === 'status:pending') {
|
||||
store.commit('ui/incrementNotifications', {
|
||||
type: 'pendingReviewRequests',
|
||||
value: response.data.count
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
onOrderingUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by username…'),
|
||||
reports: $pgettext('*/Moderation/*/Noun', 'User Requests')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-title="labels.reports">
|
||||
<section class="ui vertical stripe segment">
|
||||
|
@ -11,13 +92,13 @@
|
|||
<div class="fields">
|
||||
<div class="ui field">
|
||||
<label for="requests-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<form @submit.prevent="search.query = $refs.search.value">
|
||||
<form @submit.prevent="query = $refs.search.value">
|
||||
<input
|
||||
id="requests-search"
|
||||
ref="search"
|
||||
name="search"
|
||||
type="text"
|
||||
:value="search.query"
|
||||
:value="query"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
>
|
||||
</form>
|
||||
|
@ -28,7 +109,7 @@
|
|||
id="requests-status"
|
||||
class="ui dropdown"
|
||||
:value="getTokenValue('status', '')"
|
||||
@change="addSearchToken('status', $event.target.value)"
|
||||
@change="addSearchToken('status', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="Content/*/Dropdown">
|
||||
|
@ -111,110 +192,12 @@
|
|||
<div class="ui center aligned basic segment">
|
||||
<pagination
|
||||
v-if="result.count > paginateBy"
|
||||
:current="page"
|
||||
v-model:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import UserRequestCard from '~/components/manage/moderation/UserRequestCard.vue'
|
||||
import { normalizeQuery, parseTokens } from '~/utils/search'
|
||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
UserRequestCard
|
||||
},
|
||||
mixins: [OrderingMixin, SmartSearchMixin],
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
time,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: {
|
||||
query: this.defaultQuery,
|
||||
tokens: parseTokens(normalizeQuery(this.defaultQuery))
|
||||
},
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['handled_date', 'handled_date']
|
||||
],
|
||||
targets: {
|
||||
track: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by username…'),
|
||||
reports: this.$pgettext('*/Moderation/*/Noun', 'User Requests')
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering () {
|
||||
this.fetchData()
|
||||
},
|
||||
orderingDirection () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
const params = merge({
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.search.query,
|
||||
ordering: this.getOrderingAsString()
|
||||
}, this.filters)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
this.result = null
|
||||
axios.get('manage/moderation/requests/', { params: params }).then((response) => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
if (self.search.query === 'status:pending') {
|
||||
self.$store.commit('ui/incrementNotifications', { type: 'pendingReviewRequests', value: response.data.count })
|
||||
}
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,121 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import time from '~/utils/time'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||
import { humanSize, truncate } from '~/utils/filters'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import useSmartSearch, { SmartSearchProps } from '~/composables/useSmartSearch'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
|
||||
interface Props extends SmartSearchProps, OrderingProps {
|
||||
// TODO (wvffle): find object type
|
||||
filters?: object
|
||||
needsRefresh?: boolean
|
||||
// TODO (wvffle): find object type
|
||||
customObjects?: any[]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultQuery: '',
|
||||
updateUrl: false,
|
||||
filters: () => ({}),
|
||||
needsRefresh: false,
|
||||
customObjects: () => []
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(1)
|
||||
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.orderingConfigName)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'creation_date'],
|
||||
['title', 'track_title'],
|
||||
['size', 'size'],
|
||||
['duration', 'duration'],
|
||||
['bitrate', 'bitrate'],
|
||||
['album_title', 'album_title'],
|
||||
['artist_name', 'artist_name']
|
||||
]
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = [
|
||||
{
|
||||
name: 'delete',
|
||||
label: $pgettext('*/*/*/Verb', 'Delete'),
|
||||
isDangerous: true,
|
||||
allowAll: true,
|
||||
confirmColor: 'danger'
|
||||
},
|
||||
{
|
||||
name: 'relaunch_import',
|
||||
label: $pgettext('Content/Library/Dropdown/Verb', 'Restart import'),
|
||||
isDangerous: true,
|
||||
allowAll: true,
|
||||
// TODO (wvffle): Find correct type
|
||||
filterCheckable: (filter: { import_status: string }) => {
|
||||
return filter.import_status !== 'finished'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const emit = defineEmits(['fetch-start'])
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchData = async () => {
|
||||
emit('fetch-start')
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
include_channels: 'true',
|
||||
...props.filters
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/uploads/', {
|
||||
params
|
||||
// TODO (wvffle): Check if params should be serialized. In other similar components (Podcasts, Artists) they are
|
||||
// paramsSerializer: function (params) {
|
||||
// return qs.stringify(params, { indices: false })
|
||||
// }
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(() => (page.value = 1))
|
||||
onOrderingUpdate(() => (page.value = 1))
|
||||
watch(page, fetchData)
|
||||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Library/Input.Placeholder', 'Search by title, artist, album…'),
|
||||
showStatus: $pgettext('Content/Library/Button.Label/Verb', 'Show information about the upload status for this track')
|
||||
}))
|
||||
|
||||
const detailedUpload = ref({})
|
||||
const showUploadDetailModal = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui inline form">
|
||||
|
@ -6,13 +124,13 @@
|
|||
<label for="files-search">
|
||||
<translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
|
||||
</label>
|
||||
<form @submit.prevent="search.query = $refs.search.value">
|
||||
<form @submit.prevent="query = $refs.search.value">
|
||||
<input
|
||||
id="files-search"
|
||||
ref="search"
|
||||
name="search"
|
||||
type="text"
|
||||
:value="search.query"
|
||||
:value="query"
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
>
|
||||
</form>
|
||||
|
@ -25,7 +143,7 @@
|
|||
id="import-status"
|
||||
class="ui dropdown"
|
||||
:value="getTokenValue('status', '')"
|
||||
@change="addSearchToken('status', $event.target.value)"
|
||||
@change="addSearchToken('status', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value>
|
||||
<translate translate-context="Content/*/Dropdown">
|
||||
|
@ -112,7 +230,7 @@
|
|||
<div class="ui loader" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!result && result.results.length === 0 && !needsRefresh"
|
||||
v-else-if="!result || result?.results.length === 0 && !needsRefresh"
|
||||
class="ui placeholder segment"
|
||||
>
|
||||
<div class="ui icon header">
|
||||
|
@ -247,11 +365,10 @@
|
|||
<div>
|
||||
<pagination
|
||||
v-if="result && result.count > paginateBy"
|
||||
v-model:current="page"
|
||||
:compact="true"
|
||||
:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="page = $event; fetchData()"
|
||||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
|
@ -263,158 +380,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { merge } from 'lodash-es'
|
||||
import time from '~/utils/time'
|
||||
import { normalizeQuery, parseTokens } from '~/utils/search'
|
||||
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import ActionTable from '~/components/common/ActionTable.vue'
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||
import { humanSize, truncate } from '~/utils/filters'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
ActionTable,
|
||||
ImportStatusModal
|
||||
},
|
||||
mixins: [OrderingMixin, SmartSearchMixin],
|
||||
props: {
|
||||
filters: { type: Object, required: false, default: function () { return {} } },
|
||||
needsRefresh: { type: Boolean, required: false, default: false },
|
||||
customObjects: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels, humanSize, time, truncate }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
detailedUpload: {},
|
||||
showUploadDetailModal: false,
|
||||
isLoading: false,
|
||||
result: null,
|
||||
page: 1,
|
||||
search: {
|
||||
query: this.defaultQuery,
|
||||
tokens: parseTokens(normalizeQuery(this.defaultQuery))
|
||||
},
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['title', 'track_title'],
|
||||
['size', 'size'],
|
||||
['duration', 'duration'],
|
||||
['bitrate', 'bitrate'],
|
||||
['album_title', 'album_title'],
|
||||
['artist_name', 'artist_name']
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$pgettext(
|
||||
'Content/Library/Input.Placeholder',
|
||||
'Search by title, artist, album…'
|
||||
),
|
||||
showStatus: this.$pgettext('Content/Library/Button.Label/Verb', 'Show information about the upload status for this track')
|
||||
}
|
||||
},
|
||||
actionFilters () {
|
||||
const currentFilters = {
|
||||
q: this.search.query,
|
||||
include_channels: 'true'
|
||||
}
|
||||
if (this.filters) {
|
||||
return merge(currentFilters, this.filters)
|
||||
} else {
|
||||
return currentFilters
|
||||
}
|
||||
},
|
||||
actions () {
|
||||
const deleteMsg = this.$pgettext('*/*/*/Verb', 'Delete')
|
||||
const relaunchMsg = this.$pgettext(
|
||||
'Content/Library/Dropdown/Verb',
|
||||
'Restart import'
|
||||
)
|
||||
return [
|
||||
{
|
||||
name: 'delete',
|
||||
label: deleteMsg,
|
||||
isDangerous: true,
|
||||
allowAll: true
|
||||
},
|
||||
{
|
||||
name: 'relaunch_import',
|
||||
label: relaunchMsg,
|
||||
isDangerous: true,
|
||||
allowAll: true,
|
||||
filterCheckable: f => {
|
||||
return f.import_status !== 'finished'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
orderingDirection: function () {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
page: function () {
|
||||
this.fetchData()
|
||||
},
|
||||
ordering: function () {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
},
|
||||
search (newValue) {
|
||||
this.page = 1
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.$emit('fetch-start')
|
||||
const params = merge(
|
||||
{
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
ordering: this.getOrderingAsString(),
|
||||
q: this.search.query,
|
||||
include_channels: 'true'
|
||||
},
|
||||
this.filters || {}
|
||||
)
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.checked = []
|
||||
axios.get('/uploads/', { params: params }).then(
|
||||
response => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
},
|
||||
error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,105 @@
|
|||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
import qs from 'qs'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import PlaylistCardList from '~/components/playlists/CardList.vue'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
import useLogger from '~/composables/useLogger'
|
||||
import useOrdering, { OrderingProps } from '~/composables/useOrdering'
|
||||
import { OrderingField } from '~/store/ui'
|
||||
|
||||
interface Props extends OrderingProps {
|
||||
defaultPage?: number
|
||||
defaultPaginateBy?: number
|
||||
defaultQuery?: string
|
||||
scope?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
defaultPage: 1,
|
||||
defaultPaginateBy: 1,
|
||||
defaultQuery: '',
|
||||
scope: 'all'
|
||||
})
|
||||
|
||||
// TODO (wvffle): Make sure everything is it's own type
|
||||
const page = ref(+props.defaultPage)
|
||||
type ResponseType = { count: number, results: any[] }
|
||||
const result = ref<null | ResponseType>(null)
|
||||
const query = ref(props.defaultQuery)
|
||||
|
||||
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
||||
['creation_date', 'creation_date'],
|
||||
['modification_date', 'modification_date'],
|
||||
['name', 'name']
|
||||
]
|
||||
|
||||
const logger = useLogger()
|
||||
const sharedLabels = useSharedLabels()
|
||||
|
||||
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName, props.defaultPaginateBy)
|
||||
|
||||
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 fetchData = async () => {
|
||||
isLoading.value = true
|
||||
const params = {
|
||||
scope: props.scope,
|
||||
page: page.value,
|
||||
page_size: paginateBy.value,
|
||||
q: query.value,
|
||||
ordering: orderingString.value,
|
||||
playable: true
|
||||
}
|
||||
|
||||
logger.time('Fetching albums')
|
||||
try {
|
||||
const response = await axios.get('playlists/', {
|
||||
params,
|
||||
paramsSerializer: function (params) {
|
||||
return qs.stringify(params, { indices: false })
|
||||
}
|
||||
})
|
||||
|
||||
result.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
result.value = null
|
||||
} finally {
|
||||
logger.timeEnd('Fetching albums')
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
onBeforeRouteUpdate(fetchData)
|
||||
fetchData()
|
||||
|
||||
// @ts-expect-error semantic ui
|
||||
onMounted(() => $('.ui.dropdown').dropdown())
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const labels = computed(() => ({
|
||||
playlists: $pgettext('*/*/*', 'Playlists'),
|
||||
searchPlaceholder: $pgettext('Content/Playlist/Placeholder/Call to action', 'Enter playlist name…')
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-title="labels.playlists">
|
||||
<section class="ui vertical stripe segment">
|
||||
|
@ -126,111 +228,11 @@
|
|||
<div class="ui center aligned basic segment">
|
||||
<pagination
|
||||
v-if="result && result.results.length > 0"
|
||||
:current="page"
|
||||
v-model:current="page"
|
||||
:paginate-by="paginateBy"
|
||||
:total="result.count"
|
||||
@page-changed="selectPage"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
|
||||
import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||
import PaginationMixin from '~/components/mixins/Pagination.vue'
|
||||
import PlaylistCardList from '~/components/playlists/CardList.vue'
|
||||
import Pagination from '~/components/vui/Pagination.vue'
|
||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||
|
||||
const FETCH_URL = 'playlists/'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PlaylistCardList,
|
||||
Pagination
|
||||
},
|
||||
mixins: [OrderingMixin, PaginationMixin],
|
||||
props: {
|
||||
defaultQuery: { type: String, required: false, default: '' },
|
||||
scope: { type: String, required: false, default: 'all' }
|
||||
},
|
||||
setup () {
|
||||
const sharedLabels = useSharedLabels()
|
||||
return { sharedLabels }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: true,
|
||||
result: null,
|
||||
page: parseInt(this.defaultPage),
|
||||
query: this.defaultQuery,
|
||||
orderingOptions: [
|
||||
['creation_date', 'creation_date'],
|
||||
['modification_date', 'modification_date'],
|
||||
['name', 'name']
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
const playlists = this.$pgettext('*/*/*', 'Playlists')
|
||||
const searchPlaceholder = this.$pgettext('Content/Playlist/Placeholder/Call to action', 'Enter playlist name…')
|
||||
return {
|
||||
playlists,
|
||||
searchPlaceholder
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
page () {
|
||||
this.updateQueryString()
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
mounted () {
|
||||
$('.ui.dropdown').dropdown()
|
||||
},
|
||||
methods: {
|
||||
updateQueryString: function () {
|
||||
history.pushState(
|
||||
{},
|
||||
null,
|
||||
this.$route.path + '?' + new URLSearchParams(
|
||||
{
|
||||
query: this.query,
|
||||
page: this.page,
|
||||
paginateBy: this.paginateBy,
|
||||
ordering: this.getOrderingAsString()
|
||||
}).toString()
|
||||
)
|
||||
},
|
||||
fetchData: function () {
|
||||
const self = this
|
||||
this.isLoading = true
|
||||
const url = FETCH_URL
|
||||
const params = {
|
||||
scope: this.scope,
|
||||
page: this.page,
|
||||
page_size: this.paginateBy,
|
||||
q: this.query,
|
||||
ordering: this.getOrderingAsString(),
|
||||
playable: true
|
||||
}
|
||||
axios.get(url, { params: params }).then(response => {
|
||||
self.result = response.data
|
||||
self.isLoading = false
|
||||
})
|
||||
},
|
||||
selectPage: function (page) {
|
||||
this.page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue