Merge branch '689-artist-album-mutations' into 'develop'
Edits for artists and albums See merge request funkwhale/funkwhale!722
This commit is contained in:
commit
63b1007596
|
@ -4,6 +4,7 @@ from django.contrib.postgres.fields import JSONField
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import Lookup
|
from django.db.models import Lookup
|
||||||
from django.db.models.fields import Field
|
from django.db.models.fields import Field
|
||||||
|
@ -70,8 +71,8 @@ class Mutation(models.Model):
|
||||||
applied_date = models.DateTimeField(null=True, blank=True, db_index=True)
|
applied_date = models.DateTimeField(null=True, blank=True, db_index=True)
|
||||||
summary = models.TextField(max_length=2000, null=True, blank=True)
|
summary = models.TextField(max_length=2000, null=True, blank=True)
|
||||||
|
|
||||||
payload = JSONField()
|
payload = JSONField(encoder=DjangoJSONEncoder)
|
||||||
previous_state = JSONField(null=True, default=None)
|
previous_state = JSONField(null=True, default=None, encoder=DjangoJSONEncoder)
|
||||||
|
|
||||||
target_id = models.IntegerField(null=True)
|
target_id = models.IntegerField(null=True)
|
||||||
target_content_type = models.ForeignKey(
|
target_content_type = models.ForeignKey(
|
||||||
|
|
|
@ -346,3 +346,37 @@ def outbox_update_track(context):
|
||||||
to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}],
|
to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@outbox.register({"type": "Update", "object.type": "Album"})
|
||||||
|
def outbox_update_album(context):
|
||||||
|
album = context["album"]
|
||||||
|
serializer = serializers.ActivitySerializer(
|
||||||
|
{"type": "Update", "object": serializers.AlbumSerializer(album).data}
|
||||||
|
)
|
||||||
|
|
||||||
|
yield {
|
||||||
|
"type": "Update",
|
||||||
|
"actor": actors.get_service_actor(),
|
||||||
|
"payload": with_recipients(
|
||||||
|
serializer.data,
|
||||||
|
to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@outbox.register({"type": "Update", "object.type": "Artist"})
|
||||||
|
def outbox_update_artist(context):
|
||||||
|
artist = context["artist"]
|
||||||
|
serializer = serializers.ActivitySerializer(
|
||||||
|
{"type": "Update", "object": serializers.ArtistSerializer(artist).data}
|
||||||
|
)
|
||||||
|
|
||||||
|
yield {
|
||||||
|
"type": "Update",
|
||||||
|
"actor": actors.get_service_actor(),
|
||||||
|
"payload": with_recipients(
|
||||||
|
serializer.data,
|
||||||
|
to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
|
@ -28,3 +28,35 @@ class TrackMutationSerializer(mutations.UpdateMutationSerializer):
|
||||||
routes.outbox.dispatch(
|
routes.outbox.dispatch(
|
||||||
{"type": "Update", "object": {"type": "Track"}}, context={"track": obj}
|
{"type": "Update", "object": {"type": "Track"}}, context={"track": obj}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mutations.registry.connect(
|
||||||
|
"update",
|
||||||
|
models.Artist,
|
||||||
|
perm_checkers={"suggest": can_suggest, "approve": can_approve},
|
||||||
|
)
|
||||||
|
class ArtistMutationSerializer(mutations.UpdateMutationSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.Artist
|
||||||
|
fields = ["name"]
|
||||||
|
|
||||||
|
def post_apply(self, obj, validated_data):
|
||||||
|
routes.outbox.dispatch(
|
||||||
|
{"type": "Update", "object": {"type": "Artist"}}, context={"artist": obj}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mutations.registry.connect(
|
||||||
|
"update",
|
||||||
|
models.Album,
|
||||||
|
perm_checkers={"suggest": can_suggest, "approve": can_approve},
|
||||||
|
)
|
||||||
|
class AlbumMutationSerializer(mutations.UpdateMutationSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.Album
|
||||||
|
fields = ["title", "release_date"]
|
||||||
|
|
||||||
|
def post_apply(self, obj, validated_data):
|
||||||
|
routes.outbox.dispatch(
|
||||||
|
{"type": "Update", "object": {"type": "Album"}}, context={"album": obj}
|
||||||
|
)
|
||||||
|
|
|
@ -70,6 +70,8 @@ class ArtistViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelV
|
||||||
filterset_class = filters.ArtistFilter
|
filterset_class = filters.ArtistFilter
|
||||||
ordering_fields = ("id", "name", "creation_date")
|
ordering_fields = ("id", "name", "creation_date")
|
||||||
|
|
||||||
|
mutations = common_decorators.mutations_route(types=["update"])
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
albums = models.Album.objects.with_tracks_count()
|
albums = models.Album.objects.with_tracks_count()
|
||||||
|
@ -98,6 +100,8 @@ class AlbumViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelVi
|
||||||
ordering_fields = ("creation_date", "release_date", "title")
|
ordering_fields = ("creation_date", "release_date", "title")
|
||||||
filterset_class = filters.AlbumFilter
|
filterset_class = filters.AlbumFilter
|
||||||
|
|
||||||
|
mutations = common_decorators.mutations_route(types=["update"])
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
tracks = (
|
tracks = (
|
||||||
|
|
|
@ -448,6 +448,19 @@ def test_inbox_update_artist(factories, mocker):
|
||||||
update_library_entity.assert_called_once_with(obj, {"name": "New name"})
|
update_library_entity.assert_called_once_with(obj, {"name": "New name"})
|
||||||
|
|
||||||
|
|
||||||
|
def test_outbox_update_artist(factories):
|
||||||
|
artist = factories["music.Artist"]()
|
||||||
|
activity = list(routes.outbox_update_artist({"artist": artist}))[0]
|
||||||
|
expected = serializers.ActivitySerializer(
|
||||||
|
{"type": "Update", "object": serializers.ArtistSerializer(artist).data}
|
||||||
|
).data
|
||||||
|
|
||||||
|
expected["to"] = [contexts.AS.Public, {"type": "instances_with_followers"}]
|
||||||
|
|
||||||
|
assert dict(activity["payload"]) == dict(expected)
|
||||||
|
assert activity["actor"] == actors.get_service_actor()
|
||||||
|
|
||||||
|
|
||||||
def test_inbox_update_album(factories, mocker):
|
def test_inbox_update_album(factories, mocker):
|
||||||
update_library_entity = mocker.patch(
|
update_library_entity = mocker.patch(
|
||||||
"funkwhale_api.music.tasks.update_library_entity"
|
"funkwhale_api.music.tasks.update_library_entity"
|
||||||
|
@ -466,6 +479,19 @@ def test_inbox_update_album(factories, mocker):
|
||||||
update_library_entity.assert_called_once_with(obj, {"title": "New title"})
|
update_library_entity.assert_called_once_with(obj, {"title": "New title"})
|
||||||
|
|
||||||
|
|
||||||
|
def test_outbox_update_album(factories):
|
||||||
|
album = factories["music.Album"]()
|
||||||
|
activity = list(routes.outbox_update_album({"album": album}))[0]
|
||||||
|
expected = serializers.ActivitySerializer(
|
||||||
|
{"type": "Update", "object": serializers.AlbumSerializer(album).data}
|
||||||
|
).data
|
||||||
|
|
||||||
|
expected["to"] = [contexts.AS.Public, {"type": "instances_with_followers"}]
|
||||||
|
|
||||||
|
assert dict(activity["payload"]) == dict(expected)
|
||||||
|
assert activity["actor"] == actors.get_service_actor()
|
||||||
|
|
||||||
|
|
||||||
def test_inbox_update_track(factories, mocker):
|
def test_inbox_update_track(factories, mocker):
|
||||||
update_library_entity = mocker.patch(
|
update_library_entity = mocker.patch(
|
||||||
"funkwhale_api.music.tasks.update_library_entity"
|
"funkwhale_api.music.tasks.update_library_entity"
|
||||||
|
|
|
@ -1,6 +1,54 @@
|
||||||
|
import datetime
|
||||||
|
import pytest
|
||||||
|
|
||||||
from funkwhale_api.music import licenses
|
from funkwhale_api.music import licenses
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"field, old_value, new_value, expected", [("name", "foo", "bar", "bar")]
|
||||||
|
)
|
||||||
|
def test_artist_mutation(field, old_value, new_value, expected, factories, now, mocker):
|
||||||
|
dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||||
|
artist = factories["music.Artist"](**{field: old_value})
|
||||||
|
mutation = factories["common.Mutation"](
|
||||||
|
type="update", target=artist, payload={field: new_value}
|
||||||
|
)
|
||||||
|
mutation.apply()
|
||||||
|
artist.refresh_from_db()
|
||||||
|
|
||||||
|
assert getattr(artist, field) == expected
|
||||||
|
dispatch.assert_called_once_with(
|
||||||
|
{"type": "Update", "object": {"type": "Artist"}}, context={"artist": artist}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"field, old_value, new_value, expected",
|
||||||
|
[
|
||||||
|
("title", "foo", "bar", "bar"),
|
||||||
|
(
|
||||||
|
"release_date",
|
||||||
|
datetime.date(2016, 1, 1),
|
||||||
|
"2018-02-01",
|
||||||
|
datetime.date(2018, 2, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_album_mutation(field, old_value, new_value, expected, factories, now, mocker):
|
||||||
|
dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||||
|
album = factories["music.Album"](**{field: old_value})
|
||||||
|
mutation = factories["common.Mutation"](
|
||||||
|
type="update", target=album, payload={field: new_value}
|
||||||
|
)
|
||||||
|
mutation.apply()
|
||||||
|
album.refresh_from_db()
|
||||||
|
|
||||||
|
assert getattr(album, field) == expected
|
||||||
|
dispatch.assert_called_once_with(
|
||||||
|
{"type": "Update", "object": {"type": "Album"}}, context={"album": album}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_track_license_mutation(factories, now):
|
def test_track_license_mutation(factories, now):
|
||||||
track = factories["music.Track"](license=None)
|
track = factories["music.Track"](license=None)
|
||||||
mutation = factories["common.Mutation"](
|
mutation = factories["common.Mutation"](
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div v-if="isLoading" class="ui vertical segment" v-title="">
|
<div v-if="isLoading" class="ui vertical segment" v-title="labels.title">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="album">
|
<template v-if="object">
|
||||||
<section :class="['ui', 'head', {'with-background': album.cover.original}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="album.title">
|
<section :class="['ui', 'head', {'with-background': object.cover.original}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="object.title">
|
||||||
<div class="segment-content">
|
<div class="segment-content">
|
||||||
<h2 class="ui center aligned icon header">
|
<h2 class="ui center aligned icon header">
|
||||||
<i class="circular inverted sound yellow icon"></i>
|
<i class="circular inverted sound yellow icon"></i>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ album.title }}
|
{{ object.title }}
|
||||||
<div v-html="subtitle"></div>
|
<div v-html="subtitle"></div>
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
|
|
||||||
<div class="ui buttons">
|
<div class="ui buttons">
|
||||||
<play-button class="orange" :tracks="album.tracks">
|
<play-button class="orange" :tracks="object.tracks">
|
||||||
<translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate>
|
<translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate>
|
||||||
</play-button>
|
</play-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<embed-wizard type="album" :id="album.id" />
|
<embed-wizard type="album" :id="object.id" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,15 +61,22 @@
|
||||||
<i class="external icon"></i>
|
<i class="external icon"></i>
|
||||||
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
|
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
|
||||||
</a>
|
</a>
|
||||||
|
<router-link
|
||||||
|
v-if="object.is_local"
|
||||||
|
:to="{name: 'library.albums.edit', params: {id: object.id }}"
|
||||||
|
class="basic item">
|
||||||
|
<i class="edit icon"></i>
|
||||||
|
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
||||||
|
</router-link>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.albums.detail', params: {id: album.id}}">
|
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.albums.detail', params: {id: object.id}}">
|
||||||
<i class="wrench icon"></i>
|
<i class="wrench icon"></i>
|
||||||
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a
|
<a
|
||||||
v-if="$store.state.auth.profile.is_superuser"
|
v-if="$store.state.auth.profile.is_superuser"
|
||||||
class="basic item"
|
class="basic item"
|
||||||
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${album.id}`)"
|
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)"
|
||||||
target="_blank" rel="noopener noreferrer">
|
target="_blank" rel="noopener noreferrer">
|
||||||
<i class="wrench icon"></i>
|
<i class="wrench icon"></i>
|
||||||
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>
|
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>
|
||||||
|
@ -80,36 +87,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<template v-if="discs && discs.length > 1">
|
<router-view v-if="object" :discs="discs" @libraries-loaded="libraries = $event" :object="object" object-type="album" :key="$route.fullPath"></router-view>
|
||||||
<section v-for="(tracks, disc_number) in discs" class="ui vertical stripe segment">
|
|
||||||
<translate
|
|
||||||
tag="h2"
|
|
||||||
class="left floated"
|
|
||||||
:translate-params="{number: disc_number + 1}"
|
|
||||||
translate-context="Content/Album/"
|
|
||||||
>Volume %{ number }</translate>
|
|
||||||
<play-button class="right floated orange" :tracks="tracks">
|
|
||||||
<translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate>
|
|
||||||
</play-button>
|
|
||||||
<track-table :artist="album.artist" :display-position="true" :tracks="tracks"></track-table>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<section class="ui vertical stripe segment">
|
|
||||||
<h2>
|
|
||||||
<translate translate-context="*/*/*/Noun">Tracks</translate>
|
|
||||||
</h2>
|
|
||||||
<track-table v-if="album" :artist="album.artist" :display-position="true" :tracks="album.tracks"></track-table>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
<section class="ui vertical stripe segment">
|
|
||||||
<h2>
|
|
||||||
<translate translate-context="Content/*/Title/Noun">User libraries</translate>
|
|
||||||
</h2>
|
|
||||||
<library-widget @loaded="libraries = $event" :url="'albums/' + id + '/libraries/'">
|
|
||||||
<translate slot="subtitle" translate-context="Content/Album/Paragraph">This album is present in the following libraries:</translate>
|
|
||||||
</library-widget>
|
|
||||||
</section>
|
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
@ -119,13 +97,12 @@ import axios from "axios"
|
||||||
import logger from "@/logging"
|
import logger from "@/logging"
|
||||||
import backend from "@/audio/backend"
|
import backend from "@/audio/backend"
|
||||||
import PlayButton from "@/components/audio/PlayButton"
|
import PlayButton from "@/components/audio/PlayButton"
|
||||||
import TrackTable from "@/components/audio/track/Table"
|
|
||||||
import LibraryWidget from "@/components/federation/LibraryWidget"
|
|
||||||
import EmbedWizard from "@/components/audio/EmbedWizard"
|
import EmbedWizard from "@/components/audio/EmbedWizard"
|
||||||
import Modal from '@/components/semantic/Modal'
|
import Modal from '@/components/semantic/Modal'
|
||||||
|
|
||||||
const FETCH_URL = "albums/"
|
const FETCH_URL = "albums/"
|
||||||
|
|
||||||
|
|
||||||
function groupByDisc(acc, track) {
|
function groupByDisc(acc, track) {
|
||||||
var dn = track.disc_number - 1
|
var dn = track.disc_number - 1
|
||||||
if (dn < 0) dn = 0
|
if (dn < 0) dn = 0
|
||||||
|
@ -141,15 +118,13 @@ export default {
|
||||||
props: ["id"],
|
props: ["id"],
|
||||||
components: {
|
components: {
|
||||||
PlayButton,
|
PlayButton,
|
||||||
TrackTable,
|
|
||||||
LibraryWidget,
|
|
||||||
EmbedWizard,
|
EmbedWizard,
|
||||||
Modal
|
Modal
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
album: null,
|
object: null,
|
||||||
discs: [],
|
discs: [],
|
||||||
libraries: [],
|
libraries: [],
|
||||||
showEmbedModal: false
|
showEmbedModal: false
|
||||||
|
@ -165,8 +140,8 @@ export default {
|
||||||
let url = FETCH_URL + this.id + "/"
|
let url = FETCH_URL + this.id + "/"
|
||||||
logger.default.debug('Fetching album "' + this.id + '"')
|
logger.default.debug('Fetching album "' + this.id + '"')
|
||||||
axios.get(url).then(response => {
|
axios.get(url).then(response => {
|
||||||
self.album = backend.Album.clean(response.data)
|
self.object = backend.Album.clean(response.data)
|
||||||
self.discs = self.album.tracks.reduce(groupByDisc, [])
|
self.discs = self.object.tracks.reduce(groupByDisc, [])
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -185,28 +160,28 @@ export default {
|
||||||
wikipediaUrl() {
|
wikipediaUrl() {
|
||||||
return (
|
return (
|
||||||
"https://en.wikipedia.org/w/index.php?search=" +
|
"https://en.wikipedia.org/w/index.php?search=" +
|
||||||
encodeURI(this.album.title + " " + this.album.artist.name)
|
encodeURI(this.object.title + " " + this.object.artist.name)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
musicbrainzUrl() {
|
musicbrainzUrl() {
|
||||||
if (this.album.mbid) {
|
if (this.object.mbid) {
|
||||||
return "https://musicbrainz.org/release/" + this.album.mbid
|
return "https://musicbrainz.org/release/" + this.object.mbid
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
headerStyle() {
|
headerStyle() {
|
||||||
if (!this.album.cover.original) {
|
if (!this.object.cover.original) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
"background-image: url(" +
|
"background-image: url(" +
|
||||||
this.$store.getters["instance/absoluteUrl"](this.album.cover.original) +
|
this.$store.getters["instance/absoluteUrl"](this.object.cover.original) +
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
subtitle () {
|
subtitle () {
|
||||||
let route = this.$router.resolve({name: 'library.artists.detail', params: {id: this.album.artist.id }})
|
let route = this.$router.resolve({name: 'library.artists.detail', params: {id: this.object.artist.id }})
|
||||||
let msg = this.$npgettext('Content/Album/Header.Title', 'Album containing %{ count } track, by <a class="internal" href="%{ artistUrl }">%{ artist }</a>', 'Album containing %{ count } tracks, by <a class="internal" href="%{ artistUrl }">%{ artist }</a>', this.album.tracks.length)
|
let msg = this.$npgettext('Content/Album/Header.Title', 'Album containing %{ count } track, by <a class="internal" href="%{ artistUrl }">%{ artist }</a>', 'Album containing %{ count } tracks, by <a class="internal" href="%{ artistUrl }">%{ artist }</a>', this.object.tracks.length)
|
||||||
return this.$gettextInterpolate(msg, {count: this.album.tracks.length, artist: this.album.artist.name, artistUrl: route.location.path})
|
return this.$gettextInterpolate(msg, {count: this.object.tracks.length, artist: this.object.artist.name, artistUrl: route.location.path})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -216,7 +191,3 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped lang="scss">
|
|
||||||
</style>
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="object">
|
||||||
|
<template v-if="discs && discs.length > 1">
|
||||||
|
<section v-for="(tracks, disc_number) in discs" class="ui vertical stripe segment">
|
||||||
|
<translate
|
||||||
|
tag="h2"
|
||||||
|
class="left floated"
|
||||||
|
:translate-params="{number: disc_number + 1}"
|
||||||
|
translate-context="Content/Album/"
|
||||||
|
>Volume %{ number }</translate>
|
||||||
|
<play-button class="right floated orange" :tracks="tracks">
|
||||||
|
<translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate>
|
||||||
|
</play-button>
|
||||||
|
<track-table :artist="object.artist" :display-position="true" :tracks="tracks"></track-table>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<section class="ui vertical stripe segment">
|
||||||
|
<h2>
|
||||||
|
<translate translate-context="*/*/*/Noun">Tracks</translate>
|
||||||
|
</h2>
|
||||||
|
<track-table v-if="object" :artist="object.artist" :display-position="true" :tracks="object.tracks"></track-table>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<section class="ui vertical stripe segment">
|
||||||
|
<h2>
|
||||||
|
<translate translate-context="Content/*/Title/Noun">User libraries</translate>
|
||||||
|
</h2>
|
||||||
|
<library-widget @loaded="$emit('libraries-loaded', $event)" :url="'albums/' + object.id + '/libraries/'">
|
||||||
|
<translate slot="subtitle" translate-context="Content/Album/Paragraph">This album is present in the following libraries:</translate>
|
||||||
|
</library-widget>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import time from "@/utils/time"
|
||||||
|
import axios from "axios"
|
||||||
|
import url from "@/utils/url"
|
||||||
|
import logger from "@/logging"
|
||||||
|
import LibraryWidget from "@/components/federation/LibraryWidget"
|
||||||
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ["object", "libraries", "discs"],
|
||||||
|
components: {
|
||||||
|
LibraryWidget,
|
||||||
|
TrackTable
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
time,
|
||||||
|
id: this.object.id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped lang="scss">
|
||||||
|
</style>
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<section class="ui vertical stripe segment">
|
||||||
|
<div class="ui text container">
|
||||||
|
<h2>
|
||||||
|
<translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this album</translate>
|
||||||
|
<translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this album</translate>
|
||||||
|
</h2>
|
||||||
|
<div class="ui message" v-if="!object.is_local">
|
||||||
|
<translate translate-context="Content/*/Message">This object is managed by another server, you cannot edit it.</translate>
|
||||||
|
</div>
|
||||||
|
<edit-form
|
||||||
|
v-else
|
||||||
|
:object-type="objectType"
|
||||||
|
:object="object"
|
||||||
|
:can-edit="canEdit"></edit-form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
import EditForm from '@/components/library/EditForm'
|
||||||
|
export default {
|
||||||
|
props: ["objectType", "object", "libraries"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: this.object.id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
EditForm
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
canEdit () {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -3,13 +3,13 @@
|
||||||
<div v-if="isLoading" class="ui vertical segment">
|
<div v-if="isLoading" class="ui vertical segment">
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="artist">
|
<template v-if="object">
|
||||||
<section :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="artist.name">
|
<section :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="object.name">
|
||||||
<div class="segment-content">
|
<div class="segment-content">
|
||||||
<h2 class="ui center aligned icon header">
|
<h2 class="ui center aligned icon header">
|
||||||
<i class="circular inverted users violet icon"></i>
|
<i class="circular inverted users violet icon"></i>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ artist.name }}
|
{{ object.name }}
|
||||||
<div class="sub header" v-if="albums">
|
<div class="sub header" v-if="albums">
|
||||||
<translate translate-context="Content/Artist/Paragraph"
|
<translate translate-context="Content/Artist/Paragraph"
|
||||||
tag="div"
|
tag="div"
|
||||||
|
@ -24,11 +24,11 @@
|
||||||
<div class="ui hidden divider"></div>
|
<div class="ui hidden divider"></div>
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
<div class="ui buttons">
|
<div class="ui buttons">
|
||||||
<radio-button type="artist" :object-id="artist.id"></radio-button>
|
<radio-button type="artist" :object-id="object.id"></radio-button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ui buttons">
|
<div class="ui buttons">
|
||||||
<play-button :is-playable="isPlayable" class="orange" :artist="artist">
|
<play-button :is-playable="isPlayable" class="orange" :artist="object">
|
||||||
<translate translate-context="Content/Artist/Button.Label/Verb">Play all albums</translate>
|
<translate translate-context="Content/Artist/Button.Label/Verb">Play all albums</translate>
|
||||||
</play-button>
|
</play-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<embed-wizard type="artist" :id="artist.id" />
|
<embed-wizard type="artist" :id="object.id" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,15 +72,22 @@
|
||||||
<i class="external icon"></i>
|
<i class="external icon"></i>
|
||||||
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
|
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
|
||||||
</a>
|
</a>
|
||||||
|
<router-link
|
||||||
|
v-if="object.is_local"
|
||||||
|
:to="{name: 'library.artists.edit', params: {id: object.id }}"
|
||||||
|
class="basic item">
|
||||||
|
<i class="edit icon"></i>
|
||||||
|
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
||||||
|
</router-link>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.artists.detail', params: {id: artist.id}}">
|
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.artists.detail', params: {id: object.id}}">
|
||||||
<i class="wrench icon"></i>
|
<i class="wrench icon"></i>
|
||||||
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a
|
<a
|
||||||
v-if="$store.state.auth.profile.is_superuser"
|
v-if="$store.state.auth.profile.is_superuser"
|
||||||
class="basic item"
|
class="basic item"
|
||||||
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${artist.id}`)"
|
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)"
|
||||||
target="_blank" rel="noopener noreferrer">
|
target="_blank" rel="noopener noreferrer">
|
||||||
<i class="wrench icon"></i>
|
<i class="wrench icon"></i>
|
||||||
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>
|
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>
|
||||||
|
@ -91,84 +98,40 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="ui small text container" v-if="contentFilter">
|
<router-view v-if="object" :tracks="tracks" :albums="albums" :is-loading-albums="isLoadingAlbums" @libraries-loaded="libraries = $event" :object="object" object-type="artist" :key="$route.fullPath"></router-view>
|
||||||
<div class="ui hidden divider"></div>
|
|
||||||
<div class="ui message">
|
|
||||||
<p>
|
|
||||||
<translate translate-context="Content/Artist/Paragraph">You are currently hiding content related to this artist.</translate>
|
|
||||||
</p>
|
|
||||||
<router-link class="right floated" :to="{name: 'settings'}">
|
|
||||||
<translate translate-context="Content/Moderation/Link">Review my filters</translate>
|
|
||||||
</router-link>
|
|
||||||
<button @click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)" class="ui basic tiny button">
|
|
||||||
<translate translate-context="Content/Moderation/Button.Label">Remove filter</translate>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<section v-if="isLoadingAlbums" class="ui vertical stripe segment">
|
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
|
||||||
</section>
|
|
||||||
<section v-else-if="albums && albums.length > 0" class="ui vertical stripe segment">
|
|
||||||
<h2>
|
|
||||||
<translate translate-context="Content/Artist/Title">Albums by this artist</translate>
|
|
||||||
</h2>
|
|
||||||
<div class="ui cards" >
|
|
||||||
<album-card :mode="'rich'" :album="album" :key="album.id" v-for="album in albums"></album-card>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section v-if="tracks.length > 0" class="ui vertical stripe segment">
|
|
||||||
<h2>
|
|
||||||
<translate translate-context="Content/Artist/Title">Tracks by this artist</translate>
|
|
||||||
</h2>
|
|
||||||
<track-table :display-position="true" :tracks="tracks"></track-table>
|
|
||||||
</section>
|
|
||||||
<section class="ui vertical stripe segment">
|
|
||||||
<h2>
|
|
||||||
<translate translate-context="Content/*/Title/Noun">User libraries</translate>
|
|
||||||
</h2>
|
|
||||||
<library-widget @loaded="libraries = $event" :url="'artists/' + id + '/libraries/'">
|
|
||||||
<translate translate-context="Content/Artist/Paragraph" slot="subtitle">This artist is present in the following libraries:</translate>
|
|
||||||
</library-widget>
|
|
||||||
</section>
|
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from "@/lodash"
|
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import logger from "@/logging"
|
import logger from "@/logging"
|
||||||
import backend from "@/audio/backend"
|
import backend from "@/audio/backend"
|
||||||
import AlbumCard from "@/components/audio/album/Card"
|
|
||||||
import RadioButton from "@/components/radios/Button"
|
|
||||||
import PlayButton from "@/components/audio/PlayButton"
|
import PlayButton from "@/components/audio/PlayButton"
|
||||||
import TrackTable from "@/components/audio/track/Table"
|
|
||||||
import LibraryWidget from "@/components/federation/LibraryWidget"
|
|
||||||
import EmbedWizard from "@/components/audio/EmbedWizard"
|
import EmbedWizard from "@/components/audio/EmbedWizard"
|
||||||
import Modal from '@/components/semantic/Modal'
|
import Modal from '@/components/semantic/Modal'
|
||||||
|
import RadioButton from "@/components/radios/Button"
|
||||||
|
|
||||||
|
const FETCH_URL = "albums/"
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ["id"],
|
props: ["id"],
|
||||||
components: {
|
components: {
|
||||||
AlbumCard,
|
|
||||||
RadioButton,
|
|
||||||
PlayButton,
|
PlayButton,
|
||||||
TrackTable,
|
|
||||||
LibraryWidget,
|
|
||||||
EmbedWizard,
|
EmbedWizard,
|
||||||
Modal
|
Modal,
|
||||||
|
RadioButton
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
isLoadingAlbums: true,
|
isLoadingAlbums: true,
|
||||||
artist: null,
|
object: null,
|
||||||
albums: null,
|
albums: null,
|
||||||
totalTracks: 0,
|
|
||||||
totalAlbums: 0,
|
|
||||||
tracks: [],
|
|
||||||
libraries: [],
|
libraries: [],
|
||||||
showEmbedModal: false
|
showEmbedModal: false,
|
||||||
|
tracks: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -184,7 +147,7 @@ export default {
|
||||||
self.totalTracks = response.data.count
|
self.totalTracks = response.data.count
|
||||||
})
|
})
|
||||||
axios.get("artists/" + this.id + "/").then(response => {
|
axios.get("artists/" + this.id + "/").then(response => {
|
||||||
self.artist = response.data
|
self.object = response.data
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.isLoadingAlbums = true
|
self.isLoadingAlbums = true
|
||||||
axios
|
axios
|
||||||
|
@ -204,40 +167,31 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
labels() {
|
|
||||||
return {
|
|
||||||
title: this.$pgettext('*/*/*/Noun', "Artist")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isPlayable() {
|
isPlayable() {
|
||||||
return (
|
return (
|
||||||
this.artist.albums.filter(a => {
|
this.object.albums.filter(a => {
|
||||||
return a.is_playable
|
return a.is_playable
|
||||||
}).length > 0
|
}).length > 0
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
labels() {
|
||||||
|
return {
|
||||||
|
title: this.$pgettext('*/*/*', 'Album')
|
||||||
|
}
|
||||||
|
},
|
||||||
wikipediaUrl() {
|
wikipediaUrl() {
|
||||||
return (
|
return (
|
||||||
"https://en.wikipedia.org/w/index.php?search=" +
|
"https://en.wikipedia.org/w/index.php?search=" +
|
||||||
encodeURI(this.artist.name)
|
encodeURI(this.object.name)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
musicbrainzUrl() {
|
musicbrainzUrl() {
|
||||||
if (this.artist.mbid) {
|
if (this.object.mbid) {
|
||||||
return "https://musicbrainz.org/artist/" + this.artist.mbid
|
return "https://musicbrainz.org/artist/" + this.object.mbid
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
allTracks() {
|
|
||||||
let tracks = []
|
|
||||||
this.albums.forEach(album => {
|
|
||||||
album.tracks.forEach(track => {
|
|
||||||
tracks.push(track)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return tracks
|
|
||||||
},
|
|
||||||
cover() {
|
cover() {
|
||||||
return this.artist.albums
|
return this.object.albums
|
||||||
.filter(album => {
|
.filter(album => {
|
||||||
return album.cover
|
return album.cover
|
||||||
})
|
})
|
||||||
|
@ -264,7 +218,7 @@ export default {
|
||||||
contentFilter () {
|
contentFilter () {
|
||||||
let self = this
|
let self = this
|
||||||
return this.$store.getters['moderation/artistFilters']().filter((e) => {
|
return this.$store.getters['moderation/artistFilters']().filter((e) => {
|
||||||
return e.target.id === this.artist.id
|
return e.target.id === this.object.id
|
||||||
})[0]
|
})[0]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -275,7 +229,3 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="object">
|
||||||
|
<div class="ui small text container" v-if="contentFilter">
|
||||||
|
<div class="ui hidden divider"></div>
|
||||||
|
<div class="ui message">
|
||||||
|
<p>
|
||||||
|
<translate translate-context="Content/Artist/Paragraph">You are currently hiding content related to this artist.</translate>
|
||||||
|
</p>
|
||||||
|
<router-link class="right floated" :to="{name: 'settings'}">
|
||||||
|
<translate translate-context="Content/Moderation/Link">Review my filters</translate>
|
||||||
|
</router-link>
|
||||||
|
<button @click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)" class="ui basic tiny button">
|
||||||
|
<translate translate-context="Content/Moderation/Button.Label">Remove filter</translate>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section v-if="isLoadingAlbums" class="ui vertical stripe segment">
|
||||||
|
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||||
|
</section>
|
||||||
|
<section v-else-if="albums && albums.length > 0" class="ui vertical stripe segment">
|
||||||
|
<h2>
|
||||||
|
<translate translate-context="Content/Artist/Title">Albums by this artist</translate>
|
||||||
|
</h2>
|
||||||
|
<div class="ui cards" >
|
||||||
|
<album-card :mode="'rich'" :album="album" :key="album.id" v-for="album in albums"></album-card>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section v-if="tracks.length > 0" class="ui vertical stripe segment">
|
||||||
|
<h2>
|
||||||
|
<translate translate-context="Content/Artist/Title">Tracks by this artist</translate>
|
||||||
|
</h2>
|
||||||
|
<track-table :display-position="true" :tracks="tracks"></track-table>
|
||||||
|
</section>
|
||||||
|
<section class="ui vertical stripe segment">
|
||||||
|
<h2>
|
||||||
|
<translate translate-context="Content/*/Title/Noun">User libraries</translate>
|
||||||
|
</h2>
|
||||||
|
<library-widget @loaded="$emit('libraries-loaded', $event)" :url="'artists/' + object.id + '/libraries/'">
|
||||||
|
<translate translate-context="Content/Artist/Paragraph" slot="subtitle">This artist is present in the following libraries:</translate>
|
||||||
|
</library-widget>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from "@/lodash"
|
||||||
|
import axios from "axios"
|
||||||
|
import logger from "@/logging"
|
||||||
|
import backend from "@/audio/backend"
|
||||||
|
import AlbumCard from "@/components/audio/album/Card"
|
||||||
|
import TrackTable from "@/components/audio/track/Table"
|
||||||
|
import LibraryWidget from "@/components/federation/LibraryWidget"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ["object", "tracks", "albums", "isLoadingAlbums"],
|
||||||
|
components: {
|
||||||
|
AlbumCard,
|
||||||
|
TrackTable,
|
||||||
|
LibraryWidget,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
contentFilter () {
|
||||||
|
let self = this
|
||||||
|
return this.$store.getters['moderation/artistFilters']().filter((e) => {
|
||||||
|
return e.target.id === this.object.id
|
||||||
|
})[0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
id() {
|
||||||
|
this.fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<section class="ui vertical stripe segment">
|
||||||
|
<div class="ui text container">
|
||||||
|
<h2>
|
||||||
|
<translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this artist</translate>
|
||||||
|
<translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this artist</translate>
|
||||||
|
</h2>
|
||||||
|
<div class="ui message" v-if="!object.is_local">
|
||||||
|
<translate translate-context="Content/*/Message">This object is managed by another server, you cannot edit it.</translate>
|
||||||
|
</div>
|
||||||
|
<edit-form
|
||||||
|
v-else
|
||||||
|
:object-type="objectType"
|
||||||
|
:object="object"
|
||||||
|
:can-edit="canEdit"></edit-form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
import EditForm from '@/components/library/EditForm'
|
||||||
|
export default {
|
||||||
|
props: ["objectType", "object", "libraries"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: this.object.id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
EditForm
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
canEdit () {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -149,6 +149,12 @@ export default {
|
||||||
if (this.objectType === 'track') {
|
if (this.objectType === 'track') {
|
||||||
return `tracks/${this.object.id}/mutations/`
|
return `tracks/${this.object.id}/mutations/`
|
||||||
}
|
}
|
||||||
|
if (this.objectType === 'album') {
|
||||||
|
return `albums/${this.object.id}/mutations/`
|
||||||
|
}
|
||||||
|
if (this.objectType === 'artist') {
|
||||||
|
return `artists/${this.object.id}/mutations/`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mutationPayload () {
|
mutationPayload () {
|
||||||
let self = this
|
let self = this
|
||||||
|
|
|
@ -1,13 +1,42 @@
|
||||||
export default {
|
export default {
|
||||||
getConfigs () {
|
getConfigs () {
|
||||||
return {
|
return {
|
||||||
|
artist: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
label: this.$pgettext('*/*/*/Noun', 'Name'),
|
||||||
|
getValue: (obj) => { return obj.name }
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
album: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'title',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
label: this.$pgettext('*/*/*/Noun', 'Title'),
|
||||||
|
getValue: (obj) => { return obj.title }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'release_date',
|
||||||
|
type: 'text',
|
||||||
|
required: false,
|
||||||
|
label: this.$pgettext('Content/*/*/Noun', 'Release date'),
|
||||||
|
getValue: (obj) => { return obj.release_date }
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
track: {
|
track: {
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
id: 'title',
|
id: 'title',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
label: this.$pgettext('Content/Track/*/Noun', 'Title'),
|
label: this.$pgettext('*/*/*/Noun', 'Title'),
|
||||||
getValue: (obj) => { return obj.title }
|
getValue: (obj) => { return obj.title }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,10 +16,14 @@ import PasswordResetConfirm from '@/views/auth/PasswordResetConfirm'
|
||||||
import EmailConfirm from '@/views/auth/EmailConfirm'
|
import EmailConfirm from '@/views/auth/EmailConfirm'
|
||||||
import Library from '@/components/library/Library'
|
import Library from '@/components/library/Library'
|
||||||
import LibraryHome from '@/components/library/Home'
|
import LibraryHome from '@/components/library/Home'
|
||||||
import LibraryArtist from '@/components/library/Artist'
|
|
||||||
import LibraryArtists from '@/components/library/Artists'
|
import LibraryArtists from '@/components/library/Artists'
|
||||||
|
import LibraryArtistDetail from '@/components/library/ArtistDetail'
|
||||||
|
import LibraryArtistEdit from '@/components/library/ArtistEdit'
|
||||||
|
import LibraryArtistDetailBase from '@/components/library/ArtistBase'
|
||||||
import LibraryAlbums from '@/components/library/Albums'
|
import LibraryAlbums from '@/components/library/Albums'
|
||||||
import LibraryAlbum from '@/components/library/Album'
|
import LibraryAlbumDetail from '@/components/library/AlbumDetail'
|
||||||
|
import LibraryAlbumEdit from '@/components/library/AlbumEdit'
|
||||||
|
import LibraryAlbumDetailBase from '@/components/library/AlbumBase'
|
||||||
import LibraryTrackDetail from '@/components/library/TrackDetail'
|
import LibraryTrackDetail from '@/components/library/TrackDetail'
|
||||||
import LibraryTrackEdit from '@/components/library/TrackEdit'
|
import LibraryTrackEdit from '@/components/library/TrackEdit'
|
||||||
import EditDetail from '@/components/library/EditDetail'
|
import EditDetail from '@/components/library/EditDetail'
|
||||||
|
@ -411,8 +415,52 @@ export default new Router({
|
||||||
id: route.params.id,
|
id: route.params.id,
|
||||||
defaultEdit: route.query.mode === 'edit' })
|
defaultEdit: route.query.mode === 'edit' })
|
||||||
},
|
},
|
||||||
{ path: 'artists/:id', name: 'library.artists.detail', component: LibraryArtist, props: true },
|
{
|
||||||
{ path: 'albums/:id', name: 'library.albums.detail', component: LibraryAlbum, props: true },
|
path: 'artists/:id',
|
||||||
|
component: LibraryArtistDetailBase,
|
||||||
|
props: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'library.artists.detail',
|
||||||
|
component: LibraryArtistDetail
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit',
|
||||||
|
name: 'library.artists.edit',
|
||||||
|
component: LibraryArtistEdit
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit/:editId',
|
||||||
|
name: 'library.artists.edit.detail',
|
||||||
|
component: EditDetail,
|
||||||
|
props: true,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'albums/:id',
|
||||||
|
component: LibraryAlbumDetailBase,
|
||||||
|
props: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'library.albums.detail',
|
||||||
|
component: LibraryAlbumDetail
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit',
|
||||||
|
name: 'library.albums.edit',
|
||||||
|
component: LibraryAlbumEdit
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit/:editId',
|
||||||
|
name: 'library.albums.edit.detail',
|
||||||
|
component: EditDetail,
|
||||||
|
props: true,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'tracks/:id',
|
path: 'tracks/:id',
|
||||||
component: LibraryTrackDetailBase,
|
component: LibraryTrackDetailBase,
|
||||||
|
|
Loading…
Reference in New Issue