From b3ec6f670fec91e149759bd270bcb48313f4aec7 Mon Sep 17 00:00:00 2001 From: bkfox Date: Mon, 23 Sep 2019 12:38:44 +0200 Subject: [PATCH] update --- aircox/admin/__init__.py | 7 + aircox/admin/article.py | 18 ++ aircox/admin/episode.py | 58 +++++ aircox/admin/log.py | 13 + aircox/admin/mixins.py | 42 ++++ aircox/admin/page.py | 52 ++++ aircox/admin/program.py | 76 ++++++ aircox/admin/sound.py | 65 +++++ aircox/admin/station.py | 15 ++ aircox/admin_site.py | 56 +++++ .../models/__pycache__/sound.cpython-37.pyc | Bin 9422 -> 9654 bytes aircox/models/sound.py | 10 +- aircox/static/aircox/admin.js | 14 +- aircox/static/aircox/main.js | 14 +- aircox/static/aircox/streamer.js | 230 ++++++++++++++++++ aircox/templates/aircox/base.html | 2 +- .../aircox/{ => widgets}/player.html | 2 +- aircox/templatetags/aircox_admin.py | 9 + aircox/urls.py | 20 +- aircox/views/__init__.py | 6 +- aircox/views/api.py | 48 ---- aircox/views/base.py | 9 + aircox/views/log.py | 34 ++- aircox/viewsets.py | 26 ++ .../aircox_streamer/source_item.html | 3 +- aircox_streamer/viewsets.py | 24 +- assets/public/liveInfo.js | 4 +- assets/public/utils.js | 15 ++ assets/streamer/controllers.js | 3 +- 29 files changed, 790 insertions(+), 85 deletions(-) create mode 100644 aircox/admin/__init__.py create mode 100644 aircox/admin/article.py create mode 100644 aircox/admin/episode.py create mode 100644 aircox/admin/log.py create mode 100644 aircox/admin/mixins.py create mode 100644 aircox/admin/page.py create mode 100644 aircox/admin/program.py create mode 100644 aircox/admin/sound.py create mode 100644 aircox/admin/station.py create mode 100644 aircox/admin_site.py create mode 100644 aircox/static/aircox/streamer.js rename aircox/templates/aircox/{ => widgets}/player.html (96%) create mode 100644 aircox/templatetags/aircox_admin.py delete mode 100644 aircox/views/api.py create mode 100644 aircox/viewsets.py create mode 100644 assets/public/utils.js diff --git a/aircox/admin/__init__.py b/aircox/admin/__init__.py new file mode 100644 index 0000000..21436bb --- /dev/null +++ b/aircox/admin/__init__.py @@ -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 + diff --git a/aircox/admin/article.py b/aircox/admin/article.py new file mode 100644 index 0000000..4a08dc9 --- /dev/null +++ b/aircox/admin/article.py @@ -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 + + diff --git a/aircox/admin/episode.py b/aircox/admin/episode.py new file mode 100644 index 0000000..a6291a7 --- /dev/null +++ b/aircox/admin/episode.py @@ -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] + + diff --git a/aircox/admin/log.py b/aircox/admin/log.py new file mode 100644 index 0000000..a27f6a6 --- /dev/null +++ b/aircox/admin/log.py @@ -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'] + diff --git a/aircox/admin/mixins.py b/aircox/admin/mixins.py new file mode 100644 index 0000000..3d2ed6a --- /dev/null +++ b/aircox/admin/mixins.py @@ -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 + + diff --git a/aircox/admin/page.py b/aircox/admin/page.py new file mode 100644 index 0000000..a139f5c --- /dev/null +++ b/aircox/admin/page.py @@ -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(''.format(obj.cover.icons['64'])) \ + if obj.cover else '' + + +class NavItemInline(SortableInlineAdminMixin, admin.TabularInline): + model = NavItem + + + diff --git a/aircox/admin/program.py b/aircox/admin/program.py new file mode 100644 index 0000000..df732ea --- /dev/null +++ b/aircox/admin/program.py @@ -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') + + + diff --git a/aircox/admin/sound.py b/aircox/admin/sound.py new file mode 100644 index 0000000..ca0f9f5 --- /dev/null +++ b/aircox/admin/sound.py @@ -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'] + + diff --git a/aircox/admin/station.py b/aircox/admin/station.py new file mode 100644 index 0000000..f0d4623 --- /dev/null +++ b/aircox/admin/station.py @@ -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,) + + diff --git a/aircox/admin_site.py b/aircox/admin_site.py new file mode 100644 index 0000000..82feb7a --- /dev/null +++ b/aircox/admin_site.py @@ -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//', + 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)) + + diff --git a/aircox/models/__pycache__/sound.cpython-37.pyc b/aircox/models/__pycache__/sound.cpython-37.pyc index 2b05b65c9188ef53f362742b634fe23033d4d4c0..9e5bdf9e95e40f3598446e5191ac75e91614ddde 100644 GIT binary patch delta 2162 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 Du-emI diff --git a/aircox/models/sound.py b/aircox/models/sound.py index a215c10..4b363e3 100644 --- a/aircox/models/sound.py +++ b/aircox/models/sound.py @@ -34,6 +34,9 @@ class SoundQuerySet(models.QuerySet): id = diffusion.pk if id is None else id return self.filter(episode__diffusion__id=id) + def available(self): + return self.exclude(type=Sound.TYPE_REMOVED) + def podcasts(self): """ Return sounds available as podcasts """ return self.filter(Q(embed__isnull=False) | Q(is_public=True)) @@ -87,6 +90,9 @@ class Sound(models.Model): verbose_name=_('episode'), ) 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 # should we use FileField for more reliability? path = models.FilePathField( @@ -278,9 +284,7 @@ class Track(models.Model): verbose_name=_('sound'), ) position = models.PositiveSmallIntegerField( - _('order'), - default=0, - help_text=_('position in the playlist'), + _('order'), default=0, help_text=_('position in the playlist'), ) timestamp = models.PositiveSmallIntegerField( _('timestamp'), diff --git a/aircox/static/aircox/admin.js b/aircox/static/aircox/admin.js index ce089d1..5b087fc 100644 --- a/aircox/static/aircox/admin.js +++ b/aircox/static/aircox/admin.js @@ -282,7 +282,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue_ /***/ (function(module, __webpack_exports__, __webpack_require__) { "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??vue-loader-options!./assets/admin/statistics.vue?vue&type=script&lang=js& ***! diff --git a/aircox/static/aircox/main.js b/aircox/static/aircox/main.js index e1c6635..a3f7bda 100644 --- a/aircox/static/aircox/main.js +++ b/aircox/static/aircox/main.js @@ -223,7 +223,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue_ /***/ (function(module, __webpack_exports__, __webpack_require__) { "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??vue-loader-options!./assets/public/autocomplete.vue?vue&type=script&lang=js& ***! diff --git a/aircox/static/aircox/streamer.js b/aircox/static/aircox/streamer.js new file mode 100644 index 0000000..0604aa5 --- /dev/null +++ b/aircox/static/aircox/streamer.js @@ -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?"); + +/***/ }) + +/******/ }); \ No newline at end of file diff --git a/aircox/templates/aircox/base.html b/aircox/templates/aircox/base.html index 93cb189..204ae60 100644 --- a/aircox/templates/aircox/base.html +++ b/aircox/templates/aircox/base.html @@ -133,7 +133,7 @@ Blocks:
-
{% include "aircox/player.html" %}
+
{% include "aircox/widgets/player.html" %}
diff --git a/aircox/templates/aircox/player.html b/aircox/templates/aircox/widgets/player.html similarity index 96% rename from aircox/templates/aircox/player.html rename to aircox/templates/aircox/widgets/player.html index 272d884..76b505a 100644 --- a/aircox/templates/aircox/player.html +++ b/aircox/templates/aircox/widgets/player.html @@ -16,7 +16,7 @@