Skip to content

Commit fe06586

Browse files
CoutinhoTTSantfu
andauthored
feat: Add search components to Plugins and Modules Graph (#45)
Co-authored-by: Anthony Fu <github@antfu.me>
1 parent 60d5018 commit fe06586

File tree

6 files changed

+300
-119
lines changed

6 files changed

+300
-119
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<script setup lang="ts">
2+
import type { FilterMatchRule } from '~/utils/icon'
3+
import { useVModel } from '@vueuse/core'
4+
import { withDefaults } from 'vue'
5+
6+
interface ModelValue { search: string, selected: string[] | null }
7+
8+
const props = withDefaults(
9+
defineProps<{
10+
rules: FilterMatchRule[]
11+
modelValue?: ModelValue
12+
}>(),
13+
{
14+
modelValue: () => ({
15+
search: '',
16+
selected: null,
17+
}),
18+
},
19+
)
20+
21+
const emit = defineEmits<{
22+
(e: 'update:modelValue', value: ModelValue): void
23+
}>()
24+
25+
const model = useVModel(props, 'modelValue', emit)
26+
27+
function isRuleSelected(rule: FilterMatchRule) {
28+
const { modelValue } = props
29+
if (!modelValue?.selected)
30+
return true
31+
return modelValue.selected.includes(rule.name)
32+
}
33+
34+
function toggleRule(rule: FilterMatchRule) {
35+
const { rules } = props
36+
if (!model?.value?.selected) {
37+
model.value.selected = rules.map(r => r.name)
38+
}
39+
if (model.value.selected.includes(rule.name)) {
40+
model.value.selected = model.value.selected.filter(t => t !== rule.name)
41+
}
42+
else {
43+
model.value.selected.push(rule.name)
44+
}
45+
if (model?.value?.selected.length === props.rules.length) {
46+
model.value.selected = null
47+
}
48+
}
49+
</script>
50+
51+
<template>
52+
<div flex="col gap-2" max-w-90vw min-w-30vw border="~ base rounded-xl" bg-glass>
53+
<div border="b base">
54+
<input
55+
v-model="model.search"
56+
p2 px4
57+
w-full
58+
style="outline: none"
59+
placeholder="Search"
60+
>
61+
</div>
62+
<div flex="~ gap-2 wrap" p2>
63+
<label
64+
v-for="rule of rules"
65+
:key="rule.name"
66+
border="~ base rounded-md" px2 py1
67+
flex="~ items-center gap-1"
68+
select-none
69+
:title="rule.description"
70+
:class="isRuleSelected(rule) ? 'bg-active' : 'grayscale op50'"
71+
>
72+
<input
73+
type="checkbox"
74+
mr1
75+
:checked="isRuleSelected(rule)"
76+
@change="toggleRule(rule)"
77+
>
78+
<div :class="rule.icon" icon-catppuccin />
79+
<div text-sm>{{ rule.description || rule.name }}</div>
80+
</label>
81+
</div>
82+
<slot />
83+
</div>
84+
</template>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script setup lang="ts">
2+
import type { PluginItem } from '~~/shared/types'
3+
4+
defineProps<{
5+
plugins: PluginItem[]
6+
}>()
7+
</script>
8+
9+
<template>
10+
<div flex="~ col gap-2" p4>
11+
<template v-for="plugin of plugins" :key="plugin.id">
12+
<div font-mono border="~ rounded base" px2 py1 text-sm hover="bg-active" flex="~ gap-4 items-center">
13+
<div w-8 text-right text-xs op50>
14+
#{{ plugin.plugin_id }}
15+
</div>
16+
<DisplayPluginName :name="plugin.name" />
17+
</div>
18+
</template>
19+
</div>
20+
</template>

packages/devtools/src/app/pages/session/[session]/graph/index.vue

Lines changed: 43 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,28 @@ import { useRoute, useRouter } from '#app/composables/router'
55
import { clearUndefined, toArray } from '@antfu/utils'
66
import { computedWithControl, debouncedWatch } from '@vueuse/core'
77
import Fuse from 'fuse.js'
8-
import { computed, reactive } from 'vue'
8+
import { computed, ref } from 'vue'
99
import { settings } from '~/state/settings'
1010
import { parseReadablePath } from '~/utils/filepath'
11-
import { getFileTypeFromModuleId, getFileTypeFromName } from '~/utils/icon'
11+
import { getFileTypeFromModuleId, ModuleTypeRules } from '~/utils/icon'
1212
1313
const props = defineProps<{
1414
session: SessionContext
1515
}>()
1616
17-
interface Filters {
18-
search: string
19-
file_types: string[] | null
20-
node_modules: string[] | null
21-
}
22-
2317
const route = useRoute()
2418
const router = useRouter()
2519
26-
const filters = reactive<Filters>({
20+
const searchValue = ref<{
21+
search: string
22+
selected: string[] | null
23+
[key: string]: any
24+
}>({
2725
search: (route.query.search || '') as string,
28-
file_types: (route.query.file_types ? toArray(route.query.file_types) : null) as string[] | null,
26+
selected: (route.query.file_types ? toArray(route.query.file_types) : null) as string[] | null,
2927
node_modules: (route.query.node_modules ? toArray(route.query.node_modules) : null) as string[] | null,
3028
})
29+
3130
const moduleViewTypes = [
3231
{
3332
label: 'List',
@@ -47,12 +46,12 @@ const moduleViewTypes = [
4746
] as const
4847
4948
debouncedWatch(
50-
filters,
49+
searchValue.value,
5150
(f) => {
5251
const query: any = {
5352
...route.query,
5453
search: f.search || undefined,
55-
file_types: f.file_types || undefined,
54+
file_types: f.selected || undefined,
5655
node_modules: f.node_modules || undefined,
5756
}
5857
router.replace({
@@ -72,6 +71,12 @@ const parsedPaths = computed(() => props.session.modulesList.map((mod) => {
7271
}
7372
}))
7473
74+
const searchFilterTypes = computed(() => {
75+
return ModuleTypeRules.filter((rule) => {
76+
return parsedPaths.value.some(mod => rule.match.test(mod.mod.id))
77+
})
78+
})
79+
7580
// const allNodeModules = computed(() => {
7681
// const nodeModules = new Set<string>()
7782
// for (const mod of parsedPaths.value) {
@@ -81,45 +86,20 @@ const parsedPaths = computed(() => props.session.modulesList.map((mod) => {
8186
// return nodeModules
8287
// })
8388
84-
const allFileTypes = computed(() => {
85-
const fileTypes = new Set<string>()
86-
for (const mod of parsedPaths.value) {
87-
fileTypes.add(mod.type.name)
88-
}
89-
return fileTypes
90-
})
91-
9289
const filtered = computed(() => {
9390
let modules = parsedPaths.value
94-
if (filters.file_types) {
95-
modules = modules.filter(mod => filters.file_types!.includes(mod.type.name))
91+
if (searchValue.value.selected) {
92+
modules = modules.filter((mod) => {
93+
const type = getFileTypeFromModuleId(mod.mod.id)
94+
return searchValue.value.selected!.includes(type.name)
95+
})
9696
}
97-
if (filters.node_modules) {
98-
modules = modules.filter(mod => mod.path.moduleName && filters.node_modules!.includes(mod.path.moduleName))
97+
if (searchValue.value.node_modules) {
98+
modules = modules.filter(mod => mod.path.moduleName && searchValue.value.node_modules!.includes(mod.path.moduleName))
9999
}
100100
return modules.map(mod => ({ ...mod.mod, path: mod.path.path }))
101101
})
102102
103-
function isFileTypeSelected(type: string) {
104-
return filters.file_types == null || filters.file_types.includes(type)
105-
}
106-
107-
function toggleFileType(type: string) {
108-
if (filters.file_types == null) {
109-
filters.file_types = Array.from(allFileTypes.value)
110-
}
111-
112-
if (filters.file_types.includes(type)) {
113-
filters.file_types = filters.file_types.filter(t => t !== type)
114-
}
115-
else {
116-
filters.file_types.push(type)
117-
}
118-
if (filters.file_types.length === allFileTypes.value.size) {
119-
filters.file_types = null
120-
}
121-
}
122-
123103
const fuse = computedWithControl(
124104
() => filtered.value,
125105
() => new Fuse(filtered.value, {
@@ -131,11 +111,11 @@ const fuse = computedWithControl(
131111
)
132112
133113
const searched = computed(() => {
134-
if (filters.search === '') {
114+
if (!searchValue.value.search) {
135115
return filtered.value
136116
}
137117
return fuse.value
138-
.search(filters.search)
118+
.search(searchValue.value.search)
139119
.map(r => r.item)
140120
})
141121
@@ -149,51 +129,25 @@ function toggleDisplay(type: ClientSettings['moduleGraphViewType']) {
149129

150130
<template>
151131
<div relative max-h-screen of-hidden>
152-
<div flex="col gap-2" absolute left-4 top-4 max-w-90vw border="~ base rounded-xl" bg-glass z-panel-nav>
153-
<div border="b base">
154-
<input
155-
v-model="filters.search"
156-
p2 px4 w-full
157-
style="outline: none"
158-
placeholder="Search"
159-
>
160-
</div>
161-
<div flex="~ gap-2 wrap" p2>
162-
<label
163-
v-for="type of allFileTypes"
164-
:key="type"
165-
border="~ base rounded-md" px2 py1
166-
flex="~ items-center gap-1"
167-
select-none
168-
:title="type"
169-
:class="isFileTypeSelected(type) ? 'bg-active' : 'grayscale op50'"
170-
>
171-
<input
172-
type="checkbox"
173-
:checked="isFileTypeSelected(type)"
174-
mr1
175-
@change="toggleFileType(type)"
132+
<div absolute left-4 top-4 z-panel-nav>
133+
<DataSearchPanel v-model="searchValue" :rules="searchFilterTypes">
134+
<div flex="~ gap-2 items-center" p2 border="t base">
135+
<span op50 pl2 text-sm>View as</span>
136+
<button
137+
v-for="viewType of moduleViewTypes"
138+
:key="viewType.value"
139+
btn-action
140+
:class="settings.moduleGraphViewType === viewType.value ? 'bg-active' : 'grayscale op50'"
141+
@click="toggleDisplay(viewType.value)"
176142
>
177-
<div :class="getFileTypeFromName(type).icon" icon-catppuccin />
178-
<div text-sm>{{ getFileTypeFromName(type).description }}</div>
179-
</label>
180-
</div>
181-
<div flex="~ gap-2 items-center" p2 border="t base">
182-
<span op50 pl2 text-sm>View as</span>
183-
<button
184-
v-for="viewType of moduleViewTypes"
185-
:key="viewType.value"
186-
btn-action
187-
:class="settings.moduleGraphViewType === viewType.value ? 'bg-active' : 'grayscale op50'"
188-
@click="toggleDisplay(viewType.value)"
189-
>
190-
<div :class="viewType.icon" />
191-
{{ viewType.label }}
192-
</button>
193-
</div>
194-
<!-- TODO: should we add filters for node_modules? -->
195-
<!-- {{ allNodeModules }} -->
143+
<div :class="viewType.icon" />
144+
{{ viewType.label }}
145+
</button>
146+
</div>
147+
</DataSearchPanel>
196148
</div>
149+
<!-- TODO: should we add filters for node_modules? -->
150+
<!-- {{ allNodeModules }} -->
197151
<template v-if="settings.moduleGraphViewType === 'list'">
198152
<div of-auto h-screen pt-45>
199153
<ModulesFlatList

0 commit comments

Comments
 (0)