import Editor, { loader, useMonaco } from "@monaco-editor/react"; import { FC, useCallback, useEffect, useState } from "react"; import { useTheme } from "next-themes"; import dark from "../../theme/editor/amy.json"; import light from "../../theme/editor/xcode_default.json"; import { useSnapshot } from "valtio"; import state, { prepareState, transactionsData, TransactionState, } from "../../state"; import Text from "../Text"; import Flex from "../Flex"; import { 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"; loader.config({ paths: { vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs", }, }); interface JsonProps { value?: string; header?: string; setState: (pTx?: Partial | undefined) => void; state: TransactionState; estimateFee?: () => Promise; } export const TxJson: FC = ({ value = "", state: txState, header, setState, }) => { const { editorSettings, accounts } = useSnapshot(state); const { editorValue = value, estimatedFee } = txState; const { theme } = useTheme(); const [hasUnsaved, setHasUnsaved] = useState(false); const [currTxType, setCurrTxType] = useState( txState.selectedTransaction?.value ); useEffect(() => { setState({ editorValue: value }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); 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]); useEffect(() => { if (editorValue === value) setHasUnsaved(false); else setHasUnsaved(true); }, [editorValue, value]); const saveState = (value: string, transactionType?: string) => { const tx = prepareState(value, transactionType); if (tx) setState(tx); }; const discardChanges = () => { showAlert("Confirm", { body: "Are you sure to discard these changes?", confirmText: "Yes", onConfirm: () => setState({ editorValue: value }), }); }; 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: value }), onCancel: () => setState({ viewType: "json", editorSavedValue: value }), }); }; const path = `file:///${header}`; const monaco = useMonaco(); 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]); useEffect(() => { if (!monaco) return; getSchemas().then(schemas => { monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ validate: true, schemas, }); }); }, [getSchemas, monaco]); return ( { monaco.editor.defineTheme("dark", dark as any); monaco.editor.defineTheme("light", light as any); }} value={editorValue} onChange={val => setState({ editorValue: val })} onMount={(editor, monaco) => { editor.updateOptions({ minimap: { enabled: false }, glyphMargin: true, tabSize: editorSettings.tabSize, dragAndDrop: true, fontSize: 14, }); // register onExit cb const model = editor.getModel(); model?.onWillDispose(() => onExit(model.getValue())); }} theme={theme === "dark" ? "dark" : "light"} /> {hasUnsaved && ( This file has unsaved changes.{" "} saveState(editorValue, currTxType)}>save{" "} discard )} ); };