forked from profectus/Profectus
101 lines
2.8 KiB
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>
|