Skip to content
This repository was archived by the owner on Jan 6, 2024. It is now read-only.

Commit df46f92

Browse files
feat(settings): draggable tab, tab groups (#85)
1 parent 7be14bd commit df46f92

File tree

16 files changed

+557
-176
lines changed

16 files changed

+557
-176
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"vite-plugin-inspect": "^0.7.28",
7272
"vite-plugin-vue-inspector": "^3.4.2",
7373
"vue-router": "^4.2.2",
74+
"vuedraggable": "^4.1.0",
7475
"xterm": "^5.1.0",
7576
"xterm-addon-fit": "^0.7.0"
7677
},

pnpm-lock.yaml

Lines changed: 20 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client/auto-imports.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ declare global {
149149
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
150150
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
151151
const useCached: typeof import('@vueuse/core')['useCached']
152-
const useCategorizedTabs: typeof import('./composables/tabs')['useCategorizedTabs']
153152
const useClipboard: typeof import('@vueuse/core')['useClipboard']
154153
const useCloned: typeof import('@vueuse/core')['useCloned']
155154
const useColorMode: typeof import('@vueuse/core')['useColorMode']
@@ -196,6 +195,8 @@ declare global {
196195
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
197196
const useGamepad: typeof import('@vueuse/core')['useGamepad']
198197
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
198+
const useGroupedTabStore: typeof import('./composables/tabs')['useGroupedTabStore']
199+
const useGroupedTabs: typeof import('./composables/tabs')['useGroupedTabs']
199200
const useIdle: typeof import('@vueuse/core')['useIdle']
200201
const useImage: typeof import('@vueuse/core')['useImage']
201202
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']

src/client/components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ declare module '@vue/runtime-core' {
3333
SideNavItem: typeof import('./components/SideNavItem.vue')['default']
3434
StateFields: typeof import('./components/StateFields.vue')['default']
3535
StateFieldsTree: typeof import('./components/StateFieldsTree.vue')['default']
36+
TabGroup: typeof import('./components/TabGroup.vue')['default']
37+
TabGroupItem: typeof import('./components/TabGroupItem.vue')['default']
3638
TabIcon: typeof import('./components/TabIcon.vue')['default']
3739
TerminalView: typeof import('./components/TerminalView.vue')['default']
3840
TimelineEvent: typeof import('./components/TimelineEvent.vue')['default']
3941
VBadge: typeof import('./ui-kit/VBadge.vue')['default']
4042
VButton: typeof import('./ui-kit/VButton.vue')['default']
4143
VCard: typeof import('./ui-kit/VCard.vue')['default']
44+
VConfirm: typeof import('./ui-kit/VConfirm.vue')['default']
4245
VDarkToggle: typeof import('./ui-kit/VDarkToggle.vue')['default']
4346
VDialog: typeof import('./ui-kit/VDialog.vue')['default']
4447
VExpandIcon: typeof import('./ui-kit/VExpandIcon.vue')['default']

src/client/components/SideNav.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<script setup lang="ts">
2-
const categories = useCategorizedTabs()
2+
import { getSortedTabs } from '../store'
3+
4+
const groupedTabs = useGroupedTabs()
35
</script>
46

57
<template>
@@ -19,11 +21,11 @@ const categories = useCategorizedTabs()
1921
</div>
2022

2123
<div flex="~ auto col gap-0.5 items-center" of-auto class="no-scrollbar" py1>
22-
<template v-for="[name, tabs], idx of categories" :key="name">
23-
<template v-if="tabs.length">
24+
<template v-for="[name, { tabs, show }], idx of groupedTabs" :key="name">
25+
<template v-if="tabs.length && show">
2426
<div v-if="idx" my1 h-1px w-8 border="b base" />
2527
<SideNavItem
26-
v-for="tab of tabs"
28+
v-for="tab of getSortedTabs(tabs)"
2729
:key="tab.path"
2830
:tab="tab"
2931
/>

src/client/components/SideNavItem.vue

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
<script setup lang="ts">
2-
import type { BuiltinTab } from '../../types'
2+
import type { Tab } from '../../types'
33
import { useDevtoolsClient } from '../logic/client'
4+
import { getMappedBuiltinTabs } from '../store'
45
56
defineProps<{
6-
tab: BuiltinTab
7+
tab: Tab
78
}>()
89
const client = useDevtoolsClient()
910
const router = useRouter()
11+
12+
function handleClick(tab: Tab) {
13+
const builtinTab = getMappedBuiltinTabs(tab)
14+
if (builtinTab)
15+
builtinTab.event?.(client.value, router)
16+
}
1017
</script>
1118

1219
<template>
@@ -19,7 +26,7 @@ const router = useRouter()
1926
hover="bg-active"
2027
h-10 w-10 select-none items-center justify-center rounded-xl p1 text-secondary
2128
exact-active-class="!text-primary bg-active"
22-
@click="tab.event?.(client, router)"
29+
@click="() => handleClick(tab)"
2330
>
2431
<TabIcon
2532
text-xl

src/client/components/TabGroup.vue

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<script setup lang="ts">
2+
import type { AllTabGroup } from '../../types'
3+
import {
4+
DEFAULT_TAB_GROUP, checkGroupExist,
5+
createGroup, removeTabGroup, resetAllTabs,
6+
shouldHideTabGroup, ungroupAllTabs,
7+
} from '../store'
8+
9+
const groupTabs = useGroupedTabs()
10+
11+
const [showConfirm, toggleConfirm] = useToggle(false)
12+
13+
const currentRemovedGroup = ref<AllTabGroup | null>(null)
14+
const confirmHandlers = {
15+
ungroup: {
16+
message: 'Are you sure you want to ungroup all tabs?',
17+
handler: ungroupAllTabs,
18+
},
19+
reset: {
20+
message: 'Are you sure you want to reset group?',
21+
handler: resetAllTabs,
22+
},
23+
remove: {
24+
message: 'Are you sure you want to remove this group?',
25+
handler: () => currentRemovedGroup.value && removeTabGroup(currentRemovedGroup.value),
26+
},
27+
}
28+
const currentConfirm = ref<keyof typeof confirmHandlers>('reset')
29+
const currentConfirmHandlers = computed(() => confirmHandlers[currentConfirm.value])
30+
31+
function handleShowConfirm(confirmType: keyof typeof confirmHandlers) {
32+
currentConfirm.value = confirmType
33+
toggleConfirm(true)
34+
}
35+
36+
const groupName = ref('')
37+
38+
function handleCreateGroup() {
39+
const name = groupName.value.trim()
40+
if (checkGroupExist(name))
41+
// eslint-disable-next-line no-alert
42+
return alert('[Vue-Devtools] Group already exist')
43+
44+
createGroup(name)
45+
groupName.value = ''
46+
}
47+
</script>
48+
49+
<template>
50+
<div flex flex-wrap items-center gap-12px>
51+
<VButton n="primary" @click.prevent="handleShowConfirm('reset')">
52+
<div i-material-symbols:360 />
53+
Reset group
54+
</VButton>
55+
<VButton
56+
n="primary" @click.prevent="handleShowConfirm('ungroup')"
57+
>
58+
<div i-material-symbols-ungroup />
59+
Ungroup all tabs
60+
</VButton>
61+
<div flex gap-2>
62+
<VTextInput v-model="groupName" class="w-120px" />
63+
<VButton
64+
border-none bg-primary text-white hover:text-white
65+
:disabled="!groupName.trim().length" @click="handleCreateGroup()"
66+
>
67+
Create
68+
</VButton>
69+
</div>
70+
</div>
71+
<VConfirm v-model="showConfirm" :message="currentConfirmHandlers.message" @confirm="currentConfirmHandlers.handler" />
72+
<template v-for="[name, { show, tabs }] in groupTabs" :key="name">
73+
<div v-if="!shouldHideTabGroup(name, tabs.length) && show" mt-3>
74+
<div flex="~ gap-2" flex-auto items-center justify-between>
75+
<span capitalize op75>{{ name }}</span>
76+
<VIconButton
77+
v-if="name !== DEFAULT_TAB_GROUP"
78+
icon="material-symbols:delete" class="hover:color-red"
79+
@click="currentRemovedGroup = name; handleShowConfirm('remove')"
80+
/>
81+
</div>
82+
<TabGroupItem
83+
:tabs="tabs" :group-name="name"
84+
/>
85+
</div>
86+
</template>
87+
</template>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script setup lang="ts">
2+
import Draggable from 'vuedraggable'
3+
import type { AllTabGroup, Tab } from '../../types'
4+
import { getSortedTabs, updateTabsPosition } from '../store'
5+
6+
const props = defineProps<{
7+
groupName: AllTabGroup
8+
tabs: Tab[]
9+
}>()
10+
11+
const dragging = ref(false)
12+
13+
const dragTabs = computed(() => getSortedTabs(props.tabs))
14+
</script>
15+
16+
<template>
17+
<Draggable
18+
:model-value="dragTabs"
19+
item-key="title"
20+
group="tab" :animation="200" class="min-h-40px flex-wrap gap3 p2 container"
21+
@update:model-value="tabs => {
22+
updateTabsPosition(groupName, tabs)
23+
}"
24+
@start="dragging = true" @end="dragging = false"
25+
>
26+
<template #item="{ element }: { element: Tab }">
27+
<div :class="{ 'hover:color-primary hover:bg-gray-2/20': !dragging }" y cursor-pointer rounded px2 py1 transition-colors>
28+
<VIcon :icon="element.icon" />
29+
{{ element.title }}
30+
</div>
31+
</template>
32+
</Draggable>
33+
</template>
34+
35+
<style scoped>
36+
.container {
37+
--at-apply: "mt2 flex border-1 border-base rounded-3";
38+
}
39+
40+
.container:empty {
41+
padding: 0.8rem;
42+
text-align: center;
43+
}
44+
45+
.container:empty:before {
46+
content: 'Empty group';
47+
}
48+
</style>

src/client/composables/settings.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { toRefs } from '@vueuse/core'
33
export interface DevToolsUISettings {
44
scale: number
55
hiddenTabs: string[]
6-
hiddenTabCategories: string[]
6+
hiddenTabGroups: string[]
77
}
88

99
const devToolsSettings = useLocalStorage<DevToolsUISettings>('__vue-devtools-settings__', {
1010
scale: 1,
1111
hiddenTabs: [],
12-
hiddenTabCategories: [],
12+
hiddenTabGroups: [],
1313
}, { mergeDefaults: true })
1414

1515
const devToolsSettingsRefs = toRefs(devToolsSettings)

0 commit comments

Comments
 (0)