app/javascript/vue/tasks/nomenclature/new_taxon_name/components/originalCombination.vue
<template>
<div class="original-combination">
<div class="flex-wrap-column rank-name-label">
<label
v-for="(item, index) in orderRank"
:class="{ 'new-position': index == newPosition }"
class="row capitalize"
>
{{ item }}
</label>
</div>
<div>
<draggable
class="flex-wrap-column"
v-model="rankGroup"
:options="options"
item-key="id"
:group="options.group"
:animation="options.animation"
:filter="options.filter"
@end="onEnd"
@add="onAdd"
@update="onUpdate"
@start="onStart"
>
<template #item="{ element, index }">
<div
class="horizontal-left-content middle gap-small"
v-if="!element.value"
>
<autocomplete
url="/taxon_names/autocomplete"
label="label_html"
min="2"
:disabled="disabled"
clear-after
@getItem="addOriginalCombination($event.id, index)"
:add-params="{
type: 'Protonym',
'nomenclature_group[]': nomenclatureGroup
}"
param="term"
/>
<span
class="handle button circle-button button-submit"
title="Press and hold to drag input"
data-icon="w_scroll-v"
/>
</div>
<div
class="original-combination-item horizontal-left-content middle gap-small"
v-else
>
<div>
<span
class="vue-autocomplete-input normal-input combination middle"
>
<span v-html="element.value.subject_object_tag" />
</span>
</div>
<span
class="handle button circle-button button-submit"
title="Press and hold to drag input"
data-icon="w_scroll-v"
/>
<radialAnnotator :global-id="element.value.global_id" />
<span
class="circle-button btn-delete"
@click="removeCombination(element.value, index)"
/>
</div>
</template>
</draggable>
</div>
</div>
</template>
<script>
import { GetterNames } from '../store/getters/getters'
import { MutationNames } from '../store/mutations/mutations'
import { ActionNames } from '../store/actions/actions'
import Autocomplete from '@/components/ui/Autocomplete.vue'
import RadialAnnotator from '@/components/radials/annotator/annotator.vue'
import Draggable from 'vuedraggable'
export default {
components: {
RadialAnnotator,
Autocomplete,
Draggable
},
computed: {
taxon() {
return this.$store.getters[GetterNames.GetTaxon]
},
originalCombination() {
return this.$store.getters[GetterNames.GetOriginalCombination]
}
},
props: {
disabled: {
type: Boolean,
default: false
},
relationships: {
type: Object,
required: true
},
originalRelationship: {
type: Object,
default: () => ({})
},
nomenclatureGroup: {
type: String,
required: true
},
options: {
type: Object,
default: () => {
return {
animation: 150,
group: {
name: 'combination',
put: true,
pull: true
},
filter: '.item-filter'
}
}
}
},
emits: ['create', 'delete', 'processed'],
data() {
return {
expanded: true,
rankGroup: [],
orderRank: [],
copyRankGroup: undefined,
originalTypes: [],
newPosition: -1
}
},
watch: {
originalCombination() {
this.loadOriginalCombinationList()
}
},
created() {
this.init()
},
methods: {
init() {
this.orderRank = Object.keys(this.relationships)
this.originalTypes = Object.values(this.relationships)
},
loadOriginalCombinationList() {
this.rankGroup = this.orderRank.map((rank, index) => ({
name: rank,
value: this.GetOriginal(rank),
id: index
}))
this.copyRankGroup = this.rankGroup.splice()
},
addOriginalCombination(elementId, index) {
const data = {
type: this.originalTypes[index],
id: elementId
}
return new Promise((resolve, reject) => {
this.$store
.dispatch(ActionNames.AddOriginalCombination, data)
.then((response) => {
this.rankGroup[index].value = response
this.$emit('create')
resolve(response)
})
})
},
GetOriginal(name) {
const key = 'original_' + name
return this.originalCombination[key] || undefined
},
removeCombination(value, index) {
if (window.confirm('Are you sure you want to remove this combination?')) {
this.$store
.dispatch(ActionNames.RemoveOriginalCombination, value)
.then((response) => {
this.rankGroup[index] = response
this.$emit('delete', response)
})
}
},
onMove(evt) {
this.newPosition = evt.draggedContext.futureIndex
return !evt.related.classList.contains('item-filter')
},
onAdd(evt) {
let index = evt.newIndex
if (this.rankGroup.length - 1 === evt.newIndex) {
this.rankGroup.splice(evt.newIndex - 1, 1)
index = evt.newIndex - 1
}
this.rankGroup.splice(evt.newIndex + 1, 1)
this.addOriginalCombination(
this.rankGroup[index].value.subject_taxon_name_id,
index
).then((response) => {
this.$emit('create')
})
},
onEnd(evt) {
this.newPosition = -1
},
onStart(evt) {
this.copyRankGroup = JSON.parse(JSON.stringify(this.rankGroup))
},
async onUpdate(evt) {
const positionChanged = this.changedElementPositions()
const deletePositions = positionChanged.filter(
(index) => this.copyRankGroup[index].value
)
const createRelationships = positionChanged.filter(
(index) => this.rankGroup[index].value
)
await this.destroyRelationships(deletePositions)
this.createRelationships(createRelationships)
},
createRelationships(positions) {
const promises = positions.map((position) =>
this.addOriginalCombination(
this.rankGroup[position].value.subject_taxon_name_id,
position
)
)
Promise.all(promises).then(() => {
this.$emit('create')
})
},
changedElementPositions() {
const changedPositions = []
this.copyRankGroup.forEach((item, index) => {
if (item.id !== this.rankGroup[index].id) {
changedPositions.push(index)
}
})
return changedPositions
},
destroyRelationships(positions) {
return new Promise((resolve, reject) => {
const promises = positions.map((position) =>
this.$store.dispatch(
ActionNames.RemoveOriginalCombination,
this.copyRankGroup[position].value
)
)
Promise.all(promises).then(() => {
resolve(promises)
})
})
}
}
}
</script>
<style lang="scss">
.original-combination {
.new-position {
color: red;
font-weight: 900;
}
.original-combination-item {
min-height: 40px;
}
.vue-autocomplete-input {
min-height: 28px;
min-width: 400px;
max-width: 500px;
}
.combination {
z-index: 1;
background-color: #f5f5f5;
}
}
</style>