Skip to content

Commit c8ca41f

Browse files
committed
feat(client): support export & import settings JSON
1 parent 27fd74a commit c8ca41f

File tree

4 files changed

+81
-0
lines changed

4 files changed

+81
-0
lines changed

Diff for: packages/client/src/composables/state.ts

+46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { RemovableRef } from '@vueuse/core'
2+
import { showVueNotification } from '@vue/devtools-ui'
23
import type { GraphSettings } from './graph'
34
import type { TabSettings } from './state-tab'
5+
import { downloadFile, readFileAsText } from '~/utils'
46

57
interface DevtoolsClientState {
68
isFirstVisit: boolean
@@ -65,4 +67,48 @@ watch(() => devtoolsClientState.value.splitScreen.enabled, (enabled, o) => {
6567
devtoolsClientState.value.splitScreen.size = [50, 50]
6668
}
6769
})
70+
71+
const DEVTOOLS_STATE_KEY = '__VUE_DEVTOOLS_CLIENT_STATE__'
72+
73+
export function useExportDevtoolsClientState() {
74+
return {
75+
exportDevtoolsClientState: () => {
76+
const blob = new Blob([
77+
JSON.stringify({ [DEVTOOLS_STATE_KEY]: devtoolsClientState.value }, null, 2),
78+
], { type: 'application/json' })
79+
downloadFile(blob, 'vue-devtools-client-state.json')
80+
},
81+
}
82+
}
83+
84+
export function useImportDevtoolsClientState() {
85+
const { open, onChange } = useFileDialog({ accept: '.json', multiple: false })
86+
87+
onChange((fileList) => {
88+
const jsonFile = fileList?.[0]
89+
if (!jsonFile)
90+
return
91+
readFileAsText(jsonFile)
92+
.then((file) => {
93+
const data = JSON.parse(file as string)[DEVTOOLS_STATE_KEY]
94+
if (!data)
95+
throw new Error('Invalid file')
96+
devtoolsClientState.value = data
97+
showVueNotification({
98+
message: 'Import successful',
99+
type: 'success',
100+
})
101+
})
102+
.catch(() => {
103+
showVueNotification({
104+
type: 'error',
105+
message: 'Invalid file',
106+
})
107+
})
108+
})
109+
110+
return {
111+
openImportDialog: open,
112+
}
113+
}
68114
// #endregion

Diff for: packages/client/src/pages/settings.vue

+17
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ const minimizePanelInteractiveLabel = computed(() => {
8686
const option = minimizePanelInteractiveOptions.find(i => i.value === minimizePanelInteractive.value)
8787
return `${option?.label ?? 'Select...'}`
8888
})
89+
90+
const { openImportDialog } = useImportDevtoolsClientState()
91+
const { exportDevtoolsClientState } = useExportDevtoolsClientState()
8992
</script>
9093

9194
<template>
@@ -213,6 +216,20 @@ const minimizePanelInteractiveLabel = computed(() => {
213216
</VueCard>
214217
</template>
215218

219+
<h3 mt2 text-lg>
220+
Data
221+
</h3>
222+
<div flex="~ gap-2 wrap">
223+
<VueButton outlined @click="() => exportDevtoolsClientState()">
224+
<div i-ph-export />
225+
Export Settings
226+
</VueButton>
227+
<VueButton outlined @click="() => openImportDialog()">
228+
<div i-ph-download-simple />
229+
Import Settings
230+
</VueButton>
231+
</div>
232+
216233
<h3 mt2 text-lg>
217234
Debug
218235
</h3>

Diff for: packages/client/src/utils/file.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export function readFileAsText(file: Blob) {
2+
return new Promise((resolve, reject) => {
3+
const reader = new FileReader()
4+
reader.onload = () => resolve(reader.result)
5+
reader.onerror = () => reject(new Error('Failed to read file'))
6+
reader.readAsText(file)
7+
})
8+
}
9+
10+
export function downloadFile(content: Blob, filename: string) {
11+
const url = URL.createObjectURL(content)
12+
const a = document.createElement('a')
13+
a.href = url
14+
a.download = filename
15+
a.click()
16+
URL.revokeObjectURL(url)
17+
}

Diff for: packages/client/src/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './color'
22
export * from './time'
3+
export * from './file'

0 commit comments

Comments
 (0)