Profectus/src/components/system/Links.vue
2022-01-13 22:25:47 -06:00

111 lines
2.8 KiB
Vue

<template>
<slot />
<div ref="resizeListener" class="resize-listener" />
<svg v-bind="$attrs" v-if="validLinks">
<LinkVue
v-for="(link, index) in validLinks"
:key="index"
:link="link"
:startNode="nodes[link.startNode.id]"
:endNode="nodes[link.endNode.id]"
/>
</svg>
</template>
<script setup lang="ts">
import {
Link,
LinkNode,
RegisterLinkNodeInjectionKey,
UnregisterLinkNodeInjectionKey
} from "@/features/links";
import { computed, nextTick, onMounted, provide, ref, toRefs, unref } from "vue";
import LinkVue from "./Link.vue";
const props = toRefs(defineProps<{ links: Link[] }>());
const validLinks = computed(() =>
unref(props.links.value).filter(link => {
const n = nodes.value;
return (
link.startNode.id in n &&
link.endNode.id in n &&
n[link.startNode.id].x != undefined &&
n[link.startNode.id].y != undefined &&
n[link.endNode.id].x != undefined &&
n[link.endNode.id].y != undefined
);
})
);
const observerOptions = {
attributes: true,
childList: true,
subtree: true
};
provide(RegisterLinkNodeInjectionKey, (id, element) => {
nodes.value[id] = { element };
observer.observe(element, observerOptions);
nextTick(() => {
if (resizeListener.value != null) {
updateNode(id);
}
});
});
provide(UnregisterLinkNodeInjectionKey, id => {
delete nodes.value[id];
});
function updateNodes() {
if (resizeListener.value != null) {
Object.keys(nodes.value).forEach(id => updateNode(id));
}
}
function updateNode(id: string) {
if (!(id in nodes.value)) {
return;
}
const linkStartRect = nodes.value[id].element.getBoundingClientRect();
nodes.value[id].x = linkStartRect.x + linkStartRect.width / 2 - boundingRect.value.x;
nodes.value[id].y = linkStartRect.y + linkStartRect.height / 2 - boundingRect.value.y;
}
function updateBounds() {
if (resizeListener.value != null) {
boundingRect.value = resizeListener.value.getBoundingClientRect();
updateNodes();
}
}
const observer = new MutationObserver(updateNodes);
const resizeObserver = new ResizeObserver(updateBounds);
const nodes = ref<Record<string, LinkNode>>({});
const boundingRect = ref(new DOMRect());
const resizeListener = ref<Element | null>(null);
onMounted(() => {
// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
const resListener = resizeListener.value;
if (resListener != null) {
resizeObserver.observe(resListener);
}
updateNodes();
});
</script>
<style scoped>
svg,
.resize-listener {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -10;
pointer-events: none;
}
</style>