Skip to content

Commit

Permalink
feat: categorize tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 23, 2023
1 parent 2c2064e commit 64c48cd
Show file tree
Hide file tree
Showing 23 changed files with 137 additions and 63 deletions.
5 changes: 5 additions & 0 deletions packages/devtools-kit/src/_types/common.ts
@@ -0,0 +1,5 @@
export type TabCategory =
| 'app'
| 'analyze'
| 'server'
| 'modules'
10 changes: 8 additions & 2 deletions packages/devtools-kit/src/_types/custom-tabs.ts
@@ -1,4 +1,5 @@
import type { VNode } from 'vue'
import type { TabCategory } from './common'

export interface ModuleCustomTab {
/**
Expand All @@ -17,6 +18,11 @@ export interface ModuleCustomTab {
* Main view of the tab
*/
view: ModuleView
/**
* Category of the tab
* @default 'app'
*/
category?: TabCategory
/**
* Insert static vnode to the tab entry
*
Expand Down Expand Up @@ -105,8 +111,8 @@ export interface ModuleBuiltinTab {
icon?: string
title?: string
path?: string
requireClient?: boolean
shouldShow?: () => boolean
category?: TabCategory
shouldShow?: () => any
}

export type ModuleTabInfo = ModuleCustomTab | ModuleBuiltinTab
1 change: 1 addition & 0 deletions packages/devtools-kit/src/_types/index.ts
Expand Up @@ -8,3 +8,4 @@ export * from './wizard'
export * from './rpc'
export * from './server-ctx'
export * from './module-options'
export * from './common'
29 changes: 13 additions & 16 deletions packages/devtools/client/components/SideNav.vue
@@ -1,6 +1,6 @@
<script setup lang="ts">
const client = useClient()
const tabs = useTabs()
const categories = useCategorizedTabs()
</script>

<template>
Expand All @@ -16,23 +16,20 @@ const tabs = useTabs()
<DockingPanel />
</template>
</VDropdown>

<div mt-2 h-1px w-8 border="b base" />
</div>

<SideNavItem
v-for="tab of tabs.builtin.value"
:key="tab.name"
:tab="tab"
/>
<template v-if="tabs.custom.value.length">
<div my1 h-1px w-8 border="b base" />
<SideNavItem
v-for="tab of tabs.custom.value"
:key="tab.name"
:tab="tab"
/>
<div flex-auto />
<template v-for="[name, tabs] of categories" :key="name">
<template v-if="tabs.length">
<div my1 h-1px w-8 border="b base" />

<SideNavItem
v-for="tab of tabs"
:key="tab.name"
:tab="tab"
/>
</template>
</template>

<div flex-auto />
</div>
</template>
3 changes: 0 additions & 3 deletions packages/devtools/client/components/SideNavItem.vue
Expand Up @@ -6,13 +6,10 @@ const props = defineProps<{
}>()
const settings = useDevToolsSettings()
const client = useClient()
const isEnabled = computed(() => {
const _tab = props.tab as ModuleBuiltinTab
if (_tab.shouldShow && !_tab.shouldShow())
return false
if (_tab.requireClient && !client.value)
return false
if (settings.hiddenTabs.value.includes(_tab.name))
return false
return true
Expand Down
71 changes: 46 additions & 25 deletions packages/devtools/client/composables/state.ts
Expand Up @@ -2,7 +2,7 @@ import type { Component } from 'nuxt/schema'
import { $fetch } from 'ofetch'
import type { Ref } from 'vue'
import { objectPick } from '@antfu/utils'
import type { HookInfo, ModuleBuiltinTab, ModuleMetric, RouteInfo } from '../../src/types'
import type { HookInfo, ModuleBuiltinTab, ModuleCustomTab, ModuleMetric, RouteInfo, TabCategory } from '../../src/types'

let modules: ModuleMetric[] | undefined

Expand Down Expand Up @@ -79,13 +79,13 @@ export function useTerminals() {
return useAsyncState('getTerminals', () => rpc.getTerminals())
}

export function useTabs() {
const router = useRouter()
export function useAllTabs() {
const customTabs = useCustomTabs()
const settings = useDevToolsSettings()
const router = useRouter()

const builtin = computed(() => {
return router.getRoutes()
const builtin = computed(() => [
...router.getRoutes()
.filter(route => route.path.startsWith('/modules/') && route.meta.title && !route.meta.wip)
.filter(route => !route.meta.experimental || (route.meta.experimental && settings.showExperimentalFeatures.value))
.sort((a, b) => (a.meta.order || 100) - (b.meta.order || 100))
Expand All @@ -95,33 +95,54 @@ export function useTabs() {
path: i.path,
...i.meta,
}
})
})
}),
...(customTabs.value || []).filter(i => i.name.startsWith('builtin-')),
])

const custom = computed(() => (customTabs.value || [])
.filter(i => !i.name.startsWith('builtin-')))

return computed(() => [
...builtin.value,
...custom.value,
])
}

export function useCategorizedTabs(enabledOnly = true) {
const tabs = enabledOnly
? useEnabledTabs()
: useAllTabs()

const builtInCustom = computed(() => (customTabs.value || []).filter(i => i.name.startsWith('builtin-')))
const customCustom = computed(() => (customTabs.value || []).filter(i => !i.name.startsWith('builtin-')))
const settings = useDevToolsSettings()

return {
custom: customCustom,
builtin: computed(() => [
...builtin.value,
...builtInCustom.value,
]),
all: computed(() => [
...builtin.value,
...builtInCustom.value,
...customCustom.value,
]),
}
return computed(() => {
const categories: Record<TabCategory, (ModuleCustomTab | ModuleBuiltinTab)[]> = {
app: [],
analyze: [],
server: [],
modules: [],
}

for (const tab of tabs.value) {
const category = tab.category || 'app'
if (enabledOnly && settings.hiddenTabCategories.value.includes(category))
continue
if (!categories[category])
console.warn(`Unknown tab category: ${category}`)
else
categories[category].push(tab)
}

return Object.entries(categories)
})
}

export function useEnabledTabs() {
const client = useClient()
const tabs = useTabs()
const tabs = useAllTabs()

return computed(() => tabs.all.value.filter((tab) => {
return computed(() => tabs.value.filter((tab) => {
const _tab = tab as ModuleBuiltinTab
if (_tab.requireClient && !client.value)
if (_tab.shouldShow && !_tab.shouldShow())
return false
return true
}))
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/client/composables/storage.ts
Expand Up @@ -13,6 +13,7 @@ const devToolsSettings = useLocalStorage<DevToolsUISettings>('nuxt-devtools-sett
showExperimentalFeatures: false,
scale: 1,
hiddenTabs: [],
hiddenTabCategories: [],
}, { mergeDefaults: true })

const devToolsSettingsRefs = toRefs(devToolsSettings)
Expand Down
5 changes: 5 additions & 0 deletions packages/devtools/client/meta.d.ts
@@ -1,8 +1,13 @@
import { TabCategory } from "../src/types"

declare module '#app' {
interface PageMeta {
icon?: string
title?: string
order?: number
category?: TabCategory
shouldShow?: () => any
badge?: () => string | number | undefined
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/client/pages/modules/custom-[name].vue
Expand Up @@ -8,8 +8,8 @@ definePageMeta({
const route = useRoute()
const router = useRouter()
const name = computed(() => route.params.name)
const tabs = useTabs()
const tab = computed(() => tabs.all.value.find(i => i.name === name.value) as ModuleCustomTab)
const tabs = useAllTabs()
const tab = computed(() => tabs.value.find(i => i.name === name.value) as ModuleCustomTab)
onMounted(() => {
// if the tab is not found and passed a certain timeout, redirect to the overview page
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/client/pages/modules/hooks.vue
Expand Up @@ -2,6 +2,7 @@
definePageMeta({
icon: 'carbon-ibm-cloud-direct-link-2-connect',
title: 'Hooks',
category: 'analyze',
})
const serverHooks = useServerHooks()
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools/client/pages/modules/pages.vue
Expand Up @@ -6,7 +6,7 @@ import type { RouteInfo } from '~~/../src/types'
definePageMeta({
icon: 'carbon-tree-view-alt',
title: 'Pages',
requireClient: true,
shouldShow: () => !!useClient().value,
order: 1,
})
Expand Down
3 changes: 2 additions & 1 deletion packages/devtools/client/pages/modules/payload.vue
Expand Up @@ -2,7 +2,8 @@
definePageMeta({
icon: 'carbon-data-set',
title: 'Payload',
requireClient: true,
category: 'analyze',
shouldShow: () => !!useClient().value,
order: 7,
})
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/client/pages/modules/plugins.vue
Expand Up @@ -5,6 +5,7 @@ definePageMeta({
icon: 'carbon-plug',
title: 'Plugins',
order: 5,
category: 'analyze',
})
const config = useServerConfig()
Expand Down
3 changes: 2 additions & 1 deletion packages/devtools/client/pages/modules/runtime-configs.vue
Expand Up @@ -2,7 +2,8 @@
definePageMeta({
icon: 'carbon-settings-services',
title: 'Runtime Configs',
requireClient: true,
category: 'analyze',
shouldShow: () => !!useClient().value,
order: 6,
})
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/client/pages/modules/server-routes.vue
Expand Up @@ -6,6 +6,7 @@ definePageMeta({
title: 'Server Routes',
layout: 'full',
experimental: true,
category: 'server',
shouldShow() {
return useServerRoutes().value?.length
},
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/client/pages/modules/storage.vue
Expand Up @@ -6,6 +6,7 @@ definePageMeta({
title: 'Storage',
experimental: true,
layout: 'full',
category: 'server',
})
const nuxtApp = useNuxtApp()
Expand Down
3 changes: 3 additions & 0 deletions packages/devtools/client/pages/modules/terminals.vue
Expand Up @@ -4,6 +4,9 @@ definePageMeta({
title: 'Terminals',
layout: 'full',
shouldShow() {
return !!useTerminals().value?.length
},
badge() {
return useTerminals().value?.length
},
})
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/client/pages/modules/virtual-files.vue
Expand Up @@ -5,6 +5,7 @@ definePageMeta({
icon: 'i-carbon-border-none',
title: 'Virtual Files',
layout: 'full',
category: 'analyze',
})
interface VfsData {
Expand Down
47 changes: 36 additions & 11 deletions packages/devtools/client/pages/settings.vue
Expand Up @@ -4,6 +4,7 @@ const {
showExperimentalFeatures,
scale,
hiddenTabs,
hiddenTabCategories,
} = useDevToolsSettings()
const scaleOptions = [
Expand All @@ -14,14 +15,21 @@ const scaleOptions = [
['Huge', 18 / 15],
]
const allTabs = useTabs()
const categories = useCategorizedTabs(false)
function toggleTab(name: string, v: boolean) {
if (v)
hiddenTabs.value = hiddenTabs.value.filter(i => i !== name)
else
hiddenTabs.value.push(name)
}
function toggleTabCategory(name: string, v: boolean) {
if (v)
hiddenTabCategories.value = hiddenTabCategories.value.filter(i => i !== name)
else
hiddenTabCategories.value.push(name)
}
</script>

<template>
Expand Down Expand Up @@ -57,17 +65,34 @@ function toggleTab(name: string, v: boolean) {
<h3 mb1 text-lg>
Tabs
</h3>
<template v-for="tab of allTabs.all.value" :key="tab.name">
<NSwitch
flex="~ row-reverse" py1 n-primary
:model-value="!hiddenTabs.includes(tab.name)"
@update:model-value="v => toggleTab(tab.name, v)"
<template v-for="[name, tabs] of categories" :key="name">
<div
v-if="tabs.length"
flex="~ col gap-1"
:class="hiddenTabCategories.includes(name) ? 'op50 grayscale' : ''" pt-2
>
<div flex="~ gap-2" flex-auto items-center justify-start :class="hiddenTabs.includes(tab.name) ? 'op25' : ''">
<TabIcon text-xl :icon="tab.icon" :title="tab.title" />
<span>{{ tab.title }}</span>
</div>
</NSwitch>
<NSwitch
flex="~ row-reverse" py1 n-lime border="b base"
:model-value="!hiddenTabCategories.includes(name)"
@update:model-value="v => toggleTabCategory(name, v)"
>
<div flex="~ gap-2" flex-auto items-center justify-start>
<span capitalize op75>{{ name }}</span>
</div>
</NSwitch>
<template v-for="tab of tabs" :key="tab.name">
<NSwitch
flex="~ row-reverse" py1 n-primary
:model-value="!hiddenTabs.includes(tab.name)"
@update:model-value="v => toggleTab(tab.name, v)"
>
<div flex="~ gap-2" flex-auto items-center justify-start :class="hiddenTabs.includes(tab.name) ? 'op25' : ''">
<TabIcon text-xl :icon="tab.icon" :title="tab.title" />
<span>{{ tab.title }}</span>
</div>
</NSwitch>
</template>
</div>
</template>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/src/integrations/vite-inspect.ts
Expand Up @@ -18,6 +18,7 @@ export async function setup({ nuxt, rpc }: NuxtDevtoolsServerContext) {
name: 'builtin-vite-inspect',
title: 'Inspect',
icon: 'carbon-ibm-watson-discovery',
category: 'analyze',
view: {
type: 'iframe',
src: `${nuxt.options.app.baseURL}/_nuxt/__inspect/`.replace(/\/\//g, '/'),
Expand Down

0 comments on commit 64c48cd

Please sign in to comment.