streamer as separate application; working streamer monitor interface
This commit is contained in:
		@ -1,17 +1,53 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export var app = null;
 | 
			
		||||
export default app;
 | 
			
		||||
export const appBaseConfig = {
 | 
			
		||||
    el: '#app',
 | 
			
		||||
    delimiters: ['[[', ']]'],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loadApp() {
 | 
			
		||||
    app = new Vue({
 | 
			
		||||
      el: '#app',
 | 
			
		||||
      delimiters: [ '[[', ']]' ],
 | 
			
		||||
/**
 | 
			
		||||
 * Application config for the main application instance
 | 
			
		||||
 */
 | 
			
		||||
var appConfig = {};
 | 
			
		||||
 | 
			
		||||
export function setAppConfig(config) {
 | 
			
		||||
    for(var member in appConfig) delete appConfig[member];
 | 
			
		||||
    return Object.assign(appConfig, config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getAppConfig(config) {
 | 
			
		||||
    if(config instanceof Function)
 | 
			
		||||
        config = config()
 | 
			
		||||
    config = config == null ? appConfig : config;
 | 
			
		||||
    return {...appBaseConfig, ...config}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                resolve(new Vue(config))
 | 
			
		||||
            }
 | 
			
		||||
            catch(error) { reject(error) }
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.addEventListener('load', loadApp);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										63
									
								
								assets/public/autocomplete.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								assets/public/autocomplete.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
<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]" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import debounce from 'lodash/debounce'
 | 
			
		||||
import {Autocomplete} from 'buefy/dist/components/autocomplete';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        url: String,
 | 
			
		||||
        model: Function,
 | 
			
		||||
        placeholder: String,
 | 
			
		||||
        field: {type: String, default: 'value'},
 | 
			
		||||
        count: {type: Number, count: 10},
 | 
			
		||||
        valueAttr: String,
 | 
			
		||||
        valueField: String,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            data: [],
 | 
			
		||||
            selected: null,
 | 
			
		||||
            isFetching: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        onSelect(option) {
 | 
			
		||||
            console.log('selected', option)
 | 
			
		||||
            Vue.set(this, 'selected', option);
 | 
			
		||||
            this.$emit('select', option);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        fetch: debounce(function(query) {
 | 
			
		||||
            if(!query)
 | 
			
		||||
                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,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -10,18 +10,37 @@ import '@fortawesome/fontawesome-free/css/fontawesome.min.css';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- aircox
 | 
			
		||||
import app from './app';
 | 
			
		||||
import LiveInfo from './liveInfo';
 | 
			
		||||
import {appConfig, loadApp} from './app';
 | 
			
		||||
 | 
			
		||||
import './styles.scss';
 | 
			
		||||
 | 
			
		||||
import Player from './player.vue';
 | 
			
		||||
import Autocomplete from './autocomplete.vue';
 | 
			
		||||
 | 
			
		||||
Vue.component('a-player', Player)
 | 
			
		||||
Vue.component('a-autocomplete', Autocomplete)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
window.aircox = {
 | 
			
		||||
    app: app,
 | 
			
		||||
    LiveInfo: LiveInfo,
 | 
			
		||||
}
 | 
			
		||||
    // main application
 | 
			
		||||
    app: null,
 | 
			
		||||
 | 
			
		||||
    // main application config
 | 
			
		||||
    appConfig: {},
 | 
			
		||||
 | 
			
		||||
    // player application
 | 
			
		||||
    playerApp: null,
 | 
			
		||||
 | 
			
		||||
    // player component
 | 
			
		||||
    get player() {
 | 
			
		||||
        return this.playerApp && this.playerApp.$refs.player
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
loadApp({el: '#player'}).then(app => { window.aircox.playerApp = app },
 | 
			
		||||
                              () => undefined)
 | 
			
		||||
loadApp(() => window.aircox.appConfig ).then(app => { window.aircox.app = app },
 | 
			
		||||
                                             () => undefined)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										118
									
								
								assets/public/model.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								assets/public/model.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,118 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
function getCookie(name) {
 | 
			
		||||
    if(document.cookie && document.cookie !== '') {
 | 
			
		||||
        const cookie = document.cookie.split(';')
 | 
			
		||||
                               .find(c => c.trim().startsWith(name + '='))
 | 
			
		||||
        return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var csrfToken = null;
 | 
			
		||||
 | 
			
		||||
export function getCsrf() {
 | 
			
		||||
    if(csrfToken === null)
 | 
			
		||||
        csrfToken = getCookie('csrftoken')
 | 
			
		||||
    return csrfToken;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// TODO: move in another module for reuse
 | 
			
		||||
export default class Model {
 | 
			
		||||
    constructor(data, {url=null}={}) {
 | 
			
		||||
        this.commit(data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static getId(data) {
 | 
			
		||||
        return data.id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static getOptions(options) {
 | 
			
		||||
        return {
 | 
			
		||||
            headers: {
 | 
			
		||||
                'Content-Type': 'application/json',
 | 
			
		||||
                'Accept': 'application/json',
 | 
			
		||||
                'X-CSRFToken': getCsrf(),
 | 
			
		||||
            },
 | 
			
		||||
            ...options,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static fetch(url, options=null, initArgs=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
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch data from server.
 | 
			
		||||
     */
 | 
			
		||||
    fetch(options) {
 | 
			
		||||
        options = this.constructor.getOptions(options)
 | 
			
		||||
        return fetch(this.url, options)
 | 
			
		||||
            .then(response => response.json())
 | 
			
		||||
            .then(data => this.commit(data));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Call API action on object.
 | 
			
		||||
     */
 | 
			
		||||
    action(path, options, commit=false) {
 | 
			
		||||
        options = this.constructor.getOptions(options)
 | 
			
		||||
        const promise = fetch(this.url + path, options);
 | 
			
		||||
        return commit ? promise.then(data => data.json())
 | 
			
		||||
                               .then(data => { this.commit(data); this.data })
 | 
			
		||||
                      : promise;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update instance's data with provided data. Return None
 | 
			
		||||
     */
 | 
			
		||||
    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`.
 | 
			
		||||
     */
 | 
			
		||||
    asChild(model, data, prefix='') {
 | 
			
		||||
        return new model(data, {baseUrl: `${this.url}${prefix}/`})
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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=[]) {
 | 
			
		||||
         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))
 | 
			
		||||
            return items;
 | 
			
		||||
        }, [])
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								assets/public/sound.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								assets/public/sound.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
import Model from './model';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default class Sound extends Model {
 | 
			
		||||
    get name() { return this.data.name }
 | 
			
		||||
 | 
			
		||||
    static getId(data) { return data.pk }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,10 @@
 | 
			
		||||
@charset "utf-8";
 | 
			
		||||
@import "~bulma/sass/utilities/_all.sass";
 | 
			
		||||
@import "~bulma/sass/components/dropdown.sass";
 | 
			
		||||
 | 
			
		||||
$body-background-color: $light;
 | 
			
		||||
 | 
			
		||||
@import "~buefy/src/scss/components/_autocomplete.scss";
 | 
			
		||||
@import "~bulma";
 | 
			
		||||
 | 
			
		||||
//-- helpers/modifiers
 | 
			
		||||
@ -15,6 +17,10 @@ $body-background-color: $light;
 | 
			
		||||
}
 | 
			
		||||
.is-borderless { border: none; }
 | 
			
		||||
 | 
			
		||||
.has-text-nowrap {
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.has-background-transparent {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
@ -56,6 +62,22 @@ a.navbar-item.is-active {
 | 
			
		||||
        margin: 0em;
 | 
			
		||||
        padding: 0em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.toolbar {
 | 
			
		||||
        margin: 1em 0em;
 | 
			
		||||
        background-color: transparent;
 | 
			
		||||
        margin-bottom: 1em;
 | 
			
		||||
 | 
			
		||||
        .title {
 | 
			
		||||
            padding-right: 2em;
 | 
			
		||||
            margin-right: 1em;
 | 
			
		||||
            border-right: 1px $grey-light solid;
 | 
			
		||||
 | 
			
		||||
            font-size: $size-5;
 | 
			
		||||
            color: $text-light;
 | 
			
		||||
            font-weight: $weight-light;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//-- cards
 | 
			
		||||
@ -91,24 +113,6 @@ a.navbar-item.is-active {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- filters
 | 
			
		||||
.filters {
 | 
			
		||||
    margin: 1em 0em;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    margin-bottom: 1em;
 | 
			
		||||
 | 
			
		||||
    .title {
 | 
			
		||||
        padding-right: 2em;
 | 
			
		||||
        margin-right: 1em;
 | 
			
		||||
        border-right: 1px $grey-light solid;
 | 
			
		||||
 | 
			
		||||
        font-size: $size-5;
 | 
			
		||||
        color: $text-light;
 | 
			
		||||
        font-weight: $weight-light;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- page
 | 
			
		||||
.page {
 | 
			
		||||
    & > .cover {
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user