Fixed position of links
This commit is contained in:
parent
5ce8195779
commit
da6a47b1da
4 changed files with 81 additions and 76 deletions
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<slot />
|
<slot />
|
||||||
<div ref="resizeListener" class="resize-listener" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -10,25 +9,12 @@ import {
|
||||||
NodesInjectionKey,
|
NodesInjectionKey,
|
||||||
FeatureNode
|
FeatureNode
|
||||||
} from "game/layers";
|
} from "game/layers";
|
||||||
import { nextTick, onMounted, provide, ref } from "vue";
|
import { nextTick, provide, ref } from "vue";
|
||||||
|
|
||||||
const observer = new MutationObserver(updateNodes);
|
|
||||||
const resizeObserver = new ResizeObserver(updateNodes);
|
|
||||||
|
|
||||||
const nodes = ref<Record<string, FeatureNode | undefined>>({});
|
const nodes = ref<Record<string, FeatureNode | undefined>>({});
|
||||||
|
|
||||||
defineExpose({ nodes });
|
defineExpose({ nodes });
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const observerOptions = {
|
const observerOptions = {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
childList: true,
|
childList: true,
|
||||||
|
@ -36,54 +22,22 @@ const observerOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
provide(RegisterNodeInjectionKey, (id, element) => {
|
provide(RegisterNodeInjectionKey, (id, element) => {
|
||||||
nodes.value[id] = { element };
|
const observer = new MutationObserver(() => updateNode(id));
|
||||||
observer.observe(element, observerOptions);
|
observer.observe(element, observerOptions);
|
||||||
nextTick(() => {
|
nodes.value[id] = { element, observer };
|
||||||
if (resizeListener.value != null) {
|
nextTick(() => updateNode(id));
|
||||||
updateNode(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
provide(UnregisterNodeInjectionKey, id => {
|
provide(UnregisterNodeInjectionKey, id => {
|
||||||
|
nodes.value[id]?.observer?.disconnect();
|
||||||
nodes.value[id] = undefined;
|
nodes.value[id] = undefined;
|
||||||
});
|
});
|
||||||
provide(NodesInjectionKey, nodes);
|
provide(NodesInjectionKey, nodes);
|
||||||
|
|
||||||
let isDirty = true;
|
|
||||||
let boundingRect = resizeListener.value?.getBoundingClientRect();
|
|
||||||
function updateNodes() {
|
|
||||||
if (resizeListener.value != null && isDirty) {
|
|
||||||
isDirty = false;
|
|
||||||
nextTick(() => {
|
|
||||||
boundingRect = resizeListener.value?.getBoundingClientRect();
|
|
||||||
Object.keys(nodes.value).forEach(id => updateNode(id));
|
|
||||||
isDirty = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNode(id: string) {
|
function updateNode(id: string) {
|
||||||
const node = nodes.value[id];
|
const node = nodes.value[id];
|
||||||
if (!node || boundingRect == null) {
|
if (node == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nodeRect = node.element.getBoundingClientRect();
|
node.rect = node.element.getBoundingClientRect();
|
||||||
|
|
||||||
node.y = nodeRect.x + nodeRect.width / 2 - boundingRect.x;
|
|
||||||
node.x = nodeRect.y + nodeRect.height / 2 - boundingRect.y;
|
|
||||||
node.rect = nodeRect;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
svg,
|
|
||||||
.resize-listener {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: -10;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -13,32 +13,47 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Link } from "features/links/links";
|
import { Link } from "features/links/links";
|
||||||
import { FeatureNode } from "game/layers";
|
import { FeatureNode } from "game/layers";
|
||||||
import { computed, toRefs, unref } from "vue";
|
import { computed, toRefs } from "vue";
|
||||||
|
|
||||||
const _props = defineProps<{
|
const _props = defineProps<{
|
||||||
link: Link;
|
link: Link;
|
||||||
startNode: FeatureNode;
|
startNode: FeatureNode;
|
||||||
endNode: FeatureNode;
|
endNode: FeatureNode;
|
||||||
|
boundingRect: DOMRect | undefined;
|
||||||
}>();
|
}>();
|
||||||
const props = toRefs(_props);
|
const props = toRefs(_props);
|
||||||
|
|
||||||
const startPosition = computed(() => {
|
const startPosition = computed(() => {
|
||||||
const position = { x: props.startNode.value.x || 0, y: props.startNode.value.y || 0 };
|
const rect = props.startNode.value.rect;
|
||||||
|
const boundingRect = props.boundingRect.value;
|
||||||
|
const position =
|
||||||
|
rect && boundingRect
|
||||||
|
? {
|
||||||
|
x: rect.x + rect.width / 2 - boundingRect.x,
|
||||||
|
y: rect.y + rect.height / 2 - boundingRect.y
|
||||||
|
}
|
||||||
|
: { x: 0, y: 0 };
|
||||||
if (props.link.value.offsetStart) {
|
if (props.link.value.offsetStart) {
|
||||||
position.x += unref(props.link.value.offsetStart).x;
|
position.x += props.link.value.offsetStart.x;
|
||||||
position.y += unref(props.link.value.offsetStart).y;
|
position.y += props.link.value.offsetStart.y;
|
||||||
}
|
}
|
||||||
return position;
|
return position;
|
||||||
});
|
});
|
||||||
|
|
||||||
const endPosition = computed(() => {
|
const endPosition = computed(() => {
|
||||||
const position = { x: props.endNode.value.x || 0, y: props.endNode.value.y || 0 };
|
const rect = props.endNode.value.rect;
|
||||||
|
const boundingRect = props.boundingRect.value;
|
||||||
|
const position =
|
||||||
|
rect && boundingRect
|
||||||
|
? {
|
||||||
|
x: rect.x + rect.width / 2 - boundingRect.x,
|
||||||
|
y: rect.y + rect.height / 2 - boundingRect.y
|
||||||
|
}
|
||||||
|
: { x: 0, y: 0 };
|
||||||
if (props.link.value.offsetEnd) {
|
if (props.link.value.offsetEnd) {
|
||||||
position.x += unref(props.link.value.offsetEnd).x;
|
position.x += props.link.value.offsetEnd.x;
|
||||||
position.y += unref(props.link.value.offsetEnd).y;
|
position.y += props.link.value.offsetEnd.y;
|
||||||
}
|
}
|
||||||
return position;
|
return position;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|
|
@ -4,34 +4,71 @@
|
||||||
v-for="(link, index) in validLinks"
|
v-for="(link, index) in validLinks"
|
||||||
:key="index"
|
:key="index"
|
||||||
:link="link"
|
:link="link"
|
||||||
|
:boundingRect="boundingRect"
|
||||||
:startNode="nodes[link.startNode.id]!"
|
:startNode="nodes[link.startNode.id]!"
|
||||||
:endNode="nodes[link.endNode.id]!"
|
:endNode="nodes[link.endNode.id]!"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
<div ref="resizeListener" class="resize-listener" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Link } from "features/links/links";
|
import { Link } from "features/links/links";
|
||||||
import { NodesInjectionKey } from "game/layers";
|
import { NodesInjectionKey } from "game/layers";
|
||||||
import { computed, inject, toRef } from "vue";
|
import { computed, inject, nextTick, onMounted, ref, toRef } from "vue";
|
||||||
import LinkVue from "./Link.vue";
|
import LinkVue from "./Link.vue";
|
||||||
|
|
||||||
const _props = defineProps<{ links?: Link[] }>();
|
const _props = defineProps<{ links?: Link[] }>();
|
||||||
const links = toRef(_props, "links");
|
const links = toRef(_props, "links");
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(updateNodes);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const nodes = inject(NodesInjectionKey)!;
|
const nodes = inject(NodesInjectionKey)!;
|
||||||
|
|
||||||
const validLinks = computed(
|
const resizeListener = ref<Element | null>(null);
|
||||||
() =>
|
|
||||||
links.value?.filter(link => {
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let isDirty = true;
|
||||||
|
let boundingRect = ref(resizeListener.value?.getBoundingClientRect());
|
||||||
|
function updateNodes() {
|
||||||
|
if (resizeListener.value != null && isDirty) {
|
||||||
|
isDirty = false;
|
||||||
|
nextTick(() => {
|
||||||
|
boundingRect.value = resizeListener.value?.getBoundingClientRect();
|
||||||
|
Object.values(nodes.value).forEach(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
node => (node!.rect = node?.element.getBoundingClientRect())
|
||||||
|
);
|
||||||
|
isDirty = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validLinks = computed(() => {
|
||||||
const n = nodes.value;
|
const n = nodes.value;
|
||||||
return (
|
return (
|
||||||
n[link.startNode.id]?.x != undefined &&
|
links.value?.filter(link => n[link.startNode.id]?.rect && n[link.startNode.id]?.rect) ?? []
|
||||||
n[link.startNode.id]?.y != undefined &&
|
|
||||||
n[link.endNode.id]?.x != undefined &&
|
|
||||||
n[link.endNode.id]?.y != undefined
|
|
||||||
);
|
);
|
||||||
}) ?? []
|
});
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.resize-listener,
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -10;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -22,9 +22,8 @@ import { persistent, PersistentRef } from "./persistence";
|
||||||
import player from "./player";
|
import player from "./player";
|
||||||
|
|
||||||
export interface FeatureNode {
|
export interface FeatureNode {
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
rect?: DOMRect;
|
rect?: DOMRect;
|
||||||
|
observer?: MutationObserver;
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue