Skip to content

Commit bee3b1d

Browse files
committed
feat: graph pathfinding
1 parent 14d8183 commit bee3b1d

File tree

6 files changed

+689
-47
lines changed

6 files changed

+689
-47
lines changed

packages/client/src/components/graph/GraphDrawer.vue

Lines changed: 120 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ const keys = [
3838
['refs', 'references'],
3939
['deps', 'dependencies'],
4040
] as const
41+
42+
// Pathfinding mode
43+
const pathfindingMode = graphPathfindingMode
44+
const pathfindingResults = graphPathfindingResults
45+
const pathfindingStart = graphPathfindingStart
46+
const pathfindingEnd = graphPathfindingEnd
47+
48+
function handleFilterToThisModule() {
49+
filterId.value = data.value!.path
50+
pathfindingMode.value = false
51+
}
4152
</script>
4253

4354
<template>
@@ -51,43 +62,117 @@ const keys = [
5162
mount-to=".graph-body"
5263
>
5364
<div class="w-300px" h-full of-auto>
54-
<div text-md h-80px border-b border-base p3 flex="~ col gap1">
55-
<span text-lg flex="~ gap2 items-center">
56-
{{ data?.name }}
57-
<span v-if="copied" i-material-symbols-check-small text-primary-500 />
58-
<span
59-
v-else-if="data"
60-
hover="op-100" i-carbon-copy cursor-pointer text-sm op-50 :class="{
61-
'text-gray-200': !isSupported,
62-
}" @click="copy(data.name)"
63-
/>
64-
</span>
65-
<button hover="underline" truncate text-left text-gray-500 :title="data?.displayPath" @click="_openInEditor(data!.path)">
66-
{{ data?.displayPath }}
67-
</button>
68-
</div>
69-
<div
70-
v-for="([key, keyDisplay]) in keys" :key="key"
71-
max-h-60 of-auto border-b border-base p3 text-sm
72-
>
73-
<div pb2 text-gray-500>
74-
<span text-primary-500>{{ data?.[key].length }}</span>
75-
{{ keyDisplay }}
76-
</div>
77-
<div flex="~ col gap2 items-start">
78-
<button
79-
v-for="item in data?.[key]" :key="item.path" dark="text-gray-200"
80-
of-hidden truncate ws-nowrap pr-3 text-gray-800 hover="underline" @click="_openInEditor(item.path)"
81-
>
82-
{{ item.displayPath }}
65+
<!-- Show Selected Node Info -->
66+
<template v-if="data">
67+
<div text-md h-80px border-b border-base p3 flex="~ col gap1">
68+
<span text-lg flex="~ gap2 items-center">
69+
{{ data?.name }}
70+
<span v-if="copied" i-material-symbols-check-small text-primary-500 />
71+
<span
72+
v-else-if="data"
73+
hover="op-100" i-carbon-copy cursor-pointer text-sm op-50 :class="{
74+
'text-gray-200': !isSupported,
75+
}" @click="copy(data.name)"
76+
/>
77+
</span>
78+
<button hover="underline" truncate text-left text-gray-500 :title="data?.displayPath" @click="_openInEditor(data!.path)">
79+
{{ data?.displayPath }}
8380
</button>
8481
</div>
85-
</div>
86-
<div p3>
87-
<VueButton type="primary" @click="filterId = data!.path">
88-
Filter to this module
89-
</VueButton>
90-
</div>
82+
<div
83+
v-for="([key, keyDisplay]) in keys" :key="key"
84+
max-h-60 of-auto border-b border-base p3 text-sm
85+
>
86+
<div pb2 text-gray-500>
87+
<span text-primary-500>{{ data?.[key].length }}</span>
88+
{{ keyDisplay }}
89+
</div>
90+
<div flex="~ col gap2 items-start">
91+
<button
92+
v-for="item in data?.[key]" :key="item.path" dark="text-gray-200"
93+
of-hidden truncate ws-nowrap pr-3 text-gray-800 hover="underline" @click="_openInEditor(item.path)"
94+
>
95+
{{ item.displayPath }}
96+
</button>
97+
</div>
98+
</div>
99+
<div p3>
100+
<VueButton type="primary" @click="handleFilterToThisModule">
101+
Filter to this module
102+
</VueButton>
103+
</div>
104+
</template>
105+
106+
<!-- Show Found Paths -->
107+
<template v-if="pathfindingMode && pathfindingStart && pathfindingEnd">
108+
<div v-if="data" border-b border-base p3 />
109+
110+
<div text-md h-auto border-b border-base p3 flex="~ col gap2">
111+
<span text-lg font-bold>Path Results</span>
112+
<div text-sm text-gray-500>
113+
<span text-primary-500>{{ pathfindingResults.length }}</span>
114+
{{ pathfindingResults.length === 1 ? 'path' : 'paths' }} found
115+
</div>
116+
<div text-xs text-gray-400>
117+
From: {{ pathfindingStart }}
118+
</div>
119+
<div text-xs text-gray-400>
120+
To: {{ pathfindingEnd }}
121+
</div>
122+
</div>
123+
124+
<div v-if="pathfindingResults.length === 0" p3 text-sm text-gray-500>
125+
<div mb2>
126+
No paths found between these modules.
127+
</div>
128+
<div text-xs>
129+
Tip: Try using partial file names like "App.vue" or "main.ts"
130+
</div>
131+
</div>
132+
133+
<div
134+
v-for="(pathInfo, index) in pathfindingResults"
135+
:key="index"
136+
border-b border-base p3 text-sm
137+
>
138+
<div pb2 text-gray-500 font-bold>
139+
Path {{ index + 1 }}
140+
<span text-xs>({{ pathInfo.path.length }} steps)</span>
141+
</div>
142+
<div flex="~ col gap1">
143+
<div
144+
v-for="(nodeName, nodeIndex) in pathInfo.displayPath"
145+
:key="nodeIndex"
146+
flex="~ items-center gap-2"
147+
>
148+
<div
149+
h-4px w-4px rounded-full
150+
:class="[
151+
nodeIndex === 0 ? 'bg-green-500'
152+
: nodeIndex === pathInfo.displayPath.length - 1 ? 'bg-red-500'
153+
: 'bg-orange-500',
154+
]"
155+
/>
156+
<button
157+
truncate text-left hover="underline"
158+
:class="[
159+
nodeIndex === 0 ? 'text-green-600 dark:text-green-400 font-bold'
160+
: nodeIndex === pathInfo.displayPath.length - 1 ? 'text-red-600 dark:text-red-400 font-bold'
161+
: 'text-gray-800 dark:text-gray-200',
162+
]"
163+
:title="pathInfo.path[nodeIndex]"
164+
@click="_openInEditor(pathInfo.path[nodeIndex])"
165+
>
166+
{{ nodeName }}
167+
</button>
168+
<div
169+
v-if="nodeIndex < pathInfo.displayPath.length - 1"
170+
i-carbon-arrow-down text-xs op50
171+
/>
172+
</div>
173+
</div>
174+
</div>
175+
</template>
91176
</div>
92177
</VueDrawer>
93178
</template>

packages/client/src/components/graph/GraphNavbar.vue

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,56 @@ const selectableItems = [
1212
] as const
1313
1414
const filterId = graphFilterNodeId
15+
16+
// Pathfinding mode
17+
const pathfindingMode = graphPathfindingMode
18+
const pathfindingStart = graphPathfindingStart
19+
const pathfindingEnd = graphPathfindingEnd
20+
21+
function togglePathfindingMode() {
22+
pathfindingMode.value = !pathfindingMode.value
23+
if (!pathfindingStart.value && text.value) {
24+
pathfindingStart.value = text.value
25+
}
26+
}
27+
28+
function swapStartAndEnd() {
29+
const start = pathfindingStart.value
30+
pathfindingStart.value = pathfindingEnd.value
31+
pathfindingEnd.value = start
32+
}
1533
</script>
1634

1735
<template>
1836
<div flex="~ items-center gap-4 nowrap" class="[&_>*]:flex-[0_0_auto]" absolute left-0 top-0 z-10 navbar-base w-full overflow-x-auto glass-effect px4 text-sm>
19-
<VueInput v-model="text" placeholder="Search modules..." />
37+
<!-- Toggle Pathfinding Mode Button -->
38+
<button
39+
rounded-full px3 py1 text-xs hover:op100
40+
:class="pathfindingMode ? 'bg-primary-500 text-white op100' : 'bg-gray:20 op50'"
41+
@click="togglePathfindingMode"
42+
>
43+
<div flex="~ items-center gap-1">
44+
<div i-carbon-tree-view-alt />
45+
<span>Pathfinding</span>
46+
</div>
47+
</button>
48+
49+
<!-- Pathfinding Mode Inputs -->
50+
<template v-if="pathfindingMode">
51+
<VueInput v-model="pathfindingStart" placeholder="Start module..." />
52+
<button i-carbon-arrow-right rounded-full op50 hover:text-primary-500 hover:op100 @click="swapStartAndEnd" />
53+
<VueInput v-model="pathfindingEnd" placeholder="End module..." />
54+
</template>
55+
56+
<!-- Normal Search Mode Input -->
57+
<VueInput v-else v-model="text" placeholder="Search modules..." />
58+
2059
<div v-for="item in selectableItems" :key="item[0]" flex="~ gap-2 items-center">
2160
<VueCheckbox v-model="settings[item[0]]" />
2261
<span :class="{ 'text-gray-400 dark:text-gray-600': !settings[item[0]] }">Show {{ item[1] ?? item[0] }}</span>
2362
</div>
2463
<div flex-auto />
25-
<button v-if="filterId" rounded-full bg-gray:20 py1 pl3 pr2 text-xs op50 hover:op100 @click="filterId = ''">
64+
<button v-if="!pathfindingMode && filterId" rounded-full bg-gray:20 py1 pl3 pr2 text-xs op50 hover:op100 @click="filterId = ''">
2665
Clear filter
2766
<div i-carbon-close mb2px />
2867
</button>

0 commit comments

Comments
 (0)