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
19 changes: 18 additions & 1 deletion packages/client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { Ref } from 'vue'
import { useDevToolsBridge, useDevToolsState } from '@vue-devtools-next/core'
import { isInChromePanel } from '@vue-devtools-next/shared'
import { Pane, Splitpanes } from 'splitpanes'

// @TODO: fix browser extension cross-origin localStorage issue
useColorMode()
Expand Down Expand Up @@ -36,6 +37,13 @@ useEventListener('keydown', (e) => {
if (e.code === 'KeyD' && e.altKey && e.shiftKey)
bridge.value.emit('toggle-panel')
})

const splitScreenEnabled = computed(() => clientState.value.splitScreen.enabled)
const isSplitScreenAvailable = splitScreenAvailable
const splitScreenSize = computed({
get: () => clientState.value.splitScreen.size,
set: v => clientState.value.splitScreen.size = v,
})
</script>

<template>
Expand All @@ -49,7 +57,16 @@ useEventListener('keydown', (e) => {
h-full h-screen of-hidden font-sans bg-base
>
<SideNav v-if="!isUtilityView" of-x-hidden of-y-auto />
<RouterView />
<Splitpanes
@resize="splitScreenSize = $event.map((v) => v.size)"
>
<Pane h-full class="of-auto!" min-size="10" :size="splitScreenSize[0]">
<RouterView />
</Pane>
<Pane v-if="!isUtilityView && splitScreenEnabled && isSplitScreenAvailable" h-full class="of-auto!" :size="splitScreenSize[1]">
<SplitScreen />
</Pane>
</Splitpanes>
</div>
</main>
</template>
18 changes: 7 additions & 11 deletions packages/client/src/assets/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,35 @@ body::-webkit-scrollbar {
.splitpanes__splitter {
position: relative;
}

.splitpanes__splitter:before {
position: absolute;
left: 0;
top: 0;
transition: .2s ease;
transition: 0.2s ease;
content: '';
transition: opacity 0.4s;
z-index: 1;
}

.splitpanes__splitter:hover:before {
background: #8881;
opacity: 1;
}

.splitpanes--vertical>.splitpanes__splitter {
.splitpanes--vertical > .splitpanes__splitter {
min-width: 0 !important;
width: 0 !important;
@apply border-r border-base;
}

.splitpanes--horizontal>.splitpanes__splitter {
.splitpanes--horizontal > .splitpanes__splitter {
min-height: 0 !important;
height: 0 !important;
--uno: border-t border-base;
}

.splitpanes--vertical>.splitpanes__splitter:before {
.splitpanes--vertical > .splitpanes__splitter:before {
left: -5px;
right: -4px;
height: 100%;
}

.splitpanes--horizontal>.splitpanes__splitter:before {
.splitpanes--horizontal > .splitpanes__splitter:before {
top: -5px;
bottom: -4px;
width: 100%;
Expand Down
12 changes: 12 additions & 0 deletions packages/client/src/components/common/DockingPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ const expandSidebar = computed({
set: v => (devtoolsClientState.value.expandSidebar = v),
})

const splitScreenEnabled = computed({
get: () => devtoolsClientState.value.splitScreen.enabled,
set: v => (devtoolsClientState.value.splitScreen.enabled = v),
})
const isSplitScreenAvailable = splitScreenAvailable

function refreshPage() {
location.reload()
}
Expand All @@ -36,6 +42,12 @@ function refreshPage() {
<div i-carbon-settings-adjust /> Settings
</VueButton>
</div>
<div v-if="isSplitScreenAvailable" px3 py2 border="b base" flex="~ gap-2">
<VueButton outlined type="primary" @click="splitScreenEnabled = !splitScreenEnabled">
<div i-carbon-split-screen />
{{ splitScreenEnabled ? 'Close Split Screen' : 'Split Screen' }}
</VueButton>
</div>
<div px3 py2 flex="~ gap2">
<VueButton outlined type="primary" @click="refreshPage">
Refresh Page
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/components/common/SideNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const buttonMoreTabs = ref<HTMLButtonElement>()
const sidebarExpanded = computed(() => devtoolsClientState.value.expandSidebar)
const sidebarScrollable = ref(false)

const { categorizedTabs: displayedTabs } = useAllTabs()
const { enabledTabs } = useAllTabs()
</script>

<template>
Expand Down Expand Up @@ -50,7 +50,7 @@ const { categorizedTabs: displayedTabs } = useAllTabs()
flex="~ auto col gap-0.5 items-center" w-full p1 class="no-scrollbar"
:class="sidebarExpanded ? '' : 'of-x-hidden of-y-auto'"
>
<template v-for="[name, tabs], idx of displayedTabs.filter(([{ hidden }, items]) => items.length && !hidden)" :key="name">
<template v-for="[name, tabs], idx of enabledTabs" :key="name">
<!-- if is not the first nonempty list, render the top divider -->
<div v-if="idx" my1 h-1px w-full border="b base" />
<SideNavItem
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/components/common/SideNavItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const isActive = computed(() => route.path.startsWith(tabPath.value))
function onClick() {
if ('onClick' in props.tab && props.tab.onClick)
props.tab.onClick()
else if (props.target === 'side')
devtoolsClientState.value.splitScreen.view = props.tab.name
}
</script>

Expand Down
98 changes: 98 additions & 0 deletions packages/client/src/components/common/SplitScreen.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<script setup lang="ts">
import { VueButton, VueCard, VueDropdown, VTooltip as vTooltip } from '@vue-devtools-next/ui'

function close() {
devtoolsClientState.value.splitScreen.enabled = false
}
const splitScreenState = computed(() => devtoolsClientState.value.splitScreen)

const { enabledTabs, flattenedTabs } = useAllTabs()
const router = useRouter()
const route = useRoute()
const PageComponent = shallowRef()

function isMatchedWithRoute(tab?: typeof flattenedTabs['value'][number]) {
const routePath = route.path.startsWith('/') ? route.path.slice(1) : route.path
return tab && 'path' in tab && routePath === tab.path
}

const currentTab = computed(() => {
const tab = flattenedTabs.value.find(tab => tab.name === splitScreenState.value.view)
return isMatchedWithRoute(tab) ? undefined : tab
})

watch(
() => currentTab.value,
(tab) => {
if (!tab)
return
const routes = router.getRoutes()
const matched = tab && 'path' in tab
? routes.find(route => route.path === (tab.path.startsWith('/') ? tab.path : `/${tab.path}`))
: routes.find(route => route.name === 'custom-tab') // TODO: custom tabs
// if it's the same route as the main view, skip
const path = route.path.startsWith('/') ? route.path.slice(1) : route.path
if (matched?.path === path || route.params?.name === tab.name) {
PageComponent.value = undefined
return
}
const component = matched?.components?.default
if (typeof component === 'function')
PageComponent.value = defineAsyncComponent(component as any)
else
PageComponent.value = component
},
{ immediate: true },
)

const showGridPanel = ref(false)
</script>

<template>
<div h-full h-screen of-hidden>
<div v-if="PageComponent && currentTab" border="b base" flex="~ gap1" z-99 px4 py3 navbar-glass>
<VueDropdown placement="bottom-start" :distance="12" :skidding="5" :shown="showGridPanel" trigger="click">
<div flex gap2 items-center cursor-pointer>
<div i-carbon-chevron-down text-sm op50 />
<TabIcon
text-xl
:icon="currentTab?.icon"
title="Settings"
:show-title="false"
/>
<span capitalize>

{{ currentTab?.name }}
</span>
</div>
<template #popper>
<TabsGrid :categories="enabledTabs" target="side" />
</template>
</VueDropdown>
<div flex-auto />
<button
v-tooltip="'Close split screen'"
title="Close split screen" hover:bg-active px1 cursor-pointer
@click="close"
>
<div i-carbon:side-panel-open />
</button>
</div>
<div v-if="PageComponent && currentTab" of-auto style="height: calc(100% - 50px)">
<!-- TODO: custom tabs -->
<!-- Tabs -->
<component :is="PageComponent" :key="`tab-${currentTab.name}`" />
</div>
<PanelGrids v-else>
<span text-lg op50>
Select a tab to start
</span>
<VueCard px4 py2 bg-base>
<TabsGrid :categories="enabledTabs" target="side" />
</VueCard>
<VueButton type="warning" outlined mt2 @click="close">
Close Split Screen
</VueButton>
</PanelGrids>
</div>
</template>
26 changes: 26 additions & 0 deletions packages/client/src/components/common/TabsGrid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script setup lang="ts">
import type { CategorizedTabs } from '~/composables/state-tab'

defineProps<{
categories: CategorizedTabs
target: 'main' | 'side'
}>()
</script>

<template>
<div flex="~ col gap-1" max-w-80 py1>
<template v-for="[name, tabs], idx of categories" :key="name">
<template v-if="tabs.length">
<div v-if="idx" h-1px border="b base" />
<div flex="~ wrap" px1>
<SideNavItem
v-for="tab of tabs"
:key="tab.name"
:target="target"
:tab="tab"
/>
</div>
</template>
</template>
</div>
</template>
2 changes: 1 addition & 1 deletion packages/client/src/components/graph/GraphDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const keys = [
</script>

<template>
<VueDrawer v-model="show" :top="top" :close-outside="false" permanent content-blur>
<VueDrawer v-model="show" :top="top" :close-outside="false" permanent content-blur mount-to=".graph-body">
<div class="w-300px" h-full of-auto>
<div border-b border-base h-80px text-md p3 flex="~ col gap1">
<span text-lg flex="~ gap2 items-center">
Expand Down
17 changes: 15 additions & 2 deletions packages/client/src/composables/state-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface CategorizedCategory {
hidden: boolean
}

export type CategorizedTabs = [CategorizedCategory, CategorizedTab[]][]

export function useAllTabs() {
const state = useDevToolsState()
const customTabs = ref<CustomTab[]>(state.tabs.value || [])
Expand Down Expand Up @@ -49,7 +51,7 @@ export function useAllTabs() {
const categorizedTabs = computed(() => {
const { hiddenTabCategories, hiddenTabs, pinnedTabs } = devtoolsClientState.value.tabSettings
// TODO: custom tabs
const tabs = allTabs.value.reduce<[CategorizedCategory, CategorizedTab[]][]>((prev, [category, tabs]) => {
const tabs = allTabs.value.reduce<CategorizedTabs>((prev, [category, tabs]) => {
const data: [CategorizedCategory, CategorizedTab[]] = [{ hidden: false, name: category }, []]
let hiddenCount = 0
tabs.forEach((tab) => {
Expand All @@ -75,12 +77,23 @@ export function useAllTabs() {
tabs[0][1].sort((a, b) => pinnedTabs.indexOf(a.name) - pinnedTabs.indexOf(b.name))
return tabs
})
const enabledTabs = computed(() => {
return categorizedTabs.value.reduce<CategorizedTabs>((prev, [meta, tabs]) => {
if (meta.hidden)
return prev
const filtered = tabs.filter(t => !t.hidden)
if (filtered.length)
prev.push([meta, filtered])
return prev
}, [])
})

const bridgeRpc = useDevToolsBridgeRpc()
onDevToolsClientConnected(() => {
bridgeRpc.on.customTabsUpdated((data) => {
customTabs.value = data
})
})

return { categorizedTabs, flattenedTabs }
return { categorizedTabs, flattenedTabs, enabledTabs }
}
23 changes: 23 additions & 0 deletions packages/client/src/composables/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export const devtoolsClientState: RemovableRef<{
graphSettings: GraphSettings
tabSettings: TabSettings
expandSidebar: boolean
splitScreen: {
enabled: boolean
view: string
size: [number, number]
}
}> = useLocalStorage('__VUE_DEVTOOLS_CLIENT_STATE__', {
isFirstVisit: true,
route: '/',
Expand All @@ -21,9 +26,27 @@ export const devtoolsClientState: RemovableRef<{
hiddenTabs: [],
pinnedTabs: [],
},
splitScreen: {
enabled: false,
view: 'overview',
size: [50, 50],
},
expandSidebar: false,
}, { mergeDefaults: true })

export function clearDevtoolsClientState() {
devtoolsClientState.value = undefined
}

// #region split screen related
const windowSize = useWindowSize()

export const splitScreenAvailable = computed(() => windowSize.width.value > 1080)

watch(() => devtoolsClientState.value.splitScreen.enabled, (enabled, o) => {
if (o && !enabled) {
// reset size
devtoolsClientState.value.splitScreen.size = [50, 50]
}
})
// #endregion
6 changes: 4 additions & 2 deletions packages/client/src/constants/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export const viteOnlyTabs = [
'graph',
'vite-inspect',
]
export function getBuiltinTab(viteDetected: boolean) {
return viteDetected ? builtinTab : JSON.parse(JSON.stringify(builtinTab)).map(([_, tabs]) => [_, tabs.filter(t => !viteOnlyTabs.includes(t.name))])
export function getBuiltinTab(viteDetected: boolean): [string, ModuleBuiltinTab[]][] {
return viteDetected
? builtinTab
: deepClone(builtinTab).map(([_, tabs]) => [_, tabs.filter(t => !viteOnlyTabs.includes(t.name))])
}
4 changes: 2 additions & 2 deletions packages/client/src/pages/assets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function toggleView() {
</script>

<template>
<PanelGrids h-full of-auto block>
<PanelGrids h-full of-auto block class="drawer-container relative">
<Navbar ref="navbar" v-model:search="search" pb2 :no-padding="true">
<template #actions>
<div flex-none flex="~ gap2 items-center" text-lg>
Expand Down Expand Up @@ -162,7 +162,7 @@ function toggleView() {
<VueDrawer
:model-value="!!selected"
:top="navbar"
permanent
permanent mount-to=".drawer-container"
content-class="w120 text-sm" @update:model-value="(v) => {
if (!v) selected = undefined
}"
Expand Down