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