2022-01-14 04:25:47 +00:00
|
|
|
<template>
|
|
|
|
<slot />
|
|
|
|
<div ref="resizeListener" class="resize-listener" />
|
2022-01-25 04:25:34 +00:00
|
|
|
<svg v-if="validLinks" v-bind="$attrs">
|
2022-01-14 04:25:47 +00:00
|
|
|
<LinkVue
|
|
|
|
v-for="(link, index) in validLinks"
|
|
|
|
:key="index"
|
|
|
|
:link="link"
|
2022-01-25 04:23:30 +00:00
|
|
|
:startNode="nodes[link.startNode.id]!"
|
|
|
|
:endNode="nodes[link.endNode.id]!"
|
2022-01-14 04:25:47 +00:00
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
import {
|
|
|
|
Link,
|
|
|
|
LinkNode,
|
2022-03-20 17:30:08 +00:00
|
|
|
NodesInjectionKey,
|
2022-01-14 04:25:47 +00:00
|
|
|
RegisterLinkNodeInjectionKey,
|
|
|
|
UnregisterLinkNodeInjectionKey
|
2022-03-04 03:39:48 +00:00
|
|
|
} from "features/links";
|
2022-02-27 19:49:34 +00:00
|
|
|
import { computed, nextTick, onMounted, provide, ref, toRef } from "vue";
|
2022-01-14 04:25:47 +00:00
|
|
|
import LinkVue from "./Link.vue";
|
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
const _props = defineProps<{ links?: Link[] }>();
|
|
|
|
const links = toRef(_props, "links");
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-01-25 04:23:30 +00:00
|
|
|
const observer = new MutationObserver(updateNodes);
|
2022-03-02 05:32:21 +00:00
|
|
|
const resizeObserver = new ResizeObserver(updateNodes);
|
2022-01-25 04:23:30 +00:00
|
|
|
|
|
|
|
const nodes = ref<Record<string, LinkNode | undefined>>({});
|
|
|
|
|
2022-03-20 17:30:08 +00:00
|
|
|
defineExpose({ nodes });
|
|
|
|
|
2022-01-25 04:23:30 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
const validLinks = computed(
|
|
|
|
() =>
|
|
|
|
links.value?.filter(link => {
|
|
|
|
const n = nodes.value;
|
|
|
|
return (
|
|
|
|
n[link.startNode.id]?.x != undefined &&
|
|
|
|
n[link.startNode.id]?.y != undefined &&
|
|
|
|
n[link.endNode.id]?.x != undefined &&
|
|
|
|
n[link.endNode.id]?.y != undefined
|
|
|
|
);
|
|
|
|
}) ?? []
|
2022-01-14 04:25:47 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
const observerOptions = {
|
|
|
|
attributes: true,
|
|
|
|
childList: true,
|
2022-03-02 05:32:21 +00:00
|
|
|
subtree: false
|
2022-01-14 04:25:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
provide(RegisterLinkNodeInjectionKey, (id, element) => {
|
|
|
|
nodes.value[id] = { element };
|
|
|
|
observer.observe(element, observerOptions);
|
|
|
|
nextTick(() => {
|
|
|
|
if (resizeListener.value != null) {
|
|
|
|
updateNode(id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
provide(UnregisterLinkNodeInjectionKey, id => {
|
2022-01-25 04:23:30 +00:00
|
|
|
nodes.value[id] = undefined;
|
2022-01-14 04:25:47 +00:00
|
|
|
});
|
2022-03-20 17:30:08 +00:00
|
|
|
provide(NodesInjectionKey, nodes);
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-03-02 05:32:21 +00:00
|
|
|
let isDirty = true;
|
|
|
|
let boundingRect = resizeListener.value?.getBoundingClientRect();
|
2022-01-14 04:25:47 +00:00
|
|
|
function updateNodes() {
|
2022-03-02 05:32:21 +00:00
|
|
|
if (resizeListener.value != null && isDirty) {
|
|
|
|
isDirty = false;
|
|
|
|
nextTick(() => {
|
|
|
|
boundingRect = resizeListener.value?.getBoundingClientRect();
|
|
|
|
Object.keys(nodes.value).forEach(id => updateNode(id));
|
|
|
|
isDirty = true
|
|
|
|
});
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateNode(id: string) {
|
2022-01-25 04:23:30 +00:00
|
|
|
const node = nodes.value[id];
|
2022-03-02 05:32:21 +00:00
|
|
|
if (!node || boundingRect == null) {
|
2022-01-14 04:25:47 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-20 17:30:08 +00:00
|
|
|
const nodeRect = node.element.getBoundingClientRect();
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-03-20 17:30:08 +00:00
|
|
|
node.y = nodeRect.x + nodeRect.width / 2 - boundingRect.x;
|
|
|
|
node.x = nodeRect.y + nodeRect.height / 2 - boundingRect.y;
|
|
|
|
node.rect = nodeRect;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
svg,
|
|
|
|
.resize-listener {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
z-index: -10;
|
|
|
|
pointer-events: none;
|
|
|
|
}
|
|
|
|
</style>
|