From b60d17065717c7bccd5f0135e30e9739021f7f94 Mon Sep 17 00:00:00 2001
From: thepaperpilot <thepaperpilot@gmail.com>
Date: Sun, 20 Mar 2022 12:30:08 -0500
Subject: [PATCH] Made node positions more general purpose

---
 src/components/Game.vue              |  5 +++--
 src/components/Layer.vue             | 23 +++++++++++++++++++----
 src/components/Modal.vue             | 12 ++++++++----
 src/components/links/Links.vue       | 11 ++++++++---
 src/features/links.ts                |  5 ++++-
 src/features/particles/particles.tsx | 14 +++++---------
 src/game/layers.tsx                  |  6 ++++--
 7 files changed, 51 insertions(+), 25 deletions(-)

diff --git a/src/components/Game.vue b/src/components/Game.vue
index dda801f..33067af 100644
--- a/src/components/Game.vue
+++ b/src/components/Game.vue
@@ -29,8 +29,9 @@ const layerKeys = computed(() => Object.keys(layers));
 const useHeader = projInfo.useHeader;
 
 function gatherLayerProps(layer: GenericLayer) {
-    const { display, minimized, minWidth, name, color, style, classes, links, minimizable } = layer;
-    return { display, minimized, minWidth, name, color, style, classes, links, minimizable };
+    const { display, minimized, minWidth, name, color, style, classes, links, minimizable, nodes } =
+        layer;
+    return { display, minimized, minWidth, name, color, style, classes, links, minimizable, nodes };
 }
 </script>
 
diff --git a/src/components/Layer.vue b/src/components/Layer.vue
index cb26662..7ca24ae 100644
--- a/src/components/Layer.vue
+++ b/src/components/Layer.vue
@@ -10,7 +10,7 @@
             :class="[{ showGoBack }, unref(classes)]"
             v-else
         >
-            <Links :links="unref(links)">
+            <Links :links="unref(links)" ref="linksRef">
                 <component :is="component" />
             </Links>
         </div>
@@ -24,11 +24,11 @@
 import Links from "components/links/Links.vue";
 import projInfo from "data/projInfo.json";
 import { CoercableComponent, StyleValue } from "features/feature";
-import { Link } from "features/links";
+import { Link, LinkNode } from "features/links";
 import { PersistentRef } from "game/persistence";
 import player from "game/player";
 import { computeComponent, processedPropType, wrapRef } from "util/vue";
-import { computed, defineComponent, nextTick, PropType, toRefs, unref, watch } from "vue";
+import { computed, defineComponent, nextTick, PropType, Ref, ref, toRefs, unref, watch } from "vue";
 
 export default defineComponent({
     components: { Links },
@@ -61,7 +61,11 @@ export default defineComponent({
         style: processedPropType<StyleValue>(String, Object, Array),
         classes: processedPropType<Record<string, boolean>>(Object),
         links: processedPropType<Link[]>(Array),
-        minimizable: processedPropType<boolean>(Boolean)
+        minimizable: processedPropType<boolean>(Boolean),
+        nodes: {
+            type: Object as PropType<Ref<Record<string, LinkNode | undefined>>>,
+            required: true
+        }
     },
     setup(props) {
         const { display, index, minimized, minWidth, tab } = toRefs(props);
@@ -80,6 +84,16 @@ export default defineComponent({
             updateTab(minimized, minWidth)
         );
 
+        const linksRef = ref<typeof Links | null>(null);
+        watch(
+            () => linksRef.value?.nodes,
+            nodes => {
+                if (nodes) {
+                    props.nodes.value = nodes;
+                }
+            }
+        );
+
         function updateTab(minimized: boolean, minWidth: number) {
             const tabValue = tab.value();
             if (tabValue != undefined) {
@@ -102,6 +116,7 @@ export default defineComponent({
         return {
             component,
             showGoBack,
+            linksRef,
             unref,
             goBack
         };
diff --git a/src/components/Modal.vue b/src/components/Modal.vue
index b53de06..967036c 100644
--- a/src/components/Modal.vue
+++ b/src/components/Modal.vue
@@ -17,10 +17,9 @@
                             <slot name="header" :shown="isOpen"> default header </slot>
                         </div>
                         <div class="modal-body">
-                            <Links v-if="links" :links="links">
+                            <Links :links="links" ref="linksRef">
                                 <slot name="body" :shown="isOpen"> default body </slot>
                             </Links>
-                            <slot name="body" v-else :shown="isOpen"> default body </slot>
                         </div>
                         <div class="modal-footer">
                             <slot name="footer" :shown="isOpen">
@@ -40,7 +39,7 @@
 </template>
 
 <script setup lang="ts">
-import { Link } from "features/links";
+import { Link, LinkNode } from "features/links";
 import { computed, ref, toRefs } from "vue";
 import Links from "./links/Links.vue";
 
@@ -60,7 +59,12 @@ function close() {
 
 const isAnimating = ref(false);
 
-defineExpose({ isOpen });
+const linksRef = ref<typeof Links | null>(null);
+const nodes = computed<Record<string, LinkNode | undefined> | null>(
+    () => linksRef.value?.nodes ?? null
+);
+
+defineExpose({ isOpen, nodes });
 </script>
 
 <style>
diff --git a/src/components/links/Links.vue b/src/components/links/Links.vue
index 1369fb9..701de9f 100644
--- a/src/components/links/Links.vue
+++ b/src/components/links/Links.vue
@@ -16,6 +16,7 @@
 import {
     Link,
     LinkNode,
+    NodesInjectionKey,
     RegisterLinkNodeInjectionKey,
     UnregisterLinkNodeInjectionKey
 } from "features/links";
@@ -30,6 +31,8 @@ const resizeObserver = new ResizeObserver(updateNodes);
 
 const nodes = ref<Record<string, LinkNode | undefined>>({});
 
+defineExpose({ nodes });
+
 const resizeListener = ref<Element | null>(null);
 
 onMounted(() => {
@@ -71,6 +74,7 @@ provide(RegisterLinkNodeInjectionKey, (id, element) => {
 provide(UnregisterLinkNodeInjectionKey, id => {
     nodes.value[id] = undefined;
 });
+provide(NodesInjectionKey, nodes);
 
 let isDirty = true;
 let boundingRect = resizeListener.value?.getBoundingClientRect();
@@ -90,10 +94,11 @@ function updateNode(id: string) {
     if (!node || boundingRect == null) {
         return;
     }
-    const linkStartRect = node.element.getBoundingClientRect();
+    const nodeRect = node.element.getBoundingClientRect();
 
-    node.x = linkStartRect.x + linkStartRect.width / 2 - boundingRect.x;
-    node.y = linkStartRect.y + linkStartRect.height / 2 - boundingRect.y;
+    node.y = nodeRect.x + nodeRect.width / 2 - boundingRect.x;
+    node.x = nodeRect.y + nodeRect.height / 2 - boundingRect.y;
+    node.rect = nodeRect;
 }
 </script>
 
diff --git a/src/features/links.ts b/src/features/links.ts
index eb37cf5..e0cb276 100644
--- a/src/features/links.ts
+++ b/src/features/links.ts
@@ -1,9 +1,10 @@
 import { Position } from "game/layers";
-import { InjectionKey, SVGAttributes } from "vue";
+import { InjectionKey, Ref, SVGAttributes } from "vue";
 
 export interface LinkNode {
     x?: number;
     y?: number;
+    rect?: DOMRect;
     element: HTMLElement;
 }
 
@@ -19,3 +20,5 @@ export const RegisterLinkNodeInjectionKey: InjectionKey<
 > = Symbol("RegisterLinkNode");
 export const UnregisterLinkNodeInjectionKey: InjectionKey<(id: string) => void> =
     Symbol("UnregisterLinkNode");
+export const NodesInjectionKey: InjectionKey<Ref<Record<string, LinkNode | undefined>>> =
+    Symbol("Nodes");
diff --git a/src/features/particles/particles.tsx b/src/features/particles/particles.tsx
index 0b05ef5..010bb36 100644
--- a/src/features/particles/particles.tsx
+++ b/src/features/particles/particles.tsx
@@ -2,7 +2,6 @@ import ParticlesComponent from "./Particles.vue";
 import { IEmitter } from "tsparticles-plugin-emitters/Options/Interfaces/IEmitter";
 import { EmitterInstance } from "tsparticles-plugin-emitters/EmitterInstance";
 import { EmitterContainer } from "tsparticles-plugin-emitters/EmitterContainer";
-import { ICoordinates } from "tsparticles-engine";
 import { Ref, shallowRef } from "vue";
 import { registerGameComponent } from "game/settings";
 import { jsx } from "features/feature";
@@ -14,18 +13,17 @@ const containerRef: Ref<null | EmitterContainer> = shallowRef(null);
 let emittersToAdd: {
     resolve: (value: EmitterInstance | PromiseLike<EmitterInstance>) => void;
     options: IEmitter & { particles: Required<IEmitter>["particles"] };
-    position: ICoordinates;
 }[] = [];
 
 export function addEmitter(
-    options: IEmitter & { particles: Required<IEmitter>["particles"] },
-    position: ICoordinates
+    options: IEmitter & { particles: Required<IEmitter>["particles"] }
 ): Promise<EmitterInstance> {
     if (containerRef.value) {
-        return Promise.resolve(containerRef.value.addEmitter(options, position));
+        // TODO why does addEmitter require a position parameter
+        return Promise.resolve(containerRef.value.addEmitter(options));
     }
     return new Promise<EmitterInstance>(resolve => {
-        emittersToAdd.push({ resolve, options, position });
+        emittersToAdd.push({ resolve, options });
     });
 }
 
@@ -36,8 +34,6 @@ export function removeEmitter(emitter: EmitterInstance) {
 
 function onInit(container: EmitterContainer) {
     containerRef.value = container;
-    emittersToAdd.forEach(({ resolve, options, position }) =>
-        resolve(container.addEmitter(options, position))
-    );
+    emittersToAdd.forEach(({ resolve, options }) => resolve(container.addEmitter(options)));
     emittersToAdd = [];
 }
diff --git a/src/game/layers.tsx b/src/game/layers.tsx
index 2a7bc45..8a93374 100644
--- a/src/game/layers.tsx
+++ b/src/game/layers.tsx
@@ -7,7 +7,7 @@ import {
     setDefault,
     StyleValue
 } from "features/feature";
-import { Link } from "features/links";
+import { Link, LinkNode } from "features/links";
 import {
     Computable,
     GetComputableType,
@@ -17,7 +17,7 @@ import {
 } from "util/computed";
 import { createLazyProxy } from "util/proxies";
 import { createNanoEvents, Emitter } from "nanoevents";
-import { ref, unref } from "vue";
+import { Ref, ref, unref } from "vue";
 import { globalBus } from "./events";
 import { persistent, PersistentRef } from "./persistence";
 import player from "./player";
@@ -63,6 +63,7 @@ export interface BaseLayer {
     emitter: Emitter<LayerEvents>;
     on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
     emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
+    nodes: Ref<Record<string, LinkNode | undefined>>;
 }
 
 export type Layer<T extends LayerOptions> = Replace<
@@ -97,6 +98,7 @@ export function createLayer<T extends LayerOptions>(
         const emitter = (layer.emitter = createNanoEvents<LayerEvents>());
         layer.on = emitter.on.bind(emitter);
         layer.emit = emitter.emit.bind(emitter);
+        layer.nodes = ref({});
 
         layer.minimized = persistent(false);