Compare commits

...

31 Commits

Author SHA1 Message Date
bkfox
eb5bdcf167 work on page form; add image selector 2024-03-16 06:00:15 +01:00
bkfox
c74ec6fb16 Merge branch 'dev-1.0-118-design' into dev-1.0-121 2024-03-15 20:54:56 +01:00
bkfox
c79f040fa1 fix 2024-03-15 20:54:52 +01:00
bkfox
0ba0f8ae72 mobile device support 2024-02-12 23:28:59 +01:00
bkfox
afc2e41bdb Merge branch 'dev-1.0-118-design' of git.radiocampus.be:rc/aircox into dev-1.0-118-design 2024-02-12 14:41:19 +01:00
bkfox
bba4935791 grid and mobile 2024-02-12 14:41:09 +01:00
bkfox
dab4146735 box shadow 2024-02-12 14:40:43 +01:00
1aababe2ae docs: update user manual with simplified program management for animators 2024-02-06 09:57:21 +01:00
0dd961e0bb db: create program editors groups 2024-02-06 09:57:20 +01:00
f9da318a38 db: add missing migration on schedule timezone 2024-02-06 09:57:20 +01:00
26fa426416 views: avoid failing on missing parent cover 2024-02-06 09:40:45 +01:00
71f4d2473e episode-form: add tracks inline formset 2024-02-06 09:40:45 +01:00
2e9ebaded2 templatetags: display edit-links for admins 2024-02-06 09:40:45 +01:00
c6a4196319 templates: update after merging branch 118-design 2024-02-06 09:40:37 +01:00
be224d0efb templatetags: return on none type object 2024-02-05 10:29:58 +01:00
89f80ad103 templates: add in-context edition links 2024-02-05 10:29:58 +01:00
6d556fcd5d db: migrations merge 2024-02-05 10:29:55 +01:00
4201d50f4b templates: update container block names 2024-02-05 10:24:48 +01:00
6c942f36fa templatetags: avoid failing on nav_items when no station is defined 2024-02-05 10:24:48 +01:00
d51b9ee58b signals: disable schedule_pre_save when using loaddata 2024-02-05 10:24:48 +01:00
1a27ae2a76 misc: add in-site episode management for animators 2024-02-05 10:24:46 +01:00
e5862ee59b templates: set document type to html, prevent quicks mode 2024-02-05 10:22:16 +01:00
8f88b15536 ProgramUpdateView: use ckeditor RichTextField 2024-02-05 10:22:16 +01:00
10dfe3811b context_processors: prevent a null station error when no default station is defined 2024-02-05 10:22:16 +01:00
f71c201020 views/program: allow changing program cover 2024-02-05 10:22:16 +01:00
0812f3a0a1 misc: add a profile view for authenticated users 2024-02-05 10:22:14 +01:00
ad2ed17c34 misc: use the django authentication system 2024-02-05 10:19:05 +01:00
9db69580e0 misc: move station and audio_streams to context_processors (in order to have them available in accounts views) 2024-02-05 10:19:05 +01:00
4ead6b154b misc: edit programs in site 2024-02-05 10:19:05 +01:00
811cc97e07 templatetags: parametrize has_perm() in order to enable aircox namespace permissions 2024-02-05 10:19:05 +01:00
b794e24d0c models/program: link to editor groups 2024-02-05 10:19:05 +01:00
40 changed files with 792 additions and 713 deletions

View File

@ -1,14 +1,23 @@
import django_filters as filters import django_filters as filters
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .models import Episode, Page from . import models
__all__ = (
"PageFilters",
"EpisodeFilters",
"ImageFilterSet",
"SoundFilterSet",
"TrackFilterSet",
)
class PageFilters(filters.FilterSet): class PageFilters(filters.FilterSet):
q = filters.CharFilter(method="search_filter", label=_("Search")) q = filters.CharFilter(method="search_filter", label=_("Search"))
class Meta: class Meta:
model = Page model = models.Page
fields = { fields = {
"category__id": ["in", "exact"], "category__id": ["in", "exact"],
"pub_date": ["exact", "gte", "lte"], "pub_date": ["exact", "gte", "lte"],
@ -22,10 +31,33 @@ class EpisodeFilters(PageFilters):
podcast = filters.BooleanFilter(method="podcast_filter", label=_("Podcast")) podcast = filters.BooleanFilter(method="podcast_filter", label=_("Podcast"))
class Meta: class Meta:
model = Episode model = models.Episode
fields = PageFilters.Meta.fields.copy() fields = PageFilters.Meta.fields.copy()
def podcast_filter(self, queryset, name, value): def podcast_filter(self, queryset, name, value):
if value: if value:
return queryset.filter(sound__is_public=True).distinct() return queryset.filter(sound__is_public=True).distinct()
return queryset.filter(sound__isnull=True) return queryset.filter(sound__isnull=True)
class ImageFilterSet(filters.FilterSet):
search = filters.CharFilter(field_name="search", method="search_filter")
def search_filter(self, queryset, name, value):
return queryset.filter(original_filename__icontains=value)
class SoundFilterSet(filters.FilterSet):
station = filters.NumberFilter(field_name="program__station__id")
program = filters.NumberFilter(field_name="program_id")
episode = filters.NumberFilter(field_name="episode_id")
search = filters.CharFilter(field_name="search", method="search_filter")
def search_filter(self, queryset, name, value):
return queryset.search(value)
class TrackFilterSet(filters.FilterSet):
artist = filters.CharFilter(field_name="artist", lookup_expr="icontains")
album = filters.CharFilter(field_name="album", lookup_expr="icontains")
title = filters.CharFilter(field_name="title", lookup_expr="icontains")

View File

@ -1,15 +1,15 @@
from django import forms from django import forms
from django.forms import ModelForm, ImageField, FileField
from ckeditor.fields import RichTextField
from filer.models.imagemodels import Image
from filer.models.filemodels import File from filer.models.filemodels import File
from aircox.models import Comment, Episode, Program from aircox import models
from aircox.controllers.sound_file import SoundFile from aircox.controllers.sound_file import SoundFile
class CommentForm(ModelForm): __all__ = ("CommentForm", "PageForm", "ProgramForm", "EpisodeForm")
class CommentForm(forms.ModelForm):
nickname = forms.CharField() nickname = forms.CharField()
email = forms.EmailField(required=False) email = forms.EmailField(required=False)
content = forms.CharField(widget=forms.Textarea()) content = forms.CharField(widget=forms.Textarea())
@ -19,32 +19,31 @@ class CommentForm(ModelForm):
content.widget.attrs.update({"class": "textarea"}) content.widget.attrs.update({"class": "textarea"})
class Meta: class Meta:
model = Comment model = models.Comment
fields = ["nickname", "email", "content"] fields = ["nickname", "email", "content"]
class ProgramForm(ModelForm): class ImageForm(forms.Form):
content = RichTextField() file = forms.ImageField()
new_cover = ImageField(required=False)
class PageForm(forms.ModelForm):
class Meta:
fields = ("title", "status", "cover", "content")
model = models.Page
class ProgramForm(PageForm):
class Meta:
fields = PageForm.Meta.fields
model = models.Program
class EpisodeForm(PageForm):
new_podcast = forms.FileField(required=False)
class Meta: class Meta:
model = Program model = models.Episode
fields = ["content"]
def save(self, commit=True):
file_obj = self.cleaned_data["new_cover"]
if file_obj:
obj, _ = Image.objects.get_or_create(original_filename=file_obj.name, file=file_obj)
self.instance.cover = obj
super().save(commit=commit)
class EpisodeForm(ModelForm):
content = RichTextField()
new_podcast = FileField(required=False)
class Meta:
model = Episode
fields = ["content"] fields = ["content"]
def save(self, commit=True): def save(self, commit=True):

View File

@ -13,6 +13,7 @@ class AircoxMiddleware(object):
"""Middleware used to get default info for the given website. """Middleware used to get default info for the given website.
It provide following request attributes: It provide following request attributes:
- ``mobile``: set to True if mobile device is detected
- ``station``: current Station - ``station``: current Station
This middleware must be set after the middleware This middleware must be set after the middleware
@ -24,6 +25,11 @@ class AircoxMiddleware(object):
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
def is_mobile(self, request):
if agent := request.META.get("HTTP_USER_AGENT"):
return " Mobi" in agent
return False
def get_station(self, request): def get_station(self, request):
"""Return station for the provided request.""" """Return station for the provided request."""
host = request.get_host() host = request.get_host()
@ -45,6 +51,7 @@ class AircoxMiddleware(object):
def __call__(self, request): def __call__(self, request):
self.init_timezone(request) self.init_timezone(request)
request.station = self.get_station(request) request.station = self.get_station(request)
request.is_mobile = self.is_mobile(request)
try: try:
return self.get_response(request) return self.get_response(request)
except Redirect: except Redirect:

View File

@ -127,9 +127,9 @@ class Program(Page):
self.editors = editors self.editors = editors
super().save() super().save()
permission, _ = Permission.objects.get_or_create( permission, _ = Permission.objects.get_or_create(
name=f"change program {self.title}",
codename=self.change_permission_codename, codename=self.change_permission_codename,
content_type=ContentType.objects.get_for_model(self), content_type=ContentType.objects.get_for_model(self),
defaults={"name": f"change program {self.title}"},
) )
if permission not in editors.permissions.all(): if permission not in editors.permissions.all():
editors.permissions.add(permission) editors.permissions.add(permission)

View File

@ -81,6 +81,9 @@ class Station(models.Model):
max_length=64, max_length=64,
default=_("Music stream"), default=_("Music stream"),
) )
legal_label = models.CharField(
_("Legal label"), max_length=64, blank=True, default="", help_text=_("Displayed at the bottom of pages.")
)
objects = StationQuerySet.as_manager() objects = StationQuerySet.as_manager()

View File

@ -1,9 +1,17 @@
from rest_framework import serializers from rest_framework import serializers
from filer.models.imagemodels import Image
from taggit.serializers import TaggitSerializer, TagListSerializerField from taggit.serializers import TaggitSerializer, TagListSerializerField
from ..models import Track, UserSettings from ..models import Track, UserSettings
__all__ = ("TrackSerializer", "UserSettingsSerializer") __all__ = ("ImageSerializer", "TrackSerializer", "UserSettingsSerializer")
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = "__all__"
class TrackSerializer(TaggitSerializer, serializers.ModelSerializer): class TrackSerializer(TaggitSerializer, serializers.ModelSerializer):

View File

@ -27,8 +27,8 @@
--preview-cover-size: 14rem; --preview-cover-size: 14rem;
--preview-cover-small-size: 10rem; --preview-cover-small-size: 10rem;
--preview-cover-tiny-size: 4rem; --preview-cover-tiny-size: 4rem;
--preview-wide-content-sz: 1.6rem; --preview-wide-content-sz: 1.2rem;
--preview-heading-bg-color: var(--hg-color); --preview-heading-bg-color: var(--main-color);
--header-height: var(--cover-h); --header-height: var(--cover-h);
--a-carousel-p: 1.4rem; --a-carousel-p: 1.4rem;
--a-carousel-ml: calc(1.2rem - 0.5rem); --a-carousel-ml: calc(1.2rem - 0.5rem);
@ -76,9 +76,6 @@
--cover-tiny-w: 4rem; --cover-tiny-w: 4rem;
--cover-tiny-h: 4rem; --cover-tiny-h: 4rem;
--section-content-sz: 1rem; --section-content-sz: 1rem;
--preview-title-sz: 1rem;
--preview-subtitle-sz: 0.8rem;
--preview-wide-content-sz: 1rem;
} }
} }
.title.is-1, .header.preview .title.is-1 { .title.is-1, .header.preview .title.is-1 {
@ -124,6 +121,10 @@
color: var(--heading-hg-fg); color: var(--heading-hg-fg);
} }
.panels .panel:not(.active) {
display: none;
}
.preview { .preview {
position: relative; position: relative;
background-size: cover; background-size: cover;
@ -288,7 +289,7 @@
transition: box-shadow 0.2s; transition: box-shadow 0.2s;
} }
.preview-card:hover figure { .preview-card:hover figure {
box-shadow: 0em 0em 1.2em rgba(0, 0, 0, 0.4) !important; box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
} }
.preview-card:hover a { .preview-card:hover a {
color: var(--heading-link-hv-fg); color: var(--heading-link-hv-fg);
@ -299,9 +300,6 @@
.preview-card .headings .heading { .preview-card .headings .heading {
display: block !important; display: block !important;
} }
.preview-card .headings .title {
overflow: hidden;
}
.preview-card .headings .subtitle { .preview-card .headings .subtitle {
font-size: 1.2rem; font-size: 1.2rem;
} }
@ -310,7 +308,6 @@
position: relative; position: relative;
} }
.preview-card .card-content figure { .preview-card .card-content figure {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
height: var(--cover-h); height: var(--cover-h);
width: var(--cover-w); width: var(--cover-w);
} }
@ -321,18 +318,6 @@
right: 0rem; right: 0rem;
} }
.list-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-flow: dense;
gap: 1.2rem;
}
@media screen and (max-width: 900px) {
.list-grid {
grid-template-columns: 1fr;
}
}
.a-carousel .a-carousel-viewport { .a-carousel .a-carousel-viewport {
box-shadow: inset 0em 0em 20rem var(--a-carousel-bg); box-shadow: inset 0em 0em 20rem var(--a-carousel-bg);
padding: 0rem; padding: 0rem;
@ -544,6 +529,34 @@
overflow: hidden; overflow: hidden;
} }
.a-select-file > *:not(:last-child) {
margin-bottom: 0.6rem;
}
.a-select-file .upload-preview {
max-width: 100%;
}
.a-select-file .a-select-file-list {
max-height: 30rem;
overflow-y: auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 0.6rem;
}
.a-select-file .file-preview {
width: 100%;
overflow: hidden;
}
.a-select-file .file-preview:hover {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
}
.a-select-file .file-preview.active {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.4);
}
.a-select-file .file-preview img {
width: 100%;
max-height: 10rem;
}
/* Bulma Utilities */ /* Bulma Utilities */
.button { .button {
-moz-appearance: none; -moz-appearance: none;
@ -2795,9 +2808,9 @@ a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-i
cursor: pointer; cursor: pointer;
} }
#player .button[disabled], #player .button.disabled, #player a.button[disabled], #player a.button.disabled, #player button.button[disabled], #player button.button.disabled, .ax .button[disabled], .ax .button.disabled, .ax a.button[disabled], .ax a.button.disabled, .ax button.button[disabled], .ax button.button.disabled { #player .button[disabled], #player .button.disabled, #player a.button[disabled], #player a.button.disabled, #player button.button[disabled], #player button.button.disabled, .ax .button[disabled], .ax .button.disabled, .ax a.button[disabled], .ax a.button.disabled, .ax button.button[disabled], .ax button.button.disabled {
background-color: var(--hg-color-grey); background-color: var(--text-color-light);
color: var(--hg-color-2); color: var(--secondary-color);
border-color: var(--hg-color-2-alpha); border-color: var(--secondary-color-light);
} }
#player .button .dropdown-trigger, #player a.button .dropdown-trigger, #player button.button .dropdown-trigger, .ax .button .dropdown-trigger, .ax a.button .dropdown-trigger, .ax button.button .dropdown-trigger { #player .button .dropdown-trigger, #player a.button .dropdown-trigger, #player button.button .dropdown-trigger, .ax .button .dropdown-trigger, .ax a.button .dropdown-trigger, .ax button.button .dropdown-trigger {
border-radius: 1.5em; border-radius: 1.5em;

View File

@ -98,7 +98,7 @@ fieldset[disabled] .file-name, fieldset[disabled] .select select, .select fields
width: 0.625em; width: 0.625em;
} }
.table-container:not(:last-child), .table:not(:last-child), .content:not(:last-child), .box:not(:last-child), .message:not(:last-child) { .table-container:not(:last-child), .table:not(:last-child), .box:not(:last-child), .message:not(:last-child) {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@ -6238,175 +6238,6 @@ a.box:active {
} }
} }
.content li + li {
margin-top: 0.25em;
}
.content p:not(:last-child),
.content dl:not(:last-child),
.content ol:not(:last-child),
.content ul:not(:last-child),
.content blockquote:not(:last-child),
.content pre:not(:last-child),
.content table:not(:last-child) {
margin-bottom: 1em;
}
.content h1,
.content h2,
.content h3,
.content h4,
.content h5,
.content h6 {
color: hsl(0deg, 0%, 21%);
font-weight: 600;
line-height: 1.125;
}
.content h1 {
font-size: 2em;
margin-bottom: 0.5em;
}
.content h1:not(:first-child) {
margin-top: 1em;
}
.content h2 {
font-size: 1.75em;
margin-bottom: 0.5714em;
}
.content h2:not(:first-child) {
margin-top: 1.1428em;
}
.content h3 {
font-size: 1.5em;
margin-bottom: 0.6666em;
}
.content h3:not(:first-child) {
margin-top: 1.3333em;
}
.content h4 {
font-size: 1.25em;
margin-bottom: 0.8em;
}
.content h5 {
font-size: 1.125em;
margin-bottom: 0.8888em;
}
.content h6 {
font-size: 1em;
margin-bottom: 1em;
}
.content blockquote {
background-color: hsl(0deg, 0%, 96%);
border-left: 5px solid hsl(0deg, 0%, 86%);
padding: 1.25em 1.5em;
}
.content ol {
list-style-position: outside;
margin-left: 2em;
margin-top: 1em;
}
.content ol:not([type]) {
list-style-type: decimal;
}
.content ol:not([type]).is-lower-alpha {
list-style-type: lower-alpha;
}
.content ol:not([type]).is-lower-roman {
list-style-type: lower-roman;
}
.content ol:not([type]).is-upper-alpha {
list-style-type: upper-alpha;
}
.content ol:not([type]).is-upper-roman {
list-style-type: upper-roman;
}
.content ul {
list-style: disc outside;
margin-left: 2em;
margin-top: 1em;
}
.content ul ul {
list-style-type: circle;
margin-top: 0.5em;
}
.content ul ul ul {
list-style-type: square;
}
.content dd {
margin-left: 2em;
}
.content figure {
margin-left: 2em;
margin-right: 2em;
text-align: center;
}
.content figure:not(:first-child) {
margin-top: 2em;
}
.content figure:not(:last-child) {
margin-bottom: 2em;
}
.content figure img {
display: inline-block;
}
.content figure figcaption {
font-style: italic;
}
.content pre {
-webkit-overflow-scrolling: touch;
overflow-x: auto;
padding: 1.25em 1.5em;
white-space: pre;
word-wrap: normal;
}
.content sup,
.content sub {
font-size: 75%;
}
.content table {
width: 100%;
}
.content table td,
.content table th {
border: 1px solid hsl(0deg, 0%, 86%);
border-width: 0 0 1px;
padding: 0.5em 0.75em;
vertical-align: top;
}
.content table th {
color: hsl(0deg, 0%, 21%);
}
.content table th:not([align]) {
text-align: inherit;
}
.content table thead td,
.content table thead th {
border-width: 0 0 2px;
color: hsl(0deg, 0%, 21%);
}
.content table tfoot td,
.content table tfoot th {
border-width: 2px 0 0;
color: hsl(0deg, 0%, 21%);
}
.content table tbody tr:last-child td,
.content table tbody tr:last-child th {
border-bottom-width: 0;
}
.content .tabs li + li {
margin-top: 0;
}
.content.is-small {
font-size: 0.75rem;
}
.content.is-normal {
font-size: 1rem;
}
.content.is-medium {
font-size: 1.25rem;
}
.content.is-large {
font-size: 1.5rem;
}
.icon { .icon {
align-items: center; align-items: center;
display: inline-flex; display: inline-flex;
@ -6812,6 +6643,11 @@ a.tag:hover {
text-decoration: underline; text-decoration: underline;
} }
.text-light {
weight: 400;
color: var(--text-color-light);
}
.align-left { .align-left {
text-align: left; text-align: left;
justify-content: left; justify-content: left;
@ -6834,6 +6670,10 @@ a.tag:hover {
clear: both !important; clear: both !important;
} }
.clear-unset {
clear: unset !important;
}
.d-inline { .d-inline {
display: inline !important; display: inline !important;
} }
@ -6846,21 +6686,58 @@ a.tag:hover {
display: inline-block !important; display: inline-block !important;
} }
.push-right, .flex-push-right { .p-relative {
margin-left: auto !important; position: relative !important;
} }
.push-bottom { .p-absolute {
margin-top: auto !important; position: absolute !important;
}
.p-fixed {
position: fixed !important;
}
.p-sticky {
position: sticky !important;
}
.p-static {
position: static !important;
}
.ws-nowrap {
white-space: nowrap;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-flow: dense;
gap: 1.2rem;
}
.grid-1 {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-flow: dense;
gap: 1.2rem;
grid-template-columns: 1fr;
} }
.grid-2 { .grid-2 {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-auto-flow: dense;
gap: 1.2rem;
grid-template-columns: 1fr 1fr;
} }
.grid-3 { .grid-3 {
display: grid; display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-flow: dense;
gap: 1.2rem;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
} }
@ -6905,8 +6782,8 @@ a.tag:hover {
border-radius: 0; border-radius: 0;
} }
.is-borderless { .no-border {
border: none; border: 0px !important;
} }
.overflow-hidden { .overflow-hidden {
@ -6917,38 +6794,10 @@ a.tag:hover {
max-width: 100%; max-width: 100%;
} }
.p-relative {
position: relative !important;
}
.p-absolute {
position: absolute !important;
}
.p-fixed {
position: fixed !important;
}
.p-sticky {
position: sticky !important;
}
.p-static {
position: static !important;
}
.height-full { .height-full {
height: 100%; height: 100%;
} }
.ws-nowrap {
white-space: nowrap;
}
.no-border {
border: 0px !important;
}
*[draggable=true] { *[draggable=true] {
cursor: move; cursor: move;
} }
@ -6969,12 +6818,12 @@ a.tag:hover {
animation: 1s ease-in-out 1s infinite alternate blink; animation: 1s ease-in-out 1s infinite alternate blink;
} }
.hg-color { .main-color {
color: var(--highlight-color); color: var(--main-color);
} }
.hg-color-2 { .secondary-color {
color: var(--highlight-color-2); color: var(--secondary-color);
} }
.bg-transparent { .bg-transparent {
@ -7012,12 +6861,6 @@ input.half-field:not(:active):not(:hover) {
--disabled-bg: #eee; --disabled-bg: #eee;
--link-fg: #00A6A6; --link-fg: #00A6A6;
--link-hv-fg: var(--text-color); --link-hv-fg: var(--text-color);
--hg-color: #EFCA08;
--hg-color-alpha: #EFCA08B3;
--hg-color-grey: rgba(230, 230, 60, 1);
--hg-color-2: #F49F0A;
--hg-color-2-alpha: #F49F0AB3;
--hg-color-2-grey: rgba(50, 200, 200, 1);
--nav-primary-height: 3rem; --nav-primary-height: 3rem;
--nav-secondary-height: 2.5rem; --nav-secondary-height: 2.5rem;
--nav-fg: var(--text-color); --nav-fg: var(--text-color);
@ -7031,22 +6874,36 @@ input.half-field:not(:active):not(:hover) {
--nav-2-fs: 0.9rem; --nav-2-fs: 0.9rem;
} }
:root {
font-size: 14px;
}
body { body {
font-size: 1.4em;
background-color: var(--body-bg); background-color: var(--body-bg);
} }
@media screen and (max-width: 1280px) { body.mobile .grid {
body { grid-template-columns: 1fr;
font-size: 1.2em; }
@media screen and (max-width: 900px) {
.grid {
grid-template-columns: 1fr;
} }
} }
@media screen and (max-width: 1024px) { @media screen and (max-width: 1024px) {
body { html {
font-size: 1em; font-size: 18px !important;
} }
:root { }
--header-height: 20rem; @media screen and (max-width: 1280px) {
html {
font-size: 20px !important;
}
}
@media screen and (min-width: 1280px) {
html {
font-size: 24px !important;
} }
} }
h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle { h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
@ -7056,3 +6913,8 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
.container:empty { .container:empty {
display: none; display: none;
} }
.header-cover {
display: flex;
flex-direction: column;
}

View File

@ -1211,7 +1211,7 @@
background-color: var(--vc-bg); background-color: var(--vc-bg);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-tap-hg-color: transparent; -webkit-tap-highlight-color: transparent;
} }
.vc-container, .vc-container,

View File

@ -27,8 +27,8 @@
--preview-cover-size: 14rem; --preview-cover-size: 14rem;
--preview-cover-small-size: 10rem; --preview-cover-small-size: 10rem;
--preview-cover-tiny-size: 4rem; --preview-cover-tiny-size: 4rem;
--preview-wide-content-sz: 1.6rem; --preview-wide-content-sz: 1.2rem;
--preview-heading-bg-color: var(--hg-color); --preview-heading-bg-color: var(--main-color);
--header-height: var(--cover-h); --header-height: var(--cover-h);
--a-carousel-p: 1.4rem; --a-carousel-p: 1.4rem;
--a-carousel-ml: calc(1.2rem - 0.5rem); --a-carousel-ml: calc(1.2rem - 0.5rem);
@ -76,9 +76,6 @@
--cover-tiny-w: 4rem; --cover-tiny-w: 4rem;
--cover-tiny-h: 4rem; --cover-tiny-h: 4rem;
--section-content-sz: 1rem; --section-content-sz: 1rem;
--preview-title-sz: 1rem;
--preview-subtitle-sz: 0.8rem;
--preview-wide-content-sz: 1rem;
} }
} }
.title.is-1, .header.preview .title.is-1 { .title.is-1, .header.preview .title.is-1 {
@ -124,6 +121,10 @@
color: var(--heading-hg-fg); color: var(--heading-hg-fg);
} }
.panels .panel:not(.active) {
display: none;
}
.preview { .preview {
position: relative; position: relative;
background-size: cover; background-size: cover;
@ -288,7 +289,7 @@
transition: box-shadow 0.2s; transition: box-shadow 0.2s;
} }
.preview-card:hover figure { .preview-card:hover figure {
box-shadow: 0em 0em 1.2em rgba(0, 0, 0, 0.4) !important; box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
} }
.preview-card:hover a { .preview-card:hover a {
color: var(--heading-link-hv-fg); color: var(--heading-link-hv-fg);
@ -299,9 +300,6 @@
.preview-card .headings .heading { .preview-card .headings .heading {
display: block !important; display: block !important;
} }
.preview-card .headings .title {
overflow: hidden;
}
.preview-card .headings .subtitle { .preview-card .headings .subtitle {
font-size: 1.2rem; font-size: 1.2rem;
} }
@ -310,7 +308,6 @@
position: relative; position: relative;
} }
.preview-card .card-content figure { .preview-card .card-content figure {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
height: var(--cover-h); height: var(--cover-h);
width: var(--cover-w); width: var(--cover-w);
} }
@ -321,18 +318,6 @@
right: 0rem; right: 0rem;
} }
.list-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-flow: dense;
gap: 1.2rem;
}
@media screen and (max-width: 900px) {
.list-grid {
grid-template-columns: 1fr;
}
}
.a-carousel .a-carousel-viewport { .a-carousel .a-carousel-viewport {
box-shadow: inset 0em 0em 20rem var(--a-carousel-bg); box-shadow: inset 0em 0em 20rem var(--a-carousel-bg);
padding: 0rem; padding: 0rem;
@ -544,6 +529,34 @@
overflow: hidden; overflow: hidden;
} }
.a-select-file > *:not(:last-child) {
margin-bottom: 0.6rem;
}
.a-select-file .upload-preview {
max-width: 100%;
}
.a-select-file .a-select-file-list {
max-height: 30rem;
overflow-y: auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 0.6rem;
}
.a-select-file .file-preview {
width: 100%;
overflow: hidden;
}
.a-select-file .file-preview:hover {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
}
.a-select-file .file-preview.active {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.4);
}
.a-select-file .file-preview img {
width: 100%;
max-height: 10rem;
}
/* Bulma Utilities */ /* Bulma Utilities */
.file-cta, .file-cta,
.file-name, .select select, .textarea, .input { .file-name, .select select, .textarea, .input {
@ -602,7 +615,7 @@ fieldset[disabled] .file-name, fieldset[disabled] .select select, .select fields
width: 0.625em; width: 0.625em;
} }
.table-container:not(:last-child), .table:not(:last-child), .content:not(:last-child), .box:not(:last-child), .message:not(:last-child) { .table-container:not(:last-child), .table:not(:last-child), .box:not(:last-child), .message:not(:last-child) {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@ -6742,175 +6755,6 @@ a.box:active {
} }
} }
.content li + li {
margin-top: 0.25em;
}
.content p:not(:last-child),
.content dl:not(:last-child),
.content ol:not(:last-child),
.content ul:not(:last-child),
.content blockquote:not(:last-child),
.content pre:not(:last-child),
.content table:not(:last-child) {
margin-bottom: 1em;
}
.content h1,
.content h2,
.content h3,
.content h4,
.content h5,
.content h6 {
color: hsl(0deg, 0%, 21%);
font-weight: 600;
line-height: 1.125;
}
.content h1 {
font-size: 2em;
margin-bottom: 0.5em;
}
.content h1:not(:first-child) {
margin-top: 1em;
}
.content h2 {
font-size: 1.75em;
margin-bottom: 0.5714em;
}
.content h2:not(:first-child) {
margin-top: 1.1428em;
}
.content h3 {
font-size: 1.5em;
margin-bottom: 0.6666em;
}
.content h3:not(:first-child) {
margin-top: 1.3333em;
}
.content h4 {
font-size: 1.25em;
margin-bottom: 0.8em;
}
.content h5 {
font-size: 1.125em;
margin-bottom: 0.8888em;
}
.content h6 {
font-size: 1em;
margin-bottom: 1em;
}
.content blockquote {
background-color: hsl(0deg, 0%, 96%);
border-left: 5px solid hsl(0deg, 0%, 86%);
padding: 1.25em 1.5em;
}
.content ol {
list-style-position: outside;
margin-left: 2em;
margin-top: 1em;
}
.content ol:not([type]) {
list-style-type: decimal;
}
.content ol:not([type]).is-lower-alpha {
list-style-type: lower-alpha;
}
.content ol:not([type]).is-lower-roman {
list-style-type: lower-roman;
}
.content ol:not([type]).is-upper-alpha {
list-style-type: upper-alpha;
}
.content ol:not([type]).is-upper-roman {
list-style-type: upper-roman;
}
.content ul {
list-style: disc outside;
margin-left: 2em;
margin-top: 1em;
}
.content ul ul {
list-style-type: circle;
margin-top: 0.5em;
}
.content ul ul ul {
list-style-type: square;
}
.content dd {
margin-left: 2em;
}
.content figure {
margin-left: 2em;
margin-right: 2em;
text-align: center;
}
.content figure:not(:first-child) {
margin-top: 2em;
}
.content figure:not(:last-child) {
margin-bottom: 2em;
}
.content figure img {
display: inline-block;
}
.content figure figcaption {
font-style: italic;
}
.content pre {
-webkit-overflow-scrolling: touch;
overflow-x: auto;
padding: 1.25em 1.5em;
white-space: pre;
word-wrap: normal;
}
.content sup,
.content sub {
font-size: 75%;
}
.content table {
width: 100%;
}
.content table td,
.content table th {
border: 1px solid hsl(0deg, 0%, 86%);
border-width: 0 0 1px;
padding: 0.5em 0.75em;
vertical-align: top;
}
.content table th {
color: hsl(0deg, 0%, 21%);
}
.content table th:not([align]) {
text-align: inherit;
}
.content table thead td,
.content table thead th {
border-width: 0 0 2px;
color: hsl(0deg, 0%, 21%);
}
.content table tfoot td,
.content table tfoot th {
border-width: 2px 0 0;
color: hsl(0deg, 0%, 21%);
}
.content table tbody tr:last-child td,
.content table tbody tr:last-child th {
border-bottom-width: 0;
}
.content .tabs li + li {
margin-top: 0;
}
.content.is-small {
font-size: 0.75rem;
}
.content.is-normal {
font-size: 1rem;
}
.content.is-medium {
font-size: 1.25rem;
}
.content.is-large {
font-size: 1.5rem;
}
.icon { .icon {
align-items: center; align-items: center;
display: inline-flex; display: inline-flex;
@ -7348,7 +7192,7 @@ a.tag:hover {
} }
.vc-weekday-1, .vc-weekday-7 { .vc-weekday-1, .vc-weekday-7 {
color: var(--hg-color-2) !important; color: var(--secondary-color) !important;
} }
.schedules { .schedules {
@ -7411,9 +7255,9 @@ a.tag:hover {
cursor: pointer; cursor: pointer;
} }
.button[disabled], .button.disabled, a.button[disabled], a.button.disabled, button.button[disabled], button.button.disabled { .button[disabled], .button.disabled, a.button[disabled], a.button.disabled, button.button[disabled], button.button.disabled {
background-color: var(--hg-color-grey); background-color: var(--text-color-light);
color: var(--hg-color-2); color: var(--secondary-color);
border-color: var(--hg-color-2-alpha); border-color: var(--secondary-color-light);
} }
.button .dropdown-trigger, a.button .dropdown-trigger, button.button .dropdown-trigger { .button .dropdown-trigger, a.button .dropdown-trigger, button.button .dropdown-trigger {
border-radius: 1.5em; border-radius: 1.5em;
@ -7481,8 +7325,8 @@ a.tag:hover {
} }
} }
.navbar-item.active, .table tr.is-selected { .navbar-item.active, .table tr.is-selected {
color: var(--hg-color-2); color: var(--secondary-color);
background-color: var(--hg-color); background-color: var(--main-color);
} }
.title { .title {
@ -7743,15 +7587,6 @@ nav li a, nav li .button {
border-color: var(--secondary-color-dark) !important; border-color: var(--secondary-color-dark) !important;
} }
body {
font-size: 1.4em;
}
@media screen and (max-width: 1380px) {
body {
font-size: 1em;
}
}
@media screen and (max-width: 1024px) { @media screen and (max-width: 1024px) {
.page .container { .page .container {
margin-left: 1.2rem; margin-left: 1.2rem;

File diff suppressed because one or more lines are too long

View File

@ -1,29 +0,0 @@
/* global CKEDITOR, django */
/* Modified in order to be manually loaded after vue.js */
function initialiseCKEditor() {
var textareas = Array.prototype.slice.call(
document.querySelectorAll("textarea[data-type=ckeditortype]"),
)
for (var i = 0; i < textareas.length; ++i) {
var t = textareas[i]
if (
t.getAttribute("data-processed") == "0" &&
t.id.indexOf("__prefix__") == -1
) {
t.setAttribute("data-processed", "1")
var ext = JSON.parse(t.getAttribute("data-external-plugin-resources"))
for (var j = 0; j < ext.length; ++j) {
CKEDITOR.plugins.addExternal(ext[j][0], ext[j][1], ext[j][2])
}
CKEDITOR.replace(t.id, JSON.parse(t.getAttribute("data-config")))
}
}
}
function initialiseCKEditorInInlinedForms() {
if (typeof django === "object" && django.jQuery) {
django.jQuery(document).on("formset:added", initialiseCKEditor)
}
}
//})()

View File

@ -7,9 +7,6 @@ Usefull context:
- cover: image cover - cover: image cover
- site: current website - site: current website
- model: view model or displayed `object`'s - model: view model or displayed `object`'s
- sidebar_object_list: item to display in sidebar
- sidebar_url_name: url name sidebar item complete list
- sidebar_url_parent: parent page for sidebar items complete list
{% endcomment %} {% endcomment %}
<html> <html>
<head> <head>
@ -39,7 +36,7 @@ Usefull context:
{% block head_extra %}{% endblock %} {% block head_extra %}{% endblock %}
</head> </head>
<body> <body {% if request.is_mobile %}class="mobile"{% endif %}>
<script id="init-script"> <script id="init-script">
window.addEventListener('load', function() { window.addEventListener('load', function() {
{% block init-scripts %} {% block init-scripts %}
@ -51,7 +48,7 @@ Usefull context:
<div class="navs"> <div class="navs">
{% block nav %} {% block nav %}
<nav class="nav primary" role="navigation" aria-label="main navigation"> <nav class="nav primary" role="navigation" aria-label="main navigation">
{% block nav-primary %} {% block primary-nav %}
<a class="nav-brand" href="{% url "home" %}"> <a class="nav-brand" href="{% url "home" %}">
<img src="{{ station.logo.url }}"> <img src="{{ station.logo.url }}">
</a> </a>
@ -60,7 +57,7 @@ Usefull context:
aria-label="{% translate "Main menu" %}"> aria-label="{% translate "Main menu" %}">
</a-switch> </a-switch>
<div class="nav-menu"> <div class="nav-menu">
{% block nav-primary-menu %} {% block primary-nav-menu %}
{% nav_items "top" css_class="nav-item" active_class="active" as items %} {% nav_items "top" css_class="nav-item" active_class="active" as items %}
{% for item, render in items %} {% for item, render in items %}
{{ render }} {{ render }}
@ -70,9 +67,6 @@ Usefull context:
{% translate "Admin" %} {% translate "Admin" %}
</a> </a>
{% endif %} {% endif %}
<a class="nav-item" href="{% url "profile" %}" target="new">
{% translate "Profile" %}
</a>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a class="nav-item" href="{% url "logout" %}" title="{% translate "Disconnect" %}" <a class="nav-item" href="{% url "logout" %}" title="{% translate "Disconnect" %}"
aria-label="{% translate "Disconnect" %}"> aria-label="{% translate "Disconnect" %}">
@ -99,22 +93,23 @@ Usefull context:
{% endblock %} {% endblock %}
{% endspaceless %} {% endspaceless %}
{% block header-container %} {% block header-container %}
{% if page or cover or title %} {% if page or cover or title %}
<header class="container header preview preview-header {% if cover %}has-cover{% endif %}"> <header class="container header preview preview-header {% if cover %}has-cover{% endif %}">
{% block header %} {% block header %}
{% if cover %}
<figure class="header-cover"> <figure class="header-cover">
<img src="{{ cover }}" class="cover"> {% block header-cover %}
</figure> {% if cover %}
<img src="{{ cover }}" ref="cover" class="cover">
{% endif %} {% endif %}
{% endblock %}
</figure>
<div class="headings preview-card-headings"> <div class="headings preview-card-headings">
{% block headings %} {% block headings %}
<div> <div>
{% block title-container %}
<h1 class="title is-1 {% block title-class %}{% endblock %}">{% block title %}{{ title|default:"" }}{% endblock %}</h1> <h1 class="title is-1 {% block title-class %}{% endblock %}">{% block title %}{{ title|default:"" }}{% endblock %}</h1>
{% include "aircox/edit-link.html" %} {% endblock %}
</div> </div>
<div> <div>
{% spaceless %} {% spaceless %}
@ -147,6 +142,33 @@ Usefull context:
{% endblock %} {% endblock %}
</main> </main>
{% endblock %} {% endblock %}
{% block footer-container %}
<footer class="page-footer">
{% block footer %}
{% comment %}
{% nav_items "footer" css_class="nav-item" active_class="active" as items %}
{% for item, render in items %}
{{ render }}
{% endfor %}
{% endcomment %}
{% if not request.user.is_authenticated %}
<a class="nav-item" href="{% url "profile" %}" target="new"
title="{% translate "Profile" %}">
<span class="small icon">
<i class="fa fa-user"></i>
</span>
</a>
{% endif %}
{% endblock %}
{% if request.station and request.station.legal_label %}
{{ request.station.legal_label }} &mdash;
{% endif %}
</footer>
{% endblock %}
</div> </div>
{% block player-container %} {% block player-container %}
<div id="player">{% include "aircox/widgets/player.html" %}</div> <div id="player">{% include "aircox/widgets/player.html" %}</div>

View File

@ -15,7 +15,7 @@
{{ block.super }} {{ block.super }}
{% block list-container %} {% block list-container %}
<section class="container clear-both list list-grid {{ list_class|default:"" }}" role="list"> <section class="container clear-both list grid {{ list_class|default:"" }}" role="list">
{% block list %} {% block list %}
{% with has_headline=True %} {% with has_headline=True %}
{% for object in object_list %} {% for object in object_list %}

View File

@ -1,24 +0,0 @@
{% load aircox i18n %}
{% block user-actions-container %}
{% has_perm page page.program.change_permission_codename simple=True as can_edit %}
{% if user.is_authenticated and can_edit %}
{% with request.resolver_match.view_name as view_name %}
&nbsp;
{% if view_name in 'page-edit,program-edit,episode-edit' %}
<a href="{% url view_name|detail_view page.slug %}" target="_self">
<small>
<span title="{% translate 'View' %} {{ page }}">{% translate 'View' %} </span>
<i class="fa-regular fa-eye"></i>
</small>
</a>
{% else %}
<a href="{% url view_name|edit_view page.pk %}" target="_self">
<small>
<span title="{% translate 'Edit' %} {{ page }}">{% translate 'Edit' %} </span>
<i class="fa-solid fa-pencil"></i>
</small>
</a>
{% endif %}
{% endwith %}
{% endif %}
{% endblock %}

View File

@ -26,7 +26,6 @@
</table> </table>
<br/> <br/>
<input type="submit" value="Update" class="button is-success"> <input type="submit" value="Update" class="button is-success">
<hr> <hr>
{% include "aircox/playlist_inline.html" %} {% include "aircox/playlist_inline.html" %}

View File

@ -41,7 +41,7 @@
<section class="container"> <section class="container">
<h2 class="title">{% translate "It just happened" %}</h2> <h2 class="title">{% translate "It just happened" %}</h2>
<div class="list-grid" role="list"> <div class="grid" role="list">
{% include "./widgets/logs.html" with object_list=logs %} {% include "./widgets/logs.html" with object_list=logs %}
</div> </div>

View File

@ -23,6 +23,12 @@ Context:
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block title-container %}
{{ block.super }}
{% block page-actions %}
{% include "aircox/widgets/page_actions.html" %}
{% endblock %}
{% endblock %}
{% block main %} {% block main %}
{{ block.super }} {{ block.super }}

View File

@ -0,0 +1,71 @@
{% extends "./page_detail.html" %}
{% load static i18n %}
{% block header-cover %}
<img src="{{ cover }}" ref="cover" class="cover">
{% endblock %}
{% block content-container %}
<section class="container active">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="field">
{% if field.name == "cover" %}
<input type="hidden" name="{{ field.name }}" value="{{ field.pk }}" ref="coverField"/>
{% else %}
<label class="label">{{ field.label }}</label>
<div class="control clear-unset">
{% if field.name == "pub_date" %}
<input type="datetime-local" name="{{ field.name }}"
value="{{ field.value|date:"Y-m-d" }}T{{ field.value|date:"H:i" }}"/>
{% elif field.name == "content" %}
<textarea name="{{ field.name }}" class="is-fullwidth">{{ field.value|striptags|safe }}</textarea>
{% else %}
{{ field }}
{% endif %}
</div>
{% endif %}
</div>
{% if field.errors %}
<p class="help is-danger">{{ field.errors }}</p>
{% endif %}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
{% endfor %}
<div class="has-text-right">
<button type="submit" class="button">{% translate "Update" %}</button>
</div>
</form>
</section>
<section id="cover-modal" class="container page-edit-panel">
<h3 class="title">{% translate "Change cover" %}</h3>
<a-select-file list-url="{% url "api:image-list" %}" upload-url="{% url "api:image-list" %}"
prev-label="{% translate "Show previous" %}"
next-label="{% translate "Show next" %}"
>
<template #upload-preview="{item}">
<template v-if="item">
<img :src="item.src" class="upload-preview"/>
</template>
</template>
<template #default="{item}">
<div class="flex-column">
<div class="flex-grow-1">
<img :src="item.file"/>
</div>
<label class="label">[[ item.name || item.original_filename ]]</label>
</div>
</template>
<template #footer="{item}">
<button type="button" class="button float-right"
@click="(event) => {$refs.cover.src = item.file; $refs.coverField.value = item.id}">
{% translate "Select" %}
</button>
</template>
</a-select-file>
</section>
{% endblock %}

View File

@ -18,7 +18,7 @@
{% block list-container %} {% block list-container %}
{% with list_class="list-grid" %} {% with list_class="grid" %}
{{ block.super }} {{ block.super }}
{% endwith %} {% endwith %}
{% endblock %} {% endblock %}

View File

@ -7,7 +7,7 @@ Context:
- url_label: label of url button - url_label: label of url button
{% endcomment %} {% endcomment %}
<a-carousel section-class="card-grid"> <a-carousel>
{% for object in objects %} {% for object in objects %}
{% page_widget "card" object %} {% page_widget "card" object %}
{% endfor %} {% endfor %}

View File

@ -0,0 +1,30 @@
{% load aircox i18n %}
{% block user-actions-container %}
{% has_perm page page.program.change_permission_codename simple=True as can_edit %}
{% if user.is_authenticated %}
{{ object.get_status_display }}
({{ object.pub_date|date:"d/m/Y H:i" }})
{% endif %}
{% if user.is_authenticated and can_edit %}
{% with request.resolver_match.view_name as view_name %}
&nbsp;
{% if "-edit" in view_name %}
<a href="{% url view_name|detail_view page.slug %}" target="_self" title="{% translate 'View' %} {{ page }}">
<span class="icon">
<i class="fa-regular fa-eye"></i>
</span>
<span>{% translate 'View' %} </span>
</a>
{% else %}
<a href="{% url view_name|edit_view page.pk %}" target="_self" title="{% translate 'Edit' %} {{ page }}">
<span class="icon">
<i class="fa-solid fa-pencil"></i>
</span>
<span>{% translate 'Edit' %} </span>
</a>
{% endif %}
{% endwith %}
{% endif %}
{% endblock %}

View File

@ -30,7 +30,7 @@ The audio player
</h4> </h4>
<h4 v-else-if="current && current.data.type == 'track'" <h4 v-else-if="current && current.data.type == 'track'"
class="title" aria-description="{% translate "Track currently on air" %}"> class="title" aria-description="{% translate "Track currently on air" %}">
<span class="icon hg-color-2 mr-3"> <span class="icon secondary-color mr-3">
<i class="fas fa-music"></i> <i class="fas fa-music"></i>
</span> </span>
<span>[[ current.data.title ]]</span> <span>[[ current.data.title ]]</span>

View File

@ -6,7 +6,7 @@ Context:
{% endcomment %} {% endcomment %}
<span class="track"> <span class="track">
<span class="icon hg-color-2"> <span class="icon secondary-color">
<i class="fas fa-music"></i> <i class="fas fa-music"></i>
</span> </span>
<label> <label>

View File

@ -59,6 +59,8 @@ def do_get_tracks(obj):
@register.simple_tag(name="has_perm", takes_context=True) @register.simple_tag(name="has_perm", takes_context=True)
def do_has_perm(context, obj, perm, user=None, simple=False): def do_has_perm(context, obj, perm, user=None, simple=False):
"""Return True if ``user.has_perm('[APP].[perm]_[MODEL]')``""" """Return True if ``user.has_perm('[APP].[perm]_[MODEL]')``"""
if not obj:
return
if user is None: if user is None:
user = context["request"].user user = context["request"].user
if simple: if simple:
@ -102,6 +104,8 @@ def do_player_live_attr(context):
@register.simple_tag(name="nav_items", takes_context=True) @register.simple_tag(name="nav_items", takes_context=True)
def do_nav_items(context, menu, **kwargs): def do_nav_items(context, menu, **kwargs):
"""Render navigation items for the provided menu name.""" """Render navigation items for the provided menu name."""
if not getattr(context["request"], "station"):
return []
station, request = context["station"], context["request"] station, request = context["station"], context["request"]
return [(item, item.render(request, **kwargs)) for item in station.navitem_set.filter(menu=menu)] return [(item, item.render(request, **kwargs)) for item in station.navitem_set.filter(menu=menu)]

View File

@ -2,7 +2,7 @@ from django.urls import include, path, register_converter
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from . import models, views, viewsets from . import forms, models, views, viewsets
from .converters import DateConverter, PagePathConverter, WeekConverter from .converters import DateConverter, PagePathConverter, WeekConverter
__all__ = ["api", "urls"] __all__ = ["api", "urls"]
@ -21,6 +21,7 @@ register_converter(WeekConverter, "week")
router = DefaultRouter() router = DefaultRouter()
router.register("images", viewsets.ImageViewSet, basename="image")
router.register("sound", viewsets.SoundViewSet, basename="sound") router.register("sound", viewsets.SoundViewSet, basename="sound")
router.register("track", viewsets.TrackROViewSet, basename="track") router.register("track", viewsets.TrackROViewSet, basename="track")
@ -132,6 +133,12 @@ urls = [
views.errors.NoStationErrorView.as_view(), views.errors.NoStationErrorView.as_view(),
name="errors-no-station", name="errors-no-station",
), ),
path("gestion/", views.ProfileView.as_view(), name="profile"), # ---- backoffice
path(_("edit/"), views.ProfileView.as_view(), name="profile"),
path(
_("edit/programs/<slug:slug>"),
views.PageUpdateView.as_view(model=models.Program, form_class=forms.ProgramForm),
name="program-update",
),
path("accounts/profile/", views.ProfileView.as_view(), name="profile"), path("accounts/profile/", views.ProfileView.as_view(), name="profile"),
] ]

View File

@ -1,6 +1,5 @@
from . import admin, errors from . import admin, errors
from .article import ArticleDetailView, ArticleListView from .article import ArticleDetailView, ArticleListView
from .base import BaseAPIView, BaseView
from .diffusion import DiffusionListView, TimeTableView from .diffusion import DiffusionListView, TimeTableView
from .episode import EpisodeDetailView, EpisodeListView, PodcastListView, EpisodeUpdateView from .episode import EpisodeDetailView, EpisodeListView, PodcastListView, EpisodeUpdateView
from .home import HomeView from .home import HomeView
@ -10,6 +9,7 @@ from .page import (
BasePageListView, BasePageListView,
PageDetailView, PageDetailView,
PageListView, PageListView,
PageUpdateView,
) )
from .profile import ProfileView from .profile import ProfileView
from .program import ( from .program import (
@ -20,13 +20,12 @@ from .program import (
ProgramUpdateView, ProgramUpdateView,
) )
__all__ = ( __all__ = (
"admin", "admin",
"errors", "errors",
"ArticleDetailView", "ArticleDetailView",
"ArticleListView", "ArticleListView",
"BaseAPIView",
"BaseView",
"DiffusionListView", "DiffusionListView",
"TimeTableView", "TimeTableView",
"EpisodeDetailView", "EpisodeDetailView",
@ -39,6 +38,7 @@ __all__ = (
"BasePageDetailView", "BasePageDetailView",
"BasePageListView", "BasePageListView",
"PageDetailView", "PageDetailView",
"PageUpdateView",
"PageListView", "PageListView",
"ProfileView", "ProfileView",
"ProgramDetailView", "ProgramDetailView",

View File

@ -24,7 +24,10 @@ class BasePageMixin:
category = None category = None
def get_queryset(self): def get_queryset(self):
return super().get_queryset().select_subclasses().published().select_related("cover") qs = super().get_queryset().select_subclasses().select_related("cover")
if self.request.user.is_authenticated:
return qs
return qs.published()
def get_category(self, page, **kwargs): def get_category(self, page, **kwargs):
if page: if page:
@ -153,8 +156,8 @@ class PageDetailView(BasePageDetailView):
return super().get_queryset().select_related("category") return super().get_queryset().select_related("category")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
if self.object.allow_comments and "comment_form" not in kwargs: if "comment_form" not in kwargs:
kwargs["comment_form"] = CommentForm() kwargs["comment_form"] = self.get_comment_form()
kwargs["comments"] = Comment.objects.filter(page=self.object).order_by("-date") kwargs["comments"] = Comment.objects.filter(page=self.object).order_by("-date")
if self.object.parent_subclass: if self.object.parent_subclass:
@ -167,6 +170,11 @@ class PageDetailView(BasePageDetailView):
kwargs["related_objects"] = related kwargs["related_objects"] = related
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_comment_form(self):
if self.object.allow_comments:
return CommentForm()
return None
@classmethod @classmethod
def as_view(cls, *args, **kwargs): def as_view(cls, *args, **kwargs):
view = super(PageDetailView, cls).as_view(*args, **kwargs) view = super(PageDetailView, cls).as_view(*args, **kwargs)
@ -186,6 +194,14 @@ class PageDetailView(BasePageDetailView):
class PageUpdateView(BaseView, UpdateView): class PageUpdateView(BaseView, UpdateView):
context_object_name = "page" context_object_name = "page"
template_name = "aircox/page_form.html"
# FIXME: remove?
def get_page(self): def get_page(self):
return self.object return self.object
def get_success_url(self):
return self.request.path
def get_comment_form(self):
return None

View File

@ -56,9 +56,6 @@ class ProgramUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
model = Program model = Program
form_class = ProgramForm form_class = ProgramForm
def get_sidebar_queryset(self):
return super().get_sidebar_queryset().filter(parent=self.program)
def test_func(self): def test_func(self):
program = self.get_object() program = self.get_object()
return self.request.user.has_perm("aircox.%s" % program.change_permission_codename) return self.request.user.has_perm("aircox.%s" % program.change_permission_codename)
@ -94,7 +91,3 @@ class ProgramPageListView(BaseProgramMixin, PageListView):
def get_program(self): def get_program(self):
return self.parent return self.parent
def get_context_data(self, **kwargs):
kwargs.setdefault("sidebar_url_parent", None)
return super().get_context_data(**kwargs)

View File

@ -1,44 +1,46 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as drf_filters
from rest_framework import status, viewsets from rest_framework import status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser
from .models import Sound, Track from filer.models.imagemodels import Image
from . import models, forms, filters
from .serializers import SoundSerializer, admin from .serializers import SoundSerializer, admin
from .views import BaseAPIView from .views import BaseAPIView
__all__ = ( __all__ = (
"SoundFilter", "ImageViewSet",
"SoundViewSet", "SoundViewSet",
"TrackFilter",
"TrackROViewSet", "TrackROViewSet",
"UserSettingsViewSet", "UserSettingsViewSet",
) )
class SoundFilter(filters.FilterSet): class ImageViewSet(viewsets.ModelViewSet):
station = filters.NumberFilter(field_name="program__station__id") parsers = (MultiPartParser,)
program = filters.NumberFilter(field_name="program_id") serializer_class = admin.ImageSerializer
episode = filters.NumberFilter(field_name="episode_id") queryset = Image.objects.all().order_by("-uploaded_at")
search = filters.CharFilter(field_name="search", method="search_filter") filter_backends = (drf_filters.DjangoFilterBackend,)
filterset_class = filters.ImageFilterSet
def search_filter(self, queryset, name, value): def create(self, request, **kwargs):
return queryset.search(value) # FIXME: to be replaced by regular DRF
form = forms.ImageForm(request.POST, request.FILES)
if form.is_valid():
file = form.cleaned_data["file"]
Image.objects.create(original_filename=file.name, file=file)
return Response({"status": "ok"})
return Response({"status": "error", "errors": form.errors})
class SoundViewSet(BaseAPIView, viewsets.ModelViewSet): class SoundViewSet(BaseAPIView, viewsets.ModelViewSet):
serializer_class = SoundSerializer serializer_class = SoundSerializer
queryset = Sound.objects.available().order_by("-pk") queryset = models.Sound.objects.available().order_by("-pk")
filter_backends = (filters.DjangoFilterBackend,) filter_backends = (drf_filters.DjangoFilterBackend,)
filterset_class = SoundFilter filterset_class = filters.SoundFilterSet
# --- admin
class TrackFilter(filters.FilterSet):
artist = filters.CharFilter(field_name="artist", lookup_expr="icontains")
album = filters.CharFilter(field_name="album", lookup_expr="icontains")
title = filters.CharFilter(field_name="title", lookup_expr="icontains")
class TrackROViewSet(viewsets.ReadOnlyModelViewSet): class TrackROViewSet(viewsets.ReadOnlyModelViewSet):
@ -46,9 +48,9 @@ class TrackROViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = admin.TrackSerializer serializer_class = admin.TrackSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
filter_backends = (filters.DjangoFilterBackend,) filter_backends = (drf_filters.DjangoFilterBackend,)
filterset_class = TrackFilter filterset_class = filters.TrackFilterSet
queryset = Track.objects.all() queryset = models.Track.objects.all()
@action(name="autocomplete", detail=False) @action(name="autocomplete", detail=False)
def autocomplete(self, request): def autocomplete(self, request):
@ -60,6 +62,7 @@ class TrackROViewSet(viewsets.ReadOnlyModelViewSet):
return self.list(request) return self.list(request)
# --- admin
class UserSettingsViewSet(viewsets.ViewSet): class UserSettingsViewSet(viewsets.ViewSet):
"""User's settings specific to aircox. """User's settings specific to aircox.

View File

@ -30,13 +30,6 @@ input.half-field:not(:active):not(:hover) {
--link-fg: #00A6A6; --link-fg: #00A6A6;
--link-hv-fg: var(--text-color); --link-hv-fg: var(--text-color);
--hg-color: #EFCA08;
--hg-color-alpha: #EFCA08B3;
--hg-color-grey: rgba(230, 230, 60, 1);
--hg-color-2: #F49F0A;
--hg-color-2-alpha: #F49F0AB3;
--hg-color-2-grey: rgba(50, 200, 200, 1);
--nav-primary-height: 3rem; --nav-primary-height: 3rem;
--nav-secondary-height: 2.5rem; --nav-secondary-height: 2.5rem;
--nav-fg: var(--text-color); --nav-fg: var(--text-color);
@ -51,22 +44,38 @@ input.half-field:not(:active):not(:hover) {
} }
:root {
font-size: 14px;
}
body { body {
font-size: 1.4em;
background-color: var(--body-bg); background-color: var(--body-bg);
} }
@media screen and (max-width: v.$screen-wider) { @mixin mobile-small {
body { font-size: 1.2em; } .grid { @include grid-1; }
}
body.mobile {
@include mobile-small;
}
@media screen and (max-width: v.$screen-smaller) {
@include mobile-small;
} }
@media screen and (max-width: v.$screen-normal) { @media screen and (max-width: v.$screen-normal) {
body { font-size: 1em; } html { font-size: 18px !important; }
:root {
--header-height: 20rem;
} }
@media screen and (max-width: v.$screen-wider) {
html { font-size: 20px !important; }
}
@media screen and (min-width: v.$screen-wider) {
html { font-size: 24px !important; }
} }
h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle { h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
@ -77,3 +86,8 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
.container:empty { .container:empty {
display: none; display: none;
} }
.header-cover {
display: flex;
flex-direction: column;
}

View File

@ -31,8 +31,8 @@
--preview-cover-size: 14rem; --preview-cover-size: 14rem;
--preview-cover-small-size: 10rem; --preview-cover-small-size: 10rem;
--preview-cover-tiny-size: 4rem; --preview-cover-tiny-size: 4rem;
--preview-wide-content-sz: #{v.$text-size-bigger}; --preview-wide-content-sz: #{v.$text-size-2};
--preview-heading-bg-color: var(--hg-color); --preview-heading-bg-color: var(--main-color);
--header-height: var(--cover-h); --header-height: var(--cover-h);
--a-carousel-p: #{v.$text-size-medium}; --a-carousel-p: #{v.$text-size-medium};
@ -89,9 +89,9 @@
--section-content-sz: 1rem; --section-content-sz: 1rem;
--preview-title-sz: #{v.$text-size}; // --preview-title-sz: #{v.$text-size};
--preview-subtitle-sz: #{v.$text-size-smaller}; // --preview-subtitle-sz: #{v.$text-size-smaller};
--preview-wide-content-sz: #{v.$text-size}; // --preview-wide-content-sz: #{v.$text-size};
} }
} }
@ -138,6 +138,11 @@
} }
// ---- panels
.panels {
.panel:not(.active) { display: none; }
}
// ---- button // ---- button
@mixin button { @mixin button {
.button, a.button, button.button { .button, a.button, button.button {
@ -177,7 +182,6 @@
} }
&.active:not(:hover) { &.active:not(:hover) {
// border-color: var(--hg-color-alpha);
color: var(--button-active-fg); color: var(--button-active-fg);
background-color: var(--button-active-bg); background-color: var(--button-active-bg);
} }
@ -187,9 +191,9 @@
} }
&[disabled], &.disabled { &[disabled], &.disabled {
background-color: var(--hg-color-grey); background-color: var(--text-color-light);
color: var(--hg-color-2); color: var(--secondary-color);
border-color: var(--hg-color-2-alpha); border-color: var(--secondary-color-light);
} }
.dropdown-trigger { .dropdown-trigger {
@ -414,7 +418,8 @@
&:hover { &:hover {
figure { figure {
box-shadow: 0em 0em 1.2em rgba(0, 0, 0, 0.4) !important; // box-shadow: 0em 0em 1.2em rgba(0, 0, 0, 0.4) !important;
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
} }
a { a {
@ -429,10 +434,6 @@
display: block !important; display: block !important;
} }
.title {
overflow: hidden;
}
.subtitle { .subtitle {
font-size: v.$text-size-2; font-size: v.$text-size-2;
} }
@ -443,7 +444,7 @@
position: relative; position: relative;
figure { figure {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2); // box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
height: var(--cover-h); height: var(--cover-h);
width: var(--cover-w); width: var(--cover-w);
} }
@ -459,25 +460,6 @@
} }
// ---- grid
.list-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-flow: dense;
gap: v.$mp-4;
// .grid-wide { grid-column: 1 / 3; }
}
@media screen and (max-width: v.$screen-smaller) {
.list-grid {
grid-template-columns: 1fr;
// .grid-wide { grid-column: 1; }
}
}
// ---- ---- Carousel // ---- ---- Carousel
.a-carousel { .a-carousel {
.a-carousel-viewport { .a-carousel-viewport {
@ -724,3 +706,40 @@
overflow: hidden; overflow: hidden;
} }
} }
/// ----------------
.a-select-file {
> *:not(:last-child) {
margin-bottom: v.$mp-3;
}
.upload-preview {
max-width: 100%;
}
.a-select-file-list {
max-height: 30rem;
overflow-y: auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: v.$mp-3;
}
.file-preview {
width: 100%;
overflow: hidden;
&:hover {
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
}
&.active {
box-shadow: 0em 0em 1em rgba(0,0,0,0.4);
}
img {
width: 100%;
max-height: 10rem;
}
}
}

View File

@ -1,23 +1,46 @@
@use "./vars"; @use "./vars" as v;
.text-light { weight: 400; color: var(--text-color-light); }
// ---- layout
.align-left { text-align: left; justify-content: left; } .align-left { text-align: left; justify-content: left; }
.align-right { text-align: right; justify-content: right; } .align-right { text-align: right; justify-content: right; }
.clear-left { clear: left !important } .clear-left { clear: left !important }
.clear-right { clear: right !important } .clear-right { clear: right !important }
.clear-both { clear: both !important } .clear-both { clear: both !important }
.clear-unset { clear: unset !important }
.d-inline { display: inline !important; } .d-inline { display: inline !important; }
.d-block { display: block !important; } .d-block { display: block !important; }
.d-inline-block { display: inline-block !important; } .d-inline-block { display: inline-block !important; }
.push-right, .flex-push-right { margin-left: auto !important; } .p-relative { position: relative !important }
.push-bottom { margin-top: auto !important; } .p-absolute { position: absolute !important }
.p-fixed { position: fixed !important }
.p-sticky { position: sticky !important }
.p-static { position: static !important }
.ws-nowrap { white-space: nowrap; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr } // ---- grid
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr } @mixin grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-flow: dense;
gap: v.$mp-4;
}
@mixin grid-1 { grid-template-columns: 1fr; }
@mixin grid-2 { grid-template-columns: 1fr 1fr; }
@mixin grid-3 { grid-template-columns: 1fr 1fr 1fr; }
.grid { @include grid; }
.grid-1 { @include grid; @include grid-1; }
.grid-2 { @include grid; @include grid-2; }
.grid-3 { @include grid; @include grid-3; }
// ---- flex
.flex-row { display: flex; flex-direction: row } .flex-row { display: flex; flex-direction: row }
.flex-column { display: flex; flex-direction: column } .flex-column { display: flex; flex-direction: column }
.flex-grow-0 { flex-grow: 0 !important; } .flex-grow-0 { flex-grow: 0 !important; }
@ -26,6 +49,7 @@
.float-right { float: right } .float-right { float: right }
.float-left { float: left } .float-left { float: left }
// ---- boxing
.is-fullwidth { width: 100%; } .is-fullwidth { width: 100%; }
.is-fullheight { height: 100%; } .is-fullheight { height: 100%; }
.is-fixed-bottom { .is-fixed-bottom {
@ -34,23 +58,13 @@
margin-bottom: 0px; margin-bottom: 0px;
border-radius: 0; border-radius: 0;
} }
.is-borderless { border: none; } .no-border { border: 0px !important; }
.overflow-hidden { overflow: hidden } .overflow-hidden { overflow: hidden }
.overflow-hidden.is-fullwidth { max-width: 100%; } .overflow-hidden.is-fullwidth { max-width: 100%; }
.p-relative { position: relative !important }
.p-absolute { position: absolute !important }
.p-fixed { position: fixed !important }
.p-sticky { position: sticky !important }
.p-static { position: static !important }
.height-full { height: 100%; } .height-full { height: 100%; }
.ws-nowrap { white-space: nowrap; }
.no-border { border: 0px !important; }
*[draggable="true"] { *[draggable="true"] {
cursor: move; cursor: move;
} }
@ -67,16 +81,16 @@
// -- colors // -- colors
.hg-color { color: var(--highlight-color); } .main-color { color: var(--main-color); }
.hg-color-2 { color: var(--highlight-color-2); } .secondary-color { color: var(--secondary-color); }
.bg-transparent { background-color: transparent; } .bg-transparent { background-color: transparent; }
.is-success { .is-success {
background-color: vars.$green !important; background-color: v.$green !important;
border-color: vars.$green-dark !important; border-color: v.$green-dark !important;
} }
.is-danger { .is-danger {
background-color: vars.$red !important; background-color: v.$red !important;
border-color: vars.$red-dark !important; border-color: v.$red-dark !important;
} }

View File

@ -5,6 +5,7 @@
// ---- main theme & layout // ---- main theme & layout
.page { .page {
padding-bottom: 5rem; padding-bottom: 5rem;
@ -47,7 +48,7 @@
} }
.vc-weekday-1, .vc-weekday-7 { .vc-weekday-1, .vc-weekday-7 {
color: var(--hg-color-2) !important; color: var(--secondary-color) !important;
} }
@ -124,19 +125,15 @@
} }
.navbar-item.active, .table tr.is-selected { .navbar-item.active, .table tr.is-selected {
color: var(--hg-color-2); color: var(--secondary-color);
background-color: var(--hg-color); background-color: var(--main-color);
} }
// -- headings // -- headings
.title { .title {
text-transform: uppercase; text-transform: uppercase;
&.is-3 { margin-top: v.$mp-3; }
&.is-3 {
margin-top: v.$mp-3;
}
} }
@ -467,12 +464,6 @@ nav li {
// ---- responsive // ---- responsive
body { font-size: 1.4em; }
@media screen and (max-width: v.$screen-wide) {
body { font-size: 1em; }
}
@media screen and (max-width: v.$screen-normal) { @media screen and (max-width: v.$screen-normal) {
.page .container { .page .container {
margin-left: v.$mp-4; margin-left: v.$mp-4;
@ -485,5 +476,4 @@ body { font-size: 1.4em; }
margin-left: v.$mp-2; margin-left: v.$mp-2;
margin-right: v.$mp-2; margin-right: v.$mp-2;
} }
} }

View File

@ -23,7 +23,7 @@ $title-color: #000;
@import "~bulma/sass/elements/box"; @import "~bulma/sass/elements/box";
// @import "~bulma/sass/elements/button"; // @import "~bulma/sass/elements/button";
@import "~bulma/sass/elements/container"; @import "~bulma/sass/elements/container";
@import "~bulma/sass/elements/content"; // @import "~bulma/sass/elements/content";
@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";

View File

@ -0,0 +1,124 @@
<template>
<div class="a-select-file">
<div class="a-select-file-list" ref="list">
<div class="flex-column file-preview">
<div class="field flex-grow-1" v-if="!uploadFile">
<label class="label">{{ uploadLabel }}</label>
<input type="file" @change="previewFile"/>
</div>
<slot name="upload-preview" :item="uploadFile"></slot>
<div v-if="uploadFile">
<button class="button secondary" @click="removeUpload">
<span class="icon">
<i class="fa fa-trash"></i>
</span>
</button>
<button class="button float-right" @click="doUpload">
<span class="icon">
<i class="fa fa-upload"></i>
</span>
Upload
</button>
</div>
</div>
<div v-if="prevUrl">
<a href="#" @click="load(prevUrl)">
{{ prevLabel }}
</a>
</div>
<template v-for="item in items" v-bind:key="item.id">
<div :class="['file-preview', this.item && item.id == this.item.id && 'active']" @click="select(item)">
<slot :item="item"></slot>
</div>
</template>
<div v-if="nextUrl">
<a href="#" @click="load(nextUrl)">
{{ nextLabel }}
</a>
</div>
</div>
<div class="a-select-footer">
<slot name="footer" :item="item" :items="items"></slot>
</div>
</div>
</template>
<script>
import {getCsrf} from "../model"
export default {
props: {
name: { type: String },
prevLabel: { type: String, default: "Prev" },
nextLabel: { type: String, default: "Next" },
listUrl: { type: String },
uploadLabel: { type: String, default: "Upload a file" },
},
data() {
return {
item: null,
items: [],
uploadFile: null,
uploadUrl: null,
uploadFieldName: null,
uploadCSRF: null,
nextUrl: "",
prevUrl: "",
}
},
methods: {
previewFile(event) {
const [file] = event.target.files
this.uploadFile = file && {
file: file,
src: URL.createObjectURL(file)
}
},
removeUpload() {
this.uploadFile = null;
},
doUpload() {
const formData = new FormData();
formData.append('file', this.uploadFile.file)
formData.append('original_filename', this.uploadFile.file.name)
formData.append('csrfmiddlewaretoken', getCsrf())
fetch(this.listUrl, {
method: "POST",
body: formData
}).then(
() => {
this.uploadFile = null;
this.load()
}
)
},
load(url) {
fetch(url || this.listUrl).then(
response => response.ok ? response.json() : Promise.reject(response)
).then(data => {
this.nextUrl = data.next
this.prevUrl = data.previous
this.items = data.results
this.$forceUpdate()
this.$refs.list.scroll(0, 0)
})
},
select(item) {
this.item = item;
},
},
mounted() {
this.load()
},
}
</script>

View File

@ -1,5 +1,6 @@
<template> <template>
<button :title="ariaLabel" <button :title="ariaLabel"
type="button"
:aria-label="ariaLabel || label" :aria-description="ariaDescription" :aria-label="ariaLabel || label" :aria-description="ariaDescription"
@click="toggle" :class="buttonClass"> @click="toggle" :class="buttonClass">
<slot name="default" :active="active"> <slot name="default" :active="active">
@ -49,17 +50,21 @@ export default {
}, },
set(active) { set(active) {
if(this.el) {
const el = document.querySelector(this.el) const el = document.querySelector(this.el)
if(active) if(active)
el.classList.add(this.activeClass) el.classList.add(this.activeClass)
else else
el.classList.remove(this.activeClass) el.classList.remove(this.activeClass)
}
this.active = active this.active = active
if(active) if(active)
this.resetGroup() this.resetGroup()
}, },
resetGroup() { resetGroup() {
if(!this.groupClass)
return
const els = document.querySelectorAll("." + this.groupClass) const els = document.querySelectorAll("." + this.groupClass)
for(var el of els) for(var el of els)
if(el != this.$el) if(el != this.$el)

View File

@ -12,13 +12,15 @@ import ASoundItem from './ASoundItem.vue'
import ASwitch from './ASwitch.vue' import ASwitch from './ASwitch.vue'
import AStatistics from './AStatistics.vue' import AStatistics from './AStatistics.vue'
import AStreamer from './AStreamer.vue' import AStreamer from './AStreamer.vue'
import ASelectFile from "./ASelectFile.vue"
/** /**
* Core components * Core components
*/ */
export const base = { export const base = {
AAutocomplete, ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist, AAutocomplete, ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
AProgress, ASoundItem, ASwitch AProgress, ASoundItem, ASwitch,
ASelectFile,
} }
export default base export default base

View File

@ -30,6 +30,7 @@ export default class Live {
response.ok ? response.json() response.ok ? response.json()
: Promise.reject(response) : Promise.reject(response)
).then(data => { ).then(data => {
data = data.results
data.forEach(item => { data.forEach(item => {
if(item.start) item.start = new Date(item.start) if(item.start) item.start = new Date(item.start)
if(item.end) item.end = new Date(item.end) if(item.end) item.end = new Date(item.end)

View File

@ -252,3 +252,6 @@ TEMPLATES = [
WSGI_APPLICATION = "instance.wsgi.application" WSGI_APPLICATION = "instance.wsgi.application"
LOGOUT_REDIRECT_URL = "/" LOGOUT_REDIRECT_URL = "/"
REST_FRAMEWORK = {"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": 50}