hot reload
This commit is contained in:
		@ -1,6 +1,5 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const defaultConfig = {
 | 
			
		||||
    el: '#app',
 | 
			
		||||
    delimiters: ['[[', ']]'],
 | 
			
		||||
@ -12,12 +11,12 @@ export const defaultConfig = {
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        player() { return window.aircox.player; }
 | 
			
		||||
        player() { return window.aircox.player; },
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function App(config) {
 | 
			
		||||
    return (new AppConfig(config)).load()
 | 
			
		||||
export default function App(config, sync=false) {
 | 
			
		||||
    return (new AppConfig(config)).load(sync)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -30,30 +29,36 @@ export class AppConfig {
 | 
			
		||||
 | 
			
		||||
    get config() {
 | 
			
		||||
        let config = this._config instanceof Function ? this._config() : this._config;
 | 
			
		||||
        return {...defaultConfig, ...config};
 | 
			
		||||
        for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {
 | 
			
		||||
            if(!config[k] && defaultConfig[k])
 | 
			
		||||
                config[k] = defaultConfig[k]
 | 
			
		||||
            else if(config[k] instanceof Object)
 | 
			
		||||
                config[k] = {...defaultConfig[k], ...config[k]}
 | 
			
		||||
        }
 | 
			
		||||
        return config;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set config(value) {
 | 
			
		||||
        this._config = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    load() {
 | 
			
		||||
    load(sync=false) {
 | 
			
		||||
        var self = this;
 | 
			
		||||
        return new Promise(function(resolve, reject) {
 | 
			
		||||
            window.addEventListener('load', () => {
 | 
			
		||||
                try {
 | 
			
		||||
                    let config = self.config;
 | 
			
		||||
                    const el = document.querySelector(config.el)
 | 
			
		||||
                    if(!el) {
 | 
			
		||||
                        reject(`Error: missing element ${config.el}`);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    resolve(new Vue(config))
 | 
			
		||||
                }
 | 
			
		||||
                catch(error) { reject(error) }
 | 
			
		||||
            })
 | 
			
		||||
            let func = () => { try { resolve(self.build()) } catch(error) { reject(error) }};
 | 
			
		||||
            sync ? func() : window.addEventListener('load', 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								assets/public/episode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								assets/public/episode.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <slot :page="page" :podcasts="podcasts"></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import {Set} from './model';
 | 
			
		||||
import Sound from './sound';
 | 
			
		||||
import Page from './page';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    extends: Page,
 | 
			
		||||
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            podcasts: new Set(Sound, {items:this.page.podcasts}),
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,12 +16,14 @@ import {Set} from './model';
 | 
			
		||||
 | 
			
		||||
import './styles.scss';
 | 
			
		||||
 | 
			
		||||
import Autocomplete from './autocomplete.vue';
 | 
			
		||||
import Player from './player.vue';
 | 
			
		||||
import Playlist from './playlist.vue';
 | 
			
		||||
import Autocomplete from './autocomplete';
 | 
			
		||||
import Episode from './episode';
 | 
			
		||||
import Player from './player';
 | 
			
		||||
import Playlist from './playlist';
 | 
			
		||||
import SoundItem from './soundItem';
 | 
			
		||||
 | 
			
		||||
Vue.component('a-autocomplete', Autocomplete)
 | 
			
		||||
Vue.component('a-episode', Episode)
 | 
			
		||||
Vue.component('a-player', Player)
 | 
			
		||||
Vue.component('a-playlist', Playlist)
 | 
			
		||||
Vue.component('a-sound-item', SoundItem)
 | 
			
		||||
@ -42,14 +44,40 @@ window.aircox = {
 | 
			
		||||
        return this.playerApp && this.playerApp.$refs.player
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    loadPage(url) {
 | 
			
		||||
        fetch(url).then(response => response.text())
 | 
			
		||||
            .then(response => {
 | 
			
		||||
                let doc = new DOMParser().parseFromString(response, 'text/html');
 | 
			
		||||
 | 
			
		||||
                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;
 | 
			
		||||
                })
 | 
			
		||||
            });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    Set: Set, Sound: Sound,
 | 
			
		||||
};
 | 
			
		||||
window.Vue = Vue;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
App({el: '#player'}).then(app => window.aircox.playerApp = app,
 | 
			
		||||
                          () => undefined);
 | 
			
		||||
App(() => window.aircox.appConfig).then(app => { window.aircox.app = app },
 | 
			
		||||
                                        () => undefined)
 | 
			
		||||
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);
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -42,9 +42,14 @@ export default {
 | 
			
		||||
        find(pred) { return this.set.find(pred) },
 | 
			
		||||
        findIndex(pred) { return this.set.findIndex(pred) },
 | 
			
		||||
 | 
			
		||||
        push(...items) {
 | 
			
		||||
        /**
 | 
			
		||||
         * Add items to list, return index of the first provided item.
 | 
			
		||||
         */
 | 
			
		||||
        push(item, ...items) {
 | 
			
		||||
            let index = this.set.push(item);
 | 
			
		||||
            for(var item of items)
 | 
			
		||||
                this.set.push(item);
 | 
			
		||||
            return index;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        remove(index, select=False) {
 | 
			
		||||
 | 
			
		||||
@ -176,20 +176,21 @@ export class Set {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add item to set
 | 
			
		||||
     * Add item to set, return index.
 | 
			
		||||
     */
 | 
			
		||||
    push(item, {args={},save=true}={}) {
 | 
			
		||||
        item = item instanceof this.model ? item : new this.model(item, args);
 | 
			
		||||
        if(this.unique) {
 | 
			
		||||
            let index = this.findIndex(item);
 | 
			
		||||
            if(index > -1)
 | 
			
		||||
                return;
 | 
			
		||||
                return index;
 | 
			
		||||
        }
 | 
			
		||||
        if(this.max && this.items.length >= this.max)
 | 
			
		||||
            this.items.splice(0,this.items.length-this.max)
 | 
			
		||||
 | 
			
		||||
        this.items.push(item);
 | 
			
		||||
        save && this.save();
 | 
			
		||||
        return this.items.length-1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								assets/public/page.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								assets/public/page.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <slot></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    data() {
 | 
			
		||||
        return {}
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    props: {
 | 
			
		||||
        page: Object,
 | 
			
		||||
        title: String,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@
 | 
			
		||||
        <div :class="['player-panels', panel ? 'is-open' : '']">
 | 
			
		||||
            <Playlist ref="pin" class="player-panel menu" v-show="panel == 'pin'"
 | 
			
		||||
                name="Pinned"
 | 
			
		||||
                :actions="['page']"
 | 
			
		||||
                :editable="true" :player="self" :set="sets.pin" @select="play('pin', $event.index)" 
 | 
			
		||||
                listClass="menu-list" itemClass="menu-item">
 | 
			
		||||
                <template v-slot:header="">
 | 
			
		||||
@ -13,6 +14,7 @@
 | 
			
		||||
                </template>
 | 
			
		||||
            </Playlist>
 | 
			
		||||
            <Playlist ref="queue" class="player-panel menu" v-show="panel == 'queue'"
 | 
			
		||||
                :actions="['page']"
 | 
			
		||||
                :editable="true" :player="self" :set="sets.queue" @select="play('queue', $event.index)" 
 | 
			
		||||
                listClass="menu-list" itemClass="menu-item">
 | 
			
		||||
                <template v-slot:header="">
 | 
			
		||||
@ -26,12 +28,11 @@
 | 
			
		||||
 | 
			
		||||
        <div class="player-bar media">
 | 
			
		||||
            <div class="media-left">
 | 
			
		||||
                <div class="button" @click="togglePlay()"
 | 
			
		||||
                <button class="button" @click="togglePlay()"
 | 
			
		||||
                        :title="buttonTitle" :aria-label="buttonTitle">
 | 
			
		||||
                    <span class="fas fa-pause" v-if="playing"></span>
 | 
			
		||||
                    <span class="fas fa-play" v-else></span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <slot name="sources"></slot>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="media-left media-cover" v-if="current && current.cover">
 | 
			
		||||
                <img :src="current.cover" class="cover" />
 | 
			
		||||
@ -218,13 +219,16 @@ export default {
 | 
			
		||||
 | 
			
		||||
        /// Push and play items
 | 
			
		||||
        playItems(playlist, ...items) {
 | 
			
		||||
            this.push(playlist, ...items);
 | 
			
		||||
 | 
			
		||||
            let index = this.$refs[playlist].findIndex(items[0]);
 | 
			
		||||
            let index = this.push(playlist, ...items);
 | 
			
		||||
            this.$refs[playlist].selectedIndex = index;
 | 
			
		||||
            this.play(playlist, index);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        playButtonClick(event) {
 | 
			
		||||
            var items = JSON.parse(event.currentTarget.dataset.sounds);
 | 
			
		||||
            this.playItems('queue', ...items);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        /// Play live stream
 | 
			
		||||
        playLive() {
 | 
			
		||||
            this.load(null, {src: this.live.src});
 | 
			
		||||
@ -266,10 +270,6 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.sources = this.$slots.sources;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    components: { Playlist, Progress },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="media">
 | 
			
		||||
        <div class="media-left" v-if="hasAction('play')">
 | 
			
		||||
            <button class="button" @click="$emit('togglePlay')">
 | 
			
		||||
    <div class="media sound-item">
 | 
			
		||||
        <div class="media-left">
 | 
			
		||||
            <img class="cover is-tiny" :src="item.data.cover" v-if="item.data.cover">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="media-left">
 | 
			
		||||
            <button class="button" @click.stop="$emit('togglePlay')">
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                    <span class="fa fa-pause" v-if="playing"></span>
 | 
			
		||||
                    <span class="fa fa-play" v-else></span>
 | 
			
		||||
@ -10,9 +13,14 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="media-content">
 | 
			
		||||
            <slot name="content" :player="player" :item="item" :loaded="loaded">
 | 
			
		||||
                <h4 class="title is-4 is-inline-block">
 | 
			
		||||
                    {{ 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"
 | 
			
		||||
                    :href="item.data.page_url">
 | 
			
		||||
                    <i class="icon">
 | 
			
		||||
                        <i class="fas fa-link"></i>
 | 
			
		||||
                    </i>
 | 
			
		||||
                    {{ item.data.page_title }}
 | 
			
		||||
                </a>
 | 
			
		||||
            </slot>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="media-right">
 | 
			
		||||
@ -33,7 +41,6 @@ export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        data: {type: Object, default: x => {}},
 | 
			
		||||
        name: String,
 | 
			
		||||
        cover: String,
 | 
			
		||||
        player: Object,
 | 
			
		||||
        page_url: String,
 | 
			
		||||
        actions: {type:Array, default: x => []},
 | 
			
		||||
 | 
			
		||||
@ -167,7 +167,6 @@ a.navbar-item.is-active {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- player
 | 
			
		||||
.player {
 | 
			
		||||
    box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
 | 
			
		||||
@ -257,16 +256,12 @@ section > .toolbar {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
main {
 | 
			
		||||
    .cover {
 | 
			
		||||
        margin: 1em 0em;
 | 
			
		||||
        border: 0.2em black solid;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .small-cover {
 | 
			
		||||
        width: 10em;
 | 
			
		||||
    }
 | 
			
		||||
    .cover.is-small { width: 10em; }
 | 
			
		||||
    .cover.is-tiny { height: 2em; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sound-item .cover { height: 5em; }
 | 
			
		||||
 | 
			
		||||
aside {
 | 
			
		||||
    & > section {
 | 
			
		||||
        margin-bottom: 2em;
 | 
			
		||||
@ -276,9 +271,8 @@ aside {
 | 
			
		||||
        margin-bottom: 2em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .small-cover {
 | 
			
		||||
        width: 4em;
 | 
			
		||||
    }
 | 
			
		||||
    .cover.is-small { width: 10em; }
 | 
			
		||||
    .cover.is-tiny { height: 2em; }
 | 
			
		||||
 | 
			
		||||
    .media .subtitle {
 | 
			
		||||
        font-size: 1em;
 | 
			
		||||
@ -286,4 +280,8 @@ aside {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.is-round, .sound-item .button {
 | 
			
		||||
    border: 1px $grey solid;
 | 
			
		||||
    border-radius: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user