]> zoso.dev Git - nemo-wallet.git/commitdiff
Too many changes to list.
authorChris Duncan <chris@zoso.dev>
Mon, 21 Oct 2024 20:13:36 +0000 (13:13 -0700)
committerChris Duncan <chris@zoso.dev>
Mon, 21 Oct 2024 20:13:36 +0000 (13:13 -0700)
app/pages/nemo-wallet.html

index e12f4f7e48f965eeae9275841dbc6b8c41de491a..f70c951ff5addd84f3c0b598388c7eb72535be59 100644 (file)
@@ -4,8 +4,12 @@
        <meta charset="utf-8" />
        <meta lang="en" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
+       <meta name="xel-theme" content="https://unpkg.com/xel@latest/themes/adwaita-dark.css" />
+       <meta name="xel-accent-color" content="blue" />
+       <meta name="xel-icons" content="https://unpkg.com/bootstrap-icons@latest/bootstrap-icons.svg" />
        <script>
-               let Account, Bip44Wallet, Blake2bWallet, LedgerWallet, SendBlock, ReceiveBlock, ChangeBlock, rpc, Rolodex, Xel
+               let Account, Bip44Wallet, Blake2bWallet, SendBlock, ReceiveBlock, ChangeBlock, rpc, Rolodex, Xel
+
                const observer = new MutationObserver((mutations) => {
                        for (const mutation of mutations) {
                                if (mutation.type === "attributes") {
                                }
                        }
                })
-               function load () {
-                       loadUi().then(loadNemo).then(loadUx).then(() => { console.log('done') })
+
+               async function load () {
+                       try {
+                               document.querySelector(location.hash).classList.add('show')
+                       } catch (err) {
+                               location.hash = '#transact'
+                       }
+                       await loadUi()
+                       await loadNemo()
+                       await loadData()
+                       console.log('done')
                }
+
                async function loadUi () {
                        console.log('loading xel')
                        Xel = await import('https://unpkg.com/xel@latest')
                        }, 250)
                        console.log('loaded xel')
                }
+
                async function loadNemo () {
                        console.log('loading libnemo')
-                       await ({ Account, Bip44Wallet, Blake2bWallet, LedgerWallet, SendBlock, ReceiveBlock, ChangeBlock, Node: rpc, Rolodex } = await import('https://unpkg.com/libnemo@latest'))
+                       await ({ Account, Bip44Wallet, Blake2bWallet, SendBlock, ReceiveBlock, ChangeBlock, Node: rpc, Rolodex } = await import('https://unpkg.com/libnemo@latest'))
                        console.log('loaded libnemo')
                }
-               async function loadUx () {
+
+               async function loadData () {
                        console.log('loading data')
+                       const walletSelect = document.querySelector('#wallet')
+                       const accountSelect = document.querySelector('#account')
                        const wallets = await getFromStorage('wallets')
                        for (const wallet of wallets) {
-                               const { id, name } = JSON.parse(wallet)
-                               const option = document.createElement('option')
-                               option.value = id
-                               option.innerText = name
-                               wallet.appendChild(option)
+                               const { id, name, accounts } = JSON.parse(wallet)
+                               const walletOption = new Option(name, id)
+                               walletSelect.add(walletOption)
+                               for (const a of accounts ?? []) {
+                                       const { id, name } = JSON.parse(a)
+                                       const accountOption = new Option(name, id)
+                                       accountSelect.add(accountOption)
+                               }
                        }
-                       const wallet = document.querySelector('#wallet')
-                       if (wallet.value === '_new' || wallet.value === '_import') {
-                               wallet.value = ''
+                       if (walletSelect.value.substring(0, 1) === '_') {
+                               walletSelect.value = ''
+                       } else {
+                               updateWalletSelect()
                        }
-                       const account = document.querySelector('#account')
-                       if (account.value === '_new') {
-                               account.value = ''
-                       }
-                       try {
-                               document.querySelector(location.hash).classList.add('show')
-                       } catch (err) {
-                               location.hash = '#transact'
+                       if (accountSelect.value.substring(0, 1) === '_') {
+                               accountSelect.setAttribute('disabled', '')
+                               accountSelect.value = ''
                        }
                        console.log('loaded data')
                }
                                        document.querySelector(newHash).classList.add('show')
                                }
                        } catch (err) {
-                               notify.warn(`Page not found`)
+                               notify.error(`Page not found`)
+                       }
+               }
+
+               function addToStorage (key, value) {
+                       if (typeof value !== 'string') {
+                               throw new TypeError(`Cannot add ${typeof value} to storage`)
                        }
+                       const item = JSON.parse(sessionStorage.getItem(key) ?? '[]')
+                       if (!Array.isArray(item)) {
+                               throw new TypeError(`Storage item is not an array`)
+                       }
+                       item.push(value)
+                       return new Promise((resolve, reject) => {
+                               try {
+                                       sessionStorage.setItem(key, JSON.stringify(item))
+                                       resolve()
+                               } catch (err) {
+                                       console.error(err)
+                                       reject(err)
+                               }
+                       })
+               }
+
+               function getFromStorage (key) {
+                       return new Promise((resolve, reject) => {
+                               try {
+                                       const item = JSON.parse(sessionStorage.getItem(key) ?? '[]')
+                                       if (!Array.isArray(item)) {
+                                               throw new TypeError(`Storage item is not an array`)
+                                       }
+                                       resolve(item)
+                               } catch (err) {
+                                       console.error(err)
+                                       reject(err)
+                               }
+                       })
                }
 
                function getWalletClass (algorithm) {
                        }
                }
 
-               async function updateWallet () {
-                       const wallet = document.querySelector('#wallet')
-                       const account = document.querySelector('#account')
-                       if (wallet.value === '_new') {
-                               return await document.querySelector('#wallet-new').showModal()
-                       }
-                       if (wallet.value === '_import') {
-                               return await document.querySelector('#wallet-import').showModal()
-                       }
-                       if (wallet.value === '_ledger') {
-                               try {
-                                       await LedgerWallet.create()
-                                       account.removeAttribute('disabled')
-                               } catch (err) {
-                                       account.setAttribute('disabled', '')
-                                       notify.error(`Error connecting to Ledger: ${err}`)
-                               } finally {
+               async function updateWalletSelect () {
+                       const select = document.querySelector('#wallet')
+                       switch (select.value) {
+                               case '': {
+                                       await updateAccountSelect(null)
                                        return
                                }
+                               case '_new': {
+                                       try {
+                                               await document.querySelector('#wallet-new').showModal()
+                                       } catch (err) {
+                                               select.value = ''
+                                               notify.error(`Error creating wallet: ${err}`)
+                                       } finally {
+                                               return
+                                       }
+                               }
+                               case '_import': {
+                                       try {
+                                               await document.querySelector('#wallet-import').showModal()
+                                       } catch (err) {
+                                               select.value = ''
+                                               notify.error(`Error importing wallet: ${err}`)
+                                       } finally {
+                                               return
+                                       }
+                               }
+                               case '_ledger': {
+                                       try {
+                                               return await LedgerWallet.create()
+                                       } catch (err) {
+                                               select.value = ''
+                                               notify.error(`Error importing wallet: ${err}`)
+                                               return
+                                       }
+                               }
+                               default: {
+                                       const wallets = await getFromStorage('wallets')
+                                       const walletData = wallets.find(w => {
+                                               const { id, algorithm } = JSON.parse(w)
+                                               return id === select.value
+                                       })
+                                       const { id, algorithm } = JSON.parse(walletData)
+                                       const walletClass = getWalletClass(algorithm)
+                                       const wallet = await walletClass.restore(id)
+                                       await updateAccountSelect(wallet)
+                                       return wallet
+                               }
                        }
-                       const form = document.querySelector('#wallet-form')
-                       const walletType = document.querySelector('#wallet-form-wallet-type')
-                       const importType = document.querySelector('#wallet-form-import-type')
-                       const importValue = document.querySelector('#wallet-form-import-value')
-                       const salt = document.querySelector('#wallet-form-salt')
-                       walletType.querySelector('x-radios').value = null
-                       importType.querySelector('x-select').value = null
-                       importValue.querySelector('x-input').value = null
-                       salt.querySelector('x-input').value = null
-                       if (wallet.value === '_new') {
-                               importType.classList.add('hide')
-                               importValue.classList.add('hide')
-                               salt.classList.remove('hide')
-                       }
-                       if (wallet.value === '_import') {
-                               importType.classList.remove('hide')
-                               importValue.classList.remove('hide')
-                               salt.classList.add('hide')
-                       }
-                       return await form.showModal()
                }
 
-               async function updateAccount () {
-                       const walletId = document.querySelector('#wallet').value
-                       const account = document.querySelector('#account')
-                       const wallets = await getFromStorage('wallets')
-                       const { id, algorithm } = wallets.find(wallet => {
-                               const { id } = JSON.parse(wallet)
-                               return id === walletId
-                       })
-                       try {
-                               const walletClass = getWalletClass(algorithm)
-                               const wallet = await walletClass.fromId(id)
-                               const accounts = await wallet.accounts()
-                       } catch (err) {
-                               notify.error(err)
+               async function updateAccountSelect (wallet) {
+                       const accountSelect = document.querySelector('#account')
+                       if (wallet == null || wallet === '') {
+                               accountSelect.value = ''
+                               accountSelect.setAttribute('disabled', '')
                                return
                        }
-                       const form = document.querySelector('#wallet-form')
-                       const walletType = document.querySelector('#wallet-form-wallet-type')
-                       const importType = document.querySelector('#wallet-form-import-type')
-                       const importValue = document.querySelector('#wallet-form-import-value')
-                       const salt = document.querySelector('#wallet-form-salt')
-                       walletType.querySelector('x-radios').value = null
-                       importType.querySelector('x-select').value = null
-                       importValue.querySelector('x-input').value = null
-                       salt.querySelector('x-input').value = null
-                       if (wallet.value === '_new') {
-                               importType.classList.add('hide')
-                               importValue.classList.add('hide')
-                               salt.classList.remove('hide')
-                       }
-                       if (wallet.value === '_import') {
-                               importType.classList.remove('hide')
-                               importValue.classList.remove('hide')
-                               salt.classList.add('hide')
+                       switch (accountSelect.value) {
+                               case '': {
+                                       accountSelect.removeAttribute('disabled')
+                                       return
+                               }
+                               case '_new': {
+                                       try {
+                                               return await document.querySelector('#account-new').showModal()
+                                       } catch (err) {
+                                               accountSelect.value = ''
+                                               notify.error(`Error creating account: ${err}`)
+                                               return
+                                       }
+                               }
+                               default: {
+                                       accountSelect.removeAttribute('disabled')
+                                       return await wallet.accounts(accountSelect.value)
+                               }
                        }
-                       return await form.showModal()
                }
 
                async function createWallet () {
                        const walletSelect = document.querySelector('#wallet')
                        const form = document.querySelector('#wallet-new')
                        const name = form.querySelector('#wallet-new-name x-input').value
-                       if (name === '_ledger') {
-                               notify.warn(`Invalid wallet name "${name}".`)
+                       if (name.substring(0, 1) === '_') {
+                               notify.error(`Wallet name cannot start with an underscore`)
                                return
                        }
                        const algorithm = form.querySelector('#wallet-new-algorithm x-radios').value
                                await addToStorage(`wallets`, JSON.stringify(walletData))
 
                                const select = document.querySelector('#wallet')
-                               const option = document.createElement('option')
-                               option.value = walletData.id
-                               option.innerText = walletData.name
-                               select.appendChild(option)
-                               walletSelect.value = walletData.id
+                               const option = new Option(walletData.name, walletData.id, false, true)
+                               select.add(option)
+                               await updateWalletSelect()
                        } catch (err) {
-                               label?.remove()
-                               menuitem?.remove()
+                               option?.remove()
                                notify.error(err.msg)
                        } finally {
                                await form.close()
                        }
                }
 
-               function addToStorage (key, value) {
-                       if (typeof value !== 'string') {
-                               throw new TypeError(`Cannot add ${typeof value} to storage`)
-                       }
-                       const item = JSON.parse(sessionStorage.getItem(key) ?? '[]')
-                       if (!Array.isArray(item)) {
-                               throw new TypeError(`Storage item is not an array`)
+               async function createAccount () {
+                       const wallet = await updateWalletSelect()
+                       const accountSelect = document.querySelector('#account')
+                       const form = document.querySelector('#account-new')
+                       const name = form.querySelector('#account-new-name x-input').value
+                       if (name.substring(0, 1) === '_') {
+                               notify.error(`Account name cannot start with an underscore`)
+                               return
                        }
-                       item.push(value)
-                       return new Promise((resolve, reject) => {
-                               try {
-                                       sessionStorage.setItem(key, JSON.stringify(item))
-                                       resolve()
-                               } catch (err) {
-                                       console.error(err)
-                                       reject(err)
-                               }
-                       })
-               }
-
-               function getFromStorage (key) {
-                       return new Promise((resolve, reject) => {
-                               try {
-                                       const item = JSON.parse(sessionStorage.getItem(key) ?? '[]')
-                                       if (!Array.isArray(item)) {
-                                               throw new TypeError(`Storage item is not an array`)
-                                       }
-                                       resolve(item)
-                               } catch (err) {
-                                       console.error(err)
-                                       reject(err)
+                       const index = form.querySelector('#account-new-index x-numberinput')?.value
+                       let account
+                       try {
+                               account = await wallet.accounts(index)
+                               const accountData = {
+                                       id: index,
+                                       name: name || index
                                }
-                       })
+                               await addToStorage(`accounts`, JSON.stringify(accountData))
+                               const select = document.querySelector('#account')
+                               const option = new Option(accountData.name, accountData.id, false, true)
+                               select.add(option)
+                               await updateAccountSelect(wallet)
+                       } catch (err) {
+                               option?.remove()
+                               notify.error(err.msg)
+                       } finally {
+                               await form.close()
+                               return account
+                       }
                }
 
                function updateWalletForm (id) {
                        }
                }
 
-               async function closeForm (id) {
+               async function formCancel (id) {
                        const form = document.querySelector(id)
-                       const inputs = form.querySelector('x-input')
+                       const inputs = form.querySelectorAll('x-input')
                        for (const input of inputs) {
-                               input.value = null
+                               input.value = ''
+                       }
+                       const wallet = document.querySelector('#wallet')
+                       if (wallet.value.substring(0, 1) === '_') {
+                               wallet.value = ''
                        }
                        await form.close()
                }
                        card.replaceChildren(...walletElements)
                }
 
-               async function deriveAccounts () {
-                       const wallet = document.querySelector('#wallet')
-                       const account = document.querySelector('#account')
-                       if (!wallet.value) {
-                               account.value = null
-                               notify.error(`Select a wallet first`)
-                               return
-                       }
-                       const form = btn.querySelector('#account-form')
-                       await console.log('deriveAccounts')
-               }
                async function clearStorage () {
                        sessionStorage.clear()
+                       const options = document.querySelectorAll('#wallet option, #account option')
+                       for (const option of options) {
+                               if (option.value !== '' && option.value.substring(0, 1) != '_') {
+                                       option.remove()
+                               }
+                       }
+                       document.querySelector('#wallet').value = ''
+                       document.querySelector('#account').value = ''
+                       await updateWalletSelect()
                        notify.info(`Session storage cleared`)
                }
+
                const notify = {
                        _show: (msg, color) => {
                                const container = document.querySelector('#notifications')
                        },
                        info: (msg) => notify._show(`ℹ️ ${msg}`),
                        ok: (msg) => notify._show(`☑️ ${msg}`, 'Cyan'),
-                       warn: (msg) => notify._show(`⚠️ ${msg}`, 'Gold'),
-                       error: (msg) => notify._show(`🚫 ${msg}`, 'Tomato')
+                       error: (msg) => notify._show(`⚠️ ${msg}`, 'Gold')
                }
        </script>
-       <meta name="xel-theme" content="https://unpkg.com/xel@latest/themes/adwaita-dark.css" />
-       <meta name="xel-accent-color" content="blue" />
-       <meta name="xel-icons" content="https://unpkg.com/bootstrap-icons@latest/bootstrap-icons.svg" />
        <style>
     body {
       background-color: slategrey;
       color: #209CE9;
       fill: #209CE9;
     }
+               .flex {
+                       display: flex;
+               }
+               .flex.v-start {
+                       flex-direction: column;
+                       justify-content: flex-start;
+               }
+               .flex.v-mid {
+                       flex-direction: column;
+                       justify-content: center;
+               }
+               .flex.v-end {
+                       flex-direction: column;
+                       justify-content: flex-end;
+               }
+               .flex.v-stretch {
+                       flex-direction: column;
+                       justify-content: stretch;
+               }
+               .flex.h-start {
+                       align-items: flex-start;
+                       flex-direction: row;
+               }
+               .flex.h-mid {
+                       align-items: center;
+                       flex-direction: row;
+               }
+               .flex.h-end {
+                       align-items: flex-end;
+                       flex-direction: row;
+               }
+               .flex.h-stretch {
+                       align-items: stretch;
+                       flex-direction: row;
+               }
     #notifications {
       display: flex;
       flex-direction: column;
 
                <x-box id="wallets">
                        <label for="wallet">Wallet</label>
-                       <select id="wallet" style="width:100%;" onchange="updateWallet()">
+                       <select id="wallet" style="width:100%;" onchange="updateWalletSelect()">
                                <option value="" disabled>Choose...</option>
                                <hr />
                                <option value="_new">New</option>
                                <option value="_import">Import</option>
                                <hr />
-                               <option value="_ledger">Ledger</option>
+                               <option disabled value="_ledger">Ledger</option>
                                <hr />
                        </select>
                </x-box>
                <!-- New Wallet Form -->
 
                <dialog id="wallet-new">
-                       <x-box vertical>
-                               <x-box id="wallet-new-name">
+                       <div class="flex v-stretch">
+                               <div class="flex h-mid" id="wallet-new-name">
                                        <x-label>Name</x-label>
                                        <x-input type="text"></x-input>
-                               </x-box>
+                               </div>
                                <x-box id="wallet-new-algorithm">
                                        <x-label>Algorithm</x-label>
                                        <x-radios required ontoggle="updateWalletForm('#wallet-new')">
                                </x-box>
                                <x-box>
                                        <x-buttons>
-                                               <x-button onclick="closeForm('#wallet-new')">
+                                               <x-button onclick="formCancel('#wallet-new')">
                                                        Cancel
                                                </x-button>
                                                <x-button onclick="createWallet().then(w => { console.log(w);showWallets() })">
                                                </x-button>
                                        </x-buttons>
                                </x-box>
-                       </x-box>
+                       </div>
                </dialog>
 
                <!-- Import Wallet Form -->
 
                <dialog id="wallet-import">
-                       <x-box vertical>
+                       <div class="flex vertical">
                                <x-box id="wallet-import-name">
                                        <x-label>Name</x-label>
                                        <x-input type="text"></x-input>
                                </x-box>
                                <x-box>
                                        <x-buttons>
-                                               <x-button onclick="closeForm('#wallet-import')">
+                                               <x-button onclick="formCancel('#wallet-import')">
                                                        Cancel
                                                </x-button>
                                                <x-button onclick="importWallet().then(w => { console.log(w);showWallets() })">
                                                </x-button>
                                        </x-buttons>
                                </x-box>
-                       </x-box>
+                       </div>
                </dialog>
 
                <!-- Account Select -->
 
                <x-box id="accounts">
                        <label for="account">Account</label>
-                       <select id="account" style="width:100%;" onchange="updateAccount()">
+                       <select id="account" style="width:100%;" onchange="updateWalletSelect()">
                                <option value="" disabled>Choose...</option>
                                <hr />
                                <option value="_new">New</option>
                                <hr />
                        </select>
-               </x-box><!-- New Account Form -->
+               </x-box>
+
+               <!-- New Account Form -->
 
                <dialog id="account-new">
                        <x-box vertical>
-                               <x-box>
+                               <x-box id="account-new-name">
                                        <x-label>Name</x-label>
                                        <x-input type="text"></x-input>
                                </x-box>
-                               <x-box>
+                               <x-box id="account-new-index">
                                        <x-label>Index (optional)</x-label>
                                        <x-numberinput min="0" max="2147483647"></x-numberinput>
                                </x-box>
+                               <x-box id="account-new-password">
+                                       <x-label>Unlock</x-label>
+                                       <x-input type="password" required><x-label>Password</x-label></x-input>
+                               </x-box>
                                <x-buttons>
                                        <x-button onclick="document.querySelector('#account-new').close()">
                                                Cancel
                                        </x-button>
-                                       <x-button onclick="deriveAccounts().then(w => { console.log(w);showWallets() })">
+                                       <x-button onclick="createAccount().then(w => { console.log(w);showWallets() })">
                                                OK
                                        </x-button>
                                </x-buttons>