podcasts & player

This commit is contained in:
bkfox 2023-11-24 20:46:56 +01:00
parent 474016f776
commit 62ada47352
14 changed files with 490 additions and 486 deletions

View File

@ -10,8 +10,13 @@ from .program import ProgramChildQuerySet
__all__ = ("Episode",) __all__ = ("Episode",)
class EpisodeQuerySet(ProgramChildQuerySet):
def with_podcasts(self):
return self.filter(sound__is_public=True).distinct()
class Episode(Page): class Episode(Page):
objects = ProgramChildQuerySet.as_manager() objects = EpisodeQuerySet.as_manager()
detail_url_name = "episode-detail" detail_url_name = "episode-detail"
template_prefix = "episode" template_prefix = "episode"
@ -26,6 +31,7 @@ class Episode(Page):
@cached_property @cached_property
def podcasts(self): def podcasts(self):
"""Return serialized data about podcasts.""" """Return serialized data about podcasts."""
from .sound import Sound
from ..serializers import PodcastSerializer from ..serializers import PodcastSerializer
podcasts = [PodcastSerializer(s).data for s in self.sound_set.public().order_by("type")] podcasts = [PodcastSerializer(s).data for s in self.sound_set.public().order_by("type")]
@ -35,7 +41,14 @@ class Episode(Page):
else: else:
cover = None cover = None
archive_index = 1
for index, podcast in enumerate(podcasts): for index, podcast in enumerate(podcasts):
if podcast["type"] == Sound.TYPE_ARCHIVE:
if archive_index > 1:
podcast["name"] = f"{self.title} - {archive_index}"
else:
podcast["name"] = self.title
podcasts[index]["cover"] = cover podcasts[index]["cover"] = cover
podcasts[index]["page_url"] = self.get_absolute_url() podcasts[index]["page_url"] = self.get_absolute_url()
podcasts[index]["page_title"] = self.title podcasts[index]["page_title"] = self.title

View File

@ -84,7 +84,7 @@ fieldset[disabled] .pagination-ellipsis {
} }
.title:not(:last-child), .title:not(:last-child),
.subtitle:not(:last-child), .table-container:not(:last-child), .table:not(:last-child), .progress:not(:last-child), .content:not(:last-child), .box:not(:last-child), .pagination:not(:last-child), .message:not(:last-child), .breadcrumb:not(:last-child) { .subtitle:not(:last-child), .table-container:not(:last-child), .table:not(:last-child), .content:not(:last-child), .box:not(:last-child), .pagination:not(:last-child), .message:not(:last-child), .breadcrumb:not(:last-child) {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@ -8819,188 +8819,6 @@ div.icon-text {
display: flex; display: flex;
} }
.progress {
-moz-appearance: none;
-webkit-appearance: none;
border: none;
border-radius: 9999px;
display: block;
height: 1rem;
overflow: hidden;
padding: 0;
width: 100%;
}
.progress::-webkit-progress-bar {
background-color: hsl(0deg, 0%, 93%);
}
.progress::-webkit-progress-value {
background-color: hsl(0deg, 0%, 29%);
}
.progress::-moz-progress-bar {
background-color: hsl(0deg, 0%, 29%);
}
.progress::-ms-fill {
background-color: hsl(0deg, 0%, 29%);
border: none;
}
.progress.is-white::-webkit-progress-value {
background-color: #fff;
}
.progress.is-white::-moz-progress-bar {
background-color: #fff;
}
.progress.is-white::-ms-fill {
background-color: #fff;
}
.progress.is-white:indeterminate {
background-image: linear-gradient(to right, #fff 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-black::-webkit-progress-value {
background-color: #000;
}
.progress.is-black::-moz-progress-bar {
background-color: #000;
}
.progress.is-black::-ms-fill {
background-color: #000;
}
.progress.is-black:indeterminate {
background-image: linear-gradient(to right, #000 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-light::-webkit-progress-value {
background-color: hsl(0deg, 0%, 96%);
}
.progress.is-light::-moz-progress-bar {
background-color: hsl(0deg, 0%, 96%);
}
.progress.is-light::-ms-fill {
background-color: hsl(0deg, 0%, 96%);
}
.progress.is-light:indeterminate {
background-image: linear-gradient(to right, hsl(0deg, 0%, 96%) 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-dark::-webkit-progress-value {
background-color: hsl(0deg, 0%, 21%);
}
.progress.is-dark::-moz-progress-bar {
background-color: hsl(0deg, 0%, 21%);
}
.progress.is-dark::-ms-fill {
background-color: hsl(0deg, 0%, 21%);
}
.progress.is-dark:indeterminate {
background-image: linear-gradient(to right, hsl(0deg, 0%, 21%) 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-primary::-webkit-progress-value {
background-color: hsl(171deg, 100%, 41%);
}
.progress.is-primary::-moz-progress-bar {
background-color: hsl(171deg, 100%, 41%);
}
.progress.is-primary::-ms-fill {
background-color: hsl(171deg, 100%, 41%);
}
.progress.is-primary:indeterminate {
background-image: linear-gradient(to right, hsl(171deg, 100%, 41%) 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-link::-webkit-progress-value {
background-color: hsl(229deg, 53%, 53%);
}
.progress.is-link::-moz-progress-bar {
background-color: hsl(229deg, 53%, 53%);
}
.progress.is-link::-ms-fill {
background-color: hsl(229deg, 53%, 53%);
}
.progress.is-link:indeterminate {
background-image: linear-gradient(to right, hsl(229deg, 53%, 53%) 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-info::-webkit-progress-value {
background-color: hsl(207deg, 61%, 53%);
}
.progress.is-info::-moz-progress-bar {
background-color: hsl(207deg, 61%, 53%);
}
.progress.is-info::-ms-fill {
background-color: hsl(207deg, 61%, 53%);
}
.progress.is-info:indeterminate {
background-image: linear-gradient(to right, hsl(207deg, 61%, 53%) 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-success::-webkit-progress-value {
background-color: hsl(153deg, 53%, 53%);
}
.progress.is-success::-moz-progress-bar {
background-color: hsl(153deg, 53%, 53%);
}
.progress.is-success::-ms-fill {
background-color: hsl(153deg, 53%, 53%);
}
.progress.is-success:indeterminate {
background-image: linear-gradient(to right, hsl(153deg, 53%, 53%) 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-warning::-webkit-progress-value {
background-color: hsl(44deg, 100%, 77%);
}
.progress.is-warning::-moz-progress-bar {
background-color: hsl(44deg, 100%, 77%);
}
.progress.is-warning::-ms-fill {
background-color: hsl(44deg, 100%, 77%);
}
.progress.is-warning:indeterminate {
background-image: linear-gradient(to right, hsl(44deg, 100%, 77%) 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress.is-danger::-webkit-progress-value {
background-color: hsl(348deg, 86%, 61%);
}
.progress.is-danger::-moz-progress-bar {
background-color: hsl(348deg, 86%, 61%);
}
.progress.is-danger::-ms-fill {
background-color: hsl(348deg, 86%, 61%);
}
.progress.is-danger:indeterminate {
background-image: linear-gradient(to right, hsl(348deg, 86%, 61%) 30%, hsl(0deg, 0%, 93%) 30%);
}
.progress:indeterminate {
animation-duration: 1.5s;
animation-iteration-count: infinite;
animation-name: moveIndeterminate;
animation-timing-function: linear;
background-color: hsl(0deg, 0%, 93%);
background-image: linear-gradient(to right, hsl(0deg, 0%, 29%) 30%, hsl(0deg, 0%, 93%) 30%);
background-position: top left;
background-repeat: no-repeat;
background-size: 150% 150%;
}
.progress:indeterminate::-webkit-progress-bar {
background-color: transparent;
}
.progress:indeterminate::-moz-progress-bar {
background-color: transparent;
}
.progress:indeterminate::-ms-fill {
animation-name: none;
}
.progress.is-small {
height: 0.75rem;
}
.progress.is-medium {
height: 1.25rem;
}
.progress.is-large {
height: 1.5rem;
}
@keyframes moveIndeterminate {
from {
background-position: 200% 0;
}
to {
background-position: -200% 0;
}
}
.table { .table {
background-color: #fff; background-color: #fff;
color: hsl(0deg, 0%, 21%); color: hsl(0deg, 0%, 21%);
@ -9567,76 +9385,27 @@ a.navbar-item.is-active {
font-weight: 100; font-weight: 100;
} }
.player {
z-index: 10000;
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
}
.player .player-panels {
height: 0%;
transition: height 3s;
}
.player .player-panels.is-open {
height: auto;
}
.player .player-panel {
margin: 0.4em;
max-height: 80%;
overflow-y: auto;
}
.player .progress {
margin: 0em;
padding: 0em;
border-color: hsl(207deg, 61%, 53%);
border-style: "solid";
}
.player .player-bar {
border-top: 1px hsl(0deg, 0%, 71%) solid;
}
.player .player-bar > div {
height: 3.75em !important;
}
.player .player-bar > .media-left:not(:last-child) {
margin-right: 0em;
}
.player .player-bar > .media-cover {
border-left: 1px black solid;
}
.player .player-bar .cover {
font-size: 1.5rem !important;
height: 2.5em !important;
}
.player .player-bar > .media-content {
padding-top: 0.4em;
padding-left: 0.4em;
}
.player .player-bar .button {
font-size: 1.5rem !important;
height: 100%;
padding: auto 0.2em !important;
min-width: 2.5em;
border-radius: 0px;
transition: background-color 1s;
}
.player .player-bar .title {
margin: 0em;
}
:root { :root {
--text-color: black; --text-color: black;
--highlight-color: rgba(255, 255, 0, 1); --highlight-color: rgba(255, 255, 0, 1);
--highlight-color-alpha: rgba(255, 255, 0, 0.6); --highlight-color-alpha: rgba(255, 255, 0, 0.7);
--highlight-color-2: rgb(0, 0, 254); --highlight-color-2: rgb(0, 0, 254);
--highlight-color-2-alpha: rgb(0, 0, 254, 0.6); --highlight-color-2-alpha: rgb(0, 0, 254, 0.7);
--header-height: 30em; --header-height: 30em;
--heading-height: 30em; --heading-height: 30em;
--heading-title-bg-color: rgba(255, 255, 0, 1); --heading-title-bg-color: rgba(255, 255, 0, 1);
--heading-bg-color: var(--highlight-color); --heading-bg-color: var(--highlight-color);
--heading-bg-highlight-color: var(--highlight-color-2); --heading-bg-highlight-color: var(--highlight-color-2);
--heading-font-family: default;
--preview-media-height: 10em; --preview-media-height: 10em;
--preview-media-cover-size: 10em; --preview-media-cover-size: 10em;
--preview-cover-size: 24em; --preview-cover-size: 24em;
--preview-cover-small-size: 10em; --preview-cover-small-size: 10em;
--heading-font-family: default; --player-panel-bg: var(--highlight-color-alpha);
--player-bar-bg: var(--highlight-color);
--progress-border: 1px var(--highlight-color-2) solid;
--progress-bg-color: transparent;
--progress-bar-color: var(--highlight-color-2);
} }
body { body {
@ -9649,35 +9418,6 @@ section > .toolbar {
margin-bottom: 1.5em; margin-bottom: 1.5em;
} }
aside > section {
margin-bottom: 2em;
}
aside .cover.is-small {
width: 10em;
}
aside .cover.is-tiny {
height: 2em;
}
aside .media .subtitle {
font-size: 1em;
}
.sound-item {
margin-bottom: 0.2em;
}
.sound-item .cover {
height: 5em;
}
.sound-item .media-content a {
padding: 0em;
}
.sound-item .media-right .button {
margin-right: 0.2em;
min-width: 2.5em;
display: inline-block;
}
h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle { h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
font-family: var(--heading-font-family); font-family: var(--heading-font-family);
} }
@ -9689,6 +9429,7 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
background-color: var(--highlight-color-alpha); background-color: var(--highlight-color-alpha);
color: var(--highlight-color-2); color: var(--highlight-color-2);
text-decoration: none; text-decoration: none;
padding: 0.4em;
} }
.page .content { .page .content {
font-size: 2em; font-size: 2em;
@ -9700,10 +9441,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
margin-top: unset; margin-top: unset;
padding-top: unset !important; padding-top: unset !important;
margin-bottom: 1.2em; margin-bottom: 1.2em;
border-bottom: 1px solid black;
} }
.page section.container:not(:first-child) { .page section.container:not(:first-child) {
margin-top: 2em; margin-top: 2em;
border-top: 1px solid black;
} }
.d-inline { .d-inline {
@ -9755,6 +9496,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
flex-grow: 0 !important; flex-grow: 0 !important;
} }
.no-border {
border: 0px !important;
}
.is-clickable { .is-clickable {
cursor: pointer; cursor: pointer;
} }
@ -9765,24 +9510,49 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
border-radius: 0.2em; border-radius: 0.2em;
} }
.button.action { .button {
border-radius: 0px;
border-color: var(--highlight-color-2-alpha);
}
.button:hover {
border-color: var(--highlight-color-2) !important;
color: var(--highlight-color-2) !important;
opacity: 1 !important;
}
.actions .button, .button.action {
background-color: var(--highlight-color); background-color: var(--highlight-color);
justify-content: center; justify-content: center;
padding: 0.4em !important; padding: 0.4em !important;
min-width: 2em; min-width: 2em;
} }
.button.action .icon { .actions .button .icon, .button.action .icon {
margin: 0em !important; margin: 0em !important;
} }
.button.action label { .actions .button .not-selected, .button.action .not-selected {
opacity: 0.6;
}
.actions .button label, .button.action label {
margin-left: 0.4em; margin-left: 0.4em;
} }
.actions .button:hover, .actions .button .selected, .button.action:hover, .button.action .selected {
color: var(--highlight-color-2) !important;
}
.button .dropdown-trigger { .button .dropdown-trigger {
border-radius: 1.5em; border-radius: 1.5em;
} }
.list-filters { .button-group .button {
text-align: right; background-color: transparent;
border-top: 0px;
border-bottom: 0px;
height: 100%;
}
.button-group .button:last-child {
border-right: 0px;
}
.actions.no-label label {
display: none;
} }
.title { .title {
@ -9809,10 +9579,6 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
background-color: var(--heading-title-bg-color); background-color: var(--heading-title-bg-color);
} }
.actions.no-label label {
display: none;
}
.dropdown-item { .dropdown-item {
font-size: unset !important; font-size: unset !important;
} }
@ -9875,7 +9641,7 @@ nav li a, nav li .button {
.preview { .preview {
position: relative; position: relative;
background-size: cover; background-size: cover;
margin-bottom: 1.2em !important; margin-bottom: 2em !important;
} }
.preview.preview-card:not(.wide) { .preview.preview-card:not(.wide) {
max-width: 30em; max-width: 30em;
@ -10069,3 +9835,137 @@ preview-header:not(.no-cover) .preview-card-headings .heading {
display: none; display: none;
} }
} }
.a-progress {
display: flex;
flex-direction: row;
margin: 0em;
padding: 0em;
background-color: var(--progress-bg-color);
}
.a-progress .a-progress-bar-container {
flex-grow: 1;
margin: 0em 0.8em;
border: var(--progress-border);
}
.a-progress .a-progress-bar {
background-color: var(--progress-bar-color);
}
.playlist .header, .a-playlist .header {
display: flex;
flex-direction: row;
}
.playlist .title, .a-playlist .title {
font-size: 1em;
margin: 0;
padding: 0.8em;
}
.playlist li, .a-playlist li {
list-style: none;
border-bottom: 1px var(--highlight-color-2) solid;
}
.playlist li:last-child, .a-playlist li:last-child {
border-bottom: 0px;
}
.a-sound-item {
display: flex;
align-items: center;
flex-direction: row;
height: 3em;
background-color: var(--highlight-color-alpha);
}
.a-sound-item.playing, .a-sound-item.playing .title {
color: var(--highlight-color-2) !important;
}
.a-sound-item:hover {
background-color: var(--highlight-color);
}
.a-sound-item:hover .title {
color: var(--highlight-color-2) !important;
}
.a-sound-item .title {
margin: 0em;
padding: 0em;
font-size: 1.4em;
}
.a-sound-item .title .icon {
padding: 0em 0.8em;
}
.a-sound-item .button {
width: 3em;
}
.a-player {
z-index: 10000;
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
}
.a-player a {
color: var(--highlight-color-2);
}
.a-player-panels {
background: var(--player-panel-bg);
height: 0%;
transition: height 1s;
}
.a-player-panels.is-open {
height: auto;
}
.a-player-panel {
padding-bottom: 0.8em;
max-height: 80%;
overflow-y: auto;
}
.a-player-panel .a-sound-item:not(:hover) {
background-color: transparent;
}
.a-player-bar {
display: flex;
background: var(--player-bar-bg);
flex-direction: row;
justify-content: center;
border-top: 1px hsl(0deg, 0%, 71%) solid;
height: 3.75em !important;
}
.a-player-bar > * {
height: 100%;
}
.a-player-bar .cover {
height: 100%;
}
.a-player-bar .title {
font-size: 1em;
margin: 0em;
}
.a-player-bar .title:last-child {
font-size: 1.6em;
}
.a-player-bar .button {
font-size: 1.6em;
height: 100%;
padding: auto 0.2em !important;
min-width: 2.5em;
border-radius: 0px;
transition: background-color 0.5s;
}
.a-player-bar .button:hover {
color: var(--highlight-color-2) !important;
}
.a-player-bar .button.active {
color: var(--highlight-color-2);
}
.a-player-bar .button.open {
background-color: var(--highlight-color-2);
color: var(--highlight-color);
}
.a-player-bar-content {
flex-grow: 1;
padding-top: 0.8em;
padding-left: 0.8em;
padding-right: 0.8em;
}

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@
{% with parent.title as title %} {% with parent.title as title %}
{% with model|default:"Publications"|verbose_name:True|capfirst as model %} {% with model|default:"Publications"|verbose_name:True|capfirst as model %}
{% comment %}Translators: title when pages are filtered for a specific parent page, e.g.: Articles of My Incredible Show{% endcomment %} {% comment %}Translators: title when pages are filtered for a specific parent page, e.g.: Articles of My Incredible Show{% endcomment %}
{% blocktranslate %}{{ model }} of {{ title }}{% endblocktranslate %} {% blocktranslate %}All {{ model }}{% endblocktranslate %}
{% endwith %} {% endwith %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}

View File

@ -8,13 +8,27 @@
{% endwith %} {% endwith %}
{% endblock %} {% endblock %}
{% block content %} {% block content-container %}
{{ block.container }}
<a-episode :page="{title: &quot;{{ page.title }}&quot;, podcasts: {{ object.podcasts|json }}}"> <a-episode :page="{title: &quot;{{ page.title }}&quot;, podcasts: {{ object.podcasts|json }}}">
<template v-slot="{podcasts,page}"> <template v-slot="{podcasts,page}">
{{ block.super }} {{ block.super }}
{% if object.podcasts %}
<section class="container no-border">
<h3 class="title is-3">{% translate "Podcasts" %}</h3>
<a-playlist v-if="page" :set="podcasts"
name="{{ page.title }}"
list-class="menu-list" item-class="menu-item"
:player="player" :actions="['play']"
@select="player.playItems('queue', $event.item)">
</a-playlist>
</section>
{% endif %}
{% if tracks %} {% if tracks %}
<section> <section class="container">
<h3 class="title is-3">{% translate "Playlist" %}</h3> <h3 class="title is-3">{% translate "Playlist" %}</h3>
<table class="table is-hoverable is-fullwidth"> <table class="table is-hoverable is-fullwidth">
<tbody> <tbody>
@ -28,24 +42,10 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<ol>
</ol>
</section> </section>
{% endif %} {% endif %}
{% if object.podcasts %}
<section>
<a-playlist v-if="page" :set="podcasts"
name="{{ page.title }}"
list-class="menu-list" item-class="menu-item"
:player="player" :actions="['play']"
@select="player.playItems('queue', $event.item)">
<template v-slot:header>
<h3 class="title is-3">{% translate "Podcasts" %}</h3>
</template>
</a-playlist>
</section>
{% endif %}
</template> </template>
</a-episode> </a-episode>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,7 @@
{{ block.super }} {{ block.super }}
{% if episodes %} {% if episodes %}
<section class="container"> <section class="container">
<h4 class="title is-3">{% translate "Last Episodes" %}</h4> <h3 class="title is-3">{% translate "Last Episodes" %}</h3>
<section class="card-grid"> <section class="card-grid">
{% for object in episodes|slice:":3" %} {% for object in episodes|slice:":3" %}
{% page_widget "card" object %} {% page_widget "card" object %}
@ -18,8 +18,7 @@
<nav class="has-text-right"> <nav class="has-text-right">
<li class="nav-item"> <li class="nav-item">
<a href="{% url "episode-list" parent_slug=program.slug %}" <a href="{% url "episode-list" parent_slug=program.slug %}">
class="button action">
{% translate "All episodes" %} {% translate "All episodes" %}
</a> </a>
</li> </li>
@ -30,7 +29,7 @@
{% if articles %} {% if articles %}
<section class="container"> <section class="container">
<h4 class="title is-4">{% translate "Last Articles" %}</h4> <h3 class="title is-3">{% translate "Last Articles" %}</h3>
<section class="card-grid"> <section class="card-grid">
{% for object in articles|slice:3 %} {% for object in articles|slice:3 %}
@ -41,7 +40,6 @@
<nav class="has-text-right"> <nav class="has-text-right">
<li class="nav-item"> <li class="nav-item">
<a href="{% url "article-list" parent_slug=program.slug %}" <a href="{% url "article-list" parent_slug=program.slug %}"
class="button action"
aria-label="{% translate "Show all program's articles" %}"> aria-label="{% translate "Show all program's articles" %}">
{% translate "All articles" %} {% translate "All articles" %}
</a> </a>

View File

@ -5,7 +5,7 @@ The audio player
<br> <br>
<div class="box is-fullwidth is-fixed-bottom is-paddingless player" <div class="is-fullwidth is-fixed-bottom is-paddingless player-container"
role="{% translate "player" %}" role="{% translate "player" %}"
aria-description="{% translate "Audio player used to listen to the radio and podcasts" %}"> aria-description="{% translate "Audio player used to listen to the radio and podcasts" %}">
<noscript> <noscript>
@ -22,11 +22,11 @@ The audio player
:live-args="{% player_live_attr %}" :live-args="{% player_live_attr %}"
button-title="{% translate "Play or pause audio" %}"> button-title="{% translate "Play or pause audio" %}">
<template v-slot:content="{ loaded, live, current }"> <template v-slot:content="{ loaded, live, current }">
<h4 v-if="loaded" class="title is-4"> <h4 v-if="loaded" class="title">
[[ loaded.name ]] [[ loaded.name ]]
</h4> </h4>
<h4 v-else-if="current && current.data.type == 'track'" <h4 v-else-if="current && current.data.type == 'track'"
class="title is-4" aria-description="{% translate "Track currently on air" %}"> class="title" aria-description="{% translate "Track currently on air" %}">
<span class="has-text-info is-size-3">&#9836;</span> <span class="has-text-info is-size-3">&#9836;</span>
<span>[[ current.data.title ]]</span> <span>[[ current.data.title ]]</span>
<span class="has-text-grey-dark has-text-weight-light"> <span class="has-text-grey-dark has-text-weight-light">
@ -34,12 +34,11 @@ The audio player
<i v-if="current.data.info">([[ current.data.info ]])</i> <i v-if="current.data.info">([[ current.data.info ]])</i>
</span> </span>
</h4> </h4>
<div v-else-if="live && current && current.data.type == 'diffusion'"> <h4 v-else-if="live && current && current.data.type == 'diffusion'"
<h4 class="title is-4" aria-description="{% translate "Diffusion currently on air" %}"> class="title"
aria-description="{% translate "Diffusion currently on air" %}">
<a :href="current.data.url">[[ current.data.title ]]</a> <a :href="current.data.url">[[ current.data.title ]]</a>
</h4> </h4>
<div class="">[[ current.data.info ]]</div>
</div>
<h4 v-else class="title is-4" aria-description="{% translate "Currently playing" %}"> <h4 v-else class="title is-4" aria-description="{% translate "Currently playing" %}">
{{ request.station.name }} {{ request.station.name }}
</h4> </h4>

View File

@ -40,10 +40,6 @@ class BasePageListView(AttachedToMixin, ParentMixin, BaseView, ListView):
if not context.get("page"): if not context.get("page"):
if not context.get("title"): if not context.get("title"):
model = self.model._meta.verbose_name_plural model = self.model._meta.verbose_name_plural
if parent:
parent = parent.display_title
title = _("{model} of {parent}")
else:
title = _("{model}") title = _("{model}")
context["title"] = title.format(model=model, parent=parent) context["title"] = title.format(model=model, parent=parent)

View File

@ -149,81 +149,13 @@ a.navbar-item.is-active {
} }
} }
//-- player
.player {
z-index: 10000;
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
.player-panels {
height: 0%;
transition: height 3s;
}
.player-panels.is-open {
height: auto;
}
.player-panel {
margin: 0.4em;
max-height: 80%;
overflow-y: auto;
}
.progress {
margin: 0em;
padding: 0em;
border-color: $info;
border-style: 'solid';
}
.player-bar {
border-top: 1px $grey-light solid;
> div {
height: 3.75em !important;
}
> .media-left:not(:last-child) {
margin-right: 0em;
}
> .media-cover {
border-left: 1px black solid;
}
.cover {
font-size: 1.5rem !important;
height: 2.5em !important;
}
> .media-content {
padding-top: 0.4em;
padding-left: 0.4em;
}
.button {
font-size: 1.5rem !important;
height: 100%;
padding: auto 0.2em !important;
min-width: 2.5em;
border-radius: 0px;
transition: background-color 1s;
}
.title {
margin: 0em;
}
}
}
//-- general //-- general
:root { :root {
--text-color: black; --text-color: black;
--highlight-color: rgba(255, 255, 0, 1); --highlight-color: rgba(255, 255, 0, 1);
--highlight-color-alpha: rgba(255, 255, 0, 0.6); --highlight-color-alpha: rgba(255, 255, 0, 0.7);
--highlight-color-2: rgb(0, 0, 254); --highlight-color-2: rgb(0, 0, 254);
--highlight-color-2-alpha: rgb(0, 0, 254, 0.6); --highlight-color-2-alpha: rgb(0, 0, 254, 0.7);
--header-height: 30em; --header-height: 30em;
@ -231,6 +163,7 @@ a.navbar-item.is-active {
--heading-title-bg-color: rgba(255, 255, 0, 1); --heading-title-bg-color: rgba(255, 255, 0, 1);
--heading-bg-color: var(--highlight-color); --heading-bg-color: var(--highlight-color);
--heading-bg-highlight-color: var(--highlight-color-2); --heading-bg-highlight-color: var(--highlight-color-2);
--heading-font-family: default;
--preview-media-height: 10em; --preview-media-height: 10em;
--preview-media-cover-size: 10em; --preview-media-cover-size: 10em;
@ -238,7 +171,11 @@ a.navbar-item.is-active {
--preview-cover-size: 24em; --preview-cover-size: 24em;
--preview-cover-small-size: 10em; --preview-cover-small-size: 10em;
--heading-font-family: default; --player-panel-bg: var(--highlight-color-alpha);
--player-bar-bg: var(--highlight-color);
--progress-border: 1px var(--highlight-color-2) solid;
--progress-bg-color: transparent;
--progress-bar-color: var(--highlight-color-2);
} }
@ -253,31 +190,6 @@ section > .toolbar {
} }
aside {
& > section {
margin-bottom: 2em;
}
.cover.is-small { width: 10em; }
.cover.is-tiny { height: 2em; }
.media .subtitle {
font-size: 1em;
}
}
.sound-item {
.cover { height: 5em; }
.media-content a { padding: 0em; }
margin-bottom: 0.2em;
}
.sound-item .media-right .button {
margin-right: 0.2em;
min-width: 2.5em;
display: inline-block;
}
// ---- main theme & layout // ---- main theme & layout
h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle { h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
font-family: var(--heading-font-family); font-family: var(--heading-font-family);
@ -290,6 +202,7 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
background-color: var(--highlight-color-alpha); background-color: var(--highlight-color-alpha);
color: var(--highlight-color-2); color: var(--highlight-color-2);
text-decoration: none; text-decoration: none;
padding: $mp-2;
} }
.content { .content {
@ -303,11 +216,11 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
margin-top: unset; margin-top: unset;
padding-top: unset !important; padding-top: unset !important;
margin-bottom: $mp-4; margin-bottom: $mp-4;
border-bottom: 1px solid black;
} }
&:not(:first-child) { &:not(:first-child) {
margin-top: $mp-6; margin-top: $mp-6;
border-top: 1px solid black;
} }
} }
} }
@ -330,6 +243,7 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
.flex-push-right { margin-left: auto; } .flex-push-right { margin-left: auto; }
.flex-grow-0 { flex-grow: 0 !important; } .flex-grow-0 { flex-grow: 0 !important; }
.no-border { border: 0px !important; }
.is-clickable { cursor: pointer; } .is-clickable { cursor: pointer; }
@ -342,17 +256,31 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
} }
.button { .button {
&.action { border-radius: 0px;
border-color: var(--highlight-color-2-alpha);
&:hover {
border-color: var(--highlight-color-2) !important;
color: var(--highlight-color-2) !important;
opacity: 1 !important;
}
.actions &, &.action {
background-color: var(--highlight-color); background-color: var(--highlight-color);
justify-content: center; justify-content: center;
padding: $mp-2 !important; padding: $mp-2 !important;
min-width: 2em; min-width: 2em;
.icon { margin: 0em !important; } .icon { margin: 0em !important; }
.not-selected { opacity: 0.6; }
label { label {
margin-left: $mp-2; margin-left: $mp-2;
} }
&:hover, .selected {
color: var(--highlight-color-2) !important;
}
} }
.dropdown-trigger { .dropdown-trigger {
@ -361,10 +289,26 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
} }
.list-filters { .button-group {
text-align: right; .button {
background-color: transparent;
border-top: 0px;
border-bottom: 0px;
height: 100%;
&:last-child {
border-right: 0px;
}
}
} }
.actions {
&.no-label label {
display: none;
}
}
.title { .title {
text-transform: uppercase; text-transform: uppercase;
@ -393,12 +337,6 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
} }
} }
.actions {
&.no-label label {
display: none;
}
}
.dropdown-item { .dropdown-item {
font-size: unset !important font-size: unset !important
} }
@ -479,7 +417,7 @@ nav li {
.preview { .preview {
position: relative; position: relative;
background-size: cover; background-size: cover;
margin-bottom: $mp-4 !important; margin-bottom: $mp-6 !important;
&.preview-card { &.preview-card {
&:not(.wide) { &:not(.wide) {
@ -720,3 +658,165 @@ nav li {
} }
} }
} }
// ---- player
// -- components
.a-progress {
display: flex;
flex-direction: row;
margin: 0em;
padding: 0em;
background-color: var(--progress-bg-color);
.a-progress-bar-container {
flex-grow: 1;
margin: 0em $mp-3;
border: var(--progress-border);
}
.a-progress-bar {
background-color: var(--progress-bar-color);
}
}
.playlist, .a-playlist {
.header {
display: flex;
flex-direction: row;
}
.title {
font-size: $text-size;
margin: 0;
padding: $mp-3;
}
li {
list-style: none;
border-bottom: 1px var(--highlight-color-2) solid;
&:last-child {
border-bottom: 0px;
}
}
}
// -- sound item
.a-sound-item {
display: flex;
align-items: center;
flex-direction: row;
height: 3em;
background-color: var(--highlight-color-alpha);
&.playing, &.playing .title {
color: var(--highlight-color-2) !important;
}
&:hover {
background-color: var(--highlight-color);
.title {
color: var(--highlight-color-2) !important;
}
}
.headings > * {
}
.title {
.icon {
padding: 0em $mp-3;
}
margin: 0em;
padding: 0em;
font-size: $text-size-medium;
}
.button {
width: 3em;
}
}
//-- player
.a-player {
z-index: 10000;
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
a { color: var(--highlight-color-2); }
}
.a-player-panels {
background: var(--player-panel-bg);
height: 0%;
transition: height 1s;
}
.a-player-panels.is-open {
height: auto;
}
.a-player-panel {
padding-bottom: $mp-3;
max-height: 80%;
overflow-y: auto;
.a-sound-item:not(:hover) {
background-color: transparent;
}
}
.a-player-bar {
display: flex;
background: var(--player-bar-bg);
flex-direction: row;
justify-content: center;
border-top: 1px $grey-light solid;
height: 3.75em !important;
> * { height: 100%; }
.cover { height: 100%; }
.title {
font-size: $text-size;
margin: 0em;
&:last-child {
font-size: $text-size-bigger;
}
}
.button {
font-size: $text-size-bigger;
height: 100%;
padding: auto 0.2em !important;
min-width: 2.5em;
border-radius: 0px;
transition: background-color 0.5s;
&:hover {
color: var(--highlight-color-2) !important;
}
&.active {
color: var(--highlight-color-2);
}
&.open {
background-color: var(--highlight-color-2);
color: var(--highlight-color);
}
}
}
.a-player-bar-content {
flex-grow: 1;
padding-top: $mp-3;
padding-left: $mp-3;
padding-right: $mp-3;
}

View File

@ -25,7 +25,7 @@ $menu-item-active-background-color: #d2d2d2;
@import "~bulma/sass/elements/icon"; @import "~bulma/sass/elements/icon";
// @import "~bulma/sass/elements/image"; // @import "~bulma/sass/elements/image";
// @import "~bulma/sass/elements/notification"; // @import "~bulma/sass/elements/notification";
@import "~bulma/sass/elements/progress"; // @import "~bulma/sass/elements/progress";
@import "~bulma/sass/elements/table"; @import "~bulma/sass/elements/table";
@import "~bulma/sass/elements/tag"; @import "~bulma/sass/elements/tag";
@import "~bulma/sass/elements/title"; @import "~bulma/sass/elements/title";

View File

@ -1,49 +1,57 @@
<template> <template>
<div class="player"> <div class="a-player">
<div :class="['player-panels', panel ? 'is-open' : '']"> <div :class="['a-player-panels', panel ? 'is-open' : '']">
<APlaylist ref="pin" class="player-panel menu" v-show="panel == 'pin' && sets.pin.length" <APlaylist ref="pin" class="a-player-panel a-playlist" v-show="panel == 'pin' && sets.pin.length"
name="Pinned" name="Pinned"
:actions="['page']" :actions="['page']"
:editable="true" :player="self" :set="sets.pin" @select="togglePlay('pin', $event.index)" :editable="true" :player="self" :set="sets.pin" @select="togglePlay('pin', $event.index)"
listClass="menu-list" itemClass="menu-item"> listClass="menu-list" itemClass="menu-item">
<template v-slot:header=""> <template v-slot:header="">
<p class="menu-label"> <div class="title is-flex-grow-1">
<span class="icon"><span class="fa fa-thumbtack"></span></span> <span class="icon"><span class="fa fa-star"></span></span>
Pinned Pinned
</p> </div>
<button class="action button no-border">
<span class="icon" @click.stop="togglePanel()">
<i class="fa fa-close"></i>
</span>
</button>
</template> </template>
</APlaylist> </APlaylist>
<APlaylist ref="queue" class="player-panel menu" v-show="panel == 'queue' && sets.queue.length" <APlaylist ref="queue" class="a-player-panel a-playlist" v-show="panel == 'queue' && sets.queue.length"
:actions="['page']" :actions="['page']"
:editable="true" :player="self" :set="sets.queue" @select="togglePlay('queue', $event.index)" :editable="true" :player="self" :set="sets.queue" @select="togglePlay('queue', $event.index)"
listClass="menu-list" itemClass="menu-item"> listClass="menu-list" itemClass="menu-item">
<template v-slot:header=""> <template v-slot:header="">
<p class="menu-label"> <div class="title is-flex-grow-1">
<span class="icon"><span class="fa fa-list"></span></span> <span class="icon"><span class="fa fa-list"></span></span>
Playlist Playlist
</p> </div>
<button class="action button no-border">
<span class="icon" @click.stop="togglePanel()">
<i class="fa fa-close"></i>
</span>
</button>
</template> </template>
</APlaylist> </APlaylist>
</div> </div>
<div class="player-bar media"> <div class="a-player-bar button-group">
<div class="media-left">
<button class="button" @click="togglePlay()" <button class="button" @click="togglePlay()"
:title="buttonTitle" :aria-label="buttonTitle"> :title="buttonTitle" :aria-label="buttonTitle">
<span class="fas fa-pause" v-if="playing"></span> <span class="fas fa-pause" v-if="playing"></span>
<span class="fas fa-play" v-else></span> <span class="fas fa-play" v-else></span>
</button> </button>
</div> <div class="media-cover" v-if="current && current.data.cover">
<div class="media-left media-cover" v-if="current && current.data.cover">
<img :src="current.data.cover" class="cover" /> <img :src="current.data.cover" class="cover" />
</div> </div>
<div class="media-content"> <div :class="['a-player-bar-content', loaded && duration ? 'has-progress' : '']">
<slot name="content" :loaded="loaded" :live="live" :current="current"></slot> <slot name="content" :loaded="loaded" :live="live" :current="current"></slot>
<AProgress v-if="loaded && duration" :value="currentTime" :max="this.duration" <AProgress v-if="loaded && duration" :value="currentTime" :max="this.duration"
:format="displayTime" class="pt-1 is-size-7" :format="displayTime" class="pt-1 is-size-7"
@select="audio.currentTime = $event"></AProgress> @select="audio.currentTime = $event"></AProgress>
</div> </div>
<div class="media-right"> <div>
<button class="button has-text-weight-bold" v-if="loaded" @click="play()"> <button class="button has-text-weight-bold" v-if="loaded" @click="play()">
<span class="icon is-size-6 has-text-danger"> <span class="icon is-size-6 has-text-danger">
<span class="fa fa-circle"></span> <span class="fa fa-circle"></span>
@ -54,7 +62,7 @@
@click="togglePanel('pin')" v-show="sets.pin.length"> @click="togglePanel('pin')" v-show="sets.pin.length">
<span class="is-size-6" v-if="sets.pin.length"> <span class="is-size-6" v-if="sets.pin.length">
{{ sets.pin.length }}</span> {{ sets.pin.length }}</span>
<span class="icon"><span class="fa fa-thumbtack"></span></span> <span class="icon"><span class="fa fa-star"></span></span>
</button> </button>
<button :class="playlistButtonClass('queue')" <button :class="playlistButtonClass('queue')"
@click="togglePanel('queue')" v-show="sets.queue.length"> @click="togglePanel('queue')" v-show="sets.queue.length">
@ -156,10 +164,9 @@ export default {
playlistButtonClass(name) { playlistButtonClass(name) {
let set = this.sets[name]; let set = this.sets[name];
return (set ? (set.length ? "" : "has-text-grey-light ") return (set ? (set.length ? "" : "has-text-grey-light ")
+ (this.panel == name ? "is-info " + (this.panel == name ? "open"
: this.playlistName == name ? 'is-primary ' : this.playlistName == name ? 'active' : '') : '')
: '') : '') + " button";
+ "button has-text-weight-bold";
}, },
/// Show/hide panel /// Show/hide panel

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="playlist"> <div class="a-playlist">
<slot name="header"></slot> <div class="header"><slot name="header"></slot></div>
<ul :class="listClass"> <ul :class="listClass">
<li v-for="(item,index) in items" :class="itemClass" @click="!hasAction('play') && select(index)" <li v-for="(item,index) in items" :class="itemClass" @click="!hasAction('play') && select(index)"
:key="index"> :key="index">
@ -9,7 +9,7 @@
:data="item" :index="index" :set="set" :player="player_" :data="item" :index="index" :set="set" :player="player_"
@togglePlay="togglePlay(index)" @togglePlay="togglePlay(index)"
:actions="actions"> :actions="actions">
<template v-slot:extra-right="{}"> <template v-slot:actions="{}">
<button class="button" v-if="editable" @click.stop="remove(index,true)"> <button class="button" v-if="editable" @click.stop="remove(index,true)">
<span class="icon is-small"><span class="fa fa-close"></span></span> <span class="icon is-small"><span class="fa fa-close"></span></span>
</button> </button>

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="media"> <div class="a-progress m-0">
<div class="media-left"> <time class="time-now">
<slot name="value" :value="valueDisplay" :max="max">{{ format(valueDisplay) }}</slot> <slot name="value" :value="valueDisplay" :max="max">{{ format(valueDisplay) }}</slot>
</div> </time>
<div ref="bar" class="media-content" @click.stop="onClick" @mouseleave.stop="onMouseMove" <div ref="bar" class="a-progress-bar-container" @click.stop="onClick" @mouseleave.stop="onMouseMove"
@mousemove.stop="onMouseMove"> @mousemove.stop="onMouseMove">
<div :class="progressClass" :style="progressStyle">&nbsp;</div> <div :class="progressClass" :style="progressStyle">&nbsp;</div>
</div> </div>
<div class="media-right"> <time class="time-total">
<slot name="value" :value="valueDisplay" :max="max">{{ format(max) }}</slot> <slot name="value" :value="valueDisplay" :max="max">{{ format(max) }}</slot>
</div> </time>
</div> </div>
</template> </template>
@ -25,7 +25,7 @@ export default {
value: Number, value: Number,
max: Number, max: Number,
format: { type: Function, default: x => x }, format: { type: Function, default: x => x },
progressClass: { default: 'has-background-primary' }, progressClass: { default: 'a-progress-bar' },
vertical: { type: Boolean, default: false }, vertical: { type: Boolean, default: false },
}, },

View File

@ -1,32 +1,23 @@
<template> <template>
<div class="media sound-item"> <div :class="['a-sound-item m-0 button-group', playing && 'playing' || '']">
<div class="media-left" @click.stop="$emit('togglePlay')"> <slot name="title" :player="player" :item="item" :loaded="loaded">
<img class="cover is-tiny" :src="item.data.cover" v-if="item.data.cover"> <span :class="['title is-flex-grow-1', playing && 'blink' || '']" @click.stop="$emit('togglePlay')">
</div> <span class="icon mr-3">
<div class="media-content"> <i class="fa fa-play"></i>
<slot name="content" :player="player" :item="item" :loaded="loaded">
<h4 class="title is-5" @click.stop="$emit('togglePlay')">
<span class="icon is-small is-size-7 blink" v-if="playing">
<span class="fa fa-play"></span>
</span> </span>
{{ name || item.name }} {{ name || item.name }}
</h4> </span>
<a class="subtitle is-6 is-inline-block" v-if="hasAction('page') && item.data.page_url"
:href="item.data.page_url">
{{ item.data.page_title }}
</a>
</slot> </slot>
</div> <div class="button-group actions">
<div class="media-right">
<a class="button" v-if="item.data.is_downloadable" <a class="button" v-if="item.data.is_downloadable"
:href="item.data.url" target="_blank"> :href="item.data.url" target="_blank">
<span class="icon is-small"> <span class="icon is-small">
<span class="fa fa-download"></span> <span class="fa fa-download"></span>
</span> </span>
</a> </a>
<button class="button" v-if="player && player.sets.pin != $parent.set" @click.stop="player.togglePin(item)"> <button :class="['button', pinned ? 'selected' : 'not-selected']" v-if="player && player.sets.pin != $parent.set" @click.stop="player.togglePin(item)">
<span class="icon is-small"> <span class="icon is-small">
<span :class="(pinned ? '' : 'has-text-grey-light ') + 'fa fa-thumbtack'"></span> <span class="fa fa-star"></span>
</span> </span>
</button> </button>
<slot name="actions" :player="player" :item="item" :loaded="loaded"></slot> <slot name="actions" :player="player" :item="item" :loaded="loaded"></slot>