refactor: extract address-list component

This commit is contained in:
Vlad Stan 2022-07-25 14:57:54 +03:00
parent 8376f08e8b
commit ff29aacace
6 changed files with 431 additions and 368 deletions

View file

@ -48,7 +48,7 @@ You can now use this wallet on the LNBits [SatsPayServer](https://github.com/lnb
- shows the UTXOs for all wallets - shows the UTXOs for all wallets
- there can be multiple UTXOs for the same address - there can be multiple UTXOs for the same address
### Make Payment ### New Payment
- create a new `Partially Signed Bitcoin Transaction` - create a new `Partially Signed Bitcoin Transaction`
- multiple `Send Addresses` can be added - multiple `Send Addresses` can be added
- the `Max` button next to an address is for sending the remaining funds to this address (no change) - the `Max` button next to an address is for sending the remaining funds to this address (no change)
@ -57,7 +57,7 @@ You can now use this wallet on the LNBits [SatsPayServer](https://github.com/lnb
- `Show Advanced` allows to (see `screenshot 2`): - `Show Advanced` allows to (see `screenshot 2`):
- select from which account the change address will be selected (defaults to the first one) - select from which account the change address will be selected (defaults to the first one)
- select the `Fee Rate` - select the `Fee Rate`
- it defaults to the `Medium` value at the moment the `Make Payment` button was clicked - it defaults to the `Medium` value at the moment the `New Payment` button was clicked
- it can be refreshed - it can be refreshed
- warnings are shown if the fee is too Low or to High - warnings are shown if the fee is too Low or to High

View file

@ -0,0 +1,204 @@
<div>
<div class="row items-center no-wrap q-mb-md">
<div class="col q-pr-lg">
<q-select
filled
clearable
dense
emit-value
v-model="selectedWallet"
:options="accounts"
label="Wallet Account"
></q-select>
</div>
<div class="col q-pr-lg">
<q-select
filled
clearable
dense
emit-value
multiple
:options="filterOptions"
v-model="filterValues"
label="Filter"
></q-select>
</div>
<div class="col-auto">
<q-input
borderless
dense
debounce="300"
v-model="addressesTable.filter"
placeholder="Search"
>
<template v-slot:append>
<q-icon name="search"></q-icon>
</template>
</q-input>
</div>
</div>
<q-table
style="height: 400px"
flat
dense
:data="getFilteredAddresses()"
row-key="id"
virtual-scroll
:columns="addressesTable.columns"
:pagination.sync="addressesTable.pagination"
:filter="addressesTable.filter"
>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
size="sm"
color="accent"
round
dense
@click="props.row.expanded= !props.row.expanded"
:icon="props.row.expanded? 'remove' : 'add'"
/>
</q-td>
<q-td key="address" :props="props">
<div>
<a
style="color: unset"
:href="mempool_endpoint + '/address/' + props.row.address"
target="_blank"
>
{{props.row.address}}</a
>
<q-badge
v-if="props.row.branch_index === 1"
color="orange"
class="q-mr-md"
outline
>
change
</q-badge>
<q-btn
v-if="props.row.gapLimitExceeded"
color="yellow"
icon="warning"
title="Gap Limit Exceeded"
@click="props.row.expanded= !props.row.expanded"
outline
class="q-ml-md"
size="xs"
>
</q-btn>
</div>
</q-td>
<q-td
key="amount"
:props="props"
:class="props.row.amount > 0 ? 'text-green-13 text-weight-bold' : ''"
>
<div>{{satBtc(props.row.amount)}}</div>
</q-td>
<q-td key="note" :props="props" :class="">
<div>{{props.row.note}}</div>
</q-td>
<q-td key="wallet" :props="props" :class="">
<div>{{getWalletName(props.row.wallet)}}</div>
</q-td>
</q-tr>
<q-tr v-show="props.row.expanded" :props="props">
<q-td colspan="100%">
<div class="row items-center q-mt-md q-mb-lg">
<div class="col-2 q-pr-lg"></div>
<div class="col-4 q-pr-lg">
<q-btn
unelevated
dense
size="md"
icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="showAddressDetails(props.row)"
>
QR Code</q-btn
>
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
dense
size="md"
icon="refresh"
color="grey"
@click="scanAddress(props.row)"
>
Rescan</q-btn
>
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
dense
size="md"
icon="history"
color="grey"
@click="searchInTab('history', props.row.address)"
>History</q-btn
>
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
dense
size="md"
color="grey"
@click="searchInTab('utxos', props.row.address)"
>View Coins</q-btn
>
</div>
</div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Note:</div>
<div class="col-8 q-pr-lg">
<q-input
filled
dense
v-model.trim="props.row.note"
type="text"
label="Note"
></q-input>
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
color="grey"
@click="updateNoteForAddress(props.row, props.row.note)"
>Update
</q-btn>
</div>
</div>
<div v-if="props.row.error" class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg"></div>
<div class="col-10 q-pr-lg">
<q-badge color="red">{{props.row.error}}</q-badge>
</div>
</div>
<div
v-if="props.row.gapLimitExceeded"
class="row items-center no-wrap q-mb-md"
>
<div class="col-2 q-pr-lg"></div>
<div class="col-10 q-pr-lg">
<q-badge color="yellow" text-color="black"
>Gap limit of 20 addresses exceeded. Other wallets might not
detect funds at this address.</q-badge
>
</div>
</div>
</q-td>
</q-tr>
</template>
</q-table>
</div>

View file

@ -0,0 +1,169 @@
async function addressList(path) {
const template = await loadTemplateAsync(path)
Vue.component('address-list', {
name: 'address-list',
template,
props: ['accounts', 'mempool_endpoint', 'inkey'],
data: function () {
return {
addresses: [],
show: false,
data: [],
history: [],
selectedWallet: null,
note: '',
filterOptions: [
'Show Change Addresses',
'Show Gap Addresses',
'Only With Amount'
],
filterValues: [],
addressesTable: {
columns: [
{
name: 'expand',
align: 'left',
label: ''
},
{
name: 'address',
align: 'left',
label: 'Address',
field: 'address',
sortable: true
},
{
name: 'amount',
align: 'left',
label: 'Amount',
field: 'amount',
sortable: true
},
{
name: 'note',
align: 'left',
label: 'Note',
field: 'note',
sortable: true
},
{
name: 'wallet',
align: 'left',
label: 'Account',
field: 'wallet',
sortable: true
}
],
pagination: {
rowsPerPage: 0,
sortBy: 'amount',
descending: true
},
filter: ''
}
}
},
watch: {
immediate: true,
accounts(newVal, oldVal) {
if ((newVal || []).length !== (oldVal || []).length) {
console.log('### refreshAddresses')
this.refreshAddresses() // todo await
}
}
},
methods: {
satBtc(val, showUnit = true) {
return satOrBtc(val, showUnit, this['sats_denominated'])
},
getWalletName: function (walletId) {
const wallet = (this.accounts || []).find(wl => wl.id === walletId)
return wallet ? wallet.title : 'unknown'
},
getFilteredAddresses: function () {
const selectedWalletId = this.selectedWallet?.id
const filter = this.filterValues || []
const includeChangeAddrs = filter.includes('Show Change Addresses')
const includeGapAddrs = filter.includes('Show Gap Addresses')
const excludeNoAmount = filter.includes('Only With Amount')
const walletsLimit = (this.accounts || []).reduce((r, w) => {
r[`_${w.id}`] = w.address_no
return r
}, {})
console.log('### walletsLimit', walletsLimit)
console.log('### this.addresses', this.addresses)
const fAddresses = this.addresses.filter(
a =>
(includeChangeAddrs || !a.isChange) &&
(includeGapAddrs ||
a.isChange ||
a.addressIndex <= walletsLimit[`_${a.wallet}`]) &&
!(excludeNoAmount && a.amount === 0) &&
(!selectedWalletId || a.wallet === selectedWalletId)
)
console.log('### fAddresses', fAddresses)
return fAddresses
},
getAddressesForWallet: async function (walletId) {
try {
const {data} = await LNbits.api.request(
'GET',
'/watchonly/api/v1/addresses/' + walletId,
this.inkey
)
return data.map(mapAddressesData)
} catch (err) {
this.$q.notify({
type: 'warning',
message: `Failed to fetch addresses for wallet with id ${walletId}.`,
timeout: 10000
})
LNbits.utils.notifyApiError(err)
}
return []
},
refreshAddresses: async function () {
console.log('### refreshAddresses, this.accounts', this.accounts)
if (!this.accounts) return
this.addresses = []
for (const {id, type} of this.accounts) {
const newAddresses = await this.getAddressesForWallet(id)
const uniqueAddresses = newAddresses.filter(
newAddr => !this.addresses.find(a => a.address === newAddr.address)
)
const lastAcctiveAddress =
uniqueAddresses.filter(a => !a.isChange && a.hasActivity).pop() ||
{}
uniqueAddresses.forEach(a => {
a.expanded = false
a.accountType = type
a.gapLimitExceeded =
!a.isChange &&
a.addressIndex >
lastAcctiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT
})
this.addresses.push(...uniqueAddresses)
}
console.log('### refreshAddresses, this.addresse', this.addresses)
this.$emit('update:addresses', this.addresses)
},
scanAddress: async function (addressData) {
this.$emit('scan:address', addressData)
},
showAddressDetails: function (addressData) {
this.$emit('show-address-details', addressData)
}
},
created: async function () {
await this.refreshAddresses()
}
})
}

View file

@ -3,6 +3,7 @@ const watchOnly = async () => {
await walletConfig('static/components/wallet-config/wallet-config.html') await walletConfig('static/components/wallet-config/wallet-config.html')
await walletList('static/components/wallet-list/wallet-list.html') await walletList('static/components/wallet-list/wallet-list.html')
await addressList('static/components/address-list/address-list.html')
Vue.filter('reverse', function (value) { Vue.filter('reverse', function (value) {
// slice to make a copy of array, then reverse the copy // slice to make a copy of array, then reverse the copy
@ -71,7 +72,12 @@ const watchOnly = async () => {
...tables, ...tables,
...tableData, ...tableData,
walletAccounts: [] walletAccounts: [],
addresses: [],
history: [],
showAddress: false,
addressNote: ''
} }
}, },
@ -79,56 +85,13 @@ const watchOnly = async () => {
//################### CONFIG ################### //################### CONFIG ###################
//################### WALLETS ################### //################### WALLETS ###################
getAddressesForWallet: async function (walletId) {
try {
const {data} = await LNbits.api.request(
'GET',
'/watchonly/api/v1/addresses/' + walletId,
this.g.user.wallets[0].inkey
)
return data.map(mapAddressesData)
} catch (err) {
this.$q.notify({
type: 'warning',
message: `Failed to fetch addresses for wallet with id ${walletId}.`,
timeout: 10000
})
LNbits.utils.notifyApiError(err)
}
return []
},
getWalletName: function (walletId) { getWalletName: function (walletId) {
const wallet = this.walletAccounts.find(wl => wl.id === walletId) const wallet = this.walletAccounts.find(wl => wl.id === walletId)
return wallet ? wallet.title : 'unknown' return wallet ? wallet.title : 'unknown'
}, },
//################### ADDRESSES ################### //################### ADDRESSES ###################
refreshAddresses: async function () {
// const wallets = await this.getWatchOnlyWallets() todo: revisit
// const wallets =
this.addresses.data = []
for (const {id, type} of this.walletAccounts) {
const newAddresses = await this.getAddressesForWallet(id)
const uniqueAddresses = newAddresses.filter(
newAddr =>
!this.addresses.data.find(a => a.address === newAddr.address)
)
const lastAcctiveAddress =
uniqueAddresses.filter(a => !a.isChange && a.hasActivity).pop() ||
{}
uniqueAddresses.forEach(a => {
a.expanded = false
a.accountType = type
a.gapLimitExceeded =
!a.isChange &&
a.addressIndex >
lastAcctiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT
})
this.addresses.data.push(...uniqueAddresses)
}
},
updateAmountForAddress: async function (addressData, amount = 0) { updateAmountForAddress: async function (addressData, amount = 0) {
try { try {
const wallet = this.g.user.wallets[0] const wallet = this.g.user.wallets[0]
@ -171,35 +134,12 @@ const watchOnly = async () => {
{note: addressData.note} {note: addressData.note}
) )
const updatedAddress = const updatedAddress =
this.addresses.data.find(a => a.id === addressData.id) || {} this.addresses.find(a => a.id === addressData.id) || {}
updatedAddress.note = note updatedAddress.note = note
} catch (err) { } catch (err) {
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
} }
}, },
getFilteredAddresses: function () {
const selectedWalletId = this.addresses.selectedWallet?.id
const filter = this.addresses.filterValues || []
const includeChangeAddrs = filter.includes('Show Change Addresses')
const includeGapAddrs = filter.includes('Show Gap Addresses')
const excludeNoAmount = filter.includes('Only With Amount')
const walletsLimit = this.walletAccounts.reduce((r, w) => {
r[`_${w.id}`] = w.address_no
return r
}, {})
const addresses = this.addresses.data.filter(
a =>
(includeChangeAddrs || !a.isChange) &&
(includeGapAddrs ||
a.isChange ||
a.addressIndex <= walletsLimit[`_${a.wallet}`]) &&
!(excludeNoAmount && a.amount === 0) &&
(!selectedWalletId || a.wallet === selectedWalletId)
)
return addresses
},
//################### ADDRESS HISTORY ################### //################### ADDRESS HISTORY ###################
addressHistoryFromTxs: function (addressData, txs) { addressHistoryFromTxs: function (addressData, txs) {
@ -219,9 +159,7 @@ const watchOnly = async () => {
return addressHistory return addressHistory
}, },
getFilteredAddressesHistory: function () { getFilteredAddressesHistory: function () {
return this.addresses.history.filter( return this.history.filter(a => (!a.isChange || a.sent) && !a.isSubItem)
a => (!a.isChange || a.sent) && !a.isSubItem
)
}, },
exportHistoryToCSV: function () { exportHistoryToCSV: function () {
const history = this.getFilteredAddressesHistory().map(a => ({ const history = this.getFilteredAddressesHistory().map(a => ({
@ -235,7 +173,7 @@ const watchOnly = async () => {
) )
}, },
markSameTxAddressHistory: function () { markSameTxAddressHistory: function () {
this.addresses.history this.history
.filter(s => s.sent) .filter(s => s.sent)
.forEach((el, i, arr) => { .forEach((el, i, arr) => {
if (el.isSubItem) return if (el.isSubItem) return
@ -321,7 +259,7 @@ const watchOnly = async () => {
}, },
initPaymentData: async function () { initPaymentData: async function () {
if (!this.payment.show) return if (!this.payment.show) return
await this.refreshAddresses() await this.$refs.addressList.refreshAddresses()
this.payment.showAdvanced = false this.payment.showAdvanced = false
this.payment.changeWallet = this.walletAccounts[0] this.payment.changeWallet = this.walletAccounts[0]
@ -347,7 +285,7 @@ const watchOnly = async () => {
}, },
selectChangeAddress: function (wallet = {}) { selectChangeAddress: function (wallet = {}) {
this.payment.changeAddress = this.payment.changeAddress =
this.addresses.data.find( this.addresses.find(
a => a.wallet === wallet.id && a.isChange && !a.hasActivity a => a.wallet === wallet.id && a.isChange && !a.hasActivity
) || {} ) || {}
}, },
@ -840,18 +778,18 @@ const watchOnly = async () => {
}, },
//################### UTXOs ################### //################### UTXOs ###################
scanAllAddresses: async function () { scanAllAddresses: async function () {
await this.refreshAddresses() await this.$refs.addressList.refreshAddresses()
this.addresses.history = [] this.history = []
let addresses = this.addresses.data let addresses = this.addresses
this.utxos.data = [] this.utxos.data = []
this.utxos.total = 0 this.utxos.total = 0
// Loop while new funds are found on the gap adresses. // Loop while new funds are found on the gap adresses.
// Use 1000 limit as a safety check (scan 20 000 addresses max) // Use 1000 limit as a safety check (scan 20 000 addresses max)
for (let i = 0; i < 1000 && addresses.length; i++) { for (let i = 0; i < 1000 && addresses.length; i++) {
await this.updateUtxosForAddresses(addresses) await this.updateUtxosForAddresses(addresses)
const oldAddresses = this.addresses.data.slice() const oldAddresses = this.addresses.slice()
await this.refreshAddresses() await this.$refs.addressList.refreshAddresses()
const newAddresses = this.addresses.data.slice() const newAddresses = this.addresses.slice()
// check if gap addresses have been extended // check if gap addresses have been extended
addresses = newAddresses.filter( addresses = newAddresses.filter(
newAddr => !oldAddresses.find(oldAddr => oldAddr.id === newAddr.id) newAddr => !oldAddresses.find(oldAddr => oldAddr.id === newAddr.id)
@ -868,11 +806,12 @@ const watchOnly = async () => {
scanAddressWithAmount: async function () { scanAddressWithAmount: async function () {
this.utxos.data = [] this.utxos.data = []
this.utxos.total = 0 this.utxos.total = 0
this.addresses.history = [] this.history = []
const addresses = this.addresses.data.filter(a => a.hasActivity) const addresses = this.addresses.filter(a => a.hasActivity)
await this.updateUtxosForAddresses(addresses) await this.updateUtxosForAddresses(addresses)
}, },
scanAddress: async function (addressData) { scanAddress: async function (addressData) {
console.log('### scanAddress', addressData)
this.updateUtxosForAddresses([addressData]) this.updateUtxosForAddresses([addressData])
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
@ -887,15 +826,13 @@ const watchOnly = async () => {
for (addrData of addresses) { for (addrData of addresses) {
const addressHistory = await this.getAddressTxsDelayed(addrData) const addressHistory = await this.getAddressTxsDelayed(addrData)
// remove old entries // remove old entries
this.addresses.history = this.addresses.history.filter( this.history = this.history.filter(
h => h.address !== addrData.address h => h.address !== addrData.address
) )
// add new entrie // add new entrie
this.addresses.history.push(...addressHistory) this.history.push(...addressHistory)
this.addresses.history.sort((a, b) => this.history.sort((a, b) => (!a.height ? -1 : b.height - a.height))
!a.height ? -1 : b.height - a.height
)
this.markSameTxAddressHistory() this.markSameTxAddressHistory()
if (addressHistory.length) { if (addressHistory.length) {
@ -1038,9 +975,10 @@ const watchOnly = async () => {
//################### OTHER ################### //################### OTHER ###################
openQrCodeDialog: function (addressData) { openQrCodeDialog: function (addressData) {
console.log('### addressData', addressData)
this.currentAddress = addressData this.currentAddress = addressData
this.addresses.note = addressData.note || '' this.addressNote = addressData.note || ''
this.addresses.show = true this.showAddress = true
}, },
searchInTab: function (tab, value) { searchInTab: function (tab, value) {
this.tab = tab this.tab = tab
@ -1052,7 +990,7 @@ const watchOnly = async () => {
}, },
updateAccounts: async function (accounts) { updateAccounts: async function (accounts) {
this.walletAccounts = accounts this.walletAccounts = accounts
await this.refreshAddresses() // await this.refreshAddressesxx() // todo: automatic now?
await this.scanAddressWithAmount() await this.scanAddressWithAmount()
if (this.payment.changeWallet) { if (this.payment.changeWallet) {
@ -1066,13 +1004,17 @@ const watchOnly = async () => {
} }
} }
}, },
handleNewReceiveAddress: function (addressData) { showAddressDetails: function (addressData) {
console.log('### showAddressDetails addressData', addressData)
this.openQrCodeDialog(addressData) this.openQrCodeDialog(addressData)
},
handleAddressesUpdated: function (addresses) {
this.addresses = addresses
} }
}, },
created: async function () { created: async function () {
if (this.g.user.wallets.length) { if (this.g.user.wallets.length) {
await this.refreshAddresses() // await this.refreshAddressesxxx() todo: done when <address-list is created
await this.scanAddressWithAmount() await this.scanAddressWithAmount()
} }
} }

View file

@ -87,49 +87,7 @@ const tables = {
} }
] ]
}, },
addressesTable: {
columns: [
{
name: 'expand',
align: 'left',
label: ''
},
{
name: 'address',
align: 'left',
label: 'Address',
field: 'address',
sortable: true
},
{
name: 'amount',
align: 'left',
label: 'Amount',
field: 'amount',
sortable: true
},
{
name: 'note',
align: 'left',
label: 'Note',
field: 'note',
sortable: true
},
{
name: 'wallet',
align: 'left',
label: 'Account',
field: 'wallet',
sortable: true
}
],
pagination: {
rowsPerPage: 0,
sortBy: 'amount',
descending: true
},
filter: ''
},
historyTable: { historyTable: {
columns: [ columns: [
{ {
@ -194,20 +152,6 @@ const tables = {
} }
const tableData = { const tableData = {
// walletAccounts: [], // todo: remove?
addresses: {
show: false,
data: [],
history: [],
selectedWallet: null,
note: '',
filterOptions: [
'Show Change Addresses',
'Show Gap Addresses',
'Only With Amount'
],
filterValues: []
},
utxos: { utxos: {
data: [], data: [],
total: 0 total: 0

View file

@ -12,14 +12,13 @@
:adminkey="g.user.wallets[0].adminkey" :adminkey="g.user.wallets[0].adminkey"
:inkey="g.user.wallets[0].inkey" :inkey="g.user.wallets[0].inkey"
:sats-denominated="config.data.sats_denominated" :sats-denominated="config.data.sats_denominated"
:addresses="addresses.data" :addresses="addresses"
@accounts-update="updateAccounts" @accounts-update="updateAccounts"
@new-receive-address="handleNewReceiveAddress" @new-receive-address="showAddressDetails"
> >
</wallet-list> </wallet-list>
{% raw %} {% raw %}
<!-- :walletAccounts.sync="walletAccounts" -->
<!-- :walletAccounts="walletAccounts" -->
<q-card> <q-card>
<div class="row q-pt-sm q-pb-sm items-center no-wrap q-mb-md"> <div class="row q-pt-sm q-pb-sm items-center no-wrap q-mb-md">
<div class="col-3 q-pl-md"> <div class="col-3 q-pl-md">
@ -46,7 +45,7 @@
class="btn-full" class="btn-full"
@click="goToPaymentView" @click="goToPaymentView"
:disabled="scan.scanning == true" :disabled="scan.scanning == true"
>Make Payment</q-btn >New Payment</q-btn
> >
</div> </div>
</div> </div>
@ -70,210 +69,16 @@
</q-tabs> </q-tabs>
<q-tab-panels v-model="tab"> <q-tab-panels v-model="tab">
<q-tab-panel name="addresses"> <q-tab-panel name="addresses">
<div class="row items-center no-wrap q-mb-md"> <address-list
<div class="col q-pr-lg"> ref="addressList"
<q-select :accounts="walletAccounts"
filled :mempool_endpoint="config.data.mempool_endpoint"
clearable @update:addresses="handleAddressesUpdated"
dense @scan:address="scanAddress"
emit-value @show-address-details="showAddressDetails"
v-model="addresses.selectedWallet" :inkey="g.user.wallets[0].inkey"
:options="walletAccounts"
label="Wallet Account"
></q-select>
</div>
<div class="col q-pr-lg">
<q-select
filled
clearable
dense
emit-value
multiple
:options="addresses.filterOptions"
v-model="addresses.filterValues"
:options="walletAccounts"
label="Filter"
></q-select>
</div>
<div class="col-auto">
<q-input
borderless
dense
debounce="300"
v-model="addressesTable.filter"
placeholder="Search"
>
<template v-slot:append>
<q-icon name="search"></q-icon>
</template>
</q-input>
</div>
</div>
<q-table
style="height: 400px"
flat
dense
:data="getFilteredAddresses()"
row-key="id"
virtual-scroll
:columns="addressesTable.columns"
:pagination.sync="addressesTable.pagination"
:filter="addressesTable.filter"
> >
<template v-slot:body="props"> </address-list>
<q-tr :props="props">
<q-td auto-width>
<q-btn
size="sm"
color="accent"
round
dense
@click="props.row.expanded= !props.row.expanded"
:icon="props.row.expanded? 'remove' : 'add'"
/>
</q-td>
<q-td key="address" :props="props">
<div>
<a
style="color: unset"
:href="config.data.mempool_endpoint + '/address/' + props.row.address"
target="_blank"
>
{{props.row.address}}</a
>
<q-badge
v-if="props.row.branch_index === 1"
color="orange"
class="q-mr-md"
outline
>
change
</q-badge>
<q-btn
v-if="props.row.gapLimitExceeded"
color="yellow"
icon="warning"
title="Gap Limit Exceeded"
@click="props.row.expanded= !props.row.expanded"
outline
class="q-ml-md"
size="xs"
>
</q-btn>
</div>
</q-td>
<q-td
key="amount"
:props="props"
:class="props.row.amount > 0 ? 'text-green-13 text-weight-bold' : ''"
>
<div>{{satBtc(props.row.amount)}}</div>
</q-td>
<q-td key="note" :props="props" :class="">
<div>{{props.row.note}}</div>
</q-td>
<q-td key="wallet" :props="props" :class="">
<div>{{getWalletName(props.row.wallet)}}</div>
</q-td>
</q-tr>
<q-tr v-show="props.row.expanded" :props="props">
<q-td colspan="100%">
<div class="row items-center q-mt-md q-mb-lg">
<div class="col-2 q-pr-lg"></div>
<div class="col-4 q-pr-lg">
<q-btn
unelevated
dense
size="md"
icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row)"
>QR Code</q-btn
>
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
dense
size="md"
icon="refresh"
color="grey"
@click="scanAddress(props.row)"
>Rescan</q-btn
>
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
dense
size="md"
icon="history"
color="grey"
@click="searchInTab('history', props.row.address)"
>History</q-btn
>
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
dense
size="md"
color="grey"
@click="searchInTab('utxos', props.row.address)"
>View Coins</q-btn
>
</div>
</div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Note:</div>
<div class="col-8 q-pr-lg">
<q-input
filled
dense
v-model.trim="props.row.note"
type="text"
label="Note"
></q-input>
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
color="grey"
@click="updateNoteForAddress(props.row, props.row.note)"
>Update</q-btn
>
</div>
</div>
<div
v-if="props.row.error"
class="row items-center no-wrap q-mb-md"
>
<div class="col-2 q-pr-lg"></div>
<div class="col-10 q-pr-lg">
<q-badge color="red">{{props.row.error}}</q-badge>
</div>
</div>
<div
v-if="props.row.gapLimitExceeded"
class="row items-center no-wrap q-mb-md"
>
<div class="col-2 q-pr-lg"></div>
<div class="col-10 q-pr-lg">
<q-badge color="yellow" text-color="black"
>Gap limit of 20 addresses exceeded. Other wallets
might not detect funds at this address.</q-badge
>
</div>
</div>
</q-td>
</q-tr>
</template>
</q-table>
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="history"> <q-tab-panel name="history">
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
@ -427,7 +232,7 @@
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col"> <div class="col">
<q-toggle <q-toggle
label="Make Payment" label="New Payment"
color="secodary" color="secodary"
v-model="payment.show" v-model="payment.show"
@input="initPaymentData()" @input="initPaymentData()"
@ -1264,11 +1069,9 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
</div> </div>
{% raw %}
<q-dialog v-model="addresses.show" position="top"> <q-dialog v-model="showAddress" position="top">
<q-card v-if="addresses.data" class="q-pa-lg lnbits__dialog-card"> <q-card class="q-pa-lg lnbits__dialog-card">
{% raw %}
<h5 class="text-subtitle1 q-my-none">Address Details</h5> <h5 class="text-subtitle1 q-my-none">Address Details</h5>
<q-separator></q-separator><br /> <q-separator></q-separator><br />
@ -1296,7 +1099,7 @@
<q-input <q-input
filled filled
dense dense
v-model.trim="addresses.note" v-model.trim="addressNote"
type="text" type="text"
label="Note" label="Note"
></q-input> ></q-input>
@ -1313,7 +1116,7 @@
outline outline
v-close-popup v-close-popup
color="grey" color="grey"
@click="updateNoteForAddress(currentAddress, addresses.note)" @click="updateNoteForAddress(currentAddress, addressNote)"
class="q-ml-sm" class="q-ml-sm"
>Save Note</q-btn >Save Note</q-btn
> >
@ -1473,5 +1276,6 @@
<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>
<script src="{{ url_for('watchonly_static', path='components/address-list/address-list.js') }}"></script>
<script src="{{ url_for('watchonly_static', path='js/index.js') }}"></script> <script src="{{ url_for('watchonly_static', path='js/index.js') }}"></script>
{% endblock %} {% endblock %}