import type { DocAndNode } from '@spyglassmc/core' import { fileUtil } from '@spyglassmc/core' import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks' import { useLocale } from '../../contexts/index.js' import { useDocAndNode, useSpyglass } from '../../contexts/Spyglass.jsx' import { useLocalStorage } from '../../hooks/index.js' import { getSourceFormats, getSourceIndent, getSourceIndents, parseSource, stringifySource } from '../../services/index.js' import { Store } from '../../Store.js' import { message } from '../../Utils.js' import { Btn, BtnMenu } from '../index.js' interface Editor { getValue(): string setValue(value: string): void configure(indent: string, format: string, wrap: boolean): void select(): void } type SourcePanelProps = { docAndNode: DocAndNode | undefined, doCopy?: number, doDownload?: number, doImport?: number, copySuccess: () => unknown, onError: (message: string | Error) => unknown, } export function SourcePanel({ docAndNode, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) { const { locale } = useLocale() const { service } = useSpyglass() const [cIndent, setIndent] = useState(Store.getIndent()) const [cFormat, setFormat] = useState(Store.getFormat()) const [inline, setInline] = useLocalStorage('misode_output_inline', false, (s) => s === 'true', (b) => b ? 'true' : 'false') // const [sort, setSort] = useLocalStorage('misode_output_sort', 'schema') const [highlighting, setHighlighting] = useState(Store.getHighlighting()) const [braceLoaded, setBraceLoaded] = useState(false) const download = useRef(null) const retransform = useRef(() => {}) const onImport = useRef<() => Promise>(async () => {}) const outputRef = useRef() const textarea = useRef(null) const editor = useRef() const { indent, format } = useMemo(() => { return inline ? { indent: 'minified', format: 'snbt' } : { indent: cIndent, format: cFormat } }, [cIndent, cFormat, inline]) const getSerializedOutput = useCallback((text: string) => { // TODO: implement sort return stringifySource(text, format, indent) }, [indent, format]) const text = useDocAndNode(docAndNode)?.doc.getText() useEffect(() => { retransform.current = () => { if (!editor.current || text === undefined) { return } try { const output = getSerializedOutput(text) outputRef.current = output editor.current.setValue(output) } catch (e) { if (e instanceof Error) { e.message = `Error getting JSON output: ${e.message}` onError(e) } else { onError(`Error getting JSON output: ${message(e)}`) } console.error(e) editor.current.setValue('') } } onImport.current = async () => { if (!editor.current) return const value = editor.current.getValue() if (value.length === 0) return if (!service || !docAndNode) return try { const text = await parseSource(value, format) await service.writeFile(docAndNode.doc.uri, text) } catch (e) { if (e instanceof Error) { e.message = `Error importing: ${e.message}` onError(e) } else { onError(`Error importing: ${message(e)}`) } console.error(e) } } }, [service, docAndNode, text, indent, format, highlighting]) useEffect(() => { if (highlighting) { setBraceLoaded(false) editor.current = { getValue() { return ''}, setValue() {}, configure() {}, select() {}, } import('brace').then(async (brace) => { await Promise.all([ import('brace/mode/json'), import('brace/mode/yaml'), ]) const braceEditor = brace.edit('editor') braceEditor.setOptions({ fontSize: 14, showFoldWidgets: false, highlightSelectedWord: false, scrollPastEnd: 0.5, indentedSoftWrap: false, }) braceEditor.$blockScrolling = Infinity braceEditor.on('blur', () => onImport.current()) braceEditor.getSession().setMode('ace/mode/json') editor.current = { getValue() { return braceEditor.getSession().getValue() }, setValue(value) { braceEditor.getSession().setValue(value) }, configure(indent, format, wrap) { braceEditor.setOption('wrap', wrap) braceEditor.setOption('useSoftTabs', indent !== 'tabs') braceEditor.setOption('tabSize', indent === 'tabs' ? 4 : getSourceIndent(indent)) braceEditor.getSession().setMode(`ace/mode/${format}`) }, select() { braceEditor.selectAll() }, } setBraceLoaded(true) }) } else { editor.current = { getValue() { if (!textarea.current) return '' return textarea.current.value }, setValue(value: string) { if (!textarea.current) return textarea.current.value = value }, configure(_indent, _format, wrap) { if (!textarea.current) return textarea.current.style.wordBreak = wrap ? 'break-all' : 'unset' textarea.current.style.whiteSpace = wrap ? 'wrap' : 'pre' }, select() {}, } } }, [highlighting]) useEffect(() => { if (retransform.current && text !== undefined) { retransform.current() } }, [text]) useEffect(() => { if (!editor.current || !retransform.current) return if (!highlighting || braceLoaded) { editor.current.configure(indent, format === 'snbt' ? 'yaml' : format, inline) retransform.current() } }, [indent, format, inline, highlighting, braceLoaded]) useEffect(() => { if (doCopy && outputRef.current) { navigator.clipboard.writeText(outputRef.current).then(() => { copySuccess() }) } }, [doCopy, textarea]) useEffect(() => { if (doDownload && docAndNode && outputRef.current && download.current) { const content = encodeURIComponent(outputRef.current) download.current.setAttribute('href', `data:text/json;charset=utf-8,${content}`) const fileName = fileUtil.basename(docAndNode.doc.uri) download.current.setAttribute('download', fileName) download.current.click() } }, [doDownload, outputRef]) useEffect(() => { if (doImport && editor.current) { editor.current.setValue('') editor.current.select() } }, [doImport]) const changeIndent = (value: string) => { Store.setIndent(value) setIndent(value) } const changeFormat = (value: string) => { Store.setFormat(value) setFormat(value) } const changeHighlighting = (value: boolean) => { Store.setHighlighting(value) setHighlighting(value) } const importFromClipboard = useCallback(async () => { if (editor.current) { const text = await navigator.clipboard.readText() editor.current.setValue(text) onImport.current() } }, [editor, onImport]) return <>
{window.matchMedia('(pointer: coarse)').matches && <> } setInline(!inline)} /> {getSourceIndents().map(key => changeIndent(key)}/> )}
{getSourceFormats().map(key => changeFormat(key)} />)}
{/* setSort(sort === 'alphabetically' ? 'schema' : 'alphabetically')} /> */} changeHighlighting(!highlighting)} />
{highlighting ?

			: }
		
	
}