413 lines
14 KiB
Vue

<template>
<!-- TEST appelle la fonction sans passer par le callback de vis-network
<button class="btn btn-sm btn-create" @click="addRelationshipModal({ from: 'person_1617', to: 'person_1614' })">add link</button>
<=== InternalError: too much recursion
-->
<div id="visgraph"></div>
<teleport to="#visgraph-legend">
<div class="my-4 post-menu legend">
<h3>{{ $t('visgraph.Legend') }}</h3>
<div class="list-group">
<label class="list-group-item" v-for="layer in legendLayers">
<input
class="form-check-input me-1"
type="checkbox"
:value="layer.id"
v-model="checkedLayers"
@change="toggleLayer"
/>
{{ layer.label }}
</label>
</div>
</div>
<button class="btn btn-sm btn-outline-secondary" @click="refreshNetwork">{{ $t('action.refresh') }}</button>
</teleport>
<teleport to="body">
<modal v-if="modal.showModal" :modalDialogClass="modal.modalDialogClass" @close="modal.showModal = false">
<template v-slot:header>
<h2 class="modal-title">{{ $t(modal.title) }}</h2>
<!-- {{ modal.data.id }} -->
</template>
<template v-slot:body>
<div v-if="modal.action === 'delete'">
<p>{{ $t('visgraph.delete_confirmation_text') }}</p>
</div>
<div v-else>
<form>
<div class="row">
<div class="col-12 text-center">{{ $t('visgraph.between') }}<br>{{ $t('visgraph.and') }}</div>
<div class="col">
<h4>
{{ getPerson(modal.data.from).text }}
</h4>
<p class="text-start" v-if="relation && relation.title">
<span v-if="reverse">
{{ $t('visgraph.relation_from_to_like', [ getPerson(modal.data.from).text, getPerson(modal.data.to).text, relation.reverseTitle.fr.toLowerCase() ])}}
</span>
<span v-else>
{{ $t('visgraph.relation_from_to_like', [ getPerson(modal.data.from).text, getPerson(modal.data.to).text, relation.title.fr.toLowerCase() ])}}
</span>
</p>
</div>
<div class="col text-end">
<h4>
{{ getPerson(modal.data.to).text }}
</h4>
<p class="text-end" v-if="relation && relation.title">
<span v-if="reverse">
{{ $t('visgraph.relation_from_to_like', [ getPerson(modal.data.to).text, getPerson(modal.data.from).text, relation.title.fr.toLowerCase() ])}}
</span>
<span v-else>
{{ $t('visgraph.relation_from_to_like', [ getPerson(modal.data.to).text, getPerson(modal.data.from).text, relation.reverseTitle.fr.toLowerCase() ])}}
</span>
</p>
</div>
</div>
<div class="my-3"><!--
:value="relation"
-->
<VueMultiselect
id="relation"
label="title"
track-by="id"
:custom-label="customLabel"
:placeholder="$t('Choisissez le lien de parenté')"
:close-on-select="true"
:multiple="false"
:searchable="true"
:options="relations"
v-model="relation"
>
</VueMultiselect>
</div>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
id="reverse"
v-model="reverse"
>
<label class="form-check-label" for="reverse">{{ $t('visgraph.reverse_relation') }}</label>
</div>
</form>
</div>
</template>
<template v-slot:footer>
<button class="btn" :class="modal.button.class" @click="submitRelationship">
{{ $t(modal.button.text)}}</button>
</template>
</modal>
</teleport>
</template>
<script>
import vis from 'vis-network/dist/vis-network'
import { mapState, mapGetters } from "vuex"
import Modal from 'ChillMainAssets/vuejs/_components/Modal'
import VueMultiselect from 'vue-multiselect'
import { getRelationsList, postRelationship } from "./api";
import { splitId } from "./vis-network";
export default {
name: "App",
components: {
Modal,
VueMultiselect
},
data() {
return {
container: '',
checkedLayers: [],
relations: [],
relation: null,
reverse: false,
modal: {
showModal: false,
modalDialogClass: "modal-md",
title: null,
action: null,
data: {},
button: {
class: null,
text: null
},
},
}
},
computed: {
...mapGetters(['nodes', 'edges']),
...mapState(['households', 'courses', 'excludedNodesIds', 'persons',
// not used
'links', 'relationships', 'personLoadedIds', 'householdLoadingIds', 'courseLoadedIds', 'relationshipLoadedIds',
]),
visgraph_data() {
console.log('::: visgraph_data :::', this.nodes.length, 'nodes,', this.edges.length, 'edges')
return {
nodes: this.nodes,
edges: this.edges
}
},
refreshNetwork() {
//console.log('--- refresh network')
window.network.setData(this.visgraph_data)
},
legendLayers() {
//console.log('--- refresh legend')
return [
...this.households,
...this.courses
]
},
rebuildCheckedLayers() {
//console.log('--- rebuild checked Layers')
this.checkedLayers = []
let layersDisplayed = [
...this.nodes.filter(n => n.id.startsWith('household')),
...this.nodes.filter(n => n.id.startsWith('accompanying'))
]
layersDisplayed.forEach(layer => {
this.checkedLayers.push(layer.id)
})
},
checkedLayers() { // required to refresh data checkedLayers
return this.checkedLayers
},
relation: {
get() {
return this.relation
},
set(value) {
//console.log('setter relation', value) // <=== InternalError: too much recursion
this.relation = value
}
},
reverse: {
get() {
return this.reverse
},
set(value) {
//console.log('setter reverse', value) // <=== InternalError: too much recursion
this.reverse = value
}
},
},
created() {
eventHub.on('add-relationship-modal', this.addRelationshipModal)
eventHub.on('edit-relationship-modal', this.editRelationshipModal)
eventHub.on('delete-relationship-modal', this.deleteRelationshipModal)
},
unmounted() {
eventHub.off('add-relationship-modal', this.addRelationshipModal)
eventHub.off('edit-relationship-modal', this.editRelationshipModal)
eventHub.off('delete-relationship-modal', this.deleteRelationshipModal)
},
mounted() {
//console.log('=== mounted: init graph')hill
this.initGraph()
this.listenOnGraph()
this.getRelationsList()
this.forceUpdateComponent()
},
methods: {
initGraph() {
this.container = document.getElementById('visgraph')
// Instanciate vis objects in separate window variables, see vis-network.js
window.network = new vis.Network(this.container, this.visgraph_data, window.options)
},
listenOnGraph() {
window.network.on('selectNode', (data) => {
if (data.nodes.length > 1) {
throw 'Multi selection is not allowed. Disable it in options.interaction !'
}
let nodeType = splitId(data.nodes[0], 'type')
switch (nodeType) {
case 'person':
let person = this.nodes.filter(n => n.id === data.nodes[0])[0]
console.log('@@@@@@ event on selected Node', person.id)
if (person.label === null) {
this.$store.commit('unfoldPerson', person)
this.forceUpdateComponent()
}
break
case 'household':
let household = this.nodes.filter(n => n.id === data.nodes[0])[0]
console.log('@@@@@@ event on selected Node', household.id)
this.$store.dispatch('unfoldPersonsByHousehold', household)
this.forceUpdateComponent()
break
case 'accompanying_period':
let course = this.nodes.filter(n => n.id === data.nodes[0])[0]
console.log('@@@@@@ event on selected Node', course.id)
this.$store.dispatch('unfoldPersonsByCourse', course)
this.forceUpdateComponent()
break
default:
throw 'this node type is undefined'
}
})
/*
window.network.on('selectEdge', (data) => {
console.log('######## event onSelectEdge ########', data)
})
*/
},
forceUpdateComponent() {
//console.log('!! forceUpdateComponent !!')
this.$forceUpdate()
this.refreshNetwork
},
toggleLayer(value) {
let id = value.target.value
console.log('@@@@@ toggle Layer', id)
this.forceUpdateComponent()
if (this.checkedLayers.includes(id)) {
this.removeLayer(id)
} else {
this.addLayer(id)
}
},
addLayer(id) {
//console.log('+ addLayer', id)
this.checkedLayers.push(id)
this.$store.dispatch('removeExcludedNode', id)
},
removeLayer(id) {
//console.log('- removeLayer', id)
this.checkedLayers = this.checkedLayers.filter(i => i !== id)
this.$store.dispatch('addExcludedNode', id)
},
addRelationshipModal(edgeData) {
this.modal.data = edgeData
console.log('==- addRelationshipModal', edgeData) // { from: "person_1617", to: "person_1614" }
this.modal.action = 'create'
this.modal.title = 'visgraph.add_relationship_link'
this.modal.button.class = 'btn-create'
this.modal.button.text = 'action.create'
this.modal.showModal = true
},
editRelationshipModal(edgeData) {
this.modal.data = edgeData
console.log('==- editRelationshipModal', edgeData)
this.modal.action = 'edit'
this.modal.title = 'visgraph.edit_relationship_link'
this.modal.button.class = 'btn-edit'
this.modal.button.text = 'action.edit'
this.modal.showModal = true
},
deleteRelationshipModal(edgeData) {
this.modal.data = edgeData
console.log('==- deleteRelationshipModal', edgeData)
this.modal.action = 'delete'
this.modal.title = 'visgraph.delete_relationship_link'
this.modal.button.class = 'btn-delete'
this.modal.button.text = 'action.delete'
this.modal.showModal = true
},
resetForm() {
console.log('==- reset Form')
this.modal.data = {}
this.modal.action = null
this.modal.title = null
this.modal.button.class = null
this.modal.button.text = null
},
getRelationsList() {
//console.log('fetch relationsList')
return getRelationsList().then(relations => new Promise(resolve => {
//console.log('+ relations list', relations.results.length)
this.relations = relations.results.filter(r => r.isActive === true)
resolve()
})).catch()
},
customLabel(value) {
//console.log('customLabel', value)
return (value.title && value.reverseTitle) ? `${value.title.fr}${value.reverseTitle.fr}` : ''
},
getPerson(idtext) {
let person = this.persons.filter(p => p.id === idtext)
return person[0]
},
submitRelationship() {
console.log('submitRelationship with action', this.modal.action)
switch (this.modal.action) {
case 'create':
return postRelationship(
this.getPerson(this.modal.data.from), this.getPerson(this.modal.data.to), this.relation, this.reverse
)
.then(relationship => new Promise(resolve => {
console.log('post response', relationship)
this.$store.dispatch('addLinkFromRelationship', relationship)
this.modal.showModal = false
this.resetForm()
resolve()
}))
.catch()
case 'edit':
/// TODO
// récupérer la relationship,
// la modifier,
// patcher en reconstruisant le body,
// récupérer la réponse,
// mettre le link (edge) à jour
return patchRelationship(relationship)
.then(response => new Promise(resolve => {
console.log('patch response', response)
this.$store.dispatch('updateLinkFromRelationship', response)
this.modal.showModal = false
this.resetForm()
resolve()
}))
.catch()
case 'delete':
break
default:
throw "undefined action"
}
}
}
}
</script>
<style src="vis-network/dist/dist/vis-network.min.css"></style>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
<style lang="scss" scoped>
div#visgraph {
height: 700px;
margin: auto;
}
div#visgraph-legend {
div.post-menu.legend {
}
}
.modal-mask {
background-color: rgba(0, 0, 0, 0.25);
}
</style>