Feat: Adds wallet icon/color select (#2917)
This commit is contained in:
parent
dd9b217fdf
commit
960c58db87
10 changed files with 205 additions and 63 deletions
|
|
@ -161,4 +161,4 @@ async def get_wallet_for_key(
|
|||
async def get_total_balance(conn: Optional[Connection] = None):
|
||||
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
|
||||
row = result.mappings().first()
|
||||
return row.get("balance", 0)
|
||||
return row.get("balance", 0) or 0
|
||||
|
|
|
|||
|
|
@ -692,3 +692,10 @@ async def m030_add_user_api_tokens_column(db: Connection):
|
|||
ALTER TABLE accounts ADD COLUMN access_control_list TEXT
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
async def m031_add_color_and_icon_to_wallets(db: Connection):
|
||||
"""
|
||||
Adds icon and color columns to wallets.
|
||||
"""
|
||||
await db.execute("ALTER TABLE wallets ADD COLUMN extra TEXT")
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ class BaseWallet(BaseModel):
|
|||
balance_msat: int
|
||||
|
||||
|
||||
class WalletExtra(BaseModel):
|
||||
icon: str = "flash_on"
|
||||
color: str = "primary"
|
||||
|
||||
|
||||
class Wallet(BaseModel):
|
||||
id: str
|
||||
user: str
|
||||
|
|
@ -34,6 +39,7 @@ class Wallet(BaseModel):
|
|||
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
currency: Optional[str] = None
|
||||
balance_msat: int = Field(default=0, no_database=True)
|
||||
extra: WalletExtra = WalletExtra()
|
||||
|
||||
@property
|
||||
def balance(self) -> int:
|
||||
|
|
|
|||
|
|
@ -206,11 +206,29 @@
|
|||
{% else %}
|
||||
<div v-if="!mobileSimple" class="col-12 col-md-5 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h6 class="text-subtitle1 q-mt-none q-mb-sm">
|
||||
{{ SITE_TITLE }} Wallet:
|
||||
<strong><em v-text="g.wallet.name"></em></strong>
|
||||
</h6>
|
||||
<q-card-section class="q-pb-xs">
|
||||
<div class="row items-center">
|
||||
<q-avatar
|
||||
size="lg"
|
||||
:icon="g.wallet.extra.icon"
|
||||
:text-color="$q.dark.isActive ? 'black' : 'grey-3'"
|
||||
:color="g.wallet.extra.color"
|
||||
>
|
||||
</q-avatar>
|
||||
<q-btn
|
||||
@click="icon.show = true"
|
||||
round
|
||||
color="grey-5"
|
||||
text-color="black"
|
||||
size="xs"
|
||||
icon="add"
|
||||
style="position: relative; left: -20px; bottom: -10px"
|
||||
></q-btn>
|
||||
<div class="text-subtitle1 q-mt-none q-mb-none">
|
||||
{{ SITE_TITLE }} Wallet:
|
||||
<strong><em v-text="g.wallet.name"></em></strong>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-pa-none">
|
||||
<q-separator></q-separator>
|
||||
|
|
@ -388,6 +406,50 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<q-dialog v-model="icon.show" position="top">
|
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||
<q-form @submit="setIcon" class="q-gutter-md">
|
||||
<div class="q-gutter-sm q-pa-sm flex flex-wrap justify-center">
|
||||
<!-- Loop through all icons -->
|
||||
<q-btn
|
||||
v-for="(thisIcon, index) in icon.options"
|
||||
:key="index"
|
||||
@click="setSelectedIcon(thisIcon)"
|
||||
round
|
||||
text-color="black"
|
||||
:color="icon.data.icon === thisIcon ? icon.data.color || 'primary' : 'grey-5'"
|
||||
size="md"
|
||||
:icon="thisIcon"
|
||||
class="q-mb-sm"
|
||||
></q-btn>
|
||||
</div>
|
||||
<div class="q-pa-sm flex justify-between items-center">
|
||||
<div class="flex q-pl-lg">
|
||||
<!-- Color options -->
|
||||
<q-btn
|
||||
v-for="(color, index) in icon.colorOptions"
|
||||
:key="'color-' + index"
|
||||
@click="setSelectedColor(color)"
|
||||
round
|
||||
:color="color"
|
||||
size="xs"
|
||||
style="width: 24px; height: 24px; min-width: 24px; padding: 0"
|
||||
class="q-mr-xs"
|
||||
></q-btn>
|
||||
</div>
|
||||
<q-btn
|
||||
unelevated
|
||||
color="primary"
|
||||
:disable="!icon.data.icon"
|
||||
type="submit"
|
||||
>
|
||||
Save Icon
|
||||
</q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog
|
||||
v-model="receive.show"
|
||||
position="top"
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ async def api_update_wallet_name(
|
|||
@wallet_router.patch("")
|
||||
async def api_update_wallet(
|
||||
name: Optional[str] = Body(None),
|
||||
icon: Optional[str] = Body(None),
|
||||
color: Optional[str] = Body(None),
|
||||
currency: Optional[str] = Body(None),
|
||||
key_info: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> Wallet:
|
||||
|
|
@ -66,6 +68,8 @@ async def api_update_wallet(
|
|||
if not wallet:
|
||||
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Wallet not found")
|
||||
wallet.name = name or wallet.name
|
||||
wallet.extra.icon = icon or wallet.extra.icon
|
||||
wallet.extra.color = color or wallet.extra.color
|
||||
wallet.currency = currency if currency is not None else wallet.currency
|
||||
await update_wallet(wallet)
|
||||
return wallet
|
||||
|
|
|
|||
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -216,7 +216,8 @@ window.LNbits = {
|
|||
name: data.name,
|
||||
adminkey: data.adminkey,
|
||||
inkey: data.inkey,
|
||||
currency: data.currency
|
||||
currency: data.currency,
|
||||
extra: data.extra
|
||||
}
|
||||
newWallet.msat = data.balance_msat
|
||||
newWallet.sat = Math.floor(data.balance_msat / 1000)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,62 @@ window.WalletPageLogic = {
|
|||
show: false,
|
||||
location: window.location
|
||||
},
|
||||
icon: {
|
||||
show: false,
|
||||
data: {},
|
||||
colorOptions: [
|
||||
'primary',
|
||||
'purple',
|
||||
'orange',
|
||||
'green',
|
||||
'brown',
|
||||
'blue',
|
||||
'red',
|
||||
'pink'
|
||||
],
|
||||
options: [
|
||||
'home',
|
||||
'star',
|
||||
'bolt',
|
||||
'paid',
|
||||
'savings',
|
||||
'store',
|
||||
'videocam',
|
||||
'music_note',
|
||||
'flight',
|
||||
'train',
|
||||
'directions_car',
|
||||
'school',
|
||||
'construction',
|
||||
'science',
|
||||
'sports_esports',
|
||||
'sports_tennis',
|
||||
'theaters',
|
||||
'water',
|
||||
'headset_mic',
|
||||
'videogame_asset',
|
||||
'person',
|
||||
'group',
|
||||
'pets',
|
||||
'sunny',
|
||||
'elderly',
|
||||
'verified',
|
||||
'snooze',
|
||||
'mail',
|
||||
'forum',
|
||||
'shopping_cart',
|
||||
'shopping_bag',
|
||||
'attach_money',
|
||||
'print_connect',
|
||||
'dark_mode',
|
||||
'light_mode',
|
||||
'android',
|
||||
'network_wifi',
|
||||
'shield',
|
||||
'fitness_center',
|
||||
'lunch_dining'
|
||||
]
|
||||
},
|
||||
update: {
|
||||
name: null,
|
||||
currency: null
|
||||
|
|
@ -149,6 +205,16 @@ window.WalletPageLogic = {
|
|||
handleBalanceUpdate(value) {
|
||||
this.g.wallet.sat = this.g.wallet.sat + value
|
||||
},
|
||||
setSelectedIcon(selectedIcon) {
|
||||
this.icon.data.icon = selectedIcon
|
||||
},
|
||||
setSelectedColor(selectedColor) {
|
||||
this.icon.data.color = selectedColor
|
||||
},
|
||||
setIcon() {
|
||||
this.updateWallet(this.icon.data)
|
||||
this.icon.show = false
|
||||
},
|
||||
createInvoice() {
|
||||
this.receive.status = 'loading'
|
||||
if (LNBITS_DENOMINATION != 'sats') {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
<link async="async" rel="manifest" href="{{ web_manifest }}" />
|
||||
{% endif %} {% block head_scripts %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body data-theme="bitcoin">
|
||||
<div id="vue">
|
||||
<q-layout view="hHh lpR lfr" v-cloak>
|
||||
|
|
@ -228,19 +229,14 @@
|
|||
<div class="row items-center">
|
||||
<q-avatar
|
||||
size="lg"
|
||||
:color="
|
||||
g.wallet && g.wallet.id === wallet.id
|
||||
? $q.dark.isActive
|
||||
? 'primary'
|
||||
: 'primary'
|
||||
: 'grey-5'
|
||||
"
|
||||
:text-color="$q.dark.isActive ? 'black' : 'grey-3'"
|
||||
:class="g.wallet && g.wallet.id === wallet.id
|
||||
? ''
|
||||
: 'disabled'
|
||||
"
|
||||
:color="g.wallet && g.wallet.id === wallet.id ? wallet.extra.color : wallet.extra.color"
|
||||
:icon="g.wallet && g.wallet.id === wallet.id ? wallet.extra.icon : wallet.extra.icon"
|
||||
>
|
||||
<q-icon
|
||||
name="flash_on"
|
||||
:size="$q.dark.isActive ? '21px' : '20px'"
|
||||
:color="$q.dark.isActive ? 'black' : 'grey-3'"
|
||||
></q-icon>
|
||||
</q-avatar>
|
||||
<div
|
||||
class="text-h6 q-pl-md"
|
||||
|
|
@ -321,39 +317,39 @@
|
|||
<script src="{{ static_url_for('static', url) }}"></script>
|
||||
{% endfor %}
|
||||
<script type="text/javascript">
|
||||
const SITE_DESCRIPTION = {{ SITE_DESCRIPTION | tojson}}
|
||||
const themes = {{ LNBITS_THEME_OPTIONS | tojson }}
|
||||
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson }}
|
||||
const LNBITS_VERSION = {{ LNBITS_VERSION | tojson }}
|
||||
const LNBITS_QR_LOGO = {{ LNBITS_QR_LOGO | tojson }}
|
||||
const USE_DEFAULT_REACTION = {{ USE_DEFAULT_REACTION | tojson }}
|
||||
const USE_DEFAULT_THEME = {{ USE_DEFAULT_THEME | tojson }}
|
||||
const USE_DEFAULT_BORDER = {{ USE_DEFAULT_BORDER | tojson }}
|
||||
const USE_DEFAULT_GRADIENT = {{ USE_DEFAULT_GRADIENT | lower | tojson }}
|
||||
const USE_DEFAULT_BGIMAGE = "{{ USE_DEFAULT_BGIMAGE or None | tojson }}"
|
||||
if (themes && themes.length) {
|
||||
window.allowedThemes = themes.map(str => str.trim())
|
||||
}
|
||||
window.langs = [
|
||||
{ value: 'en', label: 'English', display: '🇬🇧 EN' },
|
||||
{ value: 'de', label: 'Deutsch', display: '🇩🇪 DE' },
|
||||
{ value: 'es', label: 'Español', display: '🇪🇸 ES' },
|
||||
{ value: 'jp', label: '日本語', display: '🇯🇵 JP' },
|
||||
{ value: 'cn', label: '中文', display: '🇨🇳 CN' },
|
||||
{ value: 'fr', label: 'Français', display: '🇫🇷 FR' },
|
||||
{ value: 'it', label: 'Italiano', display: '🇮🇹 IT' },
|
||||
{ value: 'pi', label: 'Pirate', display: '🏴☠️ PI' },
|
||||
{ value: 'nl', label: 'Nederlands', display: '🇳🇱 NL' },
|
||||
{ value: 'we', label: 'Cymraeg', display: '🏴 CY' },
|
||||
{ value: 'pl', label: 'Polski', display: '🇵🇱 PL' },
|
||||
{ value: 'pt', label: 'Português', display: '🇵🇹 PT' },
|
||||
{ value: 'br', label: 'Português do Brasil', display: '🇧🇷 BR' },
|
||||
{ value: 'cs', label: 'Česky', display: '🇨🇿 CS' },
|
||||
{ value: 'sk', label: 'Slovensky', display: '🇸🇰 SK' },
|
||||
{ value: 'kr', label: '한국어', display: '🇰🇷 KR' },
|
||||
{ value: 'fi', label: 'Suomi', display: '🇫🇮 FI' }
|
||||
]
|
||||
window.LOCALE = 'en'
|
||||
const SITE_DESCRIPTION = {{ SITE_DESCRIPTION | tojson}}
|
||||
const themes = {{ LNBITS_THEME_OPTIONS | tojson }}
|
||||
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson }}
|
||||
const LNBITS_VERSION = {{ LNBITS_VERSION | tojson }}
|
||||
const LNBITS_QR_LOGO = {{ LNBITS_QR_LOGO | tojson }}
|
||||
const USE_DEFAULT_REACTION = {{ USE_DEFAULT_REACTION | tojson }}
|
||||
const USE_DEFAULT_THEME = {{ USE_DEFAULT_THEME | tojson }}
|
||||
const USE_DEFAULT_BORDER = {{ USE_DEFAULT_BORDER | tojson }}
|
||||
const USE_DEFAULT_GRADIENT = {{ USE_DEFAULT_GRADIENT | lower | tojson }}
|
||||
const USE_DEFAULT_BGIMAGE = "{{ USE_DEFAULT_BGIMAGE or None | tojson }}"
|
||||
if (themes && themes.length) {
|
||||
window.allowedThemes = themes.map(str => str.trim())
|
||||
}
|
||||
window.langs = [
|
||||
{ value: 'en', label: 'English', display: '🇬🇧 EN' },
|
||||
{ value: 'de', label: 'Deutsch', display: '🇩🇪 DE' },
|
||||
{ value: 'es', label: 'Español', display: '🇪🇸 ES' },
|
||||
{ value: 'jp', label: '日本語', display: '🇯🇵 JP' },
|
||||
{ value: 'cn', label: '中文', display: '🇨🇳 CN' },
|
||||
{ value: 'fr', label: 'Français', display: '🇫🇷 FR' },
|
||||
{ value: 'it', label: 'Italiano', display: '🇮🇹 IT' },
|
||||
{ value: 'pi', label: 'Pirate', display: '🏴☠️ PI' },
|
||||
{ value: 'nl', label: 'Nederlands', display: '🇳🇱 NL' },
|
||||
{ value: 'we', label: 'Cymraeg', display: '🏴 CY' },
|
||||
{ value: 'pl', label: 'Polski', display: '🇵🇱 PL' },
|
||||
{ value: 'pt', label: 'Português', display: '🇵🇹 PT' },
|
||||
{ value: 'br', label: 'Português do Brasil', display: '🇧🇷 BR' },
|
||||
{ value: 'cs', label: 'Česky', display: '🇨🇿 CS' },
|
||||
{ value: 'sk', label: 'Slovensky', display: '🇸🇰 SK' },
|
||||
{ value: 'kr', label: '한국어', display: '🇰🇷 KR' },
|
||||
{ value: 'fi', label: 'Suomi', display: '🇫🇮 FI' }
|
||||
]
|
||||
window.LOCALE = 'en'
|
||||
window.dateFormat = 'YYYY-MM-DD HH:mm'
|
||||
window.i18n = new VueI18n.createI18n({
|
||||
locale: window.LOCALE,
|
||||
|
|
|
|||
|
|
@ -13,20 +13,20 @@
|
|||
>
|
||||
<q-item-section side>
|
||||
<q-avatar
|
||||
size="md"
|
||||
size="lg"
|
||||
:text-color="$q.dark.isActive ? 'black' : 'grey-3'"
|
||||
:class="g.wallet && g.wallet.id === walletRec.id ? '' : 'disabled'"
|
||||
:color="
|
||||
g.wallet && g.wallet.id === walletRec.id
|
||||
? $q.dark.isActive
|
||||
? 'primary'
|
||||
: 'primary'
|
||||
: 'grey-5'
|
||||
? walletRec.extra.color
|
||||
: walletRec.extra.color
|
||||
"
|
||||
:icon="
|
||||
g.wallet && g.wallet.id === walletRec.id
|
||||
? walletRec.extra.icon
|
||||
: walletRec.extra.icon
|
||||
"
|
||||
>
|
||||
<q-icon
|
||||
name="flash_on"
|
||||
:size="$q.dark.isActive ? '21px' : '20px'"
|
||||
:color="$q.dark.isActive ? 'blue-grey-10' : 'grey-3'"
|
||||
></q-icon>
|
||||
</q-avatar>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue