Skip to content

Commit 32746c5

Browse files
authoredFeb 3, 2024
feat: enable editing on BigInt type (#215)
1 parent a1ff8d9 commit 32746c5

File tree

8 files changed

+78
-50
lines changed

8 files changed

+78
-50
lines changed
 

‎packages/client/src/components/inspector/InspectorDataField/Actions.vue

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { toRaw } from 'vue'
33
import { VueButton, VueDropdown, VueDropdownButton, VueIcon, VTooltip as vTooltip } from '@vue/devtools-ui'
4-
import { getRawValue } from '@vue/devtools-kit'
4+
import { getRaw } from '@vue/devtools-kit'
55
import type { InspectorState, InspectorStateEditorPayload } from '@vue/devtools-kit'
66
import type { ButtonProps } from '@vue/devtools-ui/dist/types/src/components/Button'
77
import { useDevToolsBridgeRpc } from '@vue/devtools-core'
@@ -30,7 +30,9 @@ const { copy, isSupported } = useClipboard()
3030
3131
const popupVisible = ref(false)
3232
33-
const rawValue = computed(() => getRawValue(props.data.value).value)
33+
const raw = computed(() => getRaw(props.data.value))
34+
const rawValue = computed(() => raw.value.value)
35+
const customType = computed(() => raw.value.customType)
3436
const dataType = computed(() => typeof rawValue.value)
3537
3638
const iconButtonProps = {
@@ -56,6 +58,13 @@ function quickEdit(v: unknown, remove: boolean = false) {
5658
},
5759
} satisfies InspectorStateEditorPayload)
5860
}
61+
62+
function quickEditNum(v: number | string, offset: 1 | -1) {
63+
const target = typeof v === 'number'
64+
? v + offset
65+
: BigInt(v) + BigInt(offset)
66+
quickEdit(target)
67+
}
5968
</script>
6069

6170
<template>
@@ -94,14 +103,14 @@ function quickEdit(v: unknown, remove: boolean = false) {
94103
<VueIcon :icon="rawValue ? 'i-material-symbols-check-box-sharp' : 'i-material-symbols-check-box-outline-blank-sharp'" />
95104
</template>
96105
</VueButton>
97-
<!-- increment/decrement button, numeric value only -->
98-
<template v-else-if="dataType === 'number'">
99-
<VueButton v-bind="iconButtonProps" :class="buttonClass" @click.stop="quickEdit((rawValue as number) + 1)">
106+
<!-- increment/decrement button, numeric/bigint -->
107+
<template v-else-if="dataType === 'number' || customType === 'bigint'">
108+
<VueButton v-bind="iconButtonProps" :class="buttonClass" @click.stop="quickEditNum(rawValue as number | string, 1)">
100109
<template #icon>
101110
<VueIcon icon="i-carbon-add" />
102111
</template>
103112
</VueButton>
104-
<VueButton v-bind="iconButtonProps" :class="buttonClass" @click.stop="quickEdit((rawValue as number) - 1)">
113+
<VueButton v-bind="iconButtonProps" :class="buttonClass" @click.stop="quickEditNum(rawValue as number | string, -1)">
105114
<template #icon>
106115
<VueIcon icon="i-carbon-subtract" />
107116
</template>
@@ -129,7 +138,7 @@ function quickEdit(v: unknown, remove: boolean = false) {
129138
<template #popper>
130139
<div class="w160px py5px">
131140
<VueDropdownButton
132-
@click="copy(dataType === 'object' ? JSON.stringify(rawValue) : rawValue.toString())"
141+
@click="copy(typeof rawValue === 'object' ? JSON.stringify(rawValue) : rawValue.toString())"
133142
>
134143
<template #icon>
135144
<VueIcon icon="i-material-symbols-copy-all-rounded" class="mt4px" />

‎packages/client/src/components/inspector/InspectorDataField/EditInput.vue

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<script setup lang="ts">
22
import { VueButton, VueIcon, VueInput, VTooltip as vTooltip } from '@vue/devtools-ui'
33
import { debounce } from 'perfect-debounce'
4-
import { toSubmit } from '@vue/devtools-kit'
4+
import { customTypeEnums, toSubmit } from '@vue/devtools-kit'
55
66
const props = withDefaults(defineProps<{
77
modelValue: string
8-
type: string // typeof value
8+
customType?: customTypeEnums
99
showActions?: boolean
1010
autoFocus?: boolean
1111
}>(), {
@@ -15,7 +15,7 @@ const props = withDefaults(defineProps<{
1515
1616
const emit = defineEmits<{
1717
'cancel': []
18-
'submit': [dataType: string]
18+
'submit': []
1919
'update:modelValue': [value: string]
2020
}>()
2121
@@ -28,14 +28,14 @@ watchEffect(() => {
2828
if (escape.value)
2929
emit('cancel')
3030
else if (enter.value)
31-
emit('submit', props.type)
31+
emit('submit')
3232
})
3333
3434
const value = useVModel(props, 'modelValue', emit)
3535
3636
function tryToParseJSONString(v: unknown) {
3737
try {
38-
toSubmit(v as string)
38+
toSubmit(v as string, props.customType)
3939
return true
4040
}
4141
catch {
@@ -67,7 +67,7 @@ watch(value, checkWarning())
6767
<VueButton
6868
v-tooltip="{
6969
content: 'Enter to submit change',
70-
}" size="mini" flat class="p2px!" @click.stop="$emit('submit', type)"
70+
}" size="mini" flat class="p2px!" @click.stop="$emit('submit')"
7171
>
7272
<template #icon>
7373
<VueIcon icon="i-material-symbols-save" />

‎packages/client/src/components/inspector/InspectorStateField.vue

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import type { InspectorCustomState, InspectorState, InspectorStateEditorPayload } from '@vue/devtools-kit'
33
import { isArray, isObject, sortByKey } from '@vue/devtools-shared'
4-
import { formatInspectorStateValue, getInspectorStateValueType, getRawValue, toEdit, toSubmit } from '@vue/devtools-kit'
4+
import { formatInspectorStateValue, getInspectorStateValueType, getRaw, toEdit, toSubmit } from '@vue/devtools-kit'
55
import { useDevToolsBridgeRpc } from '@vue/devtools-core'
66
import { VueButton, VueIcon, VTooltip as vTooltip } from '@vue/devtools-ui'
77
import Actions from './InspectorDataField/Actions.vue'
@@ -50,12 +50,12 @@ const normalizedValue = computed(() => {
5050
}
5151
})
5252
53-
const rawValue = computed(() => getRawValue(props.data.value))
53+
const raw = computed(() => getRaw(props.data.value))
5454
5555
const limit = ref(STATE_FIELDS_LIMIT_SIZE)
5656
5757
const normalizedChildField = computed(() => {
58-
const { value, inherit } = rawValue.value
58+
const { value, inherit } = raw.value
5959
let displayedValue: any[]
6060
if (isArray(value)) {
6161
const sliced = value.slice(0, limit.value)
@@ -86,7 +86,7 @@ const normalizedChildField = computed(() => {
8686
})
8787
8888
const fieldsCount = computed(() => {
89-
const { value } = rawValue.value
89+
const { value } = raw.value
9090
if (isArray(value))
9191
return value.length
9292
else if (isObject(value))
@@ -112,15 +112,15 @@ const { editingType, editing, editingText, toggleEditing, nodeId } = useStateEdi
112112
113113
watch(() => editing.value, (v) => {
114114
if (v) {
115-
const { value } = rawValue.value
116-
editingText.value = toEdit(value)
115+
const { value } = raw.value
116+
editingText.value = toEdit(value, raw.value.customType)
117117
}
118118
else {
119119
editingText.value = ''
120120
}
121121
})
122122
123-
function submit(dataType: string) {
123+
function submit() {
124124
const data = props.data
125125
bridgeRpc.editInspectorState({
126126
path: data.key.split('.'),
@@ -129,8 +129,8 @@ function submit(dataType: string) {
129129
nodeId,
130130
state: {
131131
newKey: null!,
132-
type: dataType,
133-
value: toSubmit(editingText.value),
132+
type: editingType.value,
133+
value: toSubmit(editingText.value, raw.value.customType),
134134
},
135135
} satisfies InspectorStateEditorPayload)
136136
toggleEditing()
@@ -142,7 +142,7 @@ const { addNewProp: addNewPropApi, draftingNewProp, resetDrafting } = useStateEd
142142
function addNewProp(type: EditorAddNewPropType) {
143143
if (!isExpanded.value)
144144
toggleCollapse()
145-
addNewPropApi(type, rawValue.value.value)
145+
addNewPropApi(type, raw.value.value)
146146
}
147147
148148
function submitDrafting() {
@@ -173,7 +173,7 @@ const { isHovering } = useHover(() => containerRef.value)
173173
<div>
174174
<span overflow-hidden text-ellipsis whitespace-nowrap state-key>{{ normalizedDisplayedKey }}</span>
175175
<span mx-1>:</span>
176-
<EditInput v-if="editing" v-model="editingText" :type="editingType" @cancel="toggleEditing" @submit="submit" />
176+
<EditInput v-if="editing" v-model="editingText" :custom-type="raw.customType" @cancel="toggleEditing" @submit="submit" />
177177
<template v-else>
178178
<span :class="stateFormatClass">
179179
<span v-html="normalizedValue" />
@@ -191,7 +191,7 @@ const { isHovering } = useHover(() => containerRef.value)
191191
<ExpandIcon :value="isExpanded" absolute left--6 group-hover:text-white />
192192
<span overflow-hidden text-ellipsis whitespace-nowrap state-key>{{ normalizedDisplayedKey }}</span>
193193
<span mx-1>:</span>
194-
<EditInput v-if="editing" v-model="editingText" :type="editingType" @cancel="toggleEditing" @submit="submit" />
194+
<EditInput v-if="editing" v-model="editingText" :custom-type="raw.customType" @cancel="toggleEditing" @submit="submit" />
195195
<template v-else>
196196
<span :class="stateFormatClass">
197197
<span v-html="normalizedValue" />
@@ -214,10 +214,10 @@ const { isHovering } = useHover(() => containerRef.value)
214214
</VueButton>
215215
<div v-if="draftingNewProp.enable" :style="{ paddingLeft: `${(depth + 1) * 15 + 4}px` }">
216216
<span overflow-hidden text-ellipsis whitespace-nowrap state-key>
217-
<EditInput v-model="draftingNewProp.key" type="string" :show-actions="false" />
217+
<EditInput v-model="draftingNewProp.key" :show-actions="false" />
218218
</span>
219219
<span mx-1>:</span>
220-
<EditInput v-model="draftingNewProp.value" type="string" :auto-focus="false" @cancel="resetDrafting" @submit="submitDrafting" />
220+
<EditInput v-model="draftingNewProp.value" :auto-focus="false" @cancel="resetDrafting" @submit="submitDrafting" />
221221
</div>
222222
</div>
223223
</div>

‎packages/devtools-kit/src/core/component/state/__tests__/format.spec.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as format from '../format'
2+
import { customTypeEnums } from '../../types'
23
import { INFINITY, NAN, NEGATIVE_INFINITY, UNDEFINED } from '../constants'
34

4-
describe('format: displayText and rawValue can be calculated by formatInspectorStateValue, getRawValue', () => {
5+
describe('format: displayText and rawValue can be calculated by formatInspectorStateValue, getRaw', () => {
56
describe('type: literals', () => {
67
// eslint-disable-next-line test/consistent-test-it
78
test.each([
@@ -16,7 +17,7 @@ describe('format: displayText and rawValue can be calculated by formatInspectorS
1617
{ literal: UNDEFINED, displayText: 'undefined' },
1718
])('type: %s', (value) => {
1819
const displayText = format.formatInspectorStateValue(value.literal)
19-
const rawValue = format.getRawValue(value.literal).value
20+
const rawValue = format.getRaw(value.literal).value
2021

2122
expect(displayText).toBe(value.displayText)
2223
expect(rawValue).toBe(value.literal)
@@ -26,7 +27,7 @@ describe('format: displayText and rawValue can be calculated by formatInspectorS
2627
it('type: plain object', () => {
2728
const value = { foo: 'bar' }
2829
const displayText = format.formatInspectorStateValue(value)
29-
const rawValue = format.getRawValue(value).value
30+
const rawValue = format.getRaw(value).value
3031

3132
expect(displayText).toBe('Object')
3233
expect(rawValue).toEqual(value)
@@ -35,7 +36,7 @@ describe('format: displayText and rawValue can be calculated by formatInspectorS
3536
it('type: array', () => {
3637
const value = ['foo', { bar: 'baz' }]
3738
const displayText = format.formatInspectorStateValue(value)
38-
const rawValue = format.getRawValue(value).value
39+
const rawValue = format.getRaw(value).value
3940

4041
expect(displayText).toBe('Array[2]')
4142
expect(rawValue).toEqual(value)
@@ -45,7 +46,7 @@ describe('format: displayText and rawValue can be calculated by formatInspectorS
4546
it('type: common custom', () => {
4647
const value = { _custom: { displayText: 'custom-display', value: Symbol(123) } }
4748
const displayText = format.formatInspectorStateValue(value)
48-
const rawValue = format.getRawValue(value).value
49+
const rawValue = format.getRaw(value).value
4950

5051
expect(displayText).toBe(value._custom.displayText)
5152
expect(rawValue).toEqual(value._custom.value)
@@ -62,7 +63,7 @@ describe('format: displayText and rawValue can be calculated by formatInspectorS
6263
}
6364

6465
const displayText = format.formatInspectorStateValue(value)
65-
const rawValue = format.getRawValue(value).value
66+
const rawValue = format.getRaw(value).value
6667

6768
expect(displayText).toBe(value._custom.value._custom.displayText)
6869
expect(rawValue).toEqual(value._custom.value._custom.value)
@@ -87,8 +88,9 @@ describe('format: toEdit', () => {
8788
{ value: { foo: NAN }, target: '{"foo":NaN}' },
8889
{ value: { foo: NEGATIVE_INFINITY }, target: '{"foo":-Infinity}' },
8990
{ value: { foo: UNDEFINED }, target: '{"foo":undefined}' },
91+
{ value: '123', customType: 'bigint' as customTypeEnums, target: '123' },
9092
])('value: $value will be deserialized to target', (value) => {
91-
const deserialized = format.toEdit(value.value)
93+
const deserialized = format.toEdit(value.value, value.customType)
9294
expect(deserialized).toBe(value.target)
9395
})
9496
})
@@ -113,8 +115,9 @@ describe('format: toSubmit', () => {
113115
{ value: '{"foo":undefined}', target: {} },
114116
// Regex test: The token in key field kept untouched.
115117
{ value: '{"undefined": NaN }', target: { undefined: Number.NaN } },
118+
{ value: '123', customType: 'bigint' as customTypeEnums, target: BigInt(123) },
116119
])('value: $value will be serialized to target', (value) => {
117-
const serialized = format.toSubmit(value.value)
120+
const serialized = format.toSubmit(value.value, value.customType)
118121
expect(serialized).toStrictEqual(value.target)
119122
})
120123
})

‎packages/devtools-kit/src/core/component/state/custom.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { InspectorState } from '../types'
1+
import type { InspectorState, customTypeEnums } from '../types'
22
import { getComponentName, getInstanceName } from '../general/util'
33
import { processInstanceState } from './process'
44
import { escape, getSetupStateType, toRaw } from './util'
@@ -21,7 +21,7 @@ export function getFunctionDetails(func: Function) {
2121
const name = typeof func.name === 'string' ? func.name : ''
2222
return {
2323
_custom: {
24-
type: 'function',
24+
type: 'function' satisfies customTypeEnums,
2525
displayText: `<span style="opacity:.5;">function</span> ${escape(name)}${args}`,
2626
tooltipText: string.trim() ? `<pre>${string}</pre>` : null,
2727
},
@@ -64,7 +64,7 @@ export function getSetDetails(val: Set<unknown>) {
6464
const list = Array.from(val)
6565
return {
6666
_custom: {
67-
type: 'set',
67+
type: 'set' satisfies customTypeEnums,
6868
displayText: `Set[${list.length}]`,
6969
value: list,
7070
readOnly: true,
@@ -120,7 +120,7 @@ function namedNodeMapToObject(map: NamedNodeMap) {
120120
export function getStoreDetails(store) {
121121
return {
122122
_custom: {
123-
type: 'store',
123+
type: 'store' satisfies customTypeEnums,
124124
displayText: 'Store',
125125
value: {
126126
state: store.state,
@@ -179,7 +179,7 @@ export function getComponentDefinitionDetails(definition) {
179179
}
180180
return {
181181
_custom: {
182-
type: 'component-definition',
182+
type: 'component-definition' satisfies customTypeEnums,
183183
displayText: display,
184184
tooltipText: 'Component definition',
185185
...definition.__file
@@ -195,7 +195,7 @@ export function getHTMLElementDetails(value: HTMLElement) {
195195
try {
196196
return {
197197
_custom: {
198-
type: 'HTMLElement',
198+
type: 'HTMLElement' satisfies customTypeEnums,
199199
displayText: `<span class="opacity-30">&lt;</span><span class="text-blue-500">${value.tagName.toLowerCase()}</span><span class="opacity-30">&gt;</span>`,
200200
value: namedNodeMapToObject(value.attributes),
201201
},
@@ -204,7 +204,7 @@ export function getHTMLElementDetails(value: HTMLElement) {
204204
catch (e) {
205205
return {
206206
_custom: {
207-
type: 'HTMLElement',
207+
type: 'HTMLElement' satisfies customTypeEnums,
208208
displayText: `<span class="text-blue-500">${String(value)}</span>`,
209209
},
210210
}
@@ -232,7 +232,7 @@ export function getObjectDetails(object: Record<string, any>) {
232232
if (typeof object.__asyncLoader === 'function') {
233233
return {
234234
_custom: {
235-
type: 'component-definition',
235+
type: 'component-definition' satisfies customTypeEnums,
236236
display: 'Async component definition',
237237
},
238238
}

‎packages/devtools-kit/src/core/component/state/format.ts

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { InspectorCustomState, InspectorState } from '../types'
1+
import { InspectorCustomState, InspectorState, customTypeEnums } from '../types'
22
import { INFINITY, NAN, NEGATIVE_INFINITY, UNDEFINED, rawTypeRE, specialTypeRE } from './constants'
33
import { isPlainObject } from './is'
44
import { escape, internalStateTokenToString, replaceStringToToken, replaceTokenToString } from './util'
@@ -87,30 +87,44 @@ export function formatInspectorStateValue(value, quotes = false) {
8787
return value
8888
}
8989

90-
export function getRawValue(value: InspectorState['value']) {
90+
export function getRaw(value: InspectorState['value']): {
91+
value: object | string | number | boolean | null
92+
inherit: {} | { abstract: true }
93+
customType?: customTypeEnums
94+
} {
95+
let customType: customTypeEnums
9196
const isCustom = getInspectorStateValueType(value) === 'custom'
9297
let inherit = {}
9398
if (isCustom) {
9499
const data = value as InspectorCustomState
95100
const customValue = data._custom?.value
101+
const currentCustomType = data._custom?.type
96102
const nestedCustom = typeof customValue === 'object' && customValue !== null && '_custom' in customValue
97-
? getRawValue(customValue)
98-
: { inherit: undefined, value: undefined }
103+
? getRaw(customValue)
104+
: { inherit: undefined, value: undefined, customType: undefined }
99105
inherit = nestedCustom.inherit || data._custom?.fields || {}
100106
value = nestedCustom.value || customValue as string
107+
customType = nestedCustom.customType || currentCustomType as customTypeEnums
101108
}
102109
// @ts-expect-error @TODO: type
103110
if (value && value._isArray)
104111
// @ts-expect-error @TODO: type
105112
value = value.items
106113

107-
return { value, inherit }
114+
// @ts-expect-error customType map be assigned as undefined.
115+
return { value, inherit, customType }
108116
}
109117

110-
export function toEdit(value: unknown) {
118+
export function toEdit(value: unknown, customType?: customTypeEnums) {
119+
if (customType === 'bigint')
120+
return value as string
121+
111122
return replaceTokenToString(JSON.stringify(value))
112123
}
113124

114-
export function toSubmit(value: string) {
125+
export function toSubmit(value: string, customType?: customTypeEnums) {
126+
if (customType === 'bigint')
127+
return BigInt(value)
128+
115129
return JSON.parse(replaceStringToToken(value), reviver)
116130
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type customTypeEnums = 'function' | 'bigint' | 'map' | 'set' | 'store' | 'router' | 'component' | 'component-definition' | 'HTMLElement' | 'component-definition'

‎packages/devtools-kit/src/core/component/types/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './state'
22
export * from './tree'
33
export * from './editor'
44
export * from './bounding-rect'
5+
export * from './custom'

0 commit comments

Comments
 (0)
Please sign in to comment.