Skip to content

Commit 77c0bc7

Browse files
authored
feat: add settings for devtools plugin (#597)
1 parent 344987a commit 77c0bc7

File tree

10 files changed

+269
-36
lines changed

10 files changed

+269
-36
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script setup lang="ts">
2+
import { VueSelect, VueSwitch } from '@vue/devtools-ui'
3+
import { rpc } from '@vue/devtools-core'
4+
import { useCustomInspectorState } from '~/composables/custom-inspector-state'
5+
6+
const props = defineProps<{
7+
pluginId: string
8+
options: Record<string, unknown>
9+
values: Record<string, unknown>
10+
}>()
11+
const emit = defineEmits(['update'])
12+
const state = useCustomInspectorState()
13+
const options = computed(() => props.options)
14+
const values = computed(() => props.values)
15+
16+
function toggleOption(key: string, v: unknown) {
17+
rpc.value.updatePluginSettings(props.pluginId, key, v)
18+
rpc.value.getPluginSettings(props.pluginId).then((_settings) => {
19+
emit('update', _settings)
20+
})
21+
}
22+
</script>
23+
24+
<template>
25+
<div class="flex-1 overflow-y-auto p2">
26+
<ul>
27+
<li v-for="(item, index) in options" :key="index" class="flex items-center py-2">
28+
<div class="max-w-[190px] flex-1 select-none py-1.5 text-sm">
29+
{{ item.label }}
30+
</div>
31+
<div class="w-4/5">
32+
<div v-if="item.type === 'boolean'" class="flex justify-start">
33+
<VueSwitch
34+
:model-value="values[index]"
35+
class="row-reverse flex hover:bg-active py1 pl2 pr1"
36+
@update:model-value="(v: boolean) => toggleOption(index, v)"
37+
>
38+
<div flex="~ gap-2" flex-auto items-center justify-start>
39+
<span capitalize op75>{{ name }}</span>
40+
</div>
41+
</VueSwitch>
42+
</div>
43+
<template v-else-if="item.type === 'choice'">
44+
<div>
45+
<VueSelect
46+
:model-value="values[index]"
47+
:options="item.options"
48+
@update:model-value="(v: string) => toggleOption(index, v)"
49+
/>
50+
</div>
51+
</template>
52+
<template v-else-if="item.type === 'text'">
53+
<VueInput :model-value="values[index]" @update:model-value="(v: string) => toggleOption(index, v)" />
54+
</template>
55+
</div>
56+
</li>
57+
</ul>
58+
</div>
59+
</template>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
import Settings from '~/components/settings/Settings.vue'
3+
import { useCustomInspectorState } from '~/composables/custom-inspector-state'
4+
import Navbar from '~/components/basic/Navbar.vue'
5+
import DevToolsHeader from '~/components/basic/DevToolsHeader.vue'
6+
7+
const settings = inject('pluginSettings')
8+
const customInspectState = useCustomInspectorState()
9+
const options = computed(() => settings.value.options)
10+
const values = computed(() => settings.value.values)
11+
12+
function update(_settings) {
13+
settings.value = _settings
14+
}
15+
</script>
16+
17+
<template>
18+
<div class="h-full flex flex-col">
19+
<DevToolsHeader :doc-link="customInspectState.homepage!">
20+
<Navbar />
21+
</DevToolsHeader>
22+
<Settings :plugin-id="customInspectState.id" :options="options" :values="values" @update="update" />
23+
</div>
24+
</template>

packages/applet/src/modules/custom-inspector/index.vue

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { computed, onUnmounted, ref, watch } from 'vue'
33
import { onRpcConnected, rpc } from '@vue/devtools-core'
44
55
import About from './components/About.vue'
6+
import Settings from './components/Settings.vue'
67
import State from './components/state/Index.vue'
78
import Timeline from './components/timeline/Index.vue'
89
import AppConnecting from '~/components/basic/AppConnecting.vue'
@@ -17,6 +18,9 @@ const emit = defineEmits(['loadError'])
1718
const inspectorState = createCustomInspectorStateContext()
1819
const loading = ref(false)
1920
21+
const pluginSettings = ref(null)
22+
provide('pluginSettings', pluginSettings)
23+
2024
const routes = computed(() => {
2125
return [
2226
{
@@ -36,6 +40,12 @@ const routes = computed(() => {
3640
name: 'About',
3741
component: About,
3842
},
43+
pluginSettings.value && ({
44+
path: '/settings',
45+
name: 'Settings',
46+
component: Settings,
47+
icon: 'i-mdi:cog-outline',
48+
}),
3949
].filter(Boolean) as VirtualRoute[]
4050
})
4151
@@ -62,6 +72,14 @@ function getInspectorInfo() {
6272
restoreRouter()
6373
loading.value = false
6474
})
75+
rpc.value.getPluginSettings(props.id).then((settings) => {
76+
if (settings.options) {
77+
pluginSettings.value = settings
78+
}
79+
else {
80+
pluginSettings.value = null
81+
}
82+
})
6583
})
6684
}
6785
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import Settings from '~/components/settings/Settings.vue'
3+
import Navbar from '~/components/basic/Navbar.vue'
4+
import DevToolsHeader from '~/components/basic/DevToolsHeader.vue'
5+
6+
const settings = inject('pluginSettings')
7+
const options = computed(() => settings.value.options)
8+
const values = computed(() => settings.value.values)
9+
const inspectorId = 'pinia'
10+
11+
function update(_settings) {
12+
settings.value = _settings
13+
}
14+
</script>
15+
16+
<template>
17+
<div class="h-full flex flex-col">
18+
<DevToolsHeader doc-link="https://pinia.vuejs.org/" github-repo-link="https://github.com/vuejs/pinia">
19+
<Navbar />
20+
</DevToolsHeader>
21+
<Settings :plugin-id="inspectorId" :options="options" :values="values" @update="update" />
22+
</div>
23+
</template>

packages/applet/src/modules/pinia/index.vue

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,57 @@
11
<script setup lang="ts">
2+
import { onRpcConnected, rpc } from '@vue/devtools-core'
23
import About from './components/About.vue'
34
import Store from './components/store/Index.vue'
45
import Timeline from './components/timeline/Index.vue'
6+
import Settings from './components/Settings.vue'
57
import { registerVirtualRouter } from '~/composables/virtual-router'
68
7-
const { VirtualRouterView } = registerVirtualRouter([
8-
{
9-
path: '/store',
10-
name: 'Store',
11-
component: Store,
12-
icon: 'i-carbon-tree-view-alt',
13-
},
14-
{
15-
path: '/timeline',
16-
name: 'Timeline',
17-
component: Timeline,
18-
icon: 'i-mdi:timeline-clock-outline',
19-
},
20-
{
21-
path: '/',
22-
name: 'About',
23-
component: About,
24-
icon: 'i-logos-pinia',
25-
},
26-
], {
9+
const pluginSettings = ref(null)
10+
provide('pluginSettings', pluginSettings)
11+
12+
const routes = computed(() => {
13+
return [
14+
{
15+
path: '/store',
16+
name: 'Store',
17+
component: Store,
18+
icon: 'i-carbon-tree-view-alt',
19+
},
20+
{
21+
path: '/timeline',
22+
name: 'Timeline',
23+
component: Timeline,
24+
icon: 'i-mdi:timeline-clock-outline',
25+
},
26+
{
27+
path: '/',
28+
name: 'About',
29+
component: About,
30+
icon: 'i-logos-pinia',
31+
},
32+
pluginSettings.value && ({
33+
path: '/settings',
34+
name: 'Settings',
35+
component: Settings,
36+
icon: 'i-mdi:cog-outline',
37+
}),
38+
].filter(Boolean) as VirtualRoute[]
39+
})
40+
41+
const { VirtualRouterView, restoreRouter } = registerVirtualRouter(routes, {
2742
defaultRoutePath: '/store',
2843
})
44+
45+
onRpcConnected(() => {
46+
rpc.value.getPluginSettings('pinia').then((settings) => {
47+
if (settings.options) {
48+
pluginSettings.value = settings
49+
}
50+
else {
51+
pluginSettings.value = null
52+
}
53+
})
54+
})
2955
</script>
3056

3157
<template>

packages/core/src/rpc/global.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ export const functions = {
121121
async toggleApp(id: string) {
122122
return devtools.ctx.api.toggleApp(id)
123123
},
124+
updatePluginSettings(pluginId: string, key: string, value: string) {
125+
return devtools.ctx.api.updatePluginSettings(pluginId, key, value)
126+
},
127+
getPluginSettings(pluginId: string) {
128+
return devtools.ctx.api.getPluginSettings(pluginId)
129+
},
124130
getRouterInfo() {
125131
return devtoolsRouterInfo
126132
},

packages/devtools-kit/src/api/v6/index.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { DevtoolsContext } from '../../ctx'
22
import type { App, ComponentBounds, ComponentInstance, CustomInspectorOptions, DevToolsPlugin, TimelineEventOptions, TimelineLayerOptions } from '../../types'
33
import { DevToolsContextHookKeys, DevToolsV6PluginAPIHookKeys, DevToolsV6PluginAPIHooks } from '../../ctx/hook'
4-
import { devtoolsPluginBuffer } from '../../ctx/plugin'
54
import { devtoolsHooks } from '../../hook'
65
import { DevToolsHooks } from '../../types'
76
import { getActiveInspectors } from '../../ctx/inspector'
7+
import { getPluginSettings, initPluginSettings } from '../../core/plugin/plugin-settings'
88

99
export class DevToolsV6PluginAPI {
1010
private plugin: DevToolsPlugin
@@ -78,6 +78,9 @@ export class DevToolsV6PluginAPI {
7878
// custom inspector
7979
addInspector(options: CustomInspectorOptions) {
8080
this.hooks.callHook(DevToolsContextHookKeys.ADD_INSPECTOR, { inspector: options, plugin: this.plugin })
81+
if (this.plugin.descriptor.settings) {
82+
initPluginSettings(options.id, this.plugin.descriptor.settings)
83+
}
8184
}
8285

8386
sendInspectorTree(inspectorId: string) {
@@ -107,21 +110,9 @@ export class DevToolsV6PluginAPI {
107110

108111
// settings
109112
getSettings(pluginId?: string) {
110-
function _getSettings(settings) {
111-
const _settings = {}
112-
Object.keys(settings!).forEach((key) => {
113-
_settings[key] = settings![key].defaultValue
114-
})
113+
const inspector = getActiveInspectors().find(i => i.packageName === this.plugin.descriptor.packageName)
115114

116-
return _settings
117-
}
118-
if (pluginId) {
119-
const item = devtoolsPluginBuffer.find(item => item[0].id === pluginId)?.[0] ?? null
120-
return _getSettings(item?.settings) ?? _getSettings(this.plugin.descriptor.settings)
121-
}
122-
else {
123-
return _getSettings(this.plugin.descriptor.settings)
124-
}
115+
return getPluginSettings((pluginId ?? inspector?.id)!, this.plugin.descriptor.settings)
125116
}
126117

127118
// utilities

packages/devtools-kit/src/core/plugin/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { target } from '@vue/devtools-shared'
22
import { App, PluginDescriptor, PluginSetupFunction } from '../../types'
33
import { hook } from '../../hook'
4-
import { devtoolsContext, devtoolsPluginBuffer } from '../../ctx'
4+
import { devtoolsContext, devtoolsInspector, devtoolsPluginBuffer } from '../../ctx'
55
import { DevToolsPluginAPI } from '../../api'
6+
import { initPluginSettings } from '../../core/plugin/plugin-settings'
67

78
export * from './components'
89

@@ -33,6 +34,13 @@ export function callDevToolsPluginSetupFn(plugin: [PluginDescriptor, PluginSetup
3334
}
3435

3536
setupFn(api)
37+
if (pluginDescriptor.settings) {
38+
const inspector = devtoolsInspector.find(inspector => inspector.descriptor.id === pluginDescriptor.id)
39+
if (inspector) {
40+
inspector.descriptor.settings = pluginDescriptor.settings
41+
initPluginSettings(inspector.options.id, pluginDescriptor.settings)
42+
}
43+
}
3644
}
3745

3846
export function removeRegisteredPluginApp(app: App) {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { devtoolsPluginBuffer } from '../../ctx/plugin'
2+
import { PluginDescriptor } from '../../types'
3+
import { DevToolsV6PluginAPIHookKeys, devtoolsContext, getInspector } from '../../ctx'
4+
5+
function _getSettings(settings: PluginDescriptor['settings']) {
6+
const _settings = {}
7+
Object.keys(settings!).forEach((key) => {
8+
_settings[key] = settings![key].defaultValue
9+
})
10+
11+
return _settings
12+
}
13+
14+
function getPluginLocalKey(pluginId: string) {
15+
return `__VUE_DEVTOOLS_NEXT_PLUGIN_SETTINGS__${pluginId}__`
16+
}
17+
18+
export function getPluginSettingsOptions(pluginId: string) {
19+
const descriptor = getInspector(pluginId)?.descriptor
20+
const item = devtoolsPluginBuffer.find(item => item[0].id === descriptor?.id)?.[0] ?? null
21+
return item?.settings ?? null
22+
}
23+
24+
export function getPluginSettings(pluginId: string, fallbackValue?: PluginDescriptor['settings']) {
25+
const localKey = getPluginLocalKey(pluginId)
26+
if (localKey) {
27+
const localSettings = localStorage.getItem(localKey)
28+
if (localSettings) {
29+
return JSON.parse(localSettings)
30+
}
31+
}
32+
33+
if (pluginId) {
34+
const item = devtoolsPluginBuffer.find(item => item[0].id === pluginId)?.[0] ?? null
35+
return _getSettings(item?.settings ?? {})
36+
}
37+
return _getSettings(fallbackValue)
38+
}
39+
40+
export function initPluginSettings(pluginId: string, settings: PluginDescriptor['settings']) {
41+
const localKey = getPluginLocalKey(pluginId)
42+
const localSettings = localStorage.getItem(localKey)
43+
if (!localSettings) {
44+
localStorage.setItem(localKey, JSON.stringify(_getSettings(settings)))
45+
}
46+
}
47+
48+
export function setPluginSettings(pluginId: string, key: string, value: string) {
49+
const localKey = getPluginLocalKey(pluginId)
50+
const localSettings = localStorage.getItem(localKey)!
51+
const parsedLocalSettings = JSON.parse(localSettings || '{}')
52+
const updated = {
53+
...parsedLocalSettings,
54+
[key]: value,
55+
}
56+
localStorage.setItem(localKey, JSON.stringify(updated))
57+
58+
// @ts-expect-error hookable
59+
devtoolsContext.hooks.callHookWith((callbacks) => {
60+
callbacks.forEach(cb => cb({
61+
pluginId,
62+
key,
63+
oldValue: parsedLocalSettings[key],
64+
newValue: value,
65+
settings: updated,
66+
}))
67+
}, DevToolsV6PluginAPIHookKeys.SET_PLUGIN_SETTINGS)
68+
}

0 commit comments

Comments
 (0)