funkwhale/front/src/views/channels/DetailBase.vue

502 wiersze
18 KiB
Vue
Czysty Zwykły widok Historia

2022-07-04 18:17:58 +00:00
<script setup lang="ts">
2022-07-04 22:52:53 +00:00
import type { Channel } from '~/types'
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
import { computed, ref, reactive, watch, watchEffect } from 'vue'
2022-09-08 14:32:45 +00:00
import { useI18n } from 'vue-i18n'
import { useStore } from '~/store'
2022-07-04 18:17:58 +00:00
import axios from 'axios'
import SubscribeButton from '~/components/channels/SubscribeButton.vue'
import ChannelForm from '~/components/audio/ChannelForm.vue'
2022-07-04 18:17:58 +00:00
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
2022-07-20 22:44:19 +00:00
import SemanticModal from '~/components/semantic/Modal.vue'
import PlayButton from '~/components/audio/PlayButton.vue'
2022-07-04 18:17:58 +00:00
import TagsList from '~/components/tags/List.vue'
import useErrorHandler from '~/composables/useErrorHandler'
2022-07-04 18:17:58 +00:00
import useReport from '~/composables/moderation/useReport'
2022-08-30 20:23:17 +00:00
interface Events {
(e: 'deleted'): void
}
2022-07-04 18:17:58 +00:00
interface Props {
2022-08-31 16:39:24 +00:00
id: number
2022-07-04 18:17:58 +00:00
}
2022-08-30 20:23:17 +00:00
const emit = defineEmits<Events>()
2022-07-04 18:17:58 +00:00
const props = defineProps<Props>()
const { report, getReportableObjects } = useReport()
const store = useStore()
const object = ref<Channel | null>(null)
const editForm = ref()
const totalTracks = ref(0)
const edit = reactive({
submittable: false,
loading: false
})
const showEmbedModal = ref(false)
const showEditModal = ref(false)
const showSubscribeModal = ref(false)
const isOwner = computed(() => store.state.auth.authenticated && object.value?.attributed_to.full_username === store.state.auth.fullUsername)
const isPodcast = computed(() => object.value?.artist?.content_category === 'podcast')
const isPlayable = computed(() => totalTracks.value > 0)
const externalDomain = computed(() => {
const parser = document.createElement('a')
parser.href = object.value?.url ?? object.value?.rss_url ?? ''
return parser.hostname
})
2022-09-08 14:32:45 +00:00
const { t } = useI18n()
2022-07-04 18:17:58 +00:00
const labels = computed(() => ({
2022-09-18 19:14:35 +00:00
title: t('views.channels.DetailBase.title')
2022-07-04 18:17:58 +00:00
}))
onBeforeRouteUpdate((to) => {
to.meta.preserveScrollPosition = true
})
const router = useRouter()
const isLoading = ref(false)
const fetchData = async () => {
showEditModal.value = false
edit.loading = false
isLoading.value = true
try {
const response = await axios.get(`channels/${props.id}`, { params: { refresh: 'true' } })
object.value = response.data
totalTracks.value = response.data.artist.tracks_count
if (props.id === response.data.uuid && response.data.actor) {
// replace with the pretty channel url if possible
const actor = response.data.actor
if (actor.is_local) {
await router.replace({ name: 'channels.detail', params: { id: actor.preferred_username } })
} else {
await router.replace({ name: 'channels.detail', params: { id: actor.full_username } })
}
}
} catch (error) {
useErrorHandler(error as Error)
2022-07-04 18:17:58 +00:00
}
isLoading.value = false
}
watch(() => props.id, fetchData, { immediate: true })
const uuid = computed(() => store.state.channels.latestPublication?.channel.uuid)
watch([uuid, object], ([uuid, object], [lastUuid, lastObject]) => {
if (object?.uuid && object.uuid === lastObject?.uuid) return
if (uuid && uuid === object?.uuid) {
2022-07-04 18:17:58 +00:00
fetchData()
}
})
const route = useRoute()
watchEffect(() => {
if (!object.value) return
if (!store.state.auth.authenticated && store.getters['instance/domain'] !== object.value.actor.domain) {
2022-07-04 18:17:58 +00:00
router.push({ name: 'login', query: { next: route.fullPath } })
}
})
const remove = async () => {
isLoading.value = true
try {
await axios.delete(`channels/${object.value?.uuid}`)
emit('deleted')
return router.push({ name: 'profile.overview', params: { username: store.state.auth.username } })
} catch (error) {
useErrorHandler(error as Error)
2022-07-04 18:17:58 +00:00
}
}
const updateSubscriptionCount = (delta: number) => {
if (object.value) {
object.value.subscriptions_count += delta
}
}
</script>
2020-02-05 14:06:07 +00:00
<template>
2021-12-06 10:35:20 +00:00
<main
v-title="labels.title"
class="main pusher"
>
<div
v-if="isLoading"
class="ui vertical segment"
>
<div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
2020-02-05 14:06:07 +00:00
</div>
<template v-if="object && !isLoading">
2021-12-06 10:35:20 +00:00
<section
2022-07-04 18:17:58 +00:00
v-title="object.artist?.name"
2021-12-06 10:35:20 +00:00
class="ui head vertical stripe segment container"
>
<div class="ui stackable grid">
<div class="seven wide column">
2020-02-05 14:06:07 +00:00
<div class="ui two column grid">
<div class="column">
2021-12-06 10:35:20 +00:00
<img
2022-07-04 18:17:58 +00:00
v-if="object.artist?.cover"
2021-12-06 10:35:20 +00:00
alt=""
class="huge channel-image"
:src="$store.getters['instance/absoluteUrl'](object.artist.cover.urls.medium_square_crop)"
>
<i
v-else
class="huge circular inverted users violet icon"
/>
2020-02-05 14:06:07 +00:00
</div>
<div class="ui column right aligned">
2021-12-06 10:35:20 +00:00
<tags-list
2022-07-04 18:17:58 +00:00
v-if="object.artist?.tags && object.artist?.tags.length > 0"
2021-12-06 10:35:20 +00:00
:tags="object.artist.tags"
/>
<actor-link
v-if="object.actor"
:avatar="false"
:actor="object.attributed_to"
:display-name="true"
/>
2020-02-05 14:06:07 +00:00
<template v-if="totalTracks > 0">
2021-12-06 10:35:20 +00:00
<div class="ui hidden very small divider" />
2022-09-18 19:14:35 +00:00
<span
2022-07-04 18:17:58 +00:00
v-if="object.artist?.content_category === 'podcast'"
2021-12-06 10:35:20 +00:00
>
2022-11-27 22:45:12 +00:00
{{ $t('views.channels.DetailBase.meta.episodes', totalTracks) }}
2022-09-18 19:14:35 +00:00
</span>
<span
2021-12-06 10:35:20 +00:00
v-else
>
2022-11-27 22:45:12 +00:00
{{ $t('views.channels.DetailBase.meta.tracks', totalTracks) }}
2022-09-18 19:14:35 +00:00
</span>
2020-03-25 14:32:10 +00:00
</template>
<template v-if="object.attributed_to.full_username === $store.state.auth.fullUsername || $store.getters['channels/isSubscribed'](object.uuid)">
2022-09-18 19:14:35 +00:00
<br>
2022-11-27 22:45:12 +00:00
{{ $t('views.channels.DetailBase.meta.subscribers', object?.subscriptions_count ?? 0) }}
2022-09-18 19:14:35 +00:00
<br>
2022-11-27 22:45:12 +00:00
{{ $t('views.channels.DetailBase.meta.listenings', object?.downloads_count ?? 0) }}
2020-02-05 14:06:07 +00:00
</template>
2021-12-06 10:35:20 +00:00
<div class="ui hidden small divider" />
<a
class="ui icon small basic button"
@click.stop.prevent="showSubscribeModal = true"
>
<i class="feed icon" />
2020-02-05 14:06:07 +00:00
</a>
2022-07-20 22:44:19 +00:00
<semantic-modal
2022-05-01 11:45:07 +00:00
v-model:show="showSubscribeModal"
2021-12-06 10:35:20 +00:00
class="tiny"
>
<h4 class="header">
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.subscribe.header') }}
</h4>
<div class="scrollable content">
<div class="description">
<template v-if="$store.state.auth.authenticated">
<h3>
2021-12-06 10:35:20 +00:00
<i class="user icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.subscribe.funkwhale.header') }}
</h3>
2021-12-06 10:35:20 +00:00
<subscribe-button
:channel="object"
2022-07-04 18:17:58 +00:00
@subscribed="updateSubscriptionCount(1)"
@unsubscribed="updateSubscriptionCount(-1)"
2021-12-06 10:35:20 +00:00
/>
</template>
<template v-if="object.rss_url">
<h3>
2021-12-06 10:35:20 +00:00
<i class="feed icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.subscribe.rss.header') }}
</h3>
2021-12-06 10:35:20 +00:00
<p>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.subscribe.rss.content.help') }}
2021-12-06 10:35:20 +00:00
</p>
<copy-input :value="object.rss_url" />
</template>
<template v-if="object.actor">
<h3>
2021-12-06 10:35:20 +00:00
<i class="bell icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.subscribe.fediverse.header') }}
</h3>
2021-12-06 10:35:20 +00:00
<p>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.subscribe.fediverse.content.help') }}
2021-12-06 10:35:20 +00:00
</p>
2022-05-02 08:25:37 +00:00
<copy-input
id="copy-tag"
:value="`@${object.actor.full_username}`"
/>
</template>
</div>
</div>
<div class="actions">
<button class="ui basic deny button">
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.cancel') }}
</button>
</div>
2022-07-20 22:44:19 +00:00
</semantic-modal>
2021-12-06 10:35:20 +00:00
<button
ref="dropdown"
v-dropdown="{direction: 'downward'}"
class="ui right floated pointing dropdown icon small basic button"
>
<i class="ellipsis vertical icon" />
2020-02-05 14:06:07 +00:00
<div class="menu">
2020-08-11 12:07:06 +00:00
<a
2020-02-05 14:06:07 +00:00
v-if="totalTracks > 0"
2021-12-06 10:35:20 +00:00
href=""
class="basic item"
2020-08-11 12:07:06 +00:00
@click.prevent="showEmbedModal = !showEmbedModal"
2021-12-06 10:35:20 +00:00
>
<i class="code icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.embed') }}
2020-08-11 12:07:06 +00:00
</a>
<a
2020-09-01 09:31:08 +00:00
v-if="object.actor && object.actor.domain != $store.getters['instance/domain']"
2021-12-06 10:35:20 +00:00
:href="object.url"
target="_blank"
2021-12-06 10:35:20 +00:00
class="basic item"
>
<i class="external icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.link.domainView', {domain: object.actor.domain}) }}
</a>
2021-12-06 10:35:20 +00:00
<div class="divider" />
2020-08-11 12:07:06 +00:00
<a
2022-07-04 18:17:58 +00:00
v-for="obj in getReportableObjects({account: object.attributed_to, channel: object})"
2020-02-05 14:06:07 +00:00
:key="obj.target.type + obj.target.id"
2021-12-06 10:35:20 +00:00
href=""
class="basic item"
2022-07-04 18:17:58 +00:00
@click.stop.prevent="report(obj)"
2021-12-06 10:35:20 +00:00
>
2020-02-05 14:06:07 +00:00
<i class="share icon" /> {{ obj.label }}
2020-08-11 12:07:06 +00:00
</a>
2020-02-05 14:06:07 +00:00
<template v-if="isOwner">
2021-12-06 10:35:20 +00:00
<div class="divider" />
2020-08-11 12:07:06 +00:00
<a
class="item"
2020-08-11 12:07:06 +00:00
href=""
2021-12-06 10:35:20 +00:00
@click.stop.prevent="showEditModal = true"
>
2022-03-22 15:13:19 +00:00
<i class="edit icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.edit') }}
2020-08-11 12:07:06 +00:00
</a>
<dangerous-button
v-if="object"
2021-12-06 10:35:20 +00:00
:class="['ui', {loading: isLoading}, 'item']"
@confirm="remove()"
>
2022-03-22 15:13:19 +00:00
<i class="ui trash icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.delete') }}
2022-05-01 14:15:57 +00:00
<template #modal-header>
2021-12-06 10:35:20 +00:00
<p>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.delete.header') }}
2021-12-06 10:35:20 +00:00
</p>
2022-05-01 14:15:57 +00:00
</template>
<template #modal-content>
<div>
<p>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.delete.content.warning') }}
2022-05-01 14:15:57 +00:00
</p>
</div>
</template>
<template #modal-confirm>
<p>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.confirm') }}
2022-05-01 14:15:57 +00:00
</p>
</template>
</dangerous-button>
</template>
2021-12-06 10:35:20 +00:00
<template v-if="$store.state.auth.availablePermissions['library']">
<div class="divider" />
<router-link
class="basic item"
:to="{name: 'manage.channels.detail', params: {id: object.uuid}}"
>
<i class="wrench icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.link.moderation') }}
</router-link>
</template>
2020-02-05 14:06:07 +00:00
</div>
</button>
2020-02-05 14:06:07 +00:00
</div>
</div>
<h1 class="ui header">
2021-12-06 10:35:20 +00:00
<div
class="left aligned"
2022-07-04 18:17:58 +00:00
:title="object.artist?.name"
2021-12-06 10:35:20 +00:00
>
2022-07-04 18:17:58 +00:00
{{ object.artist?.name }}
2021-12-06 10:35:20 +00:00
<div class="ui hidden very small divider" />
<div
v-if="object.actor"
class="sub header ellipsis"
:title="object.actor.full_username"
>
2020-02-05 14:06:07 +00:00
{{ object.actor.full_username }}
</div>
2021-12-06 10:35:20 +00:00
<div
v-else
class="sub header ellipsis"
>
<a
:href="object.url || object.rss_url"
rel="noopener noreferrer"
target="_blank"
>
<i class="external link icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.link.mirrored', {domain: externalDomain}) }}
</a>
</div>
2020-02-05 14:06:07 +00:00
</div>
</h1>
<div class="header-buttons">
2021-12-06 10:35:20 +00:00
<div
v-if="isOwner"
class="ui buttons"
>
<button
class="ui basic labeled icon button"
@click.prevent.stop="$store.commit('channels/showUploadModal', {show: true, config: {channel: object}})"
>
<i class="upload icon" />
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.upload') }}
</button>
</div>
2020-02-05 14:06:07 +00:00
<div class="ui buttons">
2021-12-06 10:35:20 +00:00
<play-button
:is-playable="isPlayable"
class="vibrant"
:artist="object.artist"
>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.play') }}
2020-02-05 14:06:07 +00:00
</play-button>
</div>
<div class="ui buttons">
2021-12-06 10:35:20 +00:00
<subscribe-button
:channel="object"
2022-07-04 18:17:58 +00:00
@subscribed="updateSubscriptionCount(1)"
@unsubscribed="updateSubscriptionCount(-1)"
2021-12-06 10:35:20 +00:00
/>
2020-02-05 14:06:07 +00:00
</div>
2022-07-20 22:44:19 +00:00
<semantic-modal
2021-12-06 10:35:20 +00:00
v-if="totalTracks > 0"
2022-05-01 11:45:07 +00:00
v-model:show="showEmbedModal"
2021-12-06 10:35:20 +00:00
>
<h4 class="header">
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.modal.embed.header') }}
</h4>
<div class="scrolling content">
2020-02-05 14:06:07 +00:00
<div class="description">
2021-12-06 10:35:20 +00:00
<embed-wizard
2022-07-04 22:52:53 +00:00
:id="object.artist!.id"
2021-12-06 10:35:20 +00:00
type="artist"
/>
2020-02-05 14:06:07 +00:00
</div>
</div>
<div class="actions">
<button class="ui basic deny button">
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.cancel') }}
</button>
2020-02-05 14:06:07 +00:00
</div>
2022-07-20 22:44:19 +00:00
</semantic-modal>
<semantic-modal
2021-12-06 10:35:20 +00:00
v-if="isOwner"
2022-05-01 11:45:07 +00:00
v-model:show="showEditModal"
2021-12-06 10:35:20 +00:00
>
<h4 class="header">
2022-09-18 19:14:35 +00:00
<span
2022-07-04 18:17:58 +00:00
v-if="object.artist?.content_category === 'podcast'"
2021-12-06 10:35:20 +00:00
>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.header.podcastChannel') }}
2022-09-18 19:14:35 +00:00
</span>
<span
2021-12-06 10:35:20 +00:00
v-else
>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.header.artistChannel') }}
2022-09-18 19:14:35 +00:00
</span>
</h4>
<div class="scrolling content">
<channel-form
ref="editForm"
:object="object"
2022-07-04 18:17:58 +00:00
@loading="edit.loading = $event"
@submittable="edit.submittable = $event"
2021-12-06 10:35:20 +00:00
@updated="fetchData"
/>
<div class="ui hidden divider" />
</div>
<div class="actions">
<button class="ui left floated basic deny button">
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.cancel') }}
</button>
2021-12-06 10:35:20 +00:00
<button
2022-07-04 18:17:58 +00:00
:class="['ui', 'primary', 'confirm', {loading: edit.loading}, 'button']"
:disabled="!edit.submittable"
@click.stop="editForm?.submit"
2021-12-06 10:35:20 +00:00
>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.button.updateChannel') }}
</button>
</div>
2022-07-20 22:44:19 +00:00
</semantic-modal>
2020-02-05 14:06:07 +00:00
</div>
<div v-if="$store.getters['ui/layoutVersion'] === 'large'">
2020-02-05 14:06:07 +00:00
<rendered-description
2022-07-04 18:17:58 +00:00
:content="object.artist?.description"
2020-02-05 14:06:07 +00:00
:update-url="`channels/${object.uuid}/`"
2021-12-06 10:35:20 +00:00
:can-update="false"
@updated="object = $event"
/>
2020-02-05 14:06:07 +00:00
</div>
</div>
<div class="nine wide column">
2020-02-05 14:06:07 +00:00
<div class="ui secondary pointing center aligned menu">
2021-12-06 10:35:20 +00:00
<router-link
class="item"
2022-05-01 12:57:04 +00:00
2021-12-06 10:35:20 +00:00
:to="{name: 'channels.detail', params: {id: id}}"
>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.link.channelOverview') }}
2020-02-05 14:06:07 +00:00
</router-link>
2021-12-06 10:35:20 +00:00
<router-link
class="item"
2022-05-01 12:57:04 +00:00
2021-12-06 10:35:20 +00:00
:to="{name: 'channels.detail.episodes', params: {id: id}}"
>
2022-09-18 19:14:35 +00:00
<span
2021-12-06 10:35:20 +00:00
v-if="isPodcast"
>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.link.channelEpisodes') }}
2022-09-18 19:14:35 +00:00
</span>
<span
2021-12-06 10:35:20 +00:00
v-else
>
2022-09-23 18:14:25 +00:00
{{ $t('views.channels.DetailBase.link.channelTracks') }}
2022-09-18 19:14:35 +00:00
</span>
2020-02-05 14:06:07 +00:00
</router-link>
</div>
2021-12-06 10:35:20 +00:00
<div class="ui hidden divider" />
<router-view
v-if="object"
:object="object"
@tracks-loaded="totalTracks = $event"
/>
2020-02-05 14:06:07 +00:00
</div>
</div>
</section>
</template>
</main>
</template>