Resolve "Add "play all" button in tag search result page"
This commit is contained in:
parent
3d825cd170
commit
0f4226e06f
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.10 on 2022-01-21 11:56
|
||||
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.core.serializers.json
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('radios', '0005_auto_20200803_1222'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='radiosession',
|
||||
name='config',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,14 @@
|
|||
# Generated by Django 3.2.13 on 2022-07-15 08:01
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('radios', '0006_alter_radio_config'),
|
||||
('radios', '0006_radiosession_config'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
|
@ -51,6 +51,8 @@ class RadioSession(models.Model):
|
|||
related_object = GenericForeignKey(
|
||||
"related_object_content_type", "related_object_id"
|
||||
)
|
||||
CONFIG_VERSION = 0
|
||||
config = JSONField(encoder=DjangoJSONEncoder, blank=True, null=True)
|
||||
|
||||
def save(self, **kwargs):
|
||||
self.radio.clean(self)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
import logging
|
||||
import random
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -15,6 +16,8 @@ from funkwhale_api.tags.models import Tag
|
|||
from . import filters, models
|
||||
from .registries import registry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SimpleRadio(object):
|
||||
related_object_field = None
|
||||
|
@ -148,6 +151,37 @@ class CustomRadio(SessionRadio):
|
|||
return data
|
||||
|
||||
|
||||
@registry.register(name="custom_multiple")
|
||||
class CustomMultiple(SessionRadio):
|
||||
"""
|
||||
Receive a vuejs generated config and use it to launch a radio session
|
||||
"""
|
||||
|
||||
config = serializers.JSONField(required=True)
|
||||
|
||||
def get_config(self, data):
|
||||
return data["config"]
|
||||
|
||||
def get_queryset_kwargs(self):
|
||||
kwargs = super().get_queryset_kwargs()
|
||||
kwargs["config"] = self.session.config
|
||||
return kwargs
|
||||
|
||||
def validate_session(self, data, **context):
|
||||
data = super().validate_session(data, **context)
|
||||
try:
|
||||
data["config"] is not None
|
||||
except KeyError:
|
||||
raise serializers.ValidationError(
|
||||
"You must provide a configuration for this radio"
|
||||
)
|
||||
return data
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
qs = super().get_queryset(**kwargs)
|
||||
return filters.run(kwargs["config"], candidates=qs)
|
||||
|
||||
|
||||
class RelatedObjectRadio(SessionRadio):
|
||||
"""Abstract radio related to an object (tag, artist, user...)"""
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ class RadioSessionSerializer(serializers.ModelSerializer):
|
|||
"user",
|
||||
"creation_date",
|
||||
"custom_radio",
|
||||
"config",
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
|
|
|
@ -413,3 +413,19 @@ def test_get_choices_for_custom_radio_exclude_tag(factories):
|
|||
|
||||
expected = [u.track.pk for u in included_uploads]
|
||||
assert list(choices.values_list("id", flat=True)) == expected
|
||||
|
||||
|
||||
def test_can_start_custom_multiple_radio_from_api(api_client, factories):
|
||||
tracks = factories["music.Track"].create_batch(5)
|
||||
url = reverse("api:v1:radios:sessions-list")
|
||||
map_filters_to_type = {"tags": "names", "artists": "ids"}
|
||||
for (key, value) in map_filters_to_type.items():
|
||||
attr = value[:-1]
|
||||
track_filter_key = [getattr(a.artist, attr) for a in tracks]
|
||||
config = {"filters": [{"type": key, value: track_filter_key}]}
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"radio_type": "custom_multiple", "config": config},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
|
|
@ -40,5 +40,6 @@ def test_tag_radio_repr(factories, to_api_date):
|
|||
"user": session.user.pk,
|
||||
"related_object_id": tag.name,
|
||||
"creation_date": to_api_date(session.creation_date),
|
||||
"config": None,
|
||||
}
|
||||
assert serializers.RadioSessionSerializer(session).data == expected
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Adding support for play all radio in search result page (#1563)
|
|
@ -7,16 +7,7 @@
|
|||
class="ui feed icon"
|
||||
role="button"
|
||||
/>
|
||||
<template v-if="running">
|
||||
<translate translate-context="*/Player/Button.Label/Short, Verb">
|
||||
Stop radio
|
||||
</translate>
|
||||
</template>
|
||||
<template v-else>
|
||||
<translate translate-context="*/Queue/Button.Label/Short, Verb">
|
||||
Play radio
|
||||
</translate>
|
||||
</template>
|
||||
{{ buttonLabel }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
@ -28,7 +19,8 @@ export default {
|
|||
customRadioId: { type: Number, required: false, default: null },
|
||||
type: { type: String, required: false, default: '' },
|
||||
clientOnly: { type: Boolean, default: false },
|
||||
objectId: { type: [String, Number, Object], default: null }
|
||||
objectId: { type: [String, Number, Object], default: null },
|
||||
config: { type: [Array, Object], required: false, default: null }
|
||||
},
|
||||
computed: {
|
||||
running () {
|
||||
|
@ -39,6 +31,25 @@ export default {
|
|||
} else {
|
||||
return current.type === this.type && lodash.isEqual(current.objectId, this.objectId) && current.customRadioId === this.customRadioId
|
||||
}
|
||||
},
|
||||
label () {
|
||||
return this.config?.[0]?.type ?? null
|
||||
},
|
||||
buttonLabel () {
|
||||
switch (this.label) {
|
||||
case 'tag':
|
||||
return this.running
|
||||
? this.$pgettext('*/Player/Button.Label/Short, Verb', 'Stop tags radio')
|
||||
: this.$pgettext('*/Player/Button.Label/Short, Verb', 'Start tags radio')
|
||||
case 'artist':
|
||||
return this.running
|
||||
? this.$pgettext('*/Player/Button.Label/Short, Verb', 'Stop artists radio')
|
||||
: this.$pgettext('*/Player/Button.Label/Short, Verb', 'Start artists radio')
|
||||
default:
|
||||
return this.running
|
||||
? this.$pgettext('*/Player/Button.Label/Short, Verb', 'Stop radio')
|
||||
: this.$pgettext('*/Queue/Button.Label/Short, Verb', 'Play radio')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -50,7 +61,8 @@ export default {
|
|||
type: this.type,
|
||||
objectId: this.objectId,
|
||||
customRadioId: this.customRadioId,
|
||||
clientOnly: this.clientOnly
|
||||
clientOnly: this.clientOnly,
|
||||
config: this.config
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,14 +48,15 @@ export default {
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
start ({ commit, dispatch }, { type, objectId, customRadioId, clientOnly }) {
|
||||
start ({ commit, dispatch }, { type, objectId, customRadioId, clientOnly, config }) {
|
||||
const params = {
|
||||
radio_type: type,
|
||||
related_object_id: objectId,
|
||||
custom_radio: customRadioId
|
||||
custom_radio: customRadioId,
|
||||
config: config
|
||||
}
|
||||
if (clientOnly) {
|
||||
commit('current', { type, objectId, customRadioId, clientOnly })
|
||||
commit('current', { type, objectId, customRadioId, clientOnly, config })
|
||||
commit('running', true)
|
||||
dispatch('populateQueue', true)
|
||||
return
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
<translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
|
||||
</label>
|
||||
</h2>
|
||||
<div class="ui two column doubling stackable grid container">
|
||||
<div class="column">
|
||||
<form
|
||||
class="ui form"
|
||||
@submit.prevent="page = 1; search()"
|
||||
|
@ -46,6 +48,16 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="column">
|
||||
<radio-button
|
||||
v-if="currentResults && currentConfigValidated && ( type === 'tags' || type === 'artists' ) "
|
||||
class="ui right floated medium button"
|
||||
type="custom_multiple"
|
||||
:config="currentConfig"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui secondary pointing menu">
|
||||
<a
|
||||
v-for="t in types"
|
||||
|
@ -143,6 +155,7 @@ import AlbumCard from '@/components/audio/album/Card.vue'
|
|||
import TrackTable from '@/components/audio/track/Table.vue'
|
||||
import Pagination from '@/components/Pagination.vue'
|
||||
import PlaylistCardList from '@/components/playlists/CardList.vue'
|
||||
import RadioButton from '@/components/radios/Button.vue'
|
||||
import RadioCard from '@/components/radios/Card.vue'
|
||||
import TagsList from '@/components/tags/List.vue'
|
||||
|
||||
|
@ -157,6 +170,7 @@ export default {
|
|||
Pagination,
|
||||
PlaylistCardList,
|
||||
RadioCard,
|
||||
RadioButton,
|
||||
TagsList
|
||||
},
|
||||
props: {
|
||||
|
@ -181,7 +195,8 @@ export default {
|
|||
series: null
|
||||
},
|
||||
isLoading: false,
|
||||
paginateBy: 25
|
||||
paginateBy: 25,
|
||||
config: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -262,6 +277,15 @@ export default {
|
|||
},
|
||||
currentResults () {
|
||||
return this.results[this.currentType.id]
|
||||
},
|
||||
currentConfig () {
|
||||
const resultDict = this.currentResults.results
|
||||
return this.generateConfig(this.currentType.id, resultDict)
|
||||
},
|
||||
currentConfigValidated () {
|
||||
const configValidate = this.currentConfig
|
||||
const array = configValidate[0][Object.keys(configValidate[0])[1]]
|
||||
return array.length >= 1
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -325,6 +349,29 @@ export default {
|
|||
type: this.type
|
||||
}).toString()
|
||||
)
|
||||
},
|
||||
generateConfig: function (type, resultDict) {
|
||||
const obj = {
|
||||
type: type.slice(0, -1)
|
||||
}
|
||||
switch (type) {
|
||||
case 'tags':
|
||||
obj.names = this.generateTagConfig(resultDict, type)
|
||||
break
|
||||
case 'artists':
|
||||
obj.ids = this.generateArtistConfig(resultDict, type)
|
||||
break
|
||||
default:
|
||||
console.info('This type is not yet supported for radio')
|
||||
obj.ids = 0
|
||||
}
|
||||
return [obj]
|
||||
},
|
||||
generateTagConfig: function (resultDict, type) {
|
||||
return Object.values(resultDict).map(({ name }) => name)
|
||||
},
|
||||
generateArtistConfig: function (resultDict, type) {
|
||||
return Object.values(resultDict).map(({ id }) => id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue