From 239580c04aafa6145b95964aca088ebeb60d4759 Mon Sep 17 00:00:00 2001 From: bkfox Date: Sun, 8 Nov 2020 14:10:03 +0100 Subject: [PATCH] work on hot reload; fix bugs in player --- aircox/static/aircox/admin.css | 11 +-- aircox/static/aircox/admin.js | 14 ++-- aircox/static/aircox/main.css | 11 +-- aircox/static/aircox/main.js | 14 ++-- aircox/static/aircox/streamer.js | 4 +- .../aircox/widgets/podcast_item.html | 2 +- assets/public/app.js | 69 +++++++++------ assets/public/index.js | 75 +++++++++-------- assets/public/player.vue | 83 ++++++++++--------- assets/public/soundItem.vue | 5 +- assets/public/styles.scss | 10 +-- 11 files changed, 162 insertions(+), 136 deletions(-) diff --git a/aircox/static/aircox/admin.css b/aircox/static/aircox/admin.css index 5f8bc7c..2a52547 100644 --- a/aircox/static/aircox/admin.css +++ b/aircox/static/aircox/admin.css @@ -7443,8 +7443,6 @@ a.navbar-item.is-active { min-width: 2.5em; border-radius: 0px; transition: background-color 1s; } - .player .player-bar .button:focus { - background-color: #209cee; } .player .player-bar .title { margin: 0em; } @@ -7469,9 +7467,6 @@ main .cover.is-small { main .cover.is-tiny { height: 2em; } -.sound-item .cover { - height: 5em; } - aside > section { margin-bottom: 2em; } @@ -7487,6 +7482,12 @@ aside .cover.is-tiny { aside .media .subtitle { font-size: 1em; } +.sound-item .cover { + height: 5em; } + +.sound-item .media-content a { + padding: 0em; } + .is-round, .sound-item .button { border: 1px #7a7a7a solid; border-radius: 1em; } diff --git a/aircox/static/aircox/admin.js b/aircox/static/aircox/admin.js index 28277f2..d4b2111 100644 --- a/aircox/static/aircox/admin.js +++ b/aircox/static/aircox/admin.js @@ -218,11 +218,11 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod /*!******************************!*\ !*** ./assets/public/app.js ***! \******************************/ -/*! exports provided: defaultConfig, default, AppConfig */ +/*! exports provided: defaultConfig, default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return App; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"AppConfig\", function() { return AppConfig; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n data() {\n return {\n page: null,\n }\n },\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\nfunction App(config, sync=false) {\n return (new AppConfig(config)).load(sync)\n}\n\n/**\n * Application config for an application instance\n */\nclass AppConfig {\n constructor(config) {\n this._config = config;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n load(sync=false) {\n var self = this;\n return new Promise(function(resolve, reject) {\n let func = () => { try { resolve(self.build()) } catch(error) { reject(error) }};\n sync ? func() : window.addEventListener('load', func);\n });\n }\n\n build() {\n let config = this.config;\n const el = document.querySelector(config.el)\n if(!el) {\n reject(`Error: missing element ${config.el}`);\n return;\n }\n return new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null, title=null, url=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n if(url && content)\n history.pushState({ content: content, title: title }, '', url)\n\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n loadFromState(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); /***/ }), @@ -306,7 +306,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__ = __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_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__ = __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_2___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./assets/public/app.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./styles.scss */ \"./assets/public/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./autocomplete */ \"./assets/public/autocomplete.vue\");\n/* harmony import */ var _episode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./episode */ \"./assets/public/episode.vue\");\n/* harmony import */ var _player__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./player */ \"./assets/public/player.vue\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _soundItem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./soundItem */ \"./assets/public/soundItem.vue\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n\n\n\n\n//-- aircox\n\n\n\n\n\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-autocomplete', _autocomplete__WEBPACK_IMPORTED_MODULE_7__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-episode', _episode__WEBPACK_IMPORTED_MODULE_8__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player__WEBPACK_IMPORTED_MODULE_9__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-playlist', _playlist__WEBPACK_IMPORTED_MODULE_10__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-sound-item', _soundItem__WEBPACK_IMPORTED_MODULE_11__[\"default\"])\n\n\nwindow.aircox = {\n // main application\n app: null,\n\n // main application config\n appConfig: {},\n\n // player application\n playerApp: null,\n\n // player component\n get player() {\n return this.playerApp && this.playerApp.$refs.player\n },\n\n loadPage(url) {\n fetch(url).then(response => response.text())\n .then(response => {\n let doc = new DOMParser().parseFromString(response, 'text/html');\n\n aircox.app && aircox.app.$destroy();\n document.getElementById('app').innerHTML = doc.getElementById('app').innerHTML;\n Object(_app__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(() => window.aircox.appConfig, true).then(app => {\n aircox.app = app;\n document.title = doc.title;\n })\n });\n },\n\n Set: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"], Sound: _sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n};\nwindow.Vue = vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"];\n\n\nObject(_app__WEBPACK_IMPORTED_MODULE_3__[\"default\"])({el: '#player'}).then(app => window.aircox.playerApp = app);\nObject(_app__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(() => window.aircox.appConfig).then(app => {\n window.aircox.app = app;\n window.addEventListener('click', event => {\n let target = event.target.tagName == 'A' ? event.target : event.target.closest('a');\n if(!target || !target.hasAttribute('href'))\n return;\n\n let href = target.getAttribute('href');\n if(href && href !='#') {\n window.aircox.loadPage(href);\n event.preventDefault();\n event.stopPropagation();\n }\n }, true);\n})\n\n\n\n\n//# sourceURL=webpack:///./assets/public/index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__ = __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_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__ = __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_2___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./assets/public/app.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./styles.scss */ \"./assets/public/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./autocomplete */ \"./assets/public/autocomplete.vue\");\n/* harmony import */ var _episode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./episode */ \"./assets/public/episode.vue\");\n/* harmony import */ var _player__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./player */ \"./assets/public/player.vue\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _soundItem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./soundItem */ \"./assets/public/soundItem.vue\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n\n\n\n\n//-- aircox\n\n\n\n\n\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-autocomplete', _autocomplete__WEBPACK_IMPORTED_MODULE_7__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-episode', _episode__WEBPACK_IMPORTED_MODULE_8__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player__WEBPACK_IMPORTED_MODULE_9__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-playlist', _playlist__WEBPACK_IMPORTED_MODULE_10__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-sound-item', _soundItem__WEBPACK_IMPORTED_MODULE_11__[\"default\"])\n\n\nwindow.aircox = {\n // main application\n appBuilder: null,\n appConfig: {},\n get app() { return this.appBuilder.app },\n\n // player application\n playerBuilder: null,\n get playerApp() { return this.playerBuilder && this.playerBuilder.app },\n get player() { return this.playerApp && this.playerApp.$refs.player },\n\n onPageFetch(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A'\n ? event.target : event.target.closest('a');\n if(!target || target.hasAttribute('target'))\n return;\n\n let url = submit ? target.getAttribute('action') || ''\n : target.getAttribute('href');\n if(url===null || !(url === '' || url.startsWith('/') || url.startsWith('?')))\n return;\n\n let options = {};\n if(submit) {\n let formData = new FormData(event.target);\n if(target.method == 'get')\n url += '?' + (new URLSearchParams(formData)).toString();\n else {\n options['method'] = target.method;\n options['body'] = formData;\n }\n }\n this.appBuilder.fetch(url, options);\n event.preventDefault();\n event.stopPropagation();\n },\n\n Set: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"], Sound: _sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n};\nwindow.Vue = vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"];\n\n\naircox.playerBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"]({el: '#player'});\naircox.playerBuilder.load({async:true});\naircox.appBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x => window.aircox.appConfig);\naircox.appBuilder.load({async:true}).then(app => {\n //-- load page hooks\n window.addEventListener('click', event => aircox.onPageFetch(event), true);\n window.addEventListener('submit', event => aircox.onPageFetch(event), true);\n window.addEventListener('popstate', event => {\n if(event.state && event.state.content)\n aircox.appBuilder.loadFromState(event.state);\n });\n})\n\n\n\n\n//# sourceURL=webpack:///./assets/public/index.js?"); /***/ }), @@ -653,7 +653,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n//\n//\n//\n//\n//\n//\n\n/* /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"State\", function() { return State; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _live__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./live */ \"./assets/public/live.js\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _progress__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./progress */ \"./assets/public/progress.vue\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\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__[\"default\"] = ({\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', e => {\n this.currentTime = this.audio.currentTime;\n });\n audio.addEventListener('durationchange', e => {\n this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;\n });\n\n return {\n audio, duration: 0, currentTime: 0, state: State.paused,\n\n /// Live instance\n live: this.liveArgs ? new _live__WEBPACK_IMPORTED_MODULE_1__[\"default\"](this.liveArgs) : null,\n /// Loaded item\n loaded: null,\n //! Active panel name\n panel: null,\n //! current playing playlist component\n playlist: null,\n //! players' playlists' sets\n sets: {\n queue: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"].storeLoad(_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"], \"playlist.queue\", { max: 30, unique: true }),\n pin: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"].storeLoad(_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"], \"player.pin\", { max: 30, unique: true }),\n }\n }\n },\n\n props: {\n buttonTitle: String,\n liveArgs: Object,\n },\n\n computed: {\n self() { return this; },\n paused() { return this.state == State.paused; },\n playing() { return this.state == State.playing; },\n loading() { return this.state == State.loading; },\n\n current() {\n return this.loaded || this.live && this.live.current;\n },\n\n buttonStyle() {\n if(!this.current)\n return;\n return { backgroundImage: `url(${this.current.cover})` }\n },\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\n let [ss,mm,hh] = [s.toString().padStart(2, '0'),\n m.toString().padStart(2, '0'),\n h.toString().padStart(2, '0')];\n return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;\n },\n\n playlistButtonClass(name) {\n let set = this.sets[name];\n return (set ? (set.length ? \"\" : \"has-text-grey-light \")\n + (this.panel == name ? \"is-info \"\n : this.playlist && this.playlist == this.$refs[name] ? 'is-primary '\n : '') : '')\n + \"button has-text-weight-bold\";\n },\n\n /// Show/hide panel\n togglePanel(panel) { this.panel = this.panel == panel ? null : panel },\n /// Return True if item is loaded\n isLoaded(item) { return this.loaded && this.loaded.id == item.id },\n /// Return True if item is loaded\n isPlaying(item) { return this.isLoaded(item) && !this.paused },\n\n _setPlaylist(playlist) {\n this.playlist = playlist ? this.$refs[playlist] : null;\n for(var p in this.sets)\n if(p != playlist)\n this.$refs[p].unselect();\n },\n\n load(playlist, {src=null, item=null}={}) {\n src = src || item.src;\n this.loaded = item;\n this._setPlaylist(playlist);\n\n const audio = this.audio;\n if(src instanceof Array) {\n audio.innerHTML = '';\n for(var s of src) {\n let source = document.createElement(source);\n source.setAttribute('src', s);\n audio.appendChild(source)\n }\n }\n else {\n audio.src = src;\n }\n audio.load();\n },\n\n /// Play a playlist's sound (by playlist name, and sound index)\n play(playlist=null, index=0) {\n if(index < 0)\n return;\n\n if(!playlist)\n playlist = 'queue';\n\n console.log('play', playlist, index, this.audio);\n let item = this.$refs[playlist].get(index);\n if(item) {\n this.load(playlist, {item});\n this.audio.play().catch(e => console.error(e))\n }\n else\n throw `No sound at index ${index} for playlist ${playlist}`;\n },\n\n /// Push items to playlist (by name)\n push(playlist, ...items) {\n this.$refs[playlist].push(...items);\n },\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\n playButtonClick(event) {\n var items = JSON.parse(event.currentTarget.dataset.sounds);\n this.playItems('queue', ...items);\n },\n\n /// Play live stream\n playLive() {\n this.load(null, {src: this.live.src});\n this.audio.play().catch(e => console.error(e))\n this.panel = '';\n },\n\n /// Pause\n pause() {\n this.audio.pause()\n },\n\n //! Play/pause\n togglePlay() {\n if(this.paused)\n this.audio.play().catch(e => console.error(e))\n else\n this.pause()\n },\n\n //! Pin/Unpin an item\n togglePin(item) {\n let index = this.sets.pin.findIndex(item);\n if(index > -1)\n this.sets.pin.remove(index);\n else {\n this.sets.pin.push(item);\n this.$refs.pinPlaylistButton.focus();\n }\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\n if(event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1))\n this.playLive();\n },\n },\n\n components: { Playlist: _playlist__WEBPACK_IMPORTED_MODULE_2__[\"default\"], Progress: _progress__WEBPACK_IMPORTED_MODULE_3__[\"default\"] },\n});\n\n\n//# sourceURL=webpack:///./assets/public/player.vue?./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"State\", function() { return State; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _live__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./live */ \"./assets/public/live.js\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _progress__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./progress */ \"./assets/public/progress.vue\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\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__[\"default\"] = ({\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', e => {\n this.currentTime = this.audio.currentTime;\n });\n audio.addEventListener('durationchange', e => {\n this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;\n });\n\n return {\n audio, duration: 0, currentTime: 0, state: State.paused,\n\n /// Live instance\n live: this.liveArgs ? new _live__WEBPACK_IMPORTED_MODULE_1__[\"default\"](this.liveArgs) : null,\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_5__[\"Set\"].storeLoad(_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"], \"playlist.queue\", { max: 30, unique: true }),\n pin: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"].storeLoad(_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"], \"player.pin\", { max: 30, unique: true }),\n }\n }\n },\n\n props: {\n buttonTitle: String,\n liveArgs: Object,\n },\n\n computed: {\n self() { return this; },\n paused() { return this.state == State.paused; },\n playing() { return this.state == State.playing; },\n loading() { return this.state == State.loading; },\n\n playlist() {\n return this.playlistName ? this.$refs[this.playlistName] : null;\n },\n\n current() {\n return this.loaded || this.live && this.live.current;\n },\n\n buttonStyle() {\n if(!this.current)\n return;\n return { backgroundImage: `url(${this.current.cover})` }\n },\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\n let [ss,mm,hh] = [s.toString().padStart(2, '0'),\n m.toString().padStart(2, '0'),\n h.toString().padStart(2, '0')];\n return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;\n },\n\n playlistButtonClass(name) {\n let set = this.sets[name];\n return (set ? (set.length ? \"\" : \"has-text-grey-light \")\n + (this.panel == name ? \"is-info \"\n : this.playlistName == name ? 'is-primary '\n : '') : '')\n + \"button has-text-weight-bold\";\n },\n\n /// Show/hide panel\n togglePanel(panel) { this.panel = this.panel == panel ? null : panel },\n /// Return True if item is loaded\n isLoaded(item) { return this.loaded && this.loaded.id == item.id },\n /// Return True if item is loaded\n isPlaying(item) { return this.isLoaded(item) && !this.paused },\n\n _setPlaylist(playlist) {\n this.playlistName = playlist;\n for(var p in this.sets)\n if(p != playlist)\n this.$refs[p].unselect();\n },\n\n /// Play a playlist's sound (by playlist name, and sound index)\n play(playlist=null, index=0) {\n let src = null;\n\n // from playlist\n if(playlist !== null) {\n let item = this.$refs[playlist].get(index);\n if(!item)\n 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\n this._setPlaylist(playlist);\n\n // load sources\n const audio = this.audio;\n if(src instanceof Array) {\n audio.innerHTML = '';\n for(var s of src) {\n let source = document.createElement(source);\n source.setAttribute('src', s);\n audio.appendChild(source)\n }\n }\n else {\n audio.src = src;\n }\n audio.load();\n audio.play();\n audio.play().catch(e => console.error(e))\n },\n\n /// Push items to playlist (by name)\n push(playlist, ...items) {\n return this.$refs[playlist].push(...items);\n },\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\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\n /// Pause\n pause() {\n this.audio.pause()\n },\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)\n this.audio.play().catch(e => console.error(e))\n else\n this.audio.pause();\n },\n\n //! Pin/Unpin an item\n togglePin(item) {\n let index = this.sets.pin.findIndex(item);\n if(index > -1)\n this.sets.pin.remove(index);\n else {\n this.sets.pin.push(item);\n this.$refs.pinPlaylistButton.focus();\n }\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\n if(event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1))\n this.play();\n },\n },\n\n components: { Playlist: _playlist__WEBPACK_IMPORTED_MODULE_2__[\"default\"], Progress: _progress__WEBPACK_IMPORTED_MODULE_3__[\"default\"] },\n});\n\n\n//# sourceURL=webpack:///./assets/public/player.vue?./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -689,7 +689,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n//\n//\n//\n//\n//\n//\n//\n/ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n props: {\n data: {type: Object, default: x => {}},\n name: String,\n player: Object,\n page_url: String,\n actions: {type:Array, default: x => []},\n index: {type:Number, default: null},\n },\n\n computed: {\n item() { return this.data instanceof _model__WEBPACK_IMPORTED_MODULE_0__[\"default\"] ? this.data : new _sound__WEBPACK_IMPORTED_MODULE_1__[\"default\"](this.data || {}); },\n loaded() { return this.player && this.player.isLoaded(this.item) },\n playing() { return this.player && this.player.isPlaying(this.item) },\n paused() { return this.player && this.player.paused && this.loaded },\n pinned() { return this.player && this.player.sets.pin.find(this.item) },\n },\n\n methods: {\n hasAction(action) {\n return this.actions && this.actions.indexOf(action) != -1;\n },\n }\n});\n\n\n//# sourceURL=webpack:///./assets/public/soundItem.vue?./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n props: {\n data: {type: Object, default: x => {}},\n name: String,\n player: Object,\n page_url: String,\n actions: {type:Array, default: x => []},\n index: {type:Number, default: null},\n },\n\n computed: {\n item() { return this.data instanceof _model__WEBPACK_IMPORTED_MODULE_0__[\"default\"] ? this.data : new _sound__WEBPACK_IMPORTED_MODULE_1__[\"default\"](this.data || {}); },\n loaded() { return this.player && this.player.isLoaded(this.item) },\n playing() { return this.player && this.player.isPlaying(this.item) },\n paused() { return this.player && this.player.paused && this.loaded },\n pinned() { return this.player && this.player.sets.pin.find(this.item) },\n },\n\n methods: {\n hasAction(action) {\n return this.actions && this.actions.indexOf(action) != -1;\n },\n }\n});\n\n\n//# sourceURL=webpack:///./assets/public/soundItem.vue?./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -761,7 +761,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", 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: \"player\" }, [\n _c(\n \"div\",\n { class: [\"player-panels\", _vm.panel ? \"is-open\" : \"\"] },\n [\n _c(\"Playlist\", {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value: _vm.panel == \"pin\",\n expression: \"panel == 'pin'\"\n }\n ],\n ref: \"pin\",\n staticClass: \"player-panel menu\",\n attrs: {\n name: \"Pinned\",\n actions: [\"page\"],\n editable: true,\n player: _vm.self,\n set: _vm.sets.pin,\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n },\n on: {\n select: function($event) {\n return _vm.play(\"pin\", $event.index)\n }\n },\n scopedSlots: _vm._u([\n {\n key: \"header\",\n fn: function() {\n return [\n _c(\"p\", { staticClass: \"menu-label\" }, [\n _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-thumbtack\" })\n ]),\n _vm._v(\"\\n Pinned\\n \")\n ])\n ]\n },\n proxy: true\n }\n ])\n }),\n _vm._v(\" \"),\n _c(\"Playlist\", {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value: _vm.panel == \"queue\",\n expression: \"panel == 'queue'\"\n }\n ],\n ref: \"queue\",\n staticClass: \"player-panel menu\",\n attrs: {\n actions: [\"page\"],\n editable: true,\n player: _vm.self,\n set: _vm.sets.queue,\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n },\n on: {\n select: function($event) {\n return _vm.play(\"queue\", $event.index)\n }\n },\n scopedSlots: _vm._u([\n {\n key: \"header\",\n fn: function() {\n return [\n _c(\"p\", { staticClass: \"menu-label\" }, [\n _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-list\" })\n ]),\n _vm._v(\"\\n Playlist\\n \")\n ])\n ]\n },\n proxy: true\n }\n ])\n })\n ],\n 1\n ),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"player-bar media\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"button\",\n {\n staticClass: \"button\",\n attrs: { title: _vm.buttonTitle, \"aria-label\": _vm.buttonTitle },\n on: {\n click: function($event) {\n return _vm.togglePlay()\n }\n }\n },\n [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fas fa-pause\" })\n : _c(\"span\", { staticClass: \"fas fa-play\" })\n ]\n )\n ]),\n _vm._v(\" \"),\n _vm.current && _vm.current.cover\n ? _c(\"div\", { staticClass: \"media-left media-cover\" }, [\n _c(\"img\", {\n staticClass: \"cover\",\n attrs: { src: _vm.current.cover }\n })\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-content\" },\n [\n _vm._t(\"content\", null, { current: _vm.current }),\n _vm._v(\" \"),\n _vm.loaded && _vm.duration\n ? _c(\"Progress\", {\n attrs: {\n value: _vm.currentTime,\n max: this.duration,\n format: _vm.displayTime\n },\n on: {\n select: function($event) {\n _vm.audio.currentTime = $event\n }\n }\n })\n : _vm._e()\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-right\" }, [\n _vm.loaded\n ? _c(\n \"button\",\n {\n staticClass: \"button has-text-weight-bold\",\n on: {\n click: function($event) {\n return _vm.playLive()\n }\n }\n },\n [_vm._m(0), _vm._v(\" \"), _c(\"span\", [_vm._v(\"Live\")])]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n ref: \"pinPlaylistButton\",\n class: _vm.playlistButtonClass(\"pin\"),\n on: {\n click: function($event) {\n return _vm.togglePanel(\"pin\")\n }\n }\n },\n [\n _vm.sets.pin.length\n ? _c(\"span\", { staticClass: \"mr-2 is-size-6\" }, [\n _vm._v(\"\\n \" + _vm._s(_vm.sets.pin.length))\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm._m(1)\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n class: _vm.playlistButtonClass(\"queue\"),\n on: {\n click: function($event) {\n return _vm.togglePanel(\"queue\")\n }\n }\n },\n [\n _vm.sets.queue.length\n ? _c(\"span\", { staticClass: \"mr-2 is-size-6\" }, [\n _vm._v(\n \"\\n \" + _vm._s(_vm.sets.queue.length)\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm._m(2)\n ]\n )\n ])\n ])\n ])\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon is-size-6 has-text-danger\" }, [\n _c(\"span\", { staticClass: \"fa fa-circle\" })\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-thumbtack\" })\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-list\" })\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/public/player.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", 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: \"player\" }, [\n _c(\n \"div\",\n { class: [\"player-panels\", _vm.panel ? \"is-open\" : \"\"] },\n [\n _c(\"Playlist\", {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value: _vm.panel == \"pin\",\n expression: \"panel == 'pin'\"\n }\n ],\n ref: \"pin\",\n staticClass: \"player-panel menu\",\n attrs: {\n name: \"Pinned\",\n actions: [\"page\"],\n editable: true,\n player: _vm.self,\n set: _vm.sets.pin,\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n },\n on: {\n select: function($event) {\n return _vm.togglePlay(\"pin\", $event.index)\n }\n },\n scopedSlots: _vm._u([\n {\n key: \"header\",\n fn: function() {\n return [\n _c(\"p\", { staticClass: \"menu-label\" }, [\n _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-thumbtack\" })\n ]),\n _vm._v(\"\\n Pinned\\n \")\n ])\n ]\n },\n proxy: true\n }\n ])\n }),\n _vm._v(\" \"),\n _c(\"Playlist\", {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value: _vm.panel == \"queue\",\n expression: \"panel == 'queue'\"\n }\n ],\n ref: \"queue\",\n staticClass: \"player-panel menu\",\n attrs: {\n actions: [\"page\"],\n editable: true,\n player: _vm.self,\n set: _vm.sets.queue,\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n },\n on: {\n select: function($event) {\n return _vm.togglePlay(\"queue\", $event.index)\n }\n },\n scopedSlots: _vm._u([\n {\n key: \"header\",\n fn: function() {\n return [\n _c(\"p\", { staticClass: \"menu-label\" }, [\n _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-list\" })\n ]),\n _vm._v(\"\\n Playlist\\n \")\n ])\n ]\n },\n proxy: true\n }\n ])\n })\n ],\n 1\n ),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"player-bar media\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"button\",\n {\n staticClass: \"button\",\n attrs: { title: _vm.buttonTitle, \"aria-label\": _vm.buttonTitle },\n on: {\n click: function($event) {\n return _vm.togglePlay()\n }\n }\n },\n [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fas fa-pause\" })\n : _c(\"span\", { staticClass: \"fas fa-play\" })\n ]\n )\n ]),\n _vm._v(\" \"),\n _vm.current && _vm.current.cover\n ? _c(\"div\", { staticClass: \"media-left media-cover\" }, [\n _c(\"img\", {\n staticClass: \"cover\",\n attrs: { src: _vm.current.cover }\n })\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-content\" },\n [\n _vm._t(\"content\", null, { current: _vm.current }),\n _vm._v(\" \"),\n _vm.loaded && _vm.duration\n ? _c(\"Progress\", {\n attrs: {\n value: _vm.currentTime,\n max: this.duration,\n format: _vm.displayTime\n },\n on: {\n select: function($event) {\n _vm.audio.currentTime = $event\n }\n }\n })\n : _vm._e()\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-right\" }, [\n _vm.loaded\n ? _c(\n \"button\",\n {\n staticClass: \"button has-text-weight-bold\",\n on: {\n click: function($event) {\n return _vm.play()\n }\n }\n },\n [_vm._m(0), _vm._v(\" \"), _c(\"span\", [_vm._v(\"Live\")])]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n ref: \"pinPlaylistButton\",\n class: _vm.playlistButtonClass(\"pin\"),\n on: {\n click: function($event) {\n return _vm.togglePanel(\"pin\")\n }\n }\n },\n [\n _vm.sets.pin.length\n ? _c(\"span\", { staticClass: \"mr-2 is-size-6\" }, [\n _vm._v(\"\\n \" + _vm._s(_vm.sets.pin.length))\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm._m(1)\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n class: _vm.playlistButtonClass(\"queue\"),\n on: {\n click: function($event) {\n return _vm.togglePanel(\"queue\")\n }\n }\n },\n [\n _vm.sets.queue.length\n ? _c(\"span\", { staticClass: \"mr-2 is-size-6\" }, [\n _vm._v(\n \"\\n \" + _vm._s(_vm.sets.queue.length)\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm._m(2)\n ]\n )\n ])\n ])\n ])\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon is-size-6 has-text-danger\" }, [\n _c(\"span\", { staticClass: \"fa fa-circle\" })\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-thumbtack\" })\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-list\" })\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/public/player.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -797,7 +797,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", 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 sound-item\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _vm.item.data.cover\n ? _c(\"img\", {\n staticClass: \"cover is-tiny\",\n attrs: { src: _vm.item.data.cover }\n })\n : _vm._e()\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"button\",\n {\n staticClass: \"button\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.$emit(\"togglePlay\")\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"icon\" }, [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fa fa-pause\" })\n : _c(\"span\", { staticClass: \"fa fa-play\" })\n ])\n ]\n )\n ]),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-content\" },\n [\n _vm._t(\n \"content\",\n [\n _c(\"h4\", { staticClass: \"title is-4\" }, [\n _vm._v(_vm._s(_vm.name || _vm.item.name))\n ]),\n _vm._v(\" \"),\n _vm.hasAction(\"page\") && _vm.item.data.page_url\n ? _c(\n \"a\",\n {\n staticClass: \"subtitle is-6\",\n attrs: { href: _vm.item.data.page_url }\n },\n [\n _vm._m(0),\n _vm._v(\n \"\\n \" +\n _vm._s(_vm.item.data.page_title) +\n \"\\n \"\n )\n ]\n )\n : _vm._e()\n ],\n { player: _vm.player, item: _vm.item, loaded: _vm.loaded }\n )\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-right\" },\n [\n _vm.player.sets.pin != _vm.$parent.set\n ? _c(\n \"button\",\n {\n staticClass: \"button\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.player.togglePin(_vm.item)\n }\n }\n },\n [\n _c(\"span\", { staticClass: \"icon is-small\" }, [\n _c(\"span\", {\n class:\n (_vm.pinned ? \"\" : \"has-text-grey-light \") +\n \"fa fa-thumbtack\"\n })\n ])\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm._t(\"actions\", null, {\n player: _vm.player,\n item: _vm.item,\n loaded: _vm.loaded\n })\n ],\n 2\n )\n ])\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"i\", { staticClass: \"icon\" }, [\n _c(\"i\", { staticClass: \"fas fa-link\" })\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/public/soundItem.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", 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 sound-item\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _vm.item.data.cover\n ? _c(\"img\", {\n staticClass: \"cover is-tiny\",\n attrs: { src: _vm.item.data.cover }\n })\n : _vm._e()\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"button\",\n {\n staticClass: \"button\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.$emit(\"togglePlay\")\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"icon\" }, [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fa fa-pause\" })\n : _c(\"span\", { staticClass: \"fa fa-play\" })\n ])\n ]\n )\n ]),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-content\" },\n [\n _vm._t(\n \"content\",\n [\n _c(\"h4\", { staticClass: \"title is-4\" }, [\n _vm._v(_vm._s(_vm.name || _vm.item.name))\n ]),\n _vm._v(\" \"),\n _vm.hasAction(\"page\") && _vm.item.data.page_url\n ? _c(\n \"a\",\n {\n staticClass: \"subtitle is-6 is-inline-block\",\n attrs: { href: _vm.item.data.page_url }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(_vm.item.data.page_title) +\n \"\\n \"\n )\n ]\n )\n : _vm._e()\n ],\n { player: _vm.player, item: _vm.item, loaded: _vm.loaded }\n )\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-right\" },\n [\n _vm.player.sets.pin != _vm.$parent.set\n ? _c(\n \"button\",\n {\n staticClass: \"button\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.player.togglePin(_vm.item)\n }\n }\n },\n [\n _c(\"span\", { staticClass: \"icon is-small\" }, [\n _c(\"span\", {\n class:\n (_vm.pinned ? \"\" : \"has-text-grey-light \") +\n \"fa fa-thumbtack\"\n })\n ])\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm._t(\"actions\", null, {\n player: _vm.player,\n item: _vm.item,\n loaded: _vm.loaded\n })\n ],\n 2\n )\n ])\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/public/soundItem.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }) diff --git a/aircox/static/aircox/main.css b/aircox/static/aircox/main.css index 9b1885a..1219a66 100644 --- a/aircox/static/aircox/main.css +++ b/aircox/static/aircox/main.css @@ -7422,8 +7422,6 @@ a.navbar-item.is-active { min-width: 2.5em; border-radius: 0px; transition: background-color 1s; } - .player .player-bar .button:focus { - background-color: #209cee; } .player .player-bar .title { margin: 0em; } @@ -7448,9 +7446,6 @@ main .cover.is-small { main .cover.is-tiny { height: 2em; } -.sound-item .cover { - height: 5em; } - aside > section { margin-bottom: 2em; } @@ -7466,6 +7461,12 @@ aside .cover.is-tiny { aside .media .subtitle { font-size: 1em; } +.sound-item .cover { + height: 5em; } + +.sound-item .media-content a { + padding: 0em; } + .is-round, .sound-item .button { border: 1px #7a7a7a solid; border-radius: 1em; } diff --git a/aircox/static/aircox/main.js b/aircox/static/aircox/main.js index 6b488da..fbdc1ab 100644 --- a/aircox/static/aircox/main.js +++ b/aircox/static/aircox/main.js @@ -159,11 +159,11 @@ /*!******************************!*\ !*** ./assets/public/app.js ***! \******************************/ -/*! exports provided: defaultConfig, default, AppConfig */ +/*! exports provided: defaultConfig, default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return App; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"AppConfig\", function() { return AppConfig; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n data() {\n return {\n page: null,\n }\n },\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\nfunction App(config, sync=false) {\n return (new AppConfig(config)).load(sync)\n}\n\n/**\n * Application config for an application instance\n */\nclass AppConfig {\n constructor(config) {\n this._config = config;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n load(sync=false) {\n var self = this;\n return new Promise(function(resolve, reject) {\n let func = () => { try { resolve(self.build()) } catch(error) { reject(error) }};\n sync ? func() : window.addEventListener('load', func);\n });\n }\n\n build() {\n let config = this.config;\n const el = document.querySelector(config.el)\n if(!el) {\n reject(`Error: missing element ${config.el}`);\n return;\n }\n return new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null, title=null, url=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n if(url && content)\n history.pushState({ content: content, title: title }, '', url)\n\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n loadFromState(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); /***/ }), @@ -247,7 +247,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__ = __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_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__ = __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_2___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./assets/public/app.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./styles.scss */ \"./assets/public/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./autocomplete */ \"./assets/public/autocomplete.vue\");\n/* harmony import */ var _episode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./episode */ \"./assets/public/episode.vue\");\n/* harmony import */ var _player__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./player */ \"./assets/public/player.vue\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _soundItem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./soundItem */ \"./assets/public/soundItem.vue\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n\n\n\n\n//-- aircox\n\n\n\n\n\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-autocomplete', _autocomplete__WEBPACK_IMPORTED_MODULE_7__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-episode', _episode__WEBPACK_IMPORTED_MODULE_8__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player__WEBPACK_IMPORTED_MODULE_9__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-playlist', _playlist__WEBPACK_IMPORTED_MODULE_10__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-sound-item', _soundItem__WEBPACK_IMPORTED_MODULE_11__[\"default\"])\n\n\nwindow.aircox = {\n // main application\n app: null,\n\n // main application config\n appConfig: {},\n\n // player application\n playerApp: null,\n\n // player component\n get player() {\n return this.playerApp && this.playerApp.$refs.player\n },\n\n loadPage(url) {\n fetch(url).then(response => response.text())\n .then(response => {\n let doc = new DOMParser().parseFromString(response, 'text/html');\n\n aircox.app && aircox.app.$destroy();\n document.getElementById('app').innerHTML = doc.getElementById('app').innerHTML;\n Object(_app__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(() => window.aircox.appConfig, true).then(app => {\n aircox.app = app;\n document.title = doc.title;\n })\n });\n },\n\n Set: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"], Sound: _sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n};\nwindow.Vue = vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"];\n\n\nObject(_app__WEBPACK_IMPORTED_MODULE_3__[\"default\"])({el: '#player'}).then(app => window.aircox.playerApp = app);\nObject(_app__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(() => window.aircox.appConfig).then(app => {\n window.aircox.app = app;\n window.addEventListener('click', event => {\n let target = event.target.tagName == 'A' ? event.target : event.target.closest('a');\n if(!target || !target.hasAttribute('href'))\n return;\n\n let href = target.getAttribute('href');\n if(href && href !='#') {\n window.aircox.loadPage(href);\n event.preventDefault();\n event.stopPropagation();\n }\n }, true);\n})\n\n\n\n\n//# sourceURL=webpack:///./assets/public/index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__ = __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_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__ = __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_2___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./assets/public/app.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./styles.scss */ \"./assets/public/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./autocomplete */ \"./assets/public/autocomplete.vue\");\n/* harmony import */ var _episode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./episode */ \"./assets/public/episode.vue\");\n/* harmony import */ var _player__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./player */ \"./assets/public/player.vue\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _soundItem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./soundItem */ \"./assets/public/soundItem.vue\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n\n\n\n\n//-- aircox\n\n\n\n\n\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-autocomplete', _autocomplete__WEBPACK_IMPORTED_MODULE_7__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-episode', _episode__WEBPACK_IMPORTED_MODULE_8__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player__WEBPACK_IMPORTED_MODULE_9__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-playlist', _playlist__WEBPACK_IMPORTED_MODULE_10__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-sound-item', _soundItem__WEBPACK_IMPORTED_MODULE_11__[\"default\"])\n\n\nwindow.aircox = {\n // main application\n appBuilder: null,\n appConfig: {},\n get app() { return this.appBuilder.app },\n\n // player application\n playerBuilder: null,\n get playerApp() { return this.playerBuilder && this.playerBuilder.app },\n get player() { return this.playerApp && this.playerApp.$refs.player },\n\n onPageFetch(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A'\n ? event.target : event.target.closest('a');\n if(!target || target.hasAttribute('target'))\n return;\n\n let url = submit ? target.getAttribute('action') || ''\n : target.getAttribute('href');\n if(url===null || !(url === '' || url.startsWith('/') || url.startsWith('?')))\n return;\n\n let options = {};\n if(submit) {\n let formData = new FormData(event.target);\n if(target.method == 'get')\n url += '?' + (new URLSearchParams(formData)).toString();\n else {\n options['method'] = target.method;\n options['body'] = formData;\n }\n }\n this.appBuilder.fetch(url, options);\n event.preventDefault();\n event.stopPropagation();\n },\n\n Set: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"], Sound: _sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n};\nwindow.Vue = vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"];\n\n\naircox.playerBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"]({el: '#player'});\naircox.playerBuilder.load({async:true});\naircox.appBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x => window.aircox.appConfig);\naircox.appBuilder.load({async:true}).then(app => {\n //-- load page hooks\n window.addEventListener('click', event => aircox.onPageFetch(event), true);\n window.addEventListener('submit', event => aircox.onPageFetch(event), true);\n window.addEventListener('popstate', event => {\n if(event.state && event.state.content)\n aircox.appBuilder.loadFromState(event.state);\n });\n})\n\n\n\n\n//# sourceURL=webpack:///./assets/public/index.js?"); /***/ }), @@ -582,7 +582,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n//\n//\n//\n//\n//\n//\n\n/* /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"State\", function() { return State; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _live__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./live */ \"./assets/public/live.js\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _progress__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./progress */ \"./assets/public/progress.vue\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\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__[\"default\"] = ({\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', e => {\n this.currentTime = this.audio.currentTime;\n });\n audio.addEventListener('durationchange', e => {\n this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;\n });\n\n return {\n audio, duration: 0, currentTime: 0, state: State.paused,\n\n /// Live instance\n live: this.liveArgs ? new _live__WEBPACK_IMPORTED_MODULE_1__[\"default\"](this.liveArgs) : null,\n /// Loaded item\n loaded: null,\n //! Active panel name\n panel: null,\n //! current playing playlist component\n playlist: null,\n //! players' playlists' sets\n sets: {\n queue: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"].storeLoad(_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"], \"playlist.queue\", { max: 30, unique: true }),\n pin: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"].storeLoad(_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"], \"player.pin\", { max: 30, unique: true }),\n }\n }\n },\n\n props: {\n buttonTitle: String,\n liveArgs: Object,\n },\n\n computed: {\n self() { return this; },\n paused() { return this.state == State.paused; },\n playing() { return this.state == State.playing; },\n loading() { return this.state == State.loading; },\n\n current() {\n return this.loaded || this.live && this.live.current;\n },\n\n buttonStyle() {\n if(!this.current)\n return;\n return { backgroundImage: `url(${this.current.cover})` }\n },\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\n let [ss,mm,hh] = [s.toString().padStart(2, '0'),\n m.toString().padStart(2, '0'),\n h.toString().padStart(2, '0')];\n return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;\n },\n\n playlistButtonClass(name) {\n let set = this.sets[name];\n return (set ? (set.length ? \"\" : \"has-text-grey-light \")\n + (this.panel == name ? \"is-info \"\n : this.playlist && this.playlist == this.$refs[name] ? 'is-primary '\n : '') : '')\n + \"button has-text-weight-bold\";\n },\n\n /// Show/hide panel\n togglePanel(panel) { this.panel = this.panel == panel ? null : panel },\n /// Return True if item is loaded\n isLoaded(item) { return this.loaded && this.loaded.id == item.id },\n /// Return True if item is loaded\n isPlaying(item) { return this.isLoaded(item) && !this.paused },\n\n _setPlaylist(playlist) {\n this.playlist = playlist ? this.$refs[playlist] : null;\n for(var p in this.sets)\n if(p != playlist)\n this.$refs[p].unselect();\n },\n\n load(playlist, {src=null, item=null}={}) {\n src = src || item.src;\n this.loaded = item;\n this._setPlaylist(playlist);\n\n const audio = this.audio;\n if(src instanceof Array) {\n audio.innerHTML = '';\n for(var s of src) {\n let source = document.createElement(source);\n source.setAttribute('src', s);\n audio.appendChild(source)\n }\n }\n else {\n audio.src = src;\n }\n audio.load();\n },\n\n /// Play a playlist's sound (by playlist name, and sound index)\n play(playlist=null, index=0) {\n if(index < 0)\n return;\n\n if(!playlist)\n playlist = 'queue';\n\n console.log('play', playlist, index, this.audio);\n let item = this.$refs[playlist].get(index);\n if(item) {\n this.load(playlist, {item});\n this.audio.play().catch(e => console.error(e))\n }\n else\n throw `No sound at index ${index} for playlist ${playlist}`;\n },\n\n /// Push items to playlist (by name)\n push(playlist, ...items) {\n this.$refs[playlist].push(...items);\n },\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\n playButtonClick(event) {\n var items = JSON.parse(event.currentTarget.dataset.sounds);\n this.playItems('queue', ...items);\n },\n\n /// Play live stream\n playLive() {\n this.load(null, {src: this.live.src});\n this.audio.play().catch(e => console.error(e))\n this.panel = '';\n },\n\n /// Pause\n pause() {\n this.audio.pause()\n },\n\n //! Play/pause\n togglePlay() {\n if(this.paused)\n this.audio.play().catch(e => console.error(e))\n else\n this.pause()\n },\n\n //! Pin/Unpin an item\n togglePin(item) {\n let index = this.sets.pin.findIndex(item);\n if(index > -1)\n this.sets.pin.remove(index);\n else {\n this.sets.pin.push(item);\n this.$refs.pinPlaylistButton.focus();\n }\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\n if(event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1))\n this.playLive();\n },\n },\n\n components: { Playlist: _playlist__WEBPACK_IMPORTED_MODULE_2__[\"default\"], Progress: _progress__WEBPACK_IMPORTED_MODULE_3__[\"default\"] },\n});\n\n\n//# sourceURL=webpack:///./assets/public/player.vue?./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"State\", function() { return State; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _live__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./live */ \"./assets/public/live.js\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _progress__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./progress */ \"./assets/public/progress.vue\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\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__[\"default\"] = ({\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', e => {\n this.currentTime = this.audio.currentTime;\n });\n audio.addEventListener('durationchange', e => {\n this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;\n });\n\n return {\n audio, duration: 0, currentTime: 0, state: State.paused,\n\n /// Live instance\n live: this.liveArgs ? new _live__WEBPACK_IMPORTED_MODULE_1__[\"default\"](this.liveArgs) : null,\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_5__[\"Set\"].storeLoad(_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"], \"playlist.queue\", { max: 30, unique: true }),\n pin: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"].storeLoad(_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"], \"player.pin\", { max: 30, unique: true }),\n }\n }\n },\n\n props: {\n buttonTitle: String,\n liveArgs: Object,\n },\n\n computed: {\n self() { return this; },\n paused() { return this.state == State.paused; },\n playing() { return this.state == State.playing; },\n loading() { return this.state == State.loading; },\n\n playlist() {\n return this.playlistName ? this.$refs[this.playlistName] : null;\n },\n\n current() {\n return this.loaded || this.live && this.live.current;\n },\n\n buttonStyle() {\n if(!this.current)\n return;\n return { backgroundImage: `url(${this.current.cover})` }\n },\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\n let [ss,mm,hh] = [s.toString().padStart(2, '0'),\n m.toString().padStart(2, '0'),\n h.toString().padStart(2, '0')];\n return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;\n },\n\n playlistButtonClass(name) {\n let set = this.sets[name];\n return (set ? (set.length ? \"\" : \"has-text-grey-light \")\n + (this.panel == name ? \"is-info \"\n : this.playlistName == name ? 'is-primary '\n : '') : '')\n + \"button has-text-weight-bold\";\n },\n\n /// Show/hide panel\n togglePanel(panel) { this.panel = this.panel == panel ? null : panel },\n /// Return True if item is loaded\n isLoaded(item) { return this.loaded && this.loaded.id == item.id },\n /// Return True if item is loaded\n isPlaying(item) { return this.isLoaded(item) && !this.paused },\n\n _setPlaylist(playlist) {\n this.playlistName = playlist;\n for(var p in this.sets)\n if(p != playlist)\n this.$refs[p].unselect();\n },\n\n /// Play a playlist's sound (by playlist name, and sound index)\n play(playlist=null, index=0) {\n let src = null;\n\n // from playlist\n if(playlist !== null) {\n let item = this.$refs[playlist].get(index);\n if(!item)\n 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\n this._setPlaylist(playlist);\n\n // load sources\n const audio = this.audio;\n if(src instanceof Array) {\n audio.innerHTML = '';\n for(var s of src) {\n let source = document.createElement(source);\n source.setAttribute('src', s);\n audio.appendChild(source)\n }\n }\n else {\n audio.src = src;\n }\n audio.load();\n audio.play();\n audio.play().catch(e => console.error(e))\n },\n\n /// Push items to playlist (by name)\n push(playlist, ...items) {\n return this.$refs[playlist].push(...items);\n },\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\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\n /// Pause\n pause() {\n this.audio.pause()\n },\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)\n this.audio.play().catch(e => console.error(e))\n else\n this.audio.pause();\n },\n\n //! Pin/Unpin an item\n togglePin(item) {\n let index = this.sets.pin.findIndex(item);\n if(index > -1)\n this.sets.pin.remove(index);\n else {\n this.sets.pin.push(item);\n this.$refs.pinPlaylistButton.focus();\n }\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\n if(event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1))\n this.play();\n },\n },\n\n components: { Playlist: _playlist__WEBPACK_IMPORTED_MODULE_2__[\"default\"], Progress: _progress__WEBPACK_IMPORTED_MODULE_3__[\"default\"] },\n});\n\n\n//# sourceURL=webpack:///./assets/public/player.vue?./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -618,7 +618,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n//\n//\n//\n//\n//\n//\n//\n/ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n props: {\n data: {type: Object, default: x => {}},\n name: String,\n player: Object,\n page_url: String,\n actions: {type:Array, default: x => []},\n index: {type:Number, default: null},\n },\n\n computed: {\n item() { return this.data instanceof _model__WEBPACK_IMPORTED_MODULE_0__[\"default\"] ? this.data : new _sound__WEBPACK_IMPORTED_MODULE_1__[\"default\"](this.data || {}); },\n loaded() { return this.player && this.player.isLoaded(this.item) },\n playing() { return this.player && this.player.isPlaying(this.item) },\n paused() { return this.player && this.player.paused && this.loaded },\n pinned() { return this.player && this.player.sets.pin.find(this.item) },\n },\n\n methods: {\n hasAction(action) {\n return this.actions && this.actions.indexOf(action) != -1;\n },\n }\n});\n\n\n//# sourceURL=webpack:///./assets/public/soundItem.vue?./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n props: {\n data: {type: Object, default: x => {}},\n name: String,\n player: Object,\n page_url: String,\n actions: {type:Array, default: x => []},\n index: {type:Number, default: null},\n },\n\n computed: {\n item() { return this.data instanceof _model__WEBPACK_IMPORTED_MODULE_0__[\"default\"] ? this.data : new _sound__WEBPACK_IMPORTED_MODULE_1__[\"default\"](this.data || {}); },\n loaded() { return this.player && this.player.isLoaded(this.item) },\n playing() { return this.player && this.player.isPlaying(this.item) },\n paused() { return this.player && this.player.paused && this.loaded },\n pinned() { return this.player && this.player.sets.pin.find(this.item) },\n },\n\n methods: {\n hasAction(action) {\n return this.actions && this.actions.indexOf(action) != -1;\n },\n }\n});\n\n\n//# sourceURL=webpack:///./assets/public/soundItem.vue?./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -678,7 +678,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", 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: \"player\" }, [\n _c(\n \"div\",\n { class: [\"player-panels\", _vm.panel ? \"is-open\" : \"\"] },\n [\n _c(\"Playlist\", {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value: _vm.panel == \"pin\",\n expression: \"panel == 'pin'\"\n }\n ],\n ref: \"pin\",\n staticClass: \"player-panel menu\",\n attrs: {\n name: \"Pinned\",\n actions: [\"page\"],\n editable: true,\n player: _vm.self,\n set: _vm.sets.pin,\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n },\n on: {\n select: function($event) {\n return _vm.play(\"pin\", $event.index)\n }\n },\n scopedSlots: _vm._u([\n {\n key: \"header\",\n fn: function() {\n return [\n _c(\"p\", { staticClass: \"menu-label\" }, [\n _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-thumbtack\" })\n ]),\n _vm._v(\"\\n Pinned\\n \")\n ])\n ]\n },\n proxy: true\n }\n ])\n }),\n _vm._v(\" \"),\n _c(\"Playlist\", {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value: _vm.panel == \"queue\",\n expression: \"panel == 'queue'\"\n }\n ],\n ref: \"queue\",\n staticClass: \"player-panel menu\",\n attrs: {\n actions: [\"page\"],\n editable: true,\n player: _vm.self,\n set: _vm.sets.queue,\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n },\n on: {\n select: function($event) {\n return _vm.play(\"queue\", $event.index)\n }\n },\n scopedSlots: _vm._u([\n {\n key: \"header\",\n fn: function() {\n return [\n _c(\"p\", { staticClass: \"menu-label\" }, [\n _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-list\" })\n ]),\n _vm._v(\"\\n Playlist\\n \")\n ])\n ]\n },\n proxy: true\n }\n ])\n })\n ],\n 1\n ),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"player-bar media\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"button\",\n {\n staticClass: \"button\",\n attrs: { title: _vm.buttonTitle, \"aria-label\": _vm.buttonTitle },\n on: {\n click: function($event) {\n return _vm.togglePlay()\n }\n }\n },\n [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fas fa-pause\" })\n : _c(\"span\", { staticClass: \"fas fa-play\" })\n ]\n )\n ]),\n _vm._v(\" \"),\n _vm.current && _vm.current.cover\n ? _c(\"div\", { staticClass: \"media-left media-cover\" }, [\n _c(\"img\", {\n staticClass: \"cover\",\n attrs: { src: _vm.current.cover }\n })\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-content\" },\n [\n _vm._t(\"content\", null, { current: _vm.current }),\n _vm._v(\" \"),\n _vm.loaded && _vm.duration\n ? _c(\"Progress\", {\n attrs: {\n value: _vm.currentTime,\n max: this.duration,\n format: _vm.displayTime\n },\n on: {\n select: function($event) {\n _vm.audio.currentTime = $event\n }\n }\n })\n : _vm._e()\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-right\" }, [\n _vm.loaded\n ? _c(\n \"button\",\n {\n staticClass: \"button has-text-weight-bold\",\n on: {\n click: function($event) {\n return _vm.playLive()\n }\n }\n },\n [_vm._m(0), _vm._v(\" \"), _c(\"span\", [_vm._v(\"Live\")])]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n ref: \"pinPlaylistButton\",\n class: _vm.playlistButtonClass(\"pin\"),\n on: {\n click: function($event) {\n return _vm.togglePanel(\"pin\")\n }\n }\n },\n [\n _vm.sets.pin.length\n ? _c(\"span\", { staticClass: \"mr-2 is-size-6\" }, [\n _vm._v(\"\\n \" + _vm._s(_vm.sets.pin.length))\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm._m(1)\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n class: _vm.playlistButtonClass(\"queue\"),\n on: {\n click: function($event) {\n return _vm.togglePanel(\"queue\")\n }\n }\n },\n [\n _vm.sets.queue.length\n ? _c(\"span\", { staticClass: \"mr-2 is-size-6\" }, [\n _vm._v(\n \"\\n \" + _vm._s(_vm.sets.queue.length)\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm._m(2)\n ]\n )\n ])\n ])\n ])\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon is-size-6 has-text-danger\" }, [\n _c(\"span\", { staticClass: \"fa fa-circle\" })\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-thumbtack\" })\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-list\" })\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/public/player.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", 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: \"player\" }, [\n _c(\n \"div\",\n { class: [\"player-panels\", _vm.panel ? \"is-open\" : \"\"] },\n [\n _c(\"Playlist\", {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value: _vm.panel == \"pin\",\n expression: \"panel == 'pin'\"\n }\n ],\n ref: \"pin\",\n staticClass: \"player-panel menu\",\n attrs: {\n name: \"Pinned\",\n actions: [\"page\"],\n editable: true,\n player: _vm.self,\n set: _vm.sets.pin,\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n },\n on: {\n select: function($event) {\n return _vm.togglePlay(\"pin\", $event.index)\n }\n },\n scopedSlots: _vm._u([\n {\n key: \"header\",\n fn: function() {\n return [\n _c(\"p\", { staticClass: \"menu-label\" }, [\n _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-thumbtack\" })\n ]),\n _vm._v(\"\\n Pinned\\n \")\n ])\n ]\n },\n proxy: true\n }\n ])\n }),\n _vm._v(\" \"),\n _c(\"Playlist\", {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value: _vm.panel == \"queue\",\n expression: \"panel == 'queue'\"\n }\n ],\n ref: \"queue\",\n staticClass: \"player-panel menu\",\n attrs: {\n actions: [\"page\"],\n editable: true,\n player: _vm.self,\n set: _vm.sets.queue,\n listClass: \"menu-list\",\n itemClass: \"menu-item\"\n },\n on: {\n select: function($event) {\n return _vm.togglePlay(\"queue\", $event.index)\n }\n },\n scopedSlots: _vm._u([\n {\n key: \"header\",\n fn: function() {\n return [\n _c(\"p\", { staticClass: \"menu-label\" }, [\n _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-list\" })\n ]),\n _vm._v(\"\\n Playlist\\n \")\n ])\n ]\n },\n proxy: true\n }\n ])\n })\n ],\n 1\n ),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"player-bar media\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"button\",\n {\n staticClass: \"button\",\n attrs: { title: _vm.buttonTitle, \"aria-label\": _vm.buttonTitle },\n on: {\n click: function($event) {\n return _vm.togglePlay()\n }\n }\n },\n [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fas fa-pause\" })\n : _c(\"span\", { staticClass: \"fas fa-play\" })\n ]\n )\n ]),\n _vm._v(\" \"),\n _vm.current && _vm.current.cover\n ? _c(\"div\", { staticClass: \"media-left media-cover\" }, [\n _c(\"img\", {\n staticClass: \"cover\",\n attrs: { src: _vm.current.cover }\n })\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-content\" },\n [\n _vm._t(\"content\", null, { current: _vm.current }),\n _vm._v(\" \"),\n _vm.loaded && _vm.duration\n ? _c(\"Progress\", {\n attrs: {\n value: _vm.currentTime,\n max: this.duration,\n format: _vm.displayTime\n },\n on: {\n select: function($event) {\n _vm.audio.currentTime = $event\n }\n }\n })\n : _vm._e()\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-right\" }, [\n _vm.loaded\n ? _c(\n \"button\",\n {\n staticClass: \"button has-text-weight-bold\",\n on: {\n click: function($event) {\n return _vm.play()\n }\n }\n },\n [_vm._m(0), _vm._v(\" \"), _c(\"span\", [_vm._v(\"Live\")])]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n ref: \"pinPlaylistButton\",\n class: _vm.playlistButtonClass(\"pin\"),\n on: {\n click: function($event) {\n return _vm.togglePanel(\"pin\")\n }\n }\n },\n [\n _vm.sets.pin.length\n ? _c(\"span\", { staticClass: \"mr-2 is-size-6\" }, [\n _vm._v(\"\\n \" + _vm._s(_vm.sets.pin.length))\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm._m(1)\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n class: _vm.playlistButtonClass(\"queue\"),\n on: {\n click: function($event) {\n return _vm.togglePanel(\"queue\")\n }\n }\n },\n [\n _vm.sets.queue.length\n ? _c(\"span\", { staticClass: \"mr-2 is-size-6\" }, [\n _vm._v(\n \"\\n \" + _vm._s(_vm.sets.queue.length)\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm._m(2)\n ]\n )\n ])\n ])\n ])\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon is-size-6 has-text-danger\" }, [\n _c(\"span\", { staticClass: \"fa fa-circle\" })\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-thumbtack\" })\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"icon\" }, [\n _c(\"span\", { staticClass: \"fa fa-list\" })\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/public/player.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -714,7 +714,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", 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 sound-item\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _vm.item.data.cover\n ? _c(\"img\", {\n staticClass: \"cover is-tiny\",\n attrs: { src: _vm.item.data.cover }\n })\n : _vm._e()\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"button\",\n {\n staticClass: \"button\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.$emit(\"togglePlay\")\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"icon\" }, [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fa fa-pause\" })\n : _c(\"span\", { staticClass: \"fa fa-play\" })\n ])\n ]\n )\n ]),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-content\" },\n [\n _vm._t(\n \"content\",\n [\n _c(\"h4\", { staticClass: \"title is-4\" }, [\n _vm._v(_vm._s(_vm.name || _vm.item.name))\n ]),\n _vm._v(\" \"),\n _vm.hasAction(\"page\") && _vm.item.data.page_url\n ? _c(\n \"a\",\n {\n staticClass: \"subtitle is-6\",\n attrs: { href: _vm.item.data.page_url }\n },\n [\n _vm._m(0),\n _vm._v(\n \"\\n \" +\n _vm._s(_vm.item.data.page_title) +\n \"\\n \"\n )\n ]\n )\n : _vm._e()\n ],\n { player: _vm.player, item: _vm.item, loaded: _vm.loaded }\n )\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-right\" },\n [\n _vm.player.sets.pin != _vm.$parent.set\n ? _c(\n \"button\",\n {\n staticClass: \"button\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.player.togglePin(_vm.item)\n }\n }\n },\n [\n _c(\"span\", { staticClass: \"icon is-small\" }, [\n _c(\"span\", {\n class:\n (_vm.pinned ? \"\" : \"has-text-grey-light \") +\n \"fa fa-thumbtack\"\n })\n ])\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm._t(\"actions\", null, {\n player: _vm.player,\n item: _vm.item,\n loaded: _vm.loaded\n })\n ],\n 2\n )\n ])\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"i\", { staticClass: \"icon\" }, [\n _c(\"i\", { staticClass: \"fas fa-link\" })\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/public/soundItem.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", 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 sound-item\" }, [\n _c(\"div\", { staticClass: \"media-left\" }, [\n _vm.item.data.cover\n ? _c(\"img\", {\n staticClass: \"cover is-tiny\",\n attrs: { src: _vm.item.data.cover }\n })\n : _vm._e()\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"media-left\" }, [\n _c(\n \"button\",\n {\n staticClass: \"button\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.$emit(\"togglePlay\")\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"icon\" }, [\n _vm.playing\n ? _c(\"span\", { staticClass: \"fa fa-pause\" })\n : _c(\"span\", { staticClass: \"fa fa-play\" })\n ])\n ]\n )\n ]),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-content\" },\n [\n _vm._t(\n \"content\",\n [\n _c(\"h4\", { staticClass: \"title is-4\" }, [\n _vm._v(_vm._s(_vm.name || _vm.item.name))\n ]),\n _vm._v(\" \"),\n _vm.hasAction(\"page\") && _vm.item.data.page_url\n ? _c(\n \"a\",\n {\n staticClass: \"subtitle is-6 is-inline-block\",\n attrs: { href: _vm.item.data.page_url }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(_vm.item.data.page_title) +\n \"\\n \"\n )\n ]\n )\n : _vm._e()\n ],\n { player: _vm.player, item: _vm.item, loaded: _vm.loaded }\n )\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"media-right\" },\n [\n _vm.player.sets.pin != _vm.$parent.set\n ? _c(\n \"button\",\n {\n staticClass: \"button\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.player.togglePin(_vm.item)\n }\n }\n },\n [\n _c(\"span\", { staticClass: \"icon is-small\" }, [\n _c(\"span\", {\n class:\n (_vm.pinned ? \"\" : \"has-text-grey-light \") +\n \"fa fa-thumbtack\"\n })\n ])\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm._t(\"actions\", null, {\n player: _vm.player,\n item: _vm.item,\n loaded: _vm.loaded\n })\n ],\n 2\n )\n ])\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/public/soundItem.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }) diff --git a/aircox/static/aircox/streamer.js b/aircox/static/aircox/streamer.js index c07bfc9..76193f5 100644 --- a/aircox/static/aircox/streamer.js +++ b/aircox/static/aircox/streamer.js @@ -159,11 +159,11 @@ /*!******************************!*\ !*** ./assets/public/app.js ***! \******************************/ -/*! exports provided: defaultConfig, default, AppConfig */ +/*! exports provided: defaultConfig, default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return App; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"AppConfig\", function() { return AppConfig; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n data() {\n return {\n page: null,\n }\n },\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\nfunction App(config, sync=false) {\n return (new AppConfig(config)).load(sync)\n}\n\n/**\n * Application config for an application instance\n */\nclass AppConfig {\n constructor(config) {\n this._config = config;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n load(sync=false) {\n var self = this;\n return new Promise(function(resolve, reject) {\n let func = () => { try { resolve(self.build()) } catch(error) { reject(error) }};\n sync ? func() : window.addEventListener('load', func);\n });\n }\n\n build() {\n let config = this.config;\n const el = document.querySelector(config.el)\n if(!el) {\n reject(`Error: missing element ${config.el}`);\n return;\n }\n return new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null, title=null, url=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n if(url && content)\n history.pushState({ content: content, title: title }, '', url)\n\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n loadFromState(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); /***/ }), diff --git a/aircox/templates/aircox/widgets/podcast_item.html b/aircox/templates/aircox/widgets/podcast_item.html index 5d74019..9f7cc6b 100644 --- a/aircox/templates/aircox/widgets/podcast_item.html +++ b/aircox/templates/aircox/widgets/podcast_item.html @@ -12,7 +12,7 @@ List item for a podcast. {% endif %} {% endcomment %} + :actions="['play']"> diff --git a/assets/public/app.js b/assets/public/app.js index 07d8953..c257dae 100644 --- a/assets/public/app.js +++ b/assets/public/app.js @@ -4,27 +4,16 @@ export const defaultConfig = { el: '#app', delimiters: ['[[', ']]'], - data() { - return { - page: null, - } - }, - computed: { player() { return window.aircox.player; }, }, } -export default function App(config, sync=false) { - return (new AppConfig(config)).load(sync) -} -/** - * Application config for an application instance - */ -export class AppConfig { - constructor(config) { +export default class AppBuilder { + constructor(config={}) { this._config = config; + this.app = null; } get config() { @@ -42,22 +31,50 @@ export class AppConfig { this._config = value; } - load(sync=false) { + destroy() { + self.app && self.app.$destroy(); + self.app = null; + } + + fetch(url, options) { + return fetch(url, options).then(response => response.text()) + .then(content => { + let doc = new DOMParser().parseFromString(content, 'text/html'); + let app = doc.getElementById('app'); + content = app ? app.innerHTML : content; + return this.load({sync: true, content, title: doc.title, url }) + }) + } + + load({async=false,content=null, title=null, url=null}={}) { var self = this; - return new Promise(function(resolve, reject) { - let func = () => { try { resolve(self.build()) } catch(error) { reject(error) }}; - sync ? func() : window.addEventListener('load', func); + return new Promise((resolve, reject) => { + let func = () => { + try { + let config = self.config; + const el = document.querySelector(config.el); + if(!el) + return reject(`Error: can't get element ${config.el}`) + + if(content) + el.innerHTML = content + if(title) + document.title = title; + if(url && content) + history.pushState({ content: content, title: title }, '', url) + + this.app = new Vue(config); + resolve(self.app) + } catch(error) { + self.destroy(); + reject(error) + }}; + async ? window.addEventListener('load', func) : func(); }); } - build() { - let config = this.config; - const el = document.querySelector(config.el) - if(!el) { - reject(`Error: missing element ${config.el}`); - return; - } - return new Vue(config); + loadFromState(state) { + return this.load({ content: state.content, title: state.title }); } } diff --git a/assets/public/index.js b/assets/public/index.js index ccf74e4..cbe3bbd 100644 --- a/assets/public/index.js +++ b/assets/public/index.js @@ -10,7 +10,7 @@ import '@fortawesome/fontawesome-free/css/fontawesome.min.css'; //-- aircox -import App from './app'; +import AppBuilder from './app'; import Sound from './sound'; import {Set} from './model'; @@ -31,31 +31,40 @@ Vue.component('a-sound-item', SoundItem) window.aircox = { // main application - app: null, - - // main application config + appBuilder: null, appConfig: {}, + get app() { return this.appBuilder.app }, // player application - playerApp: null, + playerBuilder: null, + get playerApp() { return this.playerBuilder && this.playerBuilder.app }, + get player() { return this.playerApp && this.playerApp.$refs.player }, - // player component - get player() { - return this.playerApp && this.playerApp.$refs.player - }, + onPageFetch(event) { + let submit = event.type == 'submit'; + let target = submit || event.target.tagName == 'A' + ? event.target : event.target.closest('a'); + if(!target || target.hasAttribute('target')) + return; - loadPage(url) { - fetch(url).then(response => response.text()) - .then(response => { - let doc = new DOMParser().parseFromString(response, 'text/html'); + let url = submit ? target.getAttribute('action') || '' + : target.getAttribute('href'); + if(url===null || !(url === '' || url.startsWith('/') || url.startsWith('?'))) + return; - aircox.app && aircox.app.$destroy(); - document.getElementById('app').innerHTML = doc.getElementById('app').innerHTML; - App(() => window.aircox.appConfig, true).then(app => { - aircox.app = app; - document.title = doc.title; - }) - }); + let options = {}; + if(submit) { + let formData = new FormData(event.target); + if(target.method == 'get') + url += '?' + (new URLSearchParams(formData)).toString(); + else { + options['method'] = target.method; + options['body'] = formData; + } + } + this.appBuilder.fetch(url, options); + event.preventDefault(); + event.stopPropagation(); }, Set: Set, Sound: Sound, @@ -63,21 +72,17 @@ window.aircox = { window.Vue = Vue; -App({el: '#player'}).then(app => window.aircox.playerApp = app); -App(() => window.aircox.appConfig).then(app => { - window.aircox.app = app; - window.addEventListener('click', event => { - let target = event.target.tagName == 'A' ? event.target : event.target.closest('a'); - if(!target || !target.hasAttribute('href')) - return; - - let href = target.getAttribute('href'); - if(href && href !='#') { - window.aircox.loadPage(href); - event.preventDefault(); - event.stopPropagation(); - } - }, true); +aircox.playerBuilder = new AppBuilder({el: '#player'}); +aircox.playerBuilder.load({async:true}); +aircox.appBuilder = new AppBuilder(x => window.aircox.appConfig); +aircox.appBuilder.load({async:true}).then(app => { + //-- load page hooks + window.addEventListener('click', event => aircox.onPageFetch(event), true); + window.addEventListener('submit', event => aircox.onPageFetch(event), true); + window.addEventListener('popstate', event => { + if(event.state && event.state.content) + aircox.appBuilder.loadFromState(event.state); + }); }) diff --git a/assets/public/player.vue b/assets/public/player.vue index 1a49c02..626bd7f 100644 --- a/assets/public/player.vue +++ b/assets/public/player.vue @@ -4,7 +4,7 @@