diff --git a/aircox/models/station.py b/aircox/models/station.py index a87a2db..aaf6ae3 100644 --- a/aircox/models/station.py +++ b/aircox/models/station.py @@ -2,6 +2,7 @@ import os from django.db import models from django.utils.translation import gettext_lazy as _ +from django.utils.functional import cached_property from filer.fields.image import FilerImageField @@ -74,6 +75,11 @@ class Station(models.Model): objects = StationQuerySet.as_manager() + @cached_property + def streams(self): + """ Audio streams as list of urls. """ + return self.audio_streams.split('\n') if self.audio_streams else [] + def __str__(self): return self.name diff --git a/aircox/static/aircox/css/chunk-common.css b/aircox/static/aircox/css/chunk-common.css index 264f520..90019a9 100644 --- a/aircox/static/aircox/css/chunk-common.css +++ b/aircox/static/aircox/css/chunk-common.css @@ -10807,6 +10807,9 @@ a.navbar-item.is-active { .player .player-bar { border-top: 1px hsl(0deg, 0%, 71%) solid; } +.player .player-bar > div { + height: 3.75em !important; +} .player .player-bar > .media-left:not(:last-child) { margin-right: 0em; } @@ -10823,7 +10826,8 @@ a.navbar-item.is-active { } .player .player-bar .button { font-size: 1.5rem !important; - height: 2.5em; + height: 100%; + padding: auto 0.2em !important; min-width: 2.5em; border-radius: 0px; transition: background-color 1s; diff --git a/aircox/static/aircox/js/chunk-common.js b/aircox/static/aircox/js/chunk-common.js index 4985c9e..74983bd 100644 --- a/aircox/static/aircox/js/chunk-common.js +++ b/aircox/static/aircox/js/chunk-common.js @@ -65,7 +65,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ \*****************************************************************************************************************************************************************************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"State\": function() { return /* binding */ State; }\n/* harmony export */ });\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n/* harmony import */ var _live__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../live */ \"./src/live.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n/* harmony import */ var _APlaylist__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APlaylist */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _AProgress__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./AProgress */ \"./src/components/AProgress.vue\");\n\n\n\n\n\n\n\nconst State = {\n paused: 0,\n playing: 1,\n loading: 2\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n APlaylist: _APlaylist__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n AProgress: _AProgress__WEBPACK_IMPORTED_MODULE_6__[\"default\"]\n },\n data() {\n let audio = new Audio();\n audio.addEventListener('ended', e => this.onState(e));\n audio.addEventListener('pause', e => this.onState(e));\n audio.addEventListener('playing', e => this.onState(e));\n audio.addEventListener('timeupdate', () => {\n this.currentTime = this.audio.currentTime;\n });\n audio.addEventListener('durationchange', () => {\n this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;\n });\n let live = this.liveArgs ? (0,vue__WEBPACK_IMPORTED_MODULE_1__.reactive)(new _live__WEBPACK_IMPORTED_MODULE_2__[\"default\"](this.liveArgs)) : null;\n live && live.refresh();\n return {\n audio,\n duration: 0,\n currentTime: 0,\n state: State.paused,\n live,\n /// Loaded item\n loaded: null,\n //! Active panel name\n panel: null,\n //! current playing playlist name\n playlistName: null,\n //! players' playlists' sets\n sets: {\n queue: _model__WEBPACK_IMPORTED_MODULE_4__.Set.storeLoad(_sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"], \"playlist.queue\", {\n max: 30,\n unique: true\n }),\n pin: _model__WEBPACK_IMPORTED_MODULE_4__.Set.storeLoad(_sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"], \"player.pin\", {\n max: 30,\n unique: true\n })\n }\n };\n },\n props: {\n buttonTitle: String,\n liveArgs: Object\n },\n computed: {\n self() {\n return this;\n },\n paused() {\n return this.state == State.paused;\n },\n playing() {\n return this.state == State.playing;\n },\n loading() {\n return this.state == State.loading;\n },\n playlist() {\n return this.playlistName ? this.$refs[this.playlistName] : null;\n },\n current() {\n return this.loaded ? this.loaded : this.live && this.live.current;\n }\n },\n methods: {\n displayTime(seconds) {\n seconds = parseInt(seconds);\n let s = seconds % 60;\n seconds = (seconds - s) / 60;\n let m = seconds % 60;\n let h = (seconds - m) / 60;\n let [ss, mm, hh] = [s.toString().padStart(2, '0'), m.toString().padStart(2, '0'), h.toString().padStart(2, '0')];\n return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;\n },\n playlistButtonClass(name) {\n let set = this.sets[name];\n return (set ? (set.length ? \"\" : \"has-text-grey-light \") + (this.panel == name ? \"is-info \" : this.playlistName == name ? 'is-primary ' : '') : '') + \"button has-text-weight-bold\";\n },\n /// Show/hide panel\n togglePanel(panel) {\n this.panel = this.panel == panel ? null : panel;\n },\n /// Return True if item is loaded\n isLoaded(item) {\n return this.loaded && this.loaded.id == item.id;\n },\n /// Return True if item is loaded\n isPlaying(item) {\n return this.isLoaded(item) && !this.paused;\n },\n _setPlaylist(playlist) {\n this.playlistName = playlist;\n for (var p in this.sets) if (p != playlist) this.$refs[p].unselect();\n },\n /// Load a sound from playlist or live\n load(playlist = null, index = 0) {\n let src = null;\n\n // from playlist\n if (playlist !== null && index != -1) {\n let item = this.$refs[playlist].get(index);\n if (!item) throw `No sound at index ${index} for playlist ${playlist}`;\n this.loaded = item;\n this.current = item;\n src = item.src;\n }\n // from live\n else {\n this.loaded = null;\n this.current = this.live.current;\n src = this.live.src;\n }\n this._setPlaylist(playlist);\n\n // load sources\n const audio = this.audio;\n if (src instanceof Array) {\n audio.innerHTML = '';\n audio.removeAttribute('src');\n for (var s of src) {\n let source = document.createElement('source');\n source.setAttribute('src', s);\n audio.appendChild(source);\n }\n } else {\n audio.src = src;\n }\n audio.load();\n },\n play(playlist = null, index = 0) {\n this.load(playlist, index);\n this.audio.play().catch(e => console.error(e));\n },\n /// Push items to playlist (by name)\n push(playlist, ...items) {\n return this.sets[playlist].push(...items);\n },\n /// Push and play items\n playItems(playlist, ...items) {\n let index = this.push(playlist, ...items);\n this.$refs[playlist].selectedIndex = index;\n this.play(playlist, index);\n },\n /// Handle click event that plays multiple items (from `data-sounds` attribute)\n playButtonClick(event) {\n var items = JSON.parse(event.currentTarget.dataset.sounds);\n this.playItems('queue', ...items);\n },\n /// Pause\n pause() {\n this.audio.pause();\n },\n //! Play/pause\n togglePlay(playlist = null, index = 0) {\n if (playlist !== null) {\n let item = this.sets[playlist].get(index);\n if (!this.playlist || this.playlistName !== playlist || this.loaded != item) {\n this.play(playlist, index);\n return;\n }\n }\n if (this.paused) this.audio.play().catch(e => console.error(e));else this.audio.pause();\n },\n //! Pin/Unpin an item\n togglePin(item) {\n let index = this.sets.pin.findIndex(item);\n if (index > -1) this.sets.pin.remove(index);else {\n this.sets.pin.push(item);\n this.$refs.pinPlaylistButton.focus();\n }\n },\n /// Audio player state change event\n onState(event) {\n const audio = this.audio;\n this.state = audio.paused ? State.paused : State.playing;\n if (event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1)) this.play();\n }\n },\n mounted() {\n this.load();\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/APlayer.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"State\": function() { return /* binding */ State; }\n/* harmony export */ });\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n/* harmony import */ var _live__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../live */ \"./src/live.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n/* harmony import */ var _APlaylist__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APlaylist */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _AProgress__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./AProgress */ \"./src/components/AProgress.vue\");\n\n\n\n\n\n\n\nconst State = {\n paused: 0,\n playing: 1,\n loading: 2\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n APlaylist: _APlaylist__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n AProgress: _AProgress__WEBPACK_IMPORTED_MODULE_6__[\"default\"]\n },\n data() {\n let audio = new Audio();\n audio.addEventListener('ended', e => this.onState(e));\n audio.addEventListener('pause', e => this.onState(e));\n audio.addEventListener('playing', e => this.onState(e));\n audio.addEventListener('timeupdate', () => {\n this.currentTime = this.audio.currentTime;\n });\n audio.addEventListener('durationchange', () => {\n this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;\n });\n let live = this.liveArgs ? (0,vue__WEBPACK_IMPORTED_MODULE_1__.reactive)(new _live__WEBPACK_IMPORTED_MODULE_2__[\"default\"](this.liveArgs)) : null;\n live && live.refresh();\n return {\n audio,\n duration: 0,\n currentTime: 0,\n state: State.paused,\n live,\n /// Loaded item\n loaded: null,\n //! Active panel name\n panel: null,\n //! current playing playlist name\n playlistName: null,\n //! players' playlists' sets\n sets: {\n queue: _model__WEBPACK_IMPORTED_MODULE_4__.Set.storeLoad(_sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"], \"playlist.queue\", {\n max: 30,\n unique: true\n }),\n pin: _model__WEBPACK_IMPORTED_MODULE_4__.Set.storeLoad(_sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"], \"player.pin\", {\n max: 30,\n unique: true\n })\n }\n };\n },\n props: {\n buttonTitle: String,\n liveArgs: Object\n },\n computed: {\n self() {\n return this;\n },\n paused() {\n return this.state == State.paused;\n },\n playing() {\n return this.state == State.playing;\n },\n loading() {\n return this.state == State.loading;\n },\n playlist() {\n return this.playlistName ? this.$refs[this.playlistName] : null;\n },\n current() {\n return this.loaded ? this.loaded : this.live && this.live.current;\n }\n },\n methods: {\n displayTime(seconds) {\n seconds = parseInt(seconds);\n let s = seconds % 60;\n seconds = (seconds - s) / 60;\n let m = seconds % 60;\n let h = (seconds - m) / 60;\n let [ss, mm, hh] = [s.toString().padStart(2, '0'), m.toString().padStart(2, '0'), h.toString().padStart(2, '0')];\n return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;\n },\n playlistButtonClass(name) {\n let set = this.sets[name];\n return (set ? (set.length ? \"\" : \"has-text-grey-light \") + (this.panel == name ? \"is-info \" : this.playlistName == name ? 'is-primary ' : '') : '') + \"button has-text-weight-bold\";\n },\n /// Show/hide panel\n togglePanel(panel) {\n this.panel = this.panel == panel ? null : panel;\n },\n /// Return True if item is loaded\n isLoaded(item) {\n return this.loaded && this.loaded.id == item.id;\n },\n /// Return True if item is loaded\n isPlaying(item) {\n return this.isLoaded(item) && !this.paused;\n },\n _setPlaylist(playlist) {\n this.playlistName = playlist;\n for (var p in this.sets) if (p != playlist) this.$refs[p].unselect();\n },\n /// Load a sound from playlist or live\n load(playlist = null, index = 0) {\n let src = null;\n\n // from playlist\n if (playlist !== null && index != -1) {\n let item = this.$refs[playlist].get(index);\n if (!item) throw `No sound at index ${index} for playlist ${playlist}`;\n this.loaded = item;\n src = item.src;\n }\n // from live\n else {\n this.loaded = null;\n src = this.live.src;\n }\n this._setPlaylist(playlist);\n\n // load sources\n const audio = this.audio;\n if (src instanceof Array) {\n audio.innerHTML = '';\n audio.removeAttribute('src');\n for (var s of src) {\n let source = document.createElement('source');\n source.setAttribute('src', s);\n audio.appendChild(source);\n }\n } else {\n audio.src = src;\n }\n audio.load();\n },\n play(playlist = null, index = 0) {\n this.load(playlist, index);\n this.audio.play().catch(e => console.error(e));\n },\n /// Push items to playlist (by name)\n push(playlist, ...items) {\n return this.sets[playlist].push(...items);\n },\n /// Push and play items\n playItems(playlist, ...items) {\n let index = this.push(playlist, ...items);\n this.$refs[playlist].selectedIndex = index;\n this.play(playlist, index);\n },\n /// Handle click event that plays multiple items (from `data-sounds` attribute)\n playButtonClick(event) {\n var items = JSON.parse(event.currentTarget.dataset.sounds);\n this.playItems('queue', ...items);\n },\n /// Pause\n pause() {\n this.audio.pause();\n },\n //! Play/pause\n togglePlay(playlist = null, index = 0) {\n if (playlist !== null) {\n let item = this.sets[playlist].get(index);\n if (!this.playlist || this.playlistName !== playlist || this.loaded != item) {\n this.play(playlist, index);\n return;\n }\n }\n if (this.paused) this.audio.play().catch(e => console.error(e));else this.audio.pause();\n },\n //! Pin/Unpin an item\n togglePin(item) {\n let index = this.sets.pin.findIndex(item);\n if (index > -1) this.sets.pin.remove(index);else {\n this.sets.pin.push(item);\n this.$refs.pinPlaylistButton.focus();\n }\n },\n /// Audio player state change event\n onState(event) {\n const audio = this.audio;\n this.state = audio.paused ? State.paused : State.playing;\n if (event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1)) this.play();\n }\n },\n mounted() {\n this.load();\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/APlayer.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D"); /***/ }), @@ -205,7 +205,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac \*********************************************************************************************************************************************************************************************************************************************************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": function() { return /* binding */ render; }\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\nconst _hoisted_1 = {\n class: \"player\"\n};\nconst _hoisted_2 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"p\", {\n class: \"menu-label\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-thumbtack\"\n})]), /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(\" Pinned \")], -1 /* HOISTED */);\nconst _hoisted_3 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"p\", {\n class: \"menu-label\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-list\"\n})]), /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(\" Playlist \")], -1 /* HOISTED */);\nconst _hoisted_4 = {\n class: \"player-bar media\"\n};\nconst _hoisted_5 = {\n class: \"media-left\"\n};\nconst _hoisted_6 = [\"title\", \"aria-label\"];\nconst _hoisted_7 = {\n key: 0,\n class: \"fas fa-pause\"\n};\nconst _hoisted_8 = {\n key: 1,\n class: \"fas fa-play\"\n};\nconst _hoisted_9 = {\n key: 0,\n class: \"media-left media-cover\"\n};\nconst _hoisted_10 = [\"src\"];\nconst _hoisted_11 = {\n class: \"media-content\"\n};\nconst _hoisted_12 = {\n class: \"media-right\"\n};\nconst _hoisted_13 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon is-size-6 has-text-danger\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-circle\"\n})], -1 /* HOISTED */);\nconst _hoisted_14 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", null, \"Live\", -1 /* HOISTED */);\nconst _hoisted_15 = [_hoisted_13, _hoisted_14];\nconst _hoisted_16 = {\n key: 0,\n class: \"mr-2 is-size-6\"\n};\nconst _hoisted_17 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-thumbtack\"\n})], -1 /* HOISTED */);\nconst _hoisted_18 = {\n key: 0,\n class: \"mr-2 is-size-6\"\n};\nconst _hoisted_19 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-list\"\n})], -1 /* HOISTED */);\n\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n const _component_APlaylist = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"APlaylist\");\n const _component_AProgress = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"AProgress\");\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['player-panels', $data.panel ? 'is-open' : ''])\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_APlaylist, {\n ref: \"pin\",\n class: \"player-panel menu\",\n name: \"Pinned\",\n actions: ['page'],\n editable: true,\n player: $options.self,\n set: $data.sets.pin,\n onSelect: _cache[0] || (_cache[0] = $event => $options.togglePlay('pin', $event.index)),\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n }, {\n header: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [_hoisted_2]),\n _: 1 /* STABLE */\n }, 8 /* PROPS */, [\"player\", \"set\"]), [[vue__WEBPACK_IMPORTED_MODULE_0__.vShow, $data.panel == 'pin']]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_APlaylist, {\n ref: \"queue\",\n class: \"player-panel menu\",\n actions: ['page'],\n editable: true,\n player: $options.self,\n set: $data.sets.queue,\n onSelect: _cache[1] || (_cache[1] = $event => $options.togglePlay('queue', $event.index)),\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n }, {\n header: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [_hoisted_3]),\n _: 1 /* STABLE */\n }, 8 /* PROPS */, [\"player\", \"set\"]), [[vue__WEBPACK_IMPORTED_MODULE_0__.vShow, $data.panel == 'queue']])], 2 /* CLASS */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_4, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_5, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n class: \"button\",\n onClick: _cache[2] || (_cache[2] = $event => $options.togglePlay()),\n title: $props.buttonTitle,\n \"aria-label\": $props.buttonTitle\n }, [$options.playing ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_7)) : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_8))], 8 /* PROPS */, _hoisted_6)]), $options.current && $options.current.data.cover ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_9, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"img\", {\n src: $options.current.data.cover,\n class: \"cover\"\n }, null, 8 /* PROPS */, _hoisted_10)])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_11, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"content\", {\n loaded: $data.loaded,\n live: $data.live,\n current: $options.current\n }), $data.loaded && $data.duration ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(_component_AProgress, {\n key: 0,\n value: $data.currentTime,\n max: this.duration,\n format: $options.displayTime,\n class: \"pt-1 is-size-7\",\n onSelect: _cache[3] || (_cache[3] = $event => $data.audio.currentTime = $event)\n }, null, 8 /* PROPS */, [\"value\", \"max\", \"format\"])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_12, [$data.loaded ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"button\", {\n key: 0,\n class: \"button has-text-weight-bold\",\n onClick: _cache[4] || (_cache[4] = $event => $options.play())\n }, _hoisted_15)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n ref: \"pinPlaylistButton\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($options.playlistButtonClass('pin')),\n onClick: _cache[5] || (_cache[5] = $event => $options.togglePanel('pin'))\n }, [$data.sets.pin.length ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_16, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.sets.pin.length), 1 /* TEXT */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), _hoisted_17], 2 /* CLASS */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($options.playlistButtonClass('queue')),\n onClick: _cache[6] || (_cache[6] = $event => $options.togglePanel('queue'))\n }, [$data.sets.queue.length ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_18, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.sets.queue.length), 1 /* TEXT */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), _hoisted_19], 2 /* CLASS */)])])]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/APlayer.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/templateLoader.js??ruleSet%5B1%5D.rules%5B3%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": function() { return /* binding */ render; }\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\nconst _hoisted_1 = {\n class: \"player\"\n};\nconst _hoisted_2 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"p\", {\n class: \"menu-label\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-thumbtack\"\n})]), /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(\" Pinned \")], -1 /* HOISTED */);\nconst _hoisted_3 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"p\", {\n class: \"menu-label\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-list\"\n})]), /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(\" Playlist \")], -1 /* HOISTED */);\nconst _hoisted_4 = {\n class: \"player-bar media\"\n};\nconst _hoisted_5 = {\n class: \"media-left\"\n};\nconst _hoisted_6 = [\"title\", \"aria-label\"];\nconst _hoisted_7 = {\n key: 0,\n class: \"fas fa-pause\"\n};\nconst _hoisted_8 = {\n key: 1,\n class: \"fas fa-play\"\n};\nconst _hoisted_9 = {\n key: 0,\n class: \"media-left media-cover\"\n};\nconst _hoisted_10 = [\"src\"];\nconst _hoisted_11 = {\n class: \"media-content\"\n};\nconst _hoisted_12 = {\n class: \"media-right\"\n};\nconst _hoisted_13 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon is-size-6 has-text-danger\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-circle\"\n})], -1 /* HOISTED */);\nconst _hoisted_14 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", null, \"Live\", -1 /* HOISTED */);\nconst _hoisted_15 = [_hoisted_13, _hoisted_14];\nconst _hoisted_16 = {\n key: 0,\n class: \"is-size-6\"\n};\nconst _hoisted_17 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-thumbtack\"\n})], -1 /* HOISTED */);\nconst _hoisted_18 = {\n key: 0,\n class: \"is-size-6\"\n};\nconst _hoisted_19 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"fa fa-list\"\n})], -1 /* HOISTED */);\n\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n const _component_APlaylist = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"APlaylist\");\n const _component_AProgress = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"AProgress\");\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['player-panels', $data.panel ? 'is-open' : ''])\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_APlaylist, {\n ref: \"pin\",\n class: \"player-panel menu\",\n name: \"Pinned\",\n actions: ['page'],\n editable: true,\n player: $options.self,\n set: $data.sets.pin,\n onSelect: _cache[0] || (_cache[0] = $event => $options.togglePlay('pin', $event.index)),\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n }, {\n header: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [_hoisted_2]),\n _: 1 /* STABLE */\n }, 8 /* PROPS */, [\"player\", \"set\"]), [[vue__WEBPACK_IMPORTED_MODULE_0__.vShow, $data.panel == 'pin']]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_APlaylist, {\n ref: \"queue\",\n class: \"player-panel menu\",\n actions: ['page'],\n editable: true,\n player: $options.self,\n set: $data.sets.queue,\n onSelect: _cache[1] || (_cache[1] = $event => $options.togglePlay('queue', $event.index)),\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n }, {\n header: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [_hoisted_3]),\n _: 1 /* STABLE */\n }, 8 /* PROPS */, [\"player\", \"set\"]), [[vue__WEBPACK_IMPORTED_MODULE_0__.vShow, $data.panel == 'queue']])], 2 /* CLASS */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_4, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_5, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n class: \"button\",\n onClick: _cache[2] || (_cache[2] = $event => $options.togglePlay()),\n title: $props.buttonTitle,\n \"aria-label\": $props.buttonTitle\n }, [$options.playing ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_7)) : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_8))], 8 /* PROPS */, _hoisted_6)]), $options.current && $options.current.data.cover ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_9, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"img\", {\n src: $options.current.data.cover,\n class: \"cover\"\n }, null, 8 /* PROPS */, _hoisted_10)])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_11, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"content\", {\n loaded: $data.loaded,\n live: $data.live,\n current: $options.current\n }), $data.loaded && $data.duration ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(_component_AProgress, {\n key: 0,\n value: $data.currentTime,\n max: this.duration,\n format: $options.displayTime,\n class: \"pt-1 is-size-7\",\n onSelect: _cache[3] || (_cache[3] = $event => $data.audio.currentTime = $event)\n }, null, 8 /* PROPS */, [\"value\", \"max\", \"format\"])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_12, [$data.loaded ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"button\", {\n key: 0,\n class: \"button has-text-weight-bold\",\n onClick: _cache[4] || (_cache[4] = $event => $options.play())\n }, _hoisted_15)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n ref: \"pinPlaylistButton\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($options.playlistButtonClass('pin')),\n onClick: _cache[5] || (_cache[5] = $event => $options.togglePanel('pin'))\n }, [$data.sets.pin.length ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_16, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.sets.pin.length), 1 /* TEXT */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), _hoisted_17], 2 /* CLASS */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($options.playlistButtonClass('queue')),\n onClick: _cache[6] || (_cache[6] = $event => $options.togglePanel('queue'))\n }, [$data.sets.queue.length ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_18, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.sets.queue.length), 1 /* TEXT */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), _hoisted_19], 2 /* CLASS */)])])]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/APlayer.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/templateLoader.js??ruleSet%5B1%5D.rules%5B3%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D"); /***/ }), diff --git a/aircox/templates/aircox/page_detail.html b/aircox/templates/aircox/page_detail.html index 8dc4e2b..5aa296c 100644 --- a/aircox/templates/aircox/page_detail.html +++ b/aircox/templates/aircox/page_detail.html @@ -5,12 +5,13 @@ Base template used to display a Page Context: - page: page +- parent: parent page {% endcomment %} {% block header_crumbs %} {{ block.super }} {% if page.category %} -/ {{ page.category.title }} +{% if parent %} / {% endif %} {{ page.category.title }} {% endif %} {% endblock %} diff --git a/aircox/templates/aircox/widgets/player.html b/aircox/templates/aircox/widgets/player.html index b7d8c02..aa2584c 100644 --- a/aircox/templates/aircox/widgets/player.html +++ b/aircox/templates/aircox/widgets/player.html @@ -9,8 +9,9 @@ The audio player role="{% translate "player" %}" aria-description="{% translate "Audio player used to listen to the radio and podcasts" %}">