From 69111cca5a1f5588598a81822d6b11cff3ff2613 Mon Sep 17 00:00:00 2001 From: Johann Schopplich Date: Thu, 8 Jan 2026 17:16:32 +0100 Subject: [PATCH] docs: add JSON baseline format selector to playground --- .../theme/components/PlaygroundLayout.vue | 104 +++++++++++++----- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/docs/.vitepress/theme/components/PlaygroundLayout.vue b/docs/.vitepress/theme/components/PlaygroundLayout.vue index 317786c..990ea9d 100644 --- a/docs/.vitepress/theme/components/PlaygroundLayout.vue +++ b/docs/.vitepress/theme/components/PlaygroundLayout.vue @@ -7,8 +7,11 @@ import { computed, onMounted, ref, shallowRef, watch } from 'vue' import { DEFAULT_DELIMITER, encode } from '../../../../packages/toon/src' import VPInput from './VPInput.vue' +type JsonFormat = 'pretty-2' | 'pretty-4' | 'pretty-tab' | 'compact' + interface PlaygroundState extends Required> { json: string + jsonFormat: JsonFormat } const PRESETS = { @@ -70,20 +73,37 @@ const DELIMITER_OPTIONS: { value: Delimiter, label: string }[] = [ { value: '\t', label: 'Tab (\\t)' }, { value: '|', label: 'Pipe (|)' }, ] +const JSON_FORMAT_OPTIONS: { value: JsonFormat, label: string, indent: string | number | undefined }[] = [ + { value: 'pretty-2', label: 'Pretty (2 spaces)', indent: 2 }, + { value: 'pretty-4', label: 'Pretty (4 spaces)', indent: 4 }, + { value: 'pretty-tab', label: 'Pretty (tabs)', indent: '\t' }, + { value: 'compact', label: 'Compact', indent: undefined }, +] const DEFAULT_JSON = JSON.stringify(PRESETS.hikes, undefined, 2) const SHARE_URL_LIMIT = 8 * 1024 +// Input state const jsonInput = ref(DEFAULT_JSON) +const jsonFormat = ref('pretty-2') +const currentFormatIndent = computed(() => + JSON_FORMAT_OPTIONS.find(opt => opt.value === jsonFormat.value)?.indent, +) +const formattedJson = computed(() => { + try { + return formatJson(JSON.parse(jsonInput.value)) + } + catch { + return jsonInput.value + } +}) + +// Encoder options const delimiter = ref(DEFAULT_DELIMITER) const indent = ref(2) const keyFolding = ref<'off' | 'safe'>('safe') const flattenDepth = ref(2) -const canShareState = ref(true) -const hasCopiedUrl = ref(false) - -const tokenizer = shallowRef() - +// Encoding output const encodingResult = computed(() => { try { const parsedInput = JSON.parse(jsonInput.value) @@ -104,12 +124,13 @@ const encodingResult = computed(() => { } } }) - const toonOutput = computed(() => encodingResult.value.output) const error = computed(() => encodingResult.value.error) +// Token analysis +const tokenizer = shallowRef() const jsonTokens = computed(() => - tokenizer.value?.encode(jsonInput.value).length, + tokenizer.value?.encode(formattedJson.value).length, ) const toonTokens = computed(() => tokenizer.value && toonOutput.value ? tokenizer.value.encode(toonOutput.value).length : undefined, @@ -125,17 +146,11 @@ const tokenSavings = computed(() => { return { diff, percent, sign, isSavings: diff > 0 } }) +// UI state +const canShareState = ref(true) +const hasCopiedUrl = ref(false) + const { copy, copied } = useClipboard({ source: toonOutput }) - -async function copyShareUrl() { - if (!canShareState.value) - return - - await navigator.clipboard.writeText(window.location.href) - hasCopiedUrl.value = true - setTimeout(() => (hasCopiedUrl.value = false), 2000) -} - const updateUrl = useDebounceFn(() => { const hash = encodeState() const baseUrl = `${window.location.origin}${window.location.pathname}${window.location.search}` @@ -150,10 +165,17 @@ const updateUrl = useDebounceFn(() => { window.history.replaceState(null, '', `#${hash}`) }, 300) -watch([jsonInput, delimiter, indent, keyFolding, flattenDepth], () => { +watch([jsonInput, delimiter, indent, keyFolding, flattenDepth, jsonFormat], () => { updateUrl() }) +watch(jsonFormat, () => { + try { + jsonInput.value = formatJson(JSON.parse(jsonInput.value)) + } + catch {} +}) + onMounted(() => { loadTokenizer() @@ -168,9 +190,14 @@ onMounted(() => { indent.value = state.indent keyFolding.value = state.keyFolding ?? 'safe' flattenDepth.value = state.flattenDepth ?? 2 + jsonFormat.value = state.jsonFormat ?? 'pretty-2' } }) +function formatJson(value: unknown) { + return JSON.stringify(value, undefined, currentFormatIndent.value) +} + function encodeState() { const state: PlaygroundState = { json: jsonInput.value, @@ -178,6 +205,7 @@ function encodeState() { indent: indent.value, keyFolding: keyFolding.value, flattenDepth: flattenDepth.value, + jsonFormat: jsonFormat.value, } const compressedData = zlibSync(stringToUint8Array(JSON.stringify(state))) @@ -196,7 +224,16 @@ function decodeState(hash: string) { } function loadPreset(name: keyof typeof PRESETS) { - jsonInput.value = JSON.stringify(PRESETS[name], undefined, 2) + jsonInput.value = formatJson(PRESETS[name]) +} + +async function copyShareUrl() { + if (!canShareState.value) + return + + await navigator.clipboard.writeText(window.location.href) + hasCopiedUrl.value = true + setTimeout(() => (hasCopiedUrl.value = false), 2000) } async function loadTokenizer() { @@ -258,7 +295,7 @@ async function loadTokenizer() { + + + +