work on hot reload; fix bugs in player
This commit is contained in:
		@ -7443,8 +7443,6 @@ a.navbar-item.is-active {
 | 
				
			|||||||
      min-width: 2.5em;
 | 
					      min-width: 2.5em;
 | 
				
			||||||
      border-radius: 0px;
 | 
					      border-radius: 0px;
 | 
				
			||||||
      transition: background-color 1s; }
 | 
					      transition: background-color 1s; }
 | 
				
			||||||
    .player .player-bar .button:focus {
 | 
					 | 
				
			||||||
      background-color: #209cee; }
 | 
					 | 
				
			||||||
    .player .player-bar .title {
 | 
					    .player .player-bar .title {
 | 
				
			||||||
      margin: 0em; }
 | 
					      margin: 0em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -7469,9 +7467,6 @@ main .cover.is-small {
 | 
				
			|||||||
main .cover.is-tiny {
 | 
					main .cover.is-tiny {
 | 
				
			||||||
  height: 2em; }
 | 
					  height: 2em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.sound-item .cover {
 | 
					 | 
				
			||||||
  height: 5em; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aside > section {
 | 
					aside > section {
 | 
				
			||||||
  margin-bottom: 2em; }
 | 
					  margin-bottom: 2em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -7487,6 +7482,12 @@ aside .cover.is-tiny {
 | 
				
			|||||||
aside .media .subtitle {
 | 
					aside .media .subtitle {
 | 
				
			||||||
  font-size: 1em; }
 | 
					  font-size: 1em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sound-item .cover {
 | 
				
			||||||
 | 
					  height: 5em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sound-item .media-content a {
 | 
				
			||||||
 | 
					  padding: 0em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.is-round, .sound-item .button {
 | 
					.is-round, .sound-item .button {
 | 
				
			||||||
  border: 1px #7a7a7a solid;
 | 
					  border: 1px #7a7a7a solid;
 | 
				
			||||||
  border-radius: 1em; }
 | 
					  border-radius: 1em; }
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -7422,8 +7422,6 @@ a.navbar-item.is-active {
 | 
				
			|||||||
      min-width: 2.5em;
 | 
					      min-width: 2.5em;
 | 
				
			||||||
      border-radius: 0px;
 | 
					      border-radius: 0px;
 | 
				
			||||||
      transition: background-color 1s; }
 | 
					      transition: background-color 1s; }
 | 
				
			||||||
    .player .player-bar .button:focus {
 | 
					 | 
				
			||||||
      background-color: #209cee; }
 | 
					 | 
				
			||||||
    .player .player-bar .title {
 | 
					    .player .player-bar .title {
 | 
				
			||||||
      margin: 0em; }
 | 
					      margin: 0em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -7448,9 +7446,6 @@ main .cover.is-small {
 | 
				
			|||||||
main .cover.is-tiny {
 | 
					main .cover.is-tiny {
 | 
				
			||||||
  height: 2em; }
 | 
					  height: 2em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.sound-item .cover {
 | 
					 | 
				
			||||||
  height: 5em; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aside > section {
 | 
					aside > section {
 | 
				
			||||||
  margin-bottom: 2em; }
 | 
					  margin-bottom: 2em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -7466,6 +7461,12 @@ aside .cover.is-tiny {
 | 
				
			|||||||
aside .media .subtitle {
 | 
					aside .media .subtitle {
 | 
				
			||||||
  font-size: 1em; }
 | 
					  font-size: 1em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sound-item .cover {
 | 
				
			||||||
 | 
					  height: 5em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sound-item .media-content a {
 | 
				
			||||||
 | 
					  padding: 0em; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.is-round, .sound-item .button {
 | 
					.is-round, .sound-item .button {
 | 
				
			||||||
  border: 1px #7a7a7a solid;
 | 
					  border: 1px #7a7a7a solid;
 | 
				
			||||||
  border-radius: 1em; }
 | 
					  border-radius: 1em; }
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -159,11 +159,11 @@
 | 
				
			|||||||
/*!******************************!*\
 | 
					/*!******************************!*\
 | 
				
			||||||
  !*** ./assets/public/app.js ***!
 | 
					  !*** ./assets/public/app.js ***!
 | 
				
			||||||
  \******************************/
 | 
					  \******************************/
 | 
				
			||||||
/*! exports provided: defaultConfig, default, AppConfig */
 | 
					/*! exports provided: defaultConfig, default */
 | 
				
			||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"use strict";
 | 
					"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?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***/ }),
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@ List item for a podcast.
 | 
				
			|||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
    {% endcomment %}
 | 
					    {% endcomment %}
 | 
				
			||||||
    <a-sound-item :data="{{ object|json }}" :player="player"
 | 
					    <a-sound-item :data="{{ object|json }}" :player="player"
 | 
				
			||||||
        :actions="['play']" @click="player.play(item)">
 | 
					        :actions="['play']">
 | 
				
			||||||
    </a-sound-item>
 | 
					    </a-sound-item>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,27 +4,16 @@ export const defaultConfig = {
 | 
				
			|||||||
    el: '#app',
 | 
					    el: '#app',
 | 
				
			||||||
    delimiters: ['[[', ']]'],
 | 
					    delimiters: ['[[', ']]'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data() {
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            page: null,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    computed: {
 | 
					    computed: {
 | 
				
			||||||
        player() { return window.aircox.player; },
 | 
					        player() { return window.aircox.player; },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function App(config, sync=false) {
 | 
					 | 
				
			||||||
    return (new AppConfig(config)).load(sync)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					export default class AppBuilder {
 | 
				
			||||||
 * Application config for an application instance
 | 
					    constructor(config={}) {
 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class AppConfig {
 | 
					 | 
				
			||||||
    constructor(config) {
 | 
					 | 
				
			||||||
        this._config = config;
 | 
					        this._config = config;
 | 
				
			||||||
 | 
					        this.app = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get config() {
 | 
					    get config() {
 | 
				
			||||||
@ -42,22 +31,50 @@ export class AppConfig {
 | 
				
			|||||||
        this._config = value;
 | 
					        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;
 | 
					        var self = this;
 | 
				
			||||||
        return new Promise(function(resolve, reject) {
 | 
					        return new Promise((resolve, reject) => {
 | 
				
			||||||
            let func = () => { try { resolve(self.build()) } catch(error) { reject(error) }};
 | 
					            let func = () => {
 | 
				
			||||||
            sync ? func() : window.addEventListener('load', 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() {
 | 
					    loadFromState(state) {
 | 
				
			||||||
        let config = this.config;
 | 
					        return this.load({ content: state.content, title: state.title });
 | 
				
			||||||
        const el = document.querySelector(config.el)
 | 
					 | 
				
			||||||
        if(!el) {
 | 
					 | 
				
			||||||
            reject(`Error: missing element ${config.el}`);
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return new Vue(config);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ import '@fortawesome/fontawesome-free/css/fontawesome.min.css';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//-- aircox
 | 
					//-- aircox
 | 
				
			||||||
import App from './app';
 | 
					import AppBuilder from './app';
 | 
				
			||||||
import Sound from './sound';
 | 
					import Sound from './sound';
 | 
				
			||||||
import {Set} from './model';
 | 
					import {Set} from './model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,31 +31,40 @@ Vue.component('a-sound-item', SoundItem)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
window.aircox = {
 | 
					window.aircox = {
 | 
				
			||||||
    // main application
 | 
					    // main application
 | 
				
			||||||
    app: null,
 | 
					    appBuilder: null,
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // main application config
 | 
					 | 
				
			||||||
    appConfig: {},
 | 
					    appConfig: {},
 | 
				
			||||||
 | 
					    get app() { return this.appBuilder.app  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // player application
 | 
					    // 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
 | 
					    onPageFetch(event) {
 | 
				
			||||||
    get player() {
 | 
					        let submit = event.type == 'submit';
 | 
				
			||||||
        return this.playerApp && this.playerApp.$refs.player
 | 
					        let target = submit || event.target.tagName == 'A'
 | 
				
			||||||
    },
 | 
					                        ? event.target : event.target.closest('a');
 | 
				
			||||||
 | 
					        if(!target || target.hasAttribute('target'))
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loadPage(url) {
 | 
					        let url = submit ? target.getAttribute('action') || ''
 | 
				
			||||||
        fetch(url).then(response => response.text())
 | 
					                         : target.getAttribute('href');
 | 
				
			||||||
            .then(response => {
 | 
					        if(url===null || !(url === '' || url.startsWith('/') || url.startsWith('?')))
 | 
				
			||||||
                let doc = new DOMParser().parseFromString(response, 'text/html');
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                aircox.app && aircox.app.$destroy();
 | 
					        let options = {};
 | 
				
			||||||
                document.getElementById('app').innerHTML = doc.getElementById('app').innerHTML;
 | 
					        if(submit) {
 | 
				
			||||||
                App(() => window.aircox.appConfig, true).then(app => {
 | 
					            let formData = new FormData(event.target);
 | 
				
			||||||
                    aircox.app = app;
 | 
					            if(target.method == 'get')
 | 
				
			||||||
                    document.title = doc.title;
 | 
					                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,
 | 
					    Set: Set, Sound: Sound,
 | 
				
			||||||
@ -63,21 +72,17 @@ window.aircox = {
 | 
				
			|||||||
window.Vue = Vue;
 | 
					window.Vue = Vue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
App({el: '#player'}).then(app => window.aircox.playerApp = app);
 | 
					aircox.playerBuilder = new AppBuilder({el: '#player'});
 | 
				
			||||||
App(() => window.aircox.appConfig).then(app => {
 | 
					aircox.playerBuilder.load({async:true});
 | 
				
			||||||
    window.aircox.app = app;
 | 
					aircox.appBuilder = new AppBuilder(x => window.aircox.appConfig);
 | 
				
			||||||
    window.addEventListener('click', event => {
 | 
					aircox.appBuilder.load({async:true}).then(app => {
 | 
				
			||||||
        let target = event.target.tagName == 'A' ? event.target : event.target.closest('a');
 | 
					    //-- load page hooks
 | 
				
			||||||
        if(!target || !target.hasAttribute('href'))
 | 
					    window.addEventListener('click', event => aircox.onPageFetch(event), true);
 | 
				
			||||||
            return;
 | 
					    window.addEventListener('submit', event => aircox.onPageFetch(event), true);
 | 
				
			||||||
 | 
					    window.addEventListener('popstate', event => {
 | 
				
			||||||
        let href = target.getAttribute('href');
 | 
					        if(event.state && event.state.content)
 | 
				
			||||||
        if(href && href !='#') {
 | 
					            aircox.appBuilder.loadFromState(event.state);
 | 
				
			||||||
            window.aircox.loadPage(href);
 | 
					    });
 | 
				
			||||||
            event.preventDefault();
 | 
					 | 
				
			||||||
            event.stopPropagation();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }, true);
 | 
					 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
            <Playlist ref="pin" class="player-panel menu" v-show="panel == 'pin'"
 | 
					            <Playlist ref="pin" class="player-panel menu" v-show="panel == 'pin'"
 | 
				
			||||||
                name="Pinned"
 | 
					                name="Pinned"
 | 
				
			||||||
                :actions="['page']"
 | 
					                :actions="['page']"
 | 
				
			||||||
                :editable="true" :player="self" :set="sets.pin" @select="play('pin', $event.index)" 
 | 
					                :editable="true" :player="self" :set="sets.pin" @select="togglePlay('pin', $event.index)" 
 | 
				
			||||||
                listClass="menu-list" itemClass="menu-item">
 | 
					                listClass="menu-list" itemClass="menu-item">
 | 
				
			||||||
                <template v-slot:header="">
 | 
					                <template v-slot:header="">
 | 
				
			||||||
                    <p class="menu-label">
 | 
					                    <p class="menu-label">
 | 
				
			||||||
@ -15,7 +15,7 @@
 | 
				
			|||||||
            </Playlist>
 | 
					            </Playlist>
 | 
				
			||||||
            <Playlist ref="queue" class="player-panel menu" v-show="panel == 'queue'"
 | 
					            <Playlist ref="queue" class="player-panel menu" v-show="panel == 'queue'"
 | 
				
			||||||
                :actions="['page']"
 | 
					                :actions="['page']"
 | 
				
			||||||
                :editable="true" :player="self" :set="sets.queue" @select="play('queue', $event.index)" 
 | 
					                :editable="true" :player="self" :set="sets.queue" @select="togglePlay('queue', $event.index)" 
 | 
				
			||||||
                listClass="menu-list" itemClass="menu-item">
 | 
					                listClass="menu-list" itemClass="menu-item">
 | 
				
			||||||
                <template v-slot:header="">
 | 
					                <template v-slot:header="">
 | 
				
			||||||
                    <p class="menu-label">
 | 
					                    <p class="menu-label">
 | 
				
			||||||
@ -44,7 +44,7 @@
 | 
				
			|||||||
                    @select="audio.currentTime = $event"></Progress>
 | 
					                    @select="audio.currentTime = $event"></Progress>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="media-right">
 | 
					            <div class="media-right">
 | 
				
			||||||
                <button class="button has-text-weight-bold" v-if="loaded" @click="playLive()">
 | 
					                <button class="button has-text-weight-bold" v-if="loaded" @click="play()">
 | 
				
			||||||
                    <span class="icon is-size-6 has-text-danger">
 | 
					                    <span class="icon is-size-6 has-text-danger">
 | 
				
			||||||
                        <span class="fa fa-circle"></span>
 | 
					                        <span class="fa fa-circle"></span>
 | 
				
			||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
@ -105,8 +105,8 @@ export default {
 | 
				
			|||||||
            loaded: null,
 | 
					            loaded: null,
 | 
				
			||||||
            //! Active panel name
 | 
					            //! Active panel name
 | 
				
			||||||
            panel: null,
 | 
					            panel: null,
 | 
				
			||||||
            //! current playing playlist component
 | 
					            //! current playing playlist name
 | 
				
			||||||
            playlist: null,
 | 
					            playlistName: null,
 | 
				
			||||||
            //! players' playlists' sets
 | 
					            //! players' playlists' sets
 | 
				
			||||||
            sets: {
 | 
					            sets: {
 | 
				
			||||||
                queue: Set.storeLoad(Sound, "playlist.queue", { max: 30, unique: true }),
 | 
					                queue: Set.storeLoad(Sound, "playlist.queue", { max: 30, unique: true }),
 | 
				
			||||||
@ -126,6 +126,10 @@ export default {
 | 
				
			|||||||
        playing() { return this.state == State.playing; },
 | 
					        playing() { return this.state == State.playing; },
 | 
				
			||||||
        loading() { return this.state == State.loading; },
 | 
					        loading() { return this.state == State.loading; },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playlist() {
 | 
				
			||||||
 | 
					            return this.playlistName ? this.$refs[this.playlistName] : null;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        current() {
 | 
					        current() {
 | 
				
			||||||
            return this.loaded || this.live && this.live.current;
 | 
					            return this.loaded || this.live && this.live.current;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@ -155,7 +159,7 @@ export default {
 | 
				
			|||||||
            let set = this.sets[name];
 | 
					            let set = this.sets[name];
 | 
				
			||||||
            return (set ? (set.length ? "" : "has-text-grey-light ")
 | 
					            return (set ? (set.length ? "" : "has-text-grey-light ")
 | 
				
			||||||
                       + (this.panel == name ? "is-info "
 | 
					                       + (this.panel == name ? "is-info "
 | 
				
			||||||
                          : this.playlist && this.playlist == this.$refs[name] ? 'is-primary '
 | 
					                          : this.playlistName == name ? 'is-primary '
 | 
				
			||||||
                          : '') : '')
 | 
					                          : '') : '')
 | 
				
			||||||
                + "button has-text-weight-bold";
 | 
					                + "button has-text-weight-bold";
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@ -168,17 +172,33 @@ export default {
 | 
				
			|||||||
        isPlaying(item) { return this.isLoaded(item) && !this.paused },
 | 
					        isPlaying(item) { return this.isLoaded(item) && !this.paused },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _setPlaylist(playlist) {
 | 
					        _setPlaylist(playlist) {
 | 
				
			||||||
            this.playlist = playlist ? this.$refs[playlist] : null;
 | 
					            this.playlistName = playlist;
 | 
				
			||||||
            for(var p in this.sets)
 | 
					            for(var p in this.sets)
 | 
				
			||||||
                if(p != playlist)
 | 
					                if(p != playlist)
 | 
				
			||||||
                    this.$refs[p].unselect();
 | 
					                    this.$refs[p].unselect();
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        load(playlist, {src=null, item=null}={}) {
 | 
					        /// Play a playlist's sound (by playlist name, and sound index)
 | 
				
			||||||
            src = src || item.src;
 | 
					        play(playlist=null, index=0) {
 | 
				
			||||||
            this.loaded = item;
 | 
					            let src = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // from playlist
 | 
				
			||||||
 | 
					            if(playlist !== null) {
 | 
				
			||||||
 | 
					                let item = this.$refs[playlist].get(index);
 | 
				
			||||||
 | 
					                if(!item)
 | 
				
			||||||
 | 
					                    throw `No sound at index ${index} for playlist ${playlist}`;
 | 
				
			||||||
 | 
					                this.loaded = item;
 | 
				
			||||||
 | 
					                src = item.src;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // from live
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                this.loaded = null;
 | 
				
			||||||
 | 
					                src = this.live.src;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this._setPlaylist(playlist);
 | 
					            this._setPlaylist(playlist);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // load sources
 | 
				
			||||||
            const audio = this.audio;
 | 
					            const audio = this.audio;
 | 
				
			||||||
            if(src instanceof Array) {
 | 
					            if(src instanceof Array) {
 | 
				
			||||||
                audio.innerHTML = '';
 | 
					                audio.innerHTML = '';
 | 
				
			||||||
@ -192,29 +212,13 @@ export default {
 | 
				
			|||||||
                audio.src = src;
 | 
					                audio.src = src;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            audio.load();
 | 
					            audio.load();
 | 
				
			||||||
        },
 | 
					            audio.play();
 | 
				
			||||||
 | 
					            audio.play().catch(e => console.error(e))
 | 
				
			||||||
        /// Play a playlist's sound (by playlist name, and sound index)
 | 
					 | 
				
			||||||
        play(playlist=null, index=0) {
 | 
					 | 
				
			||||||
            if(index < 0)
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if(!playlist)
 | 
					 | 
				
			||||||
                playlist = 'queue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            console.log('play', playlist, index, this.audio);
 | 
					 | 
				
			||||||
            let item = this.$refs[playlist].get(index);
 | 
					 | 
				
			||||||
            if(item) {
 | 
					 | 
				
			||||||
                this.load(playlist, {item});
 | 
					 | 
				
			||||||
                this.audio.play().catch(e => console.error(e))
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
                throw `No sound at index ${index} for playlist ${playlist}`;
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// Push items to playlist (by name)
 | 
					        /// Push items to playlist (by name)
 | 
				
			||||||
        push(playlist, ...items) {
 | 
					        push(playlist, ...items) {
 | 
				
			||||||
            this.$refs[playlist].push(...items);
 | 
					            return this.$refs[playlist].push(...items);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// Push and play items
 | 
					        /// Push and play items
 | 
				
			||||||
@ -224,29 +228,30 @@ export default {
 | 
				
			|||||||
            this.play(playlist, index);
 | 
					            this.play(playlist, index);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Handle click event that plays multiple items (from `data-sounds` attribute)
 | 
				
			||||||
        playButtonClick(event) {
 | 
					        playButtonClick(event) {
 | 
				
			||||||
            var items = JSON.parse(event.currentTarget.dataset.sounds);
 | 
					            var items = JSON.parse(event.currentTarget.dataset.sounds);
 | 
				
			||||||
            this.playItems('queue', ...items);
 | 
					            this.playItems('queue', ...items);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// Play live stream
 | 
					 | 
				
			||||||
        playLive() {
 | 
					 | 
				
			||||||
            this.load(null, {src: this.live.src});
 | 
					 | 
				
			||||||
            this.audio.play().catch(e => console.error(e))
 | 
					 | 
				
			||||||
            this.panel = '';
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// Pause
 | 
					        /// Pause
 | 
				
			||||||
        pause() {
 | 
					        pause() {
 | 
				
			||||||
            this.audio.pause()
 | 
					            this.audio.pause()
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //! Play/pause
 | 
					        //! Play/pause
 | 
				
			||||||
        togglePlay() {
 | 
					        togglePlay(playlist=null, index=0) {
 | 
				
			||||||
 | 
					            if(playlist !== null) {
 | 
				
			||||||
 | 
					                let item = this.sets[playlist].get(index);
 | 
				
			||||||
 | 
					                if(!this.playlist || this.playlistName !== playlist || this.loaded != item) {
 | 
				
			||||||
 | 
					                    this.play(playlist, index);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if(this.paused)
 | 
					            if(this.paused)
 | 
				
			||||||
                this.audio.play().catch(e => console.error(e))
 | 
					                this.audio.play().catch(e => console.error(e))
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
                this.pause()
 | 
					                this.audio.pause();
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //! Pin/Unpin an item
 | 
					        //! Pin/Unpin an item
 | 
				
			||||||
@ -266,7 +271,7 @@ export default {
 | 
				
			|||||||
            this.state = audio.paused ? State.paused : State.playing;
 | 
					            this.state = audio.paused ? State.paused : State.playing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1))
 | 
					            if(event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1))
 | 
				
			||||||
                this.playLive();
 | 
					                this.play();
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -14,11 +14,8 @@
 | 
				
			|||||||
        <div class="media-content">
 | 
					        <div class="media-content">
 | 
				
			||||||
            <slot name="content" :player="player" :item="item" :loaded="loaded">
 | 
					            <slot name="content" :player="player" :item="item" :loaded="loaded">
 | 
				
			||||||
                <h4 class="title is-4">{{ name || item.name }}</h4>
 | 
					                <h4 class="title is-4">{{ name || item.name }}</h4>
 | 
				
			||||||
                <a class="subtitle is-6" v-if="hasAction('page') && item.data.page_url"
 | 
					                <a class="subtitle is-6 is-inline-block" v-if="hasAction('page') && item.data.page_url"
 | 
				
			||||||
                    :href="item.data.page_url">
 | 
					                    :href="item.data.page_url">
 | 
				
			||||||
                    <i class="icon">
 | 
					 | 
				
			||||||
                        <i class="fas fa-link"></i>
 | 
					 | 
				
			||||||
                    </i>
 | 
					 | 
				
			||||||
                    {{ item.data.page_title }}
 | 
					                    {{ item.data.page_title }}
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
            </slot>
 | 
					            </slot>
 | 
				
			||||||
 | 
				
			|||||||
@ -221,10 +221,6 @@ a.navbar-item.is-active {
 | 
				
			|||||||
            transition: background-color 1s;
 | 
					            transition: background-color 1s;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .button:focus {
 | 
					 | 
				
			||||||
            background-color: $info;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .title {
 | 
					        .title {
 | 
				
			||||||
            margin: 0em;
 | 
					            margin: 0em;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -260,7 +256,6 @@ main {
 | 
				
			|||||||
    .cover.is-tiny { height: 2em; }
 | 
					    .cover.is-tiny { height: 2em; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.sound-item .cover { height: 5em; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
aside {
 | 
					aside {
 | 
				
			||||||
    & > section {
 | 
					    & > section {
 | 
				
			||||||
@ -280,6 +275,11 @@ aside {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sound-item {
 | 
				
			||||||
 | 
					    .cover { height: 5em; }
 | 
				
			||||||
 | 
					    .media-content a { padding: 0em; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.is-round, .sound-item .button {
 | 
					.is-round, .sound-item .button {
 | 
				
			||||||
    border: 1px $grey solid;
 | 
					    border: 1px $grey solid;
 | 
				
			||||||
    border-radius: 1em;
 | 
					    border-radius: 1em;
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user