From 58bcce3bc7dd81fe3e359ff034baddd5e060b9cb Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Mon, 11 Jul 2022 11:55:22 -0300 Subject: [PATCH 01/18] Refactor Mnemonic Import from 'onInput' to 'onPaste' + 'onKeyDown' --- packages/ui/src/pages/ImportAccount.ts | 306 +++++++------------------ packages/ui/src/styles.scss | 39 +++- 2 files changed, 112 insertions(+), 233 deletions(-) diff --git a/packages/ui/src/pages/ImportAccount.ts b/packages/ui/src/pages/ImportAccount.ts index d90979e0..19200889 100644 --- a/packages/ui/src/pages/ImportAccount.ts +++ b/packages/ui/src/pages/ImportAccount.ts @@ -1,6 +1,6 @@ import { FunctionalComponent } from 'preact'; import { html } from 'htm/preact'; -import { useState, useContext, useEffect } from 'preact/hooks'; +import { useState, useContext } from 'preact/hooks'; import { route } from 'preact-router'; import { JsonRpcMethod } from '@algosigner/common/messaging/types'; @@ -12,11 +12,13 @@ import { sendMessage } from 'services/Messaging'; import { REFERENCE_ACCOUNT_TOOLTIP } from '@algosigner/common/strings'; import algosdk from 'algosdk'; +const NUMBER_OF_WORDS = 25; +const EMPTY_MNEMONIC = new Array(NUMBER_OF_WORDS); + const ImportAccount: FunctionalComponent = (props: any) => { const store: any = useContext(StoreContext); - const wordsNumber = 25; const { ledger } = props; - const [mnemonicArray, setMnemonicArray] = useState>(new Array(wordsNumber)); + const [mnemonicArray, setMnemonicArray] = useState>(EMPTY_MNEMONIC); const [address, setAddress] = useState(''); const [isRef, setIsRef] = useState(false); const [name, setName] = useState(''); @@ -31,14 +33,9 @@ const ImportAccount: FunctionalComponent = (props: any) => { (isRef && !algosdk.isValidAddress(address)); const importAccount = (pwd: string) => { - // Trim mnemonic words - for (let i = 0; i < mnemonicArray.length; i++) { - mnemonicArray[i] = mnemonicArray[i].trim(); - } - const params = { passphrase: pwd, - mnemonic: mnemonicArray.join(' '), + mnemonic: mnemonicArray.map((w) => w.trim()).join(' '), address: address, isRef: isRef, name: name.trim(), @@ -68,38 +65,52 @@ const ImportAccount: FunctionalComponent = (props: any) => { }); }; - const handleMnemonicInput = (e) => { - const localMnemonicArray = mnemonicArray; - const inputText = e.target.value.toString(); + const handleMnemonicPaste = (e) => { + // We get the pasted text and then split it into words + const clipboardData = e.clipboardData; + const inputText = clipboardData.getData('text'); + e.preventDefault(); const inputArray = inputText.split(/[\s\t\r\n,]+/); - - // If it is a single word then update that word placement in the array + const localMnemonicArray = [...mnemonicArray]; + if (inputArray.length === 1) { + // If it is a single word then update that word placement in the array + localMnemonicArray[+e.target.id.toString().replace('mnemonicWord', '')] = inputText; + } else if (inputArray.length === NUMBER_OF_WORDS) { + // If it is multiple words then verify there are 25 and split + for (let i = 0; i < NUMBER_OF_WORDS; i++) { + localMnemonicArray[i] = inputArray[i]; + } + + // For the case the user is typing the full mnemonic + // after the split the last element will be empty + // so we must focus that element now to continue typing + const element = document.getElementById('mnemonicWord24'); + if (element) { + element.focus(); + } + } else { + // If it is multiple words but they are not 25 then warn, but allow for typing out 25. + console.log('[WARNING] - Mnemonic words must be a single word or the entire 25 mnemonic.'); localMnemonicArray[e.target.id.toString().replace('mnemonicWord', '')] = inputText; } - // If it is multiple words then verify there are 25 and split - else if (inputArray.length === wordsNumber) { - for(let i = 0; i < wordsNumber; i++) { - localMnemonicArray[i.toString()] = inputArray[i]; - } + setMnemonicArray(localMnemonicArray); + }; + + const handleWordInput = (e) => { + const key = (e as KeyboardEvent).key; + const separators = [' ', ',', '.']; - // For the case the user is typing the full mnemonic - // after the split the last element will be empty - // so we must focust that element now to continue typing - const element = document.getElementById('mnemonicWord24'); + if (separators.includes(key)) { + e.preventDefault(); + const currentIndex = +e.target.id.toString().replace('mnemonicWord', ''); + if (currentIndex < NUMBER_OF_WORDS - 1) { + const element = document.getElementById(`mnemonicWord${currentIndex + 1}`); if (element) { element.focus(); } - - } - // If it is multiple words but they are not 25 then warn, but allow for typing out 25. - else { - console.log('[WARNING] - Mnemonic words must be a single word or the entire 25 mnemonic.') - localMnemonicArray[e.target.id.toString().replace('mnemonicWord', '')] = inputText; + } } - - setMnemonicArray([]); - setMnemonicArray(localMnemonicArray); }; const handleAddressInput = (e) => { @@ -110,19 +121,18 @@ const ImportAccount: FunctionalComponent = (props: any) => { // Mnemonic boxes are prefixed with "mnemonicWord" const wordId = 'mnemonicWord' + iconNumber; const element = document.getElementById(wordId); - + // As long as we can locate the element just swap the show/hide icon and text/password if (element !== null) { if (element['type'] === 'password') { element['type'] = 'text'; - element.parentElement?.querySelectorAll('svg')[0]?.setAttribute('data-icon','eye'); - } - else { + element.parentElement?.querySelectorAll('svg')[0]?.setAttribute('data-icon', 'eye'); + } else { element['type'] = 'password'; - element.parentElement?.querySelectorAll('svg')[0]?.setAttribute('data-icon','eye-slash'); - } + element.parentElement?.querySelectorAll('svg')[0]?.setAttribute('data-icon', 'eye-slash'); + } } - } + }; return html`
@@ -175,198 +185,38 @@ const ImportAccount: FunctionalComponent = (props: any) => { `} ${!isRef && html` -

-

- Insert the 25 word mnemonic of the account -
-
- (Entire mnemonic may be pasted into a single field): -
-

-
-
- - - -