Profectus-Niffix/src/components/tree/Branches.vue

101 lines
2.8 KiB
Vue

<template>
<div v-frag>
<slot />
<div ref="resizeListener" class="resize-listener" />
<svg>
<branch-line v-for="(branch, index) in branches" :key="index"
:startNode="nodes[branch.start]" :endNode="nodes[branch.end]" :options="branch.options" />
</svg>
</div>
</template>
<script>
import Vue from 'vue';
const observerOptions = {
attributes: true,
childList: true,
subtree: true
};
export default {
name: 'branches',
data() {
return {
observer: new MutationObserver(this.updateNodes),
resizeObserver: new ResizeObserver(this.updateNodes),
nodes: {},
links: []
};
},
mounted() {
// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
this.resizeObserver.observe(this.$refs.resizeListener);
this.updateNodes();
},
provide() {
return {
registerNode: this.registerNode,
unregisterNode: this.unregisterNode,
registerBranch: this.registerBranch,
unregisterBranch: this.unregisterBranch
};
},
computed: {
branches() {
return this.links.filter(link => link.start in this.nodes && link.end in this.nodes &&
this.nodes[link.start].x != undefined && this.nodes[link.start].y != undefined &&
this.nodes[link.end].x != undefined && this.nodes[link.end].y != undefined);
}
},
methods: {
updateNodes() {
if (this.$refs.resizeListener != undefined) {
const containerRect = this.$refs.resizeListener.getBoundingClientRect();
Object.keys(this.nodes).forEach(id => this.updateNode(id, containerRect));
}
},
updateNode(id, containerRect) {
const linkStartRect = this.nodes[id].element.getBoundingClientRect();
Vue.set(this.nodes[id], 'x', linkStartRect.x + linkStartRect.width / 2 - containerRect.x);
Vue.set(this.nodes[id], 'y', linkStartRect.y + linkStartRect.height / 2 - containerRect.y);
},
registerNode(id, component) {
const element = component.$el.parentElement;
Vue.set(this.nodes, id, { component, element });
this.observer.observe(element, observerOptions);
this.$nextTick(() => {
if (this.$refs.resizeListener != undefined) {
this.updateNode(id, this.$refs.resizeListener.getBoundingClientRect());
}
});
},
unregisterNode(id) {
Vue.delete(this.nodes, id);
},
registerBranch(start, options) {
const end = typeof options === 'string' ? options : options.target;
this.links.push({ start, end, options });
Vue.set(this, 'links', this.links);
},
unregisterBranch(start, options) {
const index = this.links.findIndex(l => l.start === start && l.options === options);
this.links.splice(index, 1);
Vue.set(this, 'links', this.links);
}
}
};
</script>
<style scoped>
svg,
.resize-listener {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -10;
pointer-events: none;
}
</style>