Separated Links into a feature and Context

This commit is contained in:
thepaperpilot 2022-03-20 13:57:45 -05:00
parent b60d170657
commit 5ce8195779
21 changed files with 188 additions and 125 deletions

View file

@ -1,35 +1,21 @@
<template>
<slot />
<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>
<script setup lang="ts">
import {
Link,
LinkNode,
RegisterNodeInjectionKey,
UnregisterNodeInjectionKey,
NodesInjectionKey,
RegisterLinkNodeInjectionKey,
UnregisterLinkNodeInjectionKey
} from "features/links";
import { computed, nextTick, onMounted, provide, ref, toRef } from "vue";
import LinkVue from "./Link.vue";
const _props = defineProps<{ links?: Link[] }>();
const links = toRef(_props, "links");
FeatureNode
} from "game/layers";
import { nextTick, onMounted, provide, ref } from "vue";
const observer = new MutationObserver(updateNodes);
const resizeObserver = new ResizeObserver(updateNodes);
const nodes = ref<Record<string, LinkNode | undefined>>({});
const nodes = ref<Record<string, FeatureNode | undefined>>({});
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 = {
attributes: true,
childList: true,
subtree: false
};
provide(RegisterLinkNodeInjectionKey, (id, element) => {
provide(RegisterNodeInjectionKey, (id, element) => {
nodes.value[id] = { element };
observer.observe(element, observerOptions);
nextTick(() => {
@ -71,7 +44,7 @@ provide(RegisterLinkNodeInjectionKey, (id, element) => {
}
});
});
provide(UnregisterLinkNodeInjectionKey, id => {
provide(UnregisterNodeInjectionKey, id => {
nodes.value[id] = undefined;
});
provide(NodesInjectionKey, nodes);

View file

@ -10,9 +10,9 @@
:class="[{ showGoBack }, unref(classes)]"
v-else
>
<Links :links="unref(links)" ref="linksRef">
<Context ref="contextRef">
<component :is="component" />
</Links>
</Context>
</div>
<button v-if="unref(minimizable)" class="minimize" @click="minimized.value = true">
@ -21,17 +21,17 @@
</template>
<script lang="ts">
import Links from "components/links/Links.vue";
import projInfo from "data/projInfo.json";
import { CoercableComponent, StyleValue } from "features/feature";
import { Link, LinkNode } from "features/links";
import { FeatureNode } from "game/layers";
import { PersistentRef } from "game/persistence";
import player from "game/player";
import { computeComponent, processedPropType, wrapRef } from "util/vue";
import { computed, defineComponent, nextTick, PropType, Ref, ref, toRefs, unref, watch } from "vue";
import Context from "./Context.vue";
export default defineComponent({
components: { Links },
components: { Context },
props: {
index: {
type: Number,
@ -60,10 +60,9 @@ export default defineComponent({
color: processedPropType<string>(String),
style: processedPropType<StyleValue>(String, Object, Array),
classes: processedPropType<Record<string, boolean>>(Object),
links: processedPropType<Link[]>(Array),
minimizable: processedPropType<boolean>(Boolean),
nodes: {
type: Object as PropType<Ref<Record<string, LinkNode | undefined>>>,
type: Object as PropType<Ref<Record<string, FeatureNode | undefined>>>,
required: true
}
},
@ -84,9 +83,9 @@ export default defineComponent({
updateTab(minimized, minWidth)
);
const linksRef = ref<typeof Links | null>(null);
const contextRef = ref<typeof Context | null>(null);
watch(
() => linksRef.value?.nodes,
() => contextRef.value?.nodes,
nodes => {
if (nodes) {
props.nodes.value = nodes;
@ -116,7 +115,7 @@ export default defineComponent({
return {
component,
showGoBack,
linksRef,
contextRef,
unref,
goBack
};

View file

@ -17,9 +17,9 @@
<slot name="header" :shown="isOpen"> default header </slot>
</div>
<div class="modal-body">
<Links :links="links" ref="linksRef">
<Context ref="contextRef">
<slot name="body" :shown="isOpen"> default body </slot>
</Links>
</Context>
</div>
<div class="modal-footer">
<slot name="footer" :shown="isOpen">
@ -39,13 +39,12 @@
</template>
<script setup lang="ts">
import { Link, LinkNode } from "features/links";
import { FeatureNode } from "game/layers";
import { computed, ref, toRefs } from "vue";
import Links from "./links/Links.vue";
import Context from "./Context.vue";
const _props = defineProps<{
modelValue: boolean;
links?: Link[];
}>();
const props = toRefs(_props);
const emit = defineEmits<{
@ -59,9 +58,9 @@ function close() {
const isAnimating = ref(false);
const linksRef = ref<typeof Links | null>(null);
const nodes = computed<Record<string, LinkNode | undefined> | null>(
() => linksRef.value?.nodes ?? null
const contextRef = ref<typeof Context | null>(null);
const nodes = computed<Record<string, FeatureNode | undefined> | null>(
() => contextRef.value?.nodes ?? null
);
defineExpose({ isOpen, nodes });

View file

@ -1,16 +1,16 @@
<template>
<div class="branch" ref="node"></div>
<div class="node" ref="node"></div>
</template>
<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";
const _props = defineProps<{ id: string }>();
const props = toRefs(_props);
const register = inject(RegisterLinkNodeInjectionKey);
const unregister = inject(UnregisterLinkNodeInjectionKey);
const register = inject(RegisterNodeInjectionKey);
const unregister = inject(UnregisterNodeInjectionKey);
const node = ref<HTMLElement | null>(null);
const parentNode = computed(() => node.value && node.value.parentElement);
@ -30,7 +30,7 @@ if (register && unregister) {
</script>
<style scoped>
.branch {
.node {
position: absolute;
z-index: -10;
top: 0;

View file

@ -18,7 +18,7 @@
>
<component v-if="component" :is="component" />
<MarkNode :mark="unref(mark)" />
<LinkNode :id="id" />
<Node :id="id" />
</div>
</template>
@ -27,7 +27,7 @@ import { CoercableComponent, Visibility } from "features/feature";
import { computeOptionalComponent, processedPropType } from "util/vue";
import { defineComponent, StyleValue, toRefs, unref } from "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 "components/common/features.css";
@ -52,7 +52,7 @@ export default defineComponent({
}
},
components: {
LinkNode,
Node,
MarkNode,
Tooltip
},

View file

@ -40,7 +40,7 @@
<div class="fill" :style="[barStyle, unref(style) ?? {}, unref(fillStyle) ?? {}]" />
</div>
<MarkNode :mark="unref(mark)" />
<LinkNode :id="id" />
<Node :id="id" />
</div>
</template>
@ -50,7 +50,7 @@ import { CoercableComponent, Visibility } from "features/feature";
import Decimal, { DecimalSource } from "util/bignum";
import { computeOptionalComponent, processedPropType, unwrapRef } from "util/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";
export default defineComponent({
@ -90,7 +90,7 @@ export default defineComponent({
},
components: {
MarkNode,
LinkNode
Node
},
setup(props) {
const { progress, width, height, direction, display } = toRefs(props);

View file

@ -22,7 +22,7 @@ import {
import { createLazyProxy } from "util/proxies";
import { Unsubscribe } from "nanoevents";
import { computed, Ref, unref } from "vue";
import { Link } from "../links";
import { Link } from "../links/links";
export const BoardType = Symbol("Board");

View file

@ -22,7 +22,7 @@
</button>
<component v-if="unref(comp)" :is="unref(comp)" />
<MarkNode :mark="unref(mark)" />
<LinkNode :id="id" />
<Node :id="id" />
</div>
</template>
@ -43,7 +43,7 @@ import {
UnwrapRef,
watchEffect
} from "vue";
import LinkNode from "components/links/LinkNode.vue";
import Node from "components/Node.vue";
import MarkNode from "components/MarkNode.vue";
export default defineComponent({
@ -91,7 +91,7 @@ export default defineComponent({
},
components: {
MarkNode,
LinkNode
Node
},
setup(props) {
const { active, maxed, canComplete, display } = toRefs(props);

View file

@ -23,13 +23,13 @@
>
<component v-if="unref(comp)" :is="unref(comp)" />
<MarkNode :mark="unref(mark)" />
<LinkNode :id="id" />
<Node :id="id" />
</button>
</template>
<script lang="tsx">
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 { GenericClickable } from "features/clickables/clickable";
import { jsx, StyleValue, Visibility } from "features/feature";
@ -81,7 +81,7 @@ export default defineComponent({
}
},
components: {
LinkNode,
Node,
MarkNode
},
setup(props) {

View file

@ -18,13 +18,13 @@
>
<div v-if="title"><component :is="titleComponent" /></div>
<component :is="component" style="white-space: pre-line" />
<LinkNode :id="id" />
<Node :id="id" />
</button>
</template>
<script lang="ts">
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 {
computeComponent,
@ -58,7 +58,7 @@ export default defineComponent({
}
},
components: {
LinkNode
Node
},
setup(props) {
const { onClick, onHold, title, display } = toRefs(props);

View file

@ -24,12 +24,12 @@
<component :is="bodyComponent" :style="unref(bodyStyle)" />
</div>
</CollapseTransition>
<LinkNode :id="id" />
<Node :id="id" />
</div>
</template>
<script lang="ts">
import LinkNode from "components/links/LinkNode.vue";
import Node from "components/Node.vue";
import themes from "data/themes";
import { CoercableComponent, Visibility } from "features/feature";
import settings from "game/settings";
@ -66,7 +66,7 @@ export default defineComponent({
}
},
components: {
LinkNode,
Node,
CollapseTransition
},
setup(props) {

View file

@ -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");

View file

@ -11,13 +11,14 @@
</template>
<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";
const _props = defineProps<{
link: Link;
startNode: LinkNode;
endNode: LinkNode;
startNode: FeatureNode;
endNode: FeatureNode;
}>();
const props = toRefs(_props);

View 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>

View 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>;
});
}

View file

@ -10,7 +10,7 @@
:class="{ feature: true, milestone: true, done: unref(earned), ...unref(classes) }"
>
<component :is="unref(comp)" />
<LinkNode :id="id" />
<Node :id="id" />
</div>
</template>
@ -20,7 +20,7 @@ import { jsx, StyleValue, Visibility } from "features/feature";
import { GenericMilestone } from "features/milestones/milestone";
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/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({
props: {
@ -48,7 +48,7 @@ export default defineComponent({
}
},
components: {
LinkNode
Node
},
setup(props) {
const { display } = toRefs(props);

View file

@ -25,14 +25,16 @@
small
/>
</span>
<Links v-if="branches" :links="unref(branches)" />
</template>
<script lang="ts">
import "components/common/table.css";
import { GenericTreeNode } from "features/trees/tree";
import { GenericTreeNode, TreeBranch } from "features/trees/tree";
import { processedPropType } from "util/vue";
import { defineComponent, unref } from "vue";
import TreeNode from "./TreeNode.vue";
import Links from "features/links/Links.vue";
export default defineComponent({
props: {
@ -41,9 +43,10 @@ export default defineComponent({
required: true
},
leftSideNodes: processedPropType<GenericTreeNode[]>(Array),
rightSideNodes: processedPropType<GenericTreeNode[]>(Array)
rightSideNodes: processedPropType<GenericTreeNode[]>(Array),
branches: processedPropType<TreeBranch[]>(Array)
},
components: { TreeNode },
components: { TreeNode, Links },
setup() {
function gatherNodeProps(node: GenericTreeNode) {
const {

View file

@ -33,12 +33,12 @@
<component :is="unref(comp)" />
</button>
<MarkNode :mark="unref(mark)" />
<LinkNode :id="id" />
<Node :id="id" />
</Tooltip>
</template>
<script lang="ts">
import LinkNode from "components/links/LinkNode.vue";
import Node from "components/Node.vue";
import MarkNode from "components/MarkNode.vue";
import TooltipVue from "components/Tooltip.vue";
import { CoercableComponent, StyleValue, Visibility } from "features/feature";
@ -94,7 +94,7 @@ export default defineComponent({
components: {
Tooltip: TooltipVue,
MarkNode,
LinkNode
Node
},
setup(props) {
const { tooltip, forceTooltip, onClick, onHold, display } = toRefs(props);

View file

@ -8,7 +8,7 @@ import {
StyleValue,
Visibility
} from "features/feature";
import { Link } from "features/links";
import { Link } from "features/links/links";
import { GenericReset } from "features/reset";
import { displayResource, Resource } from "features/resources/resource";
import { Tooltip } from "features/tooltip";
@ -199,8 +199,8 @@ export function createTree<T extends TreeOptions>(
processComputable(tree as T, "branches");
tree[GatherProps] = function (this: GenericTree) {
const { nodes, leftSideNodes, rightSideNodes } = this;
return { nodes, leftSideNodes, rightSideNodes };
const { nodes, leftSideNodes, rightSideNodes, branches } = this;
return { nodes, leftSideNodes, rightSideNodes, branches };
};
return tree as unknown as Tree<T>;

View file

@ -20,13 +20,13 @@
>
<component v-if="unref(component)" :is="unref(component)" />
<MarkNode :mark="unref(mark)" />
<LinkNode :id="id" />
<Node :id="id" />
</button>
</template>
<script lang="tsx">
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 { jsx, StyleValue, Visibility } from "features/feature";
import { displayResource, Resource } from "features/resources/resource";
@ -77,7 +77,7 @@ export default defineComponent({
}
},
components: {
LinkNode,
Node,
MarkNode
},
setup(props) {

View file

@ -7,7 +7,6 @@ import {
setDefault,
StyleValue
} from "features/feature";
import { Link, LinkNode } from "features/links";
import {
Computable,
GetComputableType,
@ -17,11 +16,25 @@ import {
} from "util/computed";
import { createLazyProxy } from "util/proxies";
import { createNanoEvents, Emitter } from "nanoevents";
import { Ref, ref, unref } from "vue";
import { InjectionKey, Ref, ref, unref } from "vue";
import { globalBus } from "./events";
import { persistent, PersistentRef } from "./persistence";
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 {
// Generation
preUpdate: (diff: number) => void;
@ -55,7 +68,6 @@ export interface LayerOptions {
minimizable?: Computable<boolean>;
forceHideGoBack?: Computable<boolean>;
minWidth?: Computable<number>;
links?: Computable<Link[]>;
}
export interface BaseLayer {
@ -63,7 +75,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>>;
nodes: Ref<Record<string, FeatureNode | undefined>>;
}
export type Layer<T extends LayerOptions> = Replace<
@ -77,7 +89,6 @@ export type Layer<T extends LayerOptions> = Replace<
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
forceHideGoBack: GetComputableType<T["forceHideGoBack"]>;
links: GetComputableType<T["links"]>;
}
>;
@ -112,7 +123,6 @@ export function createLayer<T extends LayerOptions>(
setDefault(layer, "minWidth", 600);
processComputable(layer as T, "minimizable");
setDefault(layer, "minimizable", true);
processComputable(layer as T, "links");
return layer as unknown as Layer<T>;
});