hot reload

This commit is contained in:
bkfox
2020-11-08 00:45:49 +01:00
parent 222300945e
commit 5fd72c33cc
19 changed files with 473 additions and 105 deletions

View File

@ -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
View 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>

View File

@ -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);
})

View File

@ -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) {

View File

@ -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
View File

@ -0,0 +1,20 @@
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: {
page: Object,
title: String,
},
}
</script>

View File

@ -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>

View File

@ -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 => []},

View File

@ -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;
}