Migrate rest of the components
This commit is contained in:
parent
6431d0285c
commit
74d1a0a03e
|
@ -86,6 +86,7 @@
|
||||||
"sinon": "14.0.0",
|
"sinon": "14.0.0",
|
||||||
"ts-jest": "28.0.7",
|
"ts-jest": "28.0.7",
|
||||||
"typescript": "4.7.4",
|
"typescript": "4.7.4",
|
||||||
|
"utility-types": "^3.10.0",
|
||||||
"vite": "3.0.3",
|
"vite": "3.0.3",
|
||||||
"vite-plugin-pwa": "0.12.3",
|
"vite-plugin-pwa": "0.12.3",
|
||||||
"vite-plugin-vue-inspector": "1.0.1",
|
"vite-plugin-vue-inspector": "1.0.1",
|
||||||
|
|
|
@ -1,3 +1,45 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { EditObject, EditObjectType } from '~/composables/moderation/useEditConfigs'
|
||||||
|
import type { Library } from '~/types'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import store from '~/store'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
import EditForm from '~/components/library/EditForm.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
objectType: EditObjectType
|
||||||
|
object: EditObject
|
||||||
|
libraries: Library[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
libraries: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const canEdit = store.state.auth.availablePermissions.library
|
||||||
|
|
||||||
|
const isLoadingLicenses = ref(false)
|
||||||
|
const licenses = ref([])
|
||||||
|
const fetchLicenses = async () => {
|
||||||
|
isLoadingLicenses.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get('licenses/')
|
||||||
|
licenses.value = response.data.results
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingLicenses.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchLicenses()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<div class="ui text container">
|
<div class="ui text container">
|
||||||
|
@ -39,44 +81,3 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import EditForm from '~/components/library/EditForm.vue'
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
EditForm
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
objectType: { type: String, required: true },
|
|
||||||
object: { type: Object, required: true },
|
|
||||||
libraries: { type: Array, default: null }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
id: this.object.id,
|
|
||||||
isLoadingLicenses: false,
|
|
||||||
licenses: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
canEdit () {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchLicenses()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchLicenses () {
|
|
||||||
const self = this
|
|
||||||
self.isLoadingLicenses = true
|
|
||||||
axios.get('licenses/').then((response) => {
|
|
||||||
self.isLoadingLicenses = false
|
|
||||||
self.licenses = response.data.results
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,45 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`uploads/${props.id}/`, {
|
||||||
|
params: {
|
||||||
|
refresh: 'true',
|
||||||
|
include_channels: 'true'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.replace({
|
||||||
|
name: 'library.tracks.detail',
|
||||||
|
params: { id: response.data.track.id }
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -8,23 +50,3 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { id: { type: Number, required: true } },
|
|
||||||
async created () {
|
|
||||||
const upload = await this.fetchData()
|
|
||||||
this.$router.replace({ name: 'library.tracks.detail', params: { id: upload.track.id } })
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchData () {
|
|
||||||
this.isLoading = true
|
|
||||||
const response = await axios.get(`uploads/${this.id}/`, { params: { refresh: 'true', include_channels: 'true' } })
|
|
||||||
this.isLoading = false
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,200 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, reactive, watch, watchEffect, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import $ from 'jquery'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
import TrackTable from '~/components/audio/track/Table.vue'
|
||||||
|
import RadioButton from '~/components/radios/Button.vue'
|
||||||
|
import BuilderFilter from './Filter.vue'
|
||||||
|
|
||||||
|
export interface BuilderFilter {
|
||||||
|
type: string
|
||||||
|
label: string
|
||||||
|
help_text: string
|
||||||
|
fields: FilterField[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterField {
|
||||||
|
name: string
|
||||||
|
placeholder: string
|
||||||
|
type: 'list'
|
||||||
|
subtype: 'number'
|
||||||
|
autocomplete?: string
|
||||||
|
autocomplete_qs: string
|
||||||
|
autocomplete_fields: {
|
||||||
|
remoteValues?: unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterConfig extends Record<string, unknown> {
|
||||||
|
type: string
|
||||||
|
not: boolean
|
||||||
|
names: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Filter {
|
||||||
|
hash: number
|
||||||
|
config: FilterConfig
|
||||||
|
filter: BuilderFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
id: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
title: $pgettext('Head/Radio/Title', 'Radio Builder'),
|
||||||
|
placeholder: {
|
||||||
|
description: $pgettext('Content/Radio/Input.Placeholder', 'My awesome description'),
|
||||||
|
name: $pgettext('Content/Radio/Input.Placeholder', 'My awesome radio')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const filters = reactive([] as Filter[])
|
||||||
|
const checkResult = ref()
|
||||||
|
const fetchCandidates = async () => {
|
||||||
|
// TODO (wvffle): Add loader
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('radios/radios/validate/', {
|
||||||
|
filters: [{
|
||||||
|
type: 'group',
|
||||||
|
filters: filters.map(filter => ({
|
||||||
|
...filter.config,
|
||||||
|
type: filter.filter.type
|
||||||
|
}))
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
checkResult.value = response.data.filters[0]
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(filters, fetchCandidates)
|
||||||
|
const checkErrors = computed(() => checkResult.value?.errors ?? [])
|
||||||
|
|
||||||
|
const isPublic = ref(true)
|
||||||
|
const radioName = ref('')
|
||||||
|
const radioDesc = ref('')
|
||||||
|
const canSave = computed(() => radioName.value.length > 0 && checkErrors.value.length === 0)
|
||||||
|
|
||||||
|
const currentFilterType = ref()
|
||||||
|
const availableFilters = reactive([] as BuilderFilter[])
|
||||||
|
const currentFilter = computed(() => availableFilters.find(filter => filter.type === currentFilterType.value))
|
||||||
|
|
||||||
|
const fetchFilters = async () => {
|
||||||
|
// TODO (wvffle): Add loader
|
||||||
|
try {
|
||||||
|
const response = await axios.get('radios/radios/filters/')
|
||||||
|
availableFilters.length = 0
|
||||||
|
availableFilters.push(...response.data)
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`radios/radios/${props.id}/`)
|
||||||
|
filters.length = 0
|
||||||
|
filters.push(...response.data.config.map((filter: FilterConfig) => ({
|
||||||
|
config: filter,
|
||||||
|
filter: availableFilters.find(available => available.type === filter.type),
|
||||||
|
hash: +new Date()
|
||||||
|
})))
|
||||||
|
|
||||||
|
radioName.value = response.data.name
|
||||||
|
radioDesc.value = response.data.description
|
||||||
|
isPublic.value = response.data.is_public
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchFilters().then(() => watchEffect(fetchData))
|
||||||
|
|
||||||
|
const add = async () => {
|
||||||
|
if (currentFilter.value) {
|
||||||
|
filters.push({
|
||||||
|
config: {} as FilterConfig,
|
||||||
|
filter: currentFilter.value,
|
||||||
|
hash: +new Date()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchCandidates()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateConfig = async (index: number, field: keyof FilterConfig, value: unknown) => {
|
||||||
|
filters[index].config[field] = value
|
||||||
|
return fetchCandidates()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteFilter = async (index: number) => {
|
||||||
|
filters.splice(index, 1)
|
||||||
|
return fetchCandidates()
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = ref(false)
|
||||||
|
const save = async () => {
|
||||||
|
success.value = false
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = {
|
||||||
|
name: radioName.value,
|
||||||
|
description: radioDesc.value,
|
||||||
|
is_public: isPublic.value,
|
||||||
|
config: filters.map(filter => ({
|
||||||
|
...filter.config,
|
||||||
|
type: filter.filter.type
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = props.id
|
||||||
|
? await axios.put(`radios/radios/${props.id}/`, data)
|
||||||
|
: await axios.post('radios/radios/', data)
|
||||||
|
|
||||||
|
success.value = true
|
||||||
|
if (!props.id) {
|
||||||
|
router.push({
|
||||||
|
name: 'library.radios.detail',
|
||||||
|
params: {
|
||||||
|
id: response.data.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$('.ui.dropdown').dropdown()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-title="labels.title"
|
v-title="labels.title"
|
||||||
|
@ -64,7 +261,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
<button
|
<button
|
||||||
:disabled="!canSave || null"
|
:disabled="!canSave"
|
||||||
:class="['ui', 'success', {loading: isLoading}, 'button']"
|
:class="['ui', 'success', {loading: isLoading}, 'button']"
|
||||||
@click="save"
|
@click="save"
|
||||||
>
|
>
|
||||||
|
@ -96,8 +293,8 @@
|
||||||
</translate>
|
</translate>
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="(f, key) in availableFilters"
|
v-for="f in availableFilters"
|
||||||
:key="key"
|
:key="f.label"
|
||||||
:value="f.type"
|
:value="f.type"
|
||||||
>
|
>
|
||||||
{{ f.label }}
|
{{ f.label }}
|
||||||
|
@ -105,7 +302,7 @@
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
id="addFilter"
|
id="addFilter"
|
||||||
:disabled="!currentFilterType || null"
|
:disabled="!currentFilterType"
|
||||||
class="ui button"
|
class="ui button"
|
||||||
@click="add"
|
@click="add"
|
||||||
>
|
>
|
||||||
|
@ -151,7 +348,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<builder-filter
|
<builder-filter
|
||||||
v-for="(f, index) in filters"
|
v-for="(f, index) in filters"
|
||||||
:key="(f, index, f.hash)"
|
:key="f.hash"
|
||||||
:index="index"
|
:index="index"
|
||||||
:config="f.config"
|
:config="f.config"
|
||||||
:filter="f.filter"
|
:filter="f.filter"
|
||||||
|
@ -183,179 +380,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import $ from 'jquery'
|
|
||||||
import { clone } from 'lodash-es'
|
|
||||||
import BuilderFilter from './Filter.vue'
|
|
||||||
import TrackTable from '~/components/audio/track/Table.vue'
|
|
||||||
import RadioButton from '~/components/radios/Button.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
BuilderFilter,
|
|
||||||
TrackTable,
|
|
||||||
RadioButton
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
id: { type: Number, required: false, default: 0 }
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
success: false,
|
|
||||||
availableFilters: [],
|
|
||||||
currentFilterType: null,
|
|
||||||
filters: [],
|
|
||||||
checkResult: null,
|
|
||||||
radioName: '',
|
|
||||||
radioDesc: '',
|
|
||||||
isPublic: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
const title = this.$pgettext('Head/Radio/Title', 'Radio Builder')
|
|
||||||
const placeholder = {
|
|
||||||
name: this.$pgettext('Content/Radio/Input.Placeholder', 'My awesome radio'),
|
|
||||||
description: this.$pgettext('Content/Radio/Input.Placeholder', 'My awesome description')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
placeholder
|
|
||||||
}
|
|
||||||
},
|
|
||||||
canSave: function () {
|
|
||||||
return this.radioName.length > 0 && this.checkErrors.length === 0
|
|
||||||
},
|
|
||||||
checkErrors: function () {
|
|
||||||
if (!this.checkResult) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const errors = this.checkResult.errors
|
|
||||||
return errors
|
|
||||||
},
|
|
||||||
currentFilter: function () {
|
|
||||||
const self = this
|
|
||||||
return this.availableFilters.filter(e => {
|
|
||||||
return e.type === self.currentFilterType
|
|
||||||
})[0]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
filters: {
|
|
||||||
handler: function () {
|
|
||||||
this.fetchCandidates()
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
const self = this
|
|
||||||
this.fetchFilters().then(() => {
|
|
||||||
if (self.id) {
|
|
||||||
self.fetch()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
$('.ui.dropdown').dropdown()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchFilters: function () {
|
|
||||||
const self = this
|
|
||||||
const url = 'radios/radios/filters/'
|
|
||||||
return axios.get(url).then(response => {
|
|
||||||
self.availableFilters = response.data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
add () {
|
|
||||||
this.filters.push({
|
|
||||||
config: {},
|
|
||||||
filter: this.currentFilter,
|
|
||||||
hash: +new Date()
|
|
||||||
})
|
|
||||||
this.fetchCandidates()
|
|
||||||
},
|
|
||||||
updateConfig (index, field, value) {
|
|
||||||
this.filters[index].config[field] = value
|
|
||||||
this.fetchCandidates()
|
|
||||||
},
|
|
||||||
deleteFilter (index) {
|
|
||||||
this.filters.splice(index, 1)
|
|
||||||
this.fetchCandidates()
|
|
||||||
},
|
|
||||||
fetch: function () {
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
const url = 'radios/radios/' + this.id + '/'
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.filters = response.data.config.map(f => {
|
|
||||||
return {
|
|
||||||
config: f,
|
|
||||||
filter: this.availableFilters.filter(e => {
|
|
||||||
return e.type === f.type
|
|
||||||
})[0],
|
|
||||||
hash: +new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
self.radioName = response.data.name
|
|
||||||
self.radioDesc = response.data.description
|
|
||||||
self.isPublic = response.data.is_public
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchCandidates: function () {
|
|
||||||
const self = this
|
|
||||||
const url = 'radios/radios/validate/'
|
|
||||||
let final = this.filters.map(f => {
|
|
||||||
const c = clone(f.config)
|
|
||||||
c.type = f.filter.type
|
|
||||||
return c
|
|
||||||
})
|
|
||||||
final = {
|
|
||||||
filters: [{ type: 'group', filters: final }]
|
|
||||||
}
|
|
||||||
axios.post(url, final).then(response => {
|
|
||||||
self.checkResult = response.data.filters[0]
|
|
||||||
})
|
|
||||||
},
|
|
||||||
save: function () {
|
|
||||||
const self = this
|
|
||||||
self.success = false
|
|
||||||
self.isLoading = true
|
|
||||||
|
|
||||||
let final = this.filters.map(f => {
|
|
||||||
const c = clone(f.config)
|
|
||||||
c.type = f.filter.type
|
|
||||||
return c
|
|
||||||
})
|
|
||||||
final = {
|
|
||||||
name: this.radioName,
|
|
||||||
description: this.radioDesc,
|
|
||||||
is_public: this.isPublic,
|
|
||||||
config: final
|
|
||||||
}
|
|
||||||
if (this.id) {
|
|
||||||
const url = 'radios/radios/' + this.id + '/'
|
|
||||||
axios.put(url, final).then(response => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.success = true
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const url = 'radios/radios/'
|
|
||||||
axios.post(url, final).then(response => {
|
|
||||||
self.success = true
|
|
||||||
self.isLoading = false
|
|
||||||
self.$router.push({
|
|
||||||
name: 'library.radios.detail',
|
|
||||||
params: {
|
|
||||||
id: response.data.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO (wvffle): SORT IMPORTS LIKE SO EVERYWHERE
|
// TODO (wvffle): SORT IMPORTS LIKE SO EVERYWHERE
|
||||||
import type { Track } from '~/types'
|
import type { Track } from '~/types'
|
||||||
|
import type { BuilderFilter, FilterConfig } from './Builder.vue'
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import $ from 'jquery'
|
import $ from 'jquery'
|
||||||
|
@ -18,26 +19,8 @@ import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
interface Props {
|
interface Props {
|
||||||
index: number
|
index: number
|
||||||
|
|
||||||
filter: {
|
filter: BuilderFilter
|
||||||
type: string
|
config: FilterConfig
|
||||||
label: string
|
|
||||||
fields: {
|
|
||||||
name: string
|
|
||||||
placeholder: string
|
|
||||||
type: 'list'
|
|
||||||
subtype: 'number'
|
|
||||||
autocomplete?: string
|
|
||||||
autocomplete_qs: string
|
|
||||||
autocomplete_fields: {
|
|
||||||
remoteValues?: unknown
|
|
||||||
}
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|
||||||
config: {
|
|
||||||
not: boolean
|
|
||||||
names: string[]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Filter = { candidates: { count: number, sample: Track[] } }
|
type Filter = { candidates: { count: number, sample: Track[] } }
|
||||||
|
@ -153,18 +136,18 @@ watch(exclude, fetchCandidates)
|
||||||
{{ f.placeholder }}
|
{{ f.placeholder }}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
v-if="f.type === 'list' && config[f.name]"
|
v-if="f.type === 'list' && config[f.name as keyof FilterConfig]"
|
||||||
:id="f.name"
|
:id="f.name"
|
||||||
:value="config[f.name].join(',')"
|
:value="(config[f.name as keyof FilterConfig] as string[]).join(',')"
|
||||||
type="hidden"
|
type="hidden"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="config[f.name]"
|
v-if="typeof config[f.name as keyof FilterConfig] === 'object'"
|
||||||
class="ui menu"
|
class="ui menu"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(v, i) in config[f.name]"
|
v-for="(v, i) in config[f.name as keyof FilterConfig] as object"
|
||||||
:key="v"
|
:key="i"
|
||||||
class="ui item"
|
class="ui item"
|
||||||
:data-value="v"
|
:data-value="v"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,3 +1,121 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { BackendError, InstancePolicy } from '~/types'
|
||||||
|
|
||||||
|
import { computed, ref, reactive } from 'vue'
|
||||||
|
import { whenever } from '@vueuse/core'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'save', data: InstancePolicy): void
|
||||||
|
(e: 'delete'): void
|
||||||
|
(e: 'cancel'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: string
|
||||||
|
target: string
|
||||||
|
object?: InstancePolicy | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
object: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
summaryHelp: $pgettext('Content/Moderation/Help text', "Explain why you're applying this policy: this will help you remember why you added this rule. Depending on your pod configuration, this may be displayed publicly to help users understand the moderation rules in place."),
|
||||||
|
isActiveHelp: $pgettext('Content/Moderation/Help text', 'Use this setting to temporarily enable/disable the policy without completely removing it.'),
|
||||||
|
blockAllHelp: $pgettext('Content/Moderation/Help text', 'Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)'),
|
||||||
|
silenceActivity: {
|
||||||
|
help: $pgettext('Content/Moderation/Help text', 'Hide account or domain content, except from followers.'),
|
||||||
|
label: $pgettext('Content/Moderation/*/Verb', 'Mute activity')
|
||||||
|
},
|
||||||
|
silenceNotifications: {
|
||||||
|
help: $pgettext('Content/Moderation/Help text', 'Prevent account or domain from triggering notifications, except from followers.'),
|
||||||
|
label: $pgettext('Content/Moderation/*/Verb', 'Mute notifications')
|
||||||
|
},
|
||||||
|
rejectMedia: {
|
||||||
|
help: $pgettext('Content/Moderation/Help text', 'Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well.'),
|
||||||
|
label: $pgettext('Content/Moderation/*/Verb', 'Reject media')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const current = reactive({
|
||||||
|
summary: props.object?.summary ?? '',
|
||||||
|
isActive: props.object?.is_active ?? true,
|
||||||
|
blockAll: props.object?.block_all ?? true,
|
||||||
|
silenceActivity: props.object?.silence_activity ?? false,
|
||||||
|
silenceNotifications: props.object?.silence_notifications ?? false,
|
||||||
|
rejectMedia: props.object?.reject_media ?? false
|
||||||
|
})
|
||||||
|
|
||||||
|
const fieldConfig = [
|
||||||
|
// TODO: We hide those until we actually have the related features implemented :)
|
||||||
|
// { id: 'silenceActivity', icon: 'feed' },
|
||||||
|
// { id: 'silenceNotifications', icon: 'bell' },
|
||||||
|
{ id: 'rejectMedia', icon: 'file' }
|
||||||
|
] as const
|
||||||
|
|
||||||
|
whenever(() => current.silenceNotifications, () => (current.blockAll = false))
|
||||||
|
whenever(() => current.silenceActivity, () => (current.blockAll = false))
|
||||||
|
whenever(() => current.rejectMedia, () => (current.blockAll = false))
|
||||||
|
whenever(() => current.blockAll, () => {
|
||||||
|
for (const config of fieldConfig) {
|
||||||
|
current[config.id] = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const createOrUpdate = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = {
|
||||||
|
summary: current.summary,
|
||||||
|
is_active: current.isActive,
|
||||||
|
block_all: current.blockAll,
|
||||||
|
silence_activity: current.silenceActivity,
|
||||||
|
silence_notifications: current.silenceNotifications,
|
||||||
|
reject_media: current.rejectMedia,
|
||||||
|
target: {
|
||||||
|
type: props.type,
|
||||||
|
id: props.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = props.object
|
||||||
|
? await axios.patch(`manage/moderation/instance-policies/${props.object.id}/`, data)
|
||||||
|
: await axios.post('manage/moderation/instance-policies/', data)
|
||||||
|
|
||||||
|
emit('save', response.data)
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(`manage/moderation/instance-policies/${props.object?.id}/`)
|
||||||
|
emit('delete')
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form
|
<form
|
||||||
class="ui form"
|
class="ui form"
|
||||||
|
@ -111,7 +229,7 @@
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
<button
|
<button
|
||||||
class="ui basic left floated button"
|
class="ui basic left floated button"
|
||||||
@click.prevent="$emit('cancel')"
|
@click.prevent="emit('cancel')"
|
||||||
>
|
>
|
||||||
<translate translate-context="*/*/Button.Label/Verb">
|
<translate translate-context="*/*/Button.Label/Verb">
|
||||||
Cancel
|
Cancel
|
||||||
|
@ -119,7 +237,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:class="['ui', 'right', 'floated', 'success', {'disabled loading': isLoading}, 'button']"
|
:class="['ui', 'right', 'floated', 'success', {'disabled loading': isLoading}, 'button']"
|
||||||
:disabled="isLoading || null"
|
:disabled="isLoading"
|
||||||
>
|
>
|
||||||
<translate
|
<translate
|
||||||
v-if="object"
|
v-if="object"
|
||||||
|
@ -166,131 +284,3 @@
|
||||||
</dangerous-button>
|
</dangerous-button>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import { get } from 'lodash-es'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
type: { type: String, required: true },
|
|
||||||
object: { type: Object, default: null },
|
|
||||||
target: { type: String, required: true }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
const current = this.object || {}
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
errors: [],
|
|
||||||
current: {
|
|
||||||
summary: get(current, 'summary', ''),
|
|
||||||
isActive: get(current, 'is_active', true),
|
|
||||||
blockAll: get(current, 'block_all', true),
|
|
||||||
silenceActivity: get(current, 'silence_activity', false),
|
|
||||||
silenceNotifications: get(current, 'silence_notifications', false),
|
|
||||||
rejectMedia: get(current, 'reject_media', false)
|
|
||||||
},
|
|
||||||
fieldConfig: [
|
|
||||||
// we hide those until we actually have the related features implemented :)
|
|
||||||
// {id: "silenceActivity", icon: "feed"},
|
|
||||||
// {id: "silenceNotifications", icon: "bell"},
|
|
||||||
{ id: 'rejectMedia', icon: 'file' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
summaryHelp: this.$pgettext('Content/Moderation/Help text', "Explain why you're applying this policy: this will help you remember why you added this rule. Depending on your pod configuration, this may be displayed publicly to help users understand the moderation rules in place."),
|
|
||||||
isActiveHelp: this.$pgettext('Content/Moderation/Help text', 'Use this setting to temporarily enable/disable the policy without completely removing it.'),
|
|
||||||
blockAllHelp: this.$pgettext('Content/Moderation/Help text', 'Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)'),
|
|
||||||
silenceActivity: {
|
|
||||||
help: this.$pgettext('Content/Moderation/Help text', 'Hide account or domain content, except from followers.'),
|
|
||||||
label: this.$pgettext('Content/Moderation/*/Verb', 'Mute activity')
|
|
||||||
},
|
|
||||||
silenceNotifications: {
|
|
||||||
help: this.$pgettext('Content/Moderation/Help text', 'Prevent account or domain from triggering notifications, except from followers.'),
|
|
||||||
label: this.$pgettext('Content/Moderation/*/Verb', 'Mute notifications')
|
|
||||||
},
|
|
||||||
rejectMedia: {
|
|
||||||
help: this.$pgettext('Content/Moderation/Help text', 'Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well.'),
|
|
||||||
label: this.$pgettext('Content/Moderation/*/Verb', 'Reject media')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'current.silenceActivity': function (v) {
|
|
||||||
if (v) {
|
|
||||||
this.current.blockAll = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'current.silenceNotifications': function (v) {
|
|
||||||
if (v) {
|
|
||||||
this.current.blockAll = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'current.rejectMedia': function (v) {
|
|
||||||
if (v) {
|
|
||||||
this.current.blockAll = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'current.blockAll': function (v) {
|
|
||||||
if (v) {
|
|
||||||
const self = this
|
|
||||||
this.fieldConfig.forEach((f) => {
|
|
||||||
self.current[f.id] = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
createOrUpdate () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
this.errors = []
|
|
||||||
let url, method
|
|
||||||
const data = {
|
|
||||||
summary: this.current.summary,
|
|
||||||
is_active: this.current.isActive,
|
|
||||||
block_all: this.current.blockAll,
|
|
||||||
silence_activity: this.current.silenceActivity,
|
|
||||||
silence_notifications: this.current.silenceNotifications,
|
|
||||||
reject_media: this.current.rejectMedia,
|
|
||||||
target: {
|
|
||||||
type: this.type,
|
|
||||||
id: this.target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.object) {
|
|
||||||
url = `manage/moderation/instance-policies/${this.object.id}/`
|
|
||||||
method = 'patch'
|
|
||||||
} else {
|
|
||||||
url = 'manage/moderation/instance-policies/'
|
|
||||||
method = 'post'
|
|
||||||
}
|
|
||||||
axios[method](url, data).then((response) => {
|
|
||||||
this.isLoading = false
|
|
||||||
self.$emit('save', response.data)
|
|
||||||
}, (error) => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
},
|
|
||||||
remove () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
this.errors = []
|
|
||||||
|
|
||||||
const url = `manage/moderation/instance-policies/${this.object.id}/`
|
|
||||||
axios.delete(url).then((response) => {
|
|
||||||
this.isLoading = false
|
|
||||||
self.$emit('delete')
|
|
||||||
}, (error) => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,49 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { BackendError } from '~/types'
|
||||||
|
|
||||||
|
import { computed, ref, reactive } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
interface Invitation {
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const router = useRouter()
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
placeholder: $pgettext('Content/Admin/Input.Placeholder', 'Leave empty for a random code')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const invitations = reactive([] as Invitation[])
|
||||||
|
const code = ref('')
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const submit = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('manage/users/invitations/', { code: code.value })
|
||||||
|
invitations.unshift(response.data)
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUrl = (code: string) => store.getters['instance/absoluteUrl'](router.resolve({
|
||||||
|
name: 'signup',
|
||||||
|
query: { invitation: code.toUpperCase() }
|
||||||
|
}).href)
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<form
|
<form
|
||||||
|
@ -37,7 +83,7 @@
|
||||||
<div class="ui field">
|
<div class="ui field">
|
||||||
<button
|
<button
|
||||||
:class="['ui', {loading: isLoading}, 'button']"
|
:class="['ui', {loading: isLoading}, 'button']"
|
||||||
:disabled="isLoading || null"
|
:disabled="isLoading"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<translate translate-context="Content/Admin/Button.Label/Verb">
|
<translate translate-context="Content/Admin/Button.Label/Verb">
|
||||||
|
@ -90,46 +136,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
code: null,
|
|
||||||
invitations: [],
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
placeholder: this.$pgettext('Content/Admin/Input.Placeholder', 'Leave empty for a random code')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
this.errors = []
|
|
||||||
const url = 'manage/users/invitations/'
|
|
||||||
const payload = {
|
|
||||||
code: this.code
|
|
||||||
}
|
|
||||||
axios.post(url, payload).then((response) => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.invitations.unshift(response.data)
|
|
||||||
}, (error) => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getUrl (code) {
|
|
||||||
return this.$store.getters['instance/absoluteUrl'](this.$router.resolve({ name: 'signup', query: { invitation: code.toUpperCase() } }).href)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,113 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Notification, LibraryFollow } from '~/types'
|
||||||
|
|
||||||
|
import { computed, ref, watchEffect } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initialItem: Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext, $gettext } = useGettext()
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
libraryFollowMessage: $pgettext('Content/Notifications/Paragraph', '%{ username } followed your library "%{ library }"'),
|
||||||
|
libraryAcceptFollowMessage: $pgettext('Content/Notifications/Paragraph', '%{ username } accepted your follow on library "%{ library }"'),
|
||||||
|
libraryRejectMessage: $pgettext('Content/Notifications/Paragraph', 'You rejected %{ username }'s request to follow "%{ library }"'),
|
||||||
|
libraryPendingFollowMessage: $pgettext('Content/Notifications/Paragraph', '%{ username } wants to follow your library "%{ library }"'),
|
||||||
|
markRead: $pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as read'),
|
||||||
|
markUnread: $pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as unread')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const item = ref(props.initialItem)
|
||||||
|
watchEffect(() => (item.value = props.initialItem))
|
||||||
|
|
||||||
|
const username = computed(() => props.initialItem.activity.actor.preferred_username)
|
||||||
|
const notificationData = computed(() => {
|
||||||
|
const activity = props.initialItem.activity
|
||||||
|
|
||||||
|
if (activity.type === 'Follow') {
|
||||||
|
if (activity.object && activity.object.type === 'music.Library') {
|
||||||
|
const detailUrl = { name: 'content.libraries.detail', params: { id: activity.object.uuid } }
|
||||||
|
|
||||||
|
if (activity.related_object?.approved === null) {
|
||||||
|
return {
|
||||||
|
detailUrl,
|
||||||
|
message: $gettext(labels.value.libraryPendingFollowMessage, { username: username.value, library: activity.object.name }),
|
||||||
|
acceptFollow: {
|
||||||
|
buttonClass: 'success',
|
||||||
|
icon: 'check',
|
||||||
|
label: $pgettext('Content/*/Button.Label/Verb', 'Approve'),
|
||||||
|
handler: () => approveLibraryFollow(activity.related_object)
|
||||||
|
},
|
||||||
|
rejectFollow: {
|
||||||
|
buttonClass: 'danger',
|
||||||
|
icon: 'x',
|
||||||
|
label: $pgettext('Content/*/Button.Label/Verb', 'Reject'),
|
||||||
|
handler: () => rejectLibraryFollow(activity.related_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (activity.related_object?.approved) {
|
||||||
|
return {
|
||||||
|
detailUrl,
|
||||||
|
message: $gettext(labels.value.libraryFollowMessage, { username: username.value, library: activity.object.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
detailUrl,
|
||||||
|
message: $gettext(labels.value.libraryRejectMessage, { username: username.value, library: activity.object.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.type === 'Accept') {
|
||||||
|
if (activity.object?.type === 'federation.LibraryFollow') {
|
||||||
|
return {
|
||||||
|
detailUrl: { name: 'content.remote.index' },
|
||||||
|
message: $gettext(labels.value.libraryAcceptFollowMessage, { username: username.value, library: activity.related_object.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const read = ref(false)
|
||||||
|
watchEffect(async () => {
|
||||||
|
await axios.patch(`federation/inbox/${item.value.id}/`, { is_read: read.value })
|
||||||
|
|
||||||
|
item.value.is_read = read.value
|
||||||
|
store.commit('ui/incrementNotifications', { type: 'inbox', count: read.value ? -1 : 1 })
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleAction = (handler?: () => void) => {
|
||||||
|
// call handler then mark notification as read
|
||||||
|
handler?.()
|
||||||
|
read.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const approveLibraryFollow = async (follow: LibraryFollow) => {
|
||||||
|
follow.isLoading = true
|
||||||
|
await axios.post(`federation/follows/library/${follow.uuid}/accept/`)
|
||||||
|
follow.isLoading = false
|
||||||
|
follow.approved = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const rejectLibraryFollow = async (follow: LibraryFollow) => {
|
||||||
|
follow.isLoading = true
|
||||||
|
await axios.post(`federation/follows/library/${follow.uuid}/reject/`)
|
||||||
|
follow.isLoading = false
|
||||||
|
follow.approved = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<tr :class="[{'disabled-row': item.is_read}]">
|
<tr :class="[{'disabled-row': item.is_read}]">
|
||||||
<td>
|
<td>
|
||||||
|
@ -29,7 +139,7 @@
|
||||||
|
|
||||||
<button
|
<button
|
||||||
:class="['ui', 'basic', 'tiny', notificationData.acceptFollow.buttonClass || '', 'button']"
|
:class="['ui', 'basic', 'tiny', notificationData.acceptFollow.buttonClass || '', 'button']"
|
||||||
@click="handleAction(notificationData.acceptFollow.handler)"
|
@click="handleAction(notificationData.acceptFollow?.handler)"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
v-if="notificationData.acceptFollow.icon"
|
v-if="notificationData.acceptFollow.icon"
|
||||||
|
@ -39,7 +149,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:class="['ui', 'basic', 'tiny', notificationData.rejectFollow.buttonClass || '', 'button']"
|
:class="['ui', 'basic', 'tiny', notificationData.rejectFollow.buttonClass || '', 'button']"
|
||||||
@click="handleAction(notificationData.rejectFollow.handler)"
|
@click="handleAction(notificationData.rejectFollow?.handler)"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
v-if="notificationData.rejectFollow.icon"
|
v-if="notificationData.rejectFollow.icon"
|
||||||
|
@ -57,7 +167,7 @@
|
||||||
:aria-label="labels.markUnread"
|
:aria-label="labels.markUnread"
|
||||||
class="discrete link"
|
class="discrete link"
|
||||||
:title="labels.markUnread"
|
:title="labels.markUnread"
|
||||||
@click.prevent="markRead(false)"
|
@click.prevent="read = false"
|
||||||
>
|
>
|
||||||
<i class="redo icon" />
|
<i class="redo icon" />
|
||||||
</a>
|
</a>
|
||||||
|
@ -67,128 +177,10 @@
|
||||||
:aria-label="labels.markRead"
|
:aria-label="labels.markRead"
|
||||||
class="discrete link"
|
class="discrete link"
|
||||||
:title="labels.markRead"
|
:title="labels.markRead"
|
||||||
@click.prevent="markRead(true)"
|
@click.prevent="read = true"
|
||||||
>
|
>
|
||||||
<i class="check icon" />
|
<i class="check icon" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { initialItem: { type: Object, required: true } },
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
item: this.initialItem
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
message () {
|
|
||||||
return 'plop'
|
|
||||||
},
|
|
||||||
labels () {
|
|
||||||
const libraryFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } followed your library "%{ library }"')
|
|
||||||
const libraryAcceptFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } accepted your follow on library "%{ library }"')
|
|
||||||
const libraryRejectMessage = this.$pgettext('Content/Notifications/Paragraph', 'You rejected %{ username }'s request to follow "%{ library }"')
|
|
||||||
const libraryPendingFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } wants to follow your library "%{ library }"')
|
|
||||||
return {
|
|
||||||
libraryFollowMessage,
|
|
||||||
libraryAcceptFollowMessage,
|
|
||||||
libraryRejectMessage,
|
|
||||||
libraryPendingFollowMessage,
|
|
||||||
markRead: this.$pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as read'),
|
|
||||||
markUnread: this.$pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as unread')
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
username () {
|
|
||||||
return this.item.activity.actor.preferred_username
|
|
||||||
},
|
|
||||||
notificationData () {
|
|
||||||
const self = this
|
|
||||||
const a = this.item.activity
|
|
||||||
if (a.type === 'Follow') {
|
|
||||||
if (a.object && a.object.type === 'music.Library') {
|
|
||||||
let acceptFollow = null
|
|
||||||
let rejectFollow = null
|
|
||||||
let message = null
|
|
||||||
if (a.related_object && a.related_object.approved === null) {
|
|
||||||
message = this.labels.libraryPendingFollowMessage
|
|
||||||
acceptFollow = {
|
|
||||||
buttonClass: 'success',
|
|
||||||
icon: 'check',
|
|
||||||
label: this.$pgettext('Content/*/Button.Label/Verb', 'Approve'),
|
|
||||||
handler: () => { self.approveLibraryFollow(a.related_object) }
|
|
||||||
}
|
|
||||||
rejectFollow = {
|
|
||||||
buttonClass: 'danger',
|
|
||||||
icon: 'x',
|
|
||||||
label: this.$pgettext('Content/*/Button.Label/Verb', 'Reject'),
|
|
||||||
handler: () => { self.rejectLibraryFollow(a.related_object) }
|
|
||||||
}
|
|
||||||
} else if (a.related_object && a.related_object.approved) {
|
|
||||||
message = this.labels.libraryFollowMessage
|
|
||||||
} else {
|
|
||||||
message = this.labels.libraryRejectMessage
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
acceptFollow,
|
|
||||||
rejectFollow,
|
|
||||||
detailUrl: { name: 'content.libraries.detail', params: { id: a.object.uuid } },
|
|
||||||
message: this.$gettextInterpolate(
|
|
||||||
message,
|
|
||||||
{ username: this.username, library: a.object.name }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.type === 'Accept') {
|
|
||||||
if (a.object && a.object.type === 'federation.LibraryFollow') {
|
|
||||||
return {
|
|
||||||
detailUrl: { name: 'content.remote.index' },
|
|
||||||
message: this.$gettextInterpolate(
|
|
||||||
this.labels.libraryAcceptFollowMessage,
|
|
||||||
{ username: this.username, library: a.related_object.name }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleAction (handler) {
|
|
||||||
// call handler then mark notification as read
|
|
||||||
handler()
|
|
||||||
this.markRead(true)
|
|
||||||
},
|
|
||||||
approveLibraryFollow (follow) {
|
|
||||||
const action = 'accept'
|
|
||||||
axios.post(`federation/follows/library/${follow.uuid}/${action}/`).then((response) => {
|
|
||||||
follow.isLoading = false
|
|
||||||
follow.approved = true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
rejectLibraryFollow (follow) {
|
|
||||||
const action = 'reject'
|
|
||||||
axios.post(`federation/follows/library/${follow.uuid}/${action}/`).then((response) => {
|
|
||||||
follow.isLoading = false
|
|
||||||
follow.approved = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
markRead (value) {
|
|
||||||
const self = this
|
|
||||||
axios.patch(`federation/inbox/${this.item.id}/`, { is_read: value }).then((response) => {
|
|
||||||
self.item.is_read = value
|
|
||||||
if (value) {
|
|
||||||
self.$store.commit('ui/incrementNotifications', { type: 'inbox', count: -1 })
|
|
||||||
} else {
|
|
||||||
self.$store.commit('ui/incrementNotifications', { type: 'inbox', count: 1 })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,28 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Track } from '~/types'
|
||||||
|
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
track?: Track | null
|
||||||
|
button?: boolean
|
||||||
|
border?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
track: null,
|
||||||
|
button: false,
|
||||||
|
border: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…')
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
v-if="button"
|
v-if="button"
|
||||||
|
@ -19,26 +44,3 @@
|
||||||
<i :class="['list', 'basic', 'icon']" />
|
<i :class="['list', 'basic', 'icon']" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
track: { type: Object, default: function () { return {} } },
|
|
||||||
button: { type: Boolean, default: false },
|
|
||||||
border: { type: Boolean, default: false }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
showModal: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
addToPlaylist: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,53 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Playlist } from '~/types'
|
||||||
|
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
import PlaylistCard from '~/components/playlists/Card.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
filters: Record<string, unknown>
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const objects = reactive([] as Playlist[])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const nextPage = ref('')
|
||||||
|
const fetchData = async (url = props.url) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
...props.filters,
|
||||||
|
page_size: props.filters.limit ?? 3
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(url, { params })
|
||||||
|
nextPage.value = response.data.next
|
||||||
|
objects.push(...response.data.results)
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => store.state.moderation.lastUpdate,
|
||||||
|
() => fetchData(),
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h3
|
<h3
|
||||||
|
@ -13,7 +63,7 @@
|
||||||
<div class="ui loader" />
|
<div class="ui loader" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="playlistsExist"
|
v-if="objects.length > 0"
|
||||||
class="ui cards app-cards"
|
class="ui cards app-cards"
|
||||||
>
|
>
|
||||||
<playlist-card
|
<playlist-card
|
||||||
|
@ -57,73 +107,3 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { clone } from 'lodash-es'
|
|
||||||
import axios from 'axios'
|
|
||||||
import PlaylistCard from '~/components/playlists/Card.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
PlaylistCard
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
filters: { type: Object, required: true },
|
|
||||||
url: { type: String, required: true }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
objects: [],
|
|
||||||
limit: this.filters.limit || 3,
|
|
||||||
isLoading: false,
|
|
||||||
errors: null,
|
|
||||||
previousPage: null,
|
|
||||||
nextPage: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
playlistsExist: function () {
|
|
||||||
return this.objects.length > 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
offset () {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
'$store.state.moderation.lastUpdate': function () {
|
|
||||||
this.fetchData(this.url)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData(this.url)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData (url) {
|
|
||||||
if (!url) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.isLoading = true
|
|
||||||
const self = this
|
|
||||||
const params = clone(this.filters)
|
|
||||||
params.page_size = this.limit
|
|
||||||
params.offset = this.offset
|
|
||||||
axios.get(url, { params }).then((response) => {
|
|
||||||
self.previousPage = response.data.previous
|
|
||||||
self.nextPage = response.data.next
|
|
||||||
self.isLoading = false
|
|
||||||
self.objects = [...self.objects, ...response.data.results]
|
|
||||||
}, error => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateOffset (increment) {
|
|
||||||
if (increment) {
|
|
||||||
this.offset += this.limit
|
|
||||||
} else {
|
|
||||||
this.offset = Math.max(this.offset - this.limit, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,34 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Radio } from '~/types'
|
||||||
|
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
|
import RadioButton from './Button.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: string
|
||||||
|
customRadio?: Radio | null
|
||||||
|
objectId?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
customRadio: null,
|
||||||
|
objectId: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const isDescriptionExpanded = ref(false)
|
||||||
|
|
||||||
|
const radio = computed(() => props.customRadio
|
||||||
|
? props.customRadio
|
||||||
|
: store.getters['radios/types'][props.type]
|
||||||
|
)
|
||||||
|
|
||||||
|
const customRadioId = computed(() => props.customRadio?.id ?? null)
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui card">
|
<div class="ui card">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -35,7 +66,7 @@
|
||||||
:object-id="objectId"
|
:object-id="objectId"
|
||||||
/>
|
/>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="$store.state.auth.authenticated && type === 'custom' && radio.user.id === $store.state.auth.profile.id"
|
v-if="$store.state.auth.authenticated && type === 'custom' && radio.user.id === $store.state.auth.profile?.id"
|
||||||
class="ui success button right floated"
|
class="ui success button right floated"
|
||||||
:to="{name: 'library.radios.edit', params: {id: customRadioId }}"
|
:to="{name: 'library.radios.edit', params: {id: customRadioId }}"
|
||||||
>
|
>
|
||||||
|
@ -46,37 +77,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import RadioButton from './Button.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
RadioButton
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
type: { type: String, required: true, default: '' },
|
|
||||||
customRadio: { type: Object, required: false, default: () => { return {} } },
|
|
||||||
objectId: { type: String, required: false, default: null }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isDescriptionExpanded: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
radio () {
|
|
||||||
if (Object.keys(this.customRadio).length > 0) {
|
|
||||||
return this.customRadio
|
|
||||||
}
|
|
||||||
return this.$store.getters['radios/types'][this.type]
|
|
||||||
},
|
|
||||||
customRadioId: function () {
|
|
||||||
if (this.customRadio) {
|
|
||||||
return this.customRadio.id
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -21,9 +21,11 @@ const store: Module<State, RootState> = {
|
||||||
playlists (state, value) {
|
playlists (state, value) {
|
||||||
state.playlists = value
|
state.playlists = value
|
||||||
},
|
},
|
||||||
chooseTrack (state, value) {
|
chooseTrack (state, value: Track | null) {
|
||||||
state.showModal = true
|
if (value !== null) {
|
||||||
state.modalTrack = value
|
state.showModal = true
|
||||||
|
state.modalTrack = value
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showModal (state, value) {
|
showModal (state, value) {
|
||||||
state.showModal = value
|
state.showModal = value
|
||||||
|
|
|
@ -161,6 +161,10 @@ export interface LibraryFollow {
|
||||||
uuid: string
|
uuid: string
|
||||||
approved: boolean
|
approved: boolean
|
||||||
|
|
||||||
|
name: string
|
||||||
|
type?: 'music.Library' | 'federation.LibraryFollow'
|
||||||
|
target: Library
|
||||||
|
|
||||||
// TODO (wvffle): Check if it's not added only on frontend side
|
// TODO (wvffle): Check if it's not added only on frontend side
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
}
|
}
|
||||||
|
@ -199,7 +203,7 @@ export interface PlaylistTrack {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Radio {
|
export interface Radio {
|
||||||
id: string
|
id: number
|
||||||
name: string
|
name: string
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
|
@ -466,9 +470,18 @@ export interface UserRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notification stuff
|
// Notification stuff
|
||||||
|
export type Activity = {
|
||||||
|
actor: Actor
|
||||||
|
creation_date: string
|
||||||
|
related_object: LibraryFollow
|
||||||
|
type: 'Follow' | 'Accept'
|
||||||
|
object: LibraryFollow
|
||||||
|
}
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
id: number
|
id: number
|
||||||
is_read: boolean
|
is_read: boolean
|
||||||
|
activity: Activity
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tags stuff
|
// Tags stuff
|
||||||
|
|
|
@ -1,3 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import FetchButton from '~/components/federation/FetchButton.vue'
|
||||||
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
statsWarning: $pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/channels/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoadingStats = ref(false)
|
||||||
|
const stats = ref()
|
||||||
|
const fetchStats = async () => {
|
||||||
|
isLoadingStats.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/channels/${props.id}/stats/`)
|
||||||
|
stats.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingStats.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchStats()
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(`manage/channels/${props.id}/`)
|
||||||
|
router.push({ name: 'manage.channels' })
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -421,72 +496,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import TagsList from '~/components/tags/List.vue'
|
|
||||||
import FetchButton from '~/components/federation/FetchButton.vue'
|
|
||||||
import { humanSize, truncate } from '~/utils/filters'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
FetchButton,
|
|
||||||
TagsList
|
|
||||||
},
|
|
||||||
props: { id: { type: String, required: true } },
|
|
||||||
setup () {
|
|
||||||
return { humanSize, truncate }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
isLoadingStats: false,
|
|
||||||
object: null,
|
|
||||||
stats: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
this.fetchStats()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/channels/${this.id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchStats () {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingStats = true
|
|
||||||
const url = `manage/channels/${this.id}/stats/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.stats = response.data
|
|
||||||
self.isLoadingStats = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
remove () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/channels/${this.id}/`
|
|
||||||
axios.delete(url).then(response => {
|
|
||||||
self.$router.push({ name: 'manage.channels' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getQuery (field, value) {
|
|
||||||
return `${field}:"${value}"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import FetchButton from '~/components/federation/FetchButton.vue'
|
||||||
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
statsWarning: $pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/library/albums/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoadingStats = ref(false)
|
||||||
|
const stats = ref()
|
||||||
|
const fetchStats = async () => {
|
||||||
|
isLoadingStats.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/library/albums/${props.id}/stats/`)
|
||||||
|
stats.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingStats.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchStats()
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(`manage/library/albums/${props.id}/`)
|
||||||
|
router.push({ name: 'manage.library.albums' })
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -405,71 +480,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import FetchButton from '~/components/federation/FetchButton.vue'
|
|
||||||
import TagsList from '~/components/tags/List.vue'
|
|
||||||
import { humanSize, truncate } from '~/utils/filters'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
FetchButton,
|
|
||||||
TagsList
|
|
||||||
},
|
|
||||||
props: { id: { type: Number, required: true } },
|
|
||||||
setup () {
|
|
||||||
return { humanSize, truncate }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
isLoadingStats: false,
|
|
||||||
object: null,
|
|
||||||
stats: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
this.fetchStats()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/library/albums/${this.id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchStats () {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingStats = true
|
|
||||||
const url = `manage/library/albums/${this.id}/stats/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.stats = response.data
|
|
||||||
self.isLoadingStats = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
remove () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/library/albums/${this.id}/`
|
|
||||||
axios.delete(url).then(response => {
|
|
||||||
self.$router.push({ name: 'manage.library.albums' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getQuery (field, value) {
|
|
||||||
return `${field}:"${value}"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import FetchButton from '~/components/federation/FetchButton.vue'
|
||||||
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
statsWarning: $pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/library/artists/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoadingStats = ref(false)
|
||||||
|
const stats = ref()
|
||||||
|
const fetchStats = async () => {
|
||||||
|
isLoadingStats.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/library/artists/${props.id}/stats/`)
|
||||||
|
stats.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingStats.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchStats()
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(`manage/library/artists/${props.id}/`)
|
||||||
|
router.push({ name: 'manage.library.artists' })
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -416,76 +491,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import TagsList from '~/components/tags/List.vue'
|
|
||||||
import FetchButton from '~/components/federation/FetchButton.vue'
|
|
||||||
import { humanSize, truncate } from '~/utils/filters'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
FetchButton,
|
|
||||||
TagsList
|
|
||||||
},
|
|
||||||
props: { id: { type: Number, required: true } },
|
|
||||||
setup () {
|
|
||||||
return { humanSize, truncate }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
isLoadingStats: false,
|
|
||||||
object: null,
|
|
||||||
stats: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
this.fetchStats()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/library/artists/${this.id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
if (response.data.channel) {
|
|
||||||
self.$router.push({ name: 'manage.channels.detail', params: { id: response.data.channel } })
|
|
||||||
} else {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchStats () {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingStats = true
|
|
||||||
const url = `manage/library/artists/${this.id}/stats/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.stats = response.data
|
|
||||||
self.isLoadingStats = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
remove () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/library/artists/${this.id}/`
|
|
||||||
axios.delete(url).then(response => {
|
|
||||||
self.$router.push({ name: 'manage.library.artists' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getQuery (field, value) {
|
|
||||||
return `${field}:"${value}"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,98 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { PrivacyLevel } from '~/types'
|
||||||
|
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
import useLogger from '~/composables/useLogger'
|
||||||
|
|
||||||
|
const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[]
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const sharedLabels = useSharedLabels()
|
||||||
|
const router = useRouter()
|
||||||
|
const logger = useLogger()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
statsWarning: $pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/library/libraries/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoadingStats = ref(false)
|
||||||
|
const stats = ref()
|
||||||
|
const fetchStats = async () => {
|
||||||
|
isLoadingStats.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/library/libraries/${props.id}/stats/`)
|
||||||
|
stats.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingStats.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchStats()
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(`manage/library/libraries/${props.id}/`)
|
||||||
|
router.push({ name: 'manage.library.libraries' })
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
|
||||||
|
const updateObj = async (attr: string) => {
|
||||||
|
const params = {
|
||||||
|
[attr]: object.value[attr]
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.patch(`manage/library/libraries/${props.id}/`, params)
|
||||||
|
logger.info(`${attr} was updated succcessfully to ${params[attr]}`)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error while setting ${attr} to ${params[attr]}`, error)
|
||||||
|
// TODO (wvffle): Use error handler with custom msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -148,15 +243,15 @@
|
||||||
@change="updateObj('privacy_level')"
|
@change="updateObj('privacy_level')"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="(p, key) in ['me', 'instance', 'everyone']"
|
v-for="p in PRIVACY_LEVELS"
|
||||||
:key="key"
|
:key="p"
|
||||||
:value="p"
|
:value="p"
|
||||||
>
|
>
|
||||||
{{ sharedLabels.fields.privacy_level.shortChoices[p] }}
|
{{ sharedLabels.fields.privacy_level.shortChoices[p] }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ sharedLabels.fields.privacy_level.shortChoices[object.privacy_level] }}
|
{{ sharedLabels.fields.privacy_level.shortChoices[object.privacy_level as PrivacyLevel] }}
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -361,91 +456,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import { humanSize, truncate } from '~/utils/filters'
|
|
||||||
import useLogger from '~/composables/useLogger'
|
|
||||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
|
||||||
|
|
||||||
const logger = useLogger()
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { id: { type: String, required: true } },
|
|
||||||
setup () {
|
|
||||||
const sharedLabels = useSharedLabels()
|
|
||||||
return { sharedLabels, humanSize, truncate }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
isLoadingStats: false,
|
|
||||||
object: null,
|
|
||||||
stats: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
this.fetchStats()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/library/libraries/${this.id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchStats () {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingStats = true
|
|
||||||
const url = `manage/library/libraries/${this.id}/stats/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.stats = response.data
|
|
||||||
self.isLoadingStats = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
remove () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/library/libraries/${this.id}/`
|
|
||||||
axios.delete(url).then(response => {
|
|
||||||
self.$router.push({ name: 'manage.library.libraries' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getQuery (field, value) {
|
|
||||||
return `${field}:"${value}"`
|
|
||||||
},
|
|
||||||
updateObj (attr, toNull) {
|
|
||||||
let newValue = this.object[attr]
|
|
||||||
if (toNull && !newValue) {
|
|
||||||
newValue = null
|
|
||||||
}
|
|
||||||
const params = {}
|
|
||||||
params[attr] = newValue
|
|
||||||
axios.patch(`manage/library/libraries/${this.id}/`, params).then(
|
|
||||||
response => {
|
|
||||||
logger.info(
|
|
||||||
`${attr} was updated succcessfully to ${newValue}`
|
|
||||||
)
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
logger.error(
|
|
||||||
`Error while setting ${attr} to ${newValue}`,
|
|
||||||
error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,53 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { truncate } from '~/utils/filters'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/tags/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(`manage/tags/${props.id}/`)
|
||||||
|
router.push({ name: 'manage.library.tags' })
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -124,22 +174,9 @@
|
||||||
<translate translate-context="Content/Moderation/Title">
|
<translate translate-context="Content/Moderation/Title">
|
||||||
Activity
|
Activity
|
||||||
</translate>
|
</translate>
|
||||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<table class="ui very basic table">
|
||||||
v-if="isLoadingStats"
|
|
||||||
class="ui placeholder"
|
|
||||||
>
|
|
||||||
<div class="full line" />
|
|
||||||
<div class="short line" />
|
|
||||||
<div class="medium line" />
|
|
||||||
<div class="long line" />
|
|
||||||
</div>
|
|
||||||
<table
|
|
||||||
v-else
|
|
||||||
class="ui very basic table"
|
|
||||||
>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
@ -163,7 +200,6 @@
|
||||||
<translate translate-context="Content/Moderation/Title">
|
<translate translate-context="Content/Moderation/Title">
|
||||||
Audio content
|
Audio content
|
||||||
</translate>
|
</translate>
|
||||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
<table class="ui very basic table">
|
<table class="ui very basic table">
|
||||||
|
@ -213,55 +249,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import { truncate } from '~/utils/filters'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { id: { type: Number, required: true } },
|
|
||||||
setup () {
|
|
||||||
return { truncate }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
isLoadingStats: false,
|
|
||||||
object: null,
|
|
||||||
stats: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/tags/${this.id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
remove () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/tags/${this.id}/`
|
|
||||||
axios.delete(url).then(response => {
|
|
||||||
self.$router.push({ name: 'manage.library.tags' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getQuery (field, value) {
|
|
||||||
return `${field}:"${value}"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,66 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { PrivacyLevel, ImportStatus } from '~/types'
|
||||||
|
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import time from '~/utils/time'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||||
|
|
||||||
|
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const sharedLabels = useSharedLabels()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const privacyLevels = computed(() => sharedLabels.fields.privacy_level.shortChoices[object.value.library.privacy_level as PrivacyLevel])
|
||||||
|
const importStatus = computed(() => sharedLabels.fields.import_status.choices[object.value.import_status as ImportStatus].label)
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/library/uploads/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(`manage/uploads/${props.id}/`)
|
||||||
|
router.push({ name: 'manage.library.uploads' })
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
const displayName = (object: any) => object.filename ?? object.source ?? object.uuid
|
||||||
|
|
||||||
|
const showUploadDetailModal = ref(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -156,7 +219,7 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ sharedLabels.fields.privacy_level.shortChoices[object.library.privacy_level] }}
|
{{ privacyLevels }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -192,11 +255,11 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ sharedLabels.fields.import_status.choices[object.import_status].label }}
|
{{ importStatus }}
|
||||||
<button
|
<button
|
||||||
class="ui tiny basic icon button"
|
class="ui tiny basic icon button"
|
||||||
:title="sharedLabels.fields.import_status.detailTitle"
|
:title="sharedLabels.fields.import_status.detailTitle"
|
||||||
@click="detailedUpload = object; showUploadDetailModal = true"
|
@click="showUploadDetailModal = true"
|
||||||
>
|
>
|
||||||
<i class="question circle outline icon" />
|
<i class="question circle outline icon" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -380,72 +443,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
|
||||||
import time from '~/utils/time'
|
|
||||||
import { humanSize, truncate } from '~/utils/filters'
|
|
||||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ImportStatusModal
|
|
||||||
},
|
|
||||||
props: { id: { type: Number, required: true } },
|
|
||||||
setup () {
|
|
||||||
const sharedLabels = useSharedLabels()
|
|
||||||
return { sharedLabels, humanSize, time, truncate }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
detailedUpload: {},
|
|
||||||
showUploadDetailModal: false,
|
|
||||||
isLoading: true,
|
|
||||||
object: null,
|
|
||||||
stats: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/library/uploads/${this.id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
remove () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/library/uploads/${this.id}/`
|
|
||||||
axios.delete(url).then(response => {
|
|
||||||
self.$router.push({ name: 'manage.library.uploads' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getQuery (field, value) {
|
|
||||||
return `${field}:"${value}"`
|
|
||||||
},
|
|
||||||
displayName (upload) {
|
|
||||||
if (upload.filename) {
|
|
||||||
return upload.filename
|
|
||||||
}
|
|
||||||
if (upload.source) {
|
|
||||||
return upload.source
|
|
||||||
}
|
|
||||||
return upload.uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,148 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { InstancePolicy } from '~/types'
|
||||||
|
|
||||||
|
import { computed, ref, reactive, nextTick, watch } from 'vue'
|
||||||
|
import { useCurrentElement } from '@vueuse/core'
|
||||||
|
import { humanSize } from '~/utils/filters'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import $ from 'jquery'
|
||||||
|
|
||||||
|
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
||||||
|
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
import useLogger from '~/composables/useLogger'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const logger = useLogger()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
statsWarning: $pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
|
||||||
|
uploadQuota: $pgettext('Content/Moderation/Help text', 'Determine how much content the user can upload. Leave empty to use the default value of the instance.')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const allPermissions = computed(() => [
|
||||||
|
{ code: 'library', label: $pgettext('*/*/*/Noun', 'Library') },
|
||||||
|
{ code: 'moderation', label: $pgettext('*/Moderation/*', 'Moderation') },
|
||||||
|
{ code: 'settings', label: $pgettext('*/*/*/Noun', 'Settings') }
|
||||||
|
])
|
||||||
|
|
||||||
|
const isLoadingPolicy = ref(false)
|
||||||
|
const policy = ref()
|
||||||
|
const fetchPolicy = async (id: string) => {
|
||||||
|
isLoadingPolicy.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/moderation/instance-policies/${id}/`)
|
||||||
|
policy.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingPolicy.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = reactive([] as string[])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/accounts/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
|
||||||
|
if (response.data.instance_policy) {
|
||||||
|
fetchPolicy(response.data.instance_policy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.user) {
|
||||||
|
for (const { code } of allPermissions.value) {
|
||||||
|
if (response.data.user.permissions[code]) {
|
||||||
|
permissions.push(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoadingStats = ref(false)
|
||||||
|
const stats = ref()
|
||||||
|
const fetchStats = async () => {
|
||||||
|
isLoadingStats.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/accounts/${props.id}/stats/`)
|
||||||
|
stats.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingStats.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchStats()
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const el = useCurrentElement()
|
||||||
|
watch(object, async () => {
|
||||||
|
await nextTick()
|
||||||
|
$(el.value).find('select.dropdown').dropdown()
|
||||||
|
})
|
||||||
|
|
||||||
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
|
||||||
|
const updating = reactive(new Set<string>())
|
||||||
|
const updateUser = async (attr: string, toNull = false) => {
|
||||||
|
let newValue = object.value.user[attr]
|
||||||
|
if (toNull && !newValue) {
|
||||||
|
newValue = null
|
||||||
|
}
|
||||||
|
|
||||||
|
updating.add(attr)
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
[attr]: newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr === 'permissions') {
|
||||||
|
params.permissions = allPermissions.value.reduce((acc, { code }) => {
|
||||||
|
acc[code] = permissions.includes(code)
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, boolean>)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.patch(`manage/users/users/${object.value.user.id}/`, params)
|
||||||
|
logger.info(`${attr} was updated succcessfully to ${newValue}`)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error while setting ${attr} to ${newValue}`, error)
|
||||||
|
// TODO: Use error handler
|
||||||
|
}
|
||||||
|
|
||||||
|
updating.delete(attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showPolicyForm = ref(false)
|
||||||
|
const updatePolicy = (newPolicy: InstancePolicy) => {
|
||||||
|
policy.value = newPolicy
|
||||||
|
showPolicyForm.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="page-admin-account-detail">
|
<main class="page-admin-account-detail">
|
||||||
<div
|
<div
|
||||||
|
@ -207,7 +352,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div
|
<div
|
||||||
v-if="object.user.username != $store.state.auth.profile.username"
|
v-if="object.user.username != $store.state.auth.profile?.username"
|
||||||
class="ui toggle checkbox"
|
class="ui toggle checkbox"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -262,7 +407,7 @@
|
||||||
{{ p.label }}
|
{{ p.label }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<action-feedback :is-loading="updating.permissions" />
|
<action-feedback :is-loading="updating.has('permissions')" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -470,7 +615,7 @@
|
||||||
<action-feedback
|
<action-feedback
|
||||||
class="ui basic label"
|
class="ui basic label"
|
||||||
size="tiny"
|
size="tiny"
|
||||||
:is-loading="updating.upload_quota"
|
:is-loading="updating.has('upload_quota')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -560,155 +705,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import $ from 'jquery'
|
|
||||||
|
|
||||||
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
|
||||||
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
|
|
||||||
import useLogger from '~/composables/useLogger'
|
|
||||||
import { humanSize } from '~/utils/filters'
|
|
||||||
|
|
||||||
const logger = useLogger()
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
InstancePolicyForm,
|
|
||||||
InstancePolicyCard
|
|
||||||
},
|
|
||||||
props: { id: { type: Number, required: true } },
|
|
||||||
setup () {
|
|
||||||
return { humanSize }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
isLoadingStats: false,
|
|
||||||
isLoadingPolicy: false,
|
|
||||||
object: null,
|
|
||||||
stats: null,
|
|
||||||
showPolicyForm: false,
|
|
||||||
permissions: [],
|
|
||||||
updating: {
|
|
||||||
permissions: false,
|
|
||||||
upload_quota: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account'),
|
|
||||||
uploadQuota: this.$pgettext('Content/Moderation/Help text', 'Determine how much content the user can upload. Leave empty to use the default value of the instance.')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
allPermissions () {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
code: 'library',
|
|
||||||
label: this.$pgettext('*/*/*/Noun', 'Library')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'moderation',
|
|
||||||
label: this.$pgettext('*/Moderation/*', 'Moderation')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'settings',
|
|
||||||
label: this.$pgettext('*/*/*/Noun', 'Settings')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
object () {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
$(this.$el).find('select.dropdown').dropdown()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
this.fetchStats()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = 'manage/accounts/' + this.id + '/'
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
if (self.object.instance_policy) {
|
|
||||||
self.fetchPolicy(self.object.instance_policy)
|
|
||||||
}
|
|
||||||
if (response.data.user) {
|
|
||||||
self.allPermissions.forEach(p => {
|
|
||||||
if (self.object.user.permissions[p.code]) {
|
|
||||||
self.permissions.push(p.code)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchPolicy (id) {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingPolicy = true
|
|
||||||
const url = `manage/moderation/instance-policies/${id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.policy = response.data
|
|
||||||
self.isLoadingPolicy = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchStats () {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingStats = true
|
|
||||||
const url = 'manage/accounts/' + this.id + '/stats/'
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.stats = response.data
|
|
||||||
self.isLoadingStats = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
refreshNodeInfo (data) {
|
|
||||||
this.object.nodeinfo = data
|
|
||||||
this.object.nodeinfo_fetch_date = new Date()
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUser (attr, toNull) {
|
|
||||||
let newValue = this.object.user[attr]
|
|
||||||
if (toNull && !newValue) {
|
|
||||||
newValue = null
|
|
||||||
}
|
|
||||||
const self = this
|
|
||||||
this.updating[attr] = true
|
|
||||||
const params = {}
|
|
||||||
if (attr === 'permissions') {
|
|
||||||
params.permissions = {}
|
|
||||||
this.allPermissions.forEach(p => {
|
|
||||||
params.permissions[p.code] = this.permissions.indexOf(p.code) > -1
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
params[attr] = newValue
|
|
||||||
}
|
|
||||||
axios.patch(`manage/users/users/${this.object.user.id}/`, params).then(
|
|
||||||
response => {
|
|
||||||
logger.info(
|
|
||||||
`${attr} was updated succcessfully to ${newValue}`
|
|
||||||
)
|
|
||||||
self.updating[attr] = false
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
logger.error(
|
|
||||||
`Error while setting ${attr} to ${newValue}`,
|
|
||||||
error
|
|
||||||
)
|
|
||||||
self.updating[attr] = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
getQuery (field, value) {
|
|
||||||
return `${field}:"${value}"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { get } from 'lodash-es'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const allowListEnabled = ref(false)
|
||||||
|
const labels = computed(() => ({
|
||||||
|
moderation: $pgettext('*/Moderation/*', 'Moderation'),
|
||||||
|
secondaryMenu: $pgettext('Menu/*/Hidden text', 'Secondary menu')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const fetchNodeInfo = async () => {
|
||||||
|
const response = await axios.get('instance/nodeinfo/2.0/')
|
||||||
|
allowListEnabled.value = get(response.data, 'metadata.allowList.enabled', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchNodeInfo()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-title="labels.moderation"
|
v-title="labels.moderation"
|
||||||
|
@ -59,35 +82,3 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { get } from 'lodash-es'
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
allowListEnabled: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
moderation: this.$pgettext('*/Moderation/*', 'Moderation'),
|
|
||||||
secondaryMenu: this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchNodeInfo()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchNodeInfo () {
|
|
||||||
const self = this
|
|
||||||
axios.get('instance/nodeinfo/2.0/').then(response => {
|
|
||||||
self.allowListEnabled = get(response.data, 'metadata.allowList.enabled', false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,111 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { InstancePolicy } from '~/types'
|
||||||
|
|
||||||
|
import { humanSize } from '~/utils/filters'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { get } from 'lodash-es'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
||||||
|
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number
|
||||||
|
allowListEnabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
statsWarning: $pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoadingPolicy = ref(false)
|
||||||
|
const policy = ref()
|
||||||
|
const fetchPolicy = async (id: string) => {
|
||||||
|
isLoadingPolicy.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/moderation/instance-policies/${id}/`)
|
||||||
|
policy.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingPolicy.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const externalUrl = computed(() => `https://${object.value?.name}`)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/federation/domains/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
if (response.data.instance_policy) {
|
||||||
|
fetchPolicy(response.data.instance_policy)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoadingStats = ref(false)
|
||||||
|
const stats = ref()
|
||||||
|
const fetchStats = async () => {
|
||||||
|
isLoadingStats.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/federation/domains/${props.id}/stats/`)
|
||||||
|
stats.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingStats.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchStats()
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const refreshNodeInfo = (data: any) => {
|
||||||
|
object.value.nodeinfo = data
|
||||||
|
object.value.nodeinfo_fetch_date = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
|
||||||
|
const showPolicyForm = ref(false)
|
||||||
|
const updatePolicy = (newPolicy: InstancePolicy) => {
|
||||||
|
policy.value = newPolicy
|
||||||
|
showPolicyForm.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoadingAllowList = ref(false)
|
||||||
|
const setAllowList = async (value: boolean) => {
|
||||||
|
isLoadingAllowList.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.patch(`manage/federation/domains/${props.id}/`, { allowed: value })
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingAllowList.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="page-admin-domain-detail">
|
<main class="page-admin-domain-detail">
|
||||||
<div
|
<div
|
||||||
|
@ -34,7 +142,7 @@
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
<div class="ui icon buttons">
|
<div class="ui icon buttons">
|
||||||
<a
|
<a
|
||||||
v-if="$store.state.auth.profile.is_superuser"
|
v-if="$store.state.auth.profile?.is_superuser"
|
||||||
class="ui labeled icon button"
|
class="ui labeled icon button"
|
||||||
:href="$store.getters['instance/absoluteUrl'](`/api/admin/federation/domain/${object.name}`)"
|
:href="$store.getters['instance/absoluteUrl'](`/api/admin/federation/domain/${object.name}`)"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -455,103 +563,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import { get } from 'lodash-es'
|
|
||||||
|
|
||||||
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
|
||||||
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
|
|
||||||
import { humanSize } from '~/utils/filters'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
InstancePolicyForm,
|
|
||||||
InstancePolicyCard
|
|
||||||
},
|
|
||||||
props: { id: { type: String, required: true }, allowListEnabled: { type: Boolean, required: true } },
|
|
||||||
setup () {
|
|
||||||
return { humanSize }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
get,
|
|
||||||
isLoading: true,
|
|
||||||
isLoadingStats: false,
|
|
||||||
isLoadingPolicy: false,
|
|
||||||
isLoadingAllowList: false,
|
|
||||||
policy: null,
|
|
||||||
object: null,
|
|
||||||
stats: null,
|
|
||||||
showPolicyForm: false,
|
|
||||||
permissions: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
externalUrl () {
|
|
||||||
return `https://${this.object.name}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
this.fetchStats()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = 'manage/federation/domains/' + this.id + '/'
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
if (self.object.instance_policy) {
|
|
||||||
self.fetchPolicy(self.object.instance_policy)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchStats () {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingStats = true
|
|
||||||
const url = 'manage/federation/domains/' + this.id + '/stats/'
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.stats = response.data
|
|
||||||
self.isLoadingStats = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetchPolicy (id) {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingPolicy = true
|
|
||||||
const url = `manage/moderation/instance-policies/${id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.policy = response.data
|
|
||||||
self.isLoadingPolicy = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setAllowList (value) {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingAllowList = true
|
|
||||||
const url = `manage/federation/domains/${this.id}/`
|
|
||||||
axios.patch(url, { allowed: value }).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoadingAllowList = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
refreshNodeInfo (data) {
|
|
||||||
this.object.nodeinfo = data
|
|
||||||
this.object.nodeinfo_fetch_date = new Date()
|
|
||||||
},
|
|
||||||
updatePolicy (policy) {
|
|
||||||
this.policy = policy
|
|
||||||
this.showPolicyForm = false
|
|
||||||
},
|
|
||||||
getQuery (field, value) {
|
|
||||||
return `${field}:"${value}"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,51 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { BackendError } from '~/types'
|
||||||
|
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import DomainsTable from '~/components/manage/moderation/DomainsTable.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
allowListEnabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
domains: $pgettext('*/Moderation/*/Noun', 'Domains')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const domainName = ref('')
|
||||||
|
const domainAllowed = ref(props.allowListEnabled || undefined)
|
||||||
|
|
||||||
|
const isCreating = ref(false)
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const createDomain = async () => {
|
||||||
|
isCreating.value = true
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('manage/federation/domains/', { name: domainName.value, allowed: domainAllowed.value })
|
||||||
|
router.push({
|
||||||
|
name: 'manage.moderation.domains.detail',
|
||||||
|
params: { id: response.data.name }
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isCreating.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main v-title="labels.domains">
|
<main v-title="labels.domains">
|
||||||
<section class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
|
@ -55,7 +103,7 @@
|
||||||
<button
|
<button
|
||||||
:class="['ui', {'loading': isCreating}, 'success', 'button']"
|
:class="['ui', {'loading': isCreating}, 'success', 'button']"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="isCreating || null"
|
:disabled="isCreating"
|
||||||
>
|
>
|
||||||
<translate translate-context="Content/Moderation/Button/Verb">
|
<translate translate-context="Content/Moderation/Button/Verb">
|
||||||
Add
|
Add
|
||||||
|
@ -65,51 +113,10 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="ui clearing hidden divider" />
|
<div class="ui clearing hidden divider" />
|
||||||
<domains-table :allow-list-enabled="allowListEnabled" />
|
<domains-table
|
||||||
|
:ordering-config-name="null"
|
||||||
|
:allow-list-enabled="allowListEnabled"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import DomainsTable from '~/components/manage/moderation/DomainsTable.vue'
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
DomainsTable
|
|
||||||
},
|
|
||||||
props: { allowListEnabled: { type: Boolean, required: true } },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
domainName: '',
|
|
||||||
domainAllowed: this.allowListEnabled ? true : null,
|
|
||||||
isCreating: false,
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
domains: this.$pgettext('*/Moderation/*/Noun', 'Domains')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
createDomain () {
|
|
||||||
const self = this
|
|
||||||
this.isCreating = true
|
|
||||||
this.errors = []
|
|
||||||
axios.post('manage/federation/domains/', { name: this.domainName, allowed: this.domainAllowed }).then((response) => {
|
|
||||||
this.isCreating = false
|
|
||||||
this.$router.push({
|
|
||||||
name: 'manage.moderation.domains.detail',
|
|
||||||
params: { id: response.data.name }
|
|
||||||
})
|
|
||||||
}, (error) => {
|
|
||||||
self.isCreating = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,36 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import ReportCard from '~/components/manage/moderation/ReportCard.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/moderation/reports/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -13,36 +46,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import ReportCard from '~/components/manage/moderation/ReportCard.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ReportCard
|
|
||||||
},
|
|
||||||
props: { id: { type: Number, required: true } },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
object: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/moderation/reports/${this.id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,36 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import UserRequestCard from '~/components/manage/moderation/UserRequestCard.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const object = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`manage/moderation/requests/${props.id}/`)
|
||||||
|
object.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -13,36 +46,3 @@
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import UserRequestCard from '~/components/manage/moderation/UserRequestCard.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
UserRequestCard
|
|
||||||
},
|
|
||||||
props: { id: { type: Number, required: true } },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
object: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const url = `manage/moderation/requests/${this.id}/`
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.object = response.data
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
manageUsers: $pgettext('Head/Admin/Title', 'Manage users'),
|
||||||
|
secondaryMenu: $pgettext('Menu/*/Hidden text', 'Secondary menu')
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-title="labels.manageUsers"
|
v-title="labels.manageUsers"
|
||||||
|
@ -28,16 +40,3 @@
|
||||||
<router-view :key="$route.fullPath" />
|
<router-view :key="$route.fullPath" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
manageUsers: this.$pgettext('Head/Admin/Title', 'Manage users'),
|
|
||||||
secondaryMenu: this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,24 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
state: string
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await store.dispatch('auth/handleOauthCallback', props.code)
|
||||||
|
router.push(props.state ?? '/library')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="main pusher">
|
<main class="main pusher">
|
||||||
<section class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
|
@ -16,17 +37,3 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
state: { type: String, required: true },
|
|
||||||
code: { type: String, required: true }
|
|
||||||
},
|
|
||||||
async mounted () {
|
|
||||||
await this.$store.dispatch('auth/handleOauthCallback', this.code)
|
|
||||||
this.$router.push(this.state || '/library')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,46 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { BackendError } from '~/types'
|
||||||
|
|
||||||
|
import { computed, ref, onMounted } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
defaultKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
confirm: $pgettext('Head/Signup/Title', 'Confirm your e-mail address')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const key = ref(props.defaultKey)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const success = ref(false)
|
||||||
|
const submit = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post('auth/registration/verify-email/', { key: key.value })
|
||||||
|
success.value = true
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (key.value) submit()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<main
|
||||||
v-title="labels.confirm"
|
v-title="labels.confirm"
|
||||||
|
@ -76,51 +119,3 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { defaultKey: { type: String, required: true } },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
errors: [],
|
|
||||||
key: this.defaultKey,
|
|
||||||
success: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
confirm: this.$pgettext('Head/Signup/Title', 'Confirm your e-mail address')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
if (this.key) {
|
|
||||||
this.submit()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit () {
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
self.errors = []
|
|
||||||
const payload = {
|
|
||||||
key: this.key
|
|
||||||
}
|
|
||||||
return axios.post('auth/registration/verify-email/', payload).then(
|
|
||||||
response => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.success = true
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
self.isLoading = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,48 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { BackendError } from '~/types'
|
||||||
|
|
||||||
|
import { computed, ref, onMounted } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
defaultEmail: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
placeholder: $pgettext('Content/Signup/Input.Placeholder', 'Enter the e-mail address linked to your account'),
|
||||||
|
reset: $pgettext('*/Login/*/Verb', 'Reset your password')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const email = ref(props.defaultEmail)
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const submit = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post('auth/password/reset/', { email: email.value })
|
||||||
|
router.push({ name: 'auth.password-reset-confirm' })
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailInput = ref()
|
||||||
|
onMounted(() => emailInput.value.focus())
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<main
|
||||||
v-title="labels.reset"
|
v-title="labels.reset"
|
||||||
|
@ -42,7 +87,7 @@
|
||||||
<label for="account-email"><translate translate-context="Content/Signup/Input.Label">Account's e-mail address</translate></label>
|
<label for="account-email"><translate translate-context="Content/Signup/Input.Label">Account's e-mail address</translate></label>
|
||||||
<input
|
<input
|
||||||
id="account-email"
|
id="account-email"
|
||||||
ref="email"
|
ref="emailInput"
|
||||||
v-model="email"
|
v-model="email"
|
||||||
required
|
required
|
||||||
type="email"
|
type="email"
|
||||||
|
@ -69,54 +114,3 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { defaultEmail: { type: String, required: true } },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
email: this.defaultEmail,
|
|
||||||
isLoading: false,
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
const reset = this.$pgettext('*/Login/*/Verb', 'Reset your password')
|
|
||||||
const placeholder = this.$pgettext('Content/Signup/Input.Placeholder', 'Enter the e-mail address linked to your account'
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
reset,
|
|
||||||
placeholder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
this.$refs.email.focus()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit () {
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
self.errors = []
|
|
||||||
const payload = {
|
|
||||||
email: this.email
|
|
||||||
}
|
|
||||||
return axios.post('auth/password/reset/', payload).then(
|
|
||||||
response => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.$router.push({
|
|
||||||
name: 'auth.password-reset-confirm'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
self.isLoading = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,54 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { BackendError } from '~/types'
|
||||||
|
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import PasswordInput from '~/components/forms/PasswordInput.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
defaultToken: string
|
||||||
|
defaultUid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
changePassword: $pgettext('*/Signup/Title', 'Change your password')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const newPassword = ref('')
|
||||||
|
const token = ref(props.defaultToken)
|
||||||
|
const uid = ref(props.defaultUid)
|
||||||
|
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const success = ref(false)
|
||||||
|
const submit = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post('auth/password/reset/confirm/', {
|
||||||
|
uid: uid.value,
|
||||||
|
token: token.value,
|
||||||
|
new_password1: newPassword.value,
|
||||||
|
new_password2: newPassword.value
|
||||||
|
})
|
||||||
|
|
||||||
|
success.value = true
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<main
|
||||||
v-title="labels.changePassword"
|
v-title="labels.changePassword"
|
||||||
|
@ -84,58 +135,3 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import PasswordInput from '~/components/forms/PasswordInput.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
PasswordInput
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
defaultToken: { type: String, required: true },
|
|
||||||
defaultUid: { type: String, required: true }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
newPassword: '',
|
|
||||||
isLoading: false,
|
|
||||||
errors: [],
|
|
||||||
token: this.defaultToken,
|
|
||||||
uid: this.defaultUid,
|
|
||||||
success: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
changePassword: this.$pgettext('*/Signup/Title', 'Change your password')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit () {
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
self.errors = []
|
|
||||||
const payload = {
|
|
||||||
uid: this.uid,
|
|
||||||
token: this.token,
|
|
||||||
new_password1: this.newPassword,
|
|
||||||
new_password2: this.newPassword
|
|
||||||
}
|
|
||||||
return axios.post('auth/password/reset/confirm/', payload).then(
|
|
||||||
response => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.success = true
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
self.isLoading = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,43 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import PluginForm from '~/components/auth/Plugin.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
title: $pgettext('Head/Login/Title', 'Manage plugins')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const plugins = ref()
|
||||||
|
const libraries = ref()
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [pluginsResponse, librariesResponse] = await Promise.all([
|
||||||
|
axios.get('plugins'),
|
||||||
|
axios.get('libraries', { params: { scope: 'me', page_size: 50 } })
|
||||||
|
])
|
||||||
|
|
||||||
|
plugins.value = pluginsResponse.data
|
||||||
|
libraries.value = librariesResponse.data.results
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<main
|
||||||
v-title="labels.title"
|
v-title="labels.title"
|
||||||
|
@ -26,42 +66,3 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import PluginForm from '~/components/auth/Plugin.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
PluginForm
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
plugins: null,
|
|
||||||
libraries: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
const title = this.$pgettext('Head/Login/Title', 'Manage plugins')
|
|
||||||
return {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created () {
|
|
||||||
await this.fetchData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchData () {
|
|
||||||
this.isLoading = true
|
|
||||||
let response = await axios.get('plugins')
|
|
||||||
this.plugins = response.data
|
|
||||||
response = await axios.get('libraries', { paramis: { scope: 'me', page_size: 50 } })
|
|
||||||
this.libraries = response.data.results
|
|
||||||
this.isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Actor } from '~/types'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import PlaylistWidget from '~/components/playlists/Widget.vue'
|
||||||
|
import TrackWidget from '~/components/audio/track/Widget.vue'
|
||||||
|
import RadioButton from '~/components/radios/Button.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
object: Actor
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
|
||||||
|
const recentActivity = ref(0)
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<div>
|
<div>
|
||||||
|
@ -48,19 +66,3 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import TrackWidget from '~/components/audio/track/Widget.vue'
|
|
||||||
import PlaylistWidget from '~/components/playlists/Widget.vue'
|
|
||||||
import RadioButton from '~/components/radios/Button.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { TrackWidget, PlaylistWidget, RadioButton },
|
|
||||||
props: { object: { type: Object, required: true } },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
recentActivity: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,28 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
|
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import SignupForm from '~/components/auth/SignupForm.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
defaultInvitation?: string
|
||||||
|
next?: RouteLocationRaw
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
defaultInvitation: undefined,
|
||||||
|
next: '/'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
title: $pgettext('*/Signup/Title', 'Sign Up')
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<main
|
||||||
v-title="labels.title"
|
v-title="labels.title"
|
||||||
|
@ -18,37 +43,3 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import SignupForm from '~/components/auth/SignupForm.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
SignupForm
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
defaultInvitation: { type: String, required: false, default: null },
|
|
||||||
next: { type: String, default: '/' }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
username: '',
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
isLoadingInstanceSetting: true,
|
|
||||||
errors: [],
|
|
||||||
isLoading: false,
|
|
||||||
invitation: this.defaultInvitation
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
const title = this.$pgettext('*/Signup/Title', 'Sign Up')
|
|
||||||
return {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Channel } from '~/types'
|
||||||
|
|
||||||
|
import ChannelEntries from '~/components/audio/ChannelEntries.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
object: Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<channel-entries
|
<channel-entries
|
||||||
:default-cover="object.artist.cover"
|
:default-cover="object.artist?.cover"
|
||||||
:is-podcast="object.artist.content_category === 'podcast'"
|
:is-podcast="object.artist?.content_category === 'podcast'"
|
||||||
:limit="25"
|
:limit="25"
|
||||||
:filters="{channel: object.uuid, ordering: 'creation_date'}"
|
:filters="{channel: object.uuid, ordering: 'creation_date'}"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import ChannelEntries from '~/components/audio/ChannelEntries.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ChannelEntries
|
|
||||||
},
|
|
||||||
props: { object: { type: Object, required: true } }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,94 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Channel, Upload } from '~/types'
|
||||||
|
|
||||||
|
import { computed, ref, reactive, watch } from 'vue'
|
||||||
|
import { whenever } from '@vueuse/core'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import ChannelEntries from '~/components/audio/ChannelEntries.vue'
|
||||||
|
import ChannelSeries from '~/components/audio/ChannelSeries.vue'
|
||||||
|
import AlbumModal from '~/components/channels/AlbumModal.vue'
|
||||||
|
|
||||||
|
import useWebSocketHandler from '~/composables/useWebSocketHandler'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
object: Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const isPodcast = computed(() => props.object.artist?.content_category === 'podcast')
|
||||||
|
const isOwner = computed(() => store.state.auth.authenticated && props.object.attributed_to.full_username === store.state.auth.fullUsername)
|
||||||
|
|
||||||
|
const seriesFilters = computed(() => ({
|
||||||
|
artist: props.object.artist?.id,
|
||||||
|
ordering: '-creation_date',
|
||||||
|
playable: isOwner.value
|
||||||
|
? undefined
|
||||||
|
: true
|
||||||
|
}))
|
||||||
|
|
||||||
|
const pendingUploads = reactive([] as Upload[])
|
||||||
|
const processedUploads = computed(() => pendingUploads.filter(upload => upload.import_status !== 'pending'))
|
||||||
|
const finishedUploads = computed(() => pendingUploads.filter(upload => upload.import_status === 'finished'))
|
||||||
|
const erroredUploads = computed(() => pendingUploads.filter(upload => upload.import_status === 'errored'))
|
||||||
|
const skippedUploads = computed(() => pendingUploads.filter(upload => upload.import_status === 'skipped'))
|
||||||
|
|
||||||
|
const pendingUploadsById = computed(() => pendingUploads.reduce((acc, upload) => {
|
||||||
|
acc[upload.uuid] = upload
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, Upload>))
|
||||||
|
|
||||||
|
const isOver = computed(() => pendingUploads.length === processedUploads.value.length)
|
||||||
|
const isSuccessfull = computed(() => pendingUploads.length === finishedUploads.value.length)
|
||||||
|
|
||||||
|
watch(() => store.state.channels.latestPublication, (value) => {
|
||||||
|
if (value?.channel.uuid === props.object.uuid && value.uploads.length > 0) {
|
||||||
|
pendingUploads.push(...value.uploads)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const episodesKey = ref(new Date())
|
||||||
|
const seriesKey = ref(new Date())
|
||||||
|
whenever(isOver, () => {
|
||||||
|
episodesKey.value = new Date()
|
||||||
|
seriesKey.value = new Date()
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchPendingUploads = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('uploads/', {
|
||||||
|
params: { channel: props.object.uuid, import_status: ['pending', 'skipped', 'errored'], include_channels: 'true' },
|
||||||
|
paramsSerializer: function (params) {
|
||||||
|
return qs.stringify(params, { indices: false })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pendingUploads.length = 0
|
||||||
|
pendingUploads.push(...response.data.results)
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOwner.value) {
|
||||||
|
fetchPendingUploads()
|
||||||
|
.then(() => {
|
||||||
|
useWebSocketHandler('import.status_updated', (event) => {
|
||||||
|
if (!pendingUploadsById.value[event.upload.uuid]) return
|
||||||
|
Object.assign(pendingUploadsById.value[event.upload.uuid], event.upload)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const albumModal = ref()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<div
|
<div
|
||||||
|
@ -68,7 +159,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="$store.getters['ui/layoutVersion'] === 'small'">
|
<div v-if="$store.getters['ui/layoutVersion'] === 'small'">
|
||||||
<rendered-description
|
<rendered-description
|
||||||
:content="object.artist.description"
|
:content="object.artist?.description"
|
||||||
:update-url="`channels/${object.uuid}/`"
|
:update-url="`channels/${object.uuid}/`"
|
||||||
:can-update="false"
|
:can-update="false"
|
||||||
/>
|
/>
|
||||||
|
@ -77,7 +168,7 @@
|
||||||
<channel-entries
|
<channel-entries
|
||||||
:key="String(episodesKey) + 'entries'"
|
:key="String(episodesKey) + 'entries'"
|
||||||
:is-podcast="isPodcast"
|
:is-podcast="isPodcast"
|
||||||
:default-cover="object.artist.cover"
|
:default-cover="object.artist?.cover"
|
||||||
:limit="25"
|
:limit="25"
|
||||||
:filters="{channel: object.uuid, ordering: '-creation_date', page_size: '25'}"
|
:filters="{channel: object.uuid, ordering: '-creation_date', page_size: '25'}"
|
||||||
>
|
>
|
||||||
|
@ -119,7 +210,7 @@
|
||||||
v-if="isOwner"
|
v-if="isOwner"
|
||||||
class="actions"
|
class="actions"
|
||||||
>
|
>
|
||||||
<a @click.stop.prevent="$refs.albumModal.show = true">
|
<a @click.stop.prevent="albumModal.show = true">
|
||||||
<i class="plus icon" />
|
<i class="plus icon" />
|
||||||
<translate translate-context="Content/Profile/Button">Add new</translate>
|
<translate translate-context="Content/Profile/Button">Add new</translate>
|
||||||
</a>
|
</a>
|
||||||
|
@ -130,126 +221,7 @@
|
||||||
v-if="isOwner"
|
v-if="isOwner"
|
||||||
ref="albumModal"
|
ref="albumModal"
|
||||||
:channel="object"
|
:channel="object"
|
||||||
@created="$refs.albumModal.show = false; seriesKey = new Date()"
|
@created="albumModal.show = false; seriesKey = new Date()"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import qs from 'qs'
|
|
||||||
|
|
||||||
import ChannelEntries from '~/components/audio/ChannelEntries.vue'
|
|
||||||
import ChannelSeries from '~/components/audio/ChannelSeries.vue'
|
|
||||||
import AlbumModal from '~/components/channels/AlbumModal.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ChannelEntries,
|
|
||||||
ChannelSeries,
|
|
||||||
AlbumModal
|
|
||||||
},
|
|
||||||
props: { object: { type: Object, required: true } },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
seriesKey: new Date(),
|
|
||||||
episodesKey: new Date(),
|
|
||||||
pendingUploads: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isPodcast () {
|
|
||||||
return this.object.artist.content_category === 'podcast'
|
|
||||||
},
|
|
||||||
isOwner () {
|
|
||||||
return this.$store.state.auth.authenticated && this.object.attributed_to.full_username === this.$store.state.auth.fullUsername
|
|
||||||
},
|
|
||||||
seriesFilters () {
|
|
||||||
const filters = { artist: this.object.artist.id, ordering: '-creation_date' }
|
|
||||||
if (!this.isOwner) {
|
|
||||||
filters.playable = 'true'
|
|
||||||
}
|
|
||||||
return filters
|
|
||||||
},
|
|
||||||
processedUploads () {
|
|
||||||
return this.pendingUploads.filter((u) => {
|
|
||||||
return u.import_status !== 'pending'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
erroredUploads () {
|
|
||||||
return this.pendingUploads.filter((u) => {
|
|
||||||
return u.import_status === 'errored'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
skippedUploads () {
|
|
||||||
return this.pendingUploads.filter((u) => {
|
|
||||||
return u.import_status === 'skipped'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
finishedUploads () {
|
|
||||||
return this.pendingUploads.filter((u) => {
|
|
||||||
return u.import_status === 'finished'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
pendingUploadsById () {
|
|
||||||
const d = {}
|
|
||||||
this.pendingUploads.forEach((u) => {
|
|
||||||
d[u.uuid] = u
|
|
||||||
})
|
|
||||||
return d
|
|
||||||
},
|
|
||||||
isOver () {
|
|
||||||
return this.pendingUploads && this.processedUploads.length === this.pendingUploads.length
|
|
||||||
},
|
|
||||||
isSuccessfull () {
|
|
||||||
return this.pendingUploads && this.finishedUploads.length === this.pendingUploads.length
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'$store.state.channels.latestPublication' (v) {
|
|
||||||
if (v && v.uploads && v.channel.uuid === this.object.uuid) {
|
|
||||||
this.pendingUploads = [...this.pendingUploads, ...v.uploads]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'isOver' (v) {
|
|
||||||
if (v) {
|
|
||||||
this.seriesKey = new Date()
|
|
||||||
this.episodesKey = new Date()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created () {
|
|
||||||
if (this.isOwner) {
|
|
||||||
await this.fetchPendingUploads()
|
|
||||||
this.$store.commit('ui/addWebsocketEventHandler', {
|
|
||||||
eventName: 'import.status_updated',
|
|
||||||
id: 'fileUploadChannel',
|
|
||||||
handler: this.handleImportEvent
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unmounted () {
|
|
||||||
this.$store.commit('ui/removeWebsocketEventHandler', {
|
|
||||||
eventName: 'import.status_updated',
|
|
||||||
id: 'fileUploadChannel'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleImportEvent (event) {
|
|
||||||
if (!this.pendingUploadsById[event.upload.uuid]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Object.assign(this.pendingUploadsById[event.upload.uuid], event.upload)
|
|
||||||
},
|
|
||||||
async fetchPendingUploads () {
|
|
||||||
const response = await axios.get('uploads/', {
|
|
||||||
params: { channel: this.object.uuid, import_status: ['pending', 'skipped', 'errored'], include_channels: 'true' },
|
|
||||||
paramsSerializer: function (params) {
|
|
||||||
return qs.stringify(params, { indices: false })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.pendingUploads = response.data.results
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
secondaryMenu: $pgettext('Menu/*/Hidden text', 'Secondary menu'),
|
||||||
|
title: $pgettext('*/Library/*/Verb', 'Add content')
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<main
|
||||||
v-title="labels.title"
|
v-title="labels.title"
|
||||||
|
@ -28,17 +40,3 @@
|
||||||
<router-view :key="$route.fullPath" />
|
<router-view :key="$route.fullPath" />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
const title = this.$pgettext('*/Library/*/Verb', 'Add content')
|
|
||||||
const secondaryMenu = this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
secondaryMenu
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,27 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Library, PrivacyLevel } from '~/types'
|
||||||
|
|
||||||
|
import { humanSize } from '~/utils/filters'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
library: Library
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const sharedLabels = useSharedLabels()
|
||||||
|
|
||||||
|
const sizeLabel = computed(() => $pgettext('Content/Library/Card.Help text', 'Total size of the files in this library'))
|
||||||
|
|
||||||
|
const privacyTooltips = (level: PrivacyLevel) => `Visibility: ${sharedLabels.fields.privacy_level.choices[level].toLowerCase()}`
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui card">
|
<div class="ui card">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -6,21 +30,21 @@
|
||||||
<span
|
<span
|
||||||
v-if="library.privacy_level === 'me'"
|
v-if="library.privacy_level === 'me'"
|
||||||
class="right floated"
|
class="right floated"
|
||||||
:data-tooltip="privacy_tooltips('me')"
|
:data-tooltip="privacyTooltips('me')"
|
||||||
>
|
>
|
||||||
<i class="small lock icon" />
|
<i class="small lock icon" />
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="library.privacy_level === 'instance'"
|
v-else-if="library.privacy_level === 'instance'"
|
||||||
class="right floated"
|
class="right floated"
|
||||||
:data-tooltip="privacy_tooltips('instance')"
|
:data-tooltip="privacyTooltips('instance')"
|
||||||
>
|
>
|
||||||
<i class="small circle outline icon" />
|
<i class="small circle outline icon" />
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="library.privacy_level === 'everyone'"
|
v-else-if="library.privacy_level === 'everyone'"
|
||||||
class="right floated"
|
class="right floated"
|
||||||
:data-tooltip="privacy_tooltips('everyone')"
|
:data-tooltip="privacyTooltips('everyone')"
|
||||||
>
|
>
|
||||||
<i class="small globe icon" />
|
<i class="small globe icon" />
|
||||||
</span>
|
</span>
|
||||||
|
@ -39,7 +63,7 @@
|
||||||
<span
|
<span
|
||||||
v-if="library.size"
|
v-if="library.size"
|
||||||
class="right floated"
|
class="right floated"
|
||||||
:data-tooltip="size_label"
|
:data-tooltip="sizeLabel"
|
||||||
>
|
>
|
||||||
<i class="database icon" />
|
<i class="database icon" />
|
||||||
{{ humanSize(library.size) }}
|
{{ humanSize(library.size) }}
|
||||||
|
@ -75,26 +99,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { humanSize } from '~/utils/filters'
|
|
||||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { library: { type: Object, required: true } },
|
|
||||||
setup () {
|
|
||||||
const sharedLabels = useSharedLabels()
|
|
||||||
return { sharedLabels, humanSize }
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
size_label () {
|
|
||||||
return this.$pgettext('Content/Library/Card.Help text', 'Total size of the files in this library')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
privacy_tooltips (level) {
|
|
||||||
return 'Visibility: ' + this.sharedLabels.fields.privacy_level.choices[level].toLowerCase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
<template>
|
<script setup lang="ts">
|
||||||
<section class="ui vertical aligned stripe segment">
|
|
||||||
<library-files-table :default-query="query" />
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import LibraryFilesTable from './FilesTable.vue'
|
import LibraryFilesTable from './FilesTable.vue'
|
||||||
|
|
||||||
export default {
|
interface Props {
|
||||||
components: {
|
query: string
|
||||||
LibraryFilesTable
|
|
||||||
},
|
|
||||||
props: { query: { type: String, required: true } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="ui vertical aligned stripe segment">
|
||||||
|
<library-files-table
|
||||||
|
:ordering-config-name="null"
|
||||||
|
:default-query="query"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
|
@ -1,3 +1,93 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Library, BackendError, PrivacyLevel } from '~/types'
|
||||||
|
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||||
|
|
||||||
|
const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[]
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'updated', data: Library): void
|
||||||
|
(e: 'created', data: Library): void
|
||||||
|
(e: 'deleted'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
library?: Library
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const sharedLabels = useSharedLabels()
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
descriptionPlaceholder: $pgettext('Content/Library/Input.Placeholder', 'This library contains my personal music, I hope you like it.'),
|
||||||
|
namePlaceholder: $pgettext('Content/Library/Input.Placeholder', 'My awesome library')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const currentVisibilityLevel = ref(props.library?.privacy_level ?? 'me')
|
||||||
|
const currentDescription = ref(props.library?.description ?? '')
|
||||||
|
const currentName = ref(props.library?.name ?? '')
|
||||||
|
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const submit = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
name: currentName.value,
|
||||||
|
description: currentDescription.value,
|
||||||
|
privacy_level: currentVisibilityLevel.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = props.library
|
||||||
|
? await axios.patch(`libraries/${props.library.uuid}/`, payload)
|
||||||
|
: await axios.post('libraries/', payload)
|
||||||
|
|
||||||
|
if (props.library) emit('updated', response.data)
|
||||||
|
else emit('created', response.data)
|
||||||
|
|
||||||
|
store.commit('ui/addMessage', {
|
||||||
|
content: props.library
|
||||||
|
? $pgettext('Content/Library/Message', 'Library updated')
|
||||||
|
: $pgettext('Content/Library/Message', 'Library created'),
|
||||||
|
date: new Date()
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(`libraries/${props.library?.uuid}/`)
|
||||||
|
emit('deleted')
|
||||||
|
store.commit('ui/addMessage', {
|
||||||
|
content: $pgettext('Content/Library/Message', 'Library deleted'),
|
||||||
|
date: new Date()
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form
|
<form
|
||||||
class="ui form"
|
class="ui form"
|
||||||
|
@ -60,8 +150,8 @@
|
||||||
class="ui dropdown"
|
class="ui dropdown"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="(c, key) in ['me', 'instance', 'everyone']"
|
v-for="c in PRIVACY_LEVELS"
|
||||||
:key="key"
|
:key="c"
|
||||||
:value="c"
|
:value="c"
|
||||||
>
|
>
|
||||||
{{ sharedLabels.fields.privacy_level.choices[c] }}
|
{{ sharedLabels.fields.privacy_level.choices[c] }}
|
||||||
|
@ -89,7 +179,7 @@
|
||||||
v-if="library"
|
v-if="library"
|
||||||
type="button"
|
type="button"
|
||||||
class="ui right floated basic danger button"
|
class="ui right floated basic danger button"
|
||||||
@confirm="remove()"
|
@confirm="remove"
|
||||||
>
|
>
|
||||||
<translate translate-context="*/*/*/Verb">
|
<translate translate-context="*/*/*/Verb">
|
||||||
Delete
|
Delete
|
||||||
|
@ -118,98 +208,3 @@
|
||||||
</dangerous-button>
|
</dangerous-button>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { library: { type: Object, default: null } },
|
|
||||||
setup () {
|
|
||||||
const sharedLabels = useSharedLabels()
|
|
||||||
return { sharedLabels }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
const d = {
|
|
||||||
isLoading: false,
|
|
||||||
over: false,
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
if (this.library) {
|
|
||||||
d.currentVisibilityLevel = this.library.privacy_level
|
|
||||||
d.currentName = this.library.name
|
|
||||||
d.currentDescription = this.library.description
|
|
||||||
} else {
|
|
||||||
d.currentVisibilityLevel = 'me'
|
|
||||||
d.currentName = ''
|
|
||||||
d.currentDescription = ''
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
const namePlaceholder = this.$pgettext('Content/Library/Input.Placeholder', 'My awesome library')
|
|
||||||
const descriptionPlaceholder = this.$pgettext('Content/Library/Input.Placeholder', 'This library contains my personal music, I hope you like it.')
|
|
||||||
return {
|
|
||||||
namePlaceholder,
|
|
||||||
descriptionPlaceholder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit () {
|
|
||||||
const self = this
|
|
||||||
this.isLoading = true
|
|
||||||
const payload = {
|
|
||||||
name: this.currentName,
|
|
||||||
description: this.currentDescription,
|
|
||||||
privacy_level: this.currentVisibilityLevel
|
|
||||||
}
|
|
||||||
let promise
|
|
||||||
if (this.library) {
|
|
||||||
promise = axios.patch(`libraries/${this.library.uuid}/`, payload)
|
|
||||||
} else {
|
|
||||||
promise = axios.post('libraries/', payload)
|
|
||||||
}
|
|
||||||
promise.then((response) => {
|
|
||||||
self.isLoading = false
|
|
||||||
let msg
|
|
||||||
if (self.library) {
|
|
||||||
self.$emit('updated', response.data)
|
|
||||||
msg = this.$pgettext('Content/Library/Message', 'Library updated')
|
|
||||||
} else {
|
|
||||||
self.$emit('created', response.data)
|
|
||||||
msg = this.$pgettext('Content/Library/Message', 'Library created')
|
|
||||||
}
|
|
||||||
self.$store.commit('ui/addMessage', {
|
|
||||||
content: msg,
|
|
||||||
date: new Date()
|
|
||||||
})
|
|
||||||
}, error => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
},
|
|
||||||
reset () {
|
|
||||||
this.currentVisibilityLevel = 'me'
|
|
||||||
this.currentName = ''
|
|
||||||
this.currentDescription = ''
|
|
||||||
},
|
|
||||||
remove () {
|
|
||||||
const self = this
|
|
||||||
axios.delete(`libraries/${this.library.uuid}/`).then((response) => {
|
|
||||||
self.isLoading = false
|
|
||||||
const msg = this.$pgettext('Content/Library/Message', 'Library deleted')
|
|
||||||
self.$emit('deleted', {})
|
|
||||||
self.$store.commit('ui/addMessage', {
|
|
||||||
content: msg,
|
|
||||||
date: new Date()
|
|
||||||
})
|
|
||||||
}, error => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,44 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Library } from '~/types'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import LibraryForm from './Form.vue'
|
||||||
|
import LibraryCard from './Card.vue'
|
||||||
|
import Quota from './Quota.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const libraries = ref([] as Library[])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const hiddenForm = ref(true)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get('libraries/', { params: { scope: 'me' } })
|
||||||
|
libraries.value = response.data.results
|
||||||
|
if (libraries.value.length === 0) {
|
||||||
|
hiddenForm.value = false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const libraryCreated = (library: Library) => {
|
||||||
|
router.push({ name: 'library.detail', params: { id: library.uuid } })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="ui vertical aligned stripe segment">
|
<section class="ui vertical aligned stripe segment">
|
||||||
<div
|
<div
|
||||||
|
@ -63,44 +104,3 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import LibraryForm from './Form.vue'
|
|
||||||
import LibraryCard from './Card.vue'
|
|
||||||
import Quota from './Quota.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
LibraryForm,
|
|
||||||
LibraryCard,
|
|
||||||
Quota
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
hiddenForm: true,
|
|
||||||
libraries: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetch()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetch () {
|
|
||||||
this.isLoading = true
|
|
||||||
const self = this
|
|
||||||
axios.get('libraries/', { params: { scope: 'me' } }).then(response => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.libraries = response.data.results
|
|
||||||
if (self.libraries.length === 0) {
|
|
||||||
self.hiddenForm = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
libraryCreated (library) {
|
|
||||||
this.$router.push({ name: 'library.detail', params: { id: library.uuid } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,55 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ImportStatus } from '~/types'
|
||||||
|
|
||||||
|
import { compileTokens } from '~/utils/search'
|
||||||
|
import { humanSize } from '~/utils/filters'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const quotaStatus = ref()
|
||||||
|
const progress = computed(() => !quotaStatus.value
|
||||||
|
? 0
|
||||||
|
: Math.min(quotaStatus.value.current * 100 / quotaStatus.value.max, 100)
|
||||||
|
)
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get('users/me/')
|
||||||
|
quotaStatus.value = response.data.quota_status
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const purge = async (status: ImportStatus) => {
|
||||||
|
try {
|
||||||
|
await axios.post('uploads/action/', {
|
||||||
|
action: 'delete',
|
||||||
|
objects: 'all',
|
||||||
|
filters: { import_status: status }
|
||||||
|
})
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const purgeSkippedFiles = () => purge('skipped')
|
||||||
|
const purgePendingFiles = () => purge('pending')
|
||||||
|
const purgeErroredFiles = () => purge('errored')
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui segment">
|
<div class="ui segment">
|
||||||
<h3 class="ui header">
|
<h3 class="ui header">
|
||||||
|
@ -210,62 +262,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import { humanSize } from '~/utils/filters'
|
|
||||||
import { compileTokens } from '~/utils/search'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
quotaStatus: null,
|
|
||||||
isLoading: false,
|
|
||||||
humanSize,
|
|
||||||
compileTokens
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
progress () {
|
|
||||||
if (!this.quotaStatus) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return Math.min(parseInt(this.quotaStatus.current * 100 / this.quotaStatus.max), 100)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetch()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetch () {
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
axios.get('users/me/').then((response) => {
|
|
||||||
self.quotaStatus = response.data.quota_status
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
purge (status) {
|
|
||||||
const self = this
|
|
||||||
const payload = {
|
|
||||||
action: 'delete',
|
|
||||||
objects: 'all',
|
|
||||||
filters: {
|
|
||||||
import_status: status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
axios.post('uploads/action/', payload).then((response) => {
|
|
||||||
self.fetch()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
purgeSkippedFiles () {
|
|
||||||
this.purge('skipped')
|
|
||||||
},
|
|
||||||
purgePendingFiles () {
|
|
||||||
this.purge('pending')
|
|
||||||
},
|
|
||||||
purgeErroredFiles () {
|
|
||||||
this.purge('errored')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,45 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Library, LibraryFollow } from '~/types'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import ScanForm from './ScanForm.vue'
|
||||||
|
import LibraryCard from './Card.vue'
|
||||||
|
|
||||||
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
const existingFollows = ref()
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get('federation/follows/library/', { params: { page_size: 100, ordering: '-creation_date' } })
|
||||||
|
existingFollows.value = response.data
|
||||||
|
|
||||||
|
for (const follow of existingFollows.value.results) {
|
||||||
|
follow.target.follow = follow
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const getLibraryFromFollow = (follow: LibraryFollow) => {
|
||||||
|
const { target } = follow
|
||||||
|
target.follow = follow
|
||||||
|
return target as Library
|
||||||
|
}
|
||||||
|
|
||||||
|
const scanResult = ref()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui vertical aligned stripe segment">
|
<div class="ui vertical aligned stripe segment">
|
||||||
<div
|
<div
|
||||||
|
@ -45,7 +87,7 @@
|
||||||
<a
|
<a
|
||||||
href=""
|
href=""
|
||||||
class="discrete link"
|
class="discrete link"
|
||||||
@click.prevent="fetch()"
|
@click.prevent="fetchData"
|
||||||
>
|
>
|
||||||
<i :class="['ui', 'circular', 'refresh', 'icon']" /> <translate translate-context="Content/*/Button.Label/Short, Verb">Refresh</translate>
|
<i :class="['ui', 'circular', 'refresh', 'icon']" /> <translate translate-context="Content/*/Button.Label/Short, Verb">Refresh</translate>
|
||||||
</a>
|
</a>
|
||||||
|
@ -55,56 +97,11 @@
|
||||||
v-for="follow in existingFollows.results"
|
v-for="follow in existingFollows.results"
|
||||||
:key="follow.fid"
|
:key="follow.fid"
|
||||||
:initial-library="getLibraryFromFollow(follow)"
|
:initial-library="getLibraryFromFollow(follow)"
|
||||||
@deleted="fetch()"
|
@deleted="fetchData"
|
||||||
@followed="fetch()"
|
@followed="fetchData"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import ScanForm from './ScanForm.vue'
|
|
||||||
import LibraryCard from './Card.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ScanForm,
|
|
||||||
LibraryCard
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
scanResult: null,
|
|
||||||
existingFollows: null,
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetch()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetch () {
|
|
||||||
this.isLoading = true
|
|
||||||
const self = this
|
|
||||||
axios.get('federation/follows/library/', { params: { page_size: 100, ordering: '-creation_date' } }).then((response) => {
|
|
||||||
self.existingFollows = response.data
|
|
||||||
self.existingFollows.results.forEach(f => {
|
|
||||||
f.target.follow = f
|
|
||||||
})
|
|
||||||
self.isLoading = false
|
|
||||||
}, error => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors.push(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getLibraryFromFollow (follow) {
|
|
||||||
const d = follow.target
|
|
||||||
d.follow = follow
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,43 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { BackendError } from '~/types'
|
||||||
|
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'scanned', data: object): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
placeholder: $pgettext('Content/Library/Input.Placeholder', 'Enter a library URL'),
|
||||||
|
submitLibrarySearch: $pgettext('Content/Library/Input.Label', 'Submit search')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const query = ref('')
|
||||||
|
const scan = async () => {
|
||||||
|
if (!query.value) return
|
||||||
|
isLoading.value = true
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('federation/libraries/fetch/', { fid: query.value })
|
||||||
|
emit('scanned', response.data)
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form
|
<form
|
||||||
class="ui form"
|
class="ui form"
|
||||||
|
@ -43,41 +83,3 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
query: '',
|
|
||||||
isLoading: false,
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
placeholder: this.$pgettext('Content/Library/Input.Placeholder', 'Enter a library URL'),
|
|
||||||
submitLibrarySearch: this.$pgettext('Content/Library/Input.Label', 'Submit search')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
scan () {
|
|
||||||
if (!this.query) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const self = this
|
|
||||||
self.errors = []
|
|
||||||
self.isLoading = true
|
|
||||||
axios.post('federation/libraries/fetch/', { fid: this.query }).then((response) => {
|
|
||||||
self.$emit('scanned', response.data)
|
|
||||||
self.isLoading = false
|
|
||||||
}, error => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Library } from '~/types'
|
||||||
|
|
||||||
|
import AlbumWidget from '~/components/audio/album/Widget.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
object: Library
|
||||||
|
isOwner: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<album-widget
|
<album-widget
|
||||||
|
@ -28,17 +41,3 @@
|
||||||
</album-widget>
|
</album-widget>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import AlbumWidget from '~/components/audio/album/Widget.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
AlbumWidget
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
object: { type: Object, required: true },
|
|
||||||
isOwner: { type: Boolean, required: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Library } from '~/types'
|
||||||
|
|
||||||
|
import ArtistWidget from '~/components/audio/artist/Widget.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
object: Library
|
||||||
|
isOwner: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<template v-if="$store.getters['ui/layoutVersion'] === 'small'">
|
<template v-if="$store.getters['ui/layoutVersion'] === 'small'">
|
||||||
|
@ -37,22 +50,3 @@
|
||||||
</artist-widget>
|
</artist-widget>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import ArtistWidget from '~/components/audio/artist/Widget.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ArtistWidget
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
object: { type: Object, required: true },
|
|
||||||
isOwner: { type: Boolean, required: true }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
query: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,35 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Library } from '~/types'
|
||||||
|
|
||||||
|
import { onBeforeRouteLeave } from 'vue-router'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import FileUpload from '~/components/library/FileUpload.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
object: Library
|
||||||
|
defaultImportReference?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
defaultImportReference: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const fileupload = ref()
|
||||||
|
onBeforeRouteLeave((to, from, next) => {
|
||||||
|
if (!fileupload.value.hasActiveUploads) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const answer = window.confirm('This page is asking you to confirm that you want to leave - data you have entered may not be saved.')
|
||||||
|
if (answer) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
next(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<file-upload
|
<file-upload
|
||||||
|
@ -8,31 +40,3 @@
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import FileUpload from '~/components/library/FileUpload.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
FileUpload
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeRouteLeave (to, from, next) {
|
|
||||||
if (this.$refs.fileupload.hasActiveUploads) {
|
|
||||||
const answer = window.confirm('This page is asking you to confirm that you want to leave - data you have entered may not be saved.')
|
|
||||||
if (answer) {
|
|
||||||
next()
|
|
||||||
} else {
|
|
||||||
next(false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
object: { type: Object, required: true },
|
|
||||||
defaultImportReference: { type: String, default: '' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -6642,6 +6642,11 @@ util-deprecate@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||||
|
|
||||||
|
utility-types@^3.10.0:
|
||||||
|
version "3.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b"
|
||||||
|
integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3:
|
v8-compile-cache@^2.0.3:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||||
|
|
Loading…
Reference in New Issue