Separated Links into a feature and Context
This commit is contained in:
parent
b60d170657
commit
5ce8195779
21 changed files with 188 additions and 125 deletions
|
@ -1,35 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<slot />
|
<slot />
|
||||||
<div ref="resizeListener" class="resize-listener" />
|
<div ref="resizeListener" class="resize-listener" />
|
||||||
<svg v-if="validLinks" v-bind="$attrs">
|
|
||||||
<LinkVue
|
|
||||||
v-for="(link, index) in validLinks"
|
|
||||||
:key="index"
|
|
||||||
:link="link"
|
|
||||||
:startNode="nodes[link.startNode.id]!"
|
|
||||||
:endNode="nodes[link.endNode.id]!"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
Link,
|
RegisterNodeInjectionKey,
|
||||||
LinkNode,
|
UnregisterNodeInjectionKey,
|
||||||
NodesInjectionKey,
|
NodesInjectionKey,
|
||||||
RegisterLinkNodeInjectionKey,
|
FeatureNode
|
||||||
UnregisterLinkNodeInjectionKey
|
} from "game/layers";
|
||||||
} from "features/links";
|
import { nextTick, onMounted, provide, ref } from "vue";
|
||||||
import { computed, nextTick, onMounted, provide, ref, toRef } from "vue";
|
|
||||||
import LinkVue from "./Link.vue";
|
|
||||||
|
|
||||||
const _props = defineProps<{ links?: Link[] }>();
|
|
||||||
const links = toRef(_props, "links");
|
|
||||||
|
|
||||||
const observer = new MutationObserver(updateNodes);
|
const observer = new MutationObserver(updateNodes);
|
||||||
const resizeObserver = new ResizeObserver(updateNodes);
|
const resizeObserver = new ResizeObserver(updateNodes);
|
||||||
|
|
||||||
const nodes = ref<Record<string, LinkNode | undefined>>({});
|
const nodes = ref<Record<string, FeatureNode | undefined>>({});
|
||||||
|
|
||||||
defineExpose({ nodes });
|
defineExpose({ nodes });
|
||||||
|
|
||||||
|
@ -43,26 +29,13 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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
|
|
||||||
);
|
|
||||||
}) ?? []
|
|
||||||
);
|
|
||||||
|
|
||||||
const observerOptions = {
|
const observerOptions = {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: false
|
subtree: false
|
||||||
};
|
};
|
||||||
|
|
||||||
provide(RegisterLinkNodeInjectionKey, (id, element) => {
|
provide(RegisterNodeInjectionKey, (id, element) => {
|
||||||
nodes.value[id] = { element };
|
nodes.value[id] = { element };
|
||||||
observer.observe(element, observerOptions);
|
observer.observe(element, observerOptions);
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
@ -71,7 +44,7 @@ provide(RegisterLinkNodeInjectionKey, (id, element) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
provide(UnregisterLinkNodeInjectionKey, id => {
|
provide(UnregisterNodeInjectionKey, id => {
|
||||||
nodes.value[id] = undefined;
|
nodes.value[id] = undefined;
|
||||||
});
|
});
|
||||||
provide(NodesInjectionKey, nodes);
|
provide(NodesInjectionKey, nodes);
|
|
@ -10,9 +10,9 @@
|
||||||
:class="[{ showGoBack }, unref(classes)]"
|
:class="[{ showGoBack }, unref(classes)]"
|
||||||
v-else
|
v-else
|
||||||
>
|
>
|
||||||
<Links :links="unref(links)" ref="linksRef">
|
<Context ref="contextRef">
|
||||||
<component :is="component" />
|
<component :is="component" />
|
||||||
</Links>
|
</Context>
|
||||||
</div>
|
</div>
|
||||||
<button v-if="unref(minimizable)" class="minimize" @click="minimized.value = true">
|
<button v-if="unref(minimizable)" class="minimize" @click="minimized.value = true">
|
||||||
▼
|
▼
|
||||||
|
@ -21,17 +21,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Links from "components/links/Links.vue";
|
|
||||||
import projInfo from "data/projInfo.json";
|
import projInfo from "data/projInfo.json";
|
||||||
import { CoercableComponent, StyleValue } from "features/feature";
|
import { CoercableComponent, StyleValue } from "features/feature";
|
||||||
import { Link, LinkNode } from "features/links";
|
import { FeatureNode } from "game/layers";
|
||||||
import { PersistentRef } from "game/persistence";
|
import { PersistentRef } from "game/persistence";
|
||||||
import player from "game/player";
|
import player from "game/player";
|
||||||
import { computeComponent, processedPropType, wrapRef } from "util/vue";
|
import { computeComponent, processedPropType, wrapRef } from "util/vue";
|
||||||
import { computed, defineComponent, nextTick, PropType, Ref, ref, toRefs, unref, watch } from "vue";
|
import { computed, defineComponent, nextTick, PropType, Ref, ref, toRefs, unref, watch } from "vue";
|
||||||
|
import Context from "./Context.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Links },
|
components: { Context },
|
||||||
props: {
|
props: {
|
||||||
index: {
|
index: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
@ -60,10 +60,9 @@ export default defineComponent({
|
||||||
color: processedPropType<string>(String),
|
color: processedPropType<string>(String),
|
||||||
style: processedPropType<StyleValue>(String, Object, Array),
|
style: processedPropType<StyleValue>(String, Object, Array),
|
||||||
classes: processedPropType<Record<string, boolean>>(Object),
|
classes: processedPropType<Record<string, boolean>>(Object),
|
||||||
links: processedPropType<Link[]>(Array),
|
|
||||||
minimizable: processedPropType<boolean>(Boolean),
|
minimizable: processedPropType<boolean>(Boolean),
|
||||||
nodes: {
|
nodes: {
|
||||||
type: Object as PropType<Ref<Record<string, LinkNode | undefined>>>,
|
type: Object as PropType<Ref<Record<string, FeatureNode | undefined>>>,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -84,9 +83,9 @@ export default defineComponent({
|
||||||
updateTab(minimized, minWidth)
|
updateTab(minimized, minWidth)
|
||||||
);
|
);
|
||||||
|
|
||||||
const linksRef = ref<typeof Links | null>(null);
|
const contextRef = ref<typeof Context | null>(null);
|
||||||
watch(
|
watch(
|
||||||
() => linksRef.value?.nodes,
|
() => contextRef.value?.nodes,
|
||||||
nodes => {
|
nodes => {
|
||||||
if (nodes) {
|
if (nodes) {
|
||||||
props.nodes.value = nodes;
|
props.nodes.value = nodes;
|
||||||
|
@ -116,7 +115,7 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
showGoBack,
|
showGoBack,
|
||||||
linksRef,
|
contextRef,
|
||||||
unref,
|
unref,
|
||||||
goBack
|
goBack
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
<slot name="header" :shown="isOpen"> default header </slot>
|
<slot name="header" :shown="isOpen"> default header </slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<Links :links="links" ref="linksRef">
|
<Context ref="contextRef">
|
||||||
<slot name="body" :shown="isOpen"> default body </slot>
|
<slot name="body" :shown="isOpen"> default body </slot>
|
||||||
</Links>
|
</Context>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<slot name="footer" :shown="isOpen">
|
<slot name="footer" :shown="isOpen">
|
||||||
|
@ -39,13 +39,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Link, LinkNode } from "features/links";
|
import { FeatureNode } from "game/layers";
|
||||||
import { computed, ref, toRefs } from "vue";
|
import { computed, ref, toRefs } from "vue";
|
||||||
import Links from "./links/Links.vue";
|
import Context from "./Context.vue";
|
||||||
|
|
||||||
const _props = defineProps<{
|
const _props = defineProps<{
|
||||||
modelValue: boolean;
|
modelValue: boolean;
|
||||||
links?: Link[];
|
|
||||||
}>();
|
}>();
|
||||||
const props = toRefs(_props);
|
const props = toRefs(_props);
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -59,9 +58,9 @@ function close() {
|
||||||
|
|
||||||
const isAnimating = ref(false);
|
const isAnimating = ref(false);
|
||||||
|
|
||||||
const linksRef = ref<typeof Links | null>(null);
|
const contextRef = ref<typeof Context | null>(null);
|
||||||
const nodes = computed<Record<string, LinkNode | undefined> | null>(
|
const nodes = computed<Record<string, FeatureNode | undefined> | null>(
|
||||||
() => linksRef.value?.nodes ?? null
|
() => contextRef.value?.nodes ?? null
|
||||||
);
|
);
|
||||||
|
|
||||||
defineExpose({ isOpen, nodes });
|
defineExpose({ isOpen, nodes });
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="branch" ref="node"></div>
|
<div class="node" ref="node"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RegisterLinkNodeInjectionKey, UnregisterLinkNodeInjectionKey } from "features/links";
|
import { RegisterNodeInjectionKey, UnregisterNodeInjectionKey } from "game/layers";
|
||||||
import { computed, inject, onUnmounted, ref, toRefs, unref, watch } from "vue";
|
import { computed, inject, onUnmounted, ref, toRefs, unref, watch } from "vue";
|
||||||
|
|
||||||
const _props = defineProps<{ id: string }>();
|
const _props = defineProps<{ id: string }>();
|
||||||
const props = toRefs(_props);
|
const props = toRefs(_props);
|
||||||
|
|
||||||
const register = inject(RegisterLinkNodeInjectionKey);
|
const register = inject(RegisterNodeInjectionKey);
|
||||||
const unregister = inject(UnregisterLinkNodeInjectionKey);
|
const unregister = inject(UnregisterNodeInjectionKey);
|
||||||
|
|
||||||
const node = ref<HTMLElement | null>(null);
|
const node = ref<HTMLElement | null>(null);
|
||||||
const parentNode = computed(() => node.value && node.value.parentElement);
|
const parentNode = computed(() => node.value && node.value.parentElement);
|
||||||
|
@ -30,7 +30,7 @@ if (register && unregister) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.branch {
|
.node {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: -10;
|
z-index: -10;
|
||||||
top: 0;
|
top: 0;
|
|
@ -18,7 +18,7 @@
|
||||||
>
|
>
|
||||||
<component v-if="component" :is="component" />
|
<component v-if="component" :is="component" />
|
||||||
<MarkNode :mark="unref(mark)" />
|
<MarkNode :mark="unref(mark)" />
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import { CoercableComponent, Visibility } from "features/feature";
|
||||||
import { computeOptionalComponent, processedPropType } from "util/vue";
|
import { computeOptionalComponent, processedPropType } from "util/vue";
|
||||||
import { defineComponent, StyleValue, toRefs, unref } from "vue";
|
import { defineComponent, StyleValue, toRefs, unref } from "vue";
|
||||||
import Tooltip from "components/Tooltip.vue";
|
import Tooltip from "components/Tooltip.vue";
|
||||||
import LinkNode from "components/links/LinkNode.vue";
|
import Node from "components/Node.vue";
|
||||||
import MarkNode from "components/MarkNode.vue";
|
import MarkNode from "components/MarkNode.vue";
|
||||||
import "components/common/features.css";
|
import "components/common/features.css";
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
LinkNode,
|
Node,
|
||||||
MarkNode,
|
MarkNode,
|
||||||
Tooltip
|
Tooltip
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<div class="fill" :style="[barStyle, unref(style) ?? {}, unref(fillStyle) ?? {}]" />
|
<div class="fill" :style="[barStyle, unref(style) ?? {}, unref(fillStyle) ?? {}]" />
|
||||||
</div>
|
</div>
|
||||||
<MarkNode :mark="unref(mark)" />
|
<MarkNode :mark="unref(mark)" />
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ import { CoercableComponent, Visibility } from "features/feature";
|
||||||
import Decimal, { DecimalSource } from "util/bignum";
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import { computeOptionalComponent, processedPropType, unwrapRef } from "util/vue";
|
import { computeOptionalComponent, processedPropType, unwrapRef } from "util/vue";
|
||||||
import { computed, CSSProperties, defineComponent, StyleValue, toRefs, unref } from "vue";
|
import { computed, CSSProperties, defineComponent, StyleValue, toRefs, unref } from "vue";
|
||||||
import LinkNode from "components/links/LinkNode.vue";
|
import Node from "components/Node.vue";
|
||||||
import MarkNode from "components/MarkNode.vue";
|
import MarkNode from "components/MarkNode.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -90,7 +90,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
MarkNode,
|
MarkNode,
|
||||||
LinkNode
|
Node
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { progress, width, height, direction, display } = toRefs(props);
|
const { progress, width, height, direction, display } = toRefs(props);
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { Unsubscribe } from "nanoevents";
|
import { Unsubscribe } from "nanoevents";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
import { Link } from "../links";
|
import { Link } from "../links/links";
|
||||||
|
|
||||||
export const BoardType = Symbol("Board");
|
export const BoardType = Symbol("Board");
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
</button>
|
</button>
|
||||||
<component v-if="unref(comp)" :is="unref(comp)" />
|
<component v-if="unref(comp)" :is="unref(comp)" />
|
||||||
<MarkNode :mark="unref(mark)" />
|
<MarkNode :mark="unref(mark)" />
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ import {
|
||||||
UnwrapRef,
|
UnwrapRef,
|
||||||
watchEffect
|
watchEffect
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import LinkNode from "components/links/LinkNode.vue";
|
import Node from "components/Node.vue";
|
||||||
import MarkNode from "components/MarkNode.vue";
|
import MarkNode from "components/MarkNode.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -91,7 +91,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
MarkNode,
|
MarkNode,
|
||||||
LinkNode
|
Node
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { active, maxed, canComplete, display } = toRefs(props);
|
const { active, maxed, canComplete, display } = toRefs(props);
|
||||||
|
|
|
@ -23,13 +23,13 @@
|
||||||
>
|
>
|
||||||
<component v-if="unref(comp)" :is="unref(comp)" />
|
<component v-if="unref(comp)" :is="unref(comp)" />
|
||||||
<MarkNode :mark="unref(mark)" />
|
<MarkNode :mark="unref(mark)" />
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import "components/common/features.css";
|
import "components/common/features.css";
|
||||||
import LinkNode from "components/links/LinkNode.vue";
|
import Node from "components/Node.vue";
|
||||||
import MarkNode from "components/MarkNode.vue";
|
import MarkNode from "components/MarkNode.vue";
|
||||||
import { GenericClickable } from "features/clickables/clickable";
|
import { GenericClickable } from "features/clickables/clickable";
|
||||||
import { jsx, StyleValue, Visibility } from "features/feature";
|
import { jsx, StyleValue, Visibility } from "features/feature";
|
||||||
|
@ -81,7 +81,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
LinkNode,
|
Node,
|
||||||
MarkNode
|
MarkNode
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
>
|
>
|
||||||
<div v-if="title"><component :is="titleComponent" /></div>
|
<div v-if="title"><component :is="titleComponent" /></div>
|
||||||
<component :is="component" style="white-space: pre-line" />
|
<component :is="component" style="white-space: pre-line" />
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "components/common/features.css";
|
import "components/common/features.css";
|
||||||
import LinkNode from "components/links/LinkNode.vue";
|
import Node from "components/Node.vue";
|
||||||
import { CoercableComponent, StyleValue, Visibility } from "features/feature";
|
import { CoercableComponent, StyleValue, Visibility } from "features/feature";
|
||||||
import {
|
import {
|
||||||
computeComponent,
|
computeComponent,
|
||||||
|
@ -58,7 +58,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
LinkNode
|
Node
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { onClick, onHold, title, display } = toRefs(props);
|
const { onClick, onHold, title, display } = toRefs(props);
|
||||||
|
|
|
@ -24,12 +24,12 @@
|
||||||
<component :is="bodyComponent" :style="unref(bodyStyle)" />
|
<component :is="bodyComponent" :style="unref(bodyStyle)" />
|
||||||
</div>
|
</div>
|
||||||
</CollapseTransition>
|
</CollapseTransition>
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import LinkNode from "components/links/LinkNode.vue";
|
import Node from "components/Node.vue";
|
||||||
import themes from "data/themes";
|
import themes from "data/themes";
|
||||||
import { CoercableComponent, Visibility } from "features/feature";
|
import { CoercableComponent, Visibility } from "features/feature";
|
||||||
import settings from "game/settings";
|
import settings from "game/settings";
|
||||||
|
@ -66,7 +66,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
LinkNode,
|
Node,
|
||||||
CollapseTransition
|
CollapseTransition
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { Position } from "game/layers";
|
|
||||||
import { InjectionKey, Ref, SVGAttributes } from "vue";
|
|
||||||
|
|
||||||
export interface LinkNode {
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
rect?: DOMRect;
|
|
||||||
element: HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Link extends SVGAttributes {
|
|
||||||
startNode: { id: string };
|
|
||||||
endNode: { id: string };
|
|
||||||
offsetStart?: Position;
|
|
||||||
offsetEnd?: Position;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RegisterLinkNodeInjectionKey: InjectionKey<
|
|
||||||
(id: string, element: HTMLElement) => void
|
|
||||||
> = Symbol("RegisterLinkNode");
|
|
||||||
export const UnregisterLinkNodeInjectionKey: InjectionKey<(id: string) => void> =
|
|
||||||
Symbol("UnregisterLinkNode");
|
|
||||||
export const NodesInjectionKey: InjectionKey<Ref<Record<string, LinkNode | undefined>>> =
|
|
||||||
Symbol("Nodes");
|
|
|
@ -11,13 +11,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Link, LinkNode } from "features/links";
|
import { Link } from "features/links/links";
|
||||||
|
import { FeatureNode } from "game/layers";
|
||||||
import { computed, toRefs, unref } from "vue";
|
import { computed, toRefs, unref } from "vue";
|
||||||
|
|
||||||
const _props = defineProps<{
|
const _props = defineProps<{
|
||||||
link: Link;
|
link: Link;
|
||||||
startNode: LinkNode;
|
startNode: FeatureNode;
|
||||||
endNode: LinkNode;
|
endNode: FeatureNode;
|
||||||
}>();
|
}>();
|
||||||
const props = toRefs(_props);
|
const props = toRefs(_props);
|
||||||
|
|
37
src/features/links/Links.vue
Normal file
37
src/features/links/Links.vue
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<svg v-if="validLinks" v-bind="$attrs">
|
||||||
|
<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 } from "features/links/links";
|
||||||
|
import { NodesInjectionKey } from "game/layers";
|
||||||
|
import { computed, inject, toRef } from "vue";
|
||||||
|
import LinkVue from "./Link.vue";
|
||||||
|
|
||||||
|
const _props = defineProps<{ links?: Link[] }>();
|
||||||
|
const links = toRef(_props, "links");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const nodes = inject(NodesInjectionKey)!;
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}) ?? []
|
||||||
|
);
|
||||||
|
</script>
|
65
src/features/links/links.ts
Normal file
65
src/features/links/links.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import LinksComponent from "./Links.vue";
|
||||||
|
import { Component, GatherProps, Replace } from "features/feature";
|
||||||
|
import { Position } from "game/layers";
|
||||||
|
import {
|
||||||
|
Computable,
|
||||||
|
GetComputableType,
|
||||||
|
processComputable,
|
||||||
|
ProcessedComputable
|
||||||
|
} from "util/computed";
|
||||||
|
import { createLazyProxy } from "util/proxies";
|
||||||
|
import { SVGAttributes } from "vue";
|
||||||
|
|
||||||
|
export const LinksType = Symbol("Links");
|
||||||
|
|
||||||
|
export interface Link extends SVGAttributes {
|
||||||
|
startNode: { id: string };
|
||||||
|
endNode: { id: string };
|
||||||
|
offsetStart?: Position;
|
||||||
|
offsetEnd?: Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinksOptions {
|
||||||
|
links?: Computable<Link[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseLinks {
|
||||||
|
type: typeof LinksType;
|
||||||
|
[Component]: typeof LinksComponent;
|
||||||
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Links<T extends LinksOptions> = Replace<
|
||||||
|
T & BaseLinks,
|
||||||
|
{
|
||||||
|
links: GetComputableType<T["links"]>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type GenericLinks = Replace<
|
||||||
|
Links<LinksOptions>,
|
||||||
|
{
|
||||||
|
links: ProcessedComputable<Link[]>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function createLinks<T extends LinksOptions>(
|
||||||
|
optionsFunc: (() => T) & ThisType<Links<T>>
|
||||||
|
): Links<T> {
|
||||||
|
return createLazyProxy(() => {
|
||||||
|
const links: T & Partial<BaseLinks> = optionsFunc();
|
||||||
|
links.type = LinksType;
|
||||||
|
links[Component] = LinksComponent;
|
||||||
|
|
||||||
|
processComputable(links as T, "links");
|
||||||
|
|
||||||
|
links[GatherProps] = function (this: GenericLinks) {
|
||||||
|
const { links } = this;
|
||||||
|
return {
|
||||||
|
links
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return links as unknown as Links<T>;
|
||||||
|
});
|
||||||
|
}
|
|
@ -10,7 +10,7 @@
|
||||||
:class="{ feature: true, milestone: true, done: unref(earned), ...unref(classes) }"
|
:class="{ feature: true, milestone: true, done: unref(earned), ...unref(classes) }"
|
||||||
>
|
>
|
||||||
<component :is="unref(comp)" />
|
<component :is="unref(comp)" />
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import { jsx, StyleValue, Visibility } from "features/feature";
|
||||||
import { GenericMilestone } from "features/milestones/milestone";
|
import { GenericMilestone } from "features/milestones/milestone";
|
||||||
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/vue";
|
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/vue";
|
||||||
import { Component, defineComponent, shallowRef, toRefs, unref, UnwrapRef, watchEffect } from "vue";
|
import { Component, defineComponent, shallowRef, toRefs, unref, UnwrapRef, watchEffect } from "vue";
|
||||||
import LinkNode from "../../components/links/LinkNode.vue";
|
import Node from "../../components/Node.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
@ -48,7 +48,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
LinkNode
|
Node
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { display } = toRefs(props);
|
const { display } = toRefs(props);
|
||||||
|
|
|
@ -25,14 +25,16 @@
|
||||||
small
|
small
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
<Links v-if="branches" :links="unref(branches)" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "components/common/table.css";
|
import "components/common/table.css";
|
||||||
import { GenericTreeNode } from "features/trees/tree";
|
import { GenericTreeNode, TreeBranch } from "features/trees/tree";
|
||||||
import { processedPropType } from "util/vue";
|
import { processedPropType } from "util/vue";
|
||||||
import { defineComponent, unref } from "vue";
|
import { defineComponent, unref } from "vue";
|
||||||
import TreeNode from "./TreeNode.vue";
|
import TreeNode from "./TreeNode.vue";
|
||||||
|
import Links from "features/links/Links.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
@ -41,9 +43,10 @@ export default defineComponent({
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
leftSideNodes: processedPropType<GenericTreeNode[]>(Array),
|
leftSideNodes: processedPropType<GenericTreeNode[]>(Array),
|
||||||
rightSideNodes: processedPropType<GenericTreeNode[]>(Array)
|
rightSideNodes: processedPropType<GenericTreeNode[]>(Array),
|
||||||
|
branches: processedPropType<TreeBranch[]>(Array)
|
||||||
},
|
},
|
||||||
components: { TreeNode },
|
components: { TreeNode, Links },
|
||||||
setup() {
|
setup() {
|
||||||
function gatherNodeProps(node: GenericTreeNode) {
|
function gatherNodeProps(node: GenericTreeNode) {
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -33,12 +33,12 @@
|
||||||
<component :is="unref(comp)" />
|
<component :is="unref(comp)" />
|
||||||
</button>
|
</button>
|
||||||
<MarkNode :mark="unref(mark)" />
|
<MarkNode :mark="unref(mark)" />
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import LinkNode from "components/links/LinkNode.vue";
|
import Node from "components/Node.vue";
|
||||||
import MarkNode from "components/MarkNode.vue";
|
import MarkNode from "components/MarkNode.vue";
|
||||||
import TooltipVue from "components/Tooltip.vue";
|
import TooltipVue from "components/Tooltip.vue";
|
||||||
import { CoercableComponent, StyleValue, Visibility } from "features/feature";
|
import { CoercableComponent, StyleValue, Visibility } from "features/feature";
|
||||||
|
@ -94,7 +94,7 @@ export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
Tooltip: TooltipVue,
|
Tooltip: TooltipVue,
|
||||||
MarkNode,
|
MarkNode,
|
||||||
LinkNode
|
Node
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { tooltip, forceTooltip, onClick, onHold, display } = toRefs(props);
|
const { tooltip, forceTooltip, onClick, onHold, display } = toRefs(props);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
StyleValue,
|
StyleValue,
|
||||||
Visibility
|
Visibility
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
import { Link } from "features/links";
|
import { Link } from "features/links/links";
|
||||||
import { GenericReset } from "features/reset";
|
import { GenericReset } from "features/reset";
|
||||||
import { displayResource, Resource } from "features/resources/resource";
|
import { displayResource, Resource } from "features/resources/resource";
|
||||||
import { Tooltip } from "features/tooltip";
|
import { Tooltip } from "features/tooltip";
|
||||||
|
@ -199,8 +199,8 @@ export function createTree<T extends TreeOptions>(
|
||||||
processComputable(tree as T, "branches");
|
processComputable(tree as T, "branches");
|
||||||
|
|
||||||
tree[GatherProps] = function (this: GenericTree) {
|
tree[GatherProps] = function (this: GenericTree) {
|
||||||
const { nodes, leftSideNodes, rightSideNodes } = this;
|
const { nodes, leftSideNodes, rightSideNodes, branches } = this;
|
||||||
return { nodes, leftSideNodes, rightSideNodes };
|
return { nodes, leftSideNodes, rightSideNodes, branches };
|
||||||
};
|
};
|
||||||
|
|
||||||
return tree as unknown as Tree<T>;
|
return tree as unknown as Tree<T>;
|
||||||
|
|
|
@ -20,13 +20,13 @@
|
||||||
>
|
>
|
||||||
<component v-if="unref(component)" :is="unref(component)" />
|
<component v-if="unref(component)" :is="unref(component)" />
|
||||||
<MarkNode :mark="unref(mark)" />
|
<MarkNode :mark="unref(mark)" />
|
||||||
<LinkNode :id="id" />
|
<Node :id="id" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import "components/common/features.css";
|
import "components/common/features.css";
|
||||||
import LinkNode from "components/links/LinkNode.vue";
|
import Node from "components/Node.vue";
|
||||||
import MarkNode from "components/MarkNode.vue";
|
import MarkNode from "components/MarkNode.vue";
|
||||||
import { jsx, StyleValue, Visibility } from "features/feature";
|
import { jsx, StyleValue, Visibility } from "features/feature";
|
||||||
import { displayResource, Resource } from "features/resources/resource";
|
import { displayResource, Resource } from "features/resources/resource";
|
||||||
|
@ -77,7 +77,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
LinkNode,
|
Node,
|
||||||
MarkNode
|
MarkNode
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue
|
StyleValue
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
import { Link, LinkNode } from "features/links";
|
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -17,11 +16,25 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { createNanoEvents, Emitter } from "nanoevents";
|
import { createNanoEvents, Emitter } from "nanoevents";
|
||||||
import { Ref, ref, unref } from "vue";
|
import { InjectionKey, Ref, ref, unref } from "vue";
|
||||||
import { globalBus } from "./events";
|
import { globalBus } from "./events";
|
||||||
import { persistent, PersistentRef } from "./persistence";
|
import { persistent, PersistentRef } from "./persistence";
|
||||||
import player from "./player";
|
import player from "./player";
|
||||||
|
|
||||||
|
export interface FeatureNode {
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
rect?: DOMRect;
|
||||||
|
element: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegisterNodeInjectionKey: InjectionKey<(id: string, element: HTMLElement) => void> =
|
||||||
|
Symbol("RegisterNode");
|
||||||
|
export const UnregisterNodeInjectionKey: InjectionKey<(id: string) => void> =
|
||||||
|
Symbol("UnregisterNode");
|
||||||
|
export const NodesInjectionKey: InjectionKey<Ref<Record<string, FeatureNode | undefined>>> =
|
||||||
|
Symbol("Nodes");
|
||||||
|
|
||||||
export interface LayerEvents {
|
export interface LayerEvents {
|
||||||
// Generation
|
// Generation
|
||||||
preUpdate: (diff: number) => void;
|
preUpdate: (diff: number) => void;
|
||||||
|
@ -55,7 +68,6 @@ export interface LayerOptions {
|
||||||
minimizable?: Computable<boolean>;
|
minimizable?: Computable<boolean>;
|
||||||
forceHideGoBack?: Computable<boolean>;
|
forceHideGoBack?: Computable<boolean>;
|
||||||
minWidth?: Computable<number>;
|
minWidth?: Computable<number>;
|
||||||
links?: Computable<Link[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseLayer {
|
export interface BaseLayer {
|
||||||
|
@ -63,7 +75,7 @@ export interface BaseLayer {
|
||||||
emitter: Emitter<LayerEvents>;
|
emitter: Emitter<LayerEvents>;
|
||||||
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
||||||
emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
|
emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
|
||||||
nodes: Ref<Record<string, LinkNode | undefined>>;
|
nodes: Ref<Record<string, FeatureNode | undefined>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Layer<T extends LayerOptions> = Replace<
|
export type Layer<T extends LayerOptions> = Replace<
|
||||||
|
@ -77,7 +89,6 @@ export type Layer<T extends LayerOptions> = Replace<
|
||||||
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
|
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
|
||||||
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
|
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
|
||||||
forceHideGoBack: GetComputableType<T["forceHideGoBack"]>;
|
forceHideGoBack: GetComputableType<T["forceHideGoBack"]>;
|
||||||
links: GetComputableType<T["links"]>;
|
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
@ -112,7 +123,6 @@ export function createLayer<T extends LayerOptions>(
|
||||||
setDefault(layer, "minWidth", 600);
|
setDefault(layer, "minWidth", 600);
|
||||||
processComputable(layer as T, "minimizable");
|
processComputable(layer as T, "minimizable");
|
||||||
setDefault(layer, "minimizable", true);
|
setDefault(layer, "minimizable", true);
|
||||||
processComputable(layer as T, "links");
|
|
||||||
|
|
||||||
return layer as unknown as Layer<T>;
|
return layer as unknown as Layer<T>;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue