Skip to content

Commit

Permalink
feat: applet package (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
webfansplz committed Mar 17, 2024
1 parent bb57ec2 commit 4cbc985
Show file tree
Hide file tree
Showing 43 changed files with 1,970 additions and 208 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"dev:browser-extension": "turbo dev --filter=./packages/browser-extension",
"dev:ui-story": "turbo dev --filter=./packages/ui-story",
"prepare:type": "turbo prepare:type --filter='./packages/*'",
"dev": "NODE_OPTIONS=\"--max-old-space-size=8192\" nr prepare:type && nr build:ui && turbo stub",
"dev": "NODE_OPTIONS=\"--max-old-space-size=8192\" nr prepare:type && nr build:ui && turbo stub --concurrency 20",
"build": "turbo build",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
Expand Down Expand Up @@ -84,6 +84,7 @@
"eslint": "npm:[email protected]",
"eslint-plugin-format": "^0.1.0",
"eslint-ts-patch": "8.55.0-1",
"execa": "^8.0.1",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"jsdom": "^24.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/applet/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/index'
43 changes: 43 additions & 0 deletions packages/applet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@vue/devtools-applet",
"type": "module",
"version": "7.0.17",
"author": "webfansplz",
"license": "MIT",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./style.css": "./dist/index.css"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"files": [
"**.d.ts",
"dist"
],
"scripts": {
"build": "vite build && pnpm types",
"prepare:type": "pnpm types",
"stub": "vite build --watch",
"types": "vue-tsc --declaration --emitDeclarationOnly -p ./tsconfig.json"
},
"peerDependencies": {
"vue": "^3.0.0"
},
"dependencies": {
"@vue/devtools-core": "workspace:^",
"@vue/devtools-kit": "workspace:^",
"@vue/devtools-shared": "workspace:^",
"@vue/devtools-ui": "workspace:^",
"perfect-debounce": "^1.0.0",
"splitpanes": "^3.1.5"
},
"devDependencies": {
"unplugin-vue": "^5.0.3",
"vue": "^3.4.15",
"vue-router": "^4.2.5",
"vue-tsc": "^1.8.27"
}
}
24 changes: 24 additions & 0 deletions packages/applet/src/components/basic/DevToolsHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
import { defineProps } from 'vue'
import { useVirtualRouter } from '~/composables/virtual-router'
defineProps<{
githubRepoLink: string
docLink: string
}>()
const router = useVirtualRouter()
</script>

<template>
<div border="b base" class="h10 h40px flex items-center justify-between px-2">
<i class="i-ep:back cursor-pointer op70 text-base hover:op100" @click="router.push('/')" />
<div>
<a class="pr1" :href="docLink" target="_blank">
<i class="i-clarity:document-line cursor-pointer op70 text-base hover:op100" />
</a>
<a :href="githubRepoLink" target="_blank">
<i class="i-mdi:github cursor-pointer op70 text-base hover:op100" />
</a>
</div>
</div>
</template>
8 changes: 8 additions & 0 deletions packages/applet/src/components/basic/Empty.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<div class="h-full flex flex-col items-center justify-center op50">
<i class="i-lets-icons:blank-light" />
<span>
<slot />
</span>
</div>
</template>
24 changes: 24 additions & 0 deletions packages/applet/src/components/basic/SelectiveList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
import { defineModel } from 'vue'
defineProps<{ data: { id: string, label: string }[] }>()
const selected = defineModel()
function select(id: string) {
selected.value = id
}
</script>

<template>
<ul class="p2">
<li
v-for="item in data" :key="item.id"
class="selectable-item"
:class="{ active: item.id === selected }"
@click="select(item.id)"
>
{{ item.label }}
</li>
</ul>
</template>
14 changes: 14 additions & 0 deletions packages/applet/src/components/basic/ToggleExpanded.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
defineProps<{
value: boolean
}>()
</script>

<template>
<i
class="i-radix-icons:triangle-right flex-none text-4 op-50 transition-base"
:class="{
'transform rotate-90': value,
}"
/>
</template>
23 changes: 23 additions & 0 deletions packages/applet/src/components/state/ChildStateViewer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup lang="ts">
import type { InspectorState } from '@vue/devtools-kit'
import StateFieldViewer from './StateFieldViewer.vue'
withDefaults(defineProps<{
data: InspectorState[]
depth: number
index: string
}>(), {
depth: 0,
})
</script>

<template>
<div>
<div
v-for="(item, i) in data"
:key="i"
>
<StateFieldViewer :data="item" :depth="depth + 1" :index="`${index}-${i}`" />
</div>
</div>
</template>
70 changes: 70 additions & 0 deletions packages/applet/src/components/state/RootStateViewer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<script setup lang="ts">
import { watchEffect } from 'vue'
import type { InspectorState } from '@vue/devtools-kit'
import ChildStateViewer from './ChildStateViewer.vue'
import ToggleExpanded from '~/components/basic/ToggleExpanded.vue'
import { useToggleExpanded } from '~/composables/toggle-expanded'
import { createStateEditorContext } from '~/composables/state-editor'
const props = withDefaults(defineProps<{
data: Record<string, InspectorState[]>
nodeId: string
inspectorId: string
disableEdit?: boolean
}>(), {
disableEdit: false,
})
function initEditorContext() {
return {
nodeId: props.nodeId,
inspectorId: props.inspectorId,
disableEdit: props.disableEdit,
}
}
const { context } = createStateEditorContext(initEditorContext())
watchEffect(() => {
context.value = initEditorContext()
})
const { expanded, toggleExpanded } = useToggleExpanded()
watchEffect(() => {
// Expand the root level by default
Object.keys(props.data).forEach((_, index) => {
if (!expanded.value.includes(`${index}`))
toggleExpanded(`${index}`)
})
})
</script>

<template>
<div>
<div
v-for="(item, key, index) in data"
:key="index"
>
<div
class="flex items-center"
:class="[item?.length && 'cursor-pointer hover:(bg-active)']"
@click="toggleExpanded(`${index}`)"
>
<ToggleExpanded
v-if="item?.length"
:value="expanded.includes(`${index}`)"
/>
<!-- placeholder -->
<span v-else pl5 />
<span font-state-field text-4>
{{ key }}
</span>
</div>
<div
v-if="item?.length && expanded.includes(`${index}`)"
>
<ChildStateViewer :data="item" :index="`${index}`" />
</div>
</div>
</div>
</template>
165 changes: 165 additions & 0 deletions packages/applet/src/components/state/StateFieldEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<script setup lang="ts">
import { computed, ref, toRaw } from 'vue'
import { VueButton, VueDropdown, VueDropdownButton, VueIcon, VTooltip as vTooltip } from '@vue/devtools-ui'
import { getRaw } from '@vue/devtools-kit'
import type { InspectorState, InspectorStateEditorPayload } from '@vue/devtools-kit'
import type { ButtonProps } from '@vue/devtools-ui/dist/types/src/components/Button'
import { editInspectorState } from '@vue/devtools-core'
import { useClipboard } from '@vueuse/core'
import { useStateEditorContext } from '~/composables/state-editor'
import type { EditorAddNewPropType, EditorInputValidType } from '~/composables/state-editor'
const props = withDefaults(defineProps<{
data: InspectorState
hovering: boolean
depth: number
showAddIfNeeded?: boolean
disableEdit?: boolean
}>(), {
showAddIfNeeded: true,
})
defineEmits<{
enableEditInput: [type: EditorInputValidType]
addNewProp: [type: EditorAddNewPropType]
}>()
const state = useStateEditorContext()
const { copy, isSupported } = useClipboard()
const popupVisible = ref(false)
const raw = computed(() => getRaw(props.data.value))
const rawValue = computed(() => raw.value.value)
const customType = computed(() => raw.value.customType)
const dataType = computed(() => rawValue.value === null ? 'null' : typeof rawValue.value)
const iconButtonProps = {
flat: true,
size: 'mini',
} satisfies ButtonProps
const buttonClass = computed(() => ({
'opacity-0': !props.hovering,
}))
function quickEdit(v: unknown, remove: boolean = false) {
editInspectorState({
path: props.data.key.split('.'),
inspectorId: state.value.inspectorId,
type: props.data.stateType!,
nodeId: state.value.nodeId,
state: {
newKey: null!,
value: toRaw(v),
type: dataType.value,
remove,
},
} satisfies InspectorStateEditorPayload)
}
function quickEditNum(v: number | string, offset: 1 | -1) {
const target = typeof v === 'number'
? v + offset
: BigInt(v) + BigInt(offset)
quickEdit(target)
}
</script>

<template>
<div class="inline pl5px">
<!-- only editable will show operate actions -->
<template v-if="!props.disableEdit && data.editable">
<!-- input edit, number/string/object -->
<template v-if="dataType === 'string' || dataType === 'number' || dataType === 'object' || dataType === 'null'">
<VueButton
v-tooltip="{
content: 'Edit value',
}" v-bind="iconButtonProps" :class="buttonClass" @click.stop="$emit('enableEditInput', dataType)"
>
<template #icon>
<VueIcon icon="i-material-symbols-edit-rounded" />
</template>
</VueButton>
<VueButton
v-if="dataType === 'object' && showAddIfNeeded"
v-tooltip="{
content: 'Add new value',
}" v-bind="iconButtonProps" :class="buttonClass" @click.stop="
$emit('addNewProp', Array.isArray(rawValue) ? 'array' : 'object')"
>
<template #icon>
<VueIcon icon="i-material-symbols-add-circle-rounded" />
</template>
</VueButton>
</template>
<!-- checkbox, button value only -->
<VueButton
v-if="dataType === 'boolean'" v-bind="iconButtonProps" :class="buttonClass"
@click="quickEdit(!rawValue)"
>
<template #icon>
<VueIcon :icon="rawValue ? 'i-material-symbols-check-box-sharp' : 'i-material-symbols-check-box-outline-blank-sharp'" />
</template>
</VueButton>
<!-- increment/decrement button, numeric/bigint -->
<template v-else-if="dataType === 'number' || customType === 'bigint'">
<VueButton v-bind="iconButtonProps" :class="buttonClass" @click.stop="quickEditNum(rawValue as number | string, 1)">
<template #icon>
<VueIcon icon="i-carbon-add" />
</template>
</VueButton>
<VueButton v-bind="iconButtonProps" :class="buttonClass" @click.stop="quickEditNum(rawValue as number | string, -1)">
<template #icon>
<VueIcon icon="i-carbon-subtract" />
</template>
</VueButton>
</template>
</template>
<!-- delete prop, only appear if depth > 0 -->
<VueButton v-if="!props.disableEdit && depth > 0" v-bind="iconButtonProps" :class="buttonClass" @click.stop="quickEdit(rawValue, true)">
<template #icon>
<VueIcon icon="i-material-symbols-delete-rounded" />
</template>
</VueButton>
<!-- Copy key/value -->
<VueDropdown
:class="{
'opacity-0': !hovering && !popupVisible,
}"
:button-props="{
flat: true,
size: 'mini',
}"
:disabled="!isSupported"
@update:visible="v => popupVisible = v"
>
<template #popper>
<div class="w160px py5px">
<VueDropdownButton
@click="copy(typeof rawValue === 'object' ? JSON.stringify(rawValue) : rawValue.toString())"
>
<template #icon>
<VueIcon icon="i-material-symbols-copy-all-rounded" class="mt4px" />
Copy Value
</template>
</VueDropdownButton>
<VueDropdownButton
@click="() => {
copy(data.key)
}"
>
<template #icon>
<VueIcon icon="i-material-symbols-copy-all-rounded" class="mt4px" />
Copy Path
</template>
</VueDropdownButton>
</div>
</template>
<template #button-icon>
<VueIcon icon="i-material-symbols:more-vert" />
</template>
</VueDropdown>
</div>
</template>
Loading

0 comments on commit 4cbc985

Please sign in to comment.