diff --git a/package-lock.json b/package-lock.json index 36bdac60..571c0d8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "deepslate": "^0.9.0-beta.6", "deepslate-rs": "^0.1.6", "howler": "^2.2.3", + "js-yaml": "^3.14.1", "marked": "^3.0.8", "rfdc": "^1.3.0" }, @@ -26,6 +27,7 @@ "@rollup/plugin-html": "^0.2.3", "@types/google.analytics": "0.0.40", "@types/howler": "^2.2.4", + "@types/js-yaml": "^4.0.4", "@types/marked": "^3.0.2", "@types/seedrandom": "^2.4.28", "@typescript-eslint/eslint-plugin": "^4.25.0", @@ -510,6 +512,12 @@ "integrity": "sha512-/Bs5TyNUWuXPnWe3RB6bS6giQzGHRXmSycq4Mo/2i4C6zwfR7foaAAx1Vo5pMyOAT/ufTfU6WZQHhdvCDBKRig==", "dev": true }, + "node_modules/@types/js-yaml": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.4.tgz", + "integrity": "sha512-AuHubXUmg0AzkXH0Mx6sIxeY/1C110mm/EkE/gB1sTRz3h2dao2W/63q42SlVST+lICxz5Oki2hzYA6+KnnieQ==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -798,7 +806,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -1362,7 +1369,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -1816,7 +1822,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -2462,8 +2467,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "node_modules/string-width": { "version": "4.2.2", @@ -3141,6 +3145,12 @@ "integrity": "sha512-/Bs5TyNUWuXPnWe3RB6bS6giQzGHRXmSycq4Mo/2i4C6zwfR7foaAAx1Vo5pMyOAT/ufTfU6WZQHhdvCDBKRig==", "dev": true }, + "@types/js-yaml": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.4.tgz", + "integrity": "sha512-AuHubXUmg0AzkXH0Mx6sIxeY/1C110mm/EkE/gB1sTRz3h2dao2W/63q42SlVST+lICxz5Oki2hzYA6+KnnieQ==", + "dev": true + }, "@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -3326,7 +3336,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -3750,8 +3759,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.4.0", @@ -4104,7 +4112,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4568,8 +4575,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "string-width": { "version": "4.2.2", diff --git a/package.json b/package.json index e0574405..d0236aa4 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "deepslate": "^0.9.0-beta.6", "deepslate-rs": "^0.1.6", "howler": "^2.2.3", + "js-yaml": "^3.14.1", "marked": "^3.0.8", "rfdc": "^1.3.0" }, @@ -32,6 +33,7 @@ "@rollup/plugin-html": "^0.2.3", "@types/google.analytics": "0.0.40", "@types/howler": "^2.2.4", + "@types/js-yaml": "^4.0.4", "@types/marked": "^3.0.2", "@types/seedrandom": "^2.4.28", "@typescript-eslint/eslint-plugin": "^4.25.0", diff --git a/src/app/Store.ts b/src/app/Store.ts index b8aa4c35..ef10d5f8 100644 --- a/src/app/Store.ts +++ b/src/app/Store.ts @@ -6,6 +6,7 @@ export namespace Store { export const ID_THEME = 'theme' export const ID_VERSION = 'schema_version' export const ID_INDENT = 'indentation' + export const ID_FORMAT = 'output_format' export const ID_SOUNDS_VERSION = 'minecraft_sounds_version' export function getLanguage() { @@ -28,6 +29,10 @@ export namespace Store { return localStorage.getItem(ID_INDENT) ?? '2_spaces' } + export function getFormat() { + return localStorage.getItem(ID_FORMAT) ?? 'json' + } + export function getSoundsVersion() { return localStorage.getItem(ID_SOUNDS_VERSION) ?? 'latest' } @@ -48,6 +53,10 @@ export namespace Store { if (indent) localStorage.setItem(ID_INDENT, indent) } + export function setFormat(format: string | undefined) { + if (format) localStorage.setItem(ID_FORMAT, format) + } + export function setSoundsVersion(version: string | undefined) { if (version) localStorage.setItem(ID_SOUNDS_VERSION, version) } diff --git a/src/app/components/generator/SourcePanel.tsx b/src/app/components/generator/SourcePanel.tsx index 44d47299..61331f3e 100644 --- a/src/app/components/generator/SourcePanel.tsx +++ b/src/app/components/generator/SourcePanel.tsx @@ -1,4 +1,5 @@ import { DataModel, ModelPath } from '@mcschema/core' +import yaml from 'js-yaml' import { useCallback, useEffect, useRef, useState } from 'preact/hooks' import { Btn, BtnMenu } from '..' import { useModel } from '../../hooks' @@ -10,10 +11,28 @@ import { message } from '../../Utils' const OUTPUT_CHARS_LIMIT = 10000 -const INDENT: Record = { +const INDENT: Record = { '2_spaces': 2, '4_spaces': 4, tabs: '\t', + minified: undefined, +} + +const FORMATS: Record any, + stringify: (v: any, indentation: string | number | undefined) => string, +}> = { + json: { + parse: JSON.parse, + stringify: (v, i) => JSON.stringify(v, null, i), + }, + yaml: { + parse: yaml.load, + stringify: (v, i) => yaml.dump(v, { + flowLevel: i === undefined ? 0 : -1, + indent: typeof i === 'string' ? 4 : i, + }), + }, } type SourcePanelProps = { @@ -30,14 +49,15 @@ type SourcePanelProps = { export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) { const loc = locale.bind(null, lang) const [indent, setIndent] = useState(Store.getIndent()) + const [format, setFormat] = useState(Store.getFormat()) const source = useRef(null) const download = useRef(null) const retransform = useRef() const getOutput = useCallback((model: DataModel, blockStates: BlockStateRegistry) => { const data = model.schema.hook(transformOutput, new ModelPath(model), model.data, { blockStates }) - return JSON.stringify(data, null, INDENT[indent]) + '\n' - }, [indent]) + return FORMATS[format].stringify(data, INDENT[indent]) + '\n' + }, [indent, format]) useEffect(() => { retransform.current = () => { @@ -66,11 +86,12 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload useEffect(() => { retransform.current() - }, [indent]) + }, [indent, format]) const onImport = () => { + if (source.current.value.length === 0) return try { - const data = JSON.parse(source.current.value) + const data = FORMATS[format].parse(source.current.value) model?.reset(DataModel.wrapLists(data), false) } catch (e) { onError(`Error importing: ${message(e)}`) @@ -90,7 +111,7 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload if (doDownload && model && blockStates && download.current) { const content = encodeURIComponent(getOutput(model, blockStates)) download.current.setAttribute('href', `data:text/json;charset=utf-8,${content}`) - download.current.setAttribute('download', `${name}.json`) + download.current.setAttribute('download', `${name}.${format}`) download.current.click() } }, [doDownload]) @@ -107,6 +128,11 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload setIndent(value) } + const changeFormat = (value: string) => { + Store.setFormat(value) + setFormat(value) + } + return <>
@@ -114,6 +140,10 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload changeIndent(key)}/> )} +
+ {Object.keys(FORMATS).map(key => + changeFormat(key)} />)}
diff --git a/src/app/schema/renderHtml.tsx b/src/app/schema/renderHtml.tsx index 66fdb376..359aad25 100644 --- a/src/app/schema/renderHtml.tsx +++ b/src/app/schema/renderHtml.tsx @@ -59,7 +59,6 @@ const renderHtml: RenderHook = { choice({ choices, config, switchNode }, path, value, lang, states, ctx) { const choice = switchNode.activeCase(path, true) as typeof choices[number] const contextPath = (config?.context) ? new ModelPath(path.getModel(), new Path(path.getArray(), [config.context])) : path - console.log('RENDER', path.toString(), choice.type, value) const [prefix, suffix, body] = choice.node.hook(this, contextPath, value, lang, states, ctx) if (choices.length === 1) { return [prefix, suffix, body] @@ -70,7 +69,6 @@ const renderHtml: RenderHook = { const newValue = c.change ? c.change(DataModel.unwrapLists(value)) : config.choiceContext === 'feature' ? c.node.default()?.config?.feature : c.node.default() - console.warn('CHOICE', type, c, value, newValue) path.model.set(path, DataModel.wrapLists(newValue)) } const inject =