Migrate a bunch of components
This commit is contained in:
parent
0251789f82
commit
779d71abbc
|
@ -1,9 +1,159 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Playlist, Track, PlaylistTrack, BackendError, APIErrorResponse } from '~/types'
|
||||||
|
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import PlaylistForm from '~/components/playlists/Form.vue'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
import { useVModels } from '@vueuse/core'
|
||||||
|
import useQueue from '~/composables/audio/useQueue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
playlist: Playlist | null
|
||||||
|
playlistTracks: PlaylistTrack[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:playlist', 'update:playlistTracks'])
|
||||||
|
const { playlistTracks, playlist } = useVModels(props, emit)
|
||||||
|
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const duplicateTrackAddInfo = ref<APIErrorResponse | null>(null)
|
||||||
|
const showDuplicateTrackAddConfirmation = ref(false)
|
||||||
|
|
||||||
|
const { tracks: queueTracks } = useQueue()
|
||||||
|
|
||||||
|
interface ModifiedPlaylistTrack extends PlaylistTrack {
|
||||||
|
_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const tracks = computed({
|
||||||
|
get: () => playlistTracks.value.map((playlistTrack, index) => ({ ...playlistTrack, _id: `${index}-${playlistTrack.track.id}` } as ModifiedPlaylistTrack)),
|
||||||
|
set: (playlist) => {
|
||||||
|
playlistTracks.value = playlist.map((modifiedPlaylistTrack, index) => {
|
||||||
|
const res = { ...modifiedPlaylistTrack, index } as ModifiedPlaylistTrack
|
||||||
|
delete res._id
|
||||||
|
return res as PlaylistTrack
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const labels = computed(() => ({
|
||||||
|
copyTitle: $pgettext('Content/Playlist/Button.Tooltip/Verb', 'Copy the current queue to this playlist')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const status = computed(() => isLoading.value
|
||||||
|
? 'loading'
|
||||||
|
: errors.value.length
|
||||||
|
? 'errored'
|
||||||
|
: showDuplicateTrackAddConfirmation.value
|
||||||
|
? 'confirmDuplicateAdd'
|
||||||
|
: 'saved'
|
||||||
|
)
|
||||||
|
|
||||||
|
const responseHandlers = {
|
||||||
|
success () {
|
||||||
|
errors.value = []
|
||||||
|
showDuplicateTrackAddConfirmation.value = false
|
||||||
|
},
|
||||||
|
errored (error: BackendError): void {
|
||||||
|
const { backendErrors, rawPayload } = error
|
||||||
|
// if backendErrors isn't populated (e.g. duplicate track exceptions raised by
|
||||||
|
// the playlist model), read directly from the response
|
||||||
|
// TODO (wvffle): Check if such case exists after rewrite
|
||||||
|
if (error.rawPayload?.playlist) {
|
||||||
|
error.backendErrors = error.rawPayload.playlist as string[]
|
||||||
|
error.rawPayload = undefined
|
||||||
|
return this.errored(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO (wvffle): Test if it works
|
||||||
|
// if (errors.length === 1 && errors[0].code === 'tracks_already_exist_in_playlist') {
|
||||||
|
if (backendErrors.length === 1 && backendErrors[0] === 'Tracks already exist in playlist') {
|
||||||
|
duplicateTrackAddInfo.value = rawPayload ?? null
|
||||||
|
showDuplicateTrackAddConfirmation.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.value = backendErrors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const reorder = async ({ oldIndex: from, newIndex: to }: { oldIndex: number, newIndex: number }) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(`playlists/${playlist.value!.id}/move/`, { from, to })
|
||||||
|
await store.dispatch('playlists/fetchOwn')
|
||||||
|
responseHandlers.success()
|
||||||
|
} catch (error) {
|
||||||
|
responseHandlers.errored(error as BackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const removePlaylistTrack = async (index: number) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
tracks.value.splice(index, 1)
|
||||||
|
await axios.post(`playlists/${playlist.value!.id}/remove/`, { index })
|
||||||
|
await store.dispatch('playlists/fetchOwn')
|
||||||
|
responseHandlers.success()
|
||||||
|
} catch (error) {
|
||||||
|
responseHandlers.errored(error as BackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearPlaylist = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
tracks.value = []
|
||||||
|
await axios.post(`playlists/${playlist.value!.id}/clear/`)
|
||||||
|
await store.dispatch('playlists/fetchOwn')
|
||||||
|
responseHandlers.success()
|
||||||
|
} catch (error) {
|
||||||
|
responseHandlers.errored(error as BackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertMany = async (insertedTracks: Track[], allowDuplicates: boolean) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`playlists/${playlist.value!.id}/add/`, {
|
||||||
|
allow_duplicates: allowDuplicates,
|
||||||
|
tracks: insertedTracks.map(track => track.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
tracks.value.push(...response.data.results)
|
||||||
|
await store.dispatch('playlists/fetchOwn')
|
||||||
|
responseHandlers.success()
|
||||||
|
} catch (error) {
|
||||||
|
responseHandlers.errored(error as BackendError)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui text container component-playlist-editor">
|
<div class="ui text container component-playlist-editor">
|
||||||
<playlist-form
|
<playlist-form
|
||||||
:title="false"
|
:title="false"
|
||||||
:playlist="playlist"
|
v-model:playlist="playlist"
|
||||||
@updated="$emit('playlist-updated', $event)"
|
|
||||||
/>
|
/>
|
||||||
<h3 class="ui top attached header">
|
<h3 class="ui top attached header">
|
||||||
<translate translate-context="Content/Playlist/Title">
|
<translate translate-context="Content/Playlist/Title">
|
||||||
|
@ -43,14 +193,14 @@
|
||||||
class="ui warning message"
|
class="ui warning message"
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
v-translate="{playlist: playlist.name}"
|
v-translate="{playlist: playlist?.name}"
|
||||||
translate-context="Content/Playlist/Paragraph"
|
translate-context="Content/Playlist/Paragraph"
|
||||||
>
|
>
|
||||||
Some tracks in your queue are already in this playlist:
|
Some tracks in your queue are already in this playlist:
|
||||||
</p>
|
</p>
|
||||||
<ul class="ui relaxed divided list duplicate-tracks-list">
|
<ul class="ui relaxed divided list duplicate-tracks-list">
|
||||||
<li
|
<li
|
||||||
v-for="(track, key) in duplicateTrackAddInfo.tracks"
|
v-for="(track, key) in duplicateTrackAddInfo?.tracks ?? []"
|
||||||
:key="key"
|
:key="key"
|
||||||
class="ui item"
|
class="ui item"
|
||||||
>
|
>
|
||||||
|
@ -74,7 +224,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ui bottom attached segment">
|
<div class="ui bottom attached segment">
|
||||||
<button
|
<button
|
||||||
:disabled="queueTracks.length === 0 || null"
|
:disabled="queueTracks.length === 0"
|
||||||
:class="['ui', {disabled: queueTracks.length === 0}, 'labeled', 'icon', 'button']"
|
:class="['ui', {disabled: queueTracks.length === 0}, 'labeled', 'icon', 'button']"
|
||||||
:title="labels.copyTitle"
|
:title="labels.copyTitle"
|
||||||
@click="insertMany(queueTracks, false)"
|
@click="insertMany(queueTracks, false)"
|
||||||
|
@ -91,7 +241,7 @@
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<dangerous-button
|
<dangerous-button
|
||||||
:disabled="plts.length === 0 || null"
|
:disabled="tracks.length === 0"
|
||||||
class="ui labeled right floated danger icon button"
|
class="ui labeled right floated danger icon button"
|
||||||
:action="clearPlaylist"
|
:action="clearPlaylist"
|
||||||
>
|
>
|
||||||
|
@ -100,9 +250,9 @@
|
||||||
</translate>
|
</translate>
|
||||||
<template #modal-header>
|
<template #modal-header>
|
||||||
<p
|
<p
|
||||||
v-translate="{playlist: playlist.name}"
|
v-translate="{playlist: playlist?.name}"
|
||||||
translate-context="Popup/Playlist/Title"
|
translate-context="Popup/Playlist/Title"
|
||||||
:translate-params="{playlist: playlist.name}"
|
:translate-params="{playlist: playlist?.name}"
|
||||||
>
|
>
|
||||||
Do you want to clear the playlist "%{ playlist }"?
|
Do you want to clear the playlist "%{ playlist }"?
|
||||||
</p>
|
</p>
|
||||||
|
@ -123,7 +273,7 @@
|
||||||
</template>
|
</template>
|
||||||
</dangerous-button>
|
</dangerous-button>
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
<template v-if="plts.length > 0">
|
<template v-if="tracks.length > 0">
|
||||||
<p>
|
<p>
|
||||||
<translate translate-context="Content/Playlist/Paragraph/Call to action">
|
<translate translate-context="Content/Playlist/Paragraph/Call to action">
|
||||||
Drag and drop rows to reorder tracks in the playlist
|
Drag and drop rows to reorder tracks in the playlist
|
||||||
|
@ -132,7 +282,7 @@
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<table class="ui compact very basic unstackable table">
|
<table class="ui compact very basic unstackable table">
|
||||||
<draggable
|
<draggable
|
||||||
v-model:list="plts"
|
v-model="tracks"
|
||||||
tag="tbody"
|
tag="tbody"
|
||||||
item-key="_id"
|
item-key="_id"
|
||||||
@update="reorder"
|
@update="reorder"
|
||||||
|
@ -163,7 +313,7 @@
|
||||||
<td class="right aligned">
|
<td class="right aligned">
|
||||||
<button
|
<button
|
||||||
class="ui circular danger basic icon button"
|
class="ui circular danger basic icon button"
|
||||||
@click.stop="removePlt(index)"
|
@click.stop="removePlaylistTrack(index)"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
class="trash icon"
|
class="trash icon"
|
||||||
|
@ -179,148 +329,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapState } from 'vuex'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import axios from 'axios'
|
|
||||||
import PlaylistForm from '~/components/playlists/Form.vue'
|
|
||||||
|
|
||||||
import draggable from 'vuedraggable'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
draggable,
|
|
||||||
PlaylistForm
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
playlist: { type: Object, required: true },
|
|
||||||
playlistTracks: { type: Array, required: true }
|
|
||||||
},
|
|
||||||
setup (props) {
|
|
||||||
const plts = computed(() => {
|
|
||||||
return props.playlistTracks.map((plt, index) => ({ ...plt, _id: `${index}-${plt.track.id}` }))
|
|
||||||
})
|
|
||||||
|
|
||||||
return { plts }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
errors: [],
|
|
||||||
duplicateTrackAddInfo: {},
|
|
||||||
showDuplicateTrackAddConfirmation: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
queueTracks: state => state.queue.tracks
|
|
||||||
}),
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
copyTitle: this.$pgettext('Content/Playlist/Button.Tooltip/Verb', 'Copy the current queue to this playlist')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
status () {
|
|
||||||
if (this.isLoading) {
|
|
||||||
return 'loading'
|
|
||||||
}
|
|
||||||
if (this.errors.length > 0) {
|
|
||||||
return 'errored'
|
|
||||||
}
|
|
||||||
if (this.showDuplicateTrackAddConfirmation) {
|
|
||||||
return 'confirmDuplicateAdd'
|
|
||||||
}
|
|
||||||
return 'saved'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
plts: {
|
|
||||||
handler (newValue) {
|
|
||||||
newValue.forEach((e, i) => {
|
|
||||||
e.index = i
|
|
||||||
})
|
|
||||||
this.$emit('tracks-updated', newValue)
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
success () {
|
|
||||||
this.isLoading = false
|
|
||||||
this.errors = []
|
|
||||||
this.showDuplicateTrackAddConfirmation = false
|
|
||||||
},
|
|
||||||
errored (errors) {
|
|
||||||
this.isLoading = false
|
|
||||||
if (errors.length === 1 && errors[0].code === 'tracks_already_exist_in_playlist') {
|
|
||||||
this.duplicateTrackAddInfo = errors[0]
|
|
||||||
this.showDuplicateTrackAddConfirmation = true
|
|
||||||
} else {
|
|
||||||
this.errors = errors
|
|
||||||
}
|
|
||||||
},
|
|
||||||
reorder ({ oldIndex, newIndex }) {
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
const url = `playlists/${this.playlist.id}/move`
|
|
||||||
axios.post(url, { from: oldIndex, to: newIndex }).then((response) => {
|
|
||||||
self.success()
|
|
||||||
}, error => {
|
|
||||||
self.errored(error.backendErrors)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
removePlt (index) {
|
|
||||||
this.plts.splice(index, 1)
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
const url = `playlists/${this.playlist.id}/remove`
|
|
||||||
axios.post(url, { index }).then((response) => {
|
|
||||||
self.success()
|
|
||||||
self.$store.dispatch('playlists/fetchOwn')
|
|
||||||
}, error => {
|
|
||||||
self.errored(error.backendErrors)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clearPlaylist () {
|
|
||||||
this.plts = []
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
const url = 'playlists/' + this.playlist.id + '/clear'
|
|
||||||
axios.delete(url).then((response) => {
|
|
||||||
self.success()
|
|
||||||
self.$store.dispatch('playlists/fetchOwn')
|
|
||||||
}, error => {
|
|
||||||
self.errored(error.backendErrors)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
insertMany (tracks, allowDuplicates) {
|
|
||||||
const self = this
|
|
||||||
const ids = tracks.map(t => {
|
|
||||||
return t.id
|
|
||||||
})
|
|
||||||
const payload = {
|
|
||||||
tracks: ids,
|
|
||||||
allow_duplicates: allowDuplicates
|
|
||||||
}
|
|
||||||
self.isLoading = true
|
|
||||||
const url = 'playlists/' + this.playlist.id + '/add/'
|
|
||||||
axios.post(url, payload).then((response) => {
|
|
||||||
response.data.results.forEach(r => {
|
|
||||||
self.plts.push(r)
|
|
||||||
})
|
|
||||||
self.success()
|
|
||||||
self.$store.dispatch('playlists/fetchOwn')
|
|
||||||
}, error => {
|
|
||||||
// if backendErrors isn't populated (e.g. duplicate track exceptions raised by
|
|
||||||
// the playlist model), read directly from the response
|
|
||||||
if (error.rawPayload.playlist) {
|
|
||||||
self.errored(error.rawPayload.playlist)
|
|
||||||
} else {
|
|
||||||
self.errored(error.backendErrors)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,101 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Playlist, PrivacyLevel, BackendError } from '~/types'
|
||||||
|
|
||||||
|
import $ from 'jquery'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useVModels, useCurrentElement } from '@vueuse/core'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||||
|
import useLogger from '~/composables/useLogger'
|
||||||
|
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: boolean
|
||||||
|
create?: boolean
|
||||||
|
playlist?: Playlist | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: true,
|
||||||
|
create: false,
|
||||||
|
playlist: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:playlist'])
|
||||||
|
const { playlist } = useVModels(props, emit)
|
||||||
|
|
||||||
|
const logger = useLogger()
|
||||||
|
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
const success = ref(false)
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const name = ref(playlist.value?.name ?? '')
|
||||||
|
const privacyLevel = ref(playlist.value?.privacy_level ?? store.state.auth.profile?.privacy_level ?? 'me')
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const labels = computed(() => ({
|
||||||
|
placeholder: $pgettext('Content/Playlist/Input.Placeholder', 'My awesome playlist')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const sharedLabels = useSharedLabels()
|
||||||
|
const privacyLevelChoices = computed(() => [
|
||||||
|
{
|
||||||
|
value: 'me',
|
||||||
|
label: sharedLabels.fields.privacy_level.choices.me
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'instance',
|
||||||
|
label: sharedLabels.fields.privacy_level.choices.instance
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'everyone',
|
||||||
|
label: sharedLabels.fields.privacy_level.choices.everyone
|
||||||
|
}
|
||||||
|
] as { value: PrivacyLevel, label: string }[])
|
||||||
|
|
||||||
|
const el = useCurrentElement()
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick()
|
||||||
|
// @ts-expect-error dropdown is from semantic ui
|
||||||
|
$(el.value).find('.dropdown').dropdown()
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const submit = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
success.value = false
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = props.create ? 'playlists/' : `playlists/${playlist.value!.id}/`
|
||||||
|
const method = props.create ? 'post' : 'patch'
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: name.value,
|
||||||
|
privacy_level: privacyLevel.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.request({ method, url, data })
|
||||||
|
success.value = true
|
||||||
|
|
||||||
|
if (props.create) {
|
||||||
|
name.value = ''
|
||||||
|
} else {
|
||||||
|
playlist.value = response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch('playlists/fetchOwn')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error while creating playlist')
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form
|
<form
|
||||||
class="ui form"
|
class="ui form"
|
||||||
|
@ -96,100 +194,3 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import $ from 'jquery'
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import useLogger from '~/composables/useLogger'
|
|
||||||
import useSharedLabels from '~/composables/locale/useSharedLabels'
|
|
||||||
|
|
||||||
const logger = useLogger()
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
title: { type: Boolean, default: true },
|
|
||||||
playlist: { type: Object, default: null }
|
|
||||||
},
|
|
||||||
setup () {
|
|
||||||
const sharedLabels = useSharedLabels()
|
|
||||||
return { sharedLabels }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
const d = {
|
|
||||||
errors: [],
|
|
||||||
success: false,
|
|
||||||
isLoading: false
|
|
||||||
}
|
|
||||||
if (this.playlist) {
|
|
||||||
d.name = this.playlist.name
|
|
||||||
d.privacyLevel = this.playlist.privacy_level
|
|
||||||
} else {
|
|
||||||
d.privacyLevel = this.$store.state.auth.profile.privacy_level
|
|
||||||
d.name = ''
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
placeholder: this.$pgettext('Content/Playlist/Input.Placeholder', 'My awesome playlist')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
privacyLevelChoices: function () {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: 'me',
|
|
||||||
label: this.sharedLabels.fields.privacy_level.choices.me
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'instance',
|
|
||||||
label: this.sharedLabels.fields.privacy_level.choices.instance
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'everyone',
|
|
||||||
label: this.sharedLabels.fields.privacy_level.choices.everyone
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
$(this.$el).find('.dropdown').dropdown()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit () {
|
|
||||||
this.isLoading = true
|
|
||||||
this.success = false
|
|
||||||
this.errors = []
|
|
||||||
const self = this
|
|
||||||
const payload = {
|
|
||||||
name: this.name,
|
|
||||||
privacy_level: this.privacyLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
let promise
|
|
||||||
let url
|
|
||||||
if (this.playlist) {
|
|
||||||
url = `playlists/${this.playlist.id}/`
|
|
||||||
promise = axios.patch(url, payload)
|
|
||||||
} else {
|
|
||||||
url = 'playlists/'
|
|
||||||
promise = axios.post(url, payload)
|
|
||||||
}
|
|
||||||
return promise.then(response => {
|
|
||||||
self.success = true
|
|
||||||
self.isLoading = false
|
|
||||||
if (!self.playlist) {
|
|
||||||
self.name = ''
|
|
||||||
}
|
|
||||||
self.$emit('updated', response.data)
|
|
||||||
self.$store.dispatch('playlists/fetchOwn')
|
|
||||||
}, error => {
|
|
||||||
logger.error('Error while creating playlist')
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ const addToPlaylist = async (playlistId: number, allowDuplicates: boolean) => {
|
||||||
lastSelectedPlaylist.value = playlistId
|
lastSelectedPlaylist.value = playlistId
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post(`playlists/${playlistId}/add`, {
|
await axios.post(`playlists/${playlistId}/add/`, {
|
||||||
tracks: [track.value?.id].filter(i => i),
|
tracks: [track.value?.id].filter(i => i),
|
||||||
allow_duplicates: allowDuplicates
|
allow_duplicates: allowDuplicates
|
||||||
})
|
})
|
||||||
|
@ -106,7 +106,7 @@ const addToPlaylist = async (playlistId: number, allowDuplicates: boolean) => {
|
||||||
</translate>
|
</translate>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="scrolling content">
|
<div class="scrolling content">
|
||||||
<playlist-form :key="formKey" />
|
<playlist-form :create="true" :key="formKey" />
|
||||||
<div class="ui divider" />
|
<div class="ui divider" />
|
||||||
<div v-if="playlists.length > 0">
|
<div v-if="playlists.length > 0">
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { User } from '~/types'
|
||||||
import type { Module } from 'vuex'
|
import type { Module } from 'vuex'
|
||||||
import type { RootState } from '~/store/index'
|
import type { RootState } from '~/store/index'
|
||||||
|
|
||||||
|
@ -11,21 +12,11 @@ export interface State {
|
||||||
username: string
|
username: string
|
||||||
fullUsername: string
|
fullUsername: string
|
||||||
availablePermissions: Record<Permission, boolean>,
|
availablePermissions: Record<Permission, boolean>,
|
||||||
profile: null | Profile
|
profile: null | User
|
||||||
oauth: OAuthTokens
|
oauth: OAuthTokens
|
||||||
scopedTokens: ScopedTokens
|
scopedTokens: ScopedTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Profile {
|
|
||||||
id: string
|
|
||||||
avatar?: string
|
|
||||||
username: string
|
|
||||||
full_username: string
|
|
||||||
instance_support_message_display_date: string
|
|
||||||
funkwhale_support_message_display_date: string
|
|
||||||
is_superuser: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ScopedTokens {
|
interface ScopedTokens {
|
||||||
listen: null | string
|
listen: null | string
|
||||||
}
|
}
|
||||||
|
@ -136,13 +127,13 @@ const store: Module<State, RootState> = {
|
||||||
permission: (state, { key, status }: { key: Permission, status: boolean }) => {
|
permission: (state, { key, status }: { key: Permission, status: boolean }) => {
|
||||||
state.availablePermissions[key] = status
|
state.availablePermissions[key] = status
|
||||||
},
|
},
|
||||||
profilePartialUpdate: (state, payload: Profile) => {
|
profilePartialUpdate: (state, payload: User) => {
|
||||||
if (!state.profile) {
|
if (!state.profile) {
|
||||||
state.profile = {} as Profile
|
state.profile = {} as User
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(payload)) {
|
for (const [key, value] of Object.entries(payload)) {
|
||||||
state.profile[key as keyof Profile] = value as never
|
state.profile[key as keyof User] = value as never
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
oauthApp: (state, payload) => {
|
oauthApp: (state, payload) => {
|
||||||
|
@ -160,7 +151,7 @@ const store: Module<State, RootState> = {
|
||||||
const form = useFormData(credentials)
|
const form = useFormData(credentials)
|
||||||
return axios.post('users/login', form).then(() => {
|
return axios.post('users/login', form).then(() => {
|
||||||
logger.info('Successfully logged in as', credentials.username)
|
logger.info('Successfully logged in as', credentials.username)
|
||||||
dispatch('fetchProfile').then(() => {
|
dispatch('fetchUser').then(() => {
|
||||||
// Redirect to a specified route
|
// Redirect to a specified route
|
||||||
import('~/router').then((router) => {
|
import('~/router').then((router) => {
|
||||||
return router.default.push(next)
|
return router.default.push(next)
|
||||||
|
@ -190,11 +181,11 @@ const store: Module<State, RootState> = {
|
||||||
})
|
})
|
||||||
logger.info('Log out, goodbye!')
|
logger.info('Log out, goodbye!')
|
||||||
},
|
},
|
||||||
fetchProfile ({ dispatch }) {
|
fetchUser ({ dispatch }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.get('users/me/').then((response) => {
|
axios.get('users/me/').then((response) => {
|
||||||
logger.info('Successfully fetched user profile')
|
logger.info('Successfully fetched user profile')
|
||||||
dispatch('updateProfile', response.data)
|
dispatch('updateUser', response.data)
|
||||||
dispatch('ui/fetchUnreadNotifications', null, { root: true })
|
dispatch('ui/fetchUnreadNotifications', null, { root: true })
|
||||||
if (response.data.permissions.library) {
|
if (response.data.permissions.library) {
|
||||||
dispatch('ui/fetchPendingReviewEdits', null, { root: true })
|
dispatch('ui/fetchPendingReviewEdits', null, { root: true })
|
||||||
|
@ -215,7 +206,7 @@ const store: Module<State, RootState> = {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateProfile ({ commit }, data) {
|
updateUser ({ commit }, data) {
|
||||||
commit('authenticated', true)
|
commit('authenticated', true)
|
||||||
commit('profile', data)
|
commit('profile', data)
|
||||||
commit('username', data.username)
|
commit('username', data.username)
|
||||||
|
@ -253,7 +244,7 @@ const store: Module<State, RootState> = {
|
||||||
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
||||||
)
|
)
|
||||||
commit('oauthToken', response.data)
|
commit('oauthToken', response.data)
|
||||||
await dispatch('fetchProfile')
|
await dispatch('fetchUser')
|
||||||
},
|
},
|
||||||
async refreshOauthToken ({ state, commit }) {
|
async refreshOauthToken ({ state, commit }) {
|
||||||
const payload = {
|
const payload = {
|
||||||
|
|
|
@ -159,7 +159,24 @@ export interface License {
|
||||||
export interface Playlist {
|
export interface Playlist {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
modification_date: Date // TODO (wvffle): Find correct type
|
modification_date: string
|
||||||
|
user: User
|
||||||
|
privacy_level: PrivacyLevel
|
||||||
|
tracks_count: number
|
||||||
|
duration: number
|
||||||
|
|
||||||
|
is_playable: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaylistTrack {
|
||||||
|
track: Track
|
||||||
|
position?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Radio {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
user: User
|
||||||
}
|
}
|
||||||
|
|
||||||
// API stuff
|
// API stuff
|
||||||
|
@ -253,6 +270,7 @@ export interface FSLogs {
|
||||||
|
|
||||||
// Profile stuff
|
// Profile stuff
|
||||||
export interface Actor {
|
export interface Actor {
|
||||||
|
id: string
|
||||||
fid?: string
|
fid?: string
|
||||||
name?: string
|
name?: string
|
||||||
icon?: Cover
|
icon?: Cover
|
||||||
|
@ -263,6 +281,17 @@ export interface Actor {
|
||||||
domain: string
|
domain: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string
|
||||||
|
avatar?: string
|
||||||
|
username: string
|
||||||
|
full_username: string
|
||||||
|
instance_support_message_display_date: string
|
||||||
|
funkwhale_support_message_display_date: string
|
||||||
|
is_superuser: boolean
|
||||||
|
privacy_level: PrivacyLevel
|
||||||
|
}
|
||||||
|
|
||||||
// Settings stuff
|
// Settings stuff
|
||||||
export type SettingsId = 'instance'
|
export type SettingsId = 'instance'
|
||||||
export interface SettingsGroup {
|
export interface SettingsGroup {
|
||||||
|
|
|
@ -1,3 +1,74 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { PlaylistTrack, Playlist } from '~/types'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import TrackTable from '~/components/audio/track/Table.vue'
|
||||||
|
import PlayButton from '~/components/audio/PlayButton.vue'
|
||||||
|
import PlaylistEditor from '~/components/playlists/Editor.vue'
|
||||||
|
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
|
||||||
|
import Modal from '~/components/semantic/Modal.vue'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string
|
||||||
|
defaultEdit?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
defaultEdit: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const edit = ref(props.defaultEdit)
|
||||||
|
const playlist = ref<Playlist | null>(null)
|
||||||
|
const playlistTracks = ref<PlaylistTrack[]>([])
|
||||||
|
|
||||||
|
const showEmbedModal = ref(false)
|
||||||
|
|
||||||
|
const tracks = computed(() => playlistTracks.value.map(({ track }, index) => ({ ...track, position: index + 1 })))
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const labels = computed(() => ({
|
||||||
|
playlist: $pgettext('*/*/*', 'Playlist')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [playlistResponse, tracksResponse] = await Promise.all([
|
||||||
|
axios.get(`playlists/${props.id}/`),
|
||||||
|
axios.get(`playlists/${props.id}/tracks/`),
|
||||||
|
])
|
||||||
|
|
||||||
|
playlist.value = playlistResponse.data
|
||||||
|
playlistTracks.value = tracksResponse.data.results
|
||||||
|
} catch (error) {
|
||||||
|
// TODO (wvffle): Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const deletePlaylist = async () => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`playlists/${props.id}/`)
|
||||||
|
store.dispatch('playlists/fetchOwn')
|
||||||
|
return router.push({ path: '/library' })
|
||||||
|
} catch (error) {
|
||||||
|
// TODO (wvffle): Handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -45,7 +116,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ui buttons">
|
<div class="ui buttons">
|
||||||
<button
|
<button
|
||||||
v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile.id"
|
v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile?.id"
|
||||||
class="ui icon labeled button"
|
class="ui icon labeled button"
|
||||||
@click="edit = !edit"
|
@click="edit = !edit"
|
||||||
>
|
>
|
||||||
|
@ -137,10 +208,8 @@
|
||||||
<section class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
<template v-if="edit">
|
<template v-if="edit">
|
||||||
<playlist-editor
|
<playlist-editor
|
||||||
:playlist="playlist"
|
v-model:playlist="playlist"
|
||||||
:playlist-tracks="playlistTracks"
|
v-model:playlist-tracks="playlistTracks"
|
||||||
@playlist-updated="playlist = $event"
|
|
||||||
@tracks-updated="updatePlts"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="tracks.length > 0">
|
<template v-else-if="tracks.length > 0">
|
||||||
|
@ -177,81 +246,3 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import TrackTable from '~/components/audio/track/Table.vue'
|
|
||||||
import PlayButton from '~/components/audio/PlayButton.vue'
|
|
||||||
import PlaylistEditor from '~/components/playlists/Editor.vue'
|
|
||||||
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
|
|
||||||
import Modal from '~/components/semantic/Modal.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
PlaylistEditor,
|
|
||||||
TrackTable,
|
|
||||||
PlayButton,
|
|
||||||
Modal,
|
|
||||||
EmbedWizard
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
id: { type: [Number, String], required: true },
|
|
||||||
defaultEdit: { type: Boolean, default: false }
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
edit: this.defaultEdit,
|
|
||||||
isLoading: false,
|
|
||||||
playlist: null,
|
|
||||||
tracks: [],
|
|
||||||
playlistTracks: [],
|
|
||||||
showEmbedModal: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
playlist: this.$pgettext('*/*/*', 'Playlist')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
this.fetch()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updatePlts (v) {
|
|
||||||
this.playlistTracks = v
|
|
||||||
this.tracks = v.map((e, i) => {
|
|
||||||
const track = e.track
|
|
||||||
track.position = i + 1
|
|
||||||
return track
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fetch: function () {
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
const url = 'playlists/' + this.id + '/'
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.playlist = response.data
|
|
||||||
axios
|
|
||||||
.get(url + 'tracks/')
|
|
||||||
.then(response => {
|
|
||||||
self.updatePlts(response.data.results)
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deletePlaylist () {
|
|
||||||
const self = this
|
|
||||||
const url = 'playlists/' + this.id + '/'
|
|
||||||
axios.delete(url).then(response => {
|
|
||||||
self.$store.dispatch('playlists/fetchOwn')
|
|
||||||
self.$router.push({
|
|
||||||
path: '/library'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,3 +1,63 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Track, Radio } from "~/types"
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import TrackTable from '~/components/audio/track/Table.vue'
|
||||||
|
import RadioButton from '~/components/radios/Button.vue'
|
||||||
|
import Pagination from '~/components/vui/Pagination.vue'
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const radio = ref<Radio | null>(null)
|
||||||
|
const tracks = ref([] as Track[])
|
||||||
|
const totalTracks = ref(0)
|
||||||
|
const page = ref(1)
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const labels = computed(() => ({
|
||||||
|
title: $pgettext('Head/Radio/Title', 'Radio')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const url = `radios/radios/${props.id}/`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const radioResponse = await axios.get(url)
|
||||||
|
radio.value = radioResponse.data
|
||||||
|
|
||||||
|
const tracksResponse = await axios.get(url + 'tracks/', { params: { page: page.value }})
|
||||||
|
totalTracks.value = tracksResponse.data.count
|
||||||
|
tracks.value = tracksResponse.data.results
|
||||||
|
} catch (error) {
|
||||||
|
// TODO (wvffle): Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(page, fetchData, { immediate: true })
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const deleteRadio = async () => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`radios/radios/${props.id}/`)
|
||||||
|
return router.push({ path: '/library' })
|
||||||
|
} catch (error) {
|
||||||
|
// TODO (wvffle): Handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
@ -81,10 +141,9 @@
|
||||||
<div class="ui center aligned basic segment">
|
<div class="ui center aligned basic segment">
|
||||||
<pagination
|
<pagination
|
||||||
v-if="totalTracks > 25"
|
v-if="totalTracks > 25"
|
||||||
:current="page"
|
v-model:current="page"
|
||||||
:paginate-by="25"
|
:paginate-by="25"
|
||||||
:total="totalTracks"
|
:total="totalTracks"
|
||||||
@page-changed="selectPage"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -101,9 +160,9 @@
|
||||||
</translate>
|
</translate>
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="$store.state.auth.username === radio.user.username"
|
v-if="$store.state.auth.username === radio?.user.username"
|
||||||
class="ui success icon labeled button"
|
class="ui success icon labeled button"
|
||||||
:to="{name: 'library.radios.edit', params: {id: radio.id}}"
|
:to="{name: 'library.radios.edit', params: { id: radio?.id }}"
|
||||||
>
|
>
|
||||||
<i class="pencil icon" />
|
<i class="pencil icon" />
|
||||||
Edit…
|
Edit…
|
||||||
|
@ -111,76 +170,3 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import TrackTable from '~/components/audio/track/Table.vue'
|
|
||||||
import RadioButton from '~/components/radios/Button.vue'
|
|
||||||
import Pagination from '~/components/vui/Pagination.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
TrackTable,
|
|
||||||
RadioButton,
|
|
||||||
Pagination
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
id: { type: Number, required: true }
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
radio: null,
|
|
||||||
tracks: [],
|
|
||||||
totalTracks: 0,
|
|
||||||
page: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
title: this.$pgettext('Head/Radio/Title', 'Radio')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
page: function () {
|
|
||||||
this.fetch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
this.fetch()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
selectPage: function (page) {
|
|
||||||
this.page = page
|
|
||||||
},
|
|
||||||
fetch: function () {
|
|
||||||
const self = this
|
|
||||||
self.isLoading = true
|
|
||||||
const url = 'radios/radios/' + this.id + '/'
|
|
||||||
axios.get(url).then(response => {
|
|
||||||
self.radio = response.data
|
|
||||||
axios
|
|
||||||
.get(url + 'tracks/', { params: { page: this.page } })
|
|
||||||
.then(response => {
|
|
||||||
this.totalTracks = response.data.count
|
|
||||||
this.tracks = response.data.results
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
self.isLoading = false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteRadio () {
|
|
||||||
const self = this
|
|
||||||
const url = 'radios/radios/' + this.id + '/'
|
|
||||||
axios.delete(url).then(response => {
|
|
||||||
self.$router.push({
|
|
||||||
path: '/library'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
Loading…
Reference in New Issue