From 3286453b74437b80c5e0e0567fe6867b708d4eb4 Mon Sep 17 00:00:00 2001 From: Keith Date: Sun, 29 Sep 2019 17:12:56 +0800 Subject: [PATCH 1/2] feat(neuron-ui): update the view of transaction detail --- .../src/components/Addresses/index.tsx | 2 +- .../src/components/Transaction/index.tsx | 310 ++++++++++++------ .../components/Transaction/style.module.scss | 38 +++ packages/neuron-ui/src/locales/en.json | 13 +- packages/neuron-ui/src/locales/zh.json | 13 +- .../neuron-ui/src/services/remote/index.ts | 9 + .../neuron-ui/src/services/remote/networks.ts | 10 + .../neuron-ui/src/states/initStates/chain.ts | 2 + packages/neuron-ui/src/styles/index.scss | 9 +- packages/neuron-ui/src/theme.tsx | 2 + packages/neuron-ui/src/types/App/index.d.ts | 2 + .../src/controllers/transactions.ts | 26 +- 12 files changed, 314 insertions(+), 122 deletions(-) create mode 100644 packages/neuron-ui/src/components/Transaction/style.module.scss diff --git a/packages/neuron-ui/src/components/Addresses/index.tsx b/packages/neuron-ui/src/components/Addresses/index.tsx index 4302ba02b5..e37238c0f9 100644 --- a/packages/neuron-ui/src/components/Addresses/index.tsx +++ b/packages/neuron-ui/src/components/Addresses/index.tsx @@ -83,7 +83,7 @@ const Addresses = ({ maxWidth: 500, onRender: (item?: State.Address, _index?: number, column?: IColumn) => { if (item) { - if (column && (column.calculatedWidth || 0) < 400) { + if (column && (column.calculatedWidth || 0) < 420) { return (
( - - {item.lockHash || 'none'} - - ), - }, - { - key: 'outPointCell', - name: 'OutPoint Cell', - minWidth: 150, - onRender: (item: any) => { - const text = item.previousOutput ? `${item.previousOutput.txHash}[${item.previousOutput.index}]` : 'none' - return ( - - {text} - - ) - }, - }, - { - key: 'capacity', - name: 'Capacity', - minWidth: 200, - maxWidth: 250, - }, -].map( - (col): IColumn => ({ - ariaLabel: col.name, - fieldName: col.key, - ...col, - }) -) -const outputColumns: IColumn[] = [ - { - key: 'index', - name: 'Index', - minWidth: 80, - maxWidth: 150, - onRender: (item?: any | State.DetailedOutput) => { - if (item) { - return item.outPoint.index - } - return null - }, - }, - { - key: 'lockHash', - name: 'Lock Hash', - minWidth: 150, - }, - { - key: 'capacity', - name: 'Capacity', - minWidth: 200, - maxWidth: 250, - onRender: (output?: State.DetailedOutput) => { - if (output) { - return `${shannonToCKBFormatter(output.capacity)} CKB` - } - return null - }, - }, -].map(col => ({ - ariaLabel: col.name, - fieldName: col.key, - ...col, -})) - -const basicInfoColumns: IColumn[] = [ - { - key: 'label', - name: 'Label', - minWidth: 100, - maxWidth: 150, - }, - { - key: 'value', - name: 'value', - minWidth: 450, - }, -].map( - (col): IColumn => ({ - minWidth: MIN_CELL_WIDTH, - ariaLabel: col.name, - fieldName: col.key, - ...col, - }) -) const Transaction = () => { const [t] = useTranslation() const [transaction, setTransaction] = useState(transactionState) + const [addressPrefix, setAddressPrefix] = useState(ckbCore.utils.AddressPrefix.Mainnet) const [error, setError] = useState({ code: '', message: '' }) + + const inputColumns: IColumn[] = useMemo( + () => + [ + { + key: 'index', + name: t('transaction.index'), + minWidth: 60, + maxWidth: 60, + onRender: (_item?: any, index?: number) => { + if (undefined !== index) { + return index + } + return null + }, + }, + { + key: 'outPointCell', + name: 'OutPoint Cell', + minWidth: 150, + maxWidth: 600, + onRender: (item: any) => { + const text = item.previousOutput ? `${item.previousOutput.txHash}[${item.previousOutput.index}]` : 'none' + return ( + + {text} + + ) + }, + }, + { + key: 'capacity', + name: t('transaction.amount'), + minWidth: 100, + maxWidth: 250, + onRender: (input?: State.DetailedOutput) => { + if (input) { + return `${shannonToCKBFormatter(input.capacity)} CKB` + } + return null + }, + }, + ].map( + (col): IColumn => ({ + ariaLabel: col.name, + fieldName: col.key, + ...col, + }) + ), + [t] + ) + + const outputColumns: IColumn[] = useMemo( + () => + [ + { + key: 'index', + name: t('transaction.index'), + minWidth: 60, + maxWidth: 60, + onRender: (item?: any | State.DetailedOutput) => { + if (item) { + return item.outPoint.index + } + return null + }, + }, + { + key: 'address', + name: t('transaction.address'), + minWidth: 200, + maxWidth: 500, + onRender: (output?: State.DetailedOutput, _index?: number, column?: IColumn) => { + if (!output) { + return null + } + try { + const address = ckbCore.utils.bech32Address(output.lock.args[0], { + prefix: addressPrefix, + type: ckbCore.utils.AddressType.HashIdx, + codeHashIndex: '0x00', + }) + if (column && (column.calculatedWidth || 0) < 450) { + return ( +
+ {address.slice(0, -6)} + {address.slice(-6)} +
+ ) + } + return ( + + {address} + + ) + } catch { + return null + } + }, + }, + { + key: 'capacity', + name: t('transaction.amount'), + minWidth: 100, + maxWidth: 250, + onRender: (output?: State.DetailedOutput) => { + if (output) { + return `${shannonToCKBFormatter(output.capacity)} CKB` + } + return null + }, + }, + ].map(col => ({ + ariaLabel: col.name, + fieldName: col.key, + ...col, + })), + [addressPrefix, t] + ) + + const basicInfoColumns: IColumn[] = useMemo( + () => + [ + { + key: 'label', + name: 'label', + minWidth: 100, + maxWidth: 120, + }, + { + key: 'value', + name: 'value', + minWidth: 150, + }, + ].map( + (col): IColumn => ({ + minWidth: MIN_CELL_WIDTH, + ariaLabel: col.name, + fieldName: col.key, + ...col, + }) + ), + [] + ) + useEffect(() => { + Promise.all([getAllNetworks(), getCurrentNetworkID()]) + .then(([networksRes, idRes]) => { + if (networksRes.status === 1 && idRes.status === 1) { + const network = networksRes.result.find((n: any) => n.id === idRes.result) + if (!network) { + throw new Error('Cannot find current network in the network list') + } + + setAddressPrefix( + network.chain === process.env.REACT_APP_MAINNET_TAG + ? ckbCore.utils.AddressPrefix.Mainnet + : ckbCore.utils.AddressPrefix.Testnet + ) + } + }) + .catch(err => console.warn(err)) + const currentWallet = currentWalletCache.load() if (currentWallet) { const hash = window.location.href.split('/').pop() @@ -142,21 +220,26 @@ const Transaction = () => { }) }, []) + // TODO: add conditional branch on mainnet and testnet + const onExplorerBtnClick = useCallback(() => { + openExternal(`https://explorer.nervos.org/transaction/${transaction.hash}`) + }, [transaction.hash]) + const basicInfoItems = useMemo( () => [ - { label: t('history.transaction-hash'), value: transaction.hash || 'none' }, + { label: t('transaction.transaction-hash'), value: transaction.hash || 'none' }, { - label: t('history.date'), + label: t('transaction.block-number'), + value: localNumberFormatter(transaction.blockNumber) || 'none', + }, + { + label: t('transaction.date'), value: +(transaction.timestamp || transaction.createdAt) ? uniformTimeFormatter(+(transaction.timestamp || transaction.createdAt)) : 'none', }, { - label: t('history.blockNumber'), - value: localNumberFormatter(transaction.blockNumber) || 'none', - }, - { - label: t('history.amount'), + label: t('transaction.income'), value: `${shannonToCKBFormatter(transaction.value)} CKB`, }, ], @@ -185,10 +268,12 @@ const Transaction = () => { isHeaderVisible={false} /> - + - Inputs + {`${t('transaction.inputs')} (${transaction.inputs.length}/${localNumberFormatter( + transaction.inputsCount + )})`} { - Outputs + {`${t('transaction.outputs')} (${transaction.outputs.length}/${localNumberFormatter( + transaction.outputsCount + )})`} { /> + ) } diff --git a/packages/neuron-ui/src/components/Transaction/style.module.scss b/packages/neuron-ui/src/components/Transaction/style.module.scss new file mode 100644 index 0000000000..e99cb291dd --- /dev/null +++ b/packages/neuron-ui/src/components/Transaction/style.module.scss @@ -0,0 +1,38 @@ +.explorerNavButton { + position: fixed; + right: 10px; + bottom: 10px; + height: 30px; + width: 30px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + font-size: 12px; + color: #fff; + background: rgba(60, 198, 138, 0.8); + border: none; + border-radius: 15px; + transition: all 0.2s; + z-index: 1; + + span { + display: none; + } + + i { + display: flex; + } + + &:hover { + width: 100px; + + span { + display: flex; + } + + i { + display: none; + } + } +} diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index c64691cfe7..0ff51d26af 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -139,7 +139,18 @@ "confirming-with-count": "{{confirmations}} confirmations" }, "transaction": { - "goBack": "Go back" + "date": "Date", + "transaction-hash": "Transaction Hash", + "block-number": "Block Number", + "goBack": "Go back", + "index": "Index", + "address": "Address", + "income": "Income", + "amount": "Amount", + "inputs": "Inputs", + "outputs": "Outputs", + "view-in-explorer": "Explorer", + "view-in-explorer-button-title": "View in the explorer" }, "addresses": { "addresses": "Addresses", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 9b8136feb8..5c18d9f008 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -139,7 +139,18 @@ "confirming-with-count": " 已确认 {{confirmations}} 次" }, "transaction": { - "goBack": "返回" + "date": "时间", + "transaction-hash": "交易哈希", + "block-number": "区块高度", + "goBack": "返回", + "index": "序号", + "address": "地址", + "income": "收入", + "amount": "数量", + "inputs": "输入", + "outputs": "输出", + "view-in-explorer": "浏览器", + "view-in-explorer-button-title": "浏览器中查看详情" }, "addresses": { "addresses": "地址", diff --git a/packages/neuron-ui/src/services/remote/index.ts b/packages/neuron-ui/src/services/remote/index.ts index 36802546db..c7e61bfd36 100644 --- a/packages/neuron-ui/src/services/remote/index.ts +++ b/packages/neuron-ui/src/services/remote/index.ts @@ -69,6 +69,14 @@ export const showOpenDialog = (opt: { title: string; message?: string; onUpload: ) } +export const openExternal = (url: string) => { + if (!window.remote) { + window.open(url) + } else { + window.remote.require('electron').shell.openExternal(url) + } +} + export default { getLocale, validateMnemonic, @@ -77,4 +85,5 @@ export default { showErrorMessage, showOpenDialog, getWinID, + openExternal, } diff --git a/packages/neuron-ui/src/services/remote/networks.ts b/packages/neuron-ui/src/services/remote/networks.ts index 5bcda628b9..5a11114318 100644 --- a/packages/neuron-ui/src/services/remote/networks.ts +++ b/packages/neuron-ui/src/services/remote/networks.ts @@ -18,8 +18,18 @@ export const updateNetwork = controllerMethodWrapper(CONTROLLER_NAME)( } ) +export const getAllNetworks = controllerMethodWrapper(CONTROLLER_NAME)(controller => () => { + return controller.getAll() +}) + +export const getCurrentNetworkID = controllerMethodWrapper(CONTROLLER_NAME)(controller => () => { + return controller.currentID() +}) + export default { createNetwork, updateNetwork, setCurrentNetowrk, + getAllNetworks, + getCurrentNetworkID, } diff --git a/packages/neuron-ui/src/states/initStates/chain.ts b/packages/neuron-ui/src/states/initStates/chain.ts index 742b0f6c47..8cd5609b48 100644 --- a/packages/neuron-ui/src/states/initStates/chain.ts +++ b/packages/neuron-ui/src/states/initStates/chain.ts @@ -11,7 +11,9 @@ export const transactionState: State.DetailedTransaction = { description: '', status: 'pending', inputs: [], + inputsCount: '0', outputs: [], + outputsCount: '0', deps: [], blockNumber: '', blockHash: '', diff --git a/packages/neuron-ui/src/styles/index.scss b/packages/neuron-ui/src/styles/index.scss index f3ca316e1d..5f0770add3 100755 --- a/packages/neuron-ui/src/styles/index.scss +++ b/packages/neuron-ui/src/styles/index.scss @@ -109,13 +109,14 @@ navbar { } } - .textOverflow { - overflow: hidden; - text-overflow: ellipsis; - } } +.textOverflow { + overflow: hidden; + text-overflow: ellipsis; +} + // hack fabric ui experimental pagination style .ms-Pagination-container { button[aria-selected=false] { diff --git a/packages/neuron-ui/src/theme.tsx b/packages/neuron-ui/src/theme.tsx index 15220aa1da..ef4c2b8897 100644 --- a/packages/neuron-ui/src/theme.tsx +++ b/packages/neuron-ui/src/theme.tsx @@ -9,6 +9,7 @@ import { Close as DismissIcon, Close as FailIcon, Copy as CopyIcon, + Domain as ExplorerIcon, Down as ArrowDownIcon, FormEdit as EditIcon, FormClose as ClearIcon, @@ -89,6 +90,7 @@ registerIcons({ Keystore: , Edit: , Settings: , + Explorer: , }, }) diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index b38d199b9a..a87047d0ba 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -38,7 +38,9 @@ declare namespace State { } | null } }[] + inputsCount: string outputs: DetailedOutput[] + outputsCount: string witnesses: string[] } interface Output { diff --git a/packages/neuron-wallet/src/controllers/transactions.ts b/packages/neuron-wallet/src/controllers/transactions.ts index d2c0b72c12..f5331fb22c 100644 --- a/packages/neuron-wallet/src/controllers/transactions.ts +++ b/packages/neuron-wallet/src/controllers/transactions.ts @@ -9,6 +9,8 @@ import { ResponseCode } from 'utils/const' import { TransactionNotFound, CurrentWalletNotSet, ServiceHasNoResponse } from 'exceptions' import LockUtils from 'models/lock-utils' +const CELL_COUNT_THRESHOLD = 10 + /** * @class TransactionsController * @description handle messages from transactions channel @@ -16,7 +18,7 @@ import LockUtils from 'models/lock-utils' export default class TransactionsController { @CatchControllerError public static async getAll( - params: TransactionsByLockHashesParam + params: TransactionsByLockHashesParam, ): Promise>> { const transactions = await TransactionsService.getAll(params) @@ -32,7 +34,7 @@ export default class TransactionsController { @CatchControllerError public static async getAllByKeywords( - params: Controller.Params.TransactionsByKeywords + params: Controller.Params.TransactionsByKeywords, ): Promise & Controller.Params.TransactionsByKeywords>> { const { pageNo = 1, pageSize = 15, keywords = '', walletID = '' } = params @@ -56,7 +58,7 @@ export default class TransactionsController { @CatchControllerError public static async getAllByAddresses( - params: Controller.Params.TransactionsByAddresses + params: Controller.Params.TransactionsByAddresses, ): Promise & Controller.Params.TransactionsByAddresses>> { const { pageNo, pageSize, addresses = '' } = params @@ -85,7 +87,10 @@ export default class TransactionsController { } @CatchControllerError - public static async get(walletID: string, hash: string): Promise> { + public static async get( + walletID: string, + hash: string, + ): Promise> { const transaction = await TransactionsService.get(hash) if (!transaction) { @@ -114,15 +119,20 @@ export default class TransactionsController { .reduce((result, c) => result + c, BigInt(0)) const value: bigint = outputCapacities - inputCapacities transaction.value = value.toString() + const inputsCount = transaction.inputs ? transaction.inputs.length.toString() : '0' + if (transaction.inputs) { + transaction.inputs = transaction.inputs.slice(0, CELL_COUNT_THRESHOLD) + } + const outputsCount = transaction.outputs ? transaction.outputs.length.toString() : '0' if (transaction.outputs) { - transaction.outputs = transaction - .outputs.sort((o1, o2) => +o1.outPoint!.index - +o2.outPoint!.index) - .slice(0, 200) + transaction.outputs = transaction.outputs + .sort((o1, o2) => +o1.outPoint!.index - +o2.outPoint!.index) + .slice(0, CELL_COUNT_THRESHOLD) } return { status: ResponseCode.Success, - result: transaction, + result: { ...transaction, outputsCount, inputsCount }, } } From 7fbe9c4512362e83617f6ace7c879171ff8d5fa9 Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Mon, 30 Sep 2019 10:06:06 +0800 Subject: [PATCH 2/2] Update packages/neuron-ui/src/locales/en.json Co-Authored-By: James Chen --- packages/neuron-ui/src/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 0ff51d26af..34c89e06a3 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -150,7 +150,7 @@ "inputs": "Inputs", "outputs": "Outputs", "view-in-explorer": "Explorer", - "view-in-explorer-button-title": "View in the explorer" + "view-in-explorer-button-title": "View on explorer" }, "addresses": { "addresses": "Addresses",