fix logs merge with diff algorithm

This commit is contained in:
bkfox 2019-08-12 04:11:04 +02:00
parent aabbcd97fa
commit e0f1ac498f
25 changed files with 485 additions and 124 deletions

View File

@ -1,3 +1,4 @@
from collections import deque
import datetime import datetime
from enum import IntEnum from enum import IntEnum
import logging import logging
@ -25,10 +26,14 @@ class LogQuerySet(models.QuerySet):
return self.filter(station=station) if id is None else \ return self.filter(station=station) if id is None else \
self.filter(station_id=id) self.filter(station_id=id)
def at(self, date=None): def today(self, date):
date = utils.date_or_default(date)
return self.filter(date__date=date) return self.filter(date__date=date)
def after(self, date):
return self.filter(date__gte=date) \
if isinstance(date, tz.datetime) else \
self.filter(date__date__gte=date)
def on_air(self): def on_air(self):
return self.filter(type=Log.Type.on_air) return self.filter(type=Log.Type.on_air)
@ -100,13 +105,9 @@ class LogQuerySet(models.QuerySet):
} }
def rel_obj(log, attr): def rel_obj(log, attr):
attr_id = attr + '_id'
rel_id = log.get(attr + '_id') rel_id = log.get(attr + '_id')
return rels[attr][rel_id] if rel_id else None return rels[attr][rel_id] if rel_id else None
# make logs
return [ return [
Log(diffusion=rel_obj(log, 'diffusion'), Log(diffusion=rel_obj(log, 'diffusion'),
sound=rel_obj(log, 'sound'), sound=rel_obj(log, 'sound'),
@ -134,7 +135,7 @@ class LogQuerySet(models.QuerySet):
if os.path.exists(path) and not force: if os.path.exists(path) and not force:
return -1 return -1
qs = self.station(station).at(date) qs = self.station(station).today(date)
if not qs.exists(): if not qs.exists():
return 0 return 0
@ -241,6 +242,56 @@ class Log(models.Model):
""" """
return tz.localtime(self.date, tz.get_current_timezone()) return tz.localtime(self.date, tz.get_current_timezone())
def __str__(self):
return '#{} ({}, {}, {})'.format(
self.pk, self.get_type_display(),
self.source, self.local_date.strftime('%Y/%m/%d %H:%M%z'))
@classmethod
def __list_append(cls, object_list, items):
object_list += [cls(obj) for obj in items]
@classmethod
def merge_diffusions(cls, logs, diffs):
diffs = deque(diffs.order_by('start'))
logs = list(logs.order_by('date'))
object_list = []
# +++ +++ ++ ++
# ---- ----- ----
while True:
if not len(diffs):
object_list += logs
break
if not len(logs):
object_list += diffs
break
diff = diffs.popleft()
# - takes all logs before diff happens
index = next((i for i, v in enumerate(logs)
if v.date >= diff.start), len(logs))
if index is not None and index > 0:
object_list += logs[:index]
logs = logs[index:]
# - add diff
object_list.append(diff)
# - last log while diff is running
# Using of greater allow last_log to log that starts with date
# equals to diff.end
index = next((i for i, v in enumerate(logs) if v.date > diff.end),
None)
if index is not None and index > 0:
object_list.append(logs[index-1])
logs = logs[index:]
return object_list
def print(self): def print(self):
r = [] r = []
if self.diffusion: if self.diffusion:
@ -252,7 +303,3 @@ class Log(models.Model):
logger.info('log %s: %s%s', str(self), self.comment or '', logger.info('log %s: %s%s', str(self), self.comment or '',
' (' + ', '.join(r) + ')' if r else '') ' (' + ', '.join(r) + ')' if r else '')
def __str__(self):
return '#{} ({}, {}, {})'.format(
self.pk, self.get_type_display(),
self.source, self.local_date.strftime('%Y/%m/%d %H:%M%z'))

View File

@ -44,7 +44,7 @@ def program_post_save(sender, instance, created, *args, **kwargs):
Clean-up later diffusions when a program becomes inactive Clean-up later diffusions when a program becomes inactive
""" """
if not instance.active: if not instance.active:
Diffusion.objects.program(instance).after().delete() Diffusion.objects.program(instance).after(tz.now()).delete()
Episode.object.program(instance).filter(diffusion__isnull=True) \ Episode.object.program(instance).filter(diffusion__isnull=True) \
.delete() .delete()
@ -70,7 +70,7 @@ def schedule_post_save(sender, instance, created, *args, **kwargs):
today = tz.datetime.today() today = tz.datetime.today()
delta = instance.normalize(today) - initial.normalize(today) delta = instance.normalize(today) - initial.normalize(today)
qs = Diffusion.objects.program(instance.program).after() qs = Diffusion.objects.program(instance.program).after(tz.now())
pks = [d.pk for d in qs if initial.match(d.date)] pks = [d.pk for d in qs if initial.match(d.date)]
qs.filter(pk__in=pks).update( qs.filter(pk__in=pks).update(
start=F('start') + delta, start=F('start') + delta,
@ -86,7 +86,7 @@ def schedule_pre_delete(sender, instance, *args, **kwargs):
if not instance.program.sync: if not instance.program.sync:
return return
qs = Diffusion.objects.program(instance.program).after() qs = Diffusion.objects.program(instance.program).after(tz.now())
pks = [d.pk for d in qs if instance.match(d.date)] pks = [d.pk for d in qs if instance.match(d.date)]
qs.filter(pk__in=pks).delete() qs.filter(pk__in=pks).delete()

View File

@ -55,6 +55,11 @@ class Station(models.Model):
_("website's urls"), max_length=512, null=True, blank=True, _("website's urls"), max_length=512, null=True, blank=True,
help_text=_('specify one url per line') help_text=_('specify one url per line')
) )
audio_streams = models.TextField(
_("audio streams"), max_length=2048, null=True, blank=True,
help_text=_("Audio streams urls used by station's player. One url "
"a line.")
)
objects = StationQuerySet.as_manager() objects = StationQuerySet.as_manager()

View File

@ -7147,15 +7147,25 @@ label.panel-block {
background-color: #fafafa; background-color: #fafafa;
padding: 3rem 1.5rem 6rem; } padding: 3rem 1.5rem 6rem; }
.navbar { .is-fullwidth {
margin-bottom: 1em; } width: 100%; }
.navbar.has-shadow { .is-fixed-bottom {
box-shadow: 0em 0.05em 0.5em rgba(0, 0, 0, 0.1); } position: fixed;
bottom: 0;
margin-bottom: 0px;
border-radius: 0; }
.is-borderless {
border: none; }
.navbar + .container {
margin-top: 1em; }
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.1); }
/* /*
.navbar-brand img { .navbar-brand img {
min-height: 6em; min-height: 6em;
} }
@ -7200,7 +7210,3 @@ aside .media .subtitle {
aside .media .content { aside .media .content {
display: none; } display: none; }
/**[noscript="hidden"] {
display: none;
}*/

View File

@ -164,7 +164,7 @@
/***/ (function(module, __webpack_exports__, __webpack_require__) { /***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict"; "use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./js */ \"./assets/js/index.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./styles.scss */ \"./assets/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _noscript_scss__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./noscript.scss */ \"./assets/noscript.scss\");\n/* harmony import */ var _noscript_scss__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_noscript_scss__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./vue */ \"./assets/vue/index.js\");\n\n\n\n\n\n\n\n//# sourceURL=webpack:///./assets/index.js?"); eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/fontawesome.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./js */ \"./assets/js/index.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./styles.scss */ \"./assets/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./vue */ \"./assets/vue/index.js\");\n\n\n\n\n\n// import './noscript.scss';\n\n\n\n\n//# sourceURL=webpack:///./assets/index.js?");
/***/ }), /***/ }),
@ -176,18 +176,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _js_
/***/ (function(module, __webpack_exports__, __webpack_require__) { /***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict"; "use strict";
eval("/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var buefy__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! buefy */ \"./node_modules/buefy/dist/buefy.js\");\n/* harmony import */ var buefy__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(buefy__WEBPACK_IMPORTED_MODULE_1__);\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(buefy__WEBPACK_IMPORTED_MODULE_1___default.a);\n\nwindow.addEventListener('load', () => {\n var app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"]({\n el: '#app',\n delimiters: [ '[[', ']]' ],\n })\n});\n\n\n\n\n\n//# sourceURL=webpack:///./assets/js/index.js?"); eval("/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var buefy__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! buefy */ \"./node_modules/buefy/dist/buefy.js\");\n/* harmony import */ var buefy__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(buefy__WEBPACK_IMPORTED_MODULE_1__);\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(buefy__WEBPACK_IMPORTED_MODULE_1___default.a);\n\nvar app = null;\n\nfunction loadApp() {\n app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"]({\n el: '#app',\n delimiters: [ '[[', ']]' ],\n })\n}\n\n\nwindow.addEventListener('load', loadApp);\n\n\n\n\n//# sourceURL=webpack:///./assets/js/index.js?");
/***/ }),
/***/ "./assets/noscript.scss":
/*!******************************!*\
!*** ./assets/noscript.scss ***!
\******************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./assets/noscript.scss?");
/***/ }), /***/ }),
@ -206,11 +195,89 @@ eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./asse
/*!*****************************!*\ /*!*****************************!*\
!*** ./assets/vue/index.js ***! !*** ./assets/vue/index.js ***!
\*****************************/ \*****************************/
/*! exports provided: Tab, Tabs */ /*! exports provided: Player, Tab, Tabs */
/***/ (function(module, __webpack_exports__, __webpack_require__) { /***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict"; "use strict";
eval("/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _tab_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tab.vue */ \"./assets/vue/tab.vue\");\n/* harmony import */ var _tabs_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tabs.vue */ \"./assets/vue/tabs.vue\");\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-tab', _tab_vue__WEBPACK_IMPORTED_MODULE_1__[/* default */ \"a\"]);\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-tabs', _tabs_vue__WEBPACK_IMPORTED_MODULE_2__[/* default */ \"a\"]);\n\n\n\n\n\n\n//# sourceURL=webpack:///./assets/vue/index.js?"); eval("/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _onAir_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./onAir.vue */ \"./assets/vue/onAir.vue\");\n/* harmony import */ var _player_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./player.vue */ \"./assets/vue/player.vue\");\n/* harmony import */ var _tab_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./tab.vue */ \"./assets/vue/tab.vue\");\n/* harmony import */ var _tabs_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./tabs.vue */ \"./assets/vue/tabs.vue\");\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-on-air', _onAir_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"]);\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-tab', _tab_vue__WEBPACK_IMPORTED_MODULE_3__[/* default */ \"a\"]);\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-tabs', _tabs_vue__WEBPACK_IMPORTED_MODULE_4__[/* default */ \"a\"]);\n\n\n\n\n\n\n//# sourceURL=webpack:///./assets/vue/index.js?");
/***/ }),
/***/ "./assets/vue/onAir.vue":
/*!******************************!*\
!*** ./assets/vue/onAir.vue ***!
\******************************/
/*! no static exports found */
/*! exports used: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* harmony import */ var _onAir_vue_vue_type_template_id_0971e224___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./onAir.vue?vue&type=template&id=0971e224& */ \"./assets/vue/onAir.vue?vue&type=template&id=0971e224&\");\n/* harmony import */ var _onAir_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./onAir.vue?vue&type=script&lang=js& */ \"./assets/vue/onAir.vue?vue&type=script&lang=js&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[/* default */ \"a\"])(\n _onAir_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _onAir_vue_vue_type_template_id_0971e224___WEBPACK_IMPORTED_MODULE_0__[/* render */ \"a\"],\n _onAir_vue_vue_type_template_id_0971e224___WEBPACK_IMPORTED_MODULE_0__[/* staticRenderFns */ \"b\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"assets/vue/onAir.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./assets/vue/onAir.vue?");
/***/ }),
/***/ "./assets/vue/onAir.vue?vue&type=script&lang=js&":
/*!*******************************************************!*\
!*** ./assets/vue/onAir.vue?vue&type=script&lang=js& ***!
\*******************************************************/
/*! no static exports found */
/*! exports used: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* harmony import */ var _node_modules_vue_loader_lib_index_js_vue_loader_options_onAir_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-loader/lib??vue-loader-options!./onAir.vue?vue&type=script&lang=js& */ \"./node_modules/vue-loader/lib/index.js?!./assets/vue/onAir.vue?vue&type=script&lang=js&\");\n /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_vue_loader_lib_index_js_vue_loader_options_onAir_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[/* default */ \"a\"]); \n\n//# sourceURL=webpack:///./assets/vue/onAir.vue?");
/***/ }),
/***/ "./assets/vue/onAir.vue?vue&type=template&id=0971e224&":
/*!*************************************************************!*\
!*** ./assets/vue/onAir.vue?vue&type=template&id=0971e224& ***!
\*************************************************************/
/*! exports provided: render, staticRenderFns */
/*! exports used: render, staticRenderFns */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* harmony import */ var _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_onAir_vue_vue_type_template_id_0971e224___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../node_modules/vue-loader/lib??vue-loader-options!./onAir.vue?vue&type=template&id=0971e224& */ \"./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/onAir.vue?vue&type=template&id=0971e224&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_onAir_vue_vue_type_template_id_0971e224___WEBPACK_IMPORTED_MODULE_0__[\"a\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_onAir_vue_vue_type_template_id_0971e224___WEBPACK_IMPORTED_MODULE_0__[\"b\"]; });\n\n\n\n//# sourceURL=webpack:///./assets/vue/onAir.vue?");
/***/ }),
/***/ "./assets/vue/player.vue":
/*!*******************************!*\
!*** ./assets/vue/player.vue ***!
\*******************************/
/*! no static exports found */
/*! exports used: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* harmony import */ var _player_vue_vue_type_template_id_2882cef8___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./player.vue?vue&type=template&id=2882cef8& */ \"./assets/vue/player.vue?vue&type=template&id=2882cef8&\");\n/* harmony import */ var _player_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./player.vue?vue&type=script&lang=js& */ \"./assets/vue/player.vue?vue&type=script&lang=js&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[/* default */ \"a\"])(\n _player_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _player_vue_vue_type_template_id_2882cef8___WEBPACK_IMPORTED_MODULE_0__[/* render */ \"a\"],\n _player_vue_vue_type_template_id_2882cef8___WEBPACK_IMPORTED_MODULE_0__[/* staticRenderFns */ \"b\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"assets/vue/player.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./assets/vue/player.vue?");
/***/ }),
/***/ "./assets/vue/player.vue?vue&type=script&lang=js&":
/*!********************************************************!*\
!*** ./assets/vue/player.vue?vue&type=script&lang=js& ***!
\********************************************************/
/*! no static exports found */
/*! exports used: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* harmony import */ var _node_modules_vue_loader_lib_index_js_vue_loader_options_player_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-loader/lib??vue-loader-options!./player.vue?vue&type=script&lang=js& */ \"./node_modules/vue-loader/lib/index.js?!./assets/vue/player.vue?vue&type=script&lang=js&\");\n /* harmony default export */ __webpack_exports__[\"default\"] = (_node_modules_vue_loader_lib_index_js_vue_loader_options_player_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[/* default */ \"a\"]); \n\n//# sourceURL=webpack:///./assets/vue/player.vue?");
/***/ }),
/***/ "./assets/vue/player.vue?vue&type=template&id=2882cef8&":
/*!**************************************************************!*\
!*** ./assets/vue/player.vue?vue&type=template&id=2882cef8& ***!
\**************************************************************/
/*! exports provided: render, staticRenderFns */
/*! exports used: render, staticRenderFns */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* harmony import */ var _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_player_vue_vue_type_template_id_2882cef8___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../node_modules/vue-loader/lib??vue-loader-options!./player.vue?vue&type=template&id=2882cef8& */ \"./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/player.vue?vue&type=template&id=2882cef8&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_player_vue_vue_type_template_id_2882cef8___WEBPACK_IMPORTED_MODULE_0__[\"a\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_player_vue_vue_type_template_id_2882cef8___WEBPACK_IMPORTED_MODULE_0__[\"b\"]; });\n\n\n\n//# sourceURL=webpack:///./assets/vue/player.vue?");
/***/ }), /***/ }),
@ -292,6 +359,32 @@ eval("/* harmony import */ var _node_modules_vue_loader_lib_loaders_templateLoad
/***/ }), /***/ }),
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/vue/onAir.vue?vue&type=script&lang=js&":
/*!*********************************************************************************************************!*\
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/onAir.vue?vue&type=script&lang=js& ***!
\*********************************************************************************************************/
/*! exports provided: default */
/*! exports used: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ __webpack_exports__[\"a\"] = ({\n props: {\n url: String,\n timeout: Number,\n },\n\n data() {\n return {\n // promise is set to null on destroy: this is used as check\n // for timeout to know wether to repeat itself or not.\n promise: null,\n // on air infos\n on_air: null\n }\n },\n\n\n methods: {\n fetch() {\n const promise = fetch(url).then(response =>\n reponse.ok ? response.json\n : Promise.reject(response)\n ).then(data => {\n this.on_air = data.results;\n return this.on_air\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.update(), this.timeout*1000)\n })\n },\n },\n\n mounted() {\n this.update()\n },\n\n destroyed() {\n this.promise = null;\n }\n\n});\n\n\n//# sourceURL=webpack:///./assets/vue/onAir.vue?./node_modules/vue-loader/lib??vue-loader-options");
/***/ }),
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/vue/player.vue?vue&type=script&lang=js&":
/*!**********************************************************************************************************!*\
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/player.vue?vue&type=script&lang=js& ***!
\**********************************************************************************************************/
/*! exports provided: State, default */
/*! exports used: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* unused harmony export State */\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\nconst State = {\n paused: 0,\n playing: 1,\n loading: 2,\n}\n\n/* harmony default export */ __webpack_exports__[\"a\"] = ({\n data() {\n return {\n state: State.paused,\n }\n },\n\n props: {\n onAir: String,\n src: String,\n },\n\n computed: {\n paused() { return this.state == State.paused; },\n playing() { return this.state == State.playing; },\n loading() { return this.state == State.loading; },\n },\n\n methods: {\n load(src) {\n const audio = this.$refs.audio;\n audio.src = src;\n audio.load()\n },\n\n play(src) {\n if(src)\n this.load(src);\n this.$refs.audio.play().catch(e => console.error(e))\n },\n\n pause() {\n this.$refs.audio.pause()\n },\n\n toggle() {\n if(this.paused)\n this.play()\n else\n this.pause()\n },\n\n onChange(event) {\n const audio = this.$refs.audio;\n this.state = audio.paused ? State.paused : State.playing;\n },\n },\n\n mounted() {\n this.load(this.src);\n }\n});\n\n\n\n//# sourceURL=webpack:///./assets/vue/player.vue?./node_modules/vue-loader/lib??vue-loader-options");
/***/ }),
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/vue/tab.vue?vue&type=script&lang=js&": /***/ "./node_modules/vue-loader/lib/index.js?!./assets/vue/tab.vue?vue&type=script&lang=js&":
/*!*******************************************************************************************************!*\ /*!*******************************************************************************************************!*\
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tab.vue?vue&type=script&lang=js& ***! !*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tab.vue?vue&type=script&lang=js& ***!
@ -318,6 +411,32 @@ eval("//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ __w
/***/ }), /***/ }),
/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/onAir.vue?vue&type=template&id=0971e224&":
/*!*******************************************************************************************************************************************************************************************!*\
!*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/onAir.vue?vue&type=template&id=0971e224& ***!
\*******************************************************************************************************************************************************************************************/
/*! exports provided: render, staticRenderFns */
/*! exports used: render, staticRenderFns */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"media\" })\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/vue/onAir.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options");
/***/ }),
/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/player.vue?vue&type=template&id=2882cef8&":
/*!********************************************************************************************************************************************************************************************!*\
!*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/player.vue?vue&type=template&id=2882cef8& ***!
\********************************************************************************************************************************************************************************************/
/*! exports provided: render, staticRenderFns */
/*! exports used: render, staticRenderFns */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"media\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"div\",\n {\n staticClass: \"button is-size-4\",\n on: {\n click: function($event) {\n return _vm.toggle()\n }\n }\n },\n [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fas fa-pause\" })\n : _c(\"span\", { staticClass: \"fas fa-play\" })\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"audio\",\n {\n ref: \"audio\",\n on: {\n playing: _vm.onChange,\n ended: _vm.onChange,\n pause: _vm.onChange\n }\n },\n [_vm._t(\"sources\")],\n 2\n )\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-content\" })\n ])\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/vue/player.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options");
/***/ }),
/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/tab.vue?vue&type=template&id=65401e0e&": /***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/tab.vue?vue&type=template&id=65401e0e&":
/*!*****************************************************************************************************************************************************************************************!*\ /*!*****************************************************************************************************************************************************************************************!*\
!*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tab.vue?vue&type=template&id=65401e0e& ***! !*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tab.vue?vue&type=template&id=65401e0e& ***!

View File

@ -1,5 +1,27 @@
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["vendor"],{ (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["vendor"],{
/***/ "./node_modules/@fortawesome/fontawesome-free/css/all.min.css":
/*!********************************************************************!*\
!*** ./node_modules/@fortawesome/fontawesome-free/css/all.min.css ***!
\********************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./node_modules/@fortawesome/fontawesome-free/css/all.min.css?");
/***/ }),
/***/ "./node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css":
/*!****************************************************************************!*\
!*** ./node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css ***!
\****************************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css?");
/***/ }),
/***/ "./node_modules/buefy/dist/buefy.js": /***/ "./node_modules/buefy/dist/buefy.js":
/*!******************************************!*\ /*!******************************************!*\
!*** ./node_modules/buefy/dist/buefy.js ***! !*** ./node_modules/buefy/dist/buefy.js ***!

View File

@ -14,6 +14,7 @@ Context:
{% block assets %} {% block assets %}
<link rel="stylesheet" type="text/css" href="{% static "aircox/main.css" %}"/> <link rel="stylesheet" type="text/css" href="{% static "aircox/main.css" %}"/>
<link rel="stylesheet" type="text/css" href="{% static "aircox/vendor.css" %}"/>
<script src="{% static "aircox/main.js" %}"></script> <script src="{% static "aircox/main.js" %}"></script>
<script src="{% static "aircox/vendor.js" %}"></script> <script src="{% static "aircox/vendor.js" %}"></script>
{% endblock %} {% endblock %}
@ -80,6 +81,9 @@ Context:
{% endif %} {% endif %}
</div> </div>
</div> </div>
<hr>
{% include "aircox/player.html" %}
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,11 +1,24 @@
{% load i18n %} {% load i18n aircox %}
{% comment %}
Context objects:
- object: object to render
- hide_schedule: if true, hide the schedule
{% endcomment %}
{% with object.track as track %} {% if object|is_diffusion %}
<span class="has-text-info is-size-5">&#9836;</span> {% with object as diffusion %}
<span>{{ track.title }}</span> {% with diffusion.episode as object %}
<span class="has-text-grey-dark has-text-weight-light"> {% include "aircox/episode_item.html" %}
&mdash; {{ track.artist }} {% endwith %}
{% if track.info %}(<i>{{ track.info }}</i>){% endif %} {% endwith %}
</span> {% else %}
{% endwith %} {% with object.track as track %}
<span class="has-text-info is-size-5">&#9836;</span>
<span>{{ track.title }}</span>
<span class="has-text-grey-dark has-text-weight-light">
&mdash; {{ track.artist }}
{% if track.info %}(<i>{{ track.info }}</i>){% endif %}
</span>
{% endwith %}
{% endif %}

View File

@ -33,25 +33,18 @@
<table class="table is-striped is-hoverable is-fullwidth"> <table class="table is-striped is-hoverable is-fullwidth">
{% for object in object_list reversed %} {% for object in object_list reversed %}
<tr> <tr>
{% if object|is_diffusion %}
<td> <td>
{% if object|is_diffusion %}
<time datetime="{{ object.start }}" title="{{ object.start }}"> <time datetime="{{ object.start }}" title="{{ object.start }}">
{{ object.start|date:"H:i" }} - {{ object.end|date:"H:i" }} {{ object.start|date:"H:i" }} - {{ object.end|date:"H:i" }}
</time> </time>
</td> {% else %}
{% with object as diffusion %}
{% with diffusion.episode as object %}
<td>{% include "aircox/episode_item.html" %}</td>
{% endwith %}
{% endwith %}
{% else %}
<td>
<time datetime="{{ object.date }}" title="{{ object.date }}"> <time datetime="{{ object.date }}" title="{{ object.date }}">
{{ object.date|date:"H:i" }} {{ object.date|date:"H:i" }}
</time> </time>
{% endif %}
</td> </td>
<td>{% include "aircox/log_item.html" %}</td> <td>{% include "aircox/log_item.html" %}</td>
{% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -49,8 +49,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</form> </form>
</section> </section>
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
from django.urls import 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 . import views, models
@ -17,7 +17,13 @@ register_converter(WeekConverter, 'week')
# ] # ]
api = [
path('on-air/', views.api.OnAirAPIView.as_view(), name='on-air'),
]
urls = [ urls = [
path('api/', include(api)),
# path('', views.PageDetailView.as_view(model=models.Article), # path('', views.PageDetailView.as_view(model=models.Article),
# name='home'), # name='home'),
path(_('articles/'), path(_('articles/'),

View File

@ -1,3 +1,5 @@
from . import api
from .article import ArticleListView from .article import ArticleListView
from .base import BaseView from .base import BaseView
from .episode import EpisodeDetailView, EpisodeListView, TimetableView from .episode import EpisodeDetailView, EpisodeListView, TimetableView

View File

@ -28,5 +28,10 @@ class BaseView(TemplateResponseMixin, ContextMixin):
kwargs.setdefault('station', self.station) kwargs.setdefault('station', self.station)
kwargs.setdefault('cover', self.cover) kwargs.setdefault('cover', self.cover)
kwargs.setdefault('show_side_nav', self.show_side_nav) kwargs.setdefault('show_side_nav', self.show_side_nav)
if not 'audio_streams' in kwargs:
streams = self.station.audio_streams
streams = streams and streams.split('\n')
kwargs['audio_streams'] = streams
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -7,66 +7,22 @@ from ..models import Diffusion, Log
from .base import BaseView from .base import BaseView
__all__ = ['BaseLogView', 'LogListView'] __all__ = ['BaseLogListView', 'LogListView']
class BaseLogView(ListView): class BaseLogListView:
station = None
date = None date = None
delta = None
def get_queryset(self): def get_queryset(self):
# only get logs for tracks: log for diffusion will be retrieved # only get logs for tracks: log for diffusion will be retrieved
# by the diffusions' queryset. # by the diffusions' queryset.
return super().get_queryset().station(self.station).on_air() \ return super().get_queryset().on_air().filter(track__isnull=False)
.at(self.date).filter(track__isnull=False)
def get_diffusions_queryset(self): def get_diffusions_queryset(self):
return Diffusion.objects.station(self.station).on_air() \ return Diffusion.objects.station(self.station).on_air()
.today(self.date)
def get_object_list(self, queryset):
diffs = deque(self.get_diffusions_queryset().order_by('start'))
logs = list(queryset.order_by('date'))
if not len(diffs):
return logs
object_list = []
diff = None
last_collision = None
# TODO/FIXME: multiple diffs at once - recheck the whole algorithm in
# detail -- however I barely see cases except when there are diff
# collision or the streamer is not working
for index, log in enumerate(logs):
# get next diff
if diff is None or diff.end < log.date:
diff = diffs.popleft() if len(diffs) else None
# no more diff that can collide: return list
if diff is None:
if last_collision and not object_list or \
object_list[-1] is not last_collision:
object_list.append(last_collision)
return object_list + logs[index:]
# diff colliding with log
if diff.start <= log.date:
if not object_list or object_list[-1] is not diff:
object_list.append(diff)
if log.date <= diff.end:
last_collision = log
else:
# add last colliding log: track
if last_collision is not None:
object_list.append(last_collision)
object_list.append(log)
last_collision = None
return object_list
class LogListView(BaseView, BaseLogView): class LogListView(BaseView, BaseLogListView, ListView):
model = Log model = Log
date = None date = None
@ -80,6 +36,14 @@ class LogListView(BaseView, BaseLogView):
if 'date' in self.kwargs else today if 'date' in self.kwargs else today
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_queryset(self):
# only get logs for tracks: log for diffusion will be retrieved
# by the diffusions' queryset.
return super().get_queryset().today(self.date)
def get_diffusions_queryset(self):
return super().get_diffusions_queryset().today(self.date)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
today = datetime.date.today() today = datetime.date.today()
max_date = min(max(self.date + datetime.timedelta(days=3), max_date = min(max(self.date + datetime.timedelta(days=3),
@ -91,6 +55,9 @@ class LogListView(BaseView, BaseLogView):
dates=(date for date in ( dates=(date for date in (
max_date - datetime.timedelta(days=i) max_date - datetime.timedelta(days=i)
for i in range(0, 7)) if date >= self.min_date), for i in range(0, 7)) if date >= self.min_date),
object_list=self.get_object_list(self.object_list), object_list=Log.merge_diffusions(self.object_list,
self.get_diffusions_queryset()),
**kwargs **kwargs
) )

View File

@ -1,5 +1,8 @@
import '@fortawesome/fontawesome-free/css/all.min.css';
import '@fortawesome/fontawesome-free/css/fontawesome.min.css';
import './js'; import './js';
import './styles.scss'; import './styles.scss';
import './noscript.scss'; // import './noscript.scss';
import './vue'; import './vue';

View File

@ -3,12 +3,16 @@ import Buefy from 'buefy';
Vue.use(Buefy); Vue.use(Buefy);
window.addEventListener('load', () => { var app = null;
var app = new Vue({
function loadApp() {
app = new Vue({
el: '#app', el: '#app',
delimiters: [ '[[', ']]' ], delimiters: [ '[[', ']]' ],
}) })
}); }
window.addEventListener('load', loadApp);

View File

@ -5,17 +5,25 @@ $body-background-color: $light;
@import "~bulma/bulma"; @import "~bulma/bulma";
.navbar { .is-fullwidth { width: 100%; }
margin-bottom: 1em; .is-fixed-bottom {
position: fixed;
bottom: 0;
margin-bottom: 0px;
border-radius: 0;
}
.is-borderless { border: none; }
.navbar + .container {
margin-top: 1em;
} }
.navbar.has-shadow { .navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
box-shadow: 0em 0.05em 0.5em rgba(0,0,0,0.1); box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
} }
/* /*
.navbar-brand img { .navbar-brand img {
min-height: 6em; min-height: 6em;
} }

View File

@ -1,11 +1,15 @@
import Vue from 'vue'; import Vue from 'vue';
import OnAir from './onAir.vue';
import Player from './player.vue';
import Tab from './tab.vue'; import Tab from './tab.vue';
import Tabs from './tabs.vue'; import Tabs from './tabs.vue';
Vue.component('a-on-air', OnAir);
Vue.component('a-player', Player);
Vue.component('a-tab', Tab); Vue.component('a-tab', Tab);
Vue.component('a-tabs', Tabs); Vue.component('a-tabs', Tabs);
export {Tab, Tabs}; export {Player, Tab, Tabs};

63
assets/vue/onAir.vue Normal file
View File

@ -0,0 +1,63 @@
<template>
<div class="media">
<!-- TODO HERE -->
</div>
</template>
<script>
export default {
props: {
url: String,
timeout: Number,
},
data() {
return {
// promise is set to null on destroy: this is used as check
// for timeout to know wether to repeat itself or not.
promise: null,
// on air infos
on_air: null
}
},
methods: {
fetch() {
const promise = fetch(url).then(response =>
reponse.ok ? response.json
: Promise.reject(response)
).then(data => {
this.on_air = data.results;
return this.on_air
})
this.promise = promise;
return promise;
},
refresh() {
const promise = this.fetch();
promise.then(data => {
if(promise != this.promise)
return;
window.setTimeout(() => this.update(), this.timeout*1000)
})
},
},
mounted() {
this.update()
},
destroyed() {
this.promise = null;
}
}
</script>

82
assets/vue/player.vue Normal file
View File

@ -0,0 +1,82 @@
<template>
<div class="media">
<div class="media-left">
<div class="button is-size-4" @click="toggle()">
<span class="fas fa-pause" v-if="playing"></span>
<span class="fas fa-play" v-else></span>
</div>
<audio ref="audio" @playing="onChange" @ended="onChange" @pause="onChange">
<slot name="sources"></slot>
</audio>
</div>
<div class="media-content">
</div>
</div>
</template>
<script>
export const State = {
paused: 0,
playing: 1,
loading: 2,
}
export default {
data() {
return {
state: State.paused,
}
},
props: {
onAir: String,
src: String,
},
computed: {
paused() { return this.state == State.paused; },
playing() { return this.state == State.playing; },
loading() { return this.state == State.loading; },
},
methods: {
load(src) {
const audio = this.$refs.audio;
audio.src = src;
audio.load()
},
play(src) {
if(src)
this.load(src);
this.$refs.audio.play().catch(e => console.error(e))
},
pause() {
this.$refs.audio.pause()
},
toggle() {
if(this.paused)
this.play()
else
this.pause()
},
onChange(event) {
const audio = this.$refs.audio;
this.state = audio.paused ? State.paused : State.playing;
},
},
mounted() {
this.load(this.src);
}
}
</script>

View File

@ -1,5 +1,16 @@
This file is used as a reminder, can be used as crappy documentation too. This file is used as a reminder, can be used as crappy documentation too.
- player
- monitor interface
- statistics interface
- traduction
- hot reload
Améliorations:
- calendar dashboard
- accessibilité
- player: playlist
# for the 1.0 # for the 1.0
- logs: - logs:

View File

@ -56,13 +56,12 @@ module.exports = (env, argv) => Object({
sideEffects: false sideEffects: false
}, },
{ {
test: /\.scss$/, test: /\.s?css$/,
use: [ { loader: MiniCssExtractPlugin.loader }, use: [ { loader: MiniCssExtractPlugin.loader },
{ loader: 'css-loader' }, { loader: 'css-loader' },
{ loader: 'sass-loader' , options: { sourceMap: true }} ], { loader: 'sass-loader' , options: { sourceMap: true }} ],
}, },
{ {
// TODO: remove ttf eot svg
test: /\.(ttf|eot|svg|woff2?)$/, test: /\.(ttf|eot|svg|woff2?)$/,
use: [{ use: [{
loader: 'file-loader', loader: 'file-loader',