update
This commit is contained in:
parent
cbfd1c1449
commit
b3ec6f670f
7
aircox/admin/__init__.py
Normal file
7
aircox/admin/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from .article import ArticleAdmin
|
||||||
|
from .episode import DiffusionAdmin, EpisodeAdmin
|
||||||
|
from .log import LogAdmin
|
||||||
|
from .program import ProgramAdmin, ScheduleAdmin, StreamAdmin
|
||||||
|
from .sound import SoundAdmin, TrackAdmin
|
||||||
|
from .station import StationAdmin
|
||||||
|
|
18
aircox/admin/article.py
Normal file
18
aircox/admin/article.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from ..models import Article
|
||||||
|
from .page import PageAdmin
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['ArticleAdmin']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Article)
|
||||||
|
class ArticleAdmin(PageAdmin):
|
||||||
|
list_filter = PageAdmin.list_filter
|
||||||
|
search_fields = PageAdmin.search_fields + ['parent__title']
|
||||||
|
# TODO: readonly field
|
||||||
|
|
||||||
|
|
58
aircox/admin/episode.py
Normal file
58
aircox/admin/episode.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
|
from ..models import Episode, Diffusion, Sound, Track
|
||||||
|
|
||||||
|
from .page import PageAdmin
|
||||||
|
from .sound import SoundInline, TracksInline
|
||||||
|
|
||||||
|
|
||||||
|
class DiffusionBaseAdmin:
|
||||||
|
fields = ['type', 'start', 'end']
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
fields = super().get_readonly_fields(request, obj)
|
||||||
|
if not request.user.has_perm('aircox_program.scheduling'):
|
||||||
|
fields += ['program', 'start', 'end']
|
||||||
|
return [field for field in fields if field in self.fields]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Diffusion)
|
||||||
|
class DiffusionAdmin(DiffusionBaseAdmin, admin.ModelAdmin):
|
||||||
|
def start_date(self, obj):
|
||||||
|
return obj.local_start.strftime('%Y/%m/%d %H:%M')
|
||||||
|
start_date.short_description = _('start')
|
||||||
|
|
||||||
|
def end_date(self, obj):
|
||||||
|
return obj.local_end.strftime('%H:%M')
|
||||||
|
end_date.short_description = _('end')
|
||||||
|
|
||||||
|
list_display = ('episode', 'start_date', 'end_date', 'type', 'initial')
|
||||||
|
list_filter = ('type', 'start', 'program')
|
||||||
|
list_editable = ('type',)
|
||||||
|
ordering = ('-start', 'id')
|
||||||
|
|
||||||
|
fields = ['type', 'start', 'end', 'initial', 'program']
|
||||||
|
|
||||||
|
|
||||||
|
class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline):
|
||||||
|
model = Diffusion
|
||||||
|
fk_name = 'episode'
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
def has_add_permission(self, request):
|
||||||
|
return request.user.has_perm('aircox_program.scheduling')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Episode)
|
||||||
|
class EpisodeAdmin(PageAdmin):
|
||||||
|
list_display = PageAdmin.list_display
|
||||||
|
list_filter = PageAdmin.list_filter
|
||||||
|
search_fields = PageAdmin.search_fields + ['parent__title']
|
||||||
|
# readonly_fields = ('parent',)
|
||||||
|
|
||||||
|
inlines = [TracksInline, SoundInline, DiffusionInline]
|
||||||
|
|
||||||
|
|
13
aircox/admin/log.py
Normal file
13
aircox/admin/log.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from ..models import Log
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['LogAdmin']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Log)
|
||||||
|
class LogAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['id', 'date', 'station', 'source', 'type', 'comment']
|
||||||
|
list_filter = ['date', 'source', 'station']
|
||||||
|
|
42
aircox/admin/mixins.py
Normal file
42
aircox/admin/mixins.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
class UnrelatedInlineMixin:
|
||||||
|
"""
|
||||||
|
Inline class that can be included in an admin change view whose model
|
||||||
|
is not directly related to inline's model.
|
||||||
|
"""
|
||||||
|
view_model = None
|
||||||
|
parent_model = None
|
||||||
|
parent_fk = ''
|
||||||
|
|
||||||
|
def __init__(self, parent_model, admin_site):
|
||||||
|
self.view_model = parent_model
|
||||||
|
super().__init__(self.parent_model, admin_site)
|
||||||
|
|
||||||
|
def get_parent(self, view_obj):
|
||||||
|
""" Get formset's instance from `obj` of AdminSite's change form. """
|
||||||
|
field = self.parent_model._meta.get_field(self.parent_fk).remote_field
|
||||||
|
return getattr(view_obj, field.name, None)
|
||||||
|
|
||||||
|
def save_parent(self, parent, view_obj):
|
||||||
|
""" Save formset's instance. """
|
||||||
|
setattr(parent, self.parent_fk, view_obj)
|
||||||
|
parent.save()
|
||||||
|
return parent
|
||||||
|
|
||||||
|
def get_formset(self, request, obj):
|
||||||
|
ParentFormSet = super().get_formset(request, obj)
|
||||||
|
inline = self
|
||||||
|
class FormSet(ParentFormSet):
|
||||||
|
view_obj = None
|
||||||
|
|
||||||
|
def __init__(self, *args, instance=None, **kwargs):
|
||||||
|
self.view_obj = instance
|
||||||
|
instance = inline.get_parent(instance)
|
||||||
|
self.instance = instance
|
||||||
|
super().__init__(*args, instance=instance, **kwargs)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
inline.save_parent(self.instance, self.view_obj)
|
||||||
|
return super().save()
|
||||||
|
return FormSet
|
||||||
|
|
||||||
|
|
52
aircox/admin/page.py
Normal file
52
aircox/admin/page.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from adminsortable2.admin import SortableInlineAdminMixin
|
||||||
|
|
||||||
|
from ..models import Category, Article, NavItem
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['CategoryAdmin', 'PageAdmin', 'NavItemInline']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Category)
|
||||||
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['pk', 'title', 'slug']
|
||||||
|
list_editable = ['title', 'slug']
|
||||||
|
search_fields = ['title']
|
||||||
|
fields = ['title', 'slug']
|
||||||
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
|
|
||||||
|
|
||||||
|
# limit category choice
|
||||||
|
class PageAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('cover_thumb', 'title', 'status', 'category', 'parent')
|
||||||
|
list_display_links = ('cover_thumb', 'title')
|
||||||
|
list_editable = ('status', 'category')
|
||||||
|
list_filter = ('status', 'category')
|
||||||
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
|
|
||||||
|
search_fields = ['title', 'category__title']
|
||||||
|
fieldsets = [
|
||||||
|
('', {
|
||||||
|
'fields': ['title', 'slug', 'category', 'cover', 'content'],
|
||||||
|
}),
|
||||||
|
(_('Publication Settings'), {
|
||||||
|
'fields': ['featured', 'allow_comments', 'status', 'parent'],
|
||||||
|
'classes': ('collapse',),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
change_form_template = 'admin/aircox/page_change_form.html'
|
||||||
|
|
||||||
|
def cover_thumb(self, obj):
|
||||||
|
return mark_safe('<img src="{}"/>'.format(obj.cover.icons['64'])) \
|
||||||
|
if obj.cover else ''
|
||||||
|
|
||||||
|
|
||||||
|
class NavItemInline(SortableInlineAdminMixin, admin.TabularInline):
|
||||||
|
model = NavItem
|
||||||
|
|
||||||
|
|
||||||
|
|
76
aircox/admin/program.py
Normal file
76
aircox/admin/program.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from ..models import Program, Schedule, Stream
|
||||||
|
from .page import PageAdmin
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleInline(admin.TabularInline):
|
||||||
|
model = Schedule
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
class StreamInline(admin.TabularInline):
|
||||||
|
fields = ['delay', 'begin', 'end']
|
||||||
|
model = Stream
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Program)
|
||||||
|
class ProgramAdmin(PageAdmin):
|
||||||
|
def schedule(self, obj):
|
||||||
|
return Schedule.objects.filter(program=obj).count() > 0
|
||||||
|
|
||||||
|
schedule.boolean = True
|
||||||
|
schedule.short_description = _("Schedule")
|
||||||
|
|
||||||
|
list_display = PageAdmin.list_display + ('schedule', 'station', 'active')
|
||||||
|
list_filter = PageAdmin.list_filter + ('station', 'active')
|
||||||
|
fieldsets = deepcopy(PageAdmin.fieldsets) + [
|
||||||
|
(_('Program Settings'), {
|
||||||
|
'fields': ['active', 'station', 'sync'],
|
||||||
|
'classes': ('collapse',),
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
prepopulated_fields = {'slug': ('title',)}
|
||||||
|
search_fields = ['title']
|
||||||
|
|
||||||
|
inlines = [ScheduleInline, StreamInline]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Schedule)
|
||||||
|
class ScheduleAdmin(admin.ModelAdmin):
|
||||||
|
def program_title(self, obj):
|
||||||
|
return obj.program.title
|
||||||
|
program_title.short_description = _('Program')
|
||||||
|
|
||||||
|
def freq(self, obj):
|
||||||
|
return obj.get_frequency_verbose()
|
||||||
|
freq.short_description = _('Day')
|
||||||
|
|
||||||
|
def rerun(self, obj):
|
||||||
|
return obj.initial is not None
|
||||||
|
rerun.short_description = _('Rerun')
|
||||||
|
rerun.boolean = True
|
||||||
|
|
||||||
|
list_filter = ['frequency', 'program']
|
||||||
|
list_display = ['program_title', 'freq', 'time', 'timezone', 'duration',
|
||||||
|
'rerun']
|
||||||
|
list_editable = ['time', 'duration']
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
if obj:
|
||||||
|
return ['program', 'date', 'frequency']
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Stream)
|
||||||
|
class StreamAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'program', 'delay', 'begin', 'end')
|
||||||
|
|
||||||
|
|
||||||
|
|
65
aircox/admin/sound.py
Normal file
65
aircox/admin/sound.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
|
from adminsortable2.admin import SortableInlineAdminMixin
|
||||||
|
|
||||||
|
from ..models import Sound, Track
|
||||||
|
|
||||||
|
|
||||||
|
class TracksInline(SortableInlineAdminMixin, admin.TabularInline):
|
||||||
|
template = 'admin/aircox/playlist_inline.html'
|
||||||
|
model = Track
|
||||||
|
extra = 0
|
||||||
|
fields = ('position', 'artist', 'title', 'info', 'timestamp', 'tags')
|
||||||
|
|
||||||
|
list_display = ['artist', 'title', 'tags', 'related']
|
||||||
|
list_filter = ['artist', 'title', 'tags']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SoundInline(admin.TabularInline):
|
||||||
|
model = Sound
|
||||||
|
fields = ['type', 'path', 'embed', 'duration', 'is_public']
|
||||||
|
readonly_fields = ['type', 'path', 'duration']
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Sound)
|
||||||
|
class SoundAdmin(admin.ModelAdmin):
|
||||||
|
def filename(self, obj):
|
||||||
|
return '/'.join(obj.path.split('/')[-2:])
|
||||||
|
filename.short_description=_('file')
|
||||||
|
|
||||||
|
fields = None
|
||||||
|
list_display = ['id', 'name', 'program', 'type', 'duration',
|
||||||
|
'is_public', 'is_good_quality', 'episode', 'filename']
|
||||||
|
list_filter = ('program', 'type', 'is_good_quality', 'is_public')
|
||||||
|
|
||||||
|
search_fields = ['name', 'program']
|
||||||
|
fieldsets = [
|
||||||
|
(None, {'fields': ['name', 'path', 'type', 'program', 'episode']}),
|
||||||
|
(None, {'fields': ['embed', 'duration', 'is_public', 'mtime']}),
|
||||||
|
(None, {'fields': ['is_good_quality']})
|
||||||
|
]
|
||||||
|
readonly_fields = ('path', 'duration',)
|
||||||
|
inlines = [TracksInline]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Track)
|
||||||
|
class TrackAdmin(admin.ModelAdmin):
|
||||||
|
def tag_list(self, obj):
|
||||||
|
return u", ".join(o.name for o in obj.tags.all())
|
||||||
|
|
||||||
|
list_display = ['pk', 'artist', 'title', 'tag_list', 'episode', 'sound', 'timestamp']
|
||||||
|
list_editable = ['artist', 'title']
|
||||||
|
list_filter = ['artist', 'title', 'tags']
|
||||||
|
|
||||||
|
search_fields = ['artist', 'title']
|
||||||
|
fieldsets = [
|
||||||
|
(_('Playlist'), {'fields': ['episode', 'sound', 'position', 'timestamp']}),
|
||||||
|
(_('Info'), {'fields': ['artist', 'title', 'info', 'tags']}),
|
||||||
|
]
|
||||||
|
|
||||||
|
# TODO on edit: readonly_fields = ['episode', 'sound']
|
||||||
|
|
||||||
|
|
15
aircox/admin/station.py
Normal file
15
aircox/admin/station.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from ..models import Station
|
||||||
|
from .page import NavItemInline
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['StationAdmin']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Station)
|
||||||
|
class StationAdmin(admin.ModelAdmin):
|
||||||
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
|
inlines = (NavItemInline,)
|
||||||
|
|
||||||
|
|
56
aircox/admin_site.py
Normal file
56
aircox/admin_site.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include, reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
from .models import Program
|
||||||
|
from .views.admin import StatisticsView
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['AdminSite']
|
||||||
|
|
||||||
|
|
||||||
|
class AdminSite(admin.AdminSite):
|
||||||
|
extra_urls = None
|
||||||
|
tools = [
|
||||||
|
(_('Statistics'), 'admin:tools-stats'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.router = DefaultRouter()
|
||||||
|
self.extra_urls = []
|
||||||
|
self.tools = type(self).tools.copy()
|
||||||
|
|
||||||
|
def each_context(self, request):
|
||||||
|
context = super().each_context(request)
|
||||||
|
context.update({
|
||||||
|
'programs': Program.objects.all().active().values('pk', 'title'),
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_urls(self):
|
||||||
|
urls = super().get_urls() + [
|
||||||
|
path('api/', include((self.router.urls, 'api'))),
|
||||||
|
path('tools/statistics/',
|
||||||
|
self.admin_view(StatisticsView.as_view()),
|
||||||
|
name='tools-stats'),
|
||||||
|
path('tools/statistics/<date:date>/',
|
||||||
|
self.admin_view(StatisticsView.as_view()),
|
||||||
|
name='tools-stats'),
|
||||||
|
] + self.extra_urls
|
||||||
|
return urls
|
||||||
|
|
||||||
|
def get_tools(self):
|
||||||
|
return [(label, reverse(url)) for label, url in self.tools]
|
||||||
|
|
||||||
|
def route_view(self, url, view, name, admin_view=True, label=None):
|
||||||
|
self.extra_urls.append(path(
|
||||||
|
url, self.admin_view(view) if admin_view else view, name=name
|
||||||
|
))
|
||||||
|
|
||||||
|
if label:
|
||||||
|
self.tools.append((label, 'admin:' + name))
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -34,6 +34,9 @@ class SoundQuerySet(models.QuerySet):
|
||||||
id = diffusion.pk if id is None else id
|
id = diffusion.pk if id is None else id
|
||||||
return self.filter(episode__diffusion__id=id)
|
return self.filter(episode__diffusion__id=id)
|
||||||
|
|
||||||
|
def available(self):
|
||||||
|
return self.exclude(type=Sound.TYPE_REMOVED)
|
||||||
|
|
||||||
def podcasts(self):
|
def podcasts(self):
|
||||||
""" Return sounds available as podcasts """
|
""" Return sounds available as podcasts """
|
||||||
return self.filter(Q(embed__isnull=False) | Q(is_public=True))
|
return self.filter(Q(embed__isnull=False) | Q(is_public=True))
|
||||||
|
@ -87,6 +90,9 @@ class Sound(models.Model):
|
||||||
verbose_name=_('episode'),
|
verbose_name=_('episode'),
|
||||||
)
|
)
|
||||||
type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
|
type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
|
||||||
|
position = models.PositiveSmallIntegerField(
|
||||||
|
_('order'), default=0, help_text=_('position in the playlist'),
|
||||||
|
)
|
||||||
# FIXME: url() does not use the same directory than here
|
# FIXME: url() does not use the same directory than here
|
||||||
# should we use FileField for more reliability?
|
# should we use FileField for more reliability?
|
||||||
path = models.FilePathField(
|
path = models.FilePathField(
|
||||||
|
@ -278,9 +284,7 @@ class Track(models.Model):
|
||||||
verbose_name=_('sound'),
|
verbose_name=_('sound'),
|
||||||
)
|
)
|
||||||
position = models.PositiveSmallIntegerField(
|
position = models.PositiveSmallIntegerField(
|
||||||
_('order'),
|
_('order'), default=0, help_text=_('position in the playlist'),
|
||||||
default=0,
|
|
||||||
help_text=_('position in the playlist'),
|
|
||||||
)
|
)
|
||||||
timestamp = models.PositiveSmallIntegerField(
|
timestamp = models.PositiveSmallIntegerField(
|
||||||
_('timestamp'),
|
_('timestamp'),
|
||||||
|
|
|
@ -282,7 +282,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue_
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
eval("__webpack_require__.r(__webpack_exports__);\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (class {\n constructor(url, timeout) {\n this.url = url;\n this.timeout = timeout;\n this.promise = null;\n this.items = [];\n }\n\n drop() {\n this.promise = null;\n }\n\n fetch() {\n const promise = fetch(this.url).then(response =>\n response.ok ? response.json()\n : Promise.reject(response)\n ).then(data => {\n this.items = data;\n return this.items\n })\n\n this.promise = promise;\n return promise;\n }\n\n refresh() {\n const promise = this.fetch();\n promise.then(data => {\n if(promise != this.promise)\n return [];\n\n window.setTimeout(() => this.refresh(), this.timeout*1000)\n })\n return promise\n }\n});\n\n\n//# sourceURL=webpack:///./assets/public/liveInfo.js?");
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var public_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! public/utils */ \"./assets/public/utils.js\");\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (class {\n constructor(url, timeout) {\n this.url = url;\n this.timeout = timeout;\n this.promise = null;\n this.items = [];\n }\n\n drop() {\n this.promise = null;\n }\n\n fetch() {\n const promise = fetch(this.url).then(response =>\n response.ok ? response.json()\n : Promise.reject(response)\n ).then(data => {\n this.items = data;\n return this.items\n })\n\n this.promise = promise;\n return promise;\n }\n\n refresh() {\n const promise = this.fetch();\n promise.then(data => {\n if(promise != this.promise)\n return [];\n\n Object(public_utils__WEBPACK_IMPORTED_MODULE_0__[\"setEcoTimeout\"])(() => this.refresh(), this.timeout*1000)\n })\n return promise\n }\n});\n\n\n//# sourceURL=webpack:///./assets/public/liveInfo.js?");
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
@ -333,6 +333,18 @@ eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./asse
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./assets/public/utils.js":
|
||||||
|
/*!********************************!*\
|
||||||
|
!*** ./assets/public/utils.js ***!
|
||||||
|
\********************************/
|
||||||
|
/*! exports provided: setEcoTimeout, setEcoInterval */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoTimeout\", function() { return setEcoTimeout; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoInterval\", function() { return setEcoInterval; });\n\n\nfunction setEcoTimeout(func, ...args) {\n return setTimeout((...args) => {\n !document.hidden && func(...args)\n }, ...args)\n}\n\n\nfunction setEcoInterval(func, ...args) {\n return setInterval((...args) => {\n !document.hidden && func(...args)\n }, ...args)\n}\n\n\n\n//# sourceURL=webpack:///./assets/public/utils.js?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/admin/statistics.vue?vue&type=script&lang=js&":
|
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/admin/statistics.vue?vue&type=script&lang=js&":
|
||||||
/*!****************************************************************************************************************!*\
|
/*!****************************************************************************************************************!*\
|
||||||
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/admin/statistics.vue?vue&type=script&lang=js& ***!
|
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/admin/statistics.vue?vue&type=script&lang=js& ***!
|
||||||
|
|
|
@ -223,7 +223,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue_
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
eval("__webpack_require__.r(__webpack_exports__);\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (class {\n constructor(url, timeout) {\n this.url = url;\n this.timeout = timeout;\n this.promise = null;\n this.items = [];\n }\n\n drop() {\n this.promise = null;\n }\n\n fetch() {\n const promise = fetch(this.url).then(response =>\n response.ok ? response.json()\n : Promise.reject(response)\n ).then(data => {\n this.items = data;\n return this.items\n })\n\n this.promise = promise;\n return promise;\n }\n\n refresh() {\n const promise = this.fetch();\n promise.then(data => {\n if(promise != this.promise)\n return [];\n\n window.setTimeout(() => this.refresh(), this.timeout*1000)\n })\n return promise\n }\n});\n\n\n//# sourceURL=webpack:///./assets/public/liveInfo.js?");
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var public_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! public/utils */ \"./assets/public/utils.js\");\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (class {\n constructor(url, timeout) {\n this.url = url;\n this.timeout = timeout;\n this.promise = null;\n this.items = [];\n }\n\n drop() {\n this.promise = null;\n }\n\n fetch() {\n const promise = fetch(this.url).then(response =>\n response.ok ? response.json()\n : Promise.reject(response)\n ).then(data => {\n this.items = data;\n return this.items\n })\n\n this.promise = promise;\n return promise;\n }\n\n refresh() {\n const promise = this.fetch();\n promise.then(data => {\n if(promise != this.promise)\n return [];\n\n Object(public_utils__WEBPACK_IMPORTED_MODULE_0__[\"setEcoTimeout\"])(() => this.refresh(), this.timeout*1000)\n })\n return promise\n }\n});\n\n\n//# sourceURL=webpack:///./assets/public/liveInfo.js?");
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
@ -274,6 +274,18 @@ eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./asse
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./assets/public/utils.js":
|
||||||
|
/*!********************************!*\
|
||||||
|
!*** ./assets/public/utils.js ***!
|
||||||
|
\********************************/
|
||||||
|
/*! exports provided: setEcoTimeout, setEcoInterval */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoTimeout\", function() { return setEcoTimeout; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoInterval\", function() { return setEcoInterval; });\n\n\nfunction setEcoTimeout(func, ...args) {\n return setTimeout((...args) => {\n !document.hidden && func(...args)\n }, ...args)\n}\n\n\nfunction setEcoInterval(func, ...args) {\n return setInterval((...args) => {\n !document.hidden && func(...args)\n }, ...args)\n}\n\n\n\n//# sourceURL=webpack:///./assets/public/utils.js?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/public/autocomplete.vue?vue&type=script&lang=js&":
|
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/public/autocomplete.vue?vue&type=script&lang=js&":
|
||||||
/*!*******************************************************************************************************************!*\
|
/*!*******************************************************************************************************************!*\
|
||||||
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/public/autocomplete.vue?vue&type=script&lang=js& ***!
|
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/public/autocomplete.vue?vue&type=script&lang=js& ***!
|
||||||
|
|
230
aircox/static/aircox/streamer.js
Normal file
230
aircox/static/aircox/streamer.js
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
/******/ (function(modules) { // webpackBootstrap
|
||||||
|
/******/ // install a JSONP callback for chunk loading
|
||||||
|
/******/ function webpackJsonpCallback(data) {
|
||||||
|
/******/ var chunkIds = data[0];
|
||||||
|
/******/ var moreModules = data[1];
|
||||||
|
/******/ var executeModules = data[2];
|
||||||
|
/******/
|
||||||
|
/******/ // add "moreModules" to the modules object,
|
||||||
|
/******/ // then flag all "chunkIds" as loaded and fire callback
|
||||||
|
/******/ var moduleId, chunkId, i = 0, resolves = [];
|
||||||
|
/******/ for(;i < chunkIds.length; i++) {
|
||||||
|
/******/ chunkId = chunkIds[i];
|
||||||
|
/******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
|
||||||
|
/******/ resolves.push(installedChunks[chunkId][0]);
|
||||||
|
/******/ }
|
||||||
|
/******/ installedChunks[chunkId] = 0;
|
||||||
|
/******/ }
|
||||||
|
/******/ for(moduleId in moreModules) {
|
||||||
|
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
|
||||||
|
/******/ modules[moduleId] = moreModules[moduleId];
|
||||||
|
/******/ }
|
||||||
|
/******/ }
|
||||||
|
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
|
||||||
|
/******/
|
||||||
|
/******/ while(resolves.length) {
|
||||||
|
/******/ resolves.shift()();
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/ // add entry modules from loaded chunk to deferred list
|
||||||
|
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
|
||||||
|
/******/
|
||||||
|
/******/ // run deferred modules when all chunks ready
|
||||||
|
/******/ return checkDeferredModules();
|
||||||
|
/******/ };
|
||||||
|
/******/ function checkDeferredModules() {
|
||||||
|
/******/ var result;
|
||||||
|
/******/ for(var i = 0; i < deferredModules.length; i++) {
|
||||||
|
/******/ var deferredModule = deferredModules[i];
|
||||||
|
/******/ var fulfilled = true;
|
||||||
|
/******/ for(var j = 1; j < deferredModule.length; j++) {
|
||||||
|
/******/ var depId = deferredModule[j];
|
||||||
|
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
|
||||||
|
/******/ }
|
||||||
|
/******/ if(fulfilled) {
|
||||||
|
/******/ deferredModules.splice(i--, 1);
|
||||||
|
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
|
||||||
|
/******/ }
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/ return result;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/ // The module cache
|
||||||
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
|
/******/ // object to store loaded and loading chunks
|
||||||
|
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||||
|
/******/ // Promise = chunk loading, 0 = chunk loaded
|
||||||
|
/******/ var installedChunks = {
|
||||||
|
/******/ "streamer": 0
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ var deferredModules = [];
|
||||||
|
/******/
|
||||||
|
/******/ // The require function
|
||||||
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
|
/******/ // Check if module is in cache
|
||||||
|
/******/ if(installedModules[moduleId]) {
|
||||||
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
|
/******/ // Create a new module (and put it into the cache)
|
||||||
|
/******/ var module = installedModules[moduleId] = {
|
||||||
|
/******/ i: moduleId,
|
||||||
|
/******/ l: false,
|
||||||
|
/******/ exports: {}
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Execute the module function
|
||||||
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
|
/******/ // Return the exports of the module
|
||||||
|
/******/ return module.exports;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
|
/******/ // expose the module cache
|
||||||
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
|
/******/ // __webpack_public_path__
|
||||||
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
|
||||||
|
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
|
||||||
|
/******/ jsonpArray.push = webpackJsonpCallback;
|
||||||
|
/******/ jsonpArray = jsonpArray.slice();
|
||||||
|
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
|
||||||
|
/******/ var parentJsonpFunction = oldJsonpFunction;
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // add entry module to deferred list
|
||||||
|
/******/ deferredModules.push(["./assets/streamer/index.js","vendor"]);
|
||||||
|
/******/ // run deferred modules when ready
|
||||||
|
/******/ return checkDeferredModules();
|
||||||
|
/******/ })
|
||||||
|
/************************************************************************/
|
||||||
|
/******/ ({
|
||||||
|
|
||||||
|
/***/ "./assets/public/app.js":
|
||||||
|
/*!******************************!*\
|
||||||
|
!*** ./assets/public/app.js ***!
|
||||||
|
\******************************/
|
||||||
|
/*! exports provided: appBaseConfig, setAppConfig, getAppConfig, loadApp */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"appBaseConfig\", function() { return appBaseConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setAppConfig\", function() { return setAppConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getAppConfig\", function() { return getAppConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"loadApp\", function() { return loadApp; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\n\nconst appBaseConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n}\n\n/**\n * Application config for the main application instance\n */\nvar appConfig = {};\n\nfunction setAppConfig(config) {\n for(var member in appConfig) delete appConfig[member];\n return Object.assign(appConfig, config)\n}\n\nfunction getAppConfig(config) {\n if(config instanceof Function)\n config = config()\n config = config == null ? appConfig : config;\n return {...appBaseConfig, ...config}\n}\n\n\n/**\n * Create Vue application at window 'load' event and return a Promise\n * resolving to the created app.\n *\n * config: defaults to appConfig (checked when window is loaded)\n */\nfunction loadApp(config=null) {\n return new Promise(function(resolve, reject) {\n window.addEventListener('load', function() {\n try {\n config = getAppConfig(config)\n const el = document.querySelector(config.el)\n if(!el) {\n reject(`Error: missing element ${config.el}`);\n return;\n }\n\n resolve(new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config))\n }\n catch(error) { reject(error) }\n })\n })\n}\n\n\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./assets/public/model.js":
|
||||||
|
/*!********************************!*\
|
||||||
|
!*** ./assets/public/model.js ***!
|
||||||
|
\********************************/
|
||||||
|
/*! exports provided: getCsrf, default */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getCsrf\", function() { return getCsrf; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Model; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nfunction getCookie(name) {\n if(document.cookie && document.cookie !== '') {\n const cookie = document.cookie.split(';')\n .find(c => c.trim().startsWith(name + '='))\n return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;\n }\n return null;\n}\n\nvar csrfToken = null;\n\nfunction getCsrf() {\n if(csrfToken === null)\n csrfToken = getCookie('csrftoken')\n return csrfToken;\n}\n\n\n// TODO: move in another module for reuse\n// TODO: prevent duplicate simple fetch\nclass Model {\n constructor(data, {url=null}={}) {\n this.commit(data);\n }\n\n static getId(data) {\n return data.id;\n }\n\n static getOptions(options) {\n return {\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n 'X-CSRFToken': getCsrf(),\n },\n ...options,\n }\n }\n\n static fetch(url, options=null, initArgs=null) {\n options = this.getOptions(options)\n return fetch(url, options)\n .then(response => response.json())\n .then(data => new this(d, {url: url, ...initArgs}));\n }\n\n static fetchAll(url, options=null, initArgs=null) {\n options = this.getOptions(options)\n return fetch(url, options)\n .then(response => response.json())\n .then(data => {\n if(!(data instanceof Array))\n data = data.results;\n data = data.map(d => new this(d, {baseUrl: url, ...initArgs}));\n return data\n })\n }\n\n /**\n * Fetch data from server.\n */\n fetch(options) {\n options = this.constructor.getOptions(options)\n return fetch(this.url, options)\n .then(response => response.json())\n .then(data => this.commit(data));\n }\n\n /**\n * Call API action on object.\n */\n action(path, options, commit=false) {\n options = this.constructor.getOptions(options)\n const promise = fetch(this.url + path, options);\n return commit ? promise.then(data => data.json())\n .then(data => { this.commit(data); this.data })\n : promise;\n }\n\n /**\n * Update instance's data with provided data. Return None\n */\n commit(data) {\n this.id = this.constructor.getId(data);\n this.url = data.url_;\n vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'data', data);\n }\n\n /**\n * Return data as model with url prepent by `this.url`.\n */\n asChild(model, data, prefix='') {\n return new model(data, {baseUrl: `${this.url}${prefix}/`})\n }\n\n getChildOf(attr, id) {\n const index = this.data[attr].findIndex(o => o.id = id)\n return index == -1 ? null : this.data[attr][index];\n }\n\n static updateList(list=[], old=[], ...initArgs) {\n return list.reduce((items, data) => {\n const id = this.getId(data);\n let [index, obj] = [old.findIndex(o => o.id == id), null];\n if(index != -1) {\n old[index].commit(data)\n items.push(old[index]);\n }\n else\n items.push(new this(data, ...initArgs))\n return items;\n }, [])\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/model.js?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./assets/public/sound.js":
|
||||||
|
/*!********************************!*\
|
||||||
|
!*** ./assets/public/sound.js ***!
|
||||||
|
\********************************/
|
||||||
|
/*! exports provided: default */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Sound; });\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n\n\n\nclass Sound extends _model__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n get name() { return this.data.name }\n\n static getId(data) { return data.pk }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/sound.js?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./assets/public/utils.js":
|
||||||
|
/*!********************************!*\
|
||||||
|
!*** ./assets/public/utils.js ***!
|
||||||
|
\********************************/
|
||||||
|
/*! exports provided: setEcoTimeout, setEcoInterval */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoTimeout\", function() { return setEcoTimeout; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoInterval\", function() { return setEcoInterval; });\n\n\nfunction setEcoTimeout(func, ...args) {\n return setTimeout((...args) => {\n !document.hidden && func(...args)\n }, ...args)\n}\n\n\nfunction setEcoInterval(func, ...args) {\n return setInterval((...args) => {\n !document.hidden && func(...args)\n }, ...args)\n}\n\n\n\n//# sourceURL=webpack:///./assets/public/utils.js?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./assets/streamer/controllers.js":
|
||||||
|
/*!****************************************!*\
|
||||||
|
!*** ./assets/streamer/controllers.js ***!
|
||||||
|
\****************************************/
|
||||||
|
/*! exports provided: Streamer, Request, Source, Playlist, Queue */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Streamer\", function() { return Streamer; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Request\", function() { return Request; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Source\", function() { return Source; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Playlist\", function() { return Playlist; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Queue\", function() { return Queue; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var public_model__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! public/model */ \"./assets/public/model.js\");\n/* harmony import */ var public_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! public/utils */ \"./assets/public/utils.js\");\n\n\n\n\n\n\nclass Streamer extends public_model__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\n get queues() { return this.data ? this.data.queues : []; }\n get playlists() { return this.data ? this.data.playlists : []; }\n get sources() { return [...this.queues, ...this.playlists]; }\n get source() { return this.sources.find(o => o.id == this.data.source) }\n\n commit(data) {\n if(!this.data)\n this.data = { id: data.id, playlists: [], queues: [] }\n\n data.playlists = Playlist.updateList(data.playlists, this.playlists, {streamer: this})\n data.queues = Queue.updateList(data.queues, this.queues, {streamer: this})\n super.commit(data)\n }\n}\n\nclass Request extends public_model__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\n static getId(data) { return data.rid; }\n}\n\nclass Source extends public_model__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\n constructor(data, {streamer=null, ...options}={}) {\n super(data, options);\n this.streamer = streamer;\n Object(public_utils__WEBPACK_IMPORTED_MODULE_2__[\"setEcoInterval\"])(() => this.tick(), 1000)\n }\n\n get isQueue() { return false; }\n get isPlaylist() { return false; }\n get isPlaying() { return this.data.status == 'playing' }\n get isPaused() { return this.data.status == 'paused' }\n\n get remainingString() {\n if(!this.remaining)\n return '00:00';\n\n const seconds = Math.floor(this.remaining % 60);\n const minutes = Math.floor(this.remaining / 60);\n return String(minutes).padStart(2, '0') + ':' +\n String(seconds).padStart(2, '0');\n }\n\n sync() { return this.action('sync/', {method: 'POST'}, true); }\n skip() { return this.action('skip/', {method: 'POST'}, true); }\n restart() { return this.action('restart/', {method: 'POST'}, true); }\n\n seek(count) {\n return this.action('seek/', {\n method: 'POST',\n body: JSON.stringify({count: count})\n }, true)\n }\n\n tick() {\n if(!this.data.remaining || !this.isPlaying)\n return;\n const delta = (Date.now() - this.commitDate) / 1000;\n vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'remaining', this.data.remaining - delta)\n }\n\n commit(data) {\n if(data.air_time)\n data.air_time = new Date(data.air_time);\n\n this.commitDate = Date.now()\n super.commit(data)\n vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'remaining', data.remaining)\n }\n}\n\n\nclass Playlist extends Source {\n get isPlaylist() { return true; }\n}\n\n\nclass Queue extends Source {\n get isQueue() { return true; }\n get queue() { return this.data && this.data.queue; }\n\n commit(data) {\n data.queue = Request.updateList(data.queue, this.queue)\n super.commit(data)\n }\n\n push(soundId) {\n return this.action('push/', {\n method: 'POST',\n body: JSON.stringify({'sound_id': parseInt(soundId)})\n }, true);\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/streamer/controllers.js?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./assets/streamer/index.js":
|
||||||
|
/*!**********************************!*\
|
||||||
|
!*** ./assets/streamer/index.js ***!
|
||||||
|
\**********************************/
|
||||||
|
/*! no exports provided */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var buefy_dist_components_button__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! buefy/dist/components/button */ \"./node_modules/buefy/dist/components/button/index.js\");\n/* harmony import */ var buefy_dist_components_button__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(buefy_dist_components_button__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var public_app__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! public/app */ \"./assets/public/app.js\");\n/* harmony import */ var public_model__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! public/model */ \"./assets/public/model.js\");\n/* harmony import */ var public_sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! public/sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _controllers__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./controllers */ \"./assets/streamer/controllers.js\");\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(buefy_dist_components_button__WEBPACK_IMPORTED_MODULE_1___default.a)\n\n\n\n\n\n\nwindow.aircox.appConfig = {\n data() {\n return {\n // current streamer\n streamer: null,\n // all streamers\n streamers: [],\n // fetch interval id\n fetchInterval: null,\n\n Sound: public_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n }\n },\n\n computed: {\n apiUrl() {\n return this.$el && this.$el.dataset.apiUrl;\n },\n\n sources() {\n var sources = this.streamer ? this.streamer.sources : [];\n return sources.filter(s => s.data)\n },\n },\n\n methods: {\n fetchStreamers() {\n _controllers__WEBPACK_IMPORTED_MODULE_5__[\"Streamer\"].fetchAll(this.apiUrl, null)\n .then(streamers => {\n vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'streamers', streamers);\n vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'streamer', streamers ? streamers[0] : null);\n })\n },\n },\n\n mounted() {\n this.fetchStreamers();\n this.fetchInterval = setInterval(() => this.streamer && this.streamer.fetch(), 5000)\n },\n\n destroyed() {\n if(this.fetchInterval !== null)\n clearInterval(this.fetchInterval)\n }\n}\n\n\n\n//# sourceURL=webpack:///./assets/streamer/index.js?");
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
|
||||||
|
/******/ });
|
|
@ -133,7 +133,7 @@ Blocks:
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
<div id="player">{% include "aircox/player.html" %}</div>
|
<div id="player">{% include "aircox/widgets/player.html" %}</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
<a-player ref="player" src="{{ audio_streams.0 }}"
|
<a-player ref="player" src="{{ audio_streams.0 }}"
|
||||||
live-info-url="{% url "api-live" %}" :live-info-timeout="20"
|
live-info-url="{% url "api:live" %}" :live-info-timeout="20"
|
||||||
button-title="{% trans "Play or pause audio" %}">
|
button-title="{% trans "Play or pause audio" %}">
|
||||||
<template v-slot:sources>
|
<template v-slot:sources>
|
||||||
{% for stream in audio_streams %}
|
{% for stream in audio_streams %}
|
9
aircox/templatetags/aircox_admin.py
Normal file
9
aircox/templatetags/aircox_admin.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django import template
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.simple_tag(name='get_admin_tools')
|
||||||
|
def do_get_admin_tools():
|
||||||
|
return admin.site.get_tools()
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
from django.urls import include, path, register_converter
|
from django.urls import include, path, register_converter
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from . import views, models
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
from . import models, views, viewsets
|
||||||
from .converters import PagePathConverter, DateConverter, WeekConverter
|
from .converters import PagePathConverter, DateConverter, WeekConverter
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['api', 'urls']
|
||||||
|
|
||||||
|
|
||||||
register_converter(PagePathConverter, 'page_path')
|
register_converter(PagePathConverter, 'page_path')
|
||||||
register_converter(DateConverter, 'date')
|
register_converter(DateConverter, 'date')
|
||||||
register_converter(WeekConverter, 'week')
|
register_converter(WeekConverter, 'week')
|
||||||
|
@ -17,14 +22,18 @@ register_converter(WeekConverter, 'week')
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register('sound', viewsets.SoundViewSet, basename='sound')
|
||||||
|
|
||||||
|
|
||||||
api = [
|
api = [
|
||||||
path('logs/', views.api.LogListAPIView.as_view(), name='api-live'),
|
path('logs/', views.LogListAPIView.as_view(), name='live'),
|
||||||
]
|
] + router.urls
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
path('', views.HomeView.as_view(), name='home'),
|
path('', views.HomeView.as_view(), name='home'),
|
||||||
path('api/', include(api)),
|
path('api/', include((api, 'aircox'), namespace='api')),
|
||||||
|
|
||||||
# path('', views.PageDetailView.as_view(model=models.Article),
|
# path('', views.PageDetailView.as_view(model=models.Article),
|
||||||
# name='home'),
|
# name='home'),
|
||||||
|
@ -61,6 +70,5 @@ urls = [
|
||||||
views.ArticleListView.as_view(), name='article-list'),
|
views.ArticleListView.as_view(), name='article-list'),
|
||||||
path(_('programs/<slug:parent_slug>/publications/'),
|
path(_('programs/<slug:parent_slug>/publications/'),
|
||||||
views.ProgramPageListView.as_view(), name='program-page-list'),
|
views.ProgramPageListView.as_view(), name='program-page-list'),
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from . import api, admin
|
from . import admin
|
||||||
|
|
||||||
from .base import BaseView
|
from .base import BaseView, BaseAPIView
|
||||||
from .home import HomeView
|
from .home import HomeView
|
||||||
|
|
||||||
from .article import ArticleDetailView, ArticleListView
|
from .article import ArticleDetailView, ArticleListView
|
||||||
from .episode import EpisodeDetailView, EpisodeListView, DiffusionListView
|
from .episode import EpisodeDetailView, EpisodeListView, DiffusionListView
|
||||||
from .log import LogListView
|
from .log import LogListView, LogListAPIView
|
||||||
from .page import PageDetailView, PageListView
|
from .page import PageDetailView, PageListView
|
||||||
from .program import ProgramDetailView, ProgramListView, \
|
from .program import ProgramDetailView, ProgramListView, \
|
||||||
ProgramPageDetailView, ProgramPageListView
|
ProgramPageDetailView, ProgramPageListView
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.utils import timezone as tz
|
|
||||||
|
|
||||||
from rest_framework.generics import ListAPIView
|
|
||||||
from rest_framework import viewsets
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
|
|
||||||
from ..models import Log
|
|
||||||
from ..serializers import LogInfo, LogInfoSerializer
|
|
||||||
from .log import LogListMixin
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAPIView:
|
|
||||||
@property
|
|
||||||
def station(self):
|
|
||||||
return self.request.station
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return super().get_queryset().station(self.station)
|
|
||||||
|
|
||||||
|
|
||||||
class LogListAPIView(LogListMixin, BaseAPIView, ListAPIView):
|
|
||||||
"""
|
|
||||||
Return logs list, including diffusions. By default return logs of
|
|
||||||
the last 30 minutes.
|
|
||||||
|
|
||||||
Available GET parameters:
|
|
||||||
- "date": return logs for a specified date (
|
|
||||||
- "full": (staff user only) don't merge diffusion and logs
|
|
||||||
"""
|
|
||||||
serializer_class = LogInfoSerializer
|
|
||||||
queryset = Log.objects.all()
|
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
self.date = self.get_date()
|
|
||||||
if self.date is None:
|
|
||||||
self.min_date = tz.now() - tz.timedelta(minutes=30)
|
|
||||||
return super().get(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_object_list(self, logs, full):
|
|
||||||
return [LogInfo(obj) for obj in super().get_object_list(logs, full)]
|
|
||||||
|
|
||||||
def get_serializer(self, queryset, *args, **kwargs):
|
|
||||||
full = bool(self.request.GET.get('full'))
|
|
||||||
return super().get_serializer(self.get_object_list(queryset, full),
|
|
||||||
*args, **kwargs)
|
|
||||||
|
|
|
@ -59,3 +59,12 @@ class BaseView(TemplateResponseMixin, ContextMixin):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAPIView:
|
||||||
|
@property
|
||||||
|
def station(self):
|
||||||
|
return self.request.station
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().station(self.station)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,13 @@ import datetime
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
|
|
||||||
|
from rest_framework.generics import ListAPIView
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
|
||||||
from ..models import Diffusion, Log
|
from ..models import Diffusion, Log
|
||||||
from .base import BaseView
|
from ..serializers import LogInfo, LogInfoSerializer
|
||||||
|
from .base import BaseView, BaseAPIView
|
||||||
from .mixins import GetDateMixin
|
from .mixins import GetDateMixin
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,3 +73,30 @@ class LogListView(BaseView, LogListMixin, ListView):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# Logs are accessible through API only with this list view
|
||||||
|
class LogListAPIView(LogListMixin, BaseAPIView, ListAPIView):
|
||||||
|
"""
|
||||||
|
Return logs list, including diffusions. By default return logs of
|
||||||
|
the last 30 minutes.
|
||||||
|
|
||||||
|
Available GET parameters:
|
||||||
|
- "date": return logs for a specified date (
|
||||||
|
- "full": (staff user only) don't merge diffusion and logs
|
||||||
|
"""
|
||||||
|
serializer_class = LogInfoSerializer
|
||||||
|
queryset = Log.objects.all()
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
self.date = self.get_date()
|
||||||
|
if self.date is None:
|
||||||
|
self.min_date = tz.now() - tz.timedelta(minutes=30)
|
||||||
|
return super().get(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_object_list(self, logs, full):
|
||||||
|
return [LogInfo(obj) for obj in super().get_object_list(logs, full)]
|
||||||
|
|
||||||
|
def get_serializer(self, queryset, *args, **kwargs):
|
||||||
|
full = bool(self.request.GET.get('full'))
|
||||||
|
return super().get_serializer(self.get_object_list(queryset, full),
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
|
26
aircox/viewsets.py
Normal file
26
aircox/viewsets.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
|
||||||
|
from .models import Sound
|
||||||
|
from .serializers import SoundSerializer
|
||||||
|
from .views import BaseAPIView
|
||||||
|
|
||||||
|
|
||||||
|
class SoundFilter(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 SoundViewSet(BaseAPIView, viewsets.ModelViewSet):
|
||||||
|
serializer_class = SoundSerializer
|
||||||
|
queryset = Sound.objects.available().order_by('-pk')
|
||||||
|
filter_backends = (filters.DjangoFilterBackend,)
|
||||||
|
filterset_class = SoundFilter
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
<h6 class="title is-6 is-marginless">{% trans "Add sound" %}</h6>
|
<h6 class="title is-6 is-marginless">{% trans "Add sound" %}</h6>
|
||||||
<form class="columns" @submit.prevent="source.push($event.target.elements['sound_id'].value)">
|
<form class="columns" @submit.prevent="source.push($event.target.elements['sound_id'].value)">
|
||||||
<div class="column field is-small">
|
<div class="column field is-small">
|
||||||
<a-autocomplete url="{% url "admin:api:streamer-queue-autocomplete-push" station_pk=station.pk %}?q=${query}"
|
{# TODO: select station => change the shit #}
|
||||||
|
<a-autocomplete url="{% url "aircox:sound-list" %}?station={{ station.pk }}&search=${query}"
|
||||||
class="is-fullwidth"
|
class="is-fullwidth"
|
||||||
:model="Sound" field="name" value-field="sound_id" value-attr="id"
|
:model="Sound" field="name" value-field="sound_id" value-attr="id"
|
||||||
{# FIXME dirty hack awaiting the vue component #}
|
{# FIXME dirty hack awaiting the vue component #}
|
||||||
|
|
|
@ -73,7 +73,7 @@ streamers = Streamers()
|
||||||
|
|
||||||
class BaseControllerAPIView(viewsets.ViewSet):
|
class BaseControllerAPIView(viewsets.ViewSet):
|
||||||
permission_classes = (IsAdminUser,)
|
permission_classes = (IsAdminUser,)
|
||||||
serializer = None
|
serializer_class = None
|
||||||
streamer = None
|
streamer = None
|
||||||
object = None
|
object = None
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ class BaseControllerAPIView(viewsets.ViewSet):
|
||||||
return streamers[id]
|
return streamers[id]
|
||||||
|
|
||||||
def get_serializer(self, **kwargs):
|
def get_serializer(self, **kwargs):
|
||||||
return self.serializer(self.object, **kwargs)
|
return self.serializer_class(self.object, **kwargs)
|
||||||
|
|
||||||
def serialize(self, obj, **kwargs):
|
def serialize(self, obj, **kwargs):
|
||||||
self.object = obj
|
self.object = obj
|
||||||
|
@ -98,11 +98,11 @@ class BaseControllerAPIView(viewsets.ViewSet):
|
||||||
|
|
||||||
|
|
||||||
class RequestViewSet(BaseControllerAPIView):
|
class RequestViewSet(BaseControllerAPIView):
|
||||||
serializer = RequestSerializer
|
serializer_class = RequestSerializer
|
||||||
|
|
||||||
|
|
||||||
class StreamerViewSet(BaseControllerAPIView):
|
class StreamerViewSet(BaseControllerAPIView):
|
||||||
serializer = StreamerSerializer
|
serializer_class = StreamerSerializer
|
||||||
|
|
||||||
def retrieve(self, request, pk=None):
|
def retrieve(self, request, pk=None):
|
||||||
return Response(self.serialize(self.streamer))
|
return Response(self.serialize(self.streamer))
|
||||||
|
@ -121,7 +121,7 @@ class StreamerViewSet(BaseControllerAPIView):
|
||||||
|
|
||||||
|
|
||||||
class SourceViewSet(BaseControllerAPIView):
|
class SourceViewSet(BaseControllerAPIView):
|
||||||
serializer = SourceSerializer
|
serializer_class = SourceSerializer
|
||||||
model = controllers.Source
|
model = controllers.Source
|
||||||
|
|
||||||
def get_sources(self):
|
def get_sources(self):
|
||||||
|
@ -168,27 +168,17 @@ class SourceViewSet(BaseControllerAPIView):
|
||||||
|
|
||||||
|
|
||||||
class PlaylistSourceViewSet(SourceViewSet):
|
class PlaylistSourceViewSet(SourceViewSet):
|
||||||
serializer = PlaylistSerializer
|
serializer_class = PlaylistSerializer
|
||||||
model = controllers.PlaylistSource
|
model = controllers.PlaylistSource
|
||||||
|
|
||||||
|
|
||||||
class QueueSourceViewSet(SourceViewSet):
|
class QueueSourceViewSet(SourceViewSet):
|
||||||
serializer = QueueSourceSerializer
|
serializer_class = QueueSourceSerializer
|
||||||
model = controllers.QueueSource
|
model = controllers.QueueSource
|
||||||
|
|
||||||
def get_sound_queryset(self):
|
def get_sound_queryset(self):
|
||||||
return Sound.objects.station(self.request.station).archive()
|
return Sound.objects.station(self.request.station).archive()
|
||||||
|
|
||||||
@action(detail=False, url_path='autocomplete/push',
|
|
||||||
url_name='autocomplete-push')
|
|
||||||
def autcomplete_push(self, request):
|
|
||||||
query = request.GET.get('q')
|
|
||||||
qs = self.get_sound_queryset().search(query)
|
|
||||||
serializer = SoundSerializer(qs, many=True, context={
|
|
||||||
'request': self.request
|
|
||||||
})
|
|
||||||
return Response({'results': serializer.data})
|
|
||||||
|
|
||||||
@action(detail=True, methods=['POST'])
|
@action(detail=True, methods=['POST'])
|
||||||
def push(self, request, pk):
|
def push(self, request, pk):
|
||||||
if not request.data.get('sound_id'):
|
if not request.data.get('sound_id'):
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {setEcoTimeout} from 'public/utils';
|
||||||
|
|
||||||
|
|
||||||
export default class {
|
export default class {
|
||||||
constructor(url, timeout) {
|
constructor(url, timeout) {
|
||||||
|
@ -30,7 +32,7 @@ export default class {
|
||||||
if(promise != this.promise)
|
if(promise != this.promise)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
window.setTimeout(() => this.refresh(), this.timeout*1000)
|
setEcoTimeout(() => this.refresh(), this.timeout*1000)
|
||||||
})
|
})
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
15
assets/public/utils.js
Normal file
15
assets/public/utils.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
export function setEcoTimeout(func, ...args) {
|
||||||
|
return setTimeout((...args) => {
|
||||||
|
!document.hidden && func(...args)
|
||||||
|
}, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function setEcoInterval(func, ...args) {
|
||||||
|
return setInterval((...args) => {
|
||||||
|
!document.hidden && func(...args)
|
||||||
|
}, ...args)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
import Model from 'public/model';
|
import Model from 'public/model';
|
||||||
|
import {setEcoInterval} from 'public/utils';
|
||||||
|
|
||||||
|
|
||||||
export class Streamer extends Model {
|
export class Streamer extends Model {
|
||||||
|
@ -27,7 +28,7 @@ export class Source extends Model {
|
||||||
constructor(data, {streamer=null, ...options}={}) {
|
constructor(data, {streamer=null, ...options}={}) {
|
||||||
super(data, options);
|
super(data, options);
|
||||||
this.streamer = streamer;
|
this.streamer = streamer;
|
||||||
setInterval(() => this.tick(), 1000)
|
setEcoInterval(() => this.tick(), 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
get isQueue() { return false; }
|
get isQueue() { return false; }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user