forked from rc/aircox
migrate to vue3; autocomplete still needs work
This commit is contained in:
9
assets/admin/app.js
Normal file
9
assets/admin/app.js
Normal file
@ -0,0 +1,9 @@
|
||||
import App from 'public/app';
|
||||
import AStatistics from './statistics.vue';
|
||||
|
||||
export default {
|
||||
...App,
|
||||
components: {...App.components, AStatistics},
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import App from 'public/app';
|
||||
import 'public';
|
||||
import '@fortawesome/fontawesome-free/css/all.min.css'
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.min.css'
|
||||
|
||||
import AdminApp from './app';
|
||||
import './admin.scss';
|
||||
import AStatistics from './statistics.vue';
|
||||
|
||||
window.aircox_admin = {
|
||||
/**
|
||||
@ -21,11 +21,5 @@ window.aircox_admin = {
|
||||
},
|
||||
}
|
||||
|
||||
window.aircox.builder.config = {
|
||||
...App,
|
||||
components: {...App.components, AStatistics},
|
||||
}
|
||||
|
||||
|
||||
|
||||
window.AdminApp = AdminApp
|
||||
|
||||
|
@ -12,7 +12,7 @@ const App = {
|
||||
player() { return window.aircox.player; },
|
||||
},
|
||||
|
||||
components: {AAutocomplete, AEpisode, APlayer, APlaylist, ASoundItem},
|
||||
components: {AAutocomplete, AEpisode, APlaylist, ASoundItem},
|
||||
}
|
||||
|
||||
export const PlayerApp = {
|
||||
|
@ -34,16 +34,16 @@ export default class Builder {
|
||||
* @param {Boolean} [reset=False]: if True, force application recreation.
|
||||
* @return `app.mount`'s result.
|
||||
*/
|
||||
mount({content=null, title=null, el=null, reset=false}={}) {
|
||||
mount({content=null, title=null, el=null, reset=false, props=null}={}) {
|
||||
try {
|
||||
this.unmount()
|
||||
|
||||
let config = this.config
|
||||
if(el === null)
|
||||
el = config.el
|
||||
|
||||
if(reset || !this.app)
|
||||
this.app = this.createApp({title,content,el,...config})
|
||||
this.app = this.createApp({title,content,el,...config}, props)
|
||||
|
||||
this.vm = this.app.mount(el)
|
||||
window.scroll(0, 0)
|
||||
return this.vm
|
||||
@ -53,7 +53,7 @@ export default class Builder {
|
||||
}
|
||||
}
|
||||
|
||||
createApp({el, title=null, content=null, ...config}) {
|
||||
createApp({el, title=null, content=null, ...config}, props) {
|
||||
const container = document.querySelector(el)
|
||||
if(!container)
|
||||
throw `Error: can't get element ${el}`
|
||||
@ -61,7 +61,7 @@ export default class Builder {
|
||||
container.innerHTML = content
|
||||
if(title)
|
||||
document.title = title
|
||||
return createApp(config)
|
||||
return createApp(config, props)
|
||||
}
|
||||
|
||||
unmount() {
|
||||
|
@ -1,60 +1,74 @@
|
||||
<template>
|
||||
<div class="control">
|
||||
<Autocomplete ref="autocomplete" :data="data" :placeholder="placeholder" :field="field"
|
||||
:loading="isFetching" open-on-focus
|
||||
@typing="fetch" @select="object => onSelect(object)"
|
||||
>
|
||||
</Autocomplete>
|
||||
<input v-if="valueField" ref="value" type="hidden" :name="valueField"
|
||||
:value="selected && selected[valueAttr || valueField]" />
|
||||
<datalist :id="listId">
|
||||
<template v-for="item in items" :key="item.path">
|
||||
<option :value="item[field]"></option>
|
||||
</template>
|
||||
</datalist>
|
||||
<input type="text" :name="name" :placeholder="placeholder"
|
||||
:list="listId" @keyup="onKeyUp"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
import {Autocomplete} from 'buefy/dist/components/autocomplete'
|
||||
// import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
url: String,
|
||||
model: Function,
|
||||
placeholder: String,
|
||||
field: {type: String, default: 'value'},
|
||||
name: String,
|
||||
field: String,
|
||||
valueField: {type: String, default: 'id'},
|
||||
count: {type: Number, count: 10},
|
||||
valueAttr: String,
|
||||
valueField: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
value: '',
|
||||
items: [],
|
||||
selected: null,
|
||||
isFetching: false,
|
||||
listId: `autocomplete-${ Math.random() }`.replace('.',''),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSelect(option) {
|
||||
console.log('selected', option)
|
||||
select(option, value=null) {
|
||||
if(!option && value !== null)
|
||||
option = this.items.find(item => item[this.field] == value)
|
||||
|
||||
this.selected = option
|
||||
this.$emit('select', option)
|
||||
},
|
||||
|
||||
fetch: debounce(function(query) {
|
||||
if(!query)
|
||||
onKeyUp: function(event) {
|
||||
const value = event.target.value
|
||||
if(value === this.value)
|
||||
return
|
||||
|
||||
if(value !== undefined && value !== null)
|
||||
this.value = value
|
||||
|
||||
if(!value)
|
||||
return this.select(null)
|
||||
|
||||
this.fetch(value)
|
||||
},
|
||||
|
||||
fetch: function(query) {
|
||||
if(!query || this.isFetching)
|
||||
return
|
||||
|
||||
this.isFetching = true
|
||||
this.model.fetchAll(this.url.replace('${query}', query))
|
||||
.then(data => {
|
||||
this.data = data
|
||||
this.isFetching = false
|
||||
}, data => { this.isFetching = false; Promise.reject(data) })
|
||||
}),
|
||||
},
|
||||
|
||||
components: {
|
||||
Autocomplete,
|
||||
return this.model.fetch(this.url.replace('${query}', query), {many:true})
|
||||
.then(items => { this.items = items || []
|
||||
this.isFetching = false
|
||||
this.select(null, query)
|
||||
return items },
|
||||
data => {this.isFetching = false; Promise.reject(data)})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import {Set} from './model'
|
||||
|
||||
import './styles.scss'
|
||||
|
||||
|
||||
window.aircox = {
|
||||
// main application
|
||||
builder: new Builder(App),
|
||||
@ -25,9 +26,28 @@ window.aircox = {
|
||||
get playerApp() { return this.playerBuilder && this.playerBuilder.app },
|
||||
get player() { return this.playerBuilder.vm && this.playerBuilder.vm.$refs.player },
|
||||
|
||||
Set: Set, Sound: Sound,
|
||||
Set, Sound,
|
||||
|
||||
|
||||
/**
|
||||
* Initialize main application and player.
|
||||
*/
|
||||
init(props=null, {config=null, builder=null, initPlayer=true}={}) {
|
||||
builder = builder || this.builder
|
||||
this.builder = builder
|
||||
if(config)
|
||||
builder.config = config
|
||||
builder.title = document.title
|
||||
builder.mount({props})
|
||||
|
||||
if(initPlayer) {
|
||||
let playerBuilder = this.playerBuilder
|
||||
playerBuilder.mount()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
window.addEventListener('load', e => {
|
||||
const [app, player] = [aircox.builder, aircox.playerBuilder]
|
||||
app.title = document.title
|
||||
@ -36,4 +56,5 @@ window.addEventListener('load', e => {
|
||||
|
||||
player.mount()
|
||||
})
|
||||
*/
|
||||
|
||||
|
@ -20,7 +20,7 @@ export function getCsrf() {
|
||||
// TODO: prevent duplicate simple fetch
|
||||
export default class Model {
|
||||
constructor(data, {url=null}={}) {
|
||||
this.url = url;
|
||||
this.url = url || data.url_;
|
||||
this.commit(data);
|
||||
}
|
||||
|
||||
@ -45,14 +45,24 @@ export default class Model {
|
||||
}
|
||||
}
|
||||
|
||||
static fromList(items, args=null) {
|
||||
return items ? items.map(d => new this(d, args)) : []
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch item from server
|
||||
*/
|
||||
static fetch(url, options=null, args=null) {
|
||||
static fetch(url, {many=false, ...options}={}, args={}) {
|
||||
options = this.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => new this(data, {url: url, ...args}));
|
||||
const request = fetch(url, options).then(response => response.json());
|
||||
if(many)
|
||||
return request.then(data => {
|
||||
if(!(data instanceof Array))
|
||||
data = data.results
|
||||
return this.fromList(data, args)
|
||||
})
|
||||
else
|
||||
return request.then(data => new this(data, {url: url, ...args}));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,7 +133,7 @@ export class Set {
|
||||
* Fetch multiple items from server
|
||||
*/
|
||||
static fetch(model, url, options=null, args=null) {
|
||||
options = this.getOptions(options)
|
||||
options = model.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => (data instanceof Array ? data : data.results)
|
||||
|
60
assets/streamer/app.js
Normal file
60
assets/streamer/app.js
Normal file
@ -0,0 +1,60 @@
|
||||
import AdminApp from 'admin/app';
|
||||
import Model, {Set} from 'public/model';
|
||||
import Sound from 'public/sound';
|
||||
import {setEcoInterval} from 'public/utils';
|
||||
|
||||
import {Streamer, Queue} from './controllers';
|
||||
|
||||
|
||||
export default {
|
||||
...AdminApp,
|
||||
|
||||
props: {
|
||||
...(AdminApp.props || {}),
|
||||
apiUrl: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// current streamer
|
||||
streamer: null,
|
||||
// all streamers
|
||||
streamers: [],
|
||||
// fetch interval id
|
||||
fetchInterval: null,
|
||||
Sound: Sound,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...(AdminApp.computed || {}),
|
||||
|
||||
sources() {
|
||||
var sources = this.streamer ? this.streamer.sources : [];
|
||||
return sources.filter(s => s.data)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...(AdminApp.methods || {}),
|
||||
|
||||
fetchStreamers() {
|
||||
Streamer.fetch(this.apiUrl, {many:true}).then(streamers => {
|
||||
this.streamers = streamers
|
||||
this.streamer = streamers ? streamers[0] : null
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchStreamers();
|
||||
this.fetchInterval = setEcoInterval(() => this.streamer && this.streamer.fetch(), 5000)
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
if(this.fetchInterval !== null)
|
||||
clearInterval(this.fetchInterval)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Model, {Set} from 'public/model';
|
||||
import Model from 'public/model';
|
||||
import {setEcoInterval} from 'public/utils';
|
||||
|
||||
|
||||
@ -12,8 +12,8 @@ export class Streamer extends Model {
|
||||
if(!this.data)
|
||||
this.data = { id: data.id, playlists: [], queues: [] }
|
||||
|
||||
data.playlists = Playlist.Set(data.playlists, {args: {streamer: this}});
|
||||
data.queues = Queue.Set(data.queues, {args: {streamer: this}});
|
||||
data.playlists = Playlist.fromList(data.playlists, {streamer: this});
|
||||
data.queues = Queue.fromList(data.queues, {streamer: this});
|
||||
super.commit(data)
|
||||
}
|
||||
}
|
||||
@ -83,7 +83,7 @@ export class Queue extends Source {
|
||||
get queue() { return this.data && this.data.queue; }
|
||||
|
||||
commit(data) {
|
||||
data.queue = Request.Set(data.queue);
|
||||
data.queue = Request.fromList(data.queue);
|
||||
super.commit(data)
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,18 @@
|
||||
import App from 'public/app';
|
||||
import AdminApp from 'admin/app';
|
||||
import Model, {Set} from 'public/model';
|
||||
import Sound from 'public/sound';
|
||||
import {setEcoInterval} from 'public/utils';
|
||||
|
||||
import {Streamer, Queue} from './controllers';
|
||||
|
||||
window.aircox.builder.config = {
|
||||
...App,
|
||||
|
||||
export const StreamerApp = {
|
||||
...AdminApp,
|
||||
|
||||
props: {
|
||||
...(AdminApp.props || {}),
|
||||
apiUrl: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
@ -21,12 +27,8 @@ window.aircox.builder.config = {
|
||||
},
|
||||
|
||||
computed: {
|
||||
...(App.computed || {}),
|
||||
...(AdminApp.computed || {}),
|
||||
|
||||
apiUrl() {
|
||||
return this.$el && this.$el.dataset.apiUrl;
|
||||
},
|
||||
|
||||
sources() {
|
||||
var sources = this.streamer ? this.streamer.sources : [];
|
||||
return sources.filter(s => s.data)
|
||||
@ -34,10 +36,10 @@ window.aircox.builder.config = {
|
||||
},
|
||||
|
||||
methods: {
|
||||
...(App.methods || {}),
|
||||
...(AdminApp.methods || {}),
|
||||
|
||||
fetchStreamers() {
|
||||
Set.fetch(Streamer, this.apiUrl).then(streamers => {
|
||||
Streamer.fetch(this.apiUrl, {many:true}).then(streamers => {
|
||||
this.streamers = streamers
|
||||
this.streamer = streamers ? streamers[0] : null
|
||||
})
|
||||
@ -55,3 +57,5 @@ window.aircox.builder.config = {
|
||||
}
|
||||
}
|
||||
|
||||
window.StreamerApp = StreamerApp
|
||||
|
||||
|
Reference in New Issue
Block a user