diff --git a/packages/client/src/composables/state.ts b/packages/client/src/composables/state.ts index 44b8a5ec0..bcb963f4c 100644 --- a/packages/client/src/composables/state.ts +++ b/packages/client/src/composables/state.ts @@ -1,6 +1,8 @@ import type { RemovableRef } from '@vueuse/core' +import { showVueNotification } from '@vue/devtools-ui' import type { GraphSettings } from './graph' import type { TabSettings } from './state-tab' +import { downloadFile, readFileAsText } from '~/utils' interface DevtoolsClientState { isFirstVisit: boolean @@ -65,4 +67,48 @@ watch(() => devtoolsClientState.value.splitScreen.enabled, (enabled, o) => { devtoolsClientState.value.splitScreen.size = [50, 50] } }) + +const DEVTOOLS_STATE_KEY = '__VUE_DEVTOOLS_CLIENT_STATE__' + +export function useExportDevtoolsClientState() { + return { + exportDevtoolsClientState: () => { + const blob = new Blob([ + JSON.stringify({ [DEVTOOLS_STATE_KEY]: devtoolsClientState.value }, null, 2), + ], { type: 'application/json' }) + downloadFile(blob, 'vue-devtools-client-state.json') + }, + } +} + +export function useImportDevtoolsClientState() { + const { open, onChange } = useFileDialog({ accept: '.json', multiple: false }) + + onChange((fileList) => { + const jsonFile = fileList?.[0] + if (!jsonFile) + return + readFileAsText(jsonFile) + .then((file) => { + const data = JSON.parse(file as string)[DEVTOOLS_STATE_KEY] + if (!data) + throw new Error('Invalid file') + devtoolsClientState.value = data + showVueNotification({ + message: 'Import successful', + type: 'success', + }) + }) + .catch(() => { + showVueNotification({ + type: 'error', + message: 'Invalid file', + }) + }) + }) + + return { + openImportDialog: open, + } +} // #endregion diff --git a/packages/client/src/pages/settings.vue b/packages/client/src/pages/settings.vue index 4ecb19463..2a3ee1e7f 100644 --- a/packages/client/src/pages/settings.vue +++ b/packages/client/src/pages/settings.vue @@ -86,6 +86,9 @@ const minimizePanelInteractiveLabel = computed(() => { const option = minimizePanelInteractiveOptions.find(i => i.value === minimizePanelInteractive.value) return `${option?.label ?? 'Select...'}` }) + +const { openImportDialog } = useImportDevtoolsClientState() +const { exportDevtoolsClientState } = useExportDevtoolsClientState() +

+ Data +

+
+ +
+ Export Settings + + +
+ Import Settings + +
+

Debug

diff --git a/packages/client/src/utils/file.ts b/packages/client/src/utils/file.ts new file mode 100644 index 000000000..63e40e211 --- /dev/null +++ b/packages/client/src/utils/file.ts @@ -0,0 +1,17 @@ +export function readFileAsText(file: Blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = () => resolve(reader.result) + reader.onerror = () => reject(new Error('Failed to read file')) + reader.readAsText(file) + }) +} + +export function downloadFile(content: Blob, filename: string) { + const url = URL.createObjectURL(content) + const a = document.createElement('a') + a.href = url + a.download = filename + a.click() + URL.revokeObjectURL(url) +} diff --git a/packages/client/src/utils/index.ts b/packages/client/src/utils/index.ts index 4b3a1e85d..1658de998 100644 --- a/packages/client/src/utils/index.ts +++ b/packages/client/src/utils/index.ts @@ -1,2 +1,3 @@ export * from './color' export * from './time' +export * from './file'