See #432: UI to suggest tags on tracks, albums and artists
This commit is contained in:
parent
9336fec430
commit
d2b7db2cac
|
@ -49,7 +49,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="field in getUpdatedFields(obj.payload, previousState)" :key="field.id">
|
||||
<tr v-for="field in updatedFields" :key="field.id">
|
||||
<td>{{ field.id }}</td>
|
||||
|
||||
<td v-if="field.diff">
|
||||
|
@ -61,12 +61,12 @@
|
|||
<translate translate-context="*/*/*">N/A</translate>
|
||||
</td>
|
||||
|
||||
<td v-if="field.diff">
|
||||
<td v-if="field.diff" :title="field.newRepr">
|
||||
<span v-if="!part.removed" v-for="part in field.diff" :class="['diff', {added: part.added}]">
|
||||
{{ part.value }}
|
||||
</span>
|
||||
</td>
|
||||
<td v-else>{{ field.new }}</td>
|
||||
<td v-else :title="field.newRepr">{{ field.newRepr }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -126,6 +126,7 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
configs: edits.getConfigs,
|
||||
canApprove: edits.getCanApprove,
|
||||
canDelete: edits.getCanDelete,
|
||||
previousState () {
|
||||
|
@ -154,6 +155,32 @@ export default {
|
|||
namespace = 'library.artists.edit.detail'
|
||||
}
|
||||
return this.$router.resolve({name: namespace, params: {id, editId: this.obj.uuid}}).href
|
||||
},
|
||||
|
||||
updatedFields () {
|
||||
let payload = this.obj.payload
|
||||
let previousState = this.previousState
|
||||
let fields = Object.keys(payload)
|
||||
let self = this
|
||||
return fields.map((f) => {
|
||||
let fieldConfig = edits.getFieldConfig(self.configs, this.obj.target.type, f)
|
||||
let dummyRepr = (v) => { return v }
|
||||
let getValueRepr = fieldConfig.getValueRepr || dummyRepr
|
||||
let d = {
|
||||
id: f,
|
||||
}
|
||||
if (previousState && previousState[f]) {
|
||||
d.old = previousState[f]
|
||||
d.oldRepr = castValue(getValueRepr(d.old.value))
|
||||
}
|
||||
d.new = payload[f]
|
||||
d.newRepr = castValue(getValueRepr(d.new))
|
||||
if (d.old) {
|
||||
// we compute the diffs between the old and new values
|
||||
d.diff = diffWordsWithSpace(d.oldRepr, d.newRepr)
|
||||
}
|
||||
return d
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -184,26 +211,6 @@ export default {
|
|||
self.isLoading = false
|
||||
})
|
||||
},
|
||||
getUpdatedFields (payload, previousState) {
|
||||
let fields = Object.keys(payload)
|
||||
return fields.map((f) => {
|
||||
let d = {
|
||||
id: f,
|
||||
}
|
||||
if (previousState && previousState[f]) {
|
||||
d.old = previousState[f]
|
||||
}
|
||||
d.new = payload[f]
|
||||
if (d.old) {
|
||||
// we compute the diffs between the old and new values
|
||||
|
||||
let oldValue = castValue(d.old.value)
|
||||
let newValue = castValue(d.new)
|
||||
d.diff = diffWordsWithSpace(oldValue, newValue)
|
||||
}
|
||||
return d
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -77,10 +77,22 @@
|
|||
</button>
|
||||
|
||||
</template>
|
||||
<template v-else-if="fieldConfig.type === 'tags'">
|
||||
<label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
|
||||
<tags-selector
|
||||
ref="tags"
|
||||
v-model="values[fieldConfig.id]"
|
||||
:id="fieldConfig.id"
|
||||
required="fieldConfig.required"></tags-selector>
|
||||
<button class="ui tiny basic left floated button" form="noop" @click.prevent="values[fieldConfig.id] = []">
|
||||
<i class="x icon"></i>
|
||||
<translate translate-context="Content/Library/Button.Label">Clear</translate>
|
||||
</button>
|
||||
</template>
|
||||
<div v-if="values[fieldConfig.id] != initialValues[fieldConfig.id]">
|
||||
<button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = initialValues[fieldConfig.id]">
|
||||
<i class="undo icon"></i>
|
||||
<translate translate-context="Content/Library/Button.Label" :translate-params="{value: initialValues[fieldConfig.id] || ''}">Reset to initial value: %{ value }</translate>
|
||||
<translate translate-context="Content/Library/Button.Label">Reset to initial value</translate>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -110,13 +122,17 @@ import _ from '@/lodash'
|
|||
import axios from "axios"
|
||||
import EditList from '@/components/library/EditList'
|
||||
import EditCard from '@/components/library/EditCard'
|
||||
import TagsSelector from '@/components/library/TagsSelector'
|
||||
import edits from '@/edits'
|
||||
|
||||
import lodash from '@/lodash'
|
||||
|
||||
export default {
|
||||
props: ["objectType", "object", "licenses"],
|
||||
components: {
|
||||
EditList,
|
||||
EditCard
|
||||
EditCard,
|
||||
TagsSelector
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -159,7 +175,7 @@ export default {
|
|||
mutationPayload () {
|
||||
let self = this
|
||||
let changedFields = this.config.fields.filter(f => {
|
||||
return self.values[f.id] != self.initialValues[f.id]
|
||||
return !lodash.isEqual(self.values[f.id], self.initialValues[f.id])
|
||||
})
|
||||
if (changedFields.length === 0) {
|
||||
return null
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div ref="dropdown" class="ui multiple search selection dropdown">
|
||||
<input type="hidden">
|
||||
<i class="dropdown icon"></i>
|
||||
<input type="text" class="search">
|
||||
<div class="default text">
|
||||
<translate translate-context="*/Dropdown/Placeholder/Verb">Search for existing tags…</translate>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
|
||||
import lodash from '@/lodash'
|
||||
export default {
|
||||
props: ['value'],
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.initDropdown()
|
||||
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
initDropdown () {
|
||||
let self = this
|
||||
let handleUpdate = () => {
|
||||
let value = $(self.$refs.dropdown).dropdown('get value').split(',')
|
||||
self.$emit('input', value)
|
||||
return value
|
||||
}
|
||||
let settings = {
|
||||
saveRemoteData: false,
|
||||
filterRemoteData: true,
|
||||
apiSettings: {
|
||||
url: this.$store.getters['instance/absoluteUrl']('/api/v1/tags/?name__startswith={query}&ordering=length&page_size=5'),
|
||||
beforeXHR: function (xhrObject) {
|
||||
xhrObject.setRequestHeader('Authorization', self.$store.getters['auth/header'])
|
||||
return xhrObject
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
remoteValues: 'results',
|
||||
value: 'name'
|
||||
},
|
||||
allowAdditions: true,
|
||||
minCharacters: 1,
|
||||
onAdd: handleUpdate,
|
||||
onRemove: handleUpdate,
|
||||
onLabelRemove: handleUpdate,
|
||||
onChange: handleUpdate,
|
||||
}
|
||||
$(this.$refs.dropdown).dropdown(settings)
|
||||
$(this.$refs.dropdown).dropdown('set exactly', this.value)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler (v) {
|
||||
let current = $(this.$refs.dropdown).dropdown('get value').split(',').sort()
|
||||
if (!lodash.isEqual([...v].sort(), current)) {
|
||||
$(this.$refs.dropdown).dropdown('set exactly', v)
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,3 +1,10 @@
|
|||
function getTagsValueRepr (val) {
|
||||
if (!val) {
|
||||
return ''
|
||||
}
|
||||
return val.slice().sort().join('\n')
|
||||
}
|
||||
|
||||
export default {
|
||||
getConfigs () {
|
||||
return {
|
||||
|
@ -10,6 +17,14 @@ export default {
|
|||
label: this.$pgettext('*/*/*/Noun', 'Name'),
|
||||
getValue: (obj) => { return obj.name }
|
||||
},
|
||||
{
|
||||
id: 'tags',
|
||||
type: 'tags',
|
||||
required: true,
|
||||
label: this.$pgettext('*/*/*/Noun', 'Tags'),
|
||||
getValue: (obj) => { return obj.tags },
|
||||
getValueRepr: getTagsValueRepr
|
||||
}
|
||||
]
|
||||
},
|
||||
album: {
|
||||
|
@ -28,6 +43,14 @@ export default {
|
|||
label: this.$pgettext('Content/*/*/Noun', 'Release date'),
|
||||
getValue: (obj) => { return obj.release_date }
|
||||
},
|
||||
{
|
||||
id: 'tags',
|
||||
type: 'tags',
|
||||
required: true,
|
||||
label: this.$pgettext('*/*/*/Noun', 'Tags'),
|
||||
getValue: (obj) => { return obj.tags },
|
||||
getValueRepr: getTagsValueRepr
|
||||
}
|
||||
]
|
||||
},
|
||||
track: {
|
||||
|
@ -61,6 +84,14 @@ export default {
|
|||
label: this.$pgettext('Content/*/*/Noun', 'License'),
|
||||
getValue: (obj) => { return obj.license },
|
||||
},
|
||||
{
|
||||
id: 'tags',
|
||||
type: 'tags',
|
||||
required: true,
|
||||
label: this.$pgettext('*/*/*/Noun', 'Tags'),
|
||||
getValue: (obj) => { return obj.tags },
|
||||
getValueRepr: getTagsValueRepr
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +100,12 @@ export default {
|
|||
getConfig () {
|
||||
return this.configs[this.objectType]
|
||||
},
|
||||
|
||||
getFieldConfig (configs, type, fieldId) {
|
||||
let c = configs[type]
|
||||
return c.fields.filter((f) => {
|
||||
return f.id == fieldId
|
||||
})[0]
|
||||
},
|
||||
getCurrentState () {
|
||||
let self = this
|
||||
let s = {}
|
||||
|
|
|
@ -12,4 +12,5 @@ export default {
|
|||
uniq: require('lodash/uniq'),
|
||||
remove: require('lodash/remove'),
|
||||
reverse: require('lodash/reverse'),
|
||||
isEqual: require('lodash/isEqual'),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue