Skip to content
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"eslint.experimental.useFlatConfig": true,
"prettier.enable": false,
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll": "explicit"
},
"editor.formatOnSave": false,
// Silent the stylistic rules in you IDE, but still auto fix them
Expand Down
16 changes: 15 additions & 1 deletion packages/client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const bridge = useDevToolsBridge()
const isUtilityView = computed(() => route.path.startsWith('/__') || route.path === '/')
const sidebarExpanded = computed(() => clientState.value.expandSidebar)

watchEffect(() => {
const scale = devtoolsClientState.value.scale
document.body.style.fontSize = `${scale * 15}px`
})

watch(connected, (v) => {
if (v) {
router.replace(clientState.value.isFirstVisit ? '/' : clientState.value.route)
Expand All @@ -38,6 +43,14 @@ useEventListener('keydown', (e) => {
bridge.value.emit('toggle-panel')
})

watchEffect(() => {
bridge.value.emit('update-client-state', {
minimizePanelInteractive: devtoolsClientState.value.minimizePanelInteractive,
closeOnOutsideClick: devtoolsClientState.value.interactionCloseOnOutsideClick,
showFloatingPanel: devtoolsClientState.value.showPanel,
})
})

const splitScreenEnabled = computed(() => clientState.value.splitScreen.enabled)
const isSplitScreenAvailable = splitScreenAvailable
const splitScreenSize = computed({
Expand All @@ -58,12 +71,13 @@ const splitScreenSize = computed({
>
<SideNav v-if="!isUtilityView" of-x-hidden of-y-auto />
<Splitpanes
h-full of-hidden
@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]">
<Pane v-if="!isUtilityView && splitScreenEnabled && isSplitScreenAvailable" relative h-full class="of-auto!" :size="splitScreenSize[1]">
<SplitScreen />
</Pane>
</Splitpanes>
Expand Down
29 changes: 26 additions & 3 deletions packages/client/src/assets/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,32 @@ body::-webkit-scrollbar {
background: #8885;
}

.no-scrollbar {
/* Support Firefox */
scrollbar-width: none
/* Scrollbar */
::-webkit-scrollbar {
width: 6px;
}

::-webkit-scrollbar:horizontal {
height: 6px;
}

::-webkit-scrollbar-corner {
background: transparent;
}

::-webkit-scrollbar-track {
background: var(--c-border);
border-radius: 1px;
}

::-webkit-scrollbar-thumb {
background: #8881;
transition: background 0.2s ease;
border-radius: 1px;
}

::-webkit-scrollbar-thumb:hover {
background: #8885;
}

.no-scrollbar::-webkit-scrollbar {
Expand Down
64 changes: 59 additions & 5 deletions packages/client/src/components/common/SideNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,43 @@ const panel = ref()
const buttonDocking = ref<HTMLButtonElement>()
const buttonMoreTabs = ref<HTMLButtonElement>()
const sidebarExpanded = computed(() => devtoolsClientState.value.expandSidebar)
const sidebarScrollable = ref(false)
const sidebarScrollable = computed(() => devtoolsClientState.value.scrollableSidebar)

const { enabledTabs } = useAllTabs()
const { enabledTabs, flattenedTabs } = useAllTabs()

const ITEM_HEIGHT = 45
const { height: windowHeight } = useWindowSize()
const containerCapacity = computed(() => {
const containerHeight = windowHeight.value - 130
return Math.max(0, Math.floor(containerHeight / ITEM_HEIGHT))
})
const inlineTabs = computed(() => flattenedTabs.value.slice(0, containerCapacity.value))
const overflowTabs = computed(() => flattenedTabs.value.slice(containerCapacity.value))
const categorizedInlineTabs = getCategorizedTabs(inlineTabs, enabledTabs)
const categorizedOverflowTabs = getCategorizedTabs(overflowTabs, enabledTabs)

const displayedTabs = computed(() => (sidebarScrollable.value || sidebarExpanded.value)
? enabledTabs.value
: categorizedInlineTabs.value)

onClickOutside(
panel,
(e) => {
if (buttonDocking.value && e.composedPath().includes(buttonDocking.value))
return
if (buttonMoreTabs.value && e.composedPath().includes(buttonMoreTabs.value))
return
showDocking.value = false
showMoreTabs.value = false
},
{ detectIframe: true },
)
</script>

<template>
<div
border="r base" flex="~ col"
z-100 h-full items-start of-hidden bg-base
border="r base" flex="~ col items-start"
z-100 h-full of-hidden bg-base
>
<div
sticky top-0 z-1 w-full p1 bg-base border="b base"
Expand Down Expand Up @@ -50,7 +78,7 @@ const { enabledTabs } = 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 enabledTabs" :key="name">
<template v-for="[name, tabs], idx of displayedTabs" :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 All @@ -67,6 +95,32 @@ const { enabledTabs } = useAllTabs()
:flex="`~ items-center gap-1 ${sidebarExpanded ? '' : 'none col'}`"
border="t base" sticky bottom-0 w-full p1 bg-base
>
<VueDropdown
v-if="overflowTabs.length && !sidebarScrollable && !sidebarExpanded" placement="left-end"
:distance="6"
>
<button
ref="buttonMoreTabs" flex="~"
hover="bg-active" relative
h-10 w-10 select-none items-center justify-center rounded-xl p1 text-secondary
exact-active-class="!text-primary bg-active"
>
<TabIcon
text-xl
icon="i-carbon-overflow-menu-vertical" title="More tabs" :show-title="false"
/>
<div
absolute bottom-0 right-0 h-4 w-4 rounded-full text-9px
flex="~ items-center justify-center"
border="~ base"
>
<span translate-y-0.5px>{{ overflowTabs.length }}</span>
</div>
</button>
<template #popper>
<TabsGrid :categories="categorizedOverflowTabs" max-w-80 target="main" />
</template>
</VueDropdown>
<SideNavItem
:minimized="!sidebarExpanded"
:tab="{
Expand Down
34 changes: 34 additions & 0 deletions packages/client/src/composables/state-tab.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useDevToolsBridgeRpc, useDevToolsState } from '@vue-devtools-next/core'
import type { MaybeRef } from 'vue'
import type { CustomTab } from 'vue-devtools-kit'
import type { ModuleBuiltinTab } from '~/types/tab'

Expand Down Expand Up @@ -97,3 +98,36 @@ export function useAllTabs() {

return { categorizedTabs, flattenedTabs, enabledTabs }
}

export function getCategorizedTabs(flattenTabs: MaybeRef<(CustomTab | ModuleBuiltinTab)[]>, enabledTabs: MaybeRef<CategorizedTabs>) {
return computed<CategorizedTabs>(() => {
const categories: CategorizedTabs = []
const pinnedTabs = devtoolsClientState.value.tabSettings.pinnedTabs
const tabs = toValue(enabledTabs).reduce<{ tab: CategorizedTab, category: CategorizedCategory }[]>((prev, [{ name: cateName, hidden }, tabs]) => {
tabs.forEach((tab) => {
if (toValue(flattenTabs).some(i => i.name === tab.name)) {
const category = pinnedTabs.includes(tab.name) ? 'pinned' : (cateName || 'app')
prev.push({
tab,
category: {
name: category,
hidden,
},
})
}
})
return prev
}, [])
tabs.forEach(({ tab, category }) => {
const cates = categories.find(([{ name }]) => name === category.name)
if (!cates)
categories.push([category, [tab]])
else
cates[1].push(tab)
})
const pinned = categories.find(([{ name }]) => name === 'pinned')
if (pinned)
pinned.sort((a, b) => pinnedTabs.indexOf(a[0].name) - pinnedTabs.indexOf(b[0].name))
return categories
})
}
10 changes: 10 additions & 0 deletions packages/client/src/composables/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ export const devtoolsClientState: RemovableRef<{
graphSettings: GraphSettings
tabSettings: TabSettings
expandSidebar: boolean
scrollableSidebar: boolean
splitScreen: {
enabled: boolean
view: string
size: [number, number]
}
scale: number
interactionCloseOnOutsideClick: boolean
showPanel: boolean
minimizePanelInteractive: number
}> = useLocalStorage('__VUE_DEVTOOLS_CLIENT_STATE__', {
isFirstVisit: true,
route: '/',
Expand All @@ -32,6 +37,11 @@ export const devtoolsClientState: RemovableRef<{
size: [50, 50],
},
expandSidebar: false,
scrollableSidebar: true,
scale: 1,
interactionCloseOnOutsideClick: false,
showPanel: true,
minimizePanelInteractive: 5000,
}, { mergeDefaults: true })

export function clearDevtoolsClientState() {
Expand Down
67 changes: 61 additions & 6 deletions packages/client/src/pages/settings.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { VueButton, VueCard, VueCheckbox, VueConfirm, VueDarkToggle, VueSwitch } from '@vue-devtools-next/ui'
import { VueButton, VueCard, VueCheckbox, VueConfirm, VueDarkToggle, VueSelect, VueSwitch } from '@vue-devtools-next/ui'
import { isInChromePanel } from '@vue-devtools-next/shared'

// #region view mode
Expand All @@ -10,12 +10,28 @@ const { toggle: toggleViewMode } = useToggleViewMode()

const { categorizedTabs: categories } = useAllTabs()

const { hiddenTabCategories, hiddenTabs, pinnedTabs } = toRefs(devtoolsClientState.value.tabSettings)
const expandSidebar = computed({
get: () => devtoolsClientState.value.expandSidebar,
set: v => (devtoolsClientState.value.expandSidebar = v),
})
const { scale, interactionCloseOnOutsideClick, showPanel, minimizePanelInteractive, expandSidebar, scrollableSidebar } = toRefs(devtoolsClientState.value)

// #region settings
const scaleOptions = [
['Tiny', 12 / 15],
['Small', 14 / 15],
['Normal', 1],
['Large', 16 / 15],
['Huge', 18 / 15],
]
const MinimizeInactiveOptions = [
['Always', 0],
['1s', 1000],
['2s', 2000],
['5s', 5000],
['10s', 10000],
['Never', -1],
]
// #endregion

// #region tabs
const { hiddenTabCategories, hiddenTabs, pinnedTabs } = toRefs(devtoolsClientState.value.tabSettings)
function toggleTab(name: string, v: boolean) {
if (v)
hiddenTabs.value = hiddenTabs.value.filter(i => i !== name)
Expand Down Expand Up @@ -51,12 +67,19 @@ function pinMove(name: string, delta: number) {
newPinnedTabs.splice(newIndex, 0, name)
pinnedTabs.value = newPinnedTabs
}
// #endregion

const clearOptionsConfirmState = ref(false)
async function clearOptions() {
clearDevtoolsClientState()
window.location.reload()
}

const minimizePanelInteractiveOptions = MinimizeInactiveOptions.map(([label, value]) => ({ label, value }))
const minimizePanelInteractiveLabel = computed(() => {
const option = minimizePanelInteractiveOptions.find(i => i.value === minimizePanelInteractive.value)
return `${option?.label ?? 'Select...'}`
})
</script>

<template>
Expand Down Expand Up @@ -143,12 +166,44 @@ async function clearOptions() {
Switch to Overlay Mode
</VueButton>
</div>
<!-- TODO: need rewrite client/(UI package) to rem based -->
<!-- <div mx--2 my1 h-1px border="b base" op75 />
<p>UI Scale</p>
<div>
<VueSelect v-model="scale" :options="scaleOptions.map(([label, value]) => ({ label, value }))" />
</div> -->
<div mx--2 my1 h-1px border="b base" op75 />
<div class="flex items-center gap2 text-sm">
<VueCheckbox v-model="expandSidebar" />
<span op75>Expand Sidebar</span>
</div>
<div class="flex items-center gap2 text-sm">
<VueCheckbox v-model="scrollableSidebar" />
<span op75>Scrollable Sidebar</span>
</div>
</VueCard>

<h3 mt2 text-lg>
Features
</h3>
<VueCard p4 flex="~ col gap-2">
<div class="flex items-center gap2 text-sm">
<VueCheckbox v-model="interactionCloseOnOutsideClick" />
<span op75>Close DevTools when clicking outside</span>
</div>
<div class="flex items-center gap2 text-sm">
<VueCheckbox v-model="showPanel" />
<span op75>Always show the floating panel</span>
</div>

<div mx--2 my1 h-1px border="b base" op75 />

<p>Minimize floating panel on inactive</p>
<div>
<VueSelect v-model="minimizePanelInteractive" :button-props="{ outlined: true }" :options="minimizePanelInteractiveOptions" :placeholder="minimizePanelInteractiveLabel" />
</div>
</VueCard>

<h3 mt2 text-lg>
Debug
</h3>
Expand Down
6 changes: 4 additions & 2 deletions packages/overlay/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useColorMode } from '@vueuse/core'
import { Bridge, getDevToolsClientUrl, prepareInjection } from '@vue-devtools-next/core'
import { target } from '@vue-devtools-next/shared'
import { devtools } from 'vue-devtools-kit'
import { useIframe, usePanelVisible, usePosition } from '~/composables'
import { registerBridge, useFrameState, useIframe, usePanelVisible, usePosition } from '~/composables'
import { checkIsSafari } from '~/utils'
import Frame from '~/components/FrameBox.vue'

Expand Down Expand Up @@ -36,6 +36,7 @@ target.__VUE_DEVTOOLS_TOGGLE_OVERLAY__ = (visible: boolean) => {
overlayVisible.value = visible
}

const { updateState, state } = useFrameState()
function waitForClientInjection(iframe: HTMLIFrameElement, retry = 50, timeout = 200): Promise<void> | void {
return new Promise((resolve) => {
iframe?.contentWindow?.postMessage('__VUE_DEVTOOLS_CREATE_CLIENT__', '*')
Expand All @@ -61,6 +62,7 @@ function waitForClientInjection(iframe: HTMLIFrameElement, retry = 50, timeout =
})

prepareInjection(bridge)
registerBridge(bridge)

window.addEventListener('message', (data) => {
if (data.data === '__VUE_DEVTOOLS_CLIENT_READY__')
Expand Down Expand Up @@ -93,7 +95,7 @@ const { iframe, getIframe } = useIframe(clientUrl, async () => {

<template>
<div
v-show="overlayVisible"
v-show="state.preferShowFloatingPanel ? overlayVisible : panelVisible"
class="vue-devtools__anchor" :style="[anchorStyle, cssVars]" :class="{
'vue-devtools__anchor--vertical': isVertical,
'vue-devtools__anchor--hide': isHidden,
Expand Down
Loading