Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions packages/vite/src/app/components/data/SearchPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,18 @@ function unselectToggle() {

<template>
<div flex="col gap-2" max-w-90vw min-w-30vw border="~ base rounded-xl" bg-glass>
<div v-if="modelValue.search !== false">
<input
v-model="model.search"
p2 px4
w-full
style="outline: none"
placeholder="Search"
>
</div>
<slot name="search">
<div v-if="modelValue.search !== false" class="flex items-center">
<input
v-model="model.search"
p2 px4
w-full
style="outline: none"
placeholder="Search"
>
<slot name="search-end" />
</div>
</slot>
<div v-if="rules.length" :class="selectedContainerClass" flex="~ gap-2 wrap" p2 border="t base">
<label
v-for="rule of rules"
Expand Down
16 changes: 13 additions & 3 deletions packages/vite/src/app/components/modules/FlatList.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
<script setup lang="ts">
import type { ModuleListItem, SessionContext } from '~~/shared/types'

defineProps<{
withDefaults(defineProps<{
session: SessionContext
modules: ModuleListItem[]
disableTooltip?: boolean
link?: boolean
}>(), {
disableTooltip: false,
link: true,
})

const emit = defineEmits<{
(e: 'select', module: ModuleListItem): void
}>()
</script>

Expand All @@ -14,13 +23,14 @@ defineProps<{
key-prop="id"
>
<template #default="{ item }">
<div flex pb2>
<div flex pb2 @click="emit('select', item)">
<DisplayModuleId
:id="item.id"
:session
hover="bg-active" block px2 p1 w-full
border="~ base rounded"
:link="true"
:link="link"
:disable-tooltip="disableTooltip"
/>
</div>
</template>
Expand Down
120 changes: 120 additions & 0 deletions packages/vite/src/app/components/modules/PathSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<script setup lang="ts">
import type { ModuleListItem, SessionContext } from '~~/shared/types'
import { computed, watch } from 'vue'
import { useModulePathSelector } from '~/composables/moduleGraph'

const props = defineProps<{
session: SessionContext
modules: ModuleListItem[]
}>()

const emit = defineEmits<{
(e: 'close'): void
(e: 'select', nodes: { start: string, end: string }): void
}>()

const modulesMap = computed(() => {
const map = new Map<string, ModuleListItem>()
props.modules.forEach((m) => {
map.set(m.id, m)
})
return map
})

const startSelector = useModulePathSelector({
getModules: () => {
if (!startSelector.state.value.search) {
return props.modules
}
else {
return startSelector.fuse.value!.value?.search(startSelector.state.value.search).map(r => r.item) ?? []
}
},
})

startSelector.initSelector(computed(() => props.modules))

function getAllImports(moduleId: string, visited = new Set<string>()): ModuleListItem[] {
if (visited.has(moduleId))
return []
visited.add(moduleId)

const module = modulesMap.value.get(moduleId)
if (!module?.imports?.length)
return []

const res: ModuleListItem[] = []

for (const importItem of module.imports) {
const importedModule = modulesMap.value.get(importItem.module_id)
if (!importedModule)
continue

if (!visited.has(importedModule.id)) {
res.push(importedModule)
res.push(...getAllImports(importedModule.id, visited))
}
}

return res
}

const endSelector = useModulePathSelector({
getModules: () => {
return startSelector.state.value.selected ? getAllImports(startSelector.state.value.selected) : []
},
})

endSelector.initSelector(endSelector.modules)

const filteredEndModules = computed(() => {
if (!endSelector.state.value.search) {
return endSelector.modules.value
}
else {
return endSelector.fuse.value!.value?.search(endSelector.state.value.search).map(r => r.item) ?? []
}
})

watch([() => startSelector.state.value.selected, () => endSelector.state.value.selected], () => {
emit('select', {
start: startSelector.state.value.selected ?? '',
end: endSelector.state.value.selected ?? '',
})
})
</script>

<template>
<div h12 px4 p2 relative flex="~ gap2 items-center">
<div flex="~ items-center gap2" class="flex-1" min-w-0>
<ModulesPathSelectorItem
v-model:search="startSelector.state.value.search"
placeholder="Start"
:selector="startSelector"
:session="session"
:modules="startSelector.modules.value"
@clear="() => { startSelector.clear(); endSelector.clear() }"
/>
<div class="i-carbon-arrow-right op50" flex-shrink-0 />

<ModulesPathSelectorItem
v-model:search="endSelector.state.value.search"
placeholder="End"
:selector="endSelector"
:session="session"
:modules="filteredEndModules"
@clear="endSelector.clear"
>
<template #empty>
<div flex="~ items-center justify-center" w-full h-20>
<span italic op50>
{{ startSelector.state.value.selected ? 'No modules' : 'Select a start module to get end modules' }}
</span>
</div>
</template>
</ModulesPathSelectorItem>
</div>

<DisplayCloseButton class="mr--2" @click="emit('close')" />
</div>
</template>
65 changes: 65 additions & 0 deletions packages/vite/src/app/components/modules/PathSelectorItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<script setup lang="ts">
import type { ModuleListItem, SessionContext } from '~~/shared/types'
import type { ModulePathSelector } from '~/composables/moduleGraph'
import { hideAllPoppers, Menu as VMenu } from 'floating-vue'

withDefaults(
defineProps<{
selector: ModulePathSelector
placeholder: string
session: SessionContext
modules?: ModuleListItem[]
emptyStateText?: string
onClear?: () => void
}>(),
{
modules: undefined,
emptyStateText: undefined,
onClear: undefined,
},
)

const emit = defineEmits<{
(e: 'clear'): void
}>()

const search = defineModel<string>('search', { required: true })
</script>

<template>
<div flex-1 w-0>
<div v-if="selector.state.value.selected" w-full overflow-hidden flex="~ items-center" border="~ base rounded" p1 relative>
<div overflow-hidden text-ellipsis pr6 py0.5 w-0 flex-1>
<DisplayModuleId
:id="selector.state.value.selected"
:session="session"
block text-nowrap
:link="false"
:disable-tooltip="true"
/>
</div>
<button i-carbon-clean text-4 hover="op100" op50 title="Clear" absolute right-2 @click="emit('clear')" />
</div>
<VMenu v-else :distance="15" :triggers="['click']" :auto-hide="false" :delay="{ show: 300, hide: 150 }">
<input
v-model="search"
p1
px4 w-full border="~ base rounded-1" style="outline: none" :placeholder="placeholder"
@blur="hideAllPoppers"
>
<template #popper>
<div class="p2 w100" flex="~ col gap2">
<ModulesFlatList
v-if="modules?.length"
:session="session"
:modules="modules"
disable-tooltip
:link="false"
@select="selector.select"
/>
<slot v-else name="empty" />
</div>
</template>
</VMenu>
</div>
</template>
59 changes: 58 additions & 1 deletion packages/vite/src/app/composables/moduleGraph.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { ComputedRefWithControl } from '@vueuse/core'
import type { HierarchyLink, HierarchyNode } from 'd3-hierarchy'
import type { ComputedRef, InjectionKey, MaybeRef, Ref, ShallowReactive, ShallowRef } from 'vue'
import { onKeyPressed, useEventListener, useMagicKeys } from '@vueuse/core'
import type { ModuleListItem } from '~~/shared/types'
import { computedWithControl, onKeyPressed, useEventListener, useMagicKeys } from '@vueuse/core'
import { hierarchy, tree } from 'd3-hierarchy'
import { linkHorizontal, linkVertical } from 'd3-shape'
import Fuse from 'fuse.js'
import { computed, inject, nextTick, provide, ref, shallowReactive, shallowRef, unref } from 'vue'
import { useZoomElement } from './zoomElement'

Expand Down Expand Up @@ -352,3 +355,57 @@ export function useGraphDraggingScroll() {
isGrabbing,
}
}

export interface ModulePathSelector {
state: { value: { search: string, selected: string | null } }
modules: ComputedRef<ModuleListItem[]>
fuse: Ref<ComputedRefWithControl<Fuse<ModuleListItem>> | undefined>
initSelector: (modules: ComputedRef<ModuleListItem[]>) => void
select: (module: ModuleListItem) => void
clear: () => void
}

export function useModulePathSelector(options: {
getModules: () => ModuleListItem[]
}): ModulePathSelector {
const state = ref<{
search: string
selected: string | null
}>({
search: '',
selected: null,
})
const fuse = ref<ComputedRefWithControl<Fuse<ModuleListItem>>>()

const modules = computed(options.getModules)

function initSelector(modules: ComputedRef<ModuleListItem[]>) {
fuse.value = computedWithControl(
modules,
() => new Fuse(modules.value, {
includeScore: true,
keys: ['id'],
ignoreLocation: true,
threshold: 0.4,
}),
)
}

function select(node: ModuleListItem) {
state.value.selected = node.id
state.value.search = ''
}

function clear() {
state.value.selected = null
}

return {
state,
modules,
fuse,
initSelector,
select,
clear,
}
}
Loading
Loading