Implement auto save
This commit is contained in:
		@@ -4,6 +4,7 @@ import { useSnapshot } from "valtio";
 | 
			
		||||
import state from "../../state";
 | 
			
		||||
import {
 | 
			
		||||
  modifyTransaction,
 | 
			
		||||
  prepareState,
 | 
			
		||||
  prepareTransaction,
 | 
			
		||||
  TransactionState,
 | 
			
		||||
} from "../../state/transactions";
 | 
			
		||||
@@ -27,39 +28,43 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
  const { accounts, editorSettings } = useSnapshot(state);
 | 
			
		||||
  const {
 | 
			
		||||
    selectedAccount,
 | 
			
		||||
    selectedDestAccount,
 | 
			
		||||
    selectedTransaction,
 | 
			
		||||
    txFields,
 | 
			
		||||
    txIsDisabled,
 | 
			
		||||
    txIsLoading,
 | 
			
		||||
    viewType,
 | 
			
		||||
    editorSavedValue,
 | 
			
		||||
    editorValue,
 | 
			
		||||
  } = txState;
 | 
			
		||||
 | 
			
		||||
  const setState = useCallback(
 | 
			
		||||
    (pTx?: Partial<TransactionState>) => {
 | 
			
		||||
      modifyTransaction(header, pTx);
 | 
			
		||||
      return modifyTransaction(header, pTx);
 | 
			
		||||
    },
 | 
			
		||||
    [header]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const prepareOptions = useCallback(() => {
 | 
			
		||||
    const TransactionType = selectedTransaction?.value;
 | 
			
		||||
    const Destination = selectedDestAccount?.value;
 | 
			
		||||
    const Account = selectedAccount?.value;
 | 
			
		||||
  const prepareOptions = useCallback(
 | 
			
		||||
    (state: TransactionState = txState) => {
 | 
			
		||||
      const {
 | 
			
		||||
        selectedTransaction,
 | 
			
		||||
        selectedDestAccount,
 | 
			
		||||
        selectedAccount,
 | 
			
		||||
        txFields,
 | 
			
		||||
      } = state;
 | 
			
		||||
 | 
			
		||||
    return prepareTransaction({
 | 
			
		||||
      ...txFields,
 | 
			
		||||
      TransactionType,
 | 
			
		||||
      Destination,
 | 
			
		||||
      Account,
 | 
			
		||||
    });
 | 
			
		||||
  }, [
 | 
			
		||||
    selectedAccount?.value,
 | 
			
		||||
    selectedDestAccount?.value,
 | 
			
		||||
    selectedTransaction?.value,
 | 
			
		||||
    txFields,
 | 
			
		||||
  ]);
 | 
			
		||||
      const TransactionType = selectedTransaction?.value;
 | 
			
		||||
      const Destination = selectedDestAccount?.value;
 | 
			
		||||
      const Account = selectedAccount?.value;
 | 
			
		||||
 | 
			
		||||
      return prepareTransaction({
 | 
			
		||||
        ...txFields,
 | 
			
		||||
        TransactionType,
 | 
			
		||||
        Destination,
 | 
			
		||||
        Account,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [txState]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const transactionType = selectedTransaction?.value;
 | 
			
		||||
@@ -72,6 +77,15 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
  }, [txIsLoading, selectedTransaction, selectedAccount, accounts, setState]);
 | 
			
		||||
 | 
			
		||||
  const submitTest = useCallback(async () => {
 | 
			
		||||
    let st: TransactionState | undefined;
 | 
			
		||||
    if (viewType === "json") {
 | 
			
		||||
      // save the editor state first
 | 
			
		||||
      const pst = prepareState(editorValue);
 | 
			
		||||
      if (!pst) return;
 | 
			
		||||
 | 
			
		||||
      st = setState(pst);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const account = accounts.find(
 | 
			
		||||
      acc => acc.address === selectedAccount?.value
 | 
			
		||||
    );
 | 
			
		||||
@@ -80,7 +94,7 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
 | 
			
		||||
    setState({ txIsLoading: true });
 | 
			
		||||
    try {
 | 
			
		||||
      const options = prepareOptions();
 | 
			
		||||
      const options = prepareOptions(st);
 | 
			
		||||
      const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
 | 
			
		||||
 | 
			
		||||
      await sendTransaction(account, options, { logPrefix });
 | 
			
		||||
@@ -92,12 +106,14 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
    }
 | 
			
		||||
    setState({ txIsLoading: false });
 | 
			
		||||
  }, [
 | 
			
		||||
    viewType,
 | 
			
		||||
    editorValue,
 | 
			
		||||
    accounts,
 | 
			
		||||
    selectedTransaction?.value,
 | 
			
		||||
    txIsDisabled,
 | 
			
		||||
    setState,
 | 
			
		||||
    prepareOptions,
 | 
			
		||||
    selectedAccount?.value,
 | 
			
		||||
    prepareOptions,
 | 
			
		||||
    header,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
@@ -115,7 +131,12 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
  return (
 | 
			
		||||
    <Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
 | 
			
		||||
      {viewType === "json" ? (
 | 
			
		||||
        <TxJson value={jsonValue} header={header} setState={setState} />
 | 
			
		||||
        <TxJson
 | 
			
		||||
          value={jsonValue}
 | 
			
		||||
          header={header}
 | 
			
		||||
          state={txState}
 | 
			
		||||
          setState={setState}
 | 
			
		||||
        />
 | 
			
		||||
      ) : (
 | 
			
		||||
        <TxUI state={txState} setState={setState} />
 | 
			
		||||
      )}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ 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, { TransactionState } from "../../state";
 | 
			
		||||
import state, { parseJSON, prepareState, TransactionState } from "../../state";
 | 
			
		||||
import Text from "../Text";
 | 
			
		||||
import Flex from "../Flex";
 | 
			
		||||
import { Link } from "..";
 | 
			
		||||
@@ -20,25 +20,23 @@ interface JsonProps {
 | 
			
		||||
  value?: string;
 | 
			
		||||
  header?: string;
 | 
			
		||||
  setState: (pTx?: Partial<TransactionState> | undefined) => void;
 | 
			
		||||
  state: TransactionState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseJSON(str: string): any | undefined {
 | 
			
		||||
  try {
 | 
			
		||||
    const parsed = JSON.parse(str);
 | 
			
		||||
    return typeof parsed === "object" ? parsed : undefined;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const TxJson: FC<JsonProps> = ({ value = "", header, setState }) => {
 | 
			
		||||
export const TxJson: FC<JsonProps> = ({
 | 
			
		||||
  value = "",
 | 
			
		||||
  state: txState,
 | 
			
		||||
  header,
 | 
			
		||||
  setState,
 | 
			
		||||
}) => {
 | 
			
		||||
  const { editorSettings } = useSnapshot(state);
 | 
			
		||||
  const { editorValue = value } = txState;
 | 
			
		||||
  const { theme } = useTheme();
 | 
			
		||||
  const [editorValue, setEditorValue] = useState(value);
 | 
			
		||||
  const [hasUnsaved, setHasUnsaved] = useState(false);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setEditorValue(value);
 | 
			
		||||
    setState({ editorValue: value });
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, [value]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
@@ -47,79 +45,14 @@ export const TxJson: FC<JsonProps> = ({ value = "", header, setState }) => {
 | 
			
		||||
  }, [editorValue, value]);
 | 
			
		||||
 | 
			
		||||
  const saveState = (value: string) => {
 | 
			
		||||
    const options = parseJSON(value);
 | 
			
		||||
    if (!options) return alert("Cannot save dirty editor");
 | 
			
		||||
 | 
			
		||||
    const { Account, TransactionType, Destination, ...rest } = options;
 | 
			
		||||
    let tx: Partial<TransactionState> = {};
 | 
			
		||||
 | 
			
		||||
    if (Account) {
 | 
			
		||||
      const acc = state.accounts.find(acc => acc.address === Account);
 | 
			
		||||
      if (acc) {
 | 
			
		||||
        tx.selectedAccount = {
 | 
			
		||||
          label: acc.name,
 | 
			
		||||
          value: acc.address,
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        tx.selectedAccount = {
 | 
			
		||||
          label: Account,
 | 
			
		||||
          value: Account,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      tx.selectedAccount = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (TransactionType) {
 | 
			
		||||
      tx.selectedTransaction = {
 | 
			
		||||
        label: TransactionType,
 | 
			
		||||
        value: TransactionType,
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      tx.selectedTransaction = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (Destination) {
 | 
			
		||||
      const dest = state.accounts.find(acc => acc.address === Destination);
 | 
			
		||||
      if (dest) {
 | 
			
		||||
        tx.selectedDestAccount = {
 | 
			
		||||
          label: dest.name,
 | 
			
		||||
          value: dest.address,
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        tx.selectedDestAccount = {
 | 
			
		||||
          label: Destination,
 | 
			
		||||
          value: Destination,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Object.keys(rest).forEach(field => {
 | 
			
		||||
      const value = rest[field];
 | 
			
		||||
      console.log({ field, value });
 | 
			
		||||
      if (field === "Amount") {
 | 
			
		||||
        rest[field] = {
 | 
			
		||||
          type: "currency",
 | 
			
		||||
          value: +value / 1000000, // TODO handle object currencies
 | 
			
		||||
        };
 | 
			
		||||
      } else if (typeof value === "object") {
 | 
			
		||||
        rest[field] = {
 | 
			
		||||
          type: "json",
 | 
			
		||||
          value,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    tx.txFields = rest;
 | 
			
		||||
    tx.editorSavedValue = null;
 | 
			
		||||
 | 
			
		||||
    setState(tx);
 | 
			
		||||
    const tx = prepareState(value);
 | 
			
		||||
    if (tx) setState(tx);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const discardChanges = () => {
 | 
			
		||||
    let discard = confirm("Are you sure to discard these changes");
 | 
			
		||||
    if (discard) {
 | 
			
		||||
      setEditorValue(value);
 | 
			
		||||
      setState({ editorValue: value });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@@ -135,7 +68,7 @@ export const TxJson: FC<JsonProps> = ({ value = "", header, setState }) => {
 | 
			
		||||
    if (!discard) {
 | 
			
		||||
      setState({ viewType: "json", editorSavedValue: value });
 | 
			
		||||
    } else {
 | 
			
		||||
      setEditorValue(value);
 | 
			
		||||
      setState({ editorValue: value });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@@ -156,7 +89,7 @@ export const TxJson: FC<JsonProps> = ({ value = "", header, setState }) => {
 | 
			
		||||
          monaco.editor.defineTheme("light", light as any);
 | 
			
		||||
        }}
 | 
			
		||||
        value={editorValue}
 | 
			
		||||
        onChange={val => setEditorValue(val || "")}
 | 
			
		||||
        onChange={val => setState({ editorValue: val })}
 | 
			
		||||
        onMount={(editor, monaco) => {
 | 
			
		||||
          editor.updateOptions({
 | 
			
		||||
            minimap: { enabled: false },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { proxy } from 'valtio';
 | 
			
		||||
import { deepEqual } from '../utils/object';
 | 
			
		||||
import transactionsData from "../content/transactions.json";
 | 
			
		||||
import state from '.';
 | 
			
		||||
 | 
			
		||||
export type SelectOption = {
 | 
			
		||||
    value: string;
 | 
			
		||||
@@ -15,7 +16,8 @@ export interface TransactionState {
 | 
			
		||||
    txIsDisabled: boolean;
 | 
			
		||||
    txFields: TxFields;
 | 
			
		||||
    viewType: 'json' | 'ui',
 | 
			
		||||
    editorSavedValue: null | string
 | 
			
		||||
    editorSavedValue: null | string,
 | 
			
		||||
    editorValue?: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -68,14 +70,15 @@ export const modifyTransaction = (
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!tx) {
 | 
			
		||||
        const state = {
 | 
			
		||||
            ...defaultTransaction,
 | 
			
		||||
            ...partialTx,
 | 
			
		||||
        }
 | 
			
		||||
        transactionsState.transactions.push({
 | 
			
		||||
            header,
 | 
			
		||||
            state: {
 | 
			
		||||
                ...defaultTransaction,
 | 
			
		||||
                ...partialTx,
 | 
			
		||||
            },
 | 
			
		||||
            state,
 | 
			
		||||
        });
 | 
			
		||||
        return;
 | 
			
		||||
        return state;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (opts.replaceState) {
 | 
			
		||||
@@ -84,7 +87,7 @@ export const modifyTransaction = (
 | 
			
		||||
            ...partialTx,
 | 
			
		||||
        }
 | 
			
		||||
        tx.state = repTx
 | 
			
		||||
        return
 | 
			
		||||
        return repTx
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Object.keys(partialTx).forEach(k => {
 | 
			
		||||
@@ -93,8 +96,11 @@ export const modifyTransaction = (
 | 
			
		||||
        const p = partialTx as any;
 | 
			
		||||
        if (!deepEqual(s[k], p[k])) s[k] = p[k];
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return tx.state
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// state to tx options
 | 
			
		||||
export const prepareTransaction = (data: any) => {
 | 
			
		||||
    let options = { ...data };
 | 
			
		||||
 | 
			
		||||
@@ -134,4 +140,85 @@ export const prepareTransaction = (data: any) => {
 | 
			
		||||
    return options
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// editor value to state
 | 
			
		||||
export const prepareState = (value?: string) => {
 | 
			
		||||
    const options = parseJSON(value);
 | 
			
		||||
    if (!options) return alert("Cannot save dirty editor");
 | 
			
		||||
 | 
			
		||||
    const { Account, TransactionType, Destination, ...rest } = options;
 | 
			
		||||
    let tx: Partial<TransactionState> = {};
 | 
			
		||||
 | 
			
		||||
    if (Account) {
 | 
			
		||||
        const acc = state.accounts.find(acc => acc.address === Account);
 | 
			
		||||
        if (acc) {
 | 
			
		||||
            tx.selectedAccount = {
 | 
			
		||||
                label: acc.name,
 | 
			
		||||
                value: acc.address,
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            tx.selectedAccount = {
 | 
			
		||||
                label: Account,
 | 
			
		||||
                value: Account,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        tx.selectedAccount = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (TransactionType) {
 | 
			
		||||
        tx.selectedTransaction = {
 | 
			
		||||
            label: TransactionType,
 | 
			
		||||
            value: TransactionType,
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        tx.selectedTransaction = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (Destination) {
 | 
			
		||||
        const dest = state.accounts.find(acc => acc.address === Destination);
 | 
			
		||||
        if (dest) {
 | 
			
		||||
            tx.selectedDestAccount = {
 | 
			
		||||
                label: dest.name,
 | 
			
		||||
                value: dest.address,
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            tx.selectedDestAccount = {
 | 
			
		||||
                label: Destination,
 | 
			
		||||
                value: Destination,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Object.keys(rest).forEach(field => {
 | 
			
		||||
        const value = rest[field];
 | 
			
		||||
        console.log({ field, value });
 | 
			
		||||
        if (field === "Amount") {
 | 
			
		||||
            rest[field] = {
 | 
			
		||||
                type: "currency",
 | 
			
		||||
                value: +value / 1000000, // TODO handle object currencies
 | 
			
		||||
            };
 | 
			
		||||
        } else if (typeof value === "object") {
 | 
			
		||||
            rest[field] = {
 | 
			
		||||
                type: "json",
 | 
			
		||||
                value,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    tx.txFields = rest;
 | 
			
		||||
    tx.editorSavedValue = null;
 | 
			
		||||
 | 
			
		||||
    return tx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const parseJSON = (str?: string | null): any | undefined => {
 | 
			
		||||
    if (!str) return undefined
 | 
			
		||||
    try {
 | 
			
		||||
        const parsed = JSON.parse(str);
 | 
			
		||||
        return typeof parsed === "object" ? parsed : undefined;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { transactionsData }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user