playlist editor draft
This commit is contained in:
		@ -11,6 +11,7 @@ $menu-item-active-background-color: #d2d2d2;
 | 
			
		||||
 | 
			
		||||
//-- helpers/modifiers
 | 
			
		||||
.is-fullwidth { width: 100%; }
 | 
			
		||||
.is-fullheight { height: 100%; }
 | 
			
		||||
.is-fixed-bottom {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
@ -40,6 +41,19 @@ $menu-item-active-background-color: #d2d2d2;
 | 
			
		||||
.overflow-hidden.is-fullwidth { max-width: 100%; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
*[draggable="true"] {
 | 
			
		||||
    cursor: move;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//-- forms
 | 
			
		||||
input.half-field:not(:active):not(:hover) {
 | 
			
		||||
    border: none;
 | 
			
		||||
    background-color: rgba(0,0,0,0);
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-- animations
 | 
			
		||||
@keyframes blink {
 | 
			
		||||
    from { opacity: 1; }
 | 
			
		||||
    to { opacity: 0.4; }
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,22 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <!-- FIXME: header and footer should be inside list tags -->
 | 
			
		||||
        <slot name="header"></slot>
 | 
			
		||||
        <ul :class="listClass">
 | 
			
		||||
        <component :is="listTag" :class="listClass">
 | 
			
		||||
            <template v-for="(item,index) in items" :key="index">
 | 
			
		||||
                <li :class="itemClass" @click="select(index)">
 | 
			
		||||
                <component :is="itemTag" :class="itemClass" @click="select(index)"
 | 
			
		||||
                        :draggable="orderable"
 | 
			
		||||
                        @dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop">
 | 
			
		||||
                    <slot name="item" :selected="index == selectedIndex" :set="set" :index="index" :item="item"></slot>
 | 
			
		||||
                </li>
 | 
			
		||||
                </component>
 | 
			
		||||
            </template>
 | 
			
		||||
        </ul>
 | 
			
		||||
        </component>
 | 
			
		||||
        <slot name="footer"></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    emits: ['select', 'unselect'],
 | 
			
		||||
    emits: ['select', 'unselect', 'move'],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            selectedIndex: this.defaultIndex,
 | 
			
		||||
@ -25,6 +28,9 @@ export default {
 | 
			
		||||
        itemClass: String,
 | 
			
		||||
        defaultIndex: { type: Number, default: -1},
 | 
			
		||||
        set: Object,
 | 
			
		||||
        orderable: { type: Boolean, default: false },
 | 
			
		||||
        itemTag: { default: 'li' },
 | 
			
		||||
        listTag: { default: 'ul' },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
@ -61,6 +67,34 @@ export default {
 | 
			
		||||
            this.$emit('unselect', { item: this.selected, index: this.selectedIndex});
 | 
			
		||||
            this.selectedIndex = -1;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        onDragStart(ev) {
 | 
			
		||||
            const dataset = ev.target.dataset;
 | 
			
		||||
            const data = `cell:${dataset.index}`
 | 
			
		||||
            ev.dataTransfer.setData("text/cell", data)
 | 
			
		||||
            ev.dataTransfer.dropEffect = 'move'
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        onDragOver(ev) {
 | 
			
		||||
            ev.preventDefault()
 | 
			
		||||
            ev.dataTransfer.dropEffect = 'move'
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        onDrop(ev) {
 | 
			
		||||
            const data = ev.dataTransfer.getData("text/cell")
 | 
			
		||||
            if(!data || !data.startsWith('cell:'))
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            ev.preventDefault()
 | 
			
		||||
            const from = Number(data.slice(5))
 | 
			
		||||
            const target = ev.target.tagName == this.itemTag ? ev.target
 | 
			
		||||
                                : ev.target.closest(this.itemTag)
 | 
			
		||||
            this.$emit('move', {
 | 
			
		||||
                from, target,
 | 
			
		||||
                to: Number(target.dataset.index),
 | 
			
		||||
                set: this.set,
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										216
									
								
								assets/src/components/APlaylistEditor.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								assets/src/components/APlaylistEditor.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,216 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="playlist-editor">
 | 
			
		||||
        <slot name="top" :set="set" :columns="columns" :items="items"/>
 | 
			
		||||
        <div class="tabs">
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li :class="{'is-active': mode == Modes.Text}"
 | 
			
		||||
                        @click="mode = Modes.Text">
 | 
			
		||||
                    <a>
 | 
			
		||||
                        <span class="icon is-small">
 | 
			
		||||
                            <i class="fa fa-pencil"></i>
 | 
			
		||||
                        </span>
 | 
			
		||||
                        Texte
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li :class="{'is-active': mode == Modes.List}"
 | 
			
		||||
                        @click="mode = Modes.List">
 | 
			
		||||
                    <a>
 | 
			
		||||
                        <span class="icon is-small">
 | 
			
		||||
                            <i class="fa fa-list"></i>
 | 
			
		||||
                        </span>
 | 
			
		||||
                        Liste
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
        <section class="page" v-show="mode == Modes.Text">
 | 
			
		||||
            <textarea ref="textarea" class="is-fullwidth" style="height: 10em;"
 | 
			
		||||
                @change="updateList"
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <div class="columns mt-2">
 | 
			
		||||
                <div class="column field is-vcentered">
 | 
			
		||||
                    <label class="label is-inline mr-2"
 | 
			
		||||
                            style="vertical-align: middle">
 | 
			
		||||
                        Ordre</label>
 | 
			
		||||
                    <table class="table is-bordered is-inline-block"
 | 
			
		||||
                            style="vertical-align: middle">
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <a-row :columns="columns" :item="FormatLabels"
 | 
			
		||||
                                @move="formatMove" :orderable="true">
 | 
			
		||||
                            </a-row>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </table>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="column field is-vcentered">
 | 
			
		||||
                    <label class="label is-inline mr-2"
 | 
			
		||||
                            style="vertical-align: middle">
 | 
			
		||||
                        Séparateur</label>
 | 
			
		||||
                    <div class="control is-inline-block"
 | 
			
		||||
                            style="vertical-align: middle">
 | 
			
		||||
                        <input type="text" ref="sep" value="--" class="input is-inline"
 | 
			
		||||
                            @change="updateList()"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="column"/>
 | 
			
		||||
            </div>
 | 
			
		||||
        </section>
 | 
			
		||||
        <section class="page" v-show="mode == Modes.List">
 | 
			
		||||
            <a-rows :set="set" :columns="columns" :labels="FormatLabels"
 | 
			
		||||
                    :allow-create="true"
 | 
			
		||||
                    :list-class="listClass" :item-class="itemClass"
 | 
			
		||||
                    :orderable="true" @move="listItemMove"
 | 
			
		||||
                    @cell="onCellEvent">
 | 
			
		||||
                <template v-for="[name,slot] of rowsSlots" :key="slot"
 | 
			
		||||
                        v-slot:[slot]="data">
 | 
			
		||||
                    <slot :name="name" v-bind="data"/>
 | 
			
		||||
                </template>
 | 
			
		||||
            </a-rows>
 | 
			
		||||
        </section>
 | 
			
		||||
        <section class="page" v-show="mode == Modes.Settings">
 | 
			
		||||
 | 
			
		||||
        </section>
 | 
			
		||||
        <slot name="bottom" :set="set" :columns="columns" :items="items"/>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import {dropRightWhile} from 'lodash'
 | 
			
		||||
import {Set} from '../model'
 | 
			
		||||
import Track from '../track'
 | 
			
		||||
 | 
			
		||||
import ARow from './ARow.vue'
 | 
			
		||||
import ARows from './ARows.vue'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const Modes = {
 | 
			
		||||
    Text: 0, List: 1, Settings: 2,
 | 
			
		||||
}
 | 
			
		||||
const FormatLabels = {
 | 
			
		||||
    artist: 'Artiste', album: 'Album', year: 'Année', tags: 'Tags',
 | 
			
		||||
    title: 'Titre',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: { ARow, ARows },
 | 
			
		||||
    props: {
 | 
			
		||||
        listClass: String,
 | 
			
		||||
        itemClass: String,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            dataEl: String,
 | 
			
		||||
            Modes: Modes,
 | 
			
		||||
            FormatLabels: FormatLabels,
 | 
			
		||||
            mode: Modes.Text,
 | 
			
		||||
            set: new Set(Track),
 | 
			
		||||
            columns: ['artist', 'title', 'tags', 'album', 'year'],
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        items() {
 | 
			
		||||
            return this.set.items
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        rowsSlots() {
 | 
			
		||||
            return Object.keys(this.$slots)
 | 
			
		||||
                .filter(x => x.startsWith('row-') || x.startsWith('rows-'))
 | 
			
		||||
                .map(x => [x, x.startsWith('rows-') ? x.slice(5) : x])
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        onCellEvent(event) {
 | 
			
		||||
            switch(event.name) {
 | 
			
		||||
                case 'change': this.updateInput();
 | 
			
		||||
                               break;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        formatMove({from, to}) {
 | 
			
		||||
            const value = this.columns[from]
 | 
			
		||||
            this.columns.splice(from, 1)
 | 
			
		||||
            this.columns.splice(to, 0, value)
 | 
			
		||||
            this.updateList()
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        listItemMove({from, to, set}) {
 | 
			
		||||
            set.move(from, to);
 | 
			
		||||
            this.updateInput()
 | 
			
		||||
        },
 | 
			
		||||
    
 | 
			
		||||
        updateList() {
 | 
			
		||||
            const items = this.toList(this.$refs.textarea.value,
 | 
			
		||||
                                      this.$refs.sep.value)
 | 
			
		||||
            this.set.reset(items)
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        updateInput() {
 | 
			
		||||
            const input = this.toText(this.items, this.$refs.sep.value)
 | 
			
		||||
            this.$refs.textarea.value = input
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * From input and separator, return list of items.
 | 
			
		||||
         */
 | 
			
		||||
        toList(input, sep) {
 | 
			
		||||
            var lines = input.split('\n')
 | 
			
		||||
            var items = []
 | 
			
		||||
 | 
			
		||||
            for(let line of lines) {
 | 
			
		||||
                line = line.trim()
 | 
			
		||||
                if(!line)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                var lineBits = line.split(sep)
 | 
			
		||||
                var item = {}
 | 
			
		||||
                for(var col in this.columns) {
 | 
			
		||||
                    if(col >= lineBits.length)
 | 
			
		||||
                        break
 | 
			
		||||
                    const attr = this.columns[col]
 | 
			
		||||
                    item[attr] = lineBits[col].trim()
 | 
			
		||||
                }
 | 
			
		||||
                item && items.push(item)
 | 
			
		||||
            }
 | 
			
		||||
            return items
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * From items and separator return a string
 | 
			
		||||
         */
 | 
			
		||||
        toText(items, sep) {
 | 
			
		||||
            var lines = []
 | 
			
		||||
            sep = ` ${(sep || this.$refs.sep.value).trim()} `
 | 
			
		||||
            for(let item of items) {
 | 
			
		||||
                if(!item)
 | 
			
		||||
                    continue
 | 
			
		||||
                var line = []
 | 
			
		||||
                for(var col of this.columns)
 | 
			
		||||
                    line.push(item.data[col] || '')
 | 
			
		||||
                line = dropRightWhile(line, x => !x)
 | 
			
		||||
                lines.push(line.join(sep))
 | 
			
		||||
            }
 | 
			
		||||
            return lines.join('\n')
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Load initial data
 | 
			
		||||
         */
 | 
			
		||||
        loadData({items=[], errors, fieldErrors, ...data}) {
 | 
			
		||||
            for(var item of items)
 | 
			
		||||
                this.set.push(item)
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mounted() {
 | 
			
		||||
        if(this.dataEl) {
 | 
			
		||||
            const el = document.getElementById(this.dataEl)
 | 
			
		||||
            if(el) {
 | 
			
		||||
                const data = JSON.parse(el.textContext)
 | 
			
		||||
                loadData(data)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										78
									
								
								assets/src/components/ARow.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								assets/src/components/ARow.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <slot name="head" :item="item" :row="index"/>
 | 
			
		||||
        <template v-for="(attr,col) in columns" :key="col">
 | 
			
		||||
            <td :class="['cell', 'cell-' + attr]" :data-index="col"
 | 
			
		||||
                    :draggable="orderable"
 | 
			
		||||
                    @dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop">
 | 
			
		||||
                <slot :name="attr" :item="item" :row="index" :col="col"
 | 
			
		||||
                        :data="itemData" :attr="attr" :emit="cellEmit"
 | 
			
		||||
                        :value="itemData && itemData[attr]">
 | 
			
		||||
                    {{ itemData && itemData[attr] }}
 | 
			
		||||
                </slot>
 | 
			
		||||
            </td>
 | 
			
		||||
        </template>
 | 
			
		||||
        <slot name="tail" :item="item" :row="index"/>
 | 
			
		||||
    </tr>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import Model from '../model'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    emit: ['move', 'cell'],
 | 
			
		||||
 | 
			
		||||
    props: {
 | 
			
		||||
        item: Object,
 | 
			
		||||
        index: Number,
 | 
			
		||||
        columns: Array,
 | 
			
		||||
        orderable: {type: Boolean, default: false},
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        itemData() {
 | 
			
		||||
            return this.item instanceof Model ? this.item.data : this.item;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        /// Emit a 'cell' event.
 | 
			
		||||
        /// Event data: `{index, name, data, item, attr}`
 | 
			
		||||
        ///
 | 
			
		||||
        /// @param {Number} col: cell column's index
 | 
			
		||||
        /// @param {String} name: cell's event name
 | 
			
		||||
        /// @param {} data: cell's event data
 | 
			
		||||
        cellEmit(name, col, data) {
 | 
			
		||||
            this.$emit('cell', {
 | 
			
		||||
                name, col, data,
 | 
			
		||||
                item: this.item,
 | 
			
		||||
                attr: this.columns[col],
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        onDragStart(ev) {
 | 
			
		||||
            const dataset = ev.target.dataset;
 | 
			
		||||
            const data = `cell:${dataset.index}`
 | 
			
		||||
            ev.dataTransfer.setData("text/cell", data)
 | 
			
		||||
            ev.dataTransfer.dropEffect = 'move'
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        onDragOver(ev) {
 | 
			
		||||
            ev.preventDefault()
 | 
			
		||||
            ev.dataTransfer.dropEffect = 'move'
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        onDrop(ev) {
 | 
			
		||||
            const data = ev.dataTransfer.getData("text/cell")
 | 
			
		||||
            if(!data || !data.startsWith('cell:'))
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            ev.preventDefault()
 | 
			
		||||
            this.$emit('move', {
 | 
			
		||||
                from: Number(data.slice(5)),
 | 
			
		||||
                to: Number(ev.target.dataset.index),
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										91
									
								
								assets/src/components/ARows.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								assets/src/components/ARows.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <table class="table is-stripped is-fullwidth">
 | 
			
		||||
        <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <slot name="header-head"/>
 | 
			
		||||
                <th v-for="col in columns" :key="col"
 | 
			
		||||
                    style="vertical-align: middle">{{ labels[col] }}</th>
 | 
			
		||||
                <slot name="header-tail"/>
 | 
			
		||||
            </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
            <slot name="head"/>
 | 
			
		||||
            <template v-for="(item,index) in items" :key="index">
 | 
			
		||||
                <a-row :item="item" :index="index" :columns="columns" :data-index="index"
 | 
			
		||||
                        :draggable="orderable"
 | 
			
		||||
                        @dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop"
 | 
			
		||||
                        @cell="onCellEvent(index, $event)">
 | 
			
		||||
                    <template v-for="[name,slot] of rowSlots" :key="slot" v-slot:[slot]="data">
 | 
			
		||||
                        <slot :name="name" v-bind="data"/>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </a-row>
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-if="allowCreate">
 | 
			
		||||
                <a-row :item="extraItem" :index="items.length" :columns="columns"
 | 
			
		||||
                        @keypress.enter.stop.prevent="validateExtraCell">
 | 
			
		||||
                    <template v-for="[name,slot] of rowSlots" :key="slot" v-slot:[slot]="data">
 | 
			
		||||
                        <slot :name="name" v-bind="data"/>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </a-row>
 | 
			
		||||
            </template>
 | 
			
		||||
            <slot name="tail"/>
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import AList from './AList.vue'
 | 
			
		||||
import ARow from './ARow.vue'
 | 
			
		||||
 | 
			
		||||
const Component = {
 | 
			
		||||
    extends: AList,
 | 
			
		||||
    components: { ARow },
 | 
			
		||||
    emit: ['cell'],
 | 
			
		||||
 | 
			
		||||
    props: {
 | 
			
		||||
        ...AList.props,
 | 
			
		||||
        columns: Array,
 | 
			
		||||
        labels: Object,
 | 
			
		||||
        allowCreate: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            ...super.data,
 | 
			
		||||
            extraItem: new this.set.model(),
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        rowSlots() {
 | 
			
		||||
            return Object.keys(this.$slots).filter(x => x.startsWith('row-'))
 | 
			
		||||
                                           .map(x => [x, x.slice(4)])
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        validateExtraCell() {
 | 
			
		||||
            if(!this.allowCreate)
 | 
			
		||||
                return
 | 
			
		||||
            this.set.push(this.extraItem)
 | 
			
		||||
            this.extraItem = new this.set.model()
 | 
			
		||||
        },
 | 
			
		||||
    
 | 
			
		||||
        /// React on 'cell' event, re-emitting it with additional values:
 | 
			
		||||
        /// - `set`: data set
 | 
			
		||||
        /// - `row`: row index
 | 
			
		||||
        ///
 | 
			
		||||
        /// @param {Number} row: row index
 | 
			
		||||
        /// @param {} data: cell's event data
 | 
			
		||||
        onCellEvent(row, event) {
 | 
			
		||||
            this.$emit('cell', {
 | 
			
		||||
                ...event, row,
 | 
			
		||||
                set: this.set
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
Component.props.itemTag.default = 'tr'
 | 
			
		||||
Component.props.listTag.default = 'tbody'
 | 
			
		||||
 | 
			
		||||
export default Component
 | 
			
		||||
</script>
 | 
			
		||||
@ -4,6 +4,7 @@ import AList from './AList.vue'
 | 
			
		||||
import APage from './APage.vue'
 | 
			
		||||
import APlayer from './APlayer.vue'
 | 
			
		||||
import APlaylist from './APlaylist.vue'
 | 
			
		||||
import APlaylistEditor from './APlaylistEditor.vue'
 | 
			
		||||
import AProgress from './AProgress.vue'
 | 
			
		||||
import ASoundItem from './ASoundItem.vue'
 | 
			
		||||
import AStatistics from './AStatistics.vue'
 | 
			
		||||
@ -18,6 +19,6 @@ export default {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const admin = {
 | 
			
		||||
    AStatistics, AStreamer,
 | 
			
		||||
    AStatistics, AStreamer, APlaylistEditor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ window.aircox = {
 | 
			
		||||
     * Initialize main application and player.
 | 
			
		||||
     */
 | 
			
		||||
    init(props=null, {config=null, builder=null, initBuilder=true,
 | 
			
		||||
                      initPlayer=true, hotReload=false}={})
 | 
			
		||||
                      initPlayer=true, hotReload=false, el=null}={})
 | 
			
		||||
    {
 | 
			
		||||
        if(initPlayer) {
 | 
			
		||||
            let playerBuilder = this.playerBuilder
 | 
			
		||||
@ -44,6 +44,9 @@ window.aircox = {
 | 
			
		||||
            this.builder = builder
 | 
			
		||||
            if(config || window.App)
 | 
			
		||||
                builder.config = config || window.App
 | 
			
		||||
            if(el)
 | 
			
		||||
                builder.config.el = el
 | 
			
		||||
            
 | 
			
		||||
            builder.title = document.title
 | 
			
		||||
            builder.mount({props})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ export default class Model {
 | 
			
		||||
     * Instanciate model with provided data and options.
 | 
			
		||||
     * By default `url` is taken from `data.url_`.
 | 
			
		||||
     */
 | 
			
		||||
    constructor(data, {url=null, ...options}={}) {
 | 
			
		||||
    constructor(data={}, {url=null, ...options}={}) {
 | 
			
		||||
        this.url = url || data.url_;
 | 
			
		||||
        this.options = options;
 | 
			
		||||
        this.commit(data);
 | 
			
		||||
@ -133,6 +133,8 @@ export default class Model {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * List of models
 | 
			
		||||
 */
 | 
			
		||||
@ -231,6 +233,25 @@ export class Set {
 | 
			
		||||
        this.items.splice(index,1);
 | 
			
		||||
        save && this.save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clear items, assign new ones
 | 
			
		||||
     */
 | 
			
		||||
    reset(items=[]) {
 | 
			
		||||
        // TODO: check reactivity
 | 
			
		||||
        this.items = []
 | 
			
		||||
        for(var item of items)
 | 
			
		||||
            this.push(item)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    move(from, to) {
 | 
			
		||||
        if(from >= this.length || to > this.length)
 | 
			
		||||
            throw "source or target index is not in range"
 | 
			
		||||
 | 
			
		||||
        const value = this.items[from]
 | 
			
		||||
        this.items.splice(from, 1)
 | 
			
		||||
        this.items.splice(to, 0, value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Set[Symbol.iterator] = function () {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								assets/src/track.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								assets/src/track.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
import Model from './model'
 | 
			
		||||
 | 
			
		||||
export default class Track extends Model {
 | 
			
		||||
    static getId(data) { return data.pk }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user