Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Commit

Permalink
Merge pull request #425 from PureStake/jan/paste-mnemonic
Browse files Browse the repository at this point in the history
Refactor Mnemonic Import from 'onInput' to 'onPaste' + 'onKeyDown'
  • Loading branch information
PureBrent authored Jul 12, 2022
2 parents 2b0d5ff + 58bcce3 commit 94a05b6
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 233 deletions.
306 changes: 78 additions & 228 deletions packages/ui/src/pages/ImportAccount.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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<string>(NUMBER_OF_WORDS);

const ImportAccount: FunctionalComponent = (props: any) => {
const store: any = useContext(StoreContext);
const wordsNumber = 25;
const { ledger } = props;
const [mnemonicArray, setMnemonicArray] = useState<Array<string>>(new Array<string>(wordsNumber));
const [mnemonicArray, setMnemonicArray] = useState<Array<string>>(EMPTY_MNEMONIC);
const [address, setAddress] = useState<string>('');
const [isRef, setIsRef] = useState<boolean>(false);
const [name, setName] = useState<string>('');
Expand All @@ -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(),
Expand Down Expand Up @@ -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) => {
Expand All @@ -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`
<div class="main-view" style="flex-direction: column; justify-content: space-between;">
Expand Down Expand Up @@ -175,198 +185,38 @@ const ImportAccount: FunctionalComponent = (props: any) => {
`}
${!isRef &&
html`
<p class="my-3">
<div>
Insert the 25 word mnemonic of the account
</div>
<div>
(Entire mnemonic may be pasted into a single field):
</div>
</p>
<div id="mnemonicBlock" style="display: flex; flex-wrap: wrap; text-align: right; padding-right: 30px;">
<div style="display: block; width: 33%;">
<label for="mnemonicWord0" style="margin-right: 5px;">1:</label>
<input id="mnemonicWord0" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[0]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('0')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
<div class="mb-3">
<p>Insert the 25 word mnemonic of the account</p>
<p>(Entire mnemonic may be pasted into a single field):</p>
</div>
<div class="pr-2" id="mnemonicBlock">
${[...mnemonicArray].map(
(_, index) => html`
<div class="word-block">
<label for="${`mnemonicWord${index}`}">${index + 1}:</label>
<input
id="${`mnemonicWord${index}`}"
type="password"
onPaste=${handleMnemonicPaste}
onKeyDown=${handleWordInput}
value=${mnemonicArray[index]}
/>
<span
class="icon mnemonic-visibility"
onClick="${() => handleMnemonicIconClick(index)}"
>
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
`
)}
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord1" style="margin-right: 5px;">2:</label>
<input id="mnemonicWord1" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[1]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('1')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord2" style="margin-right: 5px;">3:</label>
<input id="mnemonicWord2" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[2]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('2')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord3" style="margin-right: 5px;">4:</label>
<input id="mnemonicWord3" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[3]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('3')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord4" style="margin-right: 5px;">5:</label>
<input id="mnemonicWord4" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[4]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('4')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord5" style="margin-right: 5px;">6:</label>
<input id="mnemonicWord5" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[5]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('5')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord6" style="margin-right: 5px;">7:</label>
<input id="mnemonicWord6" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[6]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('6')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord7" style="margin-right: 5px;">8:</label>
<input id="mnemonicWord7" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[7]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('7')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord8" style="margin-right: 5px;">9:</label>
<input id="mnemonicWord8" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[8]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('8')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord9" style="margin-right: 5px;">10:</label>
<input id="mnemonicWord9" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[9]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('9')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord10" style="margin-right: 5px;">11:</label>
<input id="mnemonicWord10" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[10]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('10')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord11" style="margin-right: 5px;">12:</label>
<input id="mnemonicWord11" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[11]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('11')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord12" style="margin-right: 5px;">13:</label>
<input id="mnemonicWord12" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[12]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('12')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord13" style="margin-right: 5px;">14:</label>
<input id="mnemonicWord13" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[13]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('13')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord14" style="margin-right: 5px;">15:</label>
<input id="mnemonicWord14" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[14]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('14')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord15" style="margin-right: 5px;">16:</label>
<input id="mnemonicWord15" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[15]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('15')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord16" style="margin-right: 5px;">17:</label>
<input id="mnemonicWord16" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[16]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('16')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord17" style="margin-right: 5px;">18:</label>
<input id="mnemonicWord17" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[17]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('17')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord18" style="margin-right: 5px;">19:</label>
<input id="mnemonicWord18" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[18]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('18')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord19" style="margin-right: 5px;">20:</label>
<input id="mnemonicWord19" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[19]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('19')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord20" style="margin-right: 5px;">21:</label>
<input id="mnemonicWord20" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[20]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('20')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord21" style="margin-right: 5px;">22:</label>
<input id="mnemonicWord21" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[21]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('21')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord22" style="margin-right: 5px;">23:</label>
<input id="mnemonicWord22" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[22]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('22')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord23" style="margin-right: 5px;">24:</label>
<input id="mnemonicWord23" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[23]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('23')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord24" style="margin-right: 5px;">25:</label>
<input id="mnemonicWord24" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[24]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('24')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
</div>
`}
<p class="pt-2 has-text-danger">
${error !== undefined && error.length > 0 && error}
</p>
</div>
<div style="padding: 1em;">
${error &&
error.length > 0 &&
html`<p class="pt-2 has-text-danger has-text-centered">${error}</p>`}
<div class="p-4">
<button
class="button is-primary is-fullwidth"
id="nextStep"
Expand Down
Loading

0 comments on commit 94a05b6

Please sign in to comment.