|
| 1 | +<script setup lang="ts"> |
| 2 | +import { vTooltip } from 'floating-vue' |
| 3 | +import { relative } from 'pathe' |
| 4 | +import { computed, defineComponent, h } from 'vue' |
| 5 | +import { getPluginColor } from '../../utils/color' |
| 6 | +
|
| 7 | +const props = withDefaults( |
| 8 | + defineProps<{ |
| 9 | + id?: string |
| 10 | + badges?: boolean |
| 11 | + icon?: boolean |
| 12 | + module?: boolean |
| 13 | + }>(), |
| 14 | + { |
| 15 | + icon: true, |
| 16 | + }, |
| 17 | +) |
| 18 | +
|
| 19 | +// const mod = computed(() => payload.modules.find(i => i.id === props.id)) |
| 20 | +const root = '/Users/antfu/i/vite-devtools/' // TODO: get from cwd |
| 21 | +// const isVirtual = computed(() => mod.value?.virtual) |
| 22 | +const relativePath = computed(() => { |
| 23 | + if (!props.id) |
| 24 | + return '' |
| 25 | + const id = props.id.replace(/%2F/g, '/') |
| 26 | + let relate = relative(root, id) |
| 27 | + if (!relate.startsWith('.')) |
| 28 | + relate = `./${relate}` |
| 29 | + if (relate.startsWith('./')) |
| 30 | + return relate |
| 31 | + if (relate.match(/^(?:\.\.\/){1,3}[^.]/)) |
| 32 | + return relate |
| 33 | + return id |
| 34 | +}) |
| 35 | +
|
| 36 | +const HighlightedPath = defineComponent({ |
| 37 | + render() { |
| 38 | + const parts = relativePath.value.split(/([/?&:])/g) |
| 39 | + let type: 'start' | 'path' | 'query' = 'start' |
| 40 | +
|
| 41 | + const classes: string[][] = parts.map(() => []) |
| 42 | + const nodes = parts.map((part) => { |
| 43 | + return h('span', { class: '' }, part) |
| 44 | + }) |
| 45 | +
|
| 46 | + const removeIndexes = new Set<number>() |
| 47 | +
|
| 48 | + parts.forEach((part, index) => { |
| 49 | + const _class = classes[index] |
| 50 | + if (part === '?') |
| 51 | + type = 'query' |
| 52 | +
|
| 53 | + if (type === 'start') { |
| 54 | + if (part.match(/^\.+$/)) { |
| 55 | + _class.push('op50') |
| 56 | + } |
| 57 | + else if (part === '/') { |
| 58 | + _class.push('op50') |
| 59 | + } |
| 60 | + else if (part !== '/') { |
| 61 | + type = 'path' |
| 62 | + } |
| 63 | + } |
| 64 | +
|
| 65 | + if (type === 'path') { |
| 66 | + if (part === '/' || part === 'node_modules' || part.match(/^\.\w/)) { |
| 67 | + _class.push('op75') |
| 68 | + } |
| 69 | + if (part === '.pnpm') { |
| 70 | + classes[index]?.push('op50') |
| 71 | + if (nodes[index]) |
| 72 | + nodes[index].children = '…' |
| 73 | + removeIndexes.add(index + 1) |
| 74 | + removeIndexes.add(index + 2) |
| 75 | + if (nodes[index + 4]?.children === 'node_modules') { |
| 76 | + removeIndexes.add(index + 3) |
| 77 | + removeIndexes.add(index + 4) |
| 78 | + } |
| 79 | + } |
| 80 | + if (part === ':') { |
| 81 | + if (nodes[index - 1]) { |
| 82 | + nodes[index - 1].props ||= {} |
| 83 | + nodes[index - 1].props!.style ||= {} |
| 84 | + nodes[index - 1].props!.style.color = getPluginColor(parts[index - 1]) |
| 85 | + } |
| 86 | + _class.push('op50') |
| 87 | + } |
| 88 | + if (parts[index - 2] === 'node_modules' && !part.startsWith('.')) { |
| 89 | + _class.push('text-purple-5 dark:text-purple-4') |
| 90 | + } |
| 91 | + } |
| 92 | +
|
| 93 | + if (type === 'query') { |
| 94 | + if (part === '?' || part === '&') { |
| 95 | + _class.push('text-orange-5 dark:text-orange-4') |
| 96 | + } |
| 97 | + else { |
| 98 | + _class.push('text-orange-9 dark:text-orange-2') |
| 99 | + } |
| 100 | + } |
| 101 | + }) |
| 102 | +
|
| 103 | + nodes.forEach((node, index) => { |
| 104 | + if (node.props) |
| 105 | + node.props.class = classes[index].join(' ') |
| 106 | + }) |
| 107 | +
|
| 108 | + Array.from(removeIndexes) |
| 109 | + .sort((a, b) => b - a) |
| 110 | + .forEach((index) => { |
| 111 | + nodes.splice(index, 1) |
| 112 | + classes.splice(index, 1) |
| 113 | + }) |
| 114 | +
|
| 115 | + return nodes |
| 116 | + }, |
| 117 | +}) |
| 118 | +
|
| 119 | +const gridStyles = computed(() => { |
| 120 | + if (!props.module) |
| 121 | + return '' |
| 122 | +
|
| 123 | + const gridColumns: string[] = [] |
| 124 | + if (props.icon) |
| 125 | + gridColumns.push('min-content') |
| 126 | +
|
| 127 | + if (props.module) |
| 128 | + gridColumns.push('minmax(0,1fr)') |
| 129 | + else |
| 130 | + gridColumns.push('100%') |
| 131 | +
|
| 132 | + // todo: handle slot, not being used |
| 133 | +
|
| 134 | + if (isVirtual.value) |
| 135 | + gridColumns.push('min-content') |
| 136 | +
|
| 137 | + return `grid-template-columns: ${gridColumns.join(' ')};` |
| 138 | +}) |
| 139 | +const containerClass = computed(() => { |
| 140 | + return props.module |
| 141 | + ? 'grid grid-rows-1 items-center gap-1' |
| 142 | + : 'flex items-center' |
| 143 | +}) |
| 144 | +</script> |
| 145 | + |
| 146 | +<template> |
| 147 | + <div |
| 148 | + v-if="id" |
| 149 | + v-tooltip.bottom-start="{ |
| 150 | + content: props.id, |
| 151 | + triggers: ['hover', 'focus'], |
| 152 | + disabled: !module, |
| 153 | + }" |
| 154 | + my-auto text-sm font-mono |
| 155 | + :class="containerClass" |
| 156 | + :style="gridStyles" |
| 157 | + > |
| 158 | + <DisplayFileIcon v-if="icon" :filename="id" mr1.5 /> |
| 159 | + <span :class="{ 'overflow-hidden': module, 'text-truncate': module }"> |
| 160 | + <HighlightedPath /> |
| 161 | + </span> |
| 162 | + <slot /> |
| 163 | + |
| 164 | + <!-- <DisplayBadge |
| 165 | + v-if="isVirtual" |
| 166 | + class="ml1" |
| 167 | + text="virtual" |
| 168 | + /> --> |
| 169 | + </div> |
| 170 | +</template> |
0 commit comments