mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
Co-authored-by: Jan Müller <janmueller3698@gmail.com> Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
208 lines
5.3 KiB
Vue
208 lines
5.3 KiB
Vue
<script setup lang="ts">
|
|
import type { ResizeContext } from 'd3-graph-controller'
|
|
import { GraphController, Markers, PositionInitializers, defineGraphConfig } from 'd3-graph-controller'
|
|
import type { Selection } from 'd3-selection'
|
|
import type { ModuleGraph, ModuleGraphController, ModuleLink, ModuleNode, ModuleType } from '~/composables/module-graph'
|
|
|
|
const props = defineProps<{
|
|
graph: ModuleGraph
|
|
}>()
|
|
|
|
const { graph } = toRefs(props)
|
|
|
|
const el = ref<HTMLDivElement>()
|
|
|
|
const modalShow = ref(false)
|
|
const selectedModule = ref<string | null>()
|
|
const controller = ref<ModuleGraphController | undefined>()
|
|
|
|
onMounted(() => {
|
|
resetGraphController()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
controller.value?.shutdown()
|
|
})
|
|
|
|
watch(graph, resetGraphController)
|
|
|
|
function setFilter(name: ModuleType, value: boolean) {
|
|
controller.value?.filterNodesByType(value, name)
|
|
}
|
|
|
|
function setSelectedModule(id: string) {
|
|
selectedModule.value = id
|
|
modalShow.value = true
|
|
}
|
|
|
|
function resetGraphController() {
|
|
controller.value?.shutdown()
|
|
if (!graph.value || !el.value)
|
|
return
|
|
|
|
controller.value = new GraphController(
|
|
el.value!,
|
|
graph.value,
|
|
// See https://graph-controller.yeger.eu/config/ for more options
|
|
defineGraphConfig<ModuleType, ModuleNode, ModuleLink>({
|
|
nodeRadius: 10,
|
|
autoResize: true,
|
|
simulation: {
|
|
alphas: {
|
|
initialize: 1,
|
|
resize: ({ newHeight, newWidth }: ResizeContext) => {
|
|
const willBeHidden = newHeight === 0 && newWidth === 0
|
|
if (willBeHidden)
|
|
return 0
|
|
return 0.25
|
|
},
|
|
},
|
|
forces: {
|
|
collision: {
|
|
radiusMultiplier: 10,
|
|
},
|
|
link: {
|
|
length: 240,
|
|
},
|
|
},
|
|
},
|
|
marker: Markers.Arrow(2),
|
|
modifiers: {
|
|
node: bindOnClick,
|
|
},
|
|
positionInitializer: graph.value.nodes.length > 1
|
|
? PositionInitializers.Randomized
|
|
: PositionInitializers.Centered,
|
|
zoom: {
|
|
min: 0.5,
|
|
max: 2,
|
|
},
|
|
}),
|
|
)
|
|
}
|
|
|
|
function bindOnClick(selection: Selection<SVGCircleElement, ModuleNode, SVGGElement, undefined>) {
|
|
// Only trigger on left-click and primary touch
|
|
const isValidClick = (event: PointerEvent) => event.button === 0
|
|
|
|
let px = 0
|
|
let py = 0
|
|
let pt = 0
|
|
|
|
selection
|
|
.on('pointerdown', (event: PointerEvent, node) => {
|
|
if (node.type === 'external')
|
|
return
|
|
if (!node.x || !node.y || !isValidClick(event))
|
|
return
|
|
px = node.x
|
|
py = node.y
|
|
pt = Date.now()
|
|
})
|
|
.on('pointerup', (event: PointerEvent, node: ModuleNode) => {
|
|
if (node.type === 'external')
|
|
return
|
|
if (!node.x || !node.y || !isValidClick(event))
|
|
return
|
|
if (Date.now() - pt > 500)
|
|
return
|
|
const dx = node.x - px
|
|
const dy = node.y - py
|
|
if (dx ** 2 + dy ** 2 < 100)
|
|
setSelectedModule(node.id)
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div h-full min-h-75 flex-1 overflow="hidden">
|
|
<div>
|
|
<div flex items-center gap-4 px-3 py-2>
|
|
<div v-for="node of controller?.nodeTypes.sort()" :key="node" flex="~ gap-1" items-center select-none>
|
|
<input
|
|
:id="`type-${node}`"
|
|
type="checkbox"
|
|
:checked="controller?.nodeTypeFilter.includes(node)"
|
|
@change="setFilter(node, ($event as any).target.checked)"
|
|
>
|
|
<label
|
|
font-light
|
|
text-sm
|
|
ws-nowrap
|
|
overflow-hidden
|
|
capitalize
|
|
truncate :for="`type-${node}`"
|
|
border-b-2
|
|
:style="{ 'border-color': `var(--color-node-${node})`}"
|
|
>
|
|
{{ node }} Modules
|
|
</label>
|
|
</div>
|
|
<div flex-auto />
|
|
<div>
|
|
<IconButton
|
|
icon="i-carbon-reset"
|
|
:onclick="resetGraphController"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div ref="el" />
|
|
<Modal v-model="modalShow" direction="right">
|
|
<template v-if="selectedModule">
|
|
<Suspense>
|
|
<ModuleTransformResultView :id="selectedModule" @close="modalShow=false" />
|
|
</Suspense>
|
|
</template>
|
|
</Modal>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
:root {
|
|
--color-link-label: var(--color-text);
|
|
--color-link: #ddd;
|
|
--color-node-external: #c0ad79;
|
|
--color-node-inline: #8bc4a0;
|
|
--color-node-root: #6e9aa5;
|
|
--color-node-label: var(--color-text);
|
|
--color-node-stroke: var(--color-text);
|
|
}
|
|
|
|
html.dark {
|
|
--color-text: #fff;
|
|
--color-link: #333;
|
|
--color-node-external: #857a40;
|
|
--color-node-inline: #468b60;
|
|
--color-node-root: #467d8b;
|
|
}
|
|
|
|
.graph {
|
|
/* The graph container is offset in its parent. Thus we can't use the default 100% height and have to subtract the offset. */
|
|
height: calc(100% - 39px) !important;
|
|
}
|
|
|
|
.graph .node {
|
|
stroke-width: 2px;
|
|
stroke-opacity: 0.5;
|
|
}
|
|
|
|
.graph .link {
|
|
stroke-width: 2px;
|
|
}
|
|
|
|
.graph .node:hover:not(.focused) {
|
|
filter: none !important;
|
|
}
|
|
|
|
.graph .node__label {
|
|
transform: translateY(20px);
|
|
font-weight: 100;
|
|
filter: brightness(0.5);
|
|
}
|
|
|
|
html.dark .graph .node__label {
|
|
filter: brightness(1.2);
|
|
}
|
|
</style>
|