vitest/packages/ui/client/components/views/ViewModuleGraph.vue
yoho b8f34eb8ca
feat: add html reporter based on Vitest UI (#2444)
* feat: vitest html report

* chore: copy ui to vitest dist

* feat: report copy ui dist

* feat: ui report version builder

* fix: copy file

* chore: remove

* feat: html metadata path

* feat: add declare

* feat: static file client

* feat: mock rpc

* fix: mock error

* chore: update meta

* chore: compress json

* chore: comment

* chore: update merge config

* chore: lock

* feat: remove all control command from ui

* chore: remove

* feat: report command

* feat: version tag

* chore: reset lock file

* feat: remove hooks

* chore: update

* fix: runningPromise

* chore: update debug mode and disable click on transform

* docs: report

* chore: remove the version mark

* feat: report

* fix: lint

* chore: copy ui from @vitest/ui

* chore: export report from ui

* chore: update

* fix: lint

* docs: ui html report

* feat: ensurePackageInstalled

* update

* feat: more info

* chore: improve documentation and tests

* chore: fix UI bundle size

* chore: ui tests

* perf: remove output report using the global variable to judge report mode

* chore: update

* fix: build

* fix: report

* fix: parse

* chore: fix html reporters test

* chore: don't store config in html reporter test

* chore: update ui docs

* feat: log

* chore: fix tests

* test: fix html reporter tests

* docs add vitest fo UI reporter

Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>
2022-12-19 13:23:22 +03:00

218 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 { isReport } from '~/composables/client'
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>()
watchEffect(() => {
if (modalShow.value === false)
setTimeout(() => selectedModule.value = undefined, 300)
}, { flush: 'post' })
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>) {
if (isReport)
return
// 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 v-tooltip.bottom="'Reset'" icon="i-carbon-reset" @click="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>