feat: seed input: autocomplete and validate words
This commit is contained in:
parent
7a243105f9
commit
c30ead3d9b
7 changed files with 2279 additions and 27 deletions
|
|
@ -0,0 +1,80 @@
|
||||||
|
<div>
|
||||||
|
<div v-if="done">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">Seed Input Done</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 q-pt-sm">Word Count</div>
|
||||||
|
<div class="col-6 q-pr-lg">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="wordCount"
|
||||||
|
type="number"
|
||||||
|
label="Word Count"
|
||||||
|
:options="wordCountOptions"
|
||||||
|
@input="initWords"
|
||||||
|
></q-select>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 q-pr-lg"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 q-pr-lg"></div>
|
||||||
|
<div class="col-6">Enter word at position: {{actualPosition}}</div>
|
||||||
|
<div class="col-3 q-pr-lg"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="currentPosition > 0"
|
||||||
|
@click="previousPosition"
|
||||||
|
unelevated
|
||||||
|
class="btn-full"
|
||||||
|
color="secondary"
|
||||||
|
>Previous</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 q-pr-lg">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
use-input
|
||||||
|
hide-selected
|
||||||
|
fill-input
|
||||||
|
input-debounce="0"
|
||||||
|
v-model="currentWord"
|
||||||
|
:options="options"
|
||||||
|
@filter="filterFn"
|
||||||
|
@input-value="setModel"
|
||||||
|
></q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-3 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="currentPosition < wordCount - 1"
|
||||||
|
@click="nextPosition"
|
||||||
|
unelevated
|
||||||
|
class="btn-full"
|
||||||
|
color="secondary"
|
||||||
|
>Next</q-btn
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
v-else
|
||||||
|
@click="seedInputDone"
|
||||||
|
unelevated
|
||||||
|
class="btn-full"
|
||||||
|
color="primary"
|
||||||
|
>Done</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<q-linear-progress
|
||||||
|
:value="currentPosition / (wordCount -1)"
|
||||||
|
size="5px"
|
||||||
|
color="primary"
|
||||||
|
class="q-mt-sm"
|
||||||
|
></q-linear-progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
async function seedInput(path) {
|
||||||
|
const template = await loadTemplateAsync(path)
|
||||||
|
Vue.component('seed-input', {
|
||||||
|
name: 'seed-input',
|
||||||
|
template,
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
actualPosition: function () {
|
||||||
|
return this.words[this.currentPosition].position
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
wordCountOptions: ['12', '15', '18', '21', '24'],
|
||||||
|
wordCount: 24,
|
||||||
|
words: [],
|
||||||
|
currentPosition: 0,
|
||||||
|
stringOptions: [],
|
||||||
|
options: [],
|
||||||
|
currentWord: '',
|
||||||
|
done: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
filterFn(val, update, abort) {
|
||||||
|
update(() => {
|
||||||
|
const needle = val.toLocaleLowerCase()
|
||||||
|
this.options = this.stringOptions
|
||||||
|
.filter(v => v.toLocaleLowerCase().indexOf(needle) != -1)
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.startsWith(needle)) {
|
||||||
|
if (b.startsWith(needle)) {
|
||||||
|
return a - b
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
if (b.startsWith(needle)) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return a - b
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initWords() {
|
||||||
|
const words = []
|
||||||
|
for (let i = 1; i <= this.wordCount; i++) {
|
||||||
|
words.push({
|
||||||
|
position: i,
|
||||||
|
value: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.currentPosition = 0
|
||||||
|
this.words = _.shuffle(words)
|
||||||
|
},
|
||||||
|
setModel(val) {
|
||||||
|
this.currentWord = val
|
||||||
|
this.words[this.currentPosition].value = this.currentWord
|
||||||
|
},
|
||||||
|
nextPosition() {
|
||||||
|
if (this.currentPosition < this.wordCount - 1) {
|
||||||
|
this.currentPosition++
|
||||||
|
}
|
||||||
|
this.currentWord = this.words[this.currentPosition].value
|
||||||
|
},
|
||||||
|
previousPosition() {
|
||||||
|
if (this.currentPosition > 0) {
|
||||||
|
this.currentPosition--
|
||||||
|
}
|
||||||
|
this.currentWord = this.words[this.currentPosition].value
|
||||||
|
},
|
||||||
|
seedInputDone() {
|
||||||
|
const badWordPositions = this.words
|
||||||
|
.filter(w => !w.value || !this.stringOptions.includes(w.value))
|
||||||
|
.map(w => w.position)
|
||||||
|
if (badWordPositions.length) {
|
||||||
|
this.$q.notify({
|
||||||
|
timeout: 10000,
|
||||||
|
type: 'warning',
|
||||||
|
message:
|
||||||
|
'The seed has incorrect words. Please check at these positions: ',
|
||||||
|
caption: 'Position: ' + badWordPositions.join(', ')
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const mnemonic = this.words
|
||||||
|
.sort((a, b) => a.position - b.position)
|
||||||
|
.map(w => w.value)
|
||||||
|
.join(' ')
|
||||||
|
this.$emit('on-seed-input-done', mnemonic)
|
||||||
|
this.done = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created: async function () {
|
||||||
|
this.stringOptions = bip39WordList
|
||||||
|
this.initWords()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -375,18 +375,17 @@
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<q-dialog v-model="showConsole" position="top">
|
<q-dialog v-model="showConsole" position="top">
|
||||||
|
|
||||||
<q-card class="q-pa-lg q-pt-xl">
|
<q-card class="q-pa-lg q-pt-xl">
|
||||||
<div class="row q-mt-lg q-mb-lg">
|
<div class="row q-mt-lg q-mb-lg">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-badge
|
<q-badge
|
||||||
class="text-subtitle2 float-right"
|
class="text-subtitle2 float-right"
|
||||||
color="yellow"
|
color="yellow"
|
||||||
text-color="black"
|
text-color="black"
|
||||||
>
|
>
|
||||||
Open the browser Developer Console for more Details!
|
Open the browser Developer Console for more Details!
|
||||||
</q-badge>
|
</q-badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
|
|
@ -460,26 +459,37 @@
|
||||||
>
|
>
|
||||||
For test purposes only. Do not enter word list with real funds!!!
|
For test purposes only. Do not enter word list with real funds!!!
|
||||||
</q-badge>
|
</q-badge>
|
||||||
<br /><br /><br />
|
<br />
|
||||||
<span>Enter new word list separated by space</span>
|
<q-toggle
|
||||||
<q-input
|
label="Enter word list separated by space"
|
||||||
v-model.trim="hww.mnemonic"
|
color="secodary"
|
||||||
filled
|
v-model="hww.quickMnemonicInput"
|
||||||
:type="hww.showMnemonic ? 'text' : 'password'"
|
></q-toggle>
|
||||||
filled
|
|
||||||
dense
|
|
||||||
label="Word List"
|
|
||||||
>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon
|
|
||||||
:name="hww.showMnemonic ? 'visibility' : 'visibility_off'"
|
|
||||||
class="cursor-pointer"
|
|
||||||
@click="hww.showMnemonic = !hww.showMnemonic"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
<div v-if="hww.quickMnemonicInput">
|
||||||
|
<q-input
|
||||||
|
v-model.trim="hww.mnemonic"
|
||||||
|
filled
|
||||||
|
:type="hww.showMnemonic ? 'text' : 'password'"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
label="Word List"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
:name="hww.showMnemonic ? 'visibility' : 'visibility_off'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="hww.showMnemonic = !hww.showMnemonic"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<seed-input v-else @on-seed-input-done="seedInputDone"></seed-input>
|
||||||
|
<br />
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<br />
|
||||||
<span>Enter new password (8 numbers/letters)</span>
|
<span>Enter new password (8 numbers/letters)</span>
|
||||||
<q-input
|
<q-input
|
||||||
v-model.trim="hww.password"
|
v-model.trim="hww.password"
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ async function serialSigner(path) {
|
||||||
showPassword: false,
|
showPassword: false,
|
||||||
mnemonic: null,
|
mnemonic: null,
|
||||||
showMnemonic: false,
|
showMnemonic: false,
|
||||||
|
quickMnemonicInput: false,
|
||||||
passphrase: null,
|
passphrase: null,
|
||||||
showPassphrase: false,
|
showPassphrase: false,
|
||||||
hasPassphrase: false,
|
hasPassphrase: false,
|
||||||
|
|
@ -174,6 +175,10 @@ async function serialSigner(path) {
|
||||||
isAuthenticated: function () {
|
isAuthenticated: function () {
|
||||||
return this.hww.authenticated
|
return this.hww.authenticated
|
||||||
},
|
},
|
||||||
|
|
||||||
|
seedInputDone: function (mnemonic) {
|
||||||
|
this.hww.mnemonic = mnemonic
|
||||||
|
},
|
||||||
isAuthenticating: function () {
|
isAuthenticating: function () {
|
||||||
if (this.isAuthenticated()) return false
|
if (this.isAuthenticated()) return false
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
|
|
||||||
2050
lnbits/extensions/watchonly/static/js/bip39-word-list.js
Normal file
2050
lnbits/extensions/watchonly/static/js/bip39-word-list.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,7 @@ const watchOnly = async () => {
|
||||||
await history('static/components/history/history.html')
|
await history('static/components/history/history.html')
|
||||||
await utxoList('static/components/utxo-list/utxo-list.html')
|
await utxoList('static/components/utxo-list/utxo-list.html')
|
||||||
await feeRate('static/components/fee-rate/fee-rate.html')
|
await feeRate('static/components/fee-rate/fee-rate.html')
|
||||||
|
await seedInput('static/components/seed-input/seed-input.html')
|
||||||
await sendTo('static/components/send-to/send-to.html')
|
await sendTo('static/components/send-to/send-to.html')
|
||||||
await payment('static/components/payment/payment.html')
|
await payment('static/components/payment/payment.html')
|
||||||
await serialSigner('static/components/serial-signer/serial-signer.html')
|
await serialSigner('static/components/serial-signer/serial-signer.html')
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,8 @@
|
||||||
<script src="{{ url_for('watchonly_static', path='js/tables.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/tables.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='js/map.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/map.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='js/utils.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/utils.js') }}"></script>
|
||||||
|
<script src="{{ url_for('watchonly_static', path='js/bip39-word-list.js') }}"></script>
|
||||||
|
|
||||||
<script src="{{ url_for('watchonly_static', path='components/my-checkbox/my-checkbox.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/my-checkbox/my-checkbox.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/wallet-config/wallet-config.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/wallet-config/wallet-config.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/wallet-list/wallet-list.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/wallet-list/wallet-list.js') }}"></script>
|
||||||
|
|
@ -246,10 +248,12 @@
|
||||||
<script src="{{ url_for('watchonly_static', path='components/history/history.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/history/history.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/utxo-list/utxo-list.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/utxo-list/utxo-list.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/fee-rate/fee-rate.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/fee-rate/fee-rate.js') }}"></script>
|
||||||
|
<script src="{{ url_for('watchonly_static', path='components/seed-input/seed-input.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/send-to/send-to.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/send-to/send-to.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/payment/payment.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/payment/payment.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/serial-signer/serial-signer.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/serial-signer/serial-signer.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/serial-port-config/serial-port-config.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/serial-port-config/serial-port-config.js') }}"></script>
|
||||||
|
|
||||||
<script src="{{ url_for('watchonly_static', path='js/crypto/noble-secp256k1.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/crypto/noble-secp256k1.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='js/crypto/aes.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/crypto/aes.js') }}"></script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue