import { FC, useCallback, useEffect, useMemo, useState } from 'react' import { useSnapshot } from 'valtio' import state, { prepareState, transactionsData, TransactionState } from '../../state' import Text from '../Text' import { Flex, Link } from '..' import { showAlert } from '../../state/actions/showAlert' import { parseJSON } from '../../utils/json' import { extractSchemaProps } from '../../utils/schema' import amountSchema from '../../content/amount-schema.json' import Monaco from '../Monaco' import type monaco from 'monaco-editor' interface JsonProps { getJsonString?: (state?: Partial) => string header?: string setState: (pTx?: Partial | undefined) => void state: TransactionState estimateFee?: () => Promise } export const TxJson: FC = ({ getJsonString, state: txState, header, setState }) => { const { editorSettings, accounts } = useSnapshot(state) const { editorValue, estimatedFee } = txState const [currTxType, setCurrTxType] = useState( txState.selectedTransaction?.value ) useEffect(() => { setState({ editorValue: getJsonString?.() }) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { const parsed = parseJSON(editorValue) if (!parsed) return const tt = parsed.TransactionType const tx = transactionsData.find(t => t.TransactionType === tt) if (tx) setCurrTxType(tx.TransactionType) else { setCurrTxType(undefined) } }, [editorValue]) const saveState = (value: string, transactionType?: string) => { const tx = prepareState(value, transactionType) if (tx) { setState(tx) setState({ editorValue: getJsonString?.(tx) }) } } const discardChanges = () => { showAlert('Confirm', { body: 'Are you sure to discard these changes?', confirmText: 'Yes', onCancel: () => {}, onConfirm: () => setState({ editorValue: getJsonString?.() }) }) } const onExit = (value: string) => { const options = parseJSON(value) if (options) { saveState(value, currTxType) return } showAlert('Error!', { body: `Malformed Transaction in ${header}, would you like to discard these changes?`, confirmText: 'Discard', onConfirm: () => setState({ editorValue: getJsonString?.() }), onCancel: () => setState({ viewType: 'json' }) }) } const getSchemas = useCallback(async (): Promise => { const txObj = transactionsData.find(td => td.TransactionType === currTxType) let genericSchemaProps: any if (txObj) { genericSchemaProps = extractSchemaProps(txObj) } else { genericSchemaProps = transactionsData.reduce( (cumm, td) => ({ ...cumm, ...extractSchemaProps(td) }), {} ) } return [ { uri: 'file:///main-schema.json', // id of the first schema fileMatch: ['**.json'], // associate with our model schema: { title: header, type: 'object', required: ['TransactionType', 'Account'], properties: { ...genericSchemaProps, TransactionType: { title: 'Transaction Type', enum: transactionsData.map(td => td.TransactionType) }, Account: { $ref: 'file:///account-schema.json' }, Destination: { anyOf: [ { $ref: 'file:///account-schema.json' }, { type: 'string', title: 'Destination Account' } ] }, Amount: { $ref: 'file:///amount-schema.json' }, Fee: { $ref: 'file:///fee-schema.json' } } } }, { uri: 'file:///account-schema.json', schema: { type: 'string', title: 'Account type', enum: accounts.map(acc => acc.address) } }, { uri: 'file:///fee-schema.json', schema: { type: 'string', title: 'Fee type', const: estimatedFee, description: estimatedFee ? 'Above mentioned value is recommended base fee' : undefined } }, { ...amountSchema } ] }, [accounts, currTxType, estimatedFee, header]) const [monacoInst, setMonacoInst] = useState() useEffect(() => { if (!monacoInst) return getSchemas().then(schemas => { monacoInst.languages.json.jsonDefaults.setDiagnosticsOptions({ validate: true, schemas }) }) }, [getSchemas, monacoInst]) const hasUnsaved = useMemo(() => editorValue !== getJsonString?.(), [editorValue, getJsonString]) return ( setState({ editorValue: val })} onMount={(editor, monaco) => { editor.updateOptions({ minimap: { enabled: false }, glyphMargin: true, tabSize: editorSettings.tabSize, dragAndDrop: true, fontSize: 14 }) setMonacoInst(monaco) // register onExit cb const model = editor.getModel() model?.onWillDispose(() => onExit(model.getValue())) }} overlay={ hasUnsaved ? ( This file has unsaved changes. saveState(editorValue || '', currTxType)}> save discard ) : undefined } /> ) }