forked from rc/aircox
work on player
This commit is contained in:
@ -1,53 +1,55 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
|
||||
export const appBaseConfig = {
|
||||
export const defaultConfig = {
|
||||
el: '#app',
|
||||
delimiters: ['[[', ']]'],
|
||||
|
||||
computed: {
|
||||
player() {
|
||||
return window.aircox.player;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function App(config) {
|
||||
return (new AppConfig(config)).load()
|
||||
}
|
||||
|
||||
/**
|
||||
* Application config for the main application instance
|
||||
* Application config for an application instance
|
||||
*/
|
||||
var appConfig = {};
|
||||
export class AppConfig {
|
||||
constructor(config) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
export function setAppConfig(config) {
|
||||
for(var member in appConfig) delete appConfig[member];
|
||||
return Object.assign(appConfig, config)
|
||||
}
|
||||
get config() {
|
||||
let config = this._config instanceof Function ? this._config() : this._config;
|
||||
return {...defaultConfig, ...config};
|
||||
}
|
||||
|
||||
export function getAppConfig(config) {
|
||||
if(config instanceof Function)
|
||||
config = config()
|
||||
config = config == null ? appConfig : config;
|
||||
return {...appBaseConfig, ...config}
|
||||
}
|
||||
set config(value) {
|
||||
this._config = value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create Vue application at window 'load' event and return a Promise
|
||||
* resolving to the created app.
|
||||
*
|
||||
* config: defaults to appConfig (checked when window is loaded)
|
||||
*/
|
||||
export function loadApp(config=null) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
window.addEventListener('load', function() {
|
||||
try {
|
||||
config = getAppConfig(config)
|
||||
const el = document.querySelector(config.el)
|
||||
if(!el) {
|
||||
reject(`Error: missing element ${config.el}`);
|
||||
return;
|
||||
load() {
|
||||
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))
|
||||
}
|
||||
|
||||
resolve(new Vue(config))
|
||||
}
|
||||
catch(error) { reject(error) }
|
||||
})
|
||||
})
|
||||
catch(error) { reject(error) }
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -10,15 +10,17 @@ import '@fortawesome/fontawesome-free/css/fontawesome.min.css';
|
||||
|
||||
|
||||
//-- aircox
|
||||
import {appConfig, loadApp} from './app';
|
||||
import {App} from './app';
|
||||
|
||||
import './styles.scss';
|
||||
|
||||
import Player from './player.vue';
|
||||
import Autocomplete from './autocomplete.vue';
|
||||
import Player from './player.vue';
|
||||
import SoundItem from './soundItem';
|
||||
|
||||
Vue.component('a-player', Player)
|
||||
Vue.component('a-autocomplete', Autocomplete)
|
||||
Vue.component('a-player', Player)
|
||||
Vue.component('a-sound-item', SoundItem)
|
||||
|
||||
|
||||
window.aircox = {
|
||||
@ -38,9 +40,9 @@ window.aircox = {
|
||||
};
|
||||
|
||||
|
||||
loadApp({el: '#player'}).then(app => { window.aircox.playerApp = app },
|
||||
() => undefined)
|
||||
loadApp(() => window.aircox.appConfig ).then(app => { window.aircox.app = app },
|
||||
() => undefined)
|
||||
App({el: '#player'}).then(app => window.aircox.playerApp = app,
|
||||
() => undefined);
|
||||
App(() => window.aircox.appConfig).then(app => { window.aircox.app = app },
|
||||
() => undefined)
|
||||
|
||||
|
||||
|
64
assets/public/list.vue
Normal file
64
assets/public/list.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot name="header"></slot>
|
||||
<ul :class="listClass">
|
||||
<slot name="start"></slot>
|
||||
<template v-for="(item,index) in items">
|
||||
<li :class="itemClass" @click="select(index)">
|
||||
<slot name="item" :set="set" :index="index" :item="item"></slot>
|
||||
</li>
|
||||
</template>
|
||||
<slot name="end"></slot>
|
||||
</ul>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: this.default,
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
listClass: String,
|
||||
itemClass: String,
|
||||
default: { type: Number, default: -1},
|
||||
set: Object,
|
||||
},
|
||||
|
||||
computed: {
|
||||
model() { return this.set.model },
|
||||
items() { return this.set.items },
|
||||
length() { return this.set.length },
|
||||
|
||||
selected() {
|
||||
return this.items && this.items.length > this.selectedIndex > -1
|
||||
? this.items[this.selectedIndex] : null;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
select(index=null) {
|
||||
if(index === null)
|
||||
index = this.selectedIndex;
|
||||
else if(this.selectedIndex == index)
|
||||
return;
|
||||
|
||||
this.selectedIndex = Math.min(index, this.items.length-1);
|
||||
this.$emit('select', { item: this.selected, index: this.selectedIndex });
|
||||
return this.selectedIndex;
|
||||
},
|
||||
|
||||
selectNext() {
|
||||
let index = this.selectedIndex + 1;
|
||||
return this.select(index >= this.items.length ? -1 : index);
|
||||
},
|
||||
|
||||
// add()
|
||||
// insert() + drag & drop
|
||||
// remove()
|
||||
},
|
||||
}
|
||||
</script>
|
@ -1,14 +1,24 @@
|
||||
import {setEcoTimeout} from 'public/utils';
|
||||
|
||||
|
||||
export default class {
|
||||
constructor(url, timeout) {
|
||||
export default class Live {
|
||||
constructor({url,timeout=10,src=""}={}) {
|
||||
this.url = url;
|
||||
this.timeout = timeout;
|
||||
this.src = src;
|
||||
|
||||
this.promise = null;
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
get current() {
|
||||
let items = this.logs && this.logs.items;
|
||||
let item = items && items[items.length-1];
|
||||
if(item)
|
||||
item.src = this.src;
|
||||
return item;
|
||||
}
|
||||
|
||||
//-- data refreshing
|
||||
drop() {
|
||||
this.promise = null;
|
||||
}
|
||||
@ -37,3 +47,4 @@ export default class {
|
||||
return promise
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,23 @@ export function getCsrf() {
|
||||
}
|
||||
|
||||
|
||||
// TODO: move in another module for reuse
|
||||
// TODO: prevent duplicate simple fetch
|
||||
export default class Model {
|
||||
constructor(data, {url=null}={}) {
|
||||
this.url = url;
|
||||
this.commit(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance id from its data
|
||||
*/
|
||||
static getId(data) {
|
||||
return data.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fetch options
|
||||
*/
|
||||
static getOptions(options) {
|
||||
return {
|
||||
headers: {
|
||||
@ -40,23 +46,14 @@ export default class Model {
|
||||
}
|
||||
}
|
||||
|
||||
static fetch(url, options=null, initArgs=null) {
|
||||
/**
|
||||
* Fetch item from server
|
||||
*/
|
||||
static fetch(url, options=null, args=null) {
|
||||
options = this.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => new this(d, {url: url, ...initArgs}));
|
||||
}
|
||||
|
||||
static fetchAll(url, options=null, initArgs=null) {
|
||||
options = this.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if(!(data instanceof Array))
|
||||
data = data.results;
|
||||
data = data.map(d => new this(d, {baseUrl: url, ...initArgs}));
|
||||
return data
|
||||
})
|
||||
.then(data => new this(data, {url: url, ...args}));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,35 +82,98 @@ export default class Model {
|
||||
*/
|
||||
commit(data) {
|
||||
this.id = this.constructor.getId(data);
|
||||
this.url = data.url_;
|
||||
Vue.set(this, 'data', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data as model with url prepent by `this.url`.
|
||||
* Save instance into localStorage.
|
||||
*/
|
||||
asChild(model, data, prefix='') {
|
||||
return new model(data, {baseUrl: `${this.url}${prefix}/`})
|
||||
store(key) {
|
||||
window.localStorage.setItem(key, JSON.stringify(this.data));
|
||||
}
|
||||
|
||||
getChildOf(attr, id) {
|
||||
const index = this.data[attr].findIndex(o => o.id = id)
|
||||
return index == -1 ? null : this.data[attr][index];
|
||||
}
|
||||
|
||||
static updateList(list=[], old=[], ...initArgs) {
|
||||
return list.reduce((items, data) => {
|
||||
const id = this.getId(data);
|
||||
let [index, obj] = [old.findIndex(o => o.id == id), null];
|
||||
if(index != -1) {
|
||||
old[index].commit(data)
|
||||
items.push(old[index]);
|
||||
}
|
||||
else
|
||||
items.push(new this(data, ...initArgs))
|
||||
return items;
|
||||
}, [])
|
||||
/**
|
||||
* Load model instance from localStorage.
|
||||
*/
|
||||
static storeLoad(key) {
|
||||
let item = window.localStorage.getItem(key);
|
||||
return item === null ? item : new this(JSON.parse(item));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List of models
|
||||
*/
|
||||
export class Set {
|
||||
constructor(model, {items=[],url=null,args={},unique=null,max=null,storeKey=null}={}) {
|
||||
this.items = items.map(x => x instanceof model ? x : new model(x, args));
|
||||
this.model = model;
|
||||
this.url = url;
|
||||
this.unique = unique;
|
||||
this.max = max;
|
||||
this.storeKey = storeKey;
|
||||
}
|
||||
|
||||
get length() { return this.items.length }
|
||||
indexOf(...args) { return this.items.indexOf(...args); }
|
||||
|
||||
/**
|
||||
* Fetch multiple items from server
|
||||
*/
|
||||
static fetch(url, options=null, args=null) {
|
||||
options = this.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => (data instanceof Array ? data : data.results)
|
||||
.map(d => new this.model(d, {url: url, ...args})))
|
||||
}
|
||||
|
||||
/**
|
||||
* Load list from localStorage
|
||||
*/
|
||||
static storeLoad(model, key, args={}) {
|
||||
let items = window.localStorage.getItem(key);
|
||||
return new this(model, {...args, storeKey: key, items: items ? JSON.parse(items) : []});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store list into localStorage
|
||||
*/
|
||||
store() {
|
||||
if(this.storeKey)
|
||||
window.localStorage.setItem(this.storeKey, JSON.stringify(
|
||||
this.items.map(i => i.data)));
|
||||
}
|
||||
|
||||
push(item, {args={}}={}) {
|
||||
item = item instanceof this.model ? item : new this.model(item, args);
|
||||
if(this.unique && this.items.find(x => x.id == item.id))
|
||||
return;
|
||||
if(this.max && this.items.length >= this.max)
|
||||
this.items.splice(0,this.items.length-this.max)
|
||||
|
||||
this.items.push(item);
|
||||
this._updated()
|
||||
}
|
||||
|
||||
remove(item, {args={}}={}) {
|
||||
item = item instanceof this.model ? item : new this.model(item, args);
|
||||
let index = this.items.findIndex(x => x.id == item.id);
|
||||
if(index == -1)
|
||||
return;
|
||||
|
||||
this.items.splice(index,1);
|
||||
this._updated()
|
||||
}
|
||||
|
||||
_updated() {
|
||||
Vue.set(this, 'items', this.items);
|
||||
this.store();
|
||||
}
|
||||
}
|
||||
|
||||
Set[Symbol.iterator] = function () {
|
||||
return this.items[Symbol.iterator]();
|
||||
}
|
||||
|
||||
|
@ -1,32 +1,94 @@
|
||||
<template>
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<div class="button" @click="toggle()"
|
||||
:title="buttonTitle" :aria-label="buttonTitle">
|
||||
<span class="fas fa-pause" v-if="playing"></span>
|
||||
<span class="fas fa-play" v-else></span>
|
||||
</div>
|
||||
<audio ref="audio" @playing="onChange" @ended="onChange" @pause="onChange">
|
||||
<div class="player">
|
||||
<List ref="queue" class="player-panel menu" v-show="panel == 'queue' && queue.length"
|
||||
:set="queue" @select="onSelect"
|
||||
listClass="menu-list" itemClass="menu-item">
|
||||
<template v-slot:header>
|
||||
<p class="menu-label">
|
||||
<span class="icon"><span class="fa fa-list"></span></span>
|
||||
Playlist
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:item="{item,index,set}">
|
||||
<SoundItem activeClass="is-active" :data="item" :player="self" :set="set"
|
||||
:actions="['remove']">
|
||||
<template v-slot:actions="{active,set}">
|
||||
<!-- TODO: stop player if active -->
|
||||
|
||||
</template>
|
||||
</SoundItem>
|
||||
</template>
|
||||
</List>
|
||||
<List ref="history" class="player-panel menu" v-show="panel == 'history' && history.length"
|
||||
:set="history" @select="onSelect"
|
||||
listClass="menu-list" itemClass="menu-item">
|
||||
<template v-slot:header>
|
||||
<p class="menu-label">
|
||||
<span class="icon"><span class="fa fa-clock"></span></span>
|
||||
History
|
||||
</p>
|
||||
</template>
|
||||
<template v-slot:item="{item,index,set}">
|
||||
<SoundItem activeClass="is-active" :data="item" :player="self" :set="set"
|
||||
:actions="['queue','remove']">
|
||||
</SoundItem>
|
||||
</template>
|
||||
</List>
|
||||
<div class="player-bar media">
|
||||
<div class="media-left">
|
||||
<div 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>
|
||||
<audio ref="audio" preload="metadata"
|
||||
@playing="onState" @ended="onState" @pause="onState">
|
||||
</audio>
|
||||
<slot name="sources"></slot>
|
||||
</audio>
|
||||
</div>
|
||||
<div class="media-left media-cover" v-if="current && current.cover">
|
||||
<img :src="current.cover" class="cover" />
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<slot name="content" :current="current"></slot>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<button class="button" v-if="active" @click="load() && play()">
|
||||
<span class="icon has-text-danger">
|
||||
<span class="fa fa-broadcast-tower"></span>
|
||||
</span>
|
||||
<span>Live</span>
|
||||
</button>
|
||||
|
||||
<button class="button" v-if="history.length"
|
||||
@click="togglePanel('history')"
|
||||
:class="[panel == 'history' ? 'is-info' : '']" >
|
||||
<span class="icon">
|
||||
<span class="fa fa-clock"></span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="button" v-if="queue.length"
|
||||
@click="togglePanel('queue')"
|
||||
:class="[panel == 'queue' ? 'is-info' : '']" >
|
||||
<span class="mr-2 is-size-6">{{ queue.length }}</span>
|
||||
<span class="icon"><span class="fa fa-list"></span></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-left media-cover" v-if="onAir && onAir.cover">
|
||||
<img :src="onAir.cover" class="cover" />
|
||||
</div>
|
||||
<div class="media-content" v-if="onAir && onAir.type == 'track'">
|
||||
<slot name="track" :onAir="onAir" :liveInfo="liveInfo"></slot>
|
||||
</div>
|
||||
<div class="media-content" v-else-if="onAir && onAir.type == 'diffusion'">
|
||||
<slot name="diffusion" :onAir="onAir" :liveInfo="liveInfo"></slot>
|
||||
</div>
|
||||
<div class="media-content" v-else><slot name="empty"></slot></div>
|
||||
<div v-if="progress">
|
||||
<span :style="{ width: progress }"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import LiveInfo from './liveInfo';
|
||||
import Vue, { ref } from 'vue';
|
||||
import Live from './live';
|
||||
import List from './list';
|
||||
import SoundItem from './soundItem';
|
||||
import Sound from './sound';
|
||||
import {Set} from './model';
|
||||
|
||||
|
||||
export const State = {
|
||||
paused: 0,
|
||||
@ -38,43 +100,80 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
state: State.paused,
|
||||
liveInfo: new LiveInfo(this.liveInfoUrl, this.liveInfoTimeout),
|
||||
active: null,
|
||||
live: this.liveArgs ? new Live(this.liveArgs) : null,
|
||||
panel: null,
|
||||
queue: Set.storeLoad(Sound, "player.queue", { max: 30, unique: true }),
|
||||
history: Set.storeLoad(Sound, "player.history", { max: 30, unique: true }),
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
buttonTitle: String,
|
||||
liveInfoUrl: String,
|
||||
liveInfoTimeout: { type: Number, default: 5},
|
||||
src: String,
|
||||
liveArgs: Object,
|
||||
},
|
||||
|
||||
computed: {
|
||||
paused() { return this.state == State.paused; },
|
||||
playing() { return this.state == State.playing; },
|
||||
loading() { return this.state == State.loading; },
|
||||
self() { return this; },
|
||||
|
||||
onAir() {
|
||||
return this.liveInfo.items && this.liveInfo.items[0];
|
||||
current() {
|
||||
return this.active || this.live && this.live.current;
|
||||
},
|
||||
|
||||
progress() {
|
||||
let audio = this.$refs.audio;
|
||||
return audio && Number.isFinite(audio.duration) && audio.duration ?
|
||||
audio.pos / audio.duration * 100 : null;
|
||||
},
|
||||
|
||||
buttonStyle() {
|
||||
if(!this.onAir)
|
||||
if(!this.current)
|
||||
return;
|
||||
return { backgroundImage: `url(${this.onAir.cover})` }
|
||||
}
|
||||
return { backgroundImage: `url(${this.current.cover})` }
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
load(src) {
|
||||
const audio = this.$refs.audio;
|
||||
audio.src = src;
|
||||
audio.load()
|
||||
togglePanel(panel) {
|
||||
this.panel = this.panel == panel ? null : panel;
|
||||
},
|
||||
|
||||
play(src) {
|
||||
if(src)
|
||||
this.load(src);
|
||||
isActive(item) {
|
||||
return item && this.active && this.active.src == item.src;
|
||||
},
|
||||
|
||||
_load(src) {
|
||||
const audio = this.$refs.audio;
|
||||
if(src instanceof Array) {
|
||||
audio.innerHTML = '';
|
||||
for(var s of src) {
|
||||
let source = document.createElement(source);
|
||||
source.setAttribute('src', s);
|
||||
audio.appendChild(source)
|
||||
}
|
||||
}
|
||||
else {
|
||||
audio.src = src;
|
||||
}
|
||||
audio.load();
|
||||
},
|
||||
|
||||
load(item) {
|
||||
if(item) {
|
||||
this._load(item.src)
|
||||
this.history.push(item);
|
||||
}
|
||||
else
|
||||
this._load(this.live.src);
|
||||
this.$set(this, 'active', item);
|
||||
},
|
||||
|
||||
play(item) {
|
||||
if(item)
|
||||
this.load(item);
|
||||
this.$refs.audio.play().catch(e => console.error(e))
|
||||
},
|
||||
|
||||
@ -82,31 +181,47 @@ export default {
|
||||
this.$refs.audio.pause()
|
||||
},
|
||||
|
||||
toggle() {
|
||||
console.log('tooogle', this.paused, '-', this.$refs.audio.src)
|
||||
//! Play/pause
|
||||
togglePlay() {
|
||||
if(this.paused)
|
||||
this.play()
|
||||
else
|
||||
this.pause()
|
||||
},
|
||||
|
||||
onChange(event) {
|
||||
//! Push item to queue
|
||||
push(item) {
|
||||
this.queue.push(item);
|
||||
this.panel = 'queue';
|
||||
},
|
||||
|
||||
onState(event) {
|
||||
const audio = this.$refs.audio;
|
||||
this.state = audio.paused ? State.paused : State.playing;
|
||||
|
||||
if(event.type == 'ended' && this.active) {
|
||||
this.queue.remove(this.active);
|
||||
if(this.queue.length)
|
||||
this.$refs.queue.select(0);
|
||||
else
|
||||
this.load();
|
||||
}
|
||||
},
|
||||
|
||||
onSelect({item,index}) {
|
||||
if(!this.isActive(item))
|
||||
this.play(item);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.liveInfo.refresh()
|
||||
this.sources = this.$slots.sources;
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.liveInfo.drop()
|
||||
components: {
|
||||
List, SoundItem,
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
import Model from './model';
|
||||
import Model, {Set} from './model';
|
||||
|
||||
|
||||
export default class Sound extends Model {
|
||||
get name() { return this.data.name }
|
||||
get src() { return this.data.url }
|
||||
|
||||
static getId(data) { return data.pk }
|
||||
}
|
||||
|
72
assets/public/soundItem.vue
Normal file
72
assets/public/soundItem.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<a :class="[active ? activeClass : '']" @click="actions.indexOf('play') == -1 && play()">
|
||||
<div class="media">
|
||||
<div class="media-left" v-if="hasAction('play')">
|
||||
<button class="button" @click="play()">
|
||||
<div class="icon">
|
||||
<span class="fa fa-pause" v-if="playing || loading"></span>
|
||||
<span class="fa fa-play" v-else></span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<slot name="content" :player="player" :item="item" :active="active">
|
||||
<h4 class="title is-4 is-inline-block">
|
||||
{{ name || item.name }}
|
||||
</h4>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<button class="button" v-if="hasAction('queue')" @click.stop="player.push(item)">
|
||||
<span class="icon is-small"><span class="fa fa-list"></span></span>
|
||||
</button>
|
||||
<button class="button" v-if="hasAction('remove')" @click.stop="set.remove(item)">
|
||||
<span class="icon is-small"><span class="fa fa-minus"></span></span>
|
||||
</button>
|
||||
<slot name="actions" :player="player" :item="item" :active="active"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
<script>
|
||||
import Model from './model';
|
||||
import Sound from './sound';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {type: Object, default: x => {}},
|
||||
name: String,
|
||||
cover: String,
|
||||
set: Object,
|
||||
player: Object,
|
||||
page_url: String,
|
||||
activeClass: String,
|
||||
actions: {type:Array, default: x => []},
|
||||
},
|
||||
|
||||
computed: {
|
||||
item() { return this.data instanceof Model ? this.data : new Sound(this.data || {}); },
|
||||
active() { return this.player && this.player.isActive(this.item) },
|
||||
playing() { return this.player && this.player.playing && this.active },
|
||||
paused() { return this.player && this.player.paused && this.active },
|
||||
loading() { return this.player && this.player.loading && this.active },
|
||||
},
|
||||
|
||||
methods: {
|
||||
hasAction(action) {
|
||||
return this.actions && this.actions.indexOf(action) != -1;
|
||||
},
|
||||
|
||||
play() {
|
||||
if(this.player && this.active)
|
||||
this.player.togglePlay()
|
||||
else
|
||||
this.player.play(this.item);
|
||||
},
|
||||
|
||||
push_to(playlist) {
|
||||
this.player.playlists[playlist].push(this.item, {unique_key:'id'});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
@ -129,17 +129,16 @@ a.navbar-item.is-active {
|
||||
padding: 0.2em 0em;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0.4em 0em;
|
||||
}
|
||||
p { padding: 0.4em 0em; }
|
||||
hr { background-color: $grey-light; }
|
||||
|
||||
.content {
|
||||
.h1 { font-size: 2em; font-weight: bolder; }
|
||||
.h2 { font-size: 1.5em; font-weight: bolder; }
|
||||
.h3 { font-size: 1.17em; font-weight: bolder; }
|
||||
.h4 { font-size: 1em; font-weight: bolder; }
|
||||
.h5 { font-size: 0.83em; font-weight: bolder; }
|
||||
.h6 { font-size: 0.67em; font-weight: bolder; }
|
||||
section {
|
||||
h1 { font-size: $size-1; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
|
||||
h2 { font-size: $size-3; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
|
||||
h3 { font-size: $size-4; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
|
||||
h4 { font-size: $size-5; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
|
||||
h5 { font-size: $size-6; font-weight: bolder; margin-top:0.4em; margin-bottom:0.2em; }
|
||||
h6 { font-size: $size-6; margin-top:0.4em; margin-bottom:0.2em; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,32 +172,43 @@ a.navbar-item.is-active {
|
||||
.player {
|
||||
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
|
||||
|
||||
.media-left:not(:last-child) {
|
||||
margin-right: 0em;
|
||||
.player-panel {
|
||||
margin: 0.4em;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.media-cover {
|
||||
border-left: 1px black solid;
|
||||
}
|
||||
.player-bar {
|
||||
border-top: 1px $grey-light solid;
|
||||
|
||||
.cover {
|
||||
font-size: 1.5rem !important;
|
||||
height: 2.5em !important;
|
||||
}
|
||||
.media-left:not(:last-child) {
|
||||
margin-right: 0em;
|
||||
}
|
||||
|
||||
.media-content {
|
||||
padding-top: 0.4em;
|
||||
padding-left: 0.4em;
|
||||
}
|
||||
.media-cover {
|
||||
border-left: 1px black solid;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 1.5rem !important;
|
||||
height: 2.5em;
|
||||
min-width: 2.5em;
|
||||
}
|
||||
.cover {
|
||||
font-size: 1.5rem !important;
|
||||
height: 2.5em !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0em;
|
||||
.media-content {
|
||||
padding-top: 0.4em;
|
||||
padding-left: 0.4em;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 1.5rem !important;
|
||||
height: 2.5em;
|
||||
min-width: 2.5em;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user