From 3497ca04bc8338cc1a5ceda863a59129d2d97bed Mon Sep 17 00:00:00 2001 From: Adrian Darian Date: Wed, 31 Dec 2025 03:16:49 -0800 Subject: [PATCH] feat(core): implement dock sorting, categorizing, overflow menu, and context menu - Add dock sorting by `defaultOrder` property (higher values appear first) - Add dock categorizing with visual separators between category groups - Add overflow "Show More" menu when entries exceed maxVisible (default: 8) - Add right-click context menu for dock entries with: - Refresh iframe - Open in new tab - Copy URL - Toggle address bar visibility - Hide from dock (placeholder) - Add address bar UI for iframe panels with refresh/open buttons - Add `DockEntrySettings` interface for per-entry user preferences - Assign categories and defaultOrder to builtin dock entries --- packages/core/src/client/standalone/App.vue | 1 + .../client/webcomponents/components/Dock.vue | 1 + .../components/DockContextMenu.vue | 178 ++++++++++++++++++ .../webcomponents/components/DockEntries.vue | 161 ++++++++++++++-- .../webcomponents/components/DockEntry.vue | 14 ++ .../components/DockOverflowMenu.vue | 88 +++++++++ .../webcomponents/components/ViewIframe.vue | 73 ++++++- .../src/client/webcomponents/state/docks.ts | 3 + packages/core/src/node/host-docks.ts | 8 +- packages/kit/src/client/docks.ts | 12 ++ 10 files changed, 518 insertions(+), 21 deletions(-) create mode 100644 packages/core/src/client/webcomponents/components/DockContextMenu.vue create mode 100644 packages/core/src/client/webcomponents/components/DockOverflowMenu.vue diff --git a/packages/core/src/client/standalone/App.vue b/packages/core/src/client/standalone/App.vue index 2c7aca05..d455672d 100644 --- a/packages/core/src/client/standalone/App.vue +++ b/packages/core/src/client/standalone/App.vue @@ -53,6 +53,7 @@ function switchEntry(id: string) { { +import type { DevToolsDockEntry } from '@vitejs/devtools-kit' +import type { DockEntryState } from '@vitejs/devtools-kit/client' +import { onClickOutside } from '@vueuse/core' +import { computed, useTemplateRef, watch } from 'vue' + +const props = defineProps<{ + entry: DevToolsDockEntry | null + entryState: DockEntryState | null + position: { x: number, y: number } +}>() + +const emit = defineEmits<{ + (e: 'close'): void + (e: 'hide', entryId: string): void + (e: 'toggleAddressBar', entryId: string): void +}>() + +const menuRef = useTemplateRef('menuRef') +const isVisible = computed(() => props.entry !== null) + +onClickOutside(menuRef, () => { + emit('close') +}) + +// Close on escape key +watch(isVisible, (visible) => { + if (visible) { + const handler = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + emit('close') + window.removeEventListener('keydown', handler) + } + } + window.addEventListener('keydown', handler) + } +}) + +// Menu position - ensure it stays within viewport +const menuStyle = computed(() => { + const { x, y } = props.position + const menuWidth = 180 + const menuHeight = 150 + + // Adjust position to keep menu in viewport + const adjustedX = Math.min(x, window.innerWidth - menuWidth - 10) + const adjustedY = Math.min(y, window.innerHeight - menuHeight - 10) + + return { + left: `${Math.max(10, adjustedX)}px`, + top: `${Math.max(10, adjustedY)}px`, + } +}) + +// Check if entry is an iframe type (supports refresh) +const isIframe = computed(() => props.entry?.type === 'iframe') + +// Check if entry has an iframe element mounted +const hasIframe = computed(() => !!props.entryState?.domElements.iframe) + +// Check if address bar is currently shown +const isAddressBarVisible = computed(() => props.entryState?.settings.showAddressBar ?? false) + +function refreshIframe() { + const iframe = props.entryState?.domElements.iframe + if (iframe) { + // Refresh by reassigning src + const currentSrc = iframe.src + iframe.src = '' + // Use setTimeout to ensure the src is cleared before reassigning + setTimeout(() => { + iframe.src = currentSrc + }, 0) + } + emit('close') +} + +function hideFromDock() { + if (props.entry) { + emit('hide', props.entry.id) + } + emit('close') +} + +function openInNewTab() { + if (props.entry?.type === 'iframe') { + window.open(props.entry.url, '_blank') + } + emit('close') +} + +function copyUrl() { + if (props.entry?.type === 'iframe') { + navigator.clipboard.writeText(props.entry.url) + } + emit('close') +} + +function toggleAddressBar() { + if (props.entry) { + emit('toggleAddressBar', props.entry.id) + } + emit('close') +} + + + diff --git a/packages/core/src/client/webcomponents/components/DockEntries.vue b/packages/core/src/client/webcomponents/components/DockEntries.vue index 94d9b01b..08f4d10e 100644 --- a/packages/core/src/client/webcomponents/components/DockEntries.vue +++ b/packages/core/src/client/webcomponents/components/DockEntries.vue @@ -1,19 +1,80 @@