Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: UI improvements, highlight file path #120

Merged
merged 17 commits into from
Aug 22, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"lint-staged": "^15.2.9",
"npm-run-all": "^4.1.5",
"ohash": "^1.1.3",
"pathe": "^1.1.2",
"pinia": "^2.2.2",
"prism-theme-vars": "^0.2.5",
"rimraf": "^6.0.1",
Expand Down
5 changes: 5 additions & 0 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import { RouterLink, RouterView } from 'vue-router'
<RouterLink to="/">
Home
</RouterLink>
<span px-1rem>|</span>
<RouterLink to="/other">
Other
</RouterLink>
<span px-1rem>|</span>
<RouterLink to="/error">
Error
</RouterLink>
</nav>

<RouterView />
Expand Down
5 changes: 5 additions & 0 deletions playground/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const router = createRouter({
path: '/other',
component: () => import('../views/Other.vue'),
},
{
name: 'error',
path: '/error',
component: () => import('../views/Error.vue'),
},
],
})

Expand Down
15 changes: 15 additions & 0 deletions playground/src/views/Error.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script setup>
import { onMounted, ref } from 'vue'

const message = ref('Loading...')

onMounted(() => {
import('virtual:slow:error').catch(err => message.value = err.message)
})
</script>

<template>
<div>
<div>Slow Error Message: {{ message }}</div>
</div>
</template>
12 changes: 11 additions & 1 deletion playground/src/views/Other.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
<script setup>
import { onMounted, ref } from 'vue'
import message from 'virtual:hi'

const message2 = ref('Loading...')

onMounted(() => {
import('virtual:slow:2').then(m => message2.value = m.default)
})
</script>

<template>
<div>Other: {{ message }}</div>
<div>
<div>Other: {{ message }}</div>
<div>Slow Message: {{ message2 }}</div>
</div>
</template>
4 changes: 4 additions & 0 deletions playground/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ declare module 'virtual:hi' {
const value: string
export default value
}
declare module 'virtual:slow:*' {
const value: string
export default value
}
21 changes: 21 additions & 0 deletions playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ export default defineConfig({
return 'export default \'Hi!\''
},
},
{
name: 'custom-slow-loader',
// for testing purpose, don't change it
enforce: 'post',
resolveId(id) {
return id.startsWith('virtual:slow:') ? `\0${id}` : undefined
},
async load(id) {
if (!id.startsWith('\0virtual:slow:'))
return

const matcher = /^\0virtual:slow:(\d)$/.exec(id)
if (matcher) {
const timeout = +matcher[1]
await new Promise(resolve => setTimeout(resolve, timeout * 1000))
return `export default 'Hi after ${timeout} seconds!'`
}

throw new Error('Invalid timeout!')
},
},
Inspect({
build: true,
open: true,
Expand Down
5 changes: 4 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions src/client/components/DurationDisplay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ function getDurationColor(duration: number | undefined) {
if (!props.color)
return ''
if (duration == null)
return 'text-gray:75'
return ''
duration = duration * props.factor
if (duration < 1)
return 'text-gray:75'
return ''
if (duration > 1000)
return 'text-red-400'
return 'status-red'
if (duration > 500)
return 'text-orange-400'
return 'status-yellow'
if (duration > 200)
return 'text-yellow-400'
return 'status-green'
return ''
}

Expand All @@ -42,7 +42,7 @@ const units = computed(() => {
</script>

<template>
<div :class="getDurationColor(duration)">
{{ units[0] }}<span ml-0.4 text-xs op50>{{ units[1] }}</span>
</div>
<span block>
<span :class="getDurationColor(duration)">{{ units[0] }}</span><span ml-0.4 text-xs :class="getDurationColor(duration)" op75>{{ units[1] }}</span>
</span>
</template>
8 changes: 4 additions & 4 deletions src/client/components/ErrorDisplay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ function normalizeFilename(filename?: string) {

<template>
<div of-auto p4 font-mono flex="~ col gap-4">
<div text-xl text-red7 dark:text-red flex="~ gap-2 items-center">
<div text-xl status-red flex="~ gap-2 items-center">
<div i-carbon:warning-square />
Error
</div>
<pre text-sm text-red7 dark:text-red>{{ error.message }}</pre>
<pre text-sm status-red>{{ error.message }}</pre>
<div border="t main" h-1px w-full />
<div class="text-xs" mt2 grid="~ cols-[max-content_1fr] gap-x-4 gap-y-1" font-mono>
<template v-for="item, idx of error.stack" :key="idx">
<div text-right op50>
<template v-for="(item, idx) of error.stack" :key="idx">
<div text-right op72 dark:op50>
{{ item.functionName || `(anonymous)` }}
</div>
<div ws-nowrap>
Expand Down
6 changes: 3 additions & 3 deletions src/client/components/FilepathItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const display = computed(() => {

<template>
<button flex="~" hover="underline" @click="openInEditor">
<span>{{ display[0] }}</span>
<span op60>{{ display[1] }}</span>
<span v-if="props.line != null && props.column != null" op50>:{{ props.line }}:{{ props.column }}</span>
<span fw-600 dark:fw-unset>{{ display[0] }}</span>
<span op72 dark:op50>{{ display[1] }}</span>
<span v-if="props.line != null && props.column != null" op72 dark:op50>:{{ props.line }}:{{ props.column }}</span>
</button>
</template>
130 changes: 122 additions & 8 deletions src/client/components/ModuleId.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,147 @@
<script setup lang="ts">
import { computed } from 'vue'
import { vTooltip } from 'floating-vue'
import { relative } from 'pathe'
import { list, root } from '../logic'
import { getPluginColor } from '../logic/color'

const props = withDefaults(
defineProps<{
id?: string
icon?: boolean
module?: boolean
}>(),
{
icon: true,
},
)

const isVirtual = computed(() => list.value?.modules.find(i => i.id === props.id)?.virtual)
const relativePath = computed(() => {
if (!props.id)
return ''
let relate = relative(root.value, props.id)
if (!relate.startsWith('.'))
relate = `./${relate}`
if (relate.startsWith('./'))
return relate
if (relate.match(/^(?:\.\.\/){1,3}[^.]/))
return relate
return props.id
})

const HighlightedPath = defineComponent({
render() {
const parts = relativePath.value.split(/([/?&:])/g)
let type: 'start' | 'path' | 'query' = 'start'

const classes: string[][] = parts.map(() => [])
const nodes = parts.map((part) => {
return h('span', { class: '' }, part)
})

parts.forEach((part, index) => {
const _class = classes[index]
if (part === '?')
type = 'query'

if (type === 'start') {
if (part.match(/^\.+$/)) {
_class.push('op50')
}
else if (part === '/') {
_class.push('op50')
}
else if (part !== '/') {
type = 'path'
}
}

if (type === 'path') {
if (part === '/' || part === 'node_modules' || part.match(/^\.\w/)) {
_class.push('op75')
}
if (part === '.pnpm') {
classes[index + 2]?.push('op50')
if (nodes[index + 2])
nodes[index + 2].children = '…'
}
if (part === ':') {
if (nodes[index - 1]) {
nodes[index - 1].props ||= {}
nodes[index - 1].props!.style ||= {}
nodes[index - 1].props!.style.color = getPluginColor(parts[index - 1])
}
_class.push('op50')
}
}

if (type === 'query') {
if (part === '?' || part === '&') {
_class.push('text-rose-5 dark:text-rose-4')
}
else {
_class.push('text-orange-9 dark:text-orange-2')
}
}
})

nodes.forEach((node, index) => {
if (node.props)
node.props.class = classes[index].join(' ')
})

return nodes
},
})

const gridStyles = computed(() => {
if (!props.module)
return ''

const gridColumns: string[] = []
if (props.icon)
gridColumns.push('min-content')

if (props.module)
gridColumns.push('minmax(0,1fr)')
else
gridColumns.push('100%')

// todo: handle slot, not being used

if (isVirtual.value)
gridColumns.push('min-content')

return `grid-template-columns: ${gridColumns.join(' ')};`
})
const containerClass = computed(() => {
return props.module
? 'grid grid-rows-1 items-center gap-1'
: 'flex items-center'
})
</script>

<template>
<div v-if="id" my-auto text-sm font-mono flex="~ items-center">
<div
v-if="id"
v-tooltip.bottom-start="{
content: props.id,
triggers: ['hover', 'focus'],
disabled: !module,
}"
my-auto text-sm font-mono
:class="containerClass"
:style="gridStyles"
>
<FileIcon v-if="icon" :filename="id" mr1.5 />
<template v-if="id.startsWith(root)">
<span class="op50">.</span>
<span>{{ id.slice(root.length) }}</span>
</template>
<span v-else>{{ id }}</span>
<span :class="{ 'overflow-hidden': module, 'text-truncate': module }">
<HighlightedPath />
</span>
<slot />

<Badge
v-if="isVirtual"
class="ml1 bg-teal-400:10 text-green-700 dark:text-teal-400"
class="ml1 badge-virtual"
v-text="'virtual'"
/>
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/client/components/ModuleList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,26 @@ function byteToHumanReadable(byte: number) {
:key="i"
>
<span v-if="idx !== 0" op20>|</span>
<span op50>
<span>
<PluginName :name="i.name" :hide="true" />
</span>
</template>
<template v-if="m.data.invokeCount > 2">
<span op40>·</span>
<span
text-green
status-green
:title="`Transform invoked ${m.data.invokeCount} times`"
>x{{ m.data.invokeCount }}</span>
</template>
<div flex-auto />
<span op75>
<span>
<DurationDisplay :duration="m.data.totalTime" />
</span>
<template v-if="m.data.sourceSize && m.data.distSize">
<span op40>·</span>
<span op50>{{ byteToHumanReadable(m.data.sourceSize) }}</span>
<span class="op75 dark:op50">{{ byteToHumanReadable(m.data.sourceSize) }}</span>
<span i-carbon-arrow-right op40 />
<span op50 :class="m.data.distSize > m.data.sourceSize ? 'text-orange' : 'text-green'">{{ byteToHumanReadable(m.data.distSize) }}</span>
<span class="op100 dark:op-65" :class="m.data.distSize > m.data.sourceSize ? 'status-yellow' : 'status-green'">{{ byteToHumanReadable(m.data.distSize) }}</span>
</template>
</div>
</RouterLink>
Expand Down
4 changes: 2 additions & 2 deletions src/client/components/NavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isStaticMode, refetch, toggleDark } from '../logic'
<slot />
<slot name="actions">
<button v-if="!isStaticMode" class="text-lg icon-btn" title="Refetch" @click="refetch()">
<div i-carbon-renew />
<span i-carbon-renew block />
</button>
<div h-full w-1 border="r main" />
<a
Expand All @@ -18,7 +18,7 @@ import { isStaticMode, refetch, toggleDark } from '../logic'
<div i-carbon-logo-github />
</a>
<button class="text-lg icon-btn" title="Toggle Dark Mode" @click="toggleDark()">
<div i-carbon-sun dark:i-carbon-moon />
<span i-carbon-sun dark:i-carbon-moon block />
</button>
</slot>
</nav>
Expand Down
Loading