Skip to content

Commit f093c89

Browse files
elecmonkeyantfu
andauthored
feat(core): add dock context menu support to the webcomponents client (#179)
Co-authored-by: Anthony Fu <github@antfu.me>
1 parent b624840 commit f093c89

File tree

11 files changed

+907
-291
lines changed

11 files changed

+907
-291
lines changed

packages/core/src/client/webcomponents/.generated/css.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type { DevToolsDockEntry } from '@vitejs/devtools-kit'
2+
import type { DocksContext } from '@vitejs/devtools-kit/client'
3+
import { h } from 'vue'
4+
import { setDockContextMenu } from '../state/floating-tooltip'
5+
6+
// @unocss-include
7+
8+
interface DockMenuItem {
9+
label: string
10+
icon: string
11+
action: () => void
12+
visible: boolean
13+
}
14+
15+
function renderMenuItem(item: DockMenuItem) {
16+
return h('button', {
17+
class: 'flex items-center gap-2 px3 py1.5 rounded hover:bg-active transition text-left',
18+
onClick: item.action,
19+
}, [
20+
h('div', { class: `${item.icon} text-base op60` }),
21+
h('span', item.label),
22+
])
23+
}
24+
25+
function hideDock(context: DocksContext, entry: DevToolsDockEntry) {
26+
const settingsStore = context.docks.settings
27+
const id = entry.id
28+
settingsStore.mutate((state) => {
29+
if (!state.docksHidden.includes(id))
30+
state.docksHidden = [...state.docksHidden, id]
31+
})
32+
if (context.docks.selected?.id === id)
33+
context.docks.switchEntry(null)
34+
setDockContextMenu(null)
35+
}
36+
37+
function refreshDock(context: DocksContext, entry: DevToolsDockEntry) {
38+
const state = context.docks.getStateById(entry.id)
39+
const iframe = state?.domElements.iframe
40+
if (!iframe) {
41+
setDockContextMenu(null)
42+
return
43+
}
44+
const src = iframe.src
45+
iframe.src = ''
46+
iframe.src = src
47+
setDockContextMenu(null)
48+
}
49+
50+
function canHide(context: DocksContext, entry: DevToolsDockEntry) {
51+
return context.docks.entries.some(item => item.id === entry.id)
52+
}
53+
54+
function canRefresh(entry: DevToolsDockEntry) {
55+
return entry.type === 'iframe'
56+
}
57+
58+
export function openDockContextMenu(options: {
59+
context: DocksContext
60+
entry: DevToolsDockEntry
61+
el: HTMLElement
62+
gap?: number
63+
}) {
64+
const { context, entry, el, gap = 6 } = options
65+
const items: DockMenuItem[] = [
66+
{
67+
label: 'Hide',
68+
icon: 'i-ph-eye-slash-duotone',
69+
action: () => hideDock(context, entry),
70+
visible: canHide(context, entry),
71+
},
72+
{
73+
label: 'Refresh',
74+
icon: 'i-ph-arrow-clockwise-duotone',
75+
action: () => refreshDock(context, entry),
76+
visible: canRefresh(entry),
77+
},
78+
].filter(item => item.visible)
79+
80+
if (items.length === 0)
81+
return
82+
83+
setDockContextMenu({
84+
el,
85+
gap,
86+
content: () => h('div', { class: 'flex flex-col text-sm min-w-36 mx--1' }, items.map(renderMenuItem)),
87+
})
88+
}

packages/core/src/client/webcomponents/components/DockEntries.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ function toggleDockEntry(dock: DevToolsDockEntry) {
2929
<template v-for="dock of entries" :key="dock.id">
3030
<DockEntry
3131
v-if="!dock.isHidden"
32+
:context="context"
3233
:dock
3334
:is-selected="selected?.id === dock.id"
3435
:is-dimmed="selected ? (selected.id !== dock.id) : false"

packages/core/src/client/webcomponents/components/DockEntry.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
<script setup lang="ts">
22
import type { DevToolsDockEntryBase } from '@vitejs/devtools-kit'
3+
import type { DocksContext } from '@vitejs/devtools-kit/client'
34
import { useEventListener } from '@vueuse/core'
45
import { useTemplateRef } from 'vue'
56
import { setFloatingTooltip } from '../state/floating-tooltip'
7+
import { openDockContextMenu } from './DockContextMenu'
68
import DockIcon from './DockIcon.vue'
79
810
const props = withDefaults(
911
defineProps<{
12+
context: DocksContext
1013
dock: DevToolsDockEntryBase
1114
isSelected?: boolean
1215
isDimmed?: boolean
@@ -38,6 +41,24 @@ function clearTitle() {
3841
setFloatingTooltip(null)
3942
}
4043
44+
function openContextMenu(e: MouseEvent) {
45+
if (!button.value)
46+
return
47+
if (props.dock.id === 'overflow')
48+
return
49+
e.preventDefault()
50+
clearTitle()
51+
const entry = props.context.docks.entries.find(item => item.id === props.dock.id)
52+
if (!entry)
53+
return
54+
openDockContextMenu({
55+
context: props.context,
56+
entry,
57+
el: button.value,
58+
gap: 6,
59+
})
60+
}
61+
4162
useEventListener('pointerdown', () => {
4263
if (!props.tooltip)
4364
return
@@ -51,6 +72,7 @@ useEventListener('pointerdown', () => {
5172
class="relative group vite-devtools-dock-entry"
5273
@pointerenter="updateTooltip"
5374
@pointerleave="clearTitle"
75+
@contextmenu="openContextMenu"
5476
>
5577
<button
5678
ref="button"

packages/core/src/client/webcomponents/components/DockOverflowButton.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ function hideOverflowPanel() {
7575
<template>
7676
<div ref="overflowButton">
7777
<DockEntry
78+
:context="context"
7879
:dock="{
7980
id: 'overflow',
8081
title: 'Overflow',

packages/core/src/client/webcomponents/components/DockPanel.vue

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { CSSProperties } from 'vue'
55
import { useElementBounding, useWindowSize } from '@vueuse/core'
66
import { computed, markRaw, onMounted, reactive, ref, toRefs, useTemplateRef } from 'vue'
77
import { PersistedDomViewsManager } from '../utils/PersistedDomViewsManager'
8+
import { openDockContextMenu } from './DockContextMenu'
89
import DockPanelResizer from './DockPanelResizer.vue'
910
import ViewEntry from './ViewEntry.vue'
1011
@@ -30,6 +31,21 @@ function clamp(value: number, min: number, max: number) {
3031
return Math.min(Math.max(value, min), max)
3132
}
3233
34+
function openContextMenu(e: MouseEvent) {
35+
if (!dockPanel.value)
36+
return
37+
const entry = selected.value
38+
if (!entry)
39+
return
40+
e.preventDefault()
41+
openDockContextMenu({
42+
context,
43+
entry,
44+
el: dockPanel.value,
45+
gap: 6,
46+
})
47+
}
48+
3349
const anchorPos = computed(() => {
3450
const halfWidth = (props.dockEl?.clientWidth || 0) / 2
3551
const halfHeight = (props.dockEl?.clientHeight || 0) / 2
@@ -165,6 +181,7 @@ onMounted(() => {
165181
ref="dockPanel"
166182
class="bg-glass:75 rounded-lg border border-base shadow overflow-hidden"
167183
:style="panelStyle"
184+
@contextmenu="openContextMenu"
168185
>
169186
<DockPanelResizer :panel="context.panel" />
170187
<ViewEntry
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
<script setup lang="ts">
2-
import { setDocksOverflowPanel, useDocksOverflowPanel, useFloatingTooltip } from '../state/floating-tooltip'
2+
import { useEventListener } from '@vueuse/core'
3+
import { setDockContextMenu, setDocksOverflowPanel, useDockContextMenu, useDocksOverflowPanel, useFloatingTooltip } from '../state/floating-tooltip'
34
import FloatingPopover from './FloatingPopover'
45
56
const tooltip = useFloatingTooltip()
67
const docksOverflowPanel = useDocksOverflowPanel()
8+
const dockContextMenu = useDockContextMenu()
9+
10+
useEventListener(window, 'keydown', (e: KeyboardEvent) => {
11+
if (e.key !== 'Escape')
12+
return
13+
if (dockContextMenu.value)
14+
setDockContextMenu(null)
15+
})
716
</script>
817

918
<template>
19+
<FloatingPopover :item="dockContextMenu" @dismiss="() => setDockContextMenu(null)" />
1020
<FloatingPopover :item="docksOverflowPanel" @dismiss="() => setDocksOverflowPanel(null)" />
1121
<FloatingPopover :item="tooltip" />
1222
</template>

packages/core/src/client/webcomponents/state/context.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { DEFAULT_STATE_USER_SETTINGS } from '@vitejs/devtools-kit/constants'
66
import { computed, markRaw, reactive, ref, toRefs, watchEffect } from 'vue'
77
import { BUILTIN_ENTRIES } from '../constants'
88
import { docksGroupByCategories } from './dock-settings'
9-
import { createDockEntryState, DEFAULT_DOCK_PANEL_STORE, useDocksEntries } from './docks'
9+
import { createDockEntryState, DEFAULT_DOCK_PANEL_STORE, sharedStateToRef, useDocksEntries } from './docks'
1010
import { executeSetupScript } from './setup-script'
1111

1212
let _docksContext: DocksContext | undefined
@@ -95,8 +95,9 @@ export async function createDocksContext(
9595

9696
// Get settings store and create computed grouped entries
9797
const settingsStore = markRaw(await getSettingsStore())
98+
const settings = sharedStateToRef(settingsStore)
9899
const groupedEntries = computed(() => {
99-
return docksGroupByCategories(dockEntries.value, settingsStore.value())
100+
return docksGroupByCategories(dockEntries.value, settings.value)
100101
})
101102

102103
_docksContext = reactive({

packages/core/src/client/webcomponents/state/floating-tooltip.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface FloatingPopoverProps {
99

1010
const tooltip = shallowRef<FloatingPopoverProps | null>(null)
1111
const docksOverflowPanel = shallowRef<FloatingPopoverProps | null>(null)
12+
const dockContextMenu = shallowRef<FloatingPopoverProps | null>(null)
1213

1314
export function setFloatingTooltip(info: FloatingPopoverProps | null) {
1415
tooltip.value = info
@@ -25,3 +26,11 @@ export function setDocksOverflowPanel(info: FloatingPopoverProps | null) {
2526
export function useDocksOverflowPanel() {
2627
return docksOverflowPanel
2728
}
29+
30+
export function setDockContextMenu(info: FloatingPopoverProps | null) {
31+
dockContextMenu.value = info
32+
}
33+
34+
export function useDockContextMenu() {
35+
return dockContextMenu
36+
}

0 commit comments

Comments
 (0)