
- serializer: track search, reorder module files - autocomplete: allow simple string value selection - playlist editor: - ui & flow improve - init data - save user settings - autocomplete - fix bugs - discard changes
101 lines
3.2 KiB
Vue
101 lines
3.2 KiB
Vue
<template>
|
|
<div>
|
|
<!-- FIXME: header and footer should be inside list tags -->
|
|
<slot name="header"></slot>
|
|
<component :is="listTag" :class="listClass">
|
|
<template v-for="(item,index) in items" :key="index">
|
|
<component :is="itemTag" :class="itemClass" @click="select(index)"
|
|
:draggable="orderable" :data-index="index"
|
|
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop">
|
|
<slot name="item" :selected="index == selectedIndex" :set="set" :index="index" :item="item"></slot>
|
|
</component>
|
|
</template>
|
|
</component>
|
|
<slot name="footer"></slot>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
export default {
|
|
emits: ['select', 'unselect', 'move'],
|
|
data() {
|
|
return {
|
|
selectedIndex: this.defaultIndex,
|
|
}
|
|
},
|
|
|
|
props: {
|
|
listClass: String,
|
|
itemClass: String,
|
|
defaultIndex: { type: Number, default: -1},
|
|
set: Object,
|
|
orderable: { type: Boolean, default: false },
|
|
itemTag: { default: 'li' },
|
|
listTag: { default: 'ul' },
|
|
},
|
|
|
|
computed: {
|
|
model() { return this.set.model },
|
|
items() { return this.set.items },
|
|
length() { return this.set.length },
|
|
|
|
selected() {
|
|
return this.selectedIndex > -1 && this.items.length > this.selectedIndex > -1
|
|
? this.items[this.selectedIndex] : null;
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
get(index) { return this.set.get(index) },
|
|
find(pred) { return this.set.find(pred) },
|
|
findIndex(pred) { return this.set.findIndex(pred) },
|
|
|
|
remove(index, select=false) {
|
|
this.set.remove(index);
|
|
if(index < this.selectedIndex)
|
|
this.selectedIndex--;
|
|
if(select && this.selectedIndex == index)
|
|
this.select(index)
|
|
},
|
|
|
|
select(index) {
|
|
this.selectedIndex = index > -1 && this.items.length ? index % this.items.length : -1;
|
|
this.$emit('select', { item: this.selected, index: this.selectedIndex });
|
|
return this.selectedIndex;
|
|
},
|
|
|
|
unselect() {
|
|
this.$emit('unselect', { item: this.selected, index: this.selectedIndex});
|
|
this.selectedIndex = -1;
|
|
},
|
|
|
|
onDragStart(ev) {
|
|
const dataset = ev.target.dataset;
|
|
const data = `row:${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('row:'))
|
|
return
|
|
|
|
ev.preventDefault()
|
|
const from = Number(data.slice(4))
|
|
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>
|