From e0f1ac498f0ca2449de31f9a1bbdcf63b2f31b80 Mon Sep 17 00:00:00 2001 From: bkfox Date: Mon, 12 Aug 2019 04:11:04 +0200 Subject: [PATCH] fix logs merge with diff algorithm --- .../__pycache__/__init__.cpython-37.pyc | Bin 559 -> 559 bytes aircox/models/__pycache__/log.cpython-37.pyc | Bin 7752 -> 9093 bytes .../models/__pycache__/station.cpython-37.pyc | Bin 4989 -> 5120 bytes aircox/models/log.py | 69 ++++++-- aircox/models/signals.py | 6 +- aircox/models/station.py | 5 + aircox/static/aircox/main.css | 26 +-- aircox/static/aircox/main.js | 149 ++++++++++++++++-- aircox/static/aircox/vendor.js | 22 +++ aircox/templates/aircox/base.html | 4 + aircox/templates/aircox/log_item.html | 31 ++-- aircox/templates/aircox/log_list.html | 13 +- aircox/templates/aircox/page_list.html | 2 - aircox/urls.py | 8 +- aircox/views/__init__.py | 2 + aircox/views/base.py | 5 + aircox/views/log.py | 67 ++------ assets/index.js | 5 +- assets/js/index.js | 10 +- assets/styles.scss | 20 ++- assets/vue/index.js | 6 +- assets/vue/onAir.vue | 63 ++++++++ assets/vue/player.vue | 82 ++++++++++ notes.md | 11 ++ webpack.config.js | 3 +- 25 files changed, 485 insertions(+), 124 deletions(-) create mode 100644 assets/vue/onAir.vue create mode 100644 assets/vue/player.vue diff --git a/aircox/models/__pycache__/__init__.cpython-37.pyc b/aircox/models/__pycache__/__init__.cpython-37.pyc index f5130d95442e6412e6de0e91b5ee9180d42558fc..2b2f32353c72ad6e0cf360507758c4b49586fb64 100644 GIT binary patch delta 20 acmZ3_vYv(8iIO delta 20 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 q)CU=Sk8x!R>n5BPRh z^s+_?r3x<)S;!D2nj(=R*}@PdmLk*6kj9uIl_K53871D%z`_tE5zL?|yZIG!B2!Qm zZ(?amX1+pkNl|KIZgD092LnTujU%#{LTOP>u|jEaYKlTqB~W2vNoIbYda*)5PGV(h zk)DEoUMfh5LZU)WW?rh^WN$W4MyJhF*=iUWT_+21=rBr7w&!T%i-On`53y7r8Uq2mlPLq5Cla6&p#WMGWEErRya54$lTHxb0gsa` U5!L~hlLHdq0Zy~z5*q;lGfeF-SO5S3 diff --git a/aircox/models/log.py b/aircox/models/log.py index 6caf041..ff067de 100644 --- a/aircox/models/log.py +++ b/aircox/models/log.py @@ -1,3 +1,4 @@ +from collections import deque import datetime from enum import IntEnum import logging @@ -25,10 +26,14 @@ class LogQuerySet(models.QuerySet): return self.filter(station=station) if id is None else \ self.filter(station_id=id) - def at(self, date=None): - date = utils.date_or_default(date) + def today(self, 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): return self.filter(type=Log.Type.on_air) @@ -100,13 +105,9 @@ class LogQuerySet(models.QuerySet): } def rel_obj(log, attr): - attr_id = attr + '_id' rel_id = log.get(attr + '_id') - return rels[attr][rel_id] if rel_id else None - # make logs - return [ Log(diffusion=rel_obj(log, 'diffusion'), sound=rel_obj(log, 'sound'), @@ -134,7 +135,7 @@ class LogQuerySet(models.QuerySet): if os.path.exists(path) and not force: return -1 - qs = self.station(station).at(date) + qs = self.station(station).today(date) if not qs.exists(): return 0 @@ -241,6 +242,56 @@ class Log(models.Model): """ 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): r = [] if self.diffusion: @@ -252,7 +303,3 @@ class Log(models.Model): logger.info('log %s: %s%s', str(self), self.comment or '', ' (' + ', '.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')) diff --git a/aircox/models/signals.py b/aircox/models/signals.py index 7b2ad76..dfab8ef 100755 --- a/aircox/models/signals.py +++ b/aircox/models/signals.py @@ -44,7 +44,7 @@ def program_post_save(sender, instance, created, *args, **kwargs): Clean-up later diffusions when a program becomes inactive """ 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) \ .delete() @@ -70,7 +70,7 @@ def schedule_post_save(sender, instance, created, *args, **kwargs): today = tz.datetime.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)] qs.filter(pk__in=pks).update( start=F('start') + delta, @@ -86,7 +86,7 @@ def schedule_pre_delete(sender, instance, *args, **kwargs): if not instance.program.sync: 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)] qs.filter(pk__in=pks).delete() diff --git a/aircox/models/station.py b/aircox/models/station.py index e18f431..751cdf1 100644 --- a/aircox/models/station.py +++ b/aircox/models/station.py @@ -55,6 +55,11 @@ class Station(models.Model): _("website's urls"), max_length=512, null=True, blank=True, 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() diff --git a/aircox/static/aircox/main.css b/aircox/static/aircox/main.css index 35c8116..3c0d3ef 100644 --- a/aircox/static/aircox/main.css +++ b/aircox/static/aircox/main.css @@ -7147,15 +7147,25 @@ label.panel-block { background-color: #fafafa; padding: 3rem 1.5rem 6rem; } -.navbar { - margin-bottom: 1em; } +.is-fullwidth { + width: 100%; } -.navbar.has-shadow { - box-shadow: 0em 0.05em 0.5em rgba(0, 0, 0, 0.1); } +.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.is-fixed-bottom.has-shadow { + box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.1); } /* - - .navbar-brand img { min-height: 6em; } @@ -7200,7 +7210,3 @@ aside .media .subtitle { aside .media .content { display: none; } -/**[noscript="hidden"] { - display: none; -}*/ - diff --git a/aircox/static/aircox/main.js b/aircox/static/aircox/main.js index 91cd3bd..7b0ae3c 100644 --- a/aircox/static/aircox/main.js +++ b/aircox/static/aircox/main.js @@ -164,7 +164,7 @@ /***/ (function(module, __webpack_exports__, __webpack_require__) { "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__) { "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?"); - -/***/ }), - -/***/ "./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?"); +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?"); /***/ }), @@ -206,11 +195,89 @@ eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./asse /*!*****************************!*\ !*** ./assets/vue/index.js ***! \*****************************/ -/*! exports provided: Tab, Tabs */ +/*! exports provided: Player, Tab, Tabs */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "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??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??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tab.vue?vue&type=template&id=65401e0e& ***! diff --git a/aircox/static/aircox/vendor.js b/aircox/static/aircox/vendor.js index dc2f41b..c2ed976 100644 --- a/aircox/static/aircox/vendor.js +++ b/aircox/static/aircox/vendor.js @@ -1,5 +1,27 @@ (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 ***! diff --git a/aircox/templates/aircox/base.html b/aircox/templates/aircox/base.html index 79eb850..1812fdb 100644 --- a/aircox/templates/aircox/base.html +++ b/aircox/templates/aircox/base.html @@ -14,6 +14,7 @@ Context: {% block assets %} + {% endblock %} @@ -80,6 +81,9 @@ Context: {% endif %} + +
+ {% include "aircox/player.html" %} diff --git a/aircox/templates/aircox/log_item.html b/aircox/templates/aircox/log_item.html index 0cf518e..b6e8a9d 100644 --- a/aircox/templates/aircox/log_item.html +++ b/aircox/templates/aircox/log_item.html @@ -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 %} - -{{ track.title }} - -— {{ track.artist }} -{% if track.info %}({{ track.info }}){% endif %} - -{% endwith %} +{% if object|is_diffusion %} + {% with object as diffusion %} + {% with diffusion.episode as object %} + {% include "aircox/episode_item.html" %} + {% endwith %} + {% endwith %} +{% else %} + {% with object.track as track %} + + {{ track.title }} + + — {{ track.artist }} + {% if track.info %}({{ track.info }}){% endif %} + + {% endwith %} +{% endif %} diff --git a/aircox/templates/aircox/log_list.html b/aircox/templates/aircox/log_list.html index 4fa4830..2a81cda 100644 --- a/aircox/templates/aircox/log_list.html +++ b/aircox/templates/aircox/log_list.html @@ -33,25 +33,18 @@ {% for object in object_list reversed %} - {% if object|is_diffusion %} - {% with object as diffusion %} - {% with diffusion.episode as object %} - - {% endwith %} - {% endwith %} - {% else %} - - {% endif %} {% endfor %}
+ {% if object|is_diffusion %} - {% include "aircox/episode_item.html" %} + {% else %} + {% endif %} {% include "aircox/log_item.html" %}
diff --git a/aircox/templates/aircox/page_list.html b/aircox/templates/aircox/page_list.html index 28352d5..aff0d98 100644 --- a/aircox/templates/aircox/page_list.html +++ b/aircox/templates/aircox/page_list.html @@ -49,8 +49,6 @@ - - {% endblock %} diff --git a/aircox/urls.py b/aircox/urls.py index 923c6d5..29e4792 100755 --- a/aircox/urls.py +++ b/aircox/urls.py @@ -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 . import views, models @@ -17,7 +17,13 @@ register_converter(WeekConverter, 'week') # ] +api = [ + path('on-air/', views.api.OnAirAPIView.as_view(), name='on-air'), +] + + urls = [ + path('api/', include(api)), # path('', views.PageDetailView.as_view(model=models.Article), # name='home'), path(_('articles/'), diff --git a/aircox/views/__init__.py b/aircox/views/__init__.py index 430f02a..3a1f624 100644 --- a/aircox/views/__init__.py +++ b/aircox/views/__init__.py @@ -1,3 +1,5 @@ +from . import api + from .article import ArticleListView from .base import BaseView from .episode import EpisodeDetailView, EpisodeListView, TimetableView diff --git a/aircox/views/base.py b/aircox/views/base.py index 3bec93a..4409989 100644 --- a/aircox/views/base.py +++ b/aircox/views/base.py @@ -28,5 +28,10 @@ class BaseView(TemplateResponseMixin, ContextMixin): kwargs.setdefault('station', self.station) kwargs.setdefault('cover', self.cover) 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) diff --git a/aircox/views/log.py b/aircox/views/log.py index a886781..cee59f3 100644 --- a/aircox/views/log.py +++ b/aircox/views/log.py @@ -7,66 +7,22 @@ from ..models import Diffusion, Log from .base import BaseView -__all__ = ['BaseLogView', 'LogListView'] +__all__ = ['BaseLogListView', 'LogListView'] -class BaseLogView(ListView): - station = None +class BaseLogListView: date = None - delta = None def get_queryset(self): # only get logs for tracks: log for diffusion will be retrieved # by the diffusions' queryset. - return super().get_queryset().station(self.station).on_air() \ - .at(self.date).filter(track__isnull=False) + return super().get_queryset().on_air().filter(track__isnull=False) def get_diffusions_queryset(self): - 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 + return Diffusion.objects.station(self.station).on_air() -class LogListView(BaseView, BaseLogView): +class LogListView(BaseView, BaseLogListView, ListView): model = Log date = None @@ -80,6 +36,14 @@ class LogListView(BaseView, BaseLogView): if 'date' in self.kwargs else today 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): today = datetime.date.today() max_date = min(max(self.date + datetime.timedelta(days=3), @@ -91,6 +55,9 @@ class LogListView(BaseView, BaseLogView): dates=(date for date in ( max_date - datetime.timedelta(days=i) 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 ) + + diff --git a/assets/index.js b/assets/index.js index a52ee43..1abf65e 100644 --- a/assets/index.js +++ b/assets/index.js @@ -1,5 +1,8 @@ +import '@fortawesome/fontawesome-free/css/all.min.css'; +import '@fortawesome/fontawesome-free/css/fontawesome.min.css'; + import './js'; import './styles.scss'; -import './noscript.scss'; +// import './noscript.scss'; import './vue'; diff --git a/assets/js/index.js b/assets/js/index.js index 215a588..78e99d6 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -3,12 +3,16 @@ import Buefy from 'buefy'; Vue.use(Buefy); -window.addEventListener('load', () => { - var app = new Vue({ +var app = null; + +function loadApp() { + app = new Vue({ el: '#app', delimiters: [ '[[', ']]' ], }) -}); +} +window.addEventListener('load', loadApp); + diff --git a/assets/styles.scss b/assets/styles.scss index ec5f28a..a65725e 100644 --- a/assets/styles.scss +++ b/assets/styles.scss @@ -5,17 +5,25 @@ $body-background-color: $light; @import "~bulma/bulma"; -.navbar { - margin-bottom: 1em; +.is-fullwidth { width: 100%; } +.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 { - box-shadow: 0em 0.05em 0.5em rgba(0,0,0,0.1); +.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow { + box-shadow: 0em 0em 1em rgba(0,0,0,0.1); } /* - - .navbar-brand img { min-height: 6em; } diff --git a/assets/vue/index.js b/assets/vue/index.js index a9ea751..ea8d98f 100644 --- a/assets/vue/index.js +++ b/assets/vue/index.js @@ -1,11 +1,15 @@ import Vue from 'vue'; +import OnAir from './onAir.vue'; +import Player from './player.vue'; import Tab from './tab.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-tabs', Tabs); -export {Tab, Tabs}; +export {Player, Tab, Tabs}; diff --git a/assets/vue/onAir.vue b/assets/vue/onAir.vue new file mode 100644 index 0000000..d8ad445 --- /dev/null +++ b/assets/vue/onAir.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/assets/vue/player.vue b/assets/vue/player.vue new file mode 100644 index 0000000..268a1d3 --- /dev/null +++ b/assets/vue/player.vue @@ -0,0 +1,82 @@ + + + + + + + + diff --git a/notes.md b/notes.md index a2d4420..94aa64a 100755 --- a/notes.md +++ b/notes.md @@ -1,5 +1,16 @@ 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 - logs: diff --git a/webpack.config.js b/webpack.config.js index 728c024..f5d08de 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -56,13 +56,12 @@ module.exports = (env, argv) => Object({ sideEffects: false }, { - test: /\.scss$/, + test: /\.s?css$/, use: [ { loader: MiniCssExtractPlugin.loader }, { loader: 'css-loader' }, { loader: 'sass-loader' , options: { sourceMap: true }} ], }, { - // TODO: remove ttf eot svg test: /\.(ttf|eot|svg|woff2?)$/, use: [{ loader: 'file-loader',