Files
xrpl-hooks-ide/components/Transaction/json.tsx
2022-07-18 21:26:05 +05:30

233 lines
6.2 KiB
TypeScript

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<TransactionState>) => string;
header?: string;
setState: (pTx?: Partial<TransactionState> | undefined) => void;
state: TransactionState;
estimateFee?: () => Promise<string | undefined>;
}
export const TxJson: FC<JsonProps> = ({
getJsonString,
state: txState,
header,
setState,
}) => {
const { editorSettings, accounts } = useSnapshot(state);
const { editorValue, estimatedFee } = txState;
const [currTxType, setCurrTxType] = useState<string | undefined>(
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<any[]> => {
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<typeof monaco>();
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 (
<Monaco
rootProps={{
css: { height: "calc(100% - 45px)" },
}}
language={"json"}
id={header}
height="100%"
value={editorValue}
onChange={val => 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 ? (
<Flex
row
align="center"
css={{ fontSize: "$xs", color: "$textMuted", ml: "auto" }}
>
<Text muted small>
This file has unsaved changes.
</Text>
<Link
css={{ ml: "$1" }}
onClick={() => saveState(editorValue || "", currTxType)}
>
save
</Link>
<Link css={{ ml: "$1" }} onClick={discardChanges}>
discard
</Link>
</Flex>
) : undefined
}
/>
);
};