feat: UI/UX improvements

This commit is contained in:
Vlad Stan 2022-07-25 17:37:06 +03:00
parent e6b46301df
commit 799fb99661
8 changed files with 601 additions and 542 deletions

View file

@ -4,7 +4,7 @@ async function addressList(path) {
name: 'address-list', name: 'address-list',
template, template,
props: ['accounts', 'mempool_endpoint', 'inkey'], props: ['accounts', 'mempool_endpoint', 'inkey', 'sats_denominated'],
watch: { watch: {
immediate: true, immediate: true,
accounts(newVal, oldVal) { accounts(newVal, oldVal) {

View file

@ -0,0 +1,21 @@
async function payment(path) {
const template = await loadTemplateAsync(path)
Vue.component('payment', {
name: 'payment',
template,
props: ['mempool_endpoint', 'sats_denominated'],
data: function () {
return {}
},
methods: {
satBtc(val, showUnit = true) {
return satOrBtc(val, showUnit, this['sats_denominated'])
}
},
created: async function () {}
})
}

View file

@ -1,18 +1,18 @@
<q-card> <q-card>
<q-card-section> <q-card-section>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<!-- <div v-if="payment.show" class="col-3 q-pr-lg"> <div v-if="selectable" class="col-3 q-pr-lg">
<q-select <q-select
filled filled
dense dense
emit-value emit-value
v-model="payment.utxoSelectionMode" v-model="utxoSelectionMode"
:options="payment.utxoSelectionModes" :options="utxoSelectionModes"
label="Selection Mode" label="Selection Mode"
@input="applyUtxoSelectionMode" @input="applyUtxoSelectionMode"
></q-select> ></q-select>
</div> </div>
<div v-if="payment.show" class="col-1 q-pr-lg"> <div v-if="selectable" class="col-1 q-pr-lg">
<q-btn <q-btn
outline outline
icon="refresh" icon="refresh"
@ -20,9 +20,9 @@
@click="applyUtxoSelectionMode" @click="applyUtxoSelectionMode"
class="q-ml-sm" class="q-ml-sm"
></q-btn> ></q-btn>
</div> --> </div>
<!-- <div v-if="payment.show" class="col-5 q-pr-lg"></div> --> <div v-if="selectable" class="col-5 q-pr-lg"></div>
<div class="col-9 q-pr-lg"></div> <div v-if="!selectable" class="col-9 q-pr-lg"></div>
<div class="col-3 float-right"> <div class="col-3 float-right">
<q-input <q-input
borderless borderless
@ -43,7 +43,7 @@
dense dense
:data="utxos" :data="utxos"
row-key="id" row-key="id"
:columns="utxosTable.columns" :columns="columns"
:pagination.sync="utxosTable.pagination" :pagination.sync="utxosTable.pagination"
:filter="utxosTable.filter" :filter="utxosTable.filter"
> >
@ -60,7 +60,7 @@
/> />
</q-td> </q-td>
<q-td key="selected" :props="props"> <q-td v-if="selectable" key="selected" :props="props">
<div> <div>
<q-checkbox v-model="props.row.selected"></q-checkbox> <q-checkbox v-model="props.row.selected"></q-checkbox>
</div> </div>
@ -133,8 +133,7 @@
<template v-slot:bottom-row> <template v-slot:bottom-row>
<q-tr> <q-tr>
<q-td colspan="100%"> <q-td colspan="100%">
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md q-pt-lg">
<div class="col-12"> <div class="col-12">
<div> <div>
<span class="text-weight-bold">Selected Amount: </span> <span class="text-weight-bold">Selected Amount: </span>

View file

@ -4,7 +4,14 @@ async function utxoList(path) {
name: 'utxo-list', name: 'utxo-list',
template, template,
props: ['utxos', 'accounts', 'sats_denominated', 'mempool_endpoint'], props: [
'utxos',
'accounts',
'selectable',
'payed-amount',
'sats_denominated',
'mempool_endpoint'
],
data: function () { data: function () {
return { return {
@ -18,7 +25,8 @@ async function utxoList(path) {
{ {
name: 'selected', name: 'selected',
align: 'left', align: 'left',
label: '' label: '',
selectable: true
}, },
{ {
name: 'status', name: 'status',
@ -59,7 +67,21 @@ async function utxoList(path) {
rowsPerPage: 10 rowsPerPage: 10
}, },
filter: '' filter: ''
},
utxoSelectionModes: [
'Manual',
'Random',
'Select All',
'Smaller Inputs First',
'Larger Inputs First'
],
utxoSelectionMode: 'Random'
} }
},
computed: {
columns: function() {
return this.utxosTable.columns.filter(c => c.selectable ? this.selectable : true)
} }
}, },
@ -76,6 +98,37 @@ async function utxoList(path) {
.filter(u => u.selected) .filter(u => u.selected)
.reduce((t, a) => t + (a.amount || 0), 0) .reduce((t, a) => t + (a.amount || 0), 0)
return total return total
},
applyUtxoSelectionMode: function () {
const payedAmount = this['payed-amount']
const mode = this.payment.utxoSelectionMode
this.utxos.data.forEach(u => (u.selected = false))
const isManual = mode === 'Manual'
if (isManual || !payedAmount) return
const isSelectAll = mode === 'Select All'
if (isSelectAll || payedAmount >= this.utxos.total) {
this.utxos.data.forEach(u => (u.selected = true))
return
}
const isSmallerFirst = mode === 'Smaller Inputs First'
const isLargerFirst = mode === 'Larger Inputs First'
let selectedUtxos = this.utxos.data.slice()
if (isSmallerFirst || isLargerFirst) {
const sortFn = isSmallerFirst
? (a, b) => a.amount - b.amount
: (a, b) => b.amount - a.amount
selectedUtxos.sort(sortFn)
} else {
// default to random order
selectedUtxos = _.shuffle(selectedUtxos)
}
selectedUtxos.reduce((total, utxo) => {
utxo.selected = total < payedAmount
total += utxo.amount
return total
}, 0)
} }
}, },

View file

@ -6,6 +6,7 @@ const watchOnly = async () => {
await addressList('static/components/address-list/address-list.html') await addressList('static/components/address-list/address-list.html')
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 payment('static/components/payment/payment.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
@ -29,6 +30,7 @@ const watchOnly = async () => {
currentAddress: null, currentAddress: null,
tab: 'addresses', tab: 'addresses',
paymentTab: 'destination',
config: { config: {
data: { data: {
@ -79,7 +81,8 @@ const watchOnly = async () => {
history: [], history: [],
showAddress: false, showAddress: false,
addressNote: '' addressNote: '',
showPayment: false
} }
}, },
@ -279,8 +282,9 @@ const watchOnly = async () => {
) || {} ) || {}
}, },
goToPaymentView: async function () { goToPaymentView: async function () {
this.payment.show = true // this.payment.show = true
this.tab = 'utxos' this.showPayment = true
// this.tab = 'utxos'
await this.initPaymentData() await this.initPaymentData()
}, },
sendMaxToAddress: function (paymentAddress = {}) { sendMaxToAddress: function (paymentAddress = {}) {
@ -880,37 +884,6 @@ const watchOnly = async () => {
.reduce((t, a) => t + (a.amount || 0), 0) .reduce((t, a) => t + (a.amount || 0), 0)
return total return total
}, },
applyUtxoSelectionMode: function () {
const payedAmount = this.getTotalPaymentAmount()
const mode = this.payment.utxoSelectionMode
this.utxos.data.forEach(u => (u.selected = false))
const isManual = mode === 'Manual'
if (isManual || !payedAmount) return
const isSelectAll = mode === 'Select All'
if (isSelectAll || payedAmount >= this.utxos.total) {
this.utxos.data.forEach(u => (u.selected = true))
return
}
const isSmallerFirst = mode === 'Smaller Inputs First'
const isLargerFirst = mode === 'Larger Inputs First'
let selectedUtxos = this.utxos.data.slice()
if (isSmallerFirst || isLargerFirst) {
const sortFn = isSmallerFirst
? (a, b) => a.amount - b.amount
: (a, b) => b.amount - a.amount
selectedUtxos.sort(sortFn)
} else {
// default to random order
selectedUtxos = _.shuffle(selectedUtxos)
}
selectedUtxos.reduce((total, utxo) => {
utxo.selected = total < payedAmount
total += utxo.amount
return total
}, 0)
},
//################### MEMPOOL API ################### //################### MEMPOOL API ###################
getAddressTxsDelayed: async function (addrData) { getAddressTxsDelayed: async function (addrData) {

View file

@ -64,14 +64,7 @@ const tableData = {
signedTx: null, signedTx: null,
signedTxHex: null, signedTxHex: null,
sentTxId: null, sentTxId: null,
utxoSelectionModes: [
'Manual',
'Random',
'Select All',
'Smaller Inputs First',
'Larger Inputs First'
],
utxoSelectionMode: 'Manual',
signModes: [ signModes: [
{ {
label: 'Serial Port Device', label: 'Serial Port Device',

View file

@ -40,13 +40,21 @@
</div> </div>
<div class="col-3 q-pr-md"> <div class="col-3 q-pr-md">
<q-btn <q-btn
v-if="!showPayment"
unelevated unelevated
color="secondary" color="secondary"
class="btn-full" class="btn-full"
@click="goToPaymentView" @click="goToPaymentView"
:disabled="scan.scanning == true"
>New Payment</q-btn >New Payment</q-btn
> >
<q-btn
v-if="showPayment"
outline
color="gray"
class="btn-full"
@click="showPayment = false"
>Discard Payment</q-btn
>
</div> </div>
</div> </div>
@ -61,7 +69,7 @@
</q-card> </q-card>
<q-card> <q-card>
<q-card-section> <q-card-section v-show="!showPayment">
<q-tabs v-model="tab" no-caps class="bg-dark text-white shadow-2"> <q-tabs v-model="tab" no-caps class="bg-dark text-white shadow-2">
<q-tab name="addresses" label="Addresses"></q-tab> <q-tab name="addresses" label="Addresses"></q-tab>
<q-tab name="history" label="History"></q-tab> <q-tab name="history" label="History"></q-tab>
@ -89,132 +97,12 @@
></history> ></history>
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="utxos"> <q-tab-panel name="utxos">
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<q-toggle
label="New Payment"
color="secodary"
v-model="payment.show"
@input="initPaymentData()"
></q-toggle>
</div>
<div class="col-auto"></div>
</div>
<q-form @submit="createPsbt" class="q-gutter-md">
<q-card v-if="payment.show">
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<q-table
flat
dense
hide-header
:data="payment.data"
:columns="paymentTable.columns"
:pagination.sync="paymentTable.pagination"
>
<template v-slot:body="props">
<q-tr :props="props">
<div class="row no-wrap">
<div class="col-1">
<q-btn
flat
dense
size="l"
@click="deletePaymentAddress(props.row)"
icon="cancel"
color="pink"
class="q-mt-sm"
></q-btn>
</div>
<div class="col-7 q-pr-lg">
<q-input
filled
dense
v-model.trim="props.row.address"
type="text"
label="Address"
:rules="[val => !!val || 'Field is required']"
></q-input>
</div>
<div class="col-3 q-pr-lg">
<q-input
filled
dense
v-model.number="props.row.amount"
type="number"
step="1"
label="Amount (sats)"
:rules="[val => !!val || 'Field is required', val => +val > DUST_LIMIT || 'Amount to small (below dust limit)'] "
></q-input>
</div>
<div class="col-1">
<q-btn
outline
color="grey"
@click="sendMaxToAddress(props.row)"
>Max</q-btn
>
</div>
</div>
</q-tr>
</template>
<template v-slot:bottom-row>
<q-tr>
<q-td colspan="100%">
<div class="row items-center no-wrap">
<div class="col-3 q-pr-lg">
<q-btn
outline
color="grey"
@click="addPaymentAddress"
>Add Send Address</q-btn
>
</div>
<div class="col-4 q-pr-lg"></div>
<div class="col-5">
<div class="float-right">
<span class="text-weight-bold"
>Payed Amount:
</span>
<q-badge
class="text-subtitle2 q-ml-lg"
color="blue"
>
{{satBtc(getTotalPaymentAmount())}}
</q-badge>
</div>
</div>
</div>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</div>
<div
v-if="payment.changeAmount < 0"
class="row items-center no-wrap q-mb-md"
>
<div class="col-12">
<q-badge
class="text-subtitle2 float-left"
color="yellow"
text-color="black"
>
The payed amount is higher than the selected amount!
</q-badge>
</div>
</div>
</q-card-section>
</q-card>
<utxo-list <utxo-list
:utxos="utxos.data" :utxos="utxos.data"
:mempool_endpoint="config.data.mempool_endpoint" :mempool_endpoint="config.data.mempool_endpoint"
:sats-denominated="config.data.sats_denominated" :sats-denominated="config.data.sats_denominated"
></utxo-list> ></utxo-list>
<div v-if="payment.show" class="row items-center no-wrap q-mb-md"> <!-- <div v-if="payment.show" class="row items-center no-wrap q-mb-md">
<div class="col"> <div class="col">
<q-toggle <q-toggle
label="Show Advanced" label="Show Advanced"
@ -222,150 +110,10 @@
v-model="payment.showAdvanced" v-model="payment.showAdvanced"
></q-toggle> ></q-toggle>
</div> </div>
</div> </div> -->
<q-card v-show="payment.show && payment.showAdvanced"
><q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Change Account:</div>
<div class="col-3 q-pr-lg">
<q-select
filled
dense
emit-value
v-model="payment.changeWallet"
:options="walletAccounts"
@input="selectChangeAddress"
:rules="[val => !!val || 'Field is required']"
label="Wallet Account"
></q-select>
</div>
<div class="col-7">
<q-input
filled
dense
readonly
v-model.trim="payment.changeAddress.address"
:rules="[val => !!val || 'Field is required']"
type="text"
label="Change Address"
></q-input>
</div>
</div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Fee Rate:</div>
<div class="col-3 q-pr-lg">
<q-input
filled
dense
v-model.number="payment.feeRate"
:rules="[val => !!val || 'Field is required']"
type="number"
label="sats/vbyte"
></q-input>
</div>
<div class="col-7">
<q-slider
v-model="payment.feeRate"
color="orange"
markers
snap
label
label-always
:label-value="getFeeRateLabel(payment.feeRate)"
:min="1"
:max="payment.recommededFees.fastestFee"
/>
</div>
</div>
<div
v-if="payment.feeRate < payment.recommededFees.hourFee || payment.feeRate > payment.recommededFees.fastestFee"
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
v-if="payment.feeRate < payment.recommededFees.hourFee"
color="pink"
size="lg"
>
Warning! The fee is too low. The transaction might take
a long time to confirm.
</q-badge>
<q-badge
v-if="payment.feeRate > payment.recommededFees.fastestFee"
color="pink"
>
Warning! The fee is too high. You might be overpaying
for this transaction.
</q-badge>
</div>
</div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Fee:</div>
<div class="col-3 q-pr-lg">{{computeFee()}} sats</div>
<div class="col-7 q-pr-lg">
<q-btn
outline
dense
size="md"
icon="refresh"
color="grey"
@click="refreshRecommendedFees()"
>Refresh Fee Rates</q-btn
>
</div>
</div>
</q-card-section></q-card
>
<q-card v-if="payment.show" <q-card v-if="payment.show"
><q-card-section> ><q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<q-table
:columns="summaryTable.columns"
:data="summary.data"
hide-bottom
>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="totalInputs" :props="props">
<q-badge class="text-subtitle2" color="green">
{{satBtc(getTotalSelectedUtxoAmount())}}
</q-badge>
</q-td>
<q-td key="totalOutputs" :props="props">
<q-badge class="text-subtitle2" color="blue">
{{satBtc(getTotalPaymentAmount())}}
</q-badge>
</q-td>
<q-td key="fees" :props="props">
<q-badge class="text-subtitle2" color="orange">
{{satBtc(computeFee())}}
</q-badge>
</q-td>
<q-td key="change" :props="props">
<q-badge
v-if="payment.changeAmount >= 0"
class="text-subtitle2"
color="green"
>
{{payment.changeAmount ?
satBtc(payment.changeAmount): 'no change'}}
</q-badge>
<q-badge
v-if="payment.changeAmount > 0 && payment.changeAmount < DUST_LIMIT"
color="red"
>
Below dust limit. Will be used as feee.
</q-badge>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</div>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col-3 q-pr-lg"> <div class="col-3 q-pr-lg">
<q-btn <q-btn
@ -491,8 +239,7 @@
<q-item-section> <q-item-section>
<q-item-label>Login</q-item-label> <q-item-label>Login</q-item-label>
<q-item-label caption <q-item-label caption
>Enter password for Hardware >Enter password for Hardware Wallet.</q-item-label
Wallet.</q-item-label
> >
</q-item-section> </q-item-section>
</q-item> </q-item>
@ -519,8 +266,7 @@
<q-item-section> <q-item-section>
<q-item-label>Sign</q-item-label> <q-item-label>Sign</q-item-label>
<q-item-label caption <q-item-label caption
>Sign transaction on Hardware >Sign transaction on Hardware Wallet</q-item-label
Wallet</q-item-label
> >
</q-item-section> </q-item-section>
</q-item> </q-item>
@ -712,15 +458,11 @@
class="row items-center no-wrap q-mb-sm" class="row items-center no-wrap q-mb-sm"
> >
<div class="col-3 q-pr-lg"> <div class="col-3 q-pr-lg">
<q-badge color="orange" <q-badge color="orange">{{satBtc(out.amount)}}</q-badge>
>{{satBtc(out.amount)}}</q-badge
>
</div> </div>
<div class="col-9"> <div class="col-9">
<q-badge outline color="blue" <q-badge outline color="blue">{{out.address}}</q-badge>
>{{out.address}}</q-badge
>
</div> </div>
</div> </div>
</div> </div>
@ -747,10 +489,287 @@
</div> </div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-form>
</q-tab-panel> </q-tab-panel>
</q-tab-panels> </q-tab-panels>
</q-card-section> </q-card-section>
<q-card-section v-show="showPayment">
<q-form @submit="createPsbt" class="q-gutter-md">
<q-tabs
v-model="paymentTab"
no-caps
class="bg-dark text-white shadow-2"
>
<q-tab name="destination" label="Send To"></q-tab>
<q-tab name="fees" label="Fees"></q-tab>
<q-tab name="coinControl" label="Coin Control"></q-tab>
</q-tabs>
<q-tab-panels v-model="paymentTab">
<q-tab-panel name="destination">
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col-12">
<q-table
flat
dense
hide-header
:data="payment.data"
:columns="paymentTable.columns"
:pagination.sync="paymentTable.pagination"
>
<template v-slot:body="props">
<q-tr :props="props">
<div class="row no-wrap">
<div class="col-1">
<q-btn
flat
dense
size="l"
@click="deletePaymentAddress(props.row)"
icon="cancel"
color="pink"
class="q-mt-sm"
></q-btn>
</div>
<div class="col-7 q-pr-lg">
<q-input
filled
dense
v-model.trim="props.row.address"
type="text"
label="Address"
:rules="[val => !!val || 'Field is required']"
></q-input>
</div>
<div class="col-3 q-pr-lg">
<q-input
filled
dense
v-model.number="props.row.amount"
type="number"
step="1"
label="Amount (sats)"
:rules="[val => !!val || 'Field is required', val => +val > DUST_LIMIT || 'Amount to small (below dust limit)'] "
></q-input>
</div>
<div class="col-1">
<q-btn
outline
color="grey"
@click="sendMaxToAddress(props.row)"
>Max</q-btn
>
</div>
</div>
</q-tr>
</template>
<template v-slot:bottom-row>
<q-tr>
<q-td colspan="100%">
<div class="row items-center no-wrap">
<div class="col-3 q-pr-lg">
<q-btn
outline
color="grey"
@click="addPaymentAddress"
>Add Send Address</q-btn
>
</div>
<div class="col-4 q-pr-lg"></div>
<div class="col-5">
<div class="float-right">
<span class="text-weight-bold"
>Payed Amount:
</span>
<q-badge
class="text-subtitle2 q-ml-lg"
color="blue"
>
{{satBtc(getTotalPaymentAmount())}}
</q-badge>
</div>
</div>
</div>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</div>
<!-- <div class="row items-center no-wrap q-mb-md">
<div class="col-12">
<q-table
:columns="summaryTable.columns"
:data="summary.data"
hide-bottom
>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="totalInputs" :props="props">
<q-badge class="text-subtitle2" color="green">
{{satBtc(getTotalSelectedUtxoAmount())}}
</q-badge>
</q-td>
<q-td key="totalOutputs" :props="props">
<q-badge class="text-subtitle2" color="blue">
{{satBtc(getTotalPaymentAmount())}}
</q-badge>
</q-td>
<q-td key="fees" :props="props">
<q-badge class="text-subtitle2" color="orange">
{{satBtc(computeFee())}}
</q-badge>
</q-td>
<q-td key="change" :props="props">
<q-badge
v-if="payment.changeAmount >= 0"
class="text-subtitle2"
color="green"
>
{{payment.changeAmount ?
satBtc(payment.changeAmount): 'no change'}}
</q-badge>
<q-badge
v-if="payment.changeAmount > 0 && payment.changeAmount < DUST_LIMIT"
color="red"
>
Below dust limit. Will be used as feee.
</q-badge>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</div> -->
<!--
<div
v-if="payment.changeAmount < 0"
class="row items-center no-wrap q-mb-md"
>
<div class="col-12">
<q-badge
class="text-subtitle2 float-left"
color="yellow"
text-color="black"
>
The payed amount is higher than the selected amount!
</q-badge>
</div>
</div>
-->
</q-card-section>
</q-card>
</q-tab-panel>
<q-tab-panel name="fees">
<q-card
><q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Fee Rate:</div>
<div class="col-3 q-pr-lg">
<q-input
filled
dense
v-model.number="payment.feeRate"
:rules="[val => !!val || 'Field is required']"
type="number"
label="sats/vbyte"
></q-input>
</div>
<div class="col-7">
<q-slider
v-model="payment.feeRate"
color="orange"
markers
snap
label
label-always
:label-value="getFeeRateLabel(payment.feeRate)"
:min="1"
:max="payment.recommededFees.fastestFee"
/>
</div>
</div>
<div
v-if="payment.feeRate < payment.recommededFees.hourFee || payment.feeRate > payment.recommededFees.fastestFee"
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
v-if="payment.feeRate < payment.recommededFees.hourFee"
color="pink"
size="lg"
>
Warning! The fee is too low. The transaction might take
a long time to confirm.
</q-badge>
<q-badge
v-if="payment.feeRate > payment.recommededFees.fastestFee"
color="pink"
>
Warning! The fee is too high. You might be overpaying
for this transaction.
</q-badge>
</div>
</div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Fee:</div>
<div class="col-3 q-pr-lg">{{computeFee()}} sats</div>
<div class="col-7 q-pr-lg">
<q-btn
outline
dense
size="md"
icon="refresh"
color="grey"
@click="refreshRecommendedFees()"
>Refresh Fee Rates</q-btn
>
</div>
</div>
</q-card-section></q-card
>
</q-tab-panel>
<q-tab-panel name="coinControl">
<utxo-list
:utxos="utxos.data"
:selectable="true"
:payed-amount="getTotalPaymentAmount()"
:mempool_endpoint="config.data.mempool_endpoint"
:sats-denominated="config.data.sats_denominated"
></utxo-list>
<div class="row items-center no-wrap q-mb-md q-pt-lg">
<div class="col-2 q-pr-lg">Change Account:</div>
<div class="col-3 q-pr-lg">
<q-select
filled
dense
emit-value
v-model="payment.changeWallet"
:options="walletAccounts"
@input="selectChangeAddress"
:rules="[val => !!val || 'Field is required']"
label="Wallet Account"
></q-select>
</div>
<div class="col-7">
<q-input
filled
dense
readonly
v-model.trim="payment.changeAddress.address"
:rules="[val => !!val || 'Field is required']"
type="text"
label="Change Address"
></q-input>
</div>
</div>
</q-tab-panel>
</q-tab-panels>
</q-form>
</q-card-section>
</q-card> </q-card>
</div> </div>
@ -979,5 +998,6 @@
<script src="{{ url_for('watchonly_static', path='components/address-list/address-list.js') }}"></script> <script src="{{ url_for('watchonly_static', path='components/address-list/address-list.js') }}"></script>
<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/payment/payment.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 %}