diff --git a/src/app/Store.ts b/src/app/Store.ts index d826acac..aa39637f 100644 --- a/src/app/Store.ts +++ b/src/app/Store.ts @@ -4,6 +4,7 @@ import type { Project } from './contexts/index.js' import { DRAFT_PROJECT } from './contexts/index.js' import type { VersionId } from './services/index.js' import { DEFAULT_VERSION, VersionIds } from './services/index.js' +import { safeJsonParse } from './Utils.js' export namespace Store { export const ID_LANGUAGE = 'language' @@ -65,7 +66,7 @@ export namespace Store { export function getProjects(): Project[] { const projects = localStorage.getItem(ID_PROJECTS) if (projects) { - return JSON.parse(projects) as Project[] + return safeJsonParse(projects) ?? [] } return [DRAFT_PROJECT] } @@ -73,13 +74,13 @@ export namespace Store { export function getPreviewPanelOpen(): boolean | undefined { const open = localStorage.getItem(ID_PREVIEW_PANEL_OPEN) if (open === null) return undefined - return JSON.parse(open) + return safeJsonParse(open) } export function getProjectPanelOpen(): boolean | undefined { const open = localStorage.getItem(ID_PROJECT_PANEL_OPEN) if (open === null) return undefined - return JSON.parse(open) + return safeJsonParse(open) } export function getOpenProject() { @@ -99,7 +100,8 @@ export namespace Store { } export function getGeneratorHistory(): string[] { - return JSON.parse(localStorage.getItem(ID_GENERATOR_HISTORY) ?? '[]') + const value = localStorage.getItem(ID_GENERATOR_HISTORY) ?? '[]' + return safeJsonParse(value) ?? [] } export function setLanguage(language: string | undefined) { @@ -173,7 +175,8 @@ export namespace Store { } export function getWhatsNewSeen(): { id: string, time: string }[] { - return JSON.parse(localStorage.getItem(ID_WHATS_NEW_SEEN) ?? '[]') + const value = localStorage.getItem(ID_WHATS_NEW_SEEN) ?? '[]' + return safeJsonParse(value) ?? [] } export function seeWhatsNew(ids: string[]) { diff --git a/src/app/Utils.ts b/src/app/Utils.ts index 29757e85..fc3daefa 100644 --- a/src/app/Utils.ts +++ b/src/app/Utils.ts @@ -623,3 +623,11 @@ export function makeDescriptionId(prefix: string, id: Identifier | undefined) { } return `${prefix}.${id.namespace}.${id.path.replaceAll('/', '.')}` } + +export function safeJsonParse(text: string): any { + try { + return JSON.parse(text) + } catch (e) { + return undefined + } +} diff --git a/src/app/components/generator/FileCreation.tsx b/src/app/components/generator/FileCreation.tsx index bd349105..4afd93cf 100644 --- a/src/app/components/generator/FileCreation.tsx +++ b/src/app/components/generator/FileCreation.tsx @@ -2,6 +2,7 @@ import type { DocAndNode } from '@spyglassmc/core' import { useState } from 'preact/hooks' import { Analytics } from '../../Analytics.js' import { useLocale, useProject } from '../../contexts/index.js' +import { safeJsonParse } from '../../Utils.js' import { Btn } from '../Btn.js' import { TextInput } from '../forms/index.js' import { Modal } from '../Modal.js' @@ -29,8 +30,11 @@ export function FileCreation({ docAndNode, id, method, onClose }: Props) { return } Analytics.saveProjectFile(id, projects.length, project.files.length, method as any) - const data = JSON.parse(docAndNode.doc.getText()) - updateFile(id, undefined, { type: id, id: fileId, data }) + const text = docAndNode.doc.getText() + const data = safeJsonParse(text) + if (data !== undefined) { + updateFile(id, undefined, { type: id, id: fileId, data }) + } onClose() } diff --git a/src/app/components/generator/PreviewPanel.tsx b/src/app/components/generator/PreviewPanel.tsx index 9077f8f3..90196e28 100644 --- a/src/app/components/generator/PreviewPanel.tsx +++ b/src/app/components/generator/PreviewPanel.tsx @@ -2,6 +2,7 @@ import type { DocAndNode } from '@spyglassmc/core' import { useDocAndNode } from '../../contexts/Spyglass.jsx' import { useVersion } from '../../contexts/Version.jsx' import { checkVersion } from '../../services/index.js' +import { safeJsonParse } from '../../Utils.js' import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview, RecipePreview, StructureSetPreview } from '../previews/index.js' export const HasPreview = ['loot_table', 'recipe', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'worldgen/structure_set', 'block_definition', 'docAndNode'] @@ -27,7 +28,7 @@ export function PreviewPanel({ docAndNode: original, id, shown }: PreviewPanelPr return } - if (id === 'dimension' && JSON.parse(docAndNode.doc.getText()).generator?.type?.endsWith('noise')) { + if (id === 'dimension' && safeJsonParse(docAndNode.doc.getText())?.generator?.type?.endsWith('noise')) { return } diff --git a/src/app/components/generator/SchemaGenerator.tsx b/src/app/components/generator/SchemaGenerator.tsx index 0a7a42fc..0d87c44a 100644 --- a/src/app/components/generator/SchemaGenerator.tsx +++ b/src/app/components/generator/SchemaGenerator.tsx @@ -9,7 +9,7 @@ import { AsyncCancel, useActiveTimeout, useAsync, useSearchParam } from '../../h import type { VersionId } from '../../services/index.js' import { checkVersion, fetchPreset, fetchRegistries, getSnippet, shareSnippet } from '../../services/index.js' import { Store } from '../../Store.js' -import { cleanUrl, genPath } from '../../Utils.js' +import { cleanUrl, genPath, safeJsonParse } from '../../Utils.js' import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, FileRenaming, Footer, HasPreview, Octicon, PreviewPanel, ProjectCreation, ProjectDeletion, ProjectPanel, SearchList, SourcePanel, TextInput, Tree, VersionSwitcher } from '../index.js' export const SHARE_KEY = 'share' @@ -102,8 +102,10 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) { setSharedSnippetId(undefined, true) } if (file) { - const data = JSON.parse(doc.getText()) - updateFile(gen.id, file.id, { id: file.id, data }) + const data = safeJsonParse(doc.getText()) + if (data !== undefined) { + updateFile(gen.id, file.id, { id: file.id, data }) + } } ignoreChange.current = false setError(null) @@ -211,7 +213,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) { setShareShown(true) } else if (doc) { setShareLoading(true) - shareSnippet(gen.id, version, JSON.parse(doc.getText()), previewShown) + shareSnippet(gen.id, version, doc.getText(), previewShown) .then(({ id, length, compressed, rate }) => { Analytics.createSnippet(gen.id, id, version, length, compressed, rate) const url = `${location.origin}/${gen.url}/?${SHARE_KEY}=${id}` diff --git a/src/app/components/previews/BiomeSourcePreview.tsx b/src/app/components/previews/BiomeSourcePreview.tsx index f03e06ea..6d38092b 100644 --- a/src/app/components/previews/BiomeSourcePreview.tsx +++ b/src/app/components/previews/BiomeSourcePreview.tsx @@ -5,7 +5,7 @@ import { getProjectData, useLocale, useProject, useStore, useVersion } from '../ import { useAsync } from '../../hooks/index.js' import { checkVersion } from '../../services/Versions.js' import { Store } from '../../Store.js' -import { iterateWorld2D, randomSeed, stringToColor } from '../../Utils.js' +import { iterateWorld2D, randomSeed, safeJsonParse, stringToColor } from '../../Utils.js' import { Btn, BtnMenu, NumberInput } from '../index.js' import type { ColormapType } from './Colormap.js' import { getColormap } from './Colormap.js' @@ -32,7 +32,7 @@ export const BiomeSourcePreview = ({ docAndNode, shown }: PreviewProps) => { const [focused2, setFocused2] = useState([]) const text = docAndNode.doc.getText() - const data = JSON.parse(text) + const data = safeJsonParse(text) ?? {} const type: string = data?.generator?.biome_source?.type?.replace(/^minecraft:/, '') ?? '' const hasRandomness = type === 'multi_noise' || type === 'the_end' diff --git a/src/app/components/previews/BlockStatePreview.tsx b/src/app/components/previews/BlockStatePreview.tsx index d10e92ff..45fe000a 100644 --- a/src/app/components/previews/BlockStatePreview.tsx +++ b/src/app/components/previews/BlockStatePreview.tsx @@ -5,6 +5,7 @@ import { useVersion } from '../../contexts/index.js' import { useAsync } from '../../hooks/useAsync.js' import { AsyncCancel } from '../../hooks/useAsyncFn.js' import { getResources, ResourceWrapper } from '../../services/Resources.js' +import { safeJsonParse } from '../../Utils.js' import type { PreviewProps } from './index.js' import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx' @@ -18,7 +19,7 @@ export const BlockStatePreview = ({ docAndNode, shown }: PreviewProps) => { const { value: resources } = useAsync(async () => { if (!shown) return AsyncCancel const resources = await getResources(version) - const definition = BlockDefinition.fromJson(JSON.parse(text)) + const definition = BlockDefinition.fromJson(safeJsonParse(text) ?? {}) const wrapper = new ResourceWrapper(resources, { getBlockDefinition(id) { if (id.equals(PREVIEW_ID)) return definition diff --git a/src/app/components/previews/DecoratorPreview.tsx b/src/app/components/previews/DecoratorPreview.tsx index 5ac9c333..f8a2d63d 100644 --- a/src/app/components/previews/DecoratorPreview.tsx +++ b/src/app/components/previews/DecoratorPreview.tsx @@ -2,7 +2,7 @@ import { BlockPos, ChunkPos, LegacyRandom, PerlinNoise } from 'deepslate' import type { mat3 } from 'gl-matrix' import { useCallback, useMemo, useRef, useState } from 'preact/hooks' import { useLocale, useVersion } from '../../contexts/index.js' -import { computeIfAbsent, iterateWorld2D, randomSeed } from '../../Utils.js' +import { computeIfAbsent, iterateWorld2D, randomSeed, safeJsonParse } from '../../Utils.js' import { Btn } from '../index.js' import type { PlacedFeature, PlacementContext } from './Decorator.js' import { decorateChunk } from './Decorator.js' @@ -50,7 +50,7 @@ export const DecoratorPreview = ({ docAndNode, shown }: PreviewProps) => { }, []) const onDraw = useCallback(function onDraw(transform: mat3) { if (!ctx.current || !imageData.current || !shown) return - const data = JSON.parse(text) + const data = safeJsonParse(text) ?? {} iterateWorld2D(imageData.current, transform, (x, y) => { const pos = ChunkPos.create(Math.floor(x / 16), Math.floor(-y / 16)) const features = computeIfAbsent(chunkFeatures, `${pos[0]} ${pos[1]}`, () => decorateChunk(pos, data, context)) diff --git a/src/app/components/previews/DensityFunctionPreview.tsx b/src/app/components/previews/DensityFunctionPreview.tsx index 3f470a0b..d6bcac04 100644 --- a/src/app/components/previews/DensityFunctionPreview.tsx +++ b/src/app/components/previews/DensityFunctionPreview.tsx @@ -6,7 +6,7 @@ import { getProjectData, useLocale, useProject, useVersion } from '../../context import { useAsync } from '../../hooks/useAsync.js' import { useLocalStorage } from '../../hooks/useLocalStorage.js' import { Store } from '../../Store.js' -import { iterateWorld2D, randomSeed } from '../../Utils.js' +import { iterateWorld2D, randomSeed, safeJsonParse } from '../../Utils.js' import { Btn, BtnMenu, NumberInput } from '../index.js' import type { ColormapType } from './Colormap.js' import { getColormap } from './Colormap.js' @@ -33,7 +33,7 @@ export const DensityFunctionPreview = ({ docAndNode, shown }: PreviewProps) => { const { value: df } = useAsync(async () => { await DEEPSLATE.loadVersion(version, getProjectData(project)) - const df = DEEPSLATE.loadDensityFunction(JSON.parse(text), minY, height, seed) + const df = DEEPSLATE.loadDensityFunction(safeJsonParse(text) ?? {}, minY, height, seed) return df }, [version, project, minY, height, seed, text]) diff --git a/src/app/components/previews/LootTablePreview.tsx b/src/app/components/previews/LootTablePreview.tsx index 43eff887..0d0d575d 100644 --- a/src/app/components/previews/LootTablePreview.tsx +++ b/src/app/components/previews/LootTablePreview.tsx @@ -3,7 +3,7 @@ import { useMemo, useRef, useState } from 'preact/hooks' import { useLocale, useVersion } from '../../contexts/index.js' import { useAsync } from '../../hooks/useAsync.js' import { checkVersion, fetchAllPresets, fetchItemComponents } from '../../services/index.js' -import { clamp, jsonToNbt, randomSeed } from '../../Utils.js' +import { clamp, jsonToNbt, randomSeed, safeJsonParse } from '../../Utils.js' import { Btn, BtnMenu, NumberInput } from '../index.js' import { ItemDisplay } from '../ItemDisplay.jsx' import { ItemDisplay1204 } from '../ItemDisplay1204.jsx' @@ -40,10 +40,7 @@ export const LootTablePreview = ({ docAndNode }: PreviewProps) => { return [] } const [itemTags, lootTables, itemComponents, enchantments, enchantmentTags] = dependencies - let table = {} - try { - table = JSON.parse(text) - } catch (e) {} + const table = safeJsonParse(text) ?? {} if (use1204) { return generateLootTable1204(table, { version, seed, luck, daytime, weather, diff --git a/src/app/components/previews/ModelPreview.tsx b/src/app/components/previews/ModelPreview.tsx index 1b958c73..3f9ca064 100644 --- a/src/app/components/previews/ModelPreview.tsx +++ b/src/app/components/previews/ModelPreview.tsx @@ -5,6 +5,7 @@ import { useVersion } from '../../contexts/index.js' import { useAsync } from '../../hooks/useAsync.js' import { AsyncCancel } from '../../hooks/useAsyncFn.js' import { getResources, ResourceWrapper } from '../../services/Resources.js' +import { safeJsonParse } from '../../Utils.js' import type { PreviewProps } from './index.js' import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx' @@ -19,7 +20,7 @@ export const ModelPreview = ({ docAndNode, shown }: PreviewProps) => { const { value: resources } = useAsync(async () => { if (!shown) return AsyncCancel const resources = await getResources(version) - const blockModel = BlockModel.fromJson(JSON.parse(text)) + const blockModel = BlockModel.fromJson(safeJsonParse(text) ?? {}) blockModel.flatten(resources) const wrapper = new ResourceWrapper(resources, { getBlockDefinition(id) { diff --git a/src/app/components/previews/NoisePreview.tsx b/src/app/components/previews/NoisePreview.tsx index a0c40322..cefc07f9 100644 --- a/src/app/components/previews/NoisePreview.tsx +++ b/src/app/components/previews/NoisePreview.tsx @@ -3,7 +3,7 @@ import type { mat3 } from 'gl-matrix' import { useCallback, useMemo, useRef, useState } from 'preact/hooks' import { useLocale } from '../../contexts/index.js' import { Store } from '../../Store.js' -import { iterateWorld2D, randomSeed } from '../../Utils.js' +import { iterateWorld2D, randomSeed, safeJsonParse } from '../../Utils.js' import { Btn } from '../index.js' import type { ColormapType } from './Colormap.js' import { getColormap } from './Colormap.js' @@ -19,7 +19,7 @@ export const NoisePreview = ({ docAndNode, shown }: PreviewProps) => { const noise = useMemo(() => { const random = XoroshiroRandom.create(seed) - const params = NoiseParameters.fromJson(JSON.parse(text)) + const params = NoiseParameters.fromJson(safeJsonParse(text) ?? {}) return new NormalNoise(random, params) }, [text, seed]) diff --git a/src/app/components/previews/NoiseSettingsPreview.tsx b/src/app/components/previews/NoiseSettingsPreview.tsx index a1d4a289..ae6d5ff3 100644 --- a/src/app/components/previews/NoiseSettingsPreview.tsx +++ b/src/app/components/previews/NoiseSettingsPreview.tsx @@ -6,7 +6,7 @@ import { getProjectData, useLocale, useProject, useVersion } from '../../context import { useAsync } from '../../hooks/index.js' import { fetchRegistries } from '../../services/index.js' import { Store } from '../../Store.js' -import { iterateWorld2D, randomSeed } from '../../Utils.js' +import { iterateWorld2D, randomSeed, safeJsonParse } from '../../Utils.js' import { Btn, BtnInput, BtnMenu, ErrorPanel } from '../index.js' import type { ColormapType } from './Colormap.js' import { getColormap } from './Colormap.js' @@ -26,7 +26,7 @@ export const NoiseSettingsPreview = ({ docAndNode, shown }: PreviewProps) => { const text = docAndNode.doc.getText() const { value, error } = useAsync(async () => { - const data = JSON.parse(text) + const data = safeJsonParse(text) ?? {} await DEEPSLATE.loadVersion(version, getProjectData(project)) const biomeSource = { type: 'fixed', biome } await DEEPSLATE.loadChunkGenerator(data, biomeSource, seed) diff --git a/src/app/components/previews/RecipePreview.tsx b/src/app/components/previews/RecipePreview.tsx index 350b0f3a..b4c19b2e 100644 --- a/src/app/components/previews/RecipePreview.tsx +++ b/src/app/components/previews/RecipePreview.tsx @@ -4,7 +4,7 @@ import { useLocale, useVersion } from '../../contexts/index.js' import { useAsync } from '../../hooks/useAsync.js' import type { VersionId } from '../../services/index.js' import { checkVersion, fetchAllPresets } from '../../services/index.js' -import { jsonToNbt } from '../../Utils.js' +import { jsonToNbt, safeJsonParse } from '../../Utils.js' import { Btn, BtnMenu } from '../index.js' import { ItemDisplay } from '../ItemDisplay.jsx' import type { PreviewProps } from './index.js' @@ -30,13 +30,13 @@ export const RecipePreview = ({ docAndNode }: PreviewProps) => { }, []) const text = docAndNode.doc.getText() - const recipe = JSON.parse(text) + const recipe = safeJsonParse(text) ?? {} const items = useMemo>(() => { return placeItems(version, recipe, animation, itemTags ?? new Map()) }, [text, animation, itemTags]) const gui = useMemo(() => { - const type = recipe.type?.replace(/^minecraft:/, '') + const type = recipe?.type?.replace(/^minecraft:/, '') if (type === 'smelting' || type === 'blasting' || type === 'smoking' || type === 'campfire_cooking') { return '/images/furnace.png' } else if (type === 'stonecutting') { diff --git a/src/app/components/previews/StructureSetPreview.tsx b/src/app/components/previews/StructureSetPreview.tsx index 1c26cf17..2d2d01c0 100644 --- a/src/app/components/previews/StructureSetPreview.tsx +++ b/src/app/components/previews/StructureSetPreview.tsx @@ -5,7 +5,7 @@ import { useCallback, useMemo, useRef, useState } from 'preact/hooks' import { useLocale, useVersion } from '../../contexts/index.js' import { useAsync } from '../../hooks/useAsync.js' import type { Color } from '../../Utils.js' -import { computeIfAbsent, iterateWorld2D, randomSeed, stringToColor } from '../../Utils.js' +import { computeIfAbsent, iterateWorld2D, randomSeed, safeJsonParse, stringToColor } from '../../Utils.js' import { Btn } from '../index.js' import { featureColors } from './Decorator.js' import { DEEPSLATE } from './Deepslate.js' @@ -21,7 +21,7 @@ export const StructureSetPreview = ({ docAndNode, shown }: PreviewProps) => { const { value: structureSet } = useAsync(async () => { await DEEPSLATE.loadVersion(version) - const structureSet = DEEPSLATE.loadStructureSet(JSON.parse(text), seed) + const structureSet = DEEPSLATE.loadStructureSet(safeJsonParse(text) ?? {}, seed) return structureSet }, [text, version, seed]) diff --git a/src/app/contexts/Store.tsx b/src/app/contexts/Store.tsx index 0731ef43..7ab02c35 100644 --- a/src/app/contexts/Store.tsx +++ b/src/app/contexts/Store.tsx @@ -3,6 +3,7 @@ import { createContext } from 'preact' import { useCallback, useContext } from 'preact/hooks' import { useLocalStorage } from '../hooks/index.js' import type { Color } from '../Utils.js' +import { safeJsonParse } from '../Utils.js' interface Store { biomeColors: Record @@ -19,7 +20,7 @@ export function useStore() { } export function StoreProvider({ children }: { children: ComponentChildren }) { - const [biomeColors, setBiomeColors] = useLocalStorage>('misode_biome_colors', {}, JSON.parse, JSON.stringify) + const [biomeColors, setBiomeColors] = useLocalStorage>('misode_biome_colors', {}, s => safeJsonParse(s) ?? {}, JSON.stringify) const setBiomeColor = useCallback((biome: string, color: Color) => { setBiomeColors({...biomeColors, [biome]: color }) diff --git a/src/app/services/ResolvedItem.ts b/src/app/services/ResolvedItem.ts index a11000d7..fade600a 100644 --- a/src/app/services/ResolvedItem.ts +++ b/src/app/services/ResolvedItem.ts @@ -1,5 +1,6 @@ import type { NbtTag } from 'deepslate' import { Identifier, ItemStack } from 'deepslate' +import { safeJsonParse } from '../Utils.js' export class ResolvedItem extends ItemStack { @@ -108,11 +109,7 @@ export class ResolvedItem extends ItemStack { public getLore() { return this.get('lore', tag => { return tag.isList() ? tag.map(e => { - try { - return JSON.parse(e.getAsString()) - } catch (e) { - return { text: '(invalid lore line)' } - } + return safeJsonParse(e.getAsString()) ?? { text: '(invalid lore line)' } }) : [] }) ?? [] } @@ -153,11 +150,7 @@ export class ResolvedItem extends ItemStack { public getHoverName() { const customName = this.get('custom_name', tag => tag.isString() ? tag.getAsString() : undefined) if (customName) { - try { - return JSON.parse(customName) - } catch (e) { - return '(invalid custom name)' - } + return safeJsonParse(customName) ?? '(invalid custom name)' } const bookTitle = this.get('written_book_content', tag => tag.isCompound() ? (tag.hasCompound('title') ? tag.getCompound('title').getString('raw') : tag.getString('title')) : undefined) @@ -167,11 +160,7 @@ export class ResolvedItem extends ItemStack { const itemName = this.get('item_name', tag => tag.isString() ? tag.getAsString() : undefined) if (itemName) { - try { - return JSON.parse(itemName) - } catch (e) { - return { text: '(invalid item name)' } - } + return safeJsonParse(itemName) ?? { text: '(invalid item name)' } } const guess = this.id.path diff --git a/src/app/services/Sharing.ts b/src/app/services/Sharing.ts index 597c6ffd..1b780eaf 100644 --- a/src/app/services/Sharing.ts +++ b/src/app/services/Sharing.ts @@ -1,14 +1,14 @@ import lz from 'lz-string' +import { safeJsonParse } from '../Utils.js' import type { VersionId } from './Versions.js' const API_PREFIX = 'https://snippets.misode.workers.dev' const ShareCache = new Map() -export async function shareSnippet(type: string, version: VersionId, jsonData: any, show_preview: boolean) { +export async function shareSnippet(type: string, version: VersionId, text: string, show_preview: boolean) { try { - const raw = JSON.stringify(jsonData) - const data = lz.compressToBase64(raw) + const data = lz.compressToBase64(text) const body = JSON.stringify({ data, type, version, show_preview }) let id = ShareCache.get(body) if (!id) { @@ -16,7 +16,7 @@ export async function shareSnippet(type: string, version: VersionId, jsonData: a ShareCache.set(body, snippet.id) id = snippet.id as string } - return { id, length: raw.length, compressed: data.length, rate: raw.length / data.length } + return { id, length: text.length, compressed: data.length, rate: text.length / data.length } } catch (e) { if (e instanceof Error) { e.message = `Error creating share link: ${e.message}` @@ -30,7 +30,7 @@ export async function getSnippet(id: string) { const snippet = await fetchApi(`/${id}`) return { ...snippet, - data: JSON.parse(lz.decompressFromBase64(snippet.data) ?? '{}'), + data: safeJsonParse(lz.decompressFromBase64(snippet.data) ?? '{}') ?? {}, } } catch (e) { if (e instanceof Error) { diff --git a/src/app/services/Source.ts b/src/app/services/Source.ts index 5fc2eb4d..88b3a49d 100644 --- a/src/app/services/Source.ts +++ b/src/app/services/Source.ts @@ -1,7 +1,7 @@ import { NbtTag } from 'deepslate' import yaml from 'js-yaml' import { Store } from '../Store.js' -import { jsonToNbt } from '../Utils.js' +import { jsonToNbt, safeJsonParse } from '../Utils.js' const INDENTS: Record = { '2_spaces': 2, @@ -21,7 +21,7 @@ const FORMATS: Record JSON.stringify(NbtTag.fromString(s).toSimplifiedJson(), null, 2), stringify: (s, i) => { - const tag = jsonToNbt(JSON.parse(s)) + const tag = jsonToNbt(safeJsonParse(s) ?? {}) if (i === undefined) { return tag.toString() } @@ -30,7 +30,7 @@ const FORMATS: Record JSON.stringify(yaml.load(s), null, 2), - stringify: (s, i) => yaml.dump(JSON.parse(s), { + stringify: (s, i) => yaml.dump(safeJsonParse(s) ?? {}, { flowLevel: i === undefined ? 0 : -1, indent: typeof i === 'string' ? 4 : i, }),