Migrate rest of ordering/pagination mixins to composables

This commit is contained in:
wvffle 2022-06-25 22:06:30 +00:00 committed by Georg Krause
parent 21e5d8ddf0
commit a8fd0e3f28
27 changed files with 1120 additions and 1421 deletions

View File

@ -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: {

View File

@ -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({

View File

@ -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({

View File

@ -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({

View File

@ -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({

View File

@ -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()

View File

@ -76,11 +76,7 @@ const fetchData = async () => {
}
}
onSearch(() => {
page.value = 1
fetchData()
})
onSearch(() => (page.value = 1))
watch(page, fetchData)
onOrderingUpdate(fetchData)
fetchData()

View File

@ -74,11 +74,7 @@ const fetchData = async () => {
}
}
onSearch(() => {
page.value = 1
fetchData()
})
onSearch(() => (page.value = 1))
watch(page, fetchData)
onOrderingUpdate(fetchData)
fetchData()

View File

@ -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…')
}))

View File

@ -76,11 +76,7 @@ const fetchData = async () => {
}
}
onSearch(() => {
page.value = 1
fetchData()
})
onSearch(() => (page.value = 1))
watch(page, fetchData)
onOrderingUpdate(fetchData)
fetchData()

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,8 +0,0 @@
<script>
export default {
props: {
defaultPage: { type: Number, required: false, default: 1 },
defaultPaginateBy: { type: Number, required: false, default: 1 }
}
}
</script>

View File

@ -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'),

View File

@ -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

View File

@ -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}`

View File

@ -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 {

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>