Skip to content

Commit 2f3263f

Browse files
authored
feat(core): allow to sort by drag in settings-docks (#271)
1 parent ea6f483 commit 2f3263f

2 files changed

Lines changed: 116 additions & 5 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/core/src/client/webcomponents/components/views-builtin/SettingsDocks.vue

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import type { DocksContext } from '@vitejs/devtools-kit/client'
33
import type { SharedState } from '@vitejs/devtools-kit/utils/shared-state'
44
import type { DevToolsDocksUserSettings } from '../../state/dock-settings'
5-
import { computed } from 'vue'
5+
import { useDraggable } from '@vueuse/core'
6+
import { computed, ref, useTemplateRef } from 'vue'
67
import { docksGroupByCategories } from '../../state/dock-settings'
78
import { sharedStateToRef } from '../../state/docks'
89
import HashBadge from '../display/HashBadge.vue'
@@ -19,6 +20,102 @@ const categories = computed(() => {
1920
return docksGroupByCategories(props.context.docks.entries, props.settingsStore.value(), { includeHidden: true })
2021
})
2122
23+
const sortContainerEl = useTemplateRef<HTMLElement>('sortContainer')
24+
const entryEls = new Map<string, { el: HTMLElement, category: string }>()
25+
const draggingId = ref<string | null>(null)
26+
const draggingCategory = ref<string | null>(null)
27+
const dragOverId = ref<string | null>(null)
28+
const hasMoved = ref(false)
29+
let startY = 0
30+
const DRAG_THRESHOLD = 4
31+
32+
function findEntryFromEvent(event: PointerEvent): { dockId: string | null, category: string | null } {
33+
let el = event.target as HTMLElement | null
34+
while (el && el !== sortContainerEl.value) {
35+
if (el.dataset.dockId)
36+
return { dockId: el.dataset.dockId, category: el.dataset.category ?? null }
37+
el = el.parentElement
38+
}
39+
return { dockId: null, category: null }
40+
}
41+
42+
function isInteractiveElement(el: HTMLElement | null): boolean {
43+
while (el && el !== sortContainerEl.value) {
44+
if (el.tagName === 'BUTTON' || el.tagName === 'A' || el.tagName === 'INPUT')
45+
return true
46+
el = el.parentElement
47+
}
48+
return false
49+
}
50+
51+
useDraggable(sortContainerEl, {
52+
onStart(_, event) {
53+
if (event.button !== 0)
54+
return false
55+
if (isInteractiveElement(event.target as HTMLElement))
56+
return false
57+
const { dockId, category } = findEntryFromEvent(event)
58+
if (!dockId || !category)
59+
return false
60+
draggingId.value = dockId
61+
draggingCategory.value = category
62+
hasMoved.value = false
63+
startY = event.clientY
64+
},
65+
onMove(_, event) {
66+
if (!draggingId.value)
67+
return
68+
if (!hasMoved.value) {
69+
if (Math.abs(event.clientY - startY) < DRAG_THRESHOLD)
70+
return
71+
hasMoved.value = true
72+
}
73+
let target: string | null = null
74+
for (const [id, { el, category }] of entryEls) {
75+
if (id === draggingId.value)
76+
continue
77+
if (category !== draggingCategory.value)
78+
continue
79+
const rect = el.getBoundingClientRect()
80+
if (event.clientY >= rect.top && event.clientY <= rect.bottom) {
81+
target = id
82+
break
83+
}
84+
}
85+
dragOverId.value = target
86+
},
87+
onEnd() {
88+
if (draggingId.value && hasMoved.value && dragOverId.value && draggingCategory.value) {
89+
const categoryEntries = categories.value.find(([cat]) => cat === draggingCategory.value)
90+
if (categoryEntries) {
91+
const items = [...categoryEntries[1]]
92+
const fromIndex = items.findIndex(item => item.id === draggingId.value)
93+
const toIndex = items.findIndex(item => item.id === dragOverId.value)
94+
if (fromIndex !== -1 && toIndex !== -1) {
95+
items.splice(toIndex, 0, items.splice(fromIndex, 1)[0]!)
96+
categoryEntries[1] = items
97+
props.settingsStore.mutate((state) => {
98+
items.forEach((item, index) => {
99+
state.docksCustomOrder[item.id] = index
100+
})
101+
})
102+
}
103+
}
104+
}
105+
draggingId.value = null
106+
draggingCategory.value = null
107+
dragOverId.value = null
108+
hasMoved.value = false
109+
},
110+
})
111+
112+
function setEntryRef(el: any, dockId: string, category: string) {
113+
if (el)
114+
entryEls.set(dockId, { el: el as HTMLElement, category })
115+
else
116+
entryEls.delete(dockId)
117+
}
118+
22119
function getCategoryLabel(category: string): string {
23120
const labels: Record<string, string> = {
24121
'~viteplus': 'Vite+',
@@ -132,7 +229,7 @@ function resetCustomOrderForCategory(category: string) {
132229
Manage visibility and order of dock entries. Hidden entries will not appear in the dock bar.
133230
</p>
134231

135-
<div class="flex flex-col gap-4">
232+
<div ref="sortContainer" class="flex flex-col gap-4">
136233
<template v-for="[category, entries] of categories" :key="category">
137234
<div
138235
class="border border-base rounded-lg overflow-hidden transition-opacity"
@@ -173,9 +270,23 @@ function resetCustomOrderForCategory(category: string) {
173270
<div
174271
v-for="(dock, index) of entries"
175272
:key="dock.id"
176-
class="flex items-center gap-3 px-4 py-2.5 hover:bg-gray/5 transition-colors group border-b border-base border-t-0"
177-
:class="settings.docksHidden.includes(dock.id) ? 'op40' : ''"
273+
:ref="(el: any) => setEntryRef(el, dock.id, category)"
274+
:data-dock-id="dock.id"
275+
:data-category="category"
276+
class="flex items-center gap-3 px-2 py-2.5 hover:bg-gray/5 transition-all group border-b border-base border-t-0"
277+
:class="[
278+
settings.docksHidden.includes(dock.id) ? 'op40' : '',
279+
hasMoved && draggingId === dock.id ? 'op30 bg-gray/10' : '',
280+
dragOverId === dock.id ? 'ring-1.5 ring-purple/50 rounded' : '',
281+
hasMoved ? 'select-none' : '',
282+
]"
178283
>
284+
<!-- drag icon -->
285+
<div
286+
class="i-ph-dots-six-vertical w-4 h-4 shrink-0 op25 group-hover:op50 transition-opacity cursor-grab"
287+
:style="hasMoved && draggingId === dock.id ? 'cursor: grabbing' : ''"
288+
/>
289+
179290
<!-- Visibility toggle -->
180291
<button
181292
class="w-6 h-6 flex items-center justify-center rounded border border-transparent hover:border-base transition-colors shrink-0"

0 commit comments

Comments
 (0)