forked from rc/aircox
		
	fix logs merge with diff algorithm
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@ -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'))
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
					 | 
				
			||||||
}*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -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& ***!
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ***!
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
				
			||||||
 | 
				
			|||||||
@ -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">♬</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" %}
 | 
				
			||||||
— {{ 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">♬</span>
 | 
				
			||||||
 | 
					    <span>{{ track.title }}</span>
 | 
				
			||||||
 | 
					    <span class="has-text-grey-dark has-text-weight-light">
 | 
				
			||||||
 | 
					    — {{ track.artist }}
 | 
				
			||||||
 | 
					    {% if track.info %}(<i>{{ track.info }}</i>){% endif %}
 | 
				
			||||||
 | 
					    </span>
 | 
				
			||||||
 | 
					    {% endwith %}
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
					 | 
				
			||||||
            {% with object as diffusion %}
 | 
					 | 
				
			||||||
            {% with diffusion.episode as object %}
 | 
					 | 
				
			||||||
            <td>{% include "aircox/episode_item.html" %}</td>
 | 
					 | 
				
			||||||
            {% endwith %}
 | 
					 | 
				
			||||||
            {% endwith %}
 | 
					 | 
				
			||||||
                {% else %}
 | 
					                {% 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>
 | 
				
			||||||
 | 
				
			|||||||
@ -49,8 +49,6 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
				
			|||||||
@ -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/'),
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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
									
								
							
							
						
						
									
										63
									
								
								assets/vue/onAir.vue
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										82
									
								
								assets/vue/player.vue
									
									
									
									
									
										Normal 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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								notes.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								notes.md
									
									
									
									
									
								
							@ -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:
 | 
				
			||||||
 | 
				
			|||||||
@ -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',
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user