forked from rc/aircox
289 lines
10 KiB
Vue
289 lines
10 KiB
Vue
<template>
|
|
<div class="a-tracklist-editor">
|
|
<div class="flex-row">
|
|
<div class="flex-grow-1">
|
|
<slot name="title" />
|
|
</div>
|
|
<div class="flex-row align-right">
|
|
<div class="field has-addons">
|
|
<p class="control">
|
|
<button type="button" :class="['button','p-2', page == Page.Text ? 'is-primary' : 'is-light']"
|
|
@click="page = Page.Text">
|
|
<span class="icon is-small">
|
|
<i class="fa fa-pencil"></i>
|
|
</span>
|
|
<span>{{ labels.text }}</span>
|
|
</button>
|
|
</p>
|
|
<p class="control">
|
|
<button type="button" :class="['button','p-2', page == Page.List ? 'is-primary' : 'is-light']"
|
|
@click="page = Page.List">
|
|
<span class="icon is-small">
|
|
<i class="fa fa-list"></i>
|
|
</span>
|
|
<span>{{ labels.list }}</span>
|
|
</button>
|
|
</p>
|
|
<p class="control ml-3">
|
|
<button type="button" class="button is-info square"
|
|
:title="labels.settings"
|
|
@click="$refs.settings.open()">
|
|
<span class="icon is-small">
|
|
<i class="fa fa-cog"></i>
|
|
</span>
|
|
</button>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<section v-show="page == Page.Text" class="panel">
|
|
<textarea ref="textarea" class="is-fullwidth is-size-6" rows="20"
|
|
@change="updateList"
|
|
/>
|
|
|
|
</section>
|
|
<section v-show="page == Page.List" class="panel">
|
|
<a-form-set ref="formset"
|
|
:form-data="formData" :initials="initData.items"
|
|
:columnsOrderable="true" :labels="labels"
|
|
order-by="position"
|
|
@load="updateInput" @colmove="onColumnMove" @move="updateInput"
|
|
@cell="onCellEvent">
|
|
<template v-for="[name,slot] of rowsSlots" :key="slot"
|
|
v-slot:[slot]="data">
|
|
<slot v-if="name != 'row-tail'" :name="name" v-bind="data"/>
|
|
</template>
|
|
</a-form-set>
|
|
</section>
|
|
|
|
<a-modal ref="settings" :title="labels.settings">
|
|
<template #default>
|
|
<div class="field">
|
|
<label class="label" style="vertical-align: middle">
|
|
{{ labels.columns }}
|
|
</label>
|
|
<table class="table is-bordered"
|
|
style="vertical-align: middle">
|
|
<tr v-if="$refs.formset">
|
|
<a-row :columns="$refs.formset.rows.columnNames"
|
|
:item="$refs.formset.rows.columnLabels"
|
|
@move="$refs.formset.rows.moveColumn"
|
|
>
|
|
<template v-slot:cell-after="{cell}">
|
|
<td style="cursor:pointer;" v-if="cell.col < $refs.formset.rows.columns_.length-1">
|
|
<span class="icon" @click="$refs.formset.rows.moveColumn({from: cell.col, to: cell.col+1})"
|
|
><i class="fa fa-left-right"/>
|
|
</span>
|
|
</td>
|
|
</template>
|
|
</a-row>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="flex-row">
|
|
<div class="field is-inline-block is-vcentered flex-grow-1">
|
|
<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" class="input is-inline is-text-centered is-small"
|
|
style="max-width: 5em;"
|
|
v-model="separator" @change="updateList()"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<div class="flex-row align-right">
|
|
<a-action-button icon="fa fa-floppy-disk"
|
|
v-if="settingsChanged"
|
|
class="button control p-2 mr-3 is-secondary" run-class="blink"
|
|
:url="settingsUrl" method="POST"
|
|
:data="settings"
|
|
:aria-label="labels.save_settings"
|
|
@done="settingsSaved()">
|
|
{{ labels.save_settings }}
|
|
</a-action-button>
|
|
<button class="button" type="button" @click="$refs.settings.close()">
|
|
Fermer
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</a-modal>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import {dropRightWhile, cloneDeep, isEqual} from 'lodash'
|
|
|
|
import AActionButton from './AActionButton'
|
|
import AFormSet from './AFormSet'
|
|
import ARow from './ARow'
|
|
import AModal from "./AModal"
|
|
|
|
/// Page display
|
|
export const Page = {
|
|
Text: 0, List: 1, Settings: 2,
|
|
}
|
|
|
|
export default {
|
|
components: { AActionButton, AFormSet, ARow, AModal },
|
|
props: {
|
|
formData: Object,
|
|
labels: Object,
|
|
|
|
///! initial data as: {items: [], fields: {column_name: label, settings: {}}
|
|
initData: Object,
|
|
dataPrefix: String,
|
|
settingsUrl: String,
|
|
defaultColumns: {
|
|
type: Array,
|
|
default: () => ['artist', 'title', 'tags', 'album', 'year', 'timestamp']},
|
|
},
|
|
|
|
data() {
|
|
const settings = {
|
|
// tracklist_editor_columns: this.columns,
|
|
tracklist_editor_sep: ' -- ',
|
|
}
|
|
return {
|
|
Page: Page,
|
|
page: Page.Text,
|
|
extraData: {},
|
|
settings,
|
|
savedSettings: cloneDeep(settings),
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
rows() { return this.$refs.formset && this.$refs.formset.rows },
|
|
columns() { return this.rows && this.rows.columns_ || [] },
|
|
|
|
settingsChanged() {
|
|
var k = Object.keys(this.savedSettings)
|
|
.findIndex(k => !isEqual(this.settings[k], this.savedSettings[k]))
|
|
return k != -1
|
|
},
|
|
|
|
separator: {
|
|
set(value) {
|
|
this.settings.tracklist_editor_sep = value
|
|
if(this.page == Page.List)
|
|
this.updateInput()
|
|
},
|
|
get() { return this.settings.tracklist_editor_sep }
|
|
},
|
|
|
|
rowsSlots() {
|
|
return Object.keys(this.$slots)
|
|
.filter(x => x.startsWith('row-') || x.startsWith('rows-') || x.startsWith('control-'))
|
|
.map(x => [x, x.startsWith('rows-') ? x.slice(5) : x])
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
onCellEvent(event) {
|
|
switch(event.name) {
|
|
case 'change': this.updateInput();
|
|
break;
|
|
}
|
|
},
|
|
|
|
onColumnMove() {
|
|
this.settings.tracklist_editor_columns = this.$refs.formset.rows.columnNames
|
|
if(this.page == this.Page.List)
|
|
this.updateInput()
|
|
else
|
|
this.updateList()
|
|
},
|
|
|
|
updateList() {
|
|
const items = this.toList(this.$refs.textarea.value)
|
|
this.$refs.formset.set.reset(items)
|
|
},
|
|
|
|
updateInput() {
|
|
const input = this.toText(this.$refs.formset.items)
|
|
this.$refs.textarea.value = input
|
|
},
|
|
|
|
/**
|
|
* From input and separator, return list of items.
|
|
*/
|
|
toList(input) {
|
|
const columns = this.$refs.formset.rows.columns_
|
|
var lines = input.split('\n')
|
|
var items = []
|
|
|
|
for(let line of lines) {
|
|
line = line.trimLeft()
|
|
if(!line)
|
|
continue
|
|
|
|
var lineBits = line.split(this.separator)
|
|
var item = {}
|
|
for(var col in columns) {
|
|
if(col >= lineBits.length)
|
|
break
|
|
const column = columns[col]
|
|
item[column.name] = lineBits[col].trim()
|
|
}
|
|
item && items.push(item)
|
|
}
|
|
return items
|
|
},
|
|
|
|
/**
|
|
* From items and separator return a string
|
|
*/
|
|
toText(items) {
|
|
const columns = this.$refs.formset.rows.columns_
|
|
const sep = ` ${this.separator.trim()} `
|
|
const lines = []
|
|
for(let item of items) {
|
|
if(!item)
|
|
continue
|
|
var line = []
|
|
for(var col of columns)
|
|
line.push(item.data[col.name] || '')
|
|
line = dropRightWhile(line, x => !x || !('' + x).trim())
|
|
line = line.join(sep).trimRight()
|
|
lines.push(line)
|
|
}
|
|
return lines.join('\n')
|
|
},
|
|
|
|
|
|
_data_key(key) {
|
|
key = key.slice(this.dataPrefix.length)
|
|
try {
|
|
var [index, attr] = key.split('-', 1)
|
|
return [Number(index), attr]
|
|
}
|
|
catch(err) {
|
|
return [null, key]
|
|
}
|
|
},
|
|
|
|
//! Update saved settings from this.settings
|
|
settingsSaved(settings=null) {
|
|
if(settings !== null)
|
|
this.settings = settings
|
|
if(this.$refs.settings)
|
|
this.$refs.settings.close()
|
|
this.savedSettings = cloneDeep(this.settings)
|
|
},
|
|
},
|
|
|
|
mounted() {
|
|
const settings = this.initData && this.initData.settings
|
|
if(settings) {
|
|
this.settingsSaved(settings)
|
|
this.rows.sortColumns(settings.tracklist_editor_columns)
|
|
}
|
|
this.page = this.initData.items.length ? Page.List : Page.Text
|
|
},
|
|
}
|
|
</script>
|